1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
#!/usr/bin/env python
# Generate JSON to import Trac tickets into GitHub issues using the new import API
# described at https://gist.github.com/jonmagic/5282384165e0f86ef105
import os
import time
import json
import yaml
import sqlite3
import hashlib
import argparse
import subprocess
ticket_query = '''
SELECT
id,
type,
owner,
reporter,
milestone,
status,
resolution,
summary,
description,
component,
priority,
time / 1000000 AS createdtime,
changetime / 1000000 AS modifiedtime
FROM
ticket
ORDER BY
id
'''
comment_query = '''
SELECT
time / 1000000 AS createdtime,
author,
newvalue
FROM
ticket_change
WHERE
ticket = ?
AND
field = 'comment'
AND
newvalue <> ''
ORDER BY
time
'''
attachment_query = '''
SELECT
id,
filename,
size,
author,
description,
ipnr,
time / 1000000 AS createdtime
FROM
attachment
WHERE
id = ?
AND
type = 'ticket'
ORDER BY
time, filename
'''
def isotime(t):
return None if t == 0 else time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(t))
def hashname(whatever):
return hashlib.sha1(unicode(whatever)).hexdigest()
def ticket_text(ticket):
d = dict(ticket, createdtime = isotime(ticket["createdtime"]), modifiedtime = isotime(ticket["modifiedtime"]))
return u"{description}\n\n" \
u"_Trac ticket #{id} component {component} priority {priority}, owner {owner}," \
u" created by {reporter} on {createdtime}, last modified {modifiedtime}_\n".format(**d)
def comment_text(comment):
d = dict(comment, createdtime = isotime(comment["createdtime"]))
return u"{newvalue}\n\n_Trac comment by {author} on {createdtime}_\n".format(**d)
def attachment_text(attachment):
h1 = hashname(attachment["id"])
h2 = hashname(attachment["filename"])
fn2 = os.path.splitext(attachment["filename"])[1]
fn = os.path.join(gist_url, h1[:3], h1, h2 + fn2)
url = "{}/raw/{}/ticket.{}.{}{}".format(gist_url.rstrip("/"), gist_commit, h1, h2, fn2)
d = dict(attachment, createdtime = isotime(comment["createdtime"]), url = url)
return u"[{filename}]({url}) {description}\n_Trac attachment by {author} on {createdtime}_\n".format(**d)
def comment_merge(comments, attachments):
result = []
while comments and attachments:
result.append(comments.pop(0) if comments[0]["created_at"] <= attachments[0]["created_at"] else attachments.pop(0))
return result + comments + attachments
parser = argparse.ArgumentParser(formatter_class = argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-c", "--config", type = argparse.FileType(),
default = "generate-json.yaml",
help = "YAML config mappings")
args = parser.parse_args()
cfg = yaml.safe_load(args.config)
assignee_map = cfg["assignees"]
type_map = cfg["type_labels"]
resolution_map = cfg["resolution_labels"]
gist_url = cfg.get("attachment_gist_url")
if gist_url is not None:
gist_commit = subprocess.check_output(("git", "ls-remote", gist_url, "HEAD")).split()[0]
db = sqlite3.connect(cfg["database"])
db.row_factory = sqlite3.Row
ticket_cursor = db.cursor()
comment_cursor = db.cursor()
attachment_cursor = db.cursor()
if not os.path.isdir(cfg["ticket_directory"]):
os.makedirs(cfg["ticket_directory"])
for ticket in ticket_cursor.execute(ticket_query):
comments = comment_merge([dict(created_at = isotime(comment["createdtime"]), body = comment_text(comment))
for comment in comment_cursor.execute(comment_query, (ticket["id"],))],
[] if gist_url is None else
[dict(created_at = isotime(attachment["createdtime"]), body = attachment_text(attachment))
for attachment in attachment_cursor.execute(attachment_query, (ticket["id"],))])
issue = dict(
title = ticket["summary"],
body = ticket_text(ticket),
created_at = isotime(ticket["createdtime"]),
updated_at = isotime(ticket["modifiedtime"]))
if ticket["status"] == "closed":
issue["closed"] = True
issue["closed_at"] = isotime(ticket["modifiedtime"])
comments.append(dict(created_at = isotime(ticket["modifiedtime"]),
body = "_Closed with resolution {resolution}_\n".format(**ticket)))
if ticket["owner"] in assignee_map:
issue["assignee"] = assignee_map[ticket["owner"]]
labels = [type_map.get(ticket["type"]), resolution_map.get(ticket["resolution"])]
while None in labels:
del labels[labels.index(None)]
if labels:
issue["labels"] = labels
issue = dict(issue = issue)
if comments:
issue["comments"] = comments
with open(os.path.join(cfg["ticket_directory"], "ticket_{:03d}.json".format(ticket["id"])), "wb") as f:
json.dump(issue, f, indent = 4, sort_keys = True, separators=(",", ": "))
|