~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-02-01 13:53:24 UTC
  • mfrom: (4990.1.2 rm-obsolete)
  • Revision ID: pqm@pqm.ubuntu.com-20100201135324-cuhuolr97guf5xjp
(Jelmer) Remove obsolete (and broken) biobench and history2revfiles
        tools.

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
        # We could use
 
1927
        # self.addCleanup(filters._reset_registry, filters._reset_registry())
 
1928
        # below, but that looks a bit... hard to read even if it's exactly
 
1929
        # the same thing.
 
1930
        original_registry = filters._reset_registry()
 
1931
        def restore_registry():
 
1932
            filters._reset_registry(original_registry)
 
1933
        self.addCleanup(restore_registry)
 
1934
        def rot13(chunks, context=None):
 
1935
            return [''.join(chunks).encode('rot13')]
 
1936
        rot13filter = filters.ContentFilter(rot13, rot13)
 
1937
        filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
 
1938
        os.mkdir(self.test_home_dir + '/.bazaar')
 
1939
        rules_filename = self.test_home_dir + '/.bazaar/rules'
 
1940
        f = open(rules_filename, 'wb')
 
1941
        f.write('[name %s]\nrot13=yes\n' % (pattern,))
 
1942
        f.close()
 
1943
        def uninstall_rules():
 
1944
            os.remove(rules_filename)
 
1945
            rules.reset_rules()
 
1946
        self.addCleanup(uninstall_rules)
 
1947
        rules.reset_rules()
 
1948
 
 
1949
    def test_build_tree_content_filtered_files_are_not_hardlinked(self):
 
1950
        """build_tree will not hardlink files that have content filtering rules
 
1951
        applied to them (but will still hardlink other files from the same tree
 
1952
        if it can).
 
1953
        """
 
1954
        self.requireFeature(HardlinkFeature)
 
1955
        self.install_rot13_content_filter('file1')
 
1956
        source = self.create_ab_tree()
 
1957
        target = self.make_branch_and_tree('target')
 
1958
        revision_tree = source.basis_tree()
 
1959
        revision_tree.lock_read()
 
1960
        self.addCleanup(revision_tree.unlock)
 
1961
        build_tree(revision_tree, target, source, hardlink=True)
 
1962
        target.lock_read()
 
1963
        self.addCleanup(target.unlock)
 
1964
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
1965
        source_stat = os.stat('source/file1')
 
1966
        target_stat = os.stat('target/file1')
 
1967
        self.assertNotEqual(source_stat, target_stat)
 
1968
        source_stat = os.stat('source/file2')
 
1969
        target_stat = os.stat('target/file2')
 
1970
        self.assertEqualStat(source_stat, target_stat)
 
1971
 
 
1972
    def test_case_insensitive_build_tree_inventory(self):
 
1973
        if (tests.CaseInsensitiveFilesystemFeature.available()
 
1974
            or tests.CaseInsCasePresFilenameFeature.available()):
 
1975
            raise tests.UnavailableFeature('Fully case sensitive filesystem')
 
1976
        source = self.make_branch_and_tree('source')
 
1977
        self.build_tree(['source/file', 'source/FILE'])
 
1978
        source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
 
1979
        source.commit('added files')
 
1980
        # Don't try this at home, kids!
 
1981
        # Force the tree to report that it is case insensitive
 
1982
        target = self.make_branch_and_tree('target')
 
1983
        target.case_sensitive = False
 
1984
        build_tree(source.basis_tree(), target, source, delta_from_tree=True)
 
1985
        self.assertEqual('file.moved', target.id2path('lower-id'))
 
1986
        self.assertEqual('FILE', target.id2path('upper-id'))
 
1987
 
 
1988
 
 
1989
class TestCommitTransform(tests.TestCaseWithTransport):
 
1990
 
 
1991
    def get_branch(self):
 
1992
        tree = self.make_branch_and_tree('tree')
 
1993
        tree.lock_write()
 
1994
        self.addCleanup(tree.unlock)
 
1995
        tree.commit('empty commit')
 
1996
        return tree.branch
 
1997
 
 
1998
    def get_branch_and_transform(self):
 
1999
        branch = self.get_branch()
 
2000
        tt = TransformPreview(branch.basis_tree())
 
2001
        self.addCleanup(tt.finalize)
 
2002
        return branch, tt
 
2003
 
 
2004
    def test_commit_wrong_basis(self):
 
2005
        branch = self.get_branch()
 
2006
        basis = branch.repository.revision_tree(
 
2007
            _mod_revision.NULL_REVISION)
 
2008
        tt = TransformPreview(basis)
 
2009
        self.addCleanup(tt.finalize)
 
2010
        e = self.assertRaises(ValueError, tt.commit, branch, '')
 
2011
        self.assertEqual('TreeTransform not based on branch basis: null:',
 
2012
                         str(e))
 
2013
 
 
2014
    def test_empy_commit(self):
 
2015
        branch, tt = self.get_branch_and_transform()
 
2016
        rev = tt.commit(branch, 'my message')
 
2017
        self.assertEqual(2, branch.revno())
 
2018
        repo = branch.repository
 
2019
        self.assertEqual('my message', repo.get_revision(rev).message)
 
2020
 
 
2021
    def test_merge_parents(self):
 
2022
        branch, tt = self.get_branch_and_transform()
 
2023
        rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
 
2024
        self.assertEqual(['rev1b', 'rev1c'],
 
2025
                         branch.basis_tree().get_parent_ids()[1:])
 
2026
 
 
2027
    def test_first_commit(self):
 
2028
        branch = self.make_branch('branch')
 
2029
        branch.lock_write()
 
2030
        self.addCleanup(branch.unlock)
 
2031
        tt = TransformPreview(branch.basis_tree())
 
2032
        self.addCleanup(tt.finalize)
 
2033
        tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
 
2034
        rev = tt.commit(branch, 'my message')
 
2035
        self.assertEqual([], branch.basis_tree().get_parent_ids())
 
2036
        self.assertNotEqual(_mod_revision.NULL_REVISION,
 
2037
                            branch.last_revision())
 
2038
 
 
2039
    def test_first_commit_with_merge_parents(self):
 
2040
        branch = self.make_branch('branch')
 
2041
        branch.lock_write()
 
2042
        self.addCleanup(branch.unlock)
 
2043
        tt = TransformPreview(branch.basis_tree())
 
2044
        self.addCleanup(tt.finalize)
 
2045
        e = self.assertRaises(ValueError, tt.commit, branch,
 
2046
                          'my message', ['rev1b-id'])
 
2047
        self.assertEqual('Cannot supply merge parents for first commit.',
 
2048
                         str(e))
 
2049
        self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
 
2050
 
 
2051
    def test_add_files(self):
 
2052
        branch, tt = self.get_branch_and_transform()
 
2053
        tt.new_file('file', tt.root, 'contents', 'file-id')
 
2054
        trans_id = tt.new_directory('dir', tt.root, 'dir-id')
 
2055
        if SymlinkFeature.available():
 
2056
            tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
 
2057
        rev = tt.commit(branch, 'message')
 
2058
        tree = branch.basis_tree()
 
2059
        self.assertEqual('file', tree.id2path('file-id'))
 
2060
        self.assertEqual('contents', tree.get_file_text('file-id'))
 
2061
        self.assertEqual('dir', tree.id2path('dir-id'))
 
2062
        if SymlinkFeature.available():
 
2063
            self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
 
