/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2011-06-22 09:54:01 UTC
  • mfrom: (5991.2.1 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20110622095401-n1nkzancazl4h0kg
(vila) Slightly simplify whoami tests (Vincent Ladeuil)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 Canonical Ltd
2
 
 
 
1
# Copyright (C) 2006-2011 Canonical Ltd
 
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
 
 
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
 
17
import errno
17
18
import os
 
19
from StringIO import StringIO
 
20
import sys
 
21
import time
18
22
 
 
23
from bzrlib import (
 
24
    bencode,
 
25
    errors,
 
26
    filters,
 
27
    generate_ids,
 
28
    osutils,
 
29
    revision as _mod_revision,
 
30
    rules,
 
31
    symbol_versioning,
 
32
    tests,
 
33
    trace,
 
34
    transform,
 
35
    urlutils,
 
36
    )
19
37
from bzrlib.bzrdir import BzrDir
20
 
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
21
 
                              UnversionedParent, ParentLoop)
22
 
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
23
 
                           ReusingTransform, CantMoveRoot, NotVersionedError,
24
 
                           ExistingLimbo, ImmortalLimbo, LockError)
25
 
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
26
 
from bzrlib.merge import Merge3Merger
27
 
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
28
 
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths, 
29
 
                              resolve_conflicts, cook_conflicts, 
30
 
                              find_interesting, build_tree, get_backup_name)
31
 
 
32
 
class TestTreeTransform(TestCaseInTempDir):
 
38
from bzrlib.conflicts import (
 
39
    DeletingParent,
 
40
    DuplicateEntry,
 
41
    DuplicateID,
 
42
    MissingParent,
 
43
    NonDirectoryParent,
 
44
    ParentLoop,
 
45
    UnversionedParent,
 
46
)
 
47
from bzrlib.diff import show_diff_trees
 
48
from bzrlib.errors import (
 
49
    DuplicateKey,
 
50
    ExistingLimbo,
 
51
    ExistingPendingDeletion,
 
52
    ImmortalLimbo,
 
53
    ImmortalPendingDeletion,
 
54
    LockError,
 
55
    MalformedTransform,
 
56
    ReusingTransform,
 
57
)
 
58
from bzrlib.osutils import (
 
59
    file_kind,
 
60
    pathjoin,
 
61
)
 
62
from bzrlib.merge import Merge3Merger, Merger
 
63
from bzrlib.tests import (
 
64
    features,
 
65
    HardlinkFeature,
 
66
    SymlinkFeature,
 
67
    TestCaseInTempDir,
 
68
    TestSkipped,
 
69
)
 
70
from bzrlib.transform import (
 
71
    build_tree,
 
72
    create_from_tree,
 
73
    cook_conflicts,
 
74
    _FileMover,
 
75
    FinalPaths,
 
76
    resolve_conflicts,
 
77
    resolve_checkout,
 
78
    ROOT_PARENT,
 
79
    TransformPreview,
 
80
    TreeTransform,
 
81
)
 
82
 
 
83
 
 
84
class TestTreeTransform(tests.TestCaseWithTransport):
 
85
 
33
86
    def setUp(self):
34
87
        super(TestTreeTransform, self).setUp()
35
 
        self.wt = BzrDir.create_standalone_workingtree('.')
 
88
        self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
36
89
        os.chdir('..')
37
90
 
38
91
    def get_transform(self):
39
92
        transform = TreeTransform(self.wt)
40
 
        #self.addCleanup(transform.finalize)
41
 
        return transform, transform.trans_id_tree_file_id(self.wt.get_root_id())
 
93
        self.addCleanup(transform.finalize)
 
94
        return transform, transform.root
 
95
 
 
96
    def get_transform_for_sha1_test(self):
 
97
        trans, root = self.get_transform()
 
98
        self.wt.lock_tree_write()
 
99
        self.addCleanup(self.wt.unlock)
 
100
        contents = ['just some content\n']
 
101
        sha1 = osutils.sha_strings(contents)
 
102
        # Roll back the clock
 
103
        trans._creation_mtime = time.time() - 20.0
 
104
        return trans, root, contents, sha1
42
105
 
43
106
    def test_existing_limbo(self):
44
 
        limbo_name = self.wt._control_files.controlfilename('limbo')
45
107
        transform, root = self.get_transform()
 
108
        limbo_name = transform._limbodir
 
109
        deletion_path = transform._deletiondir
46
110
        os.mkdir(pathjoin(limbo_name, 'hehe'))
47
111
        self.assertRaises(ImmortalLimbo, transform.apply)
48
112
        self.assertRaises(LockError, self.wt.unlock)
50
114
        self.assertRaises(LockError, self.wt.unlock)
51
115
        os.rmdir(pathjoin(limbo_name, 'hehe'))
52
116
        os.rmdir(limbo_name)
 
117
        os.rmdir(deletion_path)
53
118
        transform, root = self.get_transform()
54
119
        transform.apply()
55
120
 
 
121
    def test_existing_pending_deletion(self):
 
122
        transform, root = self.get_transform()
 
123
        deletion_path = self._limbodir = urlutils.local_path_from_url(
 
124
            transform._tree._transport.abspath('pending-deletion'))
 
125
        os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
 
126
        self.assertRaises(ImmortalPendingDeletion, transform.apply)
 
127
        self.assertRaises(LockError, self.wt.unlock)
 
128
        self.assertRaises(ExistingPendingDeletion, self.get_transform)
 
129
 
56
130
    def test_build(self):
57
 
        transform, root = self.get_transform() 
 
131
        transform, root = self.get_transform()
 
132
        self.wt.lock_tree_write()
 
133
        self.addCleanup(self.wt.unlock)
58
134
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
59
135
        imaginary_id = transform.trans_id_tree_path('imaginary')
60
 
        self.assertEqual(transform.get_tree_parent(imaginary_id), root)
61
 
        self.assertEqual(transform.final_kind(root), 'directory')
62
 
        self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
 
136
        imaginary_id2 = transform.trans_id_tree_path('imaginary/')
 
137
        self.assertEqual(imaginary_id, imaginary_id2)
 
138
        self.assertEqual(root, transform.get_tree_parent(imaginary_id))
 
139
        self.assertEqual('directory', transform.final_kind(root))
 
140
        self.assertEqual(self.wt.get_root_id(), transform.final_file_id(root))
63
141
        trans_id = transform.create_path('name', root)
64
142
        self.assertIs(transform.final_file_id(trans_id), None)
65
 
        self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
 
143
        self.assertIs(None, transform.final_kind(trans_id))
66
144
        transform.create_file('contents', trans_id)
67
145
        transform.set_executability(True, trans_id)
68
146
        transform.version_file('my_pretties', trans_id)
86
164
        self.assertIs(self.wt.is_executable('my_pretties2'), False)
87
165
        self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
88
166
        self.assertEqual(len(modified_paths), 3)
89
 
        tree_mod_paths = [self.wt.id2abspath(f) for f in 
 
167
        tree_mod_paths = [self.wt.id2abspath(f) for f in
90
168
                          ('ozzie', 'my_pretties', 'my_pretties2')]
91
169
        self.assertSubset(tree_mod_paths, modified_paths)
92
170
        # is it safe to finalize repeatedly?
93
171
        transform.finalize()
94
172
        transform.finalize()
95
173
 
 
174
    def test_apply_informs_tree_of_observed_sha1(self):
 
175
        trans, root, contents, sha1 = self.get_transform_for_sha1_test()
 
176
        trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
 
177
                                  sha1=sha1)
 
178
        calls = []
 
179
        orig = self.wt._observed_sha1
 
180
        def _observed_sha1(*args):
 
181
            calls.append(args)
 
182
            orig(*args)
 
183
        self.wt._observed_sha1 = _observed_sha1
 
184
        trans.apply()
 
185
        self.assertEqual([(None, 'file1', trans._observed_sha1s[trans_id])],
 
186
                         calls)
 
187
 
 
188
    def test_create_file_caches_sha1(self):
 
189
        trans, root, contents, sha1 = self.get_transform_for_sha1_test()
 
190
        trans_id = trans.create_path('file1', root)
 
191
        trans.create_file(contents, trans_id, sha1=sha1)
 
192
        st_val = osutils.lstat(trans._limbo_name(trans_id))
 
193
        o_sha1, o_st_val = trans._observed_sha1s[trans_id]
 
194
        self.assertEqual(o_sha1, sha1)
 
195
        self.assertEqualStat(o_st_val, st_val)
 
196
 
 
197
    def test__apply_insertions_updates_sha1(self):
 
198
        trans, root, contents, sha1 = self.get_transform_for_sha1_test()
 
199
        trans_id = trans.create_path('file1', root)
 
200
        trans.create_file(contents, trans_id, sha1=sha1)
 
201
        st_val = osutils.lstat(trans._limbo_name(trans_id))
 
202
        o_sha1, o_st_val = trans._observed_sha1s[trans_id]
 
203
        self.assertEqual(o_sha1, sha1)
 
204
        self.assertEqualStat(o_st_val, st_val)
 
205
        creation_mtime = trans._creation_mtime + 10.0
 
206
        # We fake a time difference from when the file was created until now it
 
207
        # is being renamed by using os.utime. Note that the change we actually
 
208
        # want to see is the real ctime change from 'os.rename()', but as long
 
209
        # as we observe a new stat value, we should be fine.
 
210
        os.utime(trans._limbo_name(trans_id), (creation_mtime, creation_mtime))
 
211
        trans.apply()
 
212
        new_st_val = osutils.lstat(self.wt.abspath('file1'))
 
213
        o_sha1, o_st_val = trans._observed_sha1s[trans_id]
 
214
        self.assertEqual(o_sha1, sha1)
 
215
        self.assertEqualStat(o_st_val, new_st_val)
 
216
        self.assertNotEqual(st_val.st_mtime, new_st_val.st_mtime)
 
217
 
 
218
    def test_new_file_caches_sha1(self):
 
219
        trans, root, contents, sha1 = self.get_transform_for_sha1_test()
 
220
        trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
 
221
                                  sha1=sha1)
 
222
        st_val = osutils.lstat(trans._limbo_name(trans_id))
 
223
        o_sha1, o_st_val = trans._observed_sha1s[trans_id]
 
224
        self.assertEqual(o_sha1, sha1)
 
225
        self.assertEqualStat(o_st_val, st_val)
 
226
 
 
227
    def test_cancel_creation_removes_observed_sha1(self):
 
228
        trans, root, contents, sha1 = self.get_transform_for_sha1_test()
 
229
        trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
 
230
                                  sha1=sha1)
 
231
        self.assertTrue(trans_id in trans._observed_sha1s)
 
232
        trans.cancel_creation(trans_id)
 
233
        self.assertFalse(trans_id in trans._observed_sha1s)
 
234
 
 
235
    def test_create_files_same_timestamp(self):
 
236
        transform, root = self.get_transform()
 
237
        self.wt.lock_tree_write()
 
238
        self.addCleanup(self.wt.unlock)
 
239
        # Roll back the clock, so that we know everything is being set to the
 
240
        # exact time
 
241
        transform._creation_mtime = creation_mtime = time.time() - 20.0
 
242
        transform.create_file('content-one',
 
243
                              transform.create_path('one', root))
 
244
        time.sleep(1) # *ugly*
 
245
        transform.create_file('content-two',
 
246
                              transform.create_path('two', root))
 
247
        transform.apply()
 
248
        fo, st1 = self.wt.get_file_with_stat(None, path='one', filtered=False)
 
249
        fo.close()
 
250
        fo, st2 = self.wt.get_file_with_stat(None, path='two', filtered=False)
 
251
        fo.close()
 
252
        # We only guarantee 2s resolution
 
