Click here to Skip to main content
Click here to Skip to main content

Accessing CVS Repository with C#

By , 24 Nov 2004
 

Sample Image

Introduction

Most of my development projects are Visual Studio .NET projects that are stored in CVS repositories running on a Linux machine. Additionally, we use a third party bug tracking software that runs on Windows 2000 to track bug fixes/customer problems, etc. For auditing reasons, I needed to develop a program that would tie together the history of files changed in the CVS repository between different version tags with problem tracking records in the bug tracking system. To do this, I needed an easy way to access the history data in the CVS repository. In doing so, I created a simple and extensible class for accessing the CVS repository directly from a C# class.

I am not going to cover the part about accessing the bug tracking software. That software stores its information in an ODBC compliant database. That portion of the problem was neither unique nor interesting.

Accessing the CVS Repository: Setup

I do not want to go into great details on how to set up CVS. Suffice it to say that we use WinCVS on the Windows Client machines with SSH server authentication. By using this combination, not only can we access the CVS repository through WinCVS, but we can also access it from the command prompt.

The code

To start, I created a central class called CVSCommand. That class executes an arbitrary CVS command via a command line process. Additionally, it starts a secondary thread that monitors the console output of the CVS command and places it into a buffer for processing. Though you can issue CVS commands directly by using this class, I derived specific CVS command classes from CVSCommand.

To execute a command, the CVSCommand or derived class must know a few things. It must know the CVS Root, the RSH (remote shell) command, and the Working Directory for the command execution. There are property variables created to store this information. For example, to execute an arbitrary command using the CVSCommand class:

// Generic CVS command
CVSCommand cmd = new CVSCommand("log MainForm.cs");

cmd.CvsRoot = "username@myserver.com:/home/cvs";
cmd.CvsRsh = "c:/Program Files/GNU/SSHCVS/ssh.exe"
cmd.WorkingDirectory = @"c:\cvs repository\application 1\";

// Execute the command
cmd.Start();
cmd.WaitDone();

// Output the command response
Console.WriteLine(cmd.Output);

Using the CVSGetFileHistory Class

Though you can issue any CVS command using the CVSCommand class, the parsing of the response is still required. For my application, I created a simple derived class called CVSGetFileHistory. That command uses all the built in functionality of the base class to execute the CVS command, but adds special parsing code to parse the CVS response in a familiar and easy to use format. Additionally, the constructor allows you to specify the file you want the history of. For example, the above code can be changed to use the CVSGetFileHistory class as follows:

// CVS History File command
CVSGetFileHistory cmd = new CVSGetFileHistory("MainForm.cs");

cmd.CvsRoot = "username@myserver.com:/home/cvs";
cmd.CvsRsh = "c:/Program Files/GNU/SSHCVS/ssh.exe"
cmd.WorkingDirectory = @"c:\cvs repository\application 1\";

// Execute the command
cmd.Start();
cmd.WaitDone();

// Print out the results
foreach (CVSHistoryItem hi in cmd.History)
{
    Console.WriteLine("File: {0}", hi.File);
    Console.WriteLine("Revision: {0}",hi.Revision);
    Console.WriteLine("Date: {0}", hi.Date);
    Console.WriteLine("Author: {0}", hi.Author);
    Console.WriteLine("Description: {0}", hi.Description);
}

A Closer Look at CVSCommand

The CVS Command is a rather simple class. As previously stated, its main function is to execute an arbitrary CVS command via a command line process. To do so, it uses the System.Diagnostics.Process class. However, it must also ensure that certain environment variables used by CVS are defined. After it starts the process, it also starts a background thread that monitors the console output of the process and appends it to a buffer. The method Start() is where all of this is handled:

public void Start()
{

    // Do not allow if already running
    if (this.Running == true)
        return;

    ProcessStartInfo i = new ProcessStartInfo("cvs", command);
    i.UseShellExecute = false;
    if (this.CvsRoot.Length != 0)
        i.EnvironmentVariables.Add("CVSROOT", this.CvsRoot);
    if (this.CvsRsh.Length != 0)
        i.EnvironmentVariables.Add("CVS_RSH", this.CvsRsh);
    if (this.WorkingDirectory.Length != 0)
        i.WorkingDirectory = this.WorkingDirectory;
    i.RedirectStandardOutput = true;
    i.CreateNoWindow = true;
    p = Process.Start(i);

    monitor = new Thread(new System.Threading.ThreadStart(MonitorMain));
    monitor.Start();

}

Once the command is started, the Running property can be checked to see if the command is still executing or the WaitDone() method can be called to wait until the process is completed.

The background monitoring thread is rather simple. The code simply reads the StandardOutput property of the process and appends the results to the buffer. Furthermore, it monitors the Running property of the class to determine when it can stop execution.

