~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Vincent Ladeuil
  • Date: 2012-01-18 14:09:19 UTC
  • mto: This revision was merged to the branch mainline in revision 6468.
  • Revision ID: v.ladeuil+lp@free.fr-20120118140919-rlvdrhpc0nq1lbwi
Change set/remove to require a lock for the branch config files.

This means that tests (or any plugin for that matter) do not requires an
explicit lock on the branch anymore to change a single option. This also
means the optimisation becomes "opt-in" and as such won't be as
spectacular as it may be and/or harder to get right (nothing fails
anymore).

This reduces the diff by ~300 lines.

Code/tests that were updating more than one config option is still taking
a lock to at least avoid some IOs and demonstrate the benefits through
the decreased number of hpss calls.

The duplication between BranchStack and BranchOnlyStack will be removed
once the same sharing is in place for local config files, at which point
the Stack class itself may be able to host the changes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006-2011 Canonical Ltd
2
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
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
 
17
import errno
17
18
import os
18
19
from StringIO import StringIO
19
20
import sys
25
26
    filters,
26
27
    generate_ids,
27
28
    osutils,
28
 
    progress,
29
29
    revision as _mod_revision,
30
30
    rules,
 
31
    symbol_versioning,
31
32
    tests,
 
33
    trace,
 
34
    transform,
32
35
    urlutils,
33
36
    )
34
37
from bzrlib.bzrdir import BzrDir
35
 
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
36
 
                              UnversionedParent, ParentLoop, DeletingParent,
37
 
                              NonDirectoryParent)
 
38
from bzrlib.conflicts import (
 
39
    DeletingParent,
 
40
    DuplicateEntry,
 
41
    DuplicateID,
 
42
    MissingParent,
 
43
    NonDirectoryParent,
 
44
    ParentLoop,
 
45
    UnversionedParent,
 
46
)
38
47
from bzrlib.diff import show_diff_trees
39
 
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
40
 
                           ReusingTransform, CantMoveRoot,
41
 
                           PathsNotVersionedError, ExistingLimbo,
42
 
                           ExistingPendingDeletion, ImmortalLimbo,
43
 
                           ImmortalPendingDeletion, LockError)
44
 
from bzrlib.osutils import file_kind, pathjoin
 
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
)
45
62
from bzrlib.merge import Merge3Merger, Merger
46
63
from bzrlib.tests import (
 
64
    features,
 
65
    TestCaseInTempDir,
 
66
    TestSkipped,
 
67
    )
 
68
from bzrlib.tests.features import (
47
69
    HardlinkFeature,
48
70
    SymlinkFeature,
49
 
    TestCase,
50
 
    TestCaseInTempDir,
51
 
    TestSkipped,
52
71
    )
53
 
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
54
 
                              resolve_conflicts, cook_conflicts,
55
 
                              build_tree, get_backup_name,
56
 
                              _FileMover, resolve_checkout,
57
 
                              TransformPreview, create_from_tree)
 
72
from bzrlib.transform import (
 
73
    build_tree,
 
74
    create_from_tree,
 
75
    cook_conflicts,
 
76
    _FileMover,
 
77
    FinalPaths,
 
78
    resolve_conflicts,
 
79
    resolve_checkout,
 
80
    ROOT_PARENT,
 
81
    TransformPreview,
 
82
    TreeTransform,
 
83
)
58
84
 
59
85
 
60
86
class TestTreeTransform(tests.TestCaseWithTransport):
69
95
        self.addCleanup(transform.finalize)
70
96
        return transform, transform.root
71
97
 
 
98
    def get_transform_for_sha1_test(self):
 
99
        trans, root = self.get_transform()
 
100
        self.wt.lock_tree_write()
 
101
        self.addCleanup(self.wt.unlock)
 
102
        contents = ['just some content\n']
 
103
        sha1 = osutils.sha_strings(contents)
 
104
        # Roll back the clock
 
105
        trans._creation_mtime = time.time() - 20.0
 
106
        return trans, root, contents, sha1
 
107
 
72
108
    def test_existing_limbo(self):
73
109
        transform, root = self.get_transform()
74
110
        limbo_name = transform._limbodir
101
137
        imaginary_id = transform.trans_id_tree_path('imaginary')
102
138
        imaginary_id2 = transform.trans_id_tree_path('imaginary/')
103
139
        self.assertEqual(imaginary_id, imaginary_id2)
104
 
        self.assertEqual(transform.get_tree_parent(imaginary_id), root)
105
 
        self.assertEqual(transform.final_kind(root), 'directory')
106
 
        self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
 
140
        self.assertEqual(root, transform.get_tree_parent(imaginary_id))
 
141
        self.assertEqual('directory', transform.final_kind(root))
 
142
        self.assertEqual(self.wt.get_root_id(), transform.final_file_id(root))
107
143
        trans_id = transform.create_path('name', root)
108
144
        self.assertIs(transform.final_file_id(trans_id), None)
109
 
        self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
 
145
        self.assertIs(None, transform.final_kind(trans_id))
110
146
        transform.create_file('contents', trans_id)
111
147
        transform.set_executability(True, trans_id)
112
148
        transform.version_file('my_pretties', trans_id)
137
173
        transform.finalize()
138
174
        transform.finalize()
139
175
 
 
176
    def test_apply_informs_tree_of_observed_sha1(self):
 
177
        trans, root, contents, sha1 = self.get_transform_for_sha1_test()
 
178
        trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
 
179
                                  sha1=sha1)
 
180
        calls = []
 
181
        orig = self.wt._observed_sha1
 
182
        def _observed_sha1(*args):
 
183
            calls.append(args)
 
184
            orig(*args)
 
185
        self.wt._observed_sha1 = _observed_sha1
 
186
        trans.apply()
 
187
        self.assertEqual([(None, 'file1', trans._observed_sha1s[trans_id])],
 
188
                         calls)
 
189
 
 
190
    def test_create_file_caches_sha1(self):
 
191
        trans, root, contents, sha1 = self.get_transform_for_sha1_test()
 
192
        trans_id = trans.create_path('file1', root)
 
193
        trans.create_file(contents, trans_id, sha1=sha1)
 
194
        st_val = osutils.lstat(trans._limbo_name(trans_id))
 
195
        o_sha1, o_st_val = trans._observed_sha1s[trans_id]
 
196
        self.assertEqual(o_sha1, sha1)
 
197
        self.assertEqualStat(o_st_val, st_val)
 
198
 
 
199
    def test__apply_insertions_updates_sha1(self):
 
200
        trans, root, contents, sha1 = self.get_transform_for_sha1_test()
 
201
        trans_id = trans.create_path('file1', root)
 
202
        trans.create_file(contents, trans_id, sha1=sha1)
 
203
        st_val = osutils.lstat(trans._limbo_name(trans_id))
 
204
        o_sha1, o_st_val = trans._observed_sha1s[trans_id]
 
205
        self.assertEqual(o_sha1, sha1)
 
206
        self.assertEqualStat(o_st_val, st_val)
 
207
        creation_mtime = trans._creation_mtime + 10.0
 
208
        # We fake a time difference from when the file was created until now it
 
209
        # is being renamed by using os.utime. Note that the change we actually
 
210
        # want to see is the real ctime change from 'os.rename()', but as long
 
211
        # as we observe a new stat value, we should be fine.
 
212
        os.utime(trans._limbo_name(trans_id), (creation_mtime, creation_mtime))
 
213
        trans.apply()
 
214
        new_st_val = osutils.lstat(self.wt.abspath('file1'))
 
215
        o_sha1, o_st_val = trans._observed_sha1s[trans_id]
 