253
        self.assertTrue(abs(creation_mtime - st1.st_mtime) < 2.0,
 
254
            "%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
 
255
        # But if we have more than that, all files should get the same result
 
256
        self.assertEqual(st1.st_mtime, st2.st_mtime)
 
257
 
 
258
    def test_change_root_id(self):
 
259
        transform, root = self.get_transform()
 
260
        self.assertNotEqual('new-root-id', self.wt.get_root_id())
 
261
        transform.new_directory('', ROOT_PARENT, 'new-root-id')
 
262
        transform.delete_contents(root)
 
263
        transform.unversion_file(root)
 
264
        transform.fixup_new_roots()
 
265
        transform.apply()
 
266
        self.assertEqual('new-root-id', self.wt.get_root_id())
 
267
 
 
268
    def test_change_root_id_add_files(self):
 
269
        transform, root = self.get_transform()
 
270
        self.assertNotEqual('new-root-id', self.wt.get_root_id())
 
271
        new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
 
272
        transform.new_file('file', new_trans_id, ['new-contents\n'],
 
273
                           'new-file-id')
 
274
        transform.delete_contents(root)
 
275
        transform.unversion_file(root)
 
276
        transform.fixup_new_roots()
 
277
        transform.apply()
 
278
        self.assertEqual('new-root-id', self.wt.get_root_id())
 
279
        self.assertEqual('new-file-id', self.wt.path2id('file'))
 
280
        self.assertFileEqual('new-contents\n', self.wt.abspath('file'))
 
281
 
 
282
    def test_add_two_roots(self):
 
283
        transform, root = self.get_transform()
 
284
        new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
 
285
        new_trans_id = transform.new_directory('', ROOT_PARENT, 'alt-root-id')
 
286
        self.assertRaises(ValueError, transform.fixup_new_roots)
 
287
 
 
288
    def test_add_unversioned_root(self):
 
289
        transform, root = self.get_transform()
 
290
        new_trans_id = transform.new_directory('', ROOT_PARENT, None)
 
291
        transform.fixup_new_roots()
 
292
        self.assertNotIn(transform.root, transform._new_id)
 
293
 
 
294
    def test_apply_retains_root_directory(self):
 
295
        # Do not attempt to delete the physical root directory, because that
 
296
        # is impossible.
 
297
        transform, root = self.get_transform()
 
298
        with transform:
 
299
            transform.delete_contents(root)
 
300
            e = self.assertRaises(AssertionError, self.assertRaises,
 
301
                                  errors.TransformRenameFailed,
 
302
                                  transform.apply)
 
303
        self.assertContainsRe('TransformRenameFailed not raised', str(e))
 
304
 
 
305
    def test_hardlink(self):
 
306
        self.requireFeature(HardlinkFeature)
 
307
        transform, root = self.get_transform()
 
308
        transform.new_file('file1', root, 'contents')
 
309
        transform.apply()
 
310
        target = self.make_branch_and_tree('target')
 
311
        target_transform = TreeTransform(target)
 
312
        trans_id = target_transform.create_path('file1', target_transform.root)
 
313
        target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
 
314
        target_transform.apply()
 
315
        self.assertPathExists('target/file1')
 
316
        source_stat = os.stat(self.wt.abspath('file1'))
 
317
        target_stat = os.stat('target/file1')
 
318
        self.assertEqual(source_stat, target_stat)
 
319
 
96
320
    def test_convenience(self):
97
321
        transform, root = self.get_transform()
98
 
        trans_id = transform.new_file('name', root, 'contents', 
 
322
        self.wt.lock_tree_write()
 
323
        self.addCleanup(self.wt.unlock)
 
324
        trans_id = transform.new_file('name', root, 'contents',
99
325
                                      'my_pretties', True)
100
326
        oz = transform.new_directory('oz', root, 'oz-id')
101
327
        dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
102
 
        toto = transform.new_file('toto', dorothy, 'toto-contents', 
 
328
        toto = transform.new_file('toto', dorothy, 'toto-contents',
103
329
                                  'toto-id', False)
104
330
 
105
331
        self.assertEqual(len(transform.find_conflicts()), 0)
112
338
        self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
113
339
        self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
114
340
 
115
 
        self.assertEqual('toto-contents', 
 
341
        self.assertEqual('toto-contents',
116
342
                         self.wt.get_file_byname('oz/dorothy/toto').read())
117
343
        self.assertIs(self.wt.is_executable('toto-id'), False)
118
344
 
 
345
    def test_tree_reference(self):
 
346
        transform, root = self.get_transform()
 
347
        tree = transform._tree
 
348
        trans_id = transform.new_directory('reference', root, 'subtree-id')
 
349
        transform.set_tree_reference('subtree-revision', trans_id)
 
350
        transform.apply()
 
351
        tree.lock_read()
 
352
        self.addCleanup(tree.unlock)
 
353
        self.assertEqual('subtree-revision',
 
354
                         tree.inventory['subtree-id'].reference_revision)
 
355
 
119
356
    def test_conflicts(self):
120
357
        transform, root = self.get_transform()
121
 
        trans_id = transform.new_file('name', root, 'contents', 
 
358
        trans_id = transform.new_file('name', root, 'contents',
122
359
                                      'my_pretties')
123
360
        self.assertEqual(len(transform.find_conflicts()), 0)
124
361
        trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
125
 
        self.assertEqual(transform.find_conflicts(), 
 
362
        self.assertEqual(transform.find_conflicts(),
126
363
                         [('duplicate', trans_id, trans_id2, 'name')])
127
364
        self.assertRaises(MalformedTransform, transform.apply)
128
365
        transform.adjust_path('name', trans_id, trans_id2)
129
 
        self.assertEqual(transform.find_conflicts(), 
 
366
        self.assertEqual(transform.find_conflicts(),
130
367
                         [('non-directory parent', trans_id)])
131
368
        tinman_id = transform.trans_id_tree_path('tinman')
132
369
        transform.adjust_path('name', tinman_id, trans_id2)
133
 
        self.assertEqual(transform.find_conflicts(), 
134
 
                         [('unversioned parent', tinman_id), 
 
370
        self.assertEqual(transform.find_conflicts(),
 
371
                         [('unversioned parent', tinman_id),
135
372
                          ('missing parent', tinman_id)])
136
373
        lion_id = transform.create_path('lion', root)
137
 
        self.assertEqual(transform.find_conflicts(), 
138
 
                         [('unversioned parent', tinman_id), 
 
374
        self.assertEqual(transform.find_conflicts(),
 
375
                         [('unversioned parent', tinman_id),
139
376
                          ('missing parent', tinman_id)])
140
377
        transform.adjust_path('name', lion_id, trans_id2)
141
 
        self.assertEqual(transform.find_conflicts(), 
 
378
        self.assertEqual(transform.find_conflicts(),
142
379
                         [('unversioned parent', lion_id),
143
380
                          ('missing parent', lion_id)])
144
381
        transform.version_file("Courage", lion_id)
145
 
        self.assertEqual(transform.find_conflicts(), 
146
 
                         [('missing parent', lion_id), 
 
382
        self.assertEqual(transform.find_conflicts(),
 
383
                         [('missing parent', lion_id),
147
384
                          ('versioning no contents', lion_id)])
148
385
        transform.adjust_path('name2', root, trans_id2)
149
 
        self.assertEqual(transform.find_conflicts(), 
 
386
        self.assertEqual(transform.find_conflicts(),
150
387
                         [('versioning no contents', lion_id)])
151
388
        transform.create_file('Contents, okay?', lion_id)
152
389
        transform.adjust_path('name2', trans_id2, trans_id2)
153
 
        self.assertEqual(transform.find_conflicts(), 
154
 
                         [('parent loop', trans_id2), 
 
390
        self.assertEqual(transform.find_conflicts(),
 
391
                         [('parent loop', trans_id2),
155
392
                          ('non-directory parent', trans_id2)])
156
393
        transform.adjust_path('name2', root, trans_id2)
157
394
        oz_id = transform.new_directory('oz', root)
158
395
        transform.set_executability(True, oz_id)
159
 
        self.assertEqual(transform.find_conflicts(), 
 
396
        self.assertEqual(transform.find_conflicts(),
160
397
                         [('unversioned executability', oz_id)])
161
398
        transform.version_file('oz-id', oz_id)
162
 
        self.assertEqual(transform.find_conflicts(), 
 
399
        self.assertEqual(transform.find_conflicts(),
163
400
                         [('non-file executability', oz_id)])
164
401
        transform.set_executability(None, oz_id)
165
402
        tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
174
411
        self.assert_('oz/tip' in transform2._tree_path_ids)
175
412
        self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
176
413
        self.assertEqual(len(result), 2)
177
 
        self.assertEqual((result[0][0], result[0][1]), 
 
414
        self.assertEqual((result[0][0], result[0][1]),
178
415
                         ('duplicate', newtip))
179
 
        self.assertEqual((result[1][0], result[1][2]), 
 
416
        self.assertEqual((result[1][0], result[1][2]),
180
417
                         ('duplicate id', newtip))
181
418
        transform2.finalize()
182
419
        transform3 = TreeTransform(self.wt)
183
420
        self.addCleanup(transform3.finalize)
184
421
        oz_id = transform3.trans_id_tree_file_id('oz-id')
185
422
        transform3.delete_contents(oz_id)
186
 
        self.assertEqual(transform3.find_conflicts(), 
 
423
        self.assertEqual(transform3.find_conflicts(),
187
424
                         [('missing parent', oz_id)])
188
 
        root_id = transform3.trans_id_tree_file_id('TREE_ROOT')
 
425
        root_id = transform3.root
189
426
        tip_id = transform3.trans_id_tree_file_id('tip-id')
190
427
        transform3.adjust_path('tip', root_id, tip_id)
191
428
        transform3.apply()
192
429
 
 
430
    def test_conflict_on_case_insensitive(self):
 
431
        tree = self.make_branch_and_tree('tree')
 
432
        # Don't try this at home, kids!
 
433
        # Force the tree to report that it is case sensitive, for conflict
 
434
        # resolution tests
 
435
        tree.case_sensitive = True
 
436
        transform = TreeTransform(tree)
 
437
        self.addCleanup(transform.finalize)
 
438
        transform.new_file('file', transform.root, 'content')
 
439
        transform.new_file('FiLe', transform.root, 'content')
 
440
        result = transform.find_conflicts()
 
441
        self.assertEqual([], result)
 
442
        transform.finalize()
 
443
        # Force the tree to report that it is case insensitive, for conflict
 
444
        # generation tests
 
445
        tree.case_sensitive = False
 
446
        transform = TreeTransform(tree)
 
447
        self.addCleanup(transform.finalize)
 
448
        transform.new_file('file', transform.root, 'content')
 
449
        transform.new_file('FiLe', transform.root, 'content')
 
450
        result = transform.find_conflicts()
 
451
        self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
 
452
 
 
453
    def test_conflict_on_case_insensitive_existing(self):
 
454
        tree = self.make_branch_and_tree('tree')
 
455
        self.build_tree(['tree/FiLe'])
 
456
        # Don't try this at home, kids!
 
457
        # Force the tree to report that it is case sensitive, for conflict
 
458
        # resolution tests
 
459
        tree.case_sensitive = True
 
460
        transform = TreeTransform(tree)
 
461
        self.addCleanup(transform.finalize)
 
462
        transform.new_file('file', transform.root, 'content')
 
463
        result = transform.find_conflicts()
 
464
        self.assertEqual([], result)
 
465
        transform.finalize()
 
466
        # Force the tree to report that it is case insensitive, for conflict
 
467
        # generation tests
 
468
        tree.case_sensitive = False
 
469
        transform = TreeTransform(tree)
 
470
        self.addCleanup(transform.finalize)
 
471
        transform.new_file('file', transform.root, 'content')
 
472
        result = transform.find_conflicts()
 
473
        self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
 
474
 
 
475
    def test_resolve_case_insensitive_conflict(self):
 
476
        tree = self.make_branch_and_tree('tree')
 
477
        # Don't try this at home, kids!
 
478
        # Force the tree to report that it is case insensitive, for conflict
 
479
        # resolution tests
 
480
        tree.case_sensitive = False
 
481
        transform = TreeTransform(tree)
 
482
        self.addCleanup(transform.finalize)
 
483
        transform.new_file('file', transform.root, 'content')
 
484
        transform.new_file('FiLe', transform.root, 'content')
 
485
        resolve_conflicts(transform)
 
486
        transform.apply()
 
487
        self.assertPathExists('tree/file')
 
488
        self.assertPathExists('tree/FiLe.moved')
 
489
 
 
490
    def test_resolve_checkout_case_conflict(self):
 
491
        tree = self.make_branch_and_tree('tree')
 
492
        # Don't try this at home, kids!
 
493
        # Force the tree to report that it is case insensitive, for conflict
 
494
        # resolution tests
 
495
        tree.case_sensitive = False
 
496
        transform = TreeTransform(tree)
 
497
        self.addCleanup(transform.finalize)
 
498
        transform.new_file('file', transform.root, 'content')
 
499
        transform.new_file('FiLe', transform.root, 'content')
 
500
        resolve_conflicts(transform,
 
501
                          pass_func=lambda t, c: resolve_checkout(t, c, []))
 
502
        transform.apply()
 
503
        self.assertPathExists('tree/file')
 
504
        self.assertPathExists('tree/FiLe.moved')
 
505
 
 
506
    def test_apply_case_conflict(self):
 
507
        """Ensure that a transform with case conflicts can always be applied"""
 
508
        tree = self.make_branch_and_tree('tree')
 
509
        transform = TreeTransform(tree)
 
510
        self.addCleanup(transform.finalize)
 
511
        transform.new_file('file', transform.root, 'content')
 
512
        transform.new_file('FiLe', transform.root, 'content')
 
513
        dir = transform.new_directory('dir', transform.root)
 
514
        transform.new_file('dirfile', dir, 'content')
 
515
        transform.new_file('dirFiLe', dir, 'content')
 
516
        resolve_conflicts(transform)
 
517
        transform.apply()
 
518
        self.assertPathExists('tree/file')
 
519
        if not os.path.exists('tree/FiLe.moved'):
 
520
            self.assertPathExists('tree/FiLe')
 
521
        self.assertPathExists('tree/dir/dirfile')
 
522
        if not os.path.exists('tree/dir/dirFiLe.moved'):
 
523
            self.assertPathExists('tree/dir/dirFiLe')
 
524
 
 
525
    def test_case_insensitive_limbo(self):
 
526
        tree = self.make_branch_and_tree('tree')
 
527
        # Don't try this at home, kids!
 
528
        # Force the tree to report that it is case insensitive
 
529
        tree.case_sensitive = False
 
530
        transform = TreeTransform(tree)
 
531
        self.addCleanup(transform.finalize)
 
532
        dir = transform.new_directory('dir', transform.root)
 
533
        first = transform.new_file('file', dir, 'content')
 
534
        second = transform.new_file('FiLe', dir, 'content')
 
535
        self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
 
536
        self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
 
537
 
 
538
    def test_adjust_path_updates_child_limbo_names(self):
 
539
        tree = self.make_branch_and_tree('tree')
 
540
        transform = TreeTransform(tree)
 
541
        self.addCleanup(transform.finalize)
 
542
        foo_id = transform.new_directory('foo', transform.root)
 
543
        bar_id = transform.new_directory('bar', foo_id)
 
544
        baz_id = transform.new_directory('baz', bar_id)
 
545
        qux_id = transform.new_directory('qux', baz_id)
 
546
        transform.adjust_path('quxx', foo_id, bar_id)
 
547
        self.assertStartsWith(transform._limbo_name(qux_id),
 
548
                              transform._limbo_name(bar_id))
 
549
 
193
550
    def test_add_del(self):
194
551
        start, root = self.get_transform()
195
552
        start.new_directory('a', root, 'a')
208
565
        self.addCleanup(unversion.finalize)
209
566
        parent = unversion.trans_id_tree_path('parent')
210
567
        unversion.unversion_file(parent)
211
 
        self.assertEqual(unversion.find_conflicts(), 
 
568
        self.assertEqual(unversion.find_conflicts(),
212
569
                         [('unversioned parent', parent_id)])
213
570
        file_id = unversion.trans_id_tree_file_id('child-id')
214
571
        unversion.unversion_file(file_id)
217
574
    def test_name_invariants(self):
218
575
        create_tree, root = self.get_transform()
219
576
        # prepare tree
220
 
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
 
577
        root = create_tree.root
221
578
        create_tree.new_file('name1', root, 'hello1', 'name1')
222
579
        create_tree.new_file('name2', root, 'hello2', 'name2')
223
580
        ddir = create_tree.new_directory('dying_directory', root, 'ddir')
227
584
        create_tree.apply()
228
585
 
229
586
        mangle_tree,root = self.get_transform()
230
 
        root = mangle_tree.trans_id_tree_file_id('TREE_ROOT')
 
587
        root = mangle_tree.root
231
588
        #swap names
232
589
        name1 = mangle_tree.trans_id_tree_file_id('name1')
233
590
        name2 = mangle_tree.trans_id_tree_file_id('name2')
234
591
        mangle_tree.adjust_path('name2', root, name1)
235
592
        mangle_tree.adjust_path('name1', root, name2)
236
593
 
237
 
        #tests for deleting parent directories 
 
594
        #tests for deleting parent directories
238
595
        ddir = mangle_tree.trans_id_tree_file_id('ddir')
239
596
        mangle_tree.delete_contents(ddir)
240
597
        dfile = mangle_tree.trans_id_tree_file_id('dfile')
269
626
        create_tree,root = self.get_transform()
270
627
        newdir = create_tree.new_directory('selftest', root, 'selftest-id')
271
628
        create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
272
 
        create_tree.apply()        
 
629
        create_tree.apply()
273
630
        mangle_tree,root = self.get_transform()
274
631
        selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
275
632
        blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
283
640
        bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
284
641
        tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
285
642
        blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
286
 
        create_tree.new_file('test_too_much.py', blackbox, 'hello1', 
 
643
        create_tree.new_file('test_too_much.py', blackbox, 'hello1',
287
644
                             'test_too_much-id')
288
 
        create_tree.apply()        
 
645
        create_tree.apply()
289
646
        mangle_tree,root = self.get_transform()
290
647
        bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
291
648
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
292
649
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
293
650
        mangle_tree.adjust_path('selftest', bzrlib, tests)
294
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
 
651
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
295
652
        mangle_tree.set_executability(True, test_too_much)
296
653
        mangle_tree.apply()
297
654
 
298
655
    def test_both_rename3(self):
299
656
        create_tree,root = self.get_transform()
300
657
        tests = create_tree.new_directory('tests', root, 'tests-id')
301
 
        create_tree.new_file('test_too_much.py', tests, 'hello1', 
 
658
        create_tree.new_file('test_too_much.py', tests, 'hello1',
302
659
                             'test_too_much-id')
303
 
        create_tree.apply()        
 
660
        create_tree.apply()
304
661
        mangle_tree,root = self.get_transform()
305
662
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
306
663
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
307
664
        mangle_tree.adjust_path('selftest', root, tests)
308
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
 
665
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
309
666
        mangle_tree.set_executability(True, test_too_much)
310
667
        mangle_tree.apply()
311
668
 
312
669
    def test_move_dangling_ie(self):
313
670
        create_tree, root = self.get_transform()
314
671
        # prepare tree
315
 
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
 
672
        root = create_tree.root
316
673
        create_tree.new_file('name1', root, 'hello1', 'name1')
317
674
        create_tree.apply()
318
675
        delete_contents, root = self.get_transform()
324
681
        newdir = move_id.new_directory('dir', root, 'newdir')
325
682
        move_id.adjust_path('name2', newdir, name1)
326
683
        move_id.apply()
327
 
        
 
684
 
328
685
    def test_replace_dangling_ie(self):
329
686
        create_tree, root = self.get_transform()
330
687
        # prepare tree
331
 
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
 
688
        root = create_tree.root
332
689
        create_tree.new_file('name1', root, 'hello1', 'name1')
333
690
        create_tree.apply()
334
691
        delete_contents = TreeTransform(self.wt)
346
703
        resolve_conflicts(replace)
347
704
        replace.apply()
348
705
 
349
 
    def test_symlinks(self):
350
 
        if not has_symlinks():
351
 
            raise TestSkipped('Symlinks are not supported on this platform')
352
 
        transform,root = self.get_transform()
 
706
    def _test_symlinks(self, link_name1,link_target1,
 
707
                       link_name2, link_target2):
 
708
 
 
709
        def ozpath(p): return 'oz/' + p
 
710
 
 
711
        self.requireFeature(SymlinkFeature)
 
712
        transform, root = self.get_transform()
353
713
        oz_id = transform.new_directory('oz', root, 'oz-id')
354
 
        wizard = transform.new_symlink('wizard', oz_id, 'wizard-target', 
 
714
        wizard = transform.new_symlink(link_name1, oz_id, link_target1,
355
715
                                       'wizard-id')
356
 
        wiz_id = transform.create_path('wizard2', oz_id)
357
 
        transform.create_symlink('behind_curtain', wiz_id)
358
 
        transform.version_file('wiz-id2', wiz_id)            
 
716
        wiz_id = transform.create_path(link_name2, oz_id)
 
717
        transform.create_symlink(link_target2, wiz_id)
 
718
        transform.version_file('wiz-id2', wiz_id)
359
719
        transform.set_executability(True, wiz_id)
360
 
        self.assertEqual(transform.find_conflicts(), 
 
720
        self.assertEqual(transform.find_conflicts(),
361
721
                         [('non-file executability', wiz_id)])
362
722
        transform.set_executability(None, wiz_id)
363
723
        transform.apply()
364
 
        self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
365
 
        self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
366
 
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')), 
367
 
                         'behind_curtain')
368
 
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
369
 
                         'wizard-target')
370
 
 
 
724
        self.assertEqual(self.wt.path2id(ozpath(link_name1)), 'wizard-id')
 
725
        self.assertEqual('symlink',
 
726
                         file_kind(self.wt.abspath(ozpath(link_name1))))
 
727
        self.assertEqual(link_target2,
 
728
                         osutils.readlink(self.wt.abspath(ozpath(link_name2))))
 
729
        self.assertEqual(link_target1,
 
730
                         osutils.readlink(self.wt.abspath(ozpath(link_name1))))
 
731
 
 
732
    def test_symlinks(self):
 
733
        self._test_symlinks('wizard', 'wizard-target',
 
734
                            'wizard2', 'behind_curtain')
 
735
 
 
736
    def test_symlinks_unicode(self):
 
737
        self.requireFeature(tests.UnicodeFilenameFeature)
 
738
        self._test_symlinks(u'\N{Euro Sign}wizard',
 
739
                            u'wizard-targ\N{Euro Sign}t',
 
740
                            u'\N{Euro Sign}wizard2',
 
741
                            u'b\N{Euro Sign}hind_curtain')
 
742
 
 
743
    def test_unable_create_symlink(self):
 
744
        def tt_helper():
 
745
            wt = self.make_branch_and_tree('.')
 
746
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
747
            try:
 
748
                tt.new_symlink('foo', tt.root, 'bar')
 
749
                tt.apply()
 
750
            finally:
 
751
                wt.unlock()
 
752
        os_symlink = getattr(os, 'symlink', None)
 
753
        os.symlink = None
 
754
        try:
 
755
            err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
 
756
            self.assertEquals(
 
757
                "Unable to create symlink 'foo' on this platform",
 
758
                str(err))
 
759
        finally:
 
760
            if os_symlink:
 
761
                os.symlink = os_symlink
371
762
 
372
763
    def get_conflicted(self):
373
764
        create,root = self.get_transform()
377
768
        create.apply()
378
769
        conflicts,root = self.get_transform()
379
770
        # set up duplicate entry, duplicate id
380
 
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy', 
 
771
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
381
772
                                         'dorothy-id')
382
773
        old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
383
774
        oz = conflicts.trans_id_tree_file_id('oz-id')
384
 
        # set up missing, unversioned parent
 
775
        # set up DeletedParent parent conflict
385
776
        conflicts.delete_versioned(oz)
386
777
        emerald = conflicts.trans_id_tree_file_id('emerald-id')
 
778
        # set up MissingParent conflict
 
779
        munchkincity = conflicts.trans_id_file_id('munchkincity-id')
 
780
        conflicts.adjust_path('munchkincity', root, munchkincity)
 
781
        conflicts.new_directory('auntem', munchkincity, 'auntem-id')
387
782
        # set up parent loop
388
783
        conflicts.adjust_path('emeraldcity', emerald, emerald)
389
784
        return conflicts, emerald, oz, old_dorothy, new_dorothy
403
798
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
404
799
        raw_conflicts = resolve_conflicts(tt)
405
800
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
406
 
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved', 
 
801
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
407
802
                                   'dorothy', None, 'dorothy-id')
408
803
        self.assertEqual(cooked_conflicts[0], duplicate)
409
 
        duplicate_id = DuplicateID('Unversioned existing file', 
 
804
        duplicate_id = DuplicateID('Unversioned existing file',
410
805
                                   'dorothy.moved', 'dorothy', None,
411
806
                                   'dorothy-id')
412
807
        self.assertEqual(cooked_conflicts[1], duplicate_id)
413
 
        missing_parent = MissingParent('Not deleting', 'oz', 'oz-id')
 
808
        missing_parent = MissingParent('Created directory', 'munchkincity',
 
809
                                       'munchkincity-id')
 
810
        deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
414
811
        self.assertEqual(cooked_conflicts[2], missing_parent)
415
 
        unversioned_parent = UnversionedParent('Versioned directory', 'oz',
 
812
        unversioned_parent = UnversionedParent('Versioned directory',
 
813
                                               'munchkincity',
 
814
                                               'munchkincity-id')
 
815
        unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
416
816
                                               'oz-id')
417
817
        self.assertEqual(cooked_conflicts[3], unversioned_parent)
418
 
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity', 
 
818
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
419
819
                                 'oz/emeraldcity', 'emerald-id', 'emerald-id')
420
 
        self.assertEqual(cooked_conflicts[4], parent_loop)
421
 
        self.assertEqual(len(cooked_conflicts), 5)
 
820
        self.assertEqual(cooked_conflicts[4], deleted_parent)
 
821
        self.assertEqual(cooked_conflicts[5], unversioned_parent2)
 
822
        self.assertEqual(cooked_conflicts[6], parent_loop)
 
823
        self.assertEqual(len(cooked_conflicts), 7)
422
824
        tt.finalize()
423
825
 
424
826
    def test_string_conflicts(self):
426
828
        raw_conflicts = resolve_conflicts(tt)
427
829
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
428
830
        tt.finalize()
429
 
        conflicts_s = [str(c) for c in cooked_conflicts]
 
831
        conflicts_s = [unicode(c) for c in cooked_conflicts]
430
832
        self.assertEqual(len(cooked_conflicts), len(conflicts_s))
431
833
        self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy.  '
432
834
                                         'Moved existing file to '
434
836
        self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy.  '
435
837
                                         'Unversioned existing file '
436
838
                                         'dorothy.moved.')
437
 
        self.assertEqual(conflicts_s[2], 'Conflict adding files to oz.  '
438
 
                                         'Not deleting.')
439
 
        self.assertEqual(conflicts_s[3], 'Conflict adding versioned files to '
440
 
                                         'oz.  Versioned directory.')
441
 
        self.assertEqual(conflicts_s[4], 'Conflict moving oz/emeraldcity into'
442
 
                                         ' oz/emeraldcity.  Cancelled move.')
 
839
        self.assertEqual(conflicts_s[2], 'Conflict adding files to'
 
840
                                         ' munchkincity.  Created directory.')
 
841
        self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
 
842
                                         ' versioned, but has versioned'
 
843
                                         ' children.  Versioned directory.')
 
844
        self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
 
845
                                         " is not empty.  Not deleting.")
 
846
        self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
 
847
                                         ' versioned, but has versioned'
 
848
                                         ' children.  Versioned directory.')
 
849
        self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
 
850
                                         ' oz/emeraldcity. Cancelled move.')
 
851
 
 
852
    def prepare_wrong_parent_kind(self):
 
853
        tt, root = self.get_transform()
 
854
        tt.new_file('parent', root, 'contents', 'parent-id')
 
855
        tt.apply()
 
856
        tt, root = self.get_transform()
 
857
        parent_id = tt.trans_id_file_id('parent-id')
 
858
        tt.new_file('child,', parent_id, 'contents2', 'file-id')
 
859
        return tt
 
860
 
 
861
    def test_find_conflicts_wrong_parent_kind(self):
 
862
        tt = self.prepare_wrong_parent_kind()
 
863
        tt.find_conflicts()
 
864
 
 
865
    def test_resolve_conflicts_wrong_existing_parent_kind(self):
 
866
        tt = self.prepare_wrong_parent_kind()
 
867
        raw_conflicts = resolve_conflicts(tt)
 
868
        self.assertEqual(set([('non-directory parent', 'Created directory',
 
869
                         'new-3')]), raw_conflicts)
 
870
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
 
871
        self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
 
872
        'parent-id')], cooked_conflicts)
 
873
        tt.apply()
 
874
        self.assertEqual(None, self.wt.path2id('parent'))
 
875
        self.assertEqual('parent-id', self.wt.path2id('parent.new'))
 
876
 
 
877
    def test_resolve_conflicts_wrong_new_parent_kind(self):
 
878
        tt, root = self.get_transform()
 
879
        parent_id = tt.new_directory('parent', root, 'parent-id')
 
880
        tt.new_file('child,', parent_id, 'contents2', 'file-id')
 
881
        tt.apply()
 
882
        tt, root = self.get_transform()
 
883
        parent_id = tt.trans_id_file_id('parent-id')
 
884
        tt.delete_contents(parent_id)
 
885
        tt.create_file('contents', parent_id)
 
886
        raw_conflicts = resolve_conflicts(tt)
 
887
        self.assertEqual(set([('non-directory parent', 'Created directory',
 
888
                         'new-3')]), raw_conflicts)
 
889
        tt.apply()
 
890
        self.assertEqual(None, self.wt.path2id('parent'))
 
891
        self.assertEqual('parent-id', self.wt.path2id('parent.new'))
 
892
 
 
893
    def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
 
894
        tt, root = self.get_transform()
 
895
        parent_id = tt.new_directory('parent', root)
 
896
        tt.new_file('child,', parent_id, 'contents2')
 
897
        tt.apply()
 
898
        tt, root = self.get_transform()
 
899
        parent_id = tt.trans_id_tree_path('parent')
 
900
        tt.delete_contents(parent_id)
 
901
        tt.create_file('contents', parent_id)
 
902
        resolve_conflicts(tt)
 
903
        tt.apply()
 
904
        self.assertIs(None, self.wt.path2id('parent'))
 
905
        self.assertIs(None, self.wt.path2id('parent.new'))
 
906
 
 
907
    def test_resolve_conflicts_missing_parent(self):
 
908
        wt = self.make_branch_and_tree('.')
 
909
        tt = TreeTransform(wt)
 
910
        self.addCleanup(tt.finalize)
 
911
        parent = tt.trans_id_file_id('parent-id')
 
912
        tt.new_file('file', parent, 'Contents')
 
913
        raw_conflicts = resolve_conflicts(tt)
 
914
        # Since the directory doesn't exist it's seen as 'missing'.  So
 
915
        # 'resolve_conflicts' create a conflict asking for it to be created.
 
916
        self.assertLength(1, raw_conflicts)
 
917
        self.assertEqual(('missing parent', 'Created directory', 'new-1'),
 
918
                         raw_conflicts.pop())
 
919
        # apply fail since the missing directory doesn't exist
 
920
        self.assertRaises(errors.NoFinalPath, tt.apply)
443
921
 
444
922
    def test_moving_versioned_directories(self):
445
923
        create, root = self.get_transform()
461
939
        create.apply()
462
940
        transform, root = self.get_transform()
463
941
        transform.adjust_root_path('oldroot', fun)
464
 
        new_root=transform.trans_id_tree_path('')
 
942
        new_root = transform.trans_id_tree_path('')
465
943
        transform.version_file('new-root', new_root)
466
944
        transform.apply()
467
945
 
479
957
        rename.set_executability(True, myfile)
480
958
        rename.apply()
481
959
 
482
 
    def test_find_interesting(self):
483
 
        create, root = self.get_transform()
484
 
        wt = create._tree
485
 
        create.new_file('vfile', root, 'myfile-text', 'myfile-id')
486
 
        create.new_file('uvfile', root, 'othertext')
 
960
    def test_rename_fails(self):
 
961
        self.requireFeature(features.not_running_as_root)
 
962
        # see https://bugs.launchpad.net/bzr/+bug/491763
 
963
        create, root_id = self.get_transform()
 
964
        first_dir = create.new_directory('first-dir', root_id, 'first-id')
 
965
        myfile = create.new_file('myfile', root_id, 'myfile-text',
 
966
                                 'myfile-id')
487
967
        create.apply()
488
 
        self.assertEqual(find_interesting(wt, wt, ['vfile']),
489
 
                         set(['myfile-id']))
490
 
        self.assertRaises(NotVersionedError, find_interesting, wt, wt,
491
 
                          ['uvfile'])
 
968
        if os.name == "posix" and sys.platform != "cygwin":
 
969
            # posix filesystems fail on renaming if the readonly bit is set
 
970
            osutils.make_readonly(self.wt.abspath('first-dir'))
 
971
        elif os.name == "nt":
 
972
            # windows filesystems fail on renaming open files
 
973
            self.addCleanup(file(self.wt.abspath('myfile')).close)
 
974
        else:
 
975
            self.skip("Don't know how to force a permissions error on rename")
 
976
        # now transform to rename
 
977
        rename_transform, root_id = self.get_transform()
 
978
        file_trans_id = rename_transform.trans_id_file_id('myfile-id')
 
979
        dir_id = rename_transform.trans_id_file_id('first-id')
 
980
        rename_transform.adjust_path('newname', dir_id, file_trans_id)
 
981
        e = self.assertRaises(errors.TransformRenameFailed,
 
982
            rename_transform.apply)
 
983
        # On nix looks like: 
 
984
        # "Failed to rename .../work/.bzr/checkout/limbo/new-1
 
985
        # to .../first-dir/newname: [Errno 13] Permission denied"
 
986
        # On windows looks like:
 
987
        # "Failed to rename .../work/myfile to 
 
988
        # .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
 
989
        # This test isn't concerned with exactly what the error looks like,
 
990
        # and the strerror will vary across OS and locales, but the assert
 
991
        # that the exeception attributes are what we expect
 
992
        self.assertEqual(e.errno, errno.EACCES)
 
993
        if os.name == "posix":
 
994
            self.assertEndsWith(e.to_path, "/first-dir/newname")
 
995
        else:
 
996
            self.assertEqual(os.path.basename(e.from_path), "myfile")
 
997
 
 
998
    def test_set_executability_order(self):
 
999
        """Ensure that executability behaves the same, no matter what order.
 
1000
 
 
1001
        - create file and set executability simultaneously
 
1002
        - create file and set executability afterward
 
1003
        - unsetting the executability of a file whose executability has not been
 
1004
        declared should throw an exception (this may happen when a
 
1005
        merge attempts to create a file with a duplicate ID)
 
1006
        """
 
1007
        transform, root = self.get_transform()
 
1008
        wt = transform._tree
 
1009
        wt.lock_read()
 
1010
        self.addCleanup(wt.unlock)
 
1011
        transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
 
1012
                           True)
 
1013
        sac = transform.new_file('set_after_creation', root,
 
1014
                                 'Set after creation', 'sac')
 
1015
        transform.set_executability(True, sac)
 
1016
        uws = transform.new_file('unset_without_set', root, 'Unset badly',
 
1017
                                 'uws')
 
1018
        self.assertRaises(KeyError, transform.set_executability, None, uws)
 
1019
        transform.apply()
 
1020
        self.assertTrue(wt.is_executable('soc'))
 
1021
        self.assertTrue(wt.is_executable('sac'))
 
1022
 
 
1023
    def test_preserve_mode(self):
 
1024
        """File mode is preserved when replacing content"""
 
1025
        if sys.platform == 'win32':
 
1026
            raise TestSkipped('chmod has no effect on win32')
 
1027
        transform, root = self.get_transform()
 
1028
        transform.new_file('file1', root, 'contents', 'file1-id', True)
 
1029
        transform.apply()
 
1030
        self.wt.lock_write()
 
1031
        self.addCleanup(self.wt.unlock)
 
1032
        self.assertTrue(self.wt.is_executable('file1-id'))
 
1033
        transform, root = self.get_transform()
 
1034
        file1_id = transform.trans_id_tree_file_id('file1-id')
 
1035
        transform.delete_contents(file1_id)
 
1036
        transform.create_file('contents2', file1_id)
 
1037
        transform.apply()
 
1038
        self.assertTrue(self.wt.is_executable('file1-id'))
 
1039
 
 
1040
    def test__set_mode_stats_correctly(self):
 
1041
        """_set_mode stats to determine file mode."""
 
1042
        if sys.platform == 'win32':
 
1043
            raise TestSkipped('chmod has no effect on win32')
 
1044
 
 
1045
        stat_paths = []
 
1046
        real_stat = os.stat
 
1047
        def instrumented_stat(path):
 
1048
            stat_paths.append(path)
 
1049
            return real_stat(path)
 
1050
 
 
1051
        transform, root = self.get_transform()
 
1052
 
 
1053
        bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
 
1054
                                     file_id='bar-id-1', executable=False)
 
1055
        transform.apply()
 
1056
 
 
1057
        transform, root = self.get_transform()
 
1058
        bar1_id = transform.trans_id_tree_path('bar')
 
1059
        bar2_id = transform.trans_id_tree_path('bar2')
 
1060
        try:
 
1061
            os.stat = instrumented_stat
 
1062
            transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
 
1063
        finally:
 
1064
            os.stat = real_stat
 
1065
            transform.finalize()
 
1066
 
 
1067
        bar1_abspath = self.wt.abspath('bar')
 
1068
        self.assertEqual([bar1_abspath], stat_paths)
 
1069
 
 
1070
    def test_iter_changes(self):
 
1071
        self.wt.set_root_id('eert_toor')
 
1072
        transform, root = self.get_transform()
 
1073
        transform.new_file('old', root, 'blah', 'id-1', True)
 
1074
        transform.apply()
 
1075
        transform, root = self.get_transform()
 
1076
        try:
 
1077
            self.assertEqual([], list(transform.iter_changes()))
 
1078
            old = transform.trans_id_tree_file_id('id-1')
 
1079
            transform.unversion_file(old)
 
1080
            self.assertEqual([('id-1', ('old', None), False, (True, False),
 
1081
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
1082
                (True, True))], list(transform.iter_changes()))
 
1083
            transform.new_directory('new', root, 'id-1')
 
1084
            self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
 
1085
                ('eert_toor', 'eert_toor'), ('old', 'new'),
 
1086
                ('file', 'directory'),
 
1087
                (True, False))], list(transform.iter_changes()))
 
1088
        finally:
 
1089
            transform.finalize()
 
1090
 
 
1091
    def test_iter_changes_new(self):
 
1092
        self.wt.set_root_id('eert_toor')
 
1093
        transform, root = self.get_transform()
 
1094
        transform.new_file('old', root, 'blah')
 
1095
        transform.apply()
 
1096
        transform, root = self.get_transform()
 
1097
        try:
 
1098
            old = transform.trans_id_tree_path('old')
 
1099
            transform.version_file('id-1', old)
 
1100
            self.assertEqual([('id-1', (None, 'old'), False, (False, True),
 
1101
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
1102
                (False, False))], list(transform.iter_changes()))
 
1103
        finally:
 
1104
            transform.finalize()
 
1105
 
 
1106
    def test_iter_changes_modifications(self):
 
1107
        self.wt.set_root_id('eert_toor')
 
1108
        transform, root = self.get_transform()
 
1109
        transform.new_file('old', root, 'blah', 'id-1')
 
1110
        transform.new_file('new', root, 'blah')
 
1111
        transform.new_directory('subdir', root, 'subdir-id')
 
1112
        transform.apply()
 
1113
        transform, root = self.get_transform()
 
1114
        try:
 
1115
            old = transform.trans_id_tree_path('old')
 
1116
            subdir = transform.trans_id_tree_file_id('subdir-id')
 
1117
            new = transform.trans_id_tree_path('new')
 
1118
            self.assertEqual([], list(transform.iter_changes()))
 
1119
 
 
1120
            #content deletion
 
1121
            transform.delete_contents(old)
 
1122
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
1123
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
 
1124
                (False, False))], list(transform.iter_changes()))
 
1125
 
 
1126
            #content change
 
1127
            transform.create_file('blah', old)
 
1128
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
1129
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
1130
                (False, False))], list(transform.iter_changes()))
 
1131
            transform.cancel_deletion(old)
 
1132
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
1133
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
1134
                (False, False))], list(transform.iter_changes()))
 
1135
            transform.cancel_creation(old)
 
1136
 
 
1137
            # move file_id to a different file
 
1138
            self.assertEqual([], list(transform.iter_changes()))
 
1139
            transform.unversion_file(old)
 
1140
            transform.version_file('id-1', new)
 
1141
            transform.adjust_path('old', root, new)
 
1142
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
1143
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
1144
                (False, False))], list(transform.iter_changes()))
 
