~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Martin Pool
  • Date: 2005-07-04 08:06:51 UTC
  • Revision ID: mbp@sourcefrog.net-20050704080651-6ecec49164359e48
- track pending-merges

- unit tests for this

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_move_missing(self):
736
 
        """Test moving ids with no files around"""
737
 
        self.wt.set_root_id('toor_eert')
738
 
        # Need two steps because versioning a non-existant file is a conflict.
739
 
        transform, root = self.get_transform()
740
 
        transform.new_directory('floater', root, 'floater-id')
741
 
        transform.apply()
742
 
        transform, root = self.get_transform()
743
 
        transform.delete_contents(transform.trans_id_tree_path('floater'))
744
 
        transform.apply()
745
 
        transform, root = self.get_transform()
746
 
        floater = transform.trans_id_tree_path('floater')
747
 
        try:
748
 
            transform.adjust_path('flitter', root, floater)
749
 
            self.assertEqual([('floater-id', ('floater', 'flitter'), False,
750
 
            (True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
751
 
            (None, None), (False, False))], list(transform._iter_changes()))
752
 
        finally:
753
 
            transform.finalize()
754
 
 
755
 
    def test_iter_changes_pointless(self):
756
 
        """Ensure that no-ops are not treated as modifications"""
757
 
        self.wt.set_root_id('eert_toor')
758
 
        transform, root = self.get_transform()
759
 
        transform.new_file('old', root, 'blah', 'id-1')
760
 
        transform.new_directory('subdir', root, 'subdir-id')
761
 
        transform.apply()
762
 
        transform, root = self.get_transform()
763
 
        try:
764
 
            old = transform.trans_id_tree_path('old')
765
 
            subdir = transform.trans_id_tree_file_id('subdir-id')
766
 
            self.assertEqual([], list(transform._iter_changes()))
767
 
            transform.delete_contents(subdir)
768
 
            transform.create_directory(subdir)
769
 
            transform.set_executability(False, old)
770
 
            transform.unversion_file(old)
771
 
            transform.version_file('id-1', old)
772
 
            transform.adjust_path('old', root, old)
773
 
            self.assertEqual([], list(transform._iter_changes()))
774
 
        finally:
775
 
            transform.finalize()
776
 
 
777
 
class TransformGroup(object):
778
 
    def __init__(self, dirname, root_id):
779
 
        self.name = dirname
780
 
        os.mkdir(dirname)
781
 
        self.wt = BzrDir.create_standalone_workingtree(dirname)
782
 
        self.wt.set_root_id(root_id)
783
 
        self.b = self.wt.branch
784
 
        self.tt = TreeTransform(self.wt)
785
 
        self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
786
 
 
787
 
 
788
 
def conflict_text(tree, merge):
789
 
    template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
790
 
    return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
791
 
 
792
 
 
793
 
class TestTransformMerge(TestCaseInTempDir):
794
 
    def test_text_merge(self):
795
 
        root_id = generate_ids.gen_root_id()
796
 
        base = TransformGroup("base", root_id)
797
 
        base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
798
 
        base.tt.new_file('b', base.root, 'b1', 'b')
799
 
        base.tt.new_file('c', base.root, 'c', 'c')
800
 
        base.tt.new_file('d', base.root, 'd', 'd')
801
 
        base.tt.new_file('e', base.root, 'e', 'e')
802
 
        base.tt.new_file('f', base.root, 'f', 'f')
803
 
        base.tt.new_directory('g', base.root, 'g')
804
 
        base.tt.new_directory('h', base.root, 'h')
805
 
        base.tt.apply()
806
 
        other = TransformGroup("other", root_id)
807
 
        other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
808
 
        other.tt.new_file('b', other.root, 'b2', 'b')
809
 
        other.tt.new_file('c', other.root, 'c2', 'c')
810
 
        other.tt.new_file('d', other.root, 'd', 'd')
811
 
        other.tt.new_file('e', other.root, 'e2', 'e')
812
 
        other.tt.new_file('f', other.root, 'f', 'f')
813
 
        other.tt.new_file('g', other.root, 'g', 'g')
814
 
        other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
815
 
        other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
816
 
        other.tt.apply()
817
 
        this = TransformGroup("this", root_id)
818
 
        this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
819
 
        this.tt.new_file('b', this.root, 'b', 'b')
820
 
        this.tt.new_file('c', this.root, 'c', 'c')
821
 
        this.tt.new_file('d', this.root, 'd2', 'd')
822
 
        this.tt.new_file('e', this.root, 'e2', 'e')
823
 
        this.tt.new_file('f', this.root, 'f', 'f')
824
 
        this.tt.new_file('g', this.root, 'g', 'g')
825
 
        this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
826
 
        this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
827
 
        this.tt.apply()
828
 
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
829
 
        # textual merge
830
 
        self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
831
 
        # three-way text conflict
832
 
        self.assertEqual(this.wt.get_file('b').read(), 
833
 
                         conflict_text('b', 'b2'))
834
 
        # OTHER wins
835
 
        self.assertEqual(this.wt.get_file('c').read(), 'c2')
836
 
        # THIS wins
837
 
        self.assertEqual(this.wt.get_file('d').read(), 'd2')
838
 
        # Ambigious clean merge
839
 
        self.assertEqual(this.wt.get_file('e').read(), 'e2')
840
 
        # No change
841
 
        self.assertEqual(this.wt.get_file('f').read(), 'f')
842
 
        # Correct correct results when THIS == OTHER 
843
 
        self.assertEqual(this.wt.get_file('g').read(), 'g')
844
 
        # Text conflict when THIS & OTHER are text and BASE is dir
845
 
        self.assertEqual(this.wt.get_file('h').read(), 
846
 
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
847
 
        self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
848
 
                         '1\n2\n3\n4\n')
849
 
        self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
850
 
                         'h\ni\nj\nk\n')