216
        self.assertEqual(o_sha1, sha1)
 
217
        self.assertEqualStat(o_st_val, new_st_val)
 
218
        self.assertNotEqual(st_val.st_mtime, new_st_val.st_mtime)
 
219
 
 
220
    def test_new_file_caches_sha1(self):
 
221
        trans, root, contents, sha1 = self.get_transform_for_sha1_test()
 
222
        trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
 
223
                                  sha1=sha1)
 
224
        st_val = osutils.lstat(trans._limbo_name(trans_id))
 
225
        o_sha1, o_st_val = trans._observed_sha1s[trans_id]
 
226
        self.assertEqual(o_sha1, sha1)
 
227
        self.assertEqualStat(o_st_val, st_val)
 
228
 
 
229
    def test_cancel_creation_removes_observed_sha1(self):
 
230
        trans, root, contents, sha1 = self.get_transform_for_sha1_test()
 
231
        trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
 
232
                                  sha1=sha1)
 
233
        self.assertTrue(trans_id in trans._observed_sha1s)
 
234
        trans.cancel_creation(trans_id)
 
235
        self.assertFalse(trans_id in trans._observed_sha1s)
 
236
 
140
237
    def test_create_files_same_timestamp(self):
141
238
        transform, root = self.get_transform()
142
239
        self.wt.lock_tree_write()
190
287
        new_trans_id = transform.new_directory('', ROOT_PARENT, 'alt-root-id')
191
288
        self.assertRaises(ValueError, transform.fixup_new_roots)
192
289
 
 
290
    def test_retain_existing_root(self):
 
291
        tt, root = self.get_transform()
 
292
        with tt:
 
293
            tt.new_directory('', ROOT_PARENT, 'new-root-id')
 
294
            tt.fixup_new_roots()
 
295
            self.assertNotEqual('new-root-id', tt.final_file_id(tt.root))
 
296
 
 
297
    def test_retain_existing_root_added_file(self):
 
298
        tt, root = self.get_transform()
 
299
        new_trans_id = tt.new_directory('', ROOT_PARENT, 'new-root-id')
 
300
        child = tt.new_directory('child', new_trans_id, 'child-id')
 
301
        tt.fixup_new_roots()
 
302
        self.assertEqual(tt.root, tt.final_parent(child))
 
303
 
 
304
    def test_add_unversioned_root(self):
 
305
        transform, root = self.get_transform()
 
306
        new_trans_id = transform.new_directory('', ROOT_PARENT, None)
 
307
        transform.delete_contents(transform.root)
 
308
        transform.fixup_new_roots()
 
309
        self.assertNotIn(transform.root, transform._new_id)
 
310
 
 
311
    def test_remove_root_fixup(self):
 
312
        transform, root = self.get_transform()
 
313
        old_root_id = self.wt.get_root_id()
 
314
        self.assertNotEqual('new-root-id', old_root_id)
 
315
        transform.delete_contents(root)
 
316
        transform.unversion_file(root)
 
317
        transform.fixup_new_roots()
 
318
        transform.apply()
 
319
        self.assertEqual(old_root_id, self.wt.get_root_id())
 
320
 
 
321
        transform, root = self.get_transform()
 
322
        new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
 
323
        new_trans_id = transform.new_directory('', ROOT_PARENT, 'alt-root-id')
 
324
        self.assertRaises(ValueError, transform.fixup_new_roots)
 
325
 
 
326
    def test_fixup_new_roots_permits_empty_tree(self):
 
327
        transform, root = self.get_transform()
 
328
        transform.delete_contents(root)
 
329
        transform.unversion_file(root)
 
330
        transform.fixup_new_roots()
 
331
        self.assertIs(None, transform.final_kind(root))
 
332
        self.assertIs(None, transform.final_file_id(root))
 
333
 
 
334
    def test_apply_retains_root_directory(self):
 
335
        # Do not attempt to delete the physical root directory, because that
 
336
        # is impossible.
 
337
        transform, root = self.get_transform()
 
338
        with transform:
 
339
            transform.delete_contents(root)
 
340
            e = self.assertRaises(AssertionError, self.assertRaises,
 
341
                                  errors.TransformRenameFailed,
 
342
                                  transform.apply)
 
343
        self.assertContainsRe('TransformRenameFailed not raised', str(e))
 
344
 
 
345
    def test_apply_retains_file_id(self):
 
346
        transform, root = self.get_transform()
 
347
        old_root_id = transform.tree_file_id(root)
 
348
        transform.unversion_file(root)
 
349
        transform.apply()
 
350
        self.assertEqual(old_root_id, self.wt.get_root_id())
 
351
 
193
352
    def test_hardlink(self):
194
353
        self.requireFeature(HardlinkFeature)
195
354
        transform, root = self.get_transform()
200
359
        trans_id = target_transform.create_path('file1', target_transform.root)
201
360
        target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
202
361
        target_transform.apply()
203
 
        self.failUnlessExists('target/file1')
 
362
        self.assertPathExists('target/file1')
204
363
        source_stat = os.stat(self.wt.abspath('file1'))
205
364
        target_stat = os.stat('target/file1')
206
365
        self.assertEqual(source_stat, target_stat)
372
531
        transform.new_file('FiLe', transform.root, 'content')
373
532
        resolve_conflicts(transform)
374
533
        transform.apply()
375
 
        self.failUnlessExists('tree/file')
376
 
        self.failUnlessExists('tree/FiLe.moved')
 
534
        self.assertPathExists('tree/file')
 
535
        self.assertPathExists('tree/FiLe.moved')
377
536
 
378
537
    def test_resolve_checkout_case_conflict(self):
379
538
        tree = self.make_branch_and_tree('tree')
388
547
        resolve_conflicts(transform,
389
548
                          pass_func=lambda t, c: resolve_checkout(t, c, []))
390
549
        transform.apply()
391
 
        self.failUnlessExists('tree/file')
392
 
        self.failUnlessExists('tree/FiLe.moved')
 
550
        self.assertPathExists('tree/file')
 
551
        self.assertPathExists('tree/FiLe.moved')
393
552
 
394
553
    def test_apply_case_conflict(self):
395
554
        """Ensure that a transform with case conflicts can always be applied"""
403
562
        transform.new_file('dirFiLe', dir, 'content')
404
563
        resolve_conflicts(transform)
405
564
        transform.apply()
406
 
        self.failUnlessExists('tree/file')
 
565
        self.assertPathExists('tree/file')
407
566
        if not os.path.exists('tree/FiLe.moved'):
408
 
            self.failUnlessExists('tree/FiLe')
409
 
        self.failUnlessExists('tree/dir/dirfile')
 
567
            self.assertPathExists('tree/FiLe')
 
568
        self.assertPathExists('tree/dir/dirfile')
410
569
        if not os.path.exists('tree/dir/dirFiLe.moved'):
411
 
            self.failUnlessExists('tree/dir/dirFiLe')
 
570
            self.assertPathExists('tree/dir/dirFiLe')
412
571
 
413
572
    def test_case_insensitive_limbo(self):
414
573
        tree = self.make_branch_and_tree('tree')
622
781
                            'wizard2', 'behind_curtain')
623
782
 
624
783
    def test_symlinks_unicode(self):
625
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
784
        self.requireFeature(features.UnicodeFilenameFeature)
