/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-11-26 01:42:06 UTC
  • mfrom: (4827 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4828.
  • Revision ID: mbp@sourcefrog.net-20091126014206-qvf8jfpwpro558r4
merge news

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
import stat
 
19
from StringIO import StringIO
 
20
import sys
18
21
 
 
22
from bzrlib import (
 
23
    bencode,
 
24
    errors,
 
25
    generate_ids,
 
26
    osutils,
 
27
    progress,
 
28
    revision as _mod_revision,
 
29
    symbol_versioning,
 
30
    tests,
 
31
    urlutils,
 
32
    )
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 test_case_insensitive_build_tree_inventory(self):
 
1872
        if (tests.CaseInsensitiveFilesystemFeature.available()
 
1873
            or tests.CaseInsCasePresFilenameFeature.available()):
 
1874
            raise tests.UnavailableFeature('Fully case sensitive filesystem')
 
1875
        source = self.make_branch_and_tree('source')
 
1876
        self.build_tree(['source/file', 'source/FILE'])
 
1877
        source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
 
1878
        source.commit('added files')
 
1879
        # Don't try this at home, kids!
 
1880
        # Force the tree to report that it is case insensitive
 
1881
        target = self.make_branch_and_tree('target')
 
1882
        target.case_sensitive = False
 
1883
        build_tree(source.basis_tree(), target, source, delta_from_tree=True)
 
1884
        self.assertEqual('file.moved', target.id2path('lower-id'))
 
1885
        self.assertEqual('FILE', target.id2path('upper-id'))
 
1886
 
 
1887
 
 
1888
class TestCommitTransform(tests.TestCaseWithTransport):
 
1889
 
 
1890
    def get_branch(self):
 
1891
        tree = self.make_branch_and_tree('tree')
 
1892
        tree.lock_write()
 
1893
        self.addCleanup(tree.unlock)
 
1894
        tree.commit('empty commit')
 
1895
        return tree.branch
 
1896
 
 
1897
    def get_branch_and_transform(self):
 
1898
        branch = self.get_branch()
 
1899
        tt = TransformPreview(branch.basis_tree())
 
1900
        self.addCleanup(tt.finalize)
 
1901
        return branch, tt
 
1902
 
 
1903
    def test_commit_wrong_basis(self):
 
1904
        branch = self.get_branch()
 
1905
        basis = branch.repository.revision_tree(
 
1906
            _mod_revision.NULL_REVISION)
 
1907
        tt = TransformPreview(basis)
 
1908
        self.addCleanup(tt.finalize)
 
1909
        e = self.assertRaises(ValueError, tt.commit, branch, '')
 
1910
        self.assertEqual('TreeTransform not based on branch basis: null:',
 
1911
                         str(e))
 
1912
 
 
1913
    def test_empy_commit(self):
 
1914
        branch, tt = self.get_branch_and_transform()
 
1915
        rev = tt.commit(branch, 'my message')
 
1916
        self.assertEqual(2, branch.revno())
 
1917
        repo = branch.repository
 
1918
        self.assertEqual('my message', repo.get_revision(rev).message)
 
1919
 
 
1920
    def test_merge_parents(self):
 
1921
        branch, tt = self.get_branch_and_transform()
 
1922
        rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
 
1923
        self.assertEqual(['rev1b', 'rev1c'],
 
1924
                         branch.basis_tree().get_parent_ids()[1:])
 
1925
 
 
1926
    def test_first_commit(self):
 
1927
        branch = self.make_branch('branch')
 
1928
        branch.lock_write()
 
1929
        self.addCleanup(branch.unlock)
 
1930
        tt = TransformPreview(branch.basis_tree())
 
1931
        self.addCleanup(tt.finalize)
 
1932
        tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
 
1933
        rev = tt.commit(branch, 'my message')
 
1934
        self.assertEqual([], branch.basis_tree().get_parent_ids())
 
1935
        self.assertNotEqual(_mod_revision.NULL_REVISION,
 
1936
                            branch.last_revision())
 
1937
 
 
1938
    def test_first_commit_with_merge_parents(self):
 
1939
        branch = self.make_branch('branch')
 
1940
        branch.lock_write()
 
1941
        self.addCleanup(branch.unlock)
 
1942
        tt = TransformPreview(branch.basis_tree())
 
1943
        self.addCleanup(tt.finalize)
 
1944
        e = self.assertRaises(ValueError, tt.commit, branch,
 
1945
                          'my message', ['rev1b-id'])
 
1946
        self.assertEqual('Cannot supply merge parents for first commit.',
 
1947
                         str(e))
 
1948
        self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
 
1949
 
 
1950
    def test_add_files(self):
 
1951
        branch, tt = self.get_branch_and_transform()
 
1952
        tt.new_file('file', tt.root, 'contents', 'file-id')
 
1953
        trans_id = tt.new_directory('dir', tt.root, 'dir-id')
 
1954
        if SymlinkFeature.available():
 
1955
            tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
 
1956
        rev = tt.commit(branch, 'message')
 
1957
        tree = branch.basis_tree()
 
1958
        self.assertEqual('file', tree.id2path('file-id'))
 
1959
        self.assertEqual('contents', tree.get_file_text('file-id'))
 
1960
        self.assertEqual('dir', tree.id2path('dir-id'))
 
1961
        if SymlinkFeature.available():
 
1962
            self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
 
1963
            self.assertEqual('target', tree.get_symlink_target('symlink-id'))
 
1964
 
 
1965
    def test_add_unversioned(self):
 
1966
        branch, tt = self.get_branch_and_transform()
 
1967
        tt.new_file('file', tt.root, 'contents')
 
1968
        self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
 
1969
                          'message', strict=True)
 
1970
 
 
1971
    def test_modify_strict(self):
 
1972
        branch, tt = self.get_branch_and_transform()
 
1973
        tt.new_file('file', tt.root, 'contents', 'file-id')
 
1974
        tt.commit(branch, 'message', strict=True)
 
1975
        tt = TransformPreview(branch.basis_tree())
 
1976
        self.addCleanup(tt.finalize)
 
1977
        trans_id = tt.trans_id_file_id('file-id')
 
1978
        tt.delete_contents(trans_id)
 
1979
        tt.create_file('contents', trans_id)
 
1980
        tt.commit(branch, 'message', strict=True)
 
1981
 
 
1982
    def test_commit_malformed(self):
 
1983
        """Committing a malformed transform should raise an exception.
 
1984
 
 
1985
        In this case, we are adding a file without adding its parent.
 
1986
        """
 
1987
        branch, tt = self.get_branch_and_transform()
 
1988
        parent_id = tt.trans_id_file_id('parent-id')
 
1989
        tt.new_file('file', parent_id, 'contents', 'file-id')
 
1990
        self.assertRaises(errors.MalformedTransform, tt.commit, branch,
 
1991
                          'message')
 
1992
 
 
1993
 
706
1994
class MockTransform(object):
707
1995
 
708
1996
    def has_named_child(self, by_parent, parent_id, name):
714
2002
                return True
715
2003
        return False
716
2004
 
 
2005
 
717
2006
class MockEntry(object):
718
2007
    def __init__(self):
