/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

Merge with serialize-transform

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
import os
 
18
import stat
 
19
from StringIO import StringIO
 
20
import sys
 
21
 
 
22
from bzrlib import (
 
23
    errors,
 
24
    generate_ids,
 
25
    osutils,
 
26
    pack,
 
27
    progress,
 
28
    revision as _mod_revision,
 
29
    symbol_versioning,
 
30
    tests,
 
31
    urlutils,
 
32
    )
 
33
from bzrlib.bzrdir import BzrDir
 
34
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
 
35
                              UnversionedParent, ParentLoop, DeletingParent,
 
36
                              NonDirectoryParent)
 
37
from bzrlib.diff import show_diff_trees
 
38
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
 
39
                           ReusingTransform, CantMoveRoot, 
 
40
                           PathsNotVersionedError, ExistingLimbo,
 
41
                           ExistingPendingDeletion, ImmortalLimbo,
 
42
                           ImmortalPendingDeletion, LockError)
 
43
from bzrlib.osutils import file_kind, pathjoin
 
44
from bzrlib.merge import Merge3Merger, Merger
 
45
from bzrlib.tests import (
 
46
    CaseInsensitiveFilesystemFeature,
 
47
    HardlinkFeature,
 
48
    SymlinkFeature,
 
49
    TestCase,
 
50
    TestCaseInTempDir,
 
51
    TestSkipped,
 
52
    )
 
53
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths, 
 
54
                              resolve_conflicts, cook_conflicts, 
 
55
                              build_tree, get_backup_name,
 
56
                              _FileMover, resolve_checkout,
 
57
                              TransformPreview, create_from_tree)
 
58
 
 
59
class TestTreeTransform(tests.TestCaseWithTransport):
 
60
 
 
61
    def setUp(self):
 
62
        super(TestTreeTransform, self).setUp()
 
63
        self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
 
64
        os.chdir('..')
 
65
 
 
66
    def get_transform(self):
 
67
        transform = TreeTransform(self.wt)
 
68
        self.addCleanup(transform.finalize)
 
69
        return transform, transform.root
 
70
 
 
71
    def test_existing_limbo(self):
 
72
        transform, root = self.get_transform()
 
73
        limbo_name = transform._limbodir
 
74
        deletion_path = transform._deletiondir
 
75
        os.mkdir(pathjoin(limbo_name, 'hehe'))
 
76
        self.assertRaises(ImmortalLimbo, transform.apply)
 
77
        self.assertRaises(LockError, self.wt.unlock)
 
78
        self.assertRaises(ExistingLimbo, self.get_transform)
 
79
        self.assertRaises(LockError, self.wt.unlock)
 
80
        os.rmdir(pathjoin(limbo_name, 'hehe'))
 
81
        os.rmdir(limbo_name)
 
82
        os.rmdir(deletion_path)
 
83
        transform, root = self.get_transform()
 
84
        transform.apply()
 
85
 
 
86
    def test_existing_pending_deletion(self):
 
87
        transform, root = self.get_transform()
 
88
        deletion_path = self._limbodir = urlutils.local_path_from_url(
 
89
            transform._tree._transport.abspath('pending-deletion'))
 
90
        os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
 
91
        self.assertRaises(ImmortalPendingDeletion, transform.apply)
 
92
        self.assertRaises(LockError, self.wt.unlock)
 
93
        self.assertRaises(ExistingPendingDeletion, self.get_transform)
 
94
 
 
95
    def test_build(self):
 
96
        transform, root = self.get_transform()
 
97
        self.wt.lock_tree_write()
 
98
        self.addCleanup(self.wt.unlock)
 
99
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
 
100
        imaginary_id = transform.trans_id_tree_path('imaginary')
 
101
        imaginary_id2 = transform.trans_id_tree_path('imaginary/')
 
102
        self.assertEqual(imaginary_id, imaginary_id2)
 
103
        self.assertEqual(transform.get_tree_parent(imaginary_id), root)
 
104
        self.assertEqual(transform.final_kind(root), 'directory')
 
105
        self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
 
106
        trans_id = transform.create_path('name', root)
 
107
        self.assertIs(transform.final_file_id(trans_id), None)
 
108
        self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
 
109
        transform.create_file('contents', trans_id)
 
110
        transform.set_executability(True, trans_id)
 
111
        transform.version_file('my_pretties', trans_id)
 
112
        self.assertRaises(DuplicateKey, transform.version_file,
 
113
                          'my_pretties', trans_id)
 
114
        self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
 
115
        self.assertEqual(transform.final_parent(trans_id), root)
 
116
        self.assertIs(transform.final_parent(root), ROOT_PARENT)
 
117
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
 
118
        oz_id = transform.create_path('oz', root)
 
119
        transform.create_directory(oz_id)
 
120
        transform.version_file('ozzie', oz_id)
 
121
        trans_id2 = transform.create_path('name2', root)
 
122
        transform.create_file('contents', trans_id2)
 
123
        transform.set_executability(False, trans_id2)
 
124
        transform.version_file('my_pretties2', trans_id2)
 
125
        modified_paths = transform.apply().modified_paths
 
126
        self.assertEqual('contents', self.wt.get_file_byname('name').read())
 
127
        self.assertEqual(self.wt.path2id('name'), 'my_pretties')
 
128
        self.assertIs(self.wt.is_executable('my_pretties'), True)
 
129
        self.assertIs(self.wt.is_executable('my_pretties2'), False)
 
130
        self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
 
131
        self.assertEqual(len(modified_paths), 3)
 
132
        tree_mod_paths = [self.wt.id2abspath(f) for f in 
 
133
                          ('ozzie', 'my_pretties', 'my_pretties2')]
 
134
        self.assertSubset(tree_mod_paths, modified_paths)
 
135
        # is it safe to finalize repeatedly?
 
136
        transform.finalize()
 
137
        transform.finalize()
 
138
 
 
139
    def test_hardlink(self):
 
140
        self.requireFeature(HardlinkFeature)
 
141
        transform, root = self.get_transform()
 
142
        transform.new_file('file1', root, 'contents')
 
143
        transform.apply()
 
144
        target = self.make_branch_and_tree('target')
 
145
        target_transform = TreeTransform(target)
 
146
        trans_id = target_transform.create_path('file1', target_transform.root)
 
147
        target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
 
148
        target_transform.apply()
 
149
        self.failUnlessExists('target/file1')
 
150
        source_stat = os.stat(self.wt.abspath('file1'))
 
151
        target_stat = os.stat('target/file1')
 
152
        self.assertEqual(source_stat, target_stat)
 
153
 
 
154
    def test_convenience(self):
 
155
        transform, root = self.get_transform()
 
156
        self.wt.lock_tree_write()
 
157
        self.addCleanup(self.wt.unlock)
 
158
        trans_id = transform.new_file('name', root, 'contents', 
 
159
                                      'my_pretties', True)
 
160
        oz = transform.new_directory('oz', root, 'oz-id')
 
161
        dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
 
162
        toto = transform.new_file('toto', dorothy, 'toto-contents', 
 
163
                                  'toto-id', False)
 
164
 
 
165
        self.assertEqual(len(transform.find_conflicts()), 0)
 
166
        transform.apply()
 
167
        self.assertRaises(ReusingTransform, transform.find_conflicts)
 
168
        self.assertEqual('contents', file(self.wt.abspath('name')).read())
 
169
        self.assertEqual(self.wt.path2id('name'), 'my_pretties')
 
170
        self.assertIs(self.wt.is_executable('my_pretties'), True)
 
171
        self.assertEqual(self.wt.path2id('oz'), 'oz-id')
 
172
        self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
 
173
        self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
 
174
 
 
175
        self.assertEqual('toto-contents',
 
176
                         self.wt.get_file_byname('oz/dorothy/toto').read())
 
177
        self.assertIs(self.wt.is_executable('toto-id'), False)
 
178
 
 
179
    def test_tree_reference(self):
 
180
        transform, root = self.get_transform()
 
181
        tree = transform._tree
 
182
        trans_id = transform.new_directory('reference', root, 'subtree-id')
 
183
        transform.set_tree_reference('subtree-revision', trans_id)
 
184
        transform.apply()
 
185
        tree.lock_read()
 
186
        self.addCleanup(tree.unlock)
 
187
        self.assertEqual('subtree-revision',
 
188
                         tree.inventory['subtree-id'].reference_revision)
 
189
 
 
190
    def test_conflicts(self):
 
191
        transform, root = self.get_transform()
 
192
        trans_id = transform.new_file('name', root, 'contents', 
 
193
                                      'my_pretties')
 
194
        self.assertEqual(len(transform.find_conflicts()), 0)
 
195
        trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
 
196
        self.assertEqual(transform.find_conflicts(), 
 
197
                         [('duplicate', trans_id, trans_id2, 'name')])
 
198
        self.assertRaises(MalformedTransform, transform.apply)
 
199
        transform.adjust_path('name', trans_id, trans_id2)
 
200
        self.assertEqual(transform.find_conflicts(), 
 
201
                         [('non-directory parent', trans_id)])
 
202
        tinman_id = transform.trans_id_tree_path('tinman')
 
203
        transform.adjust_path('name', tinman_id, trans_id2)
 
204
        self.assertEqual(transform.find_conflicts(), 
 
205
                         [('unversioned parent', tinman_id), 
 
206
                          ('missing parent', tinman_id)])
 
207
        lion_id = transform.create_path('lion', root)
 
208
        self.assertEqual(transform.find_conflicts(), 
 
209
                         [('unversioned parent', tinman_id), 
 
210
                          ('missing parent', tinman_id)])
 
211
        transform.adjust_path('name', lion_id, trans_id2)
 
212
        self.assertEqual(transform.find_conflicts(), 
 
213
                         [('unversioned parent', lion_id),
 
214
                          ('missing parent', lion_id)])
 
215
        transform.version_file("Courage", lion_id)
 
216
        self.assertEqual(transform.find_conflicts(), 
 
217
                         [('missing parent', lion_id), 
 
218
                          ('versioning no contents', lion_id)])
 
219
        transform.adjust_path('name2', root, trans_id2)
 
220
        self.assertEqual(transform.find_conflicts(), 
 
221
                         [('versioning no contents', lion_id)])
 
222
        transform.create_file('Contents, okay?', lion_id)
 
223
        transform.adjust_path('name2', trans_id2, trans_id2)
 
224
        self.assertEqual(transform.find_conflicts(), 
 
225
                         [('parent loop', trans_id2), 
 
226
                          ('non-directory parent', trans_id2)])
 
227
        transform.adjust_path('name2', root, trans_id2)
 
228
        oz_id = transform.new_directory('oz', root)
 
229
        transform.set_executability(True, oz_id)
 
230
        self.assertEqual(transform.find_conflicts(), 
 
231
                         [('unversioned executability', oz_id)])
 
232
        transform.version_file('oz-id', oz_id)
 
233
        self.assertEqual(transform.find_conflicts(), 
 
234
                         [('non-file executability', oz_id)])
 
235
        transform.set_executability(None, oz_id)
 
236
        tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
 
237
        transform.apply()
 
238
        self.assertEqual(self.wt.path2id('name'), 'my_pretties')
 
239
        self.assertEqual('contents', file(self.wt.abspath('name')).read())
 
240
        transform2, root = self.get_transform()
 
241
        oz_id = transform2.trans_id_tree_file_id('oz-id')
 
242
        newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
 
243
        result = transform2.find_conflicts()
 
244
        fp = FinalPaths(transform2)
 
245
        self.assert_('oz/tip' in transform2._tree_path_ids)
 
246
        self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
 
247
        self.assertEqual(len(result), 2)
 
248
        self.assertEqual((result[0][0], result[0][1]), 
 
249
                         ('duplicate', newtip))
 
250
        self.assertEqual((result[1][0], result[1][2]), 
 
251
                         ('duplicate id', newtip))
 
252
        transform2.finalize()
 
253
        transform3 = TreeTransform(self.wt)
 
254
        self.addCleanup(transform3.finalize)
 
255
        oz_id = transform3.trans_id_tree_file_id('oz-id')
 
256
        transform3.delete_contents(oz_id)
 
257
        self.assertEqual(transform3.find_conflicts(), 
 
258
                         [('missing parent', oz_id)])
 
259
        root_id = transform3.root
 
260
        tip_id = transform3.trans_id_tree_file_id('tip-id')
 
261
        transform3.adjust_path('tip', root_id, tip_id)
 
262
        transform3.apply()
 
263
 
 
264
    def test_conflict_on_case_insensitive(self):
 
265
        tree = self.make_branch_and_tree('tree')
 
266
        # Don't try this at home, kids!
 
267
        # Force the tree to report that it is case sensitive, for conflict
 
268
        # resolution tests
 
269
        tree.case_sensitive = True
 
270
        transform = TreeTransform(tree)
 
271
        self.addCleanup(transform.finalize)
 
272
        transform.new_file('file', transform.root, 'content')
 
273
        transform.new_file('FiLe', transform.root, 'content')
 
274
        result = transform.find_conflicts()
 
275
        self.assertEqual([], result)
 
276
        transform.finalize()
 
277
        # Force the tree to report that it is case insensitive, for conflict
 
278
        # generation tests
 
279
        tree.case_sensitive = False
 
280
        transform = TreeTransform(tree)
 
281
        self.addCleanup(transform.finalize)
 
282
        transform.new_file('file', transform.root, 'content')
 
283
        transform.new_file('FiLe', transform.root, 'content')
 
284
        result = transform.find_conflicts()
 
285
        self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
 
286
 
 
287
    def test_conflict_on_case_insensitive_existing(self):
 
288
        tree = self.make_branch_and_tree('tree')
 
289
        self.build_tree(['tree/FiLe'])
 
290
        # Don't try this at home, kids!
 
291
        # Force the tree to report that it is case sensitive, for conflict
 
292
        # resolution tests
 
293
        tree.case_sensitive = True
 
294
        transform = TreeTransform(tree)
 
295
        self.addCleanup(transform.finalize)
 
296
        transform.new_file('file', transform.root, 'content')
 
297
        result = transform.find_conflicts()
 
298
        self.assertEqual([], result)
 
299
        transform.finalize()
 
300
        # Force the tree to report that it is case insensitive, for conflict
 
301
        # generation tests
 
302
        tree.case_sensitive = False
 
303
        transform = TreeTransform(tree)
 
304
        self.addCleanup(transform.finalize)
 
305
        transform.new_file('file', transform.root, 'content')
 
306
        result = transform.find_conflicts()
 
307
        self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
 
308
 
 
309
    def test_resolve_case_insensitive_conflict(self):
 
310
        tree = self.make_branch_and_tree('tree')
 
311
        # Don't try this at home, kids!
 
312
        # Force the tree to report that it is case insensitive, for conflict
 
313
        # resolution tests
 
314
        tree.case_sensitive = False
 
315
        transform = TreeTransform(tree)
 
316
        self.addCleanup(transform.finalize)
 
317
        transform.new_file('file', transform.root, 'content')
 
318
        transform.new_file('FiLe', transform.root, 'content')
 
319
        resolve_conflicts(transform)
 
320
        transform.apply()
 
321
        self.failUnlessExists('tree/file')
 
322
        self.failUnlessExists('tree/FiLe.moved')
 
323
 
 
324
    def test_resolve_checkout_case_conflict(self):
 
325
        tree = self.make_branch_and_tree('tree')
 
326
        # Don't try this at home, kids!
 
327
        # Force the tree to report that it is case insensitive, for conflict
 
328
        # resolution tests
 
329
        tree.case_sensitive = False
 
330
        transform = TreeTransform(tree)
 
331
        self.addCleanup(transform.finalize)
 
332
        transform.new_file('file', transform.root, 'content')
 
333
        transform.new_file('FiLe', transform.root, 'content')
 
334
        resolve_conflicts(transform,
 
335
                          pass_func=lambda t, c: resolve_checkout(t, c, []))
 
336
        transform.apply()
 
337
        self.failUnlessExists('tree/file')
 
338
        self.failUnlessExists('tree/FiLe.moved')
 
339
 
 
340
    def test_apply_case_conflict(self):
 
341
        """Ensure that a transform with case conflicts can always be applied"""
 
342
        tree = self.make_branch_and_tree('tree')
 
343
        transform = TreeTransform(tree)
 
