/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: Martin Pool
  • Date: 2009-12-09 05:47:32 UTC
  • mfrom: (4879 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4880.
  • Revision ID: mbp@sourcefrog.net-20091209054732-7414e9uma23mfv6x
trivial merge of trunk

Show diffs side-by-side

added added

removed removed

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