719
2008
        object.__init__(self)
720
2009
        self.name = "name"
721
2010
 
 
2011
 
722
2012
class TestGetBackupName(TestCase):
723
2013
    def test_get_backup_name(self):
724
2014
        tt = MockTransform()
732
2022
        self.assertEqual(name, 'name.~1~')
733
2023
        name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
734
2024
        self.assertEqual(name, 'name.~4~')
 
2025
 
 
2026
 
 
2027
class TestFileMover(tests.TestCaseWithTransport):
 
2028
 
 
2029
    def test_file_mover(self):
 
2030
        self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
 
2031
        mover = _FileMover()
 
2032
        mover.rename('a', 'q')
 
2033
        self.failUnlessExists('q')
 
2034
        self.failIfExists('a')
 
2035
        self.failUnlessExists('q/b')
 
2036
        self.failUnlessExists('c')
 
2037
        self.failUnlessExists('c/d')
 
2038
 
 
2039
    def test_pre_delete_rollback(self):
 
2040
        self.build_tree(['a/'])
 
2041
        mover = _FileMover()
 
2042
        mover.pre_delete('a', 'q')
 
2043
        self.failUnlessExists('q')
 
2044
        self.failIfExists('a')
 
2045
        mover.rollback()
 
2046
        self.failIfExists('q')
 
2047
        self.failUnlessExists('a')
 
2048
 
 
2049
    def test_apply_deletions(self):
 
2050
        self.build_tree(['a/', 'b/'])
 
2051
        mover = _FileMover()
 
2052
        mover.pre_delete('a', 'q')
 
2053
        mover.pre_delete('b', 'r')
 
2054
        self.failUnlessExists('q')
 
2055
        self.failUnlessExists('r')
 
2056
        self.failIfExists('a')
 
2057
        self.failIfExists('b')
 
2058
        mover.apply_deletions()
 
2059
        self.failIfExists('q')
 
2060
        self.failIfExists('r')
 
2061
        self.failIfExists('a')
 
2062
        self.failIfExists('b')
 
2063
 
 
2064
    def test_file_mover_rollback(self):
 
2065
        self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
 
2066
        mover = _FileMover()
 
2067
        mover.rename('c/d', 'c/f')
 
2068
        mover.rename('c/e', 'c/d')
 
2069
        try:
 
2070
            mover.rename('a', 'c')
 
2071
        except errors.FileExists, e:
 
2072
            mover.rollback()
 
2073
        self.failUnlessExists('a')
 
2074
        self.failUnlessExists('c/d')
 
2075
 
 
2076
 
 
2077
class Bogus(Exception):
 
2078
    pass
 
2079
 
 
2080
 
 
2081
class TestTransformRollback(tests.TestCaseWithTransport):
 
2082
 
 
2083
    class ExceptionFileMover(_FileMover):
 
2084
 
 
2085
        def __init__(self, bad_source=None, bad_target=None):
 
2086
            _FileMover.__init__(self)
 
2087
            self.bad_source = bad_source
 
2088
            self.bad_target = bad_target
 
2089
 
 
2090
        def rename(self, source, target):
 
2091
            if (self.bad_source is not None and
 
2092
                source.endswith(self.bad_source)):
 
2093
                raise Bogus
 
2094
            elif (self.bad_target is not None and
 
2095
                target.endswith(self.bad_target)):
 
2096
                raise Bogus
 
2097
            else:
 
2098
                _FileMover.rename(self, source, target)
 
2099
 
 
2100
    def test_rollback_rename(self):
 
2101
        tree = self.make_branch_and_tree('.')
 
2102
        self.build_tree(['a/', 'a/b'])
 
2103
        tt = TreeTransform(tree)
 
2104
        self.addCleanup(tt.finalize)
 
2105
        a_id = tt.trans_id_tree_path('a')
 
2106
        tt.adjust_path('c', tt.root, a_id)
 
2107
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
 
2108
        self.assertRaises(Bogus, tt.apply,
 
2109
                          _mover=self.ExceptionFileMover(bad_source='a'))
 
2110
        self.failUnlessExists('a')
 
2111
        self.failUnlessExists('a/b')
 
2112
        tt.apply()
 
2113
        self.failUnlessExists('c')
 
2114
        self.failUnlessExists('c/d')
 
2115
 
 
2116
    def test_rollback_rename_into_place(self):
 
2117
        tree = self.make_branch_and_tree('.')
 
2118
        self.build_tree(['a/', 'a/b'])
 
2119
        tt = TreeTransform(tree)
 
2120
        self.addCleanup(tt.finalize)
 
2121
        a_id = tt.trans_id_tree_path('a')
 
2122
        tt.adjust_path('c', tt.root, a_id)
 
2123
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
 
2124
        self.assertRaises(Bogus, tt.apply,
 
2125
                          _mover=self.ExceptionFileMover(bad_target='c/d'))
 
2126
        self.failUnlessExists('a')
 
2127
        self.failUnlessExists('a/b')
 
2128
        tt.apply()
 
2129
        self.failUnlessExists('c')
 
2130
        self.failUnlessExists('c/d')
 
2131
 
 
2132
    def test_rollback_deletion(self):
 
2133
        tree = self.make_branch_and_tree('.')
 
2134
        self.build_tree(['a/', 'a/b'])
 
2135
        tt = TreeTransform(tree)
 
2136
        self.addCleanup(tt.finalize)
 
2137
        a_id = tt.trans_id_tree_path('a')
 
2138
        tt.delete_contents(a_id)
 
2139
        tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
 
2140
        self.assertRaises(Bogus, tt.apply,
 
2141
                          _mover=self.ExceptionFileMover(bad_target='d'))
 
2142
        self.failUnlessExists('a')
 
2143
        self.failUnlessExists('a/b')
 
2144
 
 
2145
    def test_resolve_no_parent(self):
 
2146
        wt = self.make_branch_and_tree('.')
 
2147
        tt = TreeTransform(wt)
 
2148
        self.addCleanup(tt.finalize)
 
2149
        parent = tt.trans_id_file_id('parent-id')
 
2150
        tt.new_file('file', parent, 'Contents')
 
2151
        resolve_conflicts(tt)
 
2152
 
 
2153
 
 
2154
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
 
2155
                  ('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
 
2156
                  (False, False))
 
2157
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
 
2158
              ('', ''), ('directory', 'directory'), (False, None))
 
2159
 
 
2160
 
 
2161
class TestTransformPreview(tests.TestCaseWithTransport):
 
2162
 
 
2163
    def create_tree(self):
 
2164
        tree = self.make_branch_and_tree('.')
 
2165
        self.build_tree_contents([('a', 'content 1')])
 
2166
        tree.set_root_id('TREE_ROOT')
 
2167
        tree.add('a', 'a-id')
 
2168
        tree.commit('rev1', rev_id='rev1')
 
2169
        return tree.branch.repository.revision_tree('rev1')
 
2170
 
 
2171
    def get_empty_preview(self):
 
2172
        repository = self.make_repository('repo')
 
2173
        tree = repository.revision_tree(_mod_revision.NULL_REVISION)
 
