source: tracdarcs/tracdarcs/components.py @ 197

Revision 197, 9.4 KB checked in by lele@…, 3 years ago (diff)

Corrected the generation of equivalent changesets property

Line 
1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2005 Edgewall Software
4# Copyright (C) 2005-2010 Lele Gaifax <lele@metapensiero.it>
5#
6# This software is licensed as described in the file COPYING, which
7# you should have received as part of this distribution. The terms
8# are also available at http://trac.edgewall.com/license.html.
9#
10# This software consists of voluntary contributions made by many
11# individuals. For the exact contribution history, see the revision
12# history and logs, available at http://projects.edgewall.com/trac/.
13#
14# Author: Lele Gaifax <lele@metapensiero.it>
15
16from genshi.builder import tag
17
18from trac.config import BoolOption, Option
19from trac.core import Component, implements
20from trac.db import Column, DatabaseManager, Index, Table
21from trac.env import IEnvironmentSetupParticipant
22from trac.util.text import shorten_line, to_unicode
23from trac.versioncontrol import IRepositoryConnector, NoSuchChangeset
24from trac.versioncontrol.web_ui import IPropertyRenderer, RenderedProperty
25
26from trac.wiki import IWikiSyntaxProvider
27
28from tracdarcs.repository import DarcsRepository
29
30class DarcsConnector(Component):
31
32    implements(IRepositoryConnector, IWikiSyntaxProvider)
33
34    dont_escape_8bit = BoolOption('darcs', 'dont_escape_8bit', 'false',
35                                  "Avoid darcs automatic escape of non-7bit chars.")
36
37    darcs_command = Option('darcs', 'command', 'darcs',
38                           "Name of the external darcs binary.")
39
40    max_concurrent_darcses = Option('darcs', 'max_concurrent_darcses', 0,
41                                    "Max number of concurrent darcses running per "
42                                    "repository (0 means unlimited).")
43
44    possible_encodings = Option('darcs', 'possible_encodings', 'utf-8,iso8859-1',
45                                "Specify possible repository encodings.")
46
47    eager_annotations = BoolOption('darcs', 'eager_annotations', 'false',
48                                   "Compute the annotation cache as soon as possible.")
49
50    # IRepositoryConnector methods
51
52    def get_supported_types(self):
53        """Support the `darcs:` scheme"""
54        yield ("darcs", 8)
55
56    def get_repository(self, type, dir, params):
57        """Return a `DarcsRepository`"""
58        db = self.env.get_db_cnx()
59        darcs = self.darcs_command
60        if self.dont_escape_8bit:
61            darcs = "DARCS_DONT_ESCAPE_8BIT=1 " + darcs
62        if self.possible_encodings:
63            possible_encodings = [e.strip()
64                                  for e in self.possible_encodings.split(',')]
65
66        # Setup the semaphore used to limit the number of concurrent running
67        # darcs within a single repository.
68
69        if self.max_concurrent_darcses and int(self.max_concurrent_darcses)>0:
70            from command import DarcsCommand
71            if DarcsCommand.RUNNING_DARCSES is None:
72                from threading import BoundedSemaphore
73                DarcsCommand.RUNNING_DARCSES = BoundedSemaphore(value=int(self.max_concurrent_darcses))
74
75        return DarcsRepository(db, dir, self.env.log, darcs, possible_encodings, params,
76                               self.eager_annotations)
77
78    # IWikiSyntaxProvider methods
79
80    def get_wiki_syntax(self):
81        yield (r'[0-9]{14}-[0-9a-f]{5}-[0-9a-f]{40}(.gz)?',
82               lambda formatter, label, match: self._format_link(formatter, 'cset', label, label))
83
84    def get_link_resolvers(self):
85        yield ('cset', self._format_link)
86
87    def _format_link(self, formatter, ns, rev, label):
88        reponame = None
89
90        # See if the context carries a repository...
91        context = formatter.context
92        while context:
93            if context.resource.realm in ('source', 'changeset'):
94                reponame = context.resource.parent.id
95                break
96            context = context.parent
97
98        # If it does not, take the first repository containing the
99        # specified revision, if any. We can do this assuming that
100        #   a) we are dealing with full darcs hashes and
101        #   b) darcs hashes are globally unique
102        if reponame is None:
103            db = self.env.get_db_cnx()
104            c = db.cursor()
105            c.execute('SELECT r.value '
106                      'FROM darcs_changesets c JOIN repository r ON c.repo_id = r.id '
107                      'WHERE c.hash = %s AND r.name = %s '
108                      'ORDER BY r.id '
109                      'LIMIT 1', (rev, 'name',))
110            row = c.fetchone()
111            reponame = row and row[0] or ''
112
113        repos = self.env.get_repository(reponame)
114        if repos:
115            try:
116                chgset = repos.get_changeset(rev)
117                return tag.a(chgset.rev, class_="changeset",
118                             title=shorten_line(chgset.message),
119                             href=formatter.href.changeset(chgset.rev, reponame))
120            except NoSuchChangeset, e:
121                errmsg = to_unicode(e)
122        else:
123            errmsg = 'Repository "%s" not found' % reponame
124
125        return tag.a(label, class_="missing changeset", title=errmsg, rel="nofollow")
126
127class DarcsSetup(Component):
128    """Darcs customizer.
129
130    The db tables required for the darcs backend are created here.
131    """
132
133    implements(IEnvironmentSetupParticipant)
134
135    def environment_created(self):
136        """After standard environment has been created, add the needed
137        tables."""
138
139        db = self.env.get_db_cnx()
140        self.upgrade_environment(db)
141        db.commit()
142
143    def environment_needs_upgrade(self, db):
144        """Check to see if the darcs tables are already there."""
145
146        c = db.cursor()
147        try:
148            c.execute('SELECT repo_id,rev,hash,name '
149                      'FROM darcs_changesets LIMIT 1')
150            c.execute('SELECT repo_id,node_id,node_type,add_rev,remove_rev '
151                      'FROM darcs_nodes LIMIT 1')
152            c.execute('SELECT repo_id,node_id,rev,path,parent_id,change_kind '
153                      'FROM darcs_node_changes LIMIT 1')
154            c.execute('SELECT repo_id,node_id,rev,content,size '
155                      'FROM darcs_cache LIMIT 1')
156            c.execute('SELECT repo_id,node_id,rev '
157                      'FROM darcs_annotate_cache LIMIT 1')
158            return False
159        except:
160            db.rollback()
161            return True
162
163    def upgrade_environment(self, db):
164        """Actually add the new db tables."""
165
166        def drop_table(table_name):
167            c = db.cursor()
168            try:
169                c.execute('drop table %s' % table_name)
170            except:
171                db.rollback()
172                pass
173
174        drop_table('darcs_revisions')  # until 0.6
175        drop_table('darcs_changesets')
176        drop_table('darcs_nodes')
177        drop_table('darcs_node_changes')
178        drop_table('darcs_cache')
179        drop_table('darcs_annotate_cache')
180
181        connector = DatabaseManager(self.env)._get_connector()[0]
182        if 'postgres' in [supp[0] for supp in connector.get_supported_schemes()]:
183            blobtype = 'bytea'
184        else:
185            blobtype = 'blob'
186        rev_table = Table('darcs_changesets', key=('repo_id','rev'))[
187            Column('repo_id',type='int'),
188            Column('rev',type='int'),
189            Column('hash'),
190            Column('name'),
191            Index(['hash','repo_id'])]
192        node_table = Table('darcs_nodes', key=('repo_id','node_id'))[
193            Column('repo_id',type='int'),
194            Column('node_id',type='int'),
195            Column('node_type',size=1),
196            Column('add_rev',type='int'),
197            Column('remove_rev',type='int')]
198        change_table = Table('darcs_node_changes', key=('repo_id','node_id','rev'))[
199            Column('repo_id',type='int'),
200            Column('node_id',type='int'),
201            Column('rev',type='int'),
202            Column('path'),
203            Column('parent_id',type='int'),
204            Column('change_kind'),
205            Index(['path'])]
206        cache_table = Table('darcs_cache', key=('repo_id','node_id','rev'))[
207            Column('repo_id',type='int'),
208            Column('node_id',type='int'),
209            Column('rev',type='int'),
210            Column('content',type=blobtype),
211            Column('size',type='int')]
212        ann_cache_table = Table('darcs_annotate_cache', key=('repo_id','node_id','rev','up_to_line'))[
213            Column('repo_id',type='int'),
214            Column('node_id',type='int'),
215            Column('rev',type='int'),
216            Column('up_to_line',type='int'),
217            Column('blame_rev',type='int')]
218        c = db.cursor()
219        for t in [rev_table,node_table,change_table,cache_table,ann_cache_table]:
220            for stmt in connector.to_sql(t):
221                c.execute(stmt)
222
223class EquivalentChangesetsRenderer(Component):
224    implements(IPropertyRenderer)
225
226    def match_property(self, name, mode):
227        return (mode == 'revprop' and name == 'EqChangesets') and 5 or 0
228
229    def render_property(self, name, mode, context, props):
230        eqcsets = props[name]
231        eqlinks = [(tag.a(repos or '(default)', class_="changeset",
232                         title="Equivalent patch %s in repository %s" % (
233                             rev, repos or '(default)'),
234                         href=context.href.changeset(rev, repos)),)
235                   for repos, rev in eqcsets]
236        return RenderedProperty(name='Present in:',
237                                name_attributes=[("class", "property")],
238                                content=tag([(link, ', ') for link in eqlinks[:-1]],
239                                            eqlinks[-1]))
Note: See TracBrowser for help on using the repository browser.