Commits

Steve Borho committed 219524e

bugtraq: introduce bugtraq integration

Contributed by StefRave

Closes #369

Comments (0)

Files changed (3)

tortoisehg/hgtk/bugtraq.py

+from ctypes import *
+import comtypes
+import pythoncom
+from comtypes import IUnknown, GUID, COMMETHOD, POINTER
+from comtypes.typeinfo import ITypeInfo
+from comtypes.client import CreateObject
+from comtypes.automation import _midlSAFEARRAY
+from _winreg import *
+
+class IBugTraqProvider(IUnknown):
+    _iid_ = GUID("{298B927C-7220-423C-B7B4-6E241F00CD93}")
+    _methods_ = [
+       COMMETHOD([], HRESULT, "ValidateParameters",
+                       (['in'], comtypes.c_long, "hParentWnd"),
+                       (['in'], comtypes.BSTR, "parameters"),
+                       (['out', 'retval'], POINTER(comtypes.c_int16), "pRetVal") ),
+       COMMETHOD([], HRESULT, "GetLinkText",
+                       (['in'], comtypes.c_long, "hParentWnd"),
+                       (['in'], comtypes.BSTR, "parameters"),
+                       (['out', 'retval'], POINTER(comtypes.BSTR), "pRetVal") ),
+       COMMETHOD([], HRESULT, "GetCommitMessage",
+                       (['in'], comtypes.c_long, "hParentWnd"),
+                       (['in'], comtypes.BSTR, "parameters"),
+                       (['in'], comtypes.BSTR, "commonRoot"),
+                       (['in'], _midlSAFEARRAY(comtypes.BSTR), "pathList"),
+                       (['in'], comtypes.BSTR, "originalMessage"),
+                       (['out', 'retval'], POINTER(comtypes.BSTR), "pRetVal") )
+       ]
+
+class IBugTraqProvider2(IBugTraqProvider):
+    _iid_ = GUID("{C5C85E31-2F9B-4916-A7BA-8E27D481EE83}")
+    _methods_ = [
+    COMMETHOD([], HRESULT, "GetCommitMessage2",
+                    (['in'], comtypes.c_long, "hParentWnd"),
+                    (['in'], comtypes.BSTR, "parameters"),
+                    (['in'], comtypes.BSTR, "commonURL"),
+                    (['in'], comtypes.BSTR, "commonRoot"),
+                    (['in'], _midlSAFEARRAY(comtypes.BSTR), "pathList"),
+                    (['in'], comtypes.BSTR, "originalMessage"),
+                    (['in'], comtypes.BSTR, "bugID"),
+                    (['out'], POINTER(comtypes.BSTR), "bugIDOut"),
+                    (['out'], POINTER(_midlSAFEARRAY(comtypes.BSTR)), "revPropNames"),
+                    (['out'], POINTER(_midlSAFEARRAY(comtypes.BSTR)), "revPropValues"),
+                    (['out', 'retval'], POINTER(comtypes.BSTR), "pRetVal") ),
+    COMMETHOD([], HRESULT, "CheckCommit",
+                    (['in'], comtypes.c_long, "hParentWnd"),
+                    (['in'], comtypes.BSTR, "parameters"),
+                    (['in'], comtypes.BSTR, "commonURL"),
+                    (['in'], comtypes.BSTR, "commonRoot"),
+                    (['in'], _midlSAFEARRAY(comtypes.BSTR), "pathList"),
+                    (['in'], comtypes.BSTR, "commitMessage"),
+                    (['out', 'retval'], POINTER(comtypes.BSTR), "pRetVal") ),
+    COMMETHOD([], HRESULT, "OnCommitFinished",
+                    (['in'], comtypes.c_long, "hParentWnd"),
+                    (['in'], comtypes.BSTR, "commonRoot"),
+                    (['in'], _midlSAFEARRAY(comtypes.BSTR), "pathList"),
+                    (['in'], comtypes.BSTR, "logMessage"),
+                    (['in'], comtypes.c_long, "revision"),
+                    (['out', 'retval'], POINTER(comtypes.BSTR), "pRetVal") ),
+    COMMETHOD([], HRESULT, "HasOptions",
+                    (['out', 'retval'], POINTER(comtypes.c_int16), "pRetVal") ),
+    COMMETHOD([], HRESULT, "ShowOptionsDialog",
+                    (['in'], comtypes.c_long, "hParentWnd"),
+                    (['in'], comtypes.BSTR, "parameters"),
+                    (['out', 'retval'], POINTER(comtypes.BSTR), "pRetVal") )
+    ]
+
+
+class BugTraq:
+    #svnjiraguid = "{CF732FD7-AA8A-4E9D-9E15-025E4D1A7E9D}"
+
+    def __init__(self, guid):
+        self.guid = guid
+        self.bugtr = None
+
+    def _get_bugtraq_object(self):
+        if self.bugtr == None:
+            obj = CreateObject(self.guid)
+            self.bugtr = obj.QueryInterface(IBugTraqProvider2)
+        return self.bugtr
+
+    def get_commit_message(self, parameters, logmessage):
+        commonurl = ""
+        commonroot = ""
+        bugid = ""
+        bstrarray = _midlSAFEARRAY(comtypes.BSTR)
+        pathlist = bstrarray.from_param(())
+
+        bugtr = self._get_bugtraq_object()
+        (bugid, revPropNames, revPropValues, newmessage) = bugtr.GetCommitMessage2(
+                0, parameters, commonurl, commonurl, pathlist, logmessage, bugid)
+        return newmessage
+
+    def on_commit_finished(self, logmessage):
+        commonroot = ""
+        bstrarray = _midlSAFEARRAY(comtypes.BSTR)
+        pathlist = bstrarray.from_param(())
+
+        bugtr = self._get_bugtraq_object()
+        errormessage = bugtr.OnCommitFinished(0, commonroot, pathlist,
+                logmessage, 0)
+        return errormessage
+        
+    def show_options_dialog(self, options):
+        if not self.has_options():
+            return ""
+
+        bugtr = self._get_bugtraq_object()
+        options = bugtr.ShowOptionsDialog(0, options)
+        return options
+    
+    def has_options(self):
+        bugtr = self._get_bugtraq_object()
+        return bugtr.HasOptions() != 0
+        
+    def get_link_text(self, parameters):
+        bugtr = self._get_bugtraq_object()
+        return bugtr.GetLinkText(0, parameters)
+
+def get_issue_plugins():
+    cm = pythoncom.CoCreateInstance(pythoncom.CLSID_StdComponentCategoriesMgr,
+            None, pythoncom.CLSCTX_INPROC,pythoncom.IID_ICatInformation)
+    CATID_BugTraqProvider = pythoncom.MakeIID(
+            "{3494FA92-B139-4730-9591-01135D5E7831}")
+    ret = []
+    enumerator = cm.EnumClassesOfCategories((CATID_BugTraqProvider,),())
+    while 1:
+        clsid = enumerator.Next();
+        if clsid == ():
+            break;
+
+        ret.extend(clsid)
+    return ret
+
+def get_plugin_name(clsid):
+    key = OpenKey(HKEY_CLASSES_ROOT, r"CLSID\%s" % clsid)
+    keyvalue = QueryValueEx(key, None) 
+    key.Close()
+    return keyvalue[0]
+
+def get_issue_plugins_with_names():
+    pluginclsids = get_issue_plugins()
+    return [(key, get_plugin_name(key)) for key in pluginclsids]
+
+

