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