~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-10-15 07:36:59 UTC
  • mfrom: (5462.5.9 split-NEWS)
  • Revision ID: pqm@pqm.ubuntu.com-20101015073659-hes51hpjncxrezuq
(spiv) Split NEWS into per-series release notes,
 now found in doc/en/release-notes (Andrew Bennetts)

Show diffs side-by-side

added added

removed removed

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