tortoisehg/hgtk/commit.py

 # GNU General Public License version 2, incorporated herein by reference.
 
 import os
+import re
 import sys
 import errno
 import gtk
 from tortoisehg.hgtk.status import FM_PATH, FM_PATH_UTF8
 from tortoisehg.hgtk import csinfo, gtklib, thgconfig, gdialog, hgcmd
 from tortoisehg.hgtk import thgmq, textview
+from tortoisehg.hgtk import bugtraq
 
 class BranchOperationDialog(gtk.Dialog):
     def __init__(self, branch, close, repo):
         except KeyError:
             self.mqloaded = False
 
+        bugtraqplugin = self.repo.ui.config('tortoisehg', 'issue.bugtraqplugin', None)
+        if bugtraqplugin == None or bugtraqplugin == "":
+            self.bugtracker = None
+        else:
+            bugtraqquid = bugtraqplugin.split(' ', 1)[0]
+            self.bugtracker = bugtraq.BugTraq(bugtraqquid)
+
     def get_help_url(self):
         return 'commit.html'
 
             if branches[0] == branches[1]:
                 self.branchbutton.set_sensitive(False)
 
+        if not self.bugtracker == None:
+            self.bugtraqbutton = gtk.Button(use_underline=False)
+            self.bugtraqbutton.set_label('BugTraq')
+            self.bugtraqbutton.connect('clicked', self.bugtraq_clicked)
+            mbox.pack_start(self.bugtraqbutton, False, False, 2)
+
         if hasattr(self.repo, 'mq'):
             label = gtk.Label('QNew: ')
             mbox.pack_start(label, False, False, 2)
             self.closebranch = True
         self.refresh_branchop()
 