2064
            self.assertEqual('target', tree.get_symlink_target('symlink-id'))
 
2065
 
 
2066
    def test_add_unversioned(self):
 
2067
        branch, tt = self.get_branch_and_transform()
 
2068
        tt.new_file('file', tt.root, 'contents')
 
2069
        self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
 
2070
                          'message', strict=True)
 
2071
 
 
2072
    def test_modify_strict(self):
 
2073
        branch, tt = self.get_branch_and_transform()
 
2074
        tt.new_file('file', tt.root, 'contents', 'file-id')
 
2075
        tt.commit(branch, 'message', strict=True)
 
2076
        tt = TransformPreview(branch.basis_tree())
 
2077
        self.addCleanup(tt.finalize)
 
2078
        trans_id = tt.trans_id_file_id('file-id')
 
2079
        tt.delete_contents(trans_id)
 
2080
        tt.create_file('contents', trans_id)
 
2081
        tt.commit(branch, 'message', strict=True)
 
2082
 
 
2083
    def test_commit_malformed(self):
 
2084
        """Committing a malformed transform should raise an exception.
 
2085
 
 
2086
        In this case, we are adding a file without adding its parent.
 
2087
        """
 
2088
        branch, tt = self.get_branch_and_transform()
 
2089
        parent_id = tt.trans_id_file_id('parent-id')
 
2090
        tt.new_file('file', parent_id, 'contents', 'file-id')
 
2091
        self.assertRaises(errors.MalformedTransform, tt.commit, branch,
 
2092
                          'message')
 
2093
 
 
2094
 
 
2095
class MockTransform(object):
 
2096
 
 
2097
    def has_named_child(self, by_parent, parent_id, name):
 
2098
        for child_id in by_parent[parent_id]:
 
2099
            if child_id == '0':
 
2100
                if name == "name~":
 
2101
                    return True
 
2102
            elif name == "name.~%s~" % child_id:
 
2103
                return True
 
2104
        return False
 
2105
 
 
2106
 
 
2107
class MockEntry(object):
 
2108
    def __init__(self):
 
2109
        object.__init__(self)
 
2110
        self.name = "name"
 
2111
 
 
2112
 
 
2113
class TestGetBackupName(TestCase):
 
2114
    def test_get_backup_name(self):
 
2115
        tt = MockTransform()
 
2116
        name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
 
2117
        self.assertEqual(name, 'name.~1~')
 
2118
        name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
 
2119
        self.assertEqual(name, 'name.~2~')
 
2120
        name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
 
2121
        self.assertEqual(name, 'name.~1~')
 
2122
        name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
 
2123
        self.assertEqual(name, 'name.~1~')
 
2124
        name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
 
2125
        self.assertEqual(name, 'name.~4~')
 
2126
 
 
2127
 
 
2128
class TestFileMover(tests.TestCaseWithTransport):
 
2129
 
 
2130
    def test_file_mover(self):
 
2131
        self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
 
2132
        mover = _FileMover()
 
2133
        mover.rename('a', 'q')
 
2134
        self.failUnlessExists('q')
 
2135
        self.failIfExists('a')
 
2136
        self.failUnlessExists('q/b')
 
2137
        self.failUnlessExists('c')
 
2138
        self.failUnlessExists('c/d')
 
2139
 
 
2140
    def test_pre_delete_rollback(self):
 
2141
        self.build_tree(['a/'])
 
2142
        mover = _FileMover()
 
2143
        mover.pre_delete('a', 'q')
 
2144
        self.failUnlessExists('q')
 
2145
        self.failIfExists('a')
 
2146
        mover.rollback()
 
2147
        self.failIfExists('q')
 
2148
        self.failUnlessExists('a')
 
2149
 
 
2150
    def test_apply_deletions(self):
 
2151
        self.build_tree(['a/', 'b/'])
 
2152
        mover = _FileMover()
 
2153
        mover.pre_delete('a', 'q')
 
2154
        mover.pre_delete('b', 'r')
 
2155
        self.failUnlessExists('q')
 
2156
        self.failUnlessExists('r')
 
2157
        self.failIfExists('a')
 
2158
        self.failIfExists('b')
 
2159
        mover.apply_deletions()
 
2160
        self.failIfExists('q')
 
2161
        self.failIfExists('r')
 
2162
        self.failIfExists('a')
 
2163
        self.failIfExists('b')
 
2164
 
 
2165
    def test_file_mover_rollback(self):
 
2166
        self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
 
2167
        mover = _FileMover()
 
2168
        mover.rename('c/d', 'c/f')
 
2169
        mover.rename('c/e', 'c/d')
 
2170
        try:
 
2171
            mover.rename('a', 'c')
 
2172
        except errors.FileExists, e:
 
2173
            mover.rollback()
 
2174
        self.failUnlessExists('a')
 
2175
        self.failUnlessExists('c/d')
 
2176
 
 
2177
 
 
2178
class Bogus(Exception):
 
2179
    pass
 
2180
 
 
2181
 
 
2182
class TestTransformRollback(tests.TestCaseWithTransport):
 
2183
 
 
2184
    class ExceptionFileMover(_FileMover):
 
2185
 
 
2186
        def __init__(self, bad_source=None, bad_target=None):
 
2187
            _FileMover.__init__(self)
 
2188
            self.bad_source = bad_source
 
2189
            self.bad_target = bad_target
 
2190
 
 
2191
        def rename(self, source, target):
 
2192
            if (self.bad_source is not None and
 
2193
                source.endswith(self.bad_source)):
 
2194
                raise Bogus
 
2195
            elif (self.bad_target is not None and
 
2196
                target.endswith(self.bad_target)):
 
2197
                raise Bogus
 
2198
            else:
 
2199
                _FileMover.rename(self, source, target)
 
2200
 
 
2201
    def test_rollback_rename(self):
 
2202
        tree = self.make_branch_and_tree('.')
 
2203
        self.build_tree(['a/', 'a/b'])
 
2204
        tt = TreeTransform(tree)
 
2205
        self.addCleanup(tt.finalize)
 
2206
        a_id = tt.trans_id_tree_path('a')
 
2207
        tt.adjust_path('c', tt.root, a_id)
 
2208
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
 
2209
        self.assertRaises(Bogus, tt.apply,
 
2210
                          _mover=self.ExceptionFileMover(bad_source='a'))
 
2211
        self.failUnlessExists('a')
 
2212
        self.failUnlessExists('a/b')
 
2213
        tt.apply()
 
2214
        self.failUnlessExists('c')
 
2215
        self.failUnlessExists('c/d')
 
2216
 
 
2217
    def test_rollback_rename_into_place(self):
 
2218
        tree = self.make_branch_and_tree('.')
 
2219
        self.build_tree(['a/', 'a/b'])
 
2220
        tt = TreeTransform(tree)
 
2221
        self.addCleanup(tt.finalize)
 
2222
        a_id = tt.trans_id_tree_path('a')
 
2223
        tt.adjust_path('c', tt.root, a_id)
 
2224
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
 
2225
        self.assertRaises(Bogus, tt.apply,
 
2226
                          _mover=self.ExceptionFileMover(bad_target='c/d'))
 
2227
        self.failUnlessExists('a')
 
2228
        self.failUnlessExists('a/b')
 
2229
        tt.apply()
 
2230
        self.failUnlessExists('c')
 
2231
        self.failUnlessExists('c/d')
 
2232
 
 
2233
    def test_rollback_deletion(self):
 