1145
            transform.cancel_versioning(new)
 
1146
            transform._removed_id = set()
 
1147
 
 
1148
            #execute bit
 
1149
            self.assertEqual([], list(transform.iter_changes()))
 
1150
            transform.set_executability(True, old)
 
1151
            self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
 
1152
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
1153
                (False, True))], list(transform.iter_changes()))
 
1154
            transform.set_executability(None, old)
 
1155
 
 
1156
            # filename
 
1157
            self.assertEqual([], list(transform.iter_changes()))
 
1158
            transform.adjust_path('new', root, old)
 
1159
            transform._new_parent = {}
 
1160
            self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
 
1161
                ('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
 
1162
                (False, False))], list(transform.iter_changes()))
 
1163
            transform._new_name = {}
 
1164
 
 
1165
            # parent directory
 
1166
            self.assertEqual([], list(transform.iter_changes()))
 
1167
            transform.adjust_path('new', subdir, old)
 
1168
            transform._new_name = {}
 
1169
            self.assertEqual([('id-1', ('old', 'subdir/old'), False,
 
1170
                (True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
 
1171
                ('file', 'file'), (False, False))],
 
1172
                list(transform.iter_changes()))
 
1173
            transform._new_path = {}
 
1174
 
 
1175
        finally:
 
1176
            transform.finalize()
 
1177
 
 
1178
    def test_iter_changes_modified_bleed(self):
 
1179
        self.wt.set_root_id('eert_toor')
 
1180
        """Modified flag should not bleed from one change to another"""
 
1181
        # unfortunately, we have no guarantee that file1 (which is modified)
 
1182
        # will be applied before file2.  And if it's applied after file2, it
 
1183
        # obviously can't bleed into file2's change output.  But for now, it
 
1184
        # works.
 
1185
        transform, root = self.get_transform()
 
1186
        transform.new_file('file1', root, 'blah', 'id-1')
 
1187
        transform.new_file('file2', root, 'blah', 'id-2')
 
1188
        transform.apply()
 
1189
        transform, root = self.get_transform()
 
1190
        try:
 
1191
            transform.delete_contents(transform.trans_id_file_id('id-1'))
 
1192
            transform.set_executability(True,
 
1193
            transform.trans_id_file_id('id-2'))
 
1194
            self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
 
1195
                ('eert_toor', 'eert_toor'), ('file1', u'file1'),
 
1196
                ('file', None), (False, False)),
 
1197
                ('id-2', (u'file2', u'file2'), False, (True, True),
 
1198
                ('eert_toor', 'eert_toor'), ('file2', u'file2'),
 
1199
                ('file', 'file'), (False, True))],
 
1200
                list(transform.iter_changes()))
 
1201
        finally:
 
1202
            transform.finalize()
 
1203
 
 
1204
    def test_iter_changes_move_missing(self):
 
1205
        """Test moving ids with no files around"""
 
1206
        self.wt.set_root_id('toor_eert')
 
1207
        # Need two steps because versioning a non-existant file is a conflict.
 
1208
        transform, root = self.get_transform()
 
1209
        transform.new_directory('floater', root, 'floater-id')
 
1210
        transform.apply()
 
1211
        transform, root = self.get_transform()
 
1212
        transform.delete_contents(transform.trans_id_tree_path('floater'))
 
1213
        transform.apply()
 
1214
        transform, root = self.get_transform()
 
1215
        floater = transform.trans_id_tree_path('floater')
 
1216
        try:
 
1217
            transform.adjust_path('flitter', root, floater)
 
1218
            self.assertEqual([('floater-id', ('floater', 'flitter'), False,
 
1219
            (True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
 
1220
            (None, None), (False, False))], list(transform.iter_changes()))
 
1221
        finally:
 
1222
            transform.finalize()
 
1223
 
 
1224
    def test_iter_changes_pointless(self):
 
1225
        """Ensure that no-ops are not treated as modifications"""
 
1226
        self.wt.set_root_id('eert_toor')
 
1227
        transform, root = self.get_transform()
 
1228
        transform.new_file('old', root, 'blah', 'id-1')
 
1229
        transform.new_directory('subdir', root, 'subdir-id')
 
1230
        transform.apply()
 
1231
        transform, root = self.get_transform()
 
1232
        try:
 
1233
            old = transform.trans_id_tree_path('old')
 
1234
            subdir = transform.trans_id_tree_file_id('subdir-id')
 
1235
            self.assertEqual([], list(transform.iter_changes()))
 
1236
            transform.delete_contents(subdir)
 
1237
            transform.create_directory(subdir)
 
1238
            transform.set_executability(False, old)
 
1239
            transform.unversion_file(old)
 
1240
            transform.version_file('id-1', old)
 
1241
            transform.adjust_path('old', root, old)
 
1242
            self.assertEqual([], list(transform.iter_changes()))
 
1243
        finally:
 
1244
            transform.finalize()
 
1245
 
 
1246
    def test_rename_count(self):
 
1247
        transform, root = self.get_transform()
 
1248
        transform.new_file('name1', root, 'contents')
 
1249
        self.assertEqual(transform.rename_count, 0)
 
1250
        transform.apply()
 
1251
        self.assertEqual(transform.rename_count, 1)
 
1252
        transform2, root = self.get_transform()
 
1253
        transform2.adjust_path('name2', root,
 
1254
                               transform2.trans_id_tree_path('name1'))
 
1255
        self.assertEqual(transform2.rename_count, 0)
 
1256
        transform2.apply()
 
1257
        self.assertEqual(transform2.rename_count, 2)
 
1258
 
 
1259
    def test_change_parent(self):
 
1260
        """Ensure that after we change a parent, the results are still right.
 
1261
 
 
1262
        Renames and parent changes on pending transforms can happen as part
 
1263
        of conflict resolution, and are explicitly permitted by the
 
1264
        TreeTransform API.
 
1265
 
 
1266
        This test ensures they work correctly with the rename-avoidance
 
1267
        optimization.
 
1268
        """
 
1269
        transform, root = self.get_transform()
 
1270
        parent1 = transform.new_directory('parent1', root)
 
1271
        child1 = transform.new_file('child1', parent1, 'contents')
 
1272
        parent2 = transform.new_directory('parent2', root)
 
1273
        transform.adjust_path('child1', parent2, child1)
 
1274
        transform.apply()
 
1275
        self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
 
1276
        self.assertPathExists(self.wt.abspath('parent2/child1'))
 
1277
        # rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
 
1278
        # no rename for child1 (counting only renames during apply)
 
1279
        self.assertEqual(2, transform.rename_count)
 
1280
 
 
1281
    def test_cancel_parent(self):
 
1282
        """Cancelling a parent doesn't cause deletion of a non-empty directory
 
1283
 
 
1284
        This is like the test_change_parent, except that we cancel the parent
 
1285
        before adjusting the path.  The transform must detect that the
 
1286
        directory is non-empty, and move children to safe locations.
 
1287
        """
 
1288
        transform, root = self.get_transform()
 
1289
        parent1 = transform.new_directory('parent1', root)
 
1290
        child1 = transform.new_file('child1', parent1, 'contents')
 
1291
        child2 = transform.new_file('child2', parent1, 'contents')
 
1292
        try:
 
1293
            transform.cancel_creation(parent1)
 
1294
        except OSError:
 
1295
            self.fail('Failed to move child1 before deleting parent1')
 
1296
        transform.cancel_creation(child2)
 
1297
        transform.create_directory(parent1)
 
1298
        try:
 
1299
            transform.cancel_creation(parent1)
 
1300
        # If the transform incorrectly believes that child2 is still in
 
1301
        # parent1's limbo directory, it will try to rename it and fail
 
1302
        # because was already moved by the first cancel_creation.
 
1303
        except OSError:
 
1304
            self.fail('Transform still thinks child2 is a child of parent1')
 
1305
        parent2 = transform.new_directory('parent2', root)
 
1306
        transform.adjust_path('child1', parent2, child1)
 
1307
        transform.apply()
 
1308
        self.assertPathDoesNotExist(self.wt.abspath('parent1'))
 
1309
        self.assertPathExists(self.wt.abspath('parent2/child1'))
 
1310
        # rename limbo/new-3 => parent2, rename limbo/new-2 => child1
 
1311
        self.assertEqual(2, transform.rename_count)
 
1312
 
 
1313
    def test_adjust_and_cancel(self):
 
1314
        """Make sure adjust_path keeps track of limbo children properly"""
 
1315
        transform, root = self.get_transform()
 
1316
        parent1 = transform.new_directory('parent1', root)
 
1317
        child1 = transform.new_file('child1', parent1, 'contents')
 
1318
        parent2 = transform.new_directory('parent2', root)
 
1319
        transform.adjust_path('child1', parent2, child1)
 
1320
        transform.cancel_creation(child1)
 
1321
        try:
 
1322
            transform.cancel_creation(parent1)
 
1323
        # if the transform thinks child1 is still in parent1's limbo
 
1324
        # directory, it will attempt to move it and fail.
 
1325
        except OSError:
 
1326
            self.fail('Transform still thinks child1 is a child of parent1')
 
1327
        transform.finalize()
 
1328
 
 
1329
    def test_noname_contents(self):
 
1330
        """TreeTransform should permit deferring naming files."""
 
1331
        transform, root = self.get_transform()
 
1332
        parent = transform.trans_id_file_id('parent-id')
 
1333
        try:
 
1334
            transform.create_directory(parent)
 
1335
        except KeyError:
 
1336
            self.fail("Can't handle contents with no name")
 
1337
        transform.finalize()
 
1338
 
 
1339
    def test_noname_contents_nested(self):
 
1340
        """TreeTransform should permit deferring naming files."""
 
1341
        transform, root = self.get_transform()
 
1342
        parent = transform.trans_id_file_id('parent-id')
 
1343
        try:
 
1344
            transform.create_directory(parent)
 
1345
        except KeyError:
 
1346
            self.fail("Can't handle contents with no name")
 
1347
        child = transform.new_directory('child', parent)
 
1348
        transform.adjust_path('parent', root, parent)
 
1349
        transform.apply()
 
1350
        self.assertPathExists(self.wt.abspath('parent/child'))
 
1351
        self.assertEqual(1, transform.rename_count)
 
1352
 
 
1353
    def test_reuse_name(self):
 
1354
        """Avoid reusing the same limbo name for different files"""
 
1355
        transform, root = self.get_transform()
 
1356
        parent = transform.new_directory('parent', root)
 
1357
        child1 = transform.new_directory('child', parent)
 
1358
        try:
 
1359
            child2 = transform.new_directory('child', parent)
 
1360
        except OSError:
 
1361
            self.fail('Tranform tried to use the same limbo name twice')
 
1362
        transform.adjust_path('child2', parent, child2)
 
1363
        transform.apply()
 
1364
        # limbo/new-1 => parent, limbo/new-3 => parent/child2
 
1365
        # child2 is put into top-level limbo because child1 has already
 
1366
        # claimed the direct limbo path when child2 is created.  There is no
 
1367
        # advantage in renaming files once they're in top-level limbo, except
 
1368
        # as part of apply.
 
1369
        self.assertEqual(2, transform.rename_count)
 
1370
 
 
1371
    def test_reuse_when_first_moved(self):
 
1372
        """Don't avoid direct paths when it is safe to use them"""
 
1373
        transform, root = self.get_transform()
 
1374
        parent = transform.new_directory('parent', root)
 
1375
        child1 = transform.new_directory('child', parent)
 
1376
        transform.adjust_path('child1', parent, child1)
 
1377
        child2 = transform.new_directory('child', parent)
 
1378
        transform.apply()
 
1379
        # limbo/new-1 => parent
 
1380
        self.assertEqual(1, transform.rename_count)
 
1381
 
 
1382
    def test_reuse_after_cancel(self):
 
1383
        """Don't avoid direct paths when it is safe to use them"""
 
1384
        transform, root = self.get_transform()
 
1385
        parent2 = transform.new_directory('parent2', root)
 
1386
        child1 = transform.new_directory('child1', parent2)
 
1387
        transform.cancel_creation(parent2)
 
1388
        transform.create_directory(parent2)
 
1389
        child2 = transform.new_directory('child1', parent2)
 
1390
        transform.adjust_path('child2', parent2, child1)
 
1391
        transform.apply()
 
1392
        # limbo/new-1 => parent2, limbo/new-2 => parent2/child1
 
1393
        self.assertEqual(2, transform.rename_count)
 
1394
 
 
1395
    def test_finalize_order(self):
 
1396
        """Finalize must be done in child-to-parent order"""
 
1397
        transform, root = self.get_transform()
 
1398
        parent = transform.new_directory('parent', root)
 
1399
        child = transform.new_directory('child', parent)
 
1400
        try:
 
1401
            transform.finalize()
 
1402
        except OSError:
 
1403
            self.fail('Tried to remove parent before child1')
 
1404
 
 
1405
    def test_cancel_with_cancelled_child_should_succeed(self):
 
1406
        transform, root = self.get_transform()
 
1407
        parent = transform.new_directory('parent', root)
 
1408
        child = transform.new_directory('child', parent)
 
1409
        transform.cancel_creation(child)
 
1410
        transform.cancel_creation(parent)
 
1411
        transform.finalize()
 
1412
 
 
1413
    def test_rollback_on_directory_clash(self):
 
1414
        def tt_helper():
 
1415
            wt = self.make_branch_and_tree('.')
 
1416
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
1417
            try:
 
1418
                foo = tt.new_directory('foo', tt.root)
 
1419
                tt.new_file('bar', foo, 'foobar')
 
1420
                baz = tt.new_directory('baz', tt.root)
 
1421
                tt.new_file('qux', baz, 'quux')
 
1422
                # Ask for a rename 'foo' -> 'baz'
 
1423
                tt.adjust_path('baz', tt.root, foo)
 
1424
                # Lie to tt that we've already resolved all conflicts.
 
1425
                tt.apply(no_conflicts=True)
 
1426
            except:
 
1427
                wt.unlock()
 
1428
                raise
 
1429
        # The rename will fail because the target directory is not empty (but
 
1430
        # raises FileExists anyway).
 
1431
        err = self.assertRaises(errors.FileExists, tt_helper)
 
1432
        self.assertContainsRe(str(err),
 
1433
            "^File exists: .+/baz")
 
1434
 
 
1435
    def test_two_directories_clash(self):
 
1436
        def tt_helper():
 
1437
            wt = self.make_branch_and_tree('.')
 
1438
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
1439
            try:
 
1440
                foo_1 = tt.new_directory('foo', tt.root)
 
1441
                tt.new_directory('bar', foo_1)
 
1442
                # Adding the same directory with a different content
 
1443
                foo_2 = tt.new_directory('foo', tt.root)
 
1444
                tt.new_directory('baz', foo_2)
 
1445
                # Lie to tt that we've already resolved all conflicts.
 
1446
                tt.apply(no_conflicts=True)
 
1447
            except:
 
1448
                wt.unlock()
 
1449
                raise
 
1450
        err = self.assertRaises(errors.FileExists, tt_helper)
 
1451
        self.assertContainsRe(str(err),
 
1452
            "^File exists: .+/foo")
 
1453
 
 
1454
    def test_two_directories_clash_finalize(self):
 
1455
        def tt_helper():
 
1456
            wt = self.make_branch_and_tree('.')
 
1457
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
1458
            try:
 
1459
                foo_1 = tt.new_directory('foo', tt.root)
 
1460
                tt.new_directory('bar', foo_1)
 
1461
                # Adding the same directory with a different content
 
1462
                foo_2 = tt.new_directory('foo', tt.root)
 
1463
                tt.new_directory('baz', foo_2)
 
1464
                # Lie to tt that we've already resolved all conflicts.
 
1465
                tt.apply(no_conflicts=True)
 
1466
            except:
 
1467
                tt.finalize()
 
1468
                raise
 
1469
        err = self.assertRaises(errors.FileExists, tt_helper)
 
1470
        self.assertContainsRe(str(err),
 
1471
            "^File exists: .+/foo")
 
1472
 
 
1473
    def test_file_to_directory(self):
 
1474
        wt = self.make_branch_and_tree('.')
 
1475
        self.build_tree(['foo'])
 
1476
        wt.add(['foo'])
 
1477
        wt.commit("one")
 
1478
        tt = TreeTransform(wt)
 
1479
        self.addCleanup(tt.finalize)
 
1480
        foo_trans_id = tt.trans_id_tree_path("foo")
 
1481
        tt.delete_contents(foo_trans_id)
 
1482
        tt.create_directory(foo_trans_id)
 
1483
        bar_trans_id = tt.trans_id_tree_path("foo/bar")
 
1484
        tt.create_file(["aa\n"], bar_trans_id)
 
1485
        tt.version_file("bar-1", bar_trans_id)
 
1486
        tt.apply()
 
1487
        self.assertPathExists("foo/bar")
 
1488
        wt.lock_read()
 
1489
        try:
 
1490
            self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
 
1491
                    "directory")
 
