ultimatepp/bazaar/SvnLib/SvnLib.cpp
mdelfede ad3ced97b7 SVN Library -- first release + test app
git-svn-id: svn://ultimatepp.org/upp/trunk@299 f0d560ea-af0d-0410-9eb7-867de7ffcac7
2008-06-15 00:53:12 +00:00

869 lines
22 KiB
C++

#include <SysExec/SysExec.h>
#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<String, String> 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<LogInfo>&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<StatusInfo> &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<FileSystemInfo::FileInfo> 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<String> 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<String> 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<Svn::LogInfo>&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<Svn::StatusInfo>&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