~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Gordon Tyler
  • Date: 2009-11-30 00:48:51 UTC
  • mto: (4870.1.1 integration2)
  • mto: This revision was merged to the branch mainline in revision 4836.
  • Revision ID: gordon@doxxx.net-20091130004851-as4nnjedltpogxot
Fixed detection of bzr.exe when display python interpreter version.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
import os
 
18
import stat
 
19
from StringIO import StringIO
 
20
import sys
 
21
 
 
22
from bzrlib import (
 
23
    bencode,
 
24
    errors,
 
25
    generate_ids,
 
26
    osutils,
 
27
    progress,
 
28
    revision as _mod_revision,
 
29
    symbol_versioning,
 
30
    tests,
 
31
    urlutils,
 
32
    )
 
33
from bzrlib.bzrdir import BzrDir
 
34
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
 
35
                              UnversionedParent, ParentLoop, DeletingParent,
 
36
                              NonDirectoryParent)
 
37
from bzrlib.diff import show_diff_trees
 
38
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
 
39
                           ReusingTransform, CantMoveRoot,
 
40
                           PathsNotVersionedError, ExistingLimbo,
 
41
                           ExistingPendingDeletion, ImmortalLimbo,
 
42
                           ImmortalPendingDeletion, LockError)
 
43
from bzrlib.osutils import file_kind, pathjoin
 
44
from bzrlib.merge import Merge3Merger, Merger
 
45
from bzrlib.tests import (
 
46
    HardlinkFeature,
 
47
    SymlinkFeature,
 
48
    TestCase,
 
49
    TestCaseInTempDir,
 
50
    TestSkipped,
 
51
    )
 
52
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
 
53
                              resolve_conflicts, cook_conflicts,
 
54
                              build_tree, get_backup_name,
 
55
                              _FileMover, resolve_checkout,
 
56
                              TransformPreview, create_from_tree)
 
57
 
 
58
 
 
59
class TestTreeTransform(tests.TestCaseWithTransport):
 
60
 
 
61
    def setUp(self):
 
62
        super(TestTreeTransform, self).setUp()
 
63
        self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
 
64
        os.chdir('..')
 
65
 
 
66
    def get_transform(self):
 
67
        transform = TreeTransform(self.wt)
 
68
        self.addCleanup(transform.finalize)
 
69
        return transform, transform.root
 
70
 
 
71
    def test_existing_limbo(self):
 
72
        transform, root = self.get_transform()
 
73
        limbo_name = transform._limbodir
 
74
        deletion_path = transform._deletiondir
 
75
        os.mkdir(pathjoin(limbo_name, 'hehe'))
 
76
        self.assertRaises(ImmortalLimbo, transform.apply)
 
77
        self.assertRaises(LockError, self.wt.unlock)
 
78
        self.assertRaises(ExistingLimbo, self.get_transform)
 
79
        self.assertRaises(LockError, self.wt.unlock)
 
80
        os.rmdir(pathjoin(limbo_name, 'hehe'))
 
81
        os.rmdir(limbo_name)
 
82
        os.rmdir(deletion_path)
 
83
        transform, root = self.get_transform()
 
84
        transform.apply()
 
85
 
 
86
    def test_existing_pending_deletion(self):
 
87
        transform, root = self.get_transform()
 
88
        deletion_path = self._limbodir = urlutils.local_path_from_url(
 
89
            transform._tree._transport.abspath('pending-deletion'))
 
90
        os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
 
91
        self.assertRaises(ImmortalPendingDeletion, transform.apply)
 
92
        self.assertRaises(LockError, self.wt.unlock)
 
93
        self.assertRaises(ExistingPendingDeletion, self.get_transform)
 
94
 
 
95
    def test_build(self):
 
96
        transform, root = self.get_transform()
 
97
        self.wt.lock_tree_write()
 
98
        self.addCleanup(self.wt.unlock)
 
99
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
 
100
        imaginary_id = transform.trans_id_tree_path('imaginary')
 
101
        imaginary_id2 = transform.trans_id_tree_path('imaginary/')
 
102
        self.assertEqual(imaginary_id, imaginary_id2)
 
103
        self.assertEqual(transform.get_tree_parent(imaginary_id), root)
 
104
        self.assertEqual(transform.final_kind(root), 'directory')
 
105
        self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
 
106
        trans_id = transform.create_path('name', root)
 
107
        self.assertIs(transform.final_file_id(trans_id), None)
 