344
        self.addCleanup(transform.finalize)
 
345
        transform.new_file('file', transform.root, 'content')
 
346
        transform.new_file('FiLe', transform.root, 'content')
 
347
        dir = transform.new_directory('dir', transform.root)
 
348
        transform.new_file('dirfile', dir, 'content')
 
349
        transform.new_file('dirFiLe', dir, 'content')
 
350
        resolve_conflicts(transform)
 
351
        transform.apply()
 
352
        self.failUnlessExists('tree/file')
 
353
        if not os.path.exists('tree/FiLe.moved'):
 
354
            self.failUnlessExists('tree/FiLe')
 
355
        self.failUnlessExists('tree/dir/dirfile')
 
356
        if not os.path.exists('tree/dir/dirFiLe.moved'):
 
357
            self.failUnlessExists('tree/dir/dirFiLe')
 
358
 
 
359
    def test_case_insensitive_limbo(self):
 
360
        tree = self.make_branch_and_tree('tree')
 
361
        # Don't try this at home, kids!
 
362
        # Force the tree to report that it is case insensitive
 
363
        tree.case_sensitive = False
 
364
        transform = TreeTransform(tree)
 
365
        self.addCleanup(transform.finalize)
 
366
        dir = transform.new_directory('dir', transform.root)
 
367
        first = transform.new_file('file', dir, 'content')
 
368
        second = transform.new_file('FiLe', dir, 'content')
 
369
        self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
 
370
        self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
 
371
 
 
372
    def test_add_del(self):
 
373
        start, root = self.get_transform()
 
374
        start.new_directory('a', root, 'a')
 
375
        start.apply()
 
376
        transform, root = self.get_transform()
 
377
        transform.delete_versioned(transform.trans_id_tree_file_id('a'))
 
378
        transform.new_directory('a', root, 'a')
 
379
        transform.apply()
 
380
 
 
381
    def test_unversioning(self):
 
382
        create_tree, root = self.get_transform()
 
383
        parent_id = create_tree.new_directory('parent', root, 'parent-id')
 
384
        create_tree.new_file('child', parent_id, 'child', 'child-id')
 
385
        create_tree.apply()
 
386
        unversion = TreeTransform(self.wt)
 
387
        self.addCleanup(unversion.finalize)
 
388
        parent = unversion.trans_id_tree_path('parent')
 
389
        unversion.unversion_file(parent)
 
390
        self.assertEqual(unversion.find_conflicts(), 
 
391
                         [('unversioned parent', parent_id)])
 
392
        file_id = unversion.trans_id_tree_file_id('child-id')
 
393
        unversion.unversion_file(file_id)
 
394
        unversion.apply()
 
395
 
 
396
    def test_name_invariants(self):
 
397
        create_tree, root = self.get_transform()
 
398
        # prepare tree
 
399
        root = create_tree.root
 
400
        create_tree.new_file('name1', root, 'hello1', 'name1')
 
401
        create_tree.new_file('name2', root, 'hello2', 'name2')
 
402
        ddir = create_tree.new_directory('dying_directory', root, 'ddir')
 
403
        create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
 
404
        create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
 
405
        create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
 
406
        create_tree.apply()
 
407
 
 
408
        mangle_tree,root = self.get_transform()
 
409
        root = mangle_tree.root
 
410
        #swap names
 
411
        name1 = mangle_tree.trans_id_tree_file_id('name1')
 
412
        name2 = mangle_tree.trans_id_tree_file_id('name2')
 
413
        mangle_tree.adjust_path('name2', root, name1)
 
414
        mangle_tree.adjust_path('name1', root, name2)
 
415
 
 
416
        #tests for deleting parent directories 
 
417
        ddir = mangle_tree.trans_id_tree_file_id('ddir')
 
418
        mangle_tree.delete_contents(ddir)
 
419
        dfile = mangle_tree.trans_id_tree_file_id('dfile')
 
420
        mangle_tree.delete_versioned(dfile)
 
421
        mangle_tree.unversion_file(dfile)
 
422
        mfile = mangle_tree.trans_id_tree_file_id('mfile')
 
423
        mangle_tree.adjust_path('mfile', root, mfile)
 
424
 
 
425
        #tests for adding parent directories
 
426
        newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
 
427
        mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
 
428
        mangle_tree.adjust_path('mfile2', newdir, mfile2)
 
429
        mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
 
430
        self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
 
431
        self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
 
432
        self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
 
433
        mangle_tree.apply()
 
434
        self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
 
435
        self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
 
436
        mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
 
437
        self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
 
438
        self.assertEqual(file(mfile2_path).read(), 'later2')
 
439
        self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
 
440
        self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
 
441
        newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
 
442
        self.assertEqual(file(newfile_path).read(), 'hello3')
 
443
        self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
 
444
        self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
 
445
        mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
 
446
 
 
447
    def test_both_rename(self):
 
448
        create_tree,root = self.get_transform()
 
449
        newdir = create_tree.new_directory('selftest', root, 'selftest-id')
 
450
        create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
 
451
        create_tree.apply()        
 
452
        mangle_tree,root = self.get_transform()
 
453
        selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
 
454
        blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
 
455
        mangle_tree.adjust_path('test', root, selftest)
 
456
        mangle_tree.adjust_path('test_too_much', root, selftest)
 
457
        mangle_tree.set_executability(True, blackbox)
 
458
        mangle_tree.apply()
 
459
 
 
460
    def test_both_rename2(self):
 
461
        create_tree,root = self.get_transform()
 
462
        bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
 
463
        tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
 
464
        blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
 
465
        create_tree.new_file('test_too_much.py', blackbox, 'hello1', 
 
466
                             'test_too_much-id')
 
467
        create_tree.apply()        
 
468
        mangle_tree,root = self.get_transform()
 
469
        bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
 
470
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
 
471
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
 
472
        mangle_tree.adjust_path('selftest', bzrlib, tests)
 
473
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
 
474
        mangle_tree.set_executability(True, test_too_much)
 
475
        mangle_tree.apply()
 
476
 
 
477
    def test_both_rename3(self):
 
478
        create_tree,root = self.get_transform()
 
479
        tests = create_tree.new_directory('tests', root, 'tests-id')
 
480
        create_tree.new_file('test_too_much.py', tests, 'hello1', 
 
481
                             'test_too_much-id')
 
482
        create_tree.apply()        
 
483
        mangle_tree,root = self.get_transform()
 
484
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
 
485
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
 
486
        mangle_tree.adjust_path('selftest', root, tests)
 
487
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
 
488
        mangle_tree.set_executability(True, test_too_much)
 
489
        mangle_tree.apply()
 
490
 
 
491
    def test_move_dangling_ie(self):
 
492
        create_tree, root = self.get_transform()
 
493
        # prepare tree
 
494
        root = create_tree.root
 
495
        create_tree.new_file('name1', root, 'hello1', 'name1')
 
496
        create_tree.apply()
 
497
        delete_contents, root = self.get_transform()
 
498
        file = delete_contents.trans_id_tree_file_id('name1')
 
499
        delete_contents.delete_contents(file)
 
500
        delete_contents.apply()
 
501
        move_id, root = self.get_transform()
 
502
        name1 = move_id.trans_id_tree_file_id('name1')
 
503
        newdir = move_id.new_directory('dir', root, 'newdir')
 
504
        move_id.adjust_path('name2', newdir, name1)
 
505
        move_id.apply()
 
506
        
 
507
    def test_replace_dangling_ie(self):
 
508
        create_tree, root = self.get_transform()
 
509
        # prepare tree
 
510
        root = create_tree.root
 
511
        create_tree.new_file('name1', root, 'hello1', 'name1')
 
512
        create_tree.apply()
 
513
        delete_contents = TreeTransform(self.wt)
 
514
        self.addCleanup(delete_contents.finalize)
 
515
        file = delete_contents.trans_id_tree_file_id('name1')
 
516
        delete_contents.delete_contents(file)
 
517
        delete_contents.apply()
 
518
        delete_contents.finalize()
 
519
        replace = TreeTransform(self.wt)
 
520
        self.addCleanup(replace.finalize)
 
521
        name2 = replace.new_file('name2', root, 'hello2', 'name1')
 
522
        conflicts = replace.find_conflicts()
 
523
        name1 = replace.trans_id_tree_file_id('name1')
 
524
        self.assertEqual(conflicts, [('duplicate id', name1, name2)])
 
525
        resolve_conflicts(replace)
 
526
        replace.apply()
 
527
 
 
528
    def test_symlinks(self):
 
529
        self.requireFeature(SymlinkFeature)
 
530
        transform,root = self.get_transform()
 
531
        oz_id = transform.new_directory('oz', root, 'oz-id')
 
532
        wizard = transform.new_symlink('wizard', oz_id, 'wizard-target', 
 
533
                                       'wizard-id')
 
534
        wiz_id = transform.create_path('wizard2', oz_id)
 
535
        transform.create_symlink('behind_curtain', wiz_id)
 
536
        transform.version_file('wiz-id2', wiz_id)            
 
537
        transform.set_executability(True, wiz_id)
 
538
        self.assertEqual(transform.find_conflicts(), 
 
539
                         [('non-file executability', wiz_id)])
 
540
        transform.set_executability(None, wiz_id)
 
541
        transform.apply()
 
542
        self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
 
543
        self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
 
544
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')), 
 
545
                         'behind_curtain')
 
546
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
 
547
                         'wizard-target')
 
548
 
 
549
    def test_unable_create_symlink(self):
 
550
        def tt_helper():
 
551
            wt = self.make_branch_and_tree('.')
 
552
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
553
            try:
 
554
                tt.new_symlink('foo', tt.root, 'bar')
 
555
                tt.apply()
 
556
            finally:
 
557
                wt.unlock()
 
558
        os_symlink = getattr(os, 'symlink', None)
 
559
        os.symlink = None
 
560
        try:
 
561
            err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
 
562
            self.assertEquals(
 
563
                "Unable to create symlink 'foo' on this platform",
 
564
                str(err))
 
565
        finally:
 
566
            if os_symlink:
 
567
                os.symlink = os_symlink
 
568
 
 
569
    def get_conflicted(self):
 
570
        create,root = self.get_transform()
 
571
        create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
 
572
        oz = create.new_directory('oz', root, 'oz-id')
 
573
        create.new_directory('emeraldcity', oz, 'emerald-id')
 
574
        create.apply()
 
575
        conflicts,root = self.get_transform()
 
576
        # set up duplicate entry, duplicate id
 
577
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy', 
 
578
                                         'dorothy-id')
 
579
        old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
 
580
        oz = conflicts.trans_id_tree_file_id('oz-id')
 
581
        # set up DeletedParent parent conflict
 
582
        conflicts.delete_versioned(oz)
 
583
        emerald = conflicts.trans_id_tree_file_id('emerald-id')
 
584
        # set up MissingParent conflict
 
585
        munchkincity = conflicts.trans_id_file_id('munchkincity-id')
 
586
        conflicts.adjust_path('munchkincity', root, munchkincity)
 
587
        conflicts.new_directory('auntem', munchkincity, 'auntem-id')
 
588
        # set up parent loop
 
589
        conflicts.adjust_path('emeraldcity', emerald, emerald)
 
590
        return conflicts, emerald, oz, old_dorothy, new_dorothy
 
591
 
 
592
    def test_conflict_resolution(self):
 
593
        conflicts, emerald, oz, old_dorothy, new_dorothy =\
 
594
            self.get_conflicted()
 
595
        resolve_conflicts(conflicts)
 
596
        self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
 
597
        self.assertIs(conflicts.final_file_id(old_dorothy), None)
 
598
        self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
 
599
        self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
 
600
        self.assertEqual(conflicts.final_parent(emerald), oz)
 
601
        conflicts.apply()
 
602
 
 
603
    def test_cook_conflicts(self):
 
604
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
 
605
        raw_conflicts = resolve_conflicts(tt)
 
606
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
 
607
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved', 
 
608
                                   'dorothy', None, 'dorothy-id')
 
609
        self.assertEqual(cooked_conflicts[0], duplicate)
 
610
        duplicate_id = DuplicateID('Unversioned existing file', 
 
611
                                   'dorothy.moved', 'dorothy', None,
 
612
                                   'dorothy-id')
 
613
        self.assertEqual(cooked_conflicts[1], duplicate_id)
 
614
        missing_parent = MissingParent('Created directory', 'munchkincity',
 
615
                                       'munchkincity-id')
 
616
        deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
 
617
        self.assertEqual(cooked_conflicts[2], missing_parent)
 
618
        unversioned_parent = UnversionedParent('Versioned directory',
 
619
                                               'munchkincity',
 
620
                                               'munchkincity-id')
 
621
        unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
 
622
                                               'oz-id')
 
623
        self.assertEqual(cooked_conflicts[3], unversioned_parent)
 
624
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity', 
 
625
                                 'oz/emeraldcity', 'emerald-id', 'emerald-id')
 
626
        self.assertEqual(cooked_conflicts[4], deleted_parent)
 
627
        self.assertEqual(cooked_conflicts[5], unversioned_parent2)
 
628
        self.assertEqual(cooked_conflicts[6], parent_loop)
 
629
        self.assertEqual(len(cooked_conflicts), 7)
 
630
        tt.finalize()
 
631
 
 
632
    def test_string_conflicts(self):
 
633
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
 
634
        raw_conflicts = resolve_conflicts(tt)
 
635
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
 
636
        tt.finalize()
 
637
        conflicts_s = [str(c) for c in cooked_conflicts]
 
638
        self.assertEqual(len(cooked_conflicts), len(conflicts_s))
 
639
        self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy.  '
 
640
                                         'Moved existing file to '
 
641
                                         'dorothy.moved.')
 
642
        self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy.  '
 
643
                                         'Unversioned existing file '
 
644
                                         'dorothy.moved.')
 
645
        self.assertEqual(conflicts_s[2], 'Conflict adding files to'
 
646
                                         ' munchkincity.  Created directory.')
 
647
        self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
 
648
                                         ' versioned, but has versioned'
 
649
                                         ' children.  Versioned directory.')
 
650
        self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
 
651
                                         " is not empty.  Not deleting.")
 
652
        self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
 
653
                                         ' versioned, but has versioned'
 
654
                                         ' children.  Versioned directory.')
 
655
        self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
 
656
                                         ' oz/emeraldcity.  Cancelled move.')
 
657
 
 
658
    def prepare_wrong_parent_kind(self):
 
659
        tt, root = self.get_transform()
 
660
        tt.new_file('parent', root, 'contents', 'parent-id')
 
661
        tt.apply()
 
662
        tt, root = self.get_transform()
 
663
        parent_id = tt.trans_id_file_id('parent-id')
 
664
        tt.new_file('child,', parent_id, 'contents2', 'file-id')
 
665
        return tt
 
666
 
 
667
    def test_find_conflicts_wrong_parent_kind(self):
 
668
        tt = self.prepare_wrong_parent_kind()
 
669
        tt.find_conflicts()
 
670
 
 
671
    def test_resolve_conflicts_wrong_existing_parent_kind(self):
 
672
        tt = self.prepare_wrong_parent_kind()
 
673
        raw_conflicts = resolve_conflicts(tt)
 
674
        self.assertEqual(set([('non-directory parent', 'Created directory',
 
675
                         'new-3')]), raw_conflicts)
 
676
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
 
677
        self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
 
678
        'parent-id')], cooked_conflicts)
 
679
        tt.apply()
 
680
        self.assertEqual(None, self.wt.path2id('parent'))
 
681
        self.assertEqual('parent-id', self.wt.path2id('parent.new'))
 
682
 
 
683
    def test_resolve_conflicts_wrong_new_parent_kind(self):
 
684
        tt, root = self.get_transform()
 
685
        parent_id = tt.new_directory('parent', root, 'parent-id')
 
686
        tt.new_file('child,', parent_id, 'contents2', 'file-id')
 
687
        tt.apply()
 
688
        tt, root = self.get_transform()
 
689
        parent_id = tt.trans_id_file_id('parent-id')
 
690
        tt.delete_contents(parent_id)
 
691
        tt.create_file('contents', parent_id)
 
692
        raw_conflicts = resolve_conflicts(tt)
 
