mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-15 14:16:07 -06:00
598 lines
15 KiB
C++
598 lines
15 KiB
C++
#include "ide.h"
|
|
|
|
String RepoCfgFile(const String& path)
|
|
{
|
|
return ConfigFile("cfg/repo-" + SHA1String(path));
|
|
}
|
|
|
|
String GetGitUrl(const String& repo_dir)
|
|
{
|
|
Vector<String> ln = Split(GitCmd(repo_dir, "config --get remote.origin.url"), CharFilterCrLf);
|
|
return ln.GetCount() ? ln[0] : String();
|
|
}
|
|
|
|
|
|
String GetSvnUrl(const String& repo_dir)
|
|
{
|
|
Vector<String> ln = Split(RepoSys("svn info --show-item repos-root-url " + repo_dir), CharFilterCrLf);
|
|
return ln.GetCount() ? ln[0] : String();
|
|
}
|
|
|
|
int UrepoConsole::Git(const char *dir, const char *command, bool pwd)
|
|
{
|
|
String h = GetCurrentDirectory();
|
|
SetCurrentDirectory(dir);
|
|
list.Add(AttrText(String("cd ") + dir).SetFont(font().Bold().Italic()).Ink(SLtBlue()));
|
|
String cmd = String() << "git " << command;
|
|
if(pwd) {
|
|
String url = GetGitUrl(dir);
|
|
String username, password;
|
|
if(url.StartsWith("https://") && GetCredentials(url, dir, username, password)) {
|
|
String https = "https://";
|
|
cmd << ' ' << https;
|
|
if(username.GetCount())
|
|
cmd << UrlEncode(username) + ":";
|
|
int p = cmd.GetCount();
|
|
cmd << UrlEncode(password);
|
|
HidePassword(p, cmd.GetCount());
|
|
cmd << "@" << url.Mid(https.GetCount());
|
|
}
|
|
}
|
|
int code = CheckSystem(cmd);
|
|
SetCurrentDirectory(h);
|
|
return code;
|
|
}
|
|
|
|
String RepoSync::SvnCmd(UrepoConsole& sys, const char *svncmd, const String& dir)
|
|
{
|
|
String cmd;
|
|
cmd << "svn " << svncmd << " --non-interactive ";
|
|
String username, password;
|
|
if(GetCredentials(GetSvnUrl(dir), dir, username, password)) {
|
|
cmd << "--username " << username << " --password ";
|
|
int i0 = cmd.GetCount();
|
|
cmd << password;
|
|
sys.HidePassword(i0, cmd.GetCount());
|
|
cmd << " ";
|
|
}
|
|
return cmd;
|
|
}
|
|
|
|
RepoSync::RepoSync()
|
|
{
|
|
CtrlLayoutOKCancel(*this, "Version control repository synchronize");
|
|
list.AddIndex();
|
|
list.AddIndex();
|
|
list.AddColumn("Action");
|
|
list.AddColumn("Path");
|
|
list.AddColumn("Changes");
|
|
list.ColumnWidths("220 500 100");
|
|
list.NoCursor().EvenRowColor();
|
|
list.SetLineCy(max(Draw::GetStdFontCy() + Zy(4), Zy(20)));
|
|
Sizeable().Zoomable();
|
|
BackPaint();
|
|
credentials << [=] {
|
|
Index<String> hint;
|
|
for(const auto& w : ~work) {
|
|
String path = w.key;
|
|
String s = decode(w.value, SVN_DIR, GetSvnUrl(path), GIT_DIR, GetGitUrl(path), Null);
|
|
if(s.GetCount())
|
|
hint.FindAdd(s);
|
|
if(path.GetCount())
|
|
hint.FindAdd(path);
|
|
}
|
|
EditCredentials(hint.PickKeys());
|
|
};
|
|
}
|
|
|
|
RepoSync::~RepoSync()
|
|
{
|
|
int repoi = 0;
|
|
for(int i = 0; i < list.GetCount(); i++) {
|
|
SvnOptions *svn = dynamic_cast<SvnOptions *>(list.GetCtrl(i, 0));
|
|
GitOptions *git = dynamic_cast<GitOptions *>(list.GetCtrl(i, 0));
|
|
if(svn || git) {
|
|
String s;
|
|
if(svn) {
|
|
if(svn->commit)
|
|
s << "commit;";
|
|
if(svn->update)
|
|
s << "update;";
|
|
}
|
|
if(git) {
|
|
if(git->pull)
|
|
s << "pull;";
|
|
if(git->commit)
|
|
s << "commit;";
|
|
if(git->push)
|
|
s << "push;";
|
|
}
|
|
SaveChangedFile(RepoCfgFile(work.GetKey(repoi++)), s);
|
|
}
|
|
}
|
|
}
|
|
|
|
int CharFilterSvnMsgRepo(int c)
|
|
{
|
|
return c >= 32 && c < 128 && c != '\"' ? c : 0;
|
|
}
|
|
|
|
bool IsConflictFile(String path)
|
|
{
|
|
String ext = GetFileExt(path);
|
|
if(*ext == '.') {
|
|
ext = ext.Mid(1);
|
|
if(findarg(ext, "mine", "theirs", "working") >= 0 || *ext == 'r' && IsDigit(ext[1])) {
|
|
for(int i = 0; i < 3; i++) {
|
|
int q = path.ReverseFind('.');
|
|
if(q < 0)
|
|
return false;
|
|
path.Trim(q);
|
|
if(FileExists(path))
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool RepoSync::ListSvn(const String& path)
|
|
{
|
|
Vector<String> ln = Split(RepoSys("svn status " + path), CharFilterCrLf);
|
|
bool actions = false;
|
|
for(int pass = 0; pass < 2; pass++)
|
|
for(int i = 0; i < ln.GetCount(); i++) {
|
|
String h = ln[i];
|
|
if(h.GetCount() > 7) {
|
|
String file = GetFullPath(TrimLeft(h.Mid(7)));
|
|
if(IsFullPath(file)) {
|
|
actions = true;
|
|
h.Trim(7);
|
|
bool simple = h.Mid(1, 6) == " ";
|
|
int action = simple ? String("MC?!~").Find(h[0]) : SVN_IGNORE;
|
|
if(h == " S ")
|
|
action = REPLACE;
|
|
String an;
|
|
Color color;
|
|
if(action == SVN_IGNORE) {
|
|
color = Black;
|
|
if(simple && h[0] == 'A') {
|
|
an = "svn add";
|
|
action = SVN_ACTION;
|
|
}
|
|
else
|
|
if(simple && h[0] == 'D') {
|
|
an = "svn delete";
|
|
action = SVN_ACTION;
|
|
}
|
|
else {
|
|
an = h.Mid(0, 7);
|
|
color = Gray;
|
|
}
|
|
}
|
|
else {
|
|
if(action == ADD && IsConflictFile(file)) {
|
|
action = DELETEC;
|
|
an = "Delete (conflict resolved)";
|
|
color = AdjustIfDark(Black());
|
|
}
|
|
else {
|
|
static const char *as[] = {
|
|
"Modify", "Resolved", "Add", "Remove", "Replace"
|
|
};
|
|
static Color c[] = { LtBlue, Magenta, Green, LtRed, LtMagenta };
|
|
an = as[action];
|
|
color = AdjustIfDark(c[action]);
|
|
}
|
|
}
|
|
if(pass == action < 0 && action != DELETEC) {
|
|
int ii = list.GetCount();
|
|
list.Add(action, file, Null,
|
|
AttrText(action < 0 ? ln[i] : " " + file.Mid(path.GetCount() + 1)).Ink(color));
|
|
if(action >= 0) {
|
|
list.SetCtrl(ii, 0, revert.Add().SetLabel(an + (action == ADD ? "\nSkip" : "\nRevert")).NoWantFocus());
|
|
revert.Top() <<= 0;
|
|
Ctrl& b = diff.Add().SetLabel("Changes..").SizePos().NoWantFocus();
|
|
b << [=] { DoDiff(ii); };
|
|
list.SetCtrl(ii, 2, b);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return actions;
|
|
}
|
|
|
|
String GitCmd(const char *dir, const char *command)
|
|
{
|
|
LOG("GitCmd " << dir << ", " << command);
|
|
String h = GetCurrentDirectory();
|
|
SetCurrentDirectory(dir);
|
|
String r = RepoSys(String() << "git " << command);
|
|
SetCurrentDirectory(h);
|
|
return r;
|
|
}
|
|
|
|
bool RepoSync::ListGit(const String& path)
|
|
{
|
|
Vector<String> ln = Split(GitCmd(path, "status --porcelain ."), CharFilterCrLf);
|
|
bool actions = false;
|
|
for(int i = 0; i < ln.GetCount(); i++) {
|
|
String h = ln[i];
|
|
if(h.GetCount() > 3) {
|
|
if(!h.TrimEnd("/"))
|
|
h.TrimEnd("\\");
|
|
String file = AppendFileName(path, h.Mid(3));
|
|
actions = true;
|
|
int action = String("M.?DR").Find(h[1]);
|
|
if(action < 0 || h[0] != '?' && h[0] != ' ')
|
|
action = SVN_IGNORE;
|
|
String an;
|
|
Color color;
|
|
if(action == SVN_IGNORE) {
|
|
an = h;
|
|
color = Gray;
|
|
}
|
|
else {
|
|
static const char *as[] = {
|
|
"Modify", "Resolved", "Add", "Remove", "Rename"
|
|
};
|
|
static Color c[] = { LtBlue, Magenta, Green, LtRed, LtMagenta };
|
|
an = as[action];
|
|
color = AdjustIfDark(c[action]);
|
|
}
|
|
int ii = list.GetCount();
|
|
list.Add(action, file, Null, AttrText(action < 0 ? h : file).Ink(color));
|
|
if(action >= 0) {
|
|
list.SetCtrl(ii, 0, revert.Add().SetLabel(an + (action == ADD ? "\nSkip" : "\nRevert")).NoWantFocus());
|
|
revert.Top() <<= 0;
|
|
Ctrl& b = diff.Add().SetLabel("Changes..").SizePos().NoWantFocus();
|
|
b <<= THISBACK1(DoDiff, ii);
|
|
list.SetCtrl(ii, 2, b);
|
|
}
|
|
}
|
|
}
|
|
return actions;
|
|
}
|
|
|
|
void RepoSync::SyncCommits()
|
|
{
|
|
bool commit = true;
|
|
for(int i = 0; i < list.GetCount(); i++) {
|
|
if(SvnOptions *o = dynamic_cast<SvnOptions *>(list.GetCtrl(i, 0)))
|
|
commit = o->commit.IsEnabled() && o->commit;
|
|
else
|
|
if(GitOptions *o = dynamic_cast<GitOptions *>(list.GetCtrl(i, 0)))
|
|
commit = o->commit.IsEnabled() && o->commit;
|
|
else {
|
|
for(int j = 0; j < 2; j++) {
|
|
Ctrl *ctrl = list.GetCtrl(i, j);
|
|
if(ctrl)
|
|
ctrl->Enable(commit);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RepoSync::SyncList()
|
|
{
|
|
list.Clear();
|
|
credentials.Show();
|
|
svndir.Clear();
|
|
for(const auto& w : ~work) {
|
|
String path = GetFullPath(w.key);
|
|
String cfg = LoadFile(RepoCfgFile(path));
|
|
auto Default = [&](const char *s) {
|
|
if(cfg.IsVoid())
|
|
return true;
|
|
return cfg.Find(s) >= 0;
|
|
};
|
|
int hi = list.GetCount();
|
|
Color bk = AdjustIfDark(LtYellow());
|
|
list.Add(REPOSITORY, path,
|
|
AttrText().Paper(bk),
|
|
AttrText(path).SetFont(ArialZ(20).Bold()).Paper(bk),
|
|
AttrText().Paper(bk));
|
|
list.SetLineCy(hi, Zy(26));
|
|
bool actions = false;
|
|
if(w.value == SVN_DIR) {
|
|
auto& o = list.CreateCtrl<SvnOptions>(hi, 0, false);
|
|
o.SizePos();
|
|
o.commit = Default("commit");
|
|
o.commit << [=] { SyncCommits(); };
|
|
o.update = Default("update");
|
|
actions = ListSvn(path);
|
|
if(!actions) {
|
|
o.commit.Disable();
|
|
}
|
|
credentials.Show();
|
|
svndir.FindAdd(GetSvnDir(w.key));
|
|
}
|
|
if(w.value == GIT_DIR) {
|
|
auto& o = list.CreateCtrl<GitOptions>(hi, 0, false);
|
|
o.SizePos();
|
|
o.commit = Default("commit");
|
|
o.commit << [=] { SyncCommits(); };
|
|
o.push = Default("push");
|
|
o.pull = Default("pull");
|
|
actions = ListGit(path);
|
|
if(!actions) {
|
|
o.push = false;
|
|
o.commit.Disable();
|
|
}
|
|
}
|
|
if(actions) {
|
|
list.Add(MESSAGE, Null, AttrText("Commit message:").SetFont(StdFont().Bold()));
|
|
list.SetLineCy(list.GetCount() - 1, (3 * EditField::GetStdHeight()) + 4);
|
|
list.SetCtrl(list.GetCount() - 1, 1, message.Add().SetFilter(CharFilterSvnMsgRepo).VSizePos(2, 2).HSizePos());
|
|
int q = msgmap.Find(w.key);
|
|
if(q >= 0) {
|
|
message.Top() <<= msgmap[q];
|
|
msgmap.Unlink(q);
|
|
}
|
|
}
|
|
else
|
|
list.Add(-1, Null, "", AttrText("Nothing to do").SetFont(StdFont().Italic()));
|
|
}
|
|
}
|
|
|
|
void RepoSync::DoDiff(int ii)
|
|
{
|
|
String f = list.Get(ii, 1);
|
|
if(!IsNull(f))
|
|
RunRepoDiff(f);
|
|
}
|
|
|
|
#ifdef PLATFORM_WIN32
|
|
void sRepoDeleteFolderDeep(const char *dir)
|
|
{
|
|
{
|
|
FindFile ff(AppendFileName(dir, "*.*"));
|
|
while(ff) {
|
|
String name = ff.GetName();
|
|
String p = AppendFileName(dir, name);
|
|
if(ff.IsFile()) {
|
|
SetFileAttributes(p, GetFileAttributes(p) & ~FILE_ATTRIBUTE_READONLY);
|
|
FileDelete(p);
|
|
}
|
|
else
|
|
if(ff.IsFolder())
|
|
sRepoDeleteFolderDeep(p);
|
|
ff.Next();
|
|
}
|
|
}
|
|
DirectoryDelete(dir);
|
|
}
|
|
#else
|
|
void sRepoDeleteFolderDeep(const char *path)
|
|
{
|
|
DeleteFolderDeep(path);
|
|
}
|
|
#endif
|
|
|
|
void RepoSvnDel(const char *path)
|
|
{
|
|
FindFile ff(AppendFileName(path, "*.*"));
|
|
while(ff) {
|
|
if(ff.IsFolder()) {
|
|
String dir = AppendFileName(path, ff.GetName());
|
|
if(ff.GetName() == ".svn")
|
|
sRepoDeleteFolderDeep(dir);
|
|
else
|
|
RepoSvnDel(dir);
|
|
}
|
|
ff.Next();
|
|
}
|
|
}
|
|
|
|
void RepoSync::Dir(const char *dir)
|
|
{
|
|
String d = dir;
|
|
int kind = GetRepo(d);
|
|
if(kind)
|
|
work.GetAdd(kind == GIT_DIR ? d : String(dir)) = kind;
|
|
}
|
|
|
|
void RepoMoveSvn(const String& path, const String& tp)
|
|
{
|
|
FindFile ff(AppendFileName(path, "*.*"));
|
|
while(ff) {
|
|
String nm = ff.GetName();
|
|
String s = AppendFileName(path, nm);
|
|
String t = AppendFileName(tp, nm);
|
|
if(ff.IsFolder()) {
|
|
if(nm == ".svn")
|
|
FileMove(s, t);
|
|
else
|
|
RepoMoveSvn(s, t);
|
|
}
|
|
ff.Next();
|
|
}
|
|
}
|
|
|
|
void RepoSync::DoSync()
|
|
{
|
|
SyncList();
|
|
msgmap.Sweep();
|
|
again:
|
|
Enable();
|
|
if(Run() != IDOK || list.GetCount() == 0) {
|
|
int repoi = 0;
|
|
for(int i = 0; i < list.GetCount(); i++)
|
|
if(list.Get(i, 0) == MESSAGE)
|
|
msgmap.GetAdd(work.GetKey(repoi++)) = list.Get(i, 3);
|
|
return;
|
|
}
|
|
Disable();
|
|
bool changes = false;
|
|
for(int i = 0; i < list.GetCount(); i++) {
|
|
int action = list.Get(i, 0);
|
|
if(action == MESSAGE) {
|
|
if(changes && IsNull(list.Get(i, 3)) && list.GetCtrl(i, 1)->IsEnabled()
|
|
&& !PromptYesNo("Commit message is empty.&Do you want to continue?"))
|
|
goto again;
|
|
changes = false;
|
|
}
|
|
else if(action != REPOSITORY && list.Get(i, 2) == 0)
|
|
changes = true;
|
|
}
|
|
UrepoConsole sys;
|
|
int repoi = 0;
|
|
int l = 0;
|
|
while(l < list.GetCount()) {
|
|
SvnOptions *svn = dynamic_cast<SvnOptions *>(list.GetCtrl(l, 0));
|
|
GitOptions *git = dynamic_cast<GitOptions *>(list.GetCtrl(l, 0));
|
|
String repo_dir = work.GetKey(repoi++);
|
|
String url;
|
|
if(git) {
|
|
url = GetGitUrl(repo_dir);
|
|
if(url.GetCount())
|
|
sys.Log("git origin url: " + url, Gray());
|
|
|
|
}
|
|
if(svn) {
|
|
url = GetSvnUrl(repo_dir);
|
|
if(url.GetCount())
|
|
sys.Log("svn repository url: " + url, Gray());
|
|
}
|
|
l++;
|
|
String message;
|
|
String filelist; // <-- list of files to update
|
|
if(git && git->pull)
|
|
if(sys.Git(repo_dir, "pull --ff-only", true)) {
|
|
while(l < list.GetCount()) {
|
|
int action = list.Get(l, 0);
|
|
if(action == REPOSITORY)
|
|
break;
|
|
if(action == MESSAGE)
|
|
msgmap.GetAdd(repo_dir) = list.Get(l, 3);
|
|
l++;
|
|
}
|
|
continue;
|
|
}
|
|
bool commit = false;
|
|
while(l < list.GetCount()) {
|
|
int action = list.Get(l, 0);
|
|
if(action == REPOSITORY)
|
|
break;
|
|
String path = list.Get(l, 1);
|
|
bool revert = list.Get(l, 2) == 1;
|
|
if(svn && svn->commit.IsEnabled() && svn->commit) {
|
|
if(action == MESSAGE && commit) {
|
|
String msg = list.Get(l, 3);
|
|
if(sys.CheckSystem(SvnCmd(sys, "commit", repo_dir) << filelist << " -m \"" << msg << "\""))
|
|
msgmap.GetAdd(repo_dir) = msg;
|
|
l++;
|
|
break;
|
|
}
|
|
|
|
if(SvnFile(sys, filelist, action, path, revert))
|
|
commit = true;
|
|
}
|
|
if(git && git->commit.IsEnabled() && git->commit) {
|
|
if(action == MESSAGE && commit) {
|
|
String msg = list.Get(l, 3);
|
|
if(sys.Git(repo_dir, "commit -a -m \"" << msg << "\""))
|
|
msgmap.GetAdd(repo_dir) = msg;
|
|
l++;
|
|
break;
|
|
}
|
|
|
|
if(GitFile(sys, action, path, revert))
|
|
commit = true;
|
|
}
|
|
l++;
|
|
}
|
|
if(svn && svn->update)
|
|
sys.CheckSystem(SvnCmd(sys, "update", repo_dir).Cat() << repo_dir);
|
|
if(git && git->push)
|
|
sys.Git(repo_dir, "push", true);
|
|
}
|
|
sys.Log("Done", Gray());
|
|
ResetBlitz();
|
|
sys.Perform();
|
|
}
|
|
|
|
bool RepoSync::GitFile(UrepoConsole& sys, int action, const String& path, bool revert)
|
|
{
|
|
String repo_dir = GetFileFolder(path);
|
|
String file = GetFileName(path);
|
|
if(revert) {
|
|
if(action != ADD)
|
|
sys.Git(repo_dir, "checkout \"" + file + "\"");
|
|
return false;
|
|
}
|
|
if(action == ADD)
|
|
sys.Git(repo_dir, "add \"" + file + "\"");
|
|
return true;
|
|
}
|
|
|
|
bool RepoSync::SvnFile(UrepoConsole& sys, String& filelist, int action, const String& path, bool revert)
|
|
{
|
|
if(revert) {
|
|
if(action == REPLACE)
|
|
DeleteFolderDeep(path);
|
|
if(action != ADD)
|
|
sys.CheckSystem("svn revert \"" + path + "\"");
|
|
return false;
|
|
}
|
|
if(action >= 0 || action == SVN_ACTION)
|
|
filelist << " \"" << path << "\""; // <-- add the file to the list
|
|
switch(action) {
|
|
case ADD:
|
|
RepoSvnDel(path);
|
|
sys.CheckSystem("svn add --force \"" + path + "\"");
|
|
break;
|
|
case REMOVE:
|
|
sys.CheckSystem("svn delete \"" + path + "\"");
|
|
break;
|
|
case CONFLICT:
|
|
sys.CheckSystem("svn resolved \"" + path + "\"");
|
|
break;
|
|
case REPLACE: {
|
|
RepoSvnDel(path);
|
|
String tp = AppendFileName(GetFileFolder(path), Format(Uuid::Create()));
|
|
FileMove(path, tp);
|
|
sys.CheckSystem(SvnCmd(sys, "update", path) << " \"" << path << "\"");
|
|
RepoMoveSvn(path, tp);
|
|
sRepoDeleteFolderDeep(path);
|
|
FileMove(tp, path);
|
|
Vector<String> ln = Split(RepoSys("svn status \"" + path + "\""), CharFilterCrLf);
|
|
for(int l = 0; l < ln.GetCount(); l++) {
|
|
String h = ln[l];
|
|
if(h.GetCount() > 7) {
|
|
String file = h.Mid(7);
|
|
if(IsFullPath(file)) {
|
|
h.Trim(7);
|
|
if(h == "? ")
|
|
sys.CheckSystem("svn add --force \"" + file + "\"");
|
|
if(h == "! ")
|
|
sys.CheckSystem("svn delete \"" + file + "\"");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case DELETEC:
|
|
FileDelete(path);
|
|
break;
|
|
}
|
|
return findarg(action, SVN_IGNORE, DELETEC) < 0;
|
|
}
|
|
|
|
void RepoSync::Serialize(Stream& s)
|
|
{
|
|
int version = 0;
|
|
s / version;
|
|
s % msgmap;
|
|
}
|
|
|
|
void RepoSync::SetMsgs(const String& s)
|
|
{
|
|
LoadFromString(*this, Garble(s));
|
|
}
|
|
|
|
String RepoSync::GetMsgs()
|
|
{
|
|
return Garble(StoreAsString(*this));
|
|
}
|