1492
        finally:
 
1493
            wt.unlock()
 
1494
        wt.commit("two")
 
1495
        changes = wt.changes_from(wt.basis_tree())
 
1496
        self.assertFalse(changes.has_changed(), changes)
 
1497
 
 
1498
    def test_file_to_symlink(self):
 
1499
        self.requireFeature(SymlinkFeature)
 
1500
        wt = self.make_branch_and_tree('.')
 
1501
        self.build_tree(['foo'])
 
1502
        wt.add(['foo'])
 
1503
        wt.commit("one")
 
1504
        tt = TreeTransform(wt)
 
1505
        self.addCleanup(tt.finalize)
 
1506
        foo_trans_id = tt.trans_id_tree_path("foo")
 
1507
        tt.delete_contents(foo_trans_id)
 
1508
        tt.create_symlink("bar", foo_trans_id)
 
1509
        tt.apply()
 
1510
        self.assertPathExists("foo")
 
1511
        wt.lock_read()
 
1512
        self.addCleanup(wt.unlock)
 
1513
        self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
 
1514
                "symlink")
 
1515
 
 
1516
    def test_dir_to_file(self):
 
1517
        wt = self.make_branch_and_tree('.')
 
1518
        self.build_tree(['foo/', 'foo/bar'])
 
1519
        wt.add(['foo', 'foo/bar'])
 
1520
        wt.commit("one")
 
1521
        tt = TreeTransform(wt)
 
1522
        self.addCleanup(tt.finalize)
 
1523
        foo_trans_id = tt.trans_id_tree_path("foo")
 
1524
        bar_trans_id = tt.trans_id_tree_path("foo/bar")
 
1525
        tt.delete_contents(foo_trans_id)
 
1526
        tt.delete_versioned(bar_trans_id)
 
1527
        tt.create_file(["aa\n"], foo_trans_id)
 
1528
        tt.apply()
 
1529
        self.assertPathExists("foo")
 
1530
        wt.lock_read()
 
1531
        self.addCleanup(wt.unlock)
 
1532
        self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
 
1533
                "file")
 
1534
 
 
1535
    def test_dir_to_hardlink(self):
 
1536
        self.requireFeature(HardlinkFeature)
 
1537
        wt = self.make_branch_and_tree('.')
 
1538
        self.build_tree(['foo/', 'foo/bar'])
 
1539
        wt.add(['foo', 'foo/bar'])
 
1540
        wt.commit("one")
 
1541
        tt = TreeTransform(wt)
 
1542
        self.addCleanup(tt.finalize)
 
1543
        foo_trans_id = tt.trans_id_tree_path("foo")
 
1544
        bar_trans_id = tt.trans_id_tree_path("foo/bar")
 
1545
        tt.delete_contents(foo_trans_id)
 
1546
        tt.delete_versioned(bar_trans_id)
 
1547
        self.build_tree(['baz'])
 
1548
        tt.create_hardlink("baz", foo_trans_id)
 
1549
        tt.apply()
 
1550
        self.assertPathExists("foo")
 
1551
        self.assertPathExists("baz")
 
1552
        wt.lock_read()
 
1553
        self.addCleanup(wt.unlock)
 
1554
        self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
 
1555
                "file")
 
1556
 
 
1557
    def test_no_final_path(self):
 
1558
        transform, root = self.get_transform()
 
1559
        trans_id = transform.trans_id_file_id('foo')
 
1560
        transform.create_file('bar', trans_id)
 
1561
        transform.cancel_creation(trans_id)
 
1562
        transform.apply()
 
1563
 
 
1564
    def test_create_from_tree(self):
 
1565
        tree1 = self.make_branch_and_tree('tree1')
 
1566
        self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
 
1567
        tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
 
1568
        tree2 = self.make_branch_and_tree('tree2')
 
1569
        tt = TreeTransform(tree2)
 
1570
        foo_trans_id = tt.create_path('foo', tt.root)
 
1571
        create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
 
1572
        bar_trans_id = tt.create_path('bar', tt.root)
 
1573
        create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
 
1574
        tt.apply()
 
1575
        self.assertEqual('directory', osutils.file_kind('tree2/foo'))
 
1576
        self.assertFileEqual('baz', 'tree2/bar')
 
1577
 
 
1578
    def test_create_from_tree_bytes(self):
 
1579
        """Provided lines are used instead of tree content."""
 
1580
        tree1 = self.make_branch_and_tree('tree1')
 
1581
        self.build_tree_contents([('tree1/foo', 'bar'),])
 
1582
        tree1.add('foo', 'foo-id')
 
1583
        tree2 = self.make_branch_and_tree('tree2')
 
1584
        tt = TreeTransform(tree2)
 
1585
        foo_trans_id = tt.create_path('foo', tt.root)
 
1586
        create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
 
1587
        tt.apply()
 
1588
        self.assertFileEqual('qux', 'tree2/foo')
 
1589
 
 
1590
    def test_create_from_tree_symlink(self):
 
1591
        self.requireFeature(SymlinkFeature)
 
1592
        tree1 = self.make_branch_and_tree('tree1')
 
1593
        os.symlink('bar', 'tree1/foo')
 
1594
        tree1.add('foo', 'foo-id')
 
1595
        tt = TreeTransform(self.make_branch_and_tree('tree2'))
 
1596
        foo_trans_id = tt.create_path('foo', tt.root)
 
1597
        create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
 
1598
        tt.apply()
 
1599
        self.assertEqual('bar', os.readlink('tree2/foo'))
492
1600
 
493
1601
 
494
1602
class TransformGroup(object):
495
 
    def __init__(self, dirname):
 
1603
 
 
1604
    def __init__(self, dirname, root_id):
496
1605
        self.name = dirname
497
1606
        os.mkdir(dirname)
498
1607
        self.wt = BzrDir.create_standalone_workingtree(dirname)
 
1608
        self.wt.set_root_id(root_id)
499
1609
        self.b = self.wt.branch
500
1610
        self.tt = TreeTransform(self.wt)
501
1611
        self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
502
1612
 
 
1613
 
503
1614
def conflict_text(tree, merge):
504
1615
    template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
505
1616
    return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
506
1617
 
507
1618
 
 
1619
class TestInventoryAltered(tests.TestCaseWithTransport):
 
1620
 
 
1621
    def test_inventory_altered_unchanged(self):
 
1622
        tree = self.make_branch_and_tree('tree')
 
1623
        self.build_tree(['tree/foo'])
 
1624
        tree.add('foo', 'foo-id')
 
1625
        with TransformPreview(tree) as tt:
 
1626
            self.assertEqual([], tt._inventory_altered())
 
1627
 
 
1628
    def test_inventory_altered_changed_parent_id(self):
 
1629
        tree = self.make_branch_and_tree('tree')
 
1630
        self.build_tree(['tree/foo'])
 
1631
        tree.add('foo', 'foo-id')
 
1632
        with TransformPreview(tree) as tt:
 
1633
            tt.unversion_file(tt.root)
 
1634
            tt.version_file('new-id', tt.root)
 
1635
            foo_trans_id = tt.trans_id_tree_file_id('foo-id')
 
1636
            foo_tuple = ('foo', foo_trans_id)
 
1637
            root_tuple = ('', tt.root)
 
1638
            self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
 
1639
 
 
1640
    def test_inventory_altered_noop_changed_parent_id(self):
 
1641
        tree = self.make_branch_and_tree('tree')
 
1642
        self.build_tree(['tree/foo'])
 
1643
        tree.add('foo', 'foo-id')
 
1644
        with TransformPreview(tree) as tt:
 
1645
            tt.unversion_file(tt.root)
 
1646
            tt.version_file(tree.get_root_id(), tt.root)
 
1647
            foo_trans_id = tt.trans_id_tree_file_id('foo-id')
 
1648
            self.assertEqual([], tt._inventory_altered())
 
1649
 
 
1650
 
508
1651
class TestTransformMerge(TestCaseInTempDir):
 
1652
 
509
1653
    def test_text_merge(self):
510
 
        base = TransformGroup("base")
 
1654
        root_id = generate_ids.gen_root_id()
 
1655
        base = TransformGroup("base", root_id)
511
1656
        base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
512
1657
        base.tt.new_file('b', base.root, 'b1', 'b')
513
1658
        base.tt.new_file('c', base.root, 'c', 'c')
517
1662
        base.tt.new_directory('g', base.root, 'g')
518
1663
        base.tt.new_directory('h', base.root, 'h')
519
1664
        base.tt.apply()
520
 
        other = TransformGroup("other")
 
1665
        other = TransformGroup("other", root_id)
521
1666
        other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
522
1667
        other.tt.new_file('b', other.root, 'b2', 'b')
523
1668
        other.tt.new_file('c', other.root, 'c2', 'c')
528
1673
        other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
529
1674
        other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
530
1675
        other.tt.apply()
531
 
        this = TransformGroup("this")
 
1676
        this = TransformGroup("this", root_id)
532
1677
        this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
533
1678
        this.tt.new_file('b', this.root, 'b', 'b')
534
1679
        this.tt.new_file('c', this.root, 'c', 'c')
540
1685
        this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
541
1686
        this.tt.apply()
542
1687
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
1688
 
543
1689
        # textual merge
544
1690
        self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
545
1691
        # three-way text conflict
546
 
        self.assertEqual(this.wt.get_file('b').read(), 
 
1692
        self.assertEqual(this.wt.get_file('b').read(),
547
1693
                         conflict_text('b', 'b2'))
548
1694
        # OTHER wins
549
1695
        self.assertEqual(this.wt.get_file('c').read(), 'c2')
553
1699
        self.assertEqual(this.wt.get_file('e').read(), 'e2')
554
1700
        # No change
555
1701
        self.assertEqual(this.wt.get_file('f').read(), 'f')
556
 
        # Correct correct results when THIS == OTHER 
 
1702
        # Correct correct results when THIS == OTHER
557
1703
        self.assertEqual(this.wt.get_file('g').read(), 'g')
558
1704
        # Text conflict when THIS & OTHER are text and BASE is dir
559
 
        self.assertEqual(this.wt.get_file('h').read(), 
 
1705
        self.assertEqual(this.wt.get_file('h').read(),
560
1706
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
561
1707
        self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
562
1708
                         '1\n2\n3\n4\n')
563
1709
        self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
564
1710
                         'h\ni\nj\nk\n')
565
1711
        self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
566
 
        self.assertEqual(this.wt.get_file('i').read(), 
 
1712
        self.assertEqual(this.wt.get_file('i').read(),
567
1713
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
568
1714
        self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
569
1715
                         '1\n2\n3\n4\n')
580
1726
        self.assertSubset(merge_modified, modified)
581
1727
        self.assertEqual(len(merge_modified), len(modified))
582
1728
        this.wt.remove('b')
583
 
        this.wt.revert([])
 
1729
        this.wt.revert()
584
1730
 
585
1731
    def test_file_merge(self):
586
 
        if not has_symlinks():
587
 
            raise TestSkipped('Symlinks are not supported on this platform')
588
 
        base = TransformGroup("BASE")
589
 
        this = TransformGroup("THIS")
590
 
        other = TransformGroup("OTHER")
 
1732
        self.requireFeature(SymlinkFeature)
 
1733
        root_id = generate_ids.gen_root_id()
 
1734
        base = TransformGroup("BASE", root_id)
 
1735
        this = TransformGroup("THIS", root_id)
 
1736
        other = TransformGroup("OTHER", root_id)
591
1737
        for tg in this, base, other:
592
1738
            tg.tt.new_directory('a', tg.root, 'a')
593
1739
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
594
1740
            tg.tt.new_file('c', tg.root, 'c', 'c')
595
1741
            tg.tt.new_symlink('d', tg.root, tg.name, 'd')
596
 
        targets = ((base, 'base-e', 'base-f', None, None), 
597
 
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'), 
 
1742
        targets = ((base, 'base-e', 'base-f', None, None),
 
1743
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'),
598
1744
                   (other, 'other-e', None, 'other-g', 'other-h'))
599
1745
        for tg, e_target, f_target, g_target, h_target in targets:
600
 
            for link, target in (('e', e_target), ('f', f_target), 
 
1746
            for link, target in (('e', e_target), ('f', f_target),
601
1747
                                 ('g', g_target), ('h', h_target)):
602
1748
                if target is not None:
603
1749
                    tg.tt.new_symlink(link, tg.root, target, link)
625
1771
        self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
626
1772
 
627
1773
    def test_filename_merge(self):
628
 
        base = TransformGroup("BASE")
629
 
        this = TransformGroup("THIS")
630
 
        other = TransformGroup("OTHER")
631
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
 
1774
        root_id = generate_ids.gen_root_id()
 
1775
        base = TransformGroup("BASE", root_id)
 
1776
        this = TransformGroup("THIS", root_id)
 
1777
        other = TransformGroup("OTHER", root_id)
 
1778
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
632
1779
                                   for t in [base, this, other]]
633
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
 
1780
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
634
1781
                                   for t in [base, this, other]]
635
1782
        base.tt.new_directory('c', base_a, 'c')
636
1783
        this.tt.new_directory('c1', this_a, 'c')
657
1804
        self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
658
1805
 
659
1806
    def test_filename_merge_conflicts(self):
660
 
        base = TransformGroup("BASE")
661
 
        this = TransformGroup("THIS")
662
 
        other = TransformGroup("OTHER")
663
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
 
1807
        root_id = generate_ids.gen_root_id()
 
1808
        base = TransformGroup("BASE", root_id)
 
1809
        this = TransformGroup("THIS", root_id)
 
1810
        other = TransformGroup("OTHER", root_id)
 
1811
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
664
1812
                                   for t in [base, this, other]]
665
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
 
1813
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
666
1814
                                   for t in [base, this, other]]
667
1815
 
668
1816
        base.tt.new_file('g', base_a, 'g', 'g')
686
1834
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
687
1835
        self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
688
1836
 
689
 
class TestBuildTree(TestCaseInTempDir):
690
 
    def test_build_tree(self):
691
 
        if not has_symlinks():
692
 
            raise TestSkipped('Test requires symlink support')
 
1837
 
 
1838
class TestBuildTree(tests.TestCaseWithTransport):
 
1839
 
 
1840
    def test_build_tree_with_symlinks(self):
 
1841
        self.requireFeature(SymlinkFeature)
693
1842
        os.mkdir('a')
694
1843
        a = BzrDir.create_standalone_workingtree('a')
695
1844
        os.mkdir('a/foo')
698
1847
        a.add(['foo', 'foo/bar', 'foo/baz'])
699
1848
        a.commit('initial commit')
700
1849
        b = BzrDir.create_standalone_workingtree('b')
701
 
        build_tree(a.basis_tree(), b)
 
1850
        basis = a.basis_tree()
 
1851
        basis.lock_read()
 
1852
        self.addCleanup(basis.unlock)
 
1853
        build_tree(basis, b)
702
1854
        self.assertIs(os.path.isdir('b/foo'), True)
703
1855
        self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
704
1856
        self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
705
 
        
706
 
class MockTransform(object):
707
 
 
708
 
    def has_named_child(self, by_parent, parent_id, name):
709
 
        for child_id in by_parent[parent_id]:
710
 
            if child_id == '0':
711
 
                if name == "name~":
712
 
                    return True
713
 
            elif name == "name.~%s~" % child_id:
714
 
                return True
715
 
        return False
716
 
 
717
 
class MockEntry(object):
718
 
    def __init__(self):
719
 
        object.__init__(self)
720
 
        self.name = "name"
721
 
 
722
 
class TestGetBackupName(TestCase):
723
 
    def test_get_backup_name(self):
 
1857
 
 
1858
    def test_build_with_references(self):
 
1859
        tree = self.make_branch_and_tree('source',
 
1860
            format='dirstate-with-subtree')
 
1861
        subtree = self.make_branch_and_tree('source/subtree',
 
1862
            format='dirstate-with-subtree')
 
1863
        tree.add_reference(subtree)
 
1864
        tree.commit('a revision')
 
1865
        tree.branch.create_checkout('target')
 
1866
        self.assertPathExists('target')
 
1867
        self.assertPathExists('target/subtree')
 
1868
 
 
1869
    def test_file_conflict_handling(self):
 
1870
        """Ensure that when building trees, conflict handling is done"""
 
1871
        source = self.make_branch_and_tree('source')
 
1872
        target = self.make_branch_and_tree('target')
 
1873
        self.build_tree(['source/file', 'target/file'])
 
1874
        source.add('file', 'new-file')
 
1875
        source.commit('added file')
 
1876
        build_tree(source.basis_tree(), target)
 
1877
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
1878
                          'file.moved', 'file', None, 'new-file')],
 
1879
                         target.conflicts())
 
1880
        target2 = self.make_branch_and_tree('target2')
 
1881
        target_file = file('target2/file', 'wb')
 
1882
        try:
 
1883
            source_file = file('source/file', 'rb')
 
1884
            try:
 
1885
                target_file.write(source_file.read())
 
1886
            finally:
 
1887
                source_file.close()
 
1888
        finally:
 
1889
            target_file.close()
 
1890
        build_tree(source.basis_tree(), target2)
 
1891
        self.assertEqual([], target2.conflicts())
 
1892
 
 
1893
    def test_symlink_conflict_handling(self):
 
1894
        """Ensure that when building trees, conflict handling is done"""
 
1895
        self.requireFeature(SymlinkFeature)
 
1896
        source = self.make_branch_and_tree('source')
 
1897
        os.symlink('foo', 'source/symlink')
 
1898
        source.add('symlink', 'new-symlink')
 
1899
        source.commit('added file')
 
1900
        target = self.make_branch_and_tree('target')
 
1901
        os.symlink('bar', 'target/symlink')
 
1902
        build_tree(source.basis_tree(), target)
 
1903
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
1904
            'symlink.moved', 'symlink', None, 'new-symlink')],
 
1905
            target.conflicts())
 
1906
        target = self.make_branch_and_tree('target2')
 
1907
        os.symlink('foo', 'target2/symlink')
 
1908
        build_tree(source.basis_tree(), target)
 
