~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Patch Queue Manager
  • Date: 2016-02-01 19:56:05 UTC
  • mfrom: (6615.1.1 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20160201195605-o7rl92wf6uyum3fk
(vila) Open trunk again as 2.8b1 (Vincent Ladeuil)

Show diffs side-by-side

added added

removed removed

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