108
        self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
 
109
        transform.create_file('contents', trans_id)
 
110
        transform.set_executability(True, trans_id)
 
111
        transform.version_file('my_pretties', trans_id)
 
112
        self.assertRaises(DuplicateKey, transform.version_file,
 
113
                          'my_pretties', trans_id)
 
114
        self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
 
115
        self.assertEqual(transform.final_parent(trans_id), root)
 
116
        self.assertIs(transform.final_parent(root), ROOT_PARENT)
 
117
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
 
118
        oz_id = transform.create_path('oz', root)
 
119
        transform.create_directory(oz_id)
 
120
        transform.version_file('ozzie', oz_id)
 
121
        trans_id2 = transform.create_path('name2', root)
 
122
        transform.create_file('contents', trans_id2)
 
123
        transform.set_executability(False, trans_id2)
 
124
        transform.version_file('my_pretties2', trans_id2)
 
125
        modified_paths = transform.apply().modified_paths
 
126
        self.assertEqual('contents', self.wt.get_file_byname('name').read())
 
127
        self.assertEqual(self.wt.path2id('name'), 'my_pretties')
 
128
        self.assertIs(self.wt.is_executable('my_pretties'), True)
 
129
        self.assertIs(self.wt.is_executable('my_pretties2'), False)
 
130
        self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
 
131
        self.assertEqual(len(modified_paths), 3)
 
132
        tree_mod_paths = [self.wt.id2abspath(f) for f in
 
133
                          ('ozzie', 'my_pretties', 'my_pretties2')]
 
134
        self.assertSubset(tree_mod_paths, modified_paths)
 
135
        # is it safe to finalize repeatedly?
 
136
        transform.finalize()
 
137
        transform.finalize()
 
138
 
 
139
    def test_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
 
 
154
    def test_convenience(self):
 
155
        transform, root = self.get_transform()
 
156
        self.wt.lock_tree_write()
 
157
        self.addCleanup(self.wt.unlock)
 
158
        trans_id = transform.new_file('name', root, 'contents',
 
159
                                      'my_pretties', True)
 
160
        oz = transform.new_directory('oz', root, 'oz-id')
 
161
        dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
 
162
        toto = transform.new_file('toto', dorothy, 'toto-contents',
 
163
                                  'toto-id', False)
 
164
 
 
165
        self.assertEqual(len(transform.find_conflicts()), 0)
 
166
        transform.apply()
 
167
        self.assertRaises(ReusingTransform, transform.find_conflicts)
 
168
        self.assertEqual('contents', file(self.wt.abspath('name')).read())
 
169
        self.assertEqual(self.wt.path2id('name'), 'my_pretties')
 
170
        self.assertIs(self.wt.is_executable('my_pretties'), True)
 
171
        self.assertEqual(self.wt.path2id('oz'), 'oz-id')
 
172
        self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
 
173
        self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
 
174
 
 
175
        self.assertEqual('toto-contents',
 
176
                         self.wt.get_file_byname('oz/dorothy/toto').read())
 
177
        self.assertIs(self.wt.is_executable('toto-id'), False)
 
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
 
 
190
    def test_conflicts(self):
 
191
        transform, root = self.get_transform()
 
192
        trans_id = transform.new_file('name', root, 'contents',
 
193
                                      'my_pretties')
 
194
        self.assertEqual(len(transform.find_conflicts()), 0)
 
195
        trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
 
196
        self.assertEqual(transform.find_conflicts(),
 
197
                         [('duplicate', trans_id, trans_id2, 'name')])
 
198
        self.assertRaises(MalformedTransform, transform.apply)
 
199
        transform.adjust_path('name', trans_id, trans_id2)
 
200
        self.assertEqual(transform.find_conflicts(),
 
201
                         [('non-directory parent', trans_id)])
 
202
        tinman_id = transform.trans_id_tree_path('tinman')
 
203
        transform.adjust_path('name', tinman_id, trans_id2)
 
204
        self.assertEqual(transform.find_conflicts(),
 
205
                         [('unversioned parent', tinman_id),
 
206
                          ('missing parent', tinman_id)])
 
207
        lion_id = transform.create_path('lion', root)
 
208
        self.assertEqual(transform.find_conflicts(),
 
209
                         [('unversioned parent', tinman_id),
 
210
                          ('missing parent', tinman_id)])
 
211
        transform.adjust_path('name', lion_id, trans_id2)
 
