~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/per_repository/test_fileid_involved.py

Merge bzr.dev, update to use new hooks.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
2
 
#
3
 
# This program is free software; you can redistribute it and/or modify
4
 
# it under the terms of the GNU General Public License as published by
5
 
# the Free Software Foundation; either version 2 of the License, or
6
 
# (at your option) any later version.
7
 
#
8
 
# This program is distributed in the hope that it will be useful,
9
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
 
# GNU General Public License for more details.
12
 
#
13
 
# You should have received a copy of the GNU General Public License
14
 
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
import sys
18
 
import time
19
 
 
20
 
from bzrlib import (
21
 
    errors,
22
 
    remote,
23
 
    revision as _mod_revision,
24
 
    tests,
25
 
    transform,
26
 
    )
27
 
from bzrlib.tests import per_repository
28
 
 
29
 
 
30
 
class FileIdInvolvedBase(per_repository.TestCaseWithRepository):
31
 
 
32
 
    def touch(self, tree, filename):
33
 
        # use the trees transport to not depend on the tree's location or type.
34
 
        tree.bzrdir.root_transport.append_bytes(filename, "appended line\n")
35
 
 
36
 
    def compare_tree_fileids(self, branch, old_rev, new_rev):
37
 
        old_tree = self.branch.repository.revision_tree(old_rev)
38
 
        new_tree = self.branch.repository.revision_tree(new_rev)
39
 
        delta = new_tree.changes_from(old_tree)
40
 
 
41
 
        l2 = [id for path, id, kind in delta.added] + \
42
 
             [id for oldpath, newpath, id, kind, text_modified, \
43
 
                meta_modified in delta.renamed] + \
44
 
             [id for path, id, kind, text_modified, meta_modified in \
45
 
                delta.modified]
46
 
        return set(l2)
47
 
 
48
 
 
49
 
class TestFileIdInvolved(FileIdInvolvedBase):
50
 
 
51
 
    def setUp(self):
52
 
        super(TestFileIdInvolved, self).setUp()
53
 
        # create three branches, and merge it
54
 
        #
55
 
        #          ,-->J------>K                (branch2)
56
 
        #         /             \
57
 
        #  A --->B --->C---->D-->G              (main)
58
 
        #  \          /     /
59
 
        #   '--->E---+---->F                    (branch1)
60
 
 
61
 
        # A changes:
62
 
        # B changes: 'a-file-id-2006-01-01-abcd'
63
 
        # C changes:  Nothing (perfect merge)
64
 
        # D changes: 'b-file-id-2006-01-01-defg'
65
 
        # E changes: 'file-d'
66
 
        # F changes: 'file-d'
67
 
        # G changes: 'b-file-id-2006-01-01-defg'
68
 
        # J changes: 'b-file-id-2006-01-01-defg'
69
 
        # K changes: 'c-funky<file-id>quiji%bo'
70
 
 
71
 
        main_wt = self.make_branch_and_tree('main')
72
 
        main_branch = main_wt.branch
73
 
        self.build_tree(["main/a","main/b","main/c"])
74
 
 
75
 
        main_wt.add(['a', 'b', 'c'], ['a-file-id-2006-01-01-abcd',
76
 
                                 'b-file-id-2006-01-01-defg',
77
 
                                 'c-funky<file-id>quiji%bo'])
78
 
        try:
79
 
            main_wt.commit("Commit one", rev_id="rev-A")
80
 
        except errors.IllegalPath:
81
 
            # TODO: jam 20060701 Consider raising a different exception
82
 
            #       newer formats do support this, and nothin can done to
83
 
            #       correct this test - its not a bug.
84
 
            if sys.platform == 'win32':
85
 
                raise tests.TestSkipped('Old repository formats do not'
86
 
                                        ' support file ids with <> on win32')
87
 
            # This is not a known error condition
88
 
            raise
89
 
 
90
 
        #-------- end A -----------
91
 
 
92
 
        bt1 = self.make_branch_and_tree('branch1')
93
 
        bt1.pull(main_branch)
94
 
        b1 = bt1.branch
95
 
        self.build_tree(["branch1/d"])
96
 
        bt1.add(['d'], ['file-d'])
97
 
        bt1.commit("branch1, Commit one", rev_id="rev-E")
98
 
 
99
 
        #-------- end E -----------
100
 
 
101
 
        self.touch(main_wt, "a")
102
 
        main_wt.commit("Commit two", rev_id="rev-B")
103
 
 
104
 
        #-------- end B -----------
105
 
 
106
 
        bt2 = self.make_branch_and_tree('branch2')
107
 
        bt2.pull(main_branch)
108
 
        branch2_branch = bt2.branch
109
 
        set_executability(bt2, 'b', True)
110
 
        bt2.commit("branch2, Commit one", rev_id="rev-J")
111
 
 
112
 
        #-------- end J -----------
113
 
 
114
 
        main_wt.merge_from_branch(b1)
115
 
        main_wt.commit("merge branch1, rev-11", rev_id="rev-C")
116
 
 
117
 
        #-------- end C -----------
118
 
 
119
 
        bt1.rename_one("d","e")
120
 
        bt1.commit("branch1, commit two", rev_id="rev-F")
121
 
 
122
 
        #-------- end F -----------
123
 
 
124
 
        self.touch(bt2, "c")
125
 
        bt2.commit("branch2, commit two", rev_id="rev-K")
126
 
 
127
 
        #-------- end K -----------
128
 
 
129
 
        main_wt.merge_from_branch(b1)
130
 
        self.touch(main_wt, "b")
131
 
        # D gets some funky characters to make sure the unescaping works
132
 
        main_wt.commit("merge branch1, rev-12", rev_id="rev-<D>")
133
 
 
134
 
        # end D
135
 
 
136
 
        main_wt.merge_from_branch(branch2_branch)
137
 
        main_wt.commit("merge branch1, rev-22",  rev_id="rev-G")
138
 
 
139
 
        # end G
140
 
        self.branch = main_branch
141
 
 
142
 
    def test_fileids_altered_between_two_revs(self):
143
 
        self.branch.lock_read()
144
 
        self.addCleanup(self.branch.unlock)
145
 
        self.branch.repository.fileids_altered_by_revision_ids(["rev-J","rev-K"])
146
 
        self.assertEqual(
147
 
            {'b-file-id-2006-01-01-defg':set(['rev-J']),
148
 
             'c-funky<file-id>quiji%bo':set(['rev-K'])
149
 
             },
150
 
            self.branch.repository.fileids_altered_by_revision_ids(["rev-J","rev-K"]))
151
 
 
152
 
        self.assertEqual(
153
 
            {'b-file-id-2006-01-01-defg': set(['rev-<D>']),
154
 
             'file-d': set(['rev-F']),
155
 
             },
156
 
            self.branch.repository.fileids_altered_by_revision_ids(['rev-<D>', 'rev-F']))
157
 
 
158
 
        self.assertEqual(
159
 
            {
160
 
             'b-file-id-2006-01-01-defg': set(['rev-<D>', 'rev-G', 'rev-J']),
161
 
             'c-funky<file-id>quiji%bo': set(['rev-K']),
162
 
             'file-d': set(['rev-F']),
163
 
             },
164
 
            self.branch.repository.fileids_altered_by_revision_ids(
165
 
                ['rev-<D>', 'rev-G', 'rev-F', 'rev-K', 'rev-J']))
166
 
 
167
 
        self.assertEqual(
168
 
            {'a-file-id-2006-01-01-abcd': set(['rev-B']),
169
 
             'b-file-id-2006-01-01-defg': set(['rev-<D>', 'rev-G', 'rev-J']),
170
 
             'c-funky<file-id>quiji%bo': set(['rev-K']),
171
 
             'file-d': set(['rev-F']),
172
 
             },
173
 
            self.branch.repository.fileids_altered_by_revision_ids(
174
 
                ['rev-G', 'rev-F', 'rev-C', 'rev-B', 'rev-<D>', 'rev-K', 'rev-J']))