626
785
        self._test_symlinks(u'\N{Euro Sign}wizard',
627
786
                            u'wizard-targ\N{Euro Sign}t',
628
787
                            u'\N{Euro Sign}wizard2',
716
875
        raw_conflicts = resolve_conflicts(tt)
717
876
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
718
877
        tt.finalize()
719
 
        conflicts_s = [str(c) for c in cooked_conflicts]
 
878
        conflicts_s = [unicode(c) for c in cooked_conflicts]
720
879
        self.assertEqual(len(cooked_conflicts), len(conflicts_s))
721
880
        self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy.  '
722
881
                                         'Moved existing file to '
792
951
        self.assertIs(None, self.wt.path2id('parent'))
793
952
        self.assertIs(None, self.wt.path2id('parent.new'))
794
953
 
 
954
    def test_resolve_conflicts_missing_parent(self):
 
955
        wt = self.make_branch_and_tree('.')
 
956
        tt = TreeTransform(wt)
 
957
        self.addCleanup(tt.finalize)
 
958
        parent = tt.trans_id_file_id('parent-id')
 
959
        tt.new_file('file', parent, 'Contents')
 
960
        raw_conflicts = resolve_conflicts(tt)
 
961
        # Since the directory doesn't exist it's seen as 'missing'.  So
 
962
        # 'resolve_conflicts' create a conflict asking for it to be created.
 
963
        self.assertLength(1, raw_conflicts)
 
964
        self.assertEqual(('missing parent', 'Created directory', 'new-1'),
 
965
                         raw_conflicts.pop())
 
966
        # apply fail since the missing directory doesn't exist
 
967
        self.assertRaises(errors.NoFinalPath, tt.apply)
 
968
 
795
969
    def test_moving_versioned_directories(self):
796
970
        create, root = self.get_transform()
797
971
        kansas = create.new_directory('kansas', root, 'kansas-id')
830
1004
        rename.set_executability(True, myfile)
831
1005
        rename.apply()
832
1006
 
 
1007
    def test_rename_fails(self):
 
1008
        self.requireFeature(features.not_running_as_root)
 
1009
        # see https://bugs.launchpad.net/bzr/+bug/491763
 
1010
        create, root_id = self.get_transform()
 
1011
        first_dir = create.new_directory('first-dir', root_id, 'first-id')
 
1012
        myfile = create.new_file('myfile', root_id, 'myfile-text',
 
1013
                                 'myfile-id')
 
1014
        create.apply()
 
1015
        if os.name == "posix" and sys.platform != "cygwin":
 
1016
            # posix filesystems fail on renaming if the readonly bit is set
 
1017
            osutils.make_readonly(self.wt.abspath('first-dir'))
 
1018
        elif os.name == "nt":
 
1019
            # windows filesystems fail on renaming open files
 
1020
            self.addCleanup(file(self.wt.abspath('myfile')).close)
 
1021
        else:
 
1022
            self.skip("Don't know how to force a permissions error on rename")
 
1023
        # now transform to rename
 
1024
        rename_transform, root_id = self.get_transform()
 
1025
        file_trans_id = rename_transform.trans_id_file_id('myfile-id')
 
1026
        dir_id = rename_transform.trans_id_file_id('first-id')
 
1027
        rename_transform.adjust_path('newname', dir_id, file_trans_id)
 
1028
        e = self.assertRaises(errors.TransformRenameFailed,
 
1029
            rename_transform.apply)
 
1030
        # On nix looks like: 
 
1031
        # "Failed to rename .../work/.bzr/checkout/limbo/new-1
 
1032
        # to .../first-dir/newname: [Errno 13] Permission denied"
 
1033
        # On windows looks like:
 
1034
        # "Failed to rename .../work/myfile to 
 
1035
        # .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
 
1036
        # This test isn't concerned with exactly what the error looks like,
 
1037
        # and the strerror will vary across OS and locales, but the assert
 
1038
        # that the exeception attributes are what we expect
 
1039
        self.assertEqual(e.errno, errno.EACCES)
 
1040
        if os.name == "posix":
 
1041
            self.assertEndsWith(e.to_path, "/first-dir/newname")
 
1042
        else:
 
1043
            self.assertEqual(os.path.basename(e.from_path), "myfile")
 
1044
 
833
1045
    def test_set_executability_order(self):
834
1046
        """Ensure that executability behaves the same, no matter what order.
835
1047
 
1107
1319
        parent2 = transform.new_directory('parent2', root)
1108
1320
        transform.adjust_path('child1', parent2, child1)
1109
1321
        transform.apply()
1110
 
        self.failIfExists(self.wt.abspath('parent1/child1'))
1111
 
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
 
1322
        self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
 
1323
        self.assertPathExists(self.wt.abspath('parent2/child1'))
1112
1324
        # rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1113
1325
        # no rename for child1 (counting only renames during apply)
1114
 
        self.failUnlessEqual(2, transform.rename_count)
 
1326
        self.assertEqual(2, transform.rename_count)
1115
1327
 
1116
1328
    def test_cancel_parent(self):
1117
1329
        """Cancelling a parent doesn't cause deletion of a non-empty directory
1140
1352
        parent2 = transform.new_directory('parent2', root)
1141
1353
        transform.adjust_path('child1', parent2, child1)
1142
1354
        transform.apply()
1143
 
        self.failIfExists(self.wt.abspath('parent1'))
1144
 
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
 
1355
        self.assertPathDoesNotExist(self.wt.abspath('parent1'))
 
1356
        self.assertPathExists(self.wt.abspath('parent2/child1'))
1145
1357
        # rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1146
 
        self.failUnlessEqual(2, transform.rename_count)
 
1358
        self.assertEqual(2, transform.rename_count)
1147
1359
 
1148
1360
    def test_adjust_and_cancel(self):
1149
1361
        """Make sure adjust_path keeps track of limbo children properly"""
1182
1394
        child = transform.new_directory('child', parent)
1183
1395
        transform.adjust_path('parent', root, parent)
1184
1396
        transform.apply()
1185
 
        self.failUnlessExists(self.wt.abspath('parent/child'))
 
1397
        self.assertPathExists(self.wt.abspath('parent/child'))
1186
1398
        self.assertEqual(1, transform.rename_count)
1187
1399
 
1188
1400
    def test_reuse_name(self):
1264
1476
        # The rename will fail because the target directory is not empty (but
1265
1477
        # raises FileExists anyway).
1266
1478
        err = self.assertRaises(errors.FileExists, tt_helper)
1267
 
        self.assertContainsRe(str(err),
1268
 
            "^File exists: .+/baz")
 
1479
        self.assertEndsWith(err.path, "/baz")
1269
1480
 
1270
1481
    def test_two_directories_clash(self):
1271
1482
        def tt_helper():
1283
1494
                wt.unlock()
1284
1495
                raise
1285
1496
        err = self.assertRaises(errors.FileExists, tt_helper)
1286
 
        self.assertContainsRe(str(err),
1287
 
            "^File exists: .+/foo")
 
1497
        self.assertEndsWith(err.path, "/foo")
1288
1498
 
1289
1499
    def test_two_directories_clash_finalize(self):
1290
1500
        def tt_helper():
1302
1512
                tt.finalize()
1303
1513
                raise
1304
1514
        err = self.assertRaises(errors.FileExists, tt_helper)
1305
 
        self.assertContainsRe(str(err),
1306
 
            "^File exists: .+/foo")
 
1515
        self.assertEndsWith(err.path, "/foo")
1307
1516
 
1308
1517
    def test_file_to_directory(self):
1309
1518
        wt = self.make_branch_and_tree('.')
1319
1528
        tt.create_file(["aa\n"], bar_trans_id)
1320
1529
        tt.version_file("bar-1", bar_trans_id)
1321
1530
        tt.apply()
1322
 
        self.failUnlessExists("foo/bar")
 
1531
        self.assertPathExists("foo/bar")
1323
1532
        wt.lock_read()
1324
1533
        try:
1325
1534
            self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1342
1551
        tt.delete_contents(foo_trans_id)
1343
1552
        tt.create_symlink("bar", foo_trans_id)
1344
1553
        tt.apply()
1345
 
        self.failUnlessExists("foo")
 
1554
        self.assertPathExists("foo")
1346
1555
        wt.lock_read()
1347
1556
        self.addCleanup(wt.unlock)
1348
1557
        self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1361
1570
        tt.delete_versioned(bar_trans_id)
1362
1571
        tt.create_file(["aa\n"], foo_trans_id)
1363
1572
        tt.apply()
1364
 
        self.failUnlessExists("foo")
 
1573
        self.assertPathExists("foo")
1365
1574
        wt.lock_read()
1366
1575
        self.addCleanup(wt.unlock)
1367
1576
        self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1382
1591
        self.build_tree(['baz'])
1383
1592
        tt.create_hardlink("baz", foo_trans_id)
1384
1593
        tt.apply()
1385
 
        self.failUnlessExists("foo")
1386
 
        self.failUnlessExists("baz")
 
1594
        self.assertPathExists("foo")
 
1595
        self.assertPathExists("baz")
1387
1596
        wt.lock_read()
1388
1597
        self.addCleanup(wt.unlock)
1389
1598
        self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1451
1660
    return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1452
1661
 
1453
1662
 
 
1663
class TestInventoryAltered(tests.TestCaseWithTransport):
 
1664
 
 
1665
    def test_inventory_altered_unchanged(self):
 
1666
        tree = self.make_branch_and_tree('tree')
 
1667
        self.build_tree(['tree/foo'])
 
1668
        tree.add('foo', 'foo-id')
 
1669
        with TransformPreview(tree) as tt:
 
1670
            self.assertEqual([], tt._inventory_altered())
 
1671
 
 
1672
    def test_inventory_altered_changed_parent_id(self):
 
1673
        tree = self.make_branch_and_tree('tree')
 
1674
        self.build_tree(['tree/foo'])
 
1675
        tree.add('foo', 'foo-id')
 
1676
        with TransformPreview(tree) as tt:
 
1677
            tt.unversion_file(tt.root)
 
1678
            tt.version_file('new-id', tt.root)
 
1679
            foo_trans_id = tt.trans_id_tree_file_id('foo-id')
 
1680
            foo_tuple = ('foo', foo_trans_id)
 
1681
            root_tuple = ('', tt.root)
 
1682
            self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
 
1683
 
 
1684
    def test_inventory_altered_noop_changed_parent_id(self):
 
1685
        tree = self.make_branch_and_tree('tree')
 
1686
        self.build_tree(['tree/foo'])
 
1687
        tree.add('foo', 'foo-id')
 
1688
        with TransformPreview(tree) as tt:
 
1689
            tt.unversion_file(tt.root)
 
1690
            tt.version_file(tree.get_root_id(), tt.root)
 
1691
            foo_trans_id = tt.trans_id_tree_file_id('foo-id')
 
1692
            self.assertEqual([], tt._inventory_altered())
 
1693
 
 
1694
 
1454
1695
class TestTransformMerge(TestCaseInTempDir):
1455
1696
 
1456
1697
    def test_text_merge(self):
1666
1907
        tree.add_reference(subtree)
1667
1908
        tree.commit('a revision')
1668
1909
        tree.branch.create_checkout('target')
1669
 
        self.failUnlessExists('target')
1670
 
        self.failUnlessExists('target/subtree')
 
1910
        self.assertPathExists('target')
 
1911
        self.assertPathExists('target/subtree')
1671
1912
 
1672
1913
    def test_file_conflict_handling(self):
1673
1914
        """Ensure that when building trees, conflict handling is done"""
1720
1961
        source.commit('added file')
1721
1962
        build_tree(source.basis_tree(), target)
1722
1963
        self.assertEqual([], target.conflicts())
1723
 
        self.failUnlessExists('target/dir1/file')
 
1964
        self.assertPathExists('target/dir1/file')
1724
1965
 
1725
1966
        # Ensure contents are merged
1726
1967
        target = self.make_branch_and_tree('target2')
1727
1968
        self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1728
1969
        build_tree(source.basis_tree(), target)
1729
1970
        self.assertEqual([], target.conflicts())
1730
 
        self.failUnlessExists('target2/dir1/file2')
1731
 
        self.failUnlessExists('target2/dir1/file')
 
1971
        self.assertPathExists('target2/dir1/file2')
 
1972
        self.assertPathExists('target2/dir1/file')
1732
1973
 
1733
1974
        # Ensure new contents are suppressed for existing branches
1734
1975
        target = self.make_branch_and_tree('target3')
1735
1976
        self.make_branch('target3/dir1')
1736
1977
        self.build_tree(['target3/dir1/file2'])
1737
1978
        build_tree(source.basis_tree(), target)
1738
 
        self.failIfExists('target3/dir1/file')
1739
 
        self.failUnlessExists('target3/dir1/file2')
1740
 
        self.failUnlessExists('target3/dir1.diverted/file')
 
1979
        self.assertPathDoesNotExist('target3/dir1/file')
 
1980
        self.assertPathExists('target3/dir1/file2')
 
1981
        self.assertPathExists('target3/dir1.diverted/file')
1741
1982
        self.assertEqual([DuplicateEntry('Diverted to',
1742
1983
            'dir1.diverted', 'dir1', 'new-dir1', None)],
1743
1984
            target.conflicts())
1746
1987
        self.build_tree(['target4/dir1/'])
1747
1988
        self.make_branch('target4/dir1/file')
1748
1989
        build_tree(source.basis_tree(), target)
1749
 
        self.failUnlessExists('target4/dir1/file')
 
1990
        self.assertPathExists('target4/dir1/file')
1750
1991
        self.assertEqual('directory', file_kind('target4/dir1/file'))
1751
 
        self.failUnlessExists('target4/dir1/file.diverted')
 
1992
        self.assertPathExists('target4/dir1/file.diverted')
1752
1993
        self.assertEqual([DuplicateEntry('Diverted to',
1753
1994
            'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1754
1995
            target.conflicts())
1822
2063
        self.addCleanup(target.unlock)
1823
2064
        self.assertEqual([], list(target.iter_changes(revision_tree)))
1824
2065
 
 
2066
    def test_build_tree_accelerator_tree_observes_sha1(self):
 
2067
        source = self.create_ab_tree()
 
2068
        sha1 = osutils.sha_string('A')
 
2069
        target = self.make_branch_and_tree('target')
 
2070
        target.lock_write()
 
2071
        self.addCleanup(target.unlock)
 
2072
        state = target.current_dirstate()
 
2073
        state._cutoff_time = time.time() + 60
 
2074
        build_tree(source.basis_tree(), target, source)
 
2075
        entry = state._get_entry(0, path_utf8='file1')
 
2076
        self.assertEqual(sha1, entry[1][0][1])
 
2077
 
1825
2078
    def test_build_tree_accelerator_tree_missing_file(self):
1826
2079
        source = self.create_ab_tree()
1827
2080
        os.unlink('source/file1')
1970
2223
        self.assertEqualStat(source_stat, target_stat)
1971
2224
 
1972
2225
    def test_case_insensitive_build_tree_inventory(self):
1973
 
        if (tests.CaseInsensitiveFilesystemFeature.available()
1974
 
            or tests.CaseInsCasePresFilenameFeature.available()):
 
2226
        if (features.CaseInsensitiveFilesystemFeature.available()
 
2227
            or features.CaseInsCasePresFilenameFeature.available()):
1975
2228
            raise tests.UnavailableFeature('Fully case sensitive filesystem')
1976
2229
        source = self.make_branch_and_tree('source')
1977
2230
        self.build_tree(['source/file', 'source/FILE'])
1985
2238
        self.assertEqual('file.moved', target.id2path('lower-id'))
1986
2239
        self.assertEqual('FILE', target.id2path('upper-id'))
1987
2240
 
 
2241
    def test_build_tree_observes_sha(self):
 
2242
        source = self.make_branch_and_tree('source')
 
2243
        self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
 
2244
        source.add(['file1', 'dir', 'dir/file2'],
 
2245
                   ['file1-id', 'dir-id', 'file2-id'])
 
2246
        source.commit('new files')
 
2247
        target = self.make_branch_and_tree('target')
 
2248
        target.lock_write()
 
2249
        self.addCleanup(target.unlock)
 
2250
        # We make use of the fact that DirState caches its cutoff time. So we
 
2251
        # set the 'safe' time to one minute in the future.
 
2252
        state = target.current_dirstate()
 
2253
        state._cutoff_time = time.time() + 60
 
2254
        build_tree(source.basis_tree(), target)
 
2255
        entry1_sha = osutils.sha_file_by_name('source/file1')
 
2256
        entry2_sha = osutils.sha_file_by_name('source/dir/file2')
 
2257
        # entry[1] is the state information, entry[1][0] is the state of the
 
2258
        # working tree, entry[1][0][1] is the sha value for the current working
 
2259
        # tree
 
2260
        entry1 = state._get_entry(0, path_utf8='file1')
 
2261
        self.assertEqual(entry1_sha, entry1[1][0][1])
 
2262
        # The 'size' field must also be set.
 
2263
        self.assertEqual(25, entry1[1][0][2])
 
2264
        entry1_state = entry1[1][0]
 
2265
        entry2 = state._get_entry(0, path_utf8='dir/file2')
 
2266
        self.assertEqual(entry2_sha, entry2[1][0][1])
 
2267
        self.assertEqual(29, entry2[1][0][2])
 
2268
        entry2_state = entry2[1][0]
 
2269
        # Now, make sure that we don't have to re-read the content. The
 
2270
        # packed_stat should match exactly.
 
2271
        self.assertEqual(entry1_sha, target.get_file_sha1('file1-id', 'file1'))
 
2272
        self.assertEqual(entry2_sha,
 
2273
                         target.get_file_sha1('file2-id', 'dir/file2'))
 
2274
        self.assertEqual(entry1_state, entry1[1][0])
 
2275
        self.assertEqual(entry2_state, entry2[1][0])
 
2276
 
1988
2277
 
1989
2278
class TestCommitTransform(tests.TestCaseWithTransport):
1990
2279
 
2091
2380
        self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2092
2381
                          'message')
2093
2382
 
2094
 
 
2095
 
class MockTransform(object):
2096
 
 
2097
 
    def has_named_child(self, by_parent, parent_id, name):
2098
 
        for child_id in by_parent[parent_id]:
2099
 
            if child_id == '0':
2100
 
                if name == "name~":
2101
 
                    return True
2102
 
            elif name == "name.~%s~" % child_id:
2103
 
                return True
2104
 
        return False
2105
 
 
2106
 
 
2107
 
class MockEntry(object):
2108
 
    def __init__(self):
2109
 
        object.__init__(self)
2110
 
        self.name = "name"
2111
 
 
2112
 
 
2113
 
class TestGetBackupName(TestCase):
2114
 
    def test_get_backup_name(self):
 
2383
    def test_commit_rich_revision_data(self):
 
2384
        branch, tt = self.get_branch_and_transform()
 
2385
        rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
 
2386
                           committer='me <me@example.com>',
 
2387
                           revprops={'foo': 'bar'}, revision_id='revid-1',
 
2388
                           authors=['Author1 <author1@example.com>',
 
2389
                              'Author2 <author2@example.com>',
 
2390
                               ])
 
2391
        self.assertEqual('revid-1', rev_id)
 
2392
        revision = branch.repository.get_revision(rev_id)
 
2393
        self.assertEqual(1, revision.timestamp)
 
2394
        self.assertEqual(43201, revision.timezone)
 
2395
        self.assertEqual('me <me@example.com>', revision.committer)
 
2396
        self.assertEqual(['Author1 <author1@example.com>',
 
2397
                          'Author2 <author2@example.com>'],
 
2398
                         revision.get_apparent_authors())
 
2399
        del revision.properties['authors']
 
2400
        self.assertEqual({'foo': 'bar',
 
2401
                          'branch-nick': 'tree'},
 
2402
                         revision.properties)
 
2403
 
 
2404
    def test_no_explicit_revprops(self):
 
2405
        branch, tt = self.get_branch_and_transform()
 
2406
        rev_id = tt.commit(branch, 'message', authors=[
 
2407
            'Author1 <author1@example.com>',
 
2408
            'Author2 <author2@example.com>', ])
 
2409
        revision = branch.repository.get_revision(rev_id)
 
2410
        self.assertEqual(['Author1 <author1@example.com>',
 
2411
                          'Author2 <author2@example.com>'],
 
2412
                         revision.get_apparent_authors())
 
2413
        self.assertEqual('tree', revision.properties['branch-nick'])
 
2414
 
 
2415
 
 
2416
class TestBackupName(tests.TestCase):
 
2417
 
 
2418
    def test_deprecations(self):
 
2419
        class MockTransform(object):
 
2420
 
 
2421
            def has_named_child(self, by_parent, parent_id, name):
 
2422
                return name in by_parent.get(parent_id, [])
 
2423
 
 
2424
        class MockEntry(object):
 
2425
 
 
2426
            def __init__(self):
 
2427
                object.__init__(self)
 
2428
                self.name = "name"
 
2429
 
2115
2430
        tt = MockTransform()
2116
 
        name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
2117
 
        self.assertEqual(name, 'name.~1~')
2118
 
        name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
2119
 
        self.assertEqual(name, 'name.~2~')
2120
 
        name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
2121
 
        self.assertEqual(name, 'name.~1~')
2122
 
        name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
2123
 
        self.assertEqual(name, 'name.~1~')
2124
 
        name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
2125
 
        self.assertEqual(name, 'name.~4~')
 
2431
        name1 = self.applyDeprecated(
 
2432
            symbol_versioning.deprecated_in((2, 3, 0)),
 
2433
            transform.get_backup_name, MockEntry(), {'a':[]}, 'a', tt)
 
2434
        self.assertEqual('name.~1~', name1)
 
2435
        name2 = self.applyDeprecated(
 
2436
            symbol_versioning.deprecated_in((2, 3, 0)),
 
2437
            transform._get_backup_name, 'name', {'a':['name.~1~']}, 'a', tt)
 
2438
        self.assertEqual('name.~2~', name2)
2126
2439
 
2127
2440
 
2128
2441
class TestFileMover(tests.TestCaseWithTransport):
2131
2444
        self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2132
2445
        mover = _FileMover()
2133
2446
        mover.rename('a', 'q')
2134
 
        self.failUnlessExists('q')
2135
 
        self.failIfExists('a')
2136
 
        self.failUnlessExists('q/b')
2137
 
        self.failUnlessExists('c')
2138
 
        self.failUnlessExists('c/d')
 
2447
        self.assertPathExists('q')
 
2448
        self.assertPathDoesNotExist('a')
 
2449
        self.assertPathExists('q/b')
 
2450
        self.assertPathExists('c')
 
2451
        self.assertPathExists('c/d')
2139
2452
 
2140
2453
    def test_pre_delete_rollback(self):
2141
2454
        self.build_tree(['a/'])
2142
2455
        mover = _FileMover()
2143
2456
        mover.pre_delete('a', 'q')
2144
 
        self.failUnlessExists('q')
2145
 
        self.failIfExists('a')
 
2457
        self.assertPathExists('q')
 
2458
        self.assertPathDoesNotExist('a')
2146
2459
        mover.rollback()
2147
 
        self.failIfExists('q')
2148
 
        self.failUnlessExists('a')
 
2460
        self.assertPathDoesNotExist('q')
 
2461
        self.assertPathExists('a')
2149
2462
 
2150
2463
    def test_apply_deletions(self):
2151
2464
        self.build_tree(['a/', 'b/'])
2152
2465
        mover = _FileMover()
2153
2466
        mover.pre_delete('a', 'q')
2154
2467
        mover.pre_delete('b', 'r')
2155
 
        self.failUnlessExists('q')
2156
 
        self.failUnlessExists('r')
2157
 
        self.failIfExists('a')
2158
 
        self.failIfExists('b')
 
2468
        self.assertPathExists('q')
 
2469
        self.assertPathExists('r')
 
2470
        self.assertPathDoesNotExist('a')
 
2471
        self.assertPathDoesNotExist('b')
2159
2472
        mover.apply_deletions()
2160
 
        self.failIfExists('q')
2161
 
        self.failIfExists('r')
2162
 
        self.failIfExists('a')
2163
 
        self.failIfExists('b')
 
2473
        self.assertPathDoesNotExist('q')
 
2474
        self.assertPathDoesNotExist('r')
 
2475
        self.assertPathDoesNotExist('a')
 
2476
        self.assertPathDoesNotExist('b')
2164
2477
 
2165
2478
    def test_file_mover_rollback(self):
2166
2479
        self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2171
2484
            mover.rename('a', 'c')
2172
2485
        except errors.FileExists, e:
2173
2486
            mover.rollback()
2174
 
        self.failUnlessExists('a')
2175
 
        self.failUnlessExists('c/d')
 
2487
        self.assertPathExists('a')
 
2488
        self.assertPathExists('c/d')
2176
2489
 
2177
2490
 
2178
2491
class Bogus(Exception):
2208
2521
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2209
2522
        self.assertRaises(Bogus, tt.apply,
2210
2523
                          _mover=self.ExceptionFileMover(bad_source='a'))
2211
 
        self.failUnlessExists('a')
2212
 
        self.failUnlessExists('a/b')
 
2524
        self.assertPathExists('a')
 
2525
        self.assertPathExists('a/b')
2213
2526
        tt.apply()
2214
 
        self.failUnlessExists('c')
2215
 
        self.failUnlessExists('c/d')
 
2527
        self.assertPathExists('c')
 
2528
        self.assertPathExists('c/d')
2216
2529
 
2217
2530
    def test_rollback_rename_into_place(self):
2218
2531
        tree = self.make_branch_and_tree('.')
2224
2537
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2225
2538
        self.assertRaises(Bogus, tt.apply,
2226
2539
                          _mover=self.ExceptionFileMover(bad_target='c/d'))
2227
 
        self.failUnlessExists('a')
2228
 
        self.failUnlessExists('a/b')
 
2540
        self.assertPathExists('a')
 
2541
        self.assertPathExists('a/b')
2229
2542
        tt.apply()
2230
 
        self.failUnlessExists('c')
2231
 
        self.failUnlessExists('c/d')
 
2543
        self.assertPathExists('c')
 
2544
        self.assertPathExists('c/d')
2232
2545
 
2233
2546
    def test_rollback_deletion(self):
2234
2547
        tree = self.make_branch_and_tree('.')
2240
2553
        tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2241
2554
        self.assertRaises(Bogus, tt.apply,
2242
2555
                          _mover=self.ExceptionFileMover(bad_target='d'))
2243
 
        self.failUnlessExists('a')
2244
 
        self.failUnlessExists('a/b')
2245
 
 
2246
 
    def test_resolve_no_parent(self):
 
2556
        self.assertPathExists('a')
 
2557
        self.assertPathExists('a/b')
 
2558
 
 
2559
 
 
2560
class TestFinalizeRobustness(tests.TestCaseWithTransport):
 
2561
    """Ensure treetransform creation errors can be safely cleaned up after"""
 
2562
 
 
2563
    def _override_globals_in_method(self, instance, method_name, globals):
 
2564
        """Replace method on instance with one with updated globals"""
 
2565
        import types
 
2566
        func = getattr(instance, method_name).im_func
 
2567
        new_globals = dict(func.func_globals)
 
2568
        new_globals.update(globals)
 
2569
        new_func = types.FunctionType(func.func_code, new_globals,
 
2570
            func.func_name, func.func_defaults)
 
2571
        setattr(instance, method_name,
 
2572
            types.MethodType(new_func, instance, instance.__class__))
 
2573
        self.addCleanup(delattr, instance, method_name)
 
2574
 
 
2575
    @staticmethod
 
2576
    def _fake_open_raises_before(name, mode):
 
2577
        """Like open() but raises before doing anything"""
 
2578
        raise RuntimeError
 
2579
 
 
2580
    @staticmethod
 
2581
    def _fake_open_raises_after(name, mode):
 
2582
        """Like open() but raises after creating file without returning"""
 
2583
        open(name, mode).close()
 
2584
        raise RuntimeError
 
2585
 
 
2586
    def create_transform_and_root_trans_id(self):
 
2587
        """Setup a transform creating a file in limbo"""
 
2588
        tree = self.make_branch_and_tree('.')
 
2589
        tt = TreeTransform(tree)
 
2590
        return tt, tt.create_path("a", tt.root)
 
2591
 
 
2592
    def create_transform_and_subdir_trans_id(self):
 
2593
        """Setup a transform creating a directory containing a file in limbo"""
 
2594
        tree = self.make_branch_and_tree('.')
 
2595
        tt = TreeTransform(tree)
 
2596
        d_trans_id = tt.create_path("d", tt.root)
 
2597
        tt.create_directory(d_trans_id)
 
2598
        f_trans_id = tt.create_path("a", d_trans_id)
 
2599
        tt.adjust_path("a", d_trans_id, f_trans_id)
 
2600
        return tt, f_trans_id
 
2601
 
 
2602
    def test_root_create_file_open_raises_before_creation(self):
 
2603
        tt, trans_id = self.create_transform_and_root_trans_id()
 
2604
        self._override_globals_in_method(tt, "create_file",
 
2605
            {"open": self._fake_open_raises_before})
 
2606
        self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
 
2607
        path = tt._limbo_name(trans_id)
 
2608
        self.assertPathDoesNotExist(path)
 
2609
        tt.finalize()
 
2610
        self.assertPathDoesNotExist(tt._limbodir)
 
2611
 
 
2612
    def test_root_create_file_open_raises_after_creation(self):
 
2613
        tt, trans_id = self.create_transform_and_root_trans_id()
 
2614
        self._override_globals_in_method(tt, "create_file",
 
2615
            {"open": self._fake_open_raises_after})
 
2616
        self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
 
2617
        path = tt._limbo_name(trans_id)
 
2618
        self.assertPathExists(path)
 
2619
        tt.finalize()
 
2620
        self.assertPathDoesNotExist(path)
 
2621
        self.assertPathDoesNotExist(tt._limbodir)
 
2622
 
 
2623
    def test_subdir_create_file_open_raises_before_creation(self):
 
2624
        tt, trans_id = self.create_transform_and_subdir_trans_id()
 
2625
        self._override_globals_in_method(tt, "create_file",
 
2626
            {"open": self._fake_open_raises_before})
 
2627
        self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
 
2628
        path = tt._limbo_name(trans_id)
 
2629
        self.assertPathDoesNotExist(path)
 
2630
        tt.finalize()
 
2631
        self.assertPathDoesNotExist(tt._limbodir)
 
2632
 
 
2633
    def test_subdir_create_file_open_raises_after_creation(self):
 
2634
        tt, trans_id = self.create_transform_and_subdir_trans_id()
 
2635
        self._override_globals_in_method(tt, "create_file",
 
2636
            {"open": self._fake_open_raises_after})
 
2637
        self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
 
2638
        path = tt._limbo_name(trans_id)
 
2639
        self.assertPathExists(path)
 
2640
        tt.finalize()
 
2641
        self.assertPathDoesNotExist(path)
 
2642
        self.assertPathDoesNotExist(tt._limbodir)
 
2643
 
 
2644
    def test_rename_in_limbo_rename_raises_after_rename(self):
 
2645
        tt, trans_id = self.create_transform_and_root_trans_id()
 
2646
        parent1 = tt.new_directory('parent1', tt.root)
 
2647
        child1 = tt.new_file('child1', parent1, 'contents')
 
2648
        parent2 = tt.new_directory('parent2', tt.root)
 
2649
 
 
2650
        class FakeOSModule(object):
 
2651
            def rename(self, old, new):
 
2652
                os.rename(old, new)
 
2653
                raise RuntimeError
 
2654
        self._override_globals_in_method(tt, "_rename_in_limbo",
 
2655
            {"os": FakeOSModule()})
 
2656
        self.assertRaises(
 
2657
            RuntimeError, tt.adjust_path, "child1", parent2, child1)
 
2658
        path = osutils.pathjoin(tt._limbo_name(parent2), "child1")
 
2659
        self.assertPathExists(path)
 
2660
        tt.finalize()
 
2661
        self.assertPathDoesNotExist(path)
 
2662
        self.assertPathDoesNotExist(tt._limbodir)
 
2663
 
 
2664
    def test_rename_in_limbo_rename_raises_before_rename(self):
 
2665
        tt, trans_id = self.create_transform_and_root_trans_id()
 
2666
        parent1 = tt.new_directory('parent1', tt.root)
 
2667
        child1 = tt.new_file('child1', parent1, 'contents')
 
2668
        parent2 = tt.new_directory('parent2', tt.root)
 
2669
 
 
2670
        class FakeOSModule(object):
 
2671
            def rename(self, old, new):
 
2672
                raise RuntimeError
 
2673
        self._override_globals_in_method(tt, "_rename_in_limbo",
 
2674
            {"os": FakeOSModule()})
 
2675
        self.assertRaises(
 
2676
            RuntimeError, tt.adjust_path, "child1", parent2, child1)
 
2677
        path = osutils.pathjoin(tt._limbo_name(parent1), "child1")
 
2678
        self.assertPathExists(path)
 
2679
        tt.finalize()
 
2680
        self.assertPathDoesNotExist(path)
 
2681
        self.assertPathDoesNotExist(tt._limbodir)
 
2682
 
 
2683
 
 
2684
class TestTransformMissingParent(tests.TestCaseWithTransport):
 
2685
 
 
2686
    def make_tt_with_versioned_dir(self):
2247
2687
        wt = self.make_branch_and_tree('.')
 
2688
        self.build_tree(['dir/',])
 
2689
        wt.add(['dir'], ['dir-id'])
 
2690
        wt.commit('Create dir')
2248
2691
        tt = TreeTransform(wt)
2249
2692
        self.addCleanup(tt.finalize)
2250
 
        parent = tt.trans_id_file_id('parent-id')
2251
 
        tt.new_file('file', parent, 'Contents')
2252
 
        resolve_conflicts(tt)
 
2693
        return wt, tt
 
2694
 
 
2695
    def test_resolve_create_parent_for_versioned_file(self):
 
2696
        wt, tt = self.make_tt_with_versioned_dir()
 
2697
        dir_tid = tt.trans_id_tree_file_id('dir-id')
 
2698
        file_tid = tt.new_file('file', dir_tid, 'Contents', file_id='file-id')
 
2699
        tt.delete_contents(dir_tid)
 
2700
        tt.unversion_file(dir_tid)
 
2701
        conflicts = resolve_conflicts(tt)
 
2702
        # one conflict for the missing directory, one for the unversioned
 
2703
        # parent
 
2704
        self.assertLength(2, conflicts)
 
2705
 
 
2706
    def test_non_versioned_file_create_conflict(self):
 
2707
        wt, tt = self.make_tt_with_versioned_dir()
 
2708
        dir_tid = tt.trans_id_tree_file_id('dir-id')
 
2709
        tt.new_file('file', dir_tid, 'Contents')
 
2710
        tt.delete_contents(dir_tid)
 
2711
        tt.unversion_file(dir_tid)
 
2712
        conflicts = resolve_conflicts(tt)
 
2713
        # no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
 
2714
        self.assertLength(1, conflicts)
 
2715
        self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
 
2716
                         conflicts.pop())