2174
        preview = TransformPreview(tree)
 
2175
        self.addCleanup(preview.finalize)
 
2176
        return preview
 
2177
 
 
2178
    def test_transform_preview(self):
 
2179
        revision_tree = self.create_tree()
 
2180
        preview = TransformPreview(revision_tree)
 
2181
        self.addCleanup(preview.finalize)
 
2182
 
 
2183
    def test_transform_preview_tree(self):
 
2184
        revision_tree = self.create_tree()
 
2185
        preview = TransformPreview(revision_tree)
 
2186
        self.addCleanup(preview.finalize)
 
2187
        preview.get_preview_tree()
 
2188
 
 
2189
    def test_transform_new_file(self):
 
2190
        revision_tree = self.create_tree()
 
2191
        preview = TransformPreview(revision_tree)
 
2192
        self.addCleanup(preview.finalize)
 
2193
        preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
 
2194
        preview_tree = preview.get_preview_tree()
 
2195
        self.assertEqual(preview_tree.kind('file2-id'), 'file')
 
2196
        self.assertEqual(
 
2197
            preview_tree.get_file('file2-id').read(), 'content B\n')
 
2198
 
 
2199
    def test_diff_preview_tree(self):
 
2200
        revision_tree = self.create_tree()
 
2201
        preview = TransformPreview(revision_tree)
 
2202
        self.addCleanup(preview.finalize)
 
2203
        preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
 
2204
        preview_tree = preview.get_preview_tree()
 
2205
        out = StringIO()
 
2206
        show_diff_trees(revision_tree, preview_tree, out)
 
2207
        lines = out.getvalue().splitlines()
 
2208
        self.assertEqual(lines[0], "=== added file 'file2'")
 
2209
        # 3 lines of diff administrivia
 
2210
        self.assertEqual(lines[4], "+content B")
 
2211
 
 
2212
    def test_transform_conflicts(self):
 
2213
        revision_tree = self.create_tree()
 
2214
        preview = TransformPreview(revision_tree)
 
2215
        self.addCleanup(preview.finalize)
 
2216
        preview.new_file('a', preview.root, 'content 2')
 
2217
        resolve_conflicts(preview)
 
2218
        trans_id = preview.trans_id_file_id('a-id')
 
2219
        self.assertEqual('a.moved', preview.final_name(trans_id))
 
2220
 
 
2221
    def get_tree_and_preview_tree(self):
 
2222
        revision_tree = self.create_tree()
 
2223
        preview = TransformPreview(revision_tree)
 
2224
        self.addCleanup(preview.finalize)
 
2225
        a_trans_id = preview.trans_id_file_id('a-id')
 
2226
        preview.delete_contents(a_trans_id)
 
2227
        preview.create_file('b content', a_trans_id)
 
2228
        preview_tree = preview.get_preview_tree()
 
2229
        return revision_tree, preview_tree
 
2230
 
 
2231
    def test_iter_changes(self):
 
2232
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2233
        root = revision_tree.inventory.root.file_id
 
2234
        self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
 
2235
                          (root, root), ('a', 'a'), ('file', 'file'),
 
2236
                          (False, False))],
 
2237
                          list(preview_tree.iter_changes(revision_tree)))
 
2238
 
 
2239
    def test_include_unchanged_succeeds(self):
 
2240
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2241
        changes = preview_tree.iter_changes(revision_tree,
 
2242
                                            include_unchanged=True)
 
2243
        root = revision_tree.inventory.root.file_id
 
2244
 
 
2245
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
 
2246
 
 
2247
    def test_specific_files(self):
 
2248
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2249
        changes = preview_tree.iter_changes(revision_tree,
 
2250
                                            specific_files=[''])
 
2251
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
 
2252
 
 
2253
    def test_want_unversioned(self):
 
2254
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2255
        changes = preview_tree.iter_changes(revision_tree,
 
2256
                                            want_unversioned=True)
 
2257
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
 
2258
 
 
2259
    def test_ignore_extra_trees_no_specific_files(self):
 
2260
        # extra_trees is harmless without specific_files, so we'll silently
 
2261
        # accept it, even though we won't use it.
 
2262
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2263
        preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
 
2264
 
 
2265
    def test_ignore_require_versioned_no_specific_files(self):
 
2266
        # require_versioned is meaningless without specific_files.
 
2267
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2268
        preview_tree.iter_changes(revision_tree, require_versioned=False)
 
2269
 
 
2270
    def test_ignore_pb(self):
 
2271
        # pb could be supported, but TT.iter_changes doesn't support it.
 
2272
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2273
        preview_tree.iter_changes(revision_tree, pb=progress.DummyProgress())
 
2274
 
 
2275
    def test_kind(self):
 
2276
        revision_tree = self.create_tree()
 
2277
        preview = TransformPreview(revision_tree)
 
2278
        self.addCleanup(preview.finalize)
 
2279
        preview.new_file('file', preview.root, 'contents', 'file-id')
 
2280
        preview.new_directory('directory', preview.root, 'dir-id')
 
2281
        preview_tree = preview.get_preview_tree()
 
2282
        self.assertEqual('file', preview_tree.kind('file-id'))
 
2283
        self.assertEqual('directory', preview_tree.kind('dir-id'))
 
2284
 
 
2285
    def test_get_file_mtime(self):
 
2286
        preview = self.get_empty_preview()
 
2287
        file_trans_id = preview.new_file('file', preview.root, 'contents',
 
2288
                                         'file-id')
 
2289
        limbo_path = preview._limbo_name(file_trans_id)
 
2290
        preview_tree = preview.get_preview_tree()
 
2291
        self.assertEqual(os.stat(limbo_path).st_mtime,
 
2292
                         preview_tree.get_file_mtime('file-id'))
 
2293
 
 
2294
    def test_get_file_mtime_renamed(self):
 
2295
        work_tree = self.make_branch_and_tree('tree')
 
2296
        self.build_tree(['tree/file'])
 
2297
        work_tree.add('file', 'file-id')
 
2298
        preview = TransformPreview(work_tree)
 
2299
        self.addCleanup(preview.finalize)
 
2300
        file_trans_id = preview.trans_id_tree_file_id('file-id')
 
2301
        preview.adjust_path('renamed', preview.root, file_trans_id)
 
2302
        preview_tree = preview.get_preview_tree()
 
2303
        preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
 
2304
        work_mtime = work_tree.get_file_mtime('file-id', 'file')
 
2305
 
 
2306
    def test_get_file(self):
 
2307
        preview = self.get_empty_preview()
 
2308
        preview.new_file('file', preview.root, 'contents', 'file-id')
 
2309
        preview_tree = preview.get_preview_tree()
 
2310
        tree_file = preview_tree.get_file('file-id')
 
2311
        try:
 
2312
            self.assertEqual('contents', tree_file.read())
 
2313
        finally:
 
2314
            tree_file.close()
 
2315
 
 
2316
    def test_get_symlink_target(self):
 
2317
        self.requireFeature(SymlinkFeature)
 
2318
        preview = self.get_empty_preview()
 
2319
        preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
 
2320
        preview_tree = preview.get_preview_tree()
 