212
        self.assertEqual(transform.find_conflicts(),
 
213
                         [('unversioned parent', lion_id),
 
214
                          ('missing parent', lion_id)])
 
215
        transform.version_file("Courage", lion_id)
 
216
        self.assertEqual(transform.find_conflicts(),
 
217
                         [('missing parent', lion_id),
 
218
                          ('versioning no contents', lion_id)])
 
219
        transform.adjust_path('name2', root, trans_id2)
 
220
        self.assertEqual(transform.find_conflicts(),
 
221
                         [('versioning no contents', lion_id)])
 
222
        transform.create_file('Contents, okay?', lion_id)
 
223
        transform.adjust_path('name2', trans_id2, trans_id2)
 
224
        self.assertEqual(transform.find_conflicts(),
 
225
                         [('parent loop', trans_id2),
 
226
                          ('non-directory parent', trans_id2)])
 
227
        transform.adjust_path('name2', root, trans_id2)
 
228
        oz_id = transform.new_directory('oz', root)
 
229
        transform.set_executability(True, oz_id)
 
230
        self.assertEqual(transform.find_conflicts(),
 
231
                         [('unversioned executability', oz_id)])
 
232
        transform.version_file('oz-id', oz_id)
 
233
        self.assertEqual(transform.find_conflicts(),
 
234
                         [('non-file executability', oz_id)])
 
235
        transform.set_executability(None, oz_id)
 
236
        tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
 
237
        transform.apply()
 
238
        self.assertEqual(self.wt.path2id('name'), 'my_pretties')
 
239
        self.assertEqual('contents', file(self.wt.abspath('name')).read())
 
240
        transform2, root = self.get_transform()
 
241
        oz_id = transform2.trans_id_tree_file_id('oz-id')
 
242
        newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
 
243
        result = transform2.find_conflicts()
 
244
        fp = FinalPaths(transform2)
 
245
        self.assert_('oz/tip' in transform2._tree_path_ids)
 
246
        self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
 
247
        self.assertEqual(len(result), 2)
 
248
        self.assertEqual((result[0][0], result[0][1]),
 
249
                         ('duplicate', newtip))
 
250
        self.assertEqual((result[1][0], result[1][2]),
 
251
                         ('duplicate id', newtip))
 
252
        transform2.finalize()
 
253
        transform3 = TreeTransform(self.wt)
 
254
        self.addCleanup(transform3.finalize)
 
255
        oz_id = transform3.trans_id_tree_file_id('oz-id')
 
256
        transform3.delete_contents(oz_id)
 
257
        self.assertEqual(transform3.find_conflicts(),
 
258
                         [('missing parent', oz_id)])
 
259
        root_id = transform3.root
 
260
        tip_id = transform3.trans_id_tree_file_id('tip-id')
 
261
        transform3.adjust_path('tip', root_id, tip_id)
 
262
        transform3.apply()
 
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
 
 
384
    def test_add_del(self):
 
385
        start, root = self.get_transform()
 
386
        start.new_directory('a', root, 'a')
 
387
        start.apply()
 
388
        transform, root = self.get_transform()
 
389
        transform.delete_versioned(transform.trans_id_tree_file_id('a'))
 
390
        transform.new_directory('a', root, 'a')
 
391
        transform.apply()
 
392
 
 
393
    def test_unversioning(self):
 
394
        create_tree, root = self.get_transform()
 
395
        parent_id = create_tree.new_directory('parent', root, 'parent-id')
 
396
        create_tree.new_file('child', parent_id, 'child', 'child-id')
 
397
        create_tree.apply()
 
398
        unversion = TreeTransform(self.wt)
 
399
        self.addCleanup(unversion.finalize)
 
400
        parent = unversion.trans_id_tree_path('parent')
 
401
        unversion.unversion_file(parent)
 
402
        self.assertEqual(unversion.find_conflicts(),
 
403
                         [('unversioned parent', parent_id)])
 
404
        file_id = unversion.trans_id_tree_file_id('child-id')
 
405
        unversion.unversion_file(file_id)
 
406
        unversion.apply()
 
407
 
 
408
    def test_name_invariants(self):
 
409
        create_tree, root = self.get_transform()
 
410
        # prepare tree
 
411
        root = create_tree.root
 
412
        create_tree.new_file('name1', root, 'hello1', 'name1')
 
413
        create_tree.new_file('name2', root, 'hello2', 'name2')
 
414
        ddir = create_tree.new_directory('dying_directory', root, 'ddir')
 
