[PATCH 12 of 17 RFC] clfilter: introduce a `revsfilter` class to control changelog filtering on repo
pierre-yves.david at logilab.fr
pierre-yves.david at logilab.fr
Mon Sep 3 07:58:36 CDT 2012
# HG changeset patch
# User Pierre-Yves David <pierre-yves.david at logilab.fr>
# Date 1346675952 -7200
# Node ID c231611a44cef373a4f8da1add8346fa7f368b96
# Parent 96c8e124f220d706f05b5a3643945e2ba5266644
clfilter: introduce a `revsfilter` class to control changelog filtering on repo
Every local repository object get one instance in its `repo.revsfilter` attribute.
The filter is enabled by calling `repo.revsfilter.set(<name>)` were name is the
name of the filter (string). There is no valid filter for now but the "hidden"
and "unserved" are introduced by followup. None means unfiltered. This function
returns a callback to reinstall the old filter that the call replaces (kind of
context manager).
Every method needs the repo, so we keep a reference to the repo in the object
for the sake of simplicity. To avoid a circular reference we use a weakref. This
should not introduce any bug as this object is always called through its repo.
Multiple logics need to run unfiltered to work:
- phase computation
- verify
- strip XXX (not enforced yet)
- bundle creation and consumption
Code that can lead to invalid cache for filter call the `invalidatefilter`
methods. (Note: I likely forgot several places were the cache should be
invalidated but this is not caught by the test yet.)
diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -92,13 +92,20 @@ class changectx(object):
self._rev = repo.changelog.rev(self._node)
return
# lookup failed
# check if it might have come from damaged dirstate
- if changeid in repo.dirstate.parents():
- raise error.Abort(_("working directory has unknown parent '%s'!")
- % short(changeid))
+ #
+ # XXX we could avoid the unfiltered if we had a recognisable exception
+ # for filtered changeset access
+ refilter = repo.revsfilter.set(None)
+ try:
+ if changeid in repo.dirstate.parents():
+ msg = _("working directory has unknown parent '%s'!")
+ raise error.Abort(msg % short(changeid))
+ finally:
+ refilter()
try:
if len(changeid) == 20:
changeid = hex(changeid)
except TypeError:
pass
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -6,11 +6,11 @@
# GNU General Public License version 2 or any later version.
from node import bin, hex, nullid, nullrev, short
from i18n import _
import peer, changegroup, subrepo, discovery, pushkey, obsolete
import changelog, dirstate, filelog, manifest, context, bookmarks, phases
-import lock, transaction, store, encoding, base85
+import lock, transaction, store, encoding, base85, repofilter
import scmutil, util, extensions, hook, error, revset
import match as matchmod
import merge as mergemod
import tags as tagsmod
from lock import release
@@ -203,10 +203,12 @@ class localrepository(object):
# (used by the filecache decorator)
#
# Maps a property name to its util.filecacheentry
self._filecache = {}
+ self.revsfilter = repofilter.revsfilter(self)
+
def close(self):
pass
def _restrictcapabilities(self, caps):
return caps
@@ -1050,10 +1052,11 @@ class localrepository(object):
delcache('_tagscache')
self._branchcache = None # in UTF-8
self._branchcachetip = None
+ self.revsfilter.invalidatefilter()
def invalidatedirstate(self):
'''Invalidates the dirstate, causing the next call to dirstate
to check if it was modified since the last time it was read,
rereading it if it has.
@@ -1811,10 +1814,11 @@ class localrepository(object):
tr = self.transaction(trname)
for key in sorted(remoteobs, reverse=True):
if key.startswith('dump'):
data = base85.b85decode(remoteobs[key])
self.obsstore.mergemarkers(tr, data)
+ self.revsfilter.invalidatefilter()
if tr is not None:
tr.close()
finally:
if tr is not None:
tr.release()
@@ -1862,11 +1866,15 @@ class localrepository(object):
commoninc = fci(self, remote, force=force)
common, inc, remoteheads = commoninc
fco = discovery.findcommonoutgoing
outgoing = fco(self, remote, onlyheads=revs,
commoninc=commoninc, force=force)
-
+ refilter = self.revsfilter.set(None)
+ try:
+ outgoing.missing
+ finally:
+ refilter()
if not outgoing.missing:
# nothing to push
scmutil.nochangesfound(self.ui, self, outgoing.excluded)
ret = None
@@ -1892,11 +1900,12 @@ class localrepository(object):
discovery.checkheads(self, remote, outgoing,
remoteheads, newbranch,
bool(inc))
# create a changegroup from local
- if revs is None and not outgoing.excluded:
+ # XXX XXX XXX XXX XXX XXX
+ if False and revs is None and not outgoing.excluded:
# push everything,
# use the fast path, no race possible on push
cg = self._changegroup(outgoing.missing, 'push')
else:
cg = self.getlocalbundle('push', outgoing)
@@ -2082,12 +2091,16 @@ class localrepository(object):
fstate = ['', {}]
count = [0, 0]
# can we go through the fast path ?
heads.sort()
- if heads == sorted(self.heads()):
- return self._changegroup(csets, source)
+ refilter = self.revsfilter.set(None)
+ try:
+ if heads == sorted(self.heads()):
+ return self._changegroup(csets, source)
+ finally:
+ refilter()
# slow path
self.hook('preoutgoing', throw=True, source=source)
self.changegroupinfo(csets, source)
@@ -2307,10 +2320,11 @@ class localrepository(object):
cl = self.changelog
cl.delayupdate()
oldheads = cl.heads()
tr = self.transaction("\n".join([srctype, util.hidepassword(url)]))
+ refilter = None
try:
trp = weakref.proxy(tr)
# pull off the changeset group
self.ui.status(_("adding changesets\n"))
clstart = len(cl)
@@ -2325,10 +2339,11 @@ class localrepository(object):
self.count += 1
pr = prog()
source.callback = pr
source.changelogheader()
+ refilter = self.revsfilter.set(None)
srccontent = cl.addgroup(source, csmap, trp)
if not (srccontent or emptyok):
raise util.Abort(_("received changelog group is empty"))
clend = len(cl)
changesets = clend - clstart
@@ -2412,10 +2427,11 @@ class localrepository(object):
htext = _(" (%+d heads)") % dh
self.ui.status(_("added %d changesets"
" with %d changes to %d files%s\n")
% (changesets, revisions, files, htext))
+ self.revsfilter.invalidatefilter()
if changesets > 0:
p = lambda: cl.writepending() and self.root or ""
self.hook('pretxnchangegroup', throw=True,
node=hex(cl.node(clstart)), source=srctype,
@@ -2459,10 +2475,12 @@ class localrepository(object):
url=url)
self._afterlock(runhooks)
finally:
tr.release()
+ if refilter is not None:
+ refilter()
# never return 0 here:
if dh < 0:
return dh - 1
else:
return dh + 1
diff --git a/mercurial/phases.py b/mercurial/phases.py
--- a/mercurial/phases.py
+++ b/mercurial/phases.py
@@ -181,19 +181,23 @@ class phasecache(object):
for a in 'phaseroots dirty opener _phaserevs'.split():
setattr(self, a, getattr(phcache, a))
def getphaserevs(self, repo, rebuild=False):
if rebuild or self._phaserevs is None:
- revs = [public] * len(repo.changelog)
- for phase in trackedphases:
- roots = map(repo.changelog.rev, self.phaseroots[phase])
- if roots:
- for rev in roots:
- revs[rev] = phase
- for rev in repo.changelog.descendants(roots):
- revs[rev] = phase
- self._phaserevs = revs
+ refilter = repo.revsfilter.set(None)
+ try:
+ revs = [public] * len(repo.changelog)
+ for phase in trackedphases:
+ roots = map(repo.changelog.rev, self.phaseroots[phase])
+ if roots:
+ for rev in roots:
+ revs[rev] = phase
+ for rev in repo.changelog.descendants(roots):
+ revs[rev] = phase
+ self._phaserevs = revs
+ finally:
+ refilter()
return self._phaserevs
def phase(self, repo, rev):
# We need a repo argument here to be able to build _phaserevs
# if necessary. The repository instance is not stored in
@@ -224,44 +228,53 @@ class phasecache(object):
self.dirty = True
def advanceboundary(self, repo, targetphase, nodes):
# Be careful to preserve shallow-copied values: do not update
# phaseroots values, replace them.
-
- delroots = [] # set of root deleted by this path
- for phase in xrange(targetphase + 1, len(allphases)):
- # filter nodes that are not in a compatible phase already
- nodes = [n for n in nodes
- if self.phase(repo, repo[n].rev()) >= phase]
- if not nodes:
- break # no roots to move anymore
- olds = self.phaseroots[phase]
- roots = set(ctx.node() for ctx in repo.set(
- 'roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
- if olds != roots:
- self._updateroots(phase, roots)
- # some roots may need to be declared for lower phases
- delroots.extend(olds - roots)
- # declare deleted root in the target phase
- if targetphase != 0:
- self.retractboundary(repo, targetphase, delroots)
+ refilter = repo.revsfilter.set(None)
+ try:
+ delroots = [] # set of root deleted by this path
+ for phase in xrange(targetphase + 1, len(allphases)):
+ # filter nodes that are not in a compatible phase already
+ nodes = [n for n in nodes
+ if self.phase(repo, repo[n].rev()) >= phase]
+ if not nodes:
+ break # no roots to move anymore
+ olds = self.phaseroots[phase]
+ roots = set(ctx.node() for ctx in repo.set(
+ 'roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
+ if olds != roots:
+ self._updateroots(phase, roots)
+ # some roots may need to be declared for lower phases
+ delroots.extend(olds - roots)
+ # declare deleted root in the target phase
+ if targetphase != 0:
+ self.retractboundary(repo, targetphase, delroots)
+ repo.revsfilter.invalidatefilter()
+ finally:
+ refilter()
def retractboundary(self, repo, targetphase, nodes):
# Be careful to preserve shallow-copied values: do not update
# phaseroots values, replace them.
- currentroots = self.phaseroots[targetphase]
- newroots = [n for n in nodes
- if self.phase(repo, repo[n].rev()) < targetphase]
- if newroots:
- if nullid in newroots:
- raise util.Abort(_('cannot change null revision phase'))
- currentroots = currentroots.copy()
- currentroots.update(newroots)
- ctxs = repo.set('roots(%ln::)', currentroots)
- currentroots.intersection_update(ctx.node() for ctx in ctxs)
- self._updateroots(targetphase, currentroots)
+ refilter = repo.revsfilter.set(None)
+ try:
+ currentroots = self.phaseroots[targetphase]
+ newroots = [n for n in nodes
+ if self.phase(repo, repo[n].rev()) < targetphase]
+ if newroots:
+ if nullid in newroots:
+ raise util.Abort(_('cannot change null revision phase'))
+ currentroots = currentroots.copy()
+ currentroots.update(newroots)
+ ctxs = repo.set('roots(%ln::)', currentroots)
+ currentroots.intersection_update(ctx.node() for ctx in ctxs)
+ self._updateroots(targetphase, currentroots)
+ repo.revsfilter.invalidatefilter()
+ finally:
+ refilter()
def advanceboundary(repo, targetphase, nodes):
"""Add nodes to a phase changing other nodes phases if necessary.
This function move boundary *forward* this means that all nodes
diff --git a/mercurial/repofilter.py b/mercurial/repofilter.py
new file mode 100644
--- /dev/null
+++ b/mercurial/repofilter.py
@@ -0,0 +1,60 @@
+# filter -- repository filtering
+#
+# Copyright 2012 Pierre-Yves David <pierre-yves.david at ens-lyon.org>
+# Logilab SA <contact at logilab.fr>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+import weakref
+
+computefiltered = {}
+
+
+class revsfilter(object):
+
+ def __init__(self, repo):
+ self._name = None
+ self._cache = {}
+ self._repo = weakref.proxy(repo)
+
+ def set(self, filtername):
+ """set a filter for the repository
+
+ If name is None, no filtering is performed
+
+ returns a callback to reinstall the previous filter"""
+ oldfilter = self._name
+ self._name = filtername
+ self._installfilter()
+ def exit():
+ self.set(oldfilter)
+ return exit
+
+ def invalidatefilter(self):
+ """invalidate current filter
+
+ called when something might have changed the filter value:
+ - new changesets,
+ - phase change,
+ - new obsolescence marker,
+ - working directory parent change,
+ - bookmark changes"""
+ self._cache.clear()
+ self._installfilter()
+
+ def _installfilter(self):
+ """align changelog.filtered with current repo.revsfiltering"""
+ repo = self._repo
+ name = self._name
+ if 'changelog' in repo._filecache:
+ self._repo.changelog.filteredrevs = ()
+ if name is None:
+ return
+ if not name in self._cache:
+ # compute the new filter unfiltered
+ self._name = None
+ func = computefiltered[name]
+ self._cache[name] = func(repo)
+ self._name = name
+ self._repo.changelog.filteredrevs = self._cache[name]
diff --git a/mercurial/statichttprepo.py b/mercurial/statichttprepo.py
--- a/mercurial/statichttprepo.py
+++ b/mercurial/statichttprepo.py
@@ -8,11 +8,11 @@
# GNU General Public License version 2 or any later version.
from i18n import _
import changelog, byterange, url, error
import localrepo, manifest, util, scmutil, store
-import urllib, urllib2, errno
+import urllib, urllib2, errno, repofilter
class httprangereader(object):
def __init__(self, url, opener):
# we assume opener has HTTPRangeHandler
self.url = url
@@ -130,10 +130,11 @@ class statichttprepository(localrepo.loc
self.nodetagscache = None
self._branchcache = None
self._branchcachetip = None
self.encodepats = None
self.decodepats = None
+ self.revsfilter = repofilter.revsfilter(self)
def _restrictcapabilities(self, caps):
return caps.difference(["pushkey"])
def url(self):
diff --git a/mercurial/verify.py b/mercurial/verify.py
--- a/mercurial/verify.py
+++ b/mercurial/verify.py
@@ -11,11 +11,15 @@ import os
import revlog, util, error
def verify(repo):
lock = repo.lock()
try:
- return _verify(repo)
+ refilter = repo.revsfilter.set(None)
+ try:
+ return _verify(repo)
+ finally:
+ refilter()
finally:
lock.release()
def _verify(repo):
mflinkrevs = {}
More information about the Mercurial-devel
mailing list