Rev 1384: Cherrypick LogCache class from Martins svn-1.5 branch. in http://people.samba.org/bzr/jelmer/bzr-svn/0.4
Jelmer Vernooij
jelmer at samba.org
Sun Jun 29 21:47:52 BST 2008
At http://people.samba.org/bzr/jelmer/bzr-svn/0.4
------------------------------------------------------------
revno: 1384
revision-id: jelmer at samba.org-20080629204750-yzhl62tojxmzs140
parent: jelmer at samba.org-20080629133225-tyhvpab9fxchf46e
committer: Jelmer Vernooij <jelmer at samba.org>
branch nick: 0.4
timestamp: Sun 2008-06-29 22:47:50 +0200
message:
Cherrypick LogCache class from Martins svn-1.5 branch.
modified:
logwalker.py logwalker.py-20060621215743-c13fhfnyzh1xzwh2-1
=== modified file 'logwalker.py'
--- a/logwalker.py 2008-06-27 00:15:22 +0000
+++ b/logwalker.py 2008-06-29 20:47:50 +0000
@@ -92,31 +92,174 @@
return self.dict.__eq__(other)
+class LogCache(CacheTable):
+ """Log browser cache table manager. The methods of this class
+ encapsulate the SQL commands used by CachingLogWalker to access
+ the log cache tables."""
+
+ def __init__(self, cache_db=None):
+ CacheTable.__init__(self, cache_db)
+ self.commit_countdown = 1000
+
+ def _create_table(self):
+ self.cachedb.executescript("""
+ create table if not exists changed_path(rev integer, action text, path text, copyfrom_path text, copyfrom_rev integer);
+ create index if not exists path_rev on changed_path(rev);
+ create unique index if not exists path_rev_path on changed_path(rev, path);
+ create unique index if not exists path_rev_path_action on changed_path(rev, path, action);
+
+ create table if not exists revprop(rev integer, name text, value text);
+ create table if not exists revinfo(rev integer, all_revprops int);
+ create index if not exists revprop_rev on revprop(rev);
+ create unique index if not exists revprop_rev_name on revprop(rev, name);
+ create unique index if not exists revinfo_rev on revinfo(rev);
+ """)
+
+ def find_latest_change(self, path, revnum):
+ if path == "":
+ return self.cachedb.execute("select max(rev) from changed_path where rev <= ?", (revnum,)).fetchone()[0]
+ return self.cachedb.execute("""
+ select max(rev) from changed_path
+ where rev <= ?
+ and (path=?
+ or path glob ?
+ or (? glob (path || '/*')
+ and action in ('A', 'R')))
+ """, (revnum, path, path + "/*", path)).fetchone()[0]
+
+ def path_added(self, path, from_revnum, to_revnum):
+ """Determine whether a path was recently added.
+
+ It returns the latest revision number before to_revnum in which the
+ path was added or replaced, or None if the path was neither added nor
+ replaced after from_revnum but before to_revnum.
+ """
+ assert from_revnum < to_revnum
+ return self.cachedb.execute("""
+ select max(rev) from changed_path
+ where rev > ? and rev <= ?
+ and (?=path or ? glob (path || '/*'))
+ and action in ('A', 'R')
+ """, (from_revnum, to_revnum, path, path)).fetchone()[0]
+
+ def get_change(self, path, revnum):
+ return self.cachedb.execute("""
+ select action, copyfrom_path, copyfrom_rev
+ from changed_path
+ where path=? and rev=?
+ """, (path, revnum)).fetchone()
+
+ def get_previous(self, path, revnum):
+ """Determine the change that created the given path or its
+ nearest ancestor, in order to determine where it came from."""
+ return self.cachedb.execute("""
+ select path, action, copyfrom_path, copyfrom_rev
+ from changed_path
+ where rev=? and action in ('A', 'R')
+ and (path=? or path='' or ? glob (path || '/*'))
+ order by path desc
+ limit 1
+ """, (revnum, path, path)).fetchone()
+
+ def get_revision_paths(self, revnum):
+ """Return all history information for a given revision number.
+
+ :param revnum: Revision number of revision.
+ """
+ result = self.cachedb.execute("select path, action, copyfrom_path, copyfrom_rev from changed_path where rev=?", (revnum,))
+ paths = {}
+ for p, act, cf, cr in result:
+ if cf is not None:
+ cf = cf.encode("utf-8")
+ paths[p.encode("utf-8")] = (act, cf, cr)
+ return paths
+
+ def changes_path(self, path, revnum):
+ """Check whether a path was changed in a particular revision:
+
+ :param path: path to check for
+ :param revnum: revision number to fetch
+ """
+ if path == '':
+ return self.cachedb.execute("select count(*) from changed_path where rev=?", (revnum,)).fetchone()[0] > 0
+ return self.cachedb.execute("select count(*) from changed_path where rev=? and (path=? or path glob (? || '/*'))", (revnum, path, path)).fetchone()[0] > 0
+
+ def insert_path(self, rev, path, action, copyfrom_path=None, copyfrom_rev=-1):
+ """Insert new history information into the cache.
+
+ :param rev: Revision number of the revision
+ :param path: Path that was changed
+ :param action: Action on path (single-character)
+ :param copyfrom_path: Optional original path this path was copied from.
+ :param copyfrom_rev: Optional original rev this path was copied from.
+ """
+ assert action in ("A", "R", "D", "M")
+ assert not path.startswith("/")
+ self.cachedb.execute("replace into changed_path (rev, path, action, copyfrom_path, copyfrom_rev) values (?, ?, ?, ?, ?)", (rev, path, action, copyfrom_path, copyfrom_rev))
+
+ def get_revprops(self, revnum):
+ """Retrieve all the cached revision properties.
+
+ :param revnum: Revision number of revision to retrieve revprops for.
+ """
+ result = self.cachedb.execute("select name, value from revprop where rev = ?", (revnum,))
+ revprops = {}
+ for k,v in result:
+ revprops[k.encode("utf-8")] = v.encode("utf-8")
+ return revprops
+
+ def insert_revprop(self, rev, name, value):
+ """Add a revision property.
+
+ :param rev: Revision number of the revision.
+ :param name: Name of the revision property.
+ :param value: Contents of the revision property.
+ """
+ self.cachedb.execute("replace into revprop (rev, name, value) values (?, ?, ?)", (rev, name, value))
+
+ def has_all_revprops(self, revnum):
+ """Check whether all revprops for a revision have been cached.
+
+ :param revnum: Revision number of the revision.
+ """
+ return self.cachedb.execute("select all_revprops from revinfo where rev = ?", (revnum,)).fetchone()[0]
+
+ def insert_revinfo(self, rev, all_revprops):
+ """Insert metadata for a revision.
+
+ :param rev: Revision number of the revision.
+ :param all_revprops: Whether or not the full revprops have been stored.
+ """
+ self.cachedb.execute("""
+ replace into revinfo (rev, all_revprops) values (?, ?)
+ """, (rev, all_revprops))
+
+ def last_revnum(self):
+ saved_revnum = self.cachedb.execute("SELECT MAX(rev) FROM revinfo").fetchone()[0]
+ if saved_revnum is None:
+ return 0
+ return saved_revnum
+
+ def commit(self):
+ """Commit the cache database."""
+ self.cachedb.commit()
+ self.commit_countdown = 1000
+
+ def commit_conditionally(self):
+ self.commit_countdown -= 1
+ if self.commit_countdown <= 0:
+ self.commit()
+
+
class CachingLogWalker(CacheTable):
"""Subversion log browser."""
def __init__(self, actual, cache_db=None):
- CacheTable.__init__(self, cache_db)
-
+ self.cache = LogCache(cache_db)
self.actual = actual
self._transport = actual._transport
self.find_children = actual.find_children
- self.saved_revnum = self.cachedb.execute("SELECT MAX(rev) FROM revinfo").fetchone()[0]
- if self.saved_revnum is None:
- self.saved_revnum = 0
-
- def _create_table(self):
- self.cachedb.executescript("""
- create table if not exists changed_path(rev integer, action text, path text, copyfrom_path text, copyfrom_rev integer);
- create index if not exists path_rev on changed_path(rev);
- create unique index if not exists path_rev_path on changed_path(rev, path);
- create unique index if not exists path_rev_path_action on changed_path(rev, path, action);
- create table if not exists revprop(rev integer, name text, value text);
- create table if not exists revinfo(rev integer, all_revprops int);
- create index if not exists revprop_rev on revprop(rev);
- create unique index if not exists revprop_rev_name on revprop(rev, name);
- create unique index if not exists revinfo_rev on revinfo(rev);
- """)
+ self.saved_revnum = self.cache.last_revnum()
def find_latest_change(self, path, revnum):
"""Find latest revision that touched path.
@@ -129,24 +272,11 @@
self.fetch_revisions(revnum)
self.mutter("latest change: %r:%r", path, revnum)
-
- extra = ""
- if path == "":
- extra += " OR path GLOB '*'"
- else:
- extra += " OR path GLOB '%s/*'" % path.strip("/")
- extra += " OR ('%s' GLOB (path || '/*') AND (action = 'R' OR action = 'A'))" % path.strip("/")
-
- query = "SELECT rev FROM changed_path WHERE (path='%s'%s) AND rev <= %d ORDER BY rev DESC LIMIT 1" % (path.strip("/"), extra, revnum)
-
- row = self.cachedb.execute(query).fetchone()
- if row is None and path == "":
+ revnum = self.cache.find_latest_change(path.strip("/"), revnum)
+ if revnum is None and path == "":
return 0
-
- if row is None:
- return None
-
- return row[0]
+
+ return revnum
def iter_changes(self, paths, from_revnum, to_revnum=0, limit=0, pb=None):
"""Return iterator over all the revisions between from_revnum and to_revnum named path or inside path.
@@ -174,6 +304,7 @@
assert from_revnum >= to_revnum or path == ""
+ self.fetch_revisions(max(from_revnum, to_revnum))
i = 0
while ((not ascending and revnum >= to_revnum) or
@@ -181,7 +312,7 @@
if pb is not None:
pb.update("determining changes", from_revnum-revnum, from_revnum)
assert revnum > 0 or path == "", "Inconsistent path,revnum: %r,%r" % (revnum, path)
- revpaths = self._get_revision_paths(revnum)
+ revpaths = self.get_revision_paths(revnum)
if ascending:
next = (path, revnum+1)
@@ -215,42 +346,29 @@
assert revnum >= 0
self.fetch_revisions(revnum)
self.mutter("get previous %r:%r", path, revnum)
- if revnum == 0:
- return (None, -1)
- row = self.cachedb.execute("select action, copyfrom_path, copyfrom_rev from changed_path where path='%s' and rev=%d" % (path, revnum)).fetchone()
+ if path == "":
+ if revnum == 0:
+ return (None, -1)
+ return (path, revnum - 1)
+ row = self.cache.get_previous(path, revnum)
if row is None:
- return (None, -1)
- if row[2] == -1:
- if row[0] in ('A','R'):
- return (None, -1)
return (path, revnum-1)
- return (row[1], row[2])
+ (branch_path, action, copyfrom_path, copyfrom_rev) = row
+ branch_path = branch_path.encode('utf-8')
+ if copyfrom_path is not None:
+ copyfrom_path = copyfrom_path.encode('utf-8')
+ assert path == branch_path or path.startswith(branch_path + "/")
+ if copyfrom_rev == -1:
+ assert path == branch_path
+ return (None, -1) # newly added
+ return (copyfrom_path + path[len(branch_path):], copyfrom_rev)
- def _get_revision_paths(self, revnum):
+ def get_revision_paths(self, revnum):
if revnum == 0:
return {'': ('A', None, -1)}
-
self.fetch_revisions(revnum)
- query = "select path, action, copyfrom_path, copyfrom_rev from changed_path where rev="+str(revnum)
-
- paths = {}
- for p, act, cf, cr in self.cachedb.execute(query):
- if cf is not None:
- cf = cf.encode("utf-8")
- paths[p.encode("utf-8")] = (act, cf, cr)
- return paths
-
- def get_revision_paths(self, revnum):
- """Obtain dictionary with all the changes in a particular revision.
-
- :param revnum: Subversion revision number
- :returns: dictionary with paths as keys and
- (action, copyfrom_path, copyfrom_rev) as values.
- """
- self.mutter("revision paths: %r", revnum)
-
- return self._get_revision_paths(revnum)
+ return self.cache.get_revision_paths(revnum)
def revprop_list(self, revnum):
self.mutter("revprop list: %r", revnum)
@@ -258,10 +376,8 @@
self.fetch_revisions(revnum)
if revnum > 0:
- has_all_revprops = self.cachedb.execute("SELECT all_revprops FROM revinfo WHERE rev=?", (revnum,)).fetchone()[0]
- known_revprops = {}
- for k,v in self.cachedb.execute("select name, value from revprop where rev="+str(revnum)):
- known_revprops[k.encode("utf-8")] = v.encode("utf-8")
+ has_all_revprops = self.cache.has_all_revprops(revnum)
+ known_revprops = self.cache.get_revprops(revnum)
else:
has_all_revprops = False
known_revprops = {}
@@ -271,6 +387,10 @@
return lazy_dict(known_revprops, self._transport.revprop_list, revnum)
+ def changes_path(self, path, revnum):
+ self.fetch_revisions(revnum)
+ return self.cache.changes_path(path, revnum)
+
def fetch_revisions(self, to_revnum=None):
"""Fetch information about all revisions in the remote repository
until to_revnum.
@@ -301,15 +421,12 @@
if copyfrom_path is not None:
copyfrom_path = copyfrom_path.strip("/")
- self.cachedb.execute(
- "replace into changed_path (rev, path, action, copyfrom_path, copyfrom_rev) values (?, ?, ?, ?, ?)",
- (revision, p.strip("/"), orig_paths[p][0], copyfrom_path, orig_paths[p][2]))
+ self.cache.insert_path(revision, p.strip("/"), orig_paths[p][0], copyfrom_path, orig_paths[p][2])
for k,v in revprops.items():
- self.cachedb.execute("replace into revprop (rev, name, value) values (?,?,?)", (revision, k, v))
- self.cachedb.execute("replace into revinfo (rev, all_revprops) values (?,?)", (revision, todo_revprops is None))
+ self.cache.insert_revprop(revision, k, v)
+ self.cache.insert_revinfo(revision, todo_revprops is None)
self.saved_revnum = revision
- if self.saved_revnum % 5000 == 0:
- self.cachedb.commit()
+ self.cache.commit_conditionally()
self.mutter("get_log %d->%d", self.saved_revnum, to_revnum)
try:
@@ -322,7 +439,6 @@
raise
finally:
pb.finished()
- self.cachedb.commit()
def struct_revpaths_to_tuples(changed_paths):
@@ -429,6 +545,9 @@
raise NoSuchRevision(branch=self,
revision="Revision number %d" % revnum)
raise
+
+ def changes_path(self, path, revnum):
+ return self.get_revision_paths(revnum).has_key(path)
def find_children(self, path, revnum):
"""Find all children of path in revnum.
More information about the bazaar-commits
mailing list