415
        create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
 
416
        create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
 
417
        create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
 
418
        create_tree.apply()
 
419
 
 
420
        mangle_tree,root = self.get_transform()
 
421
        root = mangle_tree.root
 
422
        #swap names
 
423
        name1 = mangle_tree.trans_id_tree_file_id('name1')
 
424
        name2 = mangle_tree.trans_id_tree_file_id('name2')
 
425
        mangle_tree.adjust_path('name2', root, name1)
 
426
        mangle_tree.adjust_path('name1', root, name2)
 
427
 
 
428
        #tests for deleting parent directories
 
429
        ddir = mangle_tree.trans_id_tree_file_id('ddir')
 
430
        mangle_tree.delete_contents(ddir)
 
431
        dfile = mangle_tree.trans_id_tree_file_id('dfile')
 
432
        mangle_tree.delete_versioned(dfile)
 
433
        mangle_tree.unversion_file(dfile)
 
434
        mfile = mangle_tree.trans_id_tree_file_id('mfile')
 
435
        mangle_tree.adjust_path('mfile', root, mfile)
 
436
 
 
437
        #tests for adding parent directories
 
438
        newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
 
439
        mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
 
440
        mangle_tree.adjust_path('mfile2', newdir, mfile2)
 
441
        mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
 
442
        self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
 
443
        self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
 
444
        self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
 
445
        mangle_tree.apply()
 
446
        self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
 
447
        self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
 
448
        mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
 
449
        self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
 
450
        self.assertEqual(file(mfile2_path).read(), 'later2')
 
451
        self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
 
452
        self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
 
453
        newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
 
454
        self.assertEqual(file(newfile_path).read(), 'hello3')
 
455
        self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
 
456
        self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
 
457
        mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
 
458
 
 
459
    def test_both_rename(self):
 
460
        create_tree,root = self.get_transform()
 
461
        newdir = create_tree.new_directory('selftest', root, 'selftest-id')
 
462
        create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
 
463
        create_tree.apply()
 
464
        mangle_tree,root = self.get_transform()
 
465
        selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
 
466
        blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
 
467
        mangle_tree.adjust_path('test', root, selftest)
 
468
        mangle_tree.adjust_path('test_too_much', root, selftest)
 
469
        mangle_tree.set_executability(True, blackbox)
 
470
        mangle_tree.apply()
 
471
 
 
472
    def test_both_rename2(self):
 
473
        create_tree,root = self.get_transform()
 
474
        bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
 
475
        tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
 
476
        blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
 
477
        create_tree.new_file('test_too_much.py', blackbox, 'hello1',
 
478
                             'test_too_much-id')
 
479
        create_tree.apply()
 
480
        mangle_tree,root = self.get_transform()
 
481
        bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
 
482
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
 
483
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
 
484
        mangle_tree.adjust_path('selftest', bzrlib, tests)
 
485
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
 
486
        mangle_tree.set_executability(True, test_too_much)
 
487
        mangle_tree.apply()
 
488
 
 
489
    def test_both_rename3(self):
 
490
        create_tree,root = self.get_transform()
 
491
        tests = create_tree.new_directory('tests', root, 'tests-id')
 
492
        create_tree.new_file('test_too_much.py', tests, 'hello1',
 
493
                             'test_too_much-id')
 
494
        create_tree.apply()
 
495
        mangle_tree,root = self.get_transform()
 
496
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
 
497
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
 
498
        mangle_tree.adjust_path('selftest', root, tests)
 
499
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
 
500
        mangle_tree.set_executability(True, test_too_much)
 
501
        mangle_tree.apply()
 
502
 
 
503
    def test_move_dangling_ie(self):
 
504
        create_tree, root = self.get_transform()
 
505
        # prepare tree
 
506
        root = create_tree.root
 
507
        create_tree.new_file('name1', root, 'hello1', 'name1')
 
508
        create_tree.apply()
 
509
        delete_contents, root = self.get_transform()
 
510
        file = delete_contents.trans_id_tree_file_id('name1')
 
511
        delete_contents.delete_contents(file)
 
512
        delete_contents.apply()
 
513
        move_id, root = self.get_transform()
 
514
        name1 = move_id.trans_id_tree_file_id('name1')
 
515
        newdir = move_id.new_directory('dir', root, 'newdir')
 
516
        move_id.adjust_path('name2', newdir, name1)
 
517
        move_id.apply()
 
