/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Martin Pool
  • Date: 2010-01-15 07:00:49 UTC
  • mfrom: (4634.119.8 2.0)
  • mto: This revision was merged to the branch mainline in revision 4966.
  • Revision ID: mbp@sourcefrog.net-20100115070049-0cb4bi4lsrul2szs
Merge warning about cross-format fetch from 2.0

Show diffs side-by-side

added added

removed removed

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