diff --git a/Ide/LgiIdeProj.xml b/Ide/LgiIdeProj.xml
--- a/Ide/LgiIdeProj.xml
+++ b/Ide/LgiIdeProj.xml
@@ -1,279 +1,283 @@
- win\Makefile.windows
+ .\win\Makefile.windows
./linux/Makefile.linux
- ./MacCocoa/LgiIde.xcodeproj
- Makefile.haiku
+ ./mac/LgiIde.xcodeproj
+ ./haiku/Makefile.haiku
./lgiide
./lgiide
+
+ ./lgiide
+ ./lgiide
+
LgiIde.exe
LgiIde.exe
WINDOWS
WINNATIVE
WINDOWS
WINNATIVE
POSIX
POSIX
LIBPNG_VERSION=\"1.2\"
LIBPNG_VERSION=\"1.2\"
MingW
/home/matthew/Code/Lgi/trunk/Ide/Code/IdeProjectSettings.cpp
./src
./resources
../include
./src
./resources
../include
..\include\lgi\win
..\include\lgi\win
../include/lgi/linux
../include/lgi/linux/Gtk
../../../../codelib/openssl/include
../include/lgi/linux
../include/lgi/linux/Gtk
../../../../codelib/openssl/include
../include/lgi/haiku
../include/lgi/haiku
../include/lgi/mac/cocoa
../include/lgi/mac/cocoa
imm32
imm32
magic
pthread
`pkg-config --libs gtk+-3.0`
-static-libgcc
magic
pthread
`pkg-config --libs gtk+-3.0`
-static-libgcc
-static-libgcc
gnu
network
be
-static-libgcc
gnu
network
be
Executable
lgiide
lgiide
LgiIde.exe
LgiIde.exe
lgiide
lgiide
`pkg-config --cflags gtk+-3.0`
`pkg-config --cflags gtk+-3.0`
POSIX
_GNU_SOURCE
POSIX
_GNU_SOURCE
4
4
0
1
SOME_TEST=testing
/home/matthew/code/lgi/trunk/Lvc/LvcProject.xml
diff --git a/Lgi.xml b/Lgi.xml
--- a/Lgi.xml
+++ b/Lgi.xml
@@ -1,603 +1,604 @@
linux/Makefile.linux
win\Makefile.windows
- Makefile.macosx
+ mac/Makefile.mac
+ haiku/Makefile.haiku
gcc
0
./include
./private/common
./include
./private/common
./include/lgi/linux
./include/lgi/linux/Gtk
./private/linux
./include/lgi/linux
./include/lgi/linux/Gtk
./private/linux
./include/lgi/win
./private/win
./include/lgi/win
./private/win
./include/lgi/haiku
./private/haiku
./include/lgi/haiku
./private/haiku
/usr/include/libappindicator3-0.1
`pkg-config --cflags gtk+-3.0`
`pkg-config --cflags gstreamer-1.0`
/usr/include/libappindicator3-0.1
`pkg-config --cflags gtk+-3.0`
`pkg-config --cflags gstreamer-1.0`
magic
appindicator3
crypt
-static-libgcc
`pkg-config --libs gtk+-3.0`
magic
appindicator3
crypt
-static-libgcc
`pkg-config --libs gtk+-3.0`
-static-libgcc
gnu
network
be
-static-libgcc
gnu
network
be
lgi-gtk3
lgi-gtk3
DynamicLibrary
LGI_LIBRARY
LGI_LIBRARY
POSIX
_GNU_SOURCE
POSIX
_GNU_SOURCE
diff --git a/Lvc/src/SshConnection.cpp b/Lvc/src/SshConnection.cpp
--- a/Lvc/src/SshConnection.cpp
+++ b/Lvc/src/SshConnection.cpp
@@ -1,474 +1,474 @@
#include "lgi/common/Lgi.h"
#include "Lvc.h"
#include "SshConnection.h"
#define TIMEOUT_PROMPT 1000
#define PROFILE_WaitPrompt 0
#define PROFILE_OnEvent 0
#define DEBUG_SSH_LOGGING 0
#if DEBUG_SSH_LOGGING
#define SSH_LOG(...) d->sLog.Log(__VA_ARGS__)
#else
#define SSH_LOG(...)
#endif
//////////////////////////////////////////////////////////////////
SshConnection::SshConnection(LTextLog *log, const char *uri, const char *prompt) : LSsh(log), LEventTargetThread("SshConnection")
{
auto Wnd = log->GetWindow();
GuiHnd = Wnd->AddDispatch();
Prompt = prompt;
Host.Set(Uri = uri);
d = NULL;
LScriptArguments Args(NULL);
if (Wnd->CallMethod(METHOD_GetContext, Args))
{
if (Args.GetReturn()->Type == GV_VOID_PTR)
d = (AppPriv*) Args.GetReturn()->Value.Ptr;
}
}
bool SshConnection::DetectVcs(VcFolder *Fld)
{
LAutoPtr p(new LString(Fld->GetUri().sPath));
TypeNotify.Add(Fld);
return PostObject(GetHandle(), M_DETECT_VCS, p);
}
bool SshConnection::Command(VcFolder *Fld, LString Exe, LString Args, ParseFn Parser, ParseParams *Params)
{
if (!Fld || !Exe || !Parser)
return false;
LAutoPtr p(new SshParams(this));
p->f = Fld;
p->Exe = Exe;
p->Args = Args;
p->Parser = Parser;
p->Params = Params;
p->Path = Fld->GetUri().sPath;
return PostObject(GetHandle(), M_RUN_CMD, p);
}
LStream *SshConnection::GetConsole()
{
if (!Connected)
{
auto r = Open(Host.sHost, Host.sUser, Host.sPass, true);
Log->Print("Ssh: %s open: %i\n", Host.sHost.Get(), r);
}
if (Connected && !c)
{
c = CreateConsole();
WaitPrompt(c);
}
return c;
}
class ProgressListItem : public LListItem
{
int64_t v, maximum;
public:
ProgressListItem(int64_t mx = 100) : maximum(mx)
{
v = 0;
}
int64_t Value() { return v; }
void Value(int64_t val) { v = val; Update(); }
void OnPaint(LItem::ItemPaintCtx &Ctx)
{
auto pDC = Ctx.pDC;
pDC->Colour(Ctx.Back);
pDC->Rectangle(&Ctx);
auto Fnt = GetList()->GetFont();
LDisplayString ds(Fnt, LFormatSize(v));
Fnt->Transparent(true);
Fnt->Colour(Ctx.Fore, Ctx.Back);
ds.Draw(pDC, Ctx.x1 + 10, Ctx.y1 + ((Ctx.Y() - ds.Y()) >> 1));
pDC->Colour(LProgressView::cNormal);
int x1 = 120;
int prog = Ctx.X() - x1;
int x2 = (int) (v * prog / maximum);
pDC->Rectangle(Ctx.x1 + x1, Ctx.y1 + 1, Ctx.x1 + x1 + x2, Ctx.y2 - 1);
}
};
#if PROFILE_WaitPrompt
#define PROFILE(name) prof.Add(name)
#else
#define PROFILE(name)
#endif
LString LastLine(LString &input)
{
#define Ws(ch) ( ((ch) == '\r') || ((ch) == '\n') || ((ch) == '\b') )
char *e = input.Get() + input.Length();
while (e > input.Get() && Ws(e[-1]))
e--;
char *s = e;
while (s > input.Get() && !Ws(s[-1]))
s--;
return LString(s, e - s);
}
LString LastLine(LStringPipe &input)
{
#define Ws(ch) ( ((ch) == '\r') || ((ch) == '\n') || ((ch) == '\b') )
LString s, ln;
input.Iterate([&s, &ln](auto ptr, auto bytes)
{
s = LString((char*)ptr, bytes) + s;
auto end = s.Get() + s.Length();
for (auto p = end - 1; p >= s.Get(); p--)
{
if (Ws(*p))
{
while (p < end && (*p == 0 || Ws(*p)))
p++;
ln = p;
break;
}
}
return ln.Get() == NULL;
},
true);
// if (!ln.Get())
// ln = s;
LAssert(ln.Find("\n") < 0);
DeEscape(ln);
return ln;
}
bool SshConnection::WaitPrompt(LStream *con, LString *Data, const char *Debug)
{
LStringPipe out(4 << 10);
auto Ts = LCurrentTime();
auto LastReadTs = Ts;
ProgressListItem *Prog = NULL;
#if PROFILE_WaitPrompt
LProfile prof("WaitPrompt", 100);
#endif
size_t BytesRead = 0;
bool CheckLast = true;
while (!LSsh::Cancel->IsCancelled())
{
PROFILE("read");
auto buf = out.GetBuffer();
if (!buf.ptr)
{
LAssert(!"Alloc failed.");
LgiTrace("WaitPrompt.%s alloc failed.\n", Debug);
return false;
}
auto rd = con->Read(buf.ptr, buf.len);
if (rd < 0)
{
// Error case
if (Debug)
LgiTrace("WaitPrompt.%s rd=%i\n", Debug, rd);
return false;
}
if (rd > 0)
{
// Got some data... keep asking for more:
LString tmp((char*)buf.ptr, rd);
SSH_LOG("waitPrompt data:", rd, tmp);
BytesRead += rd;
buf.Commit(rd);
CheckLast = true;
LastReadTs = LCurrentTime();
continue;
}
if (LCurrentTime() - LastReadTs > 4000)
{
auto sz = out.GetSize();
SSH_LOG("waitPrompt out:", sz, out);
auto last = LastLine(out);
// Does the buffer end with a ':' on a line by itself?
// Various version control CLI's do that to pageinate data.
// Obviously we're not going to deal with that directly,
// but the developer will need to know that's happened.
if (out.GetSize() > 2)
{
auto last = LastLine(out);
if (last == ":")
return false;
}
}
if (!CheckLast)
{
// We've already checked the buffer for the prompt...
LSleep(10); // Don't use too much CPU
continue;
}
PROFILE("LastLine");
CheckLast = false;
auto last = LastLine(out);
- LgiTrace("last='%s'\n", last.Get());
+ // LgiTrace("last='%s'\n", last.Get());
PROFILE("matchstr");
auto result = MatchStr(Prompt, last);
SSH_LOG("waitPrompt result:", result, Prompt, last);
if (Debug)
{
LgiTrace("WaitPrompt.%s match='%s' with '%s' = %i\n", Debug, Prompt.Get(), last.Get(), result);
}
if (result)
{
if (Data)
{
PROFILE("data process");
auto response = out.NewLStr();
if (response)
{
DeEscape(response);
// Strip first line off the start.. it's the command...
// And the last line... it's the prompt
auto start = response.Get();
auto end = response.Get() + response.Length();
while (start < end && *start != '\n')
start++;
while (start < end && (*start == '\n' || *start == 0))
start++;
while (end > start && end[-1] != '\n')
end--;
Data->Set(start, end - start);
}
SSH_LOG("waitPrompt data:", *Data);
}
if (Debug)
LgiTrace("WaitPrompt.%s Prompt data=%i\n", Debug, Data?(int)Data->Length():0);
break;
}
auto Now = LCurrentTime();
if (Now - Ts >= TIMEOUT_PROMPT)
{
if (!Prog && d->Commits)
{
Prog = new ProgressListItem(1 << 20);
d->Commits->Insert(Prog);
}
if (Prog)
Prog->Value(out.GetSize());
Log->Print("...reading: %s\n", LFormatSize(BytesRead).Get());
BytesRead = 0;
Ts = Now;
}
}
DeleteObj(Prog);
return true;
}
bool SshConnection::HandleMsg(LMessage *m)
{
if (m->Msg() != M_RESPONSE)
return false;
LAutoPtr u((SshParams*)m->A());
if (!u || !u->c)
return false;
SshConnection &c = *u->c;
AppPriv *d = c.d;
if (!d)
return false;
if (u->Vcs) // Check the VCS type..
{
c.Types.Add(u->Path, u->Vcs);
for (auto f: c.TypeNotify)
{
if (d->Tree->HasItem(f))
f->OnVcsType(u->Output);
else
LgiTrace("%s:%i - Folder no longer in tree (recently deleted?).\n", _FL);
}
}
else
{
if (d && d->Tree->HasItem(u->f))
u->f->OnSshCmd(u);
else
LgiTrace("%s:%i - Folder no longer in tree (recently deleted?).\n", _FL);
}
return true;
}
LString PathFilter(LString s)
{
auto parts = s.SplitDelimit("/");
if
(
(parts[0].Equals("~") || parts[0].Equals("."))
&&
s(0) == '/'
)
{
return s(1, -1).Replace(" ", "\\ ");
}
return s.Replace(" ", "\\ ");
}
#if PROFILE_OnEvent
#define PROF(name) prof.Add(name)
#else
#define PROF(name)
#endif
LMessage::Result SshConnection::OnEvent(LMessage *Msg)
{
switch (Msg->Msg())
{
case M_DETECT_VCS:
{
LAutoPtr p;
if (!ReceiveA(p, Msg))
{
LAssert(!"Incorrect param.");
break;
}
LAutoPtr r(new SshParams(this));
LString ls, out;
LString::Array lines;
VersionCtrl Vcs = VcNone;
LString path = PathFilter(*p);
LStream *con = GetConsole();
if (!con)
{
r->Output = "Error: Failed to get console.";
r->Vcs = VcError;
}
else
{
ls.Printf("find %s -maxdepth 1 -printf \"%%f\n\"\n", path.Get());
SSH_LOG("detectVcs:", ls);
con->Write(ls, ls.Length());
auto pr = WaitPrompt(con, &out);
lines = out.SplitDelimit("\r\n");
for (auto ln: lines)
{
if (ln.Equals(".svn"))
Vcs = VcSvn;
else if (ln.Equals("CVS"))
Vcs = VcCvs;
else if (ln.Equals(".hg"))
Vcs = VcHg;
else if (ln.Equals(".git"))
Vcs = VcGit;
}
}
r->Path = *p;
printf("r->Output=%s\n", r->Output.Get());
if (Vcs == VcError)
;
else if (Vcs != VcNone)
{
r->Vcs = Vcs;
r->ExitCode = 0;
}
else
{
r->Vcs = VcError;
r->Output.Printf("Error: no VCS detected.\n%s\n%s",
ls.Get(),
lines.Length() ? lines.Last().Get() : "#nodata");
}
PostObject(GuiHnd, M_RESPONSE, r);
break;
}
case M_RUN_CMD:
{
- #if PROFILE_OnEvent
- LProfile prof("OnEvent");
- #endif
+#if PROFILE_OnEvent
+LProfile prof("OnEvent");
+#endif
LAutoPtr p;
if (!ReceiveA(p, Msg))
break;
- PROF("get console");
+PROF("get console");
LString path = PathFilter(p->Path);
LStream *con = GetConsole();
if (!con)
break;
auto Debug = p->Params && p->Params->Debug;
- PROF("cd");
+PROF("cd");
LString cmd;
cmd.Printf("cd %s\n", path.Get());
SSH_LOG(">>>> cd:", path);
auto wr = con->Write(cmd, cmd.Length());
- PROF("cd wait");
+PROF("cd wait");
auto pr = WaitPrompt(con, NULL, Debug?"Cd":NULL);
- PROF("cmd");
+PROF("cmd");
cmd.Printf("%s %s\n", p->Exe.Get(), p->Args.Get());
SSH_LOG(">>>> cmd:", cmd);
if (Log)
Log->Print("%s", cmd.Get());
wr = con->Write(cmd, cmd.Length());
- PROF("cmd wait");
+PROF("cmd wait");
pr = WaitPrompt(con, &p->Output, Debug?"Cmd":NULL);
- PROF("result");
+PROF("result");
LString result;
cmd = "echo $?\n";
SSH_LOG(">>>> result:", cmd);
wr = con->Write(cmd, cmd.Length());
- PROF("result wait");
+PROF("result wait");
pr = WaitPrompt(con, &result, Debug?"Echo":NULL);
if (pr)
{
p->ExitCode = (int)result.Int();
if (Log)
Log->Print("... result=%i\n", p->ExitCode);
}
else if (Log)
Log->Print("... result=failed\n");
PostObject(GuiHnd, M_RESPONSE, p);
break;
}
default:
{
LAssert(!"Unhandled msg.");
break;
}
}
return 0;
}
diff --git a/Lvc/src/VcFolder.cpp b/Lvc/src/VcFolder.cpp
--- a/Lvc/src/VcFolder.cpp
+++ b/Lvc/src/VcFolder.cpp
@@ -1,4575 +1,4592 @@
#include "Lvc.h"
#include "lgi/common/Combo.h"
#include "lgi/common/ClipBoard.h"
#include "lgi/common/Json.h"
#include "lgi/common/ProgressDlg.h"
#include "resdefs.h"
#ifndef CALL_MEMBER_FN
#define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember))
#endif
#define MAX_AUTO_RESIZE_ITEMS 2000
#define PROFILE_FN 0
#if PROFILE_FN
#define PROF(s) Prof.Add(s)
#else
#define PROF(s)
#endif
class TmpFile : public LFile
{
int Status;
LString Hint;
public:
TmpFile(const char *hint = NULL)
{
Status = 0;
if (hint)
Hint = hint;
else
Hint = "_lvc";
}
LFile &Create()
{
LFile::Path p(LSP_TEMP);
p += Hint;
do
{
char s[256];
sprintf_s(s, sizeof(s), "../%s%i.tmp", Hint.Get(), LRand());
p += s;
}
while (p.Exists());
Status = LFile::Open(p.GetFull(), O_READWRITE);
return *this;
}
};
bool TerminalAt(LString Path)
{
#if defined(MAC)
return LExecute("/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal", Path);
#elif defined(WINDOWS)
TCHAR w[MAX_PATH_LEN];
auto r = GetWindowsDirectory(w, CountOf(w));
if (r > 0)
{
LFile::Path p = LString(w);
p += "system32\\cmd.exe";
FileDev->SetCurrentFolder(Path);
return LExecute(p);
}
#elif defined(LINUX)
LExecute("gnome-terminal", NULL, Path);
#endif
return false;
}
int Ver2Int(LString v)
{
auto p = v.Split(".");
int i = 0;
for (auto s : p)
{
auto Int = s.Int();
if (Int < 256)
{
i <<= 8;
i |= (uint8_t)Int;
}
else
{
LAssert(0);
return 0;
}
}
return i;
}
int ToolVersion[VcMax] = {0};
#define DEBUG_READER_THREAD 0
#if DEBUG_READER_THREAD
#define LOG_READER(...) printf(__VA_ARGS__)
#else
#define LOG_READER(...)
#endif
ReaderThread::ReaderThread(VersionCtrl vcs, LAutoPtr p, LStream *out) : LThread("ReaderThread")
{
Vcs = vcs;
Process = p;
Out = out;
Result = -1;
FilterCount = 0;
// We don't start this thread immediately... because the number of threads is scaled to the system
// resources, particularly CPU cores.
}
ReaderThread::~ReaderThread()
{
Out = NULL;
while (!IsExited())
LSleep(1);
}
const char *HgFilter = "We\'re removing Mercurial support";
const char *CvsKill = "No such file or directory";
int ReaderThread::OnLine(char *s, ssize_t len)
{
switch (Vcs)
{
case VcHg:
{
if (strnistr(s, HgFilter, len))
FilterCount = 4;
if (FilterCount > 0)
{
FilterCount--;
return 0;
}
else if (LString(s, len).Strip().Equals("remote:"))
{
return 0;
}
break;
}
case VcCvs:
{
if (strnistr(s, CvsKill, len))
return -1;
break;
}
default:
break;
}
return 1;
}
bool ReaderThread::OnData(char *Buf, ssize_t &r)
{
LOG_READER("OnData %i\n", (int)r);
#if 1
char *Start = Buf;
for (char *c = Buf; c < Buf + r;)
{
bool nl = *c == '\n';
c++;
if (nl)
{
int Result = OnLine(Start, c - Start);
if (Result < 0)
{
// Kill process and exit thread.
Process->Kill();
return false;
}
if (Result == 0)
{
ssize_t LineLen = c - Start;
ssize_t NextLine = c - Buf;
ssize_t Remain = r - NextLine;
if (Remain > 0)
memmove(Start, Buf + NextLine, Remain);
r -= LineLen;
c = Start;
}
else Start = c;
}
}
#endif
Out->Write(Buf, r);
return true;
}
int ReaderThread::Main()
{
bool b = Process->Start(true, false);
if (!b)
{
LString s("Process->Start failed.\n");
Out->Write(s.Get(), s.Length(), ErrSubProcessFailed);
return ErrSubProcessFailed;
}
char Buf[1024];
ssize_t r;
LOG_READER("%s:%i - starting reader loop, pid=%i\n", _FL, Process->Handle());
while (Process->IsRunning())
{
if (Out)
{
LOG_READER("%s:%i - starting read.\n", _FL);
r = Process->Read(Buf, sizeof(Buf));
LOG_READER("%s:%i - read=%i.\n", _FL, (int)r);
if (r > 0)
{
if (!OnData(Buf, r))
return -1;
}
}
else
{
Process->Kill();
return -1;
break;
}
}
LOG_READER("%s:%i - process loop done.\n", _FL);
if (Out)
{
while ((r = Process->Read(Buf, sizeof(Buf))) > 0)
OnData(Buf, r);
}
LOG_READER("%s:%i - loop done.\n", _FL);
Result = (int) Process->GetExitValue();
#if _DEBUG
if (Result)
printf("%s:%i - Process err: %i 0x%x\n", _FL, Result, Result);
#endif
return Result;
}
/////////////////////////////////////////////////////////////////////////////////////////////
int VcFolder::CmdMaxThreads = 0;
int VcFolder::CmdActiveThreads = 0;
void VcFolder::Init(AppPriv *priv)
{
if (!CmdMaxThreads)
CmdMaxThreads = LAppInst->GetCpuCount();
d = priv;
IsCommit = false;
IsLogging = false;
IsUpdate = false;
IsFilesCmd = false;
CommitListDirty = false;
IsUpdatingCounts = false;
IsBranches = StatusNone;
IsIdent = StatusNone;
Unpushed = Unpulled = -1;
Type = VcNone;
CmdErrors = 0;
CurrentCommitIdx = -1;
Expanded(false);
Insert(Tmp = new LTreeItem);
Tmp->SetText("Loading...");
LAssert(d != NULL);
}
VcFolder::VcFolder(AppPriv *priv, const char *uri)
{
Init(priv);
Uri.Set(uri);
GetType();
}
VcFolder::VcFolder(AppPriv *priv, LXmlTag *t)
{
Init(priv);
Serialize(t, false);
}
VcFolder::~VcFolder()
{
Log.DeleteObjects();
}
VersionCtrl VcFolder::GetType()
{
if (Type == VcNone)
Type = d->DetectVcs(this);
return Type;
}
const char *VcFolder::LocalPath()
{
if (!Uri.IsProtocol("file") || Uri.sPath.IsEmpty())
{
LAssert(!"Shouldn't call this if not a file path.");
return NULL;
}
auto c = Uri.sPath.Get();
#ifdef WINDOWS
if (*c == '/')
c++;
#endif
return c;
}
const char *VcFolder::GetText(int Col)
{
switch (Col)
{
case 0:
{
if (Uri.IsFile())
Cache = LocalPath();
else
Cache.Printf("%s%s", Uri.sHost.Get(), Uri.sPath.Get());
if (Cmds.Length())
Cache += " (...)";
return Cache;
}
case 1:
{
CountCache.Printf("%i/%i", Unpulled, Unpushed);
CountCache = CountCache.Replace("-1", "--");
return CountCache;
}
}
return NULL;
}
bool VcFolder::Serialize(LXmlTag *t, bool Write)
{
if (Write)
t->SetContent(Uri.ToString());
else
{
LString s = t->GetContent();
bool isUri = s.Find("://") >= 0;
if (isUri)
Uri.Set(s);
else
Uri.SetFile(s);
}
return true;
}
LXmlTag *VcFolder::Save()
{
LXmlTag *t = new LXmlTag(OPT_Folder);
if (t)
Serialize(t, true);
return t;
}
const char *VcFolder::GetVcName()
{
const char *Def = NULL;
switch (GetType())
{
case VcGit:
Def = "git";
break;
case VcSvn:
Def = "svn";
break;
case VcHg:
Def = "hg";
break;
case VcCvs:
Def = "cvs";
break;
default:
break;
}
if (!VcCmd)
{
LString Opt;
Opt.Printf("%s-path", Def);
LVariant v;
if (d->Opts.GetValue(Opt, v))
VcCmd = v.Str();
}
if (!VcCmd)
VcCmd = Def;
return VcCmd;
}
+char VcFolder::GetPathSep()
+{
+ if (Uri.IsFile())
+ return DIR_CHAR;
+
+ return '/'; // FIXME: Assumption is that the remote system is unix based.
+}
bool VcFolder::RunCmd(const char *Args, LoggingType Logging, std::function Callback)
{
Result Ret;
Ret.Code = -1;
const char *Exe = GetVcName();
if (!Exe || CmdErrors > 2)
return false;
if (Uri.IsFile())
{
new ProcessCallback(Exe,
Args,
LocalPath(),
Logging == LogNone ? d->Log : NULL,
GetTree()->GetWindow(),
Callback);
}
else
{
LAssert(!"Impl me.");
return false;
}
return true;
}
bool VcFolder::StartCmd(const char *Args, ParseFn Parser, ParseParams *Params, LoggingType Logging)
{
const char *Exe = GetVcName();
if (!Exe)
return false;
if (CmdErrors > 2)
return false;
if (Uri.IsFile())
{
if (d->Log && Logging != LogSilo)
d->Log->Print("%s %s\n", Exe, Args);
LAutoPtr Process(new LSubProcess(Exe, Args));
if (!Process)
return false;
Process->SetInitFolder(Params && Params->AltInitPath ? Params->AltInitPath.Get() : LocalPath());
#ifdef MAC
// Mac GUI apps don't share the terminal path, so this overrides that and make it work
auto Path = LGetPath();
if (Path.Length())
{
LString Tmp = LString(LGI_PATH_SEPARATOR).Join(Path);
Process->SetEnvironment("PATH", Tmp);
}
#endif
LString::Array Ctx;
Ctx.SetFixedLength(false);
Ctx.Add(LocalPath());
Ctx.Add(Exe);
Ctx.Add(Args);
LAutoPtr c(new Cmd(Ctx, Logging, d->Log));
if (!c)
return false;
c->PostOp = Parser;
c->Params.Reset(Params);
c->Rd.Reset(new ReaderThread(GetType(), Process, c));
Cmds.Add(c.Release());
}
else
{
auto c = d->GetConnection(Uri.ToString());
if (!c)
return false;
if (!c->Command(this, Exe, Args, Parser, Params))
return false;
}
Update();
return true;
}
int LogDateCmp(LListItem *a, LListItem *b, NativeInt Data)
{
VcCommit *A = dynamic_cast(a);
VcCommit *B = dynamic_cast(b);
if ((A != NULL) ^ (B != NULL))
{
// This handles keeping the "working folder" list item at the top
return (A != NULL) - (B != NULL);
}
// Sort the by date from most recent to least
return -A->GetTs().Compare(&B->GetTs());
}
void VcFolder::AddGitName(LString Hash, LString Name)
{
LString Existing = GitNames.Find(Hash);
if (Existing)
GitNames.Add(Hash, Existing + "," + Name);
else
GitNames.Add(Hash, Name);
}
LString VcFolder::GetGitNames(LString Hash)
{
LString Short = Hash(0, 11);
return GitNames.Find(Short);
}
bool VcFolder::ParseBranches(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcGit:
{
LString::Array a = s.SplitDelimit("\r\n");
for (auto &l: a)
{
LString::Array c = l.SplitDelimit(" \t");
if (c[0].Equals("*"))
{
CurrentBranch = c[1];
AddGitName(c[2], CurrentBranch);
Branches.Add(CurrentBranch, new VcBranch(CurrentBranch, c[2]));
}
else
{
AddGitName(c[1], c[0]);
Branches.Add(c[0], new VcBranch(c[0], c[1]));
}
}
break;
}
case VcHg:
{
auto a = s.SplitDelimit("\r\n");
Branches.DeleteObjects();
for (auto b: a)
{
if (!CurrentBranch)
CurrentBranch = b;
Branches.Add(b, new VcBranch(b));
}
if (Params && Params->Str.Equals("CountToTip"))
CountToTip();
break;
}
default:
{
break;
}
}
IsBranches = Result ? StatusError : StatusNone;
OnBranchesChange();
return false;
}
void VcFolder::GetRemoteUrl(std::function Callback)
{
LAutoPtr p(new ParseParams);
p->Callback = Callback;
switch (GetType())
{
case VcGit:
{
StartCmd("config --get remote.origin.url", NULL, p.Release());
break;
}
case VcSvn:
{
StartCmd("info --show-item=url", NULL, p.Release());
break;
}
case VcHg:
{
StartCmd("paths default", NULL, p.Release());
break;
}
default:
break;
}
}
void VcFolder::OnBranchesChange()
{
auto *w = d->Tree->GetWindow();
if (!w || !LTreeItem::Select())
return;
if (Branches.Length())
{
// Set the colours up
LString Default;
for (auto b: Branches)
{
if (!stricmp(b.key, "default") ||
!stricmp(b.key, "trunk"))
Default = b.key;
/*
else
printf("Other=%s\n", b.key);
*/
}
int Idx = 1;
for (auto b: Branches)
{
if (!b.value->Colour.IsValid())
{
if (Default && !stricmp(b.key, Default))
b.value->Colour = GetPaletteColour(0);
else
b.value->Colour = GetPaletteColour(Idx++);
}
}
}
DropDownBtn *dd;
if (w->GetViewById(IDC_BRANCH_DROPDOWN, dd))
{
LString::Array a;
for (auto b: Branches)
a.Add(b.key);
dd->SetList(IDC_BRANCH, a);
}
LViewI *b;
if (Branches.Length() > 0 &&
w->GetViewById(IDC_BRANCH, b))
{
if (CurrentBranch)
{
b->Name(CurrentBranch);
}
else
{
auto it = Branches.begin();
if (it != Branches.end())
b->Name((*it).key);
}
}
}
void VcFolder::DefaultFields()
{
if (Fields.Length() == 0)
{
switch (GetType())
{
case VcHg:
{
Fields.Add(LGraph);
Fields.Add(LIndex);
Fields.Add(LRevision);
Fields.Add(LBranch);
Fields.Add(LAuthor);
Fields.Add(LTimeStamp);
Fields.Add(LMessageTxt);
break;
}
case VcGit:
{
Fields.Add(LGraph);
Fields.Add(LRevision);
Fields.Add(LBranch);
Fields.Add(LAuthor);
Fields.Add(LTimeStamp);
Fields.Add(LMessageTxt);
break;
}
default:
{
Fields.Add(LGraph);
Fields.Add(LRevision);
Fields.Add(LAuthor);
Fields.Add(LTimeStamp);
Fields.Add(LMessageTxt);
break;
}
}
}
}
void VcFolder::UpdateColumns()
{
d->Commits->EmptyColumns();
for (auto c: Fields)
{
switch (c)
{
case LGraph: d->Commits->AddColumn("---", 60); break;
case LIndex: d->Commits->AddColumn("Index", 60); break;
case LBranch: d->Commits->AddColumn("Branch", 60); break;
case LRevision: d->Commits->AddColumn("Revision", 60); break;
case LAuthor: d->Commits->AddColumn("Author", 240); break;
case LTimeStamp: d->Commits->AddColumn("Date", 130); break;
case LMessageTxt: d->Commits->AddColumn("Message", 400); break;
default: LAssert(0); break;
}
}
}
void VcFolder::FilterCurrentFiles()
{
LArray All;
d->Files->GetAll(All);
// Update the display property
for (auto i: All)
{
auto fn = i->GetText(COL_FILENAME);
bool vis = !d->FileFilter || Stristr(fn, d->FileFilter.Get());
i->GetCss(true)->Display(vis ? LCss::DispBlock : LCss::DispNone);
// LgiTrace("Filter '%s' by '%s' = %i\n", fn, d->FileFilter.Get(), vis);
}
d->Files->Sort(0);
d->Files->UpdateAllItems();
d->Files->ResizeColumnsToContent();
}
void VcFolder::Select(bool b)
{
#if PROFILE_FN
LProfile Prof("Select");
#endif
if (!b)
{
auto *w = d->Tree->GetWindow();
w->SetCtrlName(IDC_BRANCH, NULL);
}
PROF("Parent.Select");
LTreeItem::Select(b);
if (b)
{
if (Uri.IsFile() && !LDirExists(LocalPath()))
return;
PROF("DefaultFields");
DefaultFields();
PROF("Type Change");
if (GetType() != d->PrevType)
{
d->PrevType = GetType();
UpdateColumns();
}
PROF("UpdateCommitList");
if ((Log.Length() == 0 || CommitListDirty) && !IsLogging)
{
switch (GetType())
{
case VcGit:
{
LVariant Limit;
d->Opts.GetValue("git-limit", Limit);
LString cmd = "rev-list --all --header --timestamp --author-date-order", s;
if (Limit.CastInt32() > 0)
{
s.Printf(" -n %i", Limit.CastInt32());
cmd += s;
}
IsLogging = StartCmd(cmd, &VcFolder::ParseRevList);
break;
}
case VcSvn:
{
LVariant Limit;
d->Opts.GetValue("svn-limit", Limit);
if (CommitListDirty)
{
IsLogging = StartCmd("up", &VcFolder::ParsePull, new ParseParams("log"));
break;
}
LString s;
if (Limit.CastInt32() > 0)
s.Printf("log --limit %i", Limit.CastInt32());
else
s = "log";
IsLogging = StartCmd(s, &VcFolder::ParseLog);
break;
}
case VcHg:
{
IsLogging = StartCmd("log", &VcFolder::ParseLog);
StartCmd("resolve -l", &VcFolder::ParseResolveList);
break;
}
case VcPending:
{
break;
}
default:
{
IsLogging = StartCmd("log", &VcFolder::ParseLog);
break;
}
}
CommitListDirty = false;
}
PROF("GetBranches");
if (GetBranches())
OnBranchesChange();
if (d->CurFolder != this)
{
PROF("RemoveAll");
d->CurFolder = this;
d->Commits->RemoveAll();
}
PROF("Uncommit");
if (!Uncommit)
Uncommit.Reset(new UncommitedItem(d));
d->Commits->Insert(Uncommit, 0);
PROF("Log Loop");
int64 CurRev = Atoi(CurrentCommit.Get());
List Ls;
for (auto l: Log)
{
if (CurrentCommit &&
l->GetRev())
{
switch (GetType())
{
case VcSvn:
{
int64 LogRev = Atoi(l->GetRev());
if (CurRev >= 0 && CurRev >= LogRev)
{
CurRev = -1;
l->SetCurrent(true);
}
else
{
l->SetCurrent(false);
}
break;
}
default:
l->SetCurrent(!_stricmp(CurrentCommit, l->GetRev()));
break;
}
}
bool Add = !d->CommitFilter;
if (d->CommitFilter)
{
const char *s = l->GetRev();
if (s && strstr(s, d->CommitFilter) != NULL)
Add = true;
s = l->GetAuthor();
if (s && stristr(s, d->CommitFilter) != NULL)
Add = true;
s = l->GetMsg();
if (s && stristr(s, d->CommitFilter) != NULL)
Add = true;
}
LList *CurOwner = l->GetList();
if (Add ^ (CurOwner != NULL))
{
if (Add)
Ls.Insert(l);
else
d->Commits->Remove(l);
}
}
PROF("Ls Ins");
d->Commits->Insert(Ls);
if (d->Resort >= 0)
{
PROF("Resort");
d->Commits->Sort(LstCmp, d->Resort);
d->Resort = -1;
}
PROF("ColSizing");
if (d->Commits->Length() > MAX_AUTO_RESIZE_ITEMS)
{
int i = 0;
if (GetType() == VcHg && d->Commits->GetColumns() >= 7)
{
d->Commits->ColumnAt(i++)->Width(60); // LGraph
d->Commits->ColumnAt(i++)->Width(40); // LIndex
d->Commits->ColumnAt(i++)->Width(100); // LRevision
d->Commits->ColumnAt(i++)->Width(60); // LBranch
d->Commits->ColumnAt(i++)->Width(240); // LAuthor
d->Commits->ColumnAt(i++)->Width(130); // LTimeStamp
d->Commits->ColumnAt(i++)->Width(400); // LMessage
}
else if (d->Commits->GetColumns() >= 5)
{
d->Commits->ColumnAt(i++)->Width(40); // LGraph
d->Commits->ColumnAt(i++)->Width(270); // LRevision
d->Commits->ColumnAt(i++)->Width(240); // LAuthor
d->Commits->ColumnAt(i++)->Width(130); // LTimeStamp
d->Commits->ColumnAt(i++)->Width(400); // LMessage
}
}
else d->Commits->ResizeColumnsToContent();
PROF("UpdateAll");
d->Commits->UpdateAllItems();
PROF("GetCur");
GetCurrentRevision();
}
}
int CommitRevCmp(VcCommit **a, VcCommit **b)
{
int64 arev = Atoi((*a)->GetRev());
int64 brev = Atoi((*b)->GetRev());
int64 diff = (int64)brev - arev;
if (diff < 0) return -1;
return (diff > 0) ? 1 : 0;
}
int CommitIndexCmp(VcCommit **a, VcCommit **b)
{
auto ai = (*a)->GetIndex();
auto bi = (*b)->GetIndex();
auto diff = (int64)bi - ai;
if (diff < 0) return -1;
return (diff > 0) ? 1 : 0;
}
int CommitDateCmp(VcCommit **a, VcCommit **b)
{
uint64 ats, bts;
(*a)->GetTs().Get(ats);
(*b)->GetTs().Get(bts);
int64 diff = (int64)bts - ats;
if (diff < 0) return -1;
return (diff > 0) ? 1 : 0;
}
void VcFolder::GetCurrentRevision(ParseParams *Params)
{
if (CurrentCommit || IsIdent != StatusNone)
return;
switch (GetType())
{
case VcGit:
if (StartCmd("rev-parse HEAD", &VcFolder::ParseInfo, Params))
IsIdent = StatusActive;
break;
case VcSvn:
if (StartCmd("info", &VcFolder::ParseInfo, Params))
IsIdent = StatusActive;
break;
case VcHg:
if (StartCmd("id -i -n", &VcFolder::ParseInfo, Params))
IsIdent = StatusActive;
break;
case VcCvs:
break;
default:
break;
}
}
bool VcFolder::GetBranches(ParseParams *Params)
{
if (Branches.Length() > 0 || IsBranches != StatusNone)
return true;
switch (GetType())
{
case VcGit:
if (StartCmd("-P branch -a -v", &VcFolder::ParseBranches, Params))
IsBranches = StatusActive;
break;
case VcSvn:
Branches.Add("trunk", new VcBranch("trunk"));
OnBranchesChange();
break;
case VcHg:
if (StartCmd("branch", &VcFolder::ParseBranches, Params))
IsBranches = StatusActive;
break;
case VcCvs:
break;
default:
break;
}
return false;
}
bool VcFolder::ParseRevList(int Result, LString s, ParseParams *Params)
{
Log.DeleteObjects();
int Errors = 0;
switch (GetType())
{
case VcGit:
{
LString::Array Commits;
Commits.SetFixedLength(false);
// Split on the NULL chars...
char *c = s.Get();
char *e = c + s.Length();
while (c < e)
{
char *nul = c;
while (nul < e && *nul) nul++;
if (nul <= c) break;
Commits.New().Set(c, nul-c);
if (nul >= e) break;
c = nul + 1;
}
for (auto Commit: Commits)
{
LAutoPtr Rev(new VcCommit(d, this));
if (Rev->GitParse(Commit, true))
{
Log.Add(Rev.Release());
}
else
{
LAssert(!"Parse failed.");
LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, Commit.Get());
Errors++;
}
}
LinkParents();
break;
}
default:
LAssert(!"Impl me.");
break;
}
IsLogging = false;
return Errors == 0;
}
LString VcFolder::GetFilePart(const char *uri)
{
LUri u(uri);
LString File = u.IsFile() ?
u.DecodeStr(u.LocalPath()) :
u.sPath(Uri.sPath.Length(), -1).LStrip("/");
return File;
}
void VcFolder::LogFile(const char *uri)
{
LString Args;
switch (GetType())
{
case VcSvn:
case VcHg:
case VcGit:
{
LString File = GetFilePart(uri);
ParseParams *Params = new ParseParams(uri);
Args.Printf("log \"%s\"", File.Get());
IsLogging = StartCmd(Args, &VcFolder::ParseLog, Params, LogNormal);
break;
}
default:
LAssert(!"Impl me.");
break;
}
}
VcLeaf *VcFolder::FindLeaf(const char *Path, bool OpenTree)
{
VcLeaf *r = NULL;
if (OpenTree)
DoExpand();
for (auto n = GetChild(); !r && n; n = n->GetNext())
{
auto l = dynamic_cast(n);
if (l)
r = l->FindLeaf(Path, OpenTree);
}
return r;
}
bool VcFolder::ParseLog(int Result, LString s, ParseParams *Params)
{
LHashTbl, VcCommit*> Map;
for (auto pc: Log)
Map.Add(pc->GetRev(), pc);
int Skipped = 0, Errors = 0;
VcLeaf *File = Params ? FindLeaf(Params->Str, true) : NULL;
LArray *Out = File ? &File->Log : &Log;
if (File)
{
for (auto Leaf = File; Leaf; Leaf = dynamic_cast(Leaf->GetParent()))
Leaf->OnExpand(true);
File->Select(true);
File->ScrollTo();
}
switch (GetType())
{
case VcGit:
{
LString::Array c;
c.SetFixedLength(false);
char *prev = s.Get();
for (char *i = s.Get(); *i; )
{
if (!strnicmp(i, "commit ", 7))
{
if (i > prev)
{
c.New().Set(prev, i - prev);
// LgiTrace("commit=%i\n", (int)(i - prev));
}
prev = i;
}
while (*i)
{
if (*i++ == '\n')
break;
}
}
for (unsigned i=0; i Rev(new VcCommit(d, this));
if (Rev->GitParse(c[i], false))
{
if (!Map.Find(Rev->GetRev()))
Out->Add(Rev.Release());
else
Skipped++;
}
else
{
LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, c[i].Get());
Errors++;
}
}
Out->Sort(CommitDateCmp);
break;
}
case VcSvn:
{
LString::Array c = s.Split("------------------------------------------------------------------------");
for (unsigned i=0; i Rev(new VcCommit(d, this));
LString Raw = c[i].Strip();
if (Rev->SvnParse(Raw))
{
if (File || !Map.Find(Rev->GetRev()))
Out->Add(Rev.Release());
else
Skipped++;
}
else if (Raw)
{
OnCmdError(Raw, "ParseLog Failed");
Errors++;
}
}
Out->Sort(CommitRevCmp);
break;
}
case VcHg:
{
LString::Array c = s.Split("\n\n");
LHashTbl, VcCommit*> Idx;
for (auto &Commit: c)
{
LAutoPtr Rev(new VcCommit(d, this));
if (Rev->HgParse(Commit))
{
auto Existing = File ? NULL : Map.Find(Rev->GetRev());
if (!Existing)
Out->Add(Existing = Rev.Release());
if (Existing->GetIndex() >= 0)
Idx.Add(Existing->GetIndex(), Existing);
}
}
if (!File)
{
// Patch all the trivial parents...
for (auto c: Log)
{
if (c->GetParents()->Length() > 0)
continue;
auto CIdx = c->GetIndex();
if (CIdx <= 0)
continue;
auto Par = Idx.Find(CIdx - 1);
if (Par)
c->GetParents()->Add(Par->GetRev());
}
}
Out->Sort(CommitIndexCmp);
if (!File)
LinkParents();
d->Resort = 1;
break;
}
case VcCvs:
{
if (Result)
{
OnCmdError(s, "Cvs command failed.");
break;
}
LHashTbl, VcCommit*> Map;
LString::Array c = s.Split("=============================================================================");
for (auto &Commit: c)
{
if (Commit.Strip().Length())
{
LString Head, File;
LString::Array Versions = Commit.Split("----------------------------");
LString::Array Lines = Versions[0].SplitDelimit("\r\n");
for (auto &Line: Lines)
{
LString::Array p = Line.Split(":", 1);
if (p.Length() == 2)
{
// LgiTrace("Line: %s\n", Line->Get());
LString Var = p[0].Strip().Lower();
LString Val = p[1].Strip();
if (Var.Equals("branch"))
{
if (Val.Length())
Branches.Add(Val, new VcBranch(Val));
}
else if (Var.Equals("head"))
{
Head = Val;
}
else if (Var.Equals("rcs file"))
{
LString::Array f = Val.SplitDelimit(",");
File = f.First();
}
}
}
// LgiTrace("%s\n", Commit->Get());
for (unsigned i=1; i= 3)
{
LString Ver = Lines[0].Split(" ").Last();
LString::Array a = Lines[1].SplitDelimit(";");
LString Date = a[0].Split(":", 1).Last().Strip();
LString Author = a[1].Split(":", 1).Last().Strip();
LString Id = a[2].Split(":", 1).Last().Strip();
LString Msg = Lines[2];
LDateTime Dt;
if (Dt.Parse(Date))
{
uint64 Ts;
if (Dt.Get(Ts))
{
VcCommit *Cc = Map.Find(Ts);
if (!Cc)
{
Map.Add(Ts, Cc = new VcCommit(d, this));
Out->Add(Cc);
Cc->CvsParse(Dt, Author, Msg);
}
Cc->Files.Add(File.Get());
}
else LAssert(!"NO ts for date.");
}
else LAssert(!"Date parsing failed.");
}
}
}
}
break;
}
default:
LAssert(!"Impl me.");
break;
}
if (File)
File->ShowLog();
// LgiTrace("%s:%i - ParseLog: Skip=%i, Error=%i\n", _FL, Skipped, Errors);
IsLogging = false;
return !Result;
}
void VcFolder::LinkParents()
{
#if PROFILE_FN
LProfile Prof("LinkParents");
#endif
LHashTbl,VcCommit*> Map;
// Index all the commits
int i = 0;
for (auto c:Log)
{
c->Idx = i++;
c->NodeIdx = -1;
Map.Add(c->GetRev(), c);
}
// Create all the edges...
PROF("Create edges.");
for (auto c:Log)
{
auto *Par = c->GetParents();
for (auto &pRev : *Par)
{
auto *p = Map.Find(pRev);
if (p)
new VcEdge(p, c);
#if 0
else
return;
#endif
}
}
// Map the edges to positions
PROF("Map edges.");
typedef LArray EdgeArr;
LArray Active;
for (auto c:Log)
{
for (unsigned i=0; c->NodeIdx<0 && iParent == c)
{
c->NodeIdx = i;
break;
}
}
}
// Add starting edges to active set
for (auto e:c->Edges)
{
if (e->Child == c)
{
if (c->NodeIdx < 0)
c->NodeIdx = (int)Active.Length();
e->Idx = c->NodeIdx;
c->Pos.Add(e, e->Idx);
Active[e->Idx].Add(e);
}
}
// Now for all active edges... assign positions
for (unsigned i=0; iLength(); n++)
{
LAssert(Active.PtrCheck(Edges));
VcEdge *e = (*Edges)[n];
if (c == e->Child || c == e->Parent)
{
LAssert(c->NodeIdx >= 0);
c->Pos.Add(e, c->NodeIdx);
}
else
{
// May need to untangle edges with different parents here
bool Diff = false;
for (auto edge: *Edges)
{
if (edge != e &&
edge->Child != c &&
edge->Parent != e->Parent)
{
Diff = true;
break;
}
}
if (Diff)
{
int NewIndex = -1;
// Look through existing indexes for a parent match
for (unsigned ii=0; iiParent?
bool Match = true;
for (auto ee: Active[ii])
{
if (ee->Parent != e->Parent)
{
Match = false;
break;
}
}
if (Match)
NewIndex = ii;
}
if (NewIndex < 0)
// Create new index for this parent
NewIndex = (int)Active.Length();
Edges->Delete(e);
auto &NewEdges = Active[NewIndex];
NewEdges.Add(e);
Edges = &Active[i]; // The 'Add' above can invalidate the object 'Edges' refers to
e->Idx = NewIndex;
c->Pos.Add(e, NewIndex);
n--;
}
else
{
LAssert(e->Idx == i);
c->Pos.Add(e, i);
}
}
}
}
// Process terminating edges
for (auto e: c->Edges)
{
if (e->Parent == c)
{
if (e->Idx < 0)
{
// This happens with out of order commits..?
continue;
}
int i = e->Idx;
if (c->NodeIdx < 0)
c->NodeIdx = i;
if (Active[i].HasItem(e))
Active[i].Delete(e);
else
LgiTrace("%s:%i - Warning: Active doesn't have 'e'.\n", _FL);
}
}
// Collapse any empty active columns
for (unsigned i=0; iIdx > 0);
edge->Idx--;
c->Pos.Add(edge, edge->Idx);
}
}
i--;
}
}
}
// Find all the "heads", i.e. a commit without any children
PROF("Find heads.");
LCombo *Heads;
if (d->Wnd()->GetViewById(IDC_HEADS, Heads))
{
Heads->Empty();
for (auto c:Log)
{
bool Has = false;
for (auto e:c->Edges)
{
if (e->Parent == c)
{
Has = true;
break;
}
}
if (!Has)
Heads->Insert(c->GetRev());
}
Heads->SendNotify(LNotifyTableLayoutRefresh);
}
}
VcFile *AppPriv::FindFile(const char *Path)
{
if (!Path)
return NULL;
LArray files;
if (Files->GetAll(files))
{
LString p = Path;
p = p.Replace(DIR_STR, "/");
for (auto f : files)
{
auto Fn = f->GetFileName();
if (p.Equals(Fn))
return f;
}
}
return NULL;
}
VcFile *VcFolder::FindFile(const char *Path)
{
return d->FindFile(Path);
}
void VcFolder::OnCmdError(LString Output, const char *Msg)
{
if (!CmdErrors)
{
if (Output.Length())
d->Log->Write(Output, Output.Length());
auto vc_name = GetVcName();
if (vc_name)
{
LString::Array a = GetProgramsInPath(GetVcName());
d->Log->Print("'%s' executables in the path:\n", GetVcName());
for (auto Bin : a)
d->Log->Print(" %s\n", Bin.Get());
}
else if (Msg)
{
d->Log->Print("%s\n", Msg);
}
}
CmdErrors++;
d->Tabs->Value(1);
GetCss(true)->Color(LColour::Red);
+ Update();
}
void VcFolder::ClearError()
{
GetCss(true)->Color(LCss::ColorInherit);
}
bool VcFolder::ParseInfo(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcGit:
case VcHg:
{
auto p = s.Strip().SplitDelimit();
CurrentCommit = p[0].Strip(" \t\r\n+");
if (p.Length() > 1)
CurrentCommitIdx = p[1].Int();
else
CurrentCommitIdx = -1;
if (Params && Params->Str.Equals("CountToTip"))
CountToTip();
break;
}
case VcSvn:
{
if (s.Find("client is too old") >= 0)
{
OnCmdError(s, "Client too old");
break;
}
LString::Array c = s.Split("\n");
for (unsigned i=0; iIsWorking = true;
ParseStatus(Result, s, Params);
break;
}
case VcCvs:
{
bool Untracked = d->IsMenuChecked(IDM_UNTRACKED);
if (Untracked)
{
auto Lines = s.SplitDelimit("\n");
for (auto Ln: Lines)
{
auto p = Ln.SplitDelimit(" \t", 1);
if (p.Length() > 1)
{
auto f = new VcFile(d, this, LString(), true);
f->SetText(p[0], COL_STATE);
f->SetText(p[1], COL_FILENAME);
f->GetStatus();
d->Files->Insert(f);
}
}
}
// else fall thru
}
default:
{
ParseDiffs(s, LString(), true);
break;
}
}
FilterCurrentFiles();
d->Files->ResizeColumnsToContent();
if (GetType() == VcSvn)
{
Unpushed = d->Files->Length() > 0 ? 1 : 0;
Update();
}
return false;
}
void VcFolder::DiffRange(const char *FromRev, const char *ToRev)
{
if (!FromRev || !ToRev)
return;
switch (GetType())
{
case VcSvn:
{
ParseParams *p = new ParseParams;
p->IsWorking = false;
p->Str = LString(FromRev) + ":" + ToRev;
LString a;
a.Printf("diff -r%s:%s", FromRev, ToRev);
StartCmd(a, &VcFolder::ParseDiff, p);
break;
}
case VcGit:
{
ParseParams *p = new ParseParams;
p->IsWorking = false;
p->Str = LString(FromRev) + ":" + ToRev;
LString a;
a.Printf("-P diff %s..%s", FromRev, ToRev);
StartCmd(a, &VcFolder::ParseDiff, p);
break;
}
case VcCvs:
case VcHg:
default:
LAssert(!"Impl me.");
break;
}
}
bool VcFolder::ParseDiff(int Result, LString s, ParseParams *Params)
{
if (Params)
ParseDiffs(s, Params->Str, Params->IsWorking);
else
ParseDiffs(s, LString(), true);
return false;
}
void VcFolder::Diff(VcFile *file)
{
auto Fn = file->GetFileName();
if (!Fn ||
!Stricmp(Fn, ".") ||
!Stricmp(Fn, ".."))
return;
const char *Prefix = "";
switch (GetType())
{
case VcGit:
Prefix = "-P ";
// fall through
case VcHg:
{
LString a;
auto rev = file->GetRevision();
if (rev)
a.Printf("%sdiff %s \"%s\"", Prefix, rev, Fn);
else
a.Printf("%sdiff \"%s\"", Prefix, Fn);
StartCmd(a, &VcFolder::ParseDiff);
break;
}
case VcSvn:
{
LString a;
if (file->GetRevision())
a.Printf("diff -r %s \"%s\"", file->GetRevision(), Fn);
else
a.Printf("diff \"%s\"", Fn);
StartCmd(a, &VcFolder::ParseDiff);
break;
}
case VcCvs:
break;
default:
LAssert(!"Impl me.");
break;
}
}
bool VcFolder::ParseDiffs(LString s, LString Rev, bool IsWorking)
{
LAssert(IsWorking || Rev.Get() != NULL);
switch (GetType())
{
case VcGit:
{
LString::Array a = s.Split("\n");
LString Diff;
VcFile *f = NULL;
for (unsigned i=0; iSetDiff(Diff);
Diff.Empty();
auto Bits = a[i].SplitDelimit();
LString Fn, State = "M";
if (Bits[1].Equals("--cc"))
{
Fn = Bits.Last();
State = "C";
}
else
Fn = Bits.Last()(2,-1);
// LgiTrace("%s\n", a[i].Get());
f = FindFile(Fn);
if (!f)
f = new VcFile(d, this, Rev, IsWorking);
f->SetText(State, COL_STATE);
f->SetText(Fn.Replace("\\","/"), COL_FILENAME);
f->GetStatus();
d->Files->Insert(f);
}
else if (!_strnicmp(Ln, "new file", 8))
{
if (f)
f->SetText("A", COL_STATE);
}
else if (!_strnicmp(Ln, "deleted file", 12))
{
if (f)
f->SetText("D", COL_STATE);
}
else if (!_strnicmp(Ln, "index", 5) ||
!_strnicmp(Ln, "commit", 6) ||
!_strnicmp(Ln, "Author:", 7) ||
!_strnicmp(Ln, "Date:", 5) ||
!_strnicmp(Ln, "+++", 3) ||
!_strnicmp(Ln, "---", 3))
{
// Ignore
}
else
{
if (Diff) Diff += "\n";
Diff += a[i];
}
}
if (f && Diff)
{
f->SetDiff(Diff);
Diff.Empty();
}
break;
}
case VcHg:
{
LString Sep("\n");
LString::Array a = s.Split(Sep);
LString::Array Diffs;
VcFile *f = NULL;
List Files;
LProgressDlg Prog(GetTree(), 1000);
Prog.SetDescription("Reading diff lines...");
Prog.SetRange(a.Length());
// Prog.SetYieldTime(300);
for (unsigned i=0; iSetDiff(Sep.Join(Diffs));
Diffs.Empty();
auto MainParts = a[i].Split(" -r ");
auto FileParts = MainParts.Last().Split(" ",1);
LString Fn = FileParts.Last();
f = FindFile(Fn);
if (!f)
f = new VcFile(d, this, Rev, IsWorking);
f->SetText(Fn.Replace("\\","/"), COL_FILENAME);
// f->SetText(Status, COL_STATE);
Files.Insert(f);
}
else if (!_strnicmp(Ln, "index", 5) ||
!_strnicmp(Ln, "commit", 6) ||
!_strnicmp(Ln, "Author:", 7) ||
!_strnicmp(Ln, "Date:", 5) ||
!_strnicmp(Ln, "+++", 3) ||
!_strnicmp(Ln, "---", 3))
{
// Ignore
}
else
{
Diffs.Add(a[i]);
}
Prog.Value(i);
if (Prog.IsCancelled())
break;
}
if (f && Diffs.Length())
{
f->SetDiff(Sep.Join(Diffs));
Diffs.Empty();
}
d->Files->Insert(Files);
break;
}
case VcSvn:
{
LString::Array a = s.Replace("\r").Split("\n");
LString Diff;
VcFile *f = NULL;
bool InPreamble = false;
bool InDiff = false;
for (unsigned i=0; iSetDiff(Diff);
f->Select(false);
}
Diff.Empty();
InDiff = false;
InPreamble = false;
LString Fn = a[i].Split(":", 1).Last().Strip();
f = FindFile(Fn);
if (!f)
f = new VcFile(d, this, Rev, IsWorking);
f->SetText(Fn.Replace("\\","/"), COL_FILENAME);
f->SetText("M", COL_STATE);
f->GetStatus();
d->Files->Insert(f);
}
else if (!_strnicmp(Ln, "------", 6))
{
InPreamble = !InPreamble;
}
else if (!_strnicmp(Ln, "======", 6))
{
InPreamble = false;
InDiff = true;
}
else if (InDiff)
{
if (!strncmp(Ln, "--- ", 4) ||
!strncmp(Ln, "+++ ", 4))
{
}
else
{
if (Diff) Diff += "\n";
Diff += a[i];
}
}
}
if (f && Diff)
{
f->SetDiff(Diff);
Diff.Empty();
}
break;
}
case VcCvs:
{
break;
}
default:
{
LAssert(!"Impl me.");
break;
}
}
FilterCurrentFiles();
return true;
}
bool VcFolder::ParseFiles(int Result, LString s, ParseParams *Params)
{
d->ClearFiles();
ParseDiffs(s, Params->Str, false);
IsFilesCmd = false;
FilterCurrentFiles();
return false;
}
void VcFolder::OnSshCmd(SshParams *p)
{
if (!p || !p->f)
{
LAssert(!"Param error.");
return;
}
LString s = p->Output;
int Result = p->ExitCode;
if (Result == ErrSubProcessFailed)
{
CmdErrors++;
}
else if (p->Parser)
{
bool Reselect = CALL_MEMBER_FN(*this, p->Parser)(Result, s, p->Params);
if (Reselect)
{
if (LTreeItem::Select())
Select(true);
}
}
if (p->Params &&
p->Params->Callback)
{
p->Params->Callback(s);
}
}
void VcFolder::OnPulse()
{
bool Reselect = false, CmdsChanged = false;
static bool Processing = false;
if (!Processing)
{
Processing = true; // Lock out processing, if it puts up a dialog or something...
// bad things happen if we try and re-process something.
// printf("Cmds.Len=%i\n", (int)Cmds.Length());
for (unsigned i=0; iRd->GetState()=%i\n", c->Rd->GetState());
if (c->Rd->GetState() == LThread::THREAD_INIT)
{
if (CmdActiveThreads < CmdMaxThreads)
{
c->Rd->Run();
CmdActiveThreads++;
// LgiTrace("CmdActiveThreads++ = %i\n", CmdActiveThreads);
}
// else printf("Too many active threads.");
}
else if (c->Rd->IsExited())
{
CmdActiveThreads--;
// LgiTrace("CmdActiveThreads-- = %i\n", CmdActiveThreads);
LString s = c->GetBuf();
int Result = c->Rd->ExitCode();
if (Result == ErrSubProcessFailed)
{
if (!CmdErrors)
d->Log->Print("Error: Can't run '%s'\n", GetVcName());
CmdErrors++;
}
else if (c->PostOp)
{
if (s.Length() == 18 &&
s.Equals("GSUBPROCESS_ERROR\n"))
{
OnCmdError(s, "Sub process failed.");
}
else
{
Reselect |= CALL_MEMBER_FN(*this, c->PostOp)(Result, s, c->Params);
}
}
if (c->Params &&
c->Params->Callback)
{
c->Params->Callback(s);
}
Cmds.DeleteAt(i--, true);
delete c;
CmdsChanged = true;
}
// else printf("Not exited.\n");
}
Processing = false;
}
if (Reselect)
{
if (LTreeItem::Select())
Select(true);
}
if (CmdsChanged)
{
Update();
}
if (CmdErrors)
{
d->Tabs->Value(1);
CmdErrors = false;
}
}
void VcFolder::OnRemove()
{
LXmlTag *t = d->Opts.LockTag(OPT_Folders, _FL);
if (t)
{
Uncommit.Reset();
if (LTreeItem::Select())
{
d->Files->Empty();
d->Commits->RemoveAll();
}
bool Found = false;
auto u = Uri.ToString();
for (auto c: t->Children)
{
if (!c->IsTag(OPT_Folder))
printf("%s:%i - Wrong tag: %s, %s\n", _FL, c->GetTag(), OPT_Folder);
else if (!c->GetContent())
printf("%s:%i - No content.\n", _FL);
else
{
auto Content = c->GetContent();
if (!_stricmp(Content, u))
{
c->RemoveTag();
delete c;
Found = true;
break;
}
}
}
LAssert(Found);
d->Opts.Unlock();
}
}
void VcFolder::Empty()
{
Type = VcNone;
IsCommit = false;
IsLogging = false;
IsUpdate = false;
IsFilesCmd = false;
CommitListDirty = false;
IsUpdatingCounts = false;
IsBranches = StatusNone;
IsIdent = StatusNone;
Unpushed = Unpulled = -1;
CmdErrors = 0;
CurrentCommitIdx = -1;
CurrentCommit.Empty();
RepoUrl.Empty();
VcCmd.Empty();
Uncommit.Reset();
Log.DeleteObjects();
d->Commits->Empty();
d->Files->Empty();
if (!Uri.IsFile())
GetCss(true)->Color(LColour::Blue);
}
void VcFolder::OnMouseClick(LMouse &m)
{
if (m.IsContextMenu())
{
LSubMenu s;
s.AppendItem("Browse To", IDM_BROWSE_FOLDER, Uri.IsFile());
s.AppendItem(
#ifdef WINDOWS
"Command Prompt At",
#else
"Terminal At",
#endif
IDM_TERMINAL, Uri.IsFile());
s.AppendItem("Clean", IDM_CLEAN);
s.AppendSeparator();
s.AppendItem("Pull", IDM_PULL);
s.AppendItem("Status", IDM_STATUS);
s.AppendItem("Push", IDM_PUSH);
s.AppendItem("Update Subs", IDM_UPDATE_SUBS, GetType() == VcGit);
s.AppendSeparator();
s.AppendItem("Remove", IDM_REMOVE);
s.AppendItem("Remote URL", IDM_REMOTE_URL);
if (!Uri.IsFile())
{
s.AppendSeparator();
s.AppendItem("Edit Location", IDM_EDIT);
}
int Cmd = s.Float(GetTree(), m);
switch (Cmd)
{
case IDM_BROWSE_FOLDER:
{
LBrowseToFile(LocalPath());
break;
}
case IDM_TERMINAL:
{
TerminalAt(LocalPath());
break;
}
case IDM_CLEAN:
{
Clean();
break;
}
case IDM_PULL:
{
Pull();
break;
}
case IDM_STATUS:
{
FolderStatus();
break;
}
case IDM_PUSH:
{
Push();
break;
}
case IDM_UPDATE_SUBS:
{
UpdateSubs();
break;
}
case IDM_REMOVE:
{
OnRemove();
delete this;
break;
}
case IDM_EDIT:
{
auto Dlg = new LInput(GetTree(), Uri.ToString(), "URI:", "Remote Folder Location");
Dlg->DoModal([this, Dlg](auto dlg, auto ctrlId)
{
if (ctrlId)
{
Uri.Set(Dlg->GetStr());
Empty();
Select(true);
}
delete dlg;
});
break;
}
case IDM_REMOTE_URL:
{
GetRemoteUrl([this](auto str)
{
LString Url = str.Strip();
if (Url)
{
auto a = new LAlert(GetTree(), "Remote Url", Url, "Copy", "Ok");
a->DoModal([this, Url](auto dlg, auto code)
{
if (code == 1)
{
LClipBoard c(GetTree());
c.Text(Url);
}
delete dlg;
});
}
});
break;
}
default:
break;
}
}
}
void VcFolder::OnUpdate(const char *Rev)
{
if (!Rev)
return;
if (!IsUpdate)
{
LString Args;
NewRev = Rev;
switch (GetType())
{
case VcGit:
Args.Printf("checkout %s", Rev);
IsUpdate = StartCmd(Args, &VcFolder::ParseUpdate, NULL, LogNormal);
break;
case VcSvn:
Args.Printf("up -r %s", Rev);
IsUpdate = StartCmd(Args, &VcFolder::ParseUpdate, NULL, LogNormal);
break;
case VcHg:
Args.Printf("update -r %s", Rev);
IsUpdate = StartCmd(Args, &VcFolder::ParseUpdate, NULL, LogNormal);
break;
default:
{
LAssert(!"Impl me.");
break;
}
}
}
}
///////////////////////////////////////////////////////////////////////////////////////
int FolderCompare(LTreeItem *a, LTreeItem *b, NativeInt UserData)
{
VcLeaf *A = dynamic_cast(a);
VcLeaf *B = dynamic_cast(b);
if (!A || !B)
return 0;
return A->Compare(B);
}
struct SshFindEntry
{
LString Flags, Name, User, Group;
uint64_t Size;
LDateTime Modified, Access;
SshFindEntry &operator =(const LString &s)
{
auto p = s.SplitDelimit("/");
if (p.Length() == 7)
{
Flags = p[0];
Group = p[1];
User = p[2];
Access.Set((uint64_t) p[3].Int());
Modified.Set((uint64_t) p[4].Int());
Size = p[5].Int();
Name = p[6];
}
return *this;
}
bool IsDir() { return Flags(0) == 'd'; }
bool IsHidden() { return Name(0) == '.'; }
const char *GetName() { return Name; }
static int Compare(SshFindEntry *a, SshFindEntry *b) { return Stricmp(a->Name.Get(), b->Name.Get()); }
};
bool VcFolder::ParseRemoteFind(int Result, LString s, ParseParams *Params)
{
if (!Params || !s)
return false;
auto Parent = Params->Leaf ? static_cast(Params->Leaf) : static_cast(this);
LUri u(Params->Str);
auto Lines = s.SplitDelimit("\r\n");
LArray Entries;
for (size_t i=1; iStr, Dir.GetName(), true);
}
}
else if (!Dir.IsHidden())
{
char *Ext = LGetExtension(Dir.GetName());
if (!Ext) continue;
if (!stricmp(Ext, "c") ||
!stricmp(Ext, "cpp") ||
!stricmp(Ext, "h"))
{
LUri Path = u;
Path += Dir.GetName();
new VcLeaf(this, Parent, Params->Str, Dir.GetName(), false);
}
}
}
return false;
}
void VcFolder::ReadDir(LTreeItem *Parent, const char *ReadUri)
{
LUri u(ReadUri);
if (u.IsFile())
{
// Read child items
LDirectory Dir;
for (int b = Dir.First(u.LocalPath()); b; b = Dir.Next())
{
auto name = Dir.GetName();
if (Dir.IsHidden())
continue;
LUri Path = u;
Path += name;
new VcLeaf(this, Parent, u.ToString(), name, Dir.IsDir());
}
}
else
{
auto c = d->GetConnection(ReadUri);
if (!c)
return;
LString Path = u.sPath(Uri.sPath.Length(), -1).LStrip("/");
LString Args;
Args.Printf("\"%s\" -maxdepth 1 -printf \"%%M/%%g/%%u/%%A@/%%T@/%%s/%%P\n\"", Path ? Path.Get() : ".");
auto *Params = new ParseParams(ReadUri);
Params->Leaf = dynamic_cast(Parent);
c->Command(this, "find", Args, &VcFolder::ParseRemoteFind, Params);
return;
}
Parent->Sort(FolderCompare);
}
void VcFolder::OnVcsType(LString errorMsg)
{
if (!d)
{
LAssert(!"No priv instance");
return;
}
auto c = d->GetConnection(Uri.ToString(), false);
if (c)
{
auto NewType = c->Types.Find(Uri.sPath);
if (NewType && NewType != Type)
{
if (NewType == VcError)
{
OnCmdError(LString(), errorMsg);
}
else
{
Type = NewType;
ClearError();
Update();
if (LTreeItem::Select())
Select(true);
}
}
}
}
void VcFolder::DoExpand()
{
if (Tmp)
{
Tmp->Remove();
DeleteObj(Tmp);
ReadDir(this, Uri.ToString());
}
}
void VcFolder::OnExpand(bool b)
{
if (b)
DoExpand();
}
void VcFolder::ListCommit(VcCommit *c)
{
if (!IsFilesCmd)
{
LString Args;
switch (GetType())
{
case VcGit:
Args.Printf("-P show %s", c->GetRev());
IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev()));
break;
case VcSvn:
Args.Printf("log --verbose --diff -r %s", c->GetRev());
IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev()));
break;
case VcCvs:
{
d->ClearFiles();
for (unsigned i=0; iFiles.Length(); i++)
{
VcFile *f = new VcFile(d, this, c->GetRev(), false);
if (f)
{
f->SetText(c->Files[i], COL_FILENAME);
d->Files->Insert(f);
}
}
FilterCurrentFiles();
break;
}
case VcHg:
{
Args.Printf("diff --change %s", c->GetRev());
IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev()));
break;
}
default:
LAssert(!"Impl me.");
break;
}
if (IsFilesCmd)
d->ClearFiles();
}
}
LString ConvertUPlus(LString s)
{
LArray c;
LUtf8Ptr p(s);
int32 ch;
while ((ch = p))
{
if (ch == '{')
{
auto n = p.GetPtr();
if (n[1] == 'U' &&
n[2] == '+')
{
// Convert unicode code point
p += 3;
ch = (int32)htoi(p.GetPtr());
c.Add(ch);
while ((ch = p) != '}')
p++;
}
else c.Add(ch);
}
else c.Add(ch);
p++;
}
c.Add(0);
#ifdef LINUX
return LString((char16*)c.AddressOf());
#else
return LString(c.AddressOf());
#endif
}
bool VcFolder::ParseStatus(int Result, LString s, ParseParams *Params)
{
bool ShowUntracked = d->Wnd()->GetCtrlValue(IDC_UNTRACKED) != 0;
bool IsWorking = Params ? Params->IsWorking : false;
List Ins;
switch (GetType())
{
case VcCvs:
{
LHashTbl,VcFile*> Map;
for (auto i: *d->Files)
{
VcFile *f = dynamic_cast(i);
if (f)
Map.Add(f->GetText(COL_FILENAME), f);
}
#if 0
LFile Tmp("C:\\tmp\\output.txt", O_WRITE);
Tmp.Write(s);
Tmp.Close();
#endif
LString::Array a = s.Split("===================================================================");
for (auto i : a)
{
LString::Array Lines = i.SplitDelimit("\r\n");
if (Lines.Length() == 0)
continue;
LString f = Lines[0].Strip();
if (f.Find("File:") == 0)
{
LString::Array Parts = f.SplitDelimit("\t");
LString File = Parts[0].Split(": ").Last().Strip();
LString Status = Parts[1].Split(": ").Last();
LString WorkingRev;
for (auto l : Lines)
{
LString::Array p = l.Strip().Split(":", 1);
if (p.Length() > 1 &&
p[0].Strip().Equals("Working revision"))
{
WorkingRev = p[1].Strip();
}
}
VcFile *f = Map.Find(File);
if (!f)
{
if ((f = new VcFile(d, this, WorkingRev, IsWorking)))
Ins.Insert(f);
}
if (f)
{
f->SetText(Status, COL_STATE);
f->SetText(File, COL_FILENAME);
f->Update();
}
}
else if (f(0) == '?' &&
ShowUntracked)
{
LString File = f(2, -1);
VcFile *f = Map.Find(File);
if (!f)
{
if ((f = new VcFile(d, this, LString(), IsWorking)))
Ins.Insert(f);
}
if (f)
{
f->SetText("?", COL_STATE);
f->SetText(File, COL_FILENAME);
f->Update();
}
}
}
for (auto i: *d->Files)
{
VcFile *f = dynamic_cast(i);
if (f)
{
if (f->GetStatus() == VcFile::SUnknown)
f->SetStatus(VcFile::SUntracked);
}
}
break;
}
case VcGit:
{
LString::Array Lines = s.SplitDelimit("\r\n");
int Fmt = ToolVersion[VcGit] >= Ver2Int("2.8.0") ? 2 : 1;
for (auto Ln : Lines)
{
char Type = Ln(0);
if (Ln.Lower().Find("error:") >= 0)
{
}
else if (Ln.Find("usage: git") >= 0)
{
// It's probably complaining about the --porcelain=2 parameter
OnCmdError(s, "Args error");
}
else if (Type != '?')
{
VcFile *f = NULL;
if (Fmt == 2)
{
LString::Array p = Ln.SplitDelimit(" ", 8);
if (p.Length() < 7)
d->Log->Print("%s:%i - Error: not enough tokens: '%s'\n", _FL, Ln.Get());
else
{
f = new VcFile(d, this, p[6], IsWorking);
f->SetText(p[1].Strip("."), COL_STATE);
f->SetText(p.Last(), COL_FILENAME);
}
}
else if (Fmt == 1)
{
LString::Array p = Ln.SplitDelimit(" ");
f = new VcFile(d, this, LString(), IsWorking);
f->SetText(p[0], COL_STATE);
f->SetText(p.Last(), COL_FILENAME);
}
if (f)
Ins.Insert(f);
}
else if (ShowUntracked)
{
VcFile *f = new VcFile(d, this, LString(), IsWorking);
f->SetText("?", COL_STATE);
f->SetText(Ln(2,-1), COL_FILENAME);
Ins.Insert(f);
}
}
break;
}
case VcHg:
case VcSvn:
{
if (s.Find("failed to import") >= 0)
{
OnCmdError(s, "Tool error.");
return false;
}
LString::Array Lines = s.SplitDelimit("\r\n");
for (auto Ln : Lines)
{
char Type = Ln(0);
if (Ln.Lower().Find("error:") >= 0)
{
}
else if (Ln.Find("client is too old") >= 0)
{
OnCmdError(s, "Client too old.");
return false;
}
else if (Strchr(" \t", Type) ||
Ln.Find("Summary of conflicts") >= 0)
{
// Ignore
}
else if (Type != '?')
{
LString::Array p = Ln.SplitDelimit(" ", 1);
if (p.Length() == 2)
{
LString File;
if (GetType() == VcSvn)
File = ConvertUPlus(p.Last());
else
File = p.Last();
if (GetType() == VcSvn &&
File.Find("+ ") == 0)
{
File = File(5, -1);
}
VcFile *f = new VcFile(d, this, LString(), IsWorking);
f->SetText(p[0], COL_STATE);
f->SetText(File.Replace("\\","/"), COL_FILENAME);
f->GetStatus();
Ins.Insert(f);
}
else LAssert(!"What happen?");
}
else if (ShowUntracked)
{
VcFile *f = new VcFile(d, this, LString(), IsWorking);
f->SetText("?", COL_STATE);
f->SetText(Ln(2,-1), COL_FILENAME);
Ins.Insert(f);
}
}
break;
}
default:
{
LAssert(!"Impl me.");
break;
}
}
if ((Unpushed = Ins.Length() > 0))
{
if (CmdErrors == 0)
GetCss(true)->Color(LColour(255, 128, 0));
}
else if (Unpulled == 0)
{
GetCss(true)->Color(LCss::ColorInherit);
}
Update();
if (LTreeItem::Select())
{
d->Files->Insert(Ins);
FilterCurrentFiles();
}
else
{
Ins.DeleteObjects();
}
if (Params && Params->Leaf)
Params->Leaf->AfterBrowse();
return false; // Don't refresh list
}
// Clone/checkout any sub-repositries.
bool VcFolder::UpdateSubs()
{
LString Arg;
switch (GetType())
{
default:
case VcSvn:
case VcHg:
case VcCvs:
return false;
case VcGit:
Arg = "submodule update --init";
break;
}
return StartCmd(Arg, &VcFolder::ParseUpdateSubs, NULL, LogNormal);
}
bool VcFolder::ParseUpdateSubs(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
default:
case VcSvn:
case VcHg:
case VcCvs:
return false;
case VcGit:
break;
}
return false;
}
void VcFolder::FolderStatus(const char *uri, VcLeaf *Notify)
{
LUri Uri(uri);
if (Uri.IsFile() && Uri.sPath)
{
LFile::Path p(Uri.sPath(1,-1));
if (!p.IsFolder())
{
LAssert(!"Needs to be a folder.");
return;
}
}
if (LTreeItem::Select())
d->ClearFiles();
LString Arg;
switch (GetType())
{
case VcSvn:
case VcHg:
Arg = "status";
break;
case VcCvs:
Arg = "status -l";
break;
case VcGit:
if (!ToolVersion[VcGit])
LAssert(!"Where is the version?");
// What version did =2 become available? It's definitely not in v2.5.4
// Not in v2.7.4 either...
if (ToolVersion[VcGit] >= Ver2Int("2.8.0"))
Arg = "-P status --porcelain=2";
else
Arg = "-P status --porcelain";
break;
default:
return;
}
ParseParams *p = new ParseParams;
if (uri && Notify)
{
p->AltInitPath = uri;
p->Leaf = Notify;
}
else
{
p->IsWorking = true;
}
StartCmd(Arg, &VcFolder::ParseStatus, p);
switch (GetType())
{
case VcHg:
CountToTip();
break;
default:
break;
}
}
void VcFolder::CountToTip()
{
// if (Path.Equals("C:\\Users\\matthew\\Code\\Lgi\\trunk"))
{
// LgiTrace("%s: CountToTip, br=%s, idx=%i\n", Path.Get(), CurrentBranch.Get(), (int)CurrentCommitIdx);
if (!CurrentBranch)
GetBranches(new ParseParams("CountToTip"));
else if (CurrentCommitIdx < 0)
GetCurrentRevision(new ParseParams("CountToTip"));
else
{
LString Arg;
Arg.Printf("id -n -r %s", CurrentBranch.Get());
StartCmd(Arg, &VcFolder::ParseCountToTip);
}
}
}
bool VcFolder::ParseCountToTip(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcHg:
if (CurrentCommitIdx >= 0)
{
auto p = s.Strip();
auto idx = p.Int();
if (idx >= CurrentCommitIdx)
{
Unpulled = (int) (idx - CurrentCommitIdx);
Update();
}
}
break;
default:
break;
}
return false;
}
void VcFolder::ListWorkingFolder()
{
d->ClearFiles();
bool Untracked = d->IsMenuChecked(IDM_UNTRACKED);
LString Arg;
switch (GetType())
{
case VcCvs:
if (Untracked)
Arg = "-qn update";
else
Arg = "-q diff --brief";
break;
case VcSvn:
Arg = "status";
break;
case VcGit:
// Fucking git won't honour their own docs.
StartCmd("-P diff --diff-filter=AD --cached", &VcFolder::ParseWorking);
Arg = "-P diff --diff-filter=CMRTU";
break;
case VcHg:
Arg = "status -mard";
break;
default:
return;
}
StartCmd(Arg, &VcFolder::ParseWorking);
}
void VcFolder::GitAdd()
{
if (!PostAdd)
return;
LString Args;
if (PostAdd->Files.Length() == 0)
{
LString m(PostAdd->Msg);
m = m.Replace("\"", "\\\"");
Args.Printf("commit -m \"%s\"", m.Get());
IsCommit = StartCmd(Args, &VcFolder::ParseCommit, PostAdd->Param, LogNormal);
PostAdd.Reset();
}
else
{
+ char NativeSep[] = {GetPathSep(), 0};
LString Last = PostAdd->Files.Last();
- Args.Printf("add \"%s\"", Last.Replace("\"", "\\\"").Replace("/", DIR_STR).Get());
+ Args.Printf("add \"%s\"", Last.Replace("\"", "\\\"").Replace("/", NativeSep).Get());
PostAdd->Files.PopLast();
StartCmd(Args, &VcFolder::ParseGitAdd, NULL, LogNormal);
}
}
bool VcFolder::ParseGitAdd(int Result, LString s, ParseParams *Params)
{
- GitAdd();
+ if (Result)
+ {
+ OnCmdError(s, "add failed.");
+ }
+ else
+ {
+ GitAdd();
+ }
+
return false;
}
bool VcFolder::ParseCommit(int Result, LString s, ParseParams *Params)
{
if (LTreeItem::Select())
Select(true);
CommitListDirty = Result == 0;
CurrentCommit.Empty();
IsCommit = false;
if (Result)
{
switch (GetType())
{
case VcGit:
{
if (s.Find("Please tell me who you are") >= 0)
{
auto i = new LInput(GetTree(), "", "Git user name:", AppName);
i->DoModal([this, i](auto dlg, auto ctrlId)
{
if (ctrlId)
{
LString Args;
Args.Printf("config --global user.name \"%s\"", i->GetStr().Get());
StartCmd(Args);
auto inp = new LInput(GetTree(), "", "Git user email:", AppName);
i->DoModal([this, inp](auto dlg, auto ctrlId)
{
if (ctrlId)
{
LString Args;
Args.Printf("config --global user.email \"%s\"", inp->GetStr().Get());
StartCmd(Args);
}
delete dlg;
});
}
delete dlg;
});
}
break;
}
default:
break;
}
return false;
}
if (Result == 0 && LTreeItem::Select())
{
d->ClearFiles();
auto *w = d->Diff ? d->Diff->GetWindow() : NULL;
if (w)
w->SetCtrlName(IDC_MSG, NULL);
}
switch (GetType())
{
case VcGit:
{
Unpushed++;
CommitListDirty = true;
Update();
if (Params && Params->Str.Find("Push") >= 0)
Push();
break;
}
case VcSvn:
{
CurrentCommit.Empty();
CommitListDirty = true;
GetTree()->SendNotify((LNotifyType)LvcCommandEnd);
if (!Result)
{
Unpushed = 0;
Update();
GetCss(true)->Color(LColour::Green);
}
break;
}
case VcHg:
{
CurrentCommit.Empty();
CommitListDirty = true;
GetTree()->SendNotify((LNotifyType)LvcCommandEnd);
if (!Result)
{
Unpushed = 0;
Update();
if (Params && Params->Str.Find("Push") >= 0)
Push();
else
GetCss(true)->Color(LColour::Green);
}
break;
}
case VcCvs:
{
CurrentCommit.Empty();
CommitListDirty = true;
GetTree()->SendNotify((LNotifyType)LvcCommandEnd);
if (!Result)
{
Unpushed = 0;
Update();
GetCss(true)->Color(LColour::Green);
}
break;
}
default:
{
LAssert(!"Impl me.");
break;
}
}
return true;
}
void VcFolder::Commit(const char *Msg, const char *Branch, bool AndPush)
{
LArray Add;
bool Partial = false;
for (auto fp: *d->Files)
{
VcFile *f = dynamic_cast(fp);
if (f)
{
int c = f->Checked();
if (c > 0)
Add.Add(f);
else
Partial = true;
}
}
if (CurrentBranch && Branch &&
!CurrentBranch.Equals(Branch))
{
int Response = LgiMsg(GetTree(), "Do you want to start a new branch?", AppName, MB_YESNO);
if (Response != IDYES)
return;
LJson j;
j.Set("Command", "commit");
j.Set("Msg", Msg);
j.Set("AndPush", (int64_t)AndPush);
StartBranch(Branch, j.GetJson());
return;
}
if (!IsCommit)
{
LString Args;
ParseParams *Param = AndPush ? new ParseParams("Push") : NULL;
switch (GetType())
{
case VcGit:
{
if (Add.Length() == 0)
{
break;
}
else if (Partial)
{
if (PostAdd.Reset(new GitCommit))
{
PostAdd->Files.SetFixedLength(false);
for (auto f : Add)
PostAdd->Files.Add(f->GetFileName());
PostAdd->Msg = Msg;
PostAdd->Branch = Branch;
PostAdd->Param = Param;
GitAdd();
}
}
else
{
LString m(Msg);
m = m.Replace("\"", "\\\"");
Args.Printf("commit -am \"%s\"", m.Get());
IsCommit = StartCmd(Args, &VcFolder::ParseCommit, Param, LogNormal);
}
break;
}
case VcSvn:
{
LString::Array a;
a.New().Printf("commit -m \"%s\"", Msg);
for (auto pf: Add)
{
LString s = pf->GetFileName();
if (s.Find(" ") >= 0)
a.New().Printf("\"%s\"", s.Get());
else
a.New() = s;
}
Args = LString(" ").Join(a);
IsCommit = StartCmd(Args, &VcFolder::ParseCommit, Param, LogNormal);
if (d->Tabs && IsCommit)
{
d->Tabs->Value(1);
GetTree()->SendNotify((LNotifyType)LvcCommandStart);
}
break;
}
case VcHg:
{
LString::Array a;
LString CommitMsg = Msg;
TmpFile Tmp;
if (CommitMsg.Find("\n") >= 0)
{
Tmp.Create().Write(Msg);
a.New().Printf("commit -l \"%s\"", Tmp.GetName());
}
else
{
a.New().Printf("commit -m \"%s\"", Msg);
}
if (Partial)
{
for (auto pf: Add)
{
LString s = pf->GetFileName();
if (s.Find(" ") >= 0)
a.New().Printf("\"%s\"", s.Get());
else
a.New() = s;
}
}
Args = LString(" ").Join(a);
IsCommit = StartCmd(Args, &VcFolder::ParseCommit, Param, LogNormal);
if (d->Tabs && IsCommit)
{
d->Tabs->Value(1);
GetTree()->SendNotify((LNotifyType)LvcCommandStart);
}
break;
}
case VcCvs:
{
LString a;
a.Printf("commit -m \"%s\"", Msg);
IsCommit = StartCmd(a, &VcFolder::ParseCommit, NULL, LogNormal);
break;
}
default:
{
OnCmdError(LString(), "No commit impl for type.");
break;
}
}
}
}
bool VcFolder::ParseStartBranch(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcHg:
{
if (Result == 0 && Params && Params->Str)
{
LJson j(Params->Str);
auto cmd = j.Get("Command");
if (cmd.Equals("commit"))
{
auto Msg = j.Get("Msg");
auto AndPush = j.Get("AndPush").Int();
if (Msg)
{
Commit(Msg, NULL, AndPush > 0);
}
}
}
break;
}
default:
{
OnCmdError(LString(), "No commit impl for type.");
break;
}
}
return true;
}
void VcFolder::StartBranch(const char *BranchName, const char *OnCreated)
{
if (!BranchName)
return;
switch (GetType())
{
case VcHg:
{
LString a;
a.Printf("branch \"%s\"", BranchName);
StartCmd(a, &VcFolder::ParseStartBranch, OnCreated ? new ParseParams(OnCreated) : NULL);
break;
}
default:
{
OnCmdError(LString(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE));
break;
}
}
}
void VcFolder::Push(bool NewBranchOk)
{
LString Args;
bool Working = false;
switch (GetType())
{
case VcHg:
{
auto args = NewBranchOk ? "push --new-branch" : "push";
Working = StartCmd(args, &VcFolder::ParsePush, NULL, LogNormal);
break;
}
case VcGit:
{
LString args;
if (NewBranchOk)
{
if (CurrentBranch)
{
args.Printf("push --set-upstream origin %s", CurrentBranch.Get());
}
else
{
OnCmdError(LString(), "Don't have the current branch?");
return;
}
}
else
{
args = "push";
}
Working = StartCmd(args, &VcFolder::ParsePush, NULL, LogNormal);
break;
}
case VcSvn:
{
// Nothing to do here.. the commit pushed the data already
break;
}
default:
{
OnCmdError(LString(), "No push impl for type.");
break;
}
}
if (d->Tabs && Working)
{
d->Tabs->Value(1);
GetTree()->SendNotify((LNotifyType)LvcCommandStart);
}
}
bool VcFolder::ParsePush(int Result, LString s, ParseParams *Params)
{
bool Status = false;
if (Result)
{
bool needsNewBranchPerm = false;
switch (GetType())
{
case VcHg:
{
needsNewBranchPerm = s.Find("push creates new remote branches") >= 0;
break;
}
case VcGit:
{
needsNewBranchPerm = s.Find("The current branch") >= 0 &&
s.Find("has no upstream branch") >= 0;
break;
}
}
if (needsNewBranchPerm &&
LgiMsg(GetTree(), LLoadString(IDS_CREATE_NEW_BRANCH), AppName, MB_YESNO) == IDYES)
{
Push(true);
return false;
}
OnCmdError(s, "Push failed.");
}
else
{
switch (GetType())
{
case VcGit:
break;
case VcSvn:
break;
default:
break;
}
Unpushed = 0;
GetCss(true)->Color(LColour::Green);
Update();
Status = true;
}
GetTree()->SendNotify((LNotifyType)LvcCommandEnd);
return Status; // no reselect
}
void VcFolder::Pull(int AndUpdate, LoggingType Logging)
{
bool Status = false;
if (AndUpdate < 0)
AndUpdate = GetTree()->GetWindow()->GetCtrlValue(IDC_UPDATE) != 0;
switch (GetType())
{
case VcNone:
return;
case VcHg:
Status = StartCmd(AndUpdate ? "pull -u" : "pull", &VcFolder::ParsePull, NULL, Logging);
break;
case VcGit:
Status = StartCmd(AndUpdate ? "pull" : "fetch", &VcFolder::ParsePull, NULL, Logging);
break;
case VcSvn:
Status = StartCmd("up", &VcFolder::ParsePull, NULL, Logging);
break;
default:
OnCmdError(LString(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE));
break;
}
if (d->Tabs && Status)
{
d->Tabs->Value(1);
GetTree()->SendNotify((LNotifyType)LvcCommandStart);
}
}
bool VcFolder::ParsePull(int Result, LString s, ParseParams *Params)
{
GetTree()->SendNotify((LNotifyType)LvcCommandEnd);
if (Result)
{
OnCmdError(s, "Pull failed.");
return false;
}
else ClearError();
switch (GetType())
{
case VcGit:
{
// Git does a merge by default, so the current commit changes...
CurrentCommit.Empty();
break;
}
case VcHg:
{
CurrentCommit.Empty();
auto Lines = s.SplitDelimit("\n");
bool HasUpdates = false;
for (auto Ln: Lines)
{
if (Ln.Find("files updated") < 0)
continue;
auto Parts = Ln.Split(",");
for (auto p: Parts)
{
auto n = p.Strip().Split(" ", 1);
if (n.Length() == 2)
{
if (n[0].Int() > 0)
HasUpdates = true;
}
}
}
if (HasUpdates)
GetCss(true)->Color(LColour::Green);
else
GetCss(true)->Color(LCss::ColorInherit);
break;
}
case VcSvn:
{
// Svn also does a merge by default and can update our current position...
CurrentCommit.Empty();
LString::Array a = s.SplitDelimit("\r\n");
for (auto &Ln: a)
{
if (Ln.Find("At revision") >= 0)
{
LString::Array p = Ln.SplitDelimit(" .");
CurrentCommit = p.Last();
break;
}
else if (Ln.Find("svn cleanup") >= 0)
{
OnCmdError(s, "Needs cleanup");
break;
}
}
if (Params && Params->Str.Equals("log"))
{
LVariant Limit;
d->Opts.GetValue("svn-limit", Limit);
LString Args;
if (Limit.CastInt32() > 0)
Args.Printf("log --limit %i", Limit.CastInt32());
else
Args = "log";
IsLogging = StartCmd(Args, &VcFolder::ParseLog);
return false;
}
break;
}
default:
break;
}
CommitListDirty = true;
return true; // Yes - reselect and update
}
void VcFolder::MergeToLocal(LString Rev)
{
switch (GetType())
{
case VcGit:
{
LString Args;
Args.Printf("merge -m \"Merge with %s\" %s", Rev.Get(), Rev.Get());
StartCmd(Args, &VcFolder::ParseMerge, NULL, LogNormal);
break;
}
case VcHg:
{
LString Args;
Args.Printf("merge -r %s", Rev.Get());
StartCmd(Args, &VcFolder::ParseMerge, NULL, LogNormal);
break;
}
default:
LgiMsg(GetTree(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE), AppName);
break;
}
}
bool VcFolder::ParseMerge(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcGit:
case VcHg:
if (Result == 0)
CommitListDirty = true;
else
OnCmdError(s, LLoadString(IDS_ERR_MERGE_FAILED));
break;
default:
LAssert(!"Impl me.");
break;
}
return true;
}
void VcFolder::Refresh()
{
CommitListDirty = true;
CurrentCommit.Empty();
GitNames.Empty();
Branches.DeleteObjects();
if (Uncommit && Uncommit->LListItem::Select())
Uncommit->Select(true);
Select(true);
}
void VcFolder::Clean()
{
switch (GetType())
{
case VcSvn:
StartCmd("cleanup", &VcFolder::ParseClean, NULL, LogNormal);
break;
default:
LgiMsg(GetTree(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE), AppName);
break;
}
}
bool VcFolder::ParseClean(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcSvn:
if (Result == 0)
GetCss(true)->Color(LCss::ColorInherit);
break;
default:
LAssert(!"Impl me.");
break;
}
return false;
}
LColour VcFolder::BranchColour(const char *Name)
{
if (!Name)
return GetPaletteColour(0);
auto b = Branches.Find(Name);
if (!b) // Must be a new one?
{
int i = 1;
for (auto b: Branches)
{
auto &v = b.value;
if (!v->Colour.IsValid())
{
if (v->Default)
v->Colour = GetPaletteColour(0);
else
v->Colour = GetPaletteColour(i++);
}
}
Branches.Add(Name, b = new VcBranch(Name));
b->Colour = GetPaletteColour((int)Branches.Length());
}
return b ? b->Colour : GetPaletteColour(0);
}
void VcFolder::CurrentRev(std::function Callback)
{
LString Cmd;
Cmd.Printf("id -i");
RunCmd(Cmd, LogNormal, [Callback](auto r)
{
if (r.Code == 0)
Callback(r.Out.Strip());
});
}
bool VcFolder::RenameBranch(LString NewName, LArray &Revs)
{
switch (GetType())
{
case VcHg:
{
// Update to the ancestor of the commits
LHashTbl,int> Refs(0, -1);
for (auto c: Revs)
{
for (auto p:*c->GetParents())
if (Refs.Find(p) < 0)
Refs.Add(p, 0);
if (Refs.Find(c->GetRev()) >= 0)
Refs.Add(c->GetRev(), 1);
}
LString::Array Ans;
for (auto i:Refs)
{
if (i.value == 0)
Ans.Add(i.key);
}
LArray Ancestors = d->GetRevs(Ans);
if (Ans.Length() != 1)
{
// We should only have one ancestor
LString s, m;
s.Printf("Wrong number of ancestors: " LPrintfInt64 ".\n", Ans.Length());
for (auto i: Ancestors)
{
m.Printf("\t%s\n", i->GetRev());
s += m;
}
LgiMsg(GetTree(), s, AppName, MB_OK);
break;
}
LArray Top;
for (auto c:Revs)
{
for (auto p:*c->GetParents())
if (Refs.Find(p) == 0)
Top.Add(c);
}
if (Top.Length() != 1)
{
d->Log->Print("Error: Can't find top most commit. (%s:%i)\n", _FL);
return false;
}
// Create the new branch...
auto First = Ancestors.First();
LString Cmd;
Cmd.Printf("update -r " LPrintfInt64, First->GetIndex());
RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, &Top](auto r)
{
if (r.Code)
{
d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL);
return;
}
Cmd.Printf("branch \"%s\"", NewName.Get());
RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, &Top](auto r)
{
if (r.Code)
{
d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL);
return;
}
// Commit it to get a revision point to rebase to
Cmd.Printf("commit -m \"Branch: %s\"", NewName.Get());
RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, &Top](auto r)
{
if (r.Code)
{
d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL);
return;
}
CurrentRev([this, &Cmd, NewName, &Top](auto BranchNode)
{
// Rebase the old tree to this point
Cmd.Printf("rebase -s %s -d %s", Top.First()->GetRev(), BranchNode.Get());
RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, Top](auto r)
{
if (r.Code)
{
d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL);
return;
}
CommitListDirty = true;
d->Log->Print("Finished rename.\n", _FL);
});
});
});
});
});
break;
}
default:
{
LgiMsg(GetTree(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE), AppName);
break;
}
}
return true;
}
void VcFolder::GetVersion()
{
auto t = GetType();
switch (t)
{
case VcGit:
case VcSvn:
case VcHg:
case VcCvs:
StartCmd("--version", &VcFolder::ParseVersion, NULL, LogNormal);
break;
case VcPending:
break;
default:
OnCmdError(LString(), LLoadString(IDS_ERR_NO_VCS_FOUND));
break;
}
}
bool VcFolder::ParseVersion(int Result, LString s, ParseParams *Params)
{
if (Result)
return false;
auto p = s.SplitDelimit();
switch (GetType())
{
case VcGit:
{
if (p.Length() > 2)
{
ToolVersion[GetType()] = Ver2Int(p[2]);
printf("Git version: %s\n", p[2].Get());
}
else
LAssert(0);
break;
}
case VcSvn:
{
if (p.Length() > 2)
{
ToolVersion[GetType()] = Ver2Int(p[2]);
printf("Svn version: %s\n", p[2].Get());
}
else
LAssert(0);
break;
}
case VcHg:
{
if (p.Length() >= 5)
{
auto Ver = p[4].Strip("()");
ToolVersion[GetType()] = Ver2Int(Ver);
printf("Hg version: %s\n", Ver.Get());
}
break;
}
case VcCvs:
{
#ifdef _DEBUG
for (auto i : p)
printf("i='%s'\n", i.Get());
#endif
if (p.Length() > 1)
{
auto Ver = p[2];
ToolVersion[GetType()] = Ver2Int(Ver);
printf("Cvs version: %s\n", Ver.Get());
}
break;
}
default:
break;
}
return false;
}
bool VcFolder::ParseAddFile(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcCvs:
{
if (Result)
{
d->Tabs->Value(1);
OnCmdError(s, LLoadString(IDS_ERR_ADD_FAILED));
}
else ClearError();
break;
}
default:
break;
}
return false;
}
bool VcFolder::AddFile(const char *Path, bool AsBinary)
{
if (!Path)
return false;
switch (GetType())
{
case VcCvs:
{
auto p = LString(Path).RSplit(DIR_STR, 1);
ParseParams *params = NULL;
if (p.Length() >= 2)
{
if ((params = new ParseParams))
params->AltInitPath = p[0];
}
LString a;
a.Printf("add%s \"%s\"", AsBinary ? " -kb" : "", p.Length() > 1 ? p.Last().Get() : Path);
return StartCmd(a, &VcFolder::ParseAddFile, params);
break;
}
default:
{
OnCmdError(LString(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE));
break;
}
}
return false;
}
bool VcFolder::ParseRevert(int Result, LString s, ParseParams *Params)
{
if (GetType() == VcSvn)
{
if (s.Find("Skipped ") >= 0)
Result = 1; // Stupid svn... *sigh*
}
if (Result)
{
OnCmdError(s, LLoadString(IDS_ERR_REVERT_FAILED));
}
ListWorkingFolder();
return false;
}
bool VcFolder::Revert(LString::Array &Uris, const char *Revision)
{
if (Uris.Length() == 0)
return false;
switch (GetType())
{
case VcGit:
{
LStringPipe p;
p.Print("checkout");
for (auto u: Uris)
{
auto Path = GetFilePart(u);
p.Print(" \"%s\"", Path.Get());
}
auto a = p.NewLStr();
return StartCmd(a, &VcFolder::ParseRevert);
break;
}
case VcHg:
case VcSvn:
{
LStringPipe p;
if (Revision)
p.Print("up -r %s", Revision);
else
p.Print("revert");
for (auto u: Uris)
{
auto Path = GetFilePart(u);
p.Print(" \"%s\"", Path.Get());
}
auto a = p.NewLStr();
return StartCmd(a, &VcFolder::ParseRevert);
break;
}
default:
{
OnCmdError(LString(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE));
break;
}
}
return false;
}
bool VcFolder::ParseResolveList(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcHg:
{
auto lines = s.Replace("\r").Split("\n");
for (auto &ln: lines)
{
auto p = ln.Split(" ", 1);
if (p.Length() == 2)
{
if (p[0].Equals("U"))
{
auto f = new VcFile(d, this, LString(), true);
f->SetText(p[0], COL_STATE);
f->SetText(p[1], COL_FILENAME);
f->GetStatus();
d->Files->Insert(f);
}
}
}
break;
}
default:
{
OnCmdError(LString(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE));
break;
}
}
return true;
}
bool VcFolder::ParseResolve(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcGit:
{
break;
}
case VcHg:
{
d->Log->Print("Resolve: %s\n", s.Get());
break;
}
default:
{
OnCmdError(LString(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE));
break;
}
}
return true;
}
bool VcFolder::Resolve(const char *Path, LvcResolve Type)
{
if (!Path)
return false;
switch (GetType())
{
case VcGit:
{
LString a;
a.Printf("add \"%s\"", Path);
return StartCmd(a, &VcFolder::ParseResolve, new ParseParams(Path));
}
case VcHg:
{
LString a;
auto local = GetFilePart(Path);
switch (Type)
{
case ResolveMark:
a.Printf("resolve -m \"%s\"", local.Get());
break;
case ResolveUnmark:
a.Printf("resolve -u \"%s\"", local.Get());
break;
case ResolveLocal:
a.Printf("resolve -t internal:local \"%s\"", local.Get());
break;
case ResolveIncoming:
a.Printf("resolve -t internal:other \"%s\"", local.Get());
break;
default:
break;
}
if (a)
return StartCmd(a, &VcFolder::ParseResolve, new ParseParams(Path));
break;
}
case VcSvn:
case VcCvs:
default:
{
OnCmdError(LString(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE));
break;
}
}
return false;
}
bool VcFolder::ParseBlame(int Result, LString s, ParseParams *Params)
{
new BlameUi(d, GetType(), s);
return false;
}
bool VcFolder::Blame(const char *Path)
{
if (!Path)
return false;
LUri u(Path);
switch (GetType())
{
case VcGit:
{
LString a;
a.Printf("-P blame \"%s\"", u.sPath.Get());
return StartCmd(a, &VcFolder::ParseBlame);
break;
}
case VcHg:
{
LString a;
a.Printf("annotate -un \"%s\"", u.sPath.Get());
return StartCmd(a, &VcFolder::ParseBlame);
break;
}
case VcSvn:
{
LString a;
a.Printf("blame \"%s\"", u.sPath.Get());
return StartCmd(a, &VcFolder::ParseBlame);
break;
}
default:
{
OnCmdError(LString(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE));
break;
}
}
return true;
}
bool VcFolder::SaveFileAs(const char *Path, const char *Revision)
{
if (!Path || !Revision)
return false;
return true;
}
bool VcFolder::ParseSaveAs(int Result, LString s, ParseParams *Params)
{
return false;
}
bool VcFolder::ParseCounts(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcGit:
{
Unpushed = (int) s.Strip().Split("\n").Length();
break;
}
case VcSvn:
{
int64 ServerRev = 0;
bool HasUpdate = false;
LString::Array c = s.Split("\n");
for (unsigned i=0; i 1 && a[0].Equals("Status"))
ServerRev = a.Last().Int();
else if (a[0].Equals("*"))
HasUpdate = true;
}
if (ServerRev > 0 && HasUpdate)
{
int64 CurRev = CurrentCommit.Int();
Unpulled = (int) (ServerRev - CurRev);
}
else Unpulled = 0;
Update();
break;
}
default:
{
LAssert(!"Impl me.");
break;
}
}
IsUpdatingCounts = false;
Update();
return false; // No re-select
}
void VcFolder::SetEol(const char *Path, int Type)
{
if (!Path) return;
switch (Type)
{
case IDM_EOL_LF:
{
ConvertEol(Path, false);
break;
}
case IDM_EOL_CRLF:
{
ConvertEol(Path, true);
break;
}
case IDM_EOL_AUTO:
{
#ifdef WINDOWS
ConvertEol(Path, true);
#else
ConvertEol(Path, false);
#endif
break;
}
}
}
void VcFolder::UncommitedItem::Select(bool b)
{
LListItem::Select(b);
if (b)
{
LTreeItem *i = d->Tree->Selection();
VcFolder *f = dynamic_cast(i);
if (f)
f->ListWorkingFolder();
if (d->Msg)
{
d->Msg->Name(NULL);
auto *w = d->Msg->GetWindow();
if (w)
{
w->SetCtrlEnabled(IDC_COMMIT, true);
w->SetCtrlEnabled(IDC_COMMIT_AND_PUSH, true);
}
}
}
}
void VcFolder::UncommitedItem::OnPaint(LItem::ItemPaintCtx &Ctx)
{
LFont *f = GetList()->GetFont();
f->Transparent(false);
f->Colour(Ctx.Fore, Ctx.Back);
LDisplayString ds(f, "(working folder)");
ds.Draw(Ctx.pDC, Ctx.x1 + ((Ctx.X() - ds.X()) / 2), Ctx.y1 + ((Ctx.Y() - ds.Y()) / 2), &Ctx);
}
//////////////////////////////////////////////////////////////////////////////////////////
VcLeaf::VcLeaf(VcFolder *parent, LTreeItem *Item, LString uri, LString leaf, bool folder)
{
Parent = parent;
d = Parent->GetPriv();
LAssert(uri.Find("://") >= 0); // Is URI
Uri.Set(uri);
LAssert(Uri);
Leaf = leaf;
Folder = folder;
Tmp = NULL;
Item->Insert(this);
if (Folder)
{
Insert(Tmp = new LTreeItem);
Tmp->SetText("Loading...");
}
}
VcLeaf::~VcLeaf()
{
Log.DeleteObjects();
}
LString VcLeaf::Full()
{
LUri u = Uri;
u += Leaf;
return u.ToString();
}
void VcLeaf::OnBrowse()
{
auto full = Full();
LList *Files = d->Files;
Files->Empty();
LDirectory Dir;
for (int b = Dir.First(full); b; b = Dir.Next())
{
if (Dir.IsDir())
continue;
VcFile *f = new VcFile(d, Parent, LString(), true);
if (f)
{
f->SetUri(LString("file://") + full);
f->SetText(Dir.GetName(), COL_FILENAME);
Files->Insert(f);
}
}
Files->ResizeColumnsToContent();
if (Folder)
Parent->FolderStatus(full, this);
}
void VcLeaf::AfterBrowse()
{
}
VcLeaf *VcLeaf::FindLeaf(const char *Path, bool OpenTree)
{
if (!Stricmp(Path, Full().Get()))
return this;
if (OpenTree)
DoExpand();
VcLeaf *r = NULL;
for (auto n = GetChild(); !r && n; n = n->GetNext())
{
auto l = dynamic_cast(n);
if (l)
r = l->FindLeaf(Path, OpenTree);
}
return r;
}
void VcLeaf::DoExpand()
{
if (Tmp)
{
Tmp->Remove();
DeleteObj(Tmp);
Parent->ReadDir(this, Full());
}
}
void VcLeaf::OnExpand(bool b)
{
if (b)
DoExpand();
}
const char *VcLeaf::GetText(int Col)
{
if (Col == 0)
return Leaf;
return NULL;
}
int VcLeaf::GetImage(int Flags)
{
return Folder ? IcoFolder : IcoFile;
}
int VcLeaf::Compare(VcLeaf *b)
{
// Sort folders to the top...
if (Folder ^ b->Folder)
return (int)b->Folder - (int)Folder;
// Then alphabetical
return Stricmp(Leaf.Get(), b->Leaf.Get());
}
bool VcLeaf::Select()
{
return LTreeItem::Select();
}
void VcLeaf::Select(bool b)
{
LTreeItem::Select(b);
if (b)
{
d->Commits->RemoveAll();
OnBrowse();
ShowLog();
}
}
void VcLeaf::ShowLog()
{
if (Log.Length())
{
d->Commits->RemoveAll();
Parent->DefaultFields();
Parent->UpdateColumns();
for (auto i: Log)
d->Commits->Insert(i);
}
}
void VcLeaf::OnMouseClick(LMouse &m)
{
if (m.IsContextMenu())
{
LSubMenu s;
s.AppendItem("Log", IDM_LOG);
s.AppendItem("Blame", IDM_BLAME, !Folder);
s.AppendSeparator();
s.AppendItem("Browse To", IDM_BROWSE_FOLDER);
s.AppendItem("Terminal At", IDM_TERMINAL);
int Cmd = s.Float(GetTree(), m - _ScrollPos());
switch (Cmd)
{
case IDM_LOG:
{
Parent->LogFile(Full());
break;
}
case IDM_BLAME:
{
Parent->Blame(Full());
break;
}
case IDM_BROWSE_FOLDER:
{
LBrowseToFile(Full());
break;
}
case IDM_TERMINAL:
{
TerminalAt(Full());
break;
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////
ProcessCallback::ProcessCallback(LString exe,
LString args,
LString localPath,
LTextLog *log,
LView *view,
std::function callback) :
Log(log),
View(view),
Callback(callback),
LThread("ProcessCallback.Thread"),
LSubProcess(exe, args)
{
SetInitFolder(localPath);
if (Log)
Log->Print("%s %s\n", exe.Get(), args.Get());
Run();
}
int ProcessCallback::Main()
{
if (!Start())
{
Ret.Out.Printf("Process failed with %i", GetErrorCode());
Callback(Ret);
}
else
{
while (IsRunning())
{
auto Rd = Read();
if (Rd.Length())
{
Ret.Out += Rd;
if (Log)
Log->Write(Rd.Get(), Rd.Length());
}
}
auto Rd = Read();
if (Rd.Length())
{
Ret.Out += Rd;
if (Log)
Log->Write(Rd.Get(), Rd.Length());
}
Ret.Code = GetExitValue();
}
View->PostEvent(M_HANDLE_CALLBACK, (LMessage::Param)this);
return 0;
}
void ProcessCallback::OnComplete() // Called in the GUI thread...
{
Callback(Ret);
}
diff --git a/Lvc/src/VcFolder.h b/Lvc/src/VcFolder.h
--- a/Lvc/src/VcFolder.h
+++ b/Lvc/src/VcFolder.h
@@ -1,376 +1,377 @@
#ifndef _VcFolder_h_
#define _VcFolder_h_
#include "lgi/common/SubProcess.h"
#include "lgi/common/Uri.h"
#include
class VcLeaf;
enum LoggingType
{
LogNone, // No output from cmd
LogNormal, // Output appears as it's available
LogSilo, // Output appears after cmd finished (keeps it non-interleaved with other log msgs)
};
enum LvcError
{
ErrNone,
ErrSubProcessFailed = GSUBPROCESS_ERROR,
};
enum LvcStatus
{
StatusNone,
StatusActive,
StatusError,
};
enum LvcResolve
{
ResolveNone,
//---
ResolveMark,
ResolveUnmark,
//---
ResolveLocal,
ResolveIncoming,
ResolveTool,
};
class ReaderThread : public LThread
{
VersionCtrl Vcs;
LStream *Out;
LAutoPtr Process;
int FilterCount;
int OnLine(char *s, ssize_t len);
bool OnData(char *Buf, ssize_t &r);
public:
int Result;
ReaderThread(VersionCtrl vcs, LAutoPtr p, LStream *out);
~ReaderThread();
int Main();
};
extern int Ver2Int(LString v);
extern int ToolVersion[VcMax];
extern int LstCmp(LListItem *a, LListItem *b, int Col);
struct Result
{
int Code;
LString Out;
};
struct ProcessCallback : public LThread, public LSubProcess
{
Result Ret;
LView *View = NULL;
LTextLog *Log = NULL;
std::function Callback;
public:
ProcessCallback(LString exe,
LString args,
LString localPath,
LTextLog *log,
LView *view,
std::function callback);
int Main();
void OnComplete();
};
struct VcBranch : public LString
{
bool Default;
LColour Colour;
LString Hash;
VcBranch(LString name, LString hash = LString())
{
Default = name.Equals("default") ||
name.Equals("trunk") ||
name.Equals("main");
Set(name);
if (hash)
Hash = hash;
}
};
struct SshParams
{
SshConnection *c;
VcFolder *f;
LString Exe, Args, Path, Output;
VersionCtrl Vcs;
ParseFn Parser;
ParseParams *Params;
int ExitCode;
SshParams(SshConnection *con) : c(con)
{
f = NULL;
Parser = NULL;
Params = NULL;
Vcs = VcNone;
ExitCode = -1;
}
};
class VcFolder : public LTreeItem
{
friend class VcCommit;
class Cmd : public LStream
{
LString::Array Context;
LStringPipe Buf;
public:
LoggingType Logging;
LStream *Log;
LAutoPtr Rd;
ParseFn PostOp;
LAutoPtr Params;
LvcError Err;
Cmd(LString::Array &context, LoggingType logging, LStream *log)
{
Context = context;
Logging = logging;
Log = log;
Err = ErrNone;
}
~Cmd()
{
}
LString GetBuf()
{
LString s = Buf.NewLStr();
if (Log && Logging == LogSilo)
{
LString m;
m.Printf("=== %s ===\n\t%s %s\n",
Context[0].Get(),
Context[1].Get(), Context[2].Get());
Log->Write(m.Get(), m.Length());
auto Lines = s.Split("\n");
for (auto Ln : Lines)
Log->Print("\t%s\n", Ln.Get());
}
return s;
}
ssize_t Write(const void *Ptr, ssize_t Size, int Flags = 0)
{
ssize_t Wr = Buf.Write(Ptr, Size, Flags);
if (Log && Logging == LogNormal)
Log->Write(Ptr, Size, Flags);
if (Flags)
Err = (LvcError) Flags;
return Wr;
}
};
class UncommitedItem : public LListItem
{
AppPriv *d;
public:
UncommitedItem(AppPriv *priv)
{
d = priv;
}
void OnPaint(LItem::ItemPaintCtx &Ctx);
void Select(bool b);
};
AppPriv *d;
VersionCtrl Type;
LUri Uri;
LString CurrentCommit, RepoUrl, VcCmd;
int64 CurrentCommitIdx;
LArray Log;
LString CurrentBranch;
LHashTbl,VcBranch*> Branches;
LAutoPtr Uncommit;
LString Cache, NewRev;
bool CommitListDirty = false;
int Unpushed = 0, Unpulled = 0;
LString CountCache;
LTreeItem *Tmp = NULL;
int CmdErrors = 0;
LArray Fields;
// Git specific
LHashTbl,LString> GitNames;
void AddGitName(LString Hash, LString Name);
LString GetGitNames(LString Hash);
static int CmdMaxThreads;
static int CmdActiveThreads;
struct GitCommit
{
LString::Array Files;
LString Msg, Branch;
ParseParams *Param;
GitCommit()
{
Param = NULL;
}
};
LAutoPtr PostAdd;
void GitAdd();
LArray Cmds;
bool IsLogging, IsUpdate, IsFilesCmd, IsCommit, IsUpdatingCounts;
LvcStatus IsBranches, IsIdent;
void Init(AppPriv *priv);
const char *GetVcName();
+ char GetPathSep();
bool StartCmd(const char *Args, ParseFn Parser = NULL, ParseParams *Params = NULL, LoggingType Logging = LogNone);
bool RunCmd(const char *Args, LoggingType Logging, std::function Callback);
void OnBranchesChange();
void OnCmdError(LString Output, const char *Msg);
void ClearError();
VcFile *FindFile(const char *Path);
void LinkParents();
void CurrentRev(std::function Callback);
LColour BranchColour(const char *Name);
bool ParseDiffs(LString s, LString Rev, bool IsWorking);
bool ParseRevList(int Result, LString s, ParseParams *Params);
bool ParseLog(int Result, LString s, ParseParams *Params);
bool ParseInfo(int Result, LString s, ParseParams *Params);
bool ParseFiles(int Result, LString s, ParseParams *Params);
bool ParseWorking(int Result, LString s, ParseParams *Params);
bool ParseUpdate(int Result, LString s, ParseParams *Params);
bool ParseCommit(int Result, LString s, ParseParams *Params);
bool ParseGitAdd(int Result, LString s, ParseParams *Params);
bool ParsePush(int Result, LString s, ParseParams *Params);
bool ParsePull(int Result, LString s, ParseParams *Params);
bool ParseCounts(int Result, LString s, ParseParams *Params);
bool ParseRevert(int Result, LString s, ParseParams *Params);
bool ParseResolveList(int Result, LString s, ParseParams *Params);
bool ParseResolve(int Result, LString s, ParseParams *Params);
bool ParseBlame(int Result, LString s, ParseParams *Params);
bool ParseSaveAs(int Result, LString s, ParseParams *Params);
bool ParseBranches(int Result, LString s, ParseParams *Params);
bool ParseStatus(int Result, LString s, ParseParams *Params);
bool ParseAddFile(int Result, LString s, ParseParams *Params);
bool ParseVersion(int Result, LString s, ParseParams *Params);
bool ParseClean(int Result, LString s, ParseParams *Params);
bool ParseDiff(int Result, LString s, ParseParams *Params);
bool ParseMerge(int Result, LString s, ParseParams *Params);
bool ParseCountToTip(int Result, LString s, ParseParams *Params);
bool ParseUpdateSubs(int Result, LString s, ParseParams *Params);
bool ParseRemoteFind(int Result, LString s, ParseParams *Params);
bool ParseStartBranch(int Result, LString s, ParseParams *Params);
void DoExpand();
public:
VcFolder(AppPriv *priv, const char *uri);
VcFolder(AppPriv *priv, LXmlTag *t);
~VcFolder();
VersionCtrl GetType();
AppPriv *GetPriv() { return d; }
const char *LocalPath();
LUri GetUri() { return Uri; }
VcLeaf *FindLeaf(const char *Path, bool OpenTree);
void DefaultFields();
void UpdateColumns();
const char *GetText(int Col);
LArray &GetFields() { return Fields; }
bool Serialize(LXmlTag *t, bool Write);
LXmlTag *Save();
void Empty();
void Select(bool b);
void ListCommit(VcCommit *c);
void ListWorkingFolder();
void FolderStatus(const char *Path = NULL, VcLeaf *Notify = NULL);
void Commit(const char *Msg, const char *Branch, bool AndPush);
void StartBranch(const char *BranchName, const char *OnCreated = NULL);
void Push(bool NewBranchOk = false);
void Pull(int AndUpdate = -1, LoggingType Logging = LogNormal);
void Clean();
bool Revert(LString::Array &uris, const char *Revision = NULL);
bool Resolve(const char *Path, LvcResolve Type);
bool AddFile(const char *Path, bool AsBinary = true);
bool Blame(const char *Path);
bool SaveFileAs(const char *Path, const char *Revision);
void ReadDir(LTreeItem *Parent, const char *Uri);
void SetEol(const char *Path, int Type);
void GetVersion();
void Diff(VcFile *file);
void DiffRange(const char *FromRev, const char *ToRev);
void MergeToLocal(LString Rev);
bool RenameBranch(LString NewName, LArray &Revs);
void Refresh();
bool GetBranches(ParseParams *Params = NULL);
void GetCurrentRevision(ParseParams *Params = NULL);
void CountToTip();
bool UpdateSubs(); // Clone/checkout any sub-repositries.
void LogFile(const char *Path);
LString GetFilePart(const char *uri);
void FilterCurrentFiles();
void GetRemoteUrl(std::function Callback);
void OnPulse();
void OnUpdate(const char *Rev);
void OnMouseClick(LMouse &m);
void OnRemove();
void OnExpand(bool b);
void OnVcsType(LString errorMsg);
void OnSshCmd(SshParams *p);
};
class VcLeaf : public LTreeItem
{
AppPriv *d;
VcFolder *Parent;
bool Folder;
LUri Uri;
LString Leaf;
LTreeItem *Tmp;
void DoExpand();
public:
LArray Log;
VcLeaf(VcFolder *parent, LTreeItem *Item, LString uri, LString leaf, bool folder);
~VcLeaf();
LString Full();
VcLeaf *FindLeaf(const char *Path, bool OpenTree);
void OnBrowse();
void AfterBrowse();
void OnExpand(bool b);
const char *GetText(int Col);
int GetImage(int Flags);
int Compare(VcLeaf *b);
bool Select();
void Select(bool b);
void OnMouseClick(LMouse &m);
void ShowLog();
};
#endif
diff --git a/Lvc/win/Lvc.vcxproj b/Lvc/win/Lvc.vcxproj
--- a/Lvc/win/Lvc.vcxproj
+++ b/Lvc/win/Lvc.vcxproj
@@ -1,218 +1,217 @@
Debug
Win32
Debug
x64
Release
Win32
Release
x64
{28000ACC-67F6-4A82-B779-A24F065FACF4}
Lvc
Win32Proj
10.0
Application
v142
Unicode
true
Application
v142
Unicode
true
Application
v142
Unicode
Application
v142
Unicode
<_ProjectFileVersion>12.0.30501.0
$(Platform)$(Configuration)14\
$(Platform)$(Configuration)14\
true
$(Platform)$(Configuration)14\
$(Platform)$(Configuration)14\
true
$(Platform)$(Configuration)14\
$(Platform)$(Configuration)14\
false
$(Platform)$(Configuration)14\
$(Platform)$(Configuration)14\
false
Disabled
..\..\include\common;..\..\include\win32;Src;..\..\..\..\..\CodeLib\libpng\build32;..\..\..\..\..\CodeLib\libpng;%(AdditionalIncludeDirectories)
WIN32;_DEBUG;WINDOWS;%(PreprocessorDefinitions)
true
EnableFastChecks
MultiThreadedDebugDLL
Level3
ProgramDatabase
imm32.lib;Ws2_32.lib;%(AdditionalDependencies)
true
Windows
false
MachineX86
Disabled
..\src;..\resources;..\..\include;..\..\include\lgi\win;..\..\..\..\..\CodeLib\libssh\include;..\..\..\..\..\CodeLib\libpng;..\..\..\..\..\CodeLib\libpng\build64;%(AdditionalIncludeDirectories)
WIN32;_DEBUG;WINDOWS;%(PreprocessorDefinitions)
EnableFastChecks
MultiThreadedDebugDLL
Level3
ProgramDatabase
ssh.lib;imm32.lib;Ws2_32.lib;%(AdditionalDependencies)
true
Windows
false
..\..\..\..\..\CodeLib\libssh\build64\src\Debug;%(AdditionalLibraryDirectories)
..\..\include\common;..\..\include\win32;Src;..\..\..\..\..\CodeLib\libpng\build32;..\..\..\..\..\CodeLib\libpng;%(AdditionalIncludeDirectories)
WIN32;NDEBUG;WINDOWS;%(PreprocessorDefinitions)
MultiThreadedDLL
Level3
ProgramDatabase
imm32.lib;Ws2_32.lib;%(AdditionalDependencies)
true
Windows
true
true
false
MachineX86
..\src;..\resources;..\..\include;..\..\include\lgi\win;..\..\..\..\..\CodeLib\libssh\include;..\..\..\..\..\CodeLib\libpng;..\..\..\..\..\CodeLib\libpng\build64;%(AdditionalIncludeDirectories)
WIN32;NDEBUG;WINDOWS;%(PreprocessorDefinitions)
MultiThreadedDLL
Level3
ProgramDatabase
ssh.lib;imm32.lib;Ws2_32.lib;%(AdditionalDependencies)
true
Windows
true
true
false
..\..\..\..\..\CodeLib\libssh\build64\src\Release;%(AdditionalLibraryDirectories)
-
{95df9ca4-6d37-4a85-a648-80c2712e0da1}
\ No newline at end of file
diff --git a/Lvc/win/Lvc.vcxproj.filters b/Lvc/win/Lvc.vcxproj.filters
--- a/Lvc/win/Lvc.vcxproj.filters
+++ b/Lvc/win/Lvc.vcxproj.filters
@@ -1,108 +1,105 @@
{4FC737F1-C7A5-4376-A066-2A32D752A2FF}
cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
{fa200907-15ff-4b7a-a2c4-77c17b81b307}
{ceabde46-f0f5-4728-929b-43ebf21b9fa0}
{699395fd-5c52-485b-aee9-12ca42fecba2}
{954c3d3b-a151-4c83-b7cc-c36301353e1b}
{9ebbbfd3-1106-4ecf-b05d-f0366063de49}
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
Source Files
-
- Source Files
-
Source Files
Source Files
Source Files
Source Files
+
+ Source Files\Lgi
+
+
+ Source Files\Lgi
+
+
+ Source Files\PatchViewer
+
+
+ Source Files\Lgi
+
+
+ Source Files\Lgi
+
+
+ Source Files\Lgi
+
+
+ Source Files\Lgi
+
Source Files\Ssh
Source Files\StructuredLog
Source Files\StructuredLog
Source Files
Source Files
Source Files
Source Files
Resources
Resources
Resources
Resources
\ No newline at end of file