source: tracdarcs/tracdarcs/repository.py @ 54

Revision 54, 12.6 KB checked in by "Zooko O'Whielacronx <zooko@…, 6 years ago (diff)

fix a missing import (thanks to pyflakes for noticing)

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