~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: John Arbash Meinel
  • Date: 2007-03-12 16:29:47 UTC
  • mfrom: (2325.2.5 bzr.dev)
  • mto: This revision was merged to the branch mainline in revision 2343.
  • Revision ID: john@arbash-meinel.com-20070312162947-y3a3fjf0iwalwot5
(Marien Zwart) RevisionSpec.from_string() should support Unicode parameters. (especially for revid:)

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