##// END OF EJS Templates
repoview: use repo.revs() instead of a private revset method...
Augie Fackler -
r20405:cb63aa14 default
parent child Browse files
Show More
@@ -1,212 +1,213 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 import obsolete, revset
12 import obsolete
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 blockers = [r for r in revset._children(repo, revs, hideable)
32 if r not in hideable]
31 tofilter = repo.revs(
32 '(%ld) and children(%ld)', list(revs), list(hideable))
33 blockers = [r for r in tofilter if r not in hideable]
33 34 for par in repo[None].parents():
34 35 blockers.append(par.rev())
35 36 for bm in repo._bookmarks.values():
36 37 blockers.append(repo[bm].rev())
37 38 blocked = cl.ancestors(blockers, inclusive=True)
38 39 return frozenset(r for r in hideable if r not in blocked)
39 40 return frozenset()
40 41
41 42 def computeunserved(repo):
42 43 """compute the set of revision that should be filtered when used a server
43 44
44 45 Secret and hidden changeset should not pretend to be here."""
45 46 assert not repo.changelog.filteredrevs
46 47 # fast path in simple case to avoid impact of non optimised code
47 48 hiddens = filterrevs(repo, 'visible')
48 49 if phases.hassecret(repo):
49 50 cl = repo.changelog
50 51 secret = phases.secret
51 52 getphase = repo._phasecache.phase
52 53 first = min(cl.rev(n) for n in repo._phasecache.phaseroots[secret])
53 54 revs = cl.revs(start=first)
54 55 secrets = set(r for r in revs if getphase(repo, r) >= secret)
55 56 return frozenset(hiddens | secrets)
56 57 else:
57 58 return hiddens
58 59
59 60 def computemutable(repo):
60 61 """compute the set of revision that should be filtered when used a server
61 62
62 63 Secret and hidden changeset should not pretend to be here."""
63 64 assert not repo.changelog.filteredrevs
64 65 # fast check to avoid revset call on huge repo
65 66 if util.any(repo._phasecache.phaseroots[1:]):
66 67 getphase = repo._phasecache.phase
67 68 maymutable = filterrevs(repo, 'base')
68 69 return frozenset(r for r in maymutable if getphase(repo, r))
69 70 return frozenset()
70 71
71 72 def computeimpactable(repo):
72 73 """Everything impactable by mutable revision
73 74
74 75 The immutable filter still have some chance to get invalidated. This will
75 76 happen when:
76 77
77 78 - you garbage collect hidden changeset,
78 79 - public phase is moved backward,
79 80 - something is changed in the filtering (this could be fixed)
80 81
81 82 This filter out any mutable changeset and any public changeset that may be
82 83 impacted by something happening to a mutable revision.
83 84
84 85 This is achieved by filtered everything with a revision number egal or
85 86 higher than the first mutable changeset is filtered."""
86 87 assert not repo.changelog.filteredrevs
87 88 cl = repo.changelog
88 89 firstmutable = len(cl)
89 90 for roots in repo._phasecache.phaseroots[1:]:
90 91 if roots:
91 92 firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
92 93 # protect from nullrev root
93 94 firstmutable = max(0, firstmutable)
94 95 return frozenset(xrange(firstmutable, len(cl)))
95 96
96 97 # function to compute filtered set
97 98 #
98 99 # When addding a new filter you MUST update the table at:
99 100 # mercurial.branchmap.subsettable
100 101 # Otherwise your filter will have to recompute all its branches cache
101 102 # from scratch (very slow).
102 103 filtertable = {'visible': computehidden,
103 104 'served': computeunserved,
104 105 'immutable': computemutable,
105 106 'base': computeimpactable}
106 107
107 108 def filterrevs(repo, filtername):
108 109 """returns set of filtered revision for this filter name"""
109 110 if filtername not in repo.filteredrevcache:
110 111 func = filtertable[filtername]
111 112 repo.filteredrevcache[filtername] = func(repo.unfiltered())
112 113 return repo.filteredrevcache[filtername]
113 114
114 115 class repoview(object):
115 116 """Provide a read/write view of a repo through a filtered changelog
116 117
117 118 This object is used to access a filtered version of a repository without
118 119 altering the original repository object itself. We can not alter the
119 120 original object for two main reasons:
120 121 - It prevents the use of a repo with multiple filters at the same time. In
121 122 particular when multiple threads are involved.
122 123 - It makes scope of the filtering harder to control.
123 124
124 125 This object behaves very closely to the original repository. All attribute
125 126 operations are done on the original repository:
126 127 - An access to `repoview.someattr` actually returns `repo.someattr`,
127 128 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
128 129 - A deletion of `repoview.someattr` actually drops `someattr`
129 130 from `repo.__dict__`.
130 131
131 132 The only exception is the `changelog` property. It is overridden to return
132 133 a (surface) copy of `repo.changelog` with some revisions filtered. The
133 134 `filtername` attribute of the view control the revisions that need to be
134 135 filtered. (the fact the changelog is copied is an implementation detail).
135 136
136 137 Unlike attributes, this object intercepts all method calls. This means that
137 138 all methods are run on the `repoview` object with the filtered `changelog`
138 139 property. For this purpose the simple `repoview` class must be mixed with
139 140 the actual class of the repository. This ensures that the resulting
140 141 `repoview` object have the very same methods than the repo object. This
141 142 leads to the property below.
142 143
143 144 repoview.method() --> repo.__class__.method(repoview)
144 145
145 146 The inheritance has to be done dynamically because `repo` can be of any
146 147 subclasses of `localrepo`. Eg: `bundlerepo` or `statichttprepo`.
147 148 """
148 149
149 150 def __init__(self, repo, filtername):
150 151 object.__setattr__(self, '_unfilteredrepo', repo)
151 152 object.__setattr__(self, 'filtername', filtername)
152 153 object.__setattr__(self, '_clcachekey', None)
153 154 object.__setattr__(self, '_clcache', None)
154 155
155 156 # not a propertycache on purpose we shall implement a proper cache later
156 157 @property
157 158 def changelog(self):
158 159 """return a filtered version of the changeset
159 160
160 161 this changelog must not be used for writing"""
161 162 # some cache may be implemented later
162 163 unfi = self._unfilteredrepo
163 164 unfichangelog = unfi.changelog
164 165 revs = filterrevs(unfi, self.filtername)
165 166 cl = self._clcache
166 167 newkey = (len(unfichangelog), unfichangelog.tip(), hash(revs))
167 168 if cl is not None:
168 169 # we need to check curkey too for some obscure reason.
169 170 # MQ test show a corruption of the underlying repo (in _clcache)
170 171 # without change in the cachekey.
171 172 oldfilter = cl.filteredrevs
172 173 try:
173 174 cl.filterrevs = () # disable filtering for tip
174 175 curkey = (len(cl), cl.tip(), hash(oldfilter))
175 176 finally:
176 177 cl.filteredrevs = oldfilter
177 178 if newkey != self._clcachekey or newkey != curkey:
178 179 cl = None
179 180 # could have been made None by the previous if
180 181 if cl is None:
181 182 cl = copy.copy(unfichangelog)
182 183 cl.filteredrevs = revs
183 184 object.__setattr__(self, '_clcache', cl)
184 185 object.__setattr__(self, '_clcachekey', newkey)
185 186 return cl
186 187
187 188 def unfiltered(self):
188 189 """Return an unfiltered version of a repo"""
189 190 return self._unfilteredrepo
190 191
191 192 def filtered(self, name):
192 193 """Return a filtered version of a repository"""
193 194 if name == self.filtername:
194 195 return self
195 196 return self.unfiltered().filtered(name)
196 197
197 198 # everything access are forwarded to the proxied repo
198 199 def __getattr__(self, attr):
199 200 return getattr(self._unfilteredrepo, attr)
200 201
201 202 def __setattr__(self, attr, value):
202 203 return setattr(self._unfilteredrepo, attr, value)
203 204
204 205 def __delattr__(self, attr):
205 206 return delattr(self._unfilteredrepo, attr)
206 207
207 208 # The `requirements` attribute is initialized during __init__. But
208 209 # __getattr__ won't be called as it also exists on the class. We need
209 210 # explicit forwarding to main repo here
210 211 @property
211 212 def requirements(self):
212 213 return self._unfilteredrepo.requirements
@@ -1,42 +1,41 b''
1 1 This code uses the ast module, which was new in 2.6, so we'll skip
2 2 this test on anything earlier.
3 3 $ python -c 'import sys ; assert sys.version_info >= (2, 6)' || exit 80
4 4
5 5 $ import_checker="$TESTDIR"/../contrib/import-checker.py
6 6 Run the doctests from the import checker, and make sure
7 7 it's working correctly.
8 8 $ TERM=dumb
9 9 $ export TERM
10 10 $ python -m doctest $import_checker
11 11
12 12 $ cd "$TESTDIR"/..
13 13 $ if hg identify -q > /dev/null 2>&1; then :
14 14 > else
15 15 > echo "skipped: not a Mercurial working dir" >&2
16 16 > exit 80
17 17 > fi
18 18
19 19 There are a handful of cases here that require renaming a module so it
20 20 doesn't overlap with a stdlib module name. There are also some cycles
21 21 here that we should still endeavor to fix, and some cycles will be
22 22 hidden by deduplication algorithm in the cycle detector, so fixing
23 23 these may expose other cycles.
24 24
25 25 $ hg locate 'mercurial/**.py' | sed 's-\\-/-g' | xargs python "$import_checker"
26 26 mercurial/dispatch.py mixed imports
27 27 stdlib: commands
28 28 relative: error, extensions, fancyopts, hg, hook, util
29 29 mercurial/fileset.py mixed imports
30 30 stdlib: parser
31 31 relative: error, merge, util
32 32 mercurial/revset.py mixed imports
33 33 stdlib: parser
34 34 relative: discovery, error, hbisect, phases, util
35 35 mercurial/templater.py mixed imports
36 36 stdlib: parser
37 37 relative: config, error, templatefilters, util
38 38 mercurial/ui.py mixed imports
39 39 stdlib: formatter
40 40 relative: config, error, scmutil, util
41 Import cycle: mercurial.repoview -> mercurial.revset -> mercurial.repoview
42 41 Import cycle: mercurial.cmdutil -> mercurial.context -> mercurial.subrepo -> mercurial.cmdutil -> mercurial.cmdutil
General Comments 0
You need to be logged in to leave comments. Login now