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