+    def bugtraq_clicked(self, button):
+        buf = self.text.get_buffer()
+        begin, end = buf.get_bounds()
+        message = buf.get_text(begin, end)
+        bugtraqparameters = self.repo.ui.config('tortoisehg', 'issue.bugtraqparameters', "")
+        newmessage = self.bugtracker.get_commit_message(bugtraqparameters, message)
+        buf.set_text(newmessage)
+
     def repo_invalidated(self, mqwidget):
         self.reload_status()
 
             return self.relevant_checked_files(commitable)
 
         def callback(ret):
+            if ret == 0:
+                if not self.bugtracker == None:
+                    buf = self.text.get_buffer()
+                    commitmessage = buf.get_text(buf.get_start_iter(), buf.get_end_iter())
+                    errormessage = self.bugtracker.on_commit_finished(commitmessage)
+                    if errormessage != None and len(errormessage) > 0:
+                        gdialog.Prompt(_('Bug Traq'),
+                               errormessage, self).run()
+
             if ret == 0 and self.repo.ui.configbool('tortoisehg', 'closeci'):
                 self.destroy()
             else:
             self.text.grab_focus()
             return False
 
+        linkmandatory = self.repo.ui.config('tortoisehg',
+                'issue.linkmandatory', "False")
+        if linkmandatory == "True":
+            issueregex = self.repo.ui.config('tortoisehg',
+                    'issue.regex', "")
+            if issueregex:
+                commitmessage = buf.get_text(buf.get_start_iter(), 
+                        buf.get_end_iter())
+
+                m = re.search(issueregex, commitmessage)
+                if not m:
+                    gdialog.Prompt(_('Nothing Commited'),
+                           _('No issue link found in the commit message.' 
+                             'The commit message should contain an issue link. Configure this '
+                             'in the \'Issue\' section in the settings'), self).run()
+                    self.text.grab_focus()
+                    return False
+
         try:
             sumlen, maxlen = self.get_lengths(noexcept=False)
         except ValueError:

tortoisehg/hgtk/thgconfig.py

 from tortoisehg.util.i18n import _
 from tortoisehg.util import hglib, settings, paths
 
