~bzr-pqm/bzr/bzr.dev

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