693
        self.assertEqual(set([('non-directory parent', 'Created directory',
 
694
                         'new-3')]), raw_conflicts)
 
695
        tt.apply()
 
696
        self.assertEqual(None, self.wt.path2id('parent'))
 
697
        self.assertEqual('parent-id', self.wt.path2id('parent.new'))
 
698
 
 
699
    def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
 
700
        tt, root = self.get_transform()
 
701
        parent_id = tt.new_directory('parent', root)
 
702
        tt.new_file('child,', parent_id, 'contents2')
 
703
        tt.apply()
 
704
        tt, root = self.get_transform()
 
705
        parent_id = tt.trans_id_tree_path('parent')
 
706
        tt.delete_contents(parent_id)
 
707
        tt.create_file('contents', parent_id)
 
708
        resolve_conflicts(tt)
 
709
        tt.apply()
 
710
        self.assertIs(None, self.wt.path2id('parent'))
 
711
        self.assertIs(None, self.wt.path2id('parent.new'))
 
712
 
 
713
    def test_moving_versioned_directories(self):
 
714
        create, root = self.get_transform()
 
715
        kansas = create.new_directory('kansas', root, 'kansas-id')
 
716
        create.new_directory('house', kansas, 'house-id')
 
717
        create.new_directory('oz', root, 'oz-id')
 
718
        create.apply()
 
719
        cyclone, root = self.get_transform()
 
720
        oz = cyclone.trans_id_tree_file_id('oz-id')
 
721
        house = cyclone.trans_id_tree_file_id('house-id')
 
722
        cyclone.adjust_path('house', oz, house)
 
723
        cyclone.apply()
 
724
 
 
725
    def test_moving_root(self):
 
726
        create, root = self.get_transform()
 
727
        fun = create.new_directory('fun', root, 'fun-id')
 
728
        create.new_directory('sun', root, 'sun-id')
 
729
        create.new_directory('moon', root, 'moon')
 
730
        create.apply()
 
731
        transform, root = self.get_transform()
 
732
        transform.adjust_root_path('oldroot', fun)
 
733
        new_root=transform.trans_id_tree_path('')
 
734
        transform.version_file('new-root', new_root)
 
735
        transform.apply()
 
736
 
 
737
    def test_renames(self):
 
738
        create, root = self.get_transform()
 
739
        old = create.new_directory('old-parent', root, 'old-id')
 
740
        intermediate = create.new_directory('intermediate', old, 'im-id')
 
741
        myfile = create.new_file('myfile', intermediate, 'myfile-text',
 
742
                                 'myfile-id')
 
743
        create.apply()
 
744
        rename, root = self.get_transform()
 
745
        old = rename.trans_id_file_id('old-id')
 
746
        rename.adjust_path('new', root, old)
 
747
        myfile = rename.trans_id_file_id('myfile-id')
 
748
        rename.set_executability(True, myfile)
 
749
        rename.apply()
 
750
 
 
751
    def test_set_executability_order(self):
 
752
        """Ensure that executability behaves the same, no matter what order.
 
753
        
 
754
        - create file and set executability simultaneously
 
755
        - create file and set executability afterward
 
756
        - unsetting the executability of a file whose executability has not been
 
757
        declared should throw an exception (this may happen when a
 
758
        merge attempts to create a file with a duplicate ID)
 
759
        """
 
760
        transform, root = self.get_transform()
 
761
        wt = transform._tree
 
762
        wt.lock_read()
 
763
        self.addCleanup(wt.unlock)
 
764
        transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
 
765
                           True)
 
766
        sac = transform.new_file('set_after_creation', root,
 
767
                                 'Set after creation', 'sac')
 
768
        transform.set_executability(True, sac)
 
769
        uws = transform.new_file('unset_without_set', root, 'Unset badly',
 
770
                                 'uws')
 
771
        self.assertRaises(KeyError, transform.set_executability, None, uws)
 
772
        transform.apply()
 
773
        self.assertTrue(wt.is_executable('soc'))
 
774
        self.assertTrue(wt.is_executable('sac'))
 
775
 
 
776
    def test_preserve_mode(self):
 
777
        """File mode is preserved when replacing content"""
 
778
        if sys.platform == 'win32':
 
779
            raise TestSkipped('chmod has no effect on win32')
 
780
        transform, root = self.get_transform()
 
781
        transform.new_file('file1', root, 'contents', 'file1-id', True)
 
782
        transform.apply()
 
783
        self.wt.lock_write()
 
784
        self.addCleanup(self.wt.unlock)
 
785
        self.assertTrue(self.wt.is_executable('file1-id'))
 
786
        transform, root = self.get_transform()
 
787
        file1_id = transform.trans_id_tree_file_id('file1-id')
 
788
        transform.delete_contents(file1_id)
 
789
        transform.create_file('contents2', file1_id)
 
790
        transform.apply()
 
791
        self.assertTrue(self.wt.is_executable('file1-id'))
 
792
 
 
793
    def test__set_mode_stats_correctly(self):
 
794
        """_set_mode stats to determine file mode."""
 
795
        if sys.platform == 'win32':
 
796
            raise TestSkipped('chmod has no effect on win32')
 
797
 
 
798
        stat_paths = []
 
799
        real_stat = os.stat
 
800
        def instrumented_stat(path):
 
801
            stat_paths.append(path)
 
802
            return real_stat(path)
 
803
 
 
804
        transform, root = self.get_transform()
 
805
 
 
806
        bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
 
807
                                     file_id='bar-id-1', executable=False)
 
808
        transform.apply()
 
809
 
 
810
        transform, root = self.get_transform()
 
811
        bar1_id = transform.trans_id_tree_path('bar')
 
812
        bar2_id = transform.trans_id_tree_path('bar2')
 
813
        try:
 
814
            os.stat = instrumented_stat
 
815
            transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
 
816
        finally:
 
817
            os.stat = real_stat
 
818
            transform.finalize()
 
819
 
 
820
        bar1_abspath = self.wt.abspath('bar')
 
821
        self.assertEqual([bar1_abspath], stat_paths)
 
822
 
 
823
    def test_iter_changes(self):
 
824
        self.wt.set_root_id('eert_toor')
 
825
        transform, root = self.get_transform()
 
826
        transform.new_file('old', root, 'blah', 'id-1', True)
 
827
        transform.apply()
 
828
        transform, root = self.get_transform()
 
829
        try:
 
830
            self.assertEqual([], list(transform.iter_changes()))
 
831
            old = transform.trans_id_tree_file_id('id-1')
 
832
            transform.unversion_file(old)
 
833
            self.assertEqual([('id-1', ('old', None), False, (True, False),
 
834
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
835
                (True, True))], list(transform.iter_changes()))
 
836
            transform.new_directory('new', root, 'id-1')
 
837
            self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
 
838
                ('eert_toor', 'eert_toor'), ('old', 'new'),
 
839
                ('file', 'directory'),
 
840
                (True, False))], list(transform.iter_changes()))
 
841
        finally:
 
842
            transform.finalize()
 
843
 
 
844
    def test_iter_changes_new(self):
 
845
        self.wt.set_root_id('eert_toor')
 
846
        transform, root = self.get_transform()
 
847
        transform.new_file('old', root, 'blah')
 
848
        transform.apply()
 
849
        transform, root = self.get_transform()
 
850
        try:
 
851
            old = transform.trans_id_tree_path('old')
 
852
            transform.version_file('id-1', old)
 
853
            self.assertEqual([('id-1', (None, 'old'), False, (False, True),
 
854
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
855
                (False, False))], list(transform.iter_changes()))
 
856
        finally:
 
857
            transform.finalize()
 
858
 
 
859
    def test_iter_changes_modifications(self):
 
860
        self.wt.set_root_id('eert_toor')
 
861
        transform, root = self.get_transform()
 
862
        transform.new_file('old', root, 'blah', 'id-1')
 
863
        transform.new_file('new', root, 'blah')
 
864
        transform.new_directory('subdir', root, 'subdir-id')
 
865
        transform.apply()
 
866
        transform, root = self.get_transform()
 
867
        try:
 
868
            old = transform.trans_id_tree_path('old')
 
869
            subdir = transform.trans_id_tree_file_id('subdir-id')
 
870
            new = transform.trans_id_tree_path('new')
 
871
            self.assertEqual([], list(transform.iter_changes()))
 
872
 
 
873
            #content deletion
 
874
            transform.delete_contents(old)
 
875
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
876
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
 
877
                (False, False))], list(transform.iter_changes()))
 
878
 
 
879
            #content change
 
880
            transform.create_file('blah', old)
 
881
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
882
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
883
                (False, False))], list(transform.iter_changes()))
 
884
            transform.cancel_deletion(old)
 
885
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
886
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
887
                (False, False))], list(transform.iter_changes()))
 
888
            transform.cancel_creation(old)
 
889
 
 
890
            # move file_id to a different file
 
891
            self.assertEqual([], list(transform.iter_changes()))
 
892
            transform.unversion_file(old)
 
893
            transform.version_file('id-1', new)
 
894
            transform.adjust_path('old', root, new)
 
895
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
896
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
897
                (False, False))], list(transform.iter_changes()))
 
898
            transform.cancel_versioning(new)
 
899
            transform._removed_id = set()
 
900
 
 
901
            #execute bit
 
902
            self.assertEqual([], list(transform.iter_changes()))
 
903
            transform.set_executability(True, old)
 
904
            self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
 
905
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
906
                (False, True))], list(transform.iter_changes()))
 
907
            transform.set_executability(None, old)
 
908
 
 
909
            # filename
 
910
            self.assertEqual([], list(transform.iter_changes()))
 
911
            transform.adjust_path('new', root, old)
 
912
            transform._new_parent = {}
 
913
            self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
 
914
                ('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
 
915
                (False, False))], list(transform.iter_changes()))
 
916
            transform._new_name = {}
 
917
 
 
918
            # parent directory
 
919
            self.assertEqual([], list(transform.iter_changes()))
 
920
            transform.adjust_path('new', subdir, old)
 
921
            transform._new_name = {}
 
922
            self.assertEqual([('id-1', ('old', 'subdir/old'), False,
 
923
                (True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
 
924
                ('file', 'file'), (False, False))],
 
925
                list(transform.iter_changes()))
 
926
            transform._new_path = {}
 
927
 
 
928
        finally:
 
929
            transform.finalize()
 
930
 
 
931
    def test_iter_changes_modified_bleed(self):
 
932
        self.wt.set_root_id('eert_toor')
 
933
        """Modified flag should not bleed from one change to another"""
 
934
        # unfortunately, we have no guarantee that file1 (which is modified)
 
935
        # will be applied before file2.  And if it's applied after file2, it
 
936
        # obviously can't bleed into file2's change output.  But for now, it
 
937
        # works.
 
938
        transform, root = self.get_transform()
 
939
        transform.new_file('file1', root, 'blah', 'id-1')
 
940
        transform.new_file('file2', root, 'blah', 'id-2')
 
941
        transform.apply()
 
942
        transform, root = self.get_transform()
 
943
        try:
 
944
            transform.delete_contents(transform.trans_id_file_id('id-1'))
 
945
            transform.set_executability(True,
 
946
            transform.trans_id_file_id('id-2'))
 
947
            self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
 
948
                ('eert_toor', 'eert_toor'), ('file1', u'file1'),
 
949
                ('file', None), (False, False)),
 
950
                ('id-2', (u'file2', u'file2'), False, (True, True),
 
951
                ('eert_toor', 'eert_toor'), ('file2', u'file2'),
 
952
                ('file', 'file'), (False, True))],
 
953
                list(transform.iter_changes()))
 
954
        finally:
 
955
            transform.finalize()
 
956
 
 
957
    def test_iter_changes_move_missing(self):
 
958
        """Test moving ids with no files around"""
 
959
        self.wt.set_root_id('toor_eert')
 
960
        # Need two steps because versioning a non-existant file is a conflict.
 
961
        transform, root = self.get_transform()
 
962
        transform.new_directory('floater', root, 'floater-id')
 
963
        transform.apply()
 
964
        transform, root = self.get_transform()
 
965
        transform.delete_contents(transform.trans_id_tree_path('floater'))
 
966
        transform.apply()
 
967
        transform, root = self.get_transform()
 
968
        floater = transform.trans_id_tree_path('floater')
 
969
        try:
 
970
            transform.adjust_path('flitter', root, floater)
 
