~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-02-18 13:27:08 UTC
  • mfrom: (4011.4.3 ssh-hints)
  • Revision ID: pqm@pqm.ubuntu.com-20090218132708-okubrahz9exvae9r
(Jelmer) Point out bzr+ssh:// to the user when they use ssh://.

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