source: tracdarcs/tracdarcs/repository.py @ 207

Revision 207, 25.4 KB checked in by lele@…, 3 years ago (diff)

Properly store the utimestamp in the revision table

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