~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

(jelmer) Use the absolute_import feature everywhere in bzrlib,
 and add a source test to make sure it's used everywhere. (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008 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
 
import stat
19
19
from StringIO import StringIO
20
20
import sys
 
21
import time
21
22
 
22
23
from bzrlib import (
23
24
    bencode,
24
25
    errors,
 
26
    filters,
25
27
    generate_ids,
26
28
    osutils,
27
 
    progress,
28
29
    revision as _mod_revision,
 
30
    rules,
29
31
    symbol_versioning,
30
32
    tests,
 
33
    trace,
 
34
    transform,
31
35
    urlutils,
32
36
    )
33
37
from bzrlib.bzrdir import BzrDir
34
 
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
35
 
                              UnversionedParent, ParentLoop, DeletingParent,
36
 
                              NonDirectoryParent)
 
38
from bzrlib.conflicts import (
 
39
    DeletingParent,
 
40
    DuplicateEntry,
 
41
    DuplicateID,
 
42
    MissingParent,
 
43
    NonDirectoryParent,
 
44
    ParentLoop,
 
45
    UnversionedParent,
 
46
)
37
47
from bzrlib.diff import show_diff_trees
38
 
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
39
 
                           ReusingTransform, CantMoveRoot,
40
 
                           PathsNotVersionedError, ExistingLimbo,
41
 
                           ExistingPendingDeletion, ImmortalLimbo,
42
 
                           ImmortalPendingDeletion, LockError)
43
 
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
)
44
62
from bzrlib.merge import Merge3Merger, Merger
45
63
from bzrlib.tests import (
 
64
    features,
 
65
    TestCaseInTempDir,
 
66
    TestSkipped,
 
67
    )
 
68
from bzrlib.tests.features import (
46
69
    HardlinkFeature,
47
70
    SymlinkFeature,
48
 
    TestCase,
49
 
    TestCaseInTempDir,
50
 
    TestSkipped,
51
71
    )
52
 
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
53
 
                              resolve_conflicts, cook_conflicts,
54
 
                              build_tree, get_backup_name,
55
 
                              _FileMover, resolve_checkout,
56
 
                              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
)
57
84
 
58
85
 
59
86
class TestTreeTransform(tests.TestCaseWithTransport):
68
95
        self.addCleanup(transform.finalize)
69
96
        return transform, transform.root
70
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
 
71
108
    def test_existing_limbo(self):
72
109
        transform, root = self.get_transform()
73
110
        limbo_name = transform._limbodir
100
137
        imaginary_id = transform.trans_id_tree_path('imaginary')
101
138
        imaginary_id2 = transform.trans_id_tree_path('imaginary/')
102
139
        self.assertEqual(imaginary_id, imaginary_id2)
103
 
        self.assertEqual(transform.get_tree_parent(imaginary_id), root)
104
 
        self.assertEqual(transform.final_kind(root), 'directory')
105
 
        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))
106
143
        trans_id = transform.create_path('name', root)
107
144
        self.assertIs(transform.final_file_id(trans_id), None)
108
 
        self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
 
145
        self.assertIs(None, transform.final_kind(trans_id))
109
146
        transform.create_file('contents', trans_id)
110
147
        transform.set_executability(True, trans_id)
111
148
        transform.version_file('my_pretties', trans_id)
136
173
        transform.finalize()
137
174
        transform.finalize()
138
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
 
 
237
    def test_create_files_same_timestamp(self):
 
238
        transform, root = self.get_transform()
 
239
        self.wt.lock_tree_write()
 
240
        self.addCleanup(self.wt.unlock)
 
241
        # Roll back the clock, so that we know everything is being set to the
 
242
        # exact time
 
243
        transform._creation_mtime = creation_mtime = time.time() - 20.0
 
244
        transform.create_file('content-one',
 
245
                              transform.create_path('one', root))
 
246
        time.sleep(1) # *ugly*
 
247
        transform.create_file('content-two',
 
248
                              transform.create_path('two', root))
 
249
        transform.apply()
 
250
        fo, st1 = self.wt.get_file_with_stat(None, path='one', filtered=False)
 
251
        fo.close()
 
252
        fo, st2 = self.wt.get_file_with_stat(None, path='two', filtered=False)
 
253
        fo.close()
 
254
        # We only guarantee 2s resolution
 
