/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: INADA Naoki
  • Date: 2010-02-22 08:28:41 UTC
  • mto: (4634.141.1 2.0-integration)
  • mto: This revision was merged to the branch mainline in revision 5075.
  • Revision ID: songofacandy@gmail.com-20100222082841-mqbzd2allwtzpgok
Use O_NOINHERIT flag in AtomicFile.

Show diffs side-by-side

added added

removed removed

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