971
            self.assertEqual([('floater-id', ('floater', 'flitter'), False,
 
972
            (True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
 
973
            (None, None), (False, False))], list(transform.iter_changes()))
 
974
        finally:
 
975
            transform.finalize()
 
976
 
 
977
    def test_iter_changes_pointless(self):
 
978
        """Ensure that no-ops are not treated as modifications"""
 
979
        self.wt.set_root_id('eert_toor')
 
980
        transform, root = self.get_transform()
 
981
        transform.new_file('old', root, 'blah', 'id-1')
 
982
        transform.new_directory('subdir', root, 'subdir-id')
 
983
        transform.apply()
 
984
        transform, root = self.get_transform()
 
985
        try:
 
986
            old = transform.trans_id_tree_path('old')
 
987
            subdir = transform.trans_id_tree_file_id('subdir-id')
 
988
            self.assertEqual([], list(transform.iter_changes()))
 
989
            transform.delete_contents(subdir)
 
990
            transform.create_directory(subdir)
 
991
            transform.set_executability(False, old)
 
992
            transform.unversion_file(old)
 
993
            transform.version_file('id-1', old)
 
994
            transform.adjust_path('old', root, old)
 
995
            self.assertEqual([], list(transform.iter_changes()))
 
996
        finally:
 
997
            transform.finalize()
 
998
 
 
999
    def test_rename_count(self):
 
1000
        transform, root = self.get_transform()
 
1001
        transform.new_file('name1', root, 'contents')
 
1002
        self.assertEqual(transform.rename_count, 0)
 
1003
        transform.apply()
 
1004
        self.assertEqual(transform.rename_count, 1)
 
1005
        transform2, root = self.get_transform()
 
1006
        transform2.adjust_path('name2', root,
 
1007
                               transform2.trans_id_tree_path('name1'))
 
1008
        self.assertEqual(transform2.rename_count, 0)
 
1009
        transform2.apply()
 
1010
        self.assertEqual(transform2.rename_count, 2)
 
1011
 
 
1012
    def test_change_parent(self):
 
1013
        """Ensure that after we change a parent, the results are still right.
 
1014
 
 
1015
        Renames and parent changes on pending transforms can happen as part
 
1016
        of conflict resolution, and are explicitly permitted by the
 
1017
        TreeTransform API.
 
1018
 
 
1019
        This test ensures they work correctly with the rename-avoidance
 
1020
        optimization.
 
1021
        """
 
1022
        transform, root = self.get_transform()
 
1023
        parent1 = transform.new_directory('parent1', root)
 
1024
        child1 = transform.new_file('child1', parent1, 'contents')
 
1025
        parent2 = transform.new_directory('parent2', root)
 
1026
        transform.adjust_path('child1', parent2, child1)
 
1027
        transform.apply()
 
1028
        self.failIfExists(self.wt.abspath('parent1/child1'))
 
1029
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
 
1030
        # rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
 
1031
        # no rename for child1 (counting only renames during apply)
 
1032
        self.failUnlessEqual(2, transform.rename_count)
 
1033
 
 
1034
    def test_cancel_parent(self):
 
1035
        """Cancelling a parent doesn't cause deletion of a non-empty directory
 
1036
 
 
1037
        This is like the test_change_parent, except that we cancel the parent
 
1038
        before adjusting the path.  The transform must detect that the
 
1039
        directory is non-empty, and move children to safe locations.
 
1040
        """
 
1041
        transform, root = self.get_transform()
 
1042
        parent1 = transform.new_directory('parent1', root)
 
1043
        child1 = transform.new_file('child1', parent1, 'contents')
 
1044
        child2 = transform.new_file('child2', parent1, 'contents')
 
1045
        try:
 
1046
            transform.cancel_creation(parent1)
 
1047
        except OSError:
 
1048
            self.fail('Failed to move child1 before deleting parent1')
 
1049
        transform.cancel_creation(child2)
 
1050
        transform.create_directory(parent1)
 
1051
        try:
 
1052
            transform.cancel_creation(parent1)
 
1053
        # If the transform incorrectly believes that child2 is still in
 
1054
        # parent1's limbo directory, it will try to rename it and fail
 
1055
        # because was already moved by the first cancel_creation.
 
1056
        except OSError:
 
1057
            self.fail('Transform still thinks child2 is a child of parent1')
 
1058
        parent2 = transform.new_directory('parent2', root)
 
1059
        transform.adjust_path('child1', parent2, child1)
 
1060
        transform.apply()
 
1061
        self.failIfExists(self.wt.abspath('parent1'))
 
1062
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
 
1063
        # rename limbo/new-3 => parent2, rename limbo/new-2 => child1
 
1064
        self.failUnlessEqual(2, transform.rename_count)
 
1065
 
 
1066
    def test_adjust_and_cancel(self):
 
1067
        """Make sure adjust_path keeps track of limbo children properly"""
 
1068
        transform, root = self.get_transform()
 
1069
        parent1 = transform.new_directory('parent1', root)
 
1070
        child1 = transform.new_file('child1', parent1, 'contents')
 
1071
        parent2 = transform.new_directory('parent2', root)
 
1072
        transform.adjust_path('child1', parent2, child1)
 
1073
        transform.cancel_creation(child1)
 
1074
        try:
 
1075
            transform.cancel_creation(parent1)
 
1076
        # if the transform thinks child1 is still in parent1's limbo
 
1077
        # directory, it will attempt to move it and fail.
 
1078
        except OSError:
 
1079
            self.fail('Transform still thinks child1 is a child of parent1')
 
1080
        transform.finalize()
 
1081
 
 
1082
    def test_noname_contents(self):
 
1083
        """TreeTransform should permit deferring naming files."""
 
1084
        transform, root = self.get_transform()
 
1085
        parent = transform.trans_id_file_id('parent-id')
 
1086
        try:
 
1087
            transform.create_directory(parent)
 
1088
        except KeyError:
 
1089
            self.fail("Can't handle contents with no name")
 
1090
        transform.finalize()
 
1091
 
 
1092
    def test_noname_contents_nested(self):
 
1093
        """TreeTransform should permit deferring naming files."""
 
1094
        transform, root = self.get_transform()
 
1095
        parent = transform.trans_id_file_id('parent-id')
 
1096
        try:
 
1097
            transform.create_directory(parent)
 
1098
        except KeyError:
 
1099
            self.fail("Can't handle contents with no name")
 
1100
        child = transform.new_directory('child', parent)
 
1101
        transform.adjust_path('parent', root, parent)
 
1102
        transform.apply()
 
1103
        self.failUnlessExists(self.wt.abspath('parent/child'))
 
1104
        self.assertEqual(1, transform.rename_count)
 
1105
 
 
1106
    def test_reuse_name(self):
 
1107
        """Avoid reusing the same limbo name for different files"""
 
1108
        transform, root = self.get_transform()
 
1109
        parent = transform.new_directory('parent', root)
 
1110
        child1 = transform.new_directory('child', parent)
 
1111
        try:
 
1112
            child2 = transform.new_directory('child', parent)
 
1113
        except OSError:
 
1114
            self.fail('Tranform tried to use the same limbo name twice')
 
1115
        transform.adjust_path('child2', parent, child2)
 
1116
        transform.apply()
 
1117
        # limbo/new-1 => parent, limbo/new-3 => parent/child2
 
1118
        # child2 is put into top-level limbo because child1 has already
 
1119
        # claimed the direct limbo path when child2 is created.  There is no
 
1120
        # advantage in renaming files once they're in top-level limbo, except
 
1121
        # as part of apply.
 
1122
        self.assertEqual(2, transform.rename_count)
 
1123
 
 
1124
    def test_reuse_when_first_moved(self):
 
1125
        """Don't avoid direct paths when it is safe to use them"""
 
1126
        transform, root = self.get_transform()
 
1127
        parent = transform.new_directory('parent', root)
 
1128
        child1 = transform.new_directory('child', parent)
 
1129
        transform.adjust_path('child1', parent, child1)
 
1130
        child2 = transform.new_directory('child', parent)
 
1131
        transform.apply()
 
1132
        # limbo/new-1 => parent
 
1133
        self.assertEqual(1, transform.rename_count)
 
1134
 
 
1135
    def test_reuse_after_cancel(self):
 
1136
        """Don't avoid direct paths when it is safe to use them"""
 
1137
        transform, root = self.get_transform()
 
1138
        parent2 = transform.new_directory('parent2', root)
 
1139
        child1 = transform.new_directory('child1', parent2)
 
1140
        transform.cancel_creation(parent2)
 
1141
        transform.create_directory(parent2)
 
1142
        child2 = transform.new_directory('child1', parent2)
 
1143
        transform.adjust_path('child2', parent2, child1)
 
1144
        transform.apply()
 
1145
        # limbo/new-1 => parent2, limbo/new-2 => parent2/child1
 
1146
        self.assertEqual(2, transform.rename_count)
 
1147
 
 
1148
    def test_finalize_order(self):
 
1149
        """Finalize must be done in child-to-parent order"""
 
1150
        transform, root = self.get_transform()
 
1151
        parent = transform.new_directory('parent', root)
 
1152
        child = transform.new_directory('child', parent)
 
1153
        try:
 
1154
            transform.finalize()
 
1155
        except OSError:
 
1156
            self.fail('Tried to remove parent before child1')
 
1157
 
 
1158
    def test_cancel_with_cancelled_child_should_succeed(self):
 
1159
        transform, root = self.get_transform()
 
1160
        parent = transform.new_directory('parent', root)
 
1161
        child = transform.new_directory('child', parent)
 
1162
        transform.cancel_creation(child)
 
1163
        transform.cancel_creation(parent)
 
1164
        transform.finalize()
 
1165
 
 
1166
    def test_rollback_on_directory_clash(self):
 
1167
        def tt_helper():
 
1168
            wt = self.make_branch_and_tree('.')
 
1169
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
1170
            try:
 
1171
                foo = tt.new_directory('foo', tt.root)
 
1172
                tt.new_file('bar', foo, 'foobar')
 
1173
                baz = tt.new_directory('baz', tt.root)
 
1174
                tt.new_file('qux', baz, 'quux')
 
1175
                # Ask for a rename 'foo' -> 'baz'
 
1176
                tt.adjust_path('baz', tt.root, foo)
 
1177
                # Lie to tt that we've already resolved all conflicts.
 
1178
                tt.apply(no_conflicts=True)
 
1179
            except:
 
1180
                wt.unlock()
 
1181
                raise
 
1182
        # The rename will fail because the target directory is not empty (but
 
1183
        # raises FileExists anyway).
 
1184
        err = self.assertRaises(errors.FileExists, tt_helper)
 
1185
        self.assertContainsRe(str(err),
 
1186
            "^File exists: .+/baz")
 
1187
 
 
1188
    def test_two_directories_clash(self):
 
1189
        def tt_helper():
 
1190
            wt = self.make_branch_and_tree('.')
 
1191
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
1192
            try:
 
1193
                foo_1 = tt.new_directory('foo', tt.root)
 
1194
                tt.new_directory('bar', foo_1)
 
1195
                # Adding the same directory with a different content
 
1196
                foo_2 = tt.new_directory('foo', tt.root)
 
1197
                tt.new_directory('baz', foo_2)
 
1198
                # Lie to tt that we've already resolved all conflicts.
 
1199
                tt.apply(no_conflicts=True)
 
1200
            except:
 
1201
                wt.unlock()
 
1202
                raise
 
1203
        err = self.assertRaises(errors.FileExists, tt_helper)
 
1204
        self.assertContainsRe(str(err),
 
1205
            "^File exists: .+/foo")
 
1206
 
 
1207
    def test_two_directories_clash_finalize(self):
 
1208
        def tt_helper():
 
1209
            wt = self.make_branch_and_tree('.')
 
1210
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
1211
            try:
 
1212
                foo_1 = tt.new_directory('foo', tt.root)
 
1213
                tt.new_directory('bar', foo_1)
 
1214
                # Adding the same directory with a different content
 
1215
                foo_2 = tt.new_directory('foo', tt.root)
 
1216
                tt.new_directory('baz', foo_2)
 
1217
                # Lie to tt that we've already resolved all conflicts.
 
1218
                tt.apply(no_conflicts=True)
 
1219
            except:
 
1220
                tt.finalize()
 
1221
                raise
 
1222
        err = self.assertRaises(errors.FileExists, tt_helper)
 
1223
        self.assertContainsRe(str(err),
 
1224
            "^File exists: .+/foo")
 
1225
 
 
1226
    def test_file_to_directory(self):
 
1227
        wt = self.make_branch_and_tree('.')
 
1228
        self.build_tree(['foo'])
 
1229
        wt.add(['foo'])
 
1230
        wt.commit("one")
 
1231
        tt = TreeTransform(wt)
 
1232
        self.addCleanup(tt.finalize)
 
1233
        foo_trans_id = tt.trans_id_tree_path("foo")
 
1234
        tt.delete_contents(foo_trans_id)
 
1235
        tt.create_directory(foo_trans_id)
 
1236
        bar_trans_id = tt.trans_id_tree_path("foo/bar")
 
1237
        tt.create_file(["aa\n"], bar_trans_id)
 
1238
        tt.version_file("bar-1", bar_trans_id)
 
1239
        tt.apply()
 
1240
        self.failUnlessExists("foo/bar")
 
1241
        wt.lock_read()
 
1242
        try:
 
1243
            self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
 
1244
                    "directory")
 
1245
        finally:
 
1246
            wt.unlock()
 
1247
        wt.commit("two")
 
1248
        changes = wt.changes_from(wt.basis_tree())
 
1249
        self.assertFalse(changes.has_changed(), changes)
 
1250
 
 
1251
    def test_file_to_symlink(self):
 
1252
        self.requireFeature(SymlinkFeature)
 
1253
        wt = self.make_branch_and_tree('.')
 
1254
        self.build_tree(['foo'])
 
1255
        wt.add(['foo'])
 
1256
        wt.commit("one")
 
1257
        tt = TreeTransform(wt)
 
1258
        self.addCleanup(tt.finalize)
 
1259
        foo_trans_id = tt.trans_id_tree_path("foo")
 
1260
        tt.delete_contents(foo_trans_id)
 
1261
        tt.create_symlink("bar", foo_trans_id)
 
1262
        tt.apply()
 
1263
        self.failUnlessExists("foo")
 
1264
        wt.lock_read()
 
1265
        self.addCleanup(wt.unlock)
 
1266
        self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
 
1267
                "symlink")
 
1268
 
 
1269
    def test_dir_to_file(self):
 
1270
        wt = self.make_branch_and_tree('.')
 
1271
        self.build_tree(['foo/', 'foo/bar'])
 
1272
        wt.add(['foo', 'foo/bar'])
 
1273
        wt.commit("one")
 
1274
        tt = TreeTransform(wt)
 
1275
        self.addCleanup(tt.finalize)
 
1276
        foo_trans_id = tt.trans_id_tree_path("foo")
 
1277
        bar_trans_id = tt.trans_id_tree_path("foo/bar")
 
1278
        tt.delete_contents(foo_trans_id)
 
1279
        tt.delete_versioned(bar_trans_id)
 
1280
        tt.create_file(["aa\n"], foo_trans_id)
 
1281
        tt.apply()
 
1282
        self.failUnlessExists("foo")
 
1283
        wt.lock_read()
 
1284
        self.addCleanup(wt.unlock)
 
1285
        self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
 
1286
                "file")
 
1287
 
 
1288
    def test_dir_to_hardlink(self):
 
1289
        self.requireFeature(HardlinkFeature)
 
1290
        wt = self.make_branch_and_tree('.')
 
1291
        self.build_tree(['foo/', 'foo/bar'])
 
1292
        wt.add(['foo', 'foo/bar'])
 
1293
        wt.commit("one")
 
1294
        tt = TreeTransform(wt)
 
1295
        self.addCleanup(tt.finalize)
 
1296
        foo_trans_id = tt.trans_id_tree_path("foo")
 
1297
        bar_trans_id = tt.trans_id_tree_path("foo/bar")
 
1298
        tt.delete_contents(foo_trans_id)
 
1299
        tt.delete_versioned(bar_trans_id)
 
1300
        self.build_tree(['baz'])
 
1301
        tt.create_hardlink("baz", foo_trans_id)
 
1302
        tt.apply()
 
1303
        self.failUnlessExists("foo")
 
1304
        self.failUnlessExists("baz")
 
1305
        wt.lock_read()
 
1306
        self.addCleanup(wt.unlock)
 
1307
        self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
 
1308
                "file")
 
1309
 
 
1310
    def test_no_final_path(self):
 
1311
        transform, root = self.get_transform()
 
1312
        trans_id = transform.trans_id_file_id('foo')
 
1313
        transform.create_file('bar', trans_id)
 
1314
        transform.cancel_creation(trans_id)
 
1315
        transform.apply()
 
1316
 
 
1317
    def test_create_from_tree(self):
 
1318
        tree1 = self.make_branch_and_tree('tree1')
 
1319
        self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
 
1320
        tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
 
1321
        tree2 = self.make_branch_and_tree('tree2')
 
1322
        tt = TreeTransform(tree2)
 
1323
        foo_trans_id = tt.create_path('foo', tt.root)
 
1324
        create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
 
1325
        bar_trans_id = tt.create_path('bar', tt.root)
 
1326
        create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
 
1327
        tt.apply()
 
1328
        self.assertEqual('directory', osutils.file_kind('tree2/foo'))
 
1329
        self.assertFileEqual('baz', 'tree2/bar')
 
1330
 
 
1331
    def test_create_from_tree_bytes(self):
 
1332
        """Provided lines are used instead of tree content."""
 
1333
        tree1 = self.make_branch_and_tree('tree1')
 
1334
        self.build_tree_contents([('tree1/foo', 'bar'),])
 
1335
        tree1.add('foo', 'foo-id')
 
1336
        tree2 = self.make_branch_and_tree('tree2')
 
1337
        tt = TreeTransform(tree2)
 
1338
        foo_trans_id = tt.create_path('foo', tt.root)
 
1339
        create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
 
1340
        tt.apply()
 
1341
        self.assertFileEqual('qux', 'tree2/foo')
 
1342
 
 
1343
    def test_create_from_tree_symlink(self):
 
1344
        self.requireFeature(SymlinkFeature)
 
1345
        tree1 = self.make_branch_and_tree('tree1')
 
1346
        os.symlink('bar', 'tree1/foo')
 
1347
        tree1.add('foo', 'foo-id')
 
1348
        tt = TreeTransform(self.make_branch_and_tree('tree2'))
 
1349
        foo_trans_id = tt.create_path('foo', tt.root)
 
1350
        create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
 
1351
        tt.apply()
 
1352
        self.assertEqual('bar', os.readlink('tree2/foo'))
 
1353
 
 
1354
 
 
1355
class TransformGroup(object):
 
1356
 
 
1357
    def __init__(self, dirname, root_id):
 
1358
        self.name = dirname
 
1359
        os.mkdir(dirname)
 
1360
        self.wt = BzrDir.create_standalone_workingtree(dirname)
 
1361
        self.wt.set_root_id(root_id)
 
1362
        self.b = self.wt.branch
 
1363
        self.tt = TreeTransform(self.wt)
 
1364
        self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
 
1365
 
 
1366
 
 
1367
def conflict_text(tree, merge):
 
1368
    template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
 
1369
    return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
 
1370
 
 
1371
 
 
1372
class TestTransformMerge(TestCaseInTempDir):
 
1373
 
 
1374
    def test_text_merge(self):
 
1375
        root_id = generate_ids.gen_root_id()
 
1376
        base = TransformGroup("base", root_id)
 
1377
        base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
 
1378
        base.tt.new_file('b', base.root, 'b1', 'b')
 
1379
        base.tt.new_file('c', base.root, 'c', 'c')
 
1380
        base.tt.new_file('d', base.root, 'd', 'd')
 
1381
        base.tt.new_file('e', base.root, 'e', 'e')
 
1382
        base.tt.new_file('f', base.root, 'f', 'f')
 
1383
        base.tt.new_directory('g', base.root, 'g')
 
1384
        base.tt.new_directory('h', base.root, 'h')
 
1385
        base.tt.apply()
 