851
 
        self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
852
 
        self.assertEqual(this.wt.get_file('i').read(), 
853
 
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
854
 
        self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
855
 
                         '1\n2\n3\n4\n')
856
 
        self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
857
 
                         'h\ni\nj\nk\n')
858
 
        self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
859
 
        modified = ['a', 'b', 'c', 'h', 'i']
860
 
        merge_modified = this.wt.merge_modified()
861
 
        self.assertSubset(merge_modified, modified)
862
 
        self.assertEqual(len(merge_modified), len(modified))
863
 
        file(this.wt.id2abspath('a'), 'wb').write('booga')
864
 
        modified.pop(0)
865
 
        merge_modified = this.wt.merge_modified()
866
 
        self.assertSubset(merge_modified, modified)
867
 
        self.assertEqual(len(merge_modified), len(modified))
868
 
        this.wt.remove('b')
869
 
        this.wt.revert([])
870
 
 
871
 
    def test_file_merge(self):
872
 
        if not has_symlinks():
873
 
            raise TestSkipped('Symlinks are not supported on this platform')
874
 
        root_id = generate_ids.gen_root_id()
875
 
        base = TransformGroup("BASE", root_id)
876
 
        this = TransformGroup("THIS", root_id)
877
 
        other = TransformGroup("OTHER", root_id)
878
 
        for tg in this, base, other:
879
 
            tg.tt.new_directory('a', tg.root, 'a')
880
 
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
881
 
            tg.tt.new_file('c', tg.root, 'c', 'c')
882
 
            tg.tt.new_symlink('d', tg.root, tg.name, 'd')
883
 
        targets = ((base, 'base-e', 'base-f', None, None), 
884
 
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'), 
885
 
                   (other, 'other-e', None, 'other-g', 'other-h'))
886
 
        for tg, e_target, f_target, g_target, h_target in targets:
887
 
            for link, target in (('e', e_target), ('f', f_target), 
888
 
                                 ('g', g_target), ('h', h_target)):
889
 
                if target is not None:
890
 
                    tg.tt.new_symlink(link, tg.root, target, link)
891
 
 
892
 
        for tg in this, base, other:
893
 
            tg.tt.apply()
894
 
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
895
 
        self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
896
 
        self.assertIs(os.path.islink(this.wt.abspath('b')), True)
897
 
        self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
898
 
        for suffix in ('THIS', 'BASE', 'OTHER'):
899
 
            self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
900
 
        self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
901
 
        self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
902
 
        self.assertEqual(this.wt.id2path('f'), 'f.THIS')
903
 
        self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
904
 
        self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
905
 
        self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
906
 
        self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
907
 
        self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
908
 
        self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
909
 
        self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
910
 
        self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
911
 
        self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
912
 
        self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
913
 
 
914
 
    def test_filename_merge(self):
915
 
        root_id = generate_ids.gen_root_id()
916
 
        base = TransformGroup("BASE", root_id)
917
 
        this = TransformGroup("THIS", root_id)
918
 
        other = TransformGroup("OTHER", root_id)
919
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
920
 
                                   for t in [base, this, other]]
921
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
922
 
                                   for t in [base, this, other]]
923
 
        base.tt.new_directory('c', base_a, 'c')
924
 
        this.tt.new_directory('c1', this_a, 'c')
925
 
        other.tt.new_directory('c', other_b, 'c')
926
 
 
927
 
        base.tt.new_directory('d', base_a, 'd')
928
 
        this.tt.new_directory('d1', this_b, 'd')
929
 
        other.tt.new_directory('d', other_a, 'd')
930
 
 
931
 
        base.tt.new_directory('e', base_a, 'e')
932
 
        this.tt.new_directory('e', this_a, 'e')
933
 
        other.tt.new_directory('e1', other_b, 'e')
934
 
 
935
 
        base.tt.new_directory('f', base_a, 'f')
936
 
        this.tt.new_directory('f1', this_b, 'f')
937
 
        other.tt.new_directory('f1', other_b, 'f')
938
 
 
939
 
        for tg in [this, base, other]:
940
 
            tg.tt.apply()
941
 
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
942
 
        self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
943
 
        self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
944
 
        self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
945
 
        self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
946
 
 
947
 
    def test_filename_merge_conflicts(self):
948
 
        root_id = generate_ids.gen_root_id()
949
 
        base = TransformGroup("BASE", root_id)
950
 
        this = TransformGroup("THIS", root_id)
951
 
        other = TransformGroup("OTHER", root_id)
952
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
953
 
                                   for t in [base, this, other]]
954
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
955
 
                                   for t in [base, this, other]]
956
 
 
957
 
        base.tt.new_file('g', base_a, 'g', 'g')
958
 
        other.tt.new_file('g1', other_b, 'g1', 'g')
959
 
 
960
 
        base.tt.new_file('h', base_a, 'h', 'h')
961
 
        this.tt.new_file('h1', this_b, 'h1', 'h')
962
 
 
963
 
        base.tt.new_file('i', base.root, 'i', 'i')
964
 
        other.tt.new_directory('i1', this_b, 'i')
965
 
 
966
 
        for tg in [this, base, other]:
967
 
            tg.tt.apply()
968
 
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
969
 
 
970
 
        self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
971
 
        self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
972
 
        self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
973
 
        self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
974
 
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
975
 
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
976
 
        self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
977
 
 
978
 
 
979
 
class TestBuildTree(tests.TestCaseWithTransport):
980
 
 
981
 
    def test_build_tree(self):
982
 
        if not has_symlinks():
983
 
            raise TestSkipped('Test requires symlink support')
984
 
        os.mkdir('a')
985
 
        a = BzrDir.create_standalone_workingtree('a')
986
 
        os.mkdir('a/foo')
987
 
        file('a/foo/bar', 'wb').write('contents')
988
 
        os.symlink('a/foo/bar', 'a/foo/baz')
