/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: John Arbash Meinel
  • Date: 2007-03-01 19:58:38 UTC
  • mto: (2255.7.84 dirstate)
  • mto: This revision was merged to the branch mainline in revision 2322.
  • Revision ID: john@arbash-meinel.com-20070301195838-7p053os20qwr6qf7
Update WorkingTree4 so that it doesn't use a HashCache,
instead caching the sha values and stat fingerprint in the 'current'
section.

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