source: tracdarcs/changeset.py @ 3

Revision 3, 7.5 KB checked in by lele@…, 7 years ago (diff)

Reworked the trac+darcs backend into a TracPlugin?

Line 
1# -*- coding: iso-8859-1 -*-
2#
3# Copyright (C) 2005 Edgewall Software
4# Copyright (C) 2005,2006 Lele Gaifax <lele@metapensiero.it>
5#
6# This software is licensed as described in the file COPYING, which
7# you should have received as part of this distribution. The terms
8# are also available at http://trac.edgewall.com/license.html.
9#
10# This software consists of voluntary contributions made by many
11# individuals. For the exact contribution history, see the revision
12# history and logs, available at http://projects.edgewall.com/trac/.
13#
14# Author: Lele Gaifax <lele@metapensiero.it>
15
16from time import mktime, timezone
17
18from xml.sax import parseString, SAXException
19from xml.sax.handler import ContentHandler
20
21from trac.util import TracError
22from trac.versioncontrol import Changeset
23from trac.versioncontrol.cache import CachedChangeset
24
25from node import Node, DarcsNode
26from repos import reversed
27
28class DarcsChangeset(Changeset):
29    """
30    Represents a set of changes of a repository.
31    """
32
33    def __init__(self, rev, patchname, message, author, date, changes, hash):
34        if message:
35            log = patchname + '\n' + message
36        else:
37            log = patchname
38        Changeset.__init__(self, rev, log, author, date)
39        self.patchname = patchname
40        self.changes = changes
41        self.hash = hash
42        # fix up changes rev slot
43        for c in self.changes:
44            c.rev = rev
45
46    def get_changes(self):
47        """
48        Generator that produces a (path, kind, change, base_path, base_rev)
49        """
50
51        moves = {}
52        for c in self.changes:
53            last = c.get_history(limit=2)
54            try:
55                last.next()
56                basepath,baserev,basechg = last.next()
57            except StopIteration:
58                basepath = None
59                baserev = -1
60            yield (c.path, c.kind, c.change, c.oldpath or basepath, baserev)
61
62    def get_node(self, path, maybedir=False):
63        """
64        Find and return the node relative to given path.
65        """
66
67        for c in self.changes:
68            if c.path == path or c.oldpath == path:
69                return c
70            if maybedir and not path.endswith('/'):
71                path += '/'
72                if c.path == path or c.oldpath == path:
73                    return c
74
75    def insert_in_cache(self, cursor, kindmap, actionmap, log):
76        """
77        Augment standard metadata with darcs patch hash.
78        """
79
80        Changeset.insert_in_cache(self, cursor, kindmap, actionmap, log)
81        cursor.execute("UPDATE revision SET hash = %s "
82                       "WHERE rev = %s", (self.hash, self.rev))
83
84
85def changesets_from_darcschanges(changes, repository, start_revision,
86                                 force_encoding=None):
87    """
88    Parse XML output of ``darcs changes``.
89
90    Return a list of ``Changeset`` instances.
91    """
92
93    class DarcsXMLChangesHandler(ContentHandler):
94        def __init__(self):
95            self.changesets = []
96            self.index = start_revision-1
97            self.current = None
98            self.current_field = []
99
100        def startElement(self, name, attributes):
101            if name == 'patch':
102                self.current = {}
103                self.current['author'] = attributes['author']
104                date = attributes['date']
105                # 20040619130027
106                y = int(date[:4])
107                m = int(date[4:6])
108                d = int(date[6:8])
109                hh = int(date[8:10])
110                mm = int(date[10:12])
111                ss = int(date[12:14])
112                unixtime = int(mktime((y, m, d, hh, mm, ss, 0, 0, 0)))-timezone
113                self.current['date'] = unixtime
114                self.current['comment'] = ''
115                self.current['hash'] = attributes['hash']
116                self.current['entries'] = []
117            elif name in ['name', 'comment', 'add_file', 'add_directory',
118                          'remove_directory', 'modify_file', 'remove_file']:
119                self.current_field = []
120            elif name == 'move':
121                self.old_name = attributes['from']
122                self.new_name = attributes['to']
123
124        def endElement(self, name):
125            if name == 'patch':
126                # Sort the paths to make tests easier
127                self.current['entries'].sort()
128                self.index += 1
129                cset = DarcsChangeset(self.index,
130                                      self.current['name'],
131                                      self.current['comment'],
132                                      self.current['author'],
133                                      self.current['date'],
134                                      self.current['entries'],
135                                      self.current['hash'])
136                self.changesets.append(cset)
137                self.current = None
138            elif name in ['name', 'comment']:
139                self.current[name] = ''.join(self.current_field)
140            elif name == 'move':
141                kind = None
142                for cs in reversed(self.changesets):
143                    node = cs.get_node(self.old_name, maybedir=True)
144                    if node:
145                        kind = node.kind
146                        break
147                if kind is None:
148                    kind = repository._getNodeKind(self.old_name, self.index)
149                if kind == Node.DIRECTORY:
150                    self.new_name += '/'
151                    self.old_name += '/'
152                entry = DarcsNode(self.new_name, None, kind, Changeset.MOVE,
153                                  repository, self.old_name)
154                self.current['entries'].append(entry)
155            elif name in ['add_file', 'add_directory', 'modify_file',
156                          'remove_file', 'remove_directory']:
157                path = ''.join(self.current_field).strip()
158                change = { 'add_file': Changeset.ADD,
159                           'add_directory': Changeset.ADD,
160                           'modify_file': Changeset.EDIT,
161                           'remove_file': Changeset.DELETE,
162                           'remove_directory': Changeset.DELETE
163                         }[name]
164                isdir = name in ('add_directory', 'remove_directory')
165                kind = isdir and Node.DIRECTORY or Node.FILE
166                # Eventually add one final '/' to identify directories.
167                # This is because Trac brings around simple tuples at times,
168                # that cannot carry that flag with them.
169                if isdir:
170                    path += '/'
171                entry = DarcsNode(path, None, kind, change, repository)
172                self.current['entries'].append(entry)
173
174        def characters(self, data):
175            self.current_field.append(data)
176
177    if force_encoding:
178        changes = changes.decode(force_encoding, 'replace').encode('utf-8')
179        changes = '<?xml version="1.0" encoding="utf-8"?>\n' + changes
180
181    handler = DarcsXMLChangesHandler()
182    try:
183        parseString(changes, handler)
184    except SAXException, le:
185        raise TracError('Unable to parse "darcs changes" output: ' + str(le))
186
187    return handler.changesets
188
189class DarcsCachedChangeset(CachedChangeset):
190    """
191    Darcs version of the CachedChangeset that knows about the hash.
192    """
193
194    def __init__(self, rev, db, authz=None):
195        CachedChangeset.__init__(self, rev, db, authz)
196        cursor = self.db.cursor()
197        cursor.execute("SELECT hash FROM revision "
198                       "WHERE rev=%s", (rev,))
199        row = cursor.fetchone()
200        if row:
201            self.hash = row[0]
Note: See TracBrowser for help on using the repository browser.