source: tracdarcs/tracdarcs/repository.py @ 37

Revision 37, 12.7 KB checked in by lele@…, 7 years ago (diff)

Implement DarcsRepository?.sync() called by trac-admin resync

RevLine 
[36]1# -*- coding: iso-8859-1 -*-
2#
3# Copyright (C) 2005 Edgewall Software
4# Copyright (C) 2006 K.S.Sreeram <sreeram@tachyontech.net>
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: K.S.Sreeram <sreeram@tachyontech.net>
15
16import os, StringIO, mimetypes
17from trac.versioncontrol import Repository, Node, Changeset, \
18        NoSuchChangeset, NoSuchNode
19
20from command import DarcsCommand
21from updatedb import update_darcsdb
22from dbutil import NODE_FILE_TYPE, NODE_DIR_TYPE
23from dbutil import CHANGE_ADDED, CHANGE_REMOVED, CHANGE_MOVED, \
24        CHANGE_EDITED, CHANGE_MOVED_EDITED
25from dbutil import query_nodes_for_revision
26import ann2ascii
27
28'''
29This module implements the trac versioncontrol backend API.
30The API consists of 3 classes: DarcsRepository, DarcsNode
31and DarcsChangeset.
32
33Please see the docs in trac.versioncontrol.api for the interface
34which is implemented by this module.
35'''
36
37# mapping from node types used by the darcs backend and the types
38# used by the trac api
39_node_type_map = {
40        NODE_FILE_TYPE : Node.FILE,
41        NODE_DIR_TYPE : Node.DIRECTORY
42        }
43
44# mapping from change types used by the darcs backend and the types
45# used by the trac api
46_change_map = {
47        CHANGE_ADDED : Changeset.ADD,
48        CHANGE_REMOVED : Changeset.DELETE,
49        CHANGE_MOVED : Changeset.MOVE,
50        CHANGE_EDITED : Changeset.EDIT,
51        #FIXME: treat moved&edited as just moved?
52        CHANGE_MOVED_EDITED : Changeset.MOVE
53        }
54
55def get_node_type( db, node_id ) :
56    c = db.cursor()
57    c.execute( 'SELECT node_type FROM darcs_nodes ' +
58            'WHERE node_id = %s', (node_id,) )
59    return c.fetchone()[0]
60
61def get_prev_path_rev( db, node_id, rev ) :
62    c = db.cursor()
63    c.execute( 'SELECT path,rev FROM darcs_node_changes ' +
64            'WHERE node_id = %s AND rev < %s ' +
65            'ORDER BY rev DESC LIMIT 1', (node_id,rev) )
66    path,rev = c.fetchone()
67    return path,rev
68
69class DarcsRepository( Repository ) :
70    def __init__( self, db, path, log, config ) :
71        Repository.__init__( self, path, None, log )
72        self.db = db
73        self.path = path
74        self.log = log
75        self.config = config
76        self.__cmd = DarcsCommand( 'darcs', path, log )
77        # import any new changesets, if any
78        update_darcsdb( db, self.__cmd )
79
80    def close( self ) :
81        pass
82
83    def get_changeset( self, rev ) :
84        rev = self.normalize_rev( rev )
85        return DarcsChangeset( self.db, rev )
86
87    def get_node( self, path, rev=None ) :
88        path = self.normalize_path( path )
89        rev = self.normalize_rev( rev )
90        # compute node_id, node_type and last_rev and then
91        # create a DarcsNode object.
92        # 'last_rev' is the last revision <= rev where this
93        # node was modified.
94        if path == '/' :
95            node_id = None
96            node_type = NODE_DIR_TYPE
97            last_rev = rev
98        else :
99            c = self.db.cursor()
100            q = query_nodes_for_revision( rev )
101            q += ' AND dnc.path = %s'
102            c.execute( q, (path,) )
103            row = c.fetchone()
104            if row is None :
105                raise NoSuchNode( path, rev )
106            node_id,last_rev = row[:2]
107            node_type = get_node_type( self.db, node_id )
108        return DarcsNode( node_id, node_type, path, last_rev,
109                self.db, self.__cmd )
110
111    def get_oldest_rev( self ) :
112        if self.get_youngest_rev() is None :
113            return None
114        return 1
115
116    def get_youngest_rev( self ) :
117        c = self.db.cursor()
118        c.execute( 'SELECT rev FROM darcs_revisions ' +
119                'ORDER BY rev DESC LIMIT 1' )
120        row = c.fetchone()
121        return row and row[0] or None
122
123    def previous_rev( self, rev ) :
124        rev = self.normalize_rev( rev )
125        if rev > 1 :
126            return rev-1
127        return None
128
129    def next_rev( self, rev, path='' ) :
130        rev = self.normalize_rev( rev )
131        if rev < self.get_youngest_rev() :
132            return rev+1
133        return None
134
135    def rev_older_than( self, rev1, rev2 ) :
136        return self.normalize_rev(rev1) < self.normalize_rev(rev2)
137
138    def get_path_history( self, path, rev=None, limit=None ) :
139        # FIXME: this is not correct
140        return self.get_node( path, rev ).get_history( limit )
141
142    def normalize_path( self, path ) :
143        return path and path.strip('/') or '/'
144
145    def normalize_rev( self, rev ) :
146        youngest = self.get_youngest_rev()
147        if rev is None :
148            return youngest
149        rev = int( rev )
150        if rev > youngest :
151            rev = youngest
152        return rev
153
154    def get_changes( self, old_path, old_rev, new_path, new_rev,
155            ignore_ancestry=1 ) :
156        old_path = self.normalize_path( old_path )
157        old_rev = self.normalize_rev( old_rev )
158        new_path = self.normalize_path( new_path )
159        new_rev = self.normalize_rev( new_rev )
160        old_node = self.get_node( old_path, old_rev )
161        new_node = self.get_node( new_path, new_rev )
162
163        node_id = old_node._get_node_id()
164        if node_id != new_node._get_node_id() :
165            raise TracError( 'Node mismatch: base is %s in rev %d '
166                    'and target is %s in rev %d' % (old_path,old_rev,
167                        new_path,new_rev) )
168
169        if old_node.kind == Node.FILE :
170            if old_node.rev != new_node.rev :
171                yield (old_node,new_node,Node.FILE,Changeset.EDIT)
172            return
173
174        c = self.db.cursor()
175        c.execute( 'SELECT rev,path FROM darcs_node_changes '
176                'WHERE node_id = %s AND rev >= %s AND rev <= %s',
177                (node_id,old_rev,new_rev) )
178        node_set = dict()
179        node_list = []
180        c1 = self.db.cursor()
181        for rev,path in c :
182            c1.execute( 'SELECT node_id FROM darcs_node_changes '
183                    'WHERE rev = %s AND path GLOB %s',
184                    (rev,path+'/*') )
185            for nid, in c1 :
186                if nid not in node_set :
187                    node_set[nid] = 1
188                    node_list.append( nid )
189        for nid in node_list :
190            old_node = new_node = None
191            c1.execute( 'SELECT rev,path FROM darcs_node_changes '
192                    'WHERE node_id = %s AND rev < %s '
193                    'ORDER BY rev DESC LIMIT 1',
194                    (nid,old_rev) )
195            row = c1.fetchone()
196            if row is not None :
197                rev,path = row
198                old_node = self.get_node( path, rev )
199            c1.execute( 'SELECT rev,path,change FROM darcs_node_changes '
200                    'WHERE node_id = %s AND rev >= %s AND rev <= %s '
201                    'ORDER BY rev DESC LIMIT 1',
202                    (nid,old_rev,new_rev) )
203            rev,path,change = c1.fetchone()
204            if change != CHANGE_REMOVED :
205                new_node = self.get_node( path, rev )
206            assert (old_node is not None) or (new_node is not None)
207            kind = old_node and old_node.kind or new_node.kind
208            if old_node is None :
209                change = Changeset.ADD
210            elif new_node is None :
211                change = Changeset.DELETE
212            elif old_node.path != new_node.path :
213                change = Changeset.MOVE
214            else :
215                change = Changeset.EDIT
216            yield (old_node,new_node,kind,change)
217
[37]218    def sync( self ) :
219        # This is called by trac-admin resync
220        update_darcsdb(self.db, self.__cmd)
221
[36]222class DarcsNode( Node ) :
223    def __init__( self, node_id, node_type, path, rev,
224            db, cmd ) :
225        kind = _node_type_map[node_type]
226        Node.__init__( self, path, rev, kind )
227        self.__node_id = node_id
228        self.__node_type = node_type
229        self.__db = db
230        self.__cmd = cmd
231        self.created_path = path
232        self.created_rev = rev
233
234    def _get_node_id( self ) :
235        return self.__node_id
236
237    def get_content( self ) :
238        if self.__node_type == NODE_DIR_TYPE :
239            return None
240        c = self.__db.cursor()
241        # check if the file content is there in the cache
242        c.execute( 'SELECT content FROM darcs_cache '
243                'WHERE node_id = %s AND rev = %s',
244                (self.__node_id,self.rev) )
245        row = c.fetchone()
246        if row is not None :
247            # if present just return it
248            data = str(row[0])
249            return StringIO.StringIO( data )
250        # load the file content from the repo using ann2ascii
251        c.execute( 'SELECT hash FROM darcs_revisions ' +
252                'WHERE rev = %s', (self.rev,) )
253        hash = c.fetchone()[0]
254        annotate = self.__cmd.annotate( hash, self.path )
255        ann = ann2ascii.parse_annotate( StringIO.StringIO(annotate) )
256        out = StringIO.StringIO()
257        ann.write( out )
258        data = out.getvalue()
259
260        # save the file content in the cache
261
262        # UGLY HACK: the following line is required otherwise
263        # db.commit() (pysqlite-2.3.2,winxp) throws
264        # OperationalError: 'SQL logic error or missing database'
265        self.__db.cnx.cnx.isolation_level = None
266        # END UGLY HACK
267
268        c = self.__db.cursor()
269        c.execute( 'INSERT INTO darcs_cache(node_id,rev,content) '
270                'VALUES (%s,%s,%s)',
271                (self.__node_id,self.rev,buffer(data)) )
272        self.__db.commit()
273        return StringIO.StringIO( data )
274
275    def get_entries( self ) :
276        if self.__node_type == NODE_FILE_TYPE :
277            return
278        q = query_nodes_for_revision( self.rev )
279        if self.__node_id is None :
280            q += ' AND dnc.parent_id IS NULL'
281        else :
282            q += ' AND dnc.parent_id = %d' % self.__node_id
283        c = self.__db.cursor()
284        c.execute( q )
285        for node_id,rev,path,_ in c :
286            node_type = get_node_type( self.__db, node_id )
287            yield DarcsNode( node_id, node_type, path, rev,
288                    self.__db, self.__cmd )
289
290    def get_history( self, limit=None ) :
291        if self.path == '/' :
292            for i in range(self.rev,0,-1) :
293                yield ( self.path, i, Changeset.EDIT )
294            return
295        c = self.__db.cursor()
296        q = 'SELECT path,rev,change FROM darcs_node_changes ' + \
297                'WHERE node_id = %s AND rev <= %s ' + \
298                'ORDER BY rev DESC'
299        if limit is not None :
300            q += ' LIMIT %d' % limit
301        c.execute( q, (self.__node_id,self.rev) )
302        for path,rev,change in c :
303            yield ( path, rev, _change_map[change] )
304
305    def get_properties( self ) :
306        return {}
307
308    def get_content_length( self ) :
309        if self.isdir :
310            return None
311        return len(self.get_content().read())
312
313    def get_content_type( self ) :
314        if self.isdir :
315            return None
316        return mimetypes.guess_type( self.path )[0]
317
318    def get_name( self ) :
319        return os.path.split( self.path )[1]
320
321    def get_last_modified( self ) :
322        if self.__node_id is None :
323            return 0
324        c = self.__db.cursor()
325        c.execute( 'SELECT rev FROM darcs_node_changes ' +
326                'WHERE node_id = %s AND rev = %s',
327                (self.__node_id,self.rev) )
328        rev = c.fetchone()[0]
329        c.execute( 'SELECT time FROM darcs_revisions ' +
330                'WHERE rev = %s', (rev,) )
331        return c.fetchone()[0]
332
333class DarcsChangeset( Changeset ) :
334    def __init__( self, db, rev ) :
335        c = db.cursor()
336        c.execute( 'SELECT author,time,name,comment ' +
337                'FROM darcs_revisions WHERE rev = %s', (rev,) )
338        row = c.fetchone()
339        if row is None :
340            raise NoSuchChangeset( rev )
341        author,date,name,comment = row
342        msg = name
343        if comment :
344            msg += '\n' + comment
345        Changeset.__init__( self, rev, msg, author, date )
346        self.__db = db
347
348    def get_changes( self ) :
349        c = self.__db.cursor()
350        c.execute( 'SELECT node_id,path,change FROM darcs_node_changes ' +
351                'WHERE rev = %s', (self.rev,) )
352        for node_id,path,change in c :
353            node_type = get_node_type( self.__db, node_id )
354            kind = _node_type_map[node_type]
355            if change == CHANGE_ADDED :
356                prev_path = prev_rev = None
357            else :
358                prev_path,prev_rev = get_prev_path_rev( self.__db,
359                        node_id, self.rev )
360            change = _change_map[change]
361            yield (path,kind,change,prev_path,prev_rev)
362
363    def get_properties( self ) :
364        return []
Note: See TracBrowser for help on using the repository browser.