2253
2717
 
2254
2718
 
2255
2719
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2256
2720
                  ('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2257
2721
                  (False, False))
2258
2722
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2259
 
              ('', ''), ('directory', 'directory'), (False, None))
 
2723
              ('', ''), ('directory', 'directory'), (False, False))
2260
2724
 
2261
2725
 
2262
2726
class TestTransformPreview(tests.TestCaseWithTransport):
2349
2813
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
2350
2814
        changes = preview_tree.iter_changes(revision_tree,
2351
2815
                                            specific_files=[''])
2352
 
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
 
2816
        self.assertEqual([A_ENTRY], list(changes))
2353
2817
 
2354
2818
    def test_want_unversioned(self):
2355
2819
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
2356
2820
        changes = preview_tree.iter_changes(revision_tree,
2357
2821
                                            want_unversioned=True)
2358
 
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
 
2822
        self.assertEqual([A_ENTRY], list(changes))
2359
2823
 
2360
2824
    def test_ignore_extra_trees_no_specific_files(self):
2361
2825
        # extra_trees is harmless without specific_files, so we'll silently
2404
2868
        preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2405
2869
        work_mtime = work_tree.get_file_mtime('file-id', 'file')
2406
2870
 
 
2871
    def test_get_file_size(self):
 
2872
        work_tree = self.make_branch_and_tree('tree')
 
