source: tracdarcs/tracdarcs/repository.py @ 107

Revision 107, 12.9 KB checked in by lele@…, 5 years ago (diff)

Renewed the copyright on all the sources

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