1909
        self.assertEqual([], target.conflicts())
 
1910
 
 
1911
    def test_directory_conflict_handling(self):
 
1912
        """Ensure that when building trees, conflict handling is done"""
 
1913
        source = self.make_branch_and_tree('source')
 
1914
        target = self.make_branch_and_tree('target')
 
1915
        self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
 
1916
        source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
 
1917
        source.commit('added file')
 
1918
        build_tree(source.basis_tree(), target)
 
1919
        self.assertEqual([], target.conflicts())
 
1920
        self.assertPathExists('target/dir1/file')
 
1921
 
 
1922
        # Ensure contents are merged
 
1923
        target = self.make_branch_and_tree('target2')
 
1924
        self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
 
1925
        build_tree(source.basis_tree(), target)
 
1926
        self.assertEqual([], target.conflicts())
 
1927
        self.assertPathExists('target2/dir1/file2')
 
1928
        self.assertPathExists('target2/dir1/file')
 
1929
 
 
1930
        # Ensure new contents are suppressed for existing branches
 
1931
        target = self.make_branch_and_tree('target3')
 
1932
        self.make_branch('target3/dir1')
 
1933
        self.build_tree(['target3/dir1/file2'])
 
1934
        build_tree(source.basis_tree(), target)
 
1935
        self.assertPathDoesNotExist('target3/dir1/file')
 
1936
        self.assertPathExists('target3/dir1/file2')
 
1937
        self.assertPathExists('target3/dir1.diverted/file')
 
1938
        self.assertEqual([DuplicateEntry('Diverted to',
 
1939
            'dir1.diverted', 'dir1', 'new-dir1', None)],
 
1940
            target.conflicts())
 
1941
 
 
1942
        target = self.make_branch_and_tree('target4')
 
1943
        self.build_tree(['target4/dir1/'])
 
1944
        self.make_branch('target4/dir1/file')
 
1945
        build_tree(source.basis_tree(), target)
 
1946
        self.assertPathExists('target4/dir1/file')
 
1947
        self.assertEqual('directory', file_kind('target4/dir1/file'))
 
1948
        self.assertPathExists('target4/dir1/file.diverted')
 
1949
        self.assertEqual([DuplicateEntry('Diverted to',
 
1950
            'dir1/file.diverted', 'dir1/file', 'new-file', None)],
 
1951
            target.conflicts())
 
1952
 
 
1953
    def test_mixed_conflict_handling(self):
 
1954
        """Ensure that when building trees, conflict handling is done"""
 
1955
        source = self.make_branch_and_tree('source')
 
1956
        target = self.make_branch_and_tree('target')
 
1957
        self.build_tree(['source/name', 'target/name/'])
 
1958
        source.add('name', 'new-name')
 
1959
        source.commit('added file')
 
1960
        build_tree(source.basis_tree(), target)
 
1961
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
1962
            'name.moved', 'name', None, 'new-name')], target.conflicts())
 
1963
 
 
1964
    def test_raises_in_populated(self):
 
1965
        source = self.make_branch_and_tree('source')
 
1966
        self.build_tree(['source/name'])
 
1967
        source.add('name')
 
1968
        source.commit('added name')
 
1969
        target = self.make_branch_and_tree('target')
 
1970
        self.build_tree(['target/name'])
 
1971
        target.add('name')
 
1972
        self.assertRaises(errors.WorkingTreeAlreadyPopulated,
 
1973
            build_tree, source.basis_tree(), target)
 
1974
 
 
1975
    def test_build_tree_rename_count(self):
 
1976
        source = self.make_branch_and_tree('source')
 
1977
        self.build_tree(['source/file1', 'source/dir1/'])
 
1978
        source.add(['file1', 'dir1'])
 
1979
        source.commit('add1')
 
1980
        target1 = self.make_branch_and_tree('target1')
 
1981
        transform_result = build_tree(source.basis_tree(), target1)
 
1982
        self.assertEqual(2, transform_result.rename_count)
 
1983
 
 
1984
        self.build_tree(['source/dir1/file2'])
 
1985
        source.add(['dir1/file2'])
 
1986
        source.commit('add3')
 
1987
        target2 = self.make_branch_and_tree('target2')
 
1988
        transform_result = build_tree(source.basis_tree(), target2)
 
1989
        # children of non-root directories should not be renamed
 
1990
        self.assertEqual(2, transform_result.rename_count)
 
1991
 
 
1992
    def create_ab_tree(self):
 
1993
        """Create a committed test tree with two files"""
 
1994
        source = self.make_branch_and_tree('source')
 
1995
        self.build_tree_contents([('source/file1', 'A')])
 
1996
        self.build_tree_contents([('source/file2', 'B')])
 
1997
        source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
 
1998
        source.commit('commit files')
 
1999
        source.lock_write()
 
2000
        self.addCleanup(source.unlock)
 
2001
        return source
 
2002
 
 
2003
    def test_build_tree_accelerator_tree(self):
 
2004
        source = self.create_ab_tree()
 
2005
        self.build_tree_contents([('source/file2', 'C')])
 
2006
        calls = []
 
2007
        real_source_get_file = source.get_file
 
2008
        def get_file(file_id, path=None):
 
2009
            calls.append(file_id)
 
2010
            return real_source_get_file(file_id, path)
 
2011
        source.get_file = get_file
 
2012
        target = self.make_branch_and_tree('target')
 
2013
        revision_tree = source.basis_tree()
 
2014
        revision_tree.lock_read()
 
2015
        self.addCleanup(revision_tree.unlock)
 
2016
        build_tree(revision_tree, target, source)
 
2017
        self.assertEqual(['file1-id'], calls)
 
2018
        target.lock_read()
 
2019
        self.addCleanup(target.unlock)
 
2020
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
2021
 
 
2022
    def test_build_tree_accelerator_tree_observes_sha1(self):
 
2023
        source = self.create_ab_tree()
 
2024
        sha1 = osutils.sha_string('A')
 
2025
        target = self.make_branch_and_tree('target')
 
2026
        target.lock_write()
 
2027
        self.addCleanup(target.unlock)
 
2028
        state = target.current_dirstate()
 
2029
        state._cutoff_time = time.time() + 60
 
2030
        build_tree(source.basis_tree(), target, source)
 
2031
        entry = state._get_entry(0, path_utf8='file1')
 
2032
        self.assertEqual(sha1, entry[1][0][1])
 
2033
 
 
2034
    def test_build_tree_accelerator_tree_missing_file(self):
 
2035
        source = self.create_ab_tree()
 
2036
        os.unlink('source/file1')
 
2037
        source.remove(['file2'])
 
2038
        target = self.make_branch_and_tree('target')
 
2039
        revision_tree = source.basis_tree()
 
2040
        revision_tree.lock_read()
 
2041
        self.addCleanup(revision_tree.unlock)
 
2042
        build_tree(revision_tree, target, source)
 
2043
        target.lock_read()
 
2044
        self.addCleanup(target.unlock)
 
2045
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
2046
 
 
2047
    def test_build_tree_accelerator_wrong_kind(self):
 
2048
        self.requireFeature(SymlinkFeature)
 
2049
        source = self.make_branch_and_tree('source')
 
2050
        self.build_tree_contents([('source/file1', '')])
 
2051
        self.build_tree_contents([('source/file2', '')])
 
2052
        source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
 
2053
        source.commit('commit files')
 
2054
        os.unlink('source/file2')
 
2055
        self.build_tree_contents([('source/file2/', 'C')])
 
2056
        os.unlink('source/file1')
 
2057
        os.symlink('file2', 'source/file1')
 
2058
        calls = []
 
2059
        real_source_get_file = source.get_file
 
2060
        def get_file(file_id, path=None):
 
2061
            calls.append(file_id)
 
2062
            return real_source_get_file(file_id, path)
 
2063
        source.get_file = get_file
 
2064
        target = self.make_branch_and_tree('target')
 
2065
        revision_tree = source.basis_tree()
 
2066
        revision_tree.lock_read()
 
2067
        self.addCleanup(revision_tree.unlock)
 
2068
        build_tree(revision_tree, target, source)
 
2069
        self.assertEqual([], calls)
 
2070
        target.lock_read()
 
2071
        self.addCleanup(target.unlock)
 
2072
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
2073
 
 
2074
    def test_build_tree_hardlink(self):
 
2075
        self.requireFeature(HardlinkFeature)
 
2076
        source = self.create_ab_tree()
 
2077
        target = self.make_branch_and_tree('target')
 
2078
        revision_tree = source.basis_tree()
 
2079
        revision_tree.lock_read()
 
2080
        self.addCleanup(revision_tree.unlock)
 
2081
        build_tree(revision_tree, target, source, hardlink=True)
 
2082
        target.lock_read()
 
2083
        self.addCleanup(target.unlock)
 
2084
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
2085
        source_stat = os.stat('source/file1')
 
2086
        target_stat = os.stat('target/file1')
 
2087
        self.assertEqual(source_stat, target_stat)
 
2088
 
 
2089
        # Explicitly disallowing hardlinks should prevent them.
 
2090
        target2 = self.make_branch_and_tree('target2')
 
2091
        build_tree(revision_tree, target2, source, hardlink=False)
 
2092
        target2.lock_read()
 
2093
        self.addCleanup(target2.unlock)
 
2094
        self.assertEqual([], list(target2.iter_changes(revision_tree)))
 
2095
        source_stat = os.stat('source/file1')
 
2096
        target2_stat = os.stat('target2/file1')
 
2097
        self.assertNotEqual(source_stat, target2_stat)
 
2098
 
 
2099
    def test_build_tree_accelerator_tree_moved(self):
 
2100
        source = self.make_branch_and_tree('source')
 
2101
        self.build_tree_contents([('source/file1', 'A')])
 
2102
        source.add(['file1'], ['file1-id'])
 
2103
        source.commit('commit files')
 
2104
        source.rename_one('file1', 'file2')
 
2105
        source.lock_read()
 
2106
        self.addCleanup(source.unlock)
 
2107
        target = self.make_branch_and_tree('target')
 
2108
        revision_tree = source.basis_tree()
 
2109
        revision_tree.lock_read()
 
2110
        self.addCleanup(revision_tree.unlock)
 
2111
        build_tree(revision_tree, target, source)
 
2112
        target.lock_read()
 
2113
        self.addCleanup(target.unlock)
 
2114
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
2115
 
 
2116
    def test_build_tree_hardlinks_preserve_execute(self):
 
2117
        self.requireFeature(HardlinkFeature)
 
2118
        source = self.create_ab_tree()
 
2119
        tt = TreeTransform(source)
 
2120
        trans_id = tt.trans_id_tree_file_id('file1-id')
 
2121
        tt.set_executability(True, trans_id)
 
2122
        tt.apply()
 
2123
        self.assertTrue(source.is_executable('file1-id'))
 
2124
        target = self.make_branch_and_tree('target')
 
2125
        revision_tree = source.basis_tree()
 
2126
        revision_tree.lock_read()
 
2127
        self.addCleanup(revision_tree.unlock)
 
2128
        build_tree(revision_tree, target, source, hardlink=True)
 
2129
        target.lock_read()
 
2130
        self.addCleanup(target.unlock)
 
2131
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
2132
        self.assertTrue(source.is_executable('file1-id'))
 
2133
 
 
2134
    def install_rot13_content_filter(self, pattern):
 
2135
        # We could use
 
2136
        # self.addCleanup(filters._reset_registry, filters._reset_registry())
 
2137
        # below, but that looks a bit... hard to read even if it's exactly
 
2138
        # the same thing.
 
2139
        original_registry = filters._reset_registry()
 
2140
        def restore_registry():
 
2141
            filters._reset_registry(original_registry)
 
2142
        self.addCleanup(restore_registry)
 
2143
        def rot13(chunks, context=None):
 
2144
            return [''.join(chunks).encode('rot13')]
 
2145
        rot13filter = filters.ContentFilter(rot13, rot13)
 
2146
        filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
 
2147
        os.mkdir(self.test_home_dir + '/.bazaar')
 
2148
        rules_filename = self.test_home_dir + '/.bazaar/rules'
 
2149
        f = open(rules_filename, 'wb')
 
2150
        f.write('[name %s]\nrot13=yes\n' % (pattern,))
 
2151
        f.close()
 
2152
        def uninstall_rules():
 
2153
            os.remove(rules_filename)
 
2154
            rules.reset_rules()
 
2155
        self.addCleanup(uninstall_rules)
 
2156
        rules.reset_rules()
 
2157
 
 
2158
    def test_build_tree_content_filtered_files_are_not_hardlinked(self):
 
2159
        """build_tree will not hardlink files that have content filtering rules
 
2160
        applied to them (but will still hardlink other files from the same tree
 
2161
        if it can).
 
2162
        """
 
2163
        self.requireFeature(HardlinkFeature)
 
2164
        self.install_rot13_content_filter('file1')
 
2165
        source = self.create_ab_tree()
 
2166
        target = self.make_branch_and_tree('target')
 
2167
        revision_tree = source.basis_tree()
 
2168
        revision_tree.lock_read()
 
2169
        self.addCleanup(revision_tree.unlock)
 
2170
        build_tree(revision_tree, target, source, hardlink=True)
 
2171
        target.lock_read()
 
2172
        self.addCleanup(target.unlock)
 
2173
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
2174
        source_stat = os.stat('source/file1')
 
2175
        target_stat = os.stat('target/file1')
 
2176
        self.assertNotEqual(source_stat, target_stat)
 
2177
        source_stat = os.stat('source/file2')
 
2178
        target_stat = os.stat('target/file2')
 
2179
        self.assertEqualStat(source_stat, target_stat)
 
2180
 
 
2181
    def test_case_insensitive_build_tree_inventory(self):
 
2182
        if (tests.CaseInsensitiveFilesystemFeature.available()
 
2183
            or tests.CaseInsCasePresFilenameFeature.available()):
 
2184
            raise tests.UnavailableFeature('Fully case sensitive filesystem')
 
2185
        source = self.make_branch_and_tree('source')
 
2186
        self.build_tree(['source/file', 'source/FILE'])
 
2187
        source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
 
2188
        source.commit('added files')
 
2189
        # Don't try this at home, kids!
 
2190
        # Force the tree to report that it is case insensitive
 
2191
        target = self.make_branch_and_tree('target')
 
2192
        target.case_sensitive = False
 
2193
        build_tree(source.basis_tree(), target, source, delta_from_tree=True)
 
2194
        self.assertEqual('file.moved', target.id2path('lower-id'))
 
2195
        self.assertEqual('FILE', target.id2path('upper-id'))
 
2196
 
 
2197
    def test_build_tree_observes_sha(self):
 
2198
        source = self.make_branch_and_tree('source')
 
2199
        self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
 
2200
        source.add(['file1', 'dir', 'dir/file2'],
 
2201
                   ['file1-id', 'dir-id', 'file2-id'])
 
2202
        source.commit('new files')
 
2203
        target = self.make_branch_and_tree('target')
 
2204
        target.lock_write()
 
2205
        self.addCleanup(target.unlock)
 
2206
        # We make use of the fact that DirState caches its cutoff time. So we
 
2207
        # set the 'safe' time to one minute in the future.
 
2208
        state = target.current_dirstate()
 
2209
        state._cutoff_time = time.time() + 60
 
2210
        build_tree(source.basis_tree(), target)
 
2211
        entry1_sha = osutils.sha_file_by_name('source/file1')
 
2212
        entry2_sha = osutils.sha_file_by_name('source/dir/file2')
 
2213
        # entry[1] is the state information, entry[1][0] is the state of the
 
2214
        # working tree, entry[1][0][1] is the sha value for the current working
 
2215
        # tree
 
2216
        entry1 = state._get_entry(0, path_utf8='file1')
 
2217
        self.assertEqual(entry1_sha, entry1[1][0][1])
 
2218
        # The 'size' field must also be set.
 
2219
        self.assertEqual(25, entry1[1][0][2])
 
2220
        entry1_state = entry1[1][0]
 
2221
        entry2 = state._get_entry(0, path_utf8='dir/file2')
 
2222
        self.assertEqual(entry2_sha, entry2[1][0][1])
 
2223
        self.assertEqual(29, entry2[1][0][2])
 
2224
        entry2_state = entry2[1][0]
 
2225
        # Now, make sure that we don't have to re-read the content. The
 
2226
        # packed_stat should match exactly.
 
2227
        self.assertEqual(entry1_sha, target.get_file_sha1('file1-id', 'file1'))
 
2228
        self.assertEqual(entry2_sha,
 
2229
                         target.get_file_sha1('file2-id', 'dir/file2'))
 
2230
        self.assertEqual(entry1_state, entry1[1][0])
 
2231
        self.assertEqual(entry2_state, entry2[1][0])
 
2232
 
 
2233
 
 
2234
class TestCommitTransform(tests.TestCaseWithTransport):
 
2235
 
 
2236
    def get_branch(self):
 
2237
        tree = self.make_branch_and_tree('tree')
 
2238
        tree.lock_write()
 
2239
        self.addCleanup(tree.unlock)
 
2240
        tree.commit('empty commit')
 
2241
        return tree.branch
 
2242
 
 
2243
    def get_branch_and_transform(self):
 
2244
        branch = self.get_branch()
 
2245
        tt = TransformPreview(branch.basis_tree())
 
2246
        self.addCleanup(tt.finalize)
 
2247
        return branch, tt
 
2248
 
 
2249
    def test_commit_wrong_basis(self):
 
2250
        branch = self.get_branch()
 
2251
        basis = branch.repository.revision_tree(
 
2252
            _mod_revision.NULL_REVISION)
 
2253
        tt = TransformPreview(basis)
 
2254
        self.addCleanup(tt.finalize)
 
2255
        e = self.assertRaises(ValueError, tt.commit, branch, '')
 
2256
        self.assertEqual('TreeTransform not based on branch basis: null:',
 
2257
                         str(e))
 
2258
 
 
2259
    def test_empy_commit(self):
 
2260
        branch, tt = self.get_branch_and_transform()
 
2261
        rev = tt.commit(branch, 'my message')
 
2262
        self.assertEqual(2, branch.revno())
 
2263
        repo = branch.repository
 
2264
        self.assertEqual('my message', repo.get_revision(rev).message)
 
2265
 
 
2266
    def test_merge_parents(self):
 
2267
        branch, tt = self.get_branch_and_transform()
 
2268
        rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
 
2269
        self.assertEqual(['rev1b', 'rev1c'],
 
2270
                         branch.basis_tree().get_parent_ids()[1:])
 
2271
 
 
2272
    def test_first_commit(self):
 
2273
        branch = self.make_branch('branch')
 
2274
        branch.lock_write()
 
2275
        self.addCleanup(branch.unlock)
 
2276
        tt = TransformPreview(branch.basis_tree())
 
2277
        self.addCleanup(tt.finalize)
 
2278
        tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
 
2279
        rev = tt.commit(branch, 'my message')
 
2280
        self.assertEqual([], branch.basis_tree().get_parent_ids())
 
2281
        self.assertNotEqual(_mod_revision.NULL_REVISION,
 
2282
                            branch.last_revision())
 
2283
 
 
2284
    def test_first_commit_with_merge_parents(self):
 
2285
        branch = self.make_branch('branch')
 
2286
        branch.lock_write()
 
2287
        self.addCleanup(branch.unlock)
 
2288
        tt = TransformPreview(branch.basis_tree())
 
2289
        self.addCleanup(tt.finalize)
 
2290
        e = self.assertRaises(ValueError, tt.commit, branch,
 
2291
                          'my message', ['rev1b-id'])
 
2292
        self.assertEqual('Cannot supply merge parents for first commit.',
 
2293
                         str(e))
 
2294
        self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
 
2295
 
 
2296
    def test_add_files(self):
 
2297
        branch, tt = self.get_branch_and_transform()
 
2298
        tt.new_file('file', tt.root, 'contents', 'file-id')
 
2299
        trans_id = tt.new_directory('dir', tt.root, 'dir-id')
 
2300
        if SymlinkFeature.available():
 
2301
            tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
 
2302
        rev = tt.commit(branch, 'message')
 
2303
        tree = branch.basis_tree()
 
2304
        self.assertEqual('file', tree.id2path('file-id'))
 
2305
        self.assertEqual('contents', tree.get_file_text('file-id'))
 
2306
        self.assertEqual('dir', tree.id2path('dir-id'))
 
2307
        if SymlinkFeature.available():
 
2308
            self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
 
2309
            self.assertEqual('target', tree.get_symlink_target('symlink-id'))
 
2310
 
 
2311
    def test_add_unversioned(self):
 
2312
        branch, tt = self.get_branch_and_transform()
 
2313
        tt.new_file('file', tt.root, 'contents')
 
2314
        self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
 
2315
                          'message', strict=True)
 
2316
 
 
2317
    def test_modify_strict(self):
 
2318
        branch, tt = self.get_branch_and_transform()
 
2319
        tt.new_file('file', tt.root, 'contents', 'file-id')
 
2320
        tt.commit(branch, 'message', strict=True)
 
2321
        tt = TransformPreview(branch.basis_tree())
 
2322
        self.addCleanup(tt.finalize)
 
2323
        trans_id = tt.trans_id_file_id('file-id')
 
2324
        tt.delete_contents(trans_id)
 
2325
        tt.create_file('contents', trans_id)
 
2326
        tt.commit(branch, 'message', strict=True)
 
2327
 
 
2328
    def test_commit_malformed(self):
 
2329
        """Committing a malformed transform should raise an exception.
 
2330
 
 
2331
        In this case, we are adding a file without adding its parent.
 
2332
        """
 
2333
        branch, tt = self.get_branch_and_transform()
 
2334
        parent_id = tt.trans_id_file_id('parent-id')
 
2335
        tt.new_file('file', parent_id, 'contents', 'file-id')
 
2336
        self.assertRaises(errors.MalformedTransform, tt.commit, branch,
 
2337
                          'message')
 
2338
 
 
2339
    def test_commit_rich_revision_data(self):
 
2340
        branch, tt = self.get_branch_and_transform()
 