-from tortoisehg.hgtk import dialog, gdialog, gtklib, hgcmd
+from tortoisehg.hgtk import dialog, gdialog, gtklib, hgcmd, bugtraq
 
 try:
     from iniparse.config import Undefined
     )),
 
 ({'name': 'extensions', 'label': _('Extensions'), 'icon': gtk.STOCK_EXECUTE,
-  'extra': True}, ()),)
+  'extra': True}, ()),
+
+({'name': 'issue', 'label': _('Issue Tracking'), 'icon': gtk.STOCK_EDIT,
+  'extra': True}, (
+    (_('Issue Regex'), 'tortoisehg.issue.regex', [],
+        _('Defines the regex to match when picking up issue numbers.')),
+    (_('Issue Link'), 'tortoisehg.issue.link', [],
+        _('Defines the command to run when an issue number is recognized. '
+          'You may include groups in issue.regex, and corresponding {n} '
+          'tokens in issue.link (where n is a non-negative integer). '
+          '{0} refers to the entire string matched by issue.regex, '
+          'while {1} refers to the first group and so on. If no {n} tokens'
+          'are found in issue.link, the entire matched string is appended '
+          'instead.')),
+    (_('Mandatory Issue Reference'), 'tortoisehg.issue.linkmandatory',
+        ['False', 'True'],
+        _('When commiting an issue, force the user to specify a reference '
+            'to an issue. '
+          'In enabled, the regex configured in \'Issue Regex\' must find a '
+          'match in the commit message')),
+    )),
+)
 
 _font_presets = {
     'win-ja': (_('Japanese on Windows'), {
         text = ' '.join(tooltip.splitlines())
         self.descbuffer.set_text(text)
 
+    def fill_issue_frame(self, parent, table):
+        parent.pack_start(table, False, False)
+
+        issuetrackmodel = gtk.ListStore(str, # internal name
+                                    str) # GUI label
+        issuetrackmodel.append((None, _(' - Select Issue Tracker -')))
+        for (guid, label) in bugtraq.get_issue_plugins_with_names():
+            issuetrackmodel.append((guid, label))
+        issuetrackcombo = gtk.ComboBox(issuetrackmodel)
+        cell = gtk.CellRendererText()
+        issuetrackcombo.pack_start(cell)
+        issuetrackcombo.add_attribute(cell, 'text', 1)
+        self.issuetrackcombo = issuetrackcombo
+
+        table.add_row('Issue plugin:', issuetrackcombo, padding=False)
+
+        tooltip = _('Select issue tracker plugin to use. '
+            'Links to plugins can be found at '
+            'http://tortoisesvn.net/issuetrackerplugins')
+        self.tooltips.set_tip(issuetrackcombo, tooltip)
+
+        issuetrackcombo.connect('popup', self.set_help, '', tooltip)
+
+        configurepluginbutton = gtk.Button(_('Configure Plugin'))
+        table.add_row('', configurepluginbutton, padding=False)
+        self.configurepluginbutton = configurepluginbutton
+
+        def combo_changed(combo):
+            model = combo.get_model()
+            newvalue = ""
+            selection = model[self.issuetrackcombo.get_active()]
+            if not selection[0] == None:
+                newvalue = selection[0] + ' ' + selection[1]
+            self.record_new_value("tortoisehg.issue.bugtraqplugin",
+                    newvalue)
+            self.dirty_event()
+            self.configurepluginbutton.set_sensitive(newvalue != "")
+
+        def configure_bugtraq_plugin(button):
+            value = self.get_ini_config("tortoisehg.issue.bugtraqplugin")
+            if value == None or value == "":
+                return
+
+            guid = value.split(' ', 1)[0]
+            value = self.get_ini_config("tortoisehg.issue.bugtraqparameters")
+            if value == None:
+                value = ""
+
+            bugtr = bugtraq.BugTraq(guid)
+            newvalue = bugtr.show_options_dialog(value)
+
+            if value != newvalue:
+                self.record_new_value("tortoisehg.issue.bugtraqparameters", newvalue)
+                self.dirty_event()
+
+        issuetrackcombo.connect('changed', combo_changed)
+        configurepluginbutton.connect('clicked', configure_bugtraq_plugin)
+
+    def refresh_issue_frame(self):
+        value = self.get_ini_config("tortoisehg.issue.bugtraqplugin")
+        configuredguid = ""
+        if not value == None:
+            configuredguid = re.sub("[-{}]", "", value.split(' ', 1)[0])
+
+        model = self.issuetrackcombo.get_model()
+        index = 0
+        found = False
+        for (guid, label) in model:
+            if not guid == None:
+                if re.sub("[-{}]", "", guid) == configuredguid:
+                    found = True
+                    break
+            index = index + 1
+        if not found:
+            index = 0
+        self.issuetrackcombo.set_active(index)
+        self.configurepluginbutton.set_sensitive(configuredguid != "")
+
     def fill_frame(self, frame, info, build=True, width=32):
         widgets = []