##// END OF EJS Templates
repoview: protect `base` computation from weird phase root...
Pierre-Yves David -
r18443:64848f7f default
parent child Browse files
Show More
@@ -1,194 +1,196 b''
1 1 # repoview.py - Filtered view of a localrepo object
2 2 #
3 3 # Copyright 2012 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
4 4 # Logilab SA <contact@logilab.fr>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 import copy
10 10 import phases
11 11 import util
12 12 import obsolete, bookmarks, revset
13 13
14 14
15 15 def hideablerevs(repo):
16 16 """Revisions candidates to be hidden
17 17
18 18 This is a standalone function to help extensions to wrap it."""
19 19 return obsolete.getrevs(repo, 'obsolete')
20 20
21 21 def computehidden(repo):
22 22 """compute the set of hidden revision to filter
23 23
24 24 During most operation hidden should be filtered."""
25 25 assert not repo.changelog.filteredrevs
26 26 hideable = hideablerevs(repo)
27 27 if hideable:
28 28 cl = repo.changelog
29 29 firsthideable = min(hideable)
30 30 revs = cl.revs(start=firsthideable)
31 31 blockers = [r for r in revset._children(repo, revs, hideable)
32 32 if r not in hideable]
33 33 for par in repo[None].parents():
34 34 blockers.append(par.rev())
35 35 for bm in bookmarks.listbookmarks(repo).values():
36 36 blockers.append(repo[bm].rev())
37 37 blocked = cl.ancestors(blockers, inclusive=True)
38 38 return frozenset(r for r in hideable if r not in blocked)
39 39 return frozenset()
40 40
41 41 def computeunserved(repo):
42 42 """compute the set of revision that should be filtered when used a server
43 43
44 44 Secret and hidden changeset should not pretend to be here."""
45 45 assert not repo.changelog.filteredrevs
46 46 # fast path in simple case to avoid impact of non optimised code
47 47 hiddens = filterrevs(repo, 'visible')
48 48 if phases.hassecret(repo):
49 49 cl = repo.changelog
50 50 secret = phases.secret
51 51 getphase = repo._phasecache.phase
52 52 first = min(cl.rev(n) for n in repo._phasecache.phaseroots[secret])
53 53 revs = cl.revs(start=first)
54 54 secrets = set(r for r in revs if getphase(repo, r) >= secret)
55 55 return frozenset(hiddens | secrets)
56 56 else:
57 57 return hiddens
58 58 return frozenset()
59 59
60 60 def computemutable(repo):
61 61 """compute the set of revision that should be filtered when used a server
62 62
63 63 Secret and hidden changeset should not pretend to be here."""
64 64 assert not repo.changelog.filteredrevs
65 65 # fast check to avoid revset call on huge repo
66 66 if util.any(repo._phasecache.phaseroots[1:]):
67 67 getphase = repo._phasecache.phase
68 68 maymutable = filterrevs(repo, 'base')
69 69 return frozenset(r for r in maymutable if getphase(repo, r))
70 70 return frozenset()
71 71
72 72 def computeimpactable(repo):
73 73 """Everything impactable by mutable revision
74 74
75 75 The mutable filter still have some chance to get invalidated. This will
76 76 happen when:
77 77
78 78 - you garbage collect hidden changeset,
79 79 - public phase is moved backward,
80 80 - something is changed in the filtering (this could be fixed)
81 81
82 82 This filter out any mutable changeset and any public changeset that may be
83 83 impacted by something happening to a mutable revision.
84 84
85 85 This is achieved by filtered everything with a revision number egal or
86 86 higher than the first mutable changeset is filtered."""
87 87 assert not repo.changelog.filteredrevs
88 88 cl = repo.changelog
89 89 firstmutable = len(cl)
90 90 for roots in repo._phasecache.phaseroots[1:]:
91 91 if roots:
92 92 firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
93 # protect from nullrev root
94 firstmutable = max(0, firstmutable)
93 95 return frozenset(xrange(firstmutable, len(cl)))
94 96
95 97 # function to compute filtered set
96 98 filtertable = {'visible': computehidden,
97 99 'served': computeunserved,
98 100 'immutable': computemutable,
99 101 'base': computeimpactable}
100 102 ### Nearest subset relation
101 103 # Nearest subset of filter X is a filter Y so that:
102 104 # * Y is included in X,
103 105 # * X - Y is as small as possible.
104 106 # This create and ordering used for branchmap purpose.
105 107 # the ordering may be partial
106 108 subsettable = {None: 'visible',
107 109 'visible': 'served',
108 110 'served': 'immutable',
109 111 'immutable': 'base'}
110 112
111 113 def filterrevs(repo, filtername):
112 114 """returns set of filtered revision for this filter name"""
113 115 if filtername not in repo.filteredrevcache:
114 116 func = filtertable[filtername]
115 117 repo.filteredrevcache[filtername] = func(repo.unfiltered())
116 118 return repo.filteredrevcache[filtername]
117 119
118 120 class repoview(object):
119 121 """Provide a read/write view of a repo through a filtered changelog
120 122
121 123 This object is used to access a filtered version of a repository without
122 124 altering the original repository object itself. We can not alter the
123 125 original object for two main reasons:
124 126 - It prevents the use of a repo with multiple filters at the same time. In
125 127 particular when multiple threads are involved.
126 128 - It makes scope of the filtering harder to control.
127 129
128 130 This object behaves very closely to the original repository. All attribute
129 131 operations are done on the original repository:
130 132 - An access to `repoview.someattr` actually returns `repo.someattr`,
131 133 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
132 134 - A deletion of `repoview.someattr` actually drops `someattr`
133 135 from `repo.__dict__`.
134 136
135 137 The only exception is the `changelog` property. It is overridden to return
136 138 a (surface) copy of `repo.changelog` with some revisions filtered. The
137 139 `filtername` attribute of the view control the revisions that need to be
138 140 filtered. (the fact the changelog is copied is an implementation detail).
139 141
140 142 Unlike attributes, this object intercepts all method calls. This means that
141 143 all methods are run on the `repoview` object with the filtered `changelog`
142 144 property. For this purpose the simple `repoview` class must be mixed with
143 145 the actual class of the repository. This ensures that the resulting
144 146 `repoview` object have the very same methods than the repo object. This
145 147 leads to the property below.
146 148
147 149 repoview.method() --> repo.__class__.method(repoview)
148 150
149 151 The inheritance has to be done dynamically because `repo` can be of any
150 152 subclasses of `localrepo`. Eg: `bundlerepo` or `httprepo`.
151 153 """
152 154
153 155 def __init__(self, repo, filtername):
154 156 object.__setattr__(self, '_unfilteredrepo', repo)
155 157 object.__setattr__(self, 'filtername', filtername)
156 158
157 159 # not a cacheproperty on purpose we shall implement a proper cache later
158 160 @property
159 161 def changelog(self):
160 162 """return a filtered version of the changeset
161 163
162 164 this changelog must not be used for writing"""
163 165 # some cache may be implemented later
164 166 cl = copy.copy(self._unfilteredrepo.changelog)
165 167 cl.filteredrevs = filterrevs(self._unfilteredrepo, self.filtername)
166 168 return cl
167 169
168 170 def unfiltered(self):
169 171 """Return an unfiltered version of a repo"""
170 172 return self._unfilteredrepo
171 173
172 174 def filtered(self, name):
173 175 """Return a filtered version of a repository"""
174 176 if name == self.filtername:
175 177 return self
176 178 return self.unfiltered().filtered(name)
177 179
178 180 # everything access are forwarded to the proxied repo
179 181 def __getattr__(self, attr):
180 182 return getattr(self._unfilteredrepo, attr)
181 183
182 184 def __setattr__(self, attr, value):
183 185 return setattr(self._unfilteredrepo, attr, value)
184 186
185 187 def __delattr__(self, attr):
186 188 return delattr(self._unfilteredrepo, attr)
187 189
188 190 # The `requirement` attribut is initialiazed during __init__. But
189 191 # __getattr__ won't be called as it also exists on the class. We need
190 192 # explicit forwarding to main repo here
191 193 @property
192 194 def requirements(self):
193 195 return self._unfilteredrepo.requirements
194 196
General Comments 0
You need to be logged in to leave comments. Login now