Skip to content

Commit e4ab870

Browse files
author
Jeroen Baten
committed
First working version
1 parent dacd998 commit e4ab870

File tree

2 files changed

+204
-44
lines changed

2 files changed

+204
-44
lines changed

bugzilla2github.py

+202-44
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818
# 2. Create a separate repo for testing purposes. THERE IS NO UNDO!
1919
# 3. Copy bugzilla2github.conf.sample to bugzilla2github.conf
2020
# - Change all settings to fit your setup
21-
# 4. Run the script. Good luck....
21+
# 4. Please understand there is not API to delete issues. So test thoroughly before the final run!
22+
# 5. Run the script. Good luck....
2223

23-
import json, getopt, os, pprint, re, requests, sys, time, xml.etree.ElementTree
24+
import json, requests, sys
2425
import psycopg2, psycopg2.extras
2526
import ConfigParser
26-
from pprint import pprint,pformat
27-
from urlparse import urljoin
27+
from pprint import pprint
28+
from dateutil.tz import tzlocal
29+
2830

2931
reload(sys)
3032
sys.setdefaultencoding('utf-8')
@@ -68,7 +70,8 @@ def read_bugs(conn):
6870
# create a new cursor object
6971
cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
7072
# execute the SELECT statement
71-
cur.execute(""" SELECT * from bugs where bug_id=1295 """,)
73+
#cur.execute(""" SELECT * from bugs where bug_id=1284 """,)
74+
cur.execute(""" SELECT * from bugs order by bug_id """, )
7275
colnames = [desc[0] for desc in cur.description]
7376
results = cur.fetchall()
7477
except (Exception, psycopg2.DatabaseError) as error:
@@ -174,8 +177,69 @@ def get_reporter(reporters,reporter_id):
174177
reporterstr=reporterobj["realname"]+" \<<"+reporterobj["login_name"]+">\>"
175178
return reporterstr
176179

180+
def get_components(conn):
181+
"""
182+
Retrieve all components from Bugzilla database
183+
:param conn: database connection object
184+
:return: object with all results
185+
"""
186+
try:
187+
# create a new cursor object
188+
cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
189+
# execute the SELECT statement
190+
cur.execute(""" SELECT * FROM components """)
191+
results = cur.fetchall()
192+
colnames = [desc[0] for desc in cur.description]
193+
except (Exception, psycopg2.DatabaseError) as error:
194+
print(error)
195+
# Build indexed dictionary from results
196+
components={}
197+
for result in results:
198+
components[result["id"]]=result
199+
return components,colnames
200+
201+
202+
def get_component(components,component_id):
203+
"""
204+
routine to find componentname for componentid
205+
:param components: dictionary with all components, indexed on componentid
206+
:param component_id: id of component to find
207+
:return: string componentname
208+
"""
209+
# ['userid', 'login_name', 'cryptpassword', 'realname', 'disabledtext', 'disable_mail', 'mybugslink', 'extern_id']
210+
componentobj=components[component_id]
211+
componentstr=componentobj["name"]
212+
return componentstr
213+
214+
def get_duplicates(conn):
215+
"""
216+
Because we have relative few duplicates (55) compared to the total number of bugs (1709)
217+
we retrieve all duplicates from Bugzilla database into dictionary of lists for quick lookup
218+
:param conn: database connection object
219+
:return: object with all results (a dictionary of lists)
220+
"""
221+
duplicates=dict()
222+
try:
223+
# create a new cursor object
224+
cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
225+
# execute the SELECT statement
226+
cur.execute(""" SELECT * FROM duplicates """ )
227+
results = cur.fetchall()
228+
229+
for result in results:
230+
if duplicates.get(result["dupe_of"]) is None:
231+
duplicates[result["dupe_of"]]=list()
232+
duplicates[result["dupe_of"]].append(result["dupe"])
233+
else:
234+
duplicates[result["dupe_of"]].append(result["dupe"])
235+
236+
except (Exception, psycopg2.DatabaseError) as error:
237+
print(error)
238+
239+
return duplicates
240+
177241

