830 lines
23 KiB
C++
830 lines
23 KiB
C++
|
/*****************************************************************************
|
||
|
*
|
||
|
* filelog.cpp
|
||
|
*
|
||
|
* View a filelog.
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
|
||
|
#include "sdview.h"
|
||
|
|
||
|
/*****************************************************************************
|
||
|
*
|
||
|
* LogEntry
|
||
|
*
|
||
|
* A single item in a filelog treelist.
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
|
||
|
class LogEntry : public TreeItem {
|
||
|
|
||
|
public:
|
||
|
LogEntry(LPCTSTR pszRev,
|
||
|
LPCTSTR pszChange,
|
||
|
LPCTSTR pszOp,
|
||
|
LPCTSTR pszDate,
|
||
|
LPCTSTR pszDev);
|
||
|
LogEntry() { }
|
||
|
|
||
|
int GetRev() const { return _iRev; }
|
||
|
|
||
|
void SetChildPath(LPCTSTR pszChildPath);
|
||
|
const StringCache& GetChildPath() const { return _scChildPath; }
|
||
|
|
||
|
void SetIntegrateType(LPCTSTR pszType);
|
||
|
void SetIsDonor() { _fDonor = TRUE; }
|
||
|
void SetComment(LPCTSTR pszComment) { _scComment = pszComment; }
|
||
|
void SetDev(LPCTSTR pszDev) { _scDev = pszDev; }
|
||
|
void SetFullDescription(LPCTSTR pszFullDescription) { _scFullDescription = pszFullDescription; }
|
||
|
void SetChurn(int cAdded, int cDeleted) { _cAdded = cAdded; _cDeleted = cDeleted; }
|
||
|
BOOL IsChurnSet() const { return _cAdded >= 0; }
|
||
|
|
||
|
LRESULT GetDispInfo(NMTREELIST *pdi, int iColumn);
|
||
|
LRESULT GetInfoTip(NMTREELIST *pdi);
|
||
|
|
||
|
LPCTSTR GetChange() const { return _scChange; }
|
||
|
LPCTSTR GetComment() const { return _scComment; }
|
||
|
|
||
|
private:
|
||
|
void GetRevDispInfo(NMTREELIST *ptl);
|
||
|
void GetChurnDispInfo(NMTREELIST *ptl);
|
||
|
void GetImage(NMTREELIST *ptl);
|
||
|
private:
|
||
|
int _iRev; // File revision number
|
||
|
int _cDeleted; // Number of lines deleted
|
||
|
int _cAdded; // Number of lines added
|
||
|
int _iOp; // Checkin operation
|
||
|
BOOL _fDonor; // Is integration donor
|
||
|
StringCache _scChange; // Change number
|
||
|
StringCache _scOp; // Checkin operation (edit, delete, tc.)
|
||
|
StringCache _scDate; // Checkin date
|
||
|
StringCache _scDev; // Checkin dev
|
||
|
StringCache _scComment; // Checkin comment
|
||
|
StringCache _scFullDescription; // Full checkin description
|
||
|
StringCache _scChildPath; // Depot path of child items
|
||
|
};
|
||
|
|
||
|
void LogEntry::SetChildPath(LPCTSTR pszChildPath)
|
||
|
{
|
||
|
String str(pszChildPath);
|
||
|
|
||
|
LPTSTR pszSharp = StrChr(str, TEXT('#'));
|
||
|
if (pszSharp) {
|
||
|
LPTSTR pszComma = StrChr(pszSharp, TEXT(','));
|
||
|
|
||
|
if (!pszComma) {
|
||
|
String strT(pszSharp);
|
||
|
str << TEXT(",") << strT;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_scChildPath = str;
|
||
|
|
||
|
SetExpandable();
|
||
|
}
|
||
|
|
||
|
LogEntryImageMap c_rgleim[] = {
|
||
|
{ TEXT("?") , -1 }, // OP_UNKNOWN
|
||
|
{ TEXT("edit") , 0 }, // OP_EDIT
|
||
|
{ TEXT("delete") , 1 }, // OP_DELETE
|
||
|
{ TEXT("add") , 2 }, // OP_ADD
|
||
|
{ TEXT("integrate") , 3 }, // OP_INTEGRATE
|
||
|
{ TEXT("merge") , 3 }, // OP_MERGE
|
||
|
{ TEXT("branch") , 4 }, // OP_BRANCH
|
||
|
{ TEXT("copy") , 5 }, // OP_COPY
|
||
|
{ TEXT("ignored") , 6 }, // OP_IGNORED
|
||
|
};
|
||
|
|
||
|
int ParseOp(LPCTSTR psz)
|
||
|
{
|
||
|
int i;
|
||
|
for (i = ARRAYSIZE(c_rgleim) - 1; i > 0; i--) {
|
||
|
if (StrCmp(c_rgleim[i]._pszOp, psz) == 0) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return i;
|
||
|
}
|
||
|
|
||
|
void LogEntry::SetIntegrateType(LPCTSTR pszType)
|
||
|
{
|
||
|
if (_iOp == OP_INTEGRATE) {
|
||
|
_iOp = ParseOp(pszType);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
LogEntry::LogEntry(
|
||
|
LPCTSTR pszRev,
|
||
|
LPCTSTR pszChange,
|
||
|
LPCTSTR pszOp,
|
||
|
LPCTSTR pszDate,
|
||
|
LPCTSTR pszDev)
|
||
|
: _iRev(StrToInt(pszRev))
|
||
|
, _scChange(pszChange)
|
||
|
, _iOp(ParseOp(pszOp))
|
||
|
, _scDate(pszDate)
|
||
|
, _scDev(pszDev)
|
||
|
, _cAdded(-1)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
void LogEntry::GetImage(NMTREELIST *ptl)
|
||
|
{
|
||
|
ptl->iSubItem = c_rgleim[_iOp]._iImage;
|
||
|
ptl->cchTextMax = INDEXTOOVERLAYMASK(_fDonor);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Combine the pszParent and the pszRev to form the real pszRev
|
||
|
// Since the rev is the more important thing, I will display it
|
||
|
// in the form
|
||
|
//
|
||
|
// 19 Lab06_DEV/foo.cpp
|
||
|
//
|
||
|
//
|
||
|
|
||
|
void LogEntry::GetRevDispInfo(NMTREELIST *ptl)
|
||
|
{
|
||
|
OutputStringBuffer str(ptl->pszText, ptl->cchTextMax);
|
||
|
str << _iRev;
|
||
|
LogEntry *ple = SAFECAST(LogEntry*, Parent());
|
||
|
if (ple->GetChildPath()) {
|
||
|
str << TEXT(" ") << BranchOf(ple->GetChildPath()) <<
|
||
|
TEXT("/") << FilenameOf(ple->GetChildPath());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void LogEntry::GetChurnDispInfo(NMTREELIST *ptl)
|
||
|
{
|
||
|
if (_cAdded >= 0) {
|
||
|
OutputStringBuffer str(ptl->pszText, ptl->cchTextMax);
|
||
|
str << _cDeleted << TEXT('/') << _cAdded;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
LRESULT LogEntry::GetDispInfo(NMTREELIST *ptl, int iColumn)
|
||
|
{
|
||
|
switch (iColumn) {
|
||
|
case -1: GetImage(ptl); break;
|
||
|
case 0: GetRevDispInfo(ptl); break;
|
||
|
case 1: ptl->pszText = _scChange; break;
|
||
|
case 2: ptl->pszText = CCAST(LPTSTR, c_rgleim[_iOp]._pszOp); break;
|
||
|
case 3: ptl->pszText = _scDate; break;
|
||
|
case 4: ptl->pszText = _scDev; break;
|
||
|
case 5: GetChurnDispInfo(ptl); break;
|
||
|
case 6: ptl->pszText = _scComment; break;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
LRESULT LogEntry::GetInfoTip(NMTREELIST *ptl)
|
||
|
{
|
||
|
ptl->pszText = _scFullDescription;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
*
|
||
|
* class CFileLog
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
|
||
|
class CFileLog : public TLFrame {
|
||
|
|
||
|
friend DWORD CALLBACK CFileLog_ThreadProc(LPVOID lpParameter);
|
||
|
|
||
|
protected:
|
||
|
LRESULT HandleMessage(UINT uiMsg, WPARAM wParam, LPARAM lParam);
|
||
|
|
||
|
private:
|
||
|
|
||
|
enum {
|
||
|
FL_INITIALIZE = WM_APP
|
||
|
};
|
||
|
|
||
|
typedef TLFrame super;
|
||
|
|
||
|
LRESULT ON_WM_CREATE(UINT uiMsg, WPARAM wParam, LPARAM lParam);
|
||
|
LRESULT ON_WM_COMMAND(UINT uiMsg, WPARAM wParam, LPARAM lParam);
|
||
|
LRESULT ON_WM_INITMENU(UINT uiMsg, WPARAM wParam, LPARAM lParam);
|
||
|
LRESULT ON_WM_NOTIFY(UINT uiMsg, WPARAM wParam, LPARAM lParam);
|
||
|
LRESULT ON_FL_INITIALIZE(UINT uiMsg, WPARAM wParam, LPARAM lParam);
|
||
|
|
||
|
private: /* Helpers */
|
||
|
CFileLog() : TLFrame(new LogEntry)
|
||
|
{
|
||
|
SetAcceleratorTable(MAKEINTRESOURCE(IDA_FILELOG));
|
||
|
}
|
||
|
|
||
|
LogEntry *LEGetCurSel() { return SAFECAST(LogEntry*, TLGetCurSel()); }
|
||
|
|
||
|
// -ds support was added in version 1.50
|
||
|
BOOL IsChurnEnabled()
|
||
|
{ return GlobalSettings.IsChurnEnabled() &&
|
||
|
GlobalSettings.IsVersion(1, 50); }
|
||
|
|
||
|
BOOL _ChooseColumns();
|
||
|
BOOL _ParseQuery();
|
||
|
LRESULT _FillChildren(LogEntry *pleRoot, LPCTSTR pszRootPath);
|
||
|
LRESULT _OnItemActivate(LogEntry *ple);
|
||
|
LRESULT _OnGetContextMenu(LogEntry *ple);
|
||
|
|
||
|
BOOL _IsViewFileLogEnabled(LogEntry *ple);
|
||
|
LRESULT _ViewFileLog(LogEntry *ple);
|
||
|
LRESULT _ViewChangelist(LogEntry *ple);
|
||
|
void _AdjustMenu(HMENU hmenu, LogEntry *ple, BOOL fContextMenu);
|
||
|
|
||
|
int _GetChangeNumber(LogEntry *ple);
|
||
|
int _GetBugNumber(LogEntry *ple);
|
||
|
|
||
|
struct FLCOLUMN {
|
||
|
const LVFCOLUMN* _rgcol;
|
||
|
const int * _rgColMap;
|
||
|
int _ccol;
|
||
|
};
|
||
|
|
||
|
private:
|
||
|
const FLCOLUMN* _pflc;
|
||
|
BOOL _fIsRestrictedRoot;
|
||
|
|
||
|
// Used during initialization
|
||
|
int _iHighlightRev;
|
||
|
LogEntry * _pleHighlight;
|
||
|
StringCache _scSwitches;
|
||
|
StringCache _scPath;
|
||
|
};
|
||
|
|
||
|
BOOL CFileLog::_ChooseColumns()
|
||
|
{
|
||
|
static const LVFCOLUMN c_rgcolChurn[] = {
|
||
|
{ 30 ,IDS_COL_REV ,LVCFMT_LEFT },
|
||
|
{ 7 ,IDS_COL_CHANGE ,LVCFMT_RIGHT },
|
||
|
{ 7 ,IDS_COL_OP ,LVCFMT_LEFT },
|
||
|
{ 15 ,IDS_COL_DATE ,LVCFMT_LEFT },
|
||
|
{ 10 ,IDS_COL_DEV ,LVCFMT_LEFT },
|
||
|
{ 7 ,IDS_COL_CHURN ,LVCFMT_LEFT },
|
||
|
{ 30 ,IDS_COL_COMMENT ,LVCFMT_LEFT },
|
||
|
{ 0 ,0 ,0 },
|
||
|
};
|
||
|
|
||
|
static const int c_rgiChurn[] = { 0, 1, 2, 3, 4, 5, 6 };
|
||
|
|
||
|
static const FLCOLUMN c_flcChurn = {
|
||
|
c_rgcolChurn,
|
||
|
c_rgiChurn,
|
||
|
ARRAYSIZE(c_rgiChurn),
|
||
|
};
|
||
|
|
||
|
static const LVFCOLUMN c_rgcolNoChurn[] = {
|
||
|
{ 30 ,IDS_COL_REV ,LVCFMT_LEFT },
|
||
|
{ 7 ,IDS_COL_CHANGE ,LVCFMT_RIGHT },
|
||
|
{ 7 ,IDS_COL_OP ,LVCFMT_LEFT },
|
||
|
{ 15 ,IDS_COL_DATE ,LVCFMT_LEFT },
|
||
|
{ 10 ,IDS_COL_DEV ,LVCFMT_LEFT },
|
||
|
{ 30 ,IDS_COL_COMMENT ,LVCFMT_LEFT },
|
||
|
{ 0 ,0 ,0 },
|
||
|
};
|
||
|
|
||
|
static const int c_rgiNoChurn[] = { 0, 1, 2, 3, 4, 6 };
|
||
|
|
||
|
static const FLCOLUMN c_flcNoChurn = {
|
||
|
c_rgcolNoChurn,
|
||
|
c_rgiNoChurn,
|
||
|
ARRAYSIZE(c_rgiNoChurn),
|
||
|
};
|
||
|
|
||
|
if (IsChurnEnabled()) {
|
||
|
_pflc = &c_flcChurn;
|
||
|
} else {
|
||
|
_pflc = &c_flcNoChurn;
|
||
|
}
|
||
|
|
||
|
return AddColumns(_pflc->_rgcol);
|
||
|
}
|
||
|
|
||
|
|
||
|
LRESULT CFileLog::ON_WM_CREATE(UINT uiMsg, WPARAM wParam, LPARAM lParam)
|
||
|
{
|
||
|
LRESULT lres;
|
||
|
|
||
|
if (_ParseQuery()) {
|
||
|
lres = super::HandleMessage(uiMsg, wParam, lParam);
|
||
|
if (lres == 0 &&
|
||
|
_tree.GetRoot() &&
|
||
|
SetWindowMenu(MAKEINTRESOURCE(IDM_FILELOG)) &&
|
||
|
CreateChild(LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS |
|
||
|
LVS_NOSORTHEADER,
|
||
|
LVS_EX_LABELTIP | LVS_EX_HEADERDRAGDROP |
|
||
|
LVS_EX_INFOTIP | LVS_EX_FULLROWSELECT) &&
|
||
|
_ChooseColumns()) {
|
||
|
PostMessage(_hwnd, FL_INITIALIZE, 0, 0);
|
||
|
} else {
|
||
|
lres = -1;
|
||
|
}
|
||
|
} else {
|
||
|
Help(_hwnd, TEXT("#filel"));
|
||
|
lres = -1;
|
||
|
}
|
||
|
return lres;
|
||
|
}
|
||
|
|
||
|
LRESULT CFileLog::ON_FL_INITIALIZE(UINT uiMsg, WPARAM wParam, LPARAM lParam)
|
||
|
{
|
||
|
_FillChildren(SAFECAST(LogEntry*, _tree.GetRoot()), _scPath);
|
||
|
_tree.Expand(_tree.GetRoot());
|
||
|
|
||
|
// Clean out the stuff that's used only for the initial root expand
|
||
|
_scSwitches = NULL;
|
||
|
_iHighlightRev = 0;
|
||
|
if (_pleHighlight) {
|
||
|
_tree.SetCurSel(_pleHighlight);
|
||
|
} else {
|
||
|
ListView_SetCurSel(_hwndChild, 0);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int CFileLog::_GetBugNumber(LogEntry *ple)
|
||
|
{
|
||
|
if (ple) {
|
||
|
return ParseBugNumber(ple->GetComment());
|
||
|
} else {
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int CFileLog::_GetChangeNumber(LogEntry *ple)
|
||
|
{
|
||
|
if (ple) {
|
||
|
return StrToInt(ple->GetChange());
|
||
|
} else {
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// View Filelog is enabled if it would show you something different
|
||
|
// from what you're looking at right now.
|
||
|
//
|
||
|
BOOL CFileLog::_IsViewFileLogEnabled(LogEntry *ple)
|
||
|
{
|
||
|
if (!ple) {
|
||
|
return FALSE; // not even an item!
|
||
|
}
|
||
|
|
||
|
if (_fIsRestrictedRoot) {
|
||
|
return TRUE; // View Filelog shows unrestricted
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Short-circuit the common case where you are already at top-level.
|
||
|
//
|
||
|
if (ple->Parent() == _tree.GetRoot()) {
|
||
|
return FALSE; // You're looking at it already
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Watch out for the loopback scenario where you chase integrations
|
||
|
// out and then back in...
|
||
|
//
|
||
|
LPCTSTR pszRoot = SAFECAST(LogEntry *, _tree.GetRoot())->GetChildPath();
|
||
|
LPCTSTR pszThis = SAFECAST(LogEntry *, ple->Parent())->GetChildPath();
|
||
|
int cchRoot = lstrlen(pszRoot);
|
||
|
|
||
|
if (StrCmpNI(pszRoot, pszThis, cchRoot) == 0 &&
|
||
|
(pszThis[cchRoot] == TEXT('#') || pszThis[cchRoot] == TEXT('\0'))) {
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
LRESULT CFileLog::_ViewFileLog(LogEntry *ple)
|
||
|
{
|
||
|
if (!_IsViewFileLogEnabled(ple)) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
Substring ss;
|
||
|
if (Parse(TEXT("$P"), SAFECAST(LogEntry *, ple->Parent())->GetChildPath(), &ss)) {
|
||
|
String str;
|
||
|
str << TEXT("-#") << ple->GetRev() << TEXT(" ") << ss;
|
||
|
LaunchThreadTask(CFileLog_ThreadProc, str);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
LRESULT CFileLog::_ViewChangelist(LogEntry *ple)
|
||
|
{
|
||
|
if (ple) {
|
||
|
LaunchThreadTask(CDescribe_ThreadProc, ple->GetChange());
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
LRESULT CFileLog::ON_WM_COMMAND(UINT uiMsg, WPARAM wParam, LPARAM lParam)
|
||
|
{
|
||
|
int iChange, iBug;
|
||
|
|
||
|
switch (GET_WM_COMMAND_ID(wParam, lParam)) {
|
||
|
case IDM_VIEWDESC:
|
||
|
return _ViewChangelist(LEGetCurSel());
|
||
|
|
||
|
case IDM_VIEWFILEDIFF:
|
||
|
return _OnItemActivate(LEGetCurSel());
|
||
|
|
||
|
case IDM_VIEWWINDIFF:
|
||
|
WindiffChangelist(_GetChangeNumber(LEGetCurSel()));
|
||
|
return 0;
|
||
|
|
||
|
case IDM_VIEWBUG:
|
||
|
iBug = _GetBugNumber(LEGetCurSel());
|
||
|
if (iBug) {
|
||
|
OpenBugWindow(_hwnd, iBug);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case IDM_VIEWFILELOG:
|
||
|
_ViewFileLog(LEGetCurSel());
|
||
|
break;
|
||
|
}
|
||
|
return super::HandleMessage(uiMsg, wParam, lParam);
|
||
|
}
|
||
|
|
||
|
void CFileLog::_AdjustMenu(HMENU hmenu, LogEntry *ple, BOOL fContextMenu)
|
||
|
{
|
||
|
AdjustBugMenu(hmenu, _GetBugNumber(ple), fContextMenu);
|
||
|
|
||
|
// Disable IDM_VIEWFILELOG if it would just show you the same window
|
||
|
// you're looking at right now.
|
||
|
BOOL fEnable = _IsViewFileLogEnabled(ple);
|
||
|
EnableDisableOrRemoveMenuItem(hmenu, IDM_VIEWFILELOG, fEnable, fContextMenu);
|
||
|
}
|
||
|
|
||
|
LRESULT CFileLog::ON_WM_INITMENU(UINT uiMsg, WPARAM wParam, LPARAM lParam)
|
||
|
{
|
||
|
_AdjustMenu(RECAST(HMENU, wParam), LEGetCurSel(), FALSE);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
LRESULT CFileLog::_OnGetContextMenu(LogEntry *ple)
|
||
|
{
|
||
|
HMENU hmenu = LoadPopupMenu(MAKEINTRESOURCE(IDM_FILELOG_POPUP));
|
||
|
if (hmenu) {
|
||
|
_AdjustMenu(hmenu, ple, TRUE);
|
||
|
}
|
||
|
return RECAST(LRESULT, hmenu);
|
||
|
}
|
||
|
|
||
|
LRESULT CFileLog::_OnItemActivate(LogEntry *ple)
|
||
|
{
|
||
|
if (ple) {
|
||
|
LogEntry *pleParent = SAFECAST(LogEntry*, ple->Parent());
|
||
|
|
||
|
// Trim the parent path to remove the sharp.
|
||
|
String strPath(pleParent->GetChildPath());
|
||
|
LPTSTR pszSharp = StrChr(strPath, TEXT('#'));
|
||
|
if (pszSharp) {
|
||
|
strPath.SetLength((int)(pszSharp - strPath));
|
||
|
}
|
||
|
|
||
|
// Append the version we care about
|
||
|
strPath << TEXT('#') << ple->GetRev();
|
||
|
|
||
|
// And ask windiff to view it
|
||
|
|
||
|
WindiffOneChange(strPath);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
LRESULT CFileLog::ON_WM_NOTIFY(UINT uiMsg, WPARAM wParam, LPARAM lParam)
|
||
|
{
|
||
|
NMTREELIST *ptl = RECAST(NMTREELIST*, lParam);
|
||
|
LogEntry *ple;
|
||
|
|
||
|
switch (ptl->hdr.code) {
|
||
|
case TLN_GETDISPINFO:
|
||
|
ple = SAFECAST(LogEntry*, ptl->pti);
|
||
|
if (ptl->iSubItem < 0) {
|
||
|
return ple->GetDispInfo(ptl, ptl->iSubItem);
|
||
|
} else if (ptl->iSubItem < _pflc->_ccol) {
|
||
|
return ple->GetDispInfo(ptl, _pflc->_rgColMap[ptl->iSubItem]);
|
||
|
} else {
|
||
|
ASSERT(0); // invalid column
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
case TLN_FILLCHILDREN:
|
||
|
ple = SAFECAST(LogEntry*, ptl->pti);
|
||
|
return _FillChildren(ple, ple->GetChildPath());
|
||
|
|
||
|
case TLN_ITEMACTIVATE:
|
||
|
ple = SAFECAST(LogEntry*, ptl->pti);
|
||
|
return _OnItemActivate(ple);
|
||
|
|
||
|
case TLN_GETINFOTIP:
|
||
|
ple = SAFECAST(LogEntry*, ptl->pti);
|
||
|
return ple->GetInfoTip(ptl);
|
||
|
|
||
|
case TLN_DELETEITEM:
|
||
|
ple = SAFECAST(LogEntry*, ptl->pti);
|
||
|
delete ple;
|
||
|
return 0;
|
||
|
|
||
|
case TLN_GETCONTEXTMENU:
|
||
|
ple = SAFECAST(LogEntry*, ptl->pti);
|
||
|
return _OnGetContextMenu(ple);
|
||
|
}
|
||
|
|
||
|
return super::HandleMessage(uiMsg, wParam, lParam);
|
||
|
}
|
||
|
|
||
|
LRESULT
|
||
|
CFileLog::HandleMessage(UINT uiMsg, WPARAM wParam, LPARAM lParam)
|
||
|
{
|
||
|
switch (uiMsg) {
|
||
|
FW_MSG(WM_CREATE);
|
||
|
FW_MSG(WM_COMMAND);
|
||
|
FW_MSG(WM_INITMENU);
|
||
|
FW_MSG(WM_NOTIFY);
|
||
|
FW_MSG(FL_INITIALIZE);
|
||
|
}
|
||
|
|
||
|
return super::HandleMessage(uiMsg, wParam, lParam);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// A private helper class that captures the parsing state machine.
|
||
|
//
|
||
|
|
||
|
class FileLogParseState : public CommentParser
|
||
|
{
|
||
|
public:
|
||
|
FileLogParseState() : _pleCurrent(NULL), _pleInsertAfter(NULL) { }
|
||
|
|
||
|
LogEntry *GetCurrentLogEntry() const { return _pleCurrent; }
|
||
|
|
||
|
void Flush()
|
||
|
{
|
||
|
if (_pleCurrent) {
|
||
|
//
|
||
|
// Trim the trailing CRLF off the last line of the full
|
||
|
// description.
|
||
|
//
|
||
|
_strFullDescription.Chomp();
|
||
|
_pleCurrent->SetFullDescription(_strFullDescription);
|
||
|
_pleCurrent = NULL;
|
||
|
}
|
||
|
_cAdded = _cDeleted = 0;
|
||
|
CommentParser::Reset();
|
||
|
_strFullDescription.Reset();
|
||
|
}
|
||
|
|
||
|
void AddEntry(Tree &tree, LogEntry *pleRoot, String& str, Substring *rgss)
|
||
|
{
|
||
|
LPCTSTR pszChildPath = pleRoot->GetChildPath();
|
||
|
_strFullDescription.Append(pszChildPath, StrCSpn(pszChildPath, TEXT("#")));
|
||
|
_strFullDescription << TEXT("\r\n") << str;
|
||
|
LogEntry *ple = new LogEntry(rgss[0].Finalize(), // Rev
|
||
|
rgss[1].Finalize(), // Change
|
||
|
rgss[2].Finalize(), // Op
|
||
|
rgss[3].Finalize(), // Date
|
||
|
rgss[4].Finalize()); // Dev
|
||
|
if (ple) {
|
||
|
if (tree.Insert(ple, pleRoot, _pleInsertAfter)) {
|
||
|
_pleInsertAfter = _pleCurrent = ple;
|
||
|
} else {
|
||
|
delete ple;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void AddLine(const String& str)
|
||
|
{
|
||
|
_strFullDescription << str;
|
||
|
}
|
||
|
|
||
|
void SetDev(LPCTSTR psz)
|
||
|
{
|
||
|
if (_pleCurrent) {
|
||
|
_pleCurrent->SetDev(psz);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SetComment(LPCTSTR psz)
|
||
|
{
|
||
|
if (_pleCurrent) {
|
||
|
_pleCurrent->SetComment(psz);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void SetIntegrateType(LPCTSTR pszType, LPCTSTR pszDepotPath)
|
||
|
{
|
||
|
if (_pleCurrent) {
|
||
|
_pleCurrent->SetIntegrateType(pszType);
|
||
|
_pleCurrent->SetChildPath(pszDepotPath);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SetIsDonor()
|
||
|
{
|
||
|
if (_pleCurrent) {
|
||
|
_pleCurrent->SetIsDonor();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void AddedLines(LPCTSTR psz)
|
||
|
{
|
||
|
_cAdded += StrToInt(psz);
|
||
|
}
|
||
|
|
||
|
void DeletedLines(LPCTSTR psz)
|
||
|
{
|
||
|
_cDeleted += StrToInt(psz);
|
||
|
}
|
||
|
|
||
|
void SetChurn()
|
||
|
{
|
||
|
if (_pleCurrent) {
|
||
|
_pleCurrent->SetChurn(_cAdded, _cDeleted);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ParseDiffResult(String& str)
|
||
|
{
|
||
|
Substring rgss[3];
|
||
|
|
||
|
if (Parse(TEXT("add $d chunks $d"), str, rgss)) {
|
||
|
AddedLines(rgss[1].Finalize());
|
||
|
} else if (Parse(TEXT("deleted $d chunks $d"), str, rgss)) {
|
||
|
DeletedLines(rgss[1].Finalize());
|
||
|
} else if (Parse(TEXT("changed $d chunks $d / $d"), str, rgss)) {
|
||
|
DeletedLines(rgss[1].Finalize());
|
||
|
AddedLines(rgss[2].Finalize());
|
||
|
SetChurn();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
BOOL GetFinishDiffCommand(LPCTSTR pszRootPath, String& str)
|
||
|
{
|
||
|
if (_pleCurrent && !_pleCurrent->IsChurnSet() && _pleCurrent->GetRev() > 1) {
|
||
|
str = TEXT("diff2 -ds \"");
|
||
|
int cchRootPath = StrCSpn(pszRootPath, TEXT("#"));
|
||
|
str.Append(pszRootPath, cchRootPath);
|
||
|
str << TEXT("#") << (_pleCurrent->GetRev() - 1) << TEXT("\" \"");
|
||
|
str.Append(pszRootPath, cchRootPath);
|
||
|
str << TEXT("#") << _pleCurrent->GetRev() << TEXT("\"");
|
||
|
return TRUE;
|
||
|
} else {
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
int _cAdded;
|
||
|
int _cDeleted;
|
||
|
LogEntry *_pleCurrent;
|
||
|
LogEntry *_pleInsertAfter;
|
||
|
String _strFullDescription;
|
||
|
};
|
||
|
|
||
|
BOOL CFileLog::_ParseQuery()
|
||
|
{
|
||
|
String str;
|
||
|
|
||
|
/*
|
||
|
* Parse the switches as best we can.
|
||
|
*
|
||
|
*/
|
||
|
str.Reset();
|
||
|
GetOpt opt(TEXT("m#"), _pszQuery);
|
||
|
for (;;) {
|
||
|
|
||
|
switch (opt.NextSwitch()) {
|
||
|
case TEXT('m'):
|
||
|
_fIsRestrictedRoot = TRUE;
|
||
|
str << TEXT("-m ") << opt.GetValue() << TEXT(" ");
|
||
|
break;
|
||
|
|
||
|
case TEXT('#'):
|
||
|
_iHighlightRev = StrToInt(opt.GetValue());
|
||
|
break;
|
||
|
|
||
|
case TEXT('\0'):
|
||
|
goto L_switch; // two-level break
|
||
|
|
||
|
default:
|
||
|
// Caller will display help for us
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
L_switch:;
|
||
|
|
||
|
_scSwitches = str;
|
||
|
|
||
|
str.Reset();
|
||
|
str << TEXT("sdv filelog ") << _scSwitches << opt.GetTokenizer().Unparsed();
|
||
|
SetWindowText(_hwnd, str);
|
||
|
|
||
|
/*
|
||
|
* There must be exactly one token remaining and it can't be a
|
||
|
* wildcard.
|
||
|
*/
|
||
|
if (opt.Token() && opt.Finished() && !ContainsWildcards(opt.GetValue())) {
|
||
|
_scPath = opt.GetValue();
|
||
|
if (StrChr(_scPath, TEXT('#')) || StrChr(_scPath, TEXT('@'))) {
|
||
|
_fIsRestrictedRoot = TRUE;
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
LRESULT CFileLog::_FillChildren(LogEntry *pleRoot, LPCTSTR pszRootPath)
|
||
|
{
|
||
|
LRESULT lres = 0;
|
||
|
if (!pszRootPath[0]) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
WaitCursor wait;
|
||
|
|
||
|
String str("filelog -l ");
|
||
|
str << _scSwitches;
|
||
|
if (IsChurnEnabled()) {
|
||
|
str << TEXT("-ds ");
|
||
|
}
|
||
|
|
||
|
str << ResolveBranchAndQuoteSpaces(pszRootPath);
|
||
|
|
||
|
SDChildProcess proc(str);
|
||
|
FileLogParseState state;
|
||
|
IOBuffer buf(proc.Handle());
|
||
|
while (buf.NextLine(str)) {
|
||
|
|
||
|
Substring rgss[5]; // Rev, Change, Op, Date, Dev
|
||
|
LPTSTR pszRest;
|
||
|
|
||
|
if (Parse(TEXT("... #$d change $d $w on $D by $u"), str, rgss)) {
|
||
|
state.Flush();
|
||
|
state.AddEntry(_tree, pleRoot, str, rgss);
|
||
|
if (state.GetCurrentLogEntry() &&
|
||
|
state.GetCurrentLogEntry()->GetRev() == _iHighlightRev) {
|
||
|
_pleHighlight = state.GetCurrentLogEntry();
|
||
|
}
|
||
|
} else if (Parse(TEXT("$P"), str, rgss)) {
|
||
|
if (pleRoot->GetChildPath().IsEmpty()) {
|
||
|
str.Chomp();
|
||
|
pleRoot->SetChildPath(rgss[0].Finalize());
|
||
|
}
|
||
|
} else {
|
||
|
state.AddLine(str);
|
||
|
str.Chomp();
|
||
|
if (str[0] == TEXT('\t')) {
|
||
|
state.AddComment(str+1);
|
||
|
} else if ((pszRest = Parse(TEXT("... ... $w from "), str, rgss)) != NULL) {
|
||
|
state.SetIntegrateType(rgss[0].Finalize(), pszRest);
|
||
|
} else if (Parse(TEXT("... ... $w into "), str, rgss) ||
|
||
|
Parse(TEXT("... ... ignored by "), str, rgss)) {
|
||
|
state.SetIsDonor();
|
||
|
|
||
|
// SUBTLE! We check for "ignored" after "ignored by".
|
||
|
} else if ((pszRest = Parse(TEXT("... ... ignored "), str, rgss)) != NULL) {
|
||
|
state.SetIntegrateType("ignored", pszRest);
|
||
|
} else {
|
||
|
state.ParseDiffResult(str);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// "sd filelog -d" doesn't spit out a diff for the last guy,
|
||
|
// so kick off a special one-shot "sd diff2" to get that diff.
|
||
|
if (IsChurnEnabled() &&
|
||
|
state.GetFinishDiffCommand(pszRootPath, str)) {
|
||
|
SDChildProcess proc2(str);
|
||
|
if (proc2.IsRunning()) {
|
||
|
buf.Init(proc2.Handle());
|
||
|
while (buf.NextLine(str)) {
|
||
|
state.AddLine(str);
|
||
|
state.ParseDiffResult(str);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
state.Flush();
|
||
|
|
||
|
return lres;
|
||
|
}
|
||
|
|
||
|
DWORD CALLBACK CFileLog_ThreadProc(LPVOID lpParameter)
|
||
|
{
|
||
|
return FrameWindow::RunThread(new CFileLog, lpParameter);
|
||
|
}
|