Color your svn command-line output with svnc!
I know, I know. I haven't posted anything here in so long I'm afraid to actually go back and look to see how long it's been. I'm a terrible person. Anyway, I hope to write more in the future about various meatier topics like NUnit integration, our MSBuild-based asset pipeline, and a slew of other fun stuff. Today, however, you get some insight into a funny little corner of my programming world and a free tool with source to boot! Think of it as me slowly easing back into the waters of programmer-blogging.
We use Subversion for version control here at Power of Two Games (I've written about it once or twice). I love the simplicity of the command-line interface, and I almost always use it instead of the Windows client, TortoiseSVN. Don't get me wrong, Tortoise is a pretty good tool and I use it for some things, but in general if I can avoid using the mouse, I will. Accordingly, my source control bread-and-butter these days are "svn up", "svn stat", and "svn ci".
One practice that we enjoy is checking built versions of our tools into our bin directory. This way, if Noel updates our texture converter tool (which, by the way, makes heavy use of Ignacio Castaño's excellent texture tools, courtesy of NVidia), all I have to do is "svn up" and poof, the newly built tool shows up on my hard drive, ready for use. I don't have to build it myself or get it from a shared drive, it's stuffed into source control. This is usually great, except, as always, for when it's not so great. Specifically, Noel checked in changes to the same tool I was working on! I update to the latest, poor Subversion sees that my tool dll is different from both the old and new versions of the tool in the repository, and can't fix things without my help! No big deal, it's just a conflict. This happens all the time in source control land. If you don't notice the subtle "C" that Subversion shows you during the update to indicate a conflict, your compile will almost certainly fail, as the conflict markers are designed to not compile easily.
In this case, though, the conflicted file was one of our binary tool .dll files! I rarely list the contents of my bin directory, so I didn't see the conflict revision files. The version that was there was causing the game to crash because it wasn't properly preparing the game data in the way that the new game runtime I'd just sync'd to was expecting! It didn't take long to figure out, but it was frustrating and I started thinking about how to fix it.
I'm a big fan of color as context- I use Visual Assist only for the enhanced syntax coloring, I enjoy color-coded output from MSBuild, and I've never liked the inability to customize the default output from Subversion. It's notoriously hard (if possible?) to change the Windows XP console text from batch files. I started googling for it, and when I found references to ANSI.SYS my eyes glazed over and I passed out for a couple minutes, reliving awful childhood memories of making my DOS prompt flash blue and yellow (it wasn't intentionally Swedish, Astute Reader, it just happened to work out that way. PS I'm not Swedish but I know like 30 people who are and they'll read this.)
Anyway, the easiest way I could figure to change Subversion's output color was to wrap it in a simple C# file that launches and redirects the svn.exe process output. I call it svnc for "SVN Colored". If a given line matches any of the modification state regexes, it prints it out in a different color: Adds are green, deletes are blue, conflicts are red, merges are yellow, and modifications are white. Here's a before-and-after screenshot.

And here's the source, in the public domain. I threw this together in about 30 minutes, and I'm sure I'm doing things wrong and inefficiently, etc... but hey. It works and it's free code. Don't complain too much! :) It compiles under Visual Studio 2005 using C#/.NET 2.0. Its use of Win32-specific platform dlls restricts it to windows machines, but I'm just sure you linuxy types can whip something special up with ncurses. Of course, you linuxy types would probably do it in Perl using pipes anyway. As for mac users, you're on your own. But you only have one mouse button anyway so you're used to not being productive. :)
Nice utility.
I don't believe you need the interop - I've removed it and replaced it with some minor changes. The 'G' of merged can appear in the second column (e.g. svn merge) so a '.?' in the regexp should suffice. Code included below.
// This code is in the public domain -- Charles Nicholson (charles@powerof2games.com)
using System;
using System.Diagnostics;
using System.Text.RegularExpressions;
namespace svnc
{
public static class Program
{
static Regex s_conflictRegex = new Regex("^C( ){4}.+$");
static Regex s_mergedRegex = new Regex("^.?G( ){4}.+$");
static Regex s_modifyRegex = new Regex("^M( ){4}.+$");
static Regex s_addRegex = new Regex("^A( ){4}.+$");
static Regex s_deleteRegex = new Regex("^D( ){4}.+$");
static void OutputLine(string text)
{
ConsoleColor color = Console.ForegroundColor;
ConsoleColor originalColor = Console.ForegroundColor;
if (s_conflictRegex.Match(text).Success)
color = ConsoleColor.Red;
else if (s_mergedRegex.Match(text).Success)
color = ConsoleColor.Yellow;
else if (s_modifyRegex.Match(text).Success)
color = ConsoleColor.White;
else if (s_addRegex.Match(text).Success)
color = ConsoleColor.Green;
else if (s_deleteRegex.Match(text).Success)
color = ConsoleColor.Blue;
Console.ForegroundColor = color;
Console.WriteLine(text);
Console.ForegroundColor = originalColor;
}
static int Main(string[] args)
{
Regex whitespace = new Regex(@"[\s\n\r]{1,}");
for (int i = 0; i < args.Length; ++i)
{
if (whitespace.Match(args[i]).Success)
args[i] = '"' + args[i] + '"';
}
Process svnProcess = new Process();
svnProcess.StartInfo.FileName = "svn.exe";
svnProcess.StartInfo.Arguments = string.Join(" ", args);
svnProcess.StartInfo.UseShellExecute = false;
svnProcess.StartInfo.RedirectStandardOutput = true;
svnProcess.StartInfo.RedirectStandardError = true;
bool started = svnProcess.Start();
if (!started)
{
Console.WriteLine("Unable to start svn.exe process, aborting.");
return -1;
}
string output = null;
while (svnProcess.HasExited == false)
{
output = svnProcess.StandardOutput.ReadLine();
if (output != null)
OutputLine(output);
}
while (output != null)
{
output = svnProcess.StandardOutput.ReadLine();
if (output != null)
OutputLine(output);
}
string error = svnProcess.StandardError.ReadToEnd();
if (string.IsNullOrEmpty(error) == false)
Console.Write(error);
return svnProcess.ExitCode;
}
}
}
I hate regular expressions, the change in the above bit of code isn't quite right.
I suspect it has to be something like:
static Regex s_conflictRegex = new Regex("^(C[CGMAD ]|[CGMAD ]C) {3}.+$");
static Regex s_mergedRegex = new Regex("^(G[ G]|[ G]G) {3}.+$");
for svn merge to show up in colour (or color if you prefer) as well. I'm no expert though.
Hi, I rewrote it in python so it works for GNU/Linux people too (might also work with Mac OSX).
http://svn.sidvind.com/viewvc/code/trunk/Experiments/py_svnc/svnc.py?vie...
Nice!!! I have a soft spot for Python, and it's my go-to tool of choice for stuff like that. Thanks!
Hey that's great! I originally thought to write it in python, but I don't do much process redirection stuff and the lack of a debugger has bitten me before when writing that code. One thing though, I notice that you quit as soon as polling the process fails. If python behaves anything like C# in this regard, you could still have unprocessed input in the stdout pipe- that's why the C# code I wrote has 2 while loops- one to print while the process runs, and one to drain the pipe if the process exits before all the output has been redirected to me. Does python guarantee that when process.poll() fails, all of your output has been sent to your redirected handle?
oh and btw i'm a big fan of your source code license.
A tips for other Linux newbies is to add the "#!/usr/bin/python" shabang part at the top of the file. Then you don't have to use "python svnc.py" to run it. If you also put in an alias in your config file .bashrc, alias svn="svnc.py" you can simply continue writing svn instead of remembering to type svnc.py.
And as I am learning my self, I would be happy if someone told me if there are other ways of accomplishing this!
Just OOC, have you tried the C# utility in Mono (especially now that the P/Invoke has been removed)? I mean, no reason not to port it to python, I was just wondering.
Hi, you mentioned 'color-coded output from MSBuild', did you mean the Output window of the Visual studio?
I'm looking for something to color my Output window for ages now, I even tried to implement it, but it seems, the compiler uses some internal functions to report the compilation messages, so I can't push it through anything, it's just gets around it.
Great idea - brought a tear to my eye actually.
I still use CVS primarily through a UNIX-housed repository and command-line exclusively for all versioning control. After your idea, just whipped together a single-line BASH command that makes your stomach turn when you look at it:
http://www.overset.com/2008/05/29/colorized-svn-stat-and-cvs-update-outp...
Here's a bad SVN version to do the same:
svn stat -q \
| sed -e "s/^\(\A.*\)$/`printf '\033'`[1;32m\1`printf '\033'`[0;00m/g" \
| sed -e "s/^\(\C.*\)$/`printf '\033'`[1;31m\1`printf '\033'`[0;00m/g" \
| sed -e "s/^\(\M.*\)$/`printf '\033'`[1;34m\1`printf '\033'`[0;00m/g" \
| sed -e "s/^\(\G.*\)$/`printf '\033'`[1;37m\1`printf '\033'`[0;00m/g" \
| sed -e "s/^\(\D.*\)$/`printf '\033'`[1;36m\1`printf '\033'`[0;00m/g"