518
 
 
519
    def test_replace_dangling_ie(self):
 
520
        create_tree, root = self.get_transform()
 
521
        # prepare tree
 
522
        root = create_tree.root
 
523
        create_tree.new_file('name1', root, 'hello1', 'name1')
 
524
        create_tree.apply()
 
525
        delete_contents = TreeTransform(self.wt)
 
526
        self.addCleanup(delete_contents.finalize)
 
527
        file = delete_contents.trans_id_tree_file_id('name1')
 
528
        delete_contents.delete_contents(file)
 
529
        delete_contents.apply()
 
530
        delete_contents.finalize()
 
531
        replace = TreeTransform(self.wt)
 
532
        self.addCleanup(replace.finalize)
 
533
        name2 = replace.new_file('name2', root, 'hello2', 'name1')
 
534
        conflicts = replace.find_conflicts()
 
535
        name1 = replace.trans_id_tree_file_id('name1')
 
536
        self.assertEqual(conflicts, [('duplicate id', name1, name2)])
 
537
        resolve_conflicts(replace)
 
538
        replace.apply()
 
539
 
 
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()
 
547
        oz_id = transform.new_directory('oz', root, 'oz-id')
 
548
        wizard = transform.new_symlink(link_name1, oz_id, link_target1,
 
549
                                       'wizard-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)
 
553
        transform.set_executability(True, wiz_id)
 
554
        self.assertEqual(transform.find_conflicts(),
 
555
                         [('non-file executability', wiz_id)])
 
556
        transform.set_executability(None, wiz_id)
 
557
        transform.apply()
 
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
 
596
 
 
597
    def get_conflicted(self):
 
598
        create,root = self.get_transform()
 
599
        create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
 
600
        oz = create.new_directory('oz', root, 'oz-id')
 
601
        create.new_directory('emeraldcity', oz, 'emerald-id')
 
602
        create.apply()
 
603
        conflicts,root = self.get_transform()
 
604
        # set up duplicate entry, duplicate id
 
605
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
 
606
                                         'dorothy-id')
 
607
        old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
 
608
        oz = conflicts.trans_id_tree_file_id('oz-id')
 
609
        # set up DeletedParent parent conflict
 
610
        conflicts.delete_versioned(oz)
 
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')
 
616
        # set up parent loop
 
617
        conflicts.adjust_path('emeraldcity', emerald, emerald)
 
618
        return conflicts, emerald, oz, old_dorothy, new_dorothy
 
619
 
 
620
    def test_conflict_resolution(self):
 
621
        conflicts, emerald, oz, old_dorothy, new_dorothy =\
 
622
            self.get_conflicted()
 
623
        resolve_conflicts(conflicts)
 
624
        self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
 
625
        self.assertIs(conflicts.final_file_id(old_dorothy), None)
 
626
        self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
 
627
        self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
 
628
        self.assertEqual(conflicts.final_parent(emerald), oz)
 
629
        conflicts.apply()
 
630
 
 
631
    def test_cook_conflicts(self):
 
632
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
 
633
        raw_conflicts = resolve_conflicts(tt)
 
634
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
 
635
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
 
636
                                   'dorothy', None, 'dorothy-id')
 
637
        self.assertEqual(cooked_conflicts[0], duplicate)
 
638
        duplicate_id = DuplicateID('Unversioned existing file',
 
639
                                   'dorothy.moved', 'dorothy', None,
 
640
                                   'dorothy-id')
 
641
        self.assertEqual(cooked_conflicts[1], duplicate_id)
 
642
        missing_parent = MissingParent('Created directory', 'munchkincity',
 
643
                                       'munchkincity-id')
 
644
        deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
 
645
        self.assertEqual(cooked_conflicts[2], missing_parent)
 
646
        unversioned_parent = UnversionedParent('Versioned directory',
 
647
                                               'munchkincity',
 
648
                                               'munchkincity-id')
 
649
        unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
 
650
                                               'oz-id')
 
651
        self.assertEqual(cooked_conflicts[3], unversioned_parent)
 
652
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
 
653
                                 'oz/emeraldcity', 'emerald-id', 'emerald-id')
 
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)
 
658
        tt.finalize()
 
659
 
 
660
    def test_string_conflicts(self):
 
661
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
 
662
        raw_conflicts = resolve_conflicts(tt)
 
663
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
 
664
        tt.finalize()
 
665
        conflicts_s = [str(c) for c in cooked_conflicts]
 
