windows-nt/Source/XPSP1/NT/sdktools/sdv/filelog.cpp
2020-09-26 16:20:57 +08:00

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);
}