Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

829 lines
23 KiB

  1. /*****************************************************************************
  2. *
  3. * filelog.cpp
  4. *
  5. * View a filelog.
  6. *
  7. *****************************************************************************/
  8. #include "sdview.h"
  9. /*****************************************************************************
  10. *
  11. * LogEntry
  12. *
  13. * A single item in a filelog treelist.
  14. *
  15. *****************************************************************************/
  16. class LogEntry : public TreeItem {
  17. public:
  18. LogEntry(LPCTSTR pszRev,
  19. LPCTSTR pszChange,
  20. LPCTSTR pszOp,
  21. LPCTSTR pszDate,
  22. LPCTSTR pszDev);
  23. LogEntry() { }
  24. int GetRev() const { return _iRev; }
  25. void SetChildPath(LPCTSTR pszChildPath);
  26. const StringCache& GetChildPath() const { return _scChildPath; }
  27. void SetIntegrateType(LPCTSTR pszType);
  28. void SetIsDonor() { _fDonor = TRUE; }
  29. void SetComment(LPCTSTR pszComment) { _scComment = pszComment; }
  30. void SetDev(LPCTSTR pszDev) { _scDev = pszDev; }
  31. void SetFullDescription(LPCTSTR pszFullDescription) { _scFullDescription = pszFullDescription; }
  32. void SetChurn(int cAdded, int cDeleted) { _cAdded = cAdded; _cDeleted = cDeleted; }
  33. BOOL IsChurnSet() const { return _cAdded >= 0; }
  34. LRESULT GetDispInfo(NMTREELIST *pdi, int iColumn);
  35. LRESULT GetInfoTip(NMTREELIST *pdi);
  36. LPCTSTR GetChange() const { return _scChange; }
  37. LPCTSTR GetComment() const { return _scComment; }
  38. private:
  39. void GetRevDispInfo(NMTREELIST *ptl);
  40. void GetChurnDispInfo(NMTREELIST *ptl);
  41. void GetImage(NMTREELIST *ptl);
  42. private:
  43. int _iRev; // File revision number
  44. int _cDeleted; // Number of lines deleted
  45. int _cAdded; // Number of lines added
  46. int _iOp; // Checkin operation
  47. BOOL _fDonor; // Is integration donor
  48. StringCache _scChange; // Change number
  49. StringCache _scOp; // Checkin operation (edit, delete, tc.)
  50. StringCache _scDate; // Checkin date
  51. StringCache _scDev; // Checkin dev
  52. StringCache _scComment; // Checkin comment
  53. StringCache _scFullDescription; // Full checkin description
  54. StringCache _scChildPath; // Depot path of child items
  55. };
  56. void LogEntry::SetChildPath(LPCTSTR pszChildPath)
  57. {
  58. String str(pszChildPath);
  59. LPTSTR pszSharp = StrChr(str, TEXT('#'));
  60. if (pszSharp) {
  61. LPTSTR pszComma = StrChr(pszSharp, TEXT(','));
  62. if (!pszComma) {
  63. String strT(pszSharp);
  64. str << TEXT(",") << strT;
  65. }
  66. }
  67. _scChildPath = str;
  68. SetExpandable();
  69. }
  70. LogEntryImageMap c_rgleim[] = {
  71. { TEXT("?") , -1 }, // OP_UNKNOWN
  72. { TEXT("edit") , 0 }, // OP_EDIT
  73. { TEXT("delete") , 1 }, // OP_DELETE
  74. { TEXT("add") , 2 }, // OP_ADD
  75. { TEXT("integrate") , 3 }, // OP_INTEGRATE
  76. { TEXT("merge") , 3 }, // OP_MERGE
  77. { TEXT("branch") , 4 }, // OP_BRANCH
  78. { TEXT("copy") , 5 }, // OP_COPY
  79. { TEXT("ignored") , 6 }, // OP_IGNORED
  80. };
  81. int ParseOp(LPCTSTR psz)
  82. {
  83. int i;
  84. for (i = ARRAYSIZE(c_rgleim) - 1; i > 0; i--) {
  85. if (StrCmp(c_rgleim[i]._pszOp, psz) == 0) {
  86. break;
  87. }
  88. }
  89. return i;
  90. }
  91. void LogEntry::SetIntegrateType(LPCTSTR pszType)
  92. {
  93. if (_iOp == OP_INTEGRATE) {
  94. _iOp = ParseOp(pszType);
  95. }
  96. }
  97. LogEntry::LogEntry(
  98. LPCTSTR pszRev,
  99. LPCTSTR pszChange,
  100. LPCTSTR pszOp,
  101. LPCTSTR pszDate,
  102. LPCTSTR pszDev)
  103. : _iRev(StrToInt(pszRev))
  104. , _scChange(pszChange)
  105. , _iOp(ParseOp(pszOp))
  106. , _scDate(pszDate)
  107. , _scDev(pszDev)
  108. , _cAdded(-1)
  109. {
  110. }
  111. void LogEntry::GetImage(NMTREELIST *ptl)
  112. {
  113. ptl->iSubItem = c_rgleim[_iOp]._iImage;
  114. ptl->cchTextMax = INDEXTOOVERLAYMASK(_fDonor);
  115. }
  116. //
  117. // Combine the pszParent and the pszRev to form the real pszRev
  118. // Since the rev is the more important thing, I will display it
  119. // in the form
  120. //
  121. // 19 Lab06_DEV/foo.cpp
  122. //
  123. //
  124. void LogEntry::GetRevDispInfo(NMTREELIST *ptl)
  125. {
  126. OutputStringBuffer str(ptl->pszText, ptl->cchTextMax);
  127. str << _iRev;
  128. LogEntry *ple = SAFECAST(LogEntry*, Parent());
  129. if (ple->GetChildPath()) {
  130. str << TEXT(" ") << BranchOf(ple->GetChildPath()) <<
  131. TEXT("/") << FilenameOf(ple->GetChildPath());
  132. }
  133. }
  134. void LogEntry::GetChurnDispInfo(NMTREELIST *ptl)
  135. {
  136. if (_cAdded >= 0) {
  137. OutputStringBuffer str(ptl->pszText, ptl->cchTextMax);
  138. str << _cDeleted << TEXT('/') << _cAdded;
  139. }
  140. }
  141. LRESULT LogEntry::GetDispInfo(NMTREELIST *ptl, int iColumn)
  142. {
  143. switch (iColumn) {
  144. case -1: GetImage(ptl); break;
  145. case 0: GetRevDispInfo(ptl); break;
  146. case 1: ptl->pszText = _scChange; break;
  147. case 2: ptl->pszText = CCAST(LPTSTR, c_rgleim[_iOp]._pszOp); break;
  148. case 3: ptl->pszText = _scDate; break;
  149. case 4: ptl->pszText = _scDev; break;
  150. case 5: GetChurnDispInfo(ptl); break;
  151. case 6: ptl->pszText = _scComment; break;
  152. }
  153. return 0;
  154. }
  155. LRESULT LogEntry::GetInfoTip(NMTREELIST *ptl)
  156. {
  157. ptl->pszText = _scFullDescription;
  158. return 0;
  159. }
  160. /*****************************************************************************
  161. *
  162. * class CFileLog
  163. *
  164. *****************************************************************************/
  165. class CFileLog : public TLFrame {
  166. friend DWORD CALLBACK CFileLog_ThreadProc(LPVOID lpParameter);
  167. protected:
  168. LRESULT HandleMessage(UINT uiMsg, WPARAM wParam, LPARAM lParam);
  169. private:
  170. enum {
  171. FL_INITIALIZE = WM_APP
  172. };
  173. typedef TLFrame super;
  174. LRESULT ON_WM_CREATE(UINT uiMsg, WPARAM wParam, LPARAM lParam);
  175. LRESULT ON_WM_COMMAND(UINT uiMsg, WPARAM wParam, LPARAM lParam);
  176. LRESULT ON_WM_INITMENU(UINT uiMsg, WPARAM wParam, LPARAM lParam);
  177. LRESULT ON_WM_NOTIFY(UINT uiMsg, WPARAM wParam, LPARAM lParam);
  178. LRESULT ON_FL_INITIALIZE(UINT uiMsg, WPARAM wParam, LPARAM lParam);
  179. private: /* Helpers */
  180. CFileLog() : TLFrame(new LogEntry)
  181. {
  182. SetAcceleratorTable(MAKEINTRESOURCE(IDA_FILELOG));
  183. }
  184. LogEntry *LEGetCurSel() { return SAFECAST(LogEntry*, TLGetCurSel()); }
  185. // -ds support was added in version 1.50
  186. BOOL IsChurnEnabled()
  187. { return GlobalSettings.IsChurnEnabled() &&
  188. GlobalSettings.IsVersion(1, 50); }
  189. BOOL _ChooseColumns();
  190. BOOL _ParseQuery();
  191. LRESULT _FillChildren(LogEntry *pleRoot, LPCTSTR pszRootPath);
  192. LRESULT _OnItemActivate(LogEntry *ple);
  193. LRESULT _OnGetContextMenu(LogEntry *ple);
  194. BOOL _IsViewFileLogEnabled(LogEntry *ple);
  195. LRESULT _ViewFileLog(LogEntry *ple);
  196. LRESULT _ViewChangelist(LogEntry *ple);
  197. void _AdjustMenu(HMENU hmenu, LogEntry *ple, BOOL fContextMenu);
  198. int _GetChangeNumber(LogEntry *ple);
  199. int _GetBugNumber(LogEntry *ple);
  200. struct FLCOLUMN {
  201. const LVFCOLUMN* _rgcol;
  202. const int * _rgColMap;
  203. int _ccol;
  204. };
  205. private:
  206. const FLCOLUMN* _pflc;
  207. BOOL _fIsRestrictedRoot;
  208. // Used during initialization
  209. int _iHighlightRev;
  210. LogEntry * _pleHighlight;
  211. StringCache _scSwitches;
  212. StringCache _scPath;
  213. };
  214. BOOL CFileLog::_ChooseColumns()
  215. {
  216. static const LVFCOLUMN c_rgcolChurn[] = {
  217. { 30 ,IDS_COL_REV ,LVCFMT_LEFT },
  218. { 7 ,IDS_COL_CHANGE ,LVCFMT_RIGHT },
  219. { 7 ,IDS_COL_OP ,LVCFMT_LEFT },
  220. { 15 ,IDS_COL_DATE ,LVCFMT_LEFT },
  221. { 10 ,IDS_COL_DEV ,LVCFMT_LEFT },
  222. { 7 ,IDS_COL_CHURN ,LVCFMT_LEFT },
  223. { 30 ,IDS_COL_COMMENT ,LVCFMT_LEFT },
  224. { 0 ,0 ,0 },
  225. };
  226. static const int c_rgiChurn[] = { 0, 1, 2, 3, 4, 5, 6 };
  227. static const FLCOLUMN c_flcChurn = {
  228. c_rgcolChurn,
  229. c_rgiChurn,
  230. ARRAYSIZE(c_rgiChurn),
  231. };
  232. static const LVFCOLUMN c_rgcolNoChurn[] = {
  233. { 30 ,IDS_COL_REV ,LVCFMT_LEFT },
  234. { 7 ,IDS_COL_CHANGE ,LVCFMT_RIGHT },
  235. { 7 ,IDS_COL_OP ,LVCFMT_LEFT },
  236. { 15 ,IDS_COL_DATE ,LVCFMT_LEFT },
  237. { 10 ,IDS_COL_DEV ,LVCFMT_LEFT },
  238. { 30 ,IDS_COL_COMMENT ,LVCFMT_LEFT },
  239. { 0 ,0 ,0 },
  240. };
  241. static const int c_rgiNoChurn[] = { 0, 1, 2, 3, 4, 6 };
  242. static const FLCOLUMN c_flcNoChurn = {
  243. c_rgcolNoChurn,
  244. c_rgiNoChurn,
  245. ARRAYSIZE(c_rgiNoChurn),
  246. };
  247. if (IsChurnEnabled()) {
  248. _pflc = &c_flcChurn;
  249. } else {
  250. _pflc = &c_flcNoChurn;
  251. }
  252. return AddColumns(_pflc->_rgcol);
  253. }
  254. LRESULT CFileLog::ON_WM_CREATE(UINT uiMsg, WPARAM wParam, LPARAM lParam)
  255. {
  256. LRESULT lres;
  257. if (_ParseQuery()) {
  258. lres = super::HandleMessage(uiMsg, wParam, lParam);
  259. if (lres == 0 &&
  260. _tree.GetRoot() &&
  261. SetWindowMenu(MAKEINTRESOURCE(IDM_FILELOG)) &&
  262. CreateChild(LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS |
  263. LVS_NOSORTHEADER,
  264. LVS_EX_LABELTIP | LVS_EX_HEADERDRAGDROP |
  265. LVS_EX_INFOTIP | LVS_EX_FULLROWSELECT) &&
  266. _ChooseColumns()) {
  267. PostMessage(_hwnd, FL_INITIALIZE, 0, 0);
  268. } else {
  269. lres = -1;
  270. }
  271. } else {
  272. Help(_hwnd, TEXT("#filel"));
  273. lres = -1;
  274. }
  275. return lres;
  276. }
  277. LRESULT CFileLog::ON_FL_INITIALIZE(UINT uiMsg, WPARAM wParam, LPARAM lParam)
  278. {
  279. _FillChildren(SAFECAST(LogEntry*, _tree.GetRoot()), _scPath);
  280. _tree.Expand(_tree.GetRoot());
  281. // Clean out the stuff that's used only for the initial root expand
  282. _scSwitches = NULL;
  283. _iHighlightRev = 0;
  284. if (_pleHighlight) {
  285. _tree.SetCurSel(_pleHighlight);
  286. } else {
  287. ListView_SetCurSel(_hwndChild, 0);
  288. }
  289. return 0;
  290. }
  291. int CFileLog::_GetBugNumber(LogEntry *ple)
  292. {
  293. if (ple) {
  294. return ParseBugNumber(ple->GetComment());
  295. } else {
  296. return 0;
  297. }
  298. }
  299. int CFileLog::_GetChangeNumber(LogEntry *ple)
  300. {
  301. if (ple) {
  302. return StrToInt(ple->GetChange());
  303. } else {
  304. return 0;
  305. }
  306. }
  307. //
  308. // View Filelog is enabled if it would show you something different
  309. // from what you're looking at right now.
  310. //
  311. BOOL CFileLog::_IsViewFileLogEnabled(LogEntry *ple)
  312. {
  313. if (!ple) {
  314. return FALSE; // not even an item!
  315. }
  316. if (_fIsRestrictedRoot) {
  317. return TRUE; // View Filelog shows unrestricted
  318. }
  319. //
  320. // Short-circuit the common case where you are already at top-level.
  321. //
  322. if (ple->Parent() == _tree.GetRoot()) {
  323. return FALSE; // You're looking at it already
  324. }
  325. //
  326. // Watch out for the loopback scenario where you chase integrations
  327. // out and then back in...
  328. //
  329. LPCTSTR pszRoot = SAFECAST(LogEntry *, _tree.GetRoot())->GetChildPath();
  330. LPCTSTR pszThis = SAFECAST(LogEntry *, ple->Parent())->GetChildPath();
  331. int cchRoot = lstrlen(pszRoot);
  332. if (StrCmpNI(pszRoot, pszThis, cchRoot) == 0 &&
  333. (pszThis[cchRoot] == TEXT('#') || pszThis[cchRoot] == TEXT('\0'))) {
  334. return FALSE;
  335. }
  336. return TRUE;
  337. }
  338. LRESULT CFileLog::_ViewFileLog(LogEntry *ple)
  339. {
  340. if (!_IsViewFileLogEnabled(ple)) {
  341. return 0;
  342. }
  343. Substring ss;
  344. if (Parse(TEXT("$P"), SAFECAST(LogEntry *, ple->Parent())->GetChildPath(), &ss)) {
  345. String str;
  346. str << TEXT("-#") << ple->GetRev() << TEXT(" ") << ss;
  347. LaunchThreadTask(CFileLog_ThreadProc, str);
  348. }
  349. return 0;
  350. }
  351. LRESULT CFileLog::_ViewChangelist(LogEntry *ple)
  352. {
  353. if (ple) {
  354. LaunchThreadTask(CDescribe_ThreadProc, ple->GetChange());
  355. }
  356. return 0;
  357. }
  358. LRESULT CFileLog::ON_WM_COMMAND(UINT uiMsg, WPARAM wParam, LPARAM lParam)
  359. {
  360. int iChange, iBug;
  361. switch (GET_WM_COMMAND_ID(wParam, lParam)) {
  362. case IDM_VIEWDESC:
  363. return _ViewChangelist(LEGetCurSel());
  364. case IDM_VIEWFILEDIFF:
  365. return _OnItemActivate(LEGetCurSel());
  366. case IDM_VIEWWINDIFF:
  367. WindiffChangelist(_GetChangeNumber(LEGetCurSel()));
  368. return 0;
  369. case IDM_VIEWBUG:
  370. iBug = _GetBugNumber(LEGetCurSel());
  371. if (iBug) {
  372. OpenBugWindow(_hwnd, iBug);
  373. }
  374. break;
  375. case IDM_VIEWFILELOG:
  376. _ViewFileLog(LEGetCurSel());
  377. break;
  378. }
  379. return super::HandleMessage(uiMsg, wParam, lParam);
  380. }
  381. void CFileLog::_AdjustMenu(HMENU hmenu, LogEntry *ple, BOOL fContextMenu)
  382. {
  383. AdjustBugMenu(hmenu, _GetBugNumber(ple), fContextMenu);
  384. // Disable IDM_VIEWFILELOG if it would just show you the same window
  385. // you're looking at right now.
  386. BOOL fEnable = _IsViewFileLogEnabled(ple);
  387. EnableDisableOrRemoveMenuItem(hmenu, IDM_VIEWFILELOG, fEnable, fContextMenu);
  388. }
  389. LRESULT CFileLog::ON_WM_INITMENU(UINT uiMsg, WPARAM wParam, LPARAM lParam)
  390. {
  391. _AdjustMenu(RECAST(HMENU, wParam), LEGetCurSel(), FALSE);
  392. return 0;
  393. }
  394. LRESULT CFileLog::_OnGetContextMenu(LogEntry *ple)
  395. {
  396. HMENU hmenu = LoadPopupMenu(MAKEINTRESOURCE(IDM_FILELOG_POPUP));
  397. if (hmenu) {
  398. _AdjustMenu(hmenu, ple, TRUE);
  399. }
  400. return RECAST(LRESULT, hmenu);
  401. }
  402. LRESULT CFileLog::_OnItemActivate(LogEntry *ple)
  403. {
  404. if (ple) {
  405. LogEntry *pleParent = SAFECAST(LogEntry*, ple->Parent());
  406. // Trim the parent path to remove the sharp.
  407. String strPath(pleParent->GetChildPath());
  408. LPTSTR pszSharp = StrChr(strPath, TEXT('#'));
  409. if (pszSharp) {
  410. strPath.SetLength((int)(pszSharp - strPath));
  411. }
  412. // Append the version we care about
  413. strPath << TEXT('#') << ple->GetRev();
  414. // And ask windiff to view it
  415. WindiffOneChange(strPath);
  416. }
  417. return 0;
  418. }
  419. LRESULT CFileLog::ON_WM_NOTIFY(UINT uiMsg, WPARAM wParam, LPARAM lParam)
  420. {
  421. NMTREELIST *ptl = RECAST(NMTREELIST*, lParam);
  422. LogEntry *ple;
  423. switch (ptl->hdr.code) {
  424. case TLN_GETDISPINFO:
  425. ple = SAFECAST(LogEntry*, ptl->pti);
  426. if (ptl->iSubItem < 0) {
  427. return ple->GetDispInfo(ptl, ptl->iSubItem);
  428. } else if (ptl->iSubItem < _pflc->_ccol) {
  429. return ple->GetDispInfo(ptl, _pflc->_rgColMap[ptl->iSubItem]);
  430. } else {
  431. ASSERT(0); // invalid column
  432. return 0;
  433. }
  434. case TLN_FILLCHILDREN:
  435. ple = SAFECAST(LogEntry*, ptl->pti);
  436. return _FillChildren(ple, ple->GetChildPath());
  437. case TLN_ITEMACTIVATE:
  438. ple = SAFECAST(LogEntry*, ptl->pti);
  439. return _OnItemActivate(ple);
  440. case TLN_GETINFOTIP:
  441. ple = SAFECAST(LogEntry*, ptl->pti);
  442. return ple->GetInfoTip(ptl);
  443. case TLN_DELETEITEM:
  444. ple = SAFECAST(LogEntry*, ptl->pti);
  445. delete ple;
  446. return 0;
  447. case TLN_GETCONTEXTMENU:
  448. ple = SAFECAST(LogEntry*, ptl->pti);
  449. return _OnGetContextMenu(ple);
  450. }
  451. return super::HandleMessage(uiMsg, wParam, lParam);
  452. }
  453. LRESULT
  454. CFileLog::HandleMessage(UINT uiMsg, WPARAM wParam, LPARAM lParam)
  455. {
  456. switch (uiMsg) {
  457. FW_MSG(WM_CREATE);
  458. FW_MSG(WM_COMMAND);
  459. FW_MSG(WM_INITMENU);
  460. FW_MSG(WM_NOTIFY);
  461. FW_MSG(FL_INITIALIZE);
  462. }
  463. return super::HandleMessage(uiMsg, wParam, lParam);
  464. }
  465. //
  466. // A private helper class that captures the parsing state machine.
  467. //
  468. class FileLogParseState : public CommentParser
  469. {
  470. public:
  471. FileLogParseState() : _pleCurrent(NULL), _pleInsertAfter(NULL) { }
  472. LogEntry *GetCurrentLogEntry() const { return _pleCurrent; }
  473. void Flush()
  474. {
  475. if (_pleCurrent) {
  476. //
  477. // Trim the trailing CRLF off the last line of the full
  478. // description.
  479. //
  480. _strFullDescription.Chomp();
  481. _pleCurrent->SetFullDescription(_strFullDescription);
  482. _pleCurrent = NULL;
  483. }
  484. _cAdded = _cDeleted = 0;
  485. CommentParser::Reset();
  486. _strFullDescription.Reset();
  487. }
  488. void AddEntry(Tree &tree, LogEntry *pleRoot, String& str, Substring *rgss)
  489. {
  490. LPCTSTR pszChildPath = pleRoot->GetChildPath();
  491. _strFullDescription.Append(pszChildPath, StrCSpn(pszChildPath, TEXT("#")));
  492. _strFullDescription << TEXT("\r\n") << str;
  493. LogEntry *ple = new LogEntry(rgss[0].Finalize(), // Rev
  494. rgss[1].Finalize(), // Change
  495. rgss[2].Finalize(), // Op
  496. rgss[3].Finalize(), // Date
  497. rgss[4].Finalize()); // Dev
  498. if (ple) {
  499. if (tree.Insert(ple, pleRoot, _pleInsertAfter)) {
  500. _pleInsertAfter = _pleCurrent = ple;
  501. } else {
  502. delete ple;
  503. }
  504. }
  505. }
  506. void AddLine(const String& str)
  507. {
  508. _strFullDescription << str;
  509. }
  510. void SetDev(LPCTSTR psz)
  511. {
  512. if (_pleCurrent) {
  513. _pleCurrent->SetDev(psz);
  514. }
  515. }
  516. void SetComment(LPCTSTR psz)
  517. {
  518. if (_pleCurrent) {
  519. _pleCurrent->SetComment(psz);
  520. }
  521. }
  522. void SetIntegrateType(LPCTSTR pszType, LPCTSTR pszDepotPath)
  523. {
  524. if (_pleCurrent) {
  525. _pleCurrent->SetIntegrateType(pszType);
  526. _pleCurrent->SetChildPath(pszDepotPath);
  527. }
  528. }
  529. void SetIsDonor()
  530. {
  531. if (_pleCurrent) {
  532. _pleCurrent->SetIsDonor();
  533. }
  534. }
  535. void AddedLines(LPCTSTR psz)
  536. {
  537. _cAdded += StrToInt(psz);
  538. }
  539. void DeletedLines(LPCTSTR psz)
  540. {
  541. _cDeleted += StrToInt(psz);
  542. }
  543. void SetChurn()
  544. {
  545. if (_pleCurrent) {
  546. _pleCurrent->SetChurn(_cAdded, _cDeleted);
  547. }
  548. }
  549. void ParseDiffResult(String& str)
  550. {
  551. Substring rgss[3];
  552. if (Parse(TEXT("add $d chunks $d"), str, rgss)) {
  553. AddedLines(rgss[1].Finalize());
  554. } else if (Parse(TEXT("deleted $d chunks $d"), str, rgss)) {
  555. DeletedLines(rgss[1].Finalize());
  556. } else if (Parse(TEXT("changed $d chunks $d / $d"), str, rgss)) {
  557. DeletedLines(rgss[1].Finalize());
  558. AddedLines(rgss[2].Finalize());
  559. SetChurn();
  560. }
  561. }
  562. BOOL GetFinishDiffCommand(LPCTSTR pszRootPath, String& str)
  563. {
  564. if (_pleCurrent && !_pleCurrent->IsChurnSet() && _pleCurrent->GetRev() > 1) {
  565. str = TEXT("diff2 -ds \"");
  566. int cchRootPath = StrCSpn(pszRootPath, TEXT("#"));
  567. str.Append(pszRootPath, cchRootPath);
  568. str << TEXT("#") << (_pleCurrent->GetRev() - 1) << TEXT("\" \"");
  569. str.Append(pszRootPath, cchRootPath);
  570. str << TEXT("#") << _pleCurrent->GetRev() << TEXT("\"");
  571. return TRUE;
  572. } else {
  573. return FALSE;
  574. }
  575. }
  576. private:
  577. int _cAdded;
  578. int _cDeleted;
  579. LogEntry *_pleCurrent;
  580. LogEntry *_pleInsertAfter;
  581. String _strFullDescription;
  582. };
  583. BOOL CFileLog::_ParseQuery()
  584. {
  585. String str;
  586. /*
  587. * Parse the switches as best we can.
  588. *
  589. */
  590. str.Reset();
  591. GetOpt opt(TEXT("m#"), _pszQuery);
  592. for (;;) {
  593. switch (opt.NextSwitch()) {
  594. case TEXT('m'):
  595. _fIsRestrictedRoot = TRUE;
  596. str << TEXT("-m ") << opt.GetValue() << TEXT(" ");
  597. break;
  598. case TEXT('#'):
  599. _iHighlightRev = StrToInt(opt.GetValue());
  600. break;
  601. case TEXT('\0'):
  602. goto L_switch; // two-level break
  603. default:
  604. // Caller will display help for us
  605. return FALSE;
  606. }
  607. }
  608. L_switch:;
  609. _scSwitches = str;
  610. str.Reset();
  611. str << TEXT("sdv filelog ") << _scSwitches << opt.GetTokenizer().Unparsed();
  612. SetWindowText(_hwnd, str);
  613. /*
  614. * There must be exactly one token remaining and it can't be a
  615. * wildcard.
  616. */
  617. if (opt.Token() && opt.Finished() && !ContainsWildcards(opt.GetValue())) {
  618. _scPath = opt.GetValue();
  619. if (StrChr(_scPath, TEXT('#')) || StrChr(_scPath, TEXT('@'))) {
  620. _fIsRestrictedRoot = TRUE;
  621. }
  622. } else {
  623. return FALSE;
  624. }
  625. return TRUE;
  626. }
  627. LRESULT CFileLog::_FillChildren(LogEntry *pleRoot, LPCTSTR pszRootPath)
  628. {
  629. LRESULT lres = 0;
  630. if (!pszRootPath[0]) {
  631. return -1;
  632. }
  633. WaitCursor wait;
  634. String str("filelog -l ");
  635. str << _scSwitches;
  636. if (IsChurnEnabled()) {
  637. str << TEXT("-ds ");
  638. }
  639. str << ResolveBranchAndQuoteSpaces(pszRootPath);
  640. SDChildProcess proc(str);
  641. FileLogParseState state;
  642. IOBuffer buf(proc.Handle());
  643. while (buf.NextLine(str)) {
  644. Substring rgss[5]; // Rev, Change, Op, Date, Dev
  645. LPTSTR pszRest;
  646. if (Parse(TEXT("... #$d change $d $w on $D by $u"), str, rgss)) {
  647. state.Flush();
  648. state.AddEntry(_tree, pleRoot, str, rgss);
  649. if (state.GetCurrentLogEntry() &&
  650. state.GetCurrentLogEntry()->GetRev() == _iHighlightRev) {
  651. _pleHighlight = state.GetCurrentLogEntry();
  652. }
  653. } else if (Parse(TEXT("$P"), str, rgss)) {
  654. if (pleRoot->GetChildPath().IsEmpty()) {
  655. str.Chomp();
  656. pleRoot->SetChildPath(rgss[0].Finalize());
  657. }
  658. } else {
  659. state.AddLine(str);
  660. str.Chomp();
  661. if (str[0] == TEXT('\t')) {
  662. state.AddComment(str+1);
  663. } else if ((pszRest = Parse(TEXT("... ... $w from "), str, rgss)) != NULL) {
  664. state.SetIntegrateType(rgss[0].Finalize(), pszRest);
  665. } else if (Parse(TEXT("... ... $w into "), str, rgss) ||
  666. Parse(TEXT("... ... ignored by "), str, rgss)) {
  667. state.SetIsDonor();
  668. // SUBTLE! We check for "ignored" after "ignored by".
  669. } else if ((pszRest = Parse(TEXT("... ... ignored "), str, rgss)) != NULL) {
  670. state.SetIntegrateType("ignored", pszRest);
  671. } else {
  672. state.ParseDiffResult(str);
  673. }
  674. }
  675. }
  676. // "sd filelog -d" doesn't spit out a diff for the last guy,
  677. // so kick off a special one-shot "sd diff2" to get that diff.
  678. if (IsChurnEnabled() &&
  679. state.GetFinishDiffCommand(pszRootPath, str)) {
  680. SDChildProcess proc2(str);
  681. if (proc2.IsRunning()) {
  682. buf.Init(proc2.Handle());
  683. while (buf.NextLine(str)) {
  684. state.AddLine(str);
  685. state.ParseDiffResult(str);
  686. }
  687. }
  688. }
  689. state.Flush();
  690. return lres;
  691. }
  692. DWORD CALLBACK CFileLog_ThreadProc(LPVOID lpParameter)
  693. {
  694. return FrameWindow::RunThread(new CFileLog, lpParameter);
  695. }