From ad3ced97b781e23543aa731300a3c23292fe5f18 Mon Sep 17 00:00:00 2001 From: mdelfede Date: Sun, 15 Jun 2008 00:53:12 +0000 Subject: [PATCH] SVN Library -- first release + test app git-svn-id: svn://ultimatepp.org/upp/trunk@299 f0d560ea-af0d-0410-9eb7-867de7ffcac7 --- bazaar/SvnLib/SvnLib.cpp | 869 +++++++++++++++++++++++++++++++++++++ bazaar/SvnLib/SvnLib.h | 277 ++++++++++++ bazaar/SvnLib/SvnLib.upp | 8 + bazaar/SvnTest/SvnTest.h | 44 ++ bazaar/SvnTest/SvnTest.lay | 13 + bazaar/SvnTest/SvnTest.upp | 12 + bazaar/SvnTest/main.cpp | 269 ++++++++++++ 7 files changed, 1492 insertions(+) create mode 100644 bazaar/SvnLib/SvnLib.cpp create mode 100644 bazaar/SvnLib/SvnLib.h create mode 100644 bazaar/SvnLib/SvnLib.upp create mode 100644 bazaar/SvnTest/SvnTest.h create mode 100644 bazaar/SvnTest/SvnTest.lay create mode 100644 bazaar/SvnTest/SvnTest.upp create mode 100644 bazaar/SvnTest/main.cpp diff --git a/bazaar/SvnLib/SvnLib.cpp b/bazaar/SvnLib/SvnLib.cpp new file mode 100644 index 000000000..5d3410352 --- /dev/null +++ b/bazaar/SvnLib/SvnLib.cpp @@ -0,0 +1,869 @@ +#include + +#include "SvnLib.h" + +NAMESPACE_UPP + + +/////////////////////////////////////////////////////////////////////////////////////////////// +// Human-readable error messages +const char *Svn::ErrorMessages[] = +{ + "Svn : Ok", + "Svn : Unknown Error", + "Svn : Svn application not found", + "Svn : Not a SVN repository", + "Svn : Not connected to a repository", + "Svn : File not found", + "Svn : Path not found", + "Svn : File exists", + "Svn : Directory exists", + "Svn : Path exists", + "Svn : Directory is not empty", + "Svn : Path is not a directory", + "Svn : Can't remove directory. Read only?", + "Svn : Can't write do directory. Read only?" + "Svn : Path is not in active repository", + "Svn : Invalid repository URL", + "Svn : Bad XML found", + "Svn : Bad login. Check username/password" + +} ; // END Svn::ErrorMessages[] + +/////////////////////////////////////////////////////////////////////////////////////////////// +// Human-readable status strings +const char *Svn::StatusStrings[] = +{ + "unknown", + "added", + "missing", + "incomplete", + "replaced", + "modified", + "merged", + "conflicted", + "obstructed", + "ignored", + "external", + "unversioned" + +}; // END Svn::StatusStrings + +const int Svn::NumStatus = sizeof(StatusStrings) / sizeof(char *); + +/////////////////////////////////////////////////////////////////////////////////////////////// +// from status string, gets status +Svn::ItemStatus Svn::String2Status(String const &s) +{ + for(int i = 0; i < NumStatus; i++) + if(s == StatusStrings[i]) + return (ItemStatus)i; + return unknown; + +} // END Svn::String2Status() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// from status, gets human-readable string +String Svn::Status2String(ItemStatus s) +{ + if(s >= unknown && s <= unversioned) + return StatusStrings[s]; + else + return "BAD STATUS"; + +} // END Svn::Status2String() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// executes svn, passing cmdline as argument, gathering output in Output +// in xml format (where available) if xml is true +Svn::Errors Svn::ExecSvn(String const &SvnArgs, String &OutStr, String &ErrStr, bool xml) +{ + // adjust args if we wants xml output + String args = (xml ? "--xml " : "") + SvnArgs; + + // adjust environment to get output in en_IN locale + // otherwise it becomes difficult to understand command responses.... + VectorMap env; + env.Add("LC_MESSAGES", "en_IN"); + + // executes svn command + bool result = SysExec("svn", args, env, OutStr, ErrStr); + FLastCommandOutput = OutStr; + FLastCommandError = ErrStr; + + // if all ok, returns ok + if(result && ErrStr == "") + return SetError(Svn::Ok); + + // catches spawning errors + if(ErrStr.Find("Couldn't fork") >=0 || ErrStr.Find("Error spawning process") >=0) + return SetError(NoSvnApp); + + // catches svn command errors + + // checks for ill-formed URLs + if(ErrStr.Find("does not appear to be a URL") >= 0) + return SetError(Svn::NotAnURL); + + // ckecks if could write to folder + if(ErrStr.Find("Can't create directory") >= 0) + return SetError(Svn::CantWriteDirectory); + + // checks wether we tried to operate on a non-svn folder + if (ErrStr.Find("is not a working copy") >= 0) + return SetError(Svn::NotARepo); + + return SetError(Svn::UnknownError); + +} // END Svn::ExecSvn() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// builds username/password string +String Svn::BuildUserPassword(void) +{ + if(FAnonymous) + return " "; + else + return " --username " + FUserName + " --password " + FPassword + " "; + +} // END Svn::BuildUserPassword() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// builds revision string +String Svn::BuildRevision(String const &Revision) +{ + if(Revision != "") + return " --revision " + Revision + " "; + else + return " "; + +} // END Svn::BuildRevision() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// checks wether a string is a remote URL or a local path +bool Svn::IsUrl(String const &path) +{ + if( + path.Find("http://") >= 0 || + path.Find("https://") >= 0 || + path.Find("svn://") >= 0 || + path.Find("file://") >= 0 + ) + return true; + return false; + +} // END Svn::IsUrl() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// checks whether a path is inside local repository +bool Svn::IsLocalPath(String const &path) +{ + return (path.Find(FLocalPath) == 0); + +} // END Svn::IsLocalPath() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// checks whether a path is inside remote repository +bool Svn::IsRemotePath(String const &path) +{ + return (path.Find(FRepositoryRoot) == 0); + +} // END Svn::IsRemotePath() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// checks whether a path is inside repository, remote or local side +bool Svn::IsRepositoryPath(String const &path) +{ + return IsLocalPath(path) || IsRemotePath(path); + +} // END Svn::IsRepositoryPath() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// initializer... used by constructor and on disconnection +void Svn::Init(void) +{ + // log-in stuffs + FUserName = ""; + FPassword = ""; + FAnonymous = true; + + // svn repository info stuffs + FLocalPath = ""; + FRepositoryRoot = ""; + FRepositoryUUID = ""; + FCheckedRevisionAuthor = ""; + FCheckedRevision = 0; + FCheckedRevisionDate = ""; + FHeadRevisionAuthor = ""; + FHeadRevision = 0; + FHeadRevisionDate = ""; + + FLastError = Ok; + FConnected = false; + + FLastCommandOutput = ""; + FLastCommandError = ""; + +} // END Svn::Init() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// constructor class Svn +Svn::Svn() +{ + Init(); + +} // END constructor class Svn + + +/////////////////////////////////////////////////////////////////////////////////////////////// +// destructor class Svn +Svn::~Svn() +{ + +} // END destructor class Svn + + +/////////////////////////////////////////////////////////////////////////////////////////////// +// checks wether a path is a svn +bool Svn::IsSvn(String const &LocalPath) +{ + String Out, Err; + return( ExecSvn("info " + LocalPath, Out, Err) == Svn::Ok); + +} // END Svn::IsSvn() + + +/////////////////////////////////////////////////////////////////////////////////////////////// +// parses xml output of 'svn info' command +Svn::Errors Svn::ParseInfo(String const &str, EntryInfo &info) +{ + try + { + XmlParser p(str); + while (!p.IsTag() &&!p.End()) + p.Skip(); + p.PassTag("info"); + while (!p.End()) + { + if (p.Tag("entry")) + { + info.kind = p["kind"]; + info.revision = atoi(p["revision"]); + info.path = p["path"]; + while (!p.End()) + { + if (p.Tag("url")) + { + info.url = p.ReadText(); + p.SkipEnd(); + } + else if(p.Tag("repository")) + { + while(!p.End()) + { + if(p.Tag("root")) + { + info.repositoryRoot = p.ReadText(); + p.SkipEnd(); + } + else if(p.Tag("uuid")) + { + info.repositoryUUID = p.ReadText(); + p.SkipEnd(); + } + else + p.Skip(); + } + } + else if(p.Tag("commit")) + { + while(!p.End()) + { + if(p.Tag("revision")) + { + info.commitRevision = atoi(p.ReadText()); + p.SkipEnd(); + } + else if(p.Tag("author")) + { + info.commitAuthor = p.ReadText(); + p.SkipEnd(); + } + else if(p.Tag("date")) + { + info.commitDate = p.ReadText(); + p.SkipEnd(); + } + else + p.Skip(); + } + } + else + p.Skip(); + } + } + else + p.Skip(); + } + + return Ok; + } + catch (XmlError) + { + return BadXml; + } + +} // END Svn::ParseInfo() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// parses xml output of 'svn log' command +Svn::Errors Svn::ParseLog(String const &str, Array&infoArray) +{ + try + { + XmlParser p(str); + while (!p.IsTag() &&!p.End()) + p.Skip(); + p.PassTag("log"); + while (!p.End()) + { + if (p.Tag("logentry")) + { + LogInfo &info = infoArray.Add(); + info.author = ""; + info.date = ""; + info.message = ""; + info.revision = atoi(p["revision"]); + while (!p.End()) + { + if (p.Tag("author")) + { + info.author = p.ReadText(); + p.SkipEnd(); + } + else if(p.Tag("date")) + { + info.date = p.ReadText(); + p.SkipEnd(); + } + else if(p.Tag("msg")) + { + info.message = p.ReadText(); + p.SkipEnd(); + } + else + p.Skip(); + } + } + else + p.Skip(); + } + + return Ok; + } + catch (XmlError) + { + return BadXml; + } + +} // END Svn::ParseLog() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// parses xml output of 'svn status' command +Svn::Errors Svn::ParseStatus(String const &str, Array &infoArray) +{ + try + { + XmlParser p(str); + while (!p.IsTag() &&!p.End()) + p.Skip(); + p.PassTag("status"); + p.PassTag("target"); + while (!p.End()) + { + if (p.Tag("entry")) + { + StatusInfo &info = infoArray.Add(); + info.path = ""; + info.props = ""; + info.status = unknown; + info.revision = -1; + info.commitRevision = -1; + info.commitAuthor = ""; + info.commitDate = ""; + + info.path = p["path"]; + while (!p.End()) + { + if (p.Tag("wc-status")) + { + info.props = p["props"]; + info.status = String2Status(p["item"]); + info.revision = atoi(p["revision"]); + p.SkipEnd(); + } + else if(p.Tag("commit")) + { + info.commitRevision = atoi(p["revision"]); + p.SkipEnd(); + } + else if(p.Tag("author")) + { + info.commitAuthor = p.ReadText(); + p.SkipEnd(); + } + else if(p.Tag("date")) + { + info.commitDate = p.ReadText(); + p.SkipEnd(); + } + else + p.Skip(); + } + } + else + p.Skip(); + } + + return Ok; + } + catch (XmlError) + { + return BadXml; + } + +} // END Svn::ParseStatus() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// sets user authentication +void Svn::SetUser(String const &UserName, String const &Password) +{ + FUserName = UserName; + FPassword = Password; + FAnonymous = false; + +} // END Svn::SetUser() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// sets user authentication +void Svn::SetUser(void) +{ + FUserName = ""; + FPassword = ""; + FAnonymous = true; + +} // END Svn::SetUser() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// connects to the local copy +Svn::Errors Svn::Connect(String const &LocalPath) +{ + Svn::Errors result; + String Out, Err; + + // first, checks wether is a svn repo + if(!IsSvn(LocalPath)) + return SetError(Svn::NotARepo); + + // executes 'svn info' command -- gets current checked-in information + result = ExecSvn("info " + BuildUserPassword() + LocalPath, Out, Err, true); + if(result != Svn::Ok) + return SetError(result); + + // parses xml info output + EntryInfo info; + result = ParseInfo(Out, info); + if(result != Svn::Ok) + return SetError(result); + + // stores repository info + FLocalPath = LocalPath; + FRepositoryRoot = info.repositoryRoot; + FRepositoryUUID = info.repositoryUUID; + FCheckedRevisionAuthor = info.commitAuthor; + FCheckedRevision = info.revision; + FCheckedRevisionDate = info.commitDate; + + // executes 'svn info -r HEAD' command -- gets HEAD information + result = ExecSvn("info -r HEAD " + BuildUserPassword() + LocalPath, Out, Err, true); + if(result != Svn::Ok) + return SetError(result); + + // parses xml info output + result = ParseInfo(Out, info); + if(result != Svn::Ok) + return SetError(result); + + // stores repository info + FHeadRevisionAuthor = info.commitAuthor; + FHeadRevision = info.revision; + FHeadRevisionDate = info.commitDate; + + FConnected = true; + FLastError = Ok; + + return SetError(Svn::Ok); + +} // END Svn::Connect() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// disconnects from local copy +Svn::Errors Svn::Disconnect(void) +{ + // if not connected, does nothing + if(!FConnected) + return SetError(Svn::NotConnected); + + // reinitializes + Init(); + + return Svn::Ok; + +} // END Svn::Disconnect() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// checks out from server +Svn::Errors Svn::Checkout(String const &Url, String const &LocalPath, String const &Revision) +{ + String Out, Err; + String Path; + Svn::Errors result; + + // adjust final path separator + Path = LocalPath; + if(Path[Path.GetLength()-1] != DIR_SEP) + Path << DIR_SEP; + + // fists, looks at localpath; if it does exist, it must be empty + if(Upp::DirectoryExists(Path)) + { + // checks if directory is empty + Array fileInfo = StdFileSystemInfo().Find(Path + "*"); + bool found = false; + for(int i = 0; i < fileInfo.GetCount(); i++) + { + if(fileInfo[i].filename != "." && fileInfo[i].filename != "..") + { + found = true; + break; + } + } + if(found) + return SetError(DirectoryNotEmpty); + + // removes the path + if(!DirectoryDelete(Path)) + return SetError(CantRemoveDirectory); + } + + // well, now we can do the checkout + result = ExecSvn("checkout -q --no-auth-cache --non-interactive" + BuildUserPassword() + BuildRevision(Revision) + Url + " " + Path, Out, Err, false); + if(result != Svn::Ok) + return SetError(result); + + // checkout should be done, connect to checked out folder + return SetError(Connect(Path)); + +} // END Svn::Checkout() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// updates current copy from server +Svn::Errors Svn::Update(String const &Revision) +{ + String Out, Err; + Svn::Errors result; + + // if not connected, does nothing + if(!FConnected) + return SetError(Svn::NotConnected); + + // well, now we can do the update + result = ExecSvn("update -q --no-auth-cache --non-interactive" + BuildUserPassword() + BuildRevision(Revision) + FLocalPath, Out, Err, false); + if(result != Svn::Ok) + return SetError(result); + + // executes 'svn info' command -- gets current checked-in information + result = ExecSvn("info " + BuildUserPassword() + FLocalPath, Out, Err, true); + if(result != Svn::Ok) + return SetError(result); + + // parses xml info output + EntryInfo info; + result = ParseInfo(Out, info); + if(result != Svn::Ok) + return SetError(result); + + // stores repository info + FRepositoryRoot = info.repositoryRoot; + FRepositoryUUID = info.repositoryUUID; + FCheckedRevisionAuthor = info.commitAuthor; + FCheckedRevision = info.revision; + FCheckedRevisionDate = info.commitDate; + + return SetError(Svn::Ok); + +} // END Svn::Update() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// commits to server +Svn::Errors Svn::Commit(String const &Message) +{ + String Out, Err; + Svn::Errors result; + + // if not connected, does nothing + if(!FConnected) + return SetError(Svn::NotConnected); + + // well, now we can do the commit + result = ExecSvn("commit -q --no-auth-cache --non-interactive" + BuildUserPassword() + " -- message " + Message, Out, Err, false); + if(result != Svn::Ok) + return SetError(result); + + return SetError(Svn::Ok); + +} // END Svn::Commit() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// adds file(s) to current repository +Svn::Errors Svn::Add(String const &FileName) +{ + String Out, Err; + Svn::Errors result; + + // if not connected, does nothing + if(!FConnected) + return SetError(Svn::NotConnected); + + // checks whether given file/directory exists + if(!Upp::FileExists(FileName) && !Upp::DirectoryExists(FileName)) + return SetError(Svn::PathNotFound); + + // checks whether given path is inside repository + if(!IsLocalPath(FileName)) + return SetError(Svn::PathNotInRepo); + + // now we can add file to repository + result = ExecSvn("add -q " + FileName, Out, Err, false); + if(result != Svn::Ok) + return SetError(result); + + return SetError(Svn::Ok); + +} // END Svn::Add() + +Svn::Errors Svn::Add(Array const &FileNames) +{ + String Out, Err; + Svn::Errors result; + + // if not connected, does nothing + if(!FConnected) + return SetError(Svn::NotConnected); + + // checks if all furnished files/directories exists and are in local repository + for(int i = 0; i < FileNames.GetCount(); i++) + { + if(!Upp::FileExists(FileNames[i]) && !Upp::DirectoryExists(FileNames[i])) + return SetError(Svn::PathNotFound); + + if(!IsLocalPath(FileNames[i])) + return SetError(Svn::PathNotInRepo); + } + + // now we can add files to repository + for(int i = 0; i < FileNames.GetCount(); i++) + { + result = ExecSvn("add -q " + FileNames[i], Out, Err, false); + if(result != Svn::Ok) + return SetError(result); + } + return SetError(Svn::Ok); + +} // END Svn::Add() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// removes file(s) from current repository +Svn::Errors Svn::Delete(String const &FileName) +{ + String Out, Err; + Svn::Errors result; + + // if not connected, does nothing + if(!FConnected) + return SetError(Svn::NotConnected); + + // must NOT check if files exists, they can be deleted + // from folder but be listed in repo + + // checks whether given path is inside repository + if(!IsLocalPath(FileName)) + return SetError(Svn::PathNotInRepo); + + // now we can remove file to repository + result = ExecSvn("delete -q " + FileName, Out, Err, false); + if(result != Svn::Ok) + return SetError(result); + + return SetError(Svn::Ok); + +} // END Svn::Delete() + +Svn::Errors Svn::Delete(Array const &FileNames) +{ + String Out, Err; + Svn::Errors result; + + // if not connected, does nothing + if(!FConnected) + return SetError(Svn::NotConnected); + + // must NOT check if files exists, they can be deleted + // from folder but be listed in repo + + // checks if all furnished files/directories are in local repository + for(int i = 0; i < FileNames.GetCount(); i++) + { + if(!IsLocalPath(FileNames[i])) + return SetError(Svn::PathNotInRepo); + } + + // now we can add files to repository + for(int i = 0; i < FileNames.GetCount(); i++) + { + result = ExecSvn("delete -q " + FileNames[i], Out, Err, false); + if(result != Svn::Ok) + return SetError(result); + } + return SetError(Svn::Ok); + +} // END Svn::Delete() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// copy a file inside current repository +Svn::Errors Svn::Copy(String const &Src, String const &Dest) +{ + String Out, Err; + Svn::Errors result; + String AuthStr; + + // if not connected, does nothing + if(!FConnected) + return SetError(Svn::NotConnected); + + // first, checks that given paths are inside current repository + if(!IsRepositoryPath(Src) || !IsRepositoryPath(Dest)) + return SetError(Svn::PathNotInRepo); + + // if source path is local, checks if exists + if(!IsUrl(Src) && !Upp::FileExists(Src) && !Upp::DirectoryExists(Src)) + return SetError(Svn::PathNotFound); + + // dest path is local, it should not exist + if(!IsUrl(Dest) && (Upp::FileExists(Dest) || Upp::DirectoryExists(Dest))) + return SetError(Svn::PathExists); + + // if source or dest are remote, may require authentication + if(IsUrl(Src) || IsUrl(Dest)) + AuthStr = BuildUserPassword(); + else + AuthStr = " "; + + // now we can do the copy + result = ExecSvn("copy -q " + AuthStr + Src + " " + Dest, Out, Err, false); + if(result != Svn::Ok) + return SetError(result); + + return SetError(Svn::Ok); + +} // END Svn::Copy() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// moves a file inside current repository +Svn::Errors Svn::Move(String const &Src, String const &Dest) +{ + String Out, Err; + Svn::Errors result; + String AuthStr; + + // if not connected, does nothing + if(!FConnected) + return SetError(Svn::NotConnected); + + // first, checks that given paths are inside current repository + if(!IsRepositoryPath(Src) || !IsRepositoryPath(Dest)) + return SetError(Svn::PathNotInRepo); + + // if source path is local, checks if exists + if(!IsUrl(Src) && !Upp::FileExists(Src) && !Upp::DirectoryExists(Src)) + return SetError(Svn::PathNotFound); + + // dest path is local, it should not exist + if(!IsUrl(Dest) && (Upp::FileExists(Dest) || Upp::DirectoryExists(Dest))) + return SetError(Svn::PathExists); + + // if source or dest are remote, may require authentication + if(IsUrl(Src) || IsUrl(Dest)) + AuthStr = BuildUserPassword(); + else + AuthStr = " "; + + // now we can do the move + result = ExecSvn("move -q " + AuthStr + Src + " " + Dest, Out, Err, false); + if(result != Svn::Ok) + return SetError(result); + + return SetError(Svn::Ok); + +} // END Svn::Move() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// gets log from remote +Svn::Errors Svn::GetLog(Array&logArray) +{ + String Out, Err; + Svn::Errors result; + + // if not connected, does nothing + if(!FConnected) + return SetError(Svn::NotConnected); + + // executes 'svn log' command -- gets current checked-in information + result = ExecSvn("log " + BuildUserPassword() + FLocalPath, Out, Err, true); + if(result != Svn::Ok) + return SetError(result); + + // parses log into logArray + return SetError(ParseLog(Out, logArray)); + +} // END Svn::GetLog() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// gets local repository status +Svn::Errors Svn::GetStatus(Array&statusArray) +{ + String Out, Err; + Svn::Errors result; + + // if not connected, does nothing + if(!FConnected) + return SetError(Svn::NotConnected); + + // executes 'svn status' command -- gets current checked-in information + result = ExecSvn("status " + FLocalPath, Out, Err, true); + if(result != Svn::Ok) + return SetError(result); + + // parses log into logArray + return SetError(ParseStatus(Out, statusArray)); + +} // END Svn::GetStatus() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// updates info from current repository +Svn::Errors Svn::UpdateInfo(void) +{ + +} // END Svn::UpdateInfo() + +END_UPP_NAMESPACE diff --git a/bazaar/SvnLib/SvnLib.h b/bazaar/SvnLib/SvnLib.h new file mode 100644 index 000000000..765580bf5 --- /dev/null +++ b/bazaar/SvnLib/SvnLib.h @@ -0,0 +1,277 @@ +#ifndef _SvnLib_h_ +#define _SvnLib_h_ + +#include + +NAMESPACE_UPP + +////////////////////////////////////////////////////////////////////////////////////////////// +// svn interface class +// encapsulates svn operation using command line tool 'svn' +class Svn +{ + public: + // types of svn nodes + enum NodeTypes + { + SvnUnknown, + SvnDir + }; + + // errors returned by various svn functions + enum Errors + { + Ok = 0, + UnknownError, + NoSvnApp, + NotARepo, + NotConnected, + FileNotFound, + PathNotFound, + FileExists, + DirectoryExists, + PathExists, + DirectoryNotEmpty, + NotADirectory, + CantRemoveDirectory, + CantWriteDirectory, + PathNotInRepo, + NotAnURL, + BadXml, + BadLogin + }; + + // Human-readable error messages + static const char *ErrorMessages[]; + + // Item status + enum ItemStatus + { + unknown, + added, + missing, + incomplete, + replaced, + modified, + merged, + conflicted, + obstructed, + ignored, + external, + unversioned, + + }; // END enum ItemStatus + + // Human-readable status strings + static const char *StatusStrings[]; + static const int NumStatus; + + // from status string, gets status + static ItemStatus String2Status(String const &s); + + // from status, gets human-readable string + static String Status2String(ItemStatus s); + + // svn entry info + struct EntryInfo + { + String kind; + String path; + long revision; + String url; + String repositoryRoot; + String repositoryUUID; + long commitRevision; + String commitAuthor; + String commitDate; + + }; // END struct EntryInfo; + + // svn log info + struct LogInfo + { + long revision; + String author; + String message; + String date; + + }; // END struct LogInfo + + // svn status info + struct StatusInfo + { + String path; + String props; + ItemStatus status; + long revision; + long commitRevision; + String commitAuthor; + String commitDate; + + }; // END struct StatusInfo + + private: + // log-in stuffs + String FUserName; + String FPassword; + bool FAnonymous; + + // svn repository info stuffs + String FLocalPath; + String FRepositoryRoot; + String FRepositoryUUID; + + String FCheckedRevisionAuthor; + ulong FCheckedRevision; + String FCheckedRevisionDate; + + String FHeadRevisionAuthor; + ulong FHeadRevision; + String FHeadRevisionDate; + + bool FConnected; + Errors FLastError; + + String FLastCommandOutput; + String FLastCommandError; + + // executes svn, passing cmdline as argument, gathering output in Output + // in xml format (where available) if xml is true + Svn::Errors ExecSvn(String const &CmdLine, String &OutStr, String &ErrStr, bool xml = false); + + // parses output of 'svn info' command and gathers its output + Errors ParseInfo(String const &str, EntryInfo &info); + + // parses xml output of 'svn log' command + Errors ParseLog(String const &str, Array &info); + + // parses xml output of 'svn status' command + Errors ParseStatus(String const &str, Array &info); + + // builds username/password string + String BuildUserPassword(void); + + // builds revision string + String BuildRevision(String const &Revision); + + // checks whether a string is a remote URL or a local path + bool IsUrl(String const &path); + + // checks whether a path is inside local repository + bool IsLocalPath(String const &path); + + // checks whether a path is inside remote repository + bool IsRemotePath(String const &path); + + // checks whether a path is inside repository, remote or local side + bool IsRepositoryPath(String const &path); + + // initializer... used by constructor and on disconnection + void Init(void); + + public: + + // constructor + Svn(); + + // destructor + ~Svn(); + + // sets error condition + Errors SetError(Errors e) { FLastError = e; return e; } + + // checks wether a path is a svn + bool IsSvn(String const &LocalPath); + + // checks wether the repository is connected + bool IsConnected(void) { return FConnected; } + + // gets local path of repository + String GetLocalPath() { return FLocalPath; } + String GetRepositoryRoot() { return FRepositoryRoot; } + String GetRepositoryUUID() { return FRepositoryUUID; } + String GetCheckedRevisionAuthor() { return FCheckedRevisionAuthor; } + ulong GetCheckedRevision() { return FCheckedRevision; } + String GetCkeckedRevisionDate() { return FCheckedRevisionDate; } + String GetHeadRevisionAuthor() { return FHeadRevisionAuthor; } + ulong GetHeadRevision() { return FHeadRevision; } + String GetHeadRevisionDate() { return FHeadRevisionDate; } + + Errors GetLastError() { return FLastError; } + String GetErrorMessage(Svn::Errors err) { return ErrorMessages[err]; } + String GetErrorMessage(void) { return ErrorMessages[FLastError]; } + + String GetLastCommandOutput() { return FLastCommandOutput; } + String GetLastCommandError() { return FLastCommandError; } + + // sets user authentication + void SetUser(String const &UserName, String const &Password); + void SetUser(void); + + // connects to the local copy + Errors Connect(String const &LocalPath); + + // disconnects from local copy + Errors Disconnect(void); + + // checks out from server + Errors Checkout(String const &Url, String const &LocalPath, String const &Revision = ""); + + // updates current copy from server + Errors Update(String const &Revision = ""); + + // commits to server + Errors Commit(String const &Message); + + // adds file(s) to current repository + Errors Add(String const &FileName); + Errors Add(Array const &FileNames); + + // removes file(s) from current repository + Errors Delete(String const &FileName); + Errors Delete(Array const &FileNames); + + // copy a file inside current repository + Errors Copy(String const &Src, String const &Dest); + + // moves a file inside current repository + Errors Move(String const &Src, String const &Dest); + + // gets log from remote + Errors GetLog(Array&logArray); + + // gets local repository status + Errors GetStatus(Array&statusArray); + + // updates info from current repository + Errors UpdateInfo(void); + +}; // END Class Svn + +/* + blame + cat + cleanup + diff + export + import + list + lock + log + merge + mkdir + propdel (pdel, pd) + propedit (pedit, pe) + propget (pget, pg) + proplist (plist, pl) + propset (pset, ps) + resolved + revert + status (stat, st) + switch (sw) + unlock +*/ + +END_UPP_NAMESPACE + +#endif diff --git a/bazaar/SvnLib/SvnLib.upp b/bazaar/SvnLib/SvnLib.upp new file mode 100644 index 000000000..96582c2bd --- /dev/null +++ b/bazaar/SvnLib/SvnLib.upp @@ -0,0 +1,8 @@ +uses + CtrlLib, + SysExec; + +file + SvnLib.h, + SvnLib.cpp; + diff --git a/bazaar/SvnTest/SvnTest.h b/bazaar/SvnTest/SvnTest.h new file mode 100644 index 000000000..88afb14a5 --- /dev/null +++ b/bazaar/SvnTest/SvnTest.h @@ -0,0 +1,44 @@ +#ifndef _SvnTest_SvnTest_h +#define _SvnTest_SvnTest_h + +#include + +#include + +using namespace Upp; + +#define LAYOUTFILE +#include + +class SvnTest : public WithSvnTestLayout +{ + // test repository path and local path + String DestPath; + String RepositoryUrl; + + // svn object + Svn svn; + + // button handlers + void Checkout(void); + void Connect(void); + void Disconnect(void); + void Modify(void); + void GetLog(void); + void GetStatus(void); + void ListFiles(void); + void WipeFolder(void); + void Quit(void); + + // error messagebox + void ShowError(void); + void ShowError(String const &msg); + + public: + typedef SvnTest CLASSNAME; + + SvnTest(); +}; + +#endif + diff --git a/bazaar/SvnTest/SvnTest.lay b/bazaar/SvnTest/SvnTest.lay new file mode 100644 index 000000000..c0931a0ee --- /dev/null +++ b/bazaar/SvnTest/SvnTest.lay @@ -0,0 +1,13 @@ +LAYOUT(SvnTestLayout, 672, 396) + ITEM(DocEdit, TextArea, SetFont(CourierZ(10)).WantFocus(false).SetEditable(false).HSizePosZ(4, 4).VSizePosZ(4, 32)) + ITEM(Button, CheckoutButton, SetLabel(t_("Checkout")).LeftPosZ(16, 56).BottomPosZ(9, 15)) + ITEM(Button, QuitButton, SetLabel(t_("Quit")).LeftPosZ(604, 56).BottomPosZ(9, 15)) + ITEM(Button, ModifyButton, SetLabel(t_("Modify files")).LeftPosZ(216, 64).TopPosZ(372, 15)) + ITEM(Button, DisconnectButton, SetLabel(t_("Disconnect")).HSizePosZ(144, 464).TopPosZ(372, 15)) + ITEM(Button, StatusButton, SetLabel(t_("Get status")).HSizePosZ(352, 264).TopPosZ(372, 15)) + ITEM(Button, ListButton, SetLabel(t_("List files")).LeftPosZ(416, 56).TopPosZ(372, 15)) + ITEM(Button, WipeButton, SetLabel(t_("Wipe test folder")).LeftPosZ(508, 84).TopPosZ(372, 15)) + ITEM(Button, LogButton, SetLabel(t_("Get log")).LeftPosZ(288, 56).TopPosZ(372, 15)) + ITEM(Button, ConnectButton, SetLabel(t_("Connect")).HSizePosZ(80, 536).TopPosZ(372, 15)) +END_LAYOUT + diff --git a/bazaar/SvnTest/SvnTest.upp b/bazaar/SvnTest/SvnTest.upp new file mode 100644 index 000000000..071abb5d6 --- /dev/null +++ b/bazaar/SvnTest/SvnTest.upp @@ -0,0 +1,12 @@ +uses + CtrlLib, + SvnLib; + +file + SvnTest.h, + main.cpp, + SvnTest.lay; + +mainconfig + "" = "GUI"; + diff --git a/bazaar/SvnTest/main.cpp b/bazaar/SvnTest/main.cpp new file mode 100644 index 000000000..9bd28c726 --- /dev/null +++ b/bazaar/SvnTest/main.cpp @@ -0,0 +1,269 @@ +#include "SvnTest.h" + +/////////////////////////////////////////////////////////////////////////////////////////////// +// constructor +SvnTest::SvnTest() +{ + // initializes repository and local copy locations + DestPath = "/tmp/svn-test"; + RepositoryUrl = "https://upp.svn.sourceforge.net/svnroot/upp/trunk/linux_scripts"; + + // connects the layout + CtrlLayout(*this, "Window title"); + + // sets up handlers + CheckoutButton <<= THISBACK(Checkout); + ConnectButton <<= THISBACK(Connect); + DisconnectButton <<= THISBACK(Disconnect); + ModifyButton <<= THISBACK(Modify); + LogButton <<= THISBACK(GetLog); + StatusButton <<= THISBACK(GetStatus); + ListButton <<= THISBACK(ListFiles); + WipeButton <<= THISBACK(WipeFolder); + QuitButton <<= THISBACK(Quit); + +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +// handlers + +/////////////////////////////////////////////////////////////////////////////////////////////// +void SvnTest::Checkout(void) +{ + // clears text area + TextArea.Clear(); + + // sets up a welcome message + TextArea.Set(TextArea.Get() + "Checking out '" + RepositoryUrl + "' repository... please wait\n\n"); + TextArea.Sync(); + + // error if already connected + if(svn.IsConnected()) + { + ShowError("Already connected!!!"); + return; + } + + // connects to repository + if(svn.Checkout(RepositoryUrl, DestPath) == Svn::Ok) + { + StringStream s; + s << "Local path : " << svn.GetLocalPath() << "\n"; + s << "Repository root : " << svn.GetRepositoryRoot() << "\n"; + s << "Checked in revision : " << AsString(svn.GetCheckedRevision()) << "\n"; + s << "Author : " << svn.GetCheckedRevisionAuthor() << "\n"; + s << "Head revision : " << svn.GetHeadRevision() << "\n"; + s << "Revision Author : " << svn.GetHeadRevisionAuthor() << "\n"; + TextArea.Set(TextArea.Get() + s); + } + else + ShowError(); + +} // END SvnTest::Checkout() + +/////////////////////////////////////////////////////////////////////////////////////////////// +void SvnTest::Connect(void) +{ + // clears text area + TextArea.Clear(); + + // sets up a welcome message + TextArea.Set(TextArea.Get() + "Connecting to local repository '" + DestPath + "' ... please wait\n\n"); + TextArea.Sync(); + + // error if already connected + if(svn.IsConnected()) + { + ShowError("Already connected!!!"); + return; + } + + // connects to repository + if(svn.Connect(DestPath) == Svn::Ok) + { + StringStream s; + s << "Local path : " << svn.GetLocalPath() << "\n"; + s << "Repository root : " << svn.GetRepositoryRoot() << "\n"; + s << "Checked in revision : " << AsString(svn.GetCheckedRevision()) << "\n"; + s << "Author : " << svn.GetCheckedRevisionAuthor() << "\n"; + s << "Head revision : " << svn.GetHeadRevision() << "\n"; + s << "Revision Author : " << svn.GetHeadRevisionAuthor() << "\n"; + TextArea.Set(TextArea.Get() + s); + } + else + ShowError(); + +} // END SvnTest::Connect() + +/////////////////////////////////////////////////////////////////////////////////////////////// +void SvnTest::Disconnect(void) +{ + // clears text area + TextArea.Clear(); + + // sets up a welcome message + TextArea.Set(TextArea.Get() + "Disconnecting from local repository '" + DestPath + "' ... please wait\n\n"); + TextArea.Sync(); + + // disconnects from repository + if(svn.Disconnect() == Svn::Ok) + TextArea.Set(TextArea.Get() + "Success.\n"); + else + ShowError(); + +} // END SvnTest::Disconnect() + +/////////////////////////////////////////////////////////////////////////////////////////////// +void SvnTest::Modify(void) +{ + // changes readme file + String s = LoadFile(DestPath + "/README") + "\nA dummy change"; + SaveFile(DestPath + "/README", s); + + // removes dobeta file + FileDelete(DestPath + "/dobeta"); + + // adds an unversioned file + SaveFile(DestPath + "/UnversionedFile", s); + + // adds a versioned file + SaveFile(DestPath + "/VersionedFile", s); + svn.Add(DestPath + "/VersionedFile"); + +} // END SvnTest::Modify() + +/////////////////////////////////////////////////////////////////////////////////////////////// +void SvnTest::GetLog(void) +{ + // clears text area + TextArea.Clear(); + + // sets up a welcome message + TextArea.Set(TextArea.Get() + "Getting log from remote repository '" + RepositoryUrl + "' repository... please wait\n\n"); + TextArea.Sync(); + + // gets log from svn repository + TextArea.Set(TextArea.Get() + " ----- LOG OUTPUT -----\n"); + ArraylogArray; + if(svn.GetLog(logArray) == Svn::Ok) + { + StringStream s; + for(int i = 0; i< logArray.GetCount(); i++) + { + s << "Revision : " << logArray[i].revision << "\n"; + s << "Author : " << logArray[i].author << "\n"; + s << "Message : " << logArray[i].message << "\n"; + s << "Date : " << logArray[i].date << "\n"; + s << "-------------------------------------------------------------\n"; + } + TextArea.Set(TextArea.Get() + s); + } + else + ShowError(); + +} // END SvnTest::GetLog() + +/////////////////////////////////////////////////////////////////////////////////////////////// +void SvnTest::GetStatus(void) +{ + // clears text area + TextArea.Clear(); + + // sets up a welcome message + TextArea.Set(TextArea.Get() + "Getting status from local repository '" + DestPath + "' ... please wait\n\n"); + TextArea.Sync(); + + // gets repository status + TextArea.Set(TextArea.Get() + " ----- STATUS OUTPUT -----\n"); + ArraystatusArray; + if(svn.GetStatus(statusArray) == Svn::Ok) + { + StringStream s; + for(int i = 0; i< statusArray.GetCount(); i++) + { + s << "Path : " << statusArray[i].path << "\n"; + s << "Properties : " << statusArray[i].props << "\n"; + s << "Status : " << svn.Status2String(statusArray[i].status) << "\n"; + s << "Revision : " << statusArray[i].revision << "\n"; + s << "Commit Revision : " << statusArray[i].commitRevision << "\n"; + s << "Commit Author : " << statusArray[i].commitAuthor << "\n"; + s << "Commit Date : " << statusArray[i].commitDate << "\n"; + s << "-------------------------------------------------------------\n"; + } + TextArea.Set(TextArea.Get() + s); + } + else + ShowError(); + +} // END SvnTest::GetStatus() + + +/////////////////////////////////////////////////////////////////////////////////////////////// +void SvnTest::ListFiles(void) +{ + // clears text area + TextArea.Clear(); + + // sets up a welcome message + TextArea.Set(TextArea.Get() + "List of files in test folder:\n\n"); + TextArea.Sync(); + + StringStream s; + Array files = StdFileSystemInfo().Find(DestPath + "/*"); + for(int i = 0; i < files.GetCount(); i++) + s << files[i].filename << "\n"; + TextArea.Set(TextArea.Get() + s); + + +} // END SvnTest::ListFiles() + +/////////////////////////////////////////////////////////////////////////////////////////////// +void SvnTest::WipeFolder(void) +{ + // clears text area + TextArea.Clear(); + + // sets up a welcome message + TextArea.Set(TextArea.Get() + "Wiping test folder '" + DestPath + "'.....\n\n"); + TextArea.Sync(); + + DeleteFolderDeep(DestPath); + + TextArea.Set(TextArea.Get() + "Success.\n"); + +} // END SvnTest::WipeFolder() + +/////////////////////////////////////////////////////////////////////////////////////////////// +void SvnTest::Quit(void) +{ + Break(); + +} // END SvnTest::Quit() + +/////////////////////////////////////////////////////////////////////////////////////////////// +// error messagebox +void SvnTest::ShowError(void) +{ + StringStream s; + s << "ERROR : " << svn.GetErrorMessage(); + TextArea.Set(TextArea.Get() + s + "\n"); + TextArea.Sync(); + PromptOK(String(s)); + +} // END SvnTest::ShowError() + +void SvnTest::ShowError(String const &str) +{ + StringStream s; + s << "ERROR : " << str; + TextArea.Set(TextArea.Get() + s + "\n"); + TextArea.Sync(); + PromptOK(String(s)); + +} // END SvnTest::ShowError() + +GUI_APP_MAIN +{ + SvnTest().Run(); +} +