2341
        rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
 
2342
                           committer='me <me@example.com>',
 
2343
                           revprops={'foo': 'bar'}, revision_id='revid-1',
 
2344
                           authors=['Author1 <author1@example.com>',
 
2345
                              'Author2 <author2@example.com>',
 
2346
                               ])
 
2347
        self.assertEqual('revid-1', rev_id)
 
2348
        revision = branch.repository.get_revision(rev_id)
 
2349
        self.assertEqual(1, revision.timestamp)
 
2350
        self.assertEqual(43201, revision.timezone)
 
2351
        self.assertEqual('me <me@example.com>', revision.committer)
 
2352
        self.assertEqual(['Author1 <author1@example.com>',
 
2353
                          'Author2 <author2@example.com>'],
 
2354
                         revision.get_apparent_authors())
 
2355
        del revision.properties['authors']
 
2356
        self.assertEqual({'foo': 'bar',
 
2357
                          'branch-nick': 'tree'},
 
2358
                         revision.properties)
 
2359
 
 
2360
    def test_no_explicit_revprops(self):
 
2361
        branch, tt = self.get_branch_and_transform()
 
2362
        rev_id = tt.commit(branch, 'message', authors=[
 
2363
            'Author1 <author1@example.com>',
 
2364
            'Author2 <author2@example.com>', ])
 
2365
        revision = branch.repository.get_revision(rev_id)
 
2366
        self.assertEqual(['Author1 <author1@example.com>',
 
2367
                          'Author2 <author2@example.com>'],
 
2368
                         revision.get_apparent_authors())
 
2369
        self.assertEqual('tree', revision.properties['branch-nick'])
 
2370
 
 
2371
 
 
2372
class TestBackupName(tests.TestCase):
 
2373
 
 
2374
    def test_deprecations(self):
 
2375
        class MockTransform(object):
 
2376
 
 
2377
            def has_named_child(self, by_parent, parent_id, name):
 
2378
                return name in by_parent.get(parent_id, [])
 
2379
 
 
2380
        class MockEntry(object):
 
2381
 
 
2382
            def __init__(self):
 
2383
                object.__init__(self)
 
2384
                self.name = "name"
 
2385
 
724
2386
        tt = MockTransform()
725
 
        name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
726
 
        self.assertEqual(name, 'name.~1~')
727
 
        name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
728
 
        self.assertEqual(name, 'name.~2~')
729
 
        name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
730
 
        self.assertEqual(name, 'name.~1~')
731
 
        name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
732
 
        self.assertEqual(name, 'name.~1~')
733
 
        name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
734
 
        self.assertEqual(name, 'name.~4~')
 
2387
        name1 = self.applyDeprecated(
 
2388
            symbol_versioning.deprecated_in((2, 3, 0)),
 
2389
            transform.get_backup_name, MockEntry(), {'a':[]}, 'a', tt)
 
2390
        self.assertEqual('name.~1~', name1)
 
2391
        name2 = self.applyDeprecated(
 
2392
            symbol_versioning.deprecated_in((2, 3, 0)),
 
2393
            transform._get_backup_name, 'name', {'a':['name.~1~']}, 'a', tt)
 
2394
        self.assertEqual('name.~2~', name2)
 
2395
 
 
2396
 
 
2397
class TestFileMover(tests.TestCaseWithTransport):
 
2398
 
 
2399
    def test_file_mover(self):
 
2400
        self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
 
2401
        mover = _FileMover()
 
2402
        mover.rename('a', 'q')
 
2403
        self.assertPathExists('q')
 
2404
        self.assertPathDoesNotExist('a')
 
2405
        self.assertPathExists('q/b')
 
2406
        self.assertPathExists('c')
 
2407
        self.assertPathExists('c/d')
 
2408
 
 
2409
    def test_pre_delete_rollback(self):
 
2410
        self.build_tree(['a/'])
 
2411
        mover = _FileMover()
 
2412
        mover.pre_delete('a', 'q')
 
2413
        self.assertPathExists('q')
 
2414
        self.assertPathDoesNotExist('a')
 
2415
        mover.rollback()
 
2416
        self.assertPathDoesNotExist('q')
 
2417
        self.assertPathExists('a')
 
2418
 
 
2419
    def test_apply_deletions(self):
 
2420
        self.build_tree(['a/', 'b/'])
 
2421
        mover = _FileMover()
 
2422
        mover.pre_delete('a', 'q')
 
2423
        mover.pre_delete('b', 'r')
 
2424
        self.assertPathExists('q')
 
2425
        self.assertPathExists('r')
 
2426
        self.assertPathDoesNotExist('a')
 
2427
        self.assertPathDoesNotExist('b')
 
2428
        mover.apply_deletions()
 
2429
        self.assertPathDoesNotExist('q')
 
2430
        self.assertPathDoesNotExist('r')
 
2431
        self.assertPathDoesNotExist('a')
 
2432
        self.assertPathDoesNotExist('b')
 
2433
 
 
2434
    def test_file_mover_rollback(self):
 
2435
        self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
 
2436
        mover = _FileMover()
 
2437
        mover.rename('c/d', 'c/f')
 
2438
        mover.rename('c/e', 'c/d')
 
2439
        try:
 
2440
            mover.rename('a', 'c')
 
2441
        except errors.FileExists, e:
 
2442
            mover.rollback()
 
2443
        self.assertPathExists('a')
 
2444
        self.assertPathExists('c/d')
 
2445
 
 
2446
 
 
2447
class Bogus(Exception):
 
2448
    pass
 
2449
 
 
2450
 
 
2451
class TestTransformRollback(tests.TestCaseWithTransport):
 
2452
 
 
2453
    class ExceptionFileMover(_FileMover):
 
2454
 
 
2455
        def __init__(self, bad_source=None, bad_target=None):
 
2456
            _FileMover.__init__(self)
 
2457
            self.bad_source = bad_source
 
2458
            self.bad_target = bad_target
 
2459
 
 
2460
        def rename(self, source, target):
 
2461
            if (self.bad_source is not None and
 
2462
                source.endswith(self.bad_source)):
 
2463
                raise Bogus
 
2464
            elif (self.bad_target is not None and
 
2465
                target.endswith(self.bad_target)):
 
2466
                raise Bogus
 
2467
            else:
 
2468
                _FileMover.rename(self, source, target)
 
2469
 
 
2470
    def test_rollback_rename(self):
 
2471
        tree = self.make_branch_and_tree('.')
 
2472
        self.build_tree(['a/', 'a/b'])
 
2473
        tt = TreeTransform(tree)
 
2474
        self.addCleanup(tt.finalize)
 
2475
        a_id = tt.trans_id_tree_path('a')
 
2476
        tt.adjust_path('c', tt.root, a_id)
 
2477
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
 
2478
        self.assertRaises(Bogus, tt.apply,
 
2479
                          _mover=self.ExceptionFileMover(bad_source='a'))
 
2480
        self.assertPathExists('a')
 
2481
        self.assertPathExists('a/b')
 
2482
        tt.apply()
 
2483
        self.assertPathExists('c')
 
2484
        self.assertPathExists('c/d')
 
2485
 
 
2486
    def test_rollback_rename_into_place(self):
 
2487
        tree = self.make_branch_and_tree('.')
 
2488
        self.build_tree(['a/', 'a/b'])
 
2489
        tt = TreeTransform(tree)
 
2490
        self.addCleanup(tt.finalize)
 
2491
        a_id = tt.trans_id_tree_path('a')
 
2492
        tt.adjust_path('c', tt.root, a_id)
 
2493
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
 
2494
        self.assertRaises(Bogus, tt.apply,
 
2495
                          _mover=self.ExceptionFileMover(bad_target='c/d'))
 
2496
        self.assertPathExists('a')
 
2497
        self.assertPathExists('a/b')
 
2498
        tt.apply()
 
2499
        self.assertPathExists('c')
 
2500
        self.assertPathExists('c/d')
 
2501
 
 
2502
    def test_rollback_deletion(self):
 
2503
        tree = self.make_branch_and_tree('.')
 
2504
        self.build_tree(['a/', 'a/b'])
 
2505
        tt = TreeTransform(tree)
 
2506
        self.addCleanup(tt.finalize)
 
2507
        a_id = tt.trans_id_tree_path('a')
 
2508
        tt.delete_contents(a_id)
 
2509
        tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
 
2510
        self.assertRaises(Bogus, tt.apply,
 
2511
                          _mover=self.ExceptionFileMover(bad_target='d'))
 
2512
        self.assertPathExists('a')
 
2513
        self.assertPathExists('a/b')
 
2514
 
 
2515
 
 
2516
class TestFinalizeRobustness(tests.TestCaseWithTransport):
 
2517
    """Ensure treetransform creation errors can be safely cleaned up after"""
 
2518
 
 
2519
    def _override_globals_in_method(self, instance, method_name, globals):
 
2520
        """Replace method on instance with one with updated globals"""
 
2521
        import types
 
2522
        func = getattr(instance, method_name).im_func
 
2523
        new_globals = dict(func.func_globals)
 
2524
        new_globals.update(globals)
 
2525
        new_func = types.FunctionType(func.func_code, new_globals,
 
2526
            func.func_name, func.func_defaults)
 
2527
        setattr(instance, method_name,
 
2528
            types.MethodType(new_func, instance, instance.__class__))
 
2529
        self.addCleanup(delattr, instance, method_name)
 
2530
 
 
2531
    @staticmethod
 
2532
    def _fake_open_raises_before(name, mode):
 
2533
        """Like open() but raises before doing anything"""
 
2534
        raise RuntimeError
 
2535
 
 
2536
    @staticmethod
 
2537
    def _fake_open_raises_after(name, mode):
 
2538
        """Like open() but raises after creating file without returning"""
 
2539
        open(name, mode).close()
 
2540
        raise RuntimeError
 
2541
 
 
2542
    def create_transform_and_root_trans_id(self):
 
2543
        """Setup a transform creating a file in limbo"""
 
2544
        tree = self.make_branch_and_tree('.')
 
2545
        tt = TreeTransform(tree)
 
2546
        return tt, tt.create_path("a", tt.root)
 
2547
 
 
2548
    def create_transform_and_subdir_trans_id(self):
 
2549
        """Setup a transform creating a directory containing a file in limbo"""
 
2550
        tree = self.make_branch_and_tree('.')
 
2551
        tt = TreeTransform(tree)
 
2552
        d_trans_id = tt.create_path("d", tt.root)
 
2553
        tt.create_directory(d_trans_id)
 
2554
        f_trans_id = tt.create_path("a", d_trans_id)
 
2555
        tt.adjust_path("a", d_trans_id, f_trans_id)
 
2556
        return tt, f_trans_id
 
2557
 
 
2558
    def test_root_create_file_open_raises_before_creation(self):
 
2559
        tt, trans_id = self.create_transform_and_root_trans_id()
 
2560
        self._override_globals_in_method(tt, "create_file",
 
2561
            {"open": self._fake_open_raises_before})
 
2562
        self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
 
2563
        path = tt._limbo_name(trans_id)
 
2564
        self.assertPathDoesNotExist(path)
 
2565
        tt.finalize()
 
2566
        self.assertPathDoesNotExist(tt._limbodir)
 
2567
 
 
2568
    def test_root_create_file_open_raises_after_creation(self):
 
2569
        tt, trans_id = self.create_transform_and_root_trans_id()
 
2570
        self._override_globals_in_method(tt, "create_file",
 
2571
            {"open": self._fake_open_raises_after})
 
2572
        self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
 
2573
        path = tt._limbo_name(trans_id)
 
2574
        self.assertPathExists(path)
 
2575
        tt.finalize()
 
2576
        self.assertPathDoesNotExist(path)
 
2577
        self.assertPathDoesNotExist(tt._limbodir)
 
2578
 
 
2579
    def test_subdir_create_file_open_raises_before_creation(self):
 
2580
        tt, trans_id = self.create_transform_and_subdir_trans_id()
 
2581
        self._override_globals_in_method(tt, "create_file",
 
2582
            {"open": self._fake_open_raises_before})
 
2583
        self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
 
2584
        path = tt._limbo_name(trans_id)
 
2585
        self.assertPathDoesNotExist(path)
 
2586
        tt.finalize()
 
2587
        self.assertPathDoesNotExist(tt._limbodir)
 
2588
 
 
2589
    def test_subdir_create_file_open_raises_after_creation(self):
 
2590
        tt, trans_id = self.create_transform_and_subdir_trans_id()
 
2591
        self._override_globals_in_method(tt, "create_file",
 
2592
            {"open": self._fake_open_raises_after})
 
2593
        self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
 
2594
        path = tt._limbo_name(trans_id)
 
2595
        self.assertPathExists(path)
 
2596
        tt.finalize()
 
2597
        self.assertPathDoesNotExist(path)
 
2598
        self.assertPathDoesNotExist(tt._limbodir)
 
2599
 
 
2600
    def test_rename_in_limbo_rename_raises_after_rename(self):
 
2601
        tt, trans_id = self.create_transform_and_root_trans_id()
 
2602
        parent1 = tt.new_directory('parent1', tt.root)
 
2603
        child1 = tt.new_file('child1', parent1, 'contents')
 
2604
        parent2 = tt.new_directory('parent2', tt.root)
 
2605
 
 
2606
        class FakeOSModule(object):
 
2607
            def rename(self, old, new):
 
2608
                os.rename(old, new)
 
2609
                raise RuntimeError
 
2610
        self._override_globals_in_method(tt, "_rename_in_limbo",
 
2611
            {"os": FakeOSModule()})
 
2612
        self.assertRaises(
 
2613
            RuntimeError, tt.adjust_path, "child1", parent2, child1)
 
2614
        path = osutils.pathjoin(tt._limbo_name(parent2), "child1")
 
2615
        self.assertPathExists(path)
 
2616
        tt.finalize()
 
2617
        self.assertPathDoesNotExist(path)
 
2618
        self.assertPathDoesNotExist(tt._limbodir)
 
2619
 
 
2620
    def test_rename_in_limbo_rename_raises_before_rename(self):
 
2621
        tt, trans_id = self.create_transform_and_root_trans_id()
 
2622
        parent1 = tt.new_directory('parent1', tt.root)
 
2623
        child1 = tt.new_file('child1', parent1, 'contents')
 
2624
        parent2 = tt.new_directory('parent2', tt.root)
 
2625
 
 
2626
        class FakeOSModule(object):
 
2627
            def rename(self, old, new):
 
2628
                raise RuntimeError
 
2629
        self._override_globals_in_method(tt, "_rename_in_limbo",
 
2630
            {"os": FakeOSModule()})
 
2631
        self.assertRaises(
 
2632
            RuntimeError, tt.adjust_path, "child1", parent2, child1)
 
2633
        path = osutils.pathjoin(tt._limbo_name(parent1), "child1")
 
2634
        self.assertPathExists(path)
 
2635
        tt.finalize()
 
2636
        self.assertPathDoesNotExist(path)
 
2637
        self.assertPathDoesNotExist(tt._limbodir)
 
2638
 
 
2639
 
 
2640
class TestTransformMissingParent(tests.TestCaseWithTransport):
 
2641
 
 
2642
    def make_tt_with_versioned_dir(self):
 
2643
        wt = self.make_branch_and_tree('.')
 
2644
        self.build_tree(['dir/',])
 
2645
        wt.add(['dir'], ['dir-id'])
 
2646
        wt.commit('Create dir')
 
2647
        tt = TreeTransform(wt)
 
2648
        self.addCleanup(tt.finalize)
 
2649
        return wt, tt
 
2650
 
 
2651
    def test_resolve_create_parent_for_versioned_file(self):
 
2652
        wt, tt = self.make_tt_with_versioned_dir()
 
2653
        dir_tid = tt.trans_id_tree_file_id('dir-id')
 
2654
        file_tid = tt.new_file('file', dir_tid, 'Contents', file_id='file-id')
 
2655
        tt.delete_contents(dir_tid)
 
2656
        tt.unversion_file(dir_tid)
 
2657
        conflicts = resolve_conflicts(tt)
 
2658
        # one conflict for the missing directory, one for the unversioned
 
2659
        # parent
 
2660
        self.assertLength(2, conflicts)
 
2661
 
 
2662
    def test_non_versioned_file_create_conflict(self):
 
2663
        wt, tt = self.make_tt_with_versioned_dir()
 
2664
        dir_tid = tt.trans_id_tree_file_id('dir-id')
 
2665
        tt.new_file('file', dir_tid, 'Contents')
 
2666
        tt.delete_contents(dir_tid)
 
2667
        tt.unversion_file(dir_tid)
 
2668
        conflicts = resolve_conflicts(tt)
 
2669
        # no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
 
2670
        self.assertLength(1, conflicts)
 
2671
        self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
 
2672
                         conflicts.pop())
 
2673
 
 
2674
 
 
2675
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
 
2676
                  ('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
 
2677
                  (False, False))
 
2678
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
 
2679
              ('', ''), ('directory', 'directory'), (False, False))
 
2680
 
 
2681
 
 
2682
class TestTransformPreview(tests.TestCaseWithTransport):
 
2683
 
 
2684
    def create_tree(self):
 
2685
        tree = self.make_branch_and_tree('.')
 
2686
        self.build_tree_contents([('a', 'content 1')])
 
2687
        tree.set_root_id('TREE_ROOT')
 
2688
        tree.add('a', 'a-id')
 
2689
        tree.commit('rev1', rev_id='rev1')
 
2690
        return tree.branch.repository.revision_tree('rev1')
 
2691
 
 
2692
    def get_empty_preview(self):
 
2693
        repository = self.make_repository('repo')
 
2694
        tree = repository.revision_tree(_mod_revision.NULL_REVISION)
 
2695
        preview = TransformPreview(tree)
 
2696
        self.addCleanup(preview.finalize)
 
2697
        return preview
 
2698
 
 
2699
    def test_transform_preview(self):
 
2700
        revision_tree = self.create_tree()
 
2701
        preview = TransformPreview(revision_tree)
 
2702
        self.addCleanup(preview.finalize)
 
2703
 
 
2704
    def test_transform_preview_tree(self):
 
2705
        revision_tree = self.create_tree()
 
2706
        preview = TransformPreview(revision_tree)
 
2707
        self.addCleanup(preview.finalize)
 
2708
        preview.get_preview_tree()
 
2709
 
 
2710
    def test_transform_new_file(self):
 
2711
        revision_tree = self.create_tree()
 
2712
        preview = TransformPreview(revision_tree)
 
2713
        self.addCleanup(preview.finalize)
 
2714
        preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
 
2715
        preview_tree = preview.get_preview_tree()
 
2716
        self.assertEqual(preview_tree.kind('file2-id'), 'file')
 
2717
        self.assertEqual(
 
2718
            preview_tree.get_file('file2-id').read(), 'content B\n')
 
2719
 
 
2720
    def test_diff_preview_tree(self):
 
2721
        revision_tree = self.create_tree()
 
2722
        preview = TransformPreview(revision_tree)
 
2723
        self.addCleanup(preview.finalize)
 
2724
        preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
 
2725
        preview_tree = preview.get_preview_tree()
 
2726
        out = StringIO()
 
2727
        show_diff_trees(revision_tree, preview_tree, out)
 
2728
        lines = out.getvalue().splitlines()
 
2729
        self.assertEqual(lines[0], "=== added file 'file2'")
 
2730
        # 3 lines of diff administrivia
 
2731
        self.assertEqual(lines[4], "+content B")
 
2732
 
 
2733
    def test_transform_conflicts(self):
 
2734
        revision_tree = self.create_tree()
 
2735
        preview = TransformPreview(revision_tree)
 
2736
        self.addCleanup(preview.finalize)
 
2737
        preview.new_file('a', preview.root, 'content 2')
 
2738
        resolve_conflicts(preview)
 
2739
        trans_id = preview.trans_id_file_id('a-id')
 
2740
        self.assertEqual('a.moved', preview.final_name(trans_id))
 
2741
 
 
2742
    def get_tree_and_preview_tree(self):
 
2743
        revision_tree = self.create_tree()
 
2744
        preview = TransformPreview(revision_tree)
 
2745
        self.addCleanup(preview.finalize)
 
2746
        a_trans_id = preview.trans_id_file_id('a-id')
 
2747
        preview.delete_contents(a_trans_id)
 
2748
        preview.create_file('b content', a_trans_id)
 
2749
        preview_tree = preview.get_preview_tree()
 
2750
        return revision_tree, preview_tree
 
2751
 
 
2752
    def test_iter_changes(self):
 
2753
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2754
        root = revision_tree.inventory.root.file_id
 
2755
        self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
 
2756
                          (root, root), ('a', 'a'), ('file', 'file'),
 
2757
                          (False, False))],
 
2758
                          list(preview_tree.iter_changes(revision_tree)))
 
2759
 
 
2760
    def test_include_unchanged_succeeds(self):
 
2761
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2762
        changes = preview_tree.iter_changes(revision_tree,
 
2763
                                            include_unchanged=True)
 
2764
        root = revision_tree.inventory.root.file_id
 
2765
 
 
2766
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
 
2767
 
 
2768
    def test_specific_files(self):
 
2769
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2770
        changes = preview_tree.iter_changes(revision_tree,
 
2771
                                            specific_files=[''])
 
2772
        self.assertEqual([A_ENTRY], list(changes))
 
2773
 
 
2774
    def test_want_unversioned(self):
 
2775
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2776
        changes = preview_tree.iter_changes(revision_tree,
 
2777
                                            want_unversioned=True)
 
2778
        self.assertEqual([A_ENTRY], list(changes))
 
2779
 
 
2780
    def test_ignore_extra_trees_no_specific_files(self):
 
2781
        # extra_trees is harmless without specific_files, so we'll silently
 
2782
        # accept it, even though we won't use it.
 
2783
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2784
        preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
 
2785
 
 
2786
    def test_ignore_require_versioned_no_specific_files(self):
 
2787
        # require_versioned is meaningless without specific_files.
 