Deriving a new Class

Deriving a new class is not difficult. All you really need to do is provide a constructor that accepts the proper information for the command and parsing functions to parse the command response. For example, the CVSGetFileHistory constructor looks like this:

public CVSGetFileHistory(string file) : base("log "+file)
{
}

It also has several properties that act as parsing functions for the Output of the CVSCommand class. For example, when a cvs log function is executed on the command line, this is output to the console:

C:\MCS\APPLIC~1\NM90>cvs log AssemblyInfo.cs

RCS file: /home/cvs/MCS/Applications/NM90/AssemblyInfo.cs,v
Working file: AssemblyInfo.cs
head: 1.61
branch:
locks: strict
access list:
symbolic names:
        NM90_Version_2_3_Build_52: 1.57
        NM90_Version_2_3_Build_50: 1.56
        NM90_Version_2_3_Build_14: 1.41
        NM90_Version_2_2_Build_44: 1.22
        NM90_Version_2_2_Build_42: 1.21
        NM90_Version_2_2_Build_40: 1.20
        NM90_Version_2_2_Build_32: 1.15
        NM90_Version_2_2_Build_28: 1.12
        NM100_Version_2_2_Build_6: 1.5 
keyword substitution: kv
total revisions: 61;    selected revisions: 61
description:
----------------------------
revision 1.61
date: 2004/11/22 13:50:06;  author: cnelson;  state: Exp;  lines: +1 -1
PRN:302
----------------------------
revision 1.60
date: 2004/11/16 21:04:21;  author: cnelson;  state: Exp;  lines: +1 -1
PRN:310
----------------------------
...

To make this more useable from a programming perspective, the derived class needs to parse this information and present it in a way that can be easily used. For example, CVSGetFileHistory parses all the revision text listed above and places it into a container class called CVSHistoryItemList which is exposed as the History property variable. In doing so, the following can be easily done:

// Execute the command
cmd.Start();
cmd.WaitDone();

// Print out the results
foreach (CVSHistoryItem hi in cmd.History)
{
    Console.WriteLine("File: {0}", hi.File);
    Console.WriteLine("Revision: {0}",hi.Revision);
    Console.WriteLine("Date: {0}", hi.Date);
    Console.WriteLine("Author: {0}", hi.Author);
    Console.WriteLine("Description: {0}", hi.Description);
}

Additional Notes

In closing, this basic class does what I needed it to do, no more and no less. Additional error trapping certainly could be added with not too much difficulty. Additional commands could also be implemented, as well.

History

  • November 22, 2004 - Initial posting.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Jay Nelson
Web Developer
United States United States
Member
I have been developing software professionaly since 1991 writing software in Automation and Manufacturing environments. For 14 years I worked for companies that built custom robotic automated equipment for the semiconductor, telecommunications, and other industies. Presently, I work for a medical device manufacturer developing applications for the compact framework.
 
My undergraduate degrees are in Mathematics and Philosopy. My graduate degree is in Management Information Systems. I am MCSD certified in Visual C++ 6.0 and MCSD.NET certified in C#.
 
I enjoy triathlons and reading.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralNeed some technical inputmemberMember 405876924 Feb '08 - 22:47 
The following is my code to try with cvs
 
CVSCommand cmd = new CVSCommand("log MainForm.cs");
cmd.CvsRoot = "pserver:rex@101.10.3.7:/getzin/cvsrep";
cmd.CvsRsh = @"C:\Program Files\TortoiseCVS\ssh.exe";
cmd.WorkingDirectory = @"E:\cvsrep\application1\";
cmd.Start();
cmd.WaitDone();
Console.WriteLine(cmd.Output);
 
When I execute the above script I am getting the following error upon cmd.Start();
 
"An unhandled exception of type 'System.ComponentModel.Win32Exception' occurred in system.dll
Additional information: The system cannot find the file specified"
 
Which file is not found? Is there any problem in the following statement?
CVSCommand cmd = new CVSCommand("log MainForm.cs");
 
where should we have log MainForm.cs file/class in your console application. I don't have.Please advice
 
Thanks in advance
Rex
GeneralRe: Need some technical inputmemberAndy Creigh27 May '08 - 1:04 
Hi,
 
In my version I just put the file name in the initial declaration and ommited the word log.
This works fine, i.e.
CVSCommand cmd = new CVSCommand("MainForm.cs");
 
Regards,
 
Andy
QuestionAutomate Password With With Loginmembergilbo10017 Feb '08 - 1:41 
Hi,
Does anyone know how to silently login with a password. I'm using some input/output stream redirects, but am getting no where. The console always seems to pause waiting for the password to be entered, and I don't seem to be able to identify this happening in code and then force an automatic password write to the input stream.
 
