Edgewall Software

root / trunk / contrib / sourceforge2trac.py

Revision 3544, 11.1 kB (checked in by cmlenz, 22 months ago)

Update link to project site in all the boilerplate etc.

  • Property svn:eol-style set to native
Line 
1"""
2Import a Sourceforge project's tracker items into a Trac database.
3
4Requires:  Development version of Trac 0.7-pre from http://trac.edgewall.org/
5           ElementTree from effbot.org/zone/element.htm
6           Python 2.3 from http://www.python.org/
7           
8The Sourceforge tracker items can be exported from the 'Backup' page of the
9project admin section.
10
11Copyright 2004, Mark Rowe <mrowe@bluewire.net.nz>
12"""
13
14from elementtree.ElementTree import ElementTree
15from datetime import datetime
16import trac.env
17
18class FieldParser(object):
19    def __init__(self, e):
20        for field in e:
21            if field.get('name').endswith('date'):
22                setattr(self, field.get('name'), datetime.fromtimestamp(int(field.text)))
23            else:
24                setattr(self, field.get('name'), field.text)       
25
26class ArtifactHistoryItem(FieldParser):
27    def __repr__(self):
28        return '<ArtifactHistoryItem field_name=%r old_value=%r entrydate=%r mod_by=%r>' % (
29            self.field_name, self.old_value, self.entrydate, self.mod_by)
30
31class ArtifactMessage(FieldParser):
32    def __repr__(self):
33        return '<ArtifactMessage adddate=%r user_name=%r body=%r>' % (self.adddate, self.user_name, self.body)
34
35class Artifact(object):
36    def __init__(self, e):
37        self._history = []
38        self._messages = []
39       
40        for field in e:
41            if field.get('name') == 'artifact_history':
42                for h in field:
43                    self._history.append(ArtifactHistoryItem(h))
44            elif field.get('name') == 'artifact_messages':
45                for m in field:
46                    self._messages.append(ArtifactMessage(m))
47            else:
48                setattr(self, field.get('name'), field.text)
49   
50    def history(self):
51        """Returns the history items in reverse chronological order so that the "new value"
52           can easily be calculated based on the final value of the field, and the old value
53           of the items occuring before it.
54        """
55        history = [(h.entrydate, h) for h in self._history]
56        history.sort()
57        return [h[1] for h in history][::-1]
58   
59    def messages(self):
60        return self._messages[:]
61   
62    def __repr__(self):
63        return '<Artifact summary=%r artifact_type=%r category=%r status=%r>' % (self.summary, self.artifact_type, self.category, self.status)
64
65class ExportedProjectData(object):
66    def __init__(self, f):
67        self._artifacts = []
68       
69        root = ElementTree().parse(f)   
70       
71        for artifact in root.find('artifacts'):
72            self._artifacts.append(Artifact(artifact))
73   
74    def artifacts(self):
75        """Returns the artifacts in chronological order, so that they will be assigned numbers in sequence."""
76        artifacts = [(a.open_date, a) for a in self._artifacts]
77        artifacts.sort()
78        return [a[1] for a in artifacts]
79   
80    def featureRequests(self):
81        return [a for a in self._artifacts if a.artifact_type == 'Feature Requests']
82   
83    def bugs(self):
84        return [a for a in self._artifacts if a.artifact_type == 'Bugs']
85   
86    def categories(self):
87        """Returns all the category names that are used, in alphabetical order."""
88        c = {}
89        for a in self._artifacts:
90            c[a.category] = 1
91       
92        categories = c.keys()
93        categories.sort()
94        return categories
95   
96    def groups(self):
97        """Returns all the group names that are used, in alphabetical order."""
98        g = {}
99        for a in self._artifacts:
100            g[a.artifact_group_id] = 1
101        del g['None']
102       
103        groups = g.keys()
104        groups.sort()
105        return groups
106   
107    def artifactTypes(self):
108        """Returns all the artifact types that are used, in alphabetical order."""
109        t = {}
110        for a in self._artifacts:
111            t[a.artifact_type] = 1
112        types = t.keys()
113        types.sort()
114        return types
115
116class TracDatabase(object):
117    def __init__(self, path):
118        self.env = trac.env.Environment(path)
119        self._db = self.env.get_db_cnx()
120        self._db.autocommit = False
121   
122    def db(self):
123        return self._db
124   
125    def hasTickets(self):
126        c = self.db().cursor()
127        c.execute('''SELECT count(*) FROM Ticket''')
128        return int(c.fetchall()[0][0]) > 0
129   
130    def setTypeList(self, s):
131        """Remove all types, set them to `s`"""
132        if self.hasTickets():
133            raise Exception("Will not modify database with existing tickets!")
134       
135        c = self.db().cursor()
136        c.execute("""DELETE FROM enum WHERE kind='ticket_type'""")
137        for i, value in enumerate(s):
138            c.execute("""INSERT INTO enum (kind, name, value) VALUES (%s, %s, %s)""",
139                      "ticket_type",
140                      value,
141                      i)
142        self.db().commit()
143   
144    def setPriorityList(self, s):
145        """Remove all priorities, set them to `s`"""
146        if self.hasTickets():
147            raise Exception("Will not modify database with existing tickets!")
148       
149        c = self.db().cursor()
150        c.execute("""DELETE FROM enum WHERE kind='priority'""")
151        for i, value in enumerate(s):
152            c.execute("""INSERT INTO enum (kind, name, value) VALUES (%s, %s, %s)""",
153                      "priority",
154                      value,
155                      i)
156        self.db().commit()
157
158   
159    def setComponentList(self, l):
160        """Remove all components, set them to `l`"""
161        if self.hasTickets():
162            raise Exception("Will not modify database with existing tickets!")
163       
164        c = self.db().cursor()
165        c.execute("""DELETE FROM component""")
166        for value in l:
167            c.execute("""INSERT INTO component (name) VALUES (%s)""",
168                      value)
169        self.db().commit()
170   
171    def setVersionList(self, v):
172        """Remove all versions, set them to `v`"""
173        if self.hasTickets():
174            raise Exception("Will not modify database with existing tickets!")
175       
176        c = self.db().cursor()
177        c.execute("""DELETE FROM version""")
178        for value in v:
179            c.execute("""INSERT INTO version (name) VALUES (%s)""",
180                      value)
181        self.db().commit()
182       
183    def setMilestoneList(self, m):
184        """Remove all milestones, set them to `m`"""
185        if self.hasTickets():
186            raise Exception("Will not modify database with existing tickets!")
187       
188        c = self.db().cursor()
189        c.execute("""DELETE FROM milestone""")
190        for value in m:
191            c.execute("""INSERT INTO milestone (name) VALUES (%s)""",
192                      value)
193        self.db().commit()
194   
195    def addTicket(self, type, time, changetime, component,
196                  priority, owner, reporter, cc,
197                  version, milestone, status, resolution,
198                  summary, description, keywords):
199        c = self.db().cursor()
200        if status.lower() == 'open':
201            if owner != '':
202                status = 'assigned'
203            else:
204                status = 'new'
205
206        c.execute("""INSERT INTO ticket (type, time, changetime, component,
207                                         priority, owner, reporter, cc,
208                                         version, milestone, status, resolution,
209                                         summary, description, keywords)
210                                 VALUES (%s, %s, %s,
211                                         %s, %s, %s, %s, %s,
212                                         %s, %s, %s, %s,
213                                         %s, %s, %s)""",
214                  type, time, changetime, component,
215                  priority, owner, reporter, cc,
216                  version, milestone, status.lower(), resolution,
217                  summary, '{{{\n%s\n}}}' % (description, ), keywords)
218        self.db().commit()
219        return self.db().db.sqlite_last_insert_rowid()
220   
221    def addTicketComment(self, ticket, time, author, value):
222        c = self.db().cursor()
223        c.execute("""INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue)
224                                 VALUES        (%s, %s, %s, %s, %s, %s)""",
225                  ticket, time.strftime('%s'), author, 'comment', '', '{{{\n%s\n}}}' % (value, ))
226        self.db().commit()
227
228    def addTicketChange(self, ticket, time, author, field, oldvalue, newvalue):
229        c = self.db().cursor()
230        c.execute("""INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue)
231                                 VALUES        (%s, %s, %s, %s, %s, %s)""",
232                  ticket, time.strftime('%s'), author, field, oldvalue, newvalue)
233        self.db().commit()
234
235
236def main():
237    import optparse
238    p = optparse.OptionParser('usage: %prog xml_export.xml /path/to/trac/environment')
239    opt, args = p.parse_args()
240    if len(args) != 2:
241        p.error("Incorrect number of arguments")
242   
243    try:
244        importData(open(args[0]), args[1])
245    except Exception, e:
246        print 'Error:', e
247
248def importData(f, env):
249    project = ExportedProjectData(f)
250   
251    db = TracDatabase(env)
252    db.setTypeList(project.artifactTypes())
253    db.setComponentList(project.categories())
254    db.setPriorityList(range(1, 11))
255    db.setVersionList(project.groups())
256    db.setMilestoneList([])
257   
258    for a in project.artifacts():
259        i = db.addTicket(type=a.artifact_type,
260                         time=a.open_date,
261                         changetime='',
262                         component=a.category,
263                         priority=a.priority,
264                         owner=a.assigned_to,
265                         reporter=a.submitted_by,
266                         cc='',
267                         version=a.artifact_group_id,
268                         milestone='',
269                         status=a.status,
270                         resolution=a.resolution,
271                         summary=a.summary,
272                         description=a.details,
273                         keywords='')
274        print 'Imported %s as #%d' % (a.artifact_id, i)
275        for msg in a.messages():
276            db.addTicketComment(ticket=i,
277                                time=msg.adddate,
278                                author=msg.user_name,
279                                value=msg.body)
280        if a.messages():
281            print '    imported %d messages for #%d' % (len(a.messages()), i)
282       
283        values = a.__dict__.copy()
284        field_map = {'summary': 'summary'}
285        for h in a.history():
286            if h.field_name == 'close_date' and values.get(h.field_name, '') == '':
287                f = 'status'
288                oldvalue = 'assigned'
289                newvalue = 'closed'
290            else:
291                f = field_map.get(h.field_name, None)
292                oldvalue = h.old_value
293                newvalue = values.get(h.field_name, '')
294               
295            if f:
296                db.addTicketChange(ticket=i,
297                                   time=h.entrydate,
298                                   author=h.mod_by,
299                                   field=f,
300                                   oldvalue=oldvalue,
301                                   newvalue=newvalue)
302            values[h.field_name] = h.old_value
303
304if __name__ == '__main__':
305    main()
Note: See TracBrowser for help on using the browser.