2788
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2789
        preview_tree.iter_changes(revision_tree, require_versioned=False)
 
2790
 
 
2791
    def test_ignore_pb(self):
 
2792
        # pb could be supported, but TT.iter_changes doesn't support it.
 
2793
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2794
        preview_tree.iter_changes(revision_tree)
 
2795
 
 
2796
    def test_kind(self):
 
2797
        revision_tree = self.create_tree()
 
2798
        preview = TransformPreview(revision_tree)
 
2799
        self.addCleanup(preview.finalize)
 
2800
        preview.new_file('file', preview.root, 'contents', 'file-id')
 
2801
        preview.new_directory('directory', preview.root, 'dir-id')
 
2802
        preview_tree = preview.get_preview_tree()
 
2803
        self.assertEqual('file', preview_tree.kind('file-id'))
 
2804
        self.assertEqual('directory', preview_tree.kind('dir-id'))
 
2805
 
 
2806
    def test_get_file_mtime(self):
 
2807
        preview = self.get_empty_preview()
 
2808
        file_trans_id = preview.new_file('file', preview.root, 'contents',
 
2809
                                         'file-id')
 
2810
        limbo_path = preview._limbo_name(file_trans_id)
 
2811
        preview_tree = preview.get_preview_tree()
 
2812
        self.assertEqual(os.stat(limbo_path).st_mtime,
 
2813
                         preview_tree.get_file_mtime('file-id'))
 
2814
 
 
2815
    def test_get_file_mtime_renamed(self):
 
2816
        work_tree = self.make_branch_and_tree('tree')
 
2817
        self.build_tree(['tree/file'])
 
2818
        work_tree.add('file', 'file-id')
 
2819
        preview = TransformPreview(work_tree)
 
2820
        self.addCleanup(preview.finalize)
 
2821
        file_trans_id = preview.trans_id_tree_file_id('file-id')
 
2822
        preview.adjust_path('renamed', preview.root, file_trans_id)
 
2823
        preview_tree = preview.get_preview_tree()
 
2824
        preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
 
2825
        work_mtime = work_tree.get_file_mtime('file-id', 'file')
 
2826
 
 
2827
    def test_get_file_size(self):
 
2828
        work_tree = self.make_branch_and_tree('tree')
 
2829
        self.build_tree_contents([('tree/old', 'old')])
 
2830
        work_tree.add('old', 'old-id')
 
2831
        preview = TransformPreview(work_tree)
 
2832
        self.addCleanup(preview.finalize)
 
2833
        new_id = preview.new_file('name', preview.root, 'contents', 'new-id',
 
2834
                                  'executable')
 
2835
        tree = preview.get_preview_tree()
 
2836
        self.assertEqual(len('old'), tree.get_file_size('old-id'))
 
2837
        self.assertEqual(len('contents'), tree.get_file_size('new-id'))
 
2838
 
 
2839
    def test_get_file(self):
 
2840
        preview = self.get_empty_preview()
 
2841
        preview.new_file('file', preview.root, 'contents', 'file-id')
 
2842
        preview_tree = preview.get_preview_tree()
 
2843
        tree_file = preview_tree.get_file('file-id')
 
2844
        try:
 
2845
            self.assertEqual('contents', tree_file.read())
 
2846
        finally:
 
2847
            tree_file.close()
 
2848
 
 
2849
    def test_get_symlink_target(self):
 
2850
        self.requireFeature(SymlinkFeature)
 
2851
        preview = self.get_empty_preview()
 
2852
        preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
 
2853
        preview_tree = preview.get_preview_tree()
 
2854
        self.assertEqual('target',
 
2855
                         preview_tree.get_symlink_target('symlink-id'))
 
2856
 
 
2857
    def test_all_file_ids(self):
 
2858
        tree = self.make_branch_and_tree('tree')
 
2859
        self.build_tree(['tree/a', 'tree/b', 'tree/c'])
 
2860
        tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
 
2861
        preview = TransformPreview(tree)
 
2862
        self.addCleanup(preview.finalize)
 
2863
        preview.unversion_file(preview.trans_id_file_id('b-id'))
 
2864
        c_trans_id = preview.trans_id_file_id('c-id')
 
2865
        preview.unversion_file(c_trans_id)
 
2866
        preview.version_file('c-id', c_trans_id)
 
2867
        preview_tree = preview.get_preview_tree()
 
2868
        self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
 
2869
                         preview_tree.all_file_ids())
 
2870
 
 
2871
    def test_path2id_deleted_unchanged(self):
 
2872
        tree = self.make_branch_and_tree('tree')
 
2873
        self.build_tree(['tree/unchanged', 'tree/deleted'])
 
2874
        tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
 
2875
        preview = TransformPreview(tree)
 
2876
        self.addCleanup(preview.finalize)
 
2877
        preview.unversion_file(preview.trans_id_file_id('deleted-id'))
 
2878
        preview_tree = preview.get_preview_tree()
 
2879
        self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
 
2880
        self.assertIs(None, preview_tree.path2id('deleted'))
 
2881
 
 
2882
    def test_path2id_created(self):
 
2883
        tree = self.make_branch_and_tree('tree')
 
2884
        self.build_tree(['tree/unchanged'])
 
2885
        tree.add(['unchanged'], ['unchanged-id'])
 
2886
        preview = TransformPreview(tree)
 
2887
        self.addCleanup(preview.finalize)
 
2888
        preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
 
2889
            'contents', 'new-id')
 
2890
        preview_tree = preview.get_preview_tree()
 
2891
        self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
 
2892
 
 
2893
    def test_path2id_moved(self):
 
2894
        tree = self.make_branch_and_tree('tree')
 
2895
        self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
 
2896
        tree.add(['old_parent', 'old_parent/child'],
 
2897
                 ['old_parent-id', 'child-id'])
 
2898
        preview = TransformPreview(tree)
 
2899
        self.addCleanup(preview.finalize)
 
2900
        new_parent = preview.new_directory('new_parent', preview.root,
 
2901
                                           'new_parent-id')
 
2902
        preview.adjust_path('child', new_parent,
 
2903
                            preview.trans_id_file_id('child-id'))
 
2904
        preview_tree = preview.get_preview_tree()
 
2905
        self.assertIs(None, preview_tree.path2id('old_parent/child'))
 
2906
        self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
 
2907
 
 
2908
    def test_path2id_renamed_parent(self):
 
2909
        tree = self.make_branch_and_tree('tree')
 
2910
        self.build_tree(['tree/old_name/', 'tree/old_name/child'])
 
2911
        tree.add(['old_name', 'old_name/child'],
 
2912
                 ['parent-id', 'child-id'])
 
2913
        preview = TransformPreview(tree)
 
2914
        self.addCleanup(preview.finalize)
 
2915
        preview.adjust_path('new_name', preview.root,
 
2916
                            preview.trans_id_file_id('parent-id'))
 
2917
        preview_tree = preview.get_preview_tree()
 
2918
        self.assertIs(None, preview_tree.path2id('old_name/child'))
 
2919
        self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
 
2920
 
 
2921
    def assertMatchingIterEntries(self, tt, specific_file_ids=None):
 
2922
        preview_tree = tt.get_preview_tree()
 
2923
        preview_result = list(preview_tree.iter_entries_by_dir(
 
2924
                              specific_file_ids))
 
2925
        tree = tt._tree
 
2926
        tt.apply()
 
2927
        actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
 
2928
        self.assertEqual(actual_result, preview_result)
 
2929
 
 
2930
    def test_iter_entries_by_dir_new(self):
 
2931
        tree = self.make_branch_and_tree('tree')
 
2932
        tt = TreeTransform(tree)
 
2933
        tt.new_file('new', tt.root, 'contents', 'new-id')
 
2934
        self.assertMatchingIterEntries(tt)
 
2935
 
 
2936
    def test_iter_entries_by_dir_deleted(self):
 
2937
        tree = self.make_branch_and_tree('tree')
 
2938
        self.build_tree(['tree/deleted'])
 
2939
        tree.add('deleted', 'deleted-id')
 
2940
        tt = TreeTransform(tree)
 
2941
        tt.delete_contents(tt.trans_id_file_id('deleted-id'))
 
2942
        self.assertMatchingIterEntries(tt)
 
2943
 
 
2944
    def test_iter_entries_by_dir_unversioned(self):
 
2945
        tree = self.make_branch_and_tree('tree')
 
2946
        self.build_tree(['tree/removed'])
 
2947
        tree.add('removed', 'removed-id')
 
2948
        tt = TreeTransform(tree)
 
2949
        tt.unversion_file(tt.trans_id_file_id('removed-id'))
 
2950
        self.assertMatchingIterEntries(tt)
 
2951
 
 
2952
    def test_iter_entries_by_dir_moved(self):
 
2953
        tree = self.make_branch_and_tree('tree')
 
2954
        self.build_tree(['tree/moved', 'tree/new_parent/'])
 
2955
        tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
 
2956
        tt = TreeTransform(tree)
 
2957
        tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
 
2958
                       tt.trans_id_file_id('moved-id'))
 
2959
        self.assertMatchingIterEntries(tt)
 
2960
 
 
2961
    def test_iter_entries_by_dir_specific_file_ids(self):
 
2962
        tree = self.make_branch_and_tree('tree')
 
2963
        tree.set_root_id('tree-root-id')
 
2964
        self.build_tree(['tree/parent/', 'tree/parent/child'])
 
2965
        tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
 
2966
        tt = TreeTransform(tree)
 
2967
        self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
 
2968
 
 
2969
    def test_symlink_content_summary(self):
 
2970
        self.requireFeature(SymlinkFeature)
 
2971
        preview = self.get_empty_preview()
 
2972
        preview.new_symlink('path', preview.root, 'target', 'path-id')
 
2973
        summary = preview.get_preview_tree().path_content_summary('path')
 
2974
        self.assertEqual(('symlink', None, None, 'target'), summary)
 
2975
 
 
2976
    def test_missing_content_summary(self):
 
2977
        preview = self.get_empty_preview()
 
2978
        summary = preview.get_preview_tree().path_content_summary('path')
 
2979
        self.assertEqual(('missing', None, None, None), summary)
 
2980
 
 
2981
    def test_deleted_content_summary(self):
 
2982
        tree = self.make_branch_and_tree('tree')
 
2983
        self.build_tree(['tree/path/'])
 
2984
        tree.add('path')
 
2985
        preview = TransformPreview(tree)
 
2986
        self.addCleanup(preview.finalize)
 
2987
        preview.delete_contents(preview.trans_id_tree_path('path'))
 
2988
        summary = preview.get_preview_tree().path_content_summary('path')
 
2989
        self.assertEqual(('missing', None, None, None), summary)
 
2990
 
 
2991
    def test_file_content_summary_executable(self):
 
2992
        preview = self.get_empty_preview()
 
2993
        path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
 
2994
        preview.set_executability(True, path_id)
 
2995
        summary = preview.get_preview_tree().path_content_summary('path')
 
2996
        self.assertEqual(4, len(summary))
 
2997
        self.assertEqual('file', summary[0])
 
2998
        # size must be known
 
2999
        self.assertEqual(len('contents'), summary[1])
 
3000
        # executable
 
3001
        self.assertEqual(True, summary[2])
 
3002
        # will not have hash (not cheap to determine)
 
3003
        self.assertIs(None, summary[3])
 
3004
 
 
3005
    def test_change_executability(self):
 
3006
        tree = self.make_branch_and_tree('tree')
 
3007
        self.build_tree(['tree/path'])
 
3008
        tree.add('path')
 
3009
        preview = TransformPreview(tree)
 
3010
        self.addCleanup(preview.finalize)
 
3011
        path_id = preview.trans_id_tree_path('path')
 
3012
        preview.set_executability(True, path_id)
 
3013
        summary = preview.get_preview_tree().path_content_summary('path')
 
3014
        self.assertEqual(True, summary[2])
 
3015
 
 
3016
    def test_file_content_summary_non_exec(self):
 
3017
        preview = self.get_empty_preview()
 
3018
        preview.new_file('path', preview.root, 'contents', 'path-id')
 
3019
        summary = preview.get_preview_tree().path_content_summary('path')
 
3020
        self.assertEqual(4, len(summary))
 
3021
        self.assertEqual('file', summary[0])
 
3022
        # size must be known
 
3023
        self.assertEqual(len('contents'), summary[1])
 
3024
        # not executable
 
3025
        self.assertEqual(False, summary[2])
 
3026
        # will not have hash (not cheap to determine)
 
3027
        self.assertIs(None, summary[3])
 
3028
 
 
3029
    def test_dir_content_summary(self):
 
3030
        preview = self.get_empty_preview()
 
3031
        preview.new_directory('path', preview.root, 'path-id')
 
3032
        summary = preview.get_preview_tree().path_content_summary('path')
 
3033
        self.assertEqual(('directory', None, None, None), summary)
 
3034
 
 
3035
    def test_tree_content_summary(self):
 
3036
        preview = self.get_empty_preview()
 
3037
        path = preview.new_directory('path', preview.root, 'path-id')
 
3038
        preview.set_tree_reference('rev-1', path)
 
3039
        summary = preview.get_preview_tree().path_content_summary('path')
 
3040
        self.assertEqual(4, len(summary))
 
3041
        self.assertEqual('tree-reference', summary[0])
 
3042
 
 
3043
    def test_annotate(self):
 
3044
        tree = self.make_branch_and_tree('tree')
 
3045
        self.build_tree_contents([('tree/file', 'a\n')])
 
3046
        tree.add('file', 'file-id')
 
3047
        tree.commit('a', rev_id='one')
 
3048
        self.build_tree_contents([('tree/file', 'a\nb\n')])
 
3049
        preview = TransformPreview(tree)
 
3050
        self.addCleanup(preview.finalize)
 
3051
        file_trans_id = preview.trans_id_file_id('file-id')
 
3052
        preview.delete_contents(file_trans_id)
 
3053
        preview.create_file('a\nb\nc\n', file_trans_id)
 
3054
        preview_tree = preview.get_preview_tree()
 
3055
        expected = [
 
3056
            ('one', 'a\n'),
 
3057
            ('me:', 'b\n'),
 
3058
            ('me:', 'c\n'),
 
3059
        ]
 
3060
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
3061
        self.assertEqual(expected, annotation)
 
3062
 
 
3063
    def test_annotate_missing(self):
 
3064
        preview = self.get_empty_preview()
 
3065
        preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
 
3066
        preview_tree = preview.get_preview_tree()
 
3067
        expected = [
 
3068
            ('me:', 'a\n'),
 
3069
            ('me:', 'b\n'),
 
3070
            ('me:', 'c\n'),
 
3071
         ]
 
3072
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
3073
        self.assertEqual(expected, annotation)
 
3074
 
 
3075
    def test_annotate_rename(self):
 
3076
        tree = self.make_branch_and_tree('tree')
 
3077
        self.build_tree_contents([('tree/file', 'a\n')])
 
3078
        tree.add('file', 'file-id')
 
3079
        tree.commit('a', rev_id='one')
 
3080
        preview = TransformPreview(tree)
 
3081
        self.addCleanup(preview.finalize)
 
3082
        file_trans_id = preview.trans_id_file_id('file-id')
 
3083
        preview.adjust_path('newname', preview.root, file_trans_id)
 
3084
        preview_tree = preview.get_preview_tree()
 
3085
        expected = [
 
3086
            ('one', 'a\n'),
 
3087
        ]
 
3088
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
3089
        self.assertEqual(expected, annotation)
 
3090
 
 
3091
    def test_annotate_deleted(self):
 
3092
        tree = self.make_branch_and_tree('tree')
 
3093
        self.build_tree_contents([('tree/file', 'a\n')])
 
3094
        tree.add('file', 'file-id')
 
3095
        tree.commit('a', rev_id='one')
 
3096
        self.build_tree_contents([('tree/file', 'a\nb\n')])
 
3097
        preview = TransformPreview(tree)
 
3098
        self.addCleanup(preview.finalize)
 
3099
        file_trans_id = preview.trans_id_file_id('file-id')
 
3100
        preview.delete_contents(file_trans_id)
 
3101
        preview_tree = preview.get_preview_tree()
 
3102
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
3103
        self.assertIs(None, annotation)
 
3104
 
 
3105
    def test_stored_kind(self):
 
3106
        preview = self.get_empty_preview()
 
3107
        preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
 
3108
        preview_tree = preview.get_preview_tree()
 
3109
        self.assertEqual('file', preview_tree.stored_kind('file-id'))
 
3110
 
 
3111
    def test_is_executable(self):
 
3112
        preview = self.get_empty_preview()
 
3113
        preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
 
3114
        preview.set_executability(True, preview.trans_id_file_id('file-id'))
 
3115
        preview_tree = preview.get_preview_tree()
 
3116
        self.assertEqual(True, preview_tree.is_executable('file-id'))
 
3117
 
 
3118
    def test_get_set_parent_ids(self):
 
3119
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
3120
        self.assertEqual([], preview_tree.get_parent_ids())
 
3121
        preview_tree.set_parent_ids(['rev-1'])
 
3122
        self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
 
3123
 
 
3124
    def test_plan_file_merge(self):
 
3125
        work_a = self.make_branch_and_tree('wta')
 
3126
        self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
 
3127
        work_a.add('file', 'file-id')
 
3128
        base_id = work_a.commit('base version')
 
3129
        tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
 
3130
        preview = TransformPreview(work_a)
 
3131
        self.addCleanup(preview.finalize)
 
3132
        trans_id = preview.trans_id_file_id('file-id')
 
3133
        preview.delete_contents(trans_id)
 
3134
        preview.create_file('b\nc\nd\ne\n', trans_id)
 
3135
        self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
 
3136
        tree_a = preview.get_preview_tree()
 
3137
        tree_a.set_parent_ids([base_id])
 
3138
        self.assertEqual([
 
3139
            ('killed-a', 'a\n'),
 
3140
            ('killed-b', 'b\n'),
 
3141
            ('unchanged', 'c\n'),
 
3142
            ('unchanged', 'd\n'),
 
3143
            ('new-a', 'e\n'),
 
3144
            ('new-b', 'f\n'),
 
3145
        ], list(tree_a.plan_file_merge('file-id', tree_b)))
 
3146
 
 
3147
    def test_plan_file_merge_revision_tree(self):
 
3148
        work_a = self.make_branch_and_tree('wta')
 
3149
        self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
 
3150
        work_a.add('file', 'file-id')
 
3151
        base_id = work_a.commit('base version')
 
3152
        tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
 
3153
        preview = TransformPreview(work_a.basis_tree())
 
3154
        self.addCleanup(preview.finalize)
 
3155
        trans_id = preview.trans_id_file_id('file-id')
 
3156
        preview.delete_contents(trans_id)
 
3157
        preview.create_file('b\nc\nd\ne\n', trans_id)
 
3158
        self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
 
3159
        tree_a = preview.get_preview_tree()
 
3160
        tree_a.set_parent_ids([base_id])
 
3161
        self.assertEqual([
 
3162
            ('killed-a', 'a\n'),
 
3163
            ('killed-b', 'b\n'),
 
3164
            ('unchanged', 'c\n'),
 
3165
            ('unchanged', 'd\n'),
 
3166
            ('new-a', 'e\n'),
 
3167
            ('new-b', 'f\n'),
 
3168
        ], list(tree_a.plan_file_merge('file-id', tree_b)))
 
3169
 
 
3170
    def test_walkdirs(self):
 
3171
        preview = self.get_empty_preview()
 
3172
        root = preview.new_directory('', ROOT_PARENT, 'tree-root')
 
3173
        # FIXME: new_directory should mark root.
 
3174
        preview.fixup_new_roots()
 
3175
        preview_tree = preview.get_preview_tree()
 
3176
        file_trans_id = preview.new_file('a', preview.root, 'contents',
 
3177
                                         'a-id')
 
3178
        expected = [(('', 'tree-root'),
 
3179
                    [('a', 'a', 'file', None, 'a-id', 'file')])]
 
3180
        self.assertEqual(expected, list(preview_tree.walkdirs()))
 
3181
 
 
3182
    def test_extras(self):
 
3183
        work_tree = self.make_branch_and_tree('tree')
 
3184
        self.build_tree(['tree/removed-file', 'tree/existing-file',
 
3185
                         'tree/not-removed-file'])
 
3186
        work_tree.add(['removed-file', 'not-removed-file'])
 
3187
        preview = TransformPreview(work_tree)
 
3188
        self.addCleanup(preview.finalize)
 
3189
        preview.new_file('new-file', preview.root, 'contents')
 
3190
        preview.new_file('new-versioned-file', preview.root, 'contents',
 
3191
                         'new-versioned-id')
 
3192
        tree = preview.get_preview_tree()
 
3193
        preview.unversion_file(preview.trans_id_tree_path('removed-file'))
 
3194
        self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
 
3195
                         set(tree.extras()))
 
3196
 
 
3197
    def test_merge_into_preview(self):
 
3198
        work_tree = self.make_branch_and_tree('tree')
 
3199
        self.build_tree_contents([('tree/file','b\n')])
 
3200
        work_tree.add('file', 'file-id')
 
3201
        work_tree.commit('first commit')
 
3202
        child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
 
3203
        self.build_tree_contents([('child/file','b\nc\n')])
 
3204
        child_tree.commit('child commit')
 
3205
        child_tree.lock_write()
 
3206
        self.addCleanup(child_tree.unlock)
 
3207
        work_tree.lock_write()
 
3208
        self.addCleanup(work_tree.unlock)
 
3209
        preview = TransformPreview(work_tree)
 
3210
        self.addCleanup(preview.finalize)
 
3211
        file_trans_id = preview.trans_id_file_id('file-id')
 
3212
        preview.delete_contents(file_trans_id)
 
3213
        preview.create_file('a\nb\n', file_trans_id)
 
3214
        preview_tree = preview.get_preview_tree()
 
3215
        merger = Merger.from_revision_ids(None, preview_tree,
 
3216
                                          child_tree.branch.last_revision(),
 
3217
                                          other_branch=child_tree.branch,
 
3218
                                          tree_branch=work_tree.branch)
 
