~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Martin Pool
  • Author(s): Jari Aalto
  • Date: 2008-12-24 03:14:16 UTC
  • mto: This revision was merged to the branch mainline in revision 3919.
  • Revision ID: mbp@sourcefrog.net-20081224031416-krocx1r3fyu52t0j
In user guide, use 'PROJECT' as a metavariable not 'X-repo'

Show diffs side-by-side

added added

removed removed

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