2234
        tree = self.make_branch_and_tree('.')
 
2235
        self.build_tree(['a/', 'a/b'])
 
2236
        tt = TreeTransform(tree)
 
2237
        self.addCleanup(tt.finalize)
 
2238
        a_id = tt.trans_id_tree_path('a')
 
2239
        tt.delete_contents(a_id)
 
2240
        tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
 
2241
        self.assertRaises(Bogus, tt.apply,
 
2242
                          _mover=self.ExceptionFileMover(bad_target='d'))
 
2243
        self.failUnlessExists('a')
 
2244
        self.failUnlessExists('a/b')
 
2245
 
 
2246
    def test_resolve_no_parent(self):
 
2247
        wt = self.make_branch_and_tree('.')
 
2248
        tt = TreeTransform(wt)
 
2249
        self.addCleanup(tt.finalize)
 
2250
        parent = tt.trans_id_file_id('parent-id')
 
2251
        tt.new_file('file', parent, 'Contents')
 
2252
        resolve_conflicts(tt)
 
2253
 
 
2254
 
 
2255
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
 
2256
                  ('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
 
2257
                  (False, False))
 
2258
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
 
2259
              ('', ''), ('directory', 'directory'), (False, None))
 
2260
 
 
2261
 
 
2262
class TestTransformPreview(tests.TestCaseWithTransport):
 
2263
 
 
2264
    def create_tree(self):
 
2265
        tree = self.make_branch_and_tree('.')
 
2266
        self.build_tree_contents([('a', 'content 1')])
 
2267
        tree.set_root_id('TREE_ROOT')
 
2268
        tree.add('a', 'a-id')
 
2269
        tree.commit('rev1', rev_id='rev1')
 
2270
        return tree.branch.repository.revision_tree('rev1')
 
2271
 
 
2272
    def get_empty_preview(self):
 
2273
        repository = self.make_repository('repo')
 
2274
        tree = repository.revision_tree(_mod_revision.NULL_REVISION)
 
2275
        preview = TransformPreview(tree)
 
2276
        self.addCleanup(preview.finalize)
 
2277
        return preview
 
2278
 
 
2279
    def test_transform_preview(self):
 
2280
        revision_tree = self.create_tree()
 
2281
        preview = TransformPreview(revision_tree)
 
2282
        self.addCleanup(preview.finalize)
 
2283
 
 
2284
    def test_transform_preview_tree(self):
 
2285
        revision_tree = self.create_tree()
 
2286
        preview = TransformPreview(revision_tree)
 
2287
        self.addCleanup(preview.finalize)
 
2288
        preview.get_preview_tree()
 
2289
 
 
2290
    def test_transform_new_file(self):
 
2291
        revision_tree = self.create_tree()
 
2292
        preview = TransformPreview(revision_tree)
 
2293
        self.addCleanup(preview.finalize)
 
2294
        preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
 
2295
        preview_tree = preview.get_preview_tree()
 
2296
        self.assertEqual(preview_tree.kind('file2-id'), 'file')
 
2297
        self.assertEqual(
 
2298
            preview_tree.get_file('file2-id').read(), 'content B\n')
 
2299
 
 
2300
    def test_diff_preview_tree(self):
 
2301
        revision_tree = self.create_tree()
 
2302
        preview = TransformPreview(revision_tree)
 
2303
        self.addCleanup(preview.finalize)
 
2304
        preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
 
2305
        preview_tree = preview.get_preview_tree()
 
2306
        out = StringIO()
 
2307
        show_diff_trees(revision_tree, preview_tree, out)
 
2308
        lines = out.getvalue().splitlines()
 
2309
        self.assertEqual(lines[0], "=== added file 'file2'")
 
2310
        # 3 lines of diff administrivia
 
2311
        self.assertEqual(lines[4], "+content B")
 
2312
 
 
2313
    def test_transform_conflicts(self):
 
2314
        revision_tree = self.create_tree()
 
2315
        preview = TransformPreview(revision_tree)
 
2316
        self.addCleanup(preview.finalize)
 
2317
        preview.new_file('a', preview.root, 'content 2')
 
2318
        resolve_conflicts(preview)
 
2319
        trans_id = preview.trans_id_file_id('a-id')
 
2320
        self.assertEqual('a.moved', preview.final_name(trans_id))
 
2321
 
 
2322
    def get_tree_and_preview_tree(self):
 
2323
        revision_tree = self.create_tree()
 
2324
        preview = TransformPreview(revision_tree)
 
2325
        self.addCleanup(preview.finalize)
 
2326
        a_trans_id = preview.trans_id_file_id('a-id')
 
2327
        preview.delete_contents(a_trans_id)
 
2328
        preview.create_file('b content', a_trans_id)
 
2329
        preview_tree = preview.get_preview_tree()
 
2330
        return revision_tree, preview_tree
 
2331
 
 
2332
    def test_iter_changes(self):
 
2333
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2334
        root = revision_tree.inventory.root.file_id
 
2335
        self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
 
2336
                          (root, root), ('a', 'a'), ('file', 'file'),
 
2337
                          (False, False))],
 
2338
                          list(preview_tree.iter_changes(revision_tree)))
 
2339
 
 
2340
    def test_include_unchanged_succeeds(self):
 
2341
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2342
        changes = preview_tree.iter_changes(revision_tree,
 
2343
                                            include_unchanged=True)
 
2344
        root = revision_tree.inventory.root.file_id
 
2345
 
 
2346
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
 
2347
 
 
2348
    def test_specific_files(self):
 
2349
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2350
        changes = preview_tree.iter_changes(revision_tree,
 
2351
                                            specific_files=[''])
 
2352
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
 
2353
 
 
2354
    def test_want_unversioned(self):
 
2355
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2356
        changes = preview_tree.iter_changes(revision_tree,
 
2357
                                            want_unversioned=True)
 
2358
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
 
2359
 
 
2360
    def test_ignore_extra_trees_no_specific_files(self):
 
2361
        # extra_trees is harmless without specific_files, so we'll silently
 
2362
        # accept it, even though we won't use it.
 
2363
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2364
        preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
 
2365
 
 
2366
    def test_ignore_require_versioned_no_specific_files(self):
 
2367
        # require_versioned is meaningless without specific_files.
 
2368
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2369
        preview_tree.iter_changes(revision_tree, require_versioned=False)
 
2370
 
 
2371
    def test_ignore_pb(self):
 
2372
        # pb could be supported, but TT.iter_changes doesn't support it.
 
2373
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2374
        preview_tree.iter_changes(revision_tree, pb=progress.DummyProgress())
 
2375
 
 
2376
    def test_kind(self):
 
2377
        revision_tree = self.create_tree()
 
2378
        preview = TransformPreview(revision_tree)
 
2379
        self.addCleanup(preview.finalize)
 
2380
        preview.new_file('file', preview.root, 'contents', 'file-id')
 
2381
        preview.new_directory('directory', preview.root, 'dir-id')
 
2382
        preview_tree = preview.get_preview_tree()
 
2383
        self.assertEqual('file', preview_tree.kind('file-id'))
 
2384
        self.assertEqual('directory', preview_tree.kind('dir-id'))
 
2385
 
 
2386
    def test_get_file_mtime(self):
 
2387
        preview = self.get_empty_preview()
 
2388
        file_trans_id = preview.new_file('file', preview.root, 'contents',
 
2389
                                         'file-id')
 
2390
        limbo_path = preview._limbo_name(file_trans_id)
 
2391
        preview_tree = preview.get_preview_tree()
 
2392
        self.assertEqual(os.stat(limbo_path).st_mtime,
 
2393
                         preview_tree.get_file_mtime('file-id'))
 
2394
 
 
2395
    def test_get_file_mtime_renamed(self):
 
2396
        work_tree = self.make_branch_and_tree('tree')
 
2397
        self.build_tree(['tree/file'])
 
2398
        work_tree.add('file', 'file-id')
 
2399
        preview = TransformPreview(work_tree)
 
2400
        self.addCleanup(preview.finalize)
 
2401
        file_trans_id = preview.trans_id_tree_file_id('file-id')
 
2402
        preview.adjust_path('renamed', preview.root, file_trans_id)
 
2403
        preview_tree = preview.get_preview_tree()
 
2404
        preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
 
2405
        work_mtime = work_tree.get_file_mtime('file-id', 'file')
 
2406
 
 
2407
    def test_get_file(self):
 
2408
        preview = self.get_empty_preview()
 
2409
        preview.new_file('file', preview.root, 'contents', 'file-id')
 
2410
        preview_tree = preview.get_preview_tree()
 
2411
        tree_file = preview_tree.get_file('file-id')
 
2412
        try:
 
2413
            self.assertEqual('contents', tree_file.read())
 
2414
        finally:
 
2415
            tree_file.close()
 
2416
 
 
2417
    def test_get_symlink_target(self):
 
2418
        self.requireFeature(SymlinkFeature)
 
2419
        preview = self.get_empty_preview()
 
2420
        preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
 
2421
        preview_tree = preview.get_preview_tree()
 
2422
        self.assertEqual('target',
 
2423
                         preview_tree.get_symlink_target('symlink-id'))
 
2424
 
 
2425
    def test_all_file_ids(self):
 
2426
        tree = self.make_branch_and_tree('tree')
 
2427
        self.build_tree(['tree/a', 'tree/b', 'tree/c'])
 
2428
        tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
 
2429
        preview = TransformPreview(tree)
 
2430
        self.addCleanup(preview.finalize)
 
2431
        preview.unversion_file(preview.trans_id_file_id('b-id'))
 
2432
        c_trans_id = preview.trans_id_file_id('c-id')
 
2433
        preview.unversion_file(c_trans_id)
 
2434
        preview.version_file('c-id', c_trans_id)
 
2435
        preview_tree = preview.get_preview_tree()
 
2436
        self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
 
2437
                         preview_tree.all_file_ids())
 
2438
 
 
2439
    def test_path2id_deleted_unchanged(self):
 
2440
        tree = self.make_branch_and_tree('tree')
 
2441
        self.build_tree(['tree/unchanged', 'tree/deleted'])
 
2442
        tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
 
2443
        preview = TransformPreview(tree)
 
2444
        self.addCleanup(preview.finalize)
 
2445
        preview.unversion_file(preview.trans_id_file_id('deleted-id'))
 
2446
        preview_tree = preview.get_preview_tree()
 
2447
        self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
 
2448
        self.assertIs(None, preview_tree.path2id('deleted'))
 
2449
 
 
2450
    def test_path2id_created(self):
 
2451
        tree = self.make_branch_and_tree('tree')
 
2452
        self.build_tree(['tree/unchanged'])
 
2453
        tree.add(['unchanged'], ['unchanged-id'])
 
2454
        preview = TransformPreview(tree)
 
2455
        self.addCleanup(preview.finalize)
 
2456
        preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
 
2457
            'contents', 'new-id')
 
2458
        preview_tree = preview.get_preview_tree()
 
2459
        self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
 
2460
 
 
2461
    def test_path2id_moved(self):
 
2462
        tree = self.make_branch_and_tree('tree')
 
2463
        self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
 
2464
        tree.add(['old_parent', 'old_parent/child'],
 
2465
                 ['old_parent-id', 'child-id'])
 
2466
        preview = TransformPreview(tree)
 
2467
        self.addCleanup(preview.finalize)
 
2468
        new_parent = preview.new_directory('new_parent', preview.root,
 
2469
                                           'new_parent-id')
 
2470
        preview.adjust_path('child', new_parent,
 
2471
                            preview.trans_id_file_id('child-id'))
 
2472
        preview_tree = preview.get_preview_tree()
 
2473
        self.assertIs(None, preview_tree.path2id('old_parent/child'))
 
2474
        self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
 
2475
 
 
2476
    def test_path2id_renamed_parent(self):
 
2477
        tree = self.make_branch_and_tree('tree')
 
2478
        self.build_tree(['tree/old_name/', 'tree/old_name/child'])
 
2479
        tree.add(['old_name', 'old_name/child'],
 
2480
                 ['parent-id', 'child-id'])
 
2481
        preview = TransformPreview(tree)
 
2482
        self.addCleanup(preview.finalize)
 
2483
        preview.adjust_path('new_name', preview.root,
 
2484
                            preview.trans_id_file_id('parent-id'))
 
2485
        preview_tree = preview.get_preview_tree()
 
2486
        self.assertIs(None, preview_tree.path2id('old_name/child'))
 
2487
        self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
 
2488
 
 
2489
    def assertMatchingIterEntries(self, tt, specific_file_ids=None):
 
2490
        preview_tree = tt.get_preview_tree()
 
2491
        preview_result = list(preview_tree.iter_entries_by_dir(
 
2492
                              specific_file_ids))
 
2493
        tree = tt._tree
 
2494
        tt.apply()
 
2495
        actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
 
2496
        self.assertEqual(actual_result, preview_result)
 
2497
 
 
2498
    def test_iter_entries_by_dir_new(self):
 
2499
        tree = self.make_branch_and_tree('tree')
 
2500
        tt = TreeTransform(tree)
 
2501
        tt.new_file('new', tt.root, 'contents', 'new-id')
 
2502
        self.assertMatchingIterEntries(tt)
 
2503
 
 
2504
    def test_iter_entries_by_dir_deleted(self):
 
2505
        tree = self.make_branch_and_tree('tree')
 
2506
        self.build_tree(['tree/deleted'])
 
2507
        tree.add('deleted', 'deleted-id')
 
2508
        tt = TreeTransform(tree)
 
2509
        tt.delete_contents(tt.trans_id_file_id('deleted-id'))
 
2510
        self.assertMatchingIterEntries(tt)
 
2511
 
 
2512
    def test_iter_entries_by_dir_unversioned(self):
 
2513
        tree = self.make_branch_and_tree('tree')
 
2514
        self.build_tree(['tree/removed'])
 
2515
        tree.add('removed', 'removed-id')
 
2516
        tt = TreeTransform(tree)
 
2517
        tt.unversion_file(tt.trans_id_file_id('removed-id'))
 
2518
        self.assertMatchingIterEntries(tt)
 
2519
 
 
2520
    def test_iter_entries_by_dir_moved(self):
 
2521
        tree = self.make_branch_and_tree('tree')
 
2522
        self.build_tree(['tree/moved', 'tree/new_parent/'])
 
2523
        tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
 
2524
        tt = TreeTransform(tree)
 
2525
        tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
 
2526
                       tt.trans_id_file_id('moved-id'))
 
2527
        self.assertMatchingIterEntries(tt)
 
2528
 
 
2529
    def test_iter_entries_by_dir_specific_file_ids(self):
 
2530
        tree = self.make_branch_and_tree('tree')
 
