~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-07-30 14:24:06 UTC
  • mfrom: (4576.1.1 export-to-dir)
  • Revision ID: pqm@pqm.ubuntu.com-20090730142406-wg8gmxpcjz4c1z00
(bialix) Allow 'bzr export' to export into an existing (but empty)
        directory

Show diffs side-by-side

added added

removed removed

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