1 // GitLogList.cpp : implementation file
4 Description: qgit revision list view
6 Author: Marco Costalba (C) 2005-2007
8 Copyright: See COPYING file that comes with this distribution
12 #include "TortoiseProc.h"
13 #include "GitLogList.h"
15 //#include "VssStyle.h"
21 #include "SVNProgressDlg.h"
22 #include "ProgressDlg.h"
23 //#include "RepositoryBrowser.h"
24 //#include "CopyDlg.h"
25 //#include "StatGraphDlg.h"
27 #include "MessageBox.h"
30 #include "PathUtils.h"
31 #include "StringUtils.h"
32 #include "UnicodeUtils.h"
34 //#include "GitInfo.h"
35 //#include "GitDiff.h"
37 //#include "RevisionRangeDlg.h"
38 //#include "BrowseFolder.h"
39 //#include "BlameDlg.h"
41 //#include "GitHelpers.h"
42 #include "GitStatus.h"
43 //#include "LogDlgHelper.h"
44 //#include "CachedLogInfo.h"
45 //#include "RepositoryInfo.h"
46 //#include "EditPropertiesDlg.h"
47 #include "FileDiffDlg.h"
52 IMPLEMENT_DYNAMIC(CGitLogList, CHintListCtrl)
54 CGitLogList::CGitLogList():CHintListCtrl()
55 ,m_regMaxBugIDColWidth(_T("Software\\TortoiseGit\\MaxBugIDColWidth"), 200)
57 ,m_bNoDispUpdates(FALSE)
58 , m_bThreadRunning(FALSE)
59 , m_bStrictStopped(false)
60 , m_pStoreSelection(NULL)
62 // use the default GUI font, create a copy of it and
63 // change the copy to BOLD (leave the rest of the font
65 HFONT hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
67 GetObject(hFont, sizeof(LOGFONT), &lf);
68 lf.lfWeight = FW_BOLD;
69 m_boldFont = CreateFontIndirect(&lf);
71 m_wcRev.m_CommitHash=GIT_REV_ZERO;
72 m_wcRev.m_Subject=_T("Working Copy");
74 m_hModifiedIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONMODIFIED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
75 m_hReplacedIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONREPLACED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
76 m_hAddedIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONADDED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
77 m_hDeletedIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONDELETED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
79 g_Git.GetMapHashToFriendName(m_HashMap);
82 CGitLogList::~CGitLogList()
84 InterlockedExchange(&m_bNoDispUpdates, TRUE);
86 DestroyIcon(m_hModifiedIcon);
87 DestroyIcon(m_hReplacedIcon);
88 DestroyIcon(m_hAddedIcon);
89 DestroyIcon(m_hDeletedIcon);
90 m_logEntries.ClearAll();
93 DeleteObject(m_boldFont);
95 if ( m_pStoreSelection )
97 delete m_pStoreSelection;
98 m_pStoreSelection = NULL;
103 BEGIN_MESSAGE_MAP(CGitLogList, CHintListCtrl)
104 ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnNMCustomdrawLoglist)
105 ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnLvnGetdispinfoLoglist)
107 ON_NOTIFY_REFLECT(NM_DBLCLK, OnNMDblclkLoglist)
108 ON_NOTIFY_REFLECT(LVN_ODFINDITEM,OnLvnOdfinditemLoglist)
112 int CGitLogList:: OnCreate(LPCREATESTRUCT lpCreateStruct)
115 return CHintListCtrl::OnCreate(lpCreateStruct);
118 void CGitLogList::PreSubclassWindow()
120 SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER | LVS_EX_SUBITEMIMAGES);
121 // load the icons for the action columns
122 m_Theme.SetWindowTheme(GetSafeHwnd(), L"Explorer", NULL);
123 CHintListCtrl::PreSubclassWindow();
126 void CGitLogList::InsertGitColumn()
130 int c = ((CHeaderCtrl*)(GetDlgItem(0)))->GetItemCount()-1;
134 temp.LoadString(IDS_LOG_GRAPH);
136 InsertColumn(this->LOGLIST_GRAPH, temp);
139 // make the revision column right aligned
141 Column.mask = LVCF_FMT;
142 Column.fmt = LVCFMT_RIGHT;
143 SetColumn(0, &Column);
146 // g_Git.GetLog(log);
148 temp.LoadString(IDS_LOG_ACTIONS);
149 InsertColumn(this->LOGLIST_ACTION, temp);
151 temp.LoadString(IDS_LOG_MESSAGE);
152 InsertColumn(this->LOGLIST_MESSAGE, temp);
154 temp.LoadString(IDS_LOG_AUTHOR);
155 InsertColumn(this->LOGLIST_AUTHOR, temp);
157 temp.LoadString(IDS_LOG_DATE);
158 InsertColumn(this->LOGLIST_DATE, temp);
161 if (m_bShowBugtraqColumn)
163 // temp = m_ProjectProperties.sLabel;
165 temp.LoadString(IDS_LOG_BUGIDS);
166 InsertColumn(this->LOGLIST_BUG, temp);
171 ResizeAllListCtrlCols();
176 void CGitLogList::ResizeAllListCtrlCols()
179 const int nMinimumWidth = ICONITEMBORDER+16*4;
180 int maxcol = ((CHeaderCtrl*)(GetDlgItem(0)))->GetItemCount()-1;
181 int nItemCount = GetItemCount();
182 TCHAR textbuf[MAX_PATH];
183 CHeaderCtrl * pHdrCtrl = (CHeaderCtrl*)(GetDlgItem(0));
186 for (int col = 0; col <= maxcol; col++)
190 hdi.pszText = textbuf;
191 hdi.cchTextMax = sizeof(textbuf);
192 pHdrCtrl->GetItem(col, &hdi);
193 int cx = GetStringWidth(hdi.pszText)+20; // 20 pixels for col separator and margin
194 for (int index = 0; index<nItemCount; ++index)
196 // get the width of the string and add 14 pixels for the column separator and margins
197 int linewidth = GetStringWidth(GetItemText(index, col)) + 14;
198 if (index < m_arShownList.GetCount())
200 GitRev * pCurLogEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(index));
201 if ((pCurLogEntry)&&(pCurLogEntry->m_CommitHash == m_wcRev.m_CommitHash))
203 // set the bold font and ask for the string width again
204 SendMessage(WM_SETFONT, (WPARAM)m_boldFont, NULL);
205 linewidth = GetStringWidth(GetItemText(index, col)) + 14;
206 // restore the system font
207 SendMessage(WM_SETFONT, NULL, NULL);
212 // add the image size
213 CImageList * pImgList = GetImageList(LVSIL_SMALL);
214 if ((pImgList)&&(pImgList->GetImageCount()))
217 pImgList->GetImageInfo(0, &imginfo);
218 linewidth += (imginfo.rcImage.right - imginfo.rcImage.left);
219 linewidth += 3; // 3 pixels between icon and text
225 // Adjust columns "Actions" containing icons
226 if (col == this->LOGLIST_ACTION)
228 if (cx < nMinimumWidth)
234 if (col == this->LOGLIST_MESSAGE)
236 if (cx > LOGLIST_MESSAGE_MAX)
238 cx = LOGLIST_MESSAGE_MAX;
242 // keep the bug id column small
243 if ((col == 4)&&(m_bShowBugtraqColumn))
245 if (cx > (int)(DWORD)m_regMaxBugIDColWidth)
247 cx = (int)(DWORD)m_regMaxBugIDColWidth;
251 SetColumnWidth(col, cx);
256 BOOL CGitLogList::GetShortName(CString ref, CString &shortname,CString prefix)
258 if(ref.Left(prefix.GetLength()) == prefix)
260 shortname = ref.Right(ref.GetLength()-prefix.GetLength());
265 void CGitLogList::FillBackGround(HDC hdc, int Index,CRect &rect)
269 SecureZeroMemory(&rItem, sizeof(LVITEM));
270 rItem.mask = LVIF_STATE;
272 rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
275 if (m_Theme.IsAppThemed() && m_bVista)
277 m_Theme.Open(m_hWnd, L"Explorer");
278 int state = LISS_NORMAL;
279 if (rItem.state & LVIS_SELECTED)
281 if (::GetFocus() == m_hWnd)
282 state |= LISS_SELECTED;
284 state |= LISS_SELECTEDNOTFOCUS;
289 if (pLogEntry->bCopiedSelf)
291 // unfortunately, the pLVCD->nmcd.uItemState does not contain valid
292 // information at this drawing stage. But we can check the whether the
293 // previous stage changed the background color of the item
294 if (pLVCD->clrTextBk == GetSysColor(COLOR_MENU))
297 brush = ::CreateSolidBrush(::GetSysColor(COLOR_MENU));
300 ::FillRect(pLVCD->nmcd.hdc, &rect, brush);
301 ::DeleteObject(brush);
308 if (m_Theme.IsBackgroundPartiallyTransparent(LVP_LISTDETAIL, state))
309 m_Theme.DrawParentBackground(m_hWnd, hdc, &rect);
311 m_Theme.DrawBackground(hdc, LVP_LISTDETAIL, state, &rect, NULL);
316 if (rItem.state & LVIS_SELECTED)
318 if (::GetFocus() == m_hWnd)
319 brush = ::CreateSolidBrush(::GetSysColor(COLOR_HIGHLIGHT));
321 brush = ::CreateSolidBrush(::GetSysColor(COLOR_BTNFACE));
325 //if (pLogEntry->bCopiedSelf)
326 // brush = ::CreateSolidBrush(::GetSysColor(COLOR_MENU));
328 brush = ::CreateSolidBrush(::GetSysColor(COLOR_WINDOW));
333 ::FillRect(hdc, &rect, brush);
334 ::DeleteObject(brush);
339 void CGitLogList::DrawTagBranch(HDC hdc,CRect &rect,INT_PTR index)
341 GitRev* data = (GitRev*)m_arShownList.GetAt(index);
344 SecureZeroMemory(&rItem, sizeof(LVITEM));
345 rItem.mask = LVIF_STATE;
347 rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
350 for(int i=0;i<m_HashMap[data->m_CommitHash].size();i++)
353 str=m_HashMap[data->m_CommitHash][i];
358 if(GetShortName(str,shortname,_T("refs/heads/")))
360 brush = ::CreateSolidBrush(RGB(0xff, 0, 0));
361 }else if(GetShortName(str,shortname,_T("refs/remotes/")))
363 brush = ::CreateSolidBrush(RGB(0xff, 0xff, 0));
365 else if(GetShortName(str,shortname,_T("refs/tags/")))
367 brush = ::CreateSolidBrush(RGB(0, 0, 0xff));
370 if(!shortname.IsEmpty())
373 memset(&size,0,sizeof(SIZE));
374 GetTextExtentPoint32(hdc, shortname,shortname.GetLength(),&size);
376 rt.SetRect(rt.left,rt.top,rt.left+size.cx,rt.bottom);
378 ::FillRect(hdc, &rt, brush);
379 if (rItem.state & LVIS_SELECTED)
381 COLORREF clrOld = ::SetTextColor(hdc,::GetSysColor(COLOR_HIGHLIGHTTEXT));
382 ::DrawText(hdc,shortname,shortname.GetLength(),&rt,DT_CENTER);
383 ::SetTextColor(hdc,clrOld);
386 ::DrawText(hdc,shortname,shortname.GetLength(),&rt,DT_CENTER);
390 ::MoveToEx(hdc,rt.left,rt.top,NULL);
391 ::LineTo(hdc,rt.right,rt.top);
392 ::LineTo(hdc,rt.right,rt.bottom);
393 ::LineTo(hdc,rt.left,rt.bottom);
394 ::LineTo(hdc,rt.left,rt.top);
399 ::DeleteObject(brush);
403 if (rItem.state & LVIS_SELECTED)
405 COLORREF clrOld = ::SetTextColor(hdc,::GetSysColor(COLOR_HIGHLIGHTTEXT));
406 ::DrawText(hdc,data->m_Subject,data->m_Subject.GetLength(),&rt,DT_LEFT);
407 ::SetTextColor(hdc,clrOld);
410 ::DrawText(hdc,data->m_Subject,data->m_Subject.GetLength(),&rt,DT_LEFT);
415 void CGitLogList::paintGraphLane(HDC hdc, int laneHeight,int type, int x1, int x2,
416 const COLORREF& col,int top
419 int h = laneHeight / 2;
420 int m = (x1 + x2) / 2;
421 int r = (x2 - x1) / 3;
424 #define P_CENTER m , h+top
425 #define P_0 x2, h+top
426 #define P_90 m , 0+top
427 #define P_180 x1, h+top
428 #define P_270 m , 2 * h+top
429 #define R_CENTER m - r, h - r+top, m - r+d, h - r+top+d
431 //static QPen myPen(Qt::black, 2); // fast path here
433 pen.CreatePen(PS_SOLID,2,col);
434 //myPen.setColor(col);
435 HPEN oldpen=(HPEN)::SelectObject(hdc,(HPEN)pen);
442 case Lanes::NOT_ACTIVE:
443 case Lanes::MERGE_FORK:
444 case Lanes::MERGE_FORK_R:
445 case Lanes::MERGE_FORK_L:
449 DrawLine(hdc,P_90,P_270);
450 //p->drawLine(P_90, P_270);
456 DrawLine(hdc,P_CENTER,P_270);
457 //p->drawLine(P_CENTER, P_270);
463 case Lanes::BOUNDARY:
464 case Lanes::BOUNDARY_C:
465 case Lanes::BOUNDARY_R:
466 case Lanes::BOUNDARY_L:
467 DrawLine(hdc,P_90, P_CENTER);
468 //p->drawLine(P_90, P_CENTER);
476 case Lanes::MERGE_FORK:
481 case Lanes::CROSS_EMPTY:
482 case Lanes::BOUNDARY_C:
483 DrawLine(hdc,P_180,P_0);
484 //p->drawLine(P_180, P_0);
486 case Lanes::MERGE_FORK_R:
490 case Lanes::BOUNDARY_R:
491 DrawLine(hdc,P_180,P_CENTER);
492 //p->drawLine(P_180, P_CENTER);
494 case Lanes::MERGE_FORK_L:
498 case Lanes::BOUNDARY_L:
499 DrawLine(hdc,P_CENTER,P_0);
500 //p->drawLine(P_CENTER, P_0);
507 brush.CreateSolidBrush(col);
508 HBRUSH oldbrush=(HBRUSH)::SelectObject(hdc,(HBRUSH)brush);
509 // center symbol, e.g. rect or ellipse
515 //p->setPen(Qt::NoPen);
517 ::Ellipse(hdc, R_CENTER);
518 //p->drawEllipse(R_CENTER);
520 case Lanes::MERGE_FORK:
521 case Lanes::MERGE_FORK_R:
522 case Lanes::MERGE_FORK_L:
523 //p->setPen(Qt::NoPen);
525 //p->drawRect(R_CENTER);
526 Rectangle(hdc,R_CENTER);
528 case Lanes::UNAPPLIED:
530 //p->setPen(Qt::NoPen);
531 //p->setBrush(Qt::red);
532 //p->drawRect(m - r, h - 1, d, 2);
533 ::Rectangle(hdc,m-r,h-1,d,2);
537 //p->setPen(Qt::NoPen);
538 //p->setBrush(DARK_GREEN);
539 //p->drawRect(m - r, h - 1, d, 2);
540 //p->drawRect(m - 1, h - r, 2, d);
541 ::Rectangle(hdc,m-r,h-1,d,2);
542 ::Rectangle(hdc,m-1,h-r,2,d);
544 case Lanes::BOUNDARY:
546 //p->drawEllipse(R_CENTER);
547 ::Ellipse(hdc, R_CENTER);
549 case Lanes::BOUNDARY_C:
550 case Lanes::BOUNDARY_R:
551 case Lanes::BOUNDARY_L:
553 //p->drawRect(R_CENTER);
554 ::Rectangle(hdc,R_CENTER);
560 ::SelectObject(hdc,oldpen);
561 ::SelectObject(hdc,oldbrush);
570 void CGitLogList::DrawGraph(HDC hdc,CRect &rect,INT_PTR index)
574 GitRev* data = (GitRev*)m_arShownList.GetAt(index);
577 SecureZeroMemory(&rItem, sizeof(LVITEM));
578 rItem.mask = LVIF_STATE;
580 rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
583 static const COLORREF colors[Lanes::COLORS_NUM] = { RGB(0,0,0), RGB(0xFF,0,0), RGB(0,0x1F,0),
584 RGB(0,0,0xFF), RGB(128,128,128), RGB(128,128,0),
585 RGB(0,128,128), RGB(128,0,128) };
588 // p->translate(QPoint(opt.rect.left(), opt.rect.top()));
592 if (data->m_Lanes.size() == 0)
593 m_logEntries.setLane(data->m_CommitHash);
595 std::vector<int>& lanes=data->m_Lanes;
596 UINT laneNum = lanes.size();
598 for (UINT i = 0; i < laneNum; i++)
599 if (Lanes::isMerge(lanes[i])) {
605 int maxWidth = rect.Width();
606 int lw = 3 * rect.Height() / 4; //laneWidth()
607 for (UINT i = 0; i < laneNum && x2 < maxWidth; i++) {
613 if (ln == Lanes::EMPTY)
616 UINT col = ( Lanes:: isHead(ln) ||Lanes:: isTail(ln) || Lanes::isJoin(ln)
617 || ln ==Lanes:: CROSS_EMPTY) ? mergeLane : i;
619 if (ln == Lanes::CROSS) {
620 paintGraphLane(hdc, rect.Height(),Lanes::NOT_ACTIVE, x1, x2, colors[col % Lanes::COLORS_NUM],rect.top);
621 paintGraphLane(hdc, rect.Height(),Lanes::CROSS, x1, x2, colors[mergeLane % Lanes::COLORS_NUM],rect.top);
623 paintGraphLane(hdc, rect.Height(),ln, x1, x2, colors[col % Lanes::COLORS_NUM],rect.top);
626 TRACE(_T("index %d %d\r\n"),index,data->m_Lanes.size());
629 void CGitLogList::OnNMCustomdrawLoglist(NMHDR *pNMHDR, LRESULT *pResult)
632 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
633 // Take the default processing unless we set this to something else below.
634 *pResult = CDRF_DODEFAULT;
636 if (m_bNoDispUpdates)
639 switch (pLVCD->nmcd.dwDrawStage)
643 *pResult = CDRF_NOTIFYITEMDRAW;
647 case CDDS_ITEMPREPAINT:
649 // This is the prepaint stage for an item. Here's where we set the
650 // item's text color.
652 // Tell Windows to send draw notifications for each subitem.
653 *pResult = CDRF_NOTIFYSUBITEMDRAW;
655 COLORREF crText = GetSysColor(COLOR_WINDOWTEXT);
657 if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec)
659 GitRev* data = (GitRev*)m_arShownList.GetAt(pLVCD->nmcd.dwItemSpec);
663 if (data->bCopiedSelf)
665 // only change the background color if the item is not 'hot' (on vista with m_Themes enabled)
666 if (!m_Theme.IsAppm_Themed() || !m_bVista || ((pLVCD->nmcd.uItemState & CDIS_HOT)==0))
667 pLVCD->clrTextBk = GetSysColor(COLOR_MENU);
671 crText = m_Colors.GetColor(CColors::Modified);
673 // if ((data->childStackDepth)||(m_mergedRevs.find(data->Rev) != m_mergedRevs.end()))
674 // crText = GetSysColor(COLOR_GRAYTEXT);
675 // if (data->Rev == m_wcRev)
677 // SelectObject(pLVCD->nmcd.hdc, m_boldFont);
678 // We changed the font, so we're returning CDRF_NEWFONT. This
679 // tells the control to recalculate the extent of the text.
680 // *pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NEWFONT;
684 if (m_arShownList.GetCount() == (INT_PTR)pLVCD->nmcd.dwItemSpec)
686 if (m_bStrictStopped)
687 crText = GetSysColor(COLOR_GRAYTEXT);
689 // Store the color back in the NMLVCUSTOMDRAW struct.
690 pLVCD->clrText = crText;
694 case CDDS_ITEMPREPAINT|CDDS_ITEM|CDDS_SUBITEM:
696 if ((m_bStrictStopped)&&(m_arShownList.GetCount() == (INT_PTR)pLVCD->nmcd.dwItemSpec))
698 pLVCD->nmcd.uItemState &= ~(CDIS_SELECTED|CDIS_FOCUS);
701 if (pLVCD->iSubItem == LOGLIST_GRAPH)
703 if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec)
706 GetSubItemRect(pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_BOUNDS, rect);
708 FillBackGround(pLVCD->nmcd.hdc, (INT_PTR)pLVCD->nmcd.dwItemSpec,rect);
709 DrawGraph(pLVCD->nmcd.hdc,rect,pLVCD->nmcd.dwItemSpec);
711 *pResult = CDRF_SKIPDEFAULT;
717 if (pLVCD->iSubItem == LOGLIST_MESSAGE)
719 if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec)
721 GitRev* data = (GitRev*)m_arShownList.GetAt(pLVCD->nmcd.dwItemSpec);
722 if(m_HashMap[data->m_CommitHash].size()!=0)
725 GetSubItemRect(pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_BOUNDS, rect);
727 FillBackGround(pLVCD->nmcd.hdc, (INT_PTR)pLVCD->nmcd.dwItemSpec,rect);
728 DrawTagBranch(pLVCD->nmcd.hdc,rect,pLVCD->nmcd.dwItemSpec);
730 *pResult = CDRF_SKIPDEFAULT;
737 if (pLVCD->iSubItem == 1)
739 *pResult = CDRF_DODEFAULT;
741 if (m_arShownList.GetCount() <= (INT_PTR)pLVCD->nmcd.dwItemSpec)
745 int iconwidth = ::GetSystemMetrics(SM_CXSMICON);
746 int iconheight = ::GetSystemMetrics(SM_CYSMICON);
748 GitRev* pLogEntry = reinterpret_cast<GitRev *>(m_arShownList.GetAt(pLVCD->nmcd.dwItemSpec));
750 GetSubItemRect(pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_BOUNDS, rect);
751 // Get the selected state of the
754 // Fill the background
755 FillBackGround(pLVCD->nmcd.hdc, (INT_PTR)pLVCD->nmcd.dwItemSpec,rect);
757 // Draw the icon(s) into the compatible DC
758 if (pLogEntry->m_Action & CTGitPath::LOGACTIONS_MODIFIED)
759 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left + ICONITEMBORDER, rect.top, m_hModifiedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
762 if (pLogEntry->m_Action & CTGitPath::LOGACTIONS_ADDED)
763 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left+nIcons*iconwidth + ICONITEMBORDER, rect.top, m_hAddedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
766 if (pLogEntry->m_Action & CTGitPath::LOGACTIONS_DELETED)
767 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left+nIcons*iconwidth + ICONITEMBORDER, rect.top, m_hDeletedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
770 if (pLogEntry->m_Action & CTGitPath::LOGACTIONS_REPLACED)
771 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left+nIcons*iconwidth + ICONITEMBORDER, rect.top, m_hReplacedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
773 *pResult = CDRF_SKIPDEFAULT;
779 *pResult = CDRF_DODEFAULT;
783 // CGitLogList message handlers
785 void CGitLogList::OnLvnGetdispinfoLoglist(NMHDR *pNMHDR, LRESULT *pResult)
787 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
789 // Create a pointer to the item
790 LV_ITEM* pItem = &(pDispInfo)->item;
792 // Do the list need text information?
793 if (!(pItem->mask & LVIF_TEXT))
796 // By default, clear text buffer.
797 lstrcpyn(pItem->pszText, _T(""), pItem->cchTextMax);
799 bool bOutOfRange = pItem->iItem >= ShownCountWithStopped();
802 if (m_bNoDispUpdates || m_bThreadRunning || bOutOfRange)
805 // Which item number?
806 int itemid = pItem->iItem;
807 GitRev * pLogEntry = NULL;
808 if (itemid < m_arShownList.GetCount())
809 pLogEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(pItem->iItem));
812 switch (pItem->iSubItem)
814 case this->LOGLIST_GRAPH: //Graphic
819 case this->LOGLIST_ACTION: //action -- no text in the column
821 case this->LOGLIST_MESSAGE: //Message
823 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->m_Subject, pItem->cchTextMax);
825 case this->LOGLIST_AUTHOR: //Author
827 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->m_AuthorName, pItem->cchTextMax);
829 case this->LOGLIST_DATE: //Date
831 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->m_AuthorDate.Format(_T("%Y-%m-%d %H:%M")), pItem->cchTextMax);
842 void CGitLogList::OnContextMenu(CWnd* pWnd, CPoint point)
845 int selIndex = GetSelectionMark();
847 return; // nothing selected, nothing to do with a context menu
849 // if the user selected the info text telling about not all revisions shown due to
850 // the "stop on copy/rename" option, we also don't show the context menu
851 if ((m_bStrictStopped)&&(selIndex == m_arShownList.GetCount()))
854 // if the context menu is invoked through the keyboard, we have to use
855 // a calculated position on where to anchor the menu on
856 if ((point.x == -1) && (point.y == -1))
859 GetItemRect(selIndex, &rect, LVIR_LABEL);
860 ClientToScreen(&rect);
861 point = rect.CenterPoint();
863 m_nSearchIndex = selIndex;
864 m_bCancelled = FALSE;
866 // calculate some information the context menu commands can use
867 // CString pathURL = GetURLFromPath(m_path);
869 POSITION pos = GetFirstSelectedItemPosition();
870 int indexNext = GetNextSelectedItem(pos);
874 GitRev* pSelLogEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(indexNext));
876 GitRev revSelected = pSelLogEntry->Rev;
877 GitRev revPrevious = git_revnum_t(revSelected)-1;
878 if ((pSelLogEntry->pArChangedPaths)&&(pSelLogEntry->pArChangedPaths->GetCount() <= 2))
880 for (int i=0; i<pSelLogEntry->pArChangedPaths->GetCount(); ++i)
882 LogChangedPath * changedpath = (LogChangedPath *)pSelLogEntry->pArChangedPaths->GetAt(i);
883 if (changedpath->lCopyFromRev)
884 revPrevious = changedpath->lCopyFromRev;
890 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(GetNextSelectedItem(pos)));
891 revSelected2 = pLogEntry->Rev;
893 bool bAllFromTheSameAuthor = true;
895 CLogDataVector selEntries;
896 GitRev revLowest, revHighest;
897 GitRevRangeArray revisionRanges;
899 POSITION pos = GetFirstSelectedItemPosition();
900 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(GetNextSelectedItem(pos)));
901 revisionRanges.AddRevision(pLogEntry->Rev);
902 selEntries.push_back(pLogEntry);
903 firstAuthor = pLogEntry->sAuthor;
904 revLowest = pLogEntry->Rev;
905 revHighest = pLogEntry->Rev;
908 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(GetNextSelectedItem(pos)));
909 revisionRanges.AddRevision(pLogEntry->Rev);
910 selEntries.push_back(pLogEntry);
911 if (firstAuthor.Compare(pLogEntry->sAuthor))
912 bAllFromTheSameAuthor = false;
913 revLowest = (git_revnum_t(pLogEntry->Rev) > git_revnum_t(revLowest) ? revLowest : pLogEntry->Rev);
914 revHighest = (git_revnum_t(pLogEntry->Rev) < git_revnum_t(revHighest) ? revHighest : pLogEntry->Rev);
920 int FirstSelect=-1, LastSelect=-1;
921 pos = GetFirstSelectedItemPosition();
922 FirstSelect = GetNextSelectedItem(pos);
925 LastSelect = GetNextSelectedItem(pos);
927 //entry is selected, now show the popup menu
929 if (popup.CreatePopupMenu())
931 if (GetSelectedCount() == 1)
934 if (!m_path.IsDirectory())
938 popup.AppendMenuIcon(ID_COMPARE, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
939 popup.AppendMenuIcon(ID_BLAMECOMPARE, IDS_LOG_POPUP_BLAMECOMPARE, IDI_BLAME);
941 popup.AppendMenuIcon(ID_GNUDIFF1, IDS_LOG_POPUP_GNUDIFF_CH, IDI_DIFF);
942 popup.AppendMenuIcon(ID_COMPAREWITHPREVIOUS, IDS_LOG_POPUP_COMPAREWITHPREVIOUS, IDI_DIFF);
943 popup.AppendMenu(MF_SEPARATOR, NULL);
944 popup.AppendMenuIcon(ID_SAVEAS, IDS_LOG_POPUP_SAVE, IDI_SAVEAS);
945 popup.AppendMenuIcon(ID_OPEN, IDS_LOG_POPUP_OPEN, IDI_OPEN);
946 popup.AppendMenuIcon(ID_OPENWITH, IDS_LOG_POPUP_OPENWITH, IDI_OPEN);
947 popup.AppendMenuIcon(ID_BLAME, IDS_LOG_POPUP_BLAME, IDI_BLAME);
948 popup.AppendMenu(MF_SEPARATOR, NULL);
955 popup.AppendMenuIcon(ID_COMPARE, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
957 // TortoiseMerge could be improved to take a /blame switch
958 // and then not 'cat' the files from a unified diff but
960 // But until that's implemented, the context menu entry for
961 // this feature is commented out.
962 //popup.AppendMenu(ID_BLAMECOMPARE, IDS_LOG_POPUP_BLAMECOMPARE, IDI_BLAME);
964 popup.AppendMenuIcon(ID_GNUDIFF1, IDS_LOG_POPUP_GNUDIFF_CH, IDI_DIFF);
965 popup.AppendMenuIcon(ID_COMPAREWITHPREVIOUS, IDS_LOG_POPUP_COMPAREWITHPREVIOUS, IDI_DIFF);
966 //popup.AppendMenuIcon(ID_BLAMEWITHPREVIOUS, IDS_LOG_POPUP_BLAMEWITHPREVIOUS, IDI_BLAME);
967 popup.AppendMenu(MF_SEPARATOR, NULL);
970 // if (!m_ProjectProperties.sWebViewerRev.IsEmpty())
972 // popup.AppendMenuIcon(ID_VIEWREV, IDS_LOG_POPUP_VIEWREV);
974 // if (!m_ProjectProperties.sWebViewerPathRev.IsEmpty())
976 // popup.AppendMenuIcon(ID_VIEWPATHREV, IDS_LOG_POPUP_VIEWPATHREV);
978 // if ((!m_ProjectProperties.sWebViewerPathRev.IsEmpty())||
979 // (!m_ProjectProperties.sWebViewerRev.IsEmpty()))
981 // popup.AppendMenu(MF_SEPARATOR, NULL);
985 // popup.AppendMenuIcon(ID_REVERTTOREV, IDS_LOG_POPUP_REVERTTOREV, IDI_REVERT);
987 // popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREV, IDI_REVERT);
989 // popup.AppendMenuIcon(ID_MERGEREV, IDS_LOG_POPUP_MERGEREV, IDI_MERGE);
991 popup.AppendMenuIcon(ID_SWITCHTOREV, _T("Switch/Checkout to this") , IDI_SWITCH);
992 popup.AppendMenuIcon(ID_CREATE_BRANCH, _T("Create Branch at this version") , IDI_COPY);
993 popup.AppendMenuIcon(ID_CREATE_TAG, _T("Create Tag at this version"), IDI_COPY);
994 popup.AppendMenuIcon(ID_CHERRY_PICK, _T("Cherry Pick this version"), IDI_EXPORT);
995 popup.AppendMenuIcon(ID_EXPORT, _T("Export this version"), IDI_EXPORT);
998 popup.AppendMenu(MF_SEPARATOR, NULL);
1000 else if (GetSelectedCount() >= 2)
1002 bool bAddSeparator = false;
1003 if (IsSelectionContinuous() || (GetSelectedCount() == 2))
1005 popup.AppendMenuIcon(ID_COMPARETWO, IDS_LOG_POPUP_COMPARETWO, IDI_DIFF);
1007 if (GetSelectedCount() == 2)
1009 //popup.AppendMenuIcon(ID_BLAMETWO, IDS_LOG_POPUP_BLAMEREVS, IDI_BLAME);
1010 popup.AppendMenuIcon(ID_GNUDIFF2, IDS_LOG_POPUP_GNUDIFF, IDI_DIFF);
1011 bAddSeparator = true;
1015 //popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREVS, IDI_REVERT);
1017 // popup.AppendMenuIcon(ID_MERGEREV, IDS_LOG_POPUP_MERGEREVS, IDI_MERGE);
1018 bAddSeparator = true;
1021 popup.AppendMenu(MF_SEPARATOR, NULL);
1024 // if ((selEntries.size() > 0)&&(bAllFromTheSameAuthor))
1026 // popup.AppendMenuIcon(ID_EDITAUTHOR, IDS_LOG_POPUP_EDITAUTHOR);
1028 // if (GetSelectedCount() == 1)
1030 // popup.AppendMenuIcon(ID_EDITLOG, IDS_LOG_POPUP_EDITLOG);
1031 // popup.AppendMenuIcon(ID_REVPROPS, IDS_REPOBROWSE_SHOWREVPROP, IDI_PROPERTIES); // "Show Revision Properties"
1032 // popup.AppendMenu(MF_SEPARATOR, NULL);
1037 if (GetSelectedCount() == 1)
1039 popup.AppendMenuIcon(ID_COPYHASH, _T("Copy Commit Hash"));
1041 if (GetSelectedCount() != 0)
1043 popup.AppendMenuIcon(ID_COPYCLIPBOARD, IDS_LOG_POPUP_COPYTOCLIPBOARD);
1045 popup.AppendMenuIcon(ID_FINDENTRY, IDS_LOG_POPUP_FIND);
1047 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
1048 // DialogEnableWindow(IDOK, FALSE);
1049 // SetPromptApp(&theApp);
1050 theApp.DoWaitCursor(1);
1051 bool bOpenWith = false;
1057 CString tempfile=GetTempFile();
1059 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
1060 cmd.Format(_T("git.cmd diff-tree -r -p --stat %s"),r1->m_CommitHash);
1061 g_Git.RunLogFile(cmd,tempfile);
1062 CAppUtils::StartUnifiedDiffViewer(tempfile,r1->m_CommitHash.Left(6)+_T(":")+r1->m_Subject);
1068 CString tempfile=GetTempFile();
1070 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
1071 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
1072 cmd.Format(_T("git.cmd diff-tree -r -p --stat %s %s"),r1->m_CommitHash,r2->m_CommitHash);
1073 g_Git.RunLogFile(cmd,tempfile);
1074 CAppUtils::StartUnifiedDiffViewer(tempfile,r1->m_CommitHash.Left(6)+_T(":")+r2->m_CommitHash.Left(6));
1081 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
1082 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
1084 dlg.SetDiff(NULL,*r1,*r2);
1093 GitRev * r1 = &m_wcRev;
1094 GitRev * r2 = pSelLogEntry;
1096 dlg.SetDiff(NULL,*r1,*r2);
1099 //user clicked on the menu item "compare with working copy"
1100 //if (PromptShown())
1102 // GitDiff diff(this, m_hWnd, true);
1103 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1104 // diff.SetHEADPeg(m_LogRevision);
1105 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
1108 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1112 case ID_COMPAREWITHPREVIOUS:
1117 if(pSelLogEntry->m_ParentHash.size()>0)
1118 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
1120 dlg.SetDiff(NULL,pSelLogEntry->m_CommitHash,pSelLogEntry->m_ParentHash[0]);
1124 CMessageBox::Show(NULL,_T("No previous version"),_T("TortoiseGit"),MB_OK);
1126 //if (PromptShown())
1128 // GitDiff diff(this, m_hWnd, true);
1129 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1130 // diff.SetHEADPeg(m_LogRevision);
1131 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
1134 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1137 case ID_COPYCLIPBOARD:
1139 CopySelectionToClipBoard();
1144 CopySelectionToClipBoard(TRUE);
1148 CAppUtils::Export(&pSelLogEntry->m_CommitHash);
1150 case ID_CREATE_BRANCH:
1151 CAppUtils::CreateBranchTag(FALSE,&pSelLogEntry->m_CommitHash);
1153 g_Git.GetMapHashToFriendName(m_HashMap);
1157 CAppUtils::CreateBranchTag(TRUE,&pSelLogEntry->m_CommitHash);
1159 g_Git.GetMapHashToFriendName(m_HashMap);
1162 case ID_SWITCHTOREV:
1163 CAppUtils::Switch(&pSelLogEntry->m_CommitHash);
1167 //CMessageBox::Show(NULL,_T("Have not implemented"),_T("TortoiseGit"),MB_OK);
1173 // we need an URL to complete this command, so error out if we can't get an URL
1174 if (pathURL.IsEmpty())
1177 strMessage.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)(m_path.GetUIPathString()));
1178 CMessageBox::Show(this->m_hWnd, strMessage, _T("TortoiseGit"), MB_ICONERROR);
1179 TRACE(_T("could not retrieve the URL of the folder!\n"));
1183 msg.Format(IDS_LOG_REVERT_CONFIRM, m_path.GetWinPath());
1184 if (CMessageBox::Show(this->m_hWnd, msg, _T("TortoiseGit"), MB_YESNO | MB_ICONQUESTION) == IDYES)
1186 CGitProgressDlg dlg;
1187 dlg.SetCommand(CGitProgressDlg::GitProgress_Merge);
1188 dlg.SetPathList(CTGitPathList(m_path));
1189 dlg.SetUrl(pathURL);
1190 dlg.SetSecondUrl(pathURL);
1191 revisionRanges.AdjustForMerge(true);
1192 dlg.SetRevisionRanges(revisionRanges);
1193 dlg.SetPegRevision(m_LogRevision);
1200 // we need an URL to complete this command, so error out if we can't get an URL
1201 if (pathURL.IsEmpty())
1204 strMessage.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)(m_path.GetUIPathString()));
1205 CMessageBox::Show(this->m_hWnd, strMessage, _T("TortoiseGit"), MB_ICONERROR);
1206 TRACE(_T("could not retrieve the URL of the folder!\n"));
1210 CString path = m_path.GetWinPathString();
1211 bool bGotSavePath = false;
1212 if ((GetSelectedCount() == 1)&&(!m_path.IsDirectory()))
1214 bGotSavePath = CAppUtils::FileOpenSave(path, NULL, IDS_LOG_MERGETO, IDS_COMMONFILEFILTER, true, GetSafeHwnd());
1218 CBrowseFolder folderBrowser;
1219 folderBrowser.SetInfo(CString(MAKEINTRESOURCE(IDS_LOG_MERGETO)));
1220 bGotSavePath = (folderBrowser.Show(GetSafeHwnd(), path, path) == CBrowseFolder::OK);
1224 CGitProgressDlg dlg;
1225 dlg.SetCommand(CGitProgressDlg::GitProgress_Merge);
1226 dlg.SetPathList(CTGitPathList(CTGitPath(path)));
1227 dlg.SetUrl(pathURL);
1228 dlg.SetSecondUrl(pathURL);
1229 revisionRanges.AdjustForMerge(false);
1230 dlg.SetRevisionRanges(revisionRanges);
1231 dlg.SetPegRevision(m_LogRevision);
1236 case ID_REVERTTOREV:
1238 // we need an URL to complete this command, so error out if we can't get an URL
1239 if (pathURL.IsEmpty())
1242 strMessage.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)(m_path.GetUIPathString()));
1243 CMessageBox::Show(this->m_hWnd, strMessage, _T("TortoiseGit"), MB_ICONERROR);
1244 TRACE(_T("could not retrieve the URL of the folder!\n"));
1249 msg.Format(IDS_LOG_REVERTTOREV_CONFIRM, m_path.GetWinPath());
1250 if (CMessageBox::Show(this->m_hWnd, msg, _T("TortoiseGit"), MB_YESNO | MB_ICONQUESTION) == IDYES)
1252 CGitProgressDlg dlg;
1253 dlg.SetCommand(CGitProgressDlg::GitProgress_Merge);
1254 dlg.SetPathList(CTGitPathList(m_path));
1255 dlg.SetUrl(pathURL);
1256 dlg.SetSecondUrl(pathURL);
1257 GitRevRangeArray revarray;
1258 revarray.AddRevRange(GitRev::REV_HEAD, revSelected);
1259 dlg.SetRevisionRanges(revarray);
1260 dlg.SetPegRevision(m_LogRevision);
1268 case ID_BLAMECOMPARE:
1270 //user clicked on the menu item "compare with working copy"
1271 //now first get the revision which is selected
1274 GitDiff diff(this, this->m_hWnd, true);
1275 diff.SetHEADPeg(m_LogRevision);
1276 diff.ShowCompare(m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), false, true);
1279 CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), m_LogRevision, false, false, true);
1284 //user clicked on the menu item "compare and blame revisions"
1287 GitDiff diff(this, this->m_hWnd, true);
1288 diff.SetHEADPeg(m_LogRevision);
1289 diff.ShowCompare(CTGitPath(pathURL), revSelected2, CTGitPath(pathURL), revSelected, GitRev(), false, true);
1292 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revSelected2, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
1295 case ID_BLAMEWITHPREVIOUS:
1297 //user clicked on the menu item "Compare and Blame with previous revision"
1300 GitDiff diff(this, this->m_hWnd, true);
1301 diff.SetHEADPeg(m_LogRevision);
1302 diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), false, true);
1305 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
1313 CProgressDlg progDlg;
1314 progDlg.SetTitle(IDS_APPNAME);
1315 progDlg.SetAnimation(IDR_DOWNLOAD);
1317 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, m_path.GetWinPath(), (LPCTSTR)revSelected.ToString());
1318 progDlg.SetLine(1, sInfoLine, true);
1319 SetAndClearProgressInfo(&progDlg);
1320 progDlg.ShowModeless(m_hWnd);
1321 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, m_path, revSelected);
1322 bool bSuccess = true;
1323 if (!Cat(m_path, GitRev(GitRev::REV_HEAD), revSelected, tempfile))
1326 // try again, but with the selected revision as the peg revision
1327 if (!Cat(m_path, revSelected, revSelected, tempfile))
1330 SetAndClearProgressInfo((HWND)NULL);
1331 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1340 SetAndClearProgressInfo((HWND)NULL);
1341 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1344 ret = (int)ShellExecute(this->m_hWnd, NULL, tempfile.GetWinPath(), NULL, NULL, SW_SHOWNORMAL);
1345 if ((ret <= HINSTANCE_ERROR)||bOpenWith)
1347 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1348 cmd += tempfile.GetWinPathString() + _T(" ");
1349 CAppUtils::LaunchApplication(cmd, NULL, false);
1357 dlg.EndRev = revSelected;
1358 if (dlg.DoModal() == IDOK)
1363 tempfile = blame.BlameToTempFile(m_path, dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, _T(""), dlg.m_bIncludeMerge, TRUE, TRUE);
1364 if (!tempfile.IsEmpty())
1366 if (dlg.m_bTextView)
1368 //open the default text editor for the result file
1369 CAppUtils::StartTextViewer(tempfile);
1373 CString sParams = _T("/path:\"") + m_path.GetGitPathString() + _T("\" ");
1374 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(m_path.GetFileOrDirectoryName()),sParams))
1382 CMessageBox::Show(this->m_hWnd, blame.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1390 CString url = _T("tgit:")+pathURL;
1391 sCmd.Format(_T("%s /command:update /path:\"%s\" /rev:%ld"),
1392 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
1393 (LPCTSTR)m_path.GetWinPath(), (LONG)revSelected);
1394 CAppUtils::LaunchApplication(sCmd, NULL, false);
1399 m_nSearchIndex = GetSelectionMark();
1400 if (m_nSearchIndex < 0)
1408 m_pFindDialog = new CFindReplaceDialog();
1409 m_pFindDialog->Create(TRUE, NULL, NULL, FR_HIDEUPDOWN | FR_HIDEWHOLEWORD, this);
1416 sCmd.Format(_T("%s /command:repobrowser /path:\"%s\" /rev:%s"),
1417 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
1418 (LPCTSTR)pathURL, (LPCTSTR)revSelected.ToString());
1420 CAppUtils::LaunchApplication(sCmd, NULL, false);
1425 EditLogMessage(selIndex);
1430 EditAuthor(selEntries);
1435 CEditPropertiesDlg dlg;
1436 dlg.SetProjectProperties(&m_ProjectProperties);
1437 CTGitPathList escapedlist;
1438 dlg.SetPathList(CTGitPathList(CTGitPath(pathURL)));
1439 dlg.SetRevision(revSelected);
1448 sCmd.Format(_T("%s /command:export /path:\"%s\" /revision:%ld"),
1449 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
1450 (LPCTSTR)pathURL, (LONG)revSelected);
1451 CAppUtils::LaunchApplication(sCmd, NULL, false);
1457 CString url = _T("tgit:")+pathURL;
1458 sCmd.Format(_T("%s /command:checkout /url:\"%s\" /revision:%ld"),
1459 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
1460 (LPCTSTR)url, (LONG)revSelected);
1461 CAppUtils::LaunchApplication(sCmd, NULL, false);
1466 CString url = m_ProjectProperties.sWebViewerRev;
1467 url = GetAbsoluteUrlFromRelativeUrl(url);
1468 url.Replace(_T("%REVISION%"), revSelected.ToString());
1470 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1473 case ID_VIEWPATHREV:
1475 CString relurl = pathURL;
1476 CString sRoot = GetRepositoryRoot(CTGitPath(relurl));
1477 relurl = relurl.Mid(sRoot.GetLength());
1478 CString url = m_ProjectProperties.sWebViewerPathRev;
1479 url = GetAbsoluteUrlFromRelativeUrl(url);
1480 url.Replace(_T("%REVISION%"), revSelected.ToString());
1481 url.Replace(_T("%PATH%"), relurl);
1483 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1489 theApp.DoWaitCursor(-1);
1490 // EnableOKButton();
1491 } // if (popup.CreatePopupMenu())
1495 bool CGitLogList::IsSelectionContinuous()
1497 if ( GetSelectedCount()==1 )
1499 // if only one revision is selected, the selection is of course
1504 POSITION pos = GetFirstSelectedItemPosition();
1505 bool bContinuous = (m_arShownList.GetCount() == (INT_PTR)m_logEntries.size());
1508 int itemindex = GetNextSelectedItem(pos);
1511 int nextindex = GetNextSelectedItem(pos);
1512 if (nextindex - itemindex > 1)
1514 bContinuous = false;
1517 itemindex = nextindex;
1523 void CGitLogList::CopySelectionToClipBoard(bool HashOnly)
1527 POSITION pos = GetFirstSelectedItemPosition();
1531 sRev.LoadString(IDS_LOG_REVISION);
1533 sAuthor.LoadString(IDS_LOG_AUTHOR);
1535 sDate.LoadString(IDS_LOG_DATE);
1537 sMessage.LoadString(IDS_LOG_MESSAGE);
1540 CString sLogCopyText;
1542 GitRev * pLogEntry = reinterpret_cast<GitRev *>(m_arShownList.GetAt(GetNextSelectedItem(pos)));
1546 //pLogEntry->m_Files
1547 //LogChangedPathArray * cpatharray = pLogEntry->pArChangedPaths;
1549 for (int cpPathIndex = 0; cpPathIndex<pLogEntry->m_Files.GetCount(); ++cpPathIndex)
1551 sPaths += ((CTGitPath&)pLogEntry->m_Files[cpPathIndex]).GetActionName() + _T(" : ") + pLogEntry->m_Files[cpPathIndex].GetGitPathString();
1552 sPaths += _T("\r\n");
1555 sLogCopyText.Format(_T("%s: %s\r\n%s: %s\r\n%s: %s\r\n%s:\r\n%s\r\n----\r\n%s\r\n\r\n"),
1556 (LPCTSTR)sRev, pLogEntry->m_CommitHash,
1557 (LPCTSTR)sAuthor, (LPCTSTR)pLogEntry->m_AuthorName,
1558 (LPCTSTR)sDate, (LPCTSTR)pLogEntry->m_AuthorDate.Format(_T("%Y-%m-%d %H:%M")),
1559 (LPCTSTR)sMessage, pLogEntry->m_Subject+_T("\r\n")+pLogEntry->m_Body,
1561 sClipdata += sLogCopyText;
1564 sClipdata += pLogEntry->m_CommitHash;
1569 CStringUtils::WriteAsciiStringToClipboard(sClipdata, GetSafeHwnd());
1574 void CGitLogList::DiffSelectedRevWithPrevious()
1577 if (m_bThreadRunning)
1579 UpdateLogInfoLabel();
1580 int selIndex = m_LogList.GetSelectionMark();
1583 int selCount = m_LogList.GetSelectedCount();
1587 // Find selected entry in the log list
1588 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
1589 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(m_LogList.GetNextSelectedItem(pos)));
1590 long rev1 = pLogEntry->Rev;
1592 CTGitPath path = m_path;
1594 // See how many files under the relative root were changed in selected revision
1596 LogChangedPath * changed = NULL;
1597 for (INT_PTR c = 0; c < pLogEntry->pArChangedPaths->GetCount(); ++c)
1599 LogChangedPath * cpath = pLogEntry->pArChangedPaths->GetAt(c);
1600 if (cpath && cpath -> sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0)
1607 if (m_path.IsDirectory() && nChanged == 1)
1609 // We're looking at the log for a directory and only one file under dir was changed in the revision
1610 // Do diff on that file instead of whole directory
1611 path.AppendPathString(changed->sPath.Mid(m_sRelativeRoot.GetLength()));
1614 m_bCancelled = FALSE;
1615 DialogEnableWindow(IDOK, FALSE);
1616 SetPromptApp(&theApp);
1617 theApp.DoWaitCursor(1);
1621 GitDiff diff(this, m_hWnd, true);
1622 diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1623 diff.SetHEADPeg(m_LogRevision);
1624 diff.ShowCompare(path, rev2, path, rev1);
1628 CAppUtils::StartShowCompare(m_hWnd, path, rev2, path, rev1, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1631 theApp.DoWaitCursor(-1);
1636 void CGitLogList::OnLvnOdfinditemLoglist(NMHDR *pNMHDR, LRESULT *pResult)
1638 LPNMLVFINDITEM pFindInfo = reinterpret_cast<LPNMLVFINDITEM>(pNMHDR);
1641 if (pFindInfo->lvfi.flags & LVFI_PARAM)
1643 if ((pFindInfo->iStart < 0)||(pFindInfo->iStart >= m_arShownList.GetCount()))
1645 if (pFindInfo->lvfi.psz == 0)
1648 CString sCmp = pFindInfo->lvfi.psz;
1650 for (int i=pFindInfo->iStart; i<m_arShownList.GetCount(); ++i)
1652 GitRev * pLogEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
1653 sRev.Format(_T("%ld"), pLogEntry->Rev);
1654 if (pFindInfo->lvfi.flags & LVFI_PARTIAL)
1656 if (sCmp.Compare(sRev.Left(sCmp.GetLength()))==0)
1664 if (sCmp.Compare(sRev)==0)
1671 if (pFindInfo->lvfi.flags & LVFI_WRAP)
1673 for (int i=0; i<pFindInfo->iStart; ++i)
1675 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(i));
1676 sRev.Format(_T("%ld"), pLogEntry->Rev);
1677 if (pFindInfo->lvfi.flags & LVFI_PARTIAL)
1679 if (sCmp.Compare(sRev.Left(sCmp.GetLength()))==0)
1687 if (sCmp.Compare(sRev)==0)
1699 int CGitLogList::FillGitLog()
1703 this->m_logEntries.ClearAll();
1704 this->m_logEntries.ParserFromLog();
1705 SetItemCountEx(this->m_logEntries.size());
1707 this->m_arShownList.RemoveAll();
1709 for(int i=0;i<m_logEntries.size();i++)
1710 this->m_arShownList.Add(&m_logEntries[i]);
1715 BOOL CGitLogList::PreTranslateMessage(MSG* pMsg)
1717 // Skip Ctrl-C when copying text out of the log message or search filter
1718 BOOL bSkipAccelerator = ( pMsg->message == WM_KEYDOWN && pMsg->wParam=='C' && (GetFocus()==GetDlgItem(IDC_MSGVIEW) || GetFocus()==GetDlgItem(IDC_SEARCHEDIT) ) && GetKeyState(VK_CONTROL)&0x8000 );
1719 if (pMsg->message == WM_KEYDOWN && pMsg->wParam=='\r')
1721 //if (GetFocus()==GetDlgItem(IDC_LOGLIST))
1723 if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
1725 DiffSelectedRevWithPrevious();
1730 if (GetFocus()==GetDlgItem(IDC_LOGMSG))
1739 if (m_hAccel && !bSkipAccelerator)
1741 int ret = TranslateAccelerator(m_hWnd, m_hAccel, pMsg);
1747 //m_tooltips.RelayEvent(pMsg);
1748 return __super::PreTranslateMessage(pMsg);
1751 void CGitLogList::OnNMDblclkLoglist(NMHDR * /*pNMHDR*/, LRESULT *pResult)
1753 // a double click on an entry in the revision list has happened
1756 if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
1757 DiffSelectedRevWithPrevious();
1760 int CGitLogList::FetchLogAsync(CALLBACK_PROCESS *proc,void * data)
1762 m_ProcCallBack=proc;
1765 InterlockedExchange(&m_bThreadRunning, TRUE);
1766 InterlockedExchange(&m_bNoDispUpdates, TRUE);
1767 if (AfxBeginThread(LogThreadEntry, this)==NULL)
1769 InterlockedExchange(&m_bThreadRunning, FALSE);
1770 InterlockedExchange(&m_bNoDispUpdates, FALSE);
1771 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
1777 //this is the thread function which calls the subversion function
1778 UINT CGitLogList::LogThreadEntry(LPVOID pVoid)
1780 return ((CGitLogList*)pVoid)->LogThread();
1784 UINT CGitLogList::LogThread()
1788 m_ProcCallBack(m_ProcData,GITLOG_START);
1790 InterlockedExchange(&m_bThreadRunning, TRUE);
1792 //does the user force the cache to refresh (shift or control key down)?
1793 bool refresh = (GetKeyState (VK_CONTROL) < 0)
1794 || (GetKeyState (VK_SHIFT) < 0);
1796 //disable the "Get All" button while we're receiving
1800 temp.LoadString(IDS_PROGRESSWAIT);
1801 ShowText(temp, true);
1803 // git_revnum_t r = -1;
1805 // get the repository root url, because the changed-files-list has the
1806 // paths shown there relative to the repository root.
1807 // CTGitPath rootpath;
1808 // BOOL succeeded = GetRootAndHead(m_path, rootpath, r);
1810 // m_sRepositoryRoot = rootpath.GetGitPathString();
1811 // m_sURL = m_path.GetGitPathString();
1813 // we need the UUID to unambigously identify the log cache
1814 // if (logCachePool.IsEnabled())
1815 // m_sUUID = logCachePool.GetRepositoryInfo().GetRepositoryUUID (rootpath);
1817 // if the log dialog is started from a working copy, we need to turn that
1818 // local path into an url here
1821 // if (!m_path.IsUrl())
1823 // m_sURL = GetURLFromPath(m_path);
1825 // The URL is escaped because Git::logReceiver
1826 // returns the path in a native format
1827 // m_sURL = CPathUtils::PathUnescape(m_sURL);
1829 // m_sRelativeRoot = m_sURL.Mid(CPathUtils::PathUnescape(m_sRepositoryRoot).GetLength());
1830 // m_sSelfRelativeURL = m_sRelativeRoot;
1833 if (succeeded && !m_mergePath.IsEmpty() && m_mergedRevs.empty())
1835 // in case we got a merge path set, retrieve the merge info
1836 // of that path and check whether one of the merge URLs
1837 // match the URL we show the log for.
1838 GitPool localpool(pool);
1839 git_error_clear(Err);
1840 apr_hash_t * mergeinfo = NULL;
1841 if (git_client_mergeinfo_get_merged (&mergeinfo, m_mergePath.GetGitApiPath(localpool), GitRev(GitRev::REV_WC), m_pctx, localpool) == NULL)
1843 // now check the relative paths
1844 apr_hash_index_t *hi;
1850 for (hi = apr_hash_first(localpool, mergeinfo); hi; hi = apr_hash_next(hi))
1852 apr_hash_this(hi, &key, NULL, &val);
1853 if (m_sURL.Compare(CUnicodeUtils::GetUnicode((char*)key)) == 0)
1855 apr_array_header_t * arr = (apr_array_header_t*)val;
1858 for (long i=0; i<arr->nelts; ++i)
1860 git_merge_range_t * pRange = APR_ARRAY_IDX(arr, i, git_merge_range_t*);
1863 for (git_revnum_t r=pRange->start+1; r<=pRange->end; ++r)
1865 m_mergedRevs.insert(r);
1877 m_LogProgress.SetPos(1);
1878 if (m_startrev == GitRev::REV_HEAD)
1882 if (m_endrev == GitRev::REV_HEAD)
1889 m_limitcounter = m_limit;
1890 m_LogProgress.SetRange32(0, m_limit);
1893 m_LogProgress.SetRange32(m_endrev, m_startrev);
1895 if (!m_pegrev.IsValid())
1896 m_pegrev = m_startrev;
1897 size_t startcount = m_logEntries.size();
1899 m_bStrictStopped = false;
1903 succeeded = ReceiveLog (CTGitPathList(m_path), m_pegrev, m_startrev, m_endrev, m_limit, m_bStrict, m_bIncludeMerges, refresh);
1904 if ((!succeeded)&&(!m_path.IsUrl()))
1906 // try again with REV_WC as the start revision, just in case the path doesn't
1907 // exist anymore in HEAD
1908 succeeded = ReceiveLog(CTGitPathList(m_path), GitRev(), GitRev::REV_WC, m_endrev, m_limit, m_bStrict, m_bIncludeMerges, refresh);
1911 m_LogList.ClearText();
1914 m_LogList.ShowText(GetLastErrorMessage(), true);
1918 if (!m_wcRev.IsValid())
1920 // fetch the revision the wc path is on so we can mark it
1921 CTGitPath revWCPath = m_ProjectProperties.GetPropsPath();
1922 if (!m_path.IsUrl())
1924 if (DWORD(CRegDWORD(_T("Software\\TortoiseGit\\RecursiveLogRev"), FALSE)))
1926 git_revnum_t minrev, maxrev;
1927 bool switched, modified, sparse;
1928 GetWCRevisionStatus(revWCPath, true, minrev, maxrev, switched, modified, sparse);
1934 CTGitPath dummypath;
1936 git_wc_status2_t * stat = status.GetFirstFileStatus(revWCPath, dummypath, false, git_depth_empty);
1937 if (stat && stat->entry && stat->entry->cmt_rev)
1938 m_wcRev = stat->entry->cmt_rev;
1939 if (stat && stat->entry && (stat->entry->kind == git_node_dir))
1940 m_wcRev = stat->entry->revision;
1944 if (m_bStrict && (m_lowestRev>1) && ((m_limit>0) ? ((startcount + m_limit)>m_logEntries.size()) : (m_endrev<m_lowestRev)))
1945 m_bStrictStopped = true;
1946 m_LogList.SetItemCountEx(ShownCountWithStopped());
1948 m_timFrom = (__time64_t(m_tFrom));
1949 m_timTo = (__time64_t(m_tTo));
1950 m_DateFrom.SetRange(&m_timFrom, &m_timTo);
1951 m_DateTo.SetRange(&m_timFrom, &m_timTo);
1952 m_DateFrom.SetTime(&m_timFrom);
1953 m_DateTo.SetTime(&m_timTo);
1955 //DialogEnableWindow(IDC_GETALL, TRUE);
1958 InterlockedExchange(&m_bThreadRunning, FALSE);
1960 RedrawItems(0, m_arShownList.GetCount());
1962 ResizeAllListCtrlCols();
1965 if ( m_pStoreSelection )
1967 // Deleting the instance will restore the
1968 // selection of the CLogDlg.
1969 delete m_pStoreSelection;
1970 m_pStoreSelection = NULL;
1974 // If no selection has been set then this must be the first time
1975 // the revisions are shown. Let's preselect the topmost revision.
1976 if ( GetItemCount()>0 )
1978 SetSelectionMark(0);
1979 SetItemState(0, LVIS_SELECTED, LVIS_SELECTED);
1984 // make sure the filter is applied (if any) now, after we refreshed/fetched
1987 InterlockedExchange(&m_bNoDispUpdates, FALSE);
1990 m_ProcCallBack(m_ProcData,GITLOG_END);