2321
        self.assertEqual('target',
 
2322
                         preview_tree.get_symlink_target('symlink-id'))
 
2323
 
 
2324
    def test_all_file_ids(self):
 
2325
        tree = self.make_branch_and_tree('tree')
 
2326
        self.build_tree(['tree/a', 'tree/b', 'tree/c'])
 
2327
        tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
 
2328
        preview = TransformPreview(tree)
 
2329
        self.addCleanup(preview.finalize)
 
2330
        preview.unversion_file(preview.trans_id_file_id('b-id'))
 
2331
        c_trans_id = preview.trans_id_file_id('c-id')
 
2332
        preview.unversion_file(c_trans_id)
 
2333
        preview.version_file('c-id', c_trans_id)
 
2334
        preview_tree = preview.get_preview_tree()
 
2335
        self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
 
2336
                         preview_tree.all_file_ids())
 
2337
 
 
2338
    def test_path2id_deleted_unchanged(self):
 
2339
        tree = self.make_branch_and_tree('tree')
 
2340
        self.build_tree(['tree/unchanged', 'tree/deleted'])
 
2341
        tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
 
2342
        preview = TransformPreview(tree)
 
2343
        self.addCleanup(preview.finalize)
 
2344
        preview.unversion_file(preview.trans_id_file_id('deleted-id'))
 
2345
        preview_tree = preview.get_preview_tree()
 
2346
        self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
 
2347
        self.assertIs(None, preview_tree.path2id('deleted'))
 
2348
 
 
2349
    def test_path2id_created(self):
 
2350
        tree = self.make_branch_and_tree('tree')
 
2351
        self.build_tree(['tree/unchanged'])
 
2352
        tree.add(['unchanged'], ['unchanged-id'])
 
2353
        preview = TransformPreview(tree)
 
2354
        self.addCleanup(preview.finalize)
 
2355
        preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
 
2356
            'contents', 'new-id')
 
2357
        preview_tree = preview.get_preview_tree()
 
2358
        self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
 
2359
 
 
2360
    def test_path2id_moved(self):
 
2361
        tree = self.make_branch_and_tree('tree')
 
2362
        self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
 
2363
        tree.add(['old_parent', 'old_parent/child'],
 
2364
                 ['old_parent-id', 'child-id'])
 
2365
        preview = TransformPreview(tree)
 
2366
        self.addCleanup(preview.finalize)
 
2367
        new_parent = preview.new_directory('new_parent', preview.root,
 
2368
                                           'new_parent-id')
 
2369
        preview.adjust_path('child', new_parent,
 
2370
                            preview.trans_id_file_id('child-id'))
 
2371
        preview_tree = preview.get_preview_tree()
 
2372
        self.assertIs(None, preview_tree.path2id('old_parent/child'))
 
2373
        self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
 
2374
 
 
2375
    def test_path2id_renamed_parent(self):
 
2376
        tree = self.make_branch_and_tree('tree')
 
2377
        self.build_tree(['tree/old_name/', 'tree/old_name/child'])
 
2378
        tree.add(['old_name', 'old_name/child'],
 
2379
                 ['parent-id', 'child-id'])
 
2380
        preview = TransformPreview(tree)
 
2381
        self.addCleanup(preview.finalize)
 
2382
        preview.adjust_path('new_name', preview.root,
 
2383
                            preview.trans_id_file_id('parent-id'))
 
2384
        preview_tree = preview.get_preview_tree()
 
2385
        self.assertIs(None, preview_tree.path2id('old_name/child'))
 
2386
        self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
 
2387
 
 
2388
    def assertMatchingIterEntries(self, tt, specific_file_ids=None):
 
2389
        preview_tree = tt.get_preview_tree()
 
2390
        preview_result = list(preview_tree.iter_entries_by_dir(
 
2391
                              specific_file_ids))
 
2392
        tree = tt._tree
 
2393
        tt.apply()
 
2394
        actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
 
2395
        self.assertEqual(actual_result, preview_result)
 
2396
 
 
2397
    def test_iter_entries_by_dir_new(self):
 
2398
        tree = self.make_branch_and_tree('tree')
 
2399
        tt = TreeTransform(tree)
 
2400
        tt.new_file('new', tt.root, 'contents', 'new-id')
 
2401
        self.assertMatchingIterEntries(tt)
 
2402
 
 
2403
    def test_iter_entries_by_dir_deleted(self):
 
2404
        tree = self.make_branch_and_tree('tree')
 
2405
        self.build_tree(['tree/deleted'])
 
2406
        tree.add('deleted', 'deleted-id')
 
2407
        tt = TreeTransform(tree)
 
2408
        tt.delete_contents(tt.trans_id_file_id('deleted-id'))
 
2409
        self.assertMatchingIterEntries(tt)
 
2410
 
 
2411
    def test_iter_entries_by_dir_unversioned(self):
 
2412
        tree = self.make_branch_and_tree('tree')
 
2413
        self.build_tree(['tree/removed'])
 
2414
        tree.add('removed', 'removed-id')
 
2415
        tt = TreeTransform(tree)
 
2416
        tt.unversion_file(tt.trans_id_file_id('removed-id'))
 
2417
        self.assertMatchingIterEntries(tt)
 
2418
 
 
2419
    def test_iter_entries_by_dir_moved(self):
 
2420
        tree = self.make_branch_and_tree('tree')
 
2421
        self.build_tree(['tree/moved', 'tree/new_parent/'])
 
2422
        tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
 
2423
        tt = TreeTransform(tree)
 
2424
        tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
 
2425
                       tt.trans_id_file_id('moved-id'))
 
2426
        self.assertMatchingIterEntries(tt)
 
2427
 
 
2428
    def test_iter_entries_by_dir_specific_file_ids(self):
 
2429
        tree = self.make_branch_and_tree('tree')
 
2430
        tree.set_root_id('tree-root-id')
 
2431
        self.build_tree(['tree/parent/', 'tree/parent/child'])
 
2432
        tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
 
2433
        tt = TreeTransform(tree)
 
2434
        self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
 
2435
 
 
2436
    def test_symlink_content_summary(self):
 
2437
        self.requireFeature(SymlinkFeature)
 
2438
        preview = self.get_empty_preview()
 
2439
        preview.new_symlink('path', preview.root, 'target', 'path-id')
 
2440
        summary = preview.get_preview_tree().path_content_summary('path')
 
2441
        self.assertEqual(('symlink', None, None, 'target'), summary)
 
2442
 
 
2443
    def test_missing_content_summary(self):
 
2444
        preview = self.get_empty_preview()
 
2445
        summary = preview.get_preview_tree().path_content_summary('path')
 
2446
        self.assertEqual(('missing', None, None, None), summary)
 
2447
 
 
2448
    def test_deleted_content_summary(self):
 
2449
        tree = self.make_branch_and_tree('tree')
 
2450
        self.build_tree(['tree/path/'])
 
2451
        tree.add('path')
 
2452
        preview = TransformPreview(tree)
 
2453
        self.addCleanup(preview.finalize)
 
2454
        preview.delete_contents(preview.trans_id_tree_path('path'))
 
