Bugtracker: Add support for commits.

This commit is contained in:
Krytarik Raido 2020-06-21 09:04:04 +02:00
parent 7473bf6fa7
commit e0926a9a3c
4 changed files with 178 additions and 90 deletions

View File

@ -24,7 +24,7 @@ import supybot.world as world
from imp import reload
__version__ = "4.2.0"
__version__ = "4.3.0"
__author__ = supybot.Author("Krytarik Raido", "krytarik", "krytarik@tuxgarage.com")
__contributors__ = {
supybot.Author("Dennis Kaarsemaker", "Seveas", "dennis@kaarsemaker.net"): ['Original Author'],

View File

@ -49,8 +49,9 @@ def configure(advanced):
else:
return repeatdelay
output("Each of the next 3 questions can be set per-channel with the '@config channel' command.")
output("Each of the next 4 questions can be set per-channel with the '@config channel' command.")
bugSnarfer = yn("Enable detecting bug numbers and URLs in all channels?", default=Bugtracker.bugSnarfer._default)
commitSnarfer = yn("Enable detecting commit hashes and URLs in all channels?", default=Bugtracker.commitSnarfer._default)
cveSnarfer = yn("Enable detecting CVE numbers and URLs in all channels?", default=Bugtracker.cveSnarfer._default)
oopsSnarfer = yn("Enable detecting Launchpad OOPS IDs in all channels?", default=Bugtracker.oopsSnarfer._default)
if advanced:
@ -72,6 +73,7 @@ def configure(advanced):
saveDiscoveredTrackers = yn("Save automatically discovered trackers to configuration?", default=Bugtracker.saveDiscoveredTrackers._default)
Bugtracker.bugSnarfer.setValue(bugSnarfer)
Bugtracker.commitSnarfer.setValue(commitSnarfer)
Bugtracker.cveSnarfer.setValue(cveSnarfer)
Bugtracker.oopsSnarfer.setValue(oopsSnarfer)
Bugtracker.replyNoBugtracker.setValue(replyNoBugtracker)
@ -90,6 +92,11 @@ conf.registerChannelValue(Bugtracker, 'bugSnarfer',
enabled, such that any bugtracker URLs and bug ### seen in the channel
will have their information reported into the channel."""))
conf.registerChannelValue(Bugtracker, 'commitSnarfer',
registry.Boolean(False, """Determines whether the commit snarfer will be
enabled, such that any commit URLs and commit ### seen in the channel
will have their information reported into the channel."""))
conf.registerChannelValue(Bugtracker, 'cveSnarfer',
registry.Boolean(False, """Determines whether the CVE snarfer will be
enabled, such that any CVE URLs and CVE-????-???? seen in the channel

View File

@ -71,7 +71,7 @@ class Bugtracker(callbacks.PluginRegexp):
"""Show a link to a bug report with a brief description"""
threaded = True
callBefore = ('URL')
regexps = ('turlSnarfer', 'bugSnarfer', 'cveSnarfer', 'oopsSnarfer')
regexps = ('bugSnarfer', 'bugUrlSnarfer', 'commitSnarfer', 'commitUrlSnarfer', 'cveSnarfer', 'oopsSnarfer')
def __init__(self, irc):
self.__parent = super(Bugtracker, self)
@ -97,14 +97,14 @@ class Bugtracker(callbacks.PluginRegexp):
supylog.warning("Bugtracker: Unknown trackertype '%s' (%s)" % (trackers[name].trackertype(), name))
self.shorthand = utils.abbrev(list(self.db.keys()))
def is_ok(self, channel, tracker, bug):
def is_ok(self, channel, tracker, bugid):
"""Flood/repeat protection"""
now = time.time()
for k in list(self.shown.keys()):
if self.shown[k] < now - self.registryValue('repeatdelay', channel):
self.shown.pop(k)
if (channel, tracker, bug) not in self.shown:
self.shown[(channel, tracker, bug)] = now
if (channel, tracker, bugid) not in self.shown:
self.shown[(channel, tracker, bugid)] = now
return True
return False
@ -308,26 +308,39 @@ class Bugtracker(callbacks.PluginRegexp):
def bugSnarfer(self, irc, msg, match):
r"(?P<bt>[a-z][^\s:]*(\s+(bug|ticket|issue|pull|pr|merge|mr)('?s)?)?):*\s+#?(?P<bug>\d+(?!\d*[-.]\d+)(\s*([,\s]+|[,\s]*(and|und|en|et|ir|[&+]+))\s*#?\d+(?!\d*[-.]\d+))*)"
self.termSnarfer(irc, msg, match, 'bug')
def commitSnarfer(self, irc, msg, match):
r"(?P<bt>[a-z][^\s:]*(\s+(commit)('?s)?)?):*\s+#?(?P<bug>[a-f0-9]{7,}(?![a-f0-9]*[-.][a-f0-9]{7,})(\s*([,\s]+|[,\s]*(and|und|en|et|ir|[&+]+))\s*#?[a-f0-9]{7,}(?![a-f0-9]*[-.][a-f0-9]{7,}))*)"
self.termSnarfer(irc, msg, match, 'commit')
def termSnarfer(self, irc, msg, match, termtype):
channel = msg.args[0] if ircutils.isChannel(msg.args[0]) else None
if checkAddressed(msg.args[1].strip(), channel):
return
if not self.registryValue('bugSnarfer', channel):
if not self.registryValue('{}Snarfer'.format(termtype), channel):
return
nbugs = msg.tagged('nbugs') or 0
if nbugs >= 5:
return
bugids = re.split(r'[^\d]+', match.group('bug'))[:5-nbugs]
if termtype != 'commit':
bugids = re.findall(r'[0-9]+', match.group('bug'))[:5-nbugs]
else:
bugids = re.findall(r'[a-f0-9]{7,}', match.group('bug'))[:5-nbugs]
# Begin HACK
# Strings like "Ubuntu 1004" and "Ubuntu 1610" are false triggers for us
if match.group('bt').lower() == 'ubuntu':
if match.group('bt').lower() == 'ubuntu' and termtype == 'bug':
bugids = [x for x in bugids if not re.match(r'^([4-9]|[12][0-9])(04|10)$', x)]
# End HACK
# Get tracker name
bt = [x.lower() for x in match.group('bt').split()]
sure_bug = re.match(r"^(?P<type>bug|ticket|issue|pull|pr|merge|mr)('?s)?$", bt[-1])
if termtype != 'commit':
sure_bug = re.match(r"^(?P<type>bug|ticket|issue|pull|pr|merge|mr)('?s)?$", bt[-1])
else:
sure_bug = re.match(r"^(?P<type>commit)('?s)?$", bt[-1])
# Get bug type
if sure_bug:
@ -337,7 +350,7 @@ class Bugtracker(callbacks.PluginRegexp):
bugids = list(set(bugids)) # remove dupes
if not sure_bug:
if not sure_bug and termtype == 'bug':
bugids = [x for x in bugids if int(x) > 100]
msg.tag('nbugs', nbugs + len(bugids))
@ -376,13 +389,12 @@ class Bugtracker(callbacks.PluginRegexp):
return
for bugid in bugids:
bugid = int(bugid)
try:
report = self.get_bug(channel or msg.nick, tracker, bugtype, bugid, self.registryValue('showassignee', channel),
self.registryValue('extended', channel), do_tracker=showTracker)
except trackers.BugNotFoundError:
if self.registryValue('replyWhenNotFound'):
irc.error("Could not find %s bug %d" % (tracker.description, bugid))
irc.error("Could not find %s %s %s" % (tracker.description, termtype, bugid))
except trackers.BugtrackerError as e:
if self.registryValue('replyWhenError') and sure_bug:
irc.error(str(e))
@ -390,19 +402,26 @@ class Bugtracker(callbacks.PluginRegexp):
if report:
irc.reply(report)
def turlSnarfer(self, irc, msg, match):
r"(https?://)?((bugs\.debian\.org|pad\.lv)/|\S+/(show_bug\.cgi\?id=|bugreport\.cgi\?bug=|view\.php\?id=|bug=|bugs/|\+bug/|ticket/|feature-requests/|patches/|todo/|issues/|pulls?/|merge_requests/))(?P<bug>\d+)/?"
def bugUrlSnarfer(self, irc, msg, match):
r"(https?://)?((bugs\.debian\.org|pad\.lv)/|\S+/(show_bug\.cgi\?id=|bugreport\.cgi\?bug=|view\.php\?id=|bug=|bugs/|\+bug/|ticket/|feature-requests/|patches/|todo/|issues/|pulls?/|merge_requests/))(?P<bug>\d+)"
self.urlSnarfer(irc, msg, match, 'bug')
def commitUrlSnarfer(self, irc, msg, match):
r"(https?://)?\S+/commits?/(?P<bug>[a-f0-9]{7,})"
self.urlSnarfer(irc, msg, match, 'commit')
def urlSnarfer(self, irc, msg, match, urltype):
channel = msg.args[0] if ircutils.isChannel(msg.args[0]) else None
if checkAddressed(msg.args[1].strip(), channel):
return
if not self.registryValue('bugSnarfer', channel):
if not self.registryValue('{}Snarfer'.format(urltype), channel):
return
nbugs = msg.tagged('nbugs') or 0
if nbugs >= 5:
return
msg.tag('nbugs', nbugs+1)
url = match.group(0)
bugid = int(match.group('bug'))
bugid = match.group('bug')
if '://' in url:
url = url[url.rfind('://')+3:]
try:
@ -413,7 +432,7 @@ class Bugtracker(callbacks.PluginRegexp):
self.registryValue('extended', channel), do_url=False)
except trackers.BugNotFoundError:
if self.registryValue('replyWhenNotFound'):
irc.error("Could not find %s bug %s" % (tracker.description, match.group('bug')))
irc.error("Could not find %s %s %s" % (tracker.description, urltype, match.group('bug')))
except trackers.BugtrackerError as e:
if self.registryValue('replyWhenError'):
irc.error(str(e))
@ -423,7 +442,7 @@ class Bugtracker(callbacks.PluginRegexp):
# Only useful to Launchpad developers
def oopsSnarfer(self, irc, msg, match):
r"(https?://\S+[=/])?OOPS-(?P<oopsid>[\dA-Za-z]{6,})"
r"(https?://\S+[=/])?OOPS-(?P<oopsid>[a-f0-9]{6,})"
channel = msg.args[0] if ircutils.isChannel(msg.args[0]) else None
if checkAddressed(msg.args[1].strip(), channel):
return
@ -482,11 +501,11 @@ class Bugtracker(callbacks.PluginRegexp):
tracker = trackers.SourceForge().get_tracker(snarfurl)
elif 'github.com' in snarfurl:
tracker = trackers.GitHub().get_tracker(snarfurl)
elif re.match(r'[^\s/]+/[^\s/]+(/[^\s/]+)+/-/(issues|merge_requests)', snarfurl) \
elif re.match(r'[^\s/]+/[^\s/]+(/[^\s/]+)+/-/(issues|merge_requests|commits?)', snarfurl) \
or re.match(r'[^\s/]+/[^\s/]+(/[^\s/]+)+/merge_requests', snarfurl) \
or re.match(r'[^\s/]+/[^\s/]+(/[^\s/]+){2,}/issues', snarfurl):
or re.match(r'[^\s/]+/[^\s/]+(/[^\s/]+){2,}/(issues|commits?)', snarfurl):
tracker = trackers.GitLab().get_tracker(snarfurl, bugid)
elif re.match(r'[^\s/]+/[^\s/]+/[^\s/]+/issues', snarfurl):
elif re.match(r'[^\s/]+/[^\s/]+/[^\s/]+/(issues|commits?)', snarfurl):
tracker = trackers.GitLab().get_tracker(snarfurl, bugid)
if not tracker:
tracker = trackers.Gitea().get_tracker(snarfurl, bugid)
@ -519,20 +538,20 @@ class Bugtracker(callbacks.PluginRegexp):
if duplicate and not self.is_ok(channel, tracker, bugid):
return
bugtype = re.match(r'.*/(feature-)?(?P<type>request|patch|todo|issue|pull|merge|ticket)(_requests)?(e?s)?/[0-9]+/?$', url)
bugtype = re.match(r'.*/(feature-)?(?P<type>request|patch|todo|issue|pull|merge|ticket|commit)(_requests)?(e?s)?/[a-f0-9]+$', url)
if do_tracker and tracker.trackertype not in ('github', 'gitlab', 'gitea'):
if re.match(r'.*/(bugs|feature-requests|patches|todo|issues|pulls?|merge_requests|ticket)/?$', tracker.description):
report = '%s %d' % (tracker.description, bugid)
if re.match(r'.*/(bugs|feature-requests|patches|todo|issues|pulls?|merge_requests|ticket|commits?)$', tracker.description):
report = '%s %s' % (tracker.description, bugid)
else:
if bugtype:
report = '%s %s %d' % (tracker.description, bugtype.group('type'), bugid)
report = '%s %s %s' % (tracker.description, bugtype.group('type'), bugid)
else:
report = '%s bug %d' % (tracker.description, bugid)
report = '%s bug %s' % (tracker.description, bugid)
else:
if bugtype:
report = '%s %d' % (bugtype.group('type').title(), bugid)
report = '%s %s' % (bugtype.group('type').title(), bugid)
else:
report = 'Bug %d' % bugid
report = 'Bug %s' % bugid
if product:
report += ' in %s' % product
@ -548,8 +567,10 @@ class Bugtracker(callbacks.PluginRegexp):
severity_status = []
if severity:
severity_status.append(' '.join(word[0].upper() + word[1:].lower() for word in severity.split()))
severity_status.append(' '.join(word[0].upper() + word[1:].lower() for word in status.split()))
report += ' [%s]' % ', '.join(severity_status)
if status:
severity_status.append(' '.join(word[0].upper() + word[1:].lower() for word in status.split()))
if severity_status:
report += ' [%s]' % ', '.join(severity_status)
if duplicate:
report += ' [duplicate: %s]' % duplicate[0]

View File

@ -114,7 +114,7 @@ class Bugzilla(IBugtracker):
pass
def get_bug(self, bugtype, bugid):
url = "%s/rest/bug/%d" % (self.url, bugid)
url = "%s/rest/bug/%s" % (self.url, bugid)
try:
bugjson = utils.web.getUrl(url)
bug = json.loads(bugjson.decode('utf-8'))['bugs'][0]
@ -134,12 +134,12 @@ class Bugzilla(IBugtracker):
else:
assignee = ''
return (bugid, bug['product'], bug['summary'], bug['severity'], status, assignee,
"%s/show_bug.cgi?id=%d" % (self.url, bugid), [], [])
"%s/show_bug.cgi?id=%s" % (self.url, bugid), [], [])
except Exception as e:
raise BugtrackerError(self.errparse % (self.description, e, url))
def get_bug_old(self, bugtype, bugid): # Deprecated
url = "%s/show_bug.cgi?id=%d&ctype=xml" % (self.url, bugid)
url = "%s/show_bug.cgi?id=%s&ctype=xml" % (self.url, bugid)
try:
bugxml = utils.web.getUrl(url)
zilladom = minidom.parseString(bugxml)
@ -150,7 +150,7 @@ class Bugzilla(IBugtracker):
errtxt = bug_n.getAttribute('error')
if errtxt in ('NotFound', 'InvalidBugId'):
raise BugNotFoundError
s = 'Could not get %s bug #%d: %s' % (self.description, bugid, errtxt)
s = 'Could not get %s bug #%s: %s' % (self.description, bugid, errtxt)
raise BugtrackerError(s)
try:
title = _getnodetxt(bug_n.getElementsByTagName('short_desc')[0])
@ -170,7 +170,7 @@ class Bugzilla(IBugtracker):
assignee = ''
except Exception as e:
raise BugtrackerError(self.errparse % (self.description, e, url))
return (bugid, product, title, severity, status, assignee, "%s/show_bug.cgi?id=%d" % (self.url, bugid), [], [])
return (bugid, product, title, severity, status, assignee, "%s/show_bug.cgi?id=%s" % (self.url, bugid), [], [])
class Launchpad(IBugtracker):
statuses = ("Unknown", "Invalid", "Opinion", "Won't Fix", "Fix Released", "Fix Committed", "New",
@ -259,7 +259,7 @@ class Launchpad(IBugtracker):
def get_bug_new(self, bugtype, bugid): #TODO: Rename this method to 'get_bug'
try:
bugdata = self.lp.bugs[bugid]
bugdata = self.lp.bugs[int(bugid)]
if bugdata.private:
raise BugtrackerError("This bug is private")
duplicate = []
@ -287,27 +287,27 @@ class Launchpad(IBugtracker):
if type(e).__name__ == 'HTTPError': # messy, but saves trying to import lazr.restfulclient.errors.HTPError
if e.response.status == 404:
bugNo = e.content.split()[-1][2:-1] # extract the real bug number
if bugNo != str(bugid): # A duplicate of a private bug, at least we know it exists
raise BugtrackerError('Bug #%d is a duplicate of bug #%s, but it is private (%s/bugs/%s)' % (bugid, bugNo, self.url, bugNo))
raise BugtrackerError("Bug #%d is private or does not exist (%s/bugs/%d)" % (bugid, self.url, bugid)) # Could be private, could just not exist
raise BugtrackerError(self.errget % (self.description, e, '%s/bugs/%d' % (self.url, bugid)))
if bugNo != bugid: # A duplicate of a private bug, at least we know it exists
raise BugtrackerError('Bug #%s is a duplicate of bug #%s, but it is private (%s/bugs/%s)' % (bugid, bugNo, self.url, bugNo))
raise BugtrackerError("Bug #%s is private or does not exist (%s/bugs/%s)" % (bugid, self.url, bugid)) # Could be private, could just not exist
raise BugtrackerError(self.errget % (self.description, e, '%s/bugs/%s' % (self.url, bugid)))
elif isinstance(e, KeyError):
raise BugNotFoundError
raise BugtrackerError(self.errget % (self.description, e, '%s/bugs/%d' % (self.url, bugid)))
raise BugtrackerError(self.errget % (self.description, e, '%s/bugs/%s' % (self.url, bugid)))
return (bugdata.id, taskdata.bug_target_display_name, bugdata.title, taskdata.importance, taskdata.status,
assignee, "%s/bugs/%d" % (self.url, bugdata.id), extinfo, duplicate)
assignee, "%s/bugs/%s" % (self.url, bugdata.id), extinfo, duplicate)
def get_bug_old(self, bugtype, bugid, duplicate=None): # Deprecated
try:
bugdata = utils.web.getUrl("%s/bugs/%d/+text" % (self.url, bugid)).decode('utf-8')
bugdata = utils.web.getUrl("%s/bugs/%s/+text" % (self.url, bugid)).decode('utf-8')
except Exception as e:
if 'HTTP Error 404' in str(e):
if duplicate:
raise BugtrackerError('Bug #%d is a duplicate of bug #%d, but it is private (%s/bugs/%d)' % (duplicate, bugid, self.url, bugid))
raise BugtrackerError('Bug #%s is a duplicate of bug #%s, but it is private (%s/bugs/%s)' % (duplicate, bugid, self.url, bugid))
else:
raise BugNotFoundError
raise BugtrackerError(self.errget % (self.description, e, '%s/bugs/%d' % (self.url, bugid)))
raise BugtrackerError(self.errget % (self.description, e, '%s/bugs/%s' % (self.url, bugid)))
try:
# Split bug data into separate pieces (bug data, task data)
@ -324,16 +324,16 @@ class Launchpad(IBugtracker):
else:
assignee = ''
except Exception as e:
raise BugtrackerError(self.errparse % (self.description, e, '%s/bugs/%d' % (self.url, bugid)))
raise BugtrackerError(self.errparse % (self.description, e, '%s/bugs/%s' % (self.url, bugid)))
# Try and find duplicates
if bugdata['duplicate-of']:
data = self.get_bug_old(bugtype, int(bugdata['duplicate-of']), duplicate or bugid)
data = self.get_bug_old(bugtype, bugdata['duplicate-of'], duplicate or bugid)
data[8].append(bugdata['bug'])
return data
return (bugid, taskdata['task'], bugdata['title'], taskdata['importance'], taskdata['status'],
assignee, "%s/bugs/%d" % (self.url, bugid), [], [])
assignee, "%s/bugs/%s" % (self.url, bugid), [], [])
# <rant>
# Debbugs sucks donkeyballs
@ -351,7 +351,7 @@ class Debbugs(IBugtracker):
self.soap_client = SoapClient("%s/cgi-bin/soap.cgi" % self.url, namespace="Debbugs/SOAP")
def get_bug(self, bugtype, bugid):
url = "%s/cgi-bin/bugreport.cgi?bug=%d" % (self.url, bugid)
url = "%s/cgi-bin/bugreport.cgi?bug=%s" % (self.url, bugid)
try:
raw = self.soap_client.get_status(bugs=bugid)
except Exception as e:
@ -364,7 +364,7 @@ class Debbugs(IBugtracker):
status = 'Fixed'
else:
status = 'Open'
return (bugid, str(raw.package), str(raw.subject), str(raw.severity), status, '', "%s/%d" % (self.url, bugid), [], [])
return (bugid, str(raw.package), str(raw.subject), str(raw.severity), status, '', "%s/%s" % (self.url, bugid), [], [])
except Exception as e:
raise BugtrackerError(self.errparse % (self.description, e, url))
@ -380,7 +380,7 @@ class SourceForge(IBugtracker):
pass
def get_bug(self, bugtype, bugid):
url = "%s/%d/" % (self.url.replace('sourceforge.net', 'sourceforge.net/rest'), bugid)
url = "%s/%s/" % (self.url.replace('sourceforge.net', 'sourceforge.net/rest'), bugid)
try:
bugjson = utils.web.getUrl(url)
bug = json.loads(bugjson.decode('utf-8'))['ticket']
@ -393,31 +393,40 @@ class SourceForge(IBugtracker):
if '_priority' in bug['custom_fields']:
severity = 'Pri: %s' % bug['custom_fields']['_priority']
return (bugid, product, bug['summary'], severity, ': '.join(bug['status'].split('-')),
bug['assigned_to'], "%s/%d/" % (self.url, bugid), [], [])
bug['assigned_to'], "%s/%s/" % (self.url, bugid), [], [])
except Exception as e:
raise BugtrackerError(self.errparse % (self.description, e, url))
class GitHub(IBugtracker):
def get_tracker(self, url):
try:
match = re.match(r'github\.com/[^\s/]+/[^\s/]+/(issues|pulls?)', url)
match = re.match(r'github\.com/[^\s/]+/[^\s/]+/(issues|pulls?|commits?)', url)
desc = match.group(0)
url = 'https://%s' % desc
# Pulls are inconsistent in main and single page URLs
desc = re.sub(r'/pull$', r'/pulls', desc)
# Commits are inconsistent in main and single page URLs
desc = re.sub(r'/commit$', r'/commits', desc)
name = desc.lower()
return GitHub(name, url, desc, 'github')
except:
pass
def get_bug(self, bugtype, bugid):
url = "%s/%d" % (self.url.replace('github.com', 'api.github.com/repos'), bugid)
url = "%s/%s" % (self.url.replace('github.com', 'api.github.com/repos'), bugid)
# Pulls are inconsistent in web and API URLs
url = url.replace('/pull/', '/pulls/')
# Commits are inconsistent in web and API URLs
url = url.replace('/commit/', '/commits/')
if bugtype in ('issue', 'bug'):
url = url.replace('/pulls/', '/issues/')
url = url.replace('/commits/', '/issues/')
elif bugtype in ('pull', 'pr', 'merge', 'mr'):
url = url.replace('/issues/', '/pulls/')
url = url.replace('/commits/', '/pulls/')
elif bugtype == 'commit':
url = url.replace('/issues/', '/commits/')
url = url.replace('/pulls/', '/commits/')
try:
bugjson = utils.web.getUrl(url)
bug = json.loads(bugjson.decode('utf-8'))
@ -425,27 +434,40 @@ class GitHub(IBugtracker):
raise BugtrackerError(self.errget % (self.description, e, url))
try:
product = '/'.join(self.url.split('/')[-3:-1])
if 'merged' in bug and bug['merged']:
status = 'Merged'
else:
status = bug['state']
if bug['assignee']:
assignee = bug['assignee']['login']
if '/commits/' not in url:
title = bug['title']
if 'merged' in bug and bug['merged']:
status = 'Merged'
else:
status = bug['state']
if bug['assignee']:
assignee = bug['assignee']['login']
else:
assignee = ''
else:
bugid = bug['sha'][:7]
title = bug['commit']['message'].split('\n', 1)[0]
status = ''
assignee = ''
return (bugid, product, bug['title'], '', status, assignee, bug['html_url'], [], [])
return (bugid, product, title, '', status, assignee, bug['html_url'], [], [])
except Exception as e:
raise BugtrackerError(self.errparse % (self.description, e, url))
class GitLab(IBugtracker):
def get_tracker(self, url, bugid):
try:
match = re.match(r'[^\s/]+/(?P<project>[^\s/]+/[^\s/]+(/[^\s/]+)*?)/(-/)?(issues|merge_requests)', url)
match = re.match(r'[^\s/]+/(?P<project>[^\s/]+/[^\s/]+(/[^\s/]+)*?)/(-/)?(issues|merge_requests|commits?)', url)
desc = match.group(0)
name = desc.lower()
url = 'https://%s' % desc
bugurl = "%s/%d" % (re.sub(r'(://[^\s/]+)/[^\s/]+(/[^\s/]+)+/(-/)?',
# Commits are inconsistent in main and single page URLs
desc = re.sub(r'/commit$', r'/commits', desc)
name = desc.lower()
bugurl = "%s/%s" % (re.sub(r'(://[^\s/]+)/[^\s/]+(/[^\s/]+)+/(-/)?',
r'\g<1>/api/v4/projects/%s/' % match.group('project').replace('/', '%2F'), url), bugid)
# Commits are inconsistent in web and API URLs
bugurl = bugurl.replace('/commit/', '/commits/')
# Commits need an extra bit on API URLs
bugurl = bugurl.replace('/commits/', '/repository/commits/')
bugjson = utils.web.getUrl(bugurl)
bug = json.loads(bugjson.decode('utf-8'))
return GitLab(name, url, desc, 'gitlab')
@ -453,13 +475,22 @@ class GitLab(IBugtracker):
pass
def get_bug(self, bugtype, bugid):
match = re.match(r'[^\s:]+://[^\s/]+/(?P<project>[^\s/]+/[^\s/]+(/[^\s/]+)*?)/(-/)?(issues|merge_requests)', self.url)
url = "%s/%d" % (re.sub(r'(://[^\s/]+)/[^\s/]+(/[^\s/]+)+/(-/)?',
match = re.match(r'[^\s:]+://[^\s/]+/(?P<project>[^\s/]+/[^\s/]+(/[^\s/]+)*?)/(-/)?(issues|merge_requests|commits?)', self.url)
url = "%s/%s" % (re.sub(r'(://[^\s/]+)/[^\s/]+(/[^\s/]+)+/(-/)?',
r'\g<1>/api/v4/projects/%s/' % match.group('project').replace('/', '%2F'), self.url), bugid)
# Commits are inconsistent in web and API URLs
url = url.replace('/commit/', '/commits/')
if bugtype in ('issue', 'bug'):
url = url.replace('/merge_requests/', '/issues/')
url = url.replace('/commits/', '/issues/')
elif bugtype in ('merge', 'mr', 'pull', 'pr'):
url = url.replace('/issues/', '/merge_requests/')
url = url.replace('/commits/', '/merge_requests/')
elif bugtype == 'commit':
url = url.replace('/issues/', '/commits/')
url = url.replace('/merge_requests/', '/commits/')
# Commits need an extra bit on API URLs
url = url.replace('/commits/', '/repository/commits/')
try:
bugjson = utils.web.getUrl(url)
bug = json.loads(bugjson.decode('utf-8'))
@ -467,27 +498,40 @@ class GitLab(IBugtracker):
raise BugtrackerError(self.errget % (self.description, e, url))
try:
product = match.group('project')
status = bug['state']
if bug['assignees']:
assino = len(bug['assignees'])
if assino == 1:
assignee = bug['assignees'][0]['name']
if '/commits/' not in url:
title = bug['title']
status = bug['state']
if bug['assignees']:
assino = len(bug['assignees'])
if assino == 1:
assignee = bug['assignees'][0]['name']
else:
assignee = '%d people' % assino
else:
assignee = '%d people' % assino
assignee = ''
else:
bugid = bug['id'][:7]
title = bug['message'].split('\n', 1)[0]
status = ''
assignee = ''
return (bugid, product, bug['title'], '', status, assignee, bug['web_url'], [], [])
return (bugid, product, title, '', status, assignee, bug['web_url'], [], [])
except Exception as e:
raise BugtrackerError(self.errparse % (self.description, e, url))
class Gitea(IBugtracker):
def get_tracker(self, url, bugid):
try:
match = re.match(r'[^\s/]+/[^\s/]+/[^\s/]+/(issues|pulls)', url)
match = re.match(r'[^\s/]+/[^\s/]+/[^\s/]+/(issues|pulls|commits?)', url)
desc = match.group(0)
name = desc.lower()
url = 'https://%s' % desc
bugurl = '%s/%d' % (re.sub(r'(://[^\s/]+)/', r'\g<1>/api/v1/repos/', url), bugid)
# Commits are inconsistent in main and single page URLs
desc = re.sub(r'/commit$', r'/commits', desc)
name = desc.lower()
bugurl = '%s/%s' % (re.sub(r'(://[^\s/]+)/', r'\g<1>/api/v1/repos/', url), bugid)
# Commits are inconsistent in web and API URLs
bugurl = bugurl.replace('/commit/', '/commits/')
# Commits need an extra bit on API URLs
bugurl = bugurl.replace('/commits/', '/git/commits/')
bugjson = utils.web.getUrl(bugurl)
bug = json.loads(bugjson.decode('utf-8'))
return Gitea(name, url, desc, 'gitea')
@ -495,11 +539,20 @@ class Gitea(IBugtracker):
pass
def get_bug(self, bugtype, bugid):
url = "%s/%d" % (re.sub(r'(://[^\s/]+)/', r'\g<1>/api/v1/repos/', self.url), bugid)
url = "%s/%s" % (re.sub(r'(://[^\s/]+)/', r'\g<1>/api/v1/repos/', self.url), bugid)
# Commits are inconsistent in web and API URLs
url = url.replace('/commit/', '/commits/')
if bugtype in ('issue', 'bug'):
url = url.replace('/pulls/', '/issues/')
url = url.replace('/commits/', '/issues/')
elif bugtype in ('pull', 'pr', 'merge', 'mr'):
url = url.replace('/issues/', '/pulls/')
url = url.replace('/commits/', '/pulls/')
elif bugtype == 'commit':
url = url.replace('/issues/', '/commits/')
url = url.replace('/pulls/', '/commits/')
# Commits need an extra bit on API URLs
url = url.replace('/commits/', '/git/commits/')
try:
bugjson = utils.web.getUrl(url)
bug = json.loads(bugjson.decode('utf-8'))
@ -507,20 +560,27 @@ class Gitea(IBugtracker):
raise BugtrackerError(self.errget % (self.description, e, url))
try:
product = '/'.join(self.url.split('/')[-3:-1])
if 'merged' in bug and bug['merged']:
status = 'Merged'
else:
status = bug['state']
if bug['assignee']:
assignee = bug['assignee']['username']
if '/commits/' not in url:
title = bug['title']
if 'merged' in bug and bug['merged']:
status = 'Merged'
else:
status = bug['state']
if bug['assignee']:
assignee = bug['assignee']['username']
else:
assignee = ''
else:
bugid = bug['sha'][:7]
title = bug['commit']['message'].split('\n', 1)[0]
status = ''
assignee = ''
# Issues have no 'html_url', but pulls do
# Issues have no 'html_url', but pulls and commits do
if 'html_url' in bug:
htmlurl = bug['html_url']
else:
htmlurl = url.replace('/api/v1/repos/', '/')
return (bugid, product, bug['title'], '', status, assignee, htmlurl, [], [])
return (bugid, product, title, '', status, assignee, htmlurl, [], [])
except Exception as e:
raise BugtrackerError(self.errparse % (self.description, e, url))
@ -540,7 +600,7 @@ class Mantis(IBugtracker):
pass
def get_bug(self, bugtype, bugid):
url = "%s/api/rest/issues/%d" % (self.url, bugid)
url = "%s/api/rest/issues/%s" % (self.url, bugid)
try:
bugjson = utils.web.getUrl(url)
bug = json.loads(bugjson.decode('utf-8'))['issues'][0]
@ -555,11 +615,11 @@ class Mantis(IBugtracker):
raise BugtrackerError(self.errparse % (self.description, e, url))
def get_bug_old(self, bugtype, bugid): # Deprecated
url = "%s/view.php?id=%d" % (self.url, bugid)
url = "%s/view.php?id=%s" % (self.url, bugid)
try:
raw = self.soap_client.mc_issue_get(username='', password='', issue_id=bugid)
except Exception as e:
if 'Issue #%d not found' % bugid in str(e):
if 'Issue #%s not found' % bugid in str(e):
raise BugNotFoundError
# Often SOAP is not enabled
if '.' in self.name:
@ -589,7 +649,7 @@ class Trac(IBugtracker):
pass
def get_bug(self, bugtype, bugid): # This is still a little rough, but it works :)
url = "%s/%d" % (self.url, bugid)
url = "%s/%s" % (self.url, bugid)
try:
raw = utils.web.getUrl("%s?format=tab" % url).decode('utf-8')
except Exception as e: