SVN and Mantis Bug Tracker integration

Hello everyone!

This is my first post on my blog! As a first post, I thought I’d share how I have integrated my private SVN (Subversion) repository and my public Mantis bug tracker. You can automatically add comments to Mantis after you check-in files in your SVN repository, using a post-commit event. For more information, click here.

To do that, there is a file called checkin.php file inside the /plugins folder of the Mantis bug tracker package. However, this script only works using the PHP command line (not from a web server). You are supposed to use the script using a Telnet connection to your server, since anyone could call this script and add comments to your issues.

It is possible to modify this script to make it accessible from the web and make it secure. You can add an IP address check to make sure the requestor is really your server and not someone else. The script (checkincurl.php) is available here.

I am personally using VisualSVN Server which means the post-commit event uses batch files instead of scripts. I kept the batch file as simple as possible; it is calling a .NET console application which fetches the SVN log and posts it to Mantis. This console application lets you choose what method you want (PHP command line or posting a request on a web server) using the configuration file. You can see how it looks on the MPfm Mantis bug tracker.

Anyway, here is the source code:

Batch file:

D:\SVN\base\hooks\SVNMantisCheckIn.exe %1 %2

SVNMantisCheckIn.cs:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;

namespace SVNMantisCheckIn
{
    /// <summary>
    /// This program posts the content of a SVN log to the Mantis bug tracker as a comment.
    /// </summary>
    public class Program
    {
        /// <summary>
        /// Main method for the SVNMantisCheckIn application.
        /// </summary>
        /// <param name="args">Arguments ([RepositoryPath] [RevisionNumber])</param>
        public static void Main(string[] args)
        {
            // Check for parameters
            if (args == null ¦¦ args.Length < 2)
            {
                Console.Write("You must pass the following parameters: SVNMantisCheckIn.exe [RepositoryPath] [RevisionNumber]");
                return;
            }

            // Get parameters
            string repo = args[0];
            string rev = args[1];

            // Get configuration
            bool usePHPCommandLine = false;
            bool.TryParse(ConfigurationManager.AppSettings["UsePHPCommandLine"], out usePHPCommandLine);
            string svnLookPath = ConfigurationManager.AppSettings["SVNLookFilePath"];
            string phpFilePath = ConfigurationManager.AppSettings["PHPFilePath"];
            string mantisCheckInUrl = ConfigurationManager.AppSettings["MantisCheckInUrl"];
            string mantisCheckInFilePath = ConfigurationManager.AppSettings["MantisCheckInFilePath"];            

            // Fetch SVN log with processes
            string svnAuthor = ExecuteProcess(svnLookPath, "author -r " + rev + " " + repo, true);
            string svnLog = ExecuteProcess(svnLookPath, "log -r " + rev + " " + repo, false);
            string svnChanged = ExecuteProcess(svnLookPath, "changed -r " + rev + " " + repo, false);

            // Trim the excessive line carriage at the end of the log
            while (true)
            {
                // Check if the string ends with a line carriage
                if (svnLog.EndsWith("\n") ¦¦ svnLog.EndsWith("\r"))
                {
                    svnLog = svnLog.Substring(0, svnLog.Length - 2);
                }
                else
                {
                    // Break loop
                    break;
                }
            }

            // Check if the issue was fixed
            bool fixIssue = false;
            if (svnLog.ToUpper().Contains("FIXED ISSUE"))
            {
                fixIssue = true;
            }            

            // Prepare comment
            StringBuilder sbComment = new StringBuilder();
            sbComment.Append("The user <b>" + svnAuthor + "</b> has checked in files related to this issue (revision <b>" + rev + "</b>).");

            // Was this issue fixed?
            if (fixIssue)
            {
                sbComment.Append(" This check-in will automatically fix this issue.");
            }

            // Append comment and list of modified files
            sbComment.Append("\n");
            sbComment.AppendLine();
            sbComment.AppendLine("<b>Comment:</b>");
            sbComment.AppendLine(svnLog);
            sbComment.AppendLine();
            sbComment.AppendLine("<b>Files modified:</b>");
            sbComment.AppendLine(svnChanged);

            // Check if the PHP command line tool should be called
            if (usePHPCommandLine)
            {
                // Execute script using PHP command line
                string phpReturn = ExecuteProcess(phpFilePath, mantisCheckInFilePath, sbComment.ToString());
                Console.WriteLine(phpReturn);
            }
            else
            {
                // Execute script on web server
                string postContent = "user=svn&log=" + sbComment.ToString();
                string response = Post(mantisCheckInUrl, postContent);
                Console.WriteLine(response);
            }
        }