255
        self.assertTrue(abs(creation_mtime - st1.st_mtime) < 2.0,
 
256
            "%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
 
257
        # But if we have more than that, all files should get the same result
 
258
        self.assertEqual(st1.st_mtime, st2.st_mtime)
 
259
 
 
260
    def test_change_root_id(self):
 
261
        transform, root = self.get_transform()
 
262
        self.assertNotEqual('new-root-id', self.wt.get_root_id())
 
263
        transform.new_directory('', ROOT_PARENT, 'new-root-id')
 
264
        transform.delete_contents(root)
 
265
        transform.unversion_file(root)
 
266
        transform.fixup_new_roots()
 
267
        transform.apply()
 
268
        self.assertEqual('new-root-id', self.wt.get_root_id())
 
269
 
 
270
    def test_change_root_id_add_files(self):
 
271
        transform, root = self.get_transform()
 
272
        self.assertNotEqual('new-root-id', self.wt.get_root_id())
 
273
        new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
 
274
        transform.new_file('file', new_trans_id, ['new-contents\n'],
 
275
                           'new-file-id')
 
276
        transform.delete_contents(root)
 
277
        transform.unversion_file(root)
 
278
        transform.fixup_new_roots()
 
279
        transform.apply()
 
280
        self.assertEqual('new-root-id', self.wt.get_root_id())
 
281
        self.assertEqual('new-file-id', self.wt.path2id('file'))
 
282
        self.assertFileEqual('new-contents\n', self.wt.abspath('file'))
 
283
 
 
284
    def test_add_two_roots(self):
 
285
        transform, root = self.get_transform()
 
286
        new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
 
287
        new_trans_id = transform.new_directory('', ROOT_PARENT, 'alt-root-id')
 
288
        self.assertRaises(ValueError, transform.fixup_new_roots)
 
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
 
139
352
    def test_hardlink(self):
140
353
        self.requireFeature(HardlinkFeature)
141
354
        transform, root = self.get_transform()
146
359
        trans_id = target_transform.create_path('file1', target_transform.root)
147
360
        target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
148
361
        target_transform.apply()
149
 
        self.failUnlessExists('target/file1')
 
362
        self.assertPathExists('target/file1')
150
363
        source_stat = os.stat(self.wt.abspath('file1'))
151
364
        target_stat = os.stat('target/file1')
152
365
        self.assertEqual(source_stat, target_stat)
318
531
        transform.new_file('FiLe', transform.root, 'content')
319
532
        resolve_conflicts(transform)
320
533
        transform.apply()
321
 
        self.failUnlessExists('tree/file')
322
 
        self.failUnlessExists('tree/FiLe.moved')
 
534
        self.assertPathExists('tree/file')
 
535
        self.assertPathExists('tree/FiLe.moved')
323
536
 
324
537
    def test_resolve_checkout_case_conflict(self):
325
538
        tree = self.make_branch_and_tree('tree')
334
547
        resolve_conflicts(transform,
335
548
                          pass_func=lambda t, c: resolve_checkout(t, c, []))
336
549
        transform.apply()
337
 
        self.failUnlessExists('tree/file')
338
 
        self.failUnlessExists('tree/FiLe.moved')
 
550
        self.assertPathExists('tree/file')
 
551
        self.assertPathExists('tree/FiLe.moved')
339
552
 
340
553
    def test_apply_case_conflict(self):
341
554
        """Ensure that a transform with case conflicts can always be applied"""
349
562
        transform.new_file('dirFiLe', dir, 'content')
350
563
        resolve_conflicts(transform)
351
564
        transform.apply()
352
 
        self.failUnlessExists('tree/file')
 
565
        self.assertPathExists('tree/file')
353
566
        if not os.path.exists('tree/FiLe.moved'):
354
 
            self.failUnlessExists('tree/FiLe')
355
 
        self.failUnlessExists('tree/dir/dirfile')
 
567
            self.assertPathExists('tree/FiLe')
 
568
        self.assertPathExists('tree/dir/dirfile')
356
569
        if not os.path.exists('tree/dir/dirFiLe.moved'):
357
 
            self.failUnlessExists('tree/dir/dirFiLe')
 
570
            self.assertPathExists('tree/dir/dirFiLe')
358
571
 
359
572
    def test_case_insensitive_limbo(self):
360
573
        tree = self.make_branch_and_tree('tree')
568
781
                            'wizard2', 'behind_curtain')