2873
        self.build_tree_contents([('tree/old', 'old')])
 
2874
        work_tree.add('old', 'old-id')
 
2875
        preview = TransformPreview(work_tree)
 
2876
        self.addCleanup(preview.finalize)
 
2877
        new_id = preview.new_file('name', preview.root, 'contents', 'new-id',
 
2878
                                  'executable')
 
2879
        tree = preview.get_preview_tree()
 
2880
        self.assertEqual(len('old'), tree.get_file_size('old-id'))
 
2881
        self.assertEqual(len('contents'), tree.get_file_size('new-id'))
 
2882
 
2407
2883
    def test_get_file(self):
2408
2884
        preview = self.get_empty_preview()
2409
2885
        preview.new_file('file', preview.root, 'contents', 'file-id')
2821
3297
        merger.merge_type = Merge3Merger
2822
3298
        merger.do_merge()
2823
3299
 
 
3300
    def test_has_filename(self):
 
3301
        wt = self.make_branch_and_tree('tree')
 
3302
        self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
 
3303
        tt = TransformPreview(wt)
 
3304
        removed_id = tt.trans_id_tree_path('removed')
 
3305
        tt.delete_contents(removed_id)
 
3306
        tt.new_file('new', tt.root, 'contents')
 
3307
        modified_id = tt.trans_id_tree_path('modified')
 
