##// END OF EJS Templates
repoview: only pin obsolete wdir parents while there are unresolved conflicts...
Matt Harbison -
r46383:341e014f stable
parent child Browse files
Show More
@@ -1,482 +1,482 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 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import copy
11 import copy
12 import weakref
12 import weakref
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 hex,
16 hex,
17 nullrev,
17 nullrev,
18 )
18 )
19 from .pycompat import (
19 from .pycompat import (
20 delattr,
20 delattr,
21 getattr,
21 getattr,
22 setattr,
22 setattr,
23 )
23 )
24 from . import (
24 from . import (
25 error,
25 error,
26 obsolete,
26 obsolete,
27 phases,
27 phases,
28 pycompat,
28 pycompat,
29 tags as tagsmod,
29 tags as tagsmod,
30 util,
30 util,
31 )
31 )
32 from .utils import repoviewutil
32 from .utils import repoviewutil
33
33
34
34
35 def hideablerevs(repo):
35 def hideablerevs(repo):
36 """Revision candidates to be hidden
36 """Revision candidates to be hidden
37
37
38 This is a standalone function to allow extensions to wrap it.
38 This is a standalone function to allow extensions to wrap it.
39
39
40 Because we use the set of immutable changesets as a fallback subset in
40 Because we use the set of immutable changesets as a fallback subset in
41 branchmap (see mercurial.utils.repoviewutils.subsettable), you cannot set
41 branchmap (see mercurial.utils.repoviewutils.subsettable), you cannot set
42 "public" changesets as "hideable". Doing so would break multiple code
42 "public" changesets as "hideable". Doing so would break multiple code
43 assertions and lead to crashes."""
43 assertions and lead to crashes."""
44 obsoletes = obsolete.getrevs(repo, b'obsolete')
44 obsoletes = obsolete.getrevs(repo, b'obsolete')
45 internals = repo._phasecache.getrevset(repo, phases.localhiddenphases)
45 internals = repo._phasecache.getrevset(repo, phases.localhiddenphases)
46 internals = frozenset(internals)
46 internals = frozenset(internals)
47 return obsoletes | internals
47 return obsoletes | internals
48
48
49
49
50 def pinnedrevs(repo):
50 def pinnedrevs(repo):
51 """revisions blocking hidden changesets from being filtered
51 """revisions blocking hidden changesets from being filtered
52 """
52 """
53
53
54 cl = repo.changelog
54 cl = repo.changelog
55 pinned = set()
55 pinned = set()
56 pinned.update([par.rev() for par in repo[None].parents()])
56 pinned.update([par.rev() for par in repo[None].parents()])
57 pinned.update([cl.rev(bm) for bm in repo._bookmarks.values()])
57 pinned.update([cl.rev(bm) for bm in repo._bookmarks.values()])
58
58
59 tags = {}
59 tags = {}
60 tagsmod.readlocaltags(repo.ui, repo, tags, {})
60 tagsmod.readlocaltags(repo.ui, repo, tags, {})
61 if tags:
61 if tags:
62 rev = cl.index.get_rev
62 rev = cl.index.get_rev
63 pinned.update(rev(t[0]) for t in tags.values())
63 pinned.update(rev(t[0]) for t in tags.values())
64 pinned.discard(None)
64 pinned.discard(None)
65
65
66 # Avoid cycle: mercurial.filemerge -> mercurial.templater ->
66 # Avoid cycle: mercurial.filemerge -> mercurial.templater ->
67 # mercurial.templatefuncs -> mercurial.revset -> mercurial.repoview ->
67 # mercurial.templatefuncs -> mercurial.revset -> mercurial.repoview ->
68 # mercurial.mergestate -> mercurial.filemerge
68 # mercurial.mergestate -> mercurial.filemerge
69 from . import mergestate
69 from . import mergestate
70
70
71 ms = mergestate.mergestate.read(repo)
71 ms = mergestate.mergestate.read(repo)
72 if ms.active():
72 if ms.active() and ms.unresolvedcount():
73 for node in (ms.local, ms.other):
73 for node in (ms.local, ms.other):
74 rev = cl.index.get_rev(node)
74 rev = cl.index.get_rev(node)
75 if rev is not None:
75 if rev is not None:
76 pinned.add(rev)
76 pinned.add(rev)
77
77
78 return pinned
78 return pinned
79
79
80
80
81 def _revealancestors(pfunc, hidden, revs):
81 def _revealancestors(pfunc, hidden, revs):
82 """reveals contiguous chains of hidden ancestors of 'revs' by removing them
82 """reveals contiguous chains of hidden ancestors of 'revs' by removing them
83 from 'hidden'
83 from 'hidden'
84
84
85 - pfunc(r): a funtion returning parent of 'r',
85 - pfunc(r): a funtion returning parent of 'r',
86 - hidden: the (preliminary) hidden revisions, to be updated
86 - hidden: the (preliminary) hidden revisions, to be updated
87 - revs: iterable of revnum,
87 - revs: iterable of revnum,
88
88
89 (Ancestors are revealed exclusively, i.e. the elements in 'revs' are
89 (Ancestors are revealed exclusively, i.e. the elements in 'revs' are
90 *not* revealed)
90 *not* revealed)
91 """
91 """
92 stack = list(revs)
92 stack = list(revs)
93 while stack:
93 while stack:
94 for p in pfunc(stack.pop()):
94 for p in pfunc(stack.pop()):
95 if p != nullrev and p in hidden:
95 if p != nullrev and p in hidden:
96 hidden.remove(p)
96 hidden.remove(p)
97 stack.append(p)
97 stack.append(p)
98
98
99
99
100 def computehidden(repo, visibilityexceptions=None):
100 def computehidden(repo, visibilityexceptions=None):
101 """compute the set of hidden revision to filter
101 """compute the set of hidden revision to filter
102
102
103 During most operation hidden should be filtered."""
103 During most operation hidden should be filtered."""
104 assert not repo.changelog.filteredrevs
104 assert not repo.changelog.filteredrevs
105
105
106 hidden = hideablerevs(repo)
106 hidden = hideablerevs(repo)
107 if hidden:
107 if hidden:
108 hidden = set(hidden - pinnedrevs(repo))
108 hidden = set(hidden - pinnedrevs(repo))
109 if visibilityexceptions:
109 if visibilityexceptions:
110 hidden -= visibilityexceptions
110 hidden -= visibilityexceptions
111 pfunc = repo.changelog.parentrevs
111 pfunc = repo.changelog.parentrevs
112 mutable = repo._phasecache.getrevset(repo, phases.mutablephases)
112 mutable = repo._phasecache.getrevset(repo, phases.mutablephases)
113
113
114 visible = mutable - hidden
114 visible = mutable - hidden
115 _revealancestors(pfunc, hidden, visible)
115 _revealancestors(pfunc, hidden, visible)
116 return frozenset(hidden)
116 return frozenset(hidden)
117
117
118
118
119 def computesecret(repo, visibilityexceptions=None):
119 def computesecret(repo, visibilityexceptions=None):
120 """compute the set of revision that can never be exposed through hgweb
120 """compute the set of revision that can never be exposed through hgweb
121
121
122 Changeset in the secret phase (or above) should stay unaccessible."""
122 Changeset in the secret phase (or above) should stay unaccessible."""
123 assert not repo.changelog.filteredrevs
123 assert not repo.changelog.filteredrevs
124 secrets = repo._phasecache.getrevset(repo, phases.remotehiddenphases)
124 secrets = repo._phasecache.getrevset(repo, phases.remotehiddenphases)
125 return frozenset(secrets)
125 return frozenset(secrets)
126
126
127
127
128 def computeunserved(repo, visibilityexceptions=None):
128 def computeunserved(repo, visibilityexceptions=None):
129 """compute the set of revision that should be filtered when used a server
129 """compute the set of revision that should be filtered when used a server
130
130
131 Secret and hidden changeset should not pretend to be here."""
131 Secret and hidden changeset should not pretend to be here."""
132 assert not repo.changelog.filteredrevs
132 assert not repo.changelog.filteredrevs
133 # fast path in simple case to avoid impact of non optimised code
133 # fast path in simple case to avoid impact of non optimised code
134 hiddens = filterrevs(repo, b'visible')
134 hiddens = filterrevs(repo, b'visible')
135 secrets = filterrevs(repo, b'served.hidden')
135 secrets = filterrevs(repo, b'served.hidden')
136 if secrets:
136 if secrets:
137 return frozenset(hiddens | secrets)
137 return frozenset(hiddens | secrets)
138 else:
138 else:
139 return hiddens
139 return hiddens
140
140
141
141
142 def computemutable(repo, visibilityexceptions=None):
142 def computemutable(repo, visibilityexceptions=None):
143 assert not repo.changelog.filteredrevs
143 assert not repo.changelog.filteredrevs
144 # fast check to avoid revset call on huge repo
144 # fast check to avoid revset call on huge repo
145 if repo._phasecache.hasnonpublicphases(repo):
145 if repo._phasecache.hasnonpublicphases(repo):
146 return frozenset(repo._phasecache.getrevset(repo, phases.mutablephases))
146 return frozenset(repo._phasecache.getrevset(repo, phases.mutablephases))
147 return frozenset()
147 return frozenset()
148
148
149
149
150 def computeimpactable(repo, visibilityexceptions=None):
150 def computeimpactable(repo, visibilityexceptions=None):
151 """Everything impactable by mutable revision
151 """Everything impactable by mutable revision
152
152
153 The immutable filter still have some chance to get invalidated. This will
153 The immutable filter still have some chance to get invalidated. This will
154 happen when:
154 happen when:
155
155
156 - you garbage collect hidden changeset,
156 - you garbage collect hidden changeset,
157 - public phase is moved backward,
157 - public phase is moved backward,
158 - something is changed in the filtering (this could be fixed)
158 - something is changed in the filtering (this could be fixed)
159
159
160 This filter out any mutable changeset and any public changeset that may be
160 This filter out any mutable changeset and any public changeset that may be
161 impacted by something happening to a mutable revision.
161 impacted by something happening to a mutable revision.
162
162
163 This is achieved by filtered everything with a revision number egal or
163 This is achieved by filtered everything with a revision number egal or
164 higher than the first mutable changeset is filtered."""
164 higher than the first mutable changeset is filtered."""
165 assert not repo.changelog.filteredrevs
165 assert not repo.changelog.filteredrevs
166 cl = repo.changelog
166 cl = repo.changelog
167 firstmutable = len(cl)
167 firstmutable = len(cl)
168 roots = repo._phasecache.nonpublicphaseroots(repo)
168 roots = repo._phasecache.nonpublicphaseroots(repo)
169 if roots:
169 if roots:
170 firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
170 firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
171 # protect from nullrev root
171 # protect from nullrev root
172 firstmutable = max(0, firstmutable)
172 firstmutable = max(0, firstmutable)
173 return frozenset(pycompat.xrange(firstmutable, len(cl)))
173 return frozenset(pycompat.xrange(firstmutable, len(cl)))
174
174
175
175
176 # function to compute filtered set
176 # function to compute filtered set
177 #
177 #
178 # When adding a new filter you MUST update the table at:
178 # When adding a new filter you MUST update the table at:
179 # mercurial.utils.repoviewutil.subsettable
179 # mercurial.utils.repoviewutil.subsettable
180 # Otherwise your filter will have to recompute all its branches cache
180 # Otherwise your filter will have to recompute all its branches cache
181 # from scratch (very slow).
181 # from scratch (very slow).
182 filtertable = {
182 filtertable = {
183 b'visible': computehidden,
183 b'visible': computehidden,
184 b'visible-hidden': computehidden,
184 b'visible-hidden': computehidden,
185 b'served.hidden': computesecret,
185 b'served.hidden': computesecret,
186 b'served': computeunserved,
186 b'served': computeunserved,
187 b'immutable': computemutable,
187 b'immutable': computemutable,
188 b'base': computeimpactable,
188 b'base': computeimpactable,
189 }
189 }
190
190
191 # set of filter level that will include the working copy parent no matter what.
191 # set of filter level that will include the working copy parent no matter what.
192 filter_has_wc = {b'visible', b'visible-hidden'}
192 filter_has_wc = {b'visible', b'visible-hidden'}
193
193
194 _basefiltername = list(filtertable)
194 _basefiltername = list(filtertable)
195
195
196
196
197 def extrafilter(ui):
197 def extrafilter(ui):
198 """initialize extra filter and return its id
198 """initialize extra filter and return its id
199
199
200 If extra filtering is configured, we make sure the associated filtered view
200 If extra filtering is configured, we make sure the associated filtered view
201 are declared and return the associated id.
201 are declared and return the associated id.
202 """
202 """
203 frevs = ui.config(b'experimental', b'extra-filter-revs')
203 frevs = ui.config(b'experimental', b'extra-filter-revs')
204 if frevs is None:
204 if frevs is None:
205 return None
205 return None
206
206
207 fid = pycompat.sysbytes(util.DIGESTS[b'sha1'](frevs).hexdigest())[:12]
207 fid = pycompat.sysbytes(util.DIGESTS[b'sha1'](frevs).hexdigest())[:12]
208
208
209 combine = lambda fname: fname + b'%' + fid
209 combine = lambda fname: fname + b'%' + fid
210
210
211 subsettable = repoviewutil.subsettable
211 subsettable = repoviewutil.subsettable
212
212
213 if combine(b'base') not in filtertable:
213 if combine(b'base') not in filtertable:
214 for name in _basefiltername:
214 for name in _basefiltername:
215
215
216 def extrafilteredrevs(repo, *args, **kwargs):
216 def extrafilteredrevs(repo, *args, **kwargs):
217 baserevs = filtertable[name](repo, *args, **kwargs)
217 baserevs = filtertable[name](repo, *args, **kwargs)
218 extrarevs = frozenset(repo.revs(frevs))
218 extrarevs = frozenset(repo.revs(frevs))
219 return baserevs | extrarevs
219 return baserevs | extrarevs
220
220
221 filtertable[combine(name)] = extrafilteredrevs
221 filtertable[combine(name)] = extrafilteredrevs
222 if name in subsettable:
222 if name in subsettable:
223 subsettable[combine(name)] = combine(subsettable[name])
223 subsettable[combine(name)] = combine(subsettable[name])
224 return fid
224 return fid
225
225
226
226
227 def filterrevs(repo, filtername, visibilityexceptions=None):
227 def filterrevs(repo, filtername, visibilityexceptions=None):
228 """returns set of filtered revision for this filter name
228 """returns set of filtered revision for this filter name
229
229
230 visibilityexceptions is a set of revs which must are exceptions for
230 visibilityexceptions is a set of revs which must are exceptions for
231 hidden-state and must be visible. They are dynamic and hence we should not
231 hidden-state and must be visible. They are dynamic and hence we should not
232 cache it's result"""
232 cache it's result"""
233 if filtername not in repo.filteredrevcache:
233 if filtername not in repo.filteredrevcache:
234 if repo.ui.configbool(b'devel', b'debug.repo-filters'):
234 if repo.ui.configbool(b'devel', b'debug.repo-filters'):
235 msg = b'computing revision filter for "%s"'
235 msg = b'computing revision filter for "%s"'
236 msg %= filtername
236 msg %= filtername
237 if repo.ui.tracebackflag and repo.ui.debugflag:
237 if repo.ui.tracebackflag and repo.ui.debugflag:
238 # XXX use ui.write_err
238 # XXX use ui.write_err
239 util.debugstacktrace(
239 util.debugstacktrace(
240 msg,
240 msg,
241 f=repo.ui._fout,
241 f=repo.ui._fout,
242 otherf=repo.ui._ferr,
242 otherf=repo.ui._ferr,
243 prefix=b'debug.filters: ',
243 prefix=b'debug.filters: ',
244 )
244 )
245 else:
245 else:
246 repo.ui.debug(b'debug.filters: %s\n' % msg)
246 repo.ui.debug(b'debug.filters: %s\n' % msg)
247 func = filtertable[filtername]
247 func = filtertable[filtername]
248 if visibilityexceptions:
248 if visibilityexceptions:
249 return func(repo.unfiltered, visibilityexceptions)
249 return func(repo.unfiltered, visibilityexceptions)
250 repo.filteredrevcache[filtername] = func(repo.unfiltered())
250 repo.filteredrevcache[filtername] = func(repo.unfiltered())
251 return repo.filteredrevcache[filtername]
251 return repo.filteredrevcache[filtername]
252
252
253
253
254 def wrapchangelog(unfichangelog, filteredrevs):
254 def wrapchangelog(unfichangelog, filteredrevs):
255 cl = copy.copy(unfichangelog)
255 cl = copy.copy(unfichangelog)
256 cl.filteredrevs = filteredrevs
256 cl.filteredrevs = filteredrevs
257
257
258 class filteredchangelog(filteredchangelogmixin, cl.__class__):
258 class filteredchangelog(filteredchangelogmixin, cl.__class__):
259 pass
259 pass
260
260
261 cl.__class__ = filteredchangelog
261 cl.__class__ = filteredchangelog
262
262
263 return cl
263 return cl
264
264
265
265
266 class filteredchangelogmixin(object):
266 class filteredchangelogmixin(object):
267 def tiprev(self):
267 def tiprev(self):
268 """filtered version of revlog.tiprev"""
268 """filtered version of revlog.tiprev"""
269 for i in pycompat.xrange(len(self) - 1, -2, -1):
269 for i in pycompat.xrange(len(self) - 1, -2, -1):
270 if i not in self.filteredrevs:
270 if i not in self.filteredrevs:
271 return i
271 return i
272
272
273 def __contains__(self, rev):
273 def __contains__(self, rev):
274 """filtered version of revlog.__contains__"""
274 """filtered version of revlog.__contains__"""
275 return 0 <= rev < len(self) and rev not in self.filteredrevs
275 return 0 <= rev < len(self) and rev not in self.filteredrevs
276
276
277 def __iter__(self):
277 def __iter__(self):
278 """filtered version of revlog.__iter__"""
278 """filtered version of revlog.__iter__"""
279
279
280 def filterediter():
280 def filterediter():
281 for i in pycompat.xrange(len(self)):
281 for i in pycompat.xrange(len(self)):
282 if i not in self.filteredrevs:
282 if i not in self.filteredrevs:
283 yield i
283 yield i
284
284
285 return filterediter()
285 return filterediter()
286
286
287 def revs(self, start=0, stop=None):
287 def revs(self, start=0, stop=None):
288 """filtered version of revlog.revs"""
288 """filtered version of revlog.revs"""
289 for i in super(filteredchangelogmixin, self).revs(start, stop):
289 for i in super(filteredchangelogmixin, self).revs(start, stop):
290 if i not in self.filteredrevs:
290 if i not in self.filteredrevs:
291 yield i
291 yield i
292
292
293 def _checknofilteredinrevs(self, revs):
293 def _checknofilteredinrevs(self, revs):
294 """raise the appropriate error if 'revs' contains a filtered revision
294 """raise the appropriate error if 'revs' contains a filtered revision
295
295
296 This returns a version of 'revs' to be used thereafter by the caller.
296 This returns a version of 'revs' to be used thereafter by the caller.
297 In particular, if revs is an iterator, it is converted into a set.
297 In particular, if revs is an iterator, it is converted into a set.
298 """
298 """
299 safehasattr = util.safehasattr
299 safehasattr = util.safehasattr
300 if safehasattr(revs, '__next__'):
300 if safehasattr(revs, '__next__'):
301 # Note that inspect.isgenerator() is not true for iterators,
301 # Note that inspect.isgenerator() is not true for iterators,
302 revs = set(revs)
302 revs = set(revs)
303
303
304 filteredrevs = self.filteredrevs
304 filteredrevs = self.filteredrevs
305 if safehasattr(revs, 'first'): # smartset
305 if safehasattr(revs, 'first'): # smartset
306 offenders = revs & filteredrevs
306 offenders = revs & filteredrevs
307 else:
307 else:
308 offenders = filteredrevs.intersection(revs)
308 offenders = filteredrevs.intersection(revs)
309
309
310 for rev in offenders:
310 for rev in offenders:
311 raise error.FilteredIndexError(rev)
311 raise error.FilteredIndexError(rev)
312 return revs
312 return revs
313
313
314 def headrevs(self, revs=None):
314 def headrevs(self, revs=None):
315 if revs is None:
315 if revs is None:
316 try:
316 try:
317 return self.index.headrevsfiltered(self.filteredrevs)
317 return self.index.headrevsfiltered(self.filteredrevs)
318 # AttributeError covers non-c-extension environments and
318 # AttributeError covers non-c-extension environments and
319 # old c extensions without filter handling.
319 # old c extensions without filter handling.
320 except AttributeError:
320 except AttributeError:
321 return self._headrevs()
321 return self._headrevs()
322
322
323 revs = self._checknofilteredinrevs(revs)
323 revs = self._checknofilteredinrevs(revs)
324 return super(filteredchangelogmixin, self).headrevs(revs)
324 return super(filteredchangelogmixin, self).headrevs(revs)
325
325
326 def strip(self, *args, **kwargs):
326 def strip(self, *args, **kwargs):
327 # XXX make something better than assert
327 # XXX make something better than assert
328 # We can't expect proper strip behavior if we are filtered.
328 # We can't expect proper strip behavior if we are filtered.
329 assert not self.filteredrevs
329 assert not self.filteredrevs
330 super(filteredchangelogmixin, self).strip(*args, **kwargs)
330 super(filteredchangelogmixin, self).strip(*args, **kwargs)
331
331
332 def rev(self, node):
332 def rev(self, node):
333 """filtered version of revlog.rev"""
333 """filtered version of revlog.rev"""
334 r = super(filteredchangelogmixin, self).rev(node)
334 r = super(filteredchangelogmixin, self).rev(node)
335 if r in self.filteredrevs:
335 if r in self.filteredrevs:
336 raise error.FilteredLookupError(
336 raise error.FilteredLookupError(
337 hex(node), self.indexfile, _(b'filtered node')
337 hex(node), self.indexfile, _(b'filtered node')
338 )
338 )
339 return r
339 return r
340
340
341 def node(self, rev):
341 def node(self, rev):
342 """filtered version of revlog.node"""
342 """filtered version of revlog.node"""
343 if rev in self.filteredrevs:
343 if rev in self.filteredrevs:
344 raise error.FilteredIndexError(rev)
344 raise error.FilteredIndexError(rev)
345 return super(filteredchangelogmixin, self).node(rev)
345 return super(filteredchangelogmixin, self).node(rev)
346
346
347 def linkrev(self, rev):
347 def linkrev(self, rev):
348 """filtered version of revlog.linkrev"""
348 """filtered version of revlog.linkrev"""
349 if rev in self.filteredrevs:
349 if rev in self.filteredrevs:
350 raise error.FilteredIndexError(rev)
350 raise error.FilteredIndexError(rev)
351 return super(filteredchangelogmixin, self).linkrev(rev)
351 return super(filteredchangelogmixin, self).linkrev(rev)
352
352
353 def parentrevs(self, rev):
353 def parentrevs(self, rev):
354 """filtered version of revlog.parentrevs"""
354 """filtered version of revlog.parentrevs"""
355 if rev in self.filteredrevs:
355 if rev in self.filteredrevs:
356 raise error.FilteredIndexError(rev)
356 raise error.FilteredIndexError(rev)
357 return super(filteredchangelogmixin, self).parentrevs(rev)
357 return super(filteredchangelogmixin, self).parentrevs(rev)
358
358
359 def flags(self, rev):
359 def flags(self, rev):
360 """filtered version of revlog.flags"""
360 """filtered version of revlog.flags"""
361 if rev in self.filteredrevs:
361 if rev in self.filteredrevs:
362 raise error.FilteredIndexError(rev)
362 raise error.FilteredIndexError(rev)
363 return super(filteredchangelogmixin, self).flags(rev)
363 return super(filteredchangelogmixin, self).flags(rev)
364
364
365
365
366 class repoview(object):
366 class repoview(object):
367 """Provide a read/write view of a repo through a filtered changelog
367 """Provide a read/write view of a repo through a filtered changelog
368
368
369 This object is used to access a filtered version of a repository without
369 This object is used to access a filtered version of a repository without
370 altering the original repository object itself. We can not alter the
370 altering the original repository object itself. We can not alter the
371 original object for two main reasons:
371 original object for two main reasons:
372 - It prevents the use of a repo with multiple filters at the same time. In
372 - It prevents the use of a repo with multiple filters at the same time. In
373 particular when multiple threads are involved.
373 particular when multiple threads are involved.
374 - It makes scope of the filtering harder to control.
374 - It makes scope of the filtering harder to control.
375
375
376 This object behaves very closely to the original repository. All attribute
376 This object behaves very closely to the original repository. All attribute
377 operations are done on the original repository:
377 operations are done on the original repository:
378 - An access to `repoview.someattr` actually returns `repo.someattr`,
378 - An access to `repoview.someattr` actually returns `repo.someattr`,
379 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
379 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
380 - A deletion of `repoview.someattr` actually drops `someattr`
380 - A deletion of `repoview.someattr` actually drops `someattr`
381 from `repo.__dict__`.
381 from `repo.__dict__`.
382
382
383 The only exception is the `changelog` property. It is overridden to return
383 The only exception is the `changelog` property. It is overridden to return
384 a (surface) copy of `repo.changelog` with some revisions filtered. The
384 a (surface) copy of `repo.changelog` with some revisions filtered. The
385 `filtername` attribute of the view control the revisions that need to be
385 `filtername` attribute of the view control the revisions that need to be
386 filtered. (the fact the changelog is copied is an implementation detail).
386 filtered. (the fact the changelog is copied is an implementation detail).
387
387
388 Unlike attributes, this object intercepts all method calls. This means that
388 Unlike attributes, this object intercepts all method calls. This means that
389 all methods are run on the `repoview` object with the filtered `changelog`
389 all methods are run on the `repoview` object with the filtered `changelog`
390 property. For this purpose the simple `repoview` class must be mixed with
390 property. For this purpose the simple `repoview` class must be mixed with
391 the actual class of the repository. This ensures that the resulting
391 the actual class of the repository. This ensures that the resulting
392 `repoview` object have the very same methods than the repo object. This
392 `repoview` object have the very same methods than the repo object. This
393 leads to the property below.
393 leads to the property below.
394
394
395 repoview.method() --> repo.__class__.method(repoview)
395 repoview.method() --> repo.__class__.method(repoview)
396
396
397 The inheritance has to be done dynamically because `repo` can be of any
397 The inheritance has to be done dynamically because `repo` can be of any
398 subclasses of `localrepo`. Eg: `bundlerepo` or `statichttprepo`.
398 subclasses of `localrepo`. Eg: `bundlerepo` or `statichttprepo`.
399 """
399 """
400
400
401 def __init__(self, repo, filtername, visibilityexceptions=None):
401 def __init__(self, repo, filtername, visibilityexceptions=None):
402 object.__setattr__(self, '_unfilteredrepo', repo)
402 object.__setattr__(self, '_unfilteredrepo', repo)
403 object.__setattr__(self, 'filtername', filtername)
403 object.__setattr__(self, 'filtername', filtername)
404 object.__setattr__(self, '_clcachekey', None)
404 object.__setattr__(self, '_clcachekey', None)
405 object.__setattr__(self, '_clcache', None)
405 object.__setattr__(self, '_clcache', None)
406 # revs which are exceptions and must not be hidden
406 # revs which are exceptions and must not be hidden
407 object.__setattr__(self, '_visibilityexceptions', visibilityexceptions)
407 object.__setattr__(self, '_visibilityexceptions', visibilityexceptions)
408
408
409 # not a propertycache on purpose we shall implement a proper cache later
409 # not a propertycache on purpose we shall implement a proper cache later
410 @property
410 @property
411 def changelog(self):
411 def changelog(self):
412 """return a filtered version of the changeset
412 """return a filtered version of the changeset
413
413
414 this changelog must not be used for writing"""
414 this changelog must not be used for writing"""
415 # some cache may be implemented later
415 # some cache may be implemented later
416 unfi = self._unfilteredrepo
416 unfi = self._unfilteredrepo
417 unfichangelog = unfi.changelog
417 unfichangelog = unfi.changelog
418 # bypass call to changelog.method
418 # bypass call to changelog.method
419 unfiindex = unfichangelog.index
419 unfiindex = unfichangelog.index
420 unfilen = len(unfiindex)
420 unfilen = len(unfiindex)
421 unfinode = unfiindex[unfilen - 1][7]
421 unfinode = unfiindex[unfilen - 1][7]
422 with util.timedcm('repo filter for %s', self.filtername):
422 with util.timedcm('repo filter for %s', self.filtername):
423 revs = filterrevs(unfi, self.filtername, self._visibilityexceptions)
423 revs = filterrevs(unfi, self.filtername, self._visibilityexceptions)
424 cl = self._clcache
424 cl = self._clcache
425 newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
425 newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
426 # if cl.index is not unfiindex, unfi.changelog would be
426 # if cl.index is not unfiindex, unfi.changelog would be
427 # recreated, and our clcache refers to garbage object
427 # recreated, and our clcache refers to garbage object
428 if cl is not None and (
428 if cl is not None and (
429 cl.index is not unfiindex or newkey != self._clcachekey
429 cl.index is not unfiindex or newkey != self._clcachekey
430 ):
430 ):
431 cl = None
431 cl = None
432 # could have been made None by the previous if
432 # could have been made None by the previous if
433 if cl is None:
433 if cl is None:
434 # Only filter if there's something to filter
434 # Only filter if there's something to filter
435 cl = wrapchangelog(unfichangelog, revs) if revs else unfichangelog
435 cl = wrapchangelog(unfichangelog, revs) if revs else unfichangelog
436 object.__setattr__(self, '_clcache', cl)
436 object.__setattr__(self, '_clcache', cl)
437 object.__setattr__(self, '_clcachekey', newkey)
437 object.__setattr__(self, '_clcachekey', newkey)
438 return cl
438 return cl
439
439
440 def unfiltered(self):
440 def unfiltered(self):
441 """Return an unfiltered version of a repo"""
441 """Return an unfiltered version of a repo"""
442 return self._unfilteredrepo
442 return self._unfilteredrepo
443
443
444 def filtered(self, name, visibilityexceptions=None):
444 def filtered(self, name, visibilityexceptions=None):
445 """Return a filtered version of a repository"""
445 """Return a filtered version of a repository"""
446 if name == self.filtername and not visibilityexceptions:
446 if name == self.filtername and not visibilityexceptions:
447 return self
447 return self
448 return self.unfiltered().filtered(name, visibilityexceptions)
448 return self.unfiltered().filtered(name, visibilityexceptions)
449
449
450 def __repr__(self):
450 def __repr__(self):
451 return '<%s:%s %r>' % (
451 return '<%s:%s %r>' % (
452 self.__class__.__name__,
452 self.__class__.__name__,
453 pycompat.sysstr(self.filtername),
453 pycompat.sysstr(self.filtername),
454 self.unfiltered(),
454 self.unfiltered(),
455 )
455 )
456
456
457 # everything access are forwarded to the proxied repo
457 # everything access are forwarded to the proxied repo
458 def __getattr__(self, attr):
458 def __getattr__(self, attr):
459 return getattr(self._unfilteredrepo, attr)
459 return getattr(self._unfilteredrepo, attr)
460
460
461 def __setattr__(self, attr, value):
461 def __setattr__(self, attr, value):
462 return setattr(self._unfilteredrepo, attr, value)
462 return setattr(self._unfilteredrepo, attr, value)
463
463
464 def __delattr__(self, attr):
464 def __delattr__(self, attr):
465 return delattr(self._unfilteredrepo, attr)
465 return delattr(self._unfilteredrepo, attr)
466
466
467
467
468 # Python <3.4 easily leaks types via __mro__. See
468 # Python <3.4 easily leaks types via __mro__. See
469 # https://bugs.python.org/issue17950. We cache dynamically created types
469 # https://bugs.python.org/issue17950. We cache dynamically created types
470 # so they won't be leaked on every invocation of repo.filtered().
470 # so they won't be leaked on every invocation of repo.filtered().
471 _filteredrepotypes = weakref.WeakKeyDictionary()
471 _filteredrepotypes = weakref.WeakKeyDictionary()
472
472
473
473
474 def newtype(base):
474 def newtype(base):
475 """Create a new type with the repoview mixin and the given base class"""
475 """Create a new type with the repoview mixin and the given base class"""
476 if base not in _filteredrepotypes:
476 if base not in _filteredrepotypes:
477
477
478 class filteredrepo(repoview, base):
478 class filteredrepo(repoview, base):
479 pass
479 pass
480
480
481 _filteredrepotypes[base] = filteredrepo
481 _filteredrepotypes[base] = filteredrepo
482 return _filteredrepotypes[base]
482 return _filteredrepotypes[base]
@@ -1,181 +1,223 b''
1 $ cat >> $HGRCPATH <<EOF
1 $ cat >> $HGRCPATH <<EOF
2 > [extensions]
2 > [extensions]
3 > rebase=
3 > rebase=
4 >
4 >
5 > [phases]
5 > [phases]
6 > publish=False
6 > publish=False
7 >
7 >
8 > [alias]
8 > [alias]
9 > tglog = log -G --template "{rev}:{phase} '{desc}' {branches}\n"
9 > tglog = log -G --template "{rev}:{phase} '{desc}' {branches}\n"
10 > EOF
10 > EOF
11
11
12
12
13 $ hg init a
13 $ hg init a
14 $ cd a
14 $ cd a
15
15
16 $ echo A > A
16 $ echo A > A
17 $ hg add A
17 $ hg add A
18 $ hg ci -m A
18 $ hg ci -m A
19
19
20 $ echo 'B' > B
20 $ echo 'B' > B
21 $ hg add B
21 $ hg add B
22 $ hg ci -m B
22 $ hg ci -m B
23
23
24 $ echo C >> A
24 $ echo C >> A
25 $ hg ci -m C
25 $ hg ci -m C
26
26
27 $ hg up -q -C 0
27 $ hg up -q -C 0
28
28
29 $ echo D >> A
29 $ echo D >> A
30 $ hg ci -m D
30 $ hg ci -m D
31 created new head
31 created new head
32
32
33 $ echo E > E
33 $ echo E > E
34 $ hg add E
34 $ hg add E
35 $ hg ci -m E
35 $ hg ci -m E
36
36
37 $ hg up -q -C 0
37 $ hg up -q -C 0
38
38
39 $ hg branch 'notdefault'
39 $ hg branch 'notdefault'
40 marked working directory as branch notdefault
40 marked working directory as branch notdefault
41 (branches are permanent and global, did you want a bookmark?)
41 (branches are permanent and global, did you want a bookmark?)
42 $ echo F >> A
42 $ echo F >> A
43 $ hg ci -m F
43 $ hg ci -m F
44
44
45 $ cd ..
45 $ cd ..
46
46
47
47
48 Rebasing B onto E - check keep: and phases
48 Rebasing B onto E - check keep: and phases
49
49
50 $ hg clone -q -u . a a1
50 $ hg clone -q -u . a a1
51 $ cd a1
51 $ cd a1
52 $ hg phase --force --secret 2
52 $ hg phase --force --secret 2
53
53
54 $ hg tglog
54 $ hg tglog
55 @ 5:draft 'F' notdefault
55 @ 5:draft 'F' notdefault
56 |
56 |
57 | o 4:draft 'E'
57 | o 4:draft 'E'
58 | |
58 | |
59 | o 3:draft 'D'
59 | o 3:draft 'D'
60 |/
60 |/
61 | o 2:secret 'C'
61 | o 2:secret 'C'
62 | |
62 | |
63 | o 1:draft 'B'
63 | o 1:draft 'B'
64 |/
64 |/
65 o 0:draft 'A'
65 o 0:draft 'A'
66
66
67 $ hg rebase -s 1 -d 4 --keep
67 $ hg rebase -s 1 -d 4 --keep
68 rebasing 1:27547f69f254 "B"
68 rebasing 1:27547f69f254 "B"
69 rebasing 2:965c486023db "C"
69 rebasing 2:965c486023db "C"
70 merging A
70 merging A
71 warning: conflicts while merging A! (edit, then use 'hg resolve --mark')
71 warning: conflicts while merging A! (edit, then use 'hg resolve --mark')
72 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
72 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
73 [1]
73 [1]
74
74
75 Solve the conflict and go on:
75 Solve the conflict and go on:
76
76
77 $ echo 'conflict solved' > A
77 $ echo 'conflict solved' > A
78 $ rm A.orig
78 $ rm A.orig
79 $ hg resolve -m A
79 $ hg resolve -m A
80 (no more unresolved files)
80 (no more unresolved files)
81 continue: hg rebase --continue
81 continue: hg rebase --continue
82 $ hg rebase --continue
82 $ hg rebase --continue
83 already rebased 1:27547f69f254 "B" as 45396c49d53b
83 already rebased 1:27547f69f254 "B" as 45396c49d53b
84 rebasing 2:965c486023db "C"
84 rebasing 2:965c486023db "C"
85
85
86 $ hg tglog
86 $ hg tglog
87 o 7:secret 'C'
87 o 7:secret 'C'
88 |
88 |
89 o 6:draft 'B'
89 o 6:draft 'B'
90 |
90 |
91 | @ 5:draft 'F' notdefault
91 | @ 5:draft 'F' notdefault
92 | |
92 | |
93 o | 4:draft 'E'
93 o | 4:draft 'E'
94 | |
94 | |
95 o | 3:draft 'D'
95 o | 3:draft 'D'
96 |/
96 |/
97 | o 2:secret 'C'
97 | o 2:secret 'C'
98 | |
98 | |
99 | o 1:draft 'B'
99 | o 1:draft 'B'
100 |/
100 |/
101 o 0:draft 'A'
101 o 0:draft 'A'
102
102
103 $ cd ..
103 $ cd ..
104
104
105
105
106 Rebase F onto E - check keepbranches:
106 Rebase F onto E - check keepbranches:
107
107
108 $ hg clone -q -u . a a2
108 $ hg clone -q -u . a a2
109 $ cd a2
109 $ cd a2
110 $ hg phase --force --secret 2
110 $ hg phase --force --secret 2
111
111
112 $ hg tglog
112 $ hg tglog
113 @ 5:draft 'F' notdefault
113 @ 5:draft 'F' notdefault
114 |
114 |
115 | o 4:draft 'E'
115 | o 4:draft 'E'
116 | |
116 | |
117 | o 3:draft 'D'
117 | o 3:draft 'D'
118 |/
118 |/
119 | o 2:secret 'C'
119 | o 2:secret 'C'
120 | |
120 | |
121 | o 1:draft 'B'
121 | o 1:draft 'B'
122 |/
122 |/
123 o 0:draft 'A'
123 o 0:draft 'A'
124
124
125 $ hg rebase -s 5 -d 4 --keepbranches
125 $ hg rebase -s 5 -d 4 --keepbranches
126 rebasing 5:01e6ebbd8272 "F" (tip)
126 rebasing 5:01e6ebbd8272 "F" (tip)
127 merging A
127 merging A
128 warning: conflicts while merging A! (edit, then use 'hg resolve --mark')
128 warning: conflicts while merging A! (edit, then use 'hg resolve --mark')
129 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
129 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
130 [1]
130 [1]
131
131
132 Solve the conflict and go on:
132 Solve the conflict and go on:
133
133
134 $ echo 'conflict solved' > A
134 $ echo 'conflict solved' > A
135 $ rm A.orig
135 $ rm A.orig
136 $ hg resolve -m A
136 $ hg resolve -m A
137 (no more unresolved files)
137 (no more unresolved files)
138 continue: hg rebase --continue
138 continue: hg rebase --continue
139 $ hg rebase --continue
139 $ hg rebase --continue
140 rebasing 5:01e6ebbd8272 "F" (tip)
140 rebasing 5:01e6ebbd8272 "F" (tip)
141 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/01e6ebbd8272-6fd3a015-rebase.hg
141 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/01e6ebbd8272-6fd3a015-rebase.hg
142
142
143 $ hg tglog
143 $ hg tglog
144 @ 5:draft 'F' notdefault
144 @ 5:draft 'F' notdefault
145 |
145 |
146 o 4:draft 'E'
146 o 4:draft 'E'
147 |
147 |
148 o 3:draft 'D'
148 o 3:draft 'D'
149 |
149 |
150 | o 2:secret 'C'
150 | o 2:secret 'C'
151 | |
151 | |
152 | o 1:draft 'B'
152 | o 1:draft 'B'
153 |/
153 |/
154 o 0:draft 'A'
154 o 0:draft 'A'
155
155
156 $ cat >> .hg/hgrc << EOF
156 $ cat >> .hg/hgrc << EOF
157 > [experimental]
157 > [experimental]
158 > evolution.createmarkers=True
158 > evolution.createmarkers=True
159 > EOF
159 > EOF
160
160
161 When updating away from a dirty, obsolete wdir, don't complain that the old p1
161 When updating away from a dirty, obsolete wdir, don't complain that the old p1
162 is filtered and requires --hidden.
162 is filtered and requires --hidden.
163
163
164 $ echo conflict > A
164 $ echo conflict > A
165 $ hg debugobsolete 071d07019675449d53b7e312c65bcf28adbbdb64 965c486023dbfdc9c32c52dc249a231882fd5c17
165 $ hg debugobsolete 071d07019675449d53b7e312c65bcf28adbbdb64 965c486023dbfdc9c32c52dc249a231882fd5c17
166 1 new obsolescence markers
166 1 new obsolescence markers
167 obsoleted 1 changesets
167 obsoleted 1 changesets
168 $ hg update -r 2 --config ui.merge=internal:merge --merge
168 $ hg update -r 2 --config ui.merge=internal:merge --merge
169 merging A
169 merging A
170 warning: conflicts while merging A! (edit, then use 'hg resolve --mark')
170 warning: conflicts while merging A! (edit, then use 'hg resolve --mark')
171 1 files updated, 0 files merged, 1 files removed, 1 files unresolved
171 1 files updated, 0 files merged, 1 files removed, 1 files unresolved
172 use 'hg resolve' to retry unresolved file merges
172 use 'hg resolve' to retry unresolved file merges
173 [1]
173 [1]
174 $ hg resolve A
174 $ hg resolve A
175 merging A
175 merging A
176 warning: conflicts while merging A! (edit, then use 'hg resolve --mark')
176 warning: conflicts while merging A! (edit, then use 'hg resolve --mark')
177 [1]
177 [1]
178
178
179 An unresolved conflict will pin the obsolete revision
180
181 $ hg log -G -Tcompact
182 % 5[tip] 071d07019675 1970-01-01 00:00 +0000 test
183 | F
184 |
185 o 4 ae36e8e3dfd7 1970-01-01 00:00 +0000 test
186 | E
187 |
188 o 3:0 46b37eabc604 1970-01-01 00:00 +0000 test
189 | D
190 |
191 | @ 2 965c486023db 1970-01-01 00:00 +0000 test
192 | | C
193 | |
194 | o 1 27547f69f254 1970-01-01 00:00 +0000 test
195 |/ B
196 |
197 o 0 4a2df7238c3b 1970-01-01 00:00 +0000 test
198 A
199
200
201 But resolving the conflicts will unpin it
202
203 $ hg resolve -m A
204 (no more unresolved files)
205 $ hg log -G -Tcompact
206 o 4[tip] ae36e8e3dfd7 1970-01-01 00:00 +0000 test
207 | E
208 |
209 o 3:0 46b37eabc604 1970-01-01 00:00 +0000 test
210 | D
211 |
212 | @ 2 965c486023db 1970-01-01 00:00 +0000 test
213 | | C
214 | |
215 | o 1 27547f69f254 1970-01-01 00:00 +0000 test
216 |/ B
217 |
218 o 0 4a2df7238c3b 1970-01-01 00:00 +0000 test
219 A
220
179 $ hg up -C -q .
221 $ hg up -C -q .
180
222
181 $ cd ..
223 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now