3219
        merger.merge_type = Merge3Merger
 
3220
        tt = merger.make_merger().make_preview_transform()
 
3221
        self.addCleanup(tt.finalize)
 
3222
        final_tree = tt.get_preview_tree()
 
3223
        self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
 
3224
 
 
3225
    def test_merge_preview_into_workingtree(self):
 
3226
        tree = self.make_branch_and_tree('tree')
 
3227
        tree.set_root_id('TREE_ROOT')
 
3228
        tt = TransformPreview(tree)
 
3229
        self.addCleanup(tt.finalize)
 
3230
        tt.new_file('name', tt.root, 'content', 'file-id')
 
3231
        tree2 = self.make_branch_and_tree('tree2')
 
3232
        tree2.set_root_id('TREE_ROOT')
 
3233
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
 
3234
                                         None, tree.basis_tree())
 
3235
        merger.merge_type = Merge3Merger
 
3236
        merger.do_merge()
 
3237
 
 
3238
    def test_merge_preview_into_workingtree_handles_conflicts(self):
 
3239
        tree = self.make_branch_and_tree('tree')
 
3240
        self.build_tree_contents([('tree/foo', 'bar')])
 
3241
        tree.add('foo', 'foo-id')
 
3242
        tree.commit('foo')
 
3243
        tt = TransformPreview(tree)
 
3244
        self.addCleanup(tt.finalize)
 
3245
        trans_id = tt.trans_id_file_id('foo-id')
 
3246
        tt.delete_contents(trans_id)
 
3247
        tt.create_file('baz', trans_id)
 
3248
        tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
 
3249
        self.build_tree_contents([('tree2/foo', 'qux')])
 
3250
        pb = None
 
3251
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
 
3252
                                         pb, tree.basis_tree())
 
3253
        merger.merge_type = Merge3Merger
 
3254
        merger.do_merge()
 
3255
 
 
3256
    def test_has_filename(self):
 
3257
        wt = self.make_branch_and_tree('tree')
 
3258
        self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
 
3259
        tt = TransformPreview(wt)
 
3260
        removed_id = tt.trans_id_tree_path('removed')
 
3261
        tt.delete_contents(removed_id)
 
3262
        tt.new_file('new', tt.root, 'contents')
 
3263
        modified_id = tt.trans_id_tree_path('modified')
 
3264
        tt.delete_contents(modified_id)
 
3265
        tt.create_file('modified-contents', modified_id)
 
3266
        self.addCleanup(tt.finalize)
 
3267
        tree = tt.get_preview_tree()
 
3268
        self.assertTrue(tree.has_filename('unmodified'))
 
3269
        self.assertFalse(tree.has_filename('not-present'))
 
3270
        self.assertFalse(tree.has_filename('removed'))
 
3271
        self.assertTrue(tree.has_filename('new'))
 
3272
        self.assertTrue(tree.has_filename('modified'))
 
3273
 
 
3274
    def test_is_executable(self):
 
3275
        tree = self.make_branch_and_tree('tree')
 
3276
        preview = TransformPreview(tree)
 
3277
        self.addCleanup(preview.finalize)
 
3278
        preview.new_file('foo', preview.root, 'bar', 'baz-id')
 
3279
        preview_tree = preview.get_preview_tree()
 
3280
        self.assertEqual(False, preview_tree.is_executable('baz-id',
 
3281
                                                           'tree/foo'))
 
3282
        self.assertEqual(False, preview_tree.is_executable('baz-id'))
 
3283
 
 
3284
    def test_commit_preview_tree(self):
 
3285
        tree = self.make_branch_and_tree('tree')
 
3286
        rev_id = tree.commit('rev1')
 
3287
        tree.branch.lock_write()
 
3288
        self.addCleanup(tree.branch.unlock)
 
3289
        tt = TransformPreview(tree)
 
3290
        tt.new_file('file', tt.root, 'contents', 'file_id')
 
3291
        self.addCleanup(tt.finalize)
 
3292
        preview = tt.get_preview_tree()
 
3293
        preview.set_parent_ids([rev_id])
 
3294
        builder = tree.branch.get_commit_builder([rev_id])
 
3295
        list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
 
3296
        builder.finish_inventory()
 
3297
        rev2_id = builder.commit('rev2')
 
3298
        rev2_tree = tree.branch.repository.revision_tree(rev2_id)
 
3299
        self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
 
3300
 
 
3301
    def test_ascii_limbo_paths(self):
 
3302
        self.requireFeature(tests.UnicodeFilenameFeature)
 
3303
        branch = self.make_branch('any')
 
3304
        tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
 
3305
        tt = TransformPreview(tree)
 
3306
        self.addCleanup(tt.finalize)
 
3307
        foo_id = tt.new_directory('', ROOT_PARENT)
 
3308
        bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
 
3309
        limbo_path = tt._limbo_name(bar_id)
 
3310
        self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
 
3311
 
 
3312
 
 
3313
class FakeSerializer(object):
 
3314
    """Serializer implementation that simply returns the input.
 
3315
 
 
3316
    The input is returned in the order used by pack.ContainerPushParser.
 
3317
    """
 
3318
    @staticmethod
 
3319
    def bytes_record(bytes, names):
 
3320
        return names, bytes
 
3321
 
 
3322
 
 
3323
class TestSerializeTransform(tests.TestCaseWithTransport):
 
3324
 
 
3325
    _test_needs_features = [tests.UnicodeFilenameFeature]
 
3326
 
 
3327
    def get_preview(self, tree=None):
 
3328
        if tree is None:
 
3329
            tree = self.make_branch_and_tree('tree')
 
3330
        tt = TransformPreview(tree)
 
3331
        self.addCleanup(tt.finalize)
 
3332
        return tt
 
3333
 
 
3334
    def assertSerializesTo(self, expected, tt):
 
3335
        records = list(tt.serialize(FakeSerializer()))
 
3336
        self.assertEqual(expected, records)
 
3337
 
 
3338
    @staticmethod
 
3339
    def default_attribs():
 
3340
        return {
 
3341
            '_id_number': 1,
 
3342
            '_new_name': {},
 
3343
            '_new_parent': {},
 
3344
            '_new_executability': {},
 
3345
            '_new_id': {},
 
3346
            '_tree_path_ids': {'': 'new-0'},
 
3347
            '_removed_id': [],
 
3348
            '_removed_contents': [],
 
3349
            '_non_present_ids': {},
 
3350
            }
 
3351
 
 
3352
    def make_records(self, attribs, contents):
 
3353
        records = [
 
3354
            (((('attribs'),),), bencode.bencode(attribs))]
 
3355
        records.extend([(((n, k),), c) for n, k, c in contents])
 
3356
        return records
 
3357
 
 
3358
    def creation_records(self):
 
3359
        attribs = self.default_attribs()
 
3360
        attribs['_id_number'] = 3
 
3361
        attribs['_new_name'] = {
 
3362
            'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
 
3363
        attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
 
3364
        attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
 
3365
        attribs['_new_executability'] = {'new-1': 1}
 
3366
        contents = [
 
3367
            ('new-1', 'file', 'i 1\nbar\n'),
 
3368
            ('new-2', 'directory', ''),
 
3369
            ]
 
3370
        return self.make_records(attribs, contents)
 
3371
 
 
3372
    def test_serialize_creation(self):
 
3373
        tt = self.get_preview()
 
3374
        tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
 
3375
        tt.new_directory('qux', tt.root, 'quxx')
 
3376
        self.assertSerializesTo(self.creation_records(), tt)
 
3377
 
 
3378
    def test_deserialize_creation(self):
 
3379
        tt = self.get_preview()
 
3380
        tt.deserialize(iter(self.creation_records()))
 
3381
        self.assertEqual(3, tt._id_number)
 
3382
        self.assertEqual({'new-1': u'foo\u1234',
 
3383
                          'new-2': 'qux'}, tt._new_name)
 
3384
        self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
 
3385
        self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
 
3386
        self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
 
3387
        self.assertEqual({'new-1': True}, tt._new_executability)
 
3388
        self.assertEqual({'new-1': 'file',
 
3389
                          'new-2': 'directory'}, tt._new_contents)
 
3390
        foo_limbo = open(tt._limbo_name('new-1'), 'rb')
 
3391
        try:
 
3392
            foo_content = foo_limbo.read()
 
3393
        finally:
 
3394
            foo_limbo.close()
 
3395
        self.assertEqual('bar', foo_content)
 
3396
 
 
3397
    def symlink_creation_records(self):
 
3398
        attribs = self.default_attribs()
 
3399
        attribs['_id_number'] = 2
 
3400
        attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
 
3401
        attribs['_new_parent'] = {'new-1': 'new-0'}
 
3402
        contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
 
3403
        return self.make_records(attribs, contents)
 
3404
 
 
3405
    def test_serialize_symlink_creation(self):
 
3406
        self.requireFeature(tests.SymlinkFeature)
 
3407
        tt = self.get_preview()
 
3408
        tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
 
3409
        self.assertSerializesTo(self.symlink_creation_records(), tt)
 
3410
 
 
3411
    def test_deserialize_symlink_creation(self):
 
3412
        self.requireFeature(tests.SymlinkFeature)
 
3413
        tt = self.get_preview()
 
3414
        tt.deserialize(iter(self.symlink_creation_records()))
 
3415
        abspath = tt._limbo_name('new-1')
 
3416
        foo_content = osutils.readlink(abspath)
 
3417
        self.assertEqual(u'bar\u1234', foo_content)
 
3418
 
 
3419
    def make_destruction_preview(self):
 
3420
        tree = self.make_branch_and_tree('.')
 
3421
        self.build_tree([u'foo\u1234', 'bar'])
 
3422
        tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
 
3423
        return self.get_preview(tree)
 
3424
 
 
3425
    def destruction_records(self):
 
3426
        attribs = self.default_attribs()
 
3427
        attribs['_id_number'] = 3
 
3428
        attribs['_removed_id'] = ['new-1']
 
3429
        attribs['_removed_contents'] = ['new-2']
 
3430
        attribs['_tree_path_ids'] = {
 
3431
            '': 'new-0',
 
3432
            u'foo\u1234'.encode('utf-8'): 'new-1',
 
3433
            'bar': 'new-2',
 
3434
            }
 
3435
        return self.make_records(attribs, [])
 
3436
 
 
3437
    def test_serialize_destruction(self):
 
3438
        tt = self.make_destruction_preview()
 
3439
        foo_trans_id = tt.trans_id_tree_file_id('foo-id')
 
3440
        tt.unversion_file(foo_trans_id)
 
3441
        bar_trans_id = tt.trans_id_tree_file_id('bar-id')
 
3442
        tt.delete_contents(bar_trans_id)
 
3443
        self.assertSerializesTo(self.destruction_records(), tt)
 
3444
 
 
3445
    def test_deserialize_destruction(self):
 
3446
        tt = self.make_destruction_preview()
 
3447
        tt.deserialize(iter(self.destruction_records()))
 
3448
        self.assertEqual({u'foo\u1234': 'new-1',
 
3449
                          'bar': 'new-2',
 
3450
                          '': tt.root}, tt._tree_path_ids)
 
3451
        self.assertEqual({'new-1': u'foo\u1234',
 
3452
                          'new-2': 'bar',
 
3453
                          tt.root: ''}, tt._tree_id_paths)
 
3454
        self.assertEqual(set(['new-1']), tt._removed_id)
 
3455
        self.assertEqual(set(['new-2']), tt._removed_contents)
 
3456
 
 
3457
    def missing_records(self):
 
3458
        attribs = self.default_attribs()
 
3459
        attribs['_id_number'] = 2
 
3460
        attribs['_non_present_ids'] = {
 
3461
            'boo': 'new-1',}
 
3462
        return self.make_records(attribs, [])
 
3463
 
 
3464
    def test_serialize_missing(self):
 
3465
        tt = self.get_preview()
 
3466
        boo_trans_id = tt.trans_id_file_id('boo')
 
3467
        self.assertSerializesTo(self.missing_records(), tt)
 
3468
 
 
3469
    def test_deserialize_missing(self):
 
3470
        tt = self.get_preview()
 
3471
        tt.deserialize(iter(self.missing_records()))
 
3472
        self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
 
3473
 
 
3474
    def make_modification_preview(self):
 
3475
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
3476
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
3477
        tree = self.make_branch_and_tree('tree')
 
3478
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
3479
        tree.add('file', 'file-id')
 
3480
        return self.get_preview(tree), LINES_TWO
 
3481
 
 
3482
    def modification_records(self):
 
3483
        attribs = self.default_attribs()
 
3484
        attribs['_id_number'] = 2
 
3485
        attribs['_tree_path_ids'] = {
 
3486
            'file': 'new-1',
 
3487
            '': 'new-0',}
 
3488
        attribs['_removed_contents'] = ['new-1']
 
3489
        contents = [('new-1', 'file',
 
3490
                     'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
 
3491
        return self.make_records(attribs, contents)
 
3492
 
 
3493
    def test_serialize_modification(self):
 
3494
        tt, LINES = self.make_modification_preview()
 
3495
        trans_id = tt.trans_id_file_id('file-id')
 
3496
        tt.delete_contents(trans_id)
 
3497
        tt.create_file(LINES, trans_id)
 
3498
        self.assertSerializesTo(self.modification_records(), tt)
 
3499
 
 
3500
    def test_deserialize_modification(self):
 
3501
        tt, LINES = self.make_modification_preview()
 
3502
        tt.deserialize(iter(self.modification_records()))
 
3503
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
3504
 
 
3505
    def make_kind_change_preview(self):
 
3506
        LINES = 'a\nb\nc\nd\n'
 
3507
        tree = self.make_branch_and_tree('tree')
 
3508
        self.build_tree(['tree/foo/'])
 
3509
        tree.add('foo', 'foo-id')
 
3510
        return self.get_preview(tree), LINES
 
3511
 
 
3512
    def kind_change_records(self):
 
3513
        attribs = self.default_attribs()
 
3514
        attribs['_id_number'] = 2
 
3515
        attribs['_tree_path_ids'] = {
 
3516
            'foo': 'new-1',
 
3517
            '': 'new-0',}
 
3518
        attribs['_removed_contents'] = ['new-1']
 
3519
        contents = [('new-1', 'file',
 
3520
                     'i 4\na\nb\nc\nd\n\n')]
 
3521
        return self.make_records(attribs, contents)
 
3522
 
 
3523
    def test_serialize_kind_change(self):
 
3524
        tt, LINES = self.make_kind_change_preview()
 
3525
        trans_id = tt.trans_id_file_id('foo-id')
 
3526
        tt.delete_contents(trans_id)
 
3527
        tt.create_file(LINES, trans_id)
 
3528
        self.assertSerializesTo(self.kind_change_records(), tt)
 
3529
 
 
3530
    def test_deserialize_kind_change(self):
 
3531
        tt, LINES = self.make_kind_change_preview()
 
3532
        tt.deserialize(iter(self.kind_change_records()))
 
3533
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
3534
 
 
3535
    def make_add_contents_preview(self):
 
3536
        LINES = 'a\nb\nc\nd\n'
 
3537
        tree = self.make_branch_and_tree('tree')
 
3538
        self.build_tree(['tree/foo'])
 
3539
        tree.add('foo')
 
3540
        os.unlink('tree/foo')
 
3541
        return self.get_preview(tree), LINES
 
3542
 
 
3543
    def add_contents_records(self):
 
3544
        attribs = self.default_attribs()
 
3545
        attribs['_id_number'] = 2
 
3546
        attribs['_tree_path_ids'] = {
 
3547
            'foo': 'new-1',
 
3548
            '': 'new-0',}
 
3549
        contents = [('new-1', 'file',
 
3550
                     'i 4\na\nb\nc\nd\n\n')]
 
3551
        return self.make_records(attribs, contents)
 
3552
 
 
3553
    def test_serialize_add_contents(self):
 
3554
        tt, LINES = self.make_add_contents_preview()
 
3555
        trans_id = tt.trans_id_tree_path('foo')
 
3556
        tt.create_file(LINES, trans_id)
 
3557
        self.assertSerializesTo(self.add_contents_records(), tt)
 
3558
 
 
3559
    def test_deserialize_add_contents(self):
 
3560
        tt, LINES = self.make_add_contents_preview()
 
3561
        tt.deserialize(iter(self.add_contents_records()))
 
3562
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
3563
 
 
3564
    def test_get_parents_lines(self):
 
3565
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
3566
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
3567
        tree = self.make_branch_and_tree('tree')
 
3568
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
3569
        tree.add('file', 'file-id')
 
3570
        tt = self.get_preview(tree)
 
3571
        trans_id = tt.trans_id_tree_path('file')
 
3572
        self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
 
3573
            tt._get_parents_lines(trans_id))
 
3574
 
 
3575
    def test_get_parents_texts(self):
 
3576
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
3577
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
3578
        tree = self.make_branch_and_tree('tree')
 
3579
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
3580
        tree.add('file', 'file-id')
 
3581
        tt = self.get_preview(tree)
 
3582
        trans_id = tt.trans_id_tree_path('file')
 
3583
        self.assertEqual((LINES_ONE,),
 
3584
            tt._get_parents_texts(trans_id))
 
3585
 
 
3586
 
 
3587
class TestOrphan(tests.TestCaseWithTransport):
 
3588
 
 
3589
    def test_no_orphan_for_transform_preview(self):
 
3590
        tree = self.make_branch_and_tree('tree')
 
3591
        tt = transform.TransformPreview(tree)
 
3592
        self.addCleanup(tt.finalize)
 
3593
        self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
 
3594
 
 
3595
    def _set_orphan_policy(self, wt, policy):
 
3596
        wt.branch.get_config().set_user_option('bzr.transform.orphan_policy',
 
3597
                                               policy)
 
3598
 
 
3599
    def _prepare_orphan(self, wt):
 
3600
        self.build_tree(['dir/', 'dir/file', 'dir/foo'])
 
3601
        wt.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
 
3602
        wt.commit('add dir and file ignoring foo')
 
3603
        tt = transform.TreeTransform(wt)
 
3604
        self.addCleanup(tt.finalize)
 
3605
        # dir and bar are deleted
 
3606
        dir_tid = tt.trans_id_tree_path('dir')
 
3607
        file_tid = tt.trans_id_tree_path('dir/file')
 
3608
        orphan_tid = tt.trans_id_tree_path('dir/foo')
 
3609
        tt.delete_contents(file_tid)
 
3610
        tt.unversion_file(file_tid)
 
3611
        tt.delete_contents(dir_tid)
 
3612
        tt.unversion_file(dir_tid)
 
3613
        # There should be a conflict because dir still contain foo
 
3614
        raw_conflicts = tt.find_conflicts()
 
3615
        self.assertLength(1, raw_conflicts)
 
3616
        self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
 
3617
        return tt, orphan_tid
 
3618
 
 
3619
    def test_new_orphan_created(self):
 
3620
        wt = self.make_branch_and_tree('.')
 
3621
        self._set_orphan_policy(wt, 'move')
 
3622
        tt, orphan_tid = self._prepare_orphan(wt)
 
3623
        warnings = []
 
3624
        def warning(*args):
 
3625
            warnings.append(args[0] % args[1:])
 
3626
        self.overrideAttr(trace, 'warning', warning)
 
3627
        remaining_conflicts = resolve_conflicts(tt)
 
3628
        self.assertEquals(['dir/foo has been orphaned in bzr-orphans'],
 
3629
                          warnings)
 
3630
        # Yeah for resolved conflicts !
 
3631
        self.assertLength(0, remaining_conflicts)
 
3632
        # We have a new orphan
 
3633
        self.assertEquals('foo.~1~', tt.final_name(orphan_tid))
 
3634
        self.assertEquals('bzr-orphans',
 
3635
                          tt.final_name(tt.final_parent(orphan_tid)))
 
3636
 
 
3637
    def test_never_orphan(self):
 
3638
        wt = self.make_branch_and_tree('.')
 
3639
        self._set_orphan_policy(wt, 'conflict')
 
3640
        tt, orphan_tid = self._prepare_orphan(wt)
 
3641
        remaining_conflicts = resolve_conflicts(tt)
 
3642
        self.assertLength(1, remaining_conflicts)
 
3643
        self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
 
3644
                         remaining_conflicts.pop())
 
3645
 
 
3646
    def test_orphan_error(self):
 
3647
        def bogus_orphan(tt, orphan_id, parent_id):
 
3648
            raise transform.OrphaningError(tt.final_name(orphan_id),
 
3649
                                           tt.final_name(parent_id))
 
3650
        transform.orphaning_registry.register('bogus', bogus_orphan,
 
3651
                                              'Raise an error when orphaning')
 
3652
        wt = self.make_branch_and_tree('.')
 
3653
        self._set_orphan_policy(wt, 'bogus')
 
3654
        tt, orphan_tid = self._prepare_orphan(wt)
 
3655
        remaining_conflicts = resolve_conflicts(tt)
 
3656
        self.assertLength(1, remaining_conflicts)
 
3657
        self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
 
3658
                         remaining_conflicts.pop())
 
3659
 
 
3660
    def test_unknown_orphan_policy(self):
 
3661
        wt = self.make_branch_and_tree('.')
 
3662
        # Set a fictional policy nobody ever implemented
 
3663
        self._set_orphan_policy(wt, 'donttouchmypreciouuus')
 
3664
        tt, orphan_tid = self._prepare_orphan(wt)
 
3665
        warnings = []
 
3666
        def warning(*args):
 
3667
            warnings.append(args[0] % args[1:])
 
3668
        self.overrideAttr(trace, 'warning', warning)
 
3669
        remaining_conflicts = resolve_conflicts(tt)
 
3670
        # We fallback to the default policy which create a conflict
 
3671
        self.assertLength(1, remaining_conflicts)
 
3672
        self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
 
3673
                         remaining_conflicts.pop())
 
3674
        self.assertLength(1, warnings)
 
3675
        self.assertStartsWith(warnings[0], 'donttouchmypreciouuus')