666
        self.assertEqual(len(cooked_conflicts), len(conflicts_s))
 
667
        self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy.  '
 
668
                                         'Moved existing file to '
 
669
                                         'dorothy.moved.')
 
670
        self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy.  '
 
671
                                         'Unversioned existing file '
 
672
                                         'dorothy.moved.')
 
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'
 
684
                                         ' oz/emeraldcity.  Cancelled move.')
 
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
 
 
741
    def test_moving_versioned_directories(self):
 
742
        create, root = self.get_transform()
 
743
        kansas = create.new_directory('kansas', root, 'kansas-id')
 
744
        create.new_directory('house', kansas, 'house-id')
 
745
        create.new_directory('oz', root, 'oz-id')
 
746
        create.apply()
 
747
        cyclone, root = self.get_transform()
 
748
        oz = cyclone.trans_id_tree_file_id('oz-id')
 
749
        house = cyclone.trans_id_tree_file_id('house-id')
 
750
        cyclone.adjust_path('house', oz, house)
 
751
        cyclone.apply()
 
752
 
 
753
    def test_moving_root(self):
 
754
        create, root = self.get_transform()
 
755
        fun = create.new_directory('fun', root, 'fun-id')
 
756
        create.new_directory('sun', root, 'sun-id')
 
757
        create.new_directory('moon', root, 'moon')
 
758
        create.apply()
 
759
        transform, root = self.get_transform()
 
760
        transform.adjust_root_path('oldroot', fun)
 
761
        new_root=transform.trans_id_tree_path('')
 
762
        transform.version_file('new-root', new_root)
 
763
        transform.apply()
 
764
 
 
765
    def test_renames(self):
 
766
        create, root = self.get_transform()
 
767
        old = create.new_directory('old-parent', root, 'old-id')
 
768
        intermediate = create.new_directory('intermediate', old, 'im-id')
 
769
        myfile = create.new_file('myfile', intermediate, 'myfile-text',
 
770
                                 'myfile-id')
 
771
        create.apply()
 
772
        rename, root = self.get_transform()
 
773
        old = rename.trans_id_file_id('old-id')
 
774
        rename.adjust_path('new', root, old)
 
775
        myfile = rename.trans_id_file_id('myfile-id')
 
776
        rename.set_executability(True, myfile)
 
777
        rename.apply()
 
778
 
 
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'))
 
1381
 
 
1382
 
 
1383
class TransformGroup(object):
 
1384
 
 
1385
    def __init__(self, dirname, root_id):
 
1386
        self.name = dirname
 
1387
        os.mkdir(dirname)
 
1388
        self.wt = BzrDir.create_standalone_workingtree(dirname)
 
1389
        self.wt.set_root_id(root_id)
 
1390
        self.b = self.wt.branch
 
1391
        self.tt = TreeTransform(self.wt)
 
1392
        self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
 
1393
 
 
1394
 
 
1395
def conflict_text(tree, merge):
 
1396
    template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
 
1397
    return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
 
1398
 
 
1399
 
 
1400
class TestTransformMerge(TestCaseInTempDir):
 
1401
 
 
1402
    def test_text_merge(self):
 
1403
        root_id = generate_ids.gen_root_id()
 
1404
        base = TransformGroup("base", root_id)
 
1405
        base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
 
1406
        base.tt.new_file('b', base.root, 'b1', 'b')
 
1407
        base.tt.new_file('c', base.root, 'c', 'c')
 
1408
        base.tt.new_file('d', base.root, 'd', 'd')
 
1409
        base.tt.new_file('e', base.root, 'e', 'e')
 
1410
        base.tt.new_file('f', base.root, 'f', 'f')
 
1411
        base.tt.new_directory('g', base.root, 'g')
 
1412
        base.tt.new_directory('h', base.root, 'h')
 
1413
        base.tt.apply()
 
1414
        other = TransformGroup("other", root_id)
 
1415
        other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
 
1416
        other.tt.new_file('b', other.root, 'b2', 'b')
 
1417
        other.tt.new_file('c', other.root, 'c2', 'c')
 
1418
        other.tt.new_file('d', other.root, 'd', 'd')
 
1419
        other.tt.new_file('e', other.root, 'e2', 'e')
 
1420
        other.tt.new_file('f', other.root, 'f', 'f')
 
1421
        other.tt.new_file('g', other.root, 'g', 'g')
 
1422
        other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
 
1423
        other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
 