569
782
 
570
783
    def test_symlinks_unicode(self):
571
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
784
        self.requireFeature(features.UnicodeFilenameFeature)
572
785
        self._test_symlinks(u'\N{Euro Sign}wizard',
573
786
                            u'wizard-targ\N{Euro Sign}t',
574
787
                            u'\N{Euro Sign}wizard2',
662
875
        raw_conflicts = resolve_conflicts(tt)
663
876
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
664
877
        tt.finalize()
665
 
        conflicts_s = [str(c) for c in cooked_conflicts]
 
878
        conflicts_s = [unicode(c) for c in cooked_conflicts]
666
879
        self.assertEqual(len(cooked_conflicts), len(conflicts_s))
667
880
        self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy.  '
668
881
                                         'Moved existing file to '
681
894
                                         ' versioned, but has versioned'
682
895
                                         ' children.  Versioned directory.')
683
896
        self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
684
 
                                         ' oz/emeraldcity.  Cancelled move.')
 
897
                                         ' oz/emeraldcity. Cancelled move.')
685
898
 
686
899
    def prepare_wrong_parent_kind(self):
687
900
        tt, root = self.get_transform()
738
951
        self.assertIs(None, self.wt.path2id('parent'))
739
952
        self.assertIs(None, self.wt.path2id('parent.new'))
740
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
 
741
969
    def test_moving_versioned_directories(self):
742
970
        create, root = self.get_transform()
743
971
        kansas = create.new_directory('kansas', root, 'kansas-id')
758
986
        create.apply()
759
987
        transform, root = self.get_transform()
760
988
        transform.adjust_root_path('oldroot', fun)
761
 
        new_root=transform.trans_id_tree_path('')
 
989
        new_root = transform.trans_id_tree_path('')
762
990
        transform.version_file('new-root', new_root)
763
991
        transform.apply()
764
992
 
776
1004
        rename.set_executability(True, myfile)
777
1005
        rename.apply()
778
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
 
779
1045
    def test_set_executability_order(self):
780
1046
        """Ensure that executability behaves the same, no matter what order.
781
1047
 
1053
1319
        parent2 = transform.new_directory('parent2', root)
1054
1320
        transform.adjust_path('child1', parent2, child1)
1055
1321
        transform.apply()
1056
 
        self.failIfExists(self.wt.abspath('parent1/child1'))
1057
 
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
 
1322
        self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
 
1323
        self.assertPathExists(self.wt.abspath('parent2/child1'))
1058
1324
        # rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1059
1325
        # no rename for child1 (counting only renames during apply)
1060
 
        self.failUnlessEqual(2, transform.rename_count)
 
1326
        self.assertEqual(2, transform.rename_count)
1061
1327
 
1062
1328
    def test_cancel_parent(self):
1063
1329
        """Cancelling a parent doesn't cause deletion of a non-empty directory
1086
1352
        parent2 = transform.new_directory('parent2', root)
1087
1353
        transform.adjust_path('child1', parent2, child1)
1088
1354
        transform.apply()
1089
 
        self.failIfExists(self.wt.abspath('parent1'))
1090
 
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
 
1355
        self.assertPathDoesNotExist(self.wt.abspath('parent1'))
 
1356
        self.assertPathExists(self.wt.abspath('parent2/child1'))
1091
1357
        # rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1092
 
        self.failUnlessEqual(2, transform.rename_count)
 
1358
        self.assertEqual(2, transform.rename_count)
1093
1359
 
1094
1360
    def test_adjust_and_cancel(self):
1095
1361
        """Make sure adjust_path keeps track of limbo children properly"""
1128
1394
        child = transform.new_directory('child', parent)
1129
1395
        transform.adjust_path('parent', root, parent)
1130
1396
        transform.apply()
1131
 
        self.failUnlessExists(self.wt.abspath('parent/child'))
 
1397
        self.assertPathExists(self.wt.abspath('parent/child'))
1132
1398
        self.assertEqual(1, transform.rename_count)
1133
1399
 
1134
1400
    def test_reuse_name(self):
1210
1476
        # The rename will fail because the target directory is not empty (but
1211
1477
        # raises FileExists anyway).
1212
1478
        err = self.assertRaises(errors.FileExists, tt_helper)