3308
        tt.delete_contents(modified_id)
 
3309
        tt.create_file('modified-contents', modified_id)
 
3310
        self.addCleanup(tt.finalize)
 
3311
        tree = tt.get_preview_tree()
 
3312
        self.assertTrue(tree.has_filename('unmodified'))
 
3313
        self.assertFalse(tree.has_filename('not-present'))
 
3314
        self.assertFalse(tree.has_filename('removed'))
 
3315
        self.assertTrue(tree.has_filename('new'))
 
3316
        self.assertTrue(tree.has_filename('modified'))
 
3317
 
2824
3318
    def test_is_executable(self):
2825
3319
        tree = self.make_branch_and_tree('tree')
2826
3320
        preview = TransformPreview(tree)
2849
3343
        self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
2850
3344
 
2851
3345
    def test_ascii_limbo_paths(self):
2852
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
3346
        self.requireFeature(features.UnicodeFilenameFeature)
2853
3347
        branch = self.make_branch('any')
2854
3348
        tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
2855
3349
        tt = TransformPreview(tree)
2872
3366
 
2873
3367
class TestSerializeTransform(tests.TestCaseWithTransport):
2874
3368
 
2875
 
    _test_needs_features = [tests.UnicodeFilenameFeature]
 
3369
    _test_needs_features = [features.UnicodeFilenameFeature]
