source: tracdarcs/tracdarcs/repository.py @ 208

Revision 208, 25.6 KB checked in by lele@…, 3 years ago (diff)

Docstrings

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