diff --git a/Lvc/Resources/Lvc.lr8 b/Lvc/Resources/Lvc.lr8
--- a/Lvc/Resources/Lvc.lr8
+++ b/Lvc/Resources/Lvc.lr8
@@ -1,172 +1,177 @@
diff --git a/Lvc/Resources/resdefs.h b/Lvc/Resources/resdefs.h
--- a/Lvc/Resources/resdefs.h
+++ b/Lvc/Resources/resdefs.h
@@ -1,34 +1,36 @@
-// This file generated by LgiRes
-
-#define IDC_COMMIT_AND_PUSH 3
-#define IDC_BRANCH 11
-#define IDC_BRANCH_DROPDOWN 14
-#define IDC_MSG 16
-#define IDD_OPTIONS 22
-#define IDC_23 23
-#define IDC_SVN 25
-#define IDC_SVN_BROWSE 26
-#define IDC_GIT 28
-#define IDC_GIT_BROWSE 29
-#define IDC_HG 32
-#define IDC_CVS 33
-#define IDC_HG_BROWSE 36
-#define IDC_CVS_BROWSE 37
-#define IDC_UNTRACKED 38
-#define IDC_PULL_ALL 39
-#define IDC_STATUS 40
-#define IDC_TABLE 500
-#define IDC_COMMIT 501
-#define IDC_PULL 502
-#define IDC_PUSH 503
-#define IDC_COMMIT_TABLE 504
-#define IDC_FILTER 505
-#define IDC_CLEAR_FILTER 506
-#define IDD_COMMIT 507
-#define IDD_TOOLBAR 508
-#define IDC_OPEN 509
-#define IDM_FILE 510
-#define IDM_OPTIONS 511
-#define IDM_HELP 512
-#define IDM_ABOUT 513
-#define IDC_PUSH_ALL 514
+// This file generated by LgiRes
+
+#define IDC_COMMIT_AND_PUSH 3
+#define IDC_BRANCH 11
+#define IDC_BRANCH_DROPDOWN 14
+#define IDC_MSG 16
+#define IDD_OPTIONS 22
+#define IDC_23 23
+#define IDC_SVN 25
+#define IDC_SVN_BROWSE 26
+#define IDC_GIT 28
+#define IDC_GIT_BROWSE 29
+#define IDC_HG 32
+#define IDC_CVS 33
+#define IDC_HG_BROWSE 36
+#define IDC_CVS_BROWSE 37
+#define IDC_UNTRACKED 38
+#define IDC_PULL_ALL 39
+#define IDC_STATUS 40
+#define IDC_TABLE 500
+#define IDC_COMMIT 501
+#define IDC_PULL 502
+#define IDC_PUSH 503
+#define IDC_COMMIT_TABLE 504
+#define IDC_FILTER 505
+#define IDC_CLEAR_FILTER 506
+#define IDD_COMMIT 507
+#define IDD_TOOLBAR 508
+#define IDC_OPEN 509
+#define IDM_FILE 510
+#define IDM_OPTIONS 511
+#define IDM_HELP 512
+#define IDM_ABOUT 513
+#define IDC_PUSH_ALL 514
+#define IDM_EDIT_MENU 515
+#define IDM_FIND 516
diff --git a/Lvc/Src/Main.cpp b/Lvc/Src/Main.cpp
--- a/Lvc/Src/Main.cpp
+++ b/Lvc/Src/Main.cpp
@@ -1,690 +1,710 @@
#include "Lgi.h"
#include "../Resources/resdefs.h"
#include "Lvc.h"
#include "GTableLayout.h"
#ifdef WINDOWS
#include "../Resources/resource.h"
#endif
#include "GTextLog.h"
#include "GButton.h"
#include "GXmlTreeUi.h"
//////////////////////////////////////////////////////////////////
const char *AppName = "Lvc";
VersionCtrl DetectVcs(const char *Path)
{
char p[MAX_PATH];
if (!Path)
return VcNone;
if (LgiMakePath(p, sizeof(p), Path, ".git") &&
DirExists(p))
return VcGit;
if (LgiMakePath(p, sizeof(p), Path, ".svn") &&
DirExists(p))
return VcSvn;
if (LgiMakePath(p, sizeof(p), Path, ".hg") &&
DirExists(p))
return VcHg;
if (LgiMakePath(p, sizeof(p), Path, "CVS") &&
DirExists(p))
return VcCvs;
return VcNone;
}
class DiffView : public GTextLog
{
public:
DiffView(int id) : GTextLog(id)
{
}
void PourStyle(size_t Start, ssize_t Length)
{
for (auto ln : GTextView3::Line)
{
if (!ln->c.IsValid())
{
char16 *t = Text + ln->Start;
if (*t == '+')
{
ln->c = GColour::Green;
ln->Back.Rgb(245, 255, 245);
}
else if (*t == '-')
{
ln->c = GColour::Red;
ln->Back.Rgb(255, 245, 245);
}
else if (*t == '@')
{
ln->c.Rgb(128, 128, 128);
ln->Back.Rgb(235, 235, 235);
}
else
ln->c.Set(LC_TEXT, 24);
}
}
}
};
class ToolBar : public GLayout, public GLgiRes
{
public:
ToolBar()
{
GAutoString Name;
GRect Pos;
if (LoadFromResource(IDD_TOOLBAR, this, &Pos, &Name))
{
GTableLayout *v;
if (GetViewById(IDC_TABLE, v))
{
GRect r = v->GetPos();
r.Offset(-r.x1, -r.y1);
r.x2++;
v->SetPos(r);
v->OnPosChange();
r = v->GetUsedArea();
if (r.Y() <= 1)
r.Set(0, 0, 30, 30);
GetCss(true)->Height(GCss::Len(GCss::LenPx, (float)r.Y()+3));
}
else LgiAssert(!"Missing table ctrl");
}
else LgiAssert(!"Missing toolbar resource");
}
void OnCreate()
{
AttachChildren();
}
void OnPaint(GSurface *pDC)
{
pDC->Colour(LC_MED, 24);
pDC->Rectangle();
}
};
class CommitCtrls : public GLayout, public GLgiRes
{
public:
CommitCtrls()
{
GAutoString Name;
GRect Pos;
if (LoadFromResource(IDD_COMMIT, this, &Pos, &Name))
{
GTableLayout *v;
if (GetViewById(IDC_COMMIT_TABLE, v))
{
v->GetCss(true)->PaddingRight("8px");
GRect r = v->GetPos();
r.Offset(-r.x1, -r.y1);
r.x2++;
v->SetPos(r);
v->OnPosChange();
r = v->GetUsedArea();
if (r.Y() <= 1)
r.Set(0, 0, 30, 30);
GetCss(true)->Height(GCss::Len(GCss::LenPx, (float)r.Y()));
}
else LgiAssert(!"Missing table ctrl");
}
else LgiAssert(!"Missing toolbar resource");
}
void OnPosChange()
{
GTableLayout *v;
if (GetViewById(IDC_COMMIT_TABLE, v))
v->SetPos(GetClient());
}
void OnCreate()
{
AttachChildren();
}
};
GString::Array GetProgramsInPath(const char *Program)
{
GString::Array Bin;
GString Prog = Program;
#ifdef WINDOWS
Prog += LGI_EXECUTABLE_EXT;
#endif
GString::Array a = LGetPath();
for (auto p : a)
{
GFile::Path c(p, Prog);
if (c.Exists())
Bin.New() = c.GetFull();
}
return Bin;
}
class OptionsDlg : public GDialog, public GXmlTreeUi
{
GOptionsFile &Opts;
public:
OptionsDlg(GViewI *Parent, GOptionsFile &opts) : Opts(opts)
{
SetParent(Parent);
Map("svn-path", IDC_SVN, GV_STRING);
Map("git-path", IDC_GIT, GV_STRING);
Map("hg-path", IDC_HG, GV_STRING);
Map("cvs-path", IDC_CVS, GV_STRING);
if (LoadFromResource(IDD_OPTIONS))
{
MoveSameScreen(Parent);
Convert(&Opts, this, true);
}
}
void Browse(int EditId)
{
GFileSelect s;
s.Parent(this);
if (s.Open())
{
SetCtrlName(EditId, s.Name());
}
}
void BrowseFiles(GViewI *Ctrl, const char *Bin, int EditId)
{
GRect Pos = Ctrl->GetPos();
GdcPt2 Pt(Pos.x1, Pos.y2 + 1);
PointToScreen(Pt);
GSubMenu s;
GString::Array Bins = GetProgramsInPath(Bin);
for (unsigned i=0; i= 1000)
{
GString Bin = Bins[Cmd - 1000];
if (Bin)
SetCtrlName(EditId, Bin);
}
break;
}
}
}
int OnNotify(GViewI *Ctrl, int Flags)
{
switch (Ctrl->GetId())
{
case IDC_SVN_BROWSE:
BrowseFiles(Ctrl, "svn", IDC_SVN);
break;
case IDC_GIT_BROWSE:
BrowseFiles(Ctrl, "git", IDC_GIT);
break;
case IDC_HG_BROWSE:
BrowseFiles(Ctrl, "hg", IDC_HG);
break;
case IDC_CVS_BROWSE:
BrowseFiles(Ctrl, "cvs", IDC_CVS);
break;
case IDOK:
Convert(&Opts, this, false);
// fall
case IDCANCEL:
{
EndModal(Ctrl->GetId() == IDOK);
break;
}
}
return GDialog::OnNotify(Ctrl, Flags);
}
};
class CommitList : public LList
{
public:
CommitList(int id) : LList(id, 0, 0, 200, 200)
{
}
+ void SelectRevisions(GString::Array &Revs)
+ {
+ VcCommit *Scroll = NULL;
+ for (auto it = begin(); it != end(); it++)
+ {
+ VcCommit *item = dynamic_cast(*it);
+ if (item)
+ {
+ for (auto r: Revs)
+ {
+ if (item->IsRev(r))
+ {
+ if (!Scroll)
+ Scroll = item;
+ item->Select(true);
+ }
+ }
+ }
+ }
+ if (Scroll)
+ Scroll->ScrollTo();
+ }
+
bool OnKey(GKey &k)
{
switch (k.c16)
{
case 'p':
case 'P':
{
if (k.Down())
{
GArray Sel;
GetSelection(Sel);
if (Sel.Length())
{
auto p = Sel[0]->GetParents();
if (p->Length() == 0)
break;
for (auto c:Sel)
c->Select(false);
- VcCommit *Scroll = NULL;
- for (auto it = begin(); it != end(); it++)
- {
- VcCommit *item = dynamic_cast(*it);
- if (item &&
- !Sel.HasItem(item))
- {
- for (auto r:*p)
- if (item->IsRev(r))
- {
- if (!Scroll)
- Scroll = item;
- item->Select(true);
- }
- }
- }
- if (Scroll)
- Scroll->ScrollTo();
+ SelectRevisions(*p);
}
}
return true;
}
}
return LList::OnKey(k);
}
};
class App : public GWindow, public AppPriv
{
GAutoPtr ImgLst;
public:
App()
{
GString AppRev;
AppRev.Printf("%s v%s", AppName, APP_VERSION);
Name(AppRev);
GRect r(0, 0, 1400, 800);
SetPos(r);
MoveToCenter();
SetQuitOnClose(true);
Opts.SerializeFile(false);
SerializeState(&Opts, "WndPos", true);
#ifdef WINDOWS
SetIcon(MAKEINTRESOURCEA(IDI_ICON1));
#else
SetIcon("icon32.png");
#endif
ImgLst.Reset(LgiLoadImageList("image-list.png", 16, 16));
if (Attach(0))
{
if ((Menu = new GMenu))
{
Menu->SetPrefAndAboutItems(IDM_OPTIONS, IDM_ABOUT);
Menu->Attach(this);
Menu->Load(this, "IDM_MENU");
}
GBox *ToolsBox = new GBox(IDC_TOOLS_BOX, true, "ToolsBox");
GBox *FoldersBox = new GBox(IDC_FOLDERS_BOX, false, "FoldersBox");
GBox *CommitsBox = new GBox(IDC_COMMITS_BOX, true, "CommitsBox");
ToolBar *Tools = new ToolBar;
ToolsBox->Attach(this);
Tools->Attach(ToolsBox);
FoldersBox->Attach(ToolsBox);
Tree = new GTree(IDC_TREE, 0, 0, 200, 200);
Tree->GetCss(true)->Width(GCss::Len("320px"));
Tree->ShowColumnHeader(true);
Tree->AddColumn("Folder", 250);
Tree->AddColumn("Counts", 50);
Tree->Attach(FoldersBox);
Tree->SetImageList(ImgLst, false);
CommitsBox->Attach(FoldersBox);
Lst = new CommitList(IDC_LIST);
Lst->Attach(CommitsBox);
Lst->GetCss(true)->Height("40%");
Lst->AddColumn("---", 40);
Lst->AddColumn("Commit", 270);
Lst->AddColumn("Author", 240);
Lst->AddColumn("Date", 130);
Lst->AddColumn("Message", 400);
GBox *FilesBox = new GBox(IDC_FILES_BOX, false);
FilesBox->Attach(CommitsBox);
Files = new LList(IDC_FILES, 0, 0, 200, 200);
Files->GetCss(true)->Width("35%");
Files->Attach(FilesBox);
Files->AddColumn("[ ]", 30);
Files->AddColumn("State", 100);
Files->AddColumn("Name", 400);
GBox *MsgBox = new GBox(IDC_MSG_BOX, true);
MsgBox->Attach(FilesBox);
CommitCtrls *Commit = new CommitCtrls;
Commit->Attach(MsgBox);
Commit->GetCss(true)->Height("25%");
if (Commit->GetViewById(IDC_MSG, Msg))
{
GTextView3 *Tv = dynamic_cast(Msg);
if (Tv)
{
Tv->Sunken(true);
Tv->SetWrapType(TEXTED_WRAP_NONE);
}
}
else LgiAssert(!"No ctrl?");
Tabs = new GTabView(IDC_TAB_VIEW);
Tabs->Attach(MsgBox);
const char *Style = "Padding: 0px 8px 8px 0px";
Tabs->GetCss(true)->Parse(Style);
GTabPage *p = Tabs->Append("Diff");
p->Append(Diff = new DiffView(IDC_TXT));
// Diff->Sunken(true);
Diff->SetWrapType(TEXTED_WRAP_NONE);
p = Tabs->Append("Log");
p->Append(Log = new GTextLog(IDC_LOG));
// Log->Sunken(true);
Log->SetWrapType(TEXTED_WRAP_NONE);
AttachChildren();
Visible(true);
}
GXmlTag *f = Opts.LockTag(OPT_Folders, _FL);
if (!f)
{
Opts.CreateTag(OPT_Folders);
f = Opts.LockTag(OPT_Folders, _FL);
}
if (f)
{
bool Req[VcMax] = {0};
for (GXmlTag *c = f->Children.First(); c; c = f->Children.Next())
{
if (c->IsTag(OPT_Folder))
{
auto f = new VcFolder(this, c);
Tree->Insert(f);
if (!Req[f->GetType()])
{
Req[f->GetType()] = true;
f->GetVersion();
}
}
}
Opts.Unlock();
}
SetPulse(200);
}
~App()
{
SerializeState(&Opts, "WndPos", false);
GXmlTag *f = Opts.LockTag(OPT_Folders, _FL);
if (f)
{
f->EmptyChildren();
VcFolder *vcf = NULL; bool b;
while ((b = Tree->Iterate(vcf)))
f->InsertTag(vcf->Save());
Opts.Unlock();
}
Opts.SerializeFile(true);
}
int OnCommand(int Cmd, int Event, OsView Wnd)
{
switch (Cmd)
{
case IDM_OPTIONS:
{
OptionsDlg Dlg(this, Opts);
Dlg.DoModal();
break;
}
+ case IDM_FIND:
+ {
+ GInput i(this, "", "Search string:");
+ if (i.DoModal())
+ {
+ GString::Array Revs;
+ Revs.Add(i.GetStr());
+
+ CommitList *cl;
+ if (GetViewById(IDC_LIST, cl))
+ cl->SelectRevisions(Revs);
+ }
+ break;
+ }
}
return 0;
}
void OnPulse()
{
VcFolder *vcf = NULL; bool b;
while ((b = Tree->Iterate(vcf)))
vcf->OnPulse();
}
void OpenFolder()
{
GFileSelect s;
s.Parent(this);
if (s.OpenFolder())
{
if (DetectVcs(s.Name()))
Tree->Insert(new VcFolder(this, s.Name()));
else
LgiMsg(this, "Folder not under version control.", AppName);
}
}
int OnNotify(GViewI *c, int flag)
{
switch (c->GetId())
{
case IDC_FILES:
{
switch (flag)
{
case GNotifyItem_ColumnClicked:
{
int Col = -1;
GMouse m;
if (Files->GetColumnClickInfo(Col, m))
{
if (Col == 0)
{
// Select / deselect all checkboxes..
List n;
if (Files->GetAll(n))
{
bool Checked = false;
for (VcFile *f = n.First(); f; f = n.Next())
Checked |= f->Checked() > 0;
for (VcFile *f = n.First(); f; f = n.Next())
f->Checked(Checked ? 0 : 1);
}
}
}
break;
}
}
break;
}
case IDC_OPEN:
{
OpenFolder();
break;
}
case IDC_TREE:
{
switch (flag)
{
case GNotifyContainer_Click:
{
GMouse m;
c->GetMouse(m);
if (m.Right())
{
GSubMenu s;
s.AppendItem("Add", IDM_ADD);
int Cmd = s.Float(c->GetGView(), m);
switch (Cmd)
{
case IDM_ADD:
{
OpenFolder();
break;
}
}
}
break;
}
case LvcCommandStart:
{
SetCtrlEnabled(IDC_PUSH, false);
SetCtrlEnabled(IDC_PUSH_ALL, false);
SetCtrlEnabled(IDC_PULL, false);
SetCtrlEnabled(IDC_PULL_ALL, false);
break;
}
case LvcCommandEnd:
{
SetCtrlEnabled(IDC_PUSH, true);
SetCtrlEnabled(IDC_PUSH_ALL, true);
SetCtrlEnabled(IDC_PULL, true);
SetCtrlEnabled(IDC_PULL_ALL, true);
break;
}
}
break;
}
case IDC_CLEAR_FILTER:
{
SetCtrlName(IDC_FILTER, NULL);
// Fall through
}
case IDC_FILTER:
{
VcFolder *f = dynamic_cast(Tree->Selection());
if (f)
f->Select(true);
break;
}
case IDC_COMMIT_AND_PUSH:
case IDC_COMMIT:
{
const char *Msg = GetCtrlName(IDC_MSG);
if (ValidStr(Msg))
{
VcFolder *f = dynamic_cast(Tree->Selection());
if (f)
{
char *Branch = GetCtrlName(IDC_BRANCH);
bool AndPush = c->GetId() == IDC_COMMIT_AND_PUSH;
f->Commit(Msg, ValidStr(Branch) ? Branch : NULL, AndPush);
}
}
else LgiMsg(this, "No message for commit.", AppName);
break;
}
case IDC_PUSH:
{
VcFolder *f = dynamic_cast(Tree->Selection());
if (f)
f->Push();
break;
}
case IDC_PULL:
{
VcFolder *f = dynamic_cast(Tree->Selection());
if (f)
f->Pull();
break;
}
case IDC_PULL_ALL:
{
GArray Folders;
Tree->GetAll(Folders);
for (auto f : Folders)
{
f->Pull(LogSilo);
}
break;
}
case IDC_STATUS:
{
GArray Folders;
Tree->GetAll(Folders);
for (auto f : Folders)
{
f->FolderStatus();
}
break;
}
}
return 0;
}
};
//////////////////////////////////////////////////////////////////
int LgiMain(OsAppArguments &AppArgs)
{
GApp a(AppArgs, AppName);
if (a.IsOk())
{
a.AppWnd = new App;
a.Run();
}
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,2651 +1,2656 @@
#include "Lvc.h"
#include "../Resources/resdefs.h"
#ifndef CALL_MEMBER_FN
#define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember))
#endif
int Ver2Int(GString 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
{
LgiAssert(0);
return 0;
}
}
return i;
}
int ToolVersion[VcMax] = {0};
ReaderThread::ReaderThread(GSubProcess *p, GStream *out) : LThread("ReaderThread")
{
Process = p;
Out = out;
Run();
}
ReaderThread::~ReaderThread()
{
Out = NULL;
while (!IsExited())
LgiSleep(1);
}
int ReaderThread::Main()
{
bool b = Process->Start(true, false);
if (!b)
{
GString s("Process->Start failed.\n");
Out->Write(s.Get(), s.Length(), ErrSubProcessFailed);
return ErrSubProcessFailed;
}
while (Process->IsRunning())
{
if (Out)
{
char Buf[1024];
ssize_t r = Process->Read(Buf, sizeof(Buf));
if (r > 0)
Out->Write(Buf, r);
}
else
{
Process->Kill();
return -1;
break;
}
}
if (Out)
{
char Buf[1024];
ssize_t r = Process->Read(Buf, sizeof(Buf));
if (r > 0)
Out->Write(Buf, r);
}
return (int) Process->GetExitValue();
}
/////////////////////////////////////////////////////////////////////////////////////////////
void VcFolder::Init(AppPriv *priv)
{
d = priv;
IsCommit = false;
IsLogging = false;
IsGetCur = false;
IsUpdate = false;
IsFilesCmd = false;
IsWorkingFld = false;
CommitListDirty = false;
IsUpdatingCounts = false;
Unpushed = Unpulled = -1;
Type = VcNone;
CmdErrors = 0;
Expanded(false);
Insert(Tmp = new GTreeItem);
Tmp->SetText("Loading...");
LgiAssert(d != NULL);
}
VcFolder::VcFolder(AppPriv *priv, const char *p)
{
Init(priv);
Path = p;
}
VcFolder::VcFolder(AppPriv *priv, GXmlTag *t)
{
Init(priv);
Serialize(t, false);
}
VersionCtrl VcFolder::GetType()
{
if (Type == VcNone)
Type = DetectVcs(Path);
return Type;
}
char *VcFolder::GetText(int Col)
{
switch (Col)
{
case 0:
{
if (Cmds.Length())
{
Cache = Path;
Cache += " (...)";
return Cache;
}
return Path;
}
case 1:
{
CountCache.Printf("%i/%i", Unpulled, Unpushed);
CountCache = CountCache.Replace("-1", "--");
return CountCache;
}
}
return NULL;
}
bool VcFolder::Serialize(GXmlTag *t, bool Write)
{
if (Write)
t->SetContent(Path);
else
Path = t->GetContent();
return true;
}
GXmlTag *VcFolder::Save()
{
GXmlTag *t = new GXmlTag(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)
{
GString Opt;
Opt.Printf("%s-path", Def);
GVariant v;
if (d->Opts.GetValue(Opt, v))
VcCmd = v.Str();
}
if (!VcCmd)
VcCmd = Def;
return VcCmd;
}
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 (d->Log && Logging != LogSilo)
d->Log->Print("%s %s\n", Exe, Args);
GAutoPtr Process(new GSubProcess(Exe, Args));
if (!Process)
return false;
Process->SetInitFolder(Params && Params->AltInitPath ? Params->AltInitPath : Path);
GString::Array Ctx;
Ctx.SetFixedLength(false);
Ctx.Add(Path);
Ctx.Add(Exe);
Ctx.Add(Args);
GAutoPtr c(new Cmd(Ctx, Logging, d->Log));
if (!c)
return false;
c->PostOp = Parser;
c->Params.Reset(Params);
c->Rd.Reset(new ReaderThread(Process.Release(), c));
Cmds.Add(c.Release());
Update();
LgiTrace("Cmd: %s %s\n", Exe, Args);
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());
}
bool VcFolder::ParseBranches(int Result, GString s, ParseParams *Params)
{
switch (GetType())
{
case VcGit:
{
GString::Array a = s.SplitDelimit("\r\n");
for (GString *l = NULL; a.Iterate(l); )
{
GString n = l->Strip();
if (n(0) == '*')
{
GString::Array c = n.SplitDelimit(" \t", 1);
if (c.Length() > 1)
Branches.New() = CurrentBranch = c[1];
else
Branches.New() = n;
}
else Branches.New() = n;
}
break;
}
case VcHg:
{
Branches = s.SplitDelimit("\r\n");
break;
}
default:
{
break;
}
}
OnBranchesChange();
return false;
}
void VcFolder::OnBranchesChange()
{
GWindow *w = d->Tree->GetWindow();
if (!w)
return;
DropDownBtn *dd;
if (w->GetViewById(IDC_BRANCH_DROPDOWN, dd))
{
dd->SetList(IDC_BRANCH, Branches);
}
if (Branches.Length() > 0)
{
GViewI *b;
if (w->GetViewById(IDC_BRANCH, b))
{
if (!ValidStr(b->Name()))
b->Name(Branches.First());
}
}
}
void VcFolder::Select(bool b)
{
if (!b)
{
GWindow *w = d->Tree->GetWindow();
w->SetCtrlName(IDC_BRANCH, NULL);
}
GTreeItem::Select(b);
if (b)
{
if (!DirExists(Path))
return;
if ((Log.Length() == 0 || CommitListDirty) && !IsLogging)
{
switch (GetType())
{
case VcGit:
{
IsLogging = StartCmd("rev-list --all --header --timestamp --author-date-order", &VcFolder::ParseRevList);
break;
}
case VcSvn:
{
if (CommitListDirty)
{
IsLogging = StartCmd("up", &VcFolder::ParsePull, new ParseParams("log"));
break;
}
// else fall through
}
default:
{
IsLogging = StartCmd("log", &VcFolder::ParseLog);
}
}
CommitListDirty = false;
}
if (Branches.Length() == 0)
{
switch (GetType())
{
case VcGit:
StartCmd("branch -a", &VcFolder::ParseBranches);
break;
case VcSvn:
Branches.New() = "trunk";
break;
case VcHg:
StartCmd("branch", &VcFolder::ParseBranches);
break;
case VcCvs:
break;
default:
LgiAssert(!"Impl me.");
break;
}
}
OnBranchesChange();
/*
if (!IsUpdatingCounts && Unpushed < 0)
{
switch (GetType())
{
case VcGit:
IsUpdatingCounts = StartCmd("cherry -v", &VcFolder::ParseCounts);
break;
case VcSvn:
IsUpdatingCounts = StartCmd("status -u", &VcFolder::ParseCounts);
break;
default:
LgiAssert(!"Impl me.");
break;
}
}
*/
char *Ctrl = d->Lst->GetWindow()->GetCtrlName(IDC_FILTER);
GString Filter = ValidStr(Ctrl) ? Ctrl : NULL;
if (d->CurFolder != this)
{
d->CurFolder = this;
d->Lst->RemoveAll();
}
if (!Uncommit)
Uncommit.Reset(new UncommitedItem(d));
d->Lst->Insert(Uncommit, 0);
int64 CurRev = Atoi(CurrentCommit.Get());
List Ls;
for (unsigned i=0; iGetRev())
{
switch (GetType())
{
case VcSvn:
{
int64 LogRev = Atoi(Log[i]->GetRev());
if (CurRev >= 0 && CurRev >= LogRev)
{
CurRev = -1;
Log[i]->SetCurrent(true);
}
else
{
Log[i]->SetCurrent(false);
}
break;
}
default:
Log[i]->SetCurrent(!_stricmp(CurrentCommit, Log[i]->GetRev()));
break;
}
}
bool Add = !Filter;
if (Filter)
{
const char *s = Log[i]->GetRev();
if (s && strstr(s, Filter) != NULL)
Add = true;
s = Log[i]->GetAuthor();
if (s && stristr(s, Filter) != NULL)
Add = true;
s = Log[i]->GetMsg();
if (s && stristr(s, Filter) != NULL)
Add = true;
}
LList *CurOwner = Log[i]->GetList();
if (Add ^ (CurOwner != NULL))
{
if (Add)
Ls.Insert(Log[i]);
else
d->Lst->Remove(Log[i]);
}
}
d->Lst->Insert(Ls);
// d->Lst->Sort(LogDateCmp);
if (GetType() == VcGit)
{
d->Lst->ColumnAt(0)->Width(40);
d->Lst->ColumnAt(1)->Width(270);
d->Lst->ColumnAt(2)->Width(240);
d->Lst->ColumnAt(3)->Width(130);
d->Lst->ColumnAt(4)->Width(400);
}
else d->Lst->ResizeColumnsToContent();
d->Lst->UpdateAllItems();
if (!CurrentCommit && !IsGetCur)
{
switch (GetType())
{
case VcGit:
IsGetCur = StartCmd("rev-parse HEAD", &VcFolder::ParseInfo);
break;
case VcSvn:
IsGetCur = StartCmd("info", &VcFolder::ParseInfo);
break;
case VcHg:
IsGetCur = StartCmd("id -i", &VcFolder::ParseInfo);
break;
case VcCvs:
break;
default:
LgiAssert(!"Impl me.");
break;
}
}
}
}
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 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;
}
bool VcFolder::ParseRevList(int Result, GString s, ParseParams *Params)
{
GFile f("C:\\Users\\matthew\\Code\\Lgi\\trunk\\rev-list.txt", O_WRITE);
if (f.IsOpen())
{
f.SetSize(0);
f.Write(s);
f.Close();
}
LHashTbl, VcCommit*> Map;
for (VcCommit **pc = NULL; Log.Iterate(pc); )
Map.Add((*pc)->GetRev(), *pc);
int Skipped = 0, Errors = 0;
switch (GetType())
{
case VcGit:
{
GString::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)
{
GAutoPtr Rev(new VcCommit(d, this));
if (Rev->GitParse(Commit, true))
{
if (!Map.Find(Rev->GetRev()))
Log.Add(Rev.Release());
else
Skipped++;
}
else
{
LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, Commit.Get());
Errors++;
}
}
// Log.Sort(CommitDateCmp);
LinkParents();
break;
}
default:
LgiAssert(!"Impl me.");
break;
}
return true;
}
bool VcFolder::ParseLog(int Result, GString s, ParseParams *Params)
{
LHashTbl, VcCommit*> Map;
for (VcCommit **pc = NULL; Log.Iterate(pc); )
Map.Add((*pc)->GetRev(), *pc);
int Skipped = 0, Errors = 0;
switch (GetType())
{
case VcGit:
{
GString::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()))
Log.Add(Rev.Release());
else
Skipped++;
}
else
{
LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, c[i].Get());
Errors++;
}
}
Log.Sort(CommitDateCmp);
break;
}
case VcSvn:
{
GString::Array c = s.Split("------------------------------------------------------------------------");
for (unsigned i=0; i Rev(new VcCommit(d, this));
GString Raw = c[i].Strip();
if (Rev->SvnParse(Raw))
{
if (!Map.Find(Rev->GetRev()))
Log.Add(Rev.Release());
else
Skipped++;
}
else if (Raw)
{
LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, Raw.Get());
Errors++;
}
}
Log.Sort(CommitRevCmp);
break;
}
case VcHg:
{
GString::Array c = s.Split("\n\n");
for (GString *Commit = NULL; c.Iterate(Commit); )
{
GAutoPtr Rev(new VcCommit(d, this));
if (Rev->HgParse(*Commit))
{
if (!Map.Find(Rev->GetRev()))
Log.Add(Rev.Release());
}
}
break;
}
case VcCvs:
{
LHashTbl, VcCommit*> Map;
GString::Array c = s.Split("=============================================================================");
for (GString *Commit = NULL; c.Iterate(Commit);)
{
if (Commit->Strip().Length())
{
GString Head, File;
GString::Array Versions = Commit->Split("----------------------------");
GString::Array Lines = Versions[0].SplitDelimit("\r\n");
for (GString *Line = NULL; Lines.Iterate(Line);)
{
GString::Array p = Line->Split(":", 1);
if (p.Length() == 2)
{
// LgiTrace("Line: %s\n", Line->Get());
GString Var = p[0].Strip().Lower();
GString Val = p[1].Strip();
if (Var.Equals("branch"))
{
if (Val.Length())
Branches.Add(Val);
}
else if (Var.Equals("head"))
{
Head = Val;
}
else if (Var.Equals("rcs file"))
{
GString::Array f = Val.SplitDelimit(",");
File = f.First();
}
}
}
// LgiTrace("%s\n", Commit->Get());
for (unsigned i=1; i= 3)
{
GString Ver = Lines[0].Split(" ").Last();
GString::Array a = Lines[1].SplitDelimit(";");
GString Date = a[0].Split(":", 1).Last().Strip();
GString Author = a[1].Split(":", 1).Last().Strip();
GString Id = a[2].Split(":", 1).Last().Strip();
GString 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));
Log.Add(Cc);
Cc->CvsParse(Dt, Author, Msg);
}
Cc->Files.Add(File.Get());
}
else LgiAssert(!"NO ts for date.");
}
else LgiAssert(!"Date parsing failed.");
}
}
}
}
break;
}
default:
LgiAssert(!"Impl me.");
break;
}
// LgiTrace("%s:%i - ParseLog: Skip=%i, Error=%i\n", _FL, Skipped, Errors);
IsLogging = false;
return true;
}
void VcFolder::LinkParents()
{
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...
for (auto c:Log)
{
auto *Par = c->GetParents();
for (auto &pRev : *Par)
{
auto *p = Map.Find(pRev);
if (p)
new VcEdge(p, c);
else
LgiAssert(0);
}
}
// Map the edges to positions
typedef GArray EdgeArr;
GArray Active;
for (auto c:Log)
{
- #if 0
- if (c->IsRev("cb4583f3bbffea7ddc3f8d3a8eecfc4bc0a3b6a5"))
+ #if 1
+ if (c->IsRev("49d6765e37dfffb0ad4e924da5169c076c87c871"))
{
int asd=0;
}
#endif
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; iChild || c == e->Parent)
{
LgiAssert(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);
Active[NewIndex].Add(e);
e->Idx = NewIndex;
c->Pos.Add(e, NewIndex);
n--;
}
else
{
LgiAssert(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;
LgiAssert(Active[i].HasItem(e));
Active[i].Delete(e);
-
- if (Active[i].Length() == 0)
+ }
+ }
+
+ // Collapse any empty active columns
+ for (unsigned i=0; iIdx > 0);
- edge->Idx--;
- c->Pos.Add(edge, edge->Idx);
- }
+ LgiAssert(edge->Idx > 0);
+ edge->Idx--;
+ c->Pos.Add(edge, edge->Idx);
}
}
+ i--;
}
}
}
}
VcFile *VcFolder::FindFile(const char *Path)
{
if (!Path)
return NULL;
GArray Files;
if (d->Files->GetAll(Files))
{
GString p = Path;
p = p.Replace(DIR_STR, "/");
for (auto f : Files)
{
auto Fn = f->GetFileName();
if (p.Equals(Fn))
{
return f;
}
}
}
return NULL;
}
void VcFolder::OnCmdError(GString Output, const char *Msg)
{
if (!CmdErrors)
{
d->Log->Write(Output, Output.Length());
GString::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());
}
CmdErrors++;
d->Tabs->Value(1);
Color(GColour::Red);
}
void VcFolder::ClearError()
{
Color(GCss::ColorInherit);
}
bool VcFolder::ParseInfo(int Result, GString s, ParseParams *Params)
{
switch (GetType())
{
case VcGit:
case VcHg:
{
CurrentCommit = s.Strip();
break;
}
case VcSvn:
{
if (s.Find("client is too old") >= 0)
{
OnCmdError(s, "Client too old");
break;
}
GString::Array c = s.Split("\n");
for (unsigned i=0; iIsWorking = true;
ParseStatus(Result, s, Params);
}
else
ParseDiffs(s, NULL, true);
IsWorkingFld = false;
d->Files->ResizeColumnsToContent();
if (GetType() == VcSvn)
{
Unpushed = d->Files->Length() > 0 ? 1 : 0;
Update();
}
return false;
}
bool VcFolder::ParseDiff(int Result, GString s, ParseParams *Params)
{
ParseDiffs(s, NULL, true);
return false;
}
void VcFolder::Diff(VcFile *file)
{
switch (GetType())
{
case VcSvn:
case VcGit:
case VcHg:
{
GString a;
a.Printf("diff \"%s\"", file->GetFileName());
StartCmd(a, &VcFolder::ParseDiff);
break;
}
default:
LgiAssert(!"Impl me.");
break;
}
}
bool VcFolder::ParseDiffs(GString s, GString Rev, bool IsWorking)
{
LgiAssert(IsWorking || Rev.Get() != NULL);
switch (GetType())
{
case VcGit:
{
GString::Array a = s.Split("\n");
GString Diff;
VcFile *f = NULL;
for (unsigned i=0; iSetDiff(Diff);
Diff.Empty();
auto Bits = a[i].SplitDelimit();
GString 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, "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:
{
GString::Array a = s.Split("\n");
GString Diff;
VcFile *f = NULL;
for (unsigned i=0; iSetDiff(Diff);
Diff.Empty();
GString Fn = a[i].Split(" ").Last();
f = new VcFile(d, this, Rev, IsWorking);
f->SetText(Fn.Replace("\\","/"), COL_FILENAME);
d->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
{
if (Diff) Diff += "\n";
Diff += a[i];
}
}
if (f && Diff)
{
f->SetDiff(Diff);
Diff.Empty();
}
break;
}
case VcSvn:
{
GString::Array a = s.Replace("\r").Split("\n");
GString 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;
GString 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:
{
LgiAssert(!"Impl me.");
break;
}
}
return true;
}
bool VcFolder::ParseFiles(int Result, GString s, ParseParams *Params)
{
d->ClearFiles();
ParseDiffs(s, Params->Str, false);
IsFilesCmd = false;
d->Files->ResizeColumnsToContent();
return false;
}
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.
for (unsigned i=0; iRd->IsExited())
{
GString s = c->GetBuf();
int Result = c->Rd->ExitCode();
if (Result == ErrSubProcessFailed)
{
if (!CmdErrors)
d->Log->Print("Error: Can't run '%s'\n", GetVcName());
CmdErrors++;
}
if (c->PostOp)
Reselect |= CALL_MEMBER_FN(*this, c->PostOp)(Result, s, c->Params);
Cmds.DeleteAt(i--, true);
delete c;
CmdsChanged = true;
}
}
Processing = false;
}
if (Reselect)
{
if (GTreeItem::Select())
Select(true);
}
if (CmdsChanged)
Update();
}
void VcFolder::OnRemove()
{
GXmlTag *t = d->Opts.LockTag(NULL, _FL);
if (t)
{
for (GXmlTag *c = t->Children.First(); c; c = t->Children.Next())
{
if (c->IsTag(OPT_Folder) &&
c->GetContent() &&
!_stricmp(c->GetContent(), Path))
{
c->RemoveTag();
delete c;
break;
}
}
d->Opts.Unlock();
}
}
void VcFolder::OnMouseClick(GMouse &m)
{
if (m.IsContextMenu())
{
GSubMenu s;
s.AppendItem("Browse To", IDM_BROWSE_FOLDER);
s.AppendItem(
#ifdef WINDOWS
"Command Prompt At",
#else
"Terminal At",
#endif
IDM_TERMINAL);
s.AppendItem("Clean", IDM_CLEAN);
s.AppendSeparator();
s.AppendItem("Remove", IDM_REMOVE);
int Cmd = s.Float(GetTree(), m);
switch (Cmd)
{
case IDM_BROWSE_FOLDER:
{
LgiBrowseToFile(Path);
break;
}
case IDM_TERMINAL:
{
#if defined(MAC)
LgiExecute("/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal", Path);
#elif defined(WINDOWS)
TCHAR w[MAX_PATH];
auto r = GetWindowsDirectory(w, CountOf(w));
if (r > 0)
{
GFile::Path p = GString(w);
p += "system32\\cmd.exe";
FileDev->SetCurrentFolder(Path);
LgiExecute(p);
}
#elif defined(LINUX)
// #error "Impl me."
#endif
break;
}
case IDM_CLEAN:
{
Clean();
break;
}
case IDM_REMOVE:
{
OnRemove();
delete this;
break;
}
default:
break;
}
}
}
void VcFolder::OnUpdate(const char *Rev)
{
if (!Rev)
return;
if (!IsUpdate)
{
GString 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;
default:
{
LgiAssert(!"Impl me.");
break;
}
}
}
}
///////////////////////////////////////////////////////////////////////////////////////
int FolderCompare(GTreeItem *a, GTreeItem *b, NativeInt UserData)
{
VcLeaf *A = dynamic_cast(a);
VcLeaf *B = dynamic_cast(b);
if (!A || !B)
return 0;
return A->Compare(B);
}
void VcFolder::ReadDir(GTreeItem *Parent, const char *Path)
{
// Read child items
GDirectory Dir;
for (int b = Dir.First(Path); b; b = Dir.Next())
{
if (Dir.IsDir())
{
if (Dir.GetName()[0] != '.')
{
new VcLeaf(this, Parent, Path, Dir.GetName(), true);
}
}
else if (!Dir.IsHidden())
{
char *Ext = LgiGetExtension(Dir.GetName());
if (!Ext) continue;
if (!stricmp(Ext, "c") ||
!stricmp(Ext, "cpp") ||
!stricmp(Ext, "h"))
{
new VcLeaf(this, Parent, Path, Dir.GetName(), false);
}
}
}
Parent->SortChildren(FolderCompare);
}
void VcFolder::OnExpand(bool b)
{
if (Tmp && b)
{
Tmp->Remove();
DeleteObj(Tmp);
ReadDir(this, Path);
}
}
void VcFolder::OnPaint(ItemPaintCtx &Ctx)
{
auto c = Color();
if (c.IsValid())
Ctx.Fore = c;
c = BackgroundColor();
if (c.IsValid() && !GTreeItem::Select())
Ctx.Back = c;
GTreeItem::OnPaint(Ctx);
}
void VcFolder::ListCommit(VcCommit *c)
{
if (!IsFilesCmd)
{
GString Args;
switch (GetType())
{
case VcGit:
// Args.Printf("show --oneline --name-only %s", Rev);
Args.Printf("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);
}
}
d->Files->ResizeColumnsToContent();
break;
}
default:
LgiAssert(!"Impl me.");
break;
}
if (IsFilesCmd)
d->ClearFiles();
}
}
GString ConvertUPlus(GString s)
{
GArray c;
GUtf8Ptr 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 GString((char16*)c.AddressOf());
#else
return GString(c.AddressOf());
#endif
}
bool VcFolder::ParseStatus(int Result, GString s, ParseParams *Params)
{
bool ShowUntracked = d->Files->GetWindow()->GetCtrlValue(IDC_UNTRACKED) != 0;
bool IsWorking = Params ? Params->IsWorking : false;
List Ins;
switch (GetType())
{
case VcCvs:
{
GString::Array a = s.Split("===================================================================");
for (auto i : a)
{
GString::Array Lines = i.SplitDelimit("\r\n");
GString f = Lines[0].Strip();
if (f.Find("File:") == 0)
{
GString::Array Parts = f.SplitDelimit("\t");
GString File = Parts[0].Split(": ").Last();
GString Status = Parts[1].Split(": ").Last();
GString WorkingRev;
for (auto l : Lines)
{
GString::Array p = l.Strip().Split(":", 1);
if (p.Length() > 1 &&
p[0].Strip().Equals("Working revision"))
{
WorkingRev = p[1].Strip();
}
}
VcFile *f = new VcFile(d, this, WorkingRev, IsWorking);
f->SetText(Status, COL_STATE);
f->SetText(File, COL_FILENAME);
Ins.Insert(f);
}
else if (f(0) == '?' &&
ShowUntracked)
{
GString File = f(2, -1);
VcFile *f = new VcFile(d, this, NULL, IsWorking);
f->SetText("?", COL_STATE);
f->SetText(File, COL_FILENAME);
Ins.Insert(f);
}
}
break;
}
case VcGit:
{
GString::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)
{
GString::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)
{
GString::Array p = Ln.SplitDelimit(" ");
f = new VcFile(d, this, NULL, 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, NULL, IsWorking);
f->SetText("?", COL_STATE);
f->SetText(Ln(2,-1), COL_FILENAME);
Ins.Insert(f);
}
}
break;
}
case VcHg:
case VcSvn:
{
GString::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 != '?')
{
GString::Array p = Ln.SplitDelimit(" ", 1);
if (p.Length() == 2)
{
GString File;
if (GetType() == VcSvn)
File = ConvertUPlus(p.Last());
else
File = p.Last();
VcFile *f = new VcFile(d, this, NULL, IsWorking);
f->SetText(p[0], COL_STATE);
f->SetText(File.Replace("\\","/"), COL_FILENAME);
f->GetStatus();
Ins.Insert(f);
}
else LgiAssert(!"What happen?");
}
else if (ShowUntracked)
{
VcFile *f = new VcFile(d, this, NULL, IsWorking);
f->SetText("?", COL_STATE);
f->SetText(Ln(2,-1), COL_FILENAME);
Ins.Insert(f);
}
}
break;
}
default:
{
LgiAssert(!"Impl me.");
break;
}
}
Unpushed = Ins.Length() > 0;
Update();
if (GTreeItem::Select())
{
d->Files->Insert(Ins);
d->Files->ResizeColumnsToContent();
}
else
{
Ins.DeleteObjects();
}
return false; // Don't refresh list
}
void VcFolder::FolderStatus(const char *Path, VcLeaf *Notify)
{
if (GTreeItem::Select())
d->ClearFiles();
GString Arg;
switch (GetType())
{
case VcSvn:
case VcHg:
Arg = "status";
break;
case VcCvs:
Arg = "status -l";
break;
case VcGit:
if (!ToolVersion[VcGit])
LgiAssert(!"Where is the version?");
// What version did =2 become available? It's definately not in v2.5.4
// Not in v2.7.4 either...
if (ToolVersion[VcGit] >= Ver2Int("2.8.0"))
Arg = "status --porcelain=2";
else
Arg = "status --porcelain";
break;
default:
return;
}
ParseParams *p = new ParseParams;
if (Path && Notify)
{
p->AltInitPath = Path;
p->Leaf = Notify;
}
else
{
p->IsWorking = true;
}
StartCmd(Arg, &VcFolder::ParseStatus, p);
}
void VcFolder::ListWorkingFolder()
{
if (!IsWorkingFld)
{
d->ClearFiles();
GString Arg;
switch (GetType())
{
case VcCvs:
Arg = "-q diff --brief";
break;
case VcSvn:
Arg = "status";
break;
case VcGit:
StartCmd("diff --staged", &VcFolder::ParseWorking);
Arg = "diff --diff-filter=ACDMRTU";
// return FolderStatus();
break;
default:
Arg ="diff";
break;
}
IsWorkingFld = StartCmd(Arg, &VcFolder::ParseWorking);
}
}
void VcFolder::GitAdd()
{
if (!PostAdd)
return;
GString Args;
if (PostAdd->Files.Length() == 0)
{
GString m(PostAdd->Msg);
m = m.Replace("\"", "\\\"");
Args.Printf("commit -m \"%s\"", m.Get());
IsCommit = StartCmd(Args, &VcFolder::ParseCommit, PostAdd->Param, LogNormal);
PostAdd.Reset();
}
else
{
GString Last = PostAdd->Files.Last();
Args.Printf("add \"%s\"", Last.Replace("\"", "\\\"").Replace("/", DIR_STR).Get());
PostAdd->Files.PopLast();
StartCmd(Args, &VcFolder::ParseGitAdd, NULL, LogNormal);
}
}
bool VcFolder::ParseGitAdd(int Result, GString s, ParseParams *Params)
{
GitAdd();
return false;
}
bool VcFolder::ParseCommit(int Result, GString s, ParseParams *Params)
{
if (GTreeItem::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)
{
{
GInput i(GetTree(), "", "Git user name:", AppName);
if (i.DoModal())
{
GString Args;
Args.Printf("config --global user.name \"%s\"", i.GetStr().Get());
StartCmd(Args);
}
}
{
GInput i(GetTree(), "", "Git user email:", AppName);
if (i.DoModal())
{
GString Args;
Args.Printf("config --global user.email \"%s\"", i.GetStr().Get());
StartCmd(Args);
}
}
}
break;
}
default:
break;
}
return false;
}
if (Result == 0 && GTreeItem::Select())
{
d->ClearFiles();
GWindow *w = d->Diff ? d->Diff->GetWindow() : NULL;
if (w)
w->SetCtrlName(IDC_MSG, NULL);
}
switch (GetType())
{
case VcGit:
{
Unpushed++;
Update();
if (Params && Params->Str.Find("Push") >= 0)
Push();
break;
}
case VcSvn:
{
CurrentCommit.Empty();
CommitListDirty = true;
GetTree()->SendNotify(LvcCommandEnd);
if (!Result)
{
Unpushed = 0;
Update();
}
break;
}
default:
{
LgiAssert(!"Impl me.");
break;
}
}
return true;
}
void VcFolder::Commit(const char *Msg, const char *Branch, bool AndPush)
{
VcFile *f = NULL;
GArray Add;
bool Partial = false;
while (d->Files->Iterate(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;
}
if (!IsCommit)
{
GString 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
{
GString m(Msg);
m = m.Replace("\"", "\\\"");
Args.Printf("commit -am \"%s\"", m.Get());
IsCommit = StartCmd(Args, &VcFolder::ParseCommit, Param, LogNormal);
}
break;
}
case VcSvn:
{
GString::Array a;
a.New().Printf("commit -m \"%s\"", Msg);
for (VcFile **pf = NULL; Add.Iterate(pf); )
{
GString s = (*pf)->GetFileName();
if (s.Find(" ") >= 0)
a.New().Printf("\"%s\"", s.Get());
else
a.New() = s;
}
Args = GString(" ").Join(a);
IsCommit = StartCmd(Args, &VcFolder::ParseCommit, NULL, LogNormal);
if (d->Tabs && IsCommit)
{
d->Tabs->Value(1);
GetTree()->SendNotify(LvcCommandStart);
}
break;
}
default:
{
LgiAssert(!"Impl me.");
break;
}
}
}
}
void VcFolder::Push()
{
GString Args;
bool Working = false;
switch (GetType())
{
case VcGit:
Working = StartCmd("push", &VcFolder::ParsePush, NULL, LogNormal);
break;
case VcSvn:
// Nothing to do here.. the commit pushed the data already
break;
default:
LgiAssert(!"Impl me.");
break;
}
if (d->Tabs && Working)
{
d->Tabs->Value(1);
GetTree()->SendNotify(LvcCommandStart);
}
}
bool VcFolder::ParsePush(int Result, GString s, ParseParams *Params)
{
bool Status = false;
if (Result)
{
OnCmdError(s, "Push failed.");
}
else
{
ClearError();
switch (GetType())
{
case VcGit:
break;
case VcSvn:
break;
default:
break;
}
Unpushed = 0;
Update();
Status = true;
}
GetTree()->SendNotify(LvcCommandEnd);
return Status; // no reselect
}
void VcFolder::Pull(LoggingType Logging)
{
GString Args;
bool Status = false;
switch (GetType())
{
case VcHg:
case VcGit:
Status = StartCmd("pull", &VcFolder::ParsePull, NULL, Logging);
break;
case VcSvn:
Status = StartCmd("up", &VcFolder::ParsePull, NULL, Logging);
break;
default:
LgiAssert(!"Impl me.");
break;
}
if (d->Tabs && Status)
{
d->Tabs->Value(1);
GetTree()->SendNotify(LvcCommandStart);
}
}
bool VcFolder::ParsePull(int Result, GString s, ParseParams *Params)
{
if (Result)
{
OnCmdError(s, "Pull failed.");
return false;
}
else ClearError();
switch (GetType())
{
case VcGit:
case VcHg:
{
// Git does a merge by default, so the current commit changes...
CurrentCommit.Empty();
break;
}
case VcSvn:
{
// Svn also does a merge by default and can update our current position...
CurrentCommit.Empty();
GString::Array a = s.SplitDelimit("\r\n");
for (GString *Ln = NULL; a.Iterate(Ln); )
{
if (Ln->Find("At revision") >= 0)
{
GString::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"))
{
IsLogging = StartCmd("log", &VcFolder::ParseLog);
return false;
}
break;
}
default:
break;
}
GetTree()->SendNotify(LvcCommandEnd);
CommitListDirty = true;
return true; // Yes - reselect and update
}
void VcFolder::Clean()
{
switch (GetType())
{
case VcSvn:
StartCmd("cleanup", &VcFolder::ParseClean, NULL, LogNormal);
break;
default:
LgiMsg(GetTree(), "Not implemented.", AppName);
break;
}
}
bool VcFolder::ParseClean(int Result, GString s, ParseParams *Params)
{
switch (GetType())
{
case VcSvn:
if (Result == 0)
Color(ColorInherit);
break;
default:
LgiAssert(!"Impl me.");
break;
}
return false;
}
void VcFolder::GetVersion()
{
auto t = GetType();
switch (t)
{
case VcGit:
case VcSvn:
case VcHg:
case VcCvs:
StartCmd("--version", &VcFolder::ParseVersion, NULL, LogNormal);
break;
default:
OnCmdError(NULL, "No version control found.");
break;
}
}
bool VcFolder::ParseVersion(int Result, GString s, ParseParams *Params)
{
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
LgiAssert(0);
break;
}
case VcSvn:
{
if (p.Length() > 2)
{
ToolVersion[GetType()] = Ver2Int(p[2]);
printf("Svn version: %s\n", p[2].Get());
}
else
LgiAssert(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
LgiAssert(!"Impl me.");
break;
}
default:
break;
}
return false;
}
bool VcFolder::ParseAddFile(int Result, GString s, ParseParams *Params)
{
switch (GetType())
{
case VcCvs:
{
break;
}
default:
break;
}
return false;
}
bool VcFolder::AddFile(const char *Path, bool AsBinary)
{
if (!Path)
return false;
switch (GetType())
{
case VcCvs:
{
GString a;
a.Printf("add%s \"%s\"", AsBinary ? " -kb" : "", Path);
return StartCmd(a, &VcFolder::ParseAddFile);
break;
}
default:
{
LgiAssert(!"Impl me.");
break;
}
}
return false;
}
bool VcFolder::ParseRevert(int Result, GString s, ParseParams *Params)
{
ListWorkingFolder();
return false;
}
bool VcFolder::Revert(const char *Path, const char *Revision)
{
if (!Path)
return false;
switch (GetType())
{
case VcGit:
{
GString a;
a.Printf("checkout \"%s\"", Path);
return StartCmd(a, &VcFolder::ParseRevert);
break;
}
case VcSvn:
{
GString a;
a.Printf("revert \"%s\"", Path);
return StartCmd(a, &VcFolder::ParseRevert);
break;
}
default:
{
LgiAssert(!"Impl me.");
break;
}
}
return false;
}
bool VcFolder::ParseResolve(int Result, GString s, ParseParams *Params)
{
switch (GetType())
{
case VcGit:
{
break;
}
case VcSvn:
case VcHg:
case VcCvs:
default:
{
LgiAssert(!"Impl me.");
break;
}
}
return true;
}
bool VcFolder::Resolve(const char *Path)
{
if (!Path)
return false;
switch (GetType())
{
case VcGit:
{
GString a;
a.Printf("add \"%s\"", Path);
return StartCmd(a, &VcFolder::ParseResolve, new ParseParams(Path));
}
case VcSvn:
case VcHg:
case VcCvs:
default:
{
LgiAssert(!"Impl me.");
break;
}
}
return false;
}
bool VcFolder::ParseBlame(int Result, GString s, ParseParams *Params)
{
new BlameUi(d, GetType(), s);
return false;
}
bool VcFolder::Blame(const char *Path)
{
if (!Path)
return false;
switch (GetType())
{
case VcGit:
{
GString a;
a.Printf("blame \"%s\"", Path);
return StartCmd(a, &VcFolder::ParseBlame);
break;
}
case VcHg:
{
GString a;
a.Printf("annotate -un \"%s\"", Path);
return StartCmd(a, &VcFolder::ParseBlame);
break;
}
case VcSvn:
{
GString a;
a.Printf("blame \"%s\"", Path);
return StartCmd(a, &VcFolder::ParseBlame);
break;
}
default:
{
LgiAssert(!"Impl me.");
break;
}
}
return true;
}
bool VcFolder::SaveFileAs(const char *Path, const char *Revision)
{
if (!Path || !Revision)
return false;
return true;
}
bool VcFolder::ParseSaveAs(int Result, GString s, ParseParams *Params)
{
return false;
}
bool VcFolder::ParseCounts(int Result, GString s, ParseParams *Params)
{
switch (GetType())
{
case VcGit:
{
Unpushed = (int) s.Strip().Split("\n").Length();
break;
}
case VcSvn:
{
int64 ServerRev = 0;
bool HasUpdate = false;
GString::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:
{
LgiAssert(!"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)
{
GTreeItem *i = d->Tree->Selection();
VcFolder *f = dynamic_cast(i);
if (f)
f->ListWorkingFolder();
if (d->Msg)
{
d->Msg->Name(NULL);
GWindow *w = d->Msg->GetWindow();
if (w)
{
w->SetCtrlEnabled(IDC_COMMIT, true);
w->SetCtrlEnabled(IDC_COMMIT_AND_PUSH, true);
}
}
}
}
void VcFolder::UncommitedItem::OnPaint(GItem::ItemPaintCtx &Ctx)
{
GFont *f = GetList()->GetFont();
f->Transparent(false);
f->Colour(Ctx.Fore, Ctx.Back);
GDisplayString 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, GTreeItem *Item, GString path, GString leaf, bool folder)
{
Parent = parent;
d = Parent->GetPriv();
Path = path;
Leaf = leaf;
Folder = folder;
Tmp = NULL;
Item->Insert(this);
if (Folder)
{
Insert(Tmp = new GTreeItem);
Tmp->SetText("Loading...");
}
}
GString VcLeaf::Full()
{
GFile::Path p(Path);
p += Leaf;
return p.GetFull();
}
void VcLeaf::OnBrowse()
{
Parent->FolderStatus(Full(), this);
}
void VcLeaf::AfterBrowse()
{
LList *Files = d->Files;
Files->Empty();
GDirectory Dir;
for (int b = Dir.First(Full()); b; b = Dir.Next())
{
if (Dir.IsDir())
continue;
VcFile *f = new VcFile(d, Parent, NULL);
if (f)
{
// f->SetText(COL_STATE,
f->SetText(Dir.GetName(), COL_FILENAME);
Files->Insert(f);
}
}
Files->ResizeColumnsToContent();
}
void VcLeaf::OnExpand(bool b)
{
if (Tmp && b)
{
Tmp->Remove();
DeleteObj(Tmp);
GFile::Path p(Path);
p += Leaf;
Parent->ReadDir(this, p);
}
}
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 GTreeItem::Select();
}
void VcLeaf::Select(bool b)
{
GTreeItem::Select(b);
if (b) OnBrowse();
}
void VcLeaf::OnMouseClick(GMouse &m)
{
if (m.IsContextMenu())
{
GSubMenu s;
s.AppendItem("Log", IDM_LOG);
s.AppendItem("Blame", IDM_BLAME, !Folder);
int Cmd = s.Float(GetTree(), m);
switch (Cmd)
{
case IDM_LOG:
{
break;
}
case IDM_BLAME:
{
Parent->Blame(Full());
break;
}
}
}
}