1213
 
        self.assertContainsRe(str(err),
1214
 
            "^File exists: .+/baz")
 
1479
        self.assertEndsWith(err.path, "/baz")
1215
1480
 
1216
1481
    def test_two_directories_clash(self):
1217
1482
        def tt_helper():
1229
1494
                wt.unlock()
1230
1495
                raise
1231
1496
        err = self.assertRaises(errors.FileExists, tt_helper)
1232
 
        self.assertContainsRe(str(err),
1233
 
            "^File exists: .+/foo")
 
1497
        self.assertEndsWith(err.path, "/foo")
1234
1498
 
1235
1499
    def test_two_directories_clash_finalize(self):
1236
1500
        def tt_helper():
1248
1512
                tt.finalize()
1249
1513
                raise
1250
1514
        err = self.assertRaises(errors.FileExists, tt_helper)
1251
 
        self.assertContainsRe(str(err),
1252
 
            "^File exists: .+/foo")
 
1515
        self.assertEndsWith(err.path, "/foo")
1253
1516
 
1254
1517
    def test_file_to_directory(self):
1255
1518
        wt = self.make_branch_and_tree('.')
1265
1528
        tt.create_file(["aa\n"], bar_trans_id)
1266
1529
        tt.version_file("bar-1", bar_trans_id)
1267
1530
        tt.apply()
1268
 
        self.failUnlessExists("foo/bar")
 
1531
        self.assertPathExists("foo/bar")
1269
1532
        wt.lock_read()
1270
1533
        try:
1271
1534
            self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1288
1551
        tt.delete_contents(foo_trans_id)
1289
1552
        tt.create_symlink("bar", foo_trans_id)
1290
1553
        tt.apply()
1291
 
        self.failUnlessExists("foo")
 
1554
        self.assertPathExists("foo")
1292
1555
        wt.lock_read()
1293
1556
        self.addCleanup(wt.unlock)
1294
1557
        self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1307
1570
        tt.delete_versioned(bar_trans_id)
1308
1571
        tt.create_file(["aa\n"], foo_trans_id)
1309
1572
        tt.apply()
1310
 
        self.failUnlessExists("foo")
 
1573
        self.assertPathExists("foo")
1311
1574
        wt.lock_read()
1312
1575
        self.addCleanup(wt.unlock)
1313
1576
        self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1328
1591
        self.build_tree(['baz'])
1329
1592
        tt.create_hardlink("baz", foo_trans_id)
1330
1593
        tt.apply()
1331
 
        self.failUnlessExists("foo")
1332
 
        self.failUnlessExists("baz")
 
1594
        self.assertPathExists("foo")
 
1595
        self.assertPathExists("baz")
1333
1596
        wt.lock_read()
1334
1597
        self.addCleanup(wt.unlock)
1335
1598
        self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1397
1660
    return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1398
1661
 
1399
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
 
1400
1695
class TestTransformMerge(TestCaseInTempDir):
1401
1696
 
1402
1697
    def test_text_merge(self):
1612
1907
        tree.add_reference(subtree)
1613
1908
        tree.commit('a revision')
1614
1909
        tree.branch.create_checkout('target')
1615
 
        self.failUnlessExists('target')
1616
 
        self.failUnlessExists('target/subtree')
 
1910
        self.assertPathExists('target')
 
1911
        self.assertPathExists('target/subtree')
1617
1912
 
1618
1913
    def test_file_conflict_handling(self):
1619
1914
        """Ensure that when building trees, conflict handling is done"""
1666
1961
        source.commit('added file')
1667
1962
        build_tree(source.basis_tree(), target)
1668
1963
        self.assertEqual([], target.conflicts())
1669
 
        self.failUnlessExists('target/dir1/file')
 
1964
        self.assertPathExists('target/dir1/file')
1670
1965
 
1671
1966
        # Ensure contents are merged
1672
1967
        target = self.make_branch_and_tree('target2')
1673
1968
        self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1674
1969
        build_tree(source.basis_tree(), target)
1675
1970
        self.assertEqual([], target.conflicts())
1676
 
        self.failUnlessExists('target2/dir1/file2')
1677
 
        self.failUnlessExists('target2/dir1/file')
 
1971
        self.assertPathExists('target2/dir1/file2')
 
1972
        self.assertPathExists('target2/dir1/file')
1678
1973
 