1424
        other.tt.apply()
 
1425
        this = TransformGroup("this", root_id)
 
1426
        this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
 
1427
        this.tt.new_file('b', this.root, 'b', 'b')
 
1428
        this.tt.new_file('c', this.root, 'c', 'c')
 
1429
        this.tt.new_file('d', this.root, 'd2', 'd')
 
1430
        this.tt.new_file('e', this.root, 'e2', 'e')
 
1431
        this.tt.new_file('f', this.root, 'f', 'f')
 
1432
        this.tt.new_file('g', this.root, 'g', 'g')
 
1433
        this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
 
1434
        this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
 
1435
        this.tt.apply()
 
1436
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
1437
 
 
1438
        # textual merge
 
1439
        self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
 
1440
        # three-way text conflict
 
1441
        self.assertEqual(this.wt.get_file('b').read(),
 
1442
                         conflict_text('b', 'b2'))
 
1443
        # OTHER wins
 
1444
        self.assertEqual(this.wt.get_file('c').read(), 'c2')
 
1445
        # THIS wins
 
1446
        self.assertEqual(this.wt.get_file('d').read(), 'd2')
 
1447
        # Ambigious clean merge
 
1448
        self.assertEqual(this.wt.get_file('e').read(), 'e2')
 
1449
        # No change
 
1450
        self.assertEqual(this.wt.get_file('f').read(), 'f')
 
1451
        # Correct correct results when THIS == OTHER
 
1452
        self.assertEqual(this.wt.get_file('g').read(), 'g')
 
1453
        # Text conflict when THIS & OTHER are text and BASE is dir
 
1454
        self.assertEqual(this.wt.get_file('h').read(),
 
1455
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
 
1456
        self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
 
1457
                         '1\n2\n3\n4\n')
 
1458
        self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
 
1459
                         'h\ni\nj\nk\n')
 
1460
        self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
 
1461
        self.assertEqual(this.wt.get_file('i').read(),
 
1462
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
 
1463
        self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
 
1464
                         '1\n2\n3\n4\n')
 
1465
        self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
 
1466
                         'h\ni\nj\nk\n')
 
1467
        self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
 
1468
        modified = ['a', 'b', 'c', 'h', 'i']
 
1469
        merge_modified = this.wt.merge_modified()
 
1470
        self.assertSubset(merge_modified, modified)
 
1471
        self.assertEqual(len(merge_modified), len(modified))
 
1472
        file(this.wt.id2abspath('a'), 'wb').write('booga')
 
1473
        modified.pop(0)
 
1474
        merge_modified = this.wt.merge_modified()
 
1475
        self.assertSubset(merge_modified, modified)
 
1476
        self.assertEqual(len(merge_modified), len(modified))
 
1477
        this.wt.remove('b')
 
1478
        this.wt.revert()
 
1479
 
 
1480
    def test_file_merge(self):
 
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)
 
1486
        for tg in this, base, other:
 
1487
            tg.tt.new_directory('a', tg.root, 'a')
 
1488
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
 
1489
            tg.tt.new_file('c', tg.root, 'c', 'c')
 
1490
            tg.tt.new_symlink('d', tg.root, tg.name, 'd')
 
1491
        targets = ((base, 'base-e', 'base-f', None, None),
 
1492
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'),
 
1493
                   (other, 'other-e', None, 'other-g', 'other-h'))
 
1494
        for tg, e_target, f_target, g_target, h_target in targets:
 
1495
            for link, target in (('e', e_target), ('f', f_target),
 
1496
                                 ('g', g_target), ('h', h_target)):
 
1497
                if target is not None:
 
1498
                    tg.tt.new_symlink(link, tg.root, target, link)
 
1499
 
 
1500
        for tg in this, base, other:
 
1501
            tg.tt.apply()
 
1502
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
1503
        self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
 
1504
        self.assertIs(os.path.islink(this.wt.abspath('b')), True)
 
1505
        self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
 
1506
        for suffix in ('THIS', 'BASE', 'OTHER'):
 
1507
            self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
 
1508
        self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
 
1509
        self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
 
1510
        self.assertEqual(this.wt.id2path('f'), 'f.THIS')
 
1511
        self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
 
1512
        self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
 
1513
        self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
 
1514
        self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
 
1515
        self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
 
1516
        self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
 
1517
        self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
 
1518
        self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
 
1519
        self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
 
1520
        self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
 
