Go to:
Gentoo Home
Documentation
Forums
Lists
Bugs
Planet
Store
Wiki
Get Gentoo!
Gentoo's Bugzilla – Attachment 73644 Details for
Bug 109477
new ebuild for trac with darcs/bzr support
Home
|
New
–
[Ex]
|
Browse
|
Search
|
Privacy Policy
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
www-apps/trac/files/0.9-generalization.patch
0.9-generalization.patch (text/plain), 60.53 KB, created by
Julien Allanos (RETIRED)
on 2005-11-26 10:53:14 UTC
(
hide
)
Description:
www-apps/trac/files/0.9-generalization.patch
Filename:
MIME Type:
Creator:
Julien Allanos (RETIRED)
Created:
2005-11-26 10:53:14 UTC
Size:
60.53 KB
patch
obsolete
>diff -N -ur -x_darcs trac/trac/db_default.py trac+darcs/trac/db_default.py >--- trac/trac/db_default.py 2005-11-17 12:26:10.838697816 +0100 >+++ trac+darcs/trac/db_default.py 2005-11-17 12:26:34.999024888 +0100 >@@ -82,6 +82,7 @@ > Column('time', type='int'), > Column('author'), > Column('message'), >+ Column('hash'), > Index(['time'])], > Table('node_change', key=('rev', 'path', 'change'))[ > Column('rev'), >diff -N -ur -x_darcs trac/trac/env.py trac+darcs/trac/env.py >--- trac/trac/env.py 2005-11-17 12:26:10.838697816 +0100 >+++ trac+darcs/trac/env.py 2005-11-17 12:26:34.999024888 +0100 >@@ -150,17 +150,8 @@ > > @param authname: user name for authorization > """ >- from trac.versioncontrol.cache import CachedRepository >- from trac.versioncontrol.svn_authz import SubversionAuthorizer >- from trac.versioncontrol.svn_fs import SubversionRepository >- repos_dir = self.config.get('trac', 'repository_dir') >- if not repos_dir: >- raise EnvironmentError, 'Path to repository not configured' >- authz = None >- if authname: >- authz = SubversionAuthorizer(self, authname) >- repos = SubversionRepository(repos_dir, authz, self.log) >- return CachedRepository(self.get_db_cnx(), repos, authz, self.log) >+ from trac.versioncontrol import get_repository >+ return get_repository(self, authname) > > def create(self, db_str=None): > """Create the basic directory structure of the environment, initialize >@@ -350,6 +341,20 @@ > err = 'No upgrade module for version %i (%s.py)' % (i, name) > raise TracError, err > script.do_upgrade(self.env, i, cursor) >+ >+ # Allow micro-upgrades: this is mainly intended to allow third >+ # parties fixups >+ from string import ascii_lowercase >+ for l in ascii_lowercase: >+ microname = name + l >+ try: >+ upgrades = __import__('upgrades', globals(), locals(), >+ [microname]) >+ script = getattr(upgrades, microname) >+ except AttributeError: >+ break >+ script.do_upgrade(self.env, i, cursor) >+ > cursor.execute("UPDATE system SET value=%s WHERE " > "name='database_version'", (db_default.db_version,)) > self.log.info('Upgraded database version from %d to %d', >diff -N -ur -x_darcs trac/trac/upgrades/db14a.py trac+darcs/trac/upgrades/db14a.py >--- trac/trac/upgrades/db14a.py 1970-01-01 01:00:00.000000000 +0100 >+++ trac+darcs/trac/upgrades/db14a.py 2005-11-17 12:26:35.048017440 +0100 >@@ -0,0 +1,16 @@ >+sql = [ >+#-- Make the revision contain an hash, and suggest a resync >+"""DROP TABLE revision;""", >+"""CREATE TABLE revision ( >+ rev text PRIMARY KEY, >+ time integer, >+ author text, >+ message text, >+ hash text >+);""", >+] >+ >+def do_upgrade(env, ver, cursor): >+ for s in sql: >+ cursor.execute(s) >+ print 'Please perform a "resync" after this upgrade.' >diff -N -ur -x_darcs trac/trac/versioncontrol/api.py trac+darcs/trac/versioncontrol/api.py >--- trac/trac/versioncontrol/api.py 2005-11-17 12:26:10.863694016 +0100 >+++ trac+darcs/trac/versioncontrol/api.py 2005-11-17 12:26:35.052016832 +0100 >@@ -210,13 +210,32 @@ > > def get_changes(self): > """ >- Generator that produces a (path, kind, change, base_rev, base_path) >+ Generator that produces a (path, kind, change, base_path, base_rev) > tuple for every change in the changeset, where change can be one of > Changeset.ADD, Changeset.COPY, Changeset.DELETE, Changeset.EDIT or > Changeset.MOVE, and kind is one of Node.FILE or Node.DIRECTORY. > """ > raise NotImplementedError > >+ def insert_in_cache(self, cursor, kindmap, actionmap, log): >+ """ >+ Insert this changeset in the cache db. >+ """ >+ cursor.execute("INSERT INTO revision (rev,time,author,message) " >+ "VALUES (%s,%s,%s,%s)", (str(self.rev), >+ self.date, self.author, >+ self.message)) >+ for path,kind,action,base_path,base_rev in self.get_changes(): >+ log.debug("Caching node change in [%s]: %s" >+ % (self.rev, (path, kind, action, >+ base_path, base_rev))) >+ kind = kindmap[kind] >+ action = actionmap[action] >+ cursor.execute("INSERT INTO node_change (rev,path,kind," >+ "change,base_path,base_rev) " >+ "VALUES (%s,%s,%s,%s,%s,%s)", >+ (str(self.rev), path, kind, action, >+ base_path, base_rev)) > > class PermissionDenied(PermissionError): > """ >@@ -249,3 +268,55 @@ > def has_permission_for_changeset(self, rev): > return 1 > >+ >+def get_repository(env, authname): >+ """ >+ Return the right repository backend wrapped by a CachedRepository. >+ >+ This looks for a ``darcs:`` or ``bzr:`` prefix on the >+ 'repository_dir' from the configuration: if present it qualifies >+ respectively a Darcs or a Bazaar-NG repository, otherwise it uses >+ the Subversion backend. >+ """ >+ >+ authz = None >+ repos_dir = env.config.get('trac', 'repository_dir') >+ db = env.get_db_cnx() >+ if not repos_dir: >+ raise EnvironmentError, 'Path to repository not configured' >+ if repos_dir.startswith('darcs:'): >+ from trac.versioncontrol.darcs import DarcsRepository, \ >+ DarcsCachedRepository as CachedRepository >+ repos = DarcsRepository(db, repos_dir[6:], env.log, env.config) >+ elif repos_dir.startswith('bzr:'): >+ from trac.versioncontrol.bzr import BzrRepository, \ >+ BzrCachedRepository as CachedRepository >+ #from trac.versioncontrol.cache import CachedRepository >+ repos = BzrRepository(db, repos_dir[4:], env.log) >+ else: >+ from trac.versioncontrol.svn_authz import SubversionAuthorizer >+ from trac.versioncontrol.svn_fs import SubversionRepository >+ from trac.versioncontrol.cache import CachedRepository >+ if authname: >+ authz = SubversionAuthorizer(env, authname) >+ repos = SubversionRepository(repos_dir, authz, env.log) >+ return CachedRepository(db, repos, authz, env.log) >+ >+ >+def get_authorizer(env, authname): >+ """ >+ Return the right authorizer for the configured repository. >+ >+ Like ``get_repository()`` this looks for a ``darcs:`` or ``bzr:`` >+ prefix on the 'repository_dir' setting: if present it returns >+ a basic Authorizer(), otherwise a SubversionAuthorizer. >+ """ >+ >+ authz = Authorizer() >+ repos_dir = env.config.get('trac', 'repository_dir') >+ if not repos_dir.startswith('darcs:') \ >+ and not repos_dir.startswith('bzr:'): >+ from trac.versioncontrol.svn_authz import SubversionAuthorizer >+ if authname: >+ authz = SubversionAuthorizer(env, authname) >+ return authz >diff -N -ur -x_darcs trac/trac/versioncontrol/bzr.py trac+darcs/trac/versioncontrol/bzr.py >--- trac/trac/versioncontrol/bzr.py 1970-01-01 01:00:00.000000000 +0100 >+++ trac+darcs/trac/versioncontrol/bzr.py 2005-11-17 12:26:35.186996312 +0100 >@@ -0,0 +1,383 @@ >+# -*- coding: iso-8859-1 -*- >+# >+# Copyright (C) 2005 Edgewall Software >+# Copyright (C) 2005 Johan Rydberg <jrydberg@gnu.org> >+# >+# This software is licensed as described in the file COPYING, which >+# you should have received as part of this distribution. The terms >+# are also available at http://trac.edgewall.com/license.html. >+# >+# This software consists of voluntary contributions made by many >+# individuals. For the exact contribution history, see the revision >+# history and logs, available at http://projects.edgewall.com/trac/. >+# >+# Author: Johan Rydberg <jrydberg@gnu.org> >+ >+from __future__ import generators >+ >+from trac.util import TracError, NaivePopen >+from trac.versioncontrol import Changeset, Node, Repository >+from trac.versioncontrol.cache import CachedRepository, CachedChangeset >+ >+import os >+ >+ >+class BzrRepository(Repository): >+ """ >+ A Bazaar-NG repository. >+ """ >+ def __init__(self, db, path, log): >+ from bzrlib.branch import Branch >+ self.branch = Branch.open(path) >+ self.db = db >+ Repository.__init__(self, path, None, log) >+ >+ def get_base_rev(self, file_id, start_rev = None): >+ """ >+ Lookup what revision file with file id specified by file_id was >+ last modified by. start_rev gives the revision where to start >+ scanning from (in reverese order.) start_rev is _EXCLUSIVE_. >+ """ >+ def _enumerate_history(branch): >+ rh = [] >+ revno = 1 >+ for rev_id in branch.revision_history(): >+ rh.append((revno, rev_id)) >+ revno += 1 >+ return rh >+ >+ if start_rev: >+ start_rev = self.normalize_rev(start_rev) >+ if start_rev and start_rev <= 1: >+ return 0 >+ >+ which_revs = _enumerate_history(self.branch) >+ if start_rev: >+ which_revs = which_revs[:start_rev - 1] >+ >+ which_revs.reverse() >+ >+ for revno, rev_id in which_revs: >+ delta = self.branch.get_revision_delta(revno) >+ >+ if delta.touches_file_id(file_id): >+ return revno >+ >+ return 0 >+ >+ def get_oldest_rev(self): >+ """ >+ Revisions are numbered from 1 to the highest revision number, >+ return by branch.revno(). >+ """ >+ return 1 >+ >+ def get_youngest_rev(self): >+ """ >+ Return the youngest revision in the repository. >+ """ >+ return self.branch.revno() >+ >+ def get_kind(self, path, rev): >+ """ >+ Determine the kind of the path at given revision. >+ """ >+ >+ kind = 'F' >+ if not path.endswith('/') and path != "": >+ cursor = self.db.cursor() >+ cursor.execute("SELECT kind " >+ "FROM node_change " >+ "WHERE ((LENGTH(rev)<LENGTH(%s)) OR " >+ " (LENGTH(rev)=LENGTH(%s) AND rev<=%s)) " >+ " AND path=%s " >+ "ORDER BY -LENGTH(rev),rev DESC " >+ "LIMIT 1", (rev, rev, rev, path)) >+ val = cursor.fetchone() >+ if val: >+ kind, = val >+ if kind == 'D': >+ kind = Node.DIRECTORY >+ else: >+ kind = Node.FILE >+ else: >+ kind = Node.DIRECTORY >+ return kind >+ >+ def get_node(self, path, rev): >+ """ >+ Retrieve a Node (directory or file) from the repository at the >+ given path. If the rev parameter is specified, the version of the >+ node at that revision is returned, otherwise the latest version >+ of the node is returned. >+ """ >+ rev = self.normalize_rev(rev) >+ path = self.normalize_path(path) >+ kind = self.get_kind(path, rev) >+ >+ if kind == Node.DIRECTORY: >+ cursor = self.db.cursor() >+ cursor.execute("SELECT rev,change " >+ " FROM node_change " >+ "WHERE ((LENGTH(rev)<LENGTH(%s)) OR " >+ " (LENGTH(rev)=LENGTH(%s) AND rev<=%s)) " >+ " AND path LIKE %s " >+ "ORDER BY -LENGTH(rev),rev DESC " >+ "LIMIT 1", (rev, rev, rev, path+'%')) >+ lastchange = cursor.fetchone() >+ if lastchange: >+ rev, change = lastchange >+ else: >+ raise TracError, "No node at %r in revision %s" % (path, rev) >+ else: >+ cursor = self.db.cursor() >+ cursor.execute("SELECT rev FROM node_change " >+ " WHERE((LENGTH(rev)<LENGTH(%d)) OR" >+ " (LENGTH(rev)=LENGTH(%d) AND rev<=%d))" >+ " AND path='%s' " >+ "ORDER BY -LENGTH(rev),rev DESC LIMIT 1;" >+ % (rev, rev, rev, path)) >+ val = cursor.fetchone() >+ if val: >+ rev, = val >+ >+ rev = self.normalize_rev(rev) >+ return BzrNode(self, path, kind, rev) >+ >+ def get_path_history(self, path, rev=None, limit=None): >+ """ >+ Retrieve all the revisions containing this path (no newer than 'rev'). >+ The result format should be the same as the one of Node.get_history() >+ """ >+ node = self.get_node(path, rev) >+ return node.get_history() >+ >+ def get_changeset(self, rev): >+ """ >+ Retrieve a Changeset object that describes the changes made in >+ revision 'rev'. >+ """ >+ revobj = self.branch.get_revision(self.branch.get_rev_id(rev)) >+ return BzrChangeset(self, revobj, rev) >+ >+ def normalize_path(self, path): >+ """ >+ Return a canonical representation of path in the repos. >+ """ >+ if not path: >+ return '' >+ >+ if path.startswith('/'): >+ return path[1:] >+ else: >+ return path >+ >+ def normalize_rev(self, rev): >+ """ >+ Return a canonical representation of a revision in the repos. >+ 'None' is a valid revision value and represents the youngest revision. >+ """ >+ try: >+ rev = int(rev) >+ except (ValueError, TypeError): >+ rev = None >+ if rev is None: >+ rev = self.get_youngest_rev() >+ elif rev > self.get_youngest_rev(): >+ rev = self.get_youngest_rev() >+ return rev >+ >+ def previous_rev(self, rev): >+ """ >+ Return the revision immediately preceding the specified revision. >+ """ >+ rev = self.normalize_rev(rev) >+ if rev == 1: >+ return None >+ else: >+ return rev-1 >+ >+ def next_rev(self, rev): >+ """ >+ Return the revision immediately following the specified revision. >+ """ >+ rev = self.normalize_rev(rev) >+ if rev < self.get_youngest_rev(): >+ return rev+1 >+ else: >+ return None >+ >+ def rev_older_than(self, rev1, rev2): >+ """ >+ Return True if rev1 is older than rev2, i.e. if rev1 comes before rev2 >+ in the revision sequence. >+ """ >+ return self.normalize_rev(rev1) < self.normalize_rev(rev2) >+ >+class BzrNode(Node): >+ def __init__(self, repo, path, kind, rev): >+ Node.__init__(self, path, rev, kind) >+ self.repo = repo >+ revision_id = repo.branch.get_rev_id(rev) >+ self.tree = repo.branch.revision_tree(revision_id) >+ >+ def get_history(self, limit=None): >+ """ >+ Generator that yields (path, rev, chg) tuples, one for each >+ revision in which the node was changed. This generator will >+ follow copies and moves of a node (if the underlying version >+ control system supports that), which will be indicated by the >+ first element of the tuple (i.e. the path) changing. >+ """ >+ >+ cursor = self.repo.db.cursor() >+ >+ rev = self.rev >+ path = self.path >+ >+ actions = { 'A': Changeset.ADD, >+ 'E': Changeset.EDIT, >+ 'M': Changeset.MOVE, >+ 'D': Changeset.DELETE } >+ >+ if self.kind == Node.DIRECTORY: >+ revdone = {} >+ cursor.execute("SELECT rev,change,base_path" >+ " FROM node_change " >+ "WHERE ((LENGTH(rev)<LENGTH(%d)) OR " >+ " (LENGTH(rev)=LENGTH(%d) AND rev<=%d)) " >+ " AND path LIKE %s " >+ "ORDER BY -LENGTH(rev),rev DESC ", >+ (rev, rev, rev, path+'%')) >+ path = path.rstrip('/') >+ for row in cursor: >+ rev, change, base_path = row >+ if not rev in revdone: >+ revdone[rev] = True >+ yield path, rev, actions[change] >+ if limit: >+ limit -= 1 >+ if limit == 0: >+ break >+ else: >+ while rev>=1: >+ cursor.execute("SELECT kind,change,base_path,base_rev " >+ "FROM node_change WHERE rev=%d AND path=%s", >+ (rev, path)) >+ base_rev = None >+ row = cursor.fetchone() >+ if row: >+ kind, change, base_path, base_rev = row >+ yield path, rev, actions[change] >+ if limit: >+ limit -= 1 >+ if limit == 0: >+ break >+ base_rev = base_rev and int(base_rev) or 0 >+ path = base_path >+ >+ if base_rev is None: >+ rev -= 1 >+ else: >+ rev = int(base_rev) >+ >+ def get_content(self): >+ """ >+ Return a stream for reading the content of the node. This method >+ will return None for directories. The returned object should provide >+ a read([len]) function. >+ """ >+ return self.tree.get_file(self.tree.inventory.path2id(self.path)) >+ >+ def get_entries(self): >+ """ >+ Generator that yields the immediate child entries of a directory, in no >+ particular order. If the node is a file, this method returns None. >+ """ >+ if self.kind == Node.DIRECTORY: >+ file_id = self.tree.inventory.path2id(self.path) >+ ie = self.tree.inventory[file_id] >+ for x, kid in ie.sorted_children(): >+ yield self.repo.get_node(os.path.join(self.path, kid.name), >+ self.rev) >+ >+ def get_name(self): >+ return os.path.split(self.path)[1] >+ >+ def get_content_length(self): >+ if self.kind == Node.DIRECTORY: >+ return None >+ >+ file_id = self.tree.inventory.path2id(self.path) >+ ie = self.tree.inventory[file_id] >+ return ie.text_size >+ >+ def get_content_type(self): >+ if self.kind == Node.DIRECTORY: >+ return None >+ # FIXME: guess type here. >+ return None >+ >+ def get_last_modified(self): >+ # FIXME: add >+ return 0 >+ >+ def get_properties(self): >+ """ >+ Returns a dictionary containing the properties (meta-data) of the node. >+ The set of properties depends on the version control system. >+ """ >+ return {} >+ >+ >+class BzrChangeset(Changeset): >+ def __init__(self, repo, rev, revno): >+ """ >+ Initialize bazaar changeset. rev is the revision object provided >+ by bzrlib, revno is its revision number. >+ """ >+ self.revobj = rev >+ self.repo = repo >+ Changeset.__init__(self, revno, rev.message.encode('utf-8'), >+ rev.committer, rev.timestamp) >+ >+ def get_changes(self): >+ """ >+ Generator that produces a (path, kind, change, base_path, base_rev) >+ tuple for every change in the changeset, where change can be one of >+ Changeset.ADD, Changeset.COPY, Changeset.DELETE, Changeset.EDIT or >+ Changeset.MOVE, and kind is one of Node.FILE or Node.DIRECTORY. >+ """ >+ delta = self.repo.branch.get_revision_delta(self.rev) >+ >+ kinds = { 'file': Node.FILE, 'directory': Node.DIRECTORY, >+ 'root_directory': Node.DIRECTORY } >+ >+ for path, file_id, kind in delta.added: >+ base_rev = self.repo.get_base_rev(file_id, self.rev) >+ yield path, kinds[kind], Changeset.ADD, path, base_rev >+ >+ for path, file_id, kind, _, _ in delta.modified: >+ base_rev = self.repo.get_base_rev(file_id, self.rev) >+ yield path, kinds[kind], Changeset.EDIT, path, base_rev >+ >+ for path, file_id, kind in delta.removed: >+ base_rev = self.repo.get_base_rev(file_id, self.rev) >+ yield path, kinds[kind], Changeset.DELETE, path, base_rev >+ >+ for old_path, new_path, file_id, kind, modified, _ in delta.renamed: >+ base_rev = self.repo.get_base_rev(file_id, self.rev) >+ yield new_path, kinds[kind], Changeset.MOVE, old_path, base_rev >+ >+class BzrCachedRepository(CachedRepository): >+ """ >+ Bazaar-NG version of the cached repository, that serves BzrCachedChangesets >+ """ >+ >+ def get_changeset(self, rev): >+ if not self.synced: >+ self.sync() >+ self.synced = 1 >+ return CachedChangeset(self.repos.normalize_rev(rev), self.db, >+ self.authz) >+ >diff -N -ur -x_darcs trac/trac/versioncontrol/cache.py trac+darcs/trac/versioncontrol/cache.py >--- trac/trac/versioncontrol/cache.py 2005-11-17 12:26:10.863694016 +0100 >+++ trac+darcs/trac/versioncontrol/cache.py 2005-11-17 12:26:35.053016680 +0100 >@@ -76,21 +76,7 @@ > current_rev = self.repos.oldest_rev > while current_rev is not None: > changeset = self.repos.get_changeset(current_rev) >- cursor.execute("INSERT INTO revision (rev,time,author,message) " >- "VALUES (%s,%s,%s,%s)", (str(current_rev), >- changeset.date, changeset.author, >- changeset.message)) >- for path,kind,action,base_path,base_rev in changeset.get_changes(): >- self.log.debug("Caching node change in [%s]: %s" >- % (current_rev, (path, kind, action, >- base_path, base_rev))) >- kind = kindmap[kind] >- action = actionmap[action] >- cursor.execute("INSERT INTO node_change (rev,path,kind," >- "change,base_path,base_rev) " >- "VALUES (%s,%s,%s,%s,%s,%s)", >- (str(current_rev), path, kind, action, >- base_path, base_rev)) >+ changeset.insert_in_cache(cursor, kindmap, actionmap, self.log) > current_rev = self.repos.next_rev(current_rev) > self.db.commit() > self.repos.authz = authz # restore permission checking >diff -N -ur -x_darcs trac/trac/versioncontrol/darcs.py trac+darcs/trac/versioncontrol/darcs.py >--- trac/trac/versioncontrol/darcs.py 1970-01-01 01:00:00.000000000 +0100 >+++ trac+darcs/trac/versioncontrol/darcs.py 2005-11-17 12:26:35.187996160 +0100 >@@ -0,0 +1,951 @@ >+# -*- coding: iso-8859-1 -*- >+# >+# Copyright (C) 2005 Edgewall Software >+# Copyright (C) 2005 Lele Gaifax <lele@metapensiero.it> >+# >+# This software is licensed as described in the file COPYING, which >+# you should have received as part of this distribution. The terms >+# are also available at http://trac.edgewall.com/license.html. >+# >+# This software consists of voluntary contributions made by many >+# individuals. For the exact contribution history, see the revision >+# history and logs, available at http://projects.edgewall.com/trac/. >+# >+# Author: Lele Gaifax <lele@metapensiero.it> >+ >+from __future__ import generators >+ >+from trac.util import TracError, NaivePopen >+from trac.versioncontrol import Changeset, Node, Repository >+from trac.versioncontrol.cache import CachedRepository, CachedChangeset >+from os import listdir, makedirs, utime, stat >+from os.path import join, isdir, split, exists >+from time import gmtime, mktime, strftime, timezone >+from shutil import copyfile >+from mimetypes import guess_type >+ >+# Python 2.3+ compatibility >+try: >+ reversed >+except: >+ def reversed(x): >+ if hasattr(x, 'keys'): >+ raise ValueError("mappings do not support reverse iteration") >+ i = len(x) >+ while i > 0: >+ i -= 1 >+ yield x[i] >+ >+class DarcsRepository(Repository): >+ """ >+ Darcs concrete implementation of a Repository. >+ >+ Darcs (http://www.abridgegame.org/darcs/) is a distribuited SCM, >+ patch-centric instead of snapshot-centric like Subversion. The >+ approach here is to assume that the history in the repository >+ followed by Trac is immutable, which is not a requirement for the >+ underlaying darcs. This means that on that repository should never >+ be executed a `darcs unpull`, or `darcs unrecord` and the like. >+ >+ Given it's nature, this class tries to cache as much as possible >+ the requests coming from the above Trac machinery, to avoid or >+ minimize the external calls to Darcs that tend to be somewhat time >+ expensive. In particular, _getNodeContent() caches any particular >+ version of any file Trac asks to it, kept on the filesystem, >+ inside the ``_darcs`` metadir of the controlled repository in the >+ directory ``trac_cache`` (or where specified by the option >+ ``cachedir`` in the section ``darcs`` of the configuration. This >+ may require some kind of control over the size of the cache, that >+ OTOH may be completely removed whenever needed, it will be >+ recreated as soon as the first request come in. >+ """ >+ >+ def __init__(self, db, path, log, config): >+ Repository.__init__(self, path, None, log) >+ self.path = path >+ self.db = db >+ self.__youngest_rev = 0 >+ self.__history = None >+ self.__history_start = 0 >+ self.__no_pristine = exists(join(self.path, '_darcs', 'current.none')) >+ self.__darcs = config.get('darcs', 'command', 'darcs') >+ if config.get('darcs', 'dont_escape_8bit'): >+ self.__darcs = "DARCS_DONT_ESCAPE_8BIT=1 " + self.__darcs >+ self.__cachedir = config.get('darcs', 'cachedir', >+ join(self.path, '_darcs', 'trac_cache')) >+ >+ ## Low level stuff: CamelCase is used to avoid clash with inherited >+ ## interface namespace. >+ >+ def _darcs (self, command, args=''): >+ """ >+ Execute a some query on the repository running an external darcs. >+ >+ Return the XML output of the command. >+ """ >+ >+ command = "cd %r; TZ=UTC %s %s %s" % \ >+ (self.path, self.__darcs, command, args) >+ self.log.debug(command) >+ np = NaivePopen (command, capturestderr=True) >+ if np.errorlevel: >+ err = 'Running (%s) failed: %s, %s.' % (command, >+ np.errorlevel, np.err) >+ raise TracError (err, title='Darcs execution failed') >+ return np.out >+ >+ def _changes(self, args, startfrom=1): >+ """ >+ Get changes information parsing the XML output of ``darcs changes``. >+ >+ Return a sequence of Changesets. >+ """ >+ >+ return changesets_from_darcschanges( >+ self._darcs('changes --reverse --summary --xml', args), >+ self, startfrom) >+ >+ def _diff(self, path, rev1, rev2=None, patch=None): >+ """ >+ Return a darcs diff between two revisions. >+ """ >+ >+ diff = "diff --unified" >+ rev1 = self.normalize_rev(rev1) >+ cset1 = DarcsCachedChangeset(rev1, self.db) >+ diff += " --from-match 'hash %s'" % cset1.hash >+ if rev2: >+ rev2 = self.normalize_rev(rev2) >+ cset2 = DarcsCachedChangeset(rev2, self.db) >+ diff += " --to-match 'hash %s'" % cset2.hash >+ if patch: >+ path = path + patch >+ return self._darcs(diff, path) >+ >+ def _parseInventory(self, inventory): >+ """ >+ Parse an inventory, and return a dictionary of its content. >+ """ >+ >+ from sha import new >+ >+ csmap = {} >+ start = 0 >+ length = len(inventory) >+ index = self.__youngest_rev >+ while start<length: >+ start = inventory.find('[', start) >+ if start<0: >+ break >+ end = inventory.index('\n', start) >+ patchname = inventory[start+1:end] >+ start = end >+ end = inventory.index('*', start) >+ author = inventory[start+1:end] >+ inv = inventory[end+1] == '*' and 'f' or 't' >+ date = inventory[end+2:end+16] >+ y = int(date[:4]) >+ m = int(date[4:6]) >+ d = int(date[6:8]) >+ hh = int(date[8:10]) >+ mm = int(date[10:12]) >+ ss = int(date[12:14]) >+ unixtime = int(mktime((y, m, d, hh, mm, ss, 0, 0, 0)))-timezone >+ end += 16 >+ log = '' >+ if inventory[end] <> ']': >+ start = end >+ end = inventory.index('\n', start+1) >+ while True: >+ log += inventory[start+2:end] >+ start = end >+ if inventory[end+1] == ']': >+ break >+ end = inventory.index('\n', start+1) >+ end += 1 >+ >+ index += 1 >+ phash = new() >+ phash.update(patchname) >+ phash.update(author) >+ phash.update(date) >+ phash.update(log) >+ phash.update(inv) >+ patchid = '%s-%s-%s.gz' % (date, >+ new(author).hexdigest()[:5], >+ phash.hexdigest()) >+ csmap[index] = patchid >+ csmap[patchid] = index >+ start = end+1 >+ self.__youngest_rev = index >+ return csmap >+ >+ def _loadChangesetsIndex(self): >+ """ >+ Load the index of changesets in the repository, assigning an unique >+ integer number to each one, ala Subversion, for easier references. >+ >+ This is done by parsing the ``_darcs/inventory`` file using the >+ position of each changeset as its `revision number`, **assuming** >+ that **nobody's never** going to do anything that alters its order, >+ such as ``darcs optimize`` or ``darcs unpull``. >+ """ >+ >+ self.__youngest_rev = 0 >+ inventories = [join(self.path, '_darcs', 'inventories', i) for i >+ in listdir(join(self.path, '_darcs', 'inventories'))] >+ inventories.sort() >+ inventories.append(join(self.path, '_darcs', 'inventory')) >+ for inventory in inventories: >+ f = open(inventory, 'rU') >+ try: >+ index = self._parseInventory(f.read()) >+ finally: >+ f.close() >+ >+ ## Medium level, work-horse methods >+ >+ def _getCachedContentLocation(self, node): >+ """ >+ Return the location of the cache for the given node. If it does >+ not exist, compute it by applying a diff to the current version. >+ This may return None, if the node does not actually exist. >+ """ >+ >+ rev = self.normalize_rev(node.rev) >+ >+ if self.__no_pristine: >+ current = join(self.path, node.path) >+ else: >+ current = join(self.path, '_darcs', 'current', node.path) >+ >+ # Iterate over history to find out which is the revision of >+ # the given path that last changed the it. We need to find >+ # both a 'last revision' and 'second last', because later >+ # we may apply either a r1:last diff or a 2nd:current diff. >+ history = self._getPathHistory(node.path, None) >+ try: >+ lastnode = history.next() >+ except StopIteration: >+ lastnode = None >+ >+ if lastnode is None: >+ return None >+ elif lastnode.rev <= rev: >+ # Content hasn't changed, return current version >+ if exists(current): >+ return current >+ >+ prevlast = lastnode >+ for oldnode in history: >+ if oldnode.rev <= rev: >+ lastnode = oldnode >+ break >+ prevlast = oldnode >+ >+ cachedir = join(self.__cachedir, str(lastnode.rev)) >+ cache = join(cachedir, lastnode.path) >+ >+ # One may never know: should by any chance an absolute path survived >+ # in lastnode.path, or in some clever way introduced some trick like >+ # 'somepath/../../../etc/passwd'... >+ from os.path import normpath >+ assert normpath(cache).startswith(cachedir) >+ >+ if not exists(cache): >+ self.log.debug('Caching revision %d of %s' % (lastnode.rev, >+ lastnode.path)) >+ dir = split(cache)[0] >+ if not exists(dir): >+ makedirs(dir) >+ >+ # If the file doesn't current exist, create an empty file >+ # and apply a patch from revision 1 to the node revision, >+ # otherwise apply a reversed patch from the current revision >+ # and to node revision+1. >+ try: >+ if not exists(current): >+ self.log.debug('Applying a direct patch from revision 1 up' >+ ' to %d to %s' % (lastnode.rev, node.path)) >+ open(cache, 'w').close() >+ patch = "| patch -p1 -d %s" % cachedir >+ self._diff(node.path, 1, lastnode.rev, patch=patch) >+ else: >+ self.log.debug('Applying a reverse patch from current' >+ ' revision back to %d to %s' % >+ (lastnode.rev, node.path)) >+ copyfile(current, cache) >+ patch = "| patch -p1 -R -d %s" % cachedir >+ self._diff(node.path, prevlast.rev, patch=patch) >+ except TracError, exc: >+ if 'Only garbage was found in the patch input' in exc.message: >+ pass >+ else: >+ raise >+ >+ # Adjust the times of the just created cache file, to match >+ # the timestamp of the associated changeset. >+ cursor = self.db.cursor() >+ cursor.execute("SELECT time FROM revision " >+ "WHERE rev = %s", (lastnode.rev,)) >+ cstimestamp = int(cursor.fetchone()[0]) >+ utime(cache, (cstimestamp, cstimestamp)) >+ >+ if exists(cache): >+ return cache >+ else: >+ return None >+ >+ def _getNodeContent(self, node): >+ """ >+ Return the content of the node, loading it from the cache. >+ """ >+ >+ from cStringIO import StringIO >+ >+ location = self._getCachedContentLocation(node) >+ if location: >+ return file(location) >+ else: >+ return StringIO('') >+ >+ def _getNodeSize(self, node): >+ """ >+ Return the content of the node, loading it from the cache. >+ """ >+ >+ location = self._getCachedContentLocation(node) >+ if location: >+ return stat(location).st_size >+ else: >+ return None >+ >+ def _getNodeEntries(self, node): >+ """ >+ Generate the the immediate child entries of a directory at given >+ revision, in alpha order. >+ """ >+ >+ from cache import _actionmap >+ >+ # Loop over nodes touched before given rev that falls in the >+ # given path. We effectively want to look at the whole subtree, >+ # because when a child is a directory we annotate it with the >+ # latest change happened below that, instead with the revision >+ # that actually touched the directory itself. >+ >+ cursor = self.db.cursor() >+ path = node.path.strip('/') >+ cursor.execute("SELECT rev, path, change, base_path " >+ " FROM node_change " >+ "WHERE ((LENGTH(rev)<LENGTH(%s)) OR " >+ " (LENGTH(rev)=LENGTH(%s) AND rev<=%s)) " >+ " AND (path GLOB %s OR base_path GLOB %s) " >+ "ORDER BY -LENGTH(rev),rev DESC,path ", >+ (node.rev, node.rev, node.rev, path+'*', path+'*')) >+ >+ if path: >+ path += '/' >+ >+ done = {} >+ for entry in cursor: >+ rev, subtreepath, change, oldpath = entry >+ if oldpath and oldpath.startswith(path): >+ subpath = oldpath[len(path):].lstrip('/') >+ elif subtreepath.startswith(path): >+ subpath = subtreepath[len(path):].lstrip('/') >+ else: >+ # The GLOB above may return either entries in the >+ # directory or entries in other directories (usually >+ # the one that contains the folder we are listing) >+ # that share a common chunk of the name with the >+ # directory itself. >+ continue >+ >+ if '/' in subpath: >+ child, rest = subpath.split('/', 1) >+ else: >+ child, rest = subpath, None >+ >+ if not child or child in done: >+ continue >+ >+ done[child] = True >+ >+ # Return the node only if the entry is not a direct child >+ # of the directory, otherwise only if it's not a deletion >+ # or a rename. >+ if rest or not ((_actionmap[change]==Changeset.MOVE >+ and oldpath and oldpath.startswith(path)) or >+ _actionmap[change]==Changeset.DELETE): >+ yield self.get_node(path + child, rev) >+ >+ def _getNodeLastModified(self, node): >+ """ >+ Return the timestamp of the last modification to the given node. >+ """ >+ >+ location = self._getCachedContentLocation(node) >+ if location: >+ return stat(location).st_mtime >+ else: >+ return 0 >+ >+ def _getPathHistory(self, path, rev=None, limit=None): >+ """ >+ Iterate over the nodes that compose the history of the given >+ path not newer than rev. >+ """ >+ >+ from trac.versioncontrol.cache import _kindmap, _actionmap >+ >+ rev = self.normalize_rev(rev) >+ >+ # Start with the concrete history, if present >+ if self.__history and rev>self.__history_start: >+ node = None >+ for cs in reversed(self.__history): >+ if cs.rev > rev: >+ continue >+ node = cs.get_node(path) >+ if node: >+ yield node >+ if limit: >+ limit -= 1 >+ if limit==0: >+ break >+ # Expand renames >+ if node.change == Changeset.MOVE: >+ for node in self._getPathHistory(node.oldpath, >+ node.rev-1, limit): >+ yield node >+ if limit: >+ limit -= 1 >+ if node is not None: >+ rev = node.rev-1 >+ >+ # Keep going with the cache stored in the DB >+ kind = self._getNodeKind(path, rev) >+ cursor = self.db.cursor() >+ >+ path = path.rstrip('/') >+ if kind == Node.DIRECTORY: >+ revdone = {} >+ cursor.execute("SELECT rev,kind,change,base_path" >+ " FROM node_change " >+ "WHERE ((LENGTH(rev)<LENGTH(%s)) OR " >+ " (LENGTH(rev)=LENGTH(%s) AND rev<=%s)) " >+ " AND (path=%s OR path GLOB %s) " >+ "ORDER BY -LENGTH(rev),rev DESC ", >+ (rev, rev, rev, path, path+'/*')) >+ for row in cursor: >+ rev, kind, change, base_path = row >+ if not rev in revdone: >+ revdone[rev] = True >+ node = DarcsNode(path, rev, _kindmap[kind], >+ _actionmap[change], self, >+ oldpath=base_path) >+ yield node >+ if limit: >+ limit -= 1 >+ if limit==0: >+ break >+ else: >+ while rev>=1: >+ cursor.execute("SELECT kind,change,base_path,base_rev " >+ "FROM node_change WHERE rev=%s AND path=%s", >+ (rev, path)) >+ base_rev = None >+ for row in cursor: >+ kind, change, base_path, base_rev = row >+ node = DarcsNode(path, rev, _kindmap[kind], >+ _actionmap[change], self, >+ oldpath=base_path) >+ yield node >+ if limit: >+ limit -= 1 >+ if limit==0: >+ break >+ base_rev = base_rev and int(base_rev) or 0 >+ # Expand renames >+ if node.change == Changeset.MOVE: >+ for node in self._getPathHistory(node.oldpath, >+ base_rev, limit): >+ yield node >+ if limit: >+ limit -= 1 >+ >+ if base_rev is None: >+ rev -= 1 >+ else: >+ rev = base_rev >+ >+ def _getNodeKind(self, path, rev): >+ """ >+ Determine the kind of the path at given revision. >+ """ >+ >+ # Determine if the path is really a directory, except when it's >+ # already known: it is, when its name ends with a slash (a fake >+ # one introduced by changesets_from_darcschanges()) or it is the >+ # empty string, resulted from normalize_path('/'). >+ if not path.endswith("/") and path <> "": >+ cursor = self.db.cursor() >+ cursor.execute("SELECT path " >+ "FROM node_change " >+ "WHERE ((LENGTH(rev)<LENGTH(%s)) OR " >+ " (LENGTH(rev)=LENGTH(%s) AND rev<=%s)) " >+ " AND path=%s " >+ "ORDER BY -LENGTH(rev),rev DESC " >+ "LIMIT 1", (rev, rev, rev, path+'/')) >+ if cursor.fetchone(): >+ kind = Node.DIRECTORY >+ else: >+ kind = Node.FILE >+ else: >+ kind = Node.DIRECTORY >+ return kind >+ >+ ## Interface API >+ >+ def close(self): >+ """ >+ Close the connection to the repository. >+ >+ Darcs: no-op. >+ """ >+ >+ def get_changeset(self, rev): >+ """ >+ Retrieve a Changeset object that describes the changes made in >+ revision 'rev'. >+ """ >+ >+ if not self.__history: >+ youngest = self.normalize_rev(self.youngest_rev) >+ youngest_cache = self.get_youngest_rev_in_cache(self.db) or '0' >+ self.__history_start = int(youngest_cache) >+ self.__history = self._changes('--last=%d' % >+ (youngest - self.__history_start,), >+ self.__history_start+1) >+ rev = self.normalize_rev(rev) >+ return self.__history[rev-self.__history_start-1] >+ >+ def get_node(self, path, rev=None): >+ """ >+ Retrieve a Node (directory or file) from the repository at the >+ given path. If the rev parameter is specified, the version of the >+ node at that revision is returned, otherwise the latest version >+ of the node is returned. >+ """ >+ >+ rev = self.normalize_rev(rev) >+ path = self.normalize_path(path) >+ >+ if path == '': >+ return DarcsNode('', rev, Node.DIRECTORY, None, self) >+ >+ kind = self._getNodeKind(path, rev) >+ >+ if kind == Node.DIRECTORY: >+ cursor = self.db.cursor() >+ path = path.rstrip('/') >+ cursor.execute("SELECT rev,change " >+ " FROM node_change " >+ "WHERE ((LENGTH(rev)<LENGTH(%s)) OR " >+ " (LENGTH(rev)=LENGTH(%s) AND rev<=%s)) " >+ " AND (path=%s OR path GLOB %s) " >+ "ORDER BY -LENGTH(rev),rev DESC " >+ "LIMIT 1", (rev, rev, rev, path, path+'/*')) >+ lastchange = cursor.fetchone() >+ if lastchange: >+ rev, change = lastchange >+ else: >+ raise TracError, "No node at %r in revision %s" % (path, rev) >+ node = DarcsNode(path, rev, Node.DIRECTORY, change, self) >+ else: >+ history = self._getPathHistory(path, rev, limit=1) >+ try: >+ node = history.next() >+ except StopIteration: >+ raise TracError, "No node at %r in revision %s" % (path, rev) >+ >+ return node >+ >+ def get_oldest_rev(self): >+ """ >+ Return the oldest revision stored in the repository. >+ """ >+ >+ # If the repository is empty, return None >+ if not self.__youngest_rev: >+ return None >+ return 1 >+ >+ def get_youngest_rev(self): >+ """ >+ Return the youngest revision in the repository. >+ """ >+ >+ if not self.__youngest_rev: >+ self._loadChangesetsIndex() >+ return self.__youngest_rev >+ >+ def previous_rev(self, rev): >+ """ >+ Return the revision immediately preceding the specified revision. >+ """ >+ >+ rev = self.normalize_rev(rev) >+ if rev == 1: >+ return None >+ else: >+ return rev-1 >+ >+ def next_rev(self, rev): >+ """ >+ Return the revision immediately following the specified revision. >+ """ >+ >+ rev = self.normalize_rev(rev) >+ if rev < self.get_youngest_rev(): >+ return rev+1 >+ else: >+ return None >+ >+ def rev_older_than(self, rev1, rev2): >+ """ >+ Return True if rev1 is older than rev2, i.e. if rev1 comes before rev2 >+ in the revision sequence. >+ """ >+ >+ return self.normalize_rev(rev1) < self.normalize_rev(rev2) >+ >+ def get_youngest_rev_in_cache(self, db): >+ """ >+ Return the youngest revision currently cached. >+ >+ For darcs, this is the last applied revision, not necessarily >+ the youngest one. We are numbering darcs patches in order of >+ application. >+ """ >+ >+ cursor = db.cursor() >+ cursor.execute("SELECT rev " >+ "FROM revision " >+ "ORDER BY -LENGTH(rev),rev DESC " # rev is a string >+ "LIMIT 1") # in the database >+ row = cursor.fetchone() >+ return row and row[0] or None >+ >+ def get_path_history(self, path, rev=None, limit=None): >+ """ >+ Retrieve all the revisions containing this path (no newer than 'rev'). >+ The result format should be the same as the one of Node.get_history() >+ """ >+ >+ path = self.normalize_path(path) >+ rev = self.normalize_rev(rev) >+ for node in self._getPathHistory(path, rev, limit): >+ yield (node.path, node.rev, node.change) >+ >+ def normalize_path(self, path): >+ """ >+ Return a canonical representation of path in the repos. >+ """ >+ >+ if path.startswith('/'): >+ return path[1:] >+ else: >+ return path >+ >+ def normalize_rev(self, rev): >+ """ >+ Return a canonical representation of a revision in the repos. >+ 'None' is a valid revision value and represents the youngest revision. >+ """ >+ >+ try: >+ rev = int(rev) >+ except (ValueError, TypeError): >+ rev = None >+ if rev is None: >+ rev = self.get_youngest_rev() >+ elif rev > self.get_youngest_rev(): >+ rev = self.get_youngest_rev() >+ return rev >+ >+ >+class DarcsChangeset(Changeset): >+ """ >+ Represents a set of changes of a repository. >+ """ >+ >+ def __init__(self, rev, patchname, message, author, date, changes, hash): >+ if message: >+ log = patchname + '\n' + message >+ else: >+ log = patchname >+ Changeset.__init__(self, rev, log, author, date) >+ self.patchname = patchname >+ self.changes = changes >+ self.hash = hash >+ # fix up changes rev slot >+ for c in self.changes: >+ c.rev = rev >+ >+ def get_changes(self): >+ """ >+ Generator that produces a (path, kind, change, base_path, base_rev) >+ """ >+ >+ moves = {} >+ for c in self.changes: >+ last = c.get_history(limit=2) >+ try: >+ last.next() >+ basepath,baserev,basechg = last.next() >+ except StopIteration: >+ basepath = None >+ baserev = -1 >+ yield (c.path, c.kind, c.change, c.oldpath or basepath, baserev) >+ >+ def get_node(self, path, maybedir=False): >+ """ >+ Find and return the node relative to given path. >+ """ >+ >+ for c in self.changes: >+ if c.path == path or c.oldpath == path: >+ return c >+ if maybedir and not path.endswith('/'): >+ path += '/' >+ if c.path == path or c.oldpath == path: >+ return c >+ >+ def insert_in_cache(self, cursor, kindmap, actionmap, log): >+ """ >+ Augment standard metadata with darcs patch hash. >+ """ >+ >+ Changeset.insert_in_cache(self, cursor, kindmap, actionmap, log) >+ cursor.execute("UPDATE revision SET hash = %s " >+ "WHERE rev = %s", (self.hash, self.rev)) >+ >+ >+class DarcsNode(Node): >+ """ >+ Represent a single item changed within a Changeset. >+ """ >+ >+ def __init__(self, path, rev, kind, change, repository, oldpath=None): >+ Node.__init__(self, path, rev, kind) >+ self.change = change >+ self.repository = repository >+ self.oldpath = oldpath >+ >+ def __cmp__(self, other): >+ res = cmp(self.rev, other.rev) >+ if res: >+ return res >+ res = cmp(self.path, other.path) >+ if res == 0: >+ if self.change==Changeset.MOVE and other.change==Changeset.DELETE: >+ res = 1 >+ elif self.change==Changeset.DELETE and other.change==Changeset.MOVE: >+ res = -1 >+ return res >+ >+ def get_content(self): >+ """ >+ Return a stream for reading the content of the node. This method >+ will return None for directories. The returned object should provide >+ a read([len]) function. >+ """ >+ >+ if self.isdir: >+ return None >+ >+ return self.repository._getNodeContent(self) >+ >+ def get_entries(self): >+ """ >+ Generator that yields the immediate child entries of a directory, in no >+ particular order. If the node is a file, this method returns None. >+ """ >+ >+ if self.isdir: >+ return self.repository._getNodeEntries(self) >+ >+ def get_history(self, limit=None): >+ """ >+ Generator that yields (path, rev, chg) tuples, one for each >+ revision in which the node was changed. This generator will >+ follow copies and moves of a node (if the underlying version >+ control system supports that), which will be indicated by the >+ first element of the tuple (i.e. the path) changing. >+ """ >+ >+ # Start with current version >+ yield (self.path, self.rev, self.change) >+ >+ # Keep going with the previous steps, possibly following the old >+ # name of the entry if this is a move. >+ prevpath = self.oldpath or self.path >+ prevrev = self.repository.normalize_rev(self.rev)-1 >+ prevhist = self.repository.get_path_history(prevpath, prevrev, limit-1) >+ for path, rev, chg in prevhist: >+ yield (path, rev, chg) >+ >+ def get_properties(self): >+ """ >+ Returns a dictionary containing the properties (meta-data) of the node. >+ The set of properties depends on the version control system. >+ """ >+ >+ return {} >+ >+ def get_content_length(self): >+ if self.isdir: >+ return None >+ return self.repository._getNodeSize(self) >+ >+ def get_content_type(self): >+ if self.isdir: >+ return None >+ return guess_type(self.path)[0] >+ >+ def get_name(self): >+ return split(self.path)[1] >+ >+ def get_last_modified(self): >+ return self.repository._getNodeLastModified(self) >+ >+ >+def changesets_from_darcschanges(changes, repository, start_revision): >+ """ >+ Parse XML output of ``darcs changes``. >+ >+ Return a list of ``Changeset`` instances. >+ """ >+ >+ from xml.sax import parseString, SAXException >+ from xml.sax.handler import ContentHandler >+ >+ class DarcsXMLChangesHandler(ContentHandler): >+ def __init__(self): >+ self.changesets = [] >+ self.index = start_revision-1 >+ self.current = None >+ self.current_field = [] >+ >+ def startElement(self, name, attributes): >+ if name == 'patch': >+ self.current = {} >+ self.current['author'] = attributes['author'] >+ date = attributes['date'] >+ # 20040619130027 >+ y = int(date[:4]) >+ m = int(date[4:6]) >+ d = int(date[6:8]) >+ hh = int(date[8:10]) >+ mm = int(date[10:12]) >+ ss = int(date[12:14]) >+ unixtime = int(mktime((y, m, d, hh, mm, ss, 0, 0, 0)))-timezone >+ self.current['date'] = unixtime >+ self.current['comment'] = '' >+ self.current['hash'] = attributes['hash'] >+ self.current['entries'] = [] >+ elif name in ['name', 'comment', 'add_file', 'add_directory', >+ 'remove_directory', 'modify_file', 'remove_file']: >+ self.current_field = [] >+ elif name == 'move': >+ self.old_name = attributes['from'] >+ self.new_name = attributes['to'] >+ >+ def endElement(self, name): >+ if name == 'patch': >+ # Sort the paths to make tests easier >+ self.current['entries'].sort() >+ self.index += 1 >+ cset = DarcsChangeset(self.index, >+ self.current['name'], >+ self.current['comment'], >+ self.current['author'], >+ self.current['date'], >+ self.current['entries'], >+ self.current['hash']) >+ self.changesets.append(cset) >+ self.current = None >+ elif name in ['name', 'comment']: >+ self.current[name] = ''.join(self.current_field) >+ elif name == 'move': >+ kind = None >+ for cs in reversed(self.changesets): >+ node = cs.get_node(self.old_name, maybedir=True) >+ if node: >+ kind = node.kind >+ break >+ if kind is None: >+ kind = repository._getNodeKind(self.old_name, self.index) >+ if kind == Node.DIRECTORY: >+ self.new_name += '/' >+ self.old_name += '/' >+ entry = DarcsNode(self.new_name, None, kind, Changeset.MOVE, >+ repository, self.old_name) >+ self.current['entries'].append(entry) >+ elif name in ['add_file', 'add_directory', 'modify_file', >+ 'remove_file', 'remove_directory']: >+ path = ''.join(self.current_field).strip() >+ change = { 'add_file': Changeset.ADD, >+ 'add_directory': Changeset.ADD, >+ 'modify_file': Changeset.EDIT, >+ 'remove_file': Changeset.DELETE, >+ 'remove_directory': Changeset.DELETE >+ }[name] >+ isdir = name in ('add_directory', 'remove_directory') >+ kind = isdir and Node.DIRECTORY or Node.FILE >+ # Eventually add one final '/' to identify directories. >+ # This is because Trac brings around simple tuples at times, >+ # that cannot carry that flag with them. >+ if isdir: >+ path += '/' >+ entry = DarcsNode(path, None, kind, change, repository) >+ self.current['entries'].append(entry) >+ >+ def characters(self, data): >+ self.current_field.append(data) >+ >+ handler = DarcsXMLChangesHandler() >+ try: >+ parseString(changes, handler) >+ except SAXException, le: >+ raise TracError('Unable to parse "darcs changes" output: ' + str(le)) >+ >+ return handler.changesets >+ >+class DarcsCachedRepository(CachedRepository): >+ """ >+ Darcs version of the cached repository, that serves DarcsCachedChangesets >+ """ >+ >+ def get_changeset(self, rev): >+ if not self.synced: >+ self.sync() >+ self.synced = 1 >+ return DarcsCachedChangeset(self.repos.normalize_rev(rev), self.db, >+ self.authz) >+ >+class DarcsCachedChangeset(CachedChangeset): >+ """ >+ Darcs version of the CachedChangeset that knows about the hash. >+ """ >+ >+ def __init__(self, rev, db, authz=None): >+ CachedChangeset.__init__(self, rev, db, authz) >+ cursor = self.db.cursor() >+ cursor.execute("SELECT hash FROM revision " >+ "WHERE rev=%s", (rev,)) >+ row = cursor.fetchone() >+ if row: >+ self.hash = row[0] >diff -N -ur -x_darcs trac/trac/versioncontrol/web_ui/changeset.py trac+darcs/trac/versioncontrol/web_ui/changeset.py >--- trac/trac/versioncontrol/web_ui/changeset.py 2005-11-17 12:26:10.871692800 +0100 >+++ trac+darcs/trac/versioncontrol/web_ui/changeset.py 2005-11-17 12:26:35.081012424 +0100 >@@ -25,8 +25,7 @@ > from trac.perm import IPermissionRequestor > from trac.Search import ISearchSource, query_to_sql, shorten_result > from trac.Timeline import ITimelineEventProvider >-from trac.versioncontrol import Changeset, Node >-from trac.versioncontrol.svn_authz import SubversionAuthorizer >+from trac.versioncontrol import Changeset, Node, get_authorizer > from trac.versioncontrol.diff import get_diff_options, hdf_diff, unified_diff > from trac.web import IRequestHandler > from trac.web.chrome import add_link, add_stylesheet, INavigationContributor >@@ -64,7 +63,7 @@ > > rev = req.args.get('rev') > repos = self.env.get_repository(req.authname) >- authzperm = SubversionAuthorizer(self.env, req.authname) >+ authzperm = get_authorizer(self.env, req.authname) > authzperm.assert_permission_for_changeset(rev) > > diff_options = get_diff_options(req) >@@ -106,7 +105,7 @@ > 'changeset_show_files')) > db = self.env.get_db_cnx() > repos = self.env.get_repository() >- authzperm = SubversionAuthorizer(self.env, req.authname) >+ authzperm = get_authorizer(self.env, req.authname) > rev = repos.youngest_rev > while rev: > if not authzperm.has_permission_for_changeset(rev): >@@ -383,7 +382,7 @@ > def get_search_results(self, req, query, filters): > if not 'changeset' in filters: > return >- authzperm = SubversionAuthorizer(self.env, req.authname) >+ authzperm = get_authorizer(self.env, req.authname) > db = self.env.get_db_cnx() > sql = "SELECT rev,time,author,message " \ > "FROM revision WHERE %s" % \ >diff -N -ur -x_darcs trac/wiki-default/TracIni trac+darcs/wiki-default/TracIni >--- trac/wiki-default/TracIni 2005-11-17 12:26:10.894689304 +0100 >+++ trac+darcs/wiki-default/TracIni 2005-11-17 12:26:35.154001328 +0100 >@@ -88,6 +88,11 @@ > == [wiki] == > || `ignore_missing_pages` || enable/disable highlighting CamelCase links to missing pages (''since 0.9'') || > >+== [darcs] == >+|| `cachedir` || By default the darcs backend keeps a cache of the visited files at various revision inside the repository itself, in `_darcs/trac_cache`, that may be overridden by this option, setting it to the desired directory that needs to be writeable by the trac process. || >+|| `command` || Name of the external darcs executable, default to `darcs`. This can be used to set up the environment as well, like in "`DARCS_DONT_ESCAPE_ANYTHING=1 /usr/local/bin/darcs`" || >+|| `dont_escape_8bit` || This is a shortcut for "`command=DARCS_DONT_ESCAPE_8BIT=1 darcs`" (true, false). Default to false. || >+ > == [components] == > (''since 0.9'') >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 109477
:
70801
|
70802
| 73644 |
73645