1679
1974
        # Ensure new contents are suppressed for existing branches
1680
1975
        target = self.make_branch_and_tree('target3')
1681
1976
        self.make_branch('target3/dir1')
1682
1977
        self.build_tree(['target3/dir1/file2'])
1683
1978
        build_tree(source.basis_tree(), target)
1684
 
        self.failIfExists('target3/dir1/file')
1685
 
        self.failUnlessExists('target3/dir1/file2')
1686
 
        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')
1687
1982
        self.assertEqual([DuplicateEntry('Diverted to',
1688
1983
            'dir1.diverted', 'dir1', 'new-dir1', None)],
1689
1984
            target.conflicts())
1692
1987
        self.build_tree(['target4/dir1/'])
1693
1988
        self.make_branch('target4/dir1/file')
1694
1989
        build_tree(source.basis_tree(), target)
1695
 
        self.failUnlessExists('target4/dir1/file')
 
1990
        self.assertPathExists('target4/dir1/file')
1696
1991
        self.assertEqual('directory', file_kind('target4/dir1/file'))
1697
 
        self.failUnlessExists('target4/dir1/file.diverted')
 
1992
        self.assertPathExists('target4/dir1/file.diverted')
1698
1993
        self.assertEqual([DuplicateEntry('Diverted to',
1699
1994
            'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1700
1995
            target.conflicts())
1768
2063
        self.addCleanup(target.unlock)
1769
2064
        self.assertEqual([], list(target.iter_changes(revision_tree)))
1770
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
 
1771
2078
    def test_build_tree_accelerator_tree_missing_file(self):
1772
2079
        source = self.create_ab_tree()
1773
2080
        os.unlink('source/file1')
1868
2175
        self.assertEqual([], list(target.iter_changes(revision_tree)))
1869
2176
        self.assertTrue(source.is_executable('file1-id'))
1870
2177
 
 
2178
    def install_rot13_content_filter(self, pattern):
 
2179
        # We could use
 
2180
        # self.addCleanup(filters._reset_registry, filters._reset_registry())
 
2181
        # below, but that looks a bit... hard to read even if it's exactly
 
2182
        # the same thing.
 
2183
        original_registry = filters._reset_registry()
 
2184
        def restore_registry():
 
2185
            filters._reset_registry(original_registry)
 
2186
        self.addCleanup(restore_registry)
 
2187
        def rot13(chunks, context=None):
 
2188
            return [''.join(chunks).encode('rot13')]
 
2189
        rot13filter = filters.ContentFilter(rot13, rot13)
 
2190
        filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
 
2191
        os.mkdir(self.test_home_dir + '/.bazaar')
 
2192
        rules_filename = self.test_home_dir + '/.bazaar/rules'
 
2193
        f = open(rules_filename, 'wb')
 
2194
        f.write('[name %s]\nrot13=yes\n' % (pattern,))
 
2195
        f.close()
 
2196
        def uninstall_rules():
 
2197
            os.remove(rules_filename)
 
2198
            rules.reset_rules()
 
2199
        self.addCleanup(uninstall_rules)
 
2200
        rules.reset_rules()
 
2201
 
 
2202
    def test_build_tree_content_filtered_files_are_not_hardlinked(self):
 
2203
        """build_tree will not hardlink files that have content filtering rules
 
2204
        applied to them (but will still hardlink other files from the same tree
 
2205
        if it can).
 
2206
        """
 
2207
        self.requireFeature(HardlinkFeature)
 
2208
        self.install_rot13_content_filter('file1')
 
2209
        source = self.create_ab_tree()
 
2210
        target = self.make_branch_and_tree('target')
 
2211
        revision_tree = source.basis_tree()
 
2212
        revision_tree.lock_read()
 
2213
        self.addCleanup(revision_tree.unlock)
 
2214
        build_tree(revision_tree, target, source, hardlink=True)
 
2215
        target.lock_read()
 
2216
        self.addCleanup(target.unlock)
 
2217
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
2218
        source_stat = os.stat('source/file1')
 
2219
        target_stat = os.stat('target/file1')
 
2220
        self.assertNotEqual(source_stat, target_stat)
 
2221
        source_stat = os.stat('source/file2')
 
2222
        target_stat = os.stat('target/file2')
 
2223
        self.assertEqualStat(source_stat, target_stat)
 
2224
 
1871
2225
    def test_case_insensitive_build_tree_inventory(self):
1872
 
        if (tests.CaseInsensitiveFilesystemFeature.available()
1873
 
            or tests.CaseInsCasePresFilenameFeature.available()):
 
2226
        if (features.CaseInsensitiveFilesystemFeature.available()
 
2227
            or features.CaseInsCasePresFilenameFeature.available()):
1874
2228
            raise tests.UnavailableFeature('Fully case sensitive filesystem')
1875
2229
        source = self.make_branch_and_tree('source')
1876
2230
        self.build_tree(['source/file', 'source/FILE'])
1884
2238
        self.assertEqual('file.moved', target.id2path('lower-id'))
1885
2239
        self.assertEqual('FILE', target.id2path('upper-id'))
1886
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
 
1887
2277
 
1888
2278
class TestCommitTransform(tests.TestCaseWithTransport):
1889
2279
 
1990
2380
        self.assertRaises(errors.MalformedTransform, tt.commit, branch,
1991
2381
                          'message')
1992
2382
 
1993
 
 
1994
 
class MockTransform(object):
1995
 
 
1996
 
    def has_named_child(self, by_parent, parent_id, name):
1997
 
        for child_id in by_parent[parent_id]:
1998
 
            if child_id == '0':
1999
 
                if name == "name~":
2000
 
                    return True
2001
 
            elif name == "name.~%s~" % child_id:
2002
 
                return True
2003
 
        return False
2004
 
 
2005
 
 
2006
 
class MockEntry(object):
2007
 
    def __init__(self):
2008
 
        object.__init__(self)
2009
 
        self.name = "name"
2010
 
 
2011
 
 
2012
 
class TestGetBackupName(TestCase):
2013
 
    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
 
2014
2430
        tt = MockTransform()
2015
 
        name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
2016
 
        self.assertEqual(name, 'name.~1~')
2017
 
        name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
2018
 
        self.assertEqual(name, 'name.~2~')
2019
 
        name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
2020
 
        self.assertEqual(name, 'name.~1~')
2021
 
        name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
2022
 
        self.assertEqual(name, 'name.~1~')
2023
 
        name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
2024
 
        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)
2025
2439
 
2026
2440
 
2027
2441
class TestFileMover(tests.TestCaseWithTransport):
2030
2444
        self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2031
2445
        mover = _FileMover()
2032
2446
        mover.rename('a', 'q')
2033
 
        self.failUnlessExists('q')
2034
 
        self.failIfExists('a')
2035
 
        self.failUnlessExists('q/b')
2036
 
        self.failUnlessExists('c')
2037
 
        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')
2038
2452
 
2039
2453
    def test_pre_delete_rollback(self):
2040
2454
        self.build_tree(['a/'])
2041
2455
        mover = _FileMover()
2042
2456
        mover.pre_delete('a', 'q')
2043
 
        self.failUnlessExists('q')
2044
 
        self.failIfExists('a')
 
2457
        self.assertPathExists('q')
 
2458
        self.assertPathDoesNotExist('a')
2045
2459
        mover.rollback()
2046
 
        self.failIfExists('q')
2047
 
        self.failUnlessExists('a')
 
2460
        self.assertPathDoesNotExist('q')
 
2461
        self.assertPathExists('a')
2048
2462
 
2049
2463
    def test_apply_deletions(self):
2050
2464
        self.build_tree(['a/', 'b/'])
2051
2465
        mover = _FileMover()
2052
2466
        mover.pre_delete('a', 'q')
2053
2467
        mover.pre_delete('b', 'r')
2054
 
        self.failUnlessExists('q')
2055
 
        self.failUnlessExists('r')
2056
 
        self.failIfExists('a')
2057
 
        self.failIfExists('b')
 
2468
        self.assertPathExists('q')
 
2469
        self.assertPathExists('r')
 
2470
        self.assertPathDoesNotExist('a')
 
2471
        self.assertPathDoesNotExist('b')
2058
2472
        mover.apply_deletions()
2059
 
        self.failIfExists('q')
2060
 
        self.failIfExists('r')
2061
 
        self.failIfExists('a')
2062
 
        self.failIfExists('b')
 
2473
        self.assertPathDoesNotExist('q')
 
2474
        self.assertPathDoesNotExist('r')
 
2475
        self.assertPathDoesNotExist('a')
 
2476
        self.assertPathDoesNotExist('b')
2063
2477
 
2064
2478
    def test_file_mover_rollback(self):
2065
2479
        self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2070
2484
            mover.rename('a', 'c')
2071
2485
        except errors.FileExists, e:
2072
2486
            mover.rollback()
2073
 
        self.failUnlessExists('a')
2074
 
        self.failUnlessExists('c/d')
 
2487
        self.assertPathExists('a')
 
2488
        self.assertPathExists('c/d')
2075
2489
 
2076
2490
 
2077
2491
class Bogus(Exception):
2107
2521
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2108
2522
        self.assertRaises(Bogus, tt.apply,
2109
2523
                          _mover=self.ExceptionFileMover(bad_source='a'))
2110
 
        self.failUnlessExists('a')
2111
 
        self.failUnlessExists('a/b')
 
2524
        self.assertPathExists('a')
 
2525
        self.assertPathExists('a/b')
2112
2526
        tt.apply()
2113
 
        self.failUnlessExists('c')
2114
 
        self.failUnlessExists('c/d')
 
2527
        self.assertPathExists('c')
 
2528
        self.assertPathExists('c/d')
2115
2529
 
2116
2530
    def test_rollback_rename_into_place(self):
2117
2531
        tree = self.make_branch_and_tree('.')
2123
2537
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2124
2538
        self.assertRaises(Bogus, tt.apply,
2125
2539
                          _mover=self.ExceptionFileMover(bad_target='c/d'))
2126
 
        self.failUnlessExists('a')
2127
 
        self.failUnlessExists('a/b')
 
2540
        self.assertPathExists('a')
 
2541
        self.assertPathExists('a/b')
2128
2542
        tt.apply()
2129
 
        self.failUnlessExists('c')
2130
 
        self.failUnlessExists('c/d')
 
2543
        self.assertPathExists('c')
 
2544
        self.assertPathExists('c/d')
2131
2545
 
2132
2546
    def test_rollback_deletion(self):
2133
2547
        tree = self.make_branch_and_tree('.')
2139
2553
        tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2140
2554
        self.assertRaises(Bogus, tt.apply,
2141
2555
                          _mover=self.ExceptionFileMover(bad_target='d'))
2142
 
        self.failUnlessExists('a')
2143
 
        self.failUnlessExists('a/b')
2144
 
 
2145
 
    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):
2146
2687
        wt = self.make_branch_and_tree('.')
 
2688
        self.build_tree(['dir/',])
 
2689
        wt.add(['dir'], ['dir-id'])
 
2690
        wt.commit('Create dir')
2147
2691
        tt = TreeTransform(wt)
2148
2692
        self.addCleanup(tt.finalize)
2149
 
        parent = tt.trans_id_file_id('parent-id')
2150
 
        tt.new_file('file', parent, 'Contents')
2151
 
        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())
2152
2717
 
2153
2718
 
2154
2719
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2155
2720
                  ('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2156
2721
                  (False, False))
2157
2722
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2158
 
              ('', ''), ('directory', 'directory'), (False, None))
 
2723
              ('', ''), ('directory', 'directory'), (False, False))
2159
2724
 
2160
2725
 
2161
2726
class TestTransformPreview(tests.TestCaseWithTransport):
2248
2813
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
2249
2814
        changes = preview_tree.iter_changes(revision_tree,
2250
2815
                                            specific_files=[''])
2251
 
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
 
2816
        self.assertEqual([A_ENTRY], list(changes))
2252
2817
 
2253
2818
    def test_want_unversioned(self):
2254
2819
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
2255
2820
        changes = preview_tree.iter_changes(revision_tree,
2256
2821
                                            want_unversioned=True)
