source: tracdarcs/tracdarcs/repository.py @ 51

Revision 51, 12.5 KB checked in by John Goerzen <jgoerzen@…>, 6 years ago (diff)

Comment out isolation_level for Postgresql

It causes exceptions there

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