source: tracdarcs/tracdarcs/repository.py @ 197

Revision 197, 24.4 KB checked in by lele@…, 3 years ago (diff)

Corrected the generation of equivalent changesets property

RevLine 
[126]1# -*- coding: utf-8 -*-
[36]2#
3# Copyright (C) 2005 Edgewall Software
4# Copyright (C) 2006 K.S.Sreeram <sreeram@tachyontech.net>
[168]5# Copyright (C) 2007-2010 Lele Gaifax <lele@metapensiero.it>
[36]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
[106]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
[36]26import os, StringIO, mimetypes
[61]27from datetime import tzinfo, timedelta, datetime
[137]28import time
[61]29
30from trac.util import TracError
[104]31from trac.util.datefmt import to_timestamp, utc
32from trac.versioncontrol import (Repository, Node, Changeset,
33                                 NoSuchChangeset, NoSuchNode)
[136]34
[36]35from command import DarcsCommand
36from updatedb import update_darcsdb
[137]37from dbutil import (CHANGE_ADDED, CHANGE_EDITED, CHANGE_MOVED,
38                    CHANGE_MOVED_EDITED, CHANGE_REMOVED,
[154]39                    IS_TRAC_0_10_X, IS_TRAC_0_11_X, TimedDB,
[137]40                    IS_TRAC_0_12_OR_BETTER, NODE_DIR_TYPE,
41                    NODE_FILE_TYPE, get_node_type, get_prev_path_rev,
42                    get_repository_id, query_nodes_for_revision)
[36]43
44# mapping from node types used by the darcs backend and the types
45# used by the trac api
46_node_type_map = {
[88]47    NODE_FILE_TYPE : Node.FILE,
48    NODE_DIR_TYPE : Node.DIRECTORY
49    }
[36]50
51# mapping from change types used by the darcs backend and the types
52# used by the trac api
53_change_map = {
[88]54    CHANGE_ADDED : Changeset.ADD,
55    CHANGE_REMOVED : Changeset.DELETE,
56    CHANGE_MOVED : Changeset.MOVE,
57    CHANGE_EDITED : Changeset.EDIT,
58    #FIXME: treat moved&edited as just moved?
59    CHANGE_MOVED_EDITED : Changeset.MOVE
60    }
[36]61
[88]62class DarcsRepository(Repository):
[185]63    def __init__(self, db, path, log, darcscmd, possible_encodings,
64                 params, eager_annotations):
[161]65        if IS_TRAC_0_12_OR_BETTER:
66            Repository.__init__(self, 'darcs:%s' % path, params, log)
67        else:
68            Repository.__init__(self, 'darcs:%s' % path, params, None, log)
[154]69        self.db = TimedDB(db, log)
[36]70        self.path = path
[132]71        self.__cmd = DarcsCommand(darcscmd, path, log, possible_encodings)
[161]72        if not IS_TRAC_0_12_OR_BETTER:
73            self.log = log
74            self.id = get_repository_id(db, path) or 0
[185]75        self.eager_annotations = eager_annotations
[110]76        if IS_TRAC_0_10_X:
77            self.sync()
[36]78
[88]79    def close(self):
[36]80        pass
81
[88]82    def get_changeset(self, rev):
83        rev = self.normalize_rev(rev)
[161]84        return DarcsChangeset(self, rev)
[36]85
[88]86    def get_node(self, path, rev=None):
87        path = self.normalize_path(path)
88        rev = self.normalize_rev(rev)
[36]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.
[88]93        if path == '/':
[36]94            node_id = None
95            node_type = NODE_DIR_TYPE
96            last_rev = rev
[88]97        else:
[36]98            c = self.db.cursor()
[178]99            q,args = query_nodes_for_revision(self.id, rev, 'dnc.path = %s')
100            args.append(path)
101            c.execute(q, args)
[36]102            row = c.fetchone()
[88]103            if row is None:
104                raise NoSuchNode(path, rev)
[36]105            node_id,last_rev = row[:2]
[161]106            node_type = get_node_type(self.db, self.id, node_id)
[88]107        return DarcsNode(node_id, node_type, path, last_rev,
[161]108                         self, self.__cmd, self.log)
[36]109
[88]110    def get_oldest_rev(self):
111        if self.get_youngest_rev() is None:
[36]112            return None
113        return 1
114
[88]115    def get_youngest_rev(self):
[36]116        c = self.db.cursor()
[132]117        c.execute('SELECT max(rev) FROM darcs_changesets '
[161]118                  'WHERE repo_id = %s', (self.id,))
[36]119        row = c.fetchone()
120        return row and row[0] or None
121
[161]122    def previous_rev(self, rev, path=''):
[88]123        rev = self.normalize_rev(rev)
124        if rev > 1:
[36]125            return rev-1
126        return None
127
[88]128    def next_rev(self, rev, path=''):
129        rev = self.normalize_rev(rev)
130        if rev < self.get_youngest_rev():
[36]131            return rev+1
132        return None
133
[88]134    def rev_older_than(self, rev1, rev2):
[36]135        return self.normalize_rev(rev1) < self.normalize_rev(rev2)
136
[88]137    def get_path_history(self, path, rev=None, limit=None):
[36]138        # FIXME: this is not correct
[88]139        return self.get_node(path, rev).get_history(limit)
[36]140
[88]141    def normalize_path(self, path):
[36]142        return path and path.strip('/') or '/'
143
[88]144    def normalize_rev(self, rev):
[121]145        if isinstance(rev, basestring) and len(rev) in (61,64):
[176]146            if rev.endswith('.gz'):
147                # We don't store ending .gz in the db
148                rev = rev[:-3]
[120]149            c = self.db.cursor()
150            c.execute('SELECT rev FROM darcs_changesets '
[161]151                      'WHERE repo_id = %s AND hash = %s', (self.id, rev))
[120]152            row = c.fetchone()
153            if row is None:
154                raise NoSuchChangeset(rev)
[121]155            rev = int(row[0])
[120]156        else:
157            youngest = self.get_youngest_rev()
158            if rev is None or rev == "":
159                return youngest
160            try:
161                rev = int(rev)
162            except ValueError, le:
163                raise TracError('Ill-formed revision: %s, error: %s' % (rev, le))
164            if rev > youngest:
165                rev = youngest
[36]166        return rev
167
[88]168    def get_changes(self, old_path, old_rev, new_path, new_rev, ignore_ancestry=1):
169        old_path = self.normalize_path(old_path)
170        old_rev = self.normalize_rev(old_rev)
171        new_path = self.normalize_path(new_path)
172        new_rev = self.normalize_rev(new_rev)
173        old_node = self.get_node(old_path, old_rev)
174        new_node = self.get_node(new_path, new_rev)
[36]175
176        node_id = old_node._get_node_id()
[88]177        if node_id != new_node._get_node_id():
178            raise TracError('Node mismatch: base is %s in rev %d '
[162]179                            'and target is %s in rev %d' % (old_path, old_rev,
180                                                            new_path, new_rev))
[36]181
[88]182        if old_node.kind == Node.FILE:
183            if old_node.rev != new_node.rev:
[162]184                yield (old_node, new_node, Node.FILE, Changeset.EDIT)
[36]185            return
186
187        c = self.db.cursor()
[145]188        if node_id is not None:
189            c.execute('SELECT rev,path FROM darcs_node_changes '
190                      'WHERE repo_id = %s AND node_id = %s AND rev >= %s AND rev <= %s',
[161]191                      (self.id, node_id, old_rev, new_rev))
[145]192        else:
193            c.execute('SELECT rev,path FROM darcs_node_changes '
194                      'WHERE repo_id = %s AND rev >= %s AND rev <= %s',
[161]195                      (self.id, old_rev, new_rev))
[36]196        node_set = dict()
197        node_list = []
198        c1 = self.db.cursor()
[88]199        for rev,path in c:
200            c1.execute('SELECT node_id FROM darcs_node_changes '
[131]201                       'WHERE repo_id = %s AND rev = %s AND path LIKE %s',
[161]202                       (self.id, rev, path+'/%'))
[88]203            for nid, in c1:
204                if nid not in node_set:
[36]205                    node_set[nid] = 1
[88]206                    node_list.append(nid)
207        for nid in node_list:
[36]208            old_node = new_node = None
[88]209            c1.execute('SELECT rev,path FROM darcs_node_changes '
[131]210                       'WHERE repo_id = %s AND node_id = %s AND rev < %s '
211                       'ORDER BY rev DESC LIMIT 1',
[161]212                       (self.id, nid, old_rev))
[36]213            row = c1.fetchone()
[88]214            if row is not None:
[36]215                rev,path = row
[88]216                old_node = self.get_node(path, rev)
[158]217            c1.execute('SELECT rev,path,change_kind FROM darcs_node_changes '
[131]218                       'WHERE repo_id = %s AND node_id = %s AND rev >= %s AND rev <= %s '
219                       'ORDER BY rev DESC LIMIT 1',
[161]220                       (self.id, nid, old_rev, new_rev))
[36]221            rev,path,change = c1.fetchone()
[88]222            if change != CHANGE_REMOVED:
223                new_node = self.get_node(path, rev)
[36]224            assert (old_node is not None) or (new_node is not None)
225            kind = old_node and old_node.kind or new_node.kind
[88]226            if old_node is None:
[36]227                change = Changeset.ADD
[88]228            elif new_node is None:
[36]229                change = Changeset.DELETE
[88]230            elif old_node.path != new_node.path:
[36]231                change = Changeset.MOVE
[88]232            else:
[36]233                change = Changeset.EDIT
234            yield (old_node,new_node,kind,change)
235
[161]236    def sync(self, rev_callback=None, clean=False):
[185]237        from dbutil import format_elapsed_time as trepr
238
[73]239        # Import any new changesets, if any
[185]240
241        newrevs = update_darcsdb(self.db, self.__cmd, self.log, self.id,
242                                 rev_callback=rev_callback, clean=clean)
243
244        # In eager mode, precompute the content and the annotations for
245        # each file modified or added by latest changesets
246
247        if self.eager_annotations and newrevs:
248            i = 1
249            count = len(newrevs)
250            for rev in newrevs:
251                t0 = time.time()
252
253                c = self.get_changeset(rev)
254                for path,kind,change,prev_path,prev_rev in c.get_changes():
255                    if kind==Node.FILE and (change==Changeset.EDIT or change==Changeset.ADD):
256                        node = self.get_node(path, rev)
257                        node.get_content()
258                        node.get_annotations()
259
260                t1 = time.time()
261                usec = (t1-t0) * 1e6
262                self.log.info('Preannotated changeset %d/%d: %s', i, count, trepr(usec))
263                i += 1
[37]264
[88]265class DarcsNode(Node):
[131]266    def __init__(self, node_id, node_type, path, rev,
[161]267                 repos, cmd, log=None):
[36]268        kind = _node_type_map[node_type]
[161]269        Node.__init__(self, repos, path, rev, kind)
[36]270        self.__node_id = node_id
271        self.__node_type = node_type
272        self.__cmd = cmd
[69]273        self.__log = log
[36]274        self.created_path = path
275        self.created_rev = rev
276
[88]277    def _get_node_id(self):
[36]278        return self.__node_id
279
[147]280    def _get_cached_rev(self):
281        # if there are no future versions, use the HEAD
282        nrev = self._get_next()
283        if nrev is None:
284            return None
285
[150]286        maxrange = nrev[1]-1
287
288        # if it's just one hop from node's revision, we're done
289        if maxrange == self.rev:
290            return maxrange
291
[149]292        # ok, let's see if there is already a cache in the range
[161]293        c = self.repos.db.cursor()
[149]294        c.execute('SELECT max(rev) FROM darcs_cache '
[150]295                  'WHERE repo_id = %s AND node_id = %s AND rev >= %s AND rev <= %s AND content IS NOT NULL',
[161]296                  (self.repos.id, self.__node_id, self.rev, maxrange))
[149]297        row = c.fetchone()
298        if row[0] is not None:
299            return row[0]
[147]300
301        # No luck, return the most recent revision before the next
[150]302        return maxrange
[147]303
[88]304    def get_content(self):
305        if self.__node_type == NODE_DIR_TYPE:
[36]306            return None
[161]307        c = self.repos.db.cursor()
[88]308
[150]309        # Since darcs is faster and faster in building the content
310        # of a file for more and more recent changes, compute the
311        # optimal revision to build the cache of
[147]312        crev = self._get_cached_rev()
[150]313
[147]314        if crev is not None:
315            # check if the file content is there in the cache
316            c.execute('SELECT content FROM darcs_cache '
317                      'WHERE repo_id = %s AND node_id = %s AND rev = %s',
[161]318                      (self.repos.id, self.__node_id, crev))
[147]319            row = c.fetchone()
320            if row is not None:
321                self.__log.debug('Cache hit %s at rev %s', self.path, crev)
322                # if present just return it
323                data = str(buffer(row[0]))
324            else:
[187]325                self.__log.debug('Building content cache for %s at rev %s', self.path, crev)
[36]326
[147]327                # load the file content from the repo
328                c.execute('SELECT hash FROM darcs_changesets '
[161]329                          'WHERE repo_id = %s AND rev = %s', (self.repos.id, crev,))
[147]330                hash = c.fetchone()[0]
331                data = self.__cmd.cat(hash, self.path)
332
333                # save the file content in the cache
[161]334                c = self.repos.db.cursor()
[171]335                try:
336                    c.execute('INSERT INTO darcs_cache (repo_id,node_id,rev,content,size) '
337                              'VALUES (%s,%s,%s,%s,%s)',
338                              (self.repos.id, self.__node_id, crev, buffer(data), len(data)))
339                except:
340                    self.repos.db.rollback()
341                    c = self.repos.db.cursor()
342                    # Maybe some other thread computed the same content
343                    c.execute('SELECT content FROM darcs_cache '
344                              'WHERE repo_id = %s AND node_id = %s AND rev = %s',
345                              (self.repos.id, self.__node_id, crev))
346                    row = c.fetchone()
347                    if row is not None:
348                        self.__log.debug('Late cache hit %s at rev %s', self.path, crev)
349                        data = str(buffer(row[0]))
350                    else:
351                        raise
[184]352                else:
353                    self.repos.db.commit()
[147]354        else:
355            # Use the HEAD
356            self.__log.debug('Serving pristine file %s, no changes since rev %s', self.path, self.rev)
357            data = self.__cmd.cat(None, self.path)
[36]358
[88]359        return StringIO.StringIO(data)
[36]360
[88]361    def get_entries(self):
362        if self.__node_type == NODE_FILE_TYPE:
[36]363            return
[88]364        if self.__node_id is None:
[178]365            cond = 'dnc.parent_id IS NULL'
[88]366        else:
[178]367            cond = 'dnc.parent_id = %d' % self.__node_id
368        q,args = query_nodes_for_revision(self.repos.id, self.rev, cond)
[161]369        c = self.repos.db.cursor()
[178]370        c.execute(q, args)
[88]371        for node_id,rev,path,_ in c:
[161]372            node_type = get_node_type(self.repos.db, self.repos.id, node_id)
[88]373            yield DarcsNode(node_id, node_type, path, rev,
[161]374                            self.repos, self.__cmd, self.__log)
[36]375
[88]376    def get_history(self, limit=None):
377        if self.path == '/':
378            for i in range(self.rev,0,-1):
379                yield (self.path, i, Changeset.EDIT)
[36]380            return
[161]381        c = self.repos.db.cursor()
[158]382        q = ('SELECT path,rev,change_kind FROM darcs_node_changes '
[131]383             'WHERE repo_id = %s AND node_id = %s AND rev <= %s '
[88]384             'ORDER BY rev DESC')
385        if limit is not None:
[36]386            q += ' LIMIT %d' % limit
[161]387        c.execute(q, (self.repos.id, self.__node_id, self.rev))
[88]388        for path,rev,change in c:
389            yield (path, rev, _change_map[change])
[112]390
[147]391    def _get_next(self):
392        try:
393            return self._get_future(1).next()
394        except StopIteration:
395            return None
396
397    def _get_future(self, limit=None):
398        if self.path == '/':
399            youngest = self.get_youngest_rev()
400            for i in range(youngest, self.rev, -1):
401                yield (self.path, i, Changeset.EDIT)
402            return
[161]403        c = self.repos.db.cursor()
[158]404        q = ('SELECT path,rev,change_kind FROM darcs_node_changes '
[147]405             'WHERE repo_id = %s AND node_id = %s AND rev > %s '
406             'ORDER BY rev')
407        if limit is not None:
408            q += ' LIMIT %d' % limit
[161]409        c.execute(q, (self.repos.id, self.__node_id, self.rev))
[147]410        for path,rev,change in c:
411            yield (path, rev, _change_map[change])
412
[112]413    def get_annotations(self):
414        """Provide detailed backward history for the content of this Node.
415
[170]416        Retrieve an array of revisions parsing `darcs annotate`. Since
417        that is (still) not fast enough for some repository, we write
418        a cache of the information: a future annotate on the same file
419        at the same revision won't reexecute `darcs annotate`.
[112]420        """
421
422        from xml.sax import make_parser
423        from xml.sax.handler import ContentHandler, ErrorHandler
424        from datetime import datetime
425
[161]426        c = self.repos.db.cursor()
[112]427
[170]428        # Since darcs is faster and faster in building the content
429        # of a file for more and more recent changes, compute the
430        # optimal revision to build the cache of
431        crev = self._get_cached_rev()
432        if crev is None:
433            crev = self.rev
434
435        # Check if the annotate cache is already present
436        c.execute('SELECT up_to_line,blame_rev FROM darcs_annotate_cache '
437                  'WHERE repo_id = %s AND node_id = %s AND rev = %s '
438                  'ORDER BY up_to_line', (self.repos.id, self.__node_id, crev))
439        row = c.fetchone()
440        if row is not None:
441            self.__log.debug('Annotate cache hit for %s at rev %s', self.path, crev)
442            revs = []
443            line = 0
444            # Expand the cache, producing a list of revisions, one per line
445            while row is not None:
446                while line<row[0]:
447                    revs.append(row[1])
448                    line += 1
449                row = c.fetchone()
450            return revs
451
452        # No cache, build it
453
[187]454        self.__log.debug('Building annotate cache for %s at rev %s', self.path, crev)
455
[112]456        class DarcsXMLAnnotateHandler(ContentHandler):
457            def __init__(self):
458                self.revisions = []
459                self.known_hashes = {}
460
461            def startElement(self, name, attributes):
462                if name == 'patch':
[176]463                    self.current_hash = attributes['hash'][:-3]
[112]464
465            def endElement(self, name):
466                if name == 'normal_line':
467                    self.revisions.append(self.findRevision(self.current_hash))
468                elif name == 'added_line':
469                    self.revisions.append(self.findRevision(self.last_changed_hash))
470                elif name == 'modified':
471                    self.last_changed_hash = self.current_hash
472
473            def findRevision(self, hash):
474                # Return the trac revision for the given patch hash
475                try:
476                    return self.known_hashes[hash]
477                except KeyError:
478                    c.execute('SELECT rev FROM darcs_changesets '
479                              'WHERE hash = %s', (hash,))
480                    rev = self.known_hashes[hash] = c.fetchone()[0]
481                    return rev
482
483        # Get the hash of the patch
484        c.execute('SELECT hash FROM darcs_changesets '
485                  'WHERE rev = %s', (self.rev,))
486        hash = c.fetchone()[0]
487
488        # Get darcs annotate output for the given entry and patch hash
489        annotate = self.__cmd.annotate(hash, self.path)
490
491        parser = make_parser()
492        handler = DarcsXMLAnnotateHandler()
493        parser.setContentHandler(handler)
494        parser.setErrorHandler(ErrorHandler())
495
496        parser.feed(annotate)
497        parser.close()
498
[170]499        revs = handler.revisions
[186]500        if not revs:
501            self.__log.debug('Empty file, no annotations for %s at rev %s', self.path, crev)
502            return revs
[170]503
504        # Write a compressed representation
505
506        prev = None
507        for i,rev in enumerate(revs):
508            if prev is not None:
509                if prev != rev:
510                    c.execute('INSERT INTO darcs_annotate_cache (repo_id,node_id,rev,up_to_line,blame_rev) '
511                              'VALUES (%s,%s,%s,%s,%s)',
512                              (self.repos.id, self.__node_id, crev, i, prev))
513                    prev = rev
514                    lastline = i
515            else:
516                prev = rev
517                lastline = 0
518        if lastline != len(revs):
519            c.execute('INSERT INTO darcs_annotate_cache (repo_id,node_id,rev,up_to_line,blame_rev) '
520                      'VALUES (%s,%s,%s,%s,%s)',
521                      (self.repos.id, self.__node_id, crev, len(revs), revs[-1]))
522
[184]523        self.repos.db.commit()
524
[170]525        return revs
[36]526
[88]527    def get_properties(self):
[36]528        return {}
529
[88]530    def get_content_length(self):
531        if self.isdir:
[36]532            return None
[134]533
534        # first check if the file is already in the cache
[161]535        c = self.repos.db.cursor()
[134]536        c.execute('SELECT size FROM darcs_cache '
[146]537                  'WHERE repo_id = %s AND node_id = %s AND rev = %s',
[161]538                  (self.repos.id, self.__node_id, self.rev))
[134]539        row = c.fetchone()
540        if row is not None:
541            return row[0]
542
543        # if it's not, get the whole content and count...
544        # next time you'll be luckier, promise!
[36]545        return len(self.get_content().read())
546
[88]547    def get_content_type(self):
548        if self.isdir:
[36]549            return None
[88]550        return mimetypes.guess_type(self.path)[0]
[36]551
[88]552    def get_name(self):
553        return os.path.split(self.path)[1]
[36]554
[88]555    def get_last_modified(self):
556        if self.__node_id is None:
[36]557            return 0
[161]558        c = self.repos.db.cursor()
[88]559        c.execute('SELECT rev FROM darcs_node_changes '
[131]560                  'WHERE repo_id = %s AND node_id = %s AND rev = %s',
[161]561                  (self.repos.id, self.__node_id, self.rev))
[36]562        rev = c.fetchone()[0]
[152]563        if IS_TRAC_0_12_OR_BETTER:
564            c.execute('SELECT time FROM revision '
[161]565                      'WHERE repos = %s AND rev = %s', (self.repos.id, rev))
[152]566        else:
567            c.execute('SELECT time FROM revision '
568                      'WHERE rev = %s', (rev,))
[102]569        return datetime.fromtimestamp(c.fetchone()[0], utc)
[59]570
[88]571class DarcsChangeset(Changeset):
[161]572    def __init__(self, repos, rev):
573        repo_id = repos.id
574        c = repos.db.cursor()
[136]575        if IS_TRAC_0_12_OR_BETTER:
576            c.execute('SELECT r.author,r.time,c.name,r.message,c.hash '
577                      'FROM revision as r, darcs_changesets as c '
[140]578                      'WHERE r.repos = %s AND c.repo_id = r.repos '
579                      '  AND r.rev = %s AND c.rev = r.rev',
[161]580                      (repo_id, rev))
[136]581        else:
582            c.execute('SELECT r.author,r.time,c.name,r.message,c.hash '
583                      'FROM revision as r, darcs_changesets as c '
584                      'WHERE r.rev = %s '
[163]585                      '  AND c.rev = r.rev AND c.repo_id = %s', (rev, 0))
[36]586        row = c.fetchone()
[88]587        if row is None:
588            raise NoSuchChangeset(rev)
[118]589        author,date,name,comment,hash = row
[102]590        date = datetime.fromtimestamp(date, utc)
[78]591        # Trac 0.10.x hack
592        if IS_TRAC_0_10_X:
593            date = time.mktime(date.timetuple())
[36]594        msg = name
[88]595        if comment:
[36]596            msg += '\n' + comment
[161]597        Changeset.__init__(self, repos, rev, msg, author, date)
[118]598        self.__hash = hash
[36]599
[88]600    def get_changes(self):
[161]601        c = self.repos.db.cursor()
602        repo_id = self.repos.id
[158]603        c.execute('SELECT node_id,path,change_kind FROM darcs_node_changes '
[161]604                   'WHERE repo_id = %s AND rev = %s', (repo_id, self.rev,))
[88]605        for node_id,path,change in c:
[161]606            node_type = get_node_type(self.repos.db, repo_id, node_id)
[36]607            kind = _node_type_map[node_type]
[88]608            if change == CHANGE_ADDED:
[36]609                prev_path = prev_rev = None
[88]610            else:
[161]611                prev_path,prev_rev = get_prev_path_rev(self.repos.db, repo_id,
[131]612                                                       node_id, self.rev)
[36]613            change = _change_map[change]
614            yield (path,kind,change,prev_path,prev_rev)
615
[88]616    def get_properties(self):
[121]617        # omit ending .gz, because under some configuration the Apache
618        # web server automatically tags such URLs with something like
619        # "Content-Encoding: gzip" that in turn may confuse the browser.
620        # Darcs recognizes also extension-stripped hashnames.
[144]621
[176]622        props = dict(Hashname=self.__hash)
[144]623
[197]624        # Compute a list of "equivalent changesets", when the same
625        # changeset is present in other repositories.
626
[161]627        c = self.repos.db.cursor()
[197]628        c.execute('SELECT rep.value, dcs.rev '
629                  'FROM darcs_changesets dcs, darcs_changesets dcs2, repository rep '
[144]630                  'WHERE dcs2.repo_id = %s AND dcs2.rev = %s '
631                  '  AND dcs.hash = dcs2.hash '
[197]632                  '  AND dcs.repo_id <> dcs2.repo_id'
633                  '  AND rep.id = dcs.repo_id AND rep.name = \'name\''
634                  'ORDER BY rep.value', (self.repos.id, self.rev))
[144]635        eqcsets = [(repo, rev) for repo,rev in c.fetchall()]
636        if eqcsets:
637            props['EqChangesets'] = eqcsets
638
639        return props
Note: See TracBrowser for help on using the repository browser.