2876
3370
 
2877
3371
    def get_preview(self, tree=None):
2878
3372
        if tree is None:
2953
3447
        return self.make_records(attribs, contents)
2954
3448
 
2955
3449
    def test_serialize_symlink_creation(self):
2956
 
        self.requireFeature(tests.SymlinkFeature)
 
3450
        self.requireFeature(features.SymlinkFeature)
2957
3451
        tt = self.get_preview()
2958
3452
        tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
2959
3453
        self.assertSerializesTo(self.symlink_creation_records(), tt)
2960
3454
 
2961
3455
    def test_deserialize_symlink_creation(self):
2962
 
        self.requireFeature(tests.SymlinkFeature)
 
3456
        self.requireFeature(features.SymlinkFeature)
2963
3457
        tt = self.get_preview()
2964
3458
        tt.deserialize(iter(self.symlink_creation_records()))
2965
3459
        abspath = tt._limbo_name('new-1')
3132
3626
        trans_id = tt.trans_id_tree_path('file')
3133
3627
        self.assertEqual((LINES_ONE,),
3134
3628
            tt._get_parents_texts(trans_id))
 
3629
 
 
3630
 
 
3631
class TestOrphan(tests.TestCaseWithTransport):
 
3632
 
 
3633
    def test_no_orphan_for_transform_preview(self):
 