178-
def create_body(reporters,bug):
242+
def create_body(reporters,duplicates,bug):
179243
"""
180244
Routine to create issue body text
181245
:param bug: object containing bug record
@@ -191,6 +255,37 @@ def create_body(reporters,bug):
191255
192256
#
193257
# Last updated: 2012-09-10 05:56:17 +0000
258+
#
259+
# Example record info
260+
# bug_id -> 1295
261+
# assigned_to -> 6
262+
# bug_file_loc ->
263+
# bug_severity -> critical
264+
# bug_status -> RESOLVED
265+
# creation_ts -> 2011-12-14 18:05:08
266+
# delta_ts -> 2012-01-13 10:31:16
267+
# short_desc -> TaskSource is null for some TaskElements
268+
# op_sys -> All
269+
# priority -> P5
270+
# product_id -> 2
271+
# rep_platform -> All
272+
# reporter -> 6
273+
# version -> libreplan-1.2 (1.2.x)
274+
# component_id -> 10
275+
# resolution -> FIXED
276+
# target_milestone -> ---
277+
# qa_contact -> None
278+
# status_whiteboard ->
279+
# votes -> 0
280+
# lastdiffed -> 2012-01-13 10:31:16
281+
# everconfirmed -> 1
282+
# reporter_accessible -> 1
283+
# cclist_accessible -> 1
284+
# estimated_time -> 0.00
285+
# remaining_time -> 0.00
286+
# deadline -> None
287+
# alias -> None
288+
# cf_browser -> ---
194289
body=""
195290

196291
# ret["body"].append("# Bugzilla Bug ID: %d" % ret["number"])
@@ -212,19 +307,18 @@ def create_body(reporters,bug):
212307
# ret["body"].append("Version: " + ret["version"])
213308
body+="Version: " + str(bug["version"])+"\n"
214309

215-
# [I'm an inline-style link](https://www.google.com)
216-
body+="\n---\n"
217-
body+="(Note: this issue was migrated automatically with [bugzilla2github.py tool](https://github.com/LibrePlan/bugzilla2github) )\n"
218-
219-
220310
# if "cc" in bug:
221311
# ret["body"].append("CC: " + ", ".join(emails_convert(bug.pop("cc"))))
222312
# TODO: chose not to support this. Use Link to original issue to find info
313+
223314
# # Extra information
224315
# ret["body"].append("")
225316
# if "dup_id" in bug:
226317
# ret["body"].append("Duplicates: " + ids_convert(bug.pop("dup_id")))
227-
# TODO: chose not to support this. Use Link to original issue to find info
318+
if duplicates.get(bug["bug_id"]) is not None:
319+
for duplicate in duplicates[bug["bug_id"]]:
320+
body+="Duplicate: [" + str(duplicate) +"]("+BUGZILLA_URL+"show_bug.cgi?id="+str(bug["bug_id"])+"))\n"
321+
228322
# if "dependson" in bug:
229323
# ret["body"].append("Depends on: " + ids_convert(bug.pop("dependson")))
230324
# TODO: chose not to support this. Use Link to original issue to find info
@@ -233,9 +327,14 @@ def create_body(reporters,bug):
233327
# TODO: chose not to support this. Use Link to original issue to find info
234328
# if "see_also" in bug:
235329
# ret["body"].append("See also: " + bug.pop("see_also"))
236-
# TODO: chose not to support this. Use Link to original issue to find info
330+
237331
# ret["body"].append("Last updated: " + ret["updated_at"])
332+
body+="Last updated: " + str(bug["delta_ts"]) +"\n"
238333
# ret["body"].append("")
334+
# [I'm an inline-style link](https://www.google.com)
335+
body+="\n---\n"
336+
body+="(Note: this issue was migrated automatically with [bugzilla2github.py tool](https://github.com/LibrePlan/bugzilla2github) )\n"
337+
239338
return body
240339

241340
# Start connection to database
@@ -247,26 +346,31 @@ def create_body(reporters,bug):
247346

248347
# read all bug reporters
249348
reporters,colnames=get_reporters(conn)
349+
350+
# read all components
351+
components,colnames=get_components(conn)
352+
353+
# Create a duplicates lookup dictionary of lists
354+
duplicates=get_duplicates(conn)
355+
250356
# read all bug reports
251357
bugs,colnames=read_bugs(conn)
358+
252359
#pprint(bugs)
253360
#pprint(colnames)
254361
issue={}
255362
for bug in bugs:
256363
bug_id=bug['bug_id']
257-
for field in colnames:
258-
print str(field) + " -> " + str(bug[str(field)])
364+
# for field in colnames:
365+
# print str(field) + " -> " + str(bug[str(field)])
259366
print "bug_id",bug_id
260-
#print "bug_severity",bug['bug_severity']
261-
#print "bug_status", bug['bug_status']
262-
#print "short_desc", bug['short_desc']
263-
#print "priority",bug['priority']
264-
#print "version",bug['version']
265-
#print "resolution",bug['resolution']
266367

267-
body=create_body(reporters,bug)
368+
# Check for duplicates
369+
#duplicates = find_duplicates(conn,bug_id)
268370

269-
# Create issues object to send to GitHub
371+
body=create_body(reporters,duplicates,bug)
372+
373+
# Create issues object to send to GitHub: only title and body are needed.
270374
# And an issue with a comment only needs the comment body added:
271375
#
272376
# {
@@ -281,17 +385,62 @@ def create_body(reporters,bug):
281385
# ]
282386
# }
283387

388+
# Complete info:
389+
# "title": "Imported from some other system",
390+
# "body": "...",
391+
# "created_at": "2014-01-01T12:34:58Z",
392+
# "closed_at": "2014-01-02T12:24:56Z",
393+
# "updated_at": "2014-01-03T11:34:53Z",
394+
# "assignee": "jonmagic",
395+
# "milestone": 1,
396+
# "closed": true,
397+
# "labels": [
398+
# "bug",
399+
# "low"
400+
# ]
401+
402+
if bug["bug_status"]=="RESOLVED":
403+
closed=True
404+
else:
405+
closed=False
406+
407+
# Let's create some labels
408+
# NOTE to self: An empty label means issue not added to GitHub!
409+
labels=[]
410+
if bug["bug_severity"]:
411+
labels.append(bug["bug_severity"])
412+
if bug["priority"]:
413+
labels.append(bug["priority"])
414+
if bug["resolution"]:
415+
labels.append(bug["resolution"].lower())
416+
bug_component=get_component(components,bug["component_id"])
417+
if bug_component:
418+
labels.append(bug_component)
419+
#labels.append("mytest")
420+
421+
# TODO We currently do not know WHEN an issue was closed, so do not store it in the issue.
422+
# Well, we could find it through bug_activity table but too much hassle actually.
423+
# Good news is, that we do close an issue when it is closed
424+
# "closed_at":str(bug["delta_ts"].replace(tzinfo=tzlocal()).isoformat()),
284425
issue={"issue": {"title":bug['short_desc'],
285-
"body":body}}
426+
"body":body,
427+
"created_at": str(bug["creation_ts"].replace(tzinfo=tzlocal()).isoformat()),
428+
"closed": closed,
429+
"labels": labels,
430+
}
431+
}
432+
433+
#pprint(issue)
286434

287435
issuecomments=[]
288436
pprint(issuecomments)
289437
comments,colnames=read_comments(conn,bug_id)
290-
pprint(colnames)
291438
for comment in comments:
292439
# ['comment_id', 'bug_id', 'who', 'bug_when', 'work_time', 'thetext', 'isprivate', 'already_wrapped', 'type', 'extra_data']
293440
comment_id=comment["comment_id"]
294441
attach_id=comment["extra_data"]
442+
comment_type=comment["type"] # Very important: 5=attachment
443+
295444
issuecomment=""
296445
# pprint(comment)
297446
print " comment_id", comment["comment_id"]
@@ -302,14 +451,13 @@ def create_body(reporters,bug):
302451
# ret.append("Date: " + comment.pop("bug_when"))
303452
issuecomment += "Date: " + str(comment["bug_when"]) + "\n"
304453
# ret.append("From: " + email_convert(comment.pop("who"),
305-
issuecomment += "From: " + get_reporter(reporters, comment["who"]) + "\n"
454+
issuecomment += "From: " + get_reporter(reporters, comment["who"]) + "\n\n"
306455
# comment.pop("who.name", None)))
307-
# ret.append("")
308456
# ret.append(comment.pop("thetext", ""))
309457
issuecomment += comment["thetext"] + "\n\n"
310458

311459

312-
if attach_id is not None:
460+
if comment_type==5 and attach_id is not None:
313461
print " attach_id",attach_id
314462
# check attachment record for this comment
315463
attachment,colnames=read_attachment(conn,attach_id)
@@ -318,7 +466,6 @@ def create_body(reporters,bug):
318466
# 'attach_id', 'bug_id', 'creation_ts', 'modification_time', 'description', 'mimetype', 'ispatch', 'filename',
319467
# 'submitter_id', 'isobsolete', 'isprivate', 'isurl']
320468
#pprint(attachment)
321-
#attachment=attachment.pop
322469
filename=attachment["filename"]
323470
#filesize=attachment[""]
324471
print " We have an attachment:",filename
@@ -327,41 +474,52 @@ def create_body(reporters,bug):
327474
# attach.pop("type"), attach.pop("size")))
328475
# ret.append("> Description: " + attach.pop("desc"))
329476
# TODO: create link to repo file as: https://github.com/LibrePlan/bugzilla2githubattachments/blob/master/LICENSE
330-
issuecomment += "\n---\n\nAttached file: [" + filename + "](" + FILEREPO_URL + str(bug_id) + '_' + str(comment_id) + "_" + filename + ")\n"
331-
issuecomment += "Description: " + attachment["description"] + "\n---\n"
477+
issuecomment += "\n---\nAttached file: [" + filename + "](" + FILEREPO_URL + str(bug_id) + '_' + str(comment_id) + "_" + filename + ")\n"
478+
issuecomment += "File description: " + attachment["description"] + "\n"
332479

333480
# add stuff to issuecomments list
334481
issuedict = dict()
335482
issuedict["body"] = issuecomment
336483
issuecomments.append(issuedict)
337484

338-
339-
#issue["issue"].append(issuecomments)
340-
#commentsdict={ "comments", issuecomments }
341-
#commentsdict=dict()
342-
#commentsdict.setdefault("comments", issuecomments)
343485
issue["comments"]=issuecomments
344486
# What have we created?
487+
print "*" * 40 + " Final issue object " + "*" * 40
345488
pprint(issue)
346489

347490
# Now let's try to add this issue to a github repo
348491
# https://api.github.com/repos/${GITHUB_USERNAME}/foo/import/issues
349492
urlparts=(str(GITHUB_URL),"repos" ,str(GITHUB_OWNER) , str(GITHUB_REPO) , "import/issues")
350493
url="/".join(urlparts)
351-
# if url[0] == "/":
352-
# u = "%s%s" % (GITHUB_URL, url)
353-
# else:
354-
# u = "%s/repos/%s/%s/%s" % (GITHUB_URL, GITHUB_OWNER, GITHUB_REPO, url)
494+
355495
pprint(url)
356496
d=issue
357-
#sys.exit(4)
358-
#result = requests.post(u, params={"access_token": GITHUB_TOKEN,
359497
headers={"Authorization": "token "+ GITHUB_TOKEN,
360498
"Accept": "application/vnd.github.golden-comet-preview+json" }
361499
result=requests.post(url, headers=headers, data = json.dumps(d))
500+
print result.status_code
501+
# Check if something went wrong
502+
if result.status_code<>202:
503+
print result.json()
504+
result_dict=result.json()
505+
# Let's check if it all worked
506+
result_id=result_dict["id"]
507+
import_issues_url=result_dict["import_issues_url"]
508+
print "Result ID = "+str(result_id)
509+
print "import_issues_url= " + str(result_dict["import_issues_url"])
510+
# First get ID of issue added
511+
# Next request result
512+
# curl -H "Authorization: token ${GITHUB_TOKEN}" \
513+
#-H "Accept: application/vnd.github.golden-comet-preview+json" \
514+
#https://api.github.com/repos/#{GITHUB_USERNAME}/foo/import/issues/7
515+
url2=import_issues_url + "/"+ str(result_id)
516+
#print(url2)
517+
result2=requests.get(import_issues_url, headers=headers, data = json.dumps(d))
518+
pprint(result2)
519+
print result2.json()
520+
# Bail out on error
521+
sys.exit(2)
362522

363-
pprint(result)
364-
print result.json()
365523
# The end of handling all bugs
366524

367525
# close the communication with the PostgresQL database

freeze.txt

+2
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,7 @@ idna==2.6
44
pkg-resources==0.0.0
55
psycopg2==2.7.4
66
psycopg2-binary==2.7.4
7+
python-dateutil==2.6.1
78
requests==2.18.4
9+
six==1.11.0
810
urllib3==1.22

0 commit comments

Comments
 (0)