Any help with example code would be appreciated
 
Thanks.
AnswerRe: Automate Password With With LoginmemberHonglonghong9 Jul '11 - 0:00 
I am thinking before execute every command, also run login command:
cvs -d :protocol:username:password@host:/repos login
 
and then execute the correct command, any advice?
 
it would be slower?
 

besides above, I tried to run the command directly with password:
cvs -d :protocol:username:password@host:/repos ls -le Test
 
But it got "Failed to store password" error.
QuestionDon't understand the versioning codemembersmaccona21 Aug '07 - 5:57 
Good stuff, very useful (I was looking for a library which would provide me with some sort of history structure for files from a repository).
 
I suspect there may be a bug in your versioning code:
 
		public static bool operator >(CVSVersion v1, CVSVersion v2) 
		{
			if (v1.MajorVersion > v2.MajorVersion)
				return true;
			if (v1.MinorVersion > v2.MinorVersion)
				return true;
			return false;
		}
By this logic, 2.7 > 3.1 will return true:
 
2 > 3 (fails)
7 > 1 (succeeds) - returns true
Am I missing something, or shouldn't it be:
 
		public static bool operator >(CVSVersion v1, CVSVersion v2) 
		{
			if (v1.MajorVersion > v2.MajorVersion)
				return true;
			if (v1.MajorVersion == v2.MajorVersion && v1.MinorVersion > v2.MinorVersion)
				return true;
			return false;
		}

GeneralAccessing Win2003 servermemberYasirA13 Jul '06 - 3:39 
Hi There
 
I'd like to adapt your code to do the same thing to access a respository on a Windows server on the same local home network.
 
I see that your program invokes the CVS command (not installed on my pc). The setup that I have involves TortoiseCVS on my pc and CVSNT on my server.
 
I know that ssh will fall away as we won't need it for this setup.
 
I'm hoping you can advise me as to how to modify the code the get the same result as the linux setup that you had.
 
Any information would be appreciated.
 
Thanks
Generaldoesn't support :ext protocolmembercvsclient20 Jan '05 - 23:04 
this library seems not to support :ext(ssh) protocol. am i right?

GeneralMinor Problem - with fix!memberscporich10 Dec '04 - 9:29 
I could not get the code to work against my cvs repository until I found a minor difference in our output variable (which came from the cvs file): I have a line after the description: tag which contains a description. The code in the History definition will not handle this, since the first thing it looks for is "\r\n--". I have made a slight mod that handles this:
 


///
/// Get the file history
///

public CVSHistoryItemList History
{
get
{
 
// Create empty history list
CVSHistoryItemList list = new CVSHistoryItemList();
 
// Find the section
string section = "description:";
// *******Moved this up
string separator = "\r\n--";
 
int idx = this.Output.ToUpper().IndexOf(section.ToUpper());
if (idx < 0)
return list;
 
// Get the items
 
// *This is new due to the possibility of an overall description following the tag...
int skipDescIndex = (!this.Output.StartsWith("\r\n--")) ?
this.Output.IndexOf("\r\n--",idx+section.Length) : idx+section.Length;
 
string val = this.Output.Substring(skipDescIndex);

while (val.StartsWith(separator) == true)
Etc...

 

Other than that, great work! Cool | :cool:
 
---------------------------------------------------------------
try{Post.Signature.Insert(scpoRich.SignatureLocker.Signatures[0].ToString());}
catch(Exception e){throw new Exception("Can't find signature");}
finally{Post.JustSendIt();}

General#CVSlibmemberynik24 Nov '04 - 3:18 
Nice, but did you take a look at
http://sourceforge.net/projects/sharpcvslib/ [^]?
GeneralRe: #CVSlibmemberJay Nelson24 Nov '04 - 6:11 
Yes I did. When I looked at the documentation and source code, I could not find support for the log command.
GeneralRe: #CVSlibsussAnonymous27 Sep '05 - 10:26 
GPL is bad. I'd rather pay to commercial developer who provides support and stable code.
GeneralRe: #CVSlibmemberDaniel Grunwald28 Sep '05 - 2:04 
Well.. GPL is not as free as other OS licenses (ignore the claims that GPL is free).
Some developers have to make the decision between open source and freeware/shareware. They want that their program can be used and extended by others, so they choose OS, but they don't want that someone else makes money with it; so they choose the GPL license. I'm sure you can get a commercial license of GPL libraries in many cases.
And why can't you pay open source developers for support? I'm sure they are willing to give you support and improve their project for a small donation, much cheaper than a commercial developer would be.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 25 Nov 2004
Article Copyright 2004 by Jay Nelson
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid