/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_fileid_involved.py

Converted test cases to Tree Transform

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2005 by Canonical Ltd
 
2
 
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
#
 
7
 
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
#
 
12
 
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 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")
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
import os
 
18
 
 
19
from bzrlib.add import smart_add
 
20
from bzrlib.branch import Branch
 
21
from bzrlib.builtins import merge
 
22
from bzrlib.delta import compare_trees
 
23
from bzrlib.fetch import greedy_fetch
 
24
from bzrlib.merge import merge_inner
 
25
from bzrlib.revision import common_ancestor
 
26
from bzrlib.tests import TestCaseWithTransport
 
27
from bzrlib.workingtree import WorkingTree
 
28
 
 
29
class FileIdInvolvedBase(TestCaseWithTransport):
 
30
 
 
31
    def touch(self,filename):
 
32
        f = file(filename,"a")
 
33
        f.write("appended line\n")
 
34
        f.close( )
 
35
 
 
36
    def merge(self, branch_from, wt_to):
 
37
        # minimal ui-less merge.
 
38
        greedy_fetch(to_branch=wt_to.branch, from_branch=branch_from,
 
39
                     revision=branch_from.last_revision())
 
40
        base_rev = common_ancestor(branch_from.last_revision(),
 
41
                                    wt_to.branch.last_revision(),
 
42
                                    wt_to.branch.repository)
 
43
        merge_inner(wt_to.branch, branch_from.working_tree(), 
 
44
                    wt_to.branch.repository.revision_tree(base_rev),
 
45
                    this_tree=wt_to)
 
46
        wt_to.add_pending_merge(branch_from.last_revision())
35
47
 
36
48
    def compare_tree_fileids(self, branch, old_rev, new_rev):
37
49
        old_tree = self.branch.repository.revision_tree(old_rev)
38
50
        new_tree = self.branch.repository.revision_tree(new_rev)
39
 
        delta = new_tree.changes_from(old_tree)
 
51
        delta = compare_trees(old_tree, new_tree)
40
52
 
41
53
        l2 = [id for path, id, kind in delta.added] + \
42
54
             [id for oldpath, newpath, id, kind, text_modified, \
45
57
                delta.modified]
46
58
        return set(l2)
47
59
 
48
 
 
 
60
    
49
61
class TestFileIdInvolved(FileIdInvolvedBase):
50
62
 
51
63
    def setUp(self):
52
64
        super(TestFileIdInvolved, self).setUp()
53
65
        # create three branches, and merge it
54
66
        #
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'
 
67
        #           /-->J ------>K                (branch2)
 
68
        #          /              \
 
69
        #  A ---> B --->C ---->D->G               (main)
 
70
        #  \           /      /
 
71
        #   \---> E---/----> F                 (branch1)
70
72
 
71
73
        main_wt = self.make_branch_and_tree('main')
72
74
        main_branch = main_wt.branch
74
76
 
75
77
        main_wt.add(['a', 'b', 'c'], ['a-file-id-2006-01-01-abcd',
76
78
                                 '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
 
 
 
79
                                 'c-funky<file-id> quiji%bo'])
 
80
        main_wt.commit("Commit one", rev_id="rev-A")
90
81
        #-------- end A -----------
91
82
 
92
 
        bt1 = self.make_branch_and_tree('branch1')
93
 
        bt1.pull(main_branch)
94
 
        b1 = bt1.branch
 
83
        b1 = main_branch.clone("branch1")
95
84
        self.build_tree(["branch1/d"])
96
 
        bt1.add(['d'], ['file-d'])
97
 
        bt1.commit("branch1, Commit one", rev_id="rev-E")
 
85
        b1.working_tree().add('d')
 
86
        b1.working_tree().commit("branch1, Commit one", rev_id="rev-E")
98
87
 
99
88
        #-------- end E -----------
100
89
 
101
 
        self.touch(main_wt, "a")
 
90
        self.touch("main/a")
102
91
        main_wt.commit("Commit two", rev_id="rev-B")
103
92
 
104
93
        #-------- end B -----------
105
94
 
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")
 
95
        branch2_branch = main_branch.clone("branch2")
 
96
        os.chmod("branch2/b",0770)
 
97
        branch2_branch.working_tree().commit("branch2, Commit one", 
 
98
                                             rev_id="rev-J")
111
99
 
112
100
        #-------- end J -----------
113
101
 
114
 
        main_wt.merge_from_branch(b1)
 
102
        self.merge(b1, main_wt)
115
103
        main_wt.commit("merge branch1, rev-11", rev_id="rev-C")
116
104
 
117
105
        #-------- end C -----------
118
106
 
119
 
        bt1.rename_one("d","e")
120
 
        bt1.commit("branch1, commit two", rev_id="rev-F")
 
107
        tree = WorkingTree('branch1', b1)
 
108
        tree.rename_one("d","e")
 
109
        tree.commit("branch1, commit two", rev_id="rev-F")
121
110
 
122
111
        #-------- end F -----------
123
112
 
124
 
        self.touch(bt2, "c")
125
 
        bt2.commit("branch2, commit two", rev_id="rev-K")
 
113
        self.touch("branch2/c")
 
114
        branch2_branch.working_tree().commit("branch2, commit two", rev_id="rev-K")
126
115
 
127
116
        #-------- end K -----------
128
117
 
129
 
        main_wt.merge_from_branch(b1)
130
 
        self.touch(main_wt, "b")
 
118
        self.touch("main/b")
 
119
        self.merge(b1, main_wt)
131
120
        # D gets some funky characters to make sure the unescaping works
132
121
        main_wt.commit("merge branch1, rev-12", rev_id="rev-<D>")
133
122
 
134
123
        # end D
135
124
 
136
 
        main_wt.merge_from_branch(branch2_branch)
 
125
        self.merge(branch2_branch, main_wt)
137
126
        main_wt.commit("merge branch1, rev-22",  rev_id="rev-G")
138
127
 
139
128
        # end G
140
129
        self.branch = main_branch
141
130
 
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)
 
131
 
 
132
    def test_fileid_involved_all_revs(self):
 
133
 
 
134
        l = self.branch.fileid_involved( )
 
135
        self.assertEquals( sorted(map( lambda x: x[0], l )), ["a","b","c","d"])
 
136
 
 
137
    def test_fileid_involved_one_rev(self):
 
138
 
 
139
        l = self.branch.fileid_involved("rev-B" )
 
140
        self.assertEquals( sorted(map( lambda x: x[0], l )), ["a","b","c"])
 
141
 
 
142
    def test_fileid_involved_two_revs(self):
 
143
 
 
144
        l = self.branch.fileid_involved_between_revs("rev-B","rev-K" )
 
145
        self.assertEquals( sorted(map( lambda x: x[0], l )), ["b","c"])
 
146
 
 
147
        l = self.branch.fileid_involved_between_revs("rev-C","rev-<D>" )
 
148
        self.assertEquals( sorted(map( lambda x: x[0], l )), ["b","d"])
 
149
 
 
150
        l = self.branch.fileid_involved_between_revs("rev-C","rev-G" )
 
151
        self.assertEquals( sorted(map( lambda x: x[0], l )), ["b","c","d"])
 
152
 
 
153
        l = self.branch.fileid_involved_between_revs("rev-E","rev-G" )
 
154
        self.assertEquals( sorted(map( lambda x: x[0], l )), ["a", "b","c","d"])
 
155
 
 
156
    def test_fileid_involved_sets(self):
 
157
 
 
158
        l = self.branch.fileid_involved_by_set(set(["rev-B"]))
 
159
        self.assertEquals( sorted(map( lambda x: x[0], l )), ["a"])
 
160
 
 
161
        l = self.branch.fileid_involved_by_set(set(["rev-<D>"]))
 
162
        self.assertEquals( sorted(map( lambda x: x[0], l )), ["b"])
 
163
 
 
164
    def test_fileid_involved_compare(self):
 
165
 
 
166
        l1 = self.branch.fileid_involved_between_revs("rev-E", "rev-<D>")
 
167
        l2 = self.branch.fileid_involved_by_set(set(["rev-<D>","rev-F","rev-C","rev-B"]))
 
168
        self.assertEquals( l1, l2 )
 
169
 
 
170
        l1 = self.branch.fileid_involved_between_revs("rev-C", "rev-G")
 
171
        l2 = self.branch.fileid_involved_by_set(
 
172
            set(["rev-G","rev-<D>","rev-F","rev-K","rev-J"]))
 
173
        self.assertEquals( l1, l2 )
 
174
 
 
175
    def test_fileid_involved_full_compare(self):
 
176
        from bzrlib.tsort import topo_sort
211
177
        pp=[]
212
178
        history = self.branch.revision_history( )
213
179
 
217
183
            start_id = history[start]
218
184
            for end in range(start+1,len(history)):
219
185
                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())
 
186
                l1 = self.branch.fileid_involved_between_revs(start_id, end_id)
225
187
 
226
188
                l2 = self.compare_tree_fileids(self.branch, start_id, end_id)
227
189
                self.assertEquals(l1, l2)
228
190
 
229
191
 
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
192
class TestFileIdInvolvedSuperset(FileIdInvolvedBase):
260
193
 
261
194
    def setUp(self):
262
195
        super(TestFileIdInvolvedSuperset, self).setUp()
263
196
 
264
 
        self.branch = None
265
197
        main_wt = self.make_branch_and_tree('main')
266
198
        main_branch = main_wt.branch
267
199
        self.build_tree(["main/a","main/b","main/c"])
268
200
 
269
201
        main_wt.add(['a', 'b', 'c'], ['a-file-id-2006-01-01-abcd',
270
202
                                 '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)
 
203
                                 'c-funky<file-id> quiji%bo'])
 
204
        main_wt.commit("Commit one", rev_id="rev-A")
 
205
 
 
206
        branch2_branch = main_branch.clone("branch2")
 
207
        os.chmod("branch2/b",0770)
 
208
        branch2_branch.working_tree().commit("branch2, Commit one", 
 
209
                                             rev_id="rev-J")
 
210
 
 
211
        self.merge(branch2_branch, main_wt)
 
212
        os.chmod("main/b",0660)
293
213
        main_wt.commit("merge branch1, rev-22",  rev_id="rev-G")
294
214
 
295
215
        # end G
296
216
        self.branch = main_branch
297
217
 
298
218
    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
219
        history = self.branch.revision_history()
305
220
        old_rev = history[0]
306
221
        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
222
 
310
 
        l1 = self.branch.repository.fileids_altered_by_revision_ids(
311
 
            new_revs.difference(old_revs))
312
 
        l1 = set(l1.keys())
 
223
        l1 = self.branch.fileid_involved_between_revs(old_rev, new_rev)
313
224
 
314
225
        l2 = self.compare_tree_fileids(self.branch, old_rev, new_rev)
315
226
        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()
 
227
        self.AssertSubset(l2, l1)