        /// <summary>
        /// Posts the content a string to a specific url on the web server.
        /// </summary>
        /// <param name="url">Url</param>
        /// <param name="content">Content (UTF8 string)</param>
        /// <returns>Response (string)</returns>
        private static string Post(string url, string content)
        {
            // Get UTF8 byte array
            byte[] byteArray = Encoding.UTF8.GetBytes(content);

            // Create request
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";
            request.ContentLength = content.Length;
            Stream streamRequest = request.GetRequestStream();
            streamRequest.Write(byteArray, 0, byteArray.Length);
            streamRequest.Close();

            // Get response
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            Stream stream = response.GetResponseStream();
            StreamReader reader = new StreamReader(stream);
            string responseText = reader.ReadToEnd();
            reader.Close();
            stream.Close();
            response.Close();

            // Return response
            return responseText;
        }

        /// <summary>
        /// Executes a process using arguments; the process output is returned.
        /// </summary>
        /// <param name="filePath">Process file path</param>
        /// <param name="arguments">Arguments</param>
        /// <param name="readLine">Defines if only one line should be read</param>
        /// <returns>Process output (command-line text)</returns>
        private static string ExecuteProcess(string filePath, string arguments, bool readLine)
        {
            // http://msdn.microsoft.com/en-us/library/system.diagnostics.process.standardoutput.aspx

            // Create process
            Process process = new Process();

            // Create process start info
            ProcessStartInfo processStartInfo = new ProcessStartInfo(filePath, arguments);
            processStartInfo.UseShellExecute = false;
            processStartInfo.RedirectStandardOutput = true;

            // Start process
            process.StartInfo = processStartInfo;
            process.Start();

            // Get output
            StreamReader streamReader = process.StandardOutput;

            // Read the standard output of the spawned process.
            string content = string.Empty;
            if (readLine)
            {
                content = streamReader.ReadLine();
            }
            else
            {
                content = streamReader.ReadToEnd();
            }

            process.WaitForExit();
            process.Close();

            return content;
        }

        /// <summary>
        /// Executes a process using arguments; the process output is returned.
        /// This override adds support for input text.
        /// </summary>
        /// <param name="filePath">Process file path</param>
        /// <param name="arguments">Arguments</param>
        /// <param name="input">Input text to submit to process (ex: user typing)</param>
        /// <returns>Process output (command-line text)</returns>
        private static string ExecuteProcess(string filePath, string arguments, string input)
        {
            // http://msdn.microsoft.com/en-us/library/system.diagnostics.process.standardoutput.aspx

            // Create process
            Process process = new Process();

            // Create process start info
            ProcessStartInfo processStartInfo = new ProcessStartInfo(filePath, arguments);
            processStartInfo.UseShellExecute = false;
            processStartInfo.RedirectStandardOutput = true;
            processStartInfo.RedirectStandardInput = true;

            // Start process
            process.StartInfo = processStartInfo;
            process.Start();

            // Get input/output
            StreamReader streamReader = process.StandardOutput;
            StreamWriter streamWriter = process.StandardInput;
            streamWriter.Write(input);
            streamWriter.Close();

            // Read the standard output of the spawned process.
            string content = streamReader.ReadToEnd();

            process.WaitForExit();
            process.Close();

            return content;
        }
    }
}

app.config:

<?xml version="1.0"?>
<configuration>
  <appSettings>
    <!-- Defines if the tool should use the PHP command line instead of doing a web request to the PHP file. -->
    <add key="UsePHPCommandLine" value="false" />
    <!-- Defines the path to the PHP command line tool. -->
    <add key="PHPFilePath" value="C:\xampp\php\php.exe"/>
    <!-- Defines the path to the svnlook.exe process. -->
    <add key="SVNLookFilePath" value="C:\Program Files (x86)\VisualSVN Server\bin\svnlook.exe"/>
    <!-- Defines the file path to the checkin.php script (to be used with the PHP command line). -->
    <add key="MantisCheckInFilePath" value="C:\xampp\htdocs\mantisbt\core\checkin.php"/>
    <!-- Defines the url to the checkin.php script (to be used with the PHP web request). -->
    <add key="MantisCheckInUrl" value="http://www.mp4m.org/mantis/checkin.php"/>
  </appSettings>
  <!-- Disable using IE proxy (takes a few seconds to discover the proxy each time you run the tool!). -->
  <system.net>
    <defaultProxy>
      <proxy usesystemdefault="False"/>
    </defaultProxy>
  </system.net>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>

Tags: , ,

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>