2531
        tree.set_root_id('tree-root-id')
 
2532
        self.build_tree(['tree/parent/', 'tree/parent/child'])
 
2533
        tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
 
2534
        tt = TreeTransform(tree)
 
2535
        self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
 
2536
 
 
2537
    def test_symlink_content_summary(self):
 
2538
        self.requireFeature(SymlinkFeature)
 
2539
        preview = self.get_empty_preview()
 
2540
        preview.new_symlink('path', preview.root, 'target', 'path-id')
 
2541
        summary = preview.get_preview_tree().path_content_summary('path')
 
2542
        self.assertEqual(('symlink', None, None, 'target'), summary)
 
2543
 
 
2544
    def test_missing_content_summary(self):
 
2545
        preview = self.get_empty_preview()
 
2546
        summary = preview.get_preview_tree().path_content_summary('path')
 
2547
        self.assertEqual(('missing', None, None, None), summary)
 
2548
 
 
2549
    def test_deleted_content_summary(self):
 
2550
        tree = self.make_branch_and_tree('tree')
 
2551
        self.build_tree(['tree/path/'])
 
2552
        tree.add('path')
 
2553
        preview = TransformPreview(tree)
 
2554
        self.addCleanup(preview.finalize)
 
2555
        preview.delete_contents(preview.trans_id_tree_path('path'))
 
2556
        summary = preview.get_preview_tree().path_content_summary('path')
 
2557
        self.assertEqual(('missing', None, None, None), summary)
 
2558
 
 
2559
    def test_file_content_summary_executable(self):
 
2560
        preview = self.get_empty_preview()
 
2561
        path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
 
2562
        preview.set_executability(True, path_id)
 
2563
        summary = preview.get_preview_tree().path_content_summary('path')
 
2564
        self.assertEqual(4, len(summary))
 
2565
        self.assertEqual('file', summary[0])
 
2566
        # size must be known
 
2567
        self.assertEqual(len('contents'), summary[1])
 
2568
        # executable
 
2569
        self.assertEqual(True, summary[2])
 
2570
        # will not have hash (not cheap to determine)
 
2571
        self.assertIs(None, summary[3])
 
2572
 
 
2573
    def test_change_executability(self):
 
2574
        tree = self.make_branch_and_tree('tree')
 
2575
        self.build_tree(['tree/path'])
 
2576
        tree.add('path')
 
2577
        preview = TransformPreview(tree)
 
2578
        self.addCleanup(preview.finalize)
 
2579
        path_id = preview.trans_id_tree_path('path')
 
2580
        preview.set_executability(True, path_id)
 
2581
        summary = preview.get_preview_tree().path_content_summary('path')
 
2582
        self.assertEqual(True, summary[2])
 
2583
 
 
2584
    def test_file_content_summary_non_exec(self):
 
2585
        preview = self.get_empty_preview()
 
2586
        preview.new_file('path', preview.root, 'contents', 'path-id')
 
2587
        summary = preview.get_preview_tree().path_content_summary('path')
 
2588
        self.assertEqual(4, len(summary))
 
2589
        self.assertEqual('file', summary[0])
 
2590
        # size must be known
 
2591
        self.assertEqual(len('contents'), summary[1])
 
2592
        # not executable
 
2593
        self.assertEqual(False, summary[2])
 
2594
        # will not have hash (not cheap to determine)
 
2595
        self.assertIs(None, summary[3])
 
2596
 
 
2597
    def test_dir_content_summary(self):
 
2598
        preview = self.get_empty_preview()
 
2599
        preview.new_directory('path', preview.root, 'path-id')
 
2600
        summary = preview.get_preview_tree().path_content_summary('path')
 
2601
        self.assertEqual(('directory', None, None, None), summary)
 
2602
 
 
2603
    def test_tree_content_summary(self):
 
2604
        preview = self.get_empty_preview()
 
2605
        path = preview.new_directory('path', preview.root, 'path-id')
 
2606
        preview.set_tree_reference('rev-1', path)
 
2607
        summary = preview.get_preview_tree().path_content_summary('path')
 
2608
        self.assertEqual(4, len(summary))
 
2609
        self.assertEqual('tree-reference', summary[0])
 
2610
 
 
2611
    def test_annotate(self):
 
2612
        tree = self.make_branch_and_tree('tree')
 
2613
        self.build_tree_contents([('tree/file', 'a\n')])
 
2614
        tree.add('file', 'file-id')
 
2615
        tree.commit('a', rev_id='one')
 
2616
        self.build_tree_contents([('tree/file', 'a\nb\n')])
 
2617
        preview = TransformPreview(tree)
 
2618
        self.addCleanup(preview.finalize)
 
2619
        file_trans_id = preview.trans_id_file_id('file-id')
 
2620
        preview.delete_contents(file_trans_id)
 
2621
        preview.create_file('a\nb\nc\n', file_trans_id)
 
2622
        preview_tree = preview.get_preview_tree()
 
2623
        expected = [
 
2624
            ('one', 'a\n'),
 
2625
            ('me:', 'b\n'),
 
2626
            ('me:', 'c\n'),
 
2627
        ]
 
2628
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2629
        self.assertEqual(expected, annotation)
 
2630
 
 
2631
    def test_annotate_missing(self):
 
2632
        preview = self.get_empty_preview()
 
2633
        preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
 
2634
        preview_tree = preview.get_preview_tree()
 
2635
        expected = [
 
2636
            ('me:', 'a\n'),
 
2637
            ('me:', 'b\n'),
 
2638
            ('me:', 'c\n'),
 
2639
         ]
 
2640
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2641
        self.assertEqual(expected, annotation)
 
2642
 
 
2643
    def test_annotate_rename(self):
 
2644
        tree = self.make_branch_and_tree('tree')
 
2645
        self.build_tree_contents([('tree/file', 'a\n')])
 
2646
        tree.add('file', 'file-id')
 
2647
        tree.commit('a', rev_id='one')
 
2648
        preview = TransformPreview(tree)
 
2649
        self.addCleanup(preview.finalize)
 
2650
        file_trans_id = preview.trans_id_file_id('file-id')
 
2651
        preview.adjust_path('newname', preview.root, file_trans_id)
 
2652
        preview_tree = preview.get_preview_tree()
 
2653
        expected = [
 
2654
            ('one', 'a\n'),
 
2655
        ]
 
2656
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2657
        self.assertEqual(expected, annotation)
 
2658
 
 
2659
    def test_annotate_deleted(self):
 
2660
        tree = self.make_branch_and_tree('tree')
 
2661
        self.build_tree_contents([('tree/file', 'a\n')])
 
2662
        tree.add('file', 'file-id')
 
2663
        tree.commit('a', rev_id='one')
 
2664
        self.build_tree_contents([('tree/file', 'a\nb\n')])
 
2665
        preview = TransformPreview(tree)
 
2666
        self.addCleanup(preview.finalize)
 
2667
        file_trans_id = preview.trans_id_file_id('file-id')
 
2668
        preview.delete_contents(file_trans_id)
 
2669
        preview_tree = preview.get_preview_tree()
 
2670
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2671
        self.assertIs(None, annotation)
 
2672
 
 
2673
    def test_stored_kind(self):
 
2674
        preview = self.get_empty_preview()
 
2675
        preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
 
2676
        preview_tree = preview.get_preview_tree()
 
2677
        self.assertEqual('file', preview_tree.stored_kind('file-id'))
 