989
 
        a.add(['foo', 'foo/bar', 'foo/baz'])
990
 
        a.commit('initial commit')
991
 
        b = BzrDir.create_standalone_workingtree('b')
992
 
        basis = a.basis_tree()
993
 
        basis.lock_read()
994
 
        self.addCleanup(basis.unlock)
995
 
        build_tree(basis, b)
996
 
        self.assertIs(os.path.isdir('b/foo'), True)
997
 
        self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
998
 
        self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
999
 
 
1000
 
    def test_build_with_references(self):
1001
 
        tree = self.make_branch_and_tree('source',
1002
 
            format='dirstate-with-subtree')
1003
 
        subtree = self.make_branch_and_tree('source/subtree',
1004
 
            format='dirstate-with-subtree')
1005
 
        tree.add_reference(subtree)
1006
 
        tree.commit('a revision')
1007
 
        tree.branch.create_checkout('target')
1008
 
        self.failUnlessExists('target')
1009
 
        self.failUnlessExists('target/subtree')
1010
 
 
1011
 
    def test_file_conflict_handling(self):
1012
 
        """Ensure that when building trees, conflict handling is done"""
1013
 
        source = self.make_branch_and_tree('source')
1014
 
        target = self.make_branch_and_tree('target')
1015
 
        self.build_tree(['source/file', 'target/file'])
1016
 
        source.add('file', 'new-file')
1017
 
        source.commit('added file')
1018
 
        build_tree(source.basis_tree(), target)
1019
 
        self.assertEqual([DuplicateEntry('Moved existing file to',
1020
 
                          'file.moved', 'file', None, 'new-file')],
1021
 
                         target.conflicts())
1022
 
        target2 = self.make_branch_and_tree('target2')
1023
 
        target_file = file('target2/file', 'wb')
1024
 
        try:
1025
 
            source_file = file('source/file', 'rb')
1026
 
            try:
1027
 
                target_file.write(source_file.read())
1028
 
            finally:
1029
 
                source_file.close()
1030
 
        finally:
1031
 
            target_file.close()
1032
 
        build_tree(source.basis_tree(), target2)
1033
 
        self.assertEqual([], target2.conflicts())
1034
 
 
1035
 
    def test_symlink_conflict_handling(self):
1036
 
        """Ensure that when building trees, conflict handling is done"""
1037
 
        if not has_symlinks():
1038
 
            raise TestSkipped('Test requires symlink support')
1039
 
        source = self.make_branch_and_tree('source')
1040
 
        os.symlink('foo', 'source/symlink')
1041
 
        source.add('symlink', 'new-symlink')
1042
 
        source.commit('added file')
1043
 
        target = self.make_branch_and_tree('target')
1044
 
        os.symlink('bar', 'target/symlink')
1045
 
        build_tree(source.basis_tree(), target)
1046
 
        self.assertEqual([DuplicateEntry('Moved existing file to',
1047
 
            'symlink.moved', 'symlink', None, 'new-symlink')],
1048
 
            target.conflicts())
1049
 
        target = self.make_branch_and_tree('target2')
1050
 
        os.symlink('foo', 'target2/symlink')
1051
 
        build_tree(source.basis_tree(), target)
1052
 
        self.assertEqual([], target.conflicts())
1053
 
        
1054
 
    def test_directory_conflict_handling(self):
1055
 
        """Ensure that when building trees, conflict handling is done"""
1056
 
        source = self.make_branch_and_tree('source')
1057
 
        target = self.make_branch_and_tree('target')
1058
 
        self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1059
 
        source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1060
 
        source.commit('added file')
1061
 
        build_tree(source.basis_tree(), target)
1062
 
        self.assertEqual([], target.conflicts())
1063
 
        self.failUnlessExists('target/dir1/file')
1064
 
 
1065
 
        # Ensure contents are merged
1066
 
        target = self.make_branch_and_tree('target2')
