~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

Add a NEWS entry and prepare submission.

Show diffs side-by-side

added added

removed removed

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