/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-30 16:06:19 UTC
  • mfrom: (5971.1.80 bzr-gpgme)
  • Revision ID: pqm@pqm.ubuntu.com-20110630160619-3022zmfchft893nt
(jr) A new command ``bzr verify-signatures`` has been added to check that
 commits
 are correctly signed with trusted keys by GPG. This requires python-gpgme to
 be installed. ``bzr log`` has gained a ``--signatures`` option to list the
 validity of signatures for each commit. New config options
 ``acceptable_keys``
 and ``validate_signatures_in_log`` can be set to control options to these
 commands. (Jonathan Riddell)

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