source: tracdarcs/tracdarcs/repository.py @ 78

Revision 78, 13.4 KB checked in by Peter Trsko <dogmat@…>, 5 years ago (diff)

Convert datetime to float for Trac 0.10x
Trac 0.10x uses float type to store time. TracDarcs? uses datetime object
for this and it works in 0.11. This patch adds Trac version check and
conversion of datetime to float. Tested on 0.10.4 and 0.11b1 both on
python 2.4.

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