Issue Tracker Plugins ===================== What is this for? ----------------- Imagine the following scenario: 1. Marvin the manager assigns a ticket or task to Dave, the developer, using the issue-tracking or task-management software. 2. Dave looks at his list of tasks using the issue-tracking software's front end (i.e. a web-dashboard). He picks something to work on this morning. 3. Dave fires up Visual Studio; hacks on some code; does some work toward the task. 4. Dave brings up TSVN's commit dialog. He types in a comment that lists, depending on coding standards: a) The ticket/task number of what he's been working on. b) Some comments about what he's changed. 5. He clicks OK, and his changes are committed to the repository. 6. The issue-tracking software monitors the SVN repository, watching for commits.It sees Dave's commit message, extracts the information from it, and updates the ticket appropriately. 7. Marvin looks in the task-management software, and can monitor the project's progress. As it stands, TSVN supports (most of) this admirably. For example, with the Trac integration, I can put "See #43" or "Fixes #99" somewhere in the commit message, and Trac's post-commit hook will update the tickets accordingly. However: 1. Dave has to keep the issue-tracker front-end open in order to look up the ticket numbers. 2. Some issue trackers want more information (e.g. time spent, time remaining), and it needs to be formatted more rigidly. So, the user story looks like this: "From the TSVN commit dialog, the user should be able to display up a list of assigned tickets from the issue-tracker associated with that repository. The user should be able to pick one or more tasks from this list." "This will populate the commit message with the information required by the issue-tracker's SVN server-side post-commit hook." "This functionality should be available from the commit dialog, because the user may wish to choose which files to commit based on which ticket they select, or he may wish to refresh his memory about what's changed before selecting the appropriate ticket." Implementing an issue tracker plugin ------------------------------------ To write an integration plugin, you implement the IBugTraqProvider COM interface, and register your object as implementing the "TortoiseSVN BugTraq Providers" component category. This registration makes it easy for the settings dialog to find a list of available plugins. The IBugTraqProvider interface is documented in the inc\IBugTraqProvider.idl file. The component category is defined (in C++) as follows: // {3494FA92-B139-4730-9591-01135D5E7831} DEFINE_GUID(CATID_BugTraqProvider, 0x3494fa92, 0xb139, 0x4730, 0x95, 0x91, 0x1, 0x13, 0x5d, 0x5e, 0x78, 0x31); Example Plugins --------------- There are two example plugins in this folder. - ExampleAtlPlugin, written in C++, using ATL. - ExampleCsPlugin, written in C#. They get the list of available "issues" from a hard-coded list, rather than a database or web service, but this should be sufficient to demonstrate the plugin API. Licensing and GPL compatibility ------------------------------- TortoiseSVN is licensed under the GNU General Public License (see the file LICENSE for details). There is a specific exception for plugins that implement the issue tracker plugin interfaces; these do not need to be GPL-licensed. The IBugTraqProvider interface ------------------------------ In the contrib\issue-tracker-plugins\inc directory, you'll find the following files: * IBugTraqProvider.idl This is a copy of the src\IBugTraqProvider\IBugTraqProvider.idl file; it's provided for reference; you'll probably use the files below. * IBugTraqProvider_h.h, IBugTraqProvider_i.c These are the files you'll probably use if you implement a plugin in C++. * Interop.BugTraqProvider.dll Interop Assembly for implementing plugins in .NET. It's not a Primary Interop Assembly (PIA). The source code for this project is in the Interop.BugTraqProvider folder. The interface is documented in the .IDL file. Walkthrough: Creating an issue tracker plugin in C# --------------------------------------------------- (This assumes a basic familiarity with creating Windows Forms applications). In Visual Studio 2005 or 2008, create a new "Class Library" project; give it a name. Delete the "Class1" class; we'll create another one in a moment. Add a reference to the inc\Interop.BugTraqProvider.dll file. Create a new class named "MyPlugin". Derive MyPlugin from the Interop.BugTraqProvider.IBugTraqProvider interface, and then implement the first two methods as follows: The ValidateParameters method should look like this: public bool ValidateParameters(IntPtr hParentWnd, string parameters) { return true; } The GetLinkText method should look like this: public string GetLinkText(IntPtr hParentWnd, string parameters) { return "Choose Issue"; } We'll come back to GetCommitMessage shortly. The class also needs some attributes; it should look like this: [ComVisible(true), Guid("PUT-GUID-HERE"), ClassInterface(ClassInterfaceType.None)] public class Provider : IBugTraqProvider { // etc. (Replace "PUT-GUID-HERE" with a new GUID). Add a class to hold our example ticket data: internal class TicketItem { private readonly int _ticketNumber; private readonly string _ticketSummary; public TicketItem(int ticketNumber, string ticketSummary) { _ticketNumber = ticketNumber; _ticketSummary = ticketSummary; } public int Number { get { return _ticketNumber; } } public string Summary { get { return _ticketSummary; } } } We can now implement the GetCommitMessage method as follows: public string GetCommitMessage(IntPtr hParentWnd, string parameters, string commonRoot, string[] pathList, string originalMessage) { List tickets = new List(); tickets.Add(new TicketItem(12, "Service doesn't start on Windows Vista")); tickets.Add(new TicketItem(19, "About box doesn't render correctly in large fonts mode")); MyIssuesForm form = new MyIssuesForm(tickets); if (form.ShowDialog() != DialogResult.OK) return originalMessage; StringBuilder result = new StringBuilder(originalMessage); if (originalMessage.Length != 0 && !originalMessage.EndsWith("\n")) result.AppendLine(); foreach (TicketItem ticket in form.TicketsFixed) { result.AppendFormat("Fixed #{0}: {1}", ticket.Number, ticket.Summary); result.AppendLine(); } return result.ToString(); } This passes the list of open issues to the MyIssuesForm object, where the user will be able to check those ones that she's fixed. These are available through the TicketsFixed property. We use these to build a string that looks something like this: Fixed #12: Service doesn't start on Windows Vista. A commit message formatted like this will cause, (e.g.) Trac's post-commit hook to close the tickets. Anything that the user has already entered into the commit message is left there. Now we need a dialog box that displays the issues assigned to the current user. Add a Windows Form item to your project. Name it "MyIssuesForm". Set the following properties: StartPosition = CenterParent MaximizeBox = False MinimizeBox = False ShowIcon = False ShowInTaskbar = False Put the usual OK and Cancel buttons on it; arrange them and wire them up properly. Add a ListView control to the form and arrange it appropriately. Set the following properties: Checkboxes = True FullRowSelect = True View = Details HeaderStyle = Nonclickable Change the class so that it looks like this: partial class MyIssuesForm : Form { private readonly IEnumerable _tickets; private readonly List _ticketsAffected = new List(); public MyIssuesForm(IEnumerable tickets) { InitializeComponent(); _tickets = tickets; } public IEnumerable TicketsFixed { get { return _ticketsAffected; } } // etc. Then implement some event handlers in MyIssuesForm as follows: private void MyIssuesForm_Load(object sender, EventArgs e) { listView1.Columns.Add(""); listView1.Columns.Add("#"); listView1.Columns.Add("Summary"); foreach(TicketItem ticketItem in _tickets) { ListViewItem lvi = new ListViewItem(); lvi.Text = ""; lvi.SubItems.Add(ticketItem.Number.ToString()); lvi.SubItems.Add(ticketItem.Summary); lvi.Tag = ticketItem; listView1.Items.Add(lvi); } listView1.Columns[0].Width = -1; listView1.Columns[1].Width = -1; listView1.Columns[2].Width = -1; } private void okButton_Click(object sender, EventArgs e) { foreach (ListViewItem lvi in listView1.Items) { TicketItem ticketItem = lvi.Tag as TicketItem; if (ticketItem != null && lvi.Checked) _ticketsAffected.Add(ticketItem); } } Registering your new C# class can be done by using RegAsm from the command line, as follows: RegAsm bin\Debug\MyCsPlugin.dll /codebase /regfile:MyCsPlugin.reg You'll need to edit the .REG file, by adding another "Implemented Categories" entry that looks like this: [HKEY_CLASSES_ROOT\CLSID\{PUT-GUID-HERE}\Implemented Categories\{3494FA92-B139-4730-9591-01135D5E7831}] Replace "PUT-GUID-HERE" with the same value you used earlier. Then, merge that .REG file into the registry, and your plugin is ready to go!