1386
        other = TransformGroup("other", root_id)
 
1387
        other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
 
1388
        other.tt.new_file('b', other.root, 'b2', 'b')
 
1389
        other.tt.new_file('c', other.root, 'c2', 'c')
 
1390
        other.tt.new_file('d', other.root, 'd', 'd')
 
1391
        other.tt.new_file('e', other.root, 'e2', 'e')
 
1392
        other.tt.new_file('f', other.root, 'f', 'f')
 
1393
        other.tt.new_file('g', other.root, 'g', 'g')
 
1394
        other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
 
1395
        other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
 
1396
        other.tt.apply()
 
1397
        this = TransformGroup("this", root_id)
 
1398
        this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
 
1399
        this.tt.new_file('b', this.root, 'b', 'b')
 
1400
        this.tt.new_file('c', this.root, 'c', 'c')
 
1401
        this.tt.new_file('d', this.root, 'd2', 'd')
 
1402
        this.tt.new_file('e', this.root, 'e2', 'e')
 
1403
        this.tt.new_file('f', this.root, 'f', 'f')
 
1404
        this.tt.new_file('g', this.root, 'g', 'g')
 
1405
        this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
 
1406
        this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
 
1407
        this.tt.apply()
 
1408
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
1409
 
 
1410
        # textual merge
 
1411
        self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
 
1412
        # three-way text conflict
 
1413
        self.assertEqual(this.wt.get_file('b').read(), 
 
1414
                         conflict_text('b', 'b2'))
 
1415
        # OTHER wins
 
1416
        self.assertEqual(this.wt.get_file('c').read(), 'c2')
 
1417
        # THIS wins
 
1418
        self.assertEqual(this.wt.get_file('d').read(), 'd2')
 
1419
        # Ambigious clean merge
 
1420
        self.assertEqual(this.wt.get_file('e').read(), 'e2')
 
1421
        # No change
 
1422
        self.assertEqual(this.wt.get_file('f').read(), 'f')
 
1423
        # Correct correct results when THIS == OTHER 
 
1424
        self.assertEqual(this.wt.get_file('g').read(), 'g')
 
1425
        # Text conflict when THIS & OTHER are text and BASE is dir
 
1426
        self.assertEqual(this.wt.get_file('h').read(), 
 
1427
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
 
1428
        self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
 
1429
                         '1\n2\n3\n4\n')
 
1430
        self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
 
1431
                         'h\ni\nj\nk\n')
 
1432
        self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
 
1433
        self.assertEqual(this.wt.get_file('i').read(), 
 
1434
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
 
1435
        self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
 
1436
                         '1\n2\n3\n4\n')
 
1437
        self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
 
1438
                         'h\ni\nj\nk\n')
 
1439
        self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
 
1440
        modified = ['a', 'b', 'c', 'h', 'i']
 
1441
        merge_modified = this.wt.merge_modified()
 
1442
        self.assertSubset(merge_modified, modified)
 
1443
        self.assertEqual(len(merge_modified), len(modified))
 
1444
        file(this.wt.id2abspath('a'), 'wb').write('booga')
 
1445
        modified.pop(0)
 
1446
        merge_modified = this.wt.merge_modified()
 
1447
        self.assertSubset(merge_modified, modified)
 
1448
        self.assertEqual(len(merge_modified), len(modified))
 
1449
        this.wt.remove('b')
 
1450
        this.wt.revert()
 
1451
 
 
1452
    def test_file_merge(self):
 
1453
        self.requireFeature(SymlinkFeature)
 
1454
        root_id = generate_ids.gen_root_id()
 
1455
        base = TransformGroup("BASE", root_id)
 
1456
        this = TransformGroup("THIS", root_id)
 
1457
        other = TransformGroup("OTHER", root_id)
 
1458
        for tg in this, base, other:
 
1459
            tg.tt.new_directory('a', tg.root, 'a')
 
1460
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
 
1461
            tg.tt.new_file('c', tg.root, 'c', 'c')
 
1462
            tg.tt.new_symlink('d', tg.root, tg.name, 'd')
 
1463
        targets = ((base, 'base-e', 'base-f', None, None), 
 
1464
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'), 
 
1465
                   (other, 'other-e', None, 'other-g', 'other-h'))
 
1466
        for tg, e_target, f_target, g_target, h_target in targets:
 
1467
            for link, target in (('e', e_target), ('f', f_target), 
 
1468
                                 ('g', g_target), ('h', h_target)):
 
1469
                if target is not None:
 
1470
                    tg.tt.new_symlink(link, tg.root, target, link)
 
1471
 
 
1472
        for tg in this, base, other:
 
1473
            tg.tt.apply()
 
1474
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
1475
        self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
 
1476
        self.assertIs(os.path.islink(this.wt.abspath('b')), True)
 
1477
        self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
 
1478
        for suffix in ('THIS', 'BASE', 'OTHER'):
 
1479
            self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
 
1480
        self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
 
1481
        self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
 
1482
        self.assertEqual(this.wt.id2path('f'), 'f.THIS')
 
1483
        self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
 
1484
        self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
 
1485
        self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
 
1486
        self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
 
1487
        self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
 
1488
        self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
 
1489
        self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
 
1490
        self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
 
1491
        self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
 
1492
        self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
 
1493
 
 
1494
    def test_filename_merge(self):
 
1495
        root_id = generate_ids.gen_root_id()
 
1496
        base = TransformGroup("BASE", root_id)
 
1497
        this = TransformGroup("THIS", root_id)
 
1498
        other = TransformGroup("OTHER", root_id)
 
1499
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
 
1500
                                   for t in [base, this, other]]
 
1501
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
 
1502
                                   for t in [base, this, other]]
 
1503
        base.tt.new_directory('c', base_a, 'c')
 
1504
        this.tt.new_directory('c1', this_a, 'c')
 
1505
        other.tt.new_directory('c', other_b, 'c')
 
1506
 
 
1507
        base.tt.new_directory('d', base_a, 'd')
 
1508
        this.tt.new_directory('d1', this_b, 'd')
 
1509
        other.tt.new_directory('d', other_a, 'd')
 
1510
 
 
1511
        base.tt.new_directory('e', base_a, 'e')
 
1512
        this.tt.new_directory('e', this_a, 'e')
 
1513
        other.tt.new_directory('e1', other_b, 'e')
 
1514
 
 
1515
        base.tt.new_directory('f', base_a, 'f')
 
1516
        this.tt.new_directory('f1', this_b, 'f')
 
1517
        other.tt.new_directory('f1', other_b, 'f')
 
1518
 
 
1519
        for tg in [this, base, other]:
 
1520
            tg.tt.apply()
 
1521
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
1522
        self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
 
1523
        self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
 
1524
        self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
 
1525
        self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
 
1526
 
 
1527
    def test_filename_merge_conflicts(self):
 
1528
        root_id = generate_ids.gen_root_id()
 
1529
        base = TransformGroup("BASE", root_id)
 
1530
        this = TransformGroup("THIS", root_id)
 
1531
        other = TransformGroup("OTHER", root_id)
 
1532
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
 
1533
                                   for t in [base, this, other]]
 
1534
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
 
1535
                                   for t in [base, this, other]]
 
1536
 
 
1537
        base.tt.new_file('g', base_a, 'g', 'g')
 
1538
        other.tt.new_file('g1', other_b, 'g1', 'g')
 
1539
 
 
1540
        base.tt.new_file('h', base_a, 'h', 'h')
 
1541
        this.tt.new_file('h1', this_b, 'h1', 'h')
 
1542
 
 
1543
        base.tt.new_file('i', base.root, 'i', 'i')
 
1544
        other.tt.new_directory('i1', this_b, 'i')
 
1545
 
 
1546
        for tg in [this, base, other]:
 
1547
            tg.tt.apply()
 
1548
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
1549
 
 
1550
        self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
 
1551
        self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
 
1552
        self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
 
1553
        self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
 
1554
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
 
1555
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
 
1556
        self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
 
1557
 
 
1558
 
 
1559
class TestBuildTree(tests.TestCaseWithTransport):
 
1560
 
 
1561
    def test_build_tree_with_symlinks(self):
 
1562
        self.requireFeature(SymlinkFeature)
 
1563
        os.mkdir('a')
 
1564
        a = BzrDir.create_standalone_workingtree('a')
 
1565
        os.mkdir('a/foo')
 
1566
        file('a/foo/bar', 'wb').write('contents')
 
1567
        os.symlink('a/foo/bar', 'a/foo/baz')
 
1568
        a.add(['foo', 'foo/bar', 'foo/baz'])
 
1569
        a.commit('initial commit')
 
1570
        b = BzrDir.create_standalone_workingtree('b')
 
1571
        basis = a.basis_tree()
 
1572
        basis.lock_read()
 
1573
        self.addCleanup(basis.unlock)
 
1574
        build_tree(basis, b)
 
1575
        self.assertIs(os.path.isdir('b/foo'), True)
 
1576
        self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
 
1577
        self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
 
1578
 
 
1579
    def test_build_with_references(self):
 
1580
        tree = self.make_branch_and_tree('source',
 
1581
            format='dirstate-with-subtree')
 
1582
        subtree = self.make_branch_and_tree('source/subtree',
 
1583
            format='dirstate-with-subtree')
 
1584
        tree.add_reference(subtree)
 
1585
        tree.commit('a revision')
 
1586
        tree.branch.create_checkout('target')
 
1587
        self.failUnlessExists('target')
 
1588
        self.failUnlessExists('target/subtree')
 
1589
 
 
1590
    def test_file_conflict_handling(self):
 
1591
        """Ensure that when building trees, conflict handling is done"""
 
1592
        source = self.make_branch_and_tree('source')
 
1593
        target = self.make_branch_and_tree('target')
 
1594
        self.build_tree(['source/file', 'target/file'])
 
1595
        source.add('file', 'new-file')
 
1596
        source.commit('added file')
 
1597
        build_tree(source.basis_tree(), target)
 
1598
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
1599
                          'file.moved', 'file', None, 'new-file')],
 
1600
                         target.conflicts())
 
1601
        target2 = self.make_branch_and_tree('target2')
 
1602
        target_file = file('target2/file', 'wb')
 
1603
        try:
 
1604
            source_file = file('source/file', 'rb')
 
1605
            try:
 
1606
                target_file.write(source_file.read())
 
1607
            finally:
 
1608
                source_file.close()
 
1609
        finally:
 
1610
            target_file.close()
 
1611
        build_tree(source.basis_tree(), target2)
 
1612
        self.assertEqual([], target2.conflicts())
 
1613
 
 
1614
    def test_symlink_conflict_handling(self):
 
1615
        """Ensure that when building trees, conflict handling is done"""
 
1616
        self.requireFeature(SymlinkFeature)
 
1617
        source = self.make_branch_and_tree('source')
 
1618
        os.symlink('foo', 'source/symlink')
 
1619
        source.add('symlink', 'new-symlink')
 
1620
        source.commit('added file')
 
1621
        target = self.make_branch_and_tree('target')
 
1622
        os.symlink('bar', 'target/symlink')
 
1623
        build_tree(source.basis_tree(), target)
 
1624
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
1625
            'symlink.moved', 'symlink', None, 'new-symlink')],
 
1626
            target.conflicts())
 
1627
        target = self.make_branch_and_tree('target2')
 
1628
        os.symlink('foo', 'target2/symlink')
 
1629
        build_tree(source.basis_tree(), target)
 
1630
        self.assertEqual([], target.conflicts())
 
1631
        
 
1632
    def test_directory_conflict_handling(self):
 
1633
        """Ensure that when building trees, conflict handling is done"""
 
1634
        source = self.make_branch_and_tree('source')
 
1635
        target = self.make_branch_and_tree('target')
 
1636
        self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
 
1637
        source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
 
1638
        source.commit('added file')
 
1639
        build_tree(source.basis_tree(), target)
 
1640
        self.assertEqual([], target.conflicts())
 
1641
        self.failUnlessExists('target/dir1/file')
 
1642
 
 
1643
        # Ensure contents are merged
 
1644
        target = self.make_branch_and_tree('target2')
 
1645
        self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
 
1646
        build_tree(source.basis_tree(), target)
 
1647
        self.assertEqual([], target.conflicts())
 
1648
        self.failUnlessExists('target2/dir1/file2')
 
1649
        self.failUnlessExists('target2/dir1/file')
 
1650
 
 
1651
        # Ensure new contents are suppressed for existing branches
 
1652
        target = self.make_branch_and_tree('target3')
 
1653
        self.make_branch('target3/dir1')
 
1654
        self.build_tree(['target3/dir1/file2'])
 
1655
        build_tree(source.basis_tree(), target)
 
1656
        self.failIfExists('target3/dir1/file')
 
1657
        self.failUnlessExists('target3/dir1/file2')
 
1658
        self.failUnlessExists('target3/dir1.diverted/file')
 
1659
        self.assertEqual([DuplicateEntry('Diverted to',
 
1660
            'dir1.diverted', 'dir1', 'new-dir1', None)],
 
1661
            target.conflicts())
 
1662
 
 
1663
        target = self.make_branch_and_tree('target4')
 
1664
        self.build_tree(['target4/dir1/'])
 
1665
        self.make_branch('target4/dir1/file')
 
1666
        build_tree(source.basis_tree(), target)
 
1667
        self.failUnlessExists('target4/dir1/file')
 
1668
        self.assertEqual('directory', file_kind('target4/dir1/file'))
 
1669
        self.failUnlessExists('target4/dir1/file.diverted')
 
1670
        self.assertEqual([DuplicateEntry('Diverted to',
 
1671
            'dir1/file.diverted', 'dir1/file', 'new-file', None)],
 
1672
            target.conflicts())
 
1673
 
 
1674
    def test_mixed_conflict_handling(self):
 
1675
        """Ensure that when building trees, conflict handling is done"""
 
1676
        source = self.make_branch_and_tree('source')
 
1677
        target = self.make_branch_and_tree('target')
 
1678
        self.build_tree(['source/name', 'target/name/'])
 
1679
        source.add('name', 'new-name')
 
1680
        source.commit('added file')
 
1681
        build_tree(source.basis_tree(), target)
 
1682
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
1683
            'name.moved', 'name', None, 'new-name')], target.conflicts())
 
1684
 
 
1685
    def test_raises_in_populated(self):
 
1686
        source = self.make_branch_and_tree('source')
 
1687
        self.build_tree(['source/name'])
 
1688
        source.add('name')
 
1689
        source.commit('added name')
 
1690
        target = self.make_branch_and_tree('target')
 
1691
        self.build_tree(['target/name'])
 
1692
        target.add('name')
 
1693
        self.assertRaises(errors.WorkingTreeAlreadyPopulated, 
 
1694
            build_tree, source.basis_tree(), target)
 
1695
 
 
1696
    def test_build_tree_rename_count(self):
 
1697
        source = self.make_branch_and_tree('source')
 
1698
        self.build_tree(['source/file1', 'source/dir1/'])
 
1699
        source.add(['file1', 'dir1'])
 
1700
        source.commit('add1')
 
1701
        target1 = self.make_branch_and_tree('target1')
 
1702
        transform_result = build_tree(source.basis_tree(), target1)
 
1703
        self.assertEqual(2, transform_result.rename_count)
 
1704
 
 
1705
        self.build_tree(['source/dir1/file2'])
 
1706
        source.add(['dir1/file2'])
 
1707
        source.commit('add3')
 
1708
        target2 = self.make_branch_and_tree('target2')
 
1709
        transform_result = build_tree(source.basis_tree(), target2)
 
1710
        # children of non-root directories should not be renamed
 
1711
        self.assertEqual(2, transform_result.rename_count)
 
1712
 
 
1713
    def create_ab_tree(self):
 
1714
        """Create a committed test tree with two files"""
 
1715
        source = self.make_branch_and_tree('source')
 
1716
        self.build_tree_contents([('source/file1', 'A')])
 
1717
        self.build_tree_contents([('source/file2', 'B')])
 
1718
        source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
 
1719
        source.commit('commit files')
 
1720
        source.lock_write()
 
1721
        self.addCleanup(source.unlock)
 
1722
        return source
 
1723
 
 
1724
    def test_build_tree_accelerator_tree(self):
 
1725
        source = self.create_ab_tree()
 
