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