2455
        summary = preview.get_preview_tree().path_content_summary('path')
 
2456
        self.assertEqual(('missing', None, None, None), summary)
 
2457
 
 
2458
    def test_file_content_summary_executable(self):
 
2459
        preview = self.get_empty_preview()
 
2460
        path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
 
2461
        preview.set_executability(True, path_id)
 
2462
        summary = preview.get_preview_tree().path_content_summary('path')
 
2463
        self.assertEqual(4, len(summary))
 
2464
        self.assertEqual('file', summary[0])
 
2465
        # size must be known
 
2466
        self.assertEqual(len('contents'), summary[1])
 
2467
        # executable
 
2468
        self.assertEqual(True, summary[2])
 
2469
        # will not have hash (not cheap to determine)
 
2470
        self.assertIs(None, summary[3])
 
2471
 
 
2472
    def test_change_executability(self):
 
2473
        tree = self.make_branch_and_tree('tree')
 
2474
        self.build_tree(['tree/path'])
 
2475
        tree.add('path')
 
2476
        preview = TransformPreview(tree)
 
2477
        self.addCleanup(preview.finalize)
 
2478
        path_id = preview.trans_id_tree_path('path')
 
2479
        preview.set_executability(True, path_id)
 
2480
        summary = preview.get_preview_tree().path_content_summary('path')
 
2481
        self.assertEqual(True, summary[2])
 
2482
 
 
2483
    def test_file_content_summary_non_exec(self):
 
2484
        preview = self.get_empty_preview()
 
2485
        preview.new_file('path', preview.root, 'contents', 'path-id')
 
2486
        summary = preview.get_preview_tree().path_content_summary('path')
 
2487
        self.assertEqual(4, len(summary))
 
2488
        self.assertEqual('file', summary[0])
 
2489
        # size must be known
 
2490
        self.assertEqual(len('contents'), summary[1])
 
2491
        # not executable
 
2492
        self.assertEqual(False, summary[2])
 
2493
        # will not have hash (not cheap to determine)
 
2494
        self.assertIs(None, summary[3])
 
2495
 
 
2496
    def test_dir_content_summary(self):
 
2497
        preview = self.get_empty_preview()
 
2498
        preview.new_directory('path', preview.root, 'path-id')
 
2499
        summary = preview.get_preview_tree().path_content_summary('path')
 
2500
        self.assertEqual(('directory', None, None, None), summary)
 
2501
 
 
2502
    def test_tree_content_summary(self):
 
2503
        preview = self.get_empty_preview()
 
2504
        path = preview.new_directory('path', preview.root, 'path-id')
 
2505
        preview.set_tree_reference('rev-1', path)
 
2506
        summary = preview.get_preview_tree().path_content_summary('path')
 
2507
        self.assertEqual(4, len(summary))
 
2508
        self.assertEqual('tree-reference', summary[0])
 
2509
 
 
2510
    def test_annotate(self):
 
2511
        tree = self.make_branch_and_tree('tree')
 
2512
        self.build_tree_contents([('tree/file', 'a\n')])
 
2513
        tree.add('file', 'file-id')
 
2514
        tree.commit('a', rev_id='one')
 
2515
        self.build_tree_contents([('tree/file', 'a\nb\n')])
 
2516
        preview = TransformPreview(tree)
 
2517
        self.addCleanup(preview.finalize)
 
2518
        file_trans_id = preview.trans_id_file_id('file-id')
 
2519
        preview.delete_contents(file_trans_id)
 
2520
        preview.create_file('a\nb\nc\n', file_trans_id)
 
2521
        preview_tree = preview.get_preview_tree()
 
2522
        expected = [
 
2523
            ('one', 'a\n'),
 
2524
            ('me:', 'b\n'),
 
2525
            ('me:', 'c\n'),
 
2526
        ]
 
2527
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2528
        self.assertEqual(expected, annotation)
 
2529
 
 
2530
    def test_annotate_missing(self):
 
2531
        preview = self.get_empty_preview()
 
2532
        preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
 
2533
        preview_tree = preview.get_preview_tree()
 
2534
        expected = [
 
2535
            ('me:', 'a\n'),
 
2536
            ('me:', 'b\n'),
 
2537
            ('me:', 'c\n'),
 
2538
         ]
 
2539
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2540
        self.assertEqual(expected, annotation)
 
2541
 
 
2542
    def test_annotate_rename(self):
 
2543
        tree = self.make_branch_and_tree('tree')
 
2544
        self.build_tree_contents([('tree/file', 'a\n')])
 
2545
        tree.add('file', 'file-id')
 
2546
        tree.commit('a', rev_id='one')
 
2547
        preview = TransformPreview(tree)
 
2548
        self.addCleanup(preview.finalize)
 
2549
        file_trans_id = preview.trans_id_file_id('file-id')
 
2550
        preview.adjust_path('newname', preview.root, file_trans_id)
 
2551
        preview_tree = preview.get_preview_tree()
 
2552
        expected = [
 
2553
            ('one', 'a\n'),
 
2554
        ]
 
2555
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2556
        self.assertEqual(expected, annotation)
 
2557
 
 
2558
    def test_annotate_deleted(self):
 
2559
        tree = self.make_branch_and_tree('tree')
 
2560
        self.build_tree_contents([('tree/file', 'a\n')])
 
2561
        tree.add('file', 'file-id')
 
2562
        tree.commit('a', rev_id='one')
 
2563
        self.build_tree_contents([('tree/file', 'a\nb\n')])
 
2564
        preview = TransformPreview(tree)
 
2565
        self.addCleanup(preview.finalize)
 
2566
        file_trans_id = preview.trans_id_file_id('file-id')
 
2567
        preview.delete_contents(file_trans_id)
 
2568
        preview_tree = preview.get_preview_tree()
 
2569
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2570
        self.assertIs(None, annotation)
 
2571
 
 
2572
    def test_stored_kind(self):
 
2573
        preview = self.get_empty_preview()
 
2574
        preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
 
2575
        preview_tree = preview.get_preview_tree()
 
2576
        self.assertEqual('file', preview_tree.stored_kind('file-id'))
 
2577
 
 
2578
    def test_is_executable(self):
 
2579
        preview = self.get_empty_preview()
 
2580
        preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
 
2581
        preview.set_executability(True, preview.trans_id_file_id('file-id'))
 
2582
        preview_tree = preview.get_preview_tree()
 
2583
        self.assertEqual(True, preview_tree.is_executable('file-id'))
 
2584
 
 
2585
    def test_get_set_parent_ids(self):
 
2586
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2587
        self.assertEqual([], preview_tree.get_parent_ids())
 
2588
        preview_tree.set_parent_ids(['rev-1'])
 
2589
        self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
 
2590
 
 
2591
    def test_plan_file_merge(self):
 
2592
        work_a = self.make_branch_and_tree('wta')
 
2593
        self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
 
2594
        work_a.add('file', 'file-id')
 
2595
        base_id = work_a.commit('base version')
 
2596
        tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
 
2597
        preview = TransformPreview(work_a)
 
2598
        self.addCleanup(preview.finalize)
 