1726
        self.build_tree_contents([('source/file2', 'C')])
 
1727
        calls = []
 
1728
        real_source_get_file = source.get_file
 
1729
        def get_file(file_id, path=None):
 
1730
            calls.append(file_id)
 
1731
            return real_source_get_file(file_id, path)
 
1732
        source.get_file = get_file
 
1733
        target = self.make_branch_and_tree('target')
 
1734
        revision_tree = source.basis_tree()
 
1735
        revision_tree.lock_read()
 
1736
        self.addCleanup(revision_tree.unlock)
 
1737
        build_tree(revision_tree, target, source)
 
1738
        self.assertEqual(['file1-id'], calls)
 
1739
        target.lock_read()
 
1740
        self.addCleanup(target.unlock)
 
1741
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
1742
 
 
1743
    def test_build_tree_accelerator_tree_missing_file(self):
 
1744
        source = self.create_ab_tree()
 
1745
        os.unlink('source/file1')
 
1746
        source.remove(['file2'])
 
1747
        target = self.make_branch_and_tree('target')
 
1748
        revision_tree = source.basis_tree()
 
1749
        revision_tree.lock_read()
 
1750
        self.addCleanup(revision_tree.unlock)
 
1751
        build_tree(revision_tree, target, source)
 
1752
        target.lock_read()
 
1753
        self.addCleanup(target.unlock)
 
1754
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
1755
 
 
1756
    def test_build_tree_accelerator_wrong_kind(self):
 
1757
        self.requireFeature(SymlinkFeature)
 
1758
        source = self.make_branch_and_tree('source')
 
1759
        self.build_tree_contents([('source/file1', '')])
 
1760
        self.build_tree_contents([('source/file2', '')])
 
1761
        source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
 
1762
        source.commit('commit files')
 
1763
        os.unlink('source/file2')
 
1764
        self.build_tree_contents([('source/file2/', 'C')])
 
1765
        os.unlink('source/file1')
 
1766
        os.symlink('file2', 'source/file1')
 
1767
        calls = []
 
1768
        real_source_get_file = source.get_file
 
1769
        def get_file(file_id, path=None):
 
1770
            calls.append(file_id)
 
1771
            return real_source_get_file(file_id, path)
 
1772
        source.get_file = get_file
 
1773
        target = self.make_branch_and_tree('target')
 
1774
        revision_tree = source.basis_tree()
 
1775
        revision_tree.lock_read()
 
1776
        self.addCleanup(revision_tree.unlock)
 
1777
        build_tree(revision_tree, target, source)
 
1778
        self.assertEqual([], calls)
 
1779
        target.lock_read()
 
1780
        self.addCleanup(target.unlock)
 
1781
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
1782
 
 
1783
    def test_build_tree_hardlink(self):
 
1784
        self.requireFeature(HardlinkFeature)
 
1785
        source = self.create_ab_tree()
 
1786
        target = self.make_branch_and_tree('target')
 
1787
        revision_tree = source.basis_tree()
 
1788
        revision_tree.lock_read()
 
1789
        self.addCleanup(revision_tree.unlock)
 
1790
        build_tree(revision_tree, target, source, hardlink=True)
 
1791
        target.lock_read()
 
1792
        self.addCleanup(target.unlock)
 
1793
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
1794
        source_stat = os.stat('source/file1')
 
1795
        target_stat = os.stat('target/file1')
 
1796
        self.assertEqual(source_stat, target_stat)
 
1797
 
 
1798
        # Explicitly disallowing hardlinks should prevent them.
 
1799
        target2 = self.make_branch_and_tree('target2')
 
1800
        build_tree(revision_tree, target2, source, hardlink=False)
 
1801
        target2.lock_read()
 
1802
        self.addCleanup(target2.unlock)
 
1803
        self.assertEqual([], list(target2.iter_changes(revision_tree)))
 
1804
        source_stat = os.stat('source/file1')
 
1805
        target2_stat = os.stat('target2/file1')
 
1806
        self.assertNotEqual(source_stat, target2_stat)
 
1807
 
 
1808
    def test_build_tree_accelerator_tree_moved(self):
 
1809
        source = self.make_branch_and_tree('source')
 
1810
        self.build_tree_contents([('source/file1', 'A')])
 
1811
        source.add(['file1'], ['file1-id'])
 
1812
        source.commit('commit files')
 
1813
        source.rename_one('file1', 'file2')
 
1814
        source.lock_read()
 
1815
        self.addCleanup(source.unlock)
 
1816
        target = self.make_branch_and_tree('target')
 
1817
        revision_tree = source.basis_tree()
 
1818
        revision_tree.lock_read()
 
1819
        self.addCleanup(revision_tree.unlock)
 
1820
        build_tree(revision_tree, target, source)
 
1821
        target.lock_read()
 
1822
        self.addCleanup(target.unlock)
 
1823
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
1824
 
 
1825
    def test_build_tree_hardlinks_preserve_execute(self):
 
1826
        self.requireFeature(HardlinkFeature)
 
1827
        source = self.create_ab_tree()
 
1828
        tt = TreeTransform(source)
 
1829
        trans_id = tt.trans_id_tree_file_id('file1-id')
 
1830
        tt.set_executability(True, trans_id)
 
1831
        tt.apply()
 
1832
        self.assertTrue(source.is_executable('file1-id'))
 
1833
        target = self.make_branch_and_tree('target')
 
1834
        revision_tree = source.basis_tree()
 
1835
        revision_tree.lock_read()
 
1836
        self.addCleanup(revision_tree.unlock)
 
1837
        build_tree(revision_tree, target, source, hardlink=True)
 
1838
        target.lock_read()
 
1839
        self.addCleanup(target.unlock)
 
1840
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
1841
        self.assertTrue(source.is_executable('file1-id'))
 
1842
 
 
1843
    def test_case_insensitive_build_tree_inventory(self):
 
1844
        source = self.make_branch_and_tree('source')
 
1845
        self.build_tree(['source/file', 'source/FILE'])
 
1846
        source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
 
1847
        source.commit('added files')
 
1848
        # Don't try this at home, kids!
 
1849
        # Force the tree to report that it is case insensitive
 
1850
        target = self.make_branch_and_tree('target')
 
1851
        target.case_sensitive = False
 
1852
        build_tree(source.basis_tree(), target, source, delta_from_tree=True)
 
1853
        self.assertEqual('file.moved', target.id2path('lower-id'))
 
1854
        self.assertEqual('FILE', target.id2path('upper-id'))
 
1855
 
 
1856
 
 
1857
class MockTransform(object):
 
1858
 
 
1859
    def has_named_child(self, by_parent, parent_id, name):
 
1860
        for child_id in by_parent[parent_id]:
 
1861
            if child_id == '0':
 
1862
                if name == "name~":
 
1863
                    return True
 
1864
            elif name == "name.~%s~" % child_id:
 
1865
                return True
 
1866
        return False
 
1867
 
 
1868
 
 
1869
class MockEntry(object):
 
1870
    def __init__(self):
 
1871
        object.__init__(self)
 
1872
        self.name = "name"
 
1873
 
 
1874
 
 
1875
class TestGetBackupName(TestCase):
 
1876
    def test_get_backup_name(self):
 
1877
        tt = MockTransform()
 
1878
        name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
 
1879
        self.assertEqual(name, 'name.~1~')
 
1880
        name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
 
1881
        self.assertEqual(name, 'name.~2~')
 
1882
        name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
 
1883
        self.assertEqual(name, 'name.~1~')
 
1884
        name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
 
1885
        self.assertEqual(name, 'name.~1~')
 
1886
        name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
 
1887
        self.assertEqual(name, 'name.~4~')
 
1888
 
 
1889
 
 
1890
class TestFileMover(tests.TestCaseWithTransport):
 
1891
 
 
1892
    def test_file_mover(self):
 
1893
        self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
 
1894
        mover = _FileMover()
 
1895
        mover.rename('a', 'q')
 
1896
        self.failUnlessExists('q')
 
1897
        self.failIfExists('a')
 
1898
        self.failUnlessExists('q/b')
 
1899
        self.failUnlessExists('c')
 
1900
        self.failUnlessExists('c/d')
 
1901
 
 
1902
    def test_pre_delete_rollback(self):
 
1903
        self.build_tree(['a/'])
 
1904
        mover = _FileMover()
 
1905
        mover.pre_delete('a', 'q')
 
1906
        self.failUnlessExists('q')
 
1907
        self.failIfExists('a')
 
1908
        mover.rollback()
 
1909
        self.failIfExists('q')
 
1910
        self.failUnlessExists('a')
 
1911
 
 
1912
    def test_apply_deletions(self):
 
1913
        self.build_tree(['a/', 'b/'])
 
1914
        mover = _FileMover()
 
1915
        mover.pre_delete('a', 'q')
 
1916
        mover.pre_delete('b', 'r')
 
1917
        self.failUnlessExists('q')
 
1918
        self.failUnlessExists('r')
 
1919
        self.failIfExists('a')
 
1920
        self.failIfExists('b')
 
1921
        mover.apply_deletions()
 
1922
        self.failIfExists('q')
 
1923
        self.failIfExists('r')
 
1924
        self.failIfExists('a')
 
1925
        self.failIfExists('b')
 
1926
 
 
1927
    def test_file_mover_rollback(self):
 
1928
        self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
 
1929
        mover = _FileMover()
 
1930
        mover.rename('c/d', 'c/f')
 
1931
        mover.rename('c/e', 'c/d')
 
1932
        try:
 
1933
            mover.rename('a', 'c')
 
1934
        except errors.FileExists, e:
 
1935
            mover.rollback()
 
1936
        self.failUnlessExists('a')
 
1937
        self.failUnlessExists('c/d')
 
1938
 
 
1939
 
 
1940
class Bogus(Exception):
 
1941
    pass
 
1942
 
 
1943
 
 
1944
class TestTransformRollback(tests.TestCaseWithTransport):
 
1945
 
 
1946
    class ExceptionFileMover(_FileMover):
 
1947
 
 
1948
        def __init__(self, bad_source=None, bad_target=None):
 
1949
            _FileMover.__init__(self)
 
1950
            self.bad_source = bad_source
 
1951
            self.bad_target = bad_target
 
1952
 
 
1953
        def rename(self, source, target):
 
1954
            if (self.bad_source is not None and
 
1955
                source.endswith(self.bad_source)):
 
1956
                raise Bogus
 
1957
            elif (self.bad_target is not None and
 
1958
                target.endswith(self.bad_target)):
 
1959
                raise Bogus
 
1960
            else:
 
1961
                _FileMover.rename(self, source, target)
 
1962
 
 
1963
    def test_rollback_rename(self):
 
1964
        tree = self.make_branch_and_tree('.')
 
1965
        self.build_tree(['a/', 'a/b'])
 
1966
        tt = TreeTransform(tree)
 
1967
        self.addCleanup(tt.finalize)
 
1968
        a_id = tt.trans_id_tree_path('a')
 
1969
        tt.adjust_path('c', tt.root, a_id)
 
1970
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
 
1971
        self.assertRaises(Bogus, tt.apply,
 
1972
                          _mover=self.ExceptionFileMover(bad_source='a'))
 
1973
        self.failUnlessExists('a')
 
1974
        self.failUnlessExists('a/b')
 
1975
        tt.apply()
 
1976
        self.failUnlessExists('c')
 
1977
        self.failUnlessExists('c/d')
 
1978
 
 
1979
    def test_rollback_rename_into_place(self):
 
1980
        tree = self.make_branch_and_tree('.')
 
1981
        self.build_tree(['a/', 'a/b'])
 
1982
        tt = TreeTransform(tree)
 
1983
        self.addCleanup(tt.finalize)
 
1984
        a_id = tt.trans_id_tree_path('a')
 
1985
        tt.adjust_path('c', tt.root, a_id)
 
1986
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
 
1987
        self.assertRaises(Bogus, tt.apply,
 
1988
                          _mover=self.ExceptionFileMover(bad_target='c/d'))
 
1989
        self.failUnlessExists('a')
 
1990
        self.failUnlessExists('a/b')
 
1991
        tt.apply()
 
1992
        self.failUnlessExists('c')
 
1993
        self.failUnlessExists('c/d')
 
1994
 
 
1995
    def test_rollback_deletion(self):
 
1996
        tree = self.make_branch_and_tree('.')
 
1997
        self.build_tree(['a/', 'a/b'])
 
1998
        tt = TreeTransform(tree)
 
1999
        self.addCleanup(tt.finalize)
 
2000
        a_id = tt.trans_id_tree_path('a')
 
2001
        tt.delete_contents(a_id)
 
2002
        tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
 
2003
        self.assertRaises(Bogus, tt.apply,
 
2004
                          _mover=self.ExceptionFileMover(bad_target='d'))
 
2005
        self.failUnlessExists('a')
 
2006
        self.failUnlessExists('a/b')
 
2007
 
 
2008
    def test_resolve_no_parent(self):
 
2009
        wt = self.make_branch_and_tree('.')
 
2010
        tt = TreeTransform(wt)
 
2011
        self.addCleanup(tt.finalize)
 
2012
        parent = tt.trans_id_file_id('parent-id')
 
2013
        tt.new_file('file', parent, 'Contents')
 
2014
        resolve_conflicts(tt)
 
2015
 
 
2016
 
 
2017
class TestTransformPreview(tests.TestCaseWithTransport):
 
2018
 
 
2019
    def create_tree(self):
 
2020
        tree = self.make_branch_and_tree('.')
 
2021
        self.build_tree_contents([('a', 'content 1')])
 
2022
        tree.add('a', 'a-id')
 
2023
        tree.commit('rev1', rev_id='rev1')
 
2024
        return tree.branch.repository.revision_tree('rev1')
 
2025
 
 
2026
    def get_empty_preview(self):
 
2027
        repository = self.make_repository('repo')
 
2028
        tree = repository.revision_tree(_mod_revision.NULL_REVISION)
 
2029
        preview = TransformPreview(tree)
 
2030
        self.addCleanup(preview.finalize)
 
2031
        return preview
 
2032
 
 
2033
    def test_transform_preview(self):
 
2034
        revision_tree = self.create_tree()
 
2035
        preview = TransformPreview(revision_tree)
 
2036
        self.addCleanup(preview.finalize)
 
2037
 
 
2038
    def test_transform_preview_tree(self):
 
2039
        revision_tree = self.create_tree()
 
2040
        preview = TransformPreview(revision_tree)
 
2041
        self.addCleanup(preview.finalize)
 
2042
        preview.get_preview_tree()
 
2043
 
 
2044
    def test_transform_new_file(self):
 
2045
        revision_tree = self.create_tree()
 
2046
        preview = TransformPreview(revision_tree)
 
2047
        self.addCleanup(preview.finalize)
 
2048
        preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
 
2049
        preview_tree = preview.get_preview_tree()
 
2050
        self.assertEqual(preview_tree.kind('file2-id'), 'file')
 
2051
        self.assertEqual(
 
2052
            preview_tree.get_file('file2-id').read(), 'content B\n')
 
2053
 
 
2054
    def test_diff_preview_tree(self):
 
2055
        revision_tree = self.create_tree()
 
2056
        preview = TransformPreview(revision_tree)
 
2057
        self.addCleanup(preview.finalize)
 
2058
        preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
 
2059
        preview_tree = preview.get_preview_tree()
 
2060
        out = StringIO()
 
2061
        show_diff_trees(revision_tree, preview_tree, out)
 
2062
        lines = out.getvalue().splitlines()
 
2063
        self.assertEqual(lines[0], "=== added file 'file2'")
 
2064
        # 3 lines of diff administrivia
 
2065
        self.assertEqual(lines[4], "+content B")
 
2066
 
 
2067
    def test_transform_conflicts(self):
 
2068
        revision_tree = self.create_tree()
 
2069
        preview = TransformPreview(revision_tree)
 
2070
        self.addCleanup(preview.finalize)
 
2071
        preview.new_file('a', preview.root, 'content 2')
 
2072
        resolve_conflicts(preview)
 
2073
        trans_id = preview.trans_id_file_id('a-id')
 
2074
        self.assertEqual('a.moved', preview.final_name(trans_id))
 
2075
 
 
2076
    def get_tree_and_preview_tree(self):
 
2077
        revision_tree = self.create_tree()
 
