~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Martin Pool
  • Date: 2010-01-12 02:00:23 UTC
  • mto: This revision was merged to the branch mainline in revision 4949.
  • Revision ID: mbp@sourcefrog.net-20100112020023-ib3ii1wcpvljmprk
Update bug handling doc to deprecate fixcommitted and to explain other states better

Show diffs side-by-side

added added

removed removed

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