2678
 
 
2679
    def test_is_executable(self):
 
2680
        preview = self.get_empty_preview()
 
2681
        preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
 
2682
        preview.set_executability(True, preview.trans_id_file_id('file-id'))
 
2683
        preview_tree = preview.get_preview_tree()
 
2684
        self.assertEqual(True, preview_tree.is_executable('file-id'))
 
2685
 
 
2686
    def test_get_set_parent_ids(self):
 
2687
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2688
        self.assertEqual([], preview_tree.get_parent_ids())
 
2689
        preview_tree.set_parent_ids(['rev-1'])
 
2690
        self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
 
2691
 
 
2692
    def test_plan_file_merge(self):
 
2693
        work_a = self.make_branch_and_tree('wta')
 
2694
        self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
 
2695
        work_a.add('file', 'file-id')
 
2696
        base_id = work_a.commit('base version')
 
2697
        tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
 
2698
        preview = TransformPreview(work_a)
 
2699
        self.addCleanup(preview.finalize)
 
2700
        trans_id = preview.trans_id_file_id('file-id')
 
2701
        preview.delete_contents(trans_id)
 
2702
        preview.create_file('b\nc\nd\ne\n', trans_id)
 
2703
        self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
 
2704
        tree_a = preview.get_preview_tree()
 
2705
        tree_a.set_parent_ids([base_id])
 
2706
        self.assertEqual([
 
2707
            ('killed-a', 'a\n'),
 
2708
            ('killed-b', 'b\n'),
 
2709
            ('unchanged', 'c\n'),
 
2710
            ('unchanged', 'd\n'),
 
2711
            ('new-a', 'e\n'),
 
2712
            ('new-b', 'f\n'),
 
2713
        ], list(tree_a.plan_file_merge('file-id', tree_b)))
 
2714
 
 
2715
    def test_plan_file_merge_revision_tree(self):
 
2716
        work_a = self.make_branch_and_tree('wta')
 
2717
        self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
 
2718
        work_a.add('file', 'file-id')
 
2719
        base_id = work_a.commit('base version')
 
2720
        tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
 
2721
        preview = TransformPreview(work_a.basis_tree())
 
2722
        self.addCleanup(preview.finalize)
 
2723
        trans_id = preview.trans_id_file_id('file-id')
 
2724
        preview.delete_contents(trans_id)
 
2725
        preview.create_file('b\nc\nd\ne\n', trans_id)
 
2726
        self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
 
2727
        tree_a = preview.get_preview_tree()
 
2728
        tree_a.set_parent_ids([base_id])
 
2729
        self.assertEqual([
 
2730
            ('killed-a', 'a\n'),
 
2731
            ('killed-b', 'b\n'),
 
2732
            ('unchanged', 'c\n'),
 
2733
            ('unchanged', 'd\n'),
 
2734
            ('new-a', 'e\n'),
 
2735
            ('new-b', 'f\n'),
 
2736
        ], list(tree_a.plan_file_merge('file-id', tree_b)))
 
2737
 
 
2738
    def test_walkdirs(self):
 
2739
        preview = self.get_empty_preview()
 
2740
        root = preview.new_directory('', ROOT_PARENT, 'tree-root')
 
2741
        # FIXME: new_directory should mark root.
 
2742
        preview.fixup_new_roots()
 
2743
        preview_tree = preview.get_preview_tree()
 
2744
        file_trans_id = preview.new_file('a', preview.root, 'contents',
 
2745
                                         'a-id')
 
2746
        expected = [(('', 'tree-root'),
 
2747
                    [('a', 'a', 'file', None, 'a-id', 'file')])]
 
2748
        self.assertEqual(expected, list(preview_tree.walkdirs()))
 
2749
 
 
2750
    def test_extras(self):
 
2751
        work_tree = self.make_branch_and_tree('tree')
 
2752
        self.build_tree(['tree/removed-file', 'tree/existing-file',
 
2753
                         'tree/not-removed-file'])
 
2754
        work_tree.add(['removed-file', 'not-removed-file'])
 
2755
        preview = TransformPreview(work_tree)
 
2756
        self.addCleanup(preview.finalize)
 
2757
        preview.new_file('new-file', preview.root, 'contents')
 
2758
        preview.new_file('new-versioned-file', preview.root, 'contents',
 
2759
                         'new-versioned-id')
 
2760
        tree = preview.get_preview_tree()
 
2761
        preview.unversion_file(preview.trans_id_tree_path('removed-file'))
 
2762
        self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
 
2763
                         set(tree.extras()))
 
2764
 
 
2765
    def test_merge_into_preview(self):
 
2766
        work_tree = self.make_branch_and_tree('tree')
 
2767
        self.build_tree_contents([('tree/file','b\n')])
 
2768
        work_tree.add('file', 'file-id')
 
2769
        work_tree.commit('first commit')
 
2770
        child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
 
2771
        self.build_tree_contents([('child/file','b\nc\n')])
 
2772
        child_tree.commit('child commit')
 
2773
        child_tree.lock_write()
 
2774
        self.addCleanup(child_tree.unlock)
 
2775
        work_tree.lock_write()
 
2776
        self.addCleanup(work_tree.unlock)
 
2777
        preview = TransformPreview(work_tree)
 
2778
        self.addCleanup(preview.finalize)
 
2779
        file_trans_id = preview.trans_id_file_id('file-id')
 
2780
        preview.delete_contents(file_trans_id)
 
2781
        preview.create_file('a\nb\n', file_trans_id)
 
2782
        pb = progress.DummyProgress()
 
2783
        preview_tree = preview.get_preview_tree()
 
2784
        merger = Merger.from_revision_ids(pb, preview_tree,
 
2785
                                          child_tree.branch.last_revision(),
 
2786
                                          other_branch=child_tree.branch,
 
2787
                                          tree_branch=work_tree.branch)
 
2788
        merger.merge_type = Merge3Merger
 
2789
        tt = merger.make_merger().make_preview_transform()
 
2790
        self.addCleanup(tt.finalize)
 
2791
        final_tree = tt.get_preview_tree()
 
2792
        self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
 
2793
 
 
2794
    def test_merge_preview_into_workingtree(self):
 
2795
        tree = self.make_branch_and_tree('tree')
 
2796
        tree.set_root_id('TREE_ROOT')
 
2797
        tt = TransformPreview(tree)
 
2798
        self.addCleanup(tt.finalize)
 
2799
        tt.new_file('name', tt.root, 'content', 'file-id')
 
2800
        tree2 = self.make_branch_and_tree('tree2')
 
2801
        tree2.set_root_id('TREE_ROOT')
 
2802
        pb = progress.DummyProgress()
 
2803
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
 
2804
                                         pb, tree.basis_tree())
 
2805
        merger.merge_type = Merge3Merger
 
2806
        merger.do_merge()
 
2807
 
 
2808
    def test_merge_preview_into_workingtree_handles_conflicts(self):
 
2809
        tree = self.make_branch_and_tree('tree')
 
2810
        self.build_tree_contents([('tree/foo', 'bar')])
 
2811
        tree.add('foo', 'foo-id')
 
2812
        tree.commit('foo')
 
2813
        tt = TransformPreview(tree)
 
2814
        self.addCleanup(tt.finalize)
 
2815
        trans_id = tt.trans_id_file_id('foo-id')
 
2816
        tt.delete_contents(trans_id)
 
2817
        tt.create_file('baz', trans_id)
 
2818
        tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
 