2078
        preview = TransformPreview(revision_tree)
 
2079
        self.addCleanup(preview.finalize)
 
2080
        a_trans_id = preview.trans_id_file_id('a-id')
 
2081
        preview.delete_contents(a_trans_id)
 
2082
        preview.create_file('b content', a_trans_id)
 
2083
        preview_tree = preview.get_preview_tree()
 
2084
        return revision_tree, preview_tree
 
2085
 
 
2086
    def test_iter_changes(self):
 
2087
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2088
        root = revision_tree.inventory.root.file_id
 
2089
        self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
 
2090
                          (root, root), ('a', 'a'), ('file', 'file'),
 
2091
                          (False, False))],
 
2092
                          list(preview_tree.iter_changes(revision_tree)))
 
2093
 
 
2094
    def test_include_unchanged_value_error(self):
 
2095
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2096
        e = self.assertRaises(ValueError, preview_tree.iter_changes,
 
2097
                              revision_tree, include_unchanged=True)
 
2098
        self.assertEqual('include_unchanged is not supported', str(e))
 
2099
 
 
2100
    def test_specific_files(self):
 
2101
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2102
        e = self.assertRaises(ValueError, preview_tree.iter_changes,
 
2103
                              revision_tree, specific_files=['pete'])
 
2104
        self.assertEqual('specific_files is not supported', str(e))
 
2105
 
 
2106
    def test_want_unversioned_value_error(self):
 
2107
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2108
        e = self.assertRaises(ValueError, preview_tree.iter_changes,
 
2109
                              revision_tree, want_unversioned=True)
 
2110
        self.assertEqual('want_unversioned is not supported', str(e))
 
2111
 
 
2112
    def test_ignore_extra_trees_no_specific_files(self):
 
2113
        # extra_trees is harmless without specific_files, so we'll silently
 
2114
        # accept it, even though we won't use it.
 
2115
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2116
        preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
 
2117
 
 
2118
    def test_ignore_require_versioned_no_specific_files(self):
 
2119
        # require_versioned is meaningless without specific_files.
 
2120
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2121
        preview_tree.iter_changes(revision_tree, require_versioned=False)
 
2122
 
 
2123
    def test_ignore_pb(self):
 
2124
        # pb could be supported, but TT.iter_changes doesn't support it.
 
2125
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2126
        preview_tree.iter_changes(revision_tree, pb=progress.DummyProgress())
 
2127
 
 
2128
    def test_kind(self):
 
2129
        revision_tree = self.create_tree()
 
2130
        preview = TransformPreview(revision_tree)
 
2131
        self.addCleanup(preview.finalize)
 
2132
        preview.new_file('file', preview.root, 'contents', 'file-id')
 
2133
        preview.new_directory('directory', preview.root, 'dir-id')
 
2134
        preview_tree = preview.get_preview_tree()
 
2135
        self.assertEqual('file', preview_tree.kind('file-id'))
 
2136
        self.assertEqual('directory', preview_tree.kind('dir-id'))
 
2137
 
 
2138
    def test_get_file_mtime(self):
 
2139
        preview = self.get_empty_preview()
 
2140
        file_trans_id = preview.new_file('file', preview.root, 'contents',
 
2141
                                         'file-id')
 
2142
        limbo_path = preview._limbo_name(file_trans_id)
 
2143
        preview_tree = preview.get_preview_tree()
 
2144
        self.assertEqual(os.stat(limbo_path).st_mtime,
 
2145
                         preview_tree.get_file_mtime('file-id'))
 
2146
 
 
2147
    def test_get_file(self):
 
2148
        preview = self.get_empty_preview()
 
2149
        preview.new_file('file', preview.root, 'contents', 'file-id')
 
2150
        preview_tree = preview.get_preview_tree()
 
2151
        tree_file = preview_tree.get_file('file-id')
 
2152
        try:
 
2153
            self.assertEqual('contents', tree_file.read())
 
2154
        finally:
 
2155
            tree_file.close()
 
2156
 
 
2157
    def test_get_symlink_target(self):
 
2158
        self.requireFeature(SymlinkFeature)
 
2159
        preview = self.get_empty_preview()
 
2160
        preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
 
2161
        preview_tree = preview.get_preview_tree()
 
2162
        self.assertEqual('target',
 
2163
                         preview_tree.get_symlink_target('symlink-id'))
 
2164
 
 
2165
    def test_all_file_ids(self):
 
2166
        tree = self.make_branch_and_tree('tree')
 
2167
        self.build_tree(['tree/a', 'tree/b', 'tree/c'])
 
2168
        tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
 
2169
        preview = TransformPreview(tree)
 
2170
        self.addCleanup(preview.finalize)
 
2171
        preview.unversion_file(preview.trans_id_file_id('b-id'))
 
2172
        c_trans_id = preview.trans_id_file_id('c-id')
 
2173
        preview.unversion_file(c_trans_id)
 
2174
        preview.version_file('c-id', c_trans_id)
 
2175
        preview_tree = preview.get_preview_tree()
 
2176
        self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
 
2177
                         preview_tree.all_file_ids())
 
2178
 
 
2179
    def test_path2id_deleted_unchanged(self):
 
2180
        tree = self.make_branch_and_tree('tree')
 
2181
        self.build_tree(['tree/unchanged', 'tree/deleted'])
 
2182
        tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
 
2183
        preview = TransformPreview(tree)
 
2184
        self.addCleanup(preview.finalize)
 
2185
        preview.unversion_file(preview.trans_id_file_id('deleted-id'))
 
2186
        preview_tree = preview.get_preview_tree()
 
2187
        self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
 
2188
        self.assertIs(None, preview_tree.path2id('deleted'))
 
2189
 
 
2190
    def test_path2id_created(self):
 
2191
        tree = self.make_branch_and_tree('tree')
 
2192
        self.build_tree(['tree/unchanged'])
 
2193
        tree.add(['unchanged'], ['unchanged-id'])
 
2194
        preview = TransformPreview(tree)
 
2195
        self.addCleanup(preview.finalize)
 
2196
        preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
 
2197
            'contents', 'new-id')
 
2198
        preview_tree = preview.get_preview_tree()
 
2199
        self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
 
2200
 
 
2201
    def test_path2id_moved(self):
 
2202
        tree = self.make_branch_and_tree('tree')
 
2203
        self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
 
2204
        tree.add(['old_parent', 'old_parent/child'],
 
2205
                 ['old_parent-id', 'child-id'])
 
2206
        preview = TransformPreview(tree)
 
2207
        self.addCleanup(preview.finalize)
 
2208
        new_parent = preview.new_directory('new_parent', preview.root,
 
2209
                                           'new_parent-id')
 
2210
        preview.adjust_path('child', new_parent,
 
2211
                            preview.trans_id_file_id('child-id'))
 
2212
        preview_tree = preview.get_preview_tree()
 
2213
        self.assertIs(None, preview_tree.path2id('old_parent/child'))
 
2214
        self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
 
2215
 
 
2216
    def test_path2id_renamed_parent(self):
 
2217
        tree = self.make_branch_and_tree('tree')
 
2218
        self.build_tree(['tree/old_name/', 'tree/old_name/child'])
 
2219
        tree.add(['old_name', 'old_name/child'],
 
2220
                 ['parent-id', 'child-id'])
 
2221
        preview = TransformPreview(tree)
 
2222
        self.addCleanup(preview.finalize)
 
2223
        preview.adjust_path('new_name', preview.root,
 
2224
                            preview.trans_id_file_id('parent-id'))
 
2225
        preview_tree = preview.get_preview_tree()
 
2226
        self.assertIs(None, preview_tree.path2id('old_name/child'))
 
2227
        self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
 
2228
 
 
2229
    def assertMatchingIterEntries(self, tt, specific_file_ids=None):
 
2230
        preview_tree = tt.get_preview_tree()
 
2231
        preview_result = list(preview_tree.iter_entries_by_dir(
 
2232
                              specific_file_ids))
 
2233
        tree = tt._tree
 
2234
        tt.apply()
 
2235
        actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
 
2236
        self.assertEqual(actual_result, preview_result)
 
2237
 
 
2238
    def test_iter_entries_by_dir_new(self):
 
2239
        tree = self.make_branch_and_tree('tree')
 
2240
        tt = TreeTransform(tree)
 
2241
        tt.new_file('new', tt.root, 'contents', 'new-id')
 
2242
        self.assertMatchingIterEntries(tt)
 
2243
 
 
2244
    def test_iter_entries_by_dir_deleted(self):
 
2245
        tree = self.make_branch_and_tree('tree')
 
2246
        self.build_tree(['tree/deleted'])
 
2247
        tree.add('deleted', 'deleted-id')
 
2248
        tt = TreeTransform(tree)
 
2249
        tt.delete_contents(tt.trans_id_file_id('deleted-id'))
 
2250
        self.assertMatchingIterEntries(tt)
 
2251
 
 
2252
    def test_iter_entries_by_dir_unversioned(self):
 
2253
        tree = self.make_branch_and_tree('tree')
 
2254
        self.build_tree(['tree/removed'])
 
2255
        tree.add('removed', 'removed-id')
 
2256
        tt = TreeTransform(tree)
 
2257
        tt.unversion_file(tt.trans_id_file_id('removed-id'))
 
2258
        self.assertMatchingIterEntries(tt)
 
2259
 
 
2260
    def test_iter_entries_by_dir_moved(self):
 
2261
        tree = self.make_branch_and_tree('tree')
 
2262
        self.build_tree(['tree/moved', 'tree/new_parent/'])
 
2263
        tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
 
2264
        tt = TreeTransform(tree)
 
2265
        tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
 
2266
                       tt.trans_id_file_id('moved-id'))
 
2267
        self.assertMatchingIterEntries(tt)
 
2268
 
 
2269
    def test_iter_entries_by_dir_specific_file_ids(self):
 
2270
        tree = self.make_branch_and_tree('tree')
 
2271
        tree.set_root_id('tree-root-id')
 
2272
        self.build_tree(['tree/parent/', 'tree/parent/child'])
 
2273
        tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
 
2274
        tt = TreeTransform(tree)
 
2275
        self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
 
2276
 
 
2277
    def test_symlink_content_summary(self):
 
2278
        self.requireFeature(SymlinkFeature)
 
2279
        preview = self.get_empty_preview()
 
2280
        preview.new_symlink('path', preview.root, 'target', 'path-id')
 
2281
        summary = preview.get_preview_tree().path_content_summary('path')
 
2282
        self.assertEqual(('symlink', None, None, 'target'), summary)
 
2283
 
 
2284
    def test_missing_content_summary(self):
 
2285
        preview = self.get_empty_preview()
 
2286
        summary = preview.get_preview_tree().path_content_summary('path')
 
2287
        self.assertEqual(('missing', None, None, None), summary)
 
2288
 
 
2289
    def test_deleted_content_summary(self):
 
2290
        tree = self.make_branch_and_tree('tree')
 
2291
        self.build_tree(['tree/path/'])
 
2292
        tree.add('path')
 
2293
        preview = TransformPreview(tree)
 
2294
        self.addCleanup(preview.finalize)
 
2295
        preview.delete_contents(preview.trans_id_tree_path('path'))
 
2296
        summary = preview.get_preview_tree().path_content_summary('path')
 
2297
        self.assertEqual(('missing', None, None, None), summary)
 
2298
 
 
2299
    def test_file_content_summary_executable(self):
 
2300
        if not osutils.supports_executable():
 
2301
            raise TestNotApplicable()
 
2302
        preview = self.get_empty_preview()
 
2303
        path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
 
2304
        preview.set_executability(True, path_id)
 
2305
        summary = preview.get_preview_tree().path_content_summary('path')
 
2306
        self.assertEqual(4, len(summary))
 
2307
        self.assertEqual('file', summary[0])
 
2308
        # size must be known
 
2309
        self.assertEqual(len('contents'), summary[1])
 
2310
        # executable
 
2311
        self.assertEqual(True, summary[2])
 
2312
        # will not have hash (not cheap to determine)
 
2313
        self.assertIs(None, summary[3])
 
2314
 
 
2315
    def test_change_executability(self):
 
2316
        if not osutils.supports_executable():
 
2317
            raise TestNotApplicable()
 
2318
        tree = self.make_branch_and_tree('tree')
 
2319
        self.build_tree(['tree/path'])
 
2320
        tree.add('path')
 
2321
        preview = TransformPreview(tree)
 
2322
        self.addCleanup(preview.finalize)
 
2323
        path_id = preview.trans_id_tree_path('path')
 
2324
        preview.set_executability(True, path_id)
 
2325
        summary = preview.get_preview_tree().path_content_summary('path')
 
2326
        self.assertEqual(True, summary[2])
 
2327
 
 
2328
    def test_file_content_summary_non_exec(self):
 
2329
        preview = self.get_empty_preview()
 
2330
        preview.new_file('path', preview.root, 'contents', 'path-id')
 
2331
        summary = preview.get_preview_tree().path_content_summary('path')
 
2332
        self.assertEqual(4, len(summary))
 
2333
        self.assertEqual('file', summary[0])
 
2334
        # size must be known
 
2335
        self.assertEqual(len('contents'), summary[1])
 
2336
        # not executable
 
2337
        if osutils.supports_executable():
 
2338
            self.assertEqual(False, summary[2])
 
2339
        else:
 
2340
            self.assertEqual(None, summary[2])
 
2341
        # will not have hash (not cheap to determine)
 
2342
        self.assertIs(None, summary[3])
 
2343
 
 
2344
    def test_dir_content_summary(self):
 
2345
        preview = self.get_empty_preview()
 
2346
        preview.new_directory('path', preview.root, 'path-id')
 
2347
        summary = preview.get_preview_tree().path_content_summary('path')
 
2348
        self.assertEqual(('directory', None, None, None), summary)
 
2349
 
 
2350
    def test_tree_content_summary(self):
 
2351
        preview = self.get_empty_preview()
 
2352
        path = preview.new_directory('path', preview.root, 'path-id')
 
2353
        preview.set_tree_reference('rev-1', path)
 
2354
        summary = preview.get_preview_tree().path_content_summary('path')
 
2355
        self.assertEqual(4, len(summary))
 
2356
        self.assertEqual('tree-reference', summary[0])
 
2357
 
 
2358
    def test_annotate(self):
 
2359
        tree = self.make_branch_and_tree('tree')
 
2360
        self.build_tree_contents([('tree/file', 'a\n')])
 
2361
        tree.add('file', 'file-id')
 
2362
        tree.commit('a', rev_id='one')
 
2363
        self.build_tree_contents([('tree/file', 'a\nb\n')])
 
2364
        preview = TransformPreview(tree)
 
2365
        self.addCleanup(preview.finalize)
 
2366
        file_trans_id = preview.trans_id_file_id('file-id')
 
2367
        preview.delete_contents(file_trans_id)
 
2368
        preview.create_file('a\nb\nc\n', file_trans_id)
 
2369
        preview_tree = preview.get_preview_tree()
 
2370
        expected = [
 
2371
            ('one', 'a\n'),
 
2372
            ('me:', 'b\n'),
 
2373
            ('me:', 'c\n'),
 
2374
        ]
 
2375
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2376
        self.assertEqual(expected, annotation)
 
2377
 
 
2378
    def test_annotate_missing(self):
 
2379
        preview = self.get_empty_preview()
 
2380
        preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
 
2381
        preview_tree = preview.get_preview_tree()
 
2382
        expected = [
 
2383
            ('me:', 'a\n'),
 
2384
            ('me:', 'b\n'),
 
2385
            ('me:', 'c\n'),
 
2386
         ]
 
2387
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2388
        self.assertEqual(expected, annotation)
 
2389
 
 
2390
    def test_annotate_rename(self):
 
2391
        tree = self.make_branch_and_tree('tree')
 
2392
        self.build_tree_contents([('tree/file', 'a\n')])
 
2393
        tree.add('file', 'file-id')
 
2394
        tree.commit('a', rev_id='one')
 
2395
        preview = TransformPreview(tree)
 
2396
        self.addCleanup(preview.finalize)
 
2397
        file_trans_id = preview.trans_id_file_id('file-id')
 
2398
        preview.adjust_path('newname', preview.root, file_trans_id)
 
2399
        preview_tree = preview.get_preview_tree()
 
