/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Aaron Bentley
  • Date: 2008-10-26 11:52:46 UTC
  • mto: (0.14.30 prepare-shelf)
  • mto: This revision was merged to the branch mainline in revision 3820.
  • Revision ID: aaron@aaronbentley.com-20081026115246-cbzk6t33j20zls8a
Add unicode symlink targets to tests

Show diffs side-by-side

added added

removed removed

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