2819
        self.build_tree_contents([('tree2/foo', 'qux')])
 
2820
        pb = progress.DummyProgress()
 
2821
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
 
2822
                                         pb, tree.basis_tree())
 
2823
        merger.merge_type = Merge3Merger
 
2824
        merger.do_merge()
 
2825
 
 
2826
    def test_is_executable(self):
 
2827
        tree = self.make_branch_and_tree('tree')
 
2828
        preview = TransformPreview(tree)
 
2829
        self.addCleanup(preview.finalize)
 
2830
        preview.new_file('foo', preview.root, 'bar', 'baz-id')
 
2831
        preview_tree = preview.get_preview_tree()
 
2832
        self.assertEqual(False, preview_tree.is_executable('baz-id',
 
2833
                                                           'tree/foo'))
 
2834
        self.assertEqual(False, preview_tree.is_executable('baz-id'))
 
2835
 
 
2836
    def test_commit_preview_tree(self):
 
2837
        tree = self.make_branch_and_tree('tree')
 
2838
        rev_id = tree.commit('rev1')
 
2839
        tree.branch.lock_write()
 
2840
        self.addCleanup(tree.branch.unlock)
 
2841
        tt = TransformPreview(tree)
 
2842
        tt.new_file('file', tt.root, 'contents', 'file_id')
 
2843
        self.addCleanup(tt.finalize)
 
2844
        preview = tt.get_preview_tree()
 
2845
        preview.set_parent_ids([rev_id])
 
2846
        builder = tree.branch.get_commit_builder([rev_id])
 
2847
        list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
 
2848
        builder.finish_inventory()
 
2849
        rev2_id = builder.commit('rev2')
 
2850
        rev2_tree = tree.branch.repository.revision_tree(rev2_id)
 
2851
        self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
 
2852
 
 
2853
    def test_ascii_limbo_paths(self):
 
2854
        self.requireFeature(tests.UnicodeFilenameFeature)
 
2855
        branch = self.make_branch('any')
 
2856
        tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
 
2857
        tt = TransformPreview(tree)
 
2858
        self.addCleanup(tt.finalize)
 
2859
        foo_id = tt.new_directory('', ROOT_PARENT)
 
2860
        bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
 
2861
        limbo_path = tt._limbo_name(bar_id)
 
2862
        self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
 
2863
 
 
2864
 
 
2865
class FakeSerializer(object):
 
2866
    """Serializer implementation that simply returns the input.
 
2867
 
 
2868
    The input is returned in the order used by pack.ContainerPushParser.
 
2869
    """
 
2870
    @staticmethod
 
2871
    def bytes_record(bytes, names):
 
2872
        return names, bytes
 
2873
 
 
2874
 
 
2875
class TestSerializeTransform(tests.TestCaseWithTransport):
 
2876
 
 
2877
    _test_needs_features = [tests.UnicodeFilenameFeature]
 
2878
 
 
2879
    def get_preview(self, tree=None):
 
2880
        if tree is None:
 
2881
            tree = self.make_branch_and_tree('tree')
 
2882
        tt = TransformPreview(tree)
 
2883
        self.addCleanup(tt.finalize)
 
2884
        return tt
 
2885
 
 
2886
    def assertSerializesTo(self, expected, tt):
 
2887
        records = list(tt.serialize(FakeSerializer()))
 
2888
        self.assertEqual(expected, records)
 
2889
 
 
2890
    @staticmethod
 
2891
    def default_attribs():
 
2892
        return {
 
2893
            '_id_number': 1,
 
2894
            '_new_name': {},
 
2895
            '_new_parent': {},
 
2896
            '_new_executability': {},
 
2897
            '_new_id': {},
 
2898
            '_tree_path_ids': {'': 'new-0'},
 
2899
            '_removed_id': [],
 
2900
            '_removed_contents': [],
 
2901
            '_non_present_ids': {},
 
2902
            }
 
2903
 
 
2904
    def make_records(self, attribs, contents):
 
2905
        records = [
 
2906
            (((('attribs'),),), bencode.bencode(attribs))]
 
2907
        records.extend([(((n, k),), c) for n, k, c in contents])
 
2908
        return records
 
2909
 
 
2910
    def creation_records(self):
 
2911
        attribs = self.default_attribs()
 
2912
        attribs['_id_number'] = 3
 