2400
        expected = [
 
2401
            ('one', 'a\n'),
 
2402
        ]
 
2403
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2404
        self.assertEqual(expected, annotation)
 
2405
 
 
2406
    def test_annotate_deleted(self):
 
2407
        tree = self.make_branch_and_tree('tree')
 
2408
        self.build_tree_contents([('tree/file', 'a\n')])
 
2409
        tree.add('file', 'file-id')
 
2410
        tree.commit('a', rev_id='one')
 
2411
        self.build_tree_contents([('tree/file', 'a\nb\n')])
 
2412
        preview = TransformPreview(tree)
 
2413
        self.addCleanup(preview.finalize)
 
2414
        file_trans_id = preview.trans_id_file_id('file-id')
 
2415
        preview.delete_contents(file_trans_id)
 
2416
        preview_tree = preview.get_preview_tree()
 
2417
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2418
        self.assertIs(None, annotation)
 
2419
 
 
2420
    def test_stored_kind(self):
 
2421
        preview = self.get_empty_preview()
 
2422
        preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
 
2423
        preview_tree = preview.get_preview_tree()
 
2424
        self.assertEqual('file', preview_tree.stored_kind('file-id'))
 
2425
 
 
2426
    def test_is_executable(self):
 
2427
        preview = self.get_empty_preview()
 
2428
        preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
 
2429
        preview.set_executability(True, preview.trans_id_file_id('file-id'))
 
2430
        preview_tree = preview.get_preview_tree()
 
2431
        self.assertEqual(True, preview_tree.is_executable('file-id'))
 
2432
 
 
2433
    def test_get_set_parent_ids(self):
 
2434
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2435
        self.assertEqual([], preview_tree.get_parent_ids())
 
2436
        preview_tree.set_parent_ids(['rev-1'])
 
2437
        self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
 
2438
 
 
2439
    def test_plan_file_merge(self):
 
2440
        work_a = self.make_branch_and_tree('wta')
 
2441
        self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
 
2442
        work_a.add('file', 'file-id')
 
2443
        base_id = work_a.commit('base version')
 
2444
        tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
 
2445
        preview = TransformPreview(work_a)
 
2446
        self.addCleanup(preview.finalize)
 
2447
        trans_id = preview.trans_id_file_id('file-id')
 
2448
        preview.delete_contents(trans_id)
 
2449
        preview.create_file('b\nc\nd\ne\n', trans_id)
 
2450
        self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
 
2451
        tree_a = preview.get_preview_tree()
 
2452
        tree_a.set_parent_ids([base_id])
 
2453
        self.assertEqual([
 
2454
            ('killed-a', 'a\n'),
 
2455
            ('killed-b', 'b\n'),
 
2456
            ('unchanged', 'c\n'),
 
2457
            ('unchanged', 'd\n'),
 
2458
            ('new-a', 'e\n'),
 
2459
            ('new-b', 'f\n'),
 
2460
        ], list(tree_a.plan_file_merge('file-id', tree_b)))
 
2461
 
 
2462
    def test_plan_file_merge_revision_tree(self):
 
2463
        work_a = self.make_branch_and_tree('wta')
 
2464
        self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
 
2465
        work_a.add('file', 'file-id')
 
2466
        base_id = work_a.commit('base version')
 
2467
        tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
 
2468
        preview = TransformPreview(work_a.basis_tree())
 
2469
        self.addCleanup(preview.finalize)
 
2470
        trans_id = preview.trans_id_file_id('file-id')
 
2471
        preview.delete_contents(trans_id)
 
2472
        preview.create_file('b\nc\nd\ne\n', trans_id)
 
2473
        self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
 
2474
        tree_a = preview.get_preview_tree()
 
2475
        tree_a.set_parent_ids([base_id])
 
2476
        self.assertEqual([
 
2477
            ('killed-a', 'a\n'),
 
2478
            ('killed-b', 'b\n'),
 
2479
            ('unchanged', 'c\n'),
 
2480
            ('unchanged', 'd\n'),
 
2481
            ('new-a', 'e\n'),
 
2482
            ('new-b', 'f\n'),
 
2483
        ], list(tree_a.plan_file_merge('file-id', tree_b)))
 
2484
 
 
2485
    def test_walkdirs(self):
 
2486
        preview = self.get_empty_preview()
 
2487
        preview.version_file('tree-root', preview.root)
 
2488
        preview_tree = preview.get_preview_tree()
 
2489
        file_trans_id = preview.new_file('a', preview.root, 'contents',
 
2490
                                         'a-id')
 
2491
        expected = [(('', 'tree-root'),
 
2492
                    [('a', 'a', 'file', None, 'a-id', 'file')])]
 
2493
        self.assertEqual(expected, list(preview_tree.walkdirs()))
 
2494
 
 
2495
    def test_extras(self):
 
2496
        work_tree = self.make_branch_and_tree('tree')
 
2497
        self.build_tree(['tree/removed-file', 'tree/existing-file',
 
2498
                         'tree/not-removed-file'])
 
2499
        work_tree.add(['removed-file', 'not-removed-file'])
 
2500
        preview = TransformPreview(work_tree)
 
2501
        self.addCleanup(preview.finalize)
 
2502
        preview.new_file('new-file', preview.root, 'contents')
 
2503
        preview.new_file('new-versioned-file', preview.root, 'contents',
 
2504
                         'new-versioned-id')
 
2505
        tree = preview.get_preview_tree()
 
2506
        preview.unversion_file(preview.trans_id_tree_path('removed-file'))
 
2507
        self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
 
2508
                         set(tree.extras()))
 
2509
 
 
2510
    def test_merge_into_preview(self):
 
2511
        work_tree = self.make_branch_and_tree('tree')
 
2512
        self.build_tree_contents([('tree/file','b\n')])
 
2513
        work_tree.add('file', 'file-id')
 
2514
        work_tree.commit('first commit')
 
2515
        child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
 
2516
        self.build_tree_contents([('child/file','b\nc\n')])
 
2517
        child_tree.commit('child commit')
 
2518
        child_tree.lock_write()
 
2519
        self.addCleanup(child_tree.unlock)
 
2520
        work_tree.lock_write()
 
2521
        self.addCleanup(work_tree.unlock)
 
2522
        preview = TransformPreview(work_tree)
 
2523
        self.addCleanup(preview.finalize)
 
2524
        preview_tree = preview.get_preview_tree()
 
2525
        file_trans_id = preview.trans_id_file_id('file-id')
 
2526
        preview.delete_contents(file_trans_id)
 
2527
        preview.create_file('a\nb\n', file_trans_id)
 
2528
        pb = progress.DummyProgress()
 
2529
        merger = Merger.from_revision_ids(pb, preview_tree,
 
2530
                                          child_tree.branch.last_revision(),
 
2531
                                          other_branch=child_tree.branch,
 
2532
                                          tree_branch=work_tree.branch)
 
2533
        merger.merge_type = Merge3Merger
 
2534
        tt = merger.make_merger().make_preview_transform()
 
2535
        self.addCleanup(tt.finalize)
 
2536
        final_tree = tt.get_preview_tree()
 
2537
        self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
 
2538
 
 
2539
    def test_merge_preview_into_workingtree(self):
 
2540
        tree = self.make_branch_and_tree('tree')
 
2541
        tt = TransformPreview(tree)
 
2542
        self.addCleanup(tt.finalize)
 
2543
        tt.new_file('name', tt.root, 'content', 'file-id')
 
2544
        tree2 = self.make_branch_and_tree('tree2')
 
2545
        pb = progress.DummyProgress()
 
2546
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
 
2547
                                         pb, tree.basis_tree())
 
2548
        merger.merge_type = Merge3Merger
 
2549
        merger.do_merge()
 
2550
 
 
2551
    def test_merge_preview_into_workingtree_handles_conflicts(self):
 
2552
        tree = self.make_branch_and_tree('tree')
 
2553
        self.build_tree_contents([('tree/foo', 'bar')])
 
2554
        tree.add('foo', 'foo-id')
 
2555
        tree.commit('foo')
 
2556
        tt = TransformPreview(tree)
 
2557
        self.addCleanup(tt.finalize)
 
2558
        trans_id = tt.trans_id_file_id('foo-id')
 
2559
        tt.delete_contents(trans_id)
 
2560
        tt.create_file('baz', trans_id)
 
2561
        tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
 
2562
        self.build_tree_contents([('tree2/foo', 'qux')])
 
2563
        pb = progress.DummyProgress()
 
2564
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
 
2565
                                         pb, tree.basis_tree())
 
2566
        merger.merge_type = Merge3Merger
 
2567
        merger.do_merge()
 
2568
 
 
2569
    def test_is_executable(self):
 
2570
        tree = self.make_branch_and_tree('tree')
 
2571
        preview = TransformPreview(tree)
 
2572
        self.addCleanup(preview.finalize)
 
2573
        preview.new_file('foo', preview.root, 'bar', 'baz-id')
 
2574
        preview_tree = preview.get_preview_tree()
 
2575
        self.assertEqual(False, preview_tree.is_executable('baz-id',
 
2576
                                                           'tree/foo'))
 
2577
        self.assertEqual(False, preview_tree.is_executable('baz-id'))
 
2578
 
 
2579
 
 
2580
class TestSerializeTransform(tests.TestCaseWithTransport):
 
2581
 
 
2582
    def get_two_previews(self, tree):
 
2583
        tt = TransformPreview(tree)
 
2584
        self.addCleanup(tt.finalize)
 
2585
        tt2 = TransformPreview(tree)
 
2586
        self.addCleanup(tt2.finalize)
 
2587
        return tt, tt2
 
2588
 
 
2589
    @staticmethod
 
2590
    def reserialize(tt, tt2):
 
2591
        serializer = pack.ContainerSerialiser()
 
2592
        parser = pack.ContainerPushParser()
 
2593
        parser.accept_bytes(serializer.begin())
 
2594
        for bytes in tt.serialize(serializer):
 
2595
            parser.accept_bytes(bytes)
 
2596
        parser.accept_bytes(serializer.end())
 
2597
        tt2.deserialize(iter(parser.read_pending_records()))
 
2598
 
 
2599
    def test_roundtrip_creation(self):
 
2600
        tree = self.make_branch_and_tree('.')
 
2601
        tt, tt2 = self.get_two_previews(tree)
 
2602
        tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
 
2603
        tt.new_directory('qux', tt.root, 'quxx')
 
2604
        self.reserialize(tt, tt2)
 
2605
        self.assertEqual(3, tt2._id_number)
 
2606
        self.assertEqual({'new-1': u'foo\u1234',
 
2607
                          'new-2': 'qux'}, tt2._new_name)
 
2608
        self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt2._new_id)
 
2609
        self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt2._new_parent)
 
2610
        self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt2._r_new_id)
 
2611
        self.assertEqual({'new-1': True}, tt2._new_executability)
 
2612
        self.assertEqual({'new-1': 'file',
 
2613
                          'new-2': 'directory'}, tt2._new_contents)
 
2614
        foo_limbo = open(tt2._limbo_name('new-1'), 'rb')
 
2615
        try:
 
2616
            foo_content = foo_limbo.read()
 
2617
        finally:
 
2618
            foo_limbo.close()
 
2619
        self.assertEqual('bar', foo_content)
 
2620
 
 
2621
    def test_symlink_creation(self):
 
2622
        self.requireFeature(tests.SymlinkFeature)
 
2623
        tree = self.make_branch_and_tree('.')
 
2624
        tt, tt2 = self.get_two_previews(tree)
 
2625
        tt.new_symlink('foo', tt.root, 'bar')
 
2626
        self.reserialize(tt, tt2)
 
2627
        foo_content = os.readlink(tt2._limbo_name('new-1'))
 
2628
        self.assertEqual('bar', foo_content)
 
2629
 
 
2630
    def test_roundtrip_destruction(self):
 
2631
        tree = self.make_branch_and_tree('.')
 
2632
        self.build_tree([u'foo\u1234', 'bar'])
 
2633
        tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
 
2634
        tt, tt2 = self.get_two_previews(tree)
 
2635
        foo_trans_id = tt.trans_id_tree_file_id('foo-id')
 
2636
        tt.unversion_file(foo_trans_id)
 
2637
        bar_trans_id = tt.trans_id_tree_file_id('bar-id')
 
2638
        tt.delete_contents(bar_trans_id)
 
2639
        self.reserialize(tt, tt2)
 
2640
        self.assertEqual({u'foo\u1234': foo_trans_id,
 
2641
                          'bar': bar_trans_id,
 
2642
                          '': tt.root}, tt2._tree_path_ids)
 
2643
        self.assertEqual({foo_trans_id: u'foo\u1234',
 
2644
                          bar_trans_id: 'bar',
 
2645
                          tt.root: ''}, tt2._tree_id_paths)
 
2646
        self.assertEqual(set([foo_trans_id]), tt2._removed_id)
 
2647
        self.assertEqual(set([bar_trans_id]), tt2._removed_contents)
 
2648
 
 
2649
    def test_roundtrip_missing(self):
 
2650
        tree = self.make_branch_and_tree('.')
 
2651
        tt, tt2 = self.get_two_previews(tree)
 
2652
        boo_trans_id = tt.trans_id_file_id('boo')
 
2653
        self.reserialize(tt, tt2)
 
2654
        self.assertEqual({'boo': boo_trans_id}, tt2._non_present_ids)
 
2655
 
 
2656
    def test_roundtrip_modification(self):
 
2657
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
2658
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
2659
        tree = self.make_branch_and_tree('tree')
 
2660
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
2661
        tree.add('file', 'file-id')
 
2662
        tt, tt2 = self.get_two_previews(tree)
 
2663
        trans_id = tt.trans_id_file_id('file-id')
 
2664
        tt.delete_contents(trans_id)
 
2665
        tt.create_file(LINES_TWO, trans_id)
 
2666
        self.reserialize(tt, tt2)
 
2667
        self.assertFileEqual(LINES_TWO, tt2._limbo_name(trans_id))
 
2668
 
 
2669
    def test_roundtrip_kind_change(self):
 
2670
        LINES_ONE = 'a\nb\nc\nd\n'
 
2671
        tree = self.make_branch_and_tree('tree')
 
2672
        self.build_tree(['tree/foo/'])
 
2673
        tree.add('foo', 'foo-id')
 
2674
        tt, tt2 = self.get_two_previews(tree)
 
2675
        trans_id = tt.trans_id_file_id('foo-id')
 
2676
        tt.delete_contents(trans_id)
 
2677
        tt.create_file(LINES_ONE, trans_id)
 
2678
        self.reserialize(tt, tt2)
 
2679
        self.assertFileEqual(LINES_ONE, tt2._limbo_name(trans_id))
 
2680
 
 
2681
    def test_roundtrip_add_contents(self):
 
2682
        LINES_ONE = 'a\nb\nc\nd\n'
 
2683
        tree = self.make_branch_and_tree('tree')
 
2684
        self.build_tree(['tree/foo'])
 
2685
        tree.add('foo')
 
2686
        os.unlink('tree/foo')
 
2687
        tt, tt2 = self.get_two_previews(tree)
 
2688
        trans_id = tt.trans_id_tree_path('foo')
 
2689
        tt.create_file(LINES_ONE, trans_id)
 
2690
        self.reserialize(tt, tt2)
 
2691
        self.assertFileEqual(LINES_ONE, tt2._limbo_name(trans_id))
 
2692
 
 
2693
    def test_get_parents_lines(self):
 
2694
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
2695
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
2696
        tree = self.make_branch_and_tree('tree')
 
2697
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
2698
        tree.add('file', 'file-id')
 
2699
        tt, tt2 = self.get_two_previews(tree)
 
2700
        trans_id = tt.trans_id_tree_path('file')
 
2701
        self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
 
2702
            tt._get_parents_lines(trans_id))
 
2703
 
 
2704
    def test_get_parents_texts(self):
 
2705
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
2706
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
2707
        tree = self.make_branch_and_tree('tree')
 
2708
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
2709
        tree.add('file', 'file-id')
 
2710
        tt, tt2 = self.get_two_previews(tree)
 
2711
        trans_id = tt.trans_id_tree_path('file')
 
2712
        self.assertEqual((LINES_ONE,),
 
2713
            tt._get_parents_texts(trans_id))