1521
 
 
1522
    def test_filename_merge(self):
 
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')
 
1528
                                   for t in [base, this, other]]
 
1529
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
 
1530
                                   for t in [base, this, other]]
 
1531
        base.tt.new_directory('c', base_a, 'c')
 
1532
        this.tt.new_directory('c1', this_a, 'c')
 
1533
        other.tt.new_directory('c', other_b, 'c')
 
1534
 
 
1535
        base.tt.new_directory('d', base_a, 'd')
 
1536
        this.tt.new_directory('d1', this_b, 'd')
 
1537
        other.tt.new_directory('d', other_a, 'd')
 
1538
 
 
1539
        base.tt.new_directory('e', base_a, 'e')
 
1540
        this.tt.new_directory('e', this_a, 'e')
 
1541
        other.tt.new_directory('e1', other_b, 'e')
 
1542
 
 
1543
        base.tt.new_directory('f', base_a, 'f')
 
1544
        this.tt.new_directory('f1', this_b, 'f')
 
1545
        other.tt.new_directory('f1', other_b, 'f')
 
1546
 
 
1547
        for tg in [this, base, other]:
 
1548
            tg.tt.apply()
 
1549
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
1550
        self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
 
1551
        self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
 
1552
        self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
 
1553
        self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
 
1554
 
 
1555
    def test_filename_merge_conflicts(self):
 
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')
 
1561
                                   for t in [base, this, other]]
 
1562
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
 
1563
                                   for t in [base, this, other]]
 
1564
 
 
1565
        base.tt.new_file('g', base_a, 'g', 'g')
 
1566
        other.tt.new_file('g1', other_b, 'g1', 'g')
 
1567
 
 
1568
        base.tt.new_file('h', base_a, 'h', 'h')
 
1569
        this.tt.new_file('h1', this_b, 'h1', 'h')
 
1570
 
 
1571
        base.tt.new_file('i', base.root, 'i', 'i')
 
1572
        other.tt.new_directory('i1', this_b, 'i')
 
1573
 
 
1574
        for tg in [this, base, other]:
 
1575
            tg.tt.apply()
 
1576
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
1577
 
 
1578
        self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
 
1579
        self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
 
1580
        self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
 
1581
        self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
 
1582
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
 
1583
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
 
1584
        self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
 
1585
 
 
1586
 
 
1587
class TestBuildTree(tests.TestCaseWithTransport):
 
1588
 
 
1589
    def test_build_tree_with_symlinks(self):
 
1590
        self.requireFeature(SymlinkFeature)
 
1591
        os.mkdir('a')
 
1592
        a = BzrDir.create_standalone_workingtree('a')
 
1593
        os.mkdir('a/foo')
 
1594
        file('a/foo/bar', 'wb').write('contents')
 
1595
        os.symlink('a/foo/bar', 'a/foo/baz')
 
1596
        a.add(['foo', 'foo/bar', 'foo/baz'])
 
1597
        a.commit('initial commit')
 
1598
        b = BzrDir.create_standalone_workingtree('b')
 
1599
        basis = a.basis_tree()
 
1600
        basis.lock_read()
 
1601
        self.addCleanup(basis.unlock)
 
1602
        build_tree(basis, b)
 
1603
        self.assertIs(os.path.isdir('b/foo'), True)
 
1604
        self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
 
1605
        self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
 
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
 
 
1994
class MockTransform(object):
 
1995
 
 
1996
    def has_named_child(self, by_parent, parent_id, name):
 
1997
        for child_id in by_parent[parent_id]:
 
1998
            if child_id == '0':
 
1999
                if name == "name~":
 
2000
                    return True
 
2001
            elif name == "name.~%s~" % child_id:
 
2002
                return True
 
2003
        return False
 
2004
 
 
2005
 
 
2006
class MockEntry(object):
 
2007
    def __init__(self):
 
2008
        object.__init__(self)
 
2009
        self.name = "name"
 
2010
 
 
2011
 
 
2012
class TestGetBackupName(TestCase):
 
2013
    def test_get_backup_name(self):
 
2014
        tt = MockTransform()
 
2015
        name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
 
2016
        self.assertEqual(name, 'name.~1~')
 
2017
        name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
 
2018
        self.assertEqual(name, 'name.~2~')
 
2019
        name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
 
2020
        self.assertEqual(name, 'name.~1~')
 
2021
        name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
 
2022
        self.assertEqual(name, 'name.~1~')
 
2023
        name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
 
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))