2913
        attribs['_new_name'] = {
 
2914
            'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
 
2915
        attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
 
2916
        attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
 
2917
        attribs['_new_executability'] = {'new-1': 1}
 
2918
        contents = [
 
2919
            ('new-1', 'file', 'i 1\nbar\n'),
 
2920
            ('new-2', 'directory', ''),
 
2921
            ]
 
2922
        return self.make_records(attribs, contents)
 
2923
 
 
2924
    def test_serialize_creation(self):
 
2925
        tt = self.get_preview()
 
2926
        tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
 
2927
        tt.new_directory('qux', tt.root, 'quxx')
 
2928
        self.assertSerializesTo(self.creation_records(), tt)
 
2929
 
 
2930
    def test_deserialize_creation(self):
 
2931
        tt = self.get_preview()
 
2932
        tt.deserialize(iter(self.creation_records()))
 
2933
        self.assertEqual(3, tt._id_number)
 
2934
        self.assertEqual({'new-1': u'foo\u1234',
 
2935
                          'new-2': 'qux'}, tt._new_name)
 
2936
        self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
 
2937
        self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
 
2938
        self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
 
2939
        self.assertEqual({'new-1': True}, tt._new_executability)
 
2940
        self.assertEqual({'new-1': 'file',
 
2941
                          'new-2': 'directory'}, tt._new_contents)
 
2942
        foo_limbo = open(tt._limbo_name('new-1'), 'rb')
 
2943
        try:
 
2944
            foo_content = foo_limbo.read()
 
2945
        finally:
 
2946
            foo_limbo.close()
 
2947
        self.assertEqual('bar', foo_content)
 
2948
 
 
2949
    def symlink_creation_records(self):
 
2950
        attribs = self.default_attribs()
 
2951
        attribs['_id_number'] = 2
 
2952
        attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
 
2953
        attribs['_new_parent'] = {'new-1': 'new-0'}
 
2954
        contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
 
2955
        return self.make_records(attribs, contents)
 
2956
 
 
2957
    def test_serialize_symlink_creation(self):
 
2958
        self.requireFeature(tests.SymlinkFeature)
 
2959
        tt = self.get_preview()
 
2960
        tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
 
2961
        self.assertSerializesTo(self.symlink_creation_records(), tt)
 
2962
 
 
2963
    def test_deserialize_symlink_creation(self):
 
2964
        self.requireFeature(tests.SymlinkFeature)
 
2965
        tt = self.get_preview()
 
2966
        tt.deserialize(iter(self.symlink_creation_records()))
 
2967
        abspath = tt._limbo_name('new-1')
 
2968
        foo_content = osutils.readlink(abspath)
 
2969
        self.assertEqual(u'bar\u1234', foo_content)
 
2970
 
 
2971
    def make_destruction_preview(self):
 
2972
        tree = self.make_branch_and_tree('.')
 
2973
        self.build_tree([u'foo\u1234', 'bar'])
 
2974
        tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
 
2975
        return self.get_preview(tree)
 
2976
 
 
2977
    def destruction_records(self):
 
2978
        attribs = self.default_attribs()
 
2979
        attribs['_id_number'] = 3
 
2980
        attribs['_removed_id'] = ['new-1']
 
2981
        attribs['_removed_contents'] = ['new-2']
 
2982
        attribs['_tree_path_ids'] = {
 
2983
            '': 'new-0',
 
2984
            u'foo\u1234'.encode('utf-8'): 'new-1',
 
2985
            'bar': 'new-2',
 
2986
            }
 
2987
        return self.make_records(attribs, [])
 
2988
 
 
2989
    def test_serialize_destruction(self):
 
2990
        tt = self.make_destruction_preview()
 
2991
        foo_trans_id = tt.trans_id_tree_file_id('foo-id')
 
2992
        tt.unversion_file(foo_trans_id)
 
2993
        bar_trans_id = tt.trans_id_tree_file_id('bar-id')
 
2994
        tt.delete_contents(bar_trans_id)
 
2995
        self.assertSerializesTo(self.destruction_records(), tt)
 
2996
 
 
2997
    def test_deserialize_destruction(self):
 
2998
        tt = self.make_destruction_preview()
 
2999
        tt.deserialize(iter(self.destruction_records()))
 
3000
        self.assertEqual({u'foo\u1234': 'new-1',
 
3001
                          'bar': 'new-2',
 
3002
                          '': tt.root}, tt._tree_path_ids)
 
3003
        self.assertEqual({'new-1': u'foo\u1234',
 
3004
                          'new-2': 'bar',
 
3005
                          tt.root: ''}, tt._tree_id_paths)
 
3006
        self.assertEqual(set(['new-1']), tt._removed_id)
 
3007
        self.assertEqual(set(['new-2']), tt._removed_contents)
 
3008
 
 
3009
    def missing_records(self):
 
3010
        attribs = self.default_attribs()
 
3011
        attribs['_id_number'] = 2
 
3012
        attribs['_non_present_ids'] = {
 
3013
            'boo': 'new-1',}
 
3014
        return self.make_records(attribs, [])
 
3015
 
 
3016
    def test_serialize_missing(self):
 
3017
        tt = self.get_preview()
 
3018
        boo_trans_id = tt.trans_id_file_id('boo')
 
3019
        self.assertSerializesTo(self.missing_records(), tt)
 
3020
 
 
3021
    def test_deserialize_missing(self):
 
3022
        tt = self.get_preview()
 
3023
        tt.deserialize(iter(self.missing_records()))
 
3024
        self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
 
3025
 
 
3026
    def make_modification_preview(self):
 
3027
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
3028
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
3029
        tree = self.make_branch_and_tree('tree')
 
3030
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
3031
        tree.add('file', 'file-id')
 
3032
        return self.get_preview(tree), LINES_TWO
 
3033
 
 
3034
    def modification_records(self):
 
3035
        attribs = self.default_attribs()
 
3036
        attribs['_id_number'] = 2
 
3037
        attribs['_tree_path_ids'] = {
 
3038
            'file': 'new-1',
 
3039
            '': 'new-0',}
 
3040
        attribs['_removed_contents'] = ['new-1']
 
3041
        contents = [('new-1', 'file',
 
3042
                     'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
 
3043
        return self.make_records(attribs, contents)
 
3044
 
 
3045
    def test_serialize_modification(self):
 
3046
        tt, LINES = self.make_modification_preview()
 
3047
        trans_id = tt.trans_id_file_id('file-id')
 
3048
        tt.delete_contents(trans_id)
 
3049
        tt.create_file(LINES, trans_id)
 
3050
        self.assertSerializesTo(self.modification_records(), tt)
 
3051
 
 
3052
    def test_deserialize_modification(self):
 
3053
        tt, LINES = self.make_modification_preview()
 
3054
        tt.deserialize(iter(self.modification_records()))
 
3055
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
3056
 
 
3057
    def make_kind_change_preview(self):
 
3058
        LINES = 'a\nb\nc\nd\n'
 
3059
        tree = self.make_branch_and_tree('tree')
 
3060
        self.build_tree(['tree/foo/'])
 
3061
        tree.add('foo', 'foo-id')
 
3062
        return self.get_preview(tree), LINES
 
3063
 
 
3064
    def kind_change_records(self):
 
3065
        attribs = self.default_attribs()
 
3066
        attribs['_id_number'] = 2
 
3067
        attribs['_tree_path_ids'] = {
 
3068
            'foo': 'new-1',
 
3069
            '': 'new-0',}
 
3070
        attribs['_removed_contents'] = ['new-1']
 
3071
        contents = [('new-1', 'file',
 
3072
                     'i 4\na\nb\nc\nd\n\n')]
 
3073
        return self.make_records(attribs, contents)
 
3074
 
 
3075
    def test_serialize_kind_change(self):
 
3076
        tt, LINES = self.make_kind_change_preview()
 
3077
        trans_id = tt.trans_id_file_id('foo-id')
 
3078
        tt.delete_contents(trans_id)
 
3079
        tt.create_file(LINES, trans_id)
 
3080
        self.assertSerializesTo(self.kind_change_records(), tt)
 
3081
 
 
3082
    def test_deserialize_kind_change(self):
 
3083
        tt, LINES = self.make_kind_change_preview()
 
3084
        tt.deserialize(iter(self.kind_change_records()))
 
3085
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
3086
 
 
3087
    def make_add_contents_preview(self):
 
3088
        LINES = 'a\nb\nc\nd\n'
 
3089
        tree = self.make_branch_and_tree('tree')
 
3090
        self.build_tree(['tree/foo'])
 
3091
        tree.add('foo')
 
3092
        os.unlink('tree/foo')
 
3093
        return self.get_preview(tree), LINES
 
3094
 
 
3095
    def add_contents_records(self):
 
3096
        attribs = self.default_attribs()
 
3097
        attribs['_id_number'] = 2
 
3098
        attribs['_tree_path_ids'] = {
 
3099
            'foo': 'new-1',
 
3100
            '': 'new-0',}
 
3101
        contents = [('new-1', 'file',
 
3102
                     'i 4\na\nb\nc\nd\n\n')]
 
3103
        return self.make_records(attribs, contents)
 
3104
 
 
3105
    def test_serialize_add_contents(self):
 
3106
        tt, LINES = self.make_add_contents_preview()
 
3107
        trans_id = tt.trans_id_tree_path('foo')
 
3108
        tt.create_file(LINES, trans_id)
 
3109
        self.assertSerializesTo(self.add_contents_records(), tt)
 
3110
 
 
3111
    def test_deserialize_add_contents(self):
 
3112
        tt, LINES = self.make_add_contents_preview()
 
3113
        tt.deserialize(iter(self.add_contents_records()))
 
3114
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
3115
 
 
3116
    def test_get_parents_lines(self):
 
3117
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
3118
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
3119
        tree = self.make_branch_and_tree('tree')
 
3120
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
3121
        tree.add('file', 'file-id')
 
3122
        tt = self.get_preview(tree)
 
3123
        trans_id = tt.trans_id_tree_path('file')
 
3124
        self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
 
3125
            tt._get_parents_lines(trans_id))
 
3126
 
 
3127
    def test_get_parents_texts(self):
 
3128
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
3129
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
3130
        tree = self.make_branch_and_tree('tree')
 
3131
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
3132
        tree.add('file', 'file-id')
 
3133
        tt = self.get_preview(tree)
 
3134
        trans_id = tt.trans_id_tree_path('file')
 
3135
        self.assertEqual((LINES_ONE,),
 
3136
            tt._get_parents_texts(trans_id))