2257
 
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
 
2822
        self.assertEqual([A_ENTRY], list(changes))
2258
2823
 
2259
2824
    def test_ignore_extra_trees_no_specific_files(self):
2260
2825
        # extra_trees is harmless without specific_files, so we'll silently
2270
2835
    def test_ignore_pb(self):
2271
2836
        # pb could be supported, but TT.iter_changes doesn't support it.
2272
2837
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
2273
 
        preview_tree.iter_changes(revision_tree, pb=progress.DummyProgress())
 
2838
        preview_tree.iter_changes(revision_tree)
2274
2839
 
2275
2840
    def test_kind(self):
2276
2841
        revision_tree = self.create_tree()
2303
2868
        preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2304
2869
        work_mtime = work_tree.get_file_mtime('file-id', 'file')
2305
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
 
2306
2883
    def test_get_file(self):
2307
2884
        preview = self.get_empty_preview()
2308
2885
        preview.new_file('file', preview.root, 'contents', 'file-id')
2638
3215
        preview = self.get_empty_preview()
2639
3216
        root = preview.new_directory('', ROOT_PARENT, 'tree-root')
2640
3217
        # FIXME: new_directory should mark root.
2641
 
        preview.adjust_path('', ROOT_PARENT, root)
 
3218
        preview.fixup_new_roots()
