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