source: tracdarcs/tracdarcs/components.py @ 206

Revision 206, 12.8 KB checked in by lele@…, 3 years ago (diff)

Store the whole changelog in the trac revision table
Drop the historical and pointless distinction between the "name" and
the "long description", that were stored in separated tables,
preventing standard search service to operate on darcs changesets
"name". This requires a resync.

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.admin import IAdminCommandProvider
19from trac.config import BoolOption, Option
20from trac.core import Component, implements
21from trac.db import Column, DatabaseManager, Index, Table
22from trac.env import IEnvironmentSetupParticipant
23from trac.util.text import printout, shorten_line, to_unicode
24from trac.versioncontrol import IRepositoryConnector, NoSuchChangeset, \
25                                RepositoryManager
26from trac.versioncontrol.web_ui import IPropertyRenderer, RenderedProperty
27
28from trac.wiki import IWikiSyntaxProvider
29
30from tracdarcs.repository import DarcsRepository
31
32class DarcsConnector(Component):
33
34    implements(IRepositoryConnector, IWikiSyntaxProvider)
35
36    dont_escape_8bit = BoolOption('darcs', 'dont_escape_8bit', 'false',
37                                  "Avoid darcs automatic escape of non-7bit chars.")
38
39    darcs_command = Option('darcs', 'command', 'darcs',
40                           "Name of the external darcs binary.")
41
42    max_concurrent_darcses = Option('darcs', 'max_concurrent_darcses', 0,
43                                    "Max number of concurrent darcses running per "
44                                    "repository (0 means unlimited).")
45
46    possible_encodings = Option('darcs', 'possible_encodings', 'utf-8,iso8859-1',
47                                "Specify possible repository encodings.")
48
49    eager_annotations = BoolOption('darcs', 'eager_annotations', 'false',
50                                   "Compute the annotation cache as soon as possible.")
51
52    # IRepositoryConnector methods
53
54    def get_supported_types(self):
55        """Support the `darcs:` scheme"""
56        yield ("darcs", 8)
57
58    def get_repository(self, type, dir, params):
59        """Return a `DarcsRepository`"""
60        db = self.env.get_db_cnx()
61        darcs = self.darcs_command
62        if self.dont_escape_8bit:
63            darcs = "DARCS_DONT_ESCAPE_8BIT=1 " + darcs
64        if self.possible_encodings:
65            possible_encodings = [e.strip()
66                                  for e in self.possible_encodings.split(',')]
67
68        # Setup the semaphore used to limit the number of concurrent running
69        # darcs within a single repository.
70
71        if self.max_concurrent_darcses and int(self.max_concurrent_darcses)>0:
72            from command import DarcsCommand
73            if DarcsCommand.RUNNING_DARCSES is None:
74                from threading import BoundedSemaphore
75                DarcsCommand.RUNNING_DARCSES = BoundedSemaphore(value=int(self.max_concurrent_darcses))
76
77        return DarcsRepository(db, dir, self.env.log, darcs, possible_encodings, params,
78                               self.eager_annotations)
79
80    # IWikiSyntaxProvider methods
81
82    def get_wiki_syntax(self):
83        yield (r'[0-9]{14}-[0-9a-f]{5}-[0-9a-f]{40}(.gz)?',
84               lambda formatter, label, match: self._format_link(formatter, 'cset', label, label))
85
86    def get_link_resolvers(self):
87        yield ('cset', self._format_link)
88
89    def _format_link(self, formatter, ns, rev, label):
90        reponame = None
91
92        # See if the context carries a repository...
93        context = formatter.context
94        while context:
95            if context.resource.realm in ('source', 'changeset'):
96                reponame = context.resource.parent.id
97                break
98            context = context.parent
99
100        # If it does not, take the first repository containing the
101        # specified revision, if any. We can do this assuming that
102        #   a) we are dealing with full darcs hashes and
103        #   b) darcs hashes are globally unique
104        if reponame is None:
105            db = self.env.get_db_cnx()
106            c = db.cursor()
107            c.execute('SELECT r.value '
108                      'FROM darcs_changesets c JOIN repository r ON c.repo_id = r.id '
109                      'WHERE c.hash = %s AND r.name = %s '
110                      'ORDER BY r.id '
111                      'LIMIT 1', (rev, 'name',))
112            row = c.fetchone()
113            reponame = row and row[0] or ''
114
115        repos = self.env.get_repository(reponame)
116        if repos:
117            try:
118                chgset = repos.get_changeset(rev)
119                return tag.a(chgset.rev, class_="changeset",
120                             title=shorten_line(chgset.message),
121                             href=formatter.href.changeset(chgset.rev, reponame))
122            except NoSuchChangeset, e:
123                errmsg = to_unicode(e)
124        else:
125            errmsg = 'Repository "%s" not found' % reponame
126
127        return tag.a(label, class_="missing changeset", title=errmsg, rel="nofollow")
128
129class DarcsSetup(Component):
130    """Darcs customizer.
131
132    The db tables required for the darcs backend are created here.
133    """
134
135    implements(IAdminCommandProvider, IEnvironmentSetupParticipant)
136
137    def environment_created(self):
138        """After standard environment has been created, add the needed
139        tables."""
140
141        db = self.env.get_db_cnx()
142        self.upgrade_environment(db)
143        db.commit()
144
145    def environment_needs_upgrade(self, db):
146        """Check to see if the darcs tables are already there."""
147
148        c = db.cursor()
149        try:
150            try:
151                # trac-darcs < 0.9
152                c.execute('SELECT repo_id,rev,hash,name '
153                          'FROM darcs_changesets LIMIT 1')
154            except:
155                c.execute('SELECT repo_id,rev,hash '
156                          'FROM darcs_changesets LIMIT 1')
157            else:
158                return True
159
160            c.execute('SELECT repo_id,node_id,node_type,add_rev,remove_rev '
161                      'FROM darcs_nodes LIMIT 1')
162            c.execute('SELECT repo_id,node_id,rev,path,parent_id,change_kind '
163                      'FROM darcs_node_changes LIMIT 1')
164            c.execute('SELECT repo_id,node_id,rev,content,size '
165                      'FROM darcs_cache LIMIT 1')
166            c.execute('SELECT repo_id,node_id,rev '
167                      'FROM darcs_annotate_cache LIMIT 1')
168            return False
169        except:
170            db.rollback()
171            return True
172
173    def upgrade_environment(self, db):
174        """Actually add the new db tables."""
175
176        def drop_table(table_name):
177            c = db.cursor()
178            try:
179                c.execute('drop table %s' % table_name)
180            except:
181                db.rollback()
182                pass
183
184        drop_table('darcs_revisions')  # until 0.6
185        drop_table('darcs_changesets')
186        drop_table('darcs_nodes')
187        drop_table('darcs_node_changes')
188        drop_table('darcs_cache')
189        drop_table('darcs_annotate_cache')
190
191        connector = DatabaseManager(self.env)._get_connector()[0]
192        if 'postgres' in [supp[0] for supp in connector.get_supported_schemes()]:
193            blobtype = 'bytea'
194        else:
195            blobtype = 'blob'
196        rev_table = Table('darcs_changesets', key=('repo_id','rev'))[
197            Column('repo_id',type='int'),
198            Column('rev',type='int'),
199            Column('hash'),
200            Index(['hash','repo_id'])]
201        node_table = Table('darcs_nodes', key=('repo_id','node_id'))[
202            Column('repo_id',type='int'),
203            Column('node_id',type='int'),
204            Column('node_type',size=1),
205            Column('add_rev',type='int'),
206            Column('remove_rev',type='int')]
207        change_table = Table('darcs_node_changes', key=('repo_id','node_id','rev'))[
208            Column('repo_id',type='int'),
209            Column('node_id',type='int'),
210            Column('rev',type='int'),
211            Column('path'),
212            Column('parent_id',type='int'),
213            Column('change_kind'),
214            Index(['path'])]
215        cache_table = Table('darcs_cache', key=('repo_id','node_id','rev'))[
216            Column('repo_id',type='int'),
217            Column('node_id',type='int'),
218            Column('rev',type='int'),
219            Column('content',type=blobtype),
220            Column('size',type='int')]
221        ann_cache_table = Table('darcs_annotate_cache', key=('repo_id','node_id','rev','up_to_line'))[
222            Column('repo_id',type='int'),
223            Column('node_id',type='int'),
224            Column('rev',type='int'),
225            Column('up_to_line',type='int'),
226            Column('blame_rev',type='int')]
227        c = db.cursor()
228        for t in [rev_table,node_table,change_table,cache_table,ann_cache_table]:
229            for stmt in connector.to_sql(t):
230                c.execute(stmt)
231
232    # IAdminCommandProvider methods
233
234    def get_admin_commands(self):
235        yield ('repository identity', '<repos> [identity]',
236               'Get or set the identity tag of a repository',
237               self._complete_repos, self._do_repository_identity)
238
239    def _get_reponames(self):
240        rm = RepositoryManager(self.env)
241        return [reponame or '(default)' for reponame
242                in rm.get_all_repositories()]
243
244    def _complete_repos(self, args):
245        if len(args) == 1:
246            return self._get_reponames()
247
248    def _do_repository_identity(self, reponame, identity=None):
249        rm = RepositoryManager(self.env)
250        repo = rm.get_repository(reponame)
251        if repo is None:
252            printout("Invalid repository!")
253            return
254
255        repoid = repo.id
256
257        db = self.env.get_db_cnx()
258        cursor = db.cursor()
259
260        cursor.execute("SELECT value FROM repository "
261                       "WHERE id=%s AND name='identity'", (repoid,))
262        row = cursor.fetchone()
263
264        if identity is None:
265            if row is not None:
266                printout("Identity of repository %s: %s" %
267                         (reponame, row[0]))
268            else:
269                printout("No identity set on repository %s" %
270                         reponame)
271        else:
272            @self.env.with_transaction()
273            def set_identity(db):
274                if row is None and identity:
275                    cursor.execute("INSERT INTO repository (id, name, value) "
276                                   "VALUES (%s, 'identity', %s)", (repoid, identity))
277                    printout("Identity of repository %s set to %s" %
278                             (reponame, identity))
279                elif not identity:
280                    if row is not None:
281                        cursor.execute("DELETE FROM repository "
282                                       "WHERE id=%s AND name='identity'", (repoid,))
283                    printout("Identity removed from repository %s" %
284                             reponame)
285                elif identity != row[0]:
286                    cursor.execute("UPDATE repository SET value=%s "
287                                   "WHERE id=%s AND name='identity'",
288                                   (identity, repoid))
289                    printout("Identity of repository %s set to %s "
290                             "(was %s)" % (reponame, identity, row[0]))
291                else:
292                    printout("Identity of repository %s is already set to %s"
293                             % (reponame, identity))
294
295class EquivalentChangesetsRenderer(Component):
296    implements(IPropertyRenderer)
297
298    def match_property(self, name, mode):
299        return (mode == 'revprop' and name == 'PresentIn') and 5 or 0
300
301    def render_property(self, name, mode, context, props):
302        eqcsets = props[name]
303        eqlinks = [(tag.a(repos or '(default)', class_="changeset",
304                         title="Equivalent patch %s in repository %s" % (
305                             rev, repos or '(default)'),
306                         href=context.href.changeset(rev, repos)),)
307                   for repos, rev in eqcsets]
308        return RenderedProperty(name='Present in:',
309                                name_attributes=[("class", "property")],
310                                content=tag([(link, ', ') for link in eqlinks[:-1]],
311                                            eqlinks[-1]))
312
313class MissingInReposRenderer(Component):
314    implements(IPropertyRenderer)
315
316    def match_property(self, name, mode):
317        return (mode == 'revprop' and name == 'MissingIn') and 5 or 0
318
319    def render_property(self, name, mode, context, props):
320        mir = props[name]
321        return RenderedProperty(name='Missing in:',
322                                name_attributes=[("class", "property")],
323                                content=tag([(repo, ', ') for repo in mir[:-1]],
324                                            mir[-1]))
Note: See TracBrowser for help on using the repository browser.