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