175
 
 
176
 
    def fileids_altered_by_revision_ids(self, revision_ids):
177
 
        """This is a wrapper to strip TREE_ROOT if it occurs"""
178
 
        repo = self.branch.repository
179
 
        root_id = self.branch.basis_tree().get_root_id()
180
 
        result = repo.fileids_altered_by_revision_ids(revision_ids)
181
 
        if root_id in result:
182
 
            del result[root_id]
183
 
        return result
184
 
 
185
 
    def test_fileids_altered_by_revision_ids(self):
186
 
        self.branch.lock_read()
187
 
        self.addCleanup(self.branch.unlock)
188
 
        self.assertEqual(
189
 
            {'a-file-id-2006-01-01-abcd':set(['rev-A']),
190
 
             'b-file-id-2006-01-01-defg': set(['rev-A']),
191
 
             'c-funky<file-id>quiji%bo': set(['rev-A']),
192
 
             },
193
 
            self.fileids_altered_by_revision_ids(["rev-A"]))
194
 
        self.assertEqual(
195
 
            {'a-file-id-2006-01-01-abcd':set(['rev-B'])
196
 
             },
197
 
            self.branch.repository.fileids_altered_by_revision_ids(["rev-B"]))
198
 
        self.assertEqual(
199
 
            {'b-file-id-2006-01-01-defg':set(['rev-<D>'])
200
 
             },
201
 
            self.branch.repository.fileids_altered_by_revision_ids(["rev-<D>"]))
202
 
 
203
 
    def test_fileids_involved_full_compare(self):
204
 
        # this tests that the result of each fileid_involved calculation
205
 
        # along a revision history selects only the fileids selected by
206
 
        # comparing the trees - no less, and no more. This is correct
207
 
        # because in our sample data we do not revert any file ids along
208
 
        # the revision history.
209
 
        self.branch.lock_read()
210
 
        self.addCleanup(self.branch.unlock)
211
 
        pp=[]
212
 
        history = self.branch.revision_history( )
213
 
 
214
 
        if len(history) < 2: return
215
 
 
216
 
        for start in range(0,len(history)-1):
217
 
            start_id = history[start]
218
 
            for end in range(start+1,len(history)):
219
 
                end_id = history[end]
220
 
                old_revs = set(self.branch.repository.get_ancestry(start_id))
221
 
                new_revs = set(self.branch.repository.get_ancestry(end_id))
222
 
                l1 = self.branch.repository.fileids_altered_by_revision_ids(
223
 
                    new_revs.difference(old_revs))
224
 
                l1 = set(l1.keys())
225
 
 
226
 
                l2 = self.compare_tree_fileids(self.branch, start_id, end_id)
227
 
                self.assertEquals(l1, l2)
228
 
 
229
 
 
230
 
class TestFileIdInvolvedNonAscii(FileIdInvolvedBase):
231
 
 
232
 
    def test_utf8_file_ids_and_revision_ids(self):
233
 
        main_wt = self.make_branch_and_tree('main')
234
 
        main_branch = main_wt.branch
235
 
        self.build_tree(["main/a"])
236
 
 
237
 
        file_id = u'a-f\xedle-id'.encode('utf8')
238
 
        main_wt.add(['a'], [file_id])
239
 
        revision_id = u'r\xe9v-a'.encode('utf8')
240
 
        try:
241
 
            main_wt.commit('a', rev_id=revision_id)
242
 
        except errors.NonAsciiRevisionId:
243
 
            raise tests.TestSkipped('non-ascii revision ids not supported by %s'
244
 
                                    % self.repository_format)
245
 
 
246
 
        repo = main_wt.branch.repository
247
 
        repo.lock_read()
248
 
        self.addCleanup(repo.unlock)
249
 
        file_ids = repo.fileids_altered_by_revision_ids([revision_id])
250
 
        root_id = main_wt.basis_tree().get_root_id()
251
 
        if root_id in file_ids:
252
 
            self.assertEqual({file_id:set([revision_id]),
253
 
                              root_id:set([revision_id])
254
 
                             }, file_ids)
255
 
        else:
256
 
            self.assertEqual({file_id:set([revision_id])}, file_ids)
257
 
 
258
 
 
259
 
class TestFileIdInvolvedSuperset(FileIdInvolvedBase):
260
 
 
261
 
    def setUp(self):
262
 
        super(TestFileIdInvolvedSuperset, self).setUp()
263
 
 
264
 
        self.branch = None
265
 
        main_wt = self.make_branch_and_tree('main')
266
 
        main_branch = main_wt.branch
267
 
        self.build_tree(["main/a","main/b","main/c"])
268
 
 
269
 
        main_wt.add(['a', 'b', 'c'], ['a-file-id-2006-01-01-abcd',
270
 
                                 'b-file-id-2006-01-01-defg',
271
 
                                 'c-funky<file-id>quiji\'"%bo'])
272
 
        try:
273
 
            main_wt.commit("Commit one", rev_id="rev-A")
274
 
        except errors.IllegalPath:
275
 
            # TODO: jam 20060701 Consider raising a different exception
276
 
            #       newer formats do support this, and nothin can done to
277
 
            #       correct this test - its not a bug.
278
 
            if sys.platform == 'win32':
279
 
                raise tests.TestSkipped('Old repository formats do not'
280
 
                                        ' support file ids with <> on win32')
281
 
            # This is not a known error condition
282
 
            raise
283
 
 
284
 
        branch2_wt = self.make_branch_and_tree('branch2')
285
 
        branch2_wt.pull(main_branch)
286
 
        branch2_bzrdir = branch2_wt.bzrdir
287
 
        branch2_branch = branch2_bzrdir.open_branch()
288
 
        set_executability(branch2_wt, 'b', True)
289
 
        branch2_wt.commit("branch2, Commit one", rev_id="rev-J")
290
 
 
291
 
        main_wt.merge_from_branch(branch2_branch)
292
 
        set_executability(main_wt, 'b', False)
293
 
        main_wt.commit("merge branch1, rev-22",  rev_id="rev-G")
294
 
 
295
 
        # end G
296
 
        self.branch = main_branch
297
 
 
298
 
    def test_fileid_involved_full_compare2(self):
299
 
        # this tests that fileids_altered_by_revision_ids returns
300
 
        # more information than compare_tree can, because it
301
 
        # sees each change rather than the aggregate delta.
302
 
        self.branch.lock_read()
303
 
        self.addCleanup(self.branch.unlock)
304
 
        history = self.branch.revision_history()
305
 
        old_rev = history[0]
306
 
        new_rev = history[1]
307
 
        old_revs = set(self.branch.repository.get_ancestry(old_rev))
308
 
        new_revs = set(self.branch.repository.get_ancestry(new_rev))
309
 
 
310
 
        l1 = self.branch.repository.fileids_altered_by_revision_ids(
311
 
            new_revs.difference(old_revs))
312
 
        l1 = set(l1.keys())
313
 
 
314
 
        l2 = self.compare_tree_fileids(self.branch, old_rev, new_rev)
315
 
        self.assertNotEqual(l2, l1)
316
 
        self.assertSubset(l2, l1)
317
 
 
318
 
 
319
 
class FileIdInvolvedWGhosts(per_repository.TestCaseWithRepository):
320
 
 
321
 
    def create_branch_with_ghost_text(self):
322
 
        builder = self.make_branch_builder('ghost')
323
 
        builder.build_snapshot('A-id', None, [
324
 
            ('add', ('', 'root-id', 'directory', None)),
325
 
            ('add', ('a', 'a-file-id', 'file', 'some content\n'))])
326
 
        b = builder.get_branch()
327
 
        old_rt = b.repository.revision_tree('A-id')
328
 
        new_inv = old_rt.inventory._get_mutable_inventory()
329
 
        new_inv.revision_id = 'B-id'
330
 
        new_inv['a-file-id'].revision = 'ghost-id'
331
 
        new_rev = _mod_revision.Revision('B-id',
332
 
            timestamp=time.time(),
333
 
            timezone=0,
334
 
            message='Committing against a ghost',
335
 
            committer='Joe Foo <joe@foo.com>',
336
 
            properties={},
337
 
            parent_ids=('A-id', 'ghost-id'),
338
 
            )
339
 
        b.lock_write()
340
 
        self.addCleanup(b.unlock)
341
 
        b.repository.start_write_group()
342
 
        b.repository.add_revision('B-id', new_rev, new_inv)
343
 
        self.disable_commit_write_group_paranoia(b.repository)
344
 
        b.repository.commit_write_group()
345
 
        return b
346
 
 
347
 
    def disable_commit_write_group_paranoia(self, repo):
348
 
        if isinstance(repo, remote.RemoteRepository):
349
 
            # We can't easily disable the checks in a remote repo.
350
 
            repo.abort_write_group()
351
 
            raise tests.TestSkipped(
352
 
                "repository format does not support storing revisions with "
353
 
                "missing texts.")
354
 
        pack_coll = getattr(repo, '_pack_collection', None)
355
 
        if pack_coll is not None:
356
 
            # Monkey-patch the pack collection instance to allow storing
357
 
            # incomplete revisions.
358
 
            pack_coll._check_new_inventories = lambda: []
359
 
 
360
 
    def test_file_ids_include_ghosts(self):
361
 
        b = self.create_branch_with_ghost_text()
362
 
        repo = b.repository
363
 
        self.assertEqual(
364
 
            {'a-file-id':set(['ghost-id'])},
365
 
            repo.fileids_altered_by_revision_ids(['B-id']))
366
 
 
367
 
    def test_file_ids_uses_fallbacks(self):
368
 
        builder = self.make_branch_builder('source',
369
 
                                           format=self.bzrdir_format)
370
 
        repo = builder.get_branch().repository
371
 
        if not repo._format.supports_external_lookups:
372
 
            raise tests.TestNotApplicable('format does not support stacking')
373
 
        builder.start_series()
374
 
        builder.build_snapshot('A-id', None, [
375
 
            ('add', ('', 'root-id', 'directory', None)),
376
 
            ('add', ('file', 'file-id', 'file', 'contents\n'))])
377
 
        builder.build_snapshot('B-id', ['A-id'], [
378
 
            ('modify', ('file-id', 'new-content\n'))])
379
 
        builder.build_snapshot('C-id', ['B-id'], [
380
 
            ('modify', ('file-id', 'yet more content\n'))])
381
 
        builder.finish_series()
382
 
        source_b = builder.get_branch()
383
 
        source_b.lock_read()
384
 
        self.addCleanup(source_b.unlock)
385
 
        base = self.make_branch('base')
386
 
        base.pull(source_b, stop_revision='B-id')
387
 
        stacked = self.make_branch('stacked')
388
 
        stacked.set_stacked_on_url('../base')
389
 
        stacked.pull(source_b, stop_revision='C-id')
390
 
 
391
 
        stacked.lock_read()
392
 
        self.addCleanup(stacked.unlock)
393
 
        repo = stacked.repository
394
 
        keys = {'file-id': set(['A-id'])}
395
 
        if stacked.repository.supports_rich_root():
396
 
            keys['root-id'] = set(['A-id'])
397
 
        self.assertEqual(keys, repo.fileids_altered_by_revision_ids(['A-id']))
398
 
 
399
 
 
400
 
def set_executability(wt, path, executable=True):
401
 
    """Set the executable bit for the file at path in the working tree
402
 
 
403
 
    os.chmod() doesn't work on windows. But TreeTransform can mark or
404
 
    unmark a file as executable.
405
 
    """
406
 
    file_id = wt.path2id(path)
407
 
    tt = transform.TreeTransform(wt)
408
 
    try:
409
 
        tt.set_executability(executable, tt.trans_id_tree_file_id(file_id))
410
 
        tt.apply()
411
 
    finally:
412
 
        tt.finalize()