| 1 | # -*- coding: iso-8859-1 -*- |
|---|
| 2 | # |
|---|
| 3 | # Copyright (C) 2005 Edgewall Software |
|---|
| 4 | # Copyright (C) 2005 Christopher Lenz <cmlenz@gmx.de> |
|---|
| 5 | # All rights reserved. |
|---|
| 6 | # |
|---|
| 7 | # This software is licensed as described in the file COPYING, which |
|---|
| 8 | # you should have received as part of this distribution. The terms |
|---|
| 9 | # are also available at http://trac.edgewall.com/license.html. |
|---|
| 10 | # |
|---|
| 11 | # This software consists of voluntary contributions made by many |
|---|
| 12 | # individuals. For the exact contribution history, see the revision |
|---|
| 13 | # history and logs, available at http://projects.edgewall.com/trac/. |
|---|
| 14 | # |
|---|
| 15 | # Author: Christopher Lenz <cmlenz@gmx.de> |
|---|
| 16 | |
|---|
| 17 | from __future__ import generators |
|---|
| 18 | |
|---|
| 19 | from trac.util import TracError |
|---|
| 20 | from trac.versioncontrol import Changeset, Node, Repository, Authorizer |
|---|
| 21 | |
|---|
| 22 | |
|---|
| 23 | _kindmap = {'D': Node.DIRECTORY, 'F': Node.FILE} |
|---|
| 24 | _actionmap = {'A': Changeset.ADD, 'C': Changeset.COPY, |
|---|
| 25 | 'D': Changeset.DELETE, 'E': Changeset.EDIT, |
|---|
| 26 | 'M': Changeset.MOVE} |
|---|
| 27 | |
|---|
| 28 | |
|---|
| 29 | class CachedRepository(Repository): |
|---|
| 30 | |
|---|
| 31 | def __init__(self, db, repos, authz, log): |
|---|
| 32 | Repository.__init__(self, repos.name, authz, log) |
|---|
| 33 | self.db = db |
|---|
| 34 | self.repos = repos |
|---|
| 35 | self.synced = 0 |
|---|
| 36 | |
|---|
| 37 | def close(self): |
|---|
| 38 | self.repos.close() |
|---|
| 39 | |
|---|
| 40 | def get_changeset(self, rev): |
|---|
| 41 | if not self.synced: |
|---|
| 42 | self.sync() |
|---|
| 43 | self.synced = 1 |
|---|
| 44 | return CachedChangeset(self.repos.normalize_rev(rev), self.db, |
|---|
| 45 | self.authz) |
|---|
| 46 | |
|---|
| 47 | def sync(self): |
|---|
| 48 | self.log.debug("Checking whether sync with repository is needed") |
|---|
| 49 | cursor = self.db.cursor() |
|---|
| 50 | |
|---|
| 51 | # -- repository used for populating the cache |
|---|
| 52 | cursor.execute("SELECT value FROM system WHERE name='repository_dir'") |
|---|
| 53 | row = cursor.fetchone() |
|---|
| 54 | if row: |
|---|
| 55 | previous_repository_dir = row[0] |
|---|
| 56 | else: # no 'repository_dir' stored yet, assume everything's OK |
|---|
| 57 | previous_repository_dir = self.name |
|---|
| 58 | |
|---|
| 59 | if self.name != previous_repository_dir: |
|---|
| 60 | raise TracError, ("The 'repository_dir' has changed, " |
|---|
| 61 | "a 'trac-admin resync' operation is needed.") |
|---|
| 62 | |
|---|
| 63 | youngest_stored = self.repos.get_youngest_rev_in_cache(self.db) |
|---|
| 64 | |
|---|
| 65 | if youngest_stored != str(self.repos.youngest_rev): |
|---|
| 66 | authz = self.repos.authz |
|---|
| 67 | self.repos.authz = Authorizer() # remove permission checking |
|---|
| 68 | |
|---|
| 69 | kindmap = dict(zip(_kindmap.values(), _kindmap.keys())) |
|---|
| 70 | actionmap = dict(zip(_actionmap.values(), _actionmap.keys())) |
|---|
| 71 | self.log.info("Syncing with repository (%s to %s)" |
|---|
| 72 | % (youngest_stored, self.repos.youngest_rev)) |
|---|
| 73 | if youngest_stored: |
|---|
| 74 | try: |
|---|
| 75 | current_rev = self.repos.next_rev(youngest_stored) |
|---|
| 76 | except ValueError: |
|---|
| 77 | current_rev = int(youngest_stored) + 1 # for scoped repos. |
|---|
| 78 | else: |
|---|
| 79 | try: |
|---|
| 80 | current_rev = self.repos.oldest_rev |
|---|
| 81 | current_rev = self.repos.normalize_rev(current_rev) |
|---|
| 82 | except TracError: |
|---|
| 83 | current_rev = None |
|---|
| 84 | while current_rev is not None: |
|---|
| 85 | changeset = self.repos.get_changeset(current_rev) |
|---|
| 86 | cursor.execute("INSERT INTO revision (rev,time,author,message) " |
|---|
| 87 | "VALUES (%s,%s,%s,%s)", (str(current_rev), |
|---|
| 88 | changeset.date, changeset.author, |
|---|
| 89 | changeset.message)) |
|---|
| 90 | for path,kind,action,base_path,base_rev in changeset.get_changes(): |
|---|
| 91 | self.log.debug("Caching node change in [%s]: %s" |
|---|
| 92 | % (current_rev, (path, kind, action, |
|---|
| 93 | base_path, base_rev))) |
|---|
| 94 | kind = kindmap[kind] |
|---|
| 95 | action = actionmap[action] |
|---|
| 96 | cursor.execute("INSERT INTO node_change (rev,path,kind," |
|---|
| 97 | "change,base_path,base_rev) " |
|---|
| 98 | "VALUES (%s,%s,%s,%s,%s,%s)", |
|---|
| 99 | (str(current_rev), path, kind, action, |
|---|
| 100 | base_path, base_rev)) |
|---|
| 101 | current_rev = self.repos.next_rev(current_rev) |
|---|
| 102 | self.db.commit() |
|---|
| 103 | self.repos.authz = authz # restore permission checking |
|---|
| 104 | |
|---|
| 105 | def get_node(self, path, rev=None): |
|---|
| 106 | return self.repos.get_node(path, rev) |
|---|
| 107 | |
|---|
| 108 | def has_node(self, path, rev): |
|---|
| 109 | return self.repos.has_node(path, rev) |
|---|
| 110 | |
|---|
| 111 | def get_oldest_rev(self): |
|---|
| 112 | return self.repos.oldest_rev |
|---|
| 113 | |
|---|
| 114 | def get_youngest_rev(self): |
|---|
| 115 | return self.repos.youngest_rev |
|---|
| 116 | |
|---|
| 117 | def previous_rev(self, rev): |
|---|
| 118 | return self.repos.previous_rev(rev) |
|---|
| 119 | |
|---|
| 120 | def next_rev(self, rev): |
|---|
| 121 | return self.repos.next_rev(rev) |
|---|
| 122 | |
|---|
| 123 | def rev_older_than(self, rev1, rev2): |
|---|
| 124 | return self.repos.rev_older_than(rev1, rev2) |
|---|
| 125 | |
|---|
| 126 | def get_path_history(self, path, rev=None, limit=None): |
|---|
| 127 | return self.repos.get_path_history(path, rev, limit) |
|---|
| 128 | |
|---|
| 129 | def normalize_path(self, path): |
|---|
| 130 | return self.repos.normalize_path(path) |
|---|
| 131 | |
|---|
| 132 | def normalize_rev(self, rev): |
|---|
| 133 | return self.repos.normalize_rev(rev) |
|---|
| 134 | |
|---|
| 135 | |
|---|
| 136 | class CachedChangeset(Changeset): |
|---|
| 137 | |
|---|
| 138 | def __init__(self, rev, db, authz): |
|---|
| 139 | self.db = db |
|---|
| 140 | self.authz = authz |
|---|
| 141 | cursor = self.db.cursor() |
|---|
| 142 | cursor.execute("SELECT time,author,message FROM revision " |
|---|
| 143 | "WHERE rev=%s", (rev,)) |
|---|
| 144 | row = cursor.fetchone() |
|---|
| 145 | if row: |
|---|
| 146 | date, author, message = row |
|---|
| 147 | Changeset.__init__(self, rev, message, author, int(date)) |
|---|
| 148 | else: |
|---|
| 149 | raise TracError, "No changeset %s in the repository" % rev |
|---|
| 150 | |
|---|
| 151 | def get_changes(self): |
|---|
| 152 | cursor = self.db.cursor() |
|---|
| 153 | cursor.execute("SELECT path,kind,change,base_path,base_rev " |
|---|
| 154 | "FROM node_change WHERE rev=%s " |
|---|
| 155 | "ORDER BY path", (self.rev,)) |
|---|
| 156 | for path, kind, change, base_path, base_rev in cursor: |
|---|
| 157 | if not self.authz.has_permission(path): |
|---|
| 158 | # FIXME: what about the base_path? |
|---|
| 159 | continue |
|---|
| 160 | kind = _kindmap[kind] |
|---|
| 161 | change = _actionmap[change] |
|---|
| 162 | yield path, kind, change, base_path, base_rev |
|---|