1067
 
        self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1068
 
        build_tree(source.basis_tree(), target)
1069
 
        self.assertEqual([], target.conflicts())
1070
 
        self.failUnlessExists('target2/dir1/file2')
1071
 
        self.failUnlessExists('target2/dir1/file')
1072
 
 
1073
 
        # Ensure new contents are suppressed for existing branches
1074
 
        target = self.make_branch_and_tree('target3')
1075
 
        self.make_branch('target3/dir1')
1076
 
        self.build_tree(['target3/dir1/file2'])
1077
 
        build_tree(source.basis_tree(), target)
1078
 
        self.failIfExists('target3/dir1/file')
1079
 
        self.failUnlessExists('target3/dir1/file2')
1080
 
        self.failUnlessExists('target3/dir1.diverted/file')
1081
 
        self.assertEqual([DuplicateEntry('Diverted to',
1082
 
            'dir1.diverted', 'dir1', 'new-dir1', None)],
1083
 
            target.conflicts())
1084
 
 
1085
 
        target = self.make_branch_and_tree('target4')
1086
 
        self.build_tree(['target4/dir1/'])
1087
 
        self.make_branch('target4/dir1/file')
1088
 
        build_tree(source.basis_tree(), target)
1089
 
        self.failUnlessExists('target4/dir1/file')
1090
 
        self.assertEqual('directory', file_kind('target4/dir1/file'))
1091
 
        self.failUnlessExists('target4/dir1/file.diverted')
1092
 
        self.assertEqual([DuplicateEntry('Diverted to',
1093
 
            'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1094
 
            target.conflicts())
1095
 
 
1096
 
    def test_mixed_conflict_handling(self):
1097
 
        """Ensure that when building trees, conflict handling is done"""
1098
 
        source = self.make_branch_and_tree('source')
1099
 
        target = self.make_branch_and_tree('target')
1100
 
        self.build_tree(['source/name', 'target/name/'])
1101
 
        source.add('name', 'new-name')
1102
 
        source.commit('added file')
1103
 
        build_tree(source.basis_tree(), target)
1104
 
        self.assertEqual([DuplicateEntry('Moved existing file to',
1105
 
            'name.moved', 'name', None, 'new-name')], target.conflicts())
1106
 
 
1107
 
    def test_raises_in_populated(self):
1108
 
        source = self.make_branch_and_tree('source')
1109
 
        self.build_tree(['source/name'])
1110
 
        source.add('name')
1111
 
        source.commit('added name')
1112
 
        target = self.make_branch_and_tree('target')
1113
 
        self.build_tree(['target/name'])
1114
 
        target.add('name')
1115
 
        self.assertRaises(errors.WorkingTreeAlreadyPopulated, 
1116
 
            build_tree, source.basis_tree(), target)
1117
 
 
1118
 
 
1119
 
class MockTransform(object):
1120
 
 
1121
 
    def has_named_child(self, by_parent, parent_id, name):
1122
 
        for child_id in by_parent[parent_id]:
1123
 
            if child_id == '0':
1124
 
                if name == "name~":
1125
 
                    return True
1126
 
            elif name == "name.~%s~" % child_id:
1127
 
                return True
1128
 
        return False
1129
 
 
1130
 
class MockEntry(object):
1131
 
    def __init__(self):
1132
 
        object.__init__(self)
1133
 
        self.name = "name"
1134
 
 
1135
 
class TestGetBackupName(TestCase):
1136
 
    def test_get_backup_name(self):
1137
 
        tt = MockTransform()
1138
 
        name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1139
 
        self.assertEqual(name, 'name.~1~')
1140
 
        name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1141
 
        self.assertEqual(name, 'name.~2~')
1142
 
        name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1143
 
        self.assertEqual(name, 'name.~1~')
1144
 
        name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1145
 
        self.assertEqual(name, 'name.~1~')
1146
 
        name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1147
 
        self.assertEqual(name, 'name.~4~')