Update of /cvsroot/sharpcvslib/sharpcvslib/src/ICSharpCode/SharpCvsLib/Extension/LogReporter In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv23500/src/ICSharpCode/SharpCvsLib/Extension/LogReporter Added Files: LogFile.cs LogReport.cs LogReportCommand.cs LogRevision.cs Log Message: LogReporter extension (for parsing output from Log command) added together with unit tests. --- NEW FILE: LogFile.cs --- #region "Copyright" // Copyright (C) 2004 Gerald Evans // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // As a special exception, the copyright holders of this library give you // permission to link this library with independent modules to produce an // executable, regardless of the license terms of these independent // modules, and to copy and distribute the resulting executable under // terms of your choice, provided that you also meet, for each linked // independent module, the terms and conditions of the license of that // module. An independent module is a module which is not derived from // or based on this library. If you modify this library, you may extend // this exception to your version of the library, but you are not // obligated to do so. If you do not wish to do so, delete this // exception statement from your version. // // <author>Gerald Evans</author> // #endregion namespace ICSharpCode.SharpCvsLib.Extension.LogReporter { using System; using System.Collections; /// <summary> /// Represents a single file from a LogReport /// </summary> /// <remarks> /// created by - gne /// created on - 28/02/2004 15:36:56 /// </remarks> public class LogFile : IEnumerable { private string repositoryFnm; /// <summary> /// The repository path+filename /// </summary> public string RepositoryFnm { get { return repositoryFnm; } set { repositoryFnm = value; } } private string workingFnm; /// <summary> /// The repository path+filename /// </summary> public string WorkingFnm { get { return workingFnm; } set { workingFnm = value; } } private string description; /// <summary> /// Description of this file /// </summary> public string Description { get { return description; } set { description = value; } } private ArrayList revisions = new ArrayList(); /// <summary> /// Default constructor - initializes all fields to default values /// </summary> public LogFile() { this.repositoryFnm = ""; this.workingFnm = ""; this.description = ""; } // /// <summary> // /// State constructor - initializes all fields to given values // /// </summary> // public LogFile(string repositoryFnm, // string workingFnm, // string description) // { //System.Console.WriteLine("LogFile({0}, {1}, {2})", repositoryFnm, workingFnm, description); // this.repositoryFnm = repositoryFnm; // this.workingFnm = workingFnm; // this.description = description; // } /// <summary> /// Adds a LogRevision to the LogFile /// Only called when the LogReport is being constructed /// </summary> internal void AddRevision(LogRevision revision) { revisions.Add(revision); } /// <summary> /// Gets an enumerator to enumerate over the revisions in the LogFile /// </summary> public IEnumerator GetEnumerator() { return revisions.GetEnumerator(); } /// <summary> /// The number of revisions in this LogFile /// </summary> public int Count { get { return revisions.Count; } } /// <summary> /// Indexer to the revisions in this LogFile /// </summary> public LogRevision this[int index] { get { return (LogRevision)revisions[index]; } } /// <summary> /// ToString() for debugging etc. /// </summary> public override string ToString() { return String.Format("[RepositoryFnm={0}, WorkingFnm={1}, Description={2}]", repositoryFnm, workingFnm, description); } } } --- NEW FILE: LogReport.cs --- #region "Copyright" // Copyright (C) 2004 Gerald Evans // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // As a special exception, the copyright holders of this library give you // permission to link this library with independent modules to produce an // executable, regardless of the license terms of these independent // modules, and to copy and distribute the resulting executable under // terms of your choice, provided that you also meet, for each linked // independent module, the terms and conditions of the license of that // module. An independent module is a module which is not derived from // or based on this library. If you modify this library, you may extend // this exception to your version of the library, but you are not // obligated to do so. If you do not wish to do so, delete this // exception statement from your version. // // <author>Gerald Evans</author> // #endregion namespace ICSharpCode.SharpCvsLib.Extension.LogReporter { using System; using System.Collections; /// <summary> /// This is the root of the LogReport object model /// </summary> /// <remarks> /// created by - gne /// created on - 28/02/2004 15:25:05 /// </remarks> public class LogReport : IEnumerable { private ArrayList files = new ArrayList(); /// <summary> /// Default constructor - initializes all fields to default values /// </summary> public LogReport() { } /// <summary> /// Adds a LogFile to the LogReport /// Only called when the LogReport is being constructed /// </summary> internal void AddFile(LogFile file) { files.Add(file); } /// <summary> /// Gets an enumerator to enumerate over the files in the LogReport /// </summary> public IEnumerator GetEnumerator() { return files.GetEnumerator(); } /// <summary> /// The number of files in the LogReport /// </summary> public int Count { get { return files.Count; } } /// <summary> /// Indexer to the files in the LogReport /// </summary> public LogFile this[int index] { get { return (LogFile)files[index]; } } } } --- NEW FILE: LogReportCommand.cs --- #region "Copyright" // Copyright (C) 2004 Gerald Evans // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // As a special exception, the copyright holders of this library give you // permission to link this library with independent modules to produce an // executable, regardless of the license terms of these independent // modules, and to copy and distribute the resulting executable under // terms of your choice, provided that you also meet, for each linked // independent module, the terms and conditions of the license of that // module. An independent module is a module which is not derived from // or based on this library. If you modify this library, you may extend // this exception to your version of the library, but you are not // obligated to do so. If you do not wish to do so, delete this // exception statement from your version. // // <author>Gerald Evans</author> // #endregion using System; using System.Collections; using System.Collections.Specialized; using System.IO; using System.Text; using System.Xml; using log4net; using ICSharpCode.SharpCvsLib.Client; using ICSharpCode.SharpCvsLib.Commands; using ICSharpCode.SharpCvsLib.Exceptions; using ICSharpCode.SharpCvsLib.FileSystem; using ICSharpCode.SharpCvsLib.Messages; using ICSharpCode.SharpCvsLib.Misc; namespace ICSharpCode.SharpCvsLib.Extension.LogReporter { /// <summary> /// Fires off a 'log' command to the server and /// returns the log info as a tree. /// Typical usage is as follows: /// /// string password = "password"; /// string xmlFilename = "C:\\tmp\\output.xml"; /// /// LogReportCommand logCommand = new LogReportCommand("sharpcvslib", "C:\\sharpcvslib"); /// /// logCommand.SetLastNDays(7); /// // or logCommand.StartDate = new DateTime(...); /// // and/or logCommand.EndDate = new DateTime(...); /// /// LogReport logReport = logCommand.Run(password); /// foreach (LogFile logFile in logReport) /// { /// ... /// foreach (LogRevision logRevision in logFile) /// { /// ... /// } /// } /// /// </summary> public class LogReportCommand { private ILog LOGGER = LogManager.GetLogger (typeof (LogReportCommand)); // data set by ctor private string module; private string localDirectory; // date information set by caller private DateTime startDate; private bool hasStartDate; private DateTime endDate; private bool hasEndDate; // Represents what we want next from the messages output by the log command private enum LogState { WANT_FILE_HEADER_START, // initial state where we want the file header WANT_FILE_HEADER, // we are in the file header, but want more WANT_FILE_DESCRIPTION, WANT_REVISION, } private LogState logState = LogState.WANT_FILE_HEADER_START; // this is where we build up the LogReport private LogReport curLogReport; private LogFile curLogFile; private LogRevision curLogRevision; /// <summary> /// ctor /// </summary> public LogReportCommand(string module, string localDirectory) { this.module = module; this.localDirectory = localDirectory; } /// <summary> /// If set, only report changes on or after this date /// </summary> public DateTime StartDate { set { startDate = value; hasStartDate = true; } } /// <summary> /// If set, only report changes on or before this date /// </summary> public DateTime EndDate { set { endDate = value; hasEndDate = true; } } /// <summary> /// Only report changes during the last given number of days. /// </summary> public void SetLastNDays(int days) { //endDate = DateTime.Now; startDate = DateTime.Now.AddDays(- days); hasStartDate = true; //hasEndDate = true; } /// <summary> /// Produce the report /// </summary> public LogReport Run(string password) { // read Root and Repository from local directory Manager manager = new Manager(localDirectory); Root root = (Root)manager.FetchSingle (localDirectory, Factory.FileType.Root); CvsRoot cvsRoot = new CvsRoot(root.FileContents); WorkingDirectory workingDirectory = new WorkingDirectory(cvsRoot, localDirectory, module); // Get a connection CVSServerConnection connection = new CVSServerConnection(); connection.Connect(workingDirectory, password); return Run(connection); } /// <summary> /// Produce the report /// Alternate interface for when we are given a server cooection /// This is needed for the SharpCvsLib command line client /// </summary> public LogReport Run(CVSServerConnection connection) { // read Root and Repository from local directory Manager manager = new Manager(localDirectory); Repository repository = (Repository)manager.FetchSingle (localDirectory, Factory.FileType.Repository); Root root = (Root)manager.FetchSingle (localDirectory, Factory.FileType.Root); CvsRoot cvsRoot = new CvsRoot(root.FileContents); WorkingDirectory workingDirectory = new WorkingDirectory(cvsRoot, localDirectory, module); // Recursively add all cvs folders/files under the localDirectory workingDirectory.FoldersToUpdate = FetchFiles(localDirectory); LogCommand command = new LogCommand(workingDirectory, repository.FileContents, null); // add any date restrictions if (hasStartDate && hasEndDate) { command.AddInclusiveDateRange(startDate, endDate); } else if (hasStartDate) { command.AddInclusiveDateStart(startDate); } else if (hasEndDate) { command.AddInclusiveDateEnd(endDate); } // Initialse state machine curLogReport = new LogReport(); // this is what we are going to return to the caller curLogFile = new LogFile(); curLogRevision = new LogRevision(); logState = LogState.WANT_FILE_HEADER_START; connection.MessageEvent.MessageEvent += new EncodedMessage.MessageHandler(OnMessage); command.Execute(connection); // return curLogReport but clear our reference to it LogReport report = curLogReport; curLogReport = null; return report; } /// <summary> /// Returns a list of all the folders (and the files in each of those folders) /// that we need to get the change log for. /// </summary> private Folder[] FetchFiles(string localDirectory) { ArrayList folders = new ArrayList (); FetchFilesRecursive(folders, localDirectory); return (Folder[])folders.ToArray (typeof (Folder)); } private void FetchFilesRecursive(ArrayList folders, string localDirectory) { String modulePath = localDirectory; Manager manager = new Manager(modulePath); Folder folder = new Folder (); folder.Repository = (Repository)manager.FetchSingle (modulePath, Factory.FileType.Repository); Entries entries1= manager.FetchEntries(Path.Combine(modulePath, Entry.FILE_NAME)); foreach (DictionaryEntry dicEntry in entries1) { Entry entry = (Entry)dicEntry.Value; if (!entry.IsDirectory) { if (LOGGER.IsDebugEnabled) { LOGGER.Debug("Found file=[" + entry.FullPath + "]"); } folder.Entries.Add (entry.FullPath, entry); } } folders.Add (folder); foreach (DictionaryEntry dicEntry in entries1) { Entry entry = (Entry)dicEntry.Value; if (entry.IsDirectory) { string childDir = Path.Combine(localDirectory, entry.Name); if (LOGGER.IsDebugEnabled) { LOGGER.Debug("Found directory=[" + childDir + "]"); } FetchFilesRecursive(folders, childDir); } } } /// <summary> /// This is called for each Message response we receive from the cvs server. /// </summary> public void OnMessage(string message) { const string repositoryFnmPrefix = "RCS file: "; const string workingFnmPrefix = "Working file: "; const string descriptionPrefix = "description:"; const string revisionPrefix = "revision "; const string datePrefix = "date: "; const string branchesPrefix = "branches: "; const string fileEndPrefix = "=========="; const string revisionEndPrefix = "----------"; // System.Console.WriteLine(message); // for some reason the message handler is now preceeding // each message with "cvs server: " so we need to strip this off first if (message.StartsWith("cvs server: ")) { message = message.Substring(12); } // only process the lines starting with "M " if (message.StartsWith("M ")) { // Strip of the leading "M " message = message.Substring(2); if (message.StartsWith(revisionEndPrefix)) { // seperator between file and revision or between revisions if (logState == LogState.WANT_FILE_HEADER_START) { // ignore this (shouldn't happen) } else if (logState == LogState.WANT_FILE_HEADER || logState == LogState.WANT_FILE_DESCRIPTION) { // this is the seperator between te file header and the first revision } else { // seperator between revisions curLogFile.AddRevision(curLogRevision); } curLogRevision = new LogRevision(); logState = LogState.WANT_REVISION; } else if (message.StartsWith(fileEndPrefix)) { // seperator between files if (logState == LogState.WANT_FILE_HEADER_START) { // ignore this (shouldn't happen) } else if (logState == LogState.WANT_FILE_HEADER || logState == LogState.WANT_FILE_DESCRIPTION) { // file with no revisions curLogReport.AddFile(curLogFile); } else { // first add the revision curLogFile.AddRevision(curLogRevision); curLogRevision = new LogRevision(); // and now the file curLogReport.AddFile(curLogFile); } curLogFile = new LogFile(); logState = LogState.WANT_FILE_HEADER_START; } else { switch (logState) { case LogState.WANT_FILE_HEADER_START: // drop into WANT_FILE_HEADER case LogState.WANT_FILE_HEADER: if (message.StartsWith(repositoryFnmPrefix)) { // file line is of form 'RCS file: <filename>' curLogFile.RepositoryFnm = message.Substring(repositoryFnmPrefix.Length); logState = LogState.WANT_FILE_HEADER; } else if (message.StartsWith(workingFnmPrefix)) { // file line is of form 'Working file: <filename>' curLogFile.WorkingFnm = message.Substring(workingFnmPrefix.Length); logState = LogState.WANT_FILE_HEADER; } else if (message.StartsWith(descriptionPrefix)) { // description line is of form 'description:' // and is then optionally followed by a multi-line description logState = LogState.WANT_FILE_DESCRIPTION; } break; case LogState.WANT_FILE_DESCRIPTION: // append description line to the description if (curLogFile.Description.Length > 0) { curLogFile.Description += Environment.NewLine; } curLogFile.Description += message; break; case LogState.WANT_REVISION: if (message.StartsWith(revisionPrefix) && curLogRevision.Revision.Length == 0) { curLogRevision.Revision = message.Substring(revisionPrefix.Length); } else if (message.StartsWith(datePrefix) && curLogRevision.Author.Length == 0) { ExtractDateAndAuthor(message); } else if (message.StartsWith(branchesPrefix) && curLogRevision.Branches.Length == 0) { curLogRevision.Branches = message.Substring(branchesPrefix.Length); } else { // assume this is part of the comment if (curLogRevision.Comment.Length > 0) { curLogRevision.Comment += Environment.NewLine; } curLogRevision.Comment += message; } break; } } } } /// <summary> /// Extracts all required information from the date line. /// </summary> private void ExtractDateAndAuthor(string message) { // date line is of form: // 'date: yyyy/mm/dd hh:mm:ss; author: <author>; state: <state>; lines: +<added> -<deleted>' const int IDX_DATE = 1; const int IDX_TIME = 2; const int IDX_AUTHOR = 4; const int IDX_STATE = 6; const int IDX_LINES1 = 8; const int IDX_LINES2 = 9; // make things a bit easier be removing the ';' chars // and replace all double spaces with a single space string cleanedMessage = message.Replace(";", "").Replace(" ", " "); string[] tokens = cleanedMessage.Split(" ".ToCharArray()); // for (int i = 0; i < tokens.Length; i++) // { // System.Console.WriteLine("[{0}]='{1}'", i, tokens[i]); // } // get the date if (tokens.Length > IDX_TIME) { string date = String.Format("{0} {1}", tokens[IDX_DATE], tokens[IDX_TIME]); curLogRevision.Timestamp = DateTime.Parse(date); } // get the author if (tokens.Length > IDX_AUTHOR) { curLogRevision.Author = tokens[IDX_AUTHOR]; } // get the state if (tokens.Length > IDX_STATE) { curLogRevision.State = tokens[IDX_STATE]; } // get number of lines added / deleted for (int idx = IDX_LINES1; idx <= IDX_LINES2; idx++) { if (tokens.Length > idx) { try { int num = Int32.Parse(tokens[idx]); if (num < 0) { curLogRevision.LinesDeleted = - num; } else { curLogRevision.LinesAdded = num; } } catch (Exception) { // don't expect a non-number, but we'll ignore it if we get one } } } } } } --- NEW FILE: LogRevision.cs --- #region "Copyright" // Copyright (C) 2004 Gerald Evans // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // As a special exception, the copyright holders of this library give you // permission to link this library with independent modules to produce an // executable, regardless of the license terms of these independent // modules, and to copy and distribute the resulting executable under // terms of your choice, provided that you also meet, for each linked // independent module, the terms and conditions of the license of that // module. An independent module is a module which is not derived from // or based on this library. If you modify this library, you may extend // this exception to your version of the library, but you are not // obligated to do so. If you do not wish to do so, delete this // exception statement from your version. // // <author>Gerald Evans</author> // #endregion namespace ICSharpCode.SharpCvsLib.Extension.LogReporter { using System; /// <summary> /// Represents a single revision of a single file from a LogReport /// </summary> /// <remarks> /// created by - gne /// created on - 28/02/2004 15:37:13 /// </remarks> public class LogRevision : object { private string revision; /// <summary> /// The revision number /// </summary> public string Revision { get { return revision; } set { revision = value; } } private DateTime timestamp; /// <summary> /// date/time of the revision /// </summary> public DateTime Timestamp { get { return timestamp; } set { timestamp = value; } } private string author; /// <summary> /// The author of the revision /// </summary> public string Author { get { return author; } set { author = value; } } private string state; /// <summary> /// TODO: find out what the values of this can be /// </summary> public string State { get { return state; } set { state = value; } } private string comment; /// <summary> /// Comment describing revision /// </summary> public string Comment { get { return comment; } set { comment = value; } } private int linesAdded; /// <summary> /// Number of lines added in this revision /// </summary> public int LinesAdded { get { return linesAdded; } set { linesAdded = value; } } private int linesDeleted; /// <summary> /// Number of lines deleted in this revision /// </summary> public int LinesDeleted { get { return linesDeleted; } set { linesDeleted = value; } } private string branches; /// <summary> /// Revision that was brached from if applicable. /// </summary> public string Branches { get { return branches; } set { branches = value; } } /// <summary> /// Default constructor - initializes all fields to default values /// </summary> public LogRevision() { this.revision = ""; this.timestamp = DateTime.Now; this.author = ""; this.state = ""; this.comment = ""; this.linesAdded = 0; this.linesDeleted = 0; this.branches = ""; } // /// <summary> // /// State constructor - initializes all fields // /// </summary> // public LogRevision(string revision, // DateTime timestamp, // string author, // string comment) // { //System.Console.WriteLine("LogRevision({0}, {1})", revision, author); // this.revision = revision; // this.timestamp = timestamp; // this.author = author; // this.comment = comment; // } /// <summary> /// ToString() for debugging etc. /// </summary> public override string ToString() { return String.Format("[Revision={0}, Timestamp={1}, Author={2}, State={3}, LinesAdded={4}, LinesDeleted={5}, Comment={6}]", revision, timestamp, author, state, linesAdded, linesDeleted); } } } |