2599
        trans_id = preview.trans_id_file_id('file-id')
 
2600
        preview.delete_contents(trans_id)
 
2601
        preview.create_file('b\nc\nd\ne\n', trans_id)
 
2602
        self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
 
2603
        tree_a = preview.get_preview_tree()
 
2604
        tree_a.set_parent_ids([base_id])
 
2605
        self.assertEqual([
 
2606
            ('killed-a', 'a\n'),
 
2607
            ('killed-b', 'b\n'),
 
2608
            ('unchanged', 'c\n'),
 
2609
            ('unchanged', 'd\n'),
 
2610
            ('new-a', 'e\n'),
 
2611
            ('new-b', 'f\n'),
 
2612
        ], list(tree_a.plan_file_merge('file-id', tree_b)))
 
2613
 
 
2614
    def test_plan_file_merge_revision_tree(self):
 
2615
        work_a = self.make_branch_and_tree('wta')
 
2616
        self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
 
2617
        work_a.add('file', 'file-id')
 
2618
        base_id = work_a.commit('base version')
 
2619
        tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
 
2620
        preview = TransformPreview(work_a.basis_tree())
 
2621
        self.addCleanup(preview.finalize)
 
2622
        trans_id = preview.trans_id_file_id('file-id')
 
2623
        preview.delete_contents(trans_id)
 
2624
        preview.create_file('b\nc\nd\ne\n', trans_id)
 
2625
        self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
 
2626
        tree_a = preview.get_preview_tree()
 
2627
        tree_a.set_parent_ids([base_id])
 
2628
        self.assertEqual([
 
2629
            ('killed-a', 'a\n'),
 
2630
            ('killed-b', 'b\n'),
 
2631
            ('unchanged', 'c\n'),
 
2632
            ('unchanged', 'd\n'),
 
2633
            ('new-a', 'e\n'),
 
2634
            ('new-b', 'f\n'),
 
2635
        ], list(tree_a.plan_file_merge('file-id', tree_b)))
 
2636
 
 
2637
    def test_walkdirs(self):
 
2638
        preview = self.get_empty_preview()
 
2639
        root = preview.new_directory('', ROOT_PARENT, 'tree-root')
 
2640
        # FIXME: new_directory should mark root.
 
2641
        preview.adjust_path('', ROOT_PARENT, root)
 
2642
        preview_tree = preview.get_preview_tree()
 
2643
        file_trans_id = preview.new_file('a', preview.root, 'contents',
 
2644
                                         'a-id')
 
2645
        expected = [(('', 'tree-root'),
 
2646
                    [('a', 'a', 'file', None, 'a-id', 'file')])]
 
2647
        self.assertEqual(expected, list(preview_tree.walkdirs()))
 
2648
 
 
2649
    def test_extras(self):
 
2650
        work_tree = self.make_branch_and_tree('tree')
 
2651
        self.build_tree(['tree/removed-file', 'tree/existing-file',
 
2652
                         'tree/not-removed-file'])
 
2653
        work_tree.add(['removed-file', 'not-removed-file'])
 
2654
        preview = TransformPreview(work_tree)
 
2655
        self.addCleanup(preview.finalize)
 
2656
        preview.new_file('new-file', preview.root, 'contents')
 
2657
        preview.new_file('new-versioned-file', preview.root, 'contents',
 
2658
                         'new-versioned-id')
 
2659
        tree = preview.get_preview_tree()
 
2660
        preview.unversion_file(preview.trans_id_tree_path('removed-file'))
 
2661
        self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
 
2662
                         set(tree.extras()))
 
2663
 
 
2664
    def test_merge_into_preview(self):
 
2665
        work_tree = self.make_branch_and_tree('tree')
 
2666
        self.build_tree_contents([('tree/file','b\n')])
 
2667
        work_tree.add('file', 'file-id')
 
2668
        work_tree.commit('first commit')
 
2669
        child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
 
2670
        self.build_tree_contents([('child/file','b\nc\n')])
 
2671
        child_tree.commit('child commit')
 
2672
        child_tree.lock_write()
 
2673
        self.addCleanup(child_tree.unlock)
 
2674
        work_tree.lock_write()
 
2675
        self.addCleanup(work_tree.unlock)
 
2676
        preview = TransformPreview(work_tree)
 
2677
        self.addCleanup(preview.finalize)
 
2678
        file_trans_id = preview.trans_id_file_id('file-id')
 
2679
        preview.delete_contents(file_trans_id)
 
2680
        preview.create_file('a\nb\n', file_trans_id)
 
2681
        pb = progress.DummyProgress()
 
2682
        preview_tree = preview.get_preview_tree()
 
2683
        merger = Merger.from_revision_ids(pb, preview_tree,
 
2684
                                          child_tree.branch.last_revision(),
 
2685
                                          other_branch=child_tree.branch,
 
2686
                                          tree_branch=work_tree.branch)
 
2687
        merger.merge_type = Merge3Merger
 
2688
        tt = merger.make_merger().make_preview_transform()
 
2689
        self.addCleanup(tt.finalize)
 
2690
        final_tree = tt.get_preview_tree()
 
2691
        self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
 
2692
 
 
2693
    def test_merge_preview_into_workingtree(self):
 
2694
        tree = self.make_branch_and_tree('tree')
 
2695
        tree.set_root_id('TREE_ROOT')
 
2696
        tt = TransformPreview(tree)
 
2697
        self.addCleanup(tt.finalize)
 
2698
        tt.new_file('name', tt.root, 'content', 'file-id')
 
2699
        tree2 = self.make_branch_and_tree('tree2')
 
2700
        tree2.set_root_id('TREE_ROOT')
 
2701
        pb = progress.DummyProgress()
 
2702
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
 
2703
                                         pb, tree.basis_tree())
 
2704
        merger.merge_type = Merge3Merger
 
2705
        merger.do_merge()
 
2706
 
 
2707
    def test_merge_preview_into_workingtree_handles_conflicts(self):
 
2708
        tree = self.make_branch_and_tree('tree')
 
2709
        self.build_tree_contents([('tree/foo', 'bar')])
 
2710
        tree.add('foo', 'foo-id')
 
2711
        tree.commit('foo')
 
2712
        tt = TransformPreview(tree)
 
2713
        self.addCleanup(tt.finalize)
 
2714
        trans_id = tt.trans_id_file_id('foo-id')
 
2715
        tt.delete_contents(trans_id)
 
2716
        tt.create_file('baz', trans_id)
 
2717
        tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
 
2718
        self.build_tree_contents([('tree2/foo', 'qux')])
 
2719
        pb = progress.DummyProgress()
 
2720
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
 
2721
                                         pb, tree.basis_tree())
 
2722
        merger.merge_type = Merge3Merger
 
2723
        merger.do_merge()
 
2724
 
 
2725
    def test_is_executable(self):
 
2726
        tree = self.make_branch_and_tree('tree')
 
2727
        preview = TransformPreview(tree)
 
2728
        self.addCleanup(preview.finalize)
 
2729
        preview.new_file('foo', preview.root, 'bar', 'baz-id')
 
2730
        preview_tree = preview.get_preview_tree()
 
2731
        self.assertEqual(False, preview_tree.is_executable('baz-id',
 
2732
                                                           'tree/foo'))
 
2733
        self.assertEqual(False, preview_tree.is_executable('baz-id'))
 
2734
 
 
2735
    def test_commit_preview_tree(self):
 
2736
        tree = self.make_branch_and_tree('tree')
 
2737
        rev_id = tree.commit('rev1')
 
2738
        tree.branch.lock_write()
 
2739
        self.addCleanup(tree.branch.unlock)
 
2740
        tt = TransformPreview(tree)
 
2741
        tt.new_file('file', tt.root, 'contents', 'file_id')
 
2742
        self.addCleanup(tt.finalize)
 
2743
        preview = tt.get_preview_tree()
 
2744
        preview.set_parent_ids([rev_id])
 
2745
        builder = tree.branch.get_commit_builder([rev_id])
 
2746
        list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
 
2747
        builder.finish_inventory()
 
2748
        rev2_id = builder.commit('rev2')
 
2749
        rev2_tree = tree.branch.repository.revision_tree(rev2_id)
 
2750
        self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
 
2751
 
 
2752
    def test_ascii_limbo_paths(self):
 
2753
        self.requireFeature(tests.UnicodeFilenameFeature)
 
2754
        branch = self.make_branch('any')
 
2755
        tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
 
2756
        tt = TransformPreview(tree)
 
2757
        self.addCleanup(tt.finalize)
 
2758
        foo_id = tt.new_directory('', ROOT_PARENT)
 
2759
        bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
 
2760
        limbo_path = tt._limbo_name(bar_id)
 
2761
        self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
 
2762
 
 
2763
 
 
2764
class FakeSerializer(object):
 
2765
    """Serializer implementation that simply returns the input.
 
2766
 
 
2767
    The input is returned in the order used by pack.ContainerPushParser.
 
2768
    """
 
2769
    @staticmethod
 
2770
    def bytes_record(bytes, names):
 
2771
        return names, bytes
 
2772
 
 
2773
 
 
2774
class TestSerializeTransform(tests.TestCaseWithTransport):
 
2775
 
 
2776
    _test_needs_features = [tests.UnicodeFilenameFeature]
 
2777
 
 
2778
    def get_preview(self, tree=None):
 
2779
        if tree is None:
 
2780
            tree = self.make_branch_and_tree('tree')
 
2781
        tt = TransformPreview(tree)
 
2782
        self.addCleanup(tt.finalize)
 
2783
        return tt
 
2784
 
 
2785
    def assertSerializesTo(self, expected, tt):
 
2786
        records = list(tt.serialize(FakeSerializer()))
 
2787
        self.assertEqual(expected, records)
 
2788
 
 
2789
    @staticmethod
 
2790
    def default_attribs():
 
2791
        return {
 
2792
            '_id_number': 1,
 
2793
            '_new_name': {},
 
2794
            '_new_parent': {},
 
2795
            '_new_executability': {},
 
2796
            '_new_id': {},
 
2797
            '_tree_path_ids': {'': 'new-0'},
 
2798
            '_removed_id': [],
 
2799
            '_removed_contents': [],
 
2800
            '_non_present_ids': {},
 
2801
            }
 
2802
 
 
2803
    def make_records(self, attribs, contents):
 
2804
        records = [
 
2805
            (((('attribs'),),), bencode.bencode(attribs))]
 
2806
        records.extend([(((n, k),), c) for n, k, c in contents])
 
2807
        return records
 
2808
 
 
2809
    def creation_records(self):
 
2810
        attribs = self.default_attribs()
 
2811
        attribs['_id_number'] = 3
 
2812
        attribs['_new_name'] = {
 
2813
            'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
 
2814
        attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
 
2815
        attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
 
2816
        attribs['_new_executability'] = {'new-1': 1}
 
2817
        contents = [
 
2818
            ('new-1', 'file', 'i 1\nbar\n'),
 
2819
            ('new-2', 'directory', ''),
 
2820
            ]
 
2821
        return self.make_records(attribs, contents)
 
2822
 
 
2823
    def test_serialize_creation(self):
 
2824
        tt = self.get_preview()
 
2825
        tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
 
2826
        tt.new_directory('qux', tt.root, 'quxx')
 
2827
        self.assertSerializesTo(self.creation_records(), tt)
 
2828
 
 
2829
    def test_deserialize_creation(self):
 
2830
        tt = self.get_preview()
 
2831
        tt.deserialize(iter(self.creation_records()))
 
2832
        self.assertEqual(3, tt._id_number)
 
2833
        self.assertEqual({'new-1': u'foo\u1234',
 
2834
                          'new-2': 'qux'}, tt._new_name)
 
2835
        self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
 
2836
        self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
 
2837
        self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
 
2838
        self.assertEqual({'new-1': True}, tt._new_executability)
 
2839
        self.assertEqual({'new-1': 'file',
 
2840
                          'new-2': 'directory'}, tt._new_contents)
 
2841
        foo_limbo = open(tt._limbo_name('new-1'), 'rb')
 
2842
        try:
 
2843
            foo_content = foo_limbo.read()
 
2844
        finally:
 
2845
            foo_limbo.close()
 
2846
        self.assertEqual('bar', foo_content)
 
2847
 
 
2848
    def symlink_creation_records(self):
 
2849
        attribs = self.default_attribs()
 
2850
        attribs['_id_number'] = 2
 
2851
        attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
 
2852
        attribs['_new_parent'] = {'new-1': 'new-0'}
 
2853
        contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
 
2854
        return self.make_records(attribs, contents)
 
2855
 
 
2856
    def test_serialize_symlink_creation(self):
 
2857
        self.requireFeature(tests.SymlinkFeature)
 
2858
        tt = self.get_preview()
 
2859
        tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
 
2860
        self.assertSerializesTo(self.symlink_creation_records(), tt)
 
2861
 
 
2862
    def test_deserialize_symlink_creation(self):
 
2863
        self.requireFeature(tests.SymlinkFeature)
 
2864
        tt = self.get_preview()
 
2865
        tt.deserialize(iter(self.symlink_creation_records()))
 
2866
        abspath = tt._limbo_name('new-1')
 
2867
        foo_content = osutils.readlink(abspath)
 
2868
        self.assertEqual(u'bar\u1234', foo_content)
 
2869
 
 
2870
    def make_destruction_preview(self):
 
2871
        tree = self.make_branch_and_tree('.')
 
2872
        self.build_tree([u'foo\u1234', 'bar'])
 
2873
        tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
 
2874
        return self.get_preview(tree)
 
2875
 
 
2876
    def destruction_records(self):
 
2877
        attribs = self.default_attribs()
 
2878
        attribs['_id_number'] = 3
 
2879
        attribs['_removed_id'] = ['new-1']
 
2880
        attribs['_removed_contents'] = ['new-2']
 
2881
        attribs['_tree_path_ids'] = {
 
2882
            '': 'new-0',
 
2883
            u'foo\u1234'.encode('utf-8'): 'new-1',
 
2884
            'bar': 'new-2',
 
2885
            }
 
2886
        return self.make_records(attribs, [])
 
2887
 
 
2888
    def test_serialize_destruction(self):
 
2889
        tt = self.make_destruction_preview()
 
2890
        foo_trans_id = tt.trans_id_tree_file_id('foo-id')
 
2891
        tt.unversion_file(foo_trans_id)
 
2892
        bar_trans_id = tt.trans_id_tree_file_id('bar-id')
 
2893
        tt.delete_contents(bar_trans_id)
 
2894
        self.assertSerializesTo(self.destruction_records(), tt)
 
2895
 
 
2896
    def test_deserialize_destruction(self):
 
2897
        tt = self.make_destruction_preview()
 
2898
        tt.deserialize(iter(self.destruction_records()))
 
2899
        self.assertEqual({u'foo\u1234': 'new-1',
 
2900
                          'bar': 'new-2',
 
2901
                          '': tt.root}, tt._tree_path_ids)
 
2902
        self.assertEqual({'new-1': u'foo\u1234',
 
2903
                          'new-2': 'bar',
 
2904
                          tt.root: ''}, tt._tree_id_paths)
 
2905
        self.assertEqual(set(['new-1']), tt._removed_id)
 
2906
        self.assertEqual(set(['new-2']), tt._removed_contents)
 
2907
 
 
2908
    def missing_records(self):
 
2909
        attribs = self.default_attribs()
 
2910
        attribs['_id_number'] = 2
 
2911
        attribs['_non_present_ids'] = {
 
2912
            'boo': 'new-1',}
 
2913
        return self.make_records(attribs, [])
 
2914
 
 
2915
    def test_serialize_missing(self):
 
2916
        tt = self.get_preview()
 
2917
        boo_trans_id = tt.trans_id_file_id('boo')
 
2918
        self.assertSerializesTo(self.missing_records(), tt)
 
2919
 
 
2920
    def test_deserialize_missing(self):
 
2921
        tt = self.get_preview()
 
2922
        tt.deserialize(iter(self.missing_records()))
 
2923
        self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
 
2924
 
 
2925
    def make_modification_preview(self):
 
2926
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
2927
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
2928
        tree = self.make_branch_and_tree('tree')
 
2929
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
2930
        tree.add('file', 'file-id')
 
2931
        return self.get_preview(tree), LINES_TWO
 
2932
 
 
2933
    def modification_records(self):
 
2934
        attribs = self.default_attribs()
 
2935
        attribs['_id_number'] = 2
 
2936
        attribs['_tree_path_ids'] = {
 
2937
            'file': 'new-1',
 
2938
            '': 'new-0',}
 
2939
        attribs['_removed_contents'] = ['new-1']
 
2940
        contents = [('new-1', 'file',
 
2941
                     'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
 
2942
        return self.make_records(attribs, contents)
 
2943
 
 
2944
    def test_serialize_modification(self):
 
2945
        tt, LINES = self.make_modification_preview()
 
2946
        trans_id = tt.trans_id_file_id('file-id')
 
2947
        tt.delete_contents(trans_id)
 
2948
        tt.create_file(LINES, trans_id)
 
2949
        self.assertSerializesTo(self.modification_records(), tt)
 
2950
 
 
2951
    def test_deserialize_modification(self):
 
2952
        tt, LINES = self.make_modification_preview()
 
2953
        tt.deserialize(iter(self.modification_records()))
 
2954
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
2955
 
 
2956
    def make_kind_change_preview(self):
 
2957
        LINES = 'a\nb\nc\nd\n'
 
2958
        tree = self.make_branch_and_tree('tree')
 
2959
        self.build_tree(['tree/foo/'])
 
2960
        tree.add('foo', 'foo-id')
 
2961
        return self.get_preview(tree), LINES
 
2962
 
 
2963
    def kind_change_records(self):
 
2964
        attribs = self.default_attribs()
 
2965
        attribs['_id_number'] = 2
 
2966
        attribs['_tree_path_ids'] = {
 
2967
            'foo': 'new-1',
 
2968
            '': 'new-0',}
 
2969
        attribs['_removed_contents'] = ['new-1']
 
2970
        contents = [('new-1', 'file',
 
2971
                     'i 4\na\nb\nc\nd\n\n')]
 
2972
        return self.make_records(attribs, contents)
 
2973
 
 
2974
    def test_serialize_kind_change(self):
 
2975
        tt, LINES = self.make_kind_change_preview()
 
2976
        trans_id = tt.trans_id_file_id('foo-id')
 
2977
        tt.delete_contents(trans_id)
 
2978
        tt.create_file(LINES, trans_id)
 
2979
        self.assertSerializesTo(self.kind_change_records(), tt)
 
2980
 
 
2981
    def test_deserialize_kind_change(self):
 
2982
        tt, LINES = self.make_kind_change_preview()
 
2983
        tt.deserialize(iter(self.kind_change_records()))
 
2984
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
2985
 
 
2986
    def make_add_contents_preview(self):
 
2987
        LINES = 'a\nb\nc\nd\n'
 
2988
        tree = self.make_branch_and_tree('tree')
 
2989
        self.build_tree(['tree/foo'])
 
2990
        tree.add('foo')
 
2991
        os.unlink('tree/foo')
 
2992
        return self.get_preview(tree), LINES
 
2993
 
 
2994
    def add_contents_records(self):
 
2995
        attribs = self.default_attribs()
 
2996
        attribs['_id_number'] = 2
 
2997
        attribs['_tree_path_ids'] = {
 
2998
            'foo': 'new-1',
 
2999
            '': 'new-0',}
 
3000
        contents = [('new-1', 'file',
 
3001
                     'i 4\na\nb\nc\nd\n\n')]
 
3002
        return self.make_records(attribs, contents)
 
3003
 
 
3004
    def test_serialize_add_contents(self):
 
3005
        tt, LINES = self.make_add_contents_preview()
 
3006
        trans_id = tt.trans_id_tree_path('foo')
 
3007
        tt.create_file(LINES, trans_id)
 
3008
        self.assertSerializesTo(self.add_contents_records(), tt)
 
3009
 
 
3010
    def test_deserialize_add_contents(self):
 
3011
        tt, LINES = self.make_add_contents_preview()
 
3012
        tt.deserialize(iter(self.add_contents_records()))
 
3013
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
3014
 
 
3015
    def test_get_parents_lines(self):
 
3016
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
3017
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
3018
        tree = self.make_branch_and_tree('tree')
 
3019
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
3020
        tree.add('file', 'file-id')
 
3021
        tt = self.get_preview(tree)
 
3022
        trans_id = tt.trans_id_tree_path('file')
 
3023
        self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
 
3024
            tt._get_parents_lines(trans_id))
 
3025
 
 
3026
    def test_get_parents_texts(self):
 
3027
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
3028
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
3029
        tree = self.make_branch_and_tree('tree')
 
3030
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
3031
        tree.add('file', 'file-id')
 
3032
        tt = self.get_preview(tree)
 
3033
        trans_id = tt.trans_id_tree_path('file')
 
3034
        self.assertEqual((LINES_ONE,),
 
3035
            tt._get_parents_texts(trans_id))