3634
        tree = self.make_branch_and_tree('tree')
 
3635
        tt = transform.TransformPreview(tree)
 
3636
        self.addCleanup(tt.finalize)
 
3637
        self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
 
3638
 
 
3639
    def _set_orphan_policy(self, wt, policy):
 
3640
        wt.branch.get_config().set_user_option('bzr.transform.orphan_policy',
 
3641
                                               policy)
 
3642
 
 
3643
    def _prepare_orphan(self, wt):
 
3644
        self.build_tree(['dir/', 'dir/file', 'dir/foo'])
 
3645
        wt.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
 
3646
        wt.commit('add dir and file ignoring foo')
 
3647
        tt = transform.TreeTransform(wt)
 
3648
        self.addCleanup(tt.finalize)
 
3649
        # dir and bar are deleted
 
3650
        dir_tid = tt.trans_id_tree_path('dir')
 
3651
        file_tid = tt.trans_id_tree_path('dir/file')
 
3652
        orphan_tid = tt.trans_id_tree_path('dir/foo')
 
3653
        tt.delete_contents(file_tid)
 
3654
        tt.unversion_file(file_tid)
 
3655
        tt.delete_contents(dir_tid)
 
3656
        tt.unversion_file(dir_tid)
 
3657
        # There should be a conflict because dir still contain foo
 
3658
        raw_conflicts = tt.find_conflicts()
 
3659
        self.assertLength(1, raw_conflicts)
 
3660
        self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
 
3661
        return tt, orphan_tid
 
3662
 
 
3663
    def test_new_orphan_created(self):
 
3664
        wt = self.make_branch_and_tree('.')
 
3665
        self._set_orphan_policy(wt, 'move')
 
3666
        tt, orphan_tid = self._prepare_orphan(wt)
 
3667
        warnings = []
 
3668
        def warning(*args):
 
3669
            warnings.append(args[0] % args[1:])
 
3670
        self.overrideAttr(trace, 'warning', warning)
 
3671
        remaining_conflicts = resolve_conflicts(tt)
 
3672
        self.assertEquals(['dir/foo has been orphaned in bzr-orphans'],
 
3673
                          warnings)
 
3674
        # Yeah for resolved conflicts !
 
3675
        self.assertLength(0, remaining_conflicts)
 
3676
        # We have a new orphan
 
3677
        self.assertEquals('foo.~1~', tt.final_name(orphan_tid))
 
3678
        self.assertEquals('bzr-orphans',
 
3679
                          tt.final_name(tt.final_parent(orphan_tid)))
 
3680
 
 
3681
    def test_never_orphan(self):
 
3682
        wt = self.make_branch_and_tree('.')
 
3683
        self._set_orphan_policy(wt, 'conflict')
 
3684
        tt, orphan_tid = self._prepare_orphan(wt)
 
3685
        remaining_conflicts = resolve_conflicts(tt)
 
3686
        self.assertLength(1, remaining_conflicts)
 
3687
        self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
 
3688
                         remaining_conflicts.pop())
 
3689
 
 
3690
    def test_orphan_error(self):
 
3691
        def bogus_orphan(tt, orphan_id, parent_id):
 
3692
            raise transform.OrphaningError(tt.final_name(orphan_id),
 
3693
                                           tt.final_name(parent_id))
 
3694
        transform.orphaning_registry.register('bogus', bogus_orphan,
 
3695
                                              'Raise an error when orphaning')
 
3696
        wt = self.make_branch_and_tree('.')
 
3697
        self._set_orphan_policy(wt, 'bogus')
 
3698
        tt, orphan_tid = self._prepare_orphan(wt)
 
3699
        remaining_conflicts = resolve_conflicts(tt)
 
3700
        self.assertLength(1, remaining_conflicts)
 
3701
        self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
 
3702
                         remaining_conflicts.pop())
 
3703
 
 
3704
    def test_unknown_orphan_policy(self):
 
3705
        wt = self.make_branch_and_tree('.')
 
3706
        # Set a fictional policy nobody ever implemented
 
3707
        self._set_orphan_policy(wt, 'donttouchmypreciouuus')
 
3708
        tt, orphan_tid = self._prepare_orphan(wt)
 
3709
        warnings = []
 
3710
        def warning(*args):
 
3711
            warnings.append(args[0] % args[1:])
 
3712
        self.overrideAttr(trace, 'warning', warning)
 
3713
        remaining_conflicts = resolve_conflicts(tt)
 
3714
        # We fallback to the default policy which create a conflict
 
3715
        self.assertLength(1, remaining_conflicts)
 
3716
        self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
 
3717
                         remaining_conflicts.pop())
 
3718
        self.assertLength(1, warnings)
 
3719
        self.assertStartsWith(warnings[0], 'donttouchmypreciouuus')