2642
3219
        preview_tree = preview.get_preview_tree()
2643
3220
        file_trans_id = preview.new_file('a', preview.root, 'contents',
2644
3221
                                         'a-id')
2678
3255
        file_trans_id = preview.trans_id_file_id('file-id')
2679
3256
        preview.delete_contents(file_trans_id)
2680
3257
        preview.create_file('a\nb\n', file_trans_id)
2681
 
        pb = progress.DummyProgress()
2682
3258
        preview_tree = preview.get_preview_tree()
2683
 
        merger = Merger.from_revision_ids(pb, preview_tree,
 
3259
        merger = Merger.from_revision_ids(None, preview_tree,
2684
3260
                                          child_tree.branch.last_revision(),
2685
3261
                                          other_branch=child_tree.branch,
2686
3262
                                          tree_branch=work_tree.branch)
2698
3274
        tt.new_file('name', tt.root, 'content', 'file-id')
2699
3275
        tree2 = self.make_branch_and_tree('tree2')
2700
3276
        tree2.set_root_id('TREE_ROOT')
2701
 
        pb = progress.DummyProgress()
2702
3277
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2703
 
                                         pb, tree.basis_tree())
 
3278
                                         None, tree.basis_tree())
2704
3279
        merger.merge_type = Merge3Merger
2705
3280
        merger.do_merge()
2706
3281
 
2716
3291
        tt.create_file('baz', trans_id)
2717
3292
        tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
2718
3293
        self.build_tree_contents([('tree2/foo', 'qux')])
2719
 
        pb = progress.DummyProgress()
 
3294
        pb = None
2720
3295
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2721
3296
                                         pb, tree.basis_tree())
2722
3297
        merger.merge_type = Merge3Merger
2723
3298
        merger.do_merge()
2724
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
 
2725
3318
    def test_is_executable(self):
2726
3319
        tree = self.make_branch_and_tree('tree')
2727
3320
        preview = TransformPreview(tree)
2750
3343
        self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
2751
3344
 
2752
3345
    def test_ascii_limbo_paths(self):
2753
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
3346
        self.requireFeature(features.UnicodeFilenameFeature)
2754
3347
        branch = self.make_branch('any')
2755
3348
        tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
2756
3349
        tt = TransformPreview(tree)
2773
3366
 
2774
3367
class TestSerializeTransform(tests.TestCaseWithTransport):
2775
3368
 
2776
 
    _test_needs_features = [tests.UnicodeFilenameFeature]
 
3369
    _test_needs_features = [features.UnicodeFilenameFeature]
2777
3370
 
2778
3371
    def get_preview(self, tree=None):
2779
3372
        if tree is None:
2854
3447
        return self.make_records(attribs, contents)
2855
3448
 
2856
3449
    def test_serialize_symlink_creation(self):
2857
 
        self.requireFeature(tests.SymlinkFeature)
 
3450
        self.requireFeature(features.SymlinkFeature)
2858
3451
        tt = self.get_preview()
2859
3452
        tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
2860
3453
        self.assertSerializesTo(self.symlink_creation_records(), tt)
2861
3454
 
2862
3455
    def test_deserialize_symlink_creation(self):
2863
 
        self.requireFeature(tests.SymlinkFeature)
 
3456
        self.requireFeature(features.SymlinkFeature)
2864
3457
        tt = self.get_preview()
2865
3458
        tt.deserialize(iter(self.symlink_creation_records()))
2866
3459
        abspath = tt._limbo_name('new-1')
3033
3626
        trans_id = tt.trans_id_tree_path('file')
3034
3627
        self.assertEqual((LINES_ONE,),
3035
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')