OSDN Git Service

37bbe94578fc29585eecb51b7b2a0c34865cbd70
[tortoisegit/TortoiseGitJp.git] / src / TortoiseProc / GitLogList.cpp
1 // GitLogList.cpp : implementation file
2 //
3 /*
4         Description: qgit revision list view
5
6         Author: Marco Costalba (C) 2005-2007
7
8         Copyright: See COPYING file that comes with this distribution
9
10 */
11 #include "stdafx.h"
12 #include "TortoiseProc.h"
13 #include "GitLogList.h"
14 #include "GitRev.h"
15 //#include "VssStyle.h"
16 #include "IconMenu.h"
17 // CGitLogList
18 #include "cursor.h"
19 #include "InputDlg.h"
20 #include "PropDlg.h"
21 #include "SVNProgressDlg.h"
22 #include "ProgressDlg.h"
23 //#include "RepositoryBrowser.h"
24 //#include "CopyDlg.h"
25 //#include "StatGraphDlg.h"
26 #include "Logdlg.h"
27 #include "MessageBox.h"
28 #include "Registry.h"
29 #include "AppUtils.h"
30 #include "PathUtils.h"
31 #include "StringUtils.h"
32 #include "UnicodeUtils.h"
33 #include "TempFile.h"
34 //#include "GitInfo.h"
35 //#include "GitDiff.h"
36 #include "IconMenu.h"
37 //#include "RevisionRangeDlg.h"
38 //#include "BrowseFolder.h"
39 //#include "BlameDlg.h"
40 //#include "Blame.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"
48
49
50
51
52 IMPLEMENT_DYNAMIC(CGitLogList, CHintListCtrl)
53
54 CGitLogList::CGitLogList():CHintListCtrl()
55         ,m_regMaxBugIDColWidth(_T("Software\\TortoiseGit\\MaxBugIDColWidth"), 200)
56         ,m_nSearchIndex(0)
57         ,m_bNoDispUpdates(FALSE)
58         , m_bThreadRunning(FALSE)
59         , m_bStrictStopped(false)
60         , m_pStoreSelection(NULL)
61 {
62         // use the default GUI font, create a copy of it and
63         // change the copy to BOLD (leave the rest of the font
64         // the same)
65         HFONT hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
66         LOGFONT lf = {0};
67         GetObject(hFont, sizeof(LOGFONT), &lf);
68         lf.lfWeight = FW_BOLD;
69         m_boldFont = CreateFontIndirect(&lf);
70
71         m_wcRev.m_CommitHash=GIT_REV_ZERO;
72         m_wcRev.m_Subject=_T("Working Copy");
73
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);
78
79         g_Git.GetMapHashToFriendName(m_HashMap);
80 }
81
82 CGitLogList::~CGitLogList()
83 {
84         InterlockedExchange(&m_bNoDispUpdates, TRUE);
85
86         DestroyIcon(m_hModifiedIcon);
87         DestroyIcon(m_hReplacedIcon);
88         DestroyIcon(m_hAddedIcon);
89         DestroyIcon(m_hDeletedIcon);
90         m_logEntries.ClearAll();
91
92         if (m_boldFont)
93                 DeleteObject(m_boldFont);
94
95         if ( m_pStoreSelection )
96         {
97                 delete m_pStoreSelection;
98                 m_pStoreSelection = NULL;
99         }
100 }
101
102
103 BEGIN_MESSAGE_MAP(CGitLogList, CHintListCtrl)
104         ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnNMCustomdrawLoglist)
105         ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnLvnGetdispinfoLoglist)
106         ON_WM_CONTEXTMENU()
107         ON_NOTIFY_REFLECT(NM_DBLCLK, OnNMDblclkLoglist)
108         ON_NOTIFY_REFLECT(LVN_ODFINDITEM,OnLvnOdfinditemLoglist)
109         ON_WM_CREATE()
110 END_MESSAGE_MAP()
111
112 int CGitLogList:: OnCreate(LPCREATESTRUCT lpCreateStruct)
113 {
114         PreSubclassWindow();
115         return CHintListCtrl::OnCreate(lpCreateStruct);
116 }
117
118 void CGitLogList::PreSubclassWindow()
119 {
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();
124 }
125
126 void CGitLogList::InsertGitColumn()
127 {
128         CString temp;
129
130         int c = ((CHeaderCtrl*)(GetDlgItem(0)))->GetItemCount()-1;
131         
132         while (c>=0)
133                 DeleteColumn(c--);
134         temp.LoadString(IDS_LOG_GRAPH);
135
136         InsertColumn(this->LOGLIST_GRAPH, temp);
137         
138 #if 0   
139         // make the revision column right aligned
140         LVCOLUMN Column;
141         Column.mask = LVCF_FMT;
142         Column.fmt = LVCFMT_RIGHT;
143         SetColumn(0, &Column); 
144 #endif  
145 //      CString log;
146 //      g_Git.GetLog(log);
147
148         temp.LoadString(IDS_LOG_ACTIONS);
149         InsertColumn(this->LOGLIST_ACTION, temp);
150         
151         temp.LoadString(IDS_LOG_MESSAGE);
152         InsertColumn(this->LOGLIST_MESSAGE, temp);
153         
154         temp.LoadString(IDS_LOG_AUTHOR);
155         InsertColumn(this->LOGLIST_AUTHOR, temp);
156         
157         temp.LoadString(IDS_LOG_DATE);
158         InsertColumn(this->LOGLIST_DATE, temp);
159         
160
161         if (m_bShowBugtraqColumn)
162         {
163 //              temp = m_ProjectProperties.sLabel;
164                 if (temp.IsEmpty())
165                         temp.LoadString(IDS_LOG_BUGIDS);
166                 InsertColumn(this->LOGLIST_BUG, temp);
167
168         }
169         
170         SetRedraw(false);
171         ResizeAllListCtrlCols();
172         SetRedraw(true);
173
174 }
175
176 void CGitLogList::ResizeAllListCtrlCols()
177 {
178
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));
184         if (pHdrCtrl)
185         {
186                 for (int col = 0; col <= maxcol; col++)
187                 {
188                         HDITEM hdi = {0};
189                         hdi.mask = HDI_TEXT;
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)
195                         {
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())
199                                 {
200                                         GitRev * pCurLogEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(index));
201                                         if ((pCurLogEntry)&&(pCurLogEntry->m_CommitHash == m_wcRev.m_CommitHash))
202                                         {
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);
208                                         }
209                                 }
210                                 if (index == 0)
211                                 {
212                                         // add the image size
213                                         CImageList * pImgList = GetImageList(LVSIL_SMALL);
214                                         if ((pImgList)&&(pImgList->GetImageCount()))
215                                         {
216                                                 IMAGEINFO imginfo;
217                                                 pImgList->GetImageInfo(0, &imginfo);
218                                                 linewidth += (imginfo.rcImage.right - imginfo.rcImage.left);
219                                                 linewidth += 3; // 3 pixels between icon and text
220                                         }
221                                 }
222                                 if (cx < linewidth)
223                                         cx = linewidth;
224                         }
225                         // Adjust columns "Actions" containing icons
226                         if (col == this->LOGLIST_ACTION)
227                         {
228                                 if (cx < nMinimumWidth)
229                                 {
230                                         cx = nMinimumWidth;
231                                 }
232                         }
233                         
234                         if (col == this->LOGLIST_MESSAGE)
235                         {
236                                 if (cx > LOGLIST_MESSAGE_MAX)
237                                 {
238                                         cx = LOGLIST_MESSAGE_MAX;
239                                 }
240
241                         }
242                         // keep the bug id column small
243                         if ((col == 4)&&(m_bShowBugtraqColumn))
244                         {
245                                 if (cx > (int)(DWORD)m_regMaxBugIDColWidth)
246                                 {
247                                         cx = (int)(DWORD)m_regMaxBugIDColWidth;
248                                 }
249                         }
250
251                         SetColumnWidth(col, cx);
252                 }
253         }
254
255 }
256 BOOL CGitLogList::GetShortName(CString ref, CString &shortname,CString prefix)
257 {
258         if(ref.Left(prefix.GetLength()) ==  prefix)
259         {
260                 shortname = ref.Right(ref.GetLength()-prefix.GetLength());
261                 return TRUE;
262         }
263         return FALSE;
264 }
265 void CGitLogList::FillBackGround(HDC hdc, int Index,CRect &rect)
266 {       
267 //      HBRUSH brush;
268         LVITEM   rItem;
269         SecureZeroMemory(&rItem, sizeof(LVITEM));
270         rItem.mask  = LVIF_STATE;
271         rItem.iItem = Index;
272         rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
273         GetItem(&rItem);
274
275         if (m_Theme.IsAppThemed() && m_bVista)
276         {
277                 m_Theme.Open(m_hWnd, L"Explorer");
278                 int state = LISS_NORMAL;
279                 if (rItem.state & LVIS_SELECTED)
280                 {
281                         if (::GetFocus() == m_hWnd)
282                                 state |= LISS_SELECTED;
283                         else
284                                 state |= LISS_SELECTEDNOTFOCUS;
285                 }
286                 else
287                 {
288 #if 0
289                         if (pLogEntry->bCopiedSelf)
290                         {
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))
295                                 {
296                                         HBRUSH brush;
297                                         brush = ::CreateSolidBrush(::GetSysColor(COLOR_MENU));
298                                         if (brush)
299                                         {
300                                                 ::FillRect(pLVCD->nmcd.hdc, &rect, brush);
301                                                 ::DeleteObject(brush);
302                                         }
303                                 }
304                         }
305 #endif
306                 }
307
308                 if (m_Theme.IsBackgroundPartiallyTransparent(LVP_LISTDETAIL, state))
309                         m_Theme.DrawParentBackground(m_hWnd, hdc, &rect);
310
311                         m_Theme.DrawBackground(hdc, LVP_LISTDETAIL, state, &rect, NULL);
312         }
313         else
314         {
315                 HBRUSH brush;
316                 if (rItem.state & LVIS_SELECTED)
317                 {
318                         if (::GetFocus() == m_hWnd)
319                                 brush = ::CreateSolidBrush(::GetSysColor(COLOR_HIGHLIGHT));
320                         else
321                                 brush = ::CreateSolidBrush(::GetSysColor(COLOR_BTNFACE));
322                 }
323                 else
324                 {
325                         //if (pLogEntry->bCopiedSelf)
326                         //      brush = ::CreateSolidBrush(::GetSysColor(COLOR_MENU));
327                         //else
328                                 brush = ::CreateSolidBrush(::GetSysColor(COLOR_WINDOW));
329                 }
330                 if (brush == NULL)
331                         return;
332
333                 ::FillRect(hdc, &rect, brush);
334                 ::DeleteObject(brush);
335                 
336         }
337 }
338
339 void CGitLogList::DrawTagBranch(HDC hdc,CRect &rect,INT_PTR index)
340 {
341         GitRev* data = (GitRev*)m_arShownList.GetAt(index);
342         CRect rt=rect;
343         LVITEM   rItem;
344         SecureZeroMemory(&rItem, sizeof(LVITEM));
345         rItem.mask  = LVIF_STATE;
346         rItem.iItem = index;
347         rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
348         GetItem(&rItem);
349
350         for(int i=0;i<m_HashMap[data->m_CommitHash].size();i++)
351         {
352                 CString str;
353                 str=m_HashMap[data->m_CommitHash][i];
354                 
355                 CString shortname;
356                 HBRUSH brush=0;
357                 shortname=_T("");
358                 if(GetShortName(str,shortname,_T("refs/heads/")))
359                 {
360                         brush = ::CreateSolidBrush(RGB(0xff, 0, 0));
361                 }else if(GetShortName(str,shortname,_T("refs/remotes/")))
362                 {
363                         brush = ::CreateSolidBrush(RGB(0xff, 0xff, 0));
364                 }
365                 else if(GetShortName(str,shortname,_T("refs/tags/")))
366                 {
367                         brush = ::CreateSolidBrush(RGB(0, 0, 0xff));
368                 }
369
370                 if(!shortname.IsEmpty())
371                 {
372                         SIZE size;
373                         memset(&size,0,sizeof(SIZE));
374                         GetTextExtentPoint32(hdc, shortname,shortname.GetLength(),&size);
375                 
376                         rt.SetRect(rt.left,rt.top,rt.left+size.cx,rt.bottom);
377                         rt.right+=4;
378                         ::FillRect(hdc, &rt, brush);
379                         if (rItem.state & LVIS_SELECTED)
380                         {
381                                 COLORREF   clrOld   = ::SetTextColor(hdc,::GetSysColor(COLOR_HIGHLIGHTTEXT));   
382                                 ::DrawText(hdc,shortname,shortname.GetLength(),&rt,DT_CENTER);
383                                 ::SetTextColor(hdc,clrOld);   
384                         }else
385                         {
386                                 ::DrawText(hdc,shortname,shortname.GetLength(),&rt,DT_CENTER);
387                         }
388
389                         
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);
395                                 
396                         rt.left=rt.right+3;
397                 }
398                 if(brush)
399                         ::DeleteObject(brush);
400         }               
401         rt.right=rect.right;
402
403         if (rItem.state & LVIS_SELECTED)
404         {
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);   
408         }else
409         {
410                 ::DrawText(hdc,data->m_Subject,data->m_Subject.GetLength(),&rt,DT_LEFT);
411         }
412         
413 }
414
415 void CGitLogList::paintGraphLane(HDC hdc, int laneHeight,int type, int x1, int x2,
416                                       const COLORREF& col,int top
417                                                                           )  
418 {
419         int h = laneHeight / 2;
420         int m = (x1 + x2) / 2;
421         int r = (x2 - x1) / 3;
422         int d =  2 * r;
423
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
430
431         //static QPen myPen(Qt::black, 2); // fast path here
432         CPen pen;
433         pen.CreatePen(PS_SOLID,2,col);
434         //myPen.setColor(col);
435         HPEN oldpen=(HPEN)::SelectObject(hdc,(HPEN)pen);
436
437         //p->setPen(myPen);
438
439         // vertical line
440         switch (type) {
441         case Lanes::ACTIVE:
442         case Lanes::NOT_ACTIVE:
443         case Lanes::MERGE_FORK:
444         case Lanes::MERGE_FORK_R:
445         case Lanes::MERGE_FORK_L:
446         case Lanes::JOIN:
447         case Lanes::JOIN_R:
448         case Lanes::JOIN_L:
449                 DrawLine(hdc,P_90,P_270);
450                 //p->drawLine(P_90, P_270);
451                 break;
452         case Lanes::HEAD:
453         case Lanes::HEAD_R:
454         case Lanes::HEAD_L:
455         case Lanes::BRANCH:
456                 DrawLine(hdc,P_CENTER,P_270);
457                 //p->drawLine(P_CENTER, P_270);
458                 break;
459         case Lanes::TAIL:
460         case Lanes::TAIL_R:
461         case Lanes::TAIL_L:
462         case Lanes::INITIAL:
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);
469                 break;
470         default:
471                 break;
472         }
473
474         // horizontal line
475         switch (type) {
476         case Lanes::MERGE_FORK:
477         case Lanes::JOIN:
478         case Lanes::HEAD:
479         case Lanes::TAIL:
480         case Lanes::CROSS:
481         case Lanes::CROSS_EMPTY:
482         case Lanes::BOUNDARY_C:
483                 DrawLine(hdc,P_180,P_0);
484                 //p->drawLine(P_180, P_0);
485                 break;
486         case Lanes::MERGE_FORK_R:
487         case Lanes::JOIN_R:
488         case Lanes::HEAD_R:
489         case Lanes::TAIL_R:
490         case Lanes::BOUNDARY_R:
491                 DrawLine(hdc,P_180,P_CENTER);
492                 //p->drawLine(P_180, P_CENTER);
493                 break;
494         case Lanes::MERGE_FORK_L:
495         case Lanes::JOIN_L:
496         case Lanes::HEAD_L:
497         case Lanes::TAIL_L:
498         case Lanes::BOUNDARY_L:
499                 DrawLine(hdc,P_CENTER,P_0);
500                 //p->drawLine(P_CENTER, P_0);
501                 break;
502         default:
503                 break;
504         }
505
506         CBrush brush;
507         brush.CreateSolidBrush(col);
508         HBRUSH oldbrush=(HBRUSH)::SelectObject(hdc,(HBRUSH)brush);
509         // center symbol, e.g. rect or ellipse
510         switch (type) {
511         case Lanes::ACTIVE:
512         case Lanes::INITIAL:
513         case Lanes::BRANCH:
514
515                 //p->setPen(Qt::NoPen);
516                 //p->setBrush(col);
517                 ::Ellipse(hdc, R_CENTER);
518                 //p->drawEllipse(R_CENTER);
519                 break;
520         case Lanes::MERGE_FORK:
521         case Lanes::MERGE_FORK_R:
522         case Lanes::MERGE_FORK_L:
523                 //p->setPen(Qt::NoPen);
524                 //p->setBrush(col);
525                 //p->drawRect(R_CENTER);
526                 Rectangle(hdc,R_CENTER);
527                 break;
528         case Lanes::UNAPPLIED:
529                 // Red minus sign
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);
534                 break;
535         case Lanes::APPLIED:
536                 // Green plus sign
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);
543                 break;
544         case Lanes::BOUNDARY:
545                 //p->setBrush(back);
546                 //p->drawEllipse(R_CENTER);
547                 ::Ellipse(hdc, R_CENTER);
548                 break;
549         case Lanes::BOUNDARY_C:
550         case Lanes::BOUNDARY_R:
551         case Lanes::BOUNDARY_L:
552                 //p->setBrush(back);
553                 //p->drawRect(R_CENTER);
554                 ::Rectangle(hdc,R_CENTER);
555                 break;
556         default:
557                 break;
558         }
559
560         ::SelectObject(hdc,oldpen);
561         ::SelectObject(hdc,oldbrush);
562         #undef P_CENTER
563         #undef P_0
564         #undef P_90
565         #undef P_180
566         #undef P_270
567         #undef R_CENTER
568 }
569
570 void CGitLogList::DrawGraph(HDC hdc,CRect &rect,INT_PTR index)
571 {
572         //todo unfinished
573         return;
574         GitRev* data = (GitRev*)m_arShownList.GetAt(index);
575         CRect rt=rect;
576         LVITEM   rItem;
577         SecureZeroMemory(&rItem, sizeof(LVITEM));
578         rItem.mask  = LVIF_STATE;
579         rItem.iItem = index;
580         rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
581         GetItem(&rItem);
582
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) };
586
587
588 //      p->translate(QPoint(opt.rect.left(), opt.rect.top()));
589
590
591
592         if (data->m_Lanes.size() == 0)
593                 m_logEntries.setLane(data->m_CommitHash);
594
595         std::vector<int>& lanes=data->m_Lanes;
596         UINT laneNum = lanes.size();
597         UINT mergeLane = 0;
598         for (UINT i = 0; i < laneNum; i++)
599                 if (Lanes::isMerge(lanes[i])) {
600                         mergeLane = i;
601                         break;
602                 }
603
604         int x1 = 0, x2 = 0;
605         int maxWidth = rect.Width();
606         int lw = 3 * rect.Height() / 4; //laneWidth() 
607         for (UINT i = 0; i < laneNum && x2 < maxWidth; i++) {
608
609                 x1 = x2;
610                 x2 += lw;
611
612                 int ln = lanes[i];
613                 if (ln == Lanes::EMPTY)
614                         continue;
615
616                 UINT col = (  Lanes:: isHead(ln) ||Lanes:: isTail(ln) || Lanes::isJoin(ln)
617                             || ln ==Lanes:: CROSS_EMPTY) ? mergeLane : i;
618
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);
622                 } else
623                         paintGraphLane(hdc, rect.Height(),ln, x1, x2, colors[col % Lanes::COLORS_NUM],rect.top);
624         }
625
626         TRACE(_T("index %d %d\r\n"),index,data->m_Lanes.size());
627 }
628
629 void CGitLogList::OnNMCustomdrawLoglist(NMHDR *pNMHDR, LRESULT *pResult)
630 {
631
632         NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
633         // Take the default processing unless we set this to something else below.
634         *pResult = CDRF_DODEFAULT;
635
636         if (m_bNoDispUpdates)
637                 return;
638
639         switch (pLVCD->nmcd.dwDrawStage)
640         {
641         case CDDS_PREPAINT:
642                 {
643                         *pResult = CDRF_NOTIFYITEMDRAW;
644                         return;
645                 }
646                 break;
647         case CDDS_ITEMPREPAINT:
648                 {
649                         // This is the prepaint stage for an item. Here's where we set the
650                         // item's text color. 
651                         
652                         // Tell Windows to send draw notifications for each subitem.
653                         *pResult = CDRF_NOTIFYSUBITEMDRAW;
654
655                         COLORREF crText = GetSysColor(COLOR_WINDOWTEXT);
656
657                         if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec)
658                         {
659                                 GitRev* data = (GitRev*)m_arShownList.GetAt(pLVCD->nmcd.dwItemSpec);
660                                 if (data)
661                                 {
662 #if 0
663                                         if (data->bCopiedSelf)
664                                         {
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);
668                                         }
669
670                                         if (data->bCopies)
671                                                 crText = m_Colors.GetColor(CColors::Modified);
672 #endif
673 //                                      if ((data->childStackDepth)||(m_mergedRevs.find(data->Rev) != m_mergedRevs.end()))
674 //                                              crText = GetSysColor(COLOR_GRAYTEXT);
675 //                                      if (data->Rev == m_wcRev)
676 //                                      {
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;
681 //                                      }
682                                 }
683                         }
684                         if (m_arShownList.GetCount() == (INT_PTR)pLVCD->nmcd.dwItemSpec)
685                         {
686                                 if (m_bStrictStopped)
687                                         crText = GetSysColor(COLOR_GRAYTEXT);
688                         }
689                         // Store the color back in the NMLVCUSTOMDRAW struct.
690                         pLVCD->clrText = crText;
691                         return;
692                 }
693                 break;
694         case CDDS_ITEMPREPAINT|CDDS_ITEM|CDDS_SUBITEM:
695                 {
696                         if ((m_bStrictStopped)&&(m_arShownList.GetCount() == (INT_PTR)pLVCD->nmcd.dwItemSpec))
697                         {
698                                 pLVCD->nmcd.uItemState &= ~(CDIS_SELECTED|CDIS_FOCUS);
699                         }
700
701                         if (pLVCD->iSubItem == LOGLIST_GRAPH)
702                         {
703                                 if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec)
704                                 {
705                                         CRect rect;
706                                         GetSubItemRect(pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_BOUNDS, rect);
707                                         
708                                         FillBackGround(pLVCD->nmcd.hdc, (INT_PTR)pLVCD->nmcd.dwItemSpec,rect);
709                                         DrawGraph(pLVCD->nmcd.hdc,rect,pLVCD->nmcd.dwItemSpec);
710
711                                         *pResult = CDRF_SKIPDEFAULT;
712                                         return;
713                                 
714                                 }
715                         }
716
717                         if (pLVCD->iSubItem == LOGLIST_MESSAGE)
718                         {
719                                 if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec)
720                                 {
721                                         GitRev* data = (GitRev*)m_arShownList.GetAt(pLVCD->nmcd.dwItemSpec);
722                                         if(m_HashMap[data->m_CommitHash].size()!=0)
723                                         {
724                                                 CRect rect;
725                                                 GetSubItemRect(pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_BOUNDS, rect);
726                                         
727                                                 FillBackGround(pLVCD->nmcd.hdc, (INT_PTR)pLVCD->nmcd.dwItemSpec,rect);
728                                                 DrawTagBranch(pLVCD->nmcd.hdc,rect,pLVCD->nmcd.dwItemSpec);
729
730                                                 *pResult = CDRF_SKIPDEFAULT;
731                                                 return;
732
733                                         }
734                                 }
735                         }
736                         
737                         if (pLVCD->iSubItem == 1)
738                         {
739                                 *pResult = CDRF_DODEFAULT;
740
741                                 if (m_arShownList.GetCount() <= (INT_PTR)pLVCD->nmcd.dwItemSpec)
742                                         return;
743
744                                 int             nIcons = 0;
745                                 int             iconwidth = ::GetSystemMetrics(SM_CXSMICON);
746                                 int             iconheight = ::GetSystemMetrics(SM_CYSMICON);
747
748                                 GitRev* pLogEntry = reinterpret_cast<GitRev *>(m_arShownList.GetAt(pLVCD->nmcd.dwItemSpec));
749                                 CRect rect;
750                                 GetSubItemRect(pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_BOUNDS, rect);
751                                 // Get the selected state of the
752                                 // item being drawn.                                                    
753
754                                 // Fill the background
755                                 FillBackGround(pLVCD->nmcd.hdc, (INT_PTR)pLVCD->nmcd.dwItemSpec,rect);
756                                 
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);
760                                 nIcons++;
761
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);
764                                 nIcons++;
765
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);
768                                 nIcons++;
769
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);
772                                 nIcons++;
773                                 *pResult = CDRF_SKIPDEFAULT;
774                                 return;
775                         }
776                 }
777                 break;
778         }
779         *pResult = CDRF_DODEFAULT;
780
781 }
782
783 // CGitLogList message handlers
784
785 void CGitLogList::OnLvnGetdispinfoLoglist(NMHDR *pNMHDR, LRESULT *pResult)
786 {
787         NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
788
789         // Create a pointer to the item
790         LV_ITEM* pItem = &(pDispInfo)->item;
791
792         // Do the list need text information?
793         if (!(pItem->mask & LVIF_TEXT))
794                 return;
795
796         // By default, clear text buffer.
797         lstrcpyn(pItem->pszText, _T(""), pItem->cchTextMax);
798
799         bool bOutOfRange = pItem->iItem >= ShownCountWithStopped();
800         
801         *pResult = 0;
802         if (m_bNoDispUpdates || m_bThreadRunning || bOutOfRange)
803                 return;
804
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));
810     
811         // Which column?
812         switch (pItem->iSubItem)
813         {
814         case this->LOGLIST_GRAPH:       //Graphic
815                 if (pLogEntry)
816                 {
817                 }
818                 break;
819         case this->LOGLIST_ACTION: //action -- no text in the column
820                 break;
821         case this->LOGLIST_MESSAGE: //Message
822                 if (pLogEntry)
823                         lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->m_Subject, pItem->cchTextMax);
824                 break;
825         case this->LOGLIST_AUTHOR: //Author
826                 if (pLogEntry)
827                         lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->m_AuthorName, pItem->cchTextMax);
828                 break;
829         case this->LOGLIST_DATE: //Date
830                 if (pLogEntry)
831                         lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->m_AuthorDate.Format(_T("%Y-%m-%d %H:%M")), pItem->cchTextMax);
832                 break;
833                 
834         case 5:
835
836                 break;
837         default:
838                 ASSERT(false);
839         }
840 }
841
842 void CGitLogList::OnContextMenu(CWnd* pWnd, CPoint point)
843 {
844
845         int selIndex = GetSelectionMark();
846         if (selIndex < 0)
847                 return; // nothing selected, nothing to do with a context menu
848
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()))
852                 return;
853
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))
857         {
858                 CRect rect;
859                 GetItemRect(selIndex, &rect, LVIR_LABEL);
860                 ClientToScreen(&rect);
861                 point = rect.CenterPoint();
862         }
863         m_nSearchIndex = selIndex;
864         m_bCancelled = FALSE;
865
866         // calculate some information the context menu commands can use
867 //      CString pathURL = GetURLFromPath(m_path);
868
869         POSITION pos = GetFirstSelectedItemPosition();
870         int indexNext = GetNextSelectedItem(pos);
871         if (indexNext < 0)
872                 return;
873
874         GitRev* pSelLogEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(indexNext));
875 #if 0
876         GitRev revSelected = pSelLogEntry->Rev;
877         GitRev revPrevious = git_revnum_t(revSelected)-1;
878         if ((pSelLogEntry->pArChangedPaths)&&(pSelLogEntry->pArChangedPaths->GetCount() <= 2))
879         {
880                 for (int i=0; i<pSelLogEntry->pArChangedPaths->GetCount(); ++i)
881                 {
882                         LogChangedPath * changedpath = (LogChangedPath *)pSelLogEntry->pArChangedPaths->GetAt(i);
883                         if (changedpath->lCopyFromRev)
884                                 revPrevious = changedpath->lCopyFromRev;
885                 }
886         }
887         GitRev revSelected2;
888         if (pos)
889         {
890                 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(GetNextSelectedItem(pos)));
891                 revSelected2 = pLogEntry->Rev;
892         }
893         bool bAllFromTheSameAuthor = true;
894         CString firstAuthor;
895         CLogDataVector selEntries;
896         GitRev revLowest, revHighest;
897         GitRevRangeArray revisionRanges;
898         {
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;
906                 while (pos)
907                 {
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);
915                 }
916         }
917
918 #endif
919
920         int FirstSelect=-1, LastSelect=-1;
921         pos = GetFirstSelectedItemPosition();
922         FirstSelect = GetNextSelectedItem(pos);
923         while(pos)
924         {
925                 LastSelect = GetNextSelectedItem(pos);
926         }
927         //entry is selected, now show the popup menu
928         CIconMenu popup;
929         if (popup.CreatePopupMenu())
930         {
931                 if (GetSelectedCount() == 1)
932                 {
933 #if 0
934                         if (!m_path.IsDirectory())
935                         {
936                                 if (m_hasWC)
937                                 {
938                                         popup.AppendMenuIcon(ID_COMPARE, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
939                                         popup.AppendMenuIcon(ID_BLAMECOMPARE, IDS_LOG_POPUP_BLAMECOMPARE, IDI_BLAME);
940                                 }
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);
949                         }
950                         else
951 #endif 
952                         {
953                                 if (m_hasWC)
954                                 {
955                                         popup.AppendMenuIcon(ID_COMPARE, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
956                                         // TODO:
957                                         // TortoiseMerge could be improved to take a /blame switch
958                                         // and then not 'cat' the files from a unified diff but
959                                         // blame then.
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);
963                                 }
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);
968                         }
969
970 //                      if (!m_ProjectProperties.sWebViewerRev.IsEmpty())
971 //                      {
972 //                              popup.AppendMenuIcon(ID_VIEWREV, IDS_LOG_POPUP_VIEWREV);
973 //                      }
974 //                      if (!m_ProjectProperties.sWebViewerPathRev.IsEmpty())
975 //                      {
976 //                              popup.AppendMenuIcon(ID_VIEWPATHREV, IDS_LOG_POPUP_VIEWPATHREV);
977 //                      }
978 //                      if ((!m_ProjectProperties.sWebViewerPathRev.IsEmpty())||
979 //                              (!m_ProjectProperties.sWebViewerRev.IsEmpty()))
980 //                      {
981 //                              popup.AppendMenu(MF_SEPARATOR, NULL);
982 //                      }
983
984                         //if (m_hasWC)
985                         //      popup.AppendMenuIcon(ID_REVERTTOREV, IDS_LOG_POPUP_REVERTTOREV, IDI_REVERT);
986                         //if (m_hasWC)
987                         //      popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREV, IDI_REVERT);
988                         //if (m_hasWC)
989                         //      popup.AppendMenuIcon(ID_MERGEREV, IDS_LOG_POPUP_MERGEREV, IDI_MERGE);
990                         
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);
996                         
997
998                         popup.AppendMenu(MF_SEPARATOR, NULL);
999                 }
1000                 else if (GetSelectedCount() >= 2)
1001                 {
1002                         bool bAddSeparator = false;
1003                         if (IsSelectionContinuous() || (GetSelectedCount() == 2))
1004                         {
1005                                 popup.AppendMenuIcon(ID_COMPARETWO, IDS_LOG_POPUP_COMPARETWO, IDI_DIFF);
1006                         }
1007                         if (GetSelectedCount() == 2)
1008                         {
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;
1012                         }
1013                         if (m_hasWC)
1014                         {
1015                                 //popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREVS, IDI_REVERT);
1016 //                              if (m_hasWC)
1017 //                                      popup.AppendMenuIcon(ID_MERGEREV, IDS_LOG_POPUP_MERGEREVS, IDI_MERGE);
1018                                 bAddSeparator = true;
1019                         }
1020                         if (bAddSeparator)
1021                                 popup.AppendMenu(MF_SEPARATOR, NULL);
1022                 }
1023 #if 0
1024 //              if ((selEntries.size() > 0)&&(bAllFromTheSameAuthor))
1025 //              {
1026 //                      popup.AppendMenuIcon(ID_EDITAUTHOR, IDS_LOG_POPUP_EDITAUTHOR);
1027 //              }
1028 //              if (GetSelectedCount() == 1)
1029 //              {
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);
1033 //              }
1034 #endif
1035
1036                 
1037                 if (GetSelectedCount() == 1)
1038                 {
1039                         popup.AppendMenuIcon(ID_COPYHASH, _T("Copy Commit Hash"));
1040                 }
1041                 if (GetSelectedCount() != 0)
1042                 {
1043                         popup.AppendMenuIcon(ID_COPYCLIPBOARD, IDS_LOG_POPUP_COPYTOCLIPBOARD);
1044                 }
1045                 popup.AppendMenuIcon(ID_FINDENTRY, IDS_LOG_POPUP_FIND);
1046
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;
1052
1053                 switch (cmd)
1054                 {
1055                         case ID_GNUDIFF1:
1056                         {
1057                                 CString tempfile=GetTempFile();
1058                                 CString cmd;
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);
1063                         }
1064                         break;
1065
1066                         case ID_GNUDIFF2:
1067                         {
1068                                 CString tempfile=GetTempFile();
1069                                 CString cmd;
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));
1075
1076                         }
1077                         break;
1078
1079                 case ID_COMPARETWO:
1080                         {
1081                                 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
1082                                 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
1083                                 CFileDiffDlg dlg;
1084                                 dlg.SetDiff(NULL,*r1,*r2);
1085                                 dlg.DoModal();
1086                                 
1087                         }
1088                         break;
1089                 
1090
1091                 case ID_COMPARE:
1092                         {
1093                                 GitRev * r1 = &m_wcRev;
1094                                 GitRev * r2 = pSelLogEntry;
1095                                 CFileDiffDlg dlg;
1096                                 dlg.SetDiff(NULL,*r1,*r2);
1097                                 dlg.DoModal();
1098
1099                                 //user clicked on the menu item "compare with working copy"
1100                                 //if (PromptShown())
1101                                 //{
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);
1106                                 //}
1107                                 //else
1108                                 //      CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1109                         }
1110                         break;
1111
1112                 case ID_COMPAREWITHPREVIOUS:
1113                         {
1114
1115                                 CFileDiffDlg dlg;
1116                                 
1117                                 if(pSelLogEntry->m_ParentHash.size()>0)
1118                                 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
1119                                 {
1120                                         dlg.SetDiff(NULL,pSelLogEntry->m_CommitHash,pSelLogEntry->m_ParentHash[0]);
1121                                         dlg.DoModal();
1122                                 }else
1123                                 {
1124                                         CMessageBox::Show(NULL,_T("No previous version"),_T("TortoiseGit"),MB_OK);      
1125                                 }
1126                                 //if (PromptShown())
1127                                 //{
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);
1132                                 //}
1133                                 //else
1134                                 //      CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1135                         }
1136                         break;
1137                 case ID_COPYCLIPBOARD:
1138                         {
1139                                 CopySelectionToClipBoard();
1140                         }
1141                         break;
1142                 case ID_COPYHASH:
1143                         {
1144                                 CopySelectionToClipBoard(TRUE);
1145                         }
1146                         break;
1147                 case ID_EXPORT:
1148                         CAppUtils::Export(&pSelLogEntry->m_CommitHash);
1149                         break;
1150                 case ID_CREATE_BRANCH:
1151                         CAppUtils::CreateBranchTag(FALSE,&pSelLogEntry->m_CommitHash);
1152                         m_HashMap.clear();
1153                         g_Git.GetMapHashToFriendName(m_HashMap);
1154                         Invalidate();                   
1155                         break;
1156                 case ID_CREATE_TAG:
1157                         CAppUtils::CreateBranchTag(TRUE,&pSelLogEntry->m_CommitHash);
1158                         m_HashMap.clear();
1159                         g_Git.GetMapHashToFriendName(m_HashMap);
1160                         Invalidate();
1161                         break;
1162                 case ID_SWITCHTOREV:
1163                         CAppUtils::Switch(&pSelLogEntry->m_CommitHash);
1164                         break;
1165
1166                 default:
1167                         //CMessageBox::Show(NULL,_T("Have not implemented"),_T("TortoiseGit"),MB_OK);
1168                         break;
1169 #if 0
1170         
1171                 case ID_REVERTREV:
1172                         {
1173                                 // we need an URL to complete this command, so error out if we can't get an URL
1174                                 if (pathURL.IsEmpty())
1175                                 {
1176                                         CString strMessage;
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"));
1180                                         break;          //exit
1181                                 }
1182                                 CString msg;
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)
1185                                 {
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);
1194                                         dlg.DoModal();
1195                                 }
1196                         }
1197                         break;
1198                 case ID_MERGEREV:
1199                         {
1200                                 // we need an URL to complete this command, so error out if we can't get an URL
1201                                 if (pathURL.IsEmpty())
1202                                 {
1203                                         CString strMessage;
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"));
1207                                         break;          //exit
1208                                 }
1209
1210                                 CString path = m_path.GetWinPathString();
1211                                 bool bGotSavePath = false;
1212                                 if ((GetSelectedCount() == 1)&&(!m_path.IsDirectory()))
1213                                 {
1214                                         bGotSavePath = CAppUtils::FileOpenSave(path, NULL, IDS_LOG_MERGETO, IDS_COMMONFILEFILTER, true, GetSafeHwnd());
1215                                 }
1216                                 else
1217                                 {
1218                                         CBrowseFolder folderBrowser;
1219                                         folderBrowser.SetInfo(CString(MAKEINTRESOURCE(IDS_LOG_MERGETO)));
1220                                         bGotSavePath = (folderBrowser.Show(GetSafeHwnd(), path, path) == CBrowseFolder::OK);
1221                                 }
1222                                 if (bGotSavePath)
1223                                 {
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);
1232                                         dlg.DoModal();
1233                                 }
1234                         }
1235                         break;
1236                 case ID_REVERTTOREV:
1237                         {
1238                                 // we need an URL to complete this command, so error out if we can't get an URL
1239                                 if (pathURL.IsEmpty())
1240                                 {
1241                                         CString strMessage;
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"));
1245                                         break;          //exit
1246                                 }
1247
1248                                 CString msg;
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)
1251                                 {
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);
1261                                         dlg.DoModal();
1262                                 }
1263                         }
1264                         break;
1265         
1266
1267         
1268                 case ID_BLAMECOMPARE:
1269                         {
1270                                 //user clicked on the menu item "compare with working copy"
1271                                 //now first get the revision which is selected
1272                                 if (PromptShown())
1273                                 {
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);
1277                                 }
1278                                 else
1279                                         CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), m_LogRevision, false, false, true);
1280                         }
1281                         break;
1282                 case ID_BLAMETWO:
1283                         {
1284                                 //user clicked on the menu item "compare and blame revisions"
1285                                 if (PromptShown())
1286                                 {
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);
1290                                 }
1291                                 else
1292                                         CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revSelected2, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
1293                         }
1294                         break;
1295                 case ID_BLAMEWITHPREVIOUS:
1296                         {
1297                                 //user clicked on the menu item "Compare and Blame with previous revision"
1298                                 if (PromptShown())
1299                                 {
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);
1303                                 }
1304                                 else
1305                                         CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
1306                         }
1307                         break;
1308                 
1309                 case ID_OPENWITH:
1310                         bOpenWith = true;
1311                 case ID_OPEN:
1312                         {
1313                                 CProgressDlg progDlg;
1314                                 progDlg.SetTitle(IDS_APPNAME);
1315                                 progDlg.SetAnimation(IDR_DOWNLOAD);
1316                                 CString sInfoLine;
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))
1324                                 {
1325                                         bSuccess = false;
1326                                         // try again, but with the selected revision as the peg revision
1327                                         if (!Cat(m_path, revSelected, revSelected, tempfile))
1328                                         {
1329                                                 progDlg.Stop();
1330                                                 SetAndClearProgressInfo((HWND)NULL);
1331                                                 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1332                                                 EnableOKButton();
1333                                                 break;
1334                                         }
1335                                         bSuccess = true;
1336                                 }
1337                                 if (bSuccess)
1338                                 {
1339                                         progDlg.Stop();
1340                                         SetAndClearProgressInfo((HWND)NULL);
1341                                         SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1342                                         int ret = 0;
1343                                         if (!bOpenWith)
1344                                                 ret = (int)ShellExecute(this->m_hWnd, NULL, tempfile.GetWinPath(), NULL, NULL, SW_SHOWNORMAL);
1345                                         if ((ret <= HINSTANCE_ERROR)||bOpenWith)
1346                                         {
1347                                                 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1348                                                 cmd += tempfile.GetWinPathString() + _T(" ");
1349                                                 CAppUtils::LaunchApplication(cmd, NULL, false);
1350                                         }
1351                                 }
1352                         }
1353                         break;
1354                 case ID_BLAME:
1355                         {
1356                                 CBlameDlg dlg;
1357                                 dlg.EndRev = revSelected;
1358                                 if (dlg.DoModal() == IDOK)
1359                                 {
1360                                         CBlame blame;
1361                                         CString tempfile;
1362                                         CString logfile;
1363                                         tempfile = blame.BlameToTempFile(m_path, dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, _T(""), dlg.m_bIncludeMerge, TRUE, TRUE);
1364                                         if (!tempfile.IsEmpty())
1365                                         {
1366                                                 if (dlg.m_bTextView)
1367                                                 {
1368                                                         //open the default text editor for the result file
1369                                                         CAppUtils::StartTextViewer(tempfile);
1370                                                 }
1371                                                 else
1372                                                 {
1373                                                         CString sParams = _T("/path:\"") + m_path.GetGitPathString() + _T("\" ");
1374                                                         if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(m_path.GetFileOrDirectoryName()),sParams))
1375                                                         {
1376                                                                 break;
1377                                                         }
1378                                                 }
1379                                         }
1380                                         else
1381                                         {
1382                                                 CMessageBox::Show(this->m_hWnd, blame.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1383                                         }
1384                                 }
1385                         }
1386                         break;
1387                 case ID_UPDATE:
1388                         {
1389                                 CString sCmd;
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);
1395                         }
1396                         break;
1397                 case ID_FINDENTRY:
1398                         {
1399                                 m_nSearchIndex = GetSelectionMark();
1400                                 if (m_nSearchIndex < 0)
1401                                         m_nSearchIndex = 0;
1402                                 if (m_pFindDialog)
1403                                 {
1404                                         break;
1405                                 }
1406                                 else
1407                                 {
1408                                         m_pFindDialog = new CFindReplaceDialog();
1409                                         m_pFindDialog->Create(TRUE, NULL, NULL, FR_HIDEUPDOWN | FR_HIDEWHOLEWORD, this);                                                                        
1410                                 }
1411                         }
1412                         break;
1413                 case ID_REPOBROWSE:
1414                         {
1415                                 CString sCmd;
1416                                 sCmd.Format(_T("%s /command:repobrowser /path:\"%s\" /rev:%s"),
1417                                         (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
1418                                         (LPCTSTR)pathURL, (LPCTSTR)revSelected.ToString());
1419
1420                                 CAppUtils::LaunchApplication(sCmd, NULL, false);
1421                         }
1422                         break;
1423                 case ID_EDITLOG:
1424                         {
1425                                 EditLogMessage(selIndex);
1426                         }
1427                         break;
1428                 case ID_EDITAUTHOR:
1429                         {
1430                                 EditAuthor(selEntries);
1431                         }
1432                         break;
1433                 case ID_REVPROPS:
1434                         {
1435                                 CEditPropertiesDlg dlg;
1436                                 dlg.SetProjectProperties(&m_ProjectProperties);
1437                                 CTGitPathList escapedlist;
1438                                 dlg.SetPathList(CTGitPathList(CTGitPath(pathURL)));
1439                                 dlg.SetRevision(revSelected);
1440                                 dlg.RevProps(true);
1441                                 dlg.DoModal();
1442                         }
1443                         break;
1444                 
1445                 case ID_EXPORT:
1446                         {
1447                                 CString sCmd;
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);
1452                         }
1453                         break;
1454                 case ID_CHECKOUT:
1455                         {
1456                                 CString sCmd;
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);
1462                         }
1463                         break;
1464                 case ID_VIEWREV:
1465                         {
1466                                 CString url = m_ProjectProperties.sWebViewerRev;
1467                                 url = GetAbsoluteUrlFromRelativeUrl(url);
1468                                 url.Replace(_T("%REVISION%"), revSelected.ToString());
1469                                 if (!url.IsEmpty())
1470                                         ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);                                        
1471                         }
1472                         break;
1473                 case ID_VIEWPATHREV:
1474                         {
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);
1482                                 if (!url.IsEmpty())
1483                                         ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);                                        
1484                         }
1485                         break;
1486 #endif
1487                 
1488                 } // switch (cmd)
1489                 theApp.DoWaitCursor(-1);
1490 //              EnableOKButton();
1491         } // if (popup.CreatePopupMenu())
1492
1493 }
1494
1495 bool CGitLogList::IsSelectionContinuous()
1496 {
1497         if ( GetSelectedCount()==1 )
1498         {
1499                 // if only one revision is selected, the selection is of course
1500                 // continuous
1501                 return true;
1502         }
1503
1504         POSITION pos = GetFirstSelectedItemPosition();
1505         bool bContinuous = (m_arShownList.GetCount() == (INT_PTR)m_logEntries.size());
1506         if (bContinuous)
1507         {
1508                 int itemindex = GetNextSelectedItem(pos);
1509                 while (pos)
1510                 {
1511                         int nextindex = GetNextSelectedItem(pos);
1512                         if (nextindex - itemindex > 1)
1513                         {
1514                                 bContinuous = false;
1515                                 break;
1516                         }
1517                         itemindex = nextindex;
1518                 }
1519         }
1520         return bContinuous;
1521 }
1522
1523 void CGitLogList::CopySelectionToClipBoard(bool HashOnly)
1524 {
1525
1526         CString sClipdata;
1527         POSITION pos = GetFirstSelectedItemPosition();
1528         if (pos != NULL)
1529         {
1530                 CString sRev;
1531                 sRev.LoadString(IDS_LOG_REVISION);
1532                 CString sAuthor;
1533                 sAuthor.LoadString(IDS_LOG_AUTHOR);
1534                 CString sDate;
1535                 sDate.LoadString(IDS_LOG_DATE);
1536                 CString sMessage;
1537                 sMessage.LoadString(IDS_LOG_MESSAGE);
1538                 while (pos)
1539                 {
1540                         CString sLogCopyText;
1541                         CString sPaths;
1542                         GitRev * pLogEntry = reinterpret_cast<GitRev *>(m_arShownList.GetAt(GetNextSelectedItem(pos)));
1543
1544                         if(!HashOnly)
1545                         {
1546                                 //pLogEntry->m_Files
1547                                 //LogChangedPathArray * cpatharray = pLogEntry->pArChangedPaths;
1548                         
1549                                 for (int cpPathIndex = 0; cpPathIndex<pLogEntry->m_Files.GetCount(); ++cpPathIndex)
1550                                 {
1551                                         sPaths += ((CTGitPath&)pLogEntry->m_Files[cpPathIndex]).GetActionName() + _T(" : ") + pLogEntry->m_Files[cpPathIndex].GetGitPathString();
1552                                         sPaths += _T("\r\n");
1553                                 }
1554                                 sPaths.Trim();
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,
1560                                         (LPCTSTR)sPaths);
1561                                 sClipdata +=  sLogCopyText;
1562                         }else
1563                         {
1564                                 sClipdata += pLogEntry->m_CommitHash;
1565                                 break;
1566                         }
1567
1568                 }
1569                 CStringUtils::WriteAsciiStringToClipboard(sClipdata, GetSafeHwnd());
1570         }
1571
1572 }
1573
1574 void CGitLogList::DiffSelectedRevWithPrevious()
1575 {
1576 #if 0
1577         if (m_bThreadRunning)
1578                 return;
1579         UpdateLogInfoLabel();
1580         int selIndex = m_LogList.GetSelectionMark();
1581         if (selIndex < 0)
1582                 return;
1583         int selCount = m_LogList.GetSelectedCount();
1584         if (selCount != 1)
1585                 return;
1586
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;
1591         long rev2 = rev1-1;
1592         CTGitPath path = m_path;
1593
1594         // See how many files under the relative root were changed in selected revision
1595         int nChanged = 0;
1596         LogChangedPath * changed = NULL;
1597         for (INT_PTR c = 0; c < pLogEntry->pArChangedPaths->GetCount(); ++c)
1598         {
1599                 LogChangedPath * cpath = pLogEntry->pArChangedPaths->GetAt(c);
1600                 if (cpath  &&  cpath -> sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0)
1601                 {
1602                         ++nChanged;
1603                         changed = cpath;
1604                 }
1605         }
1606
1607         if (m_path.IsDirectory() && nChanged == 1) 
1608         {
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()));
1612         } 
1613
1614         m_bCancelled = FALSE;
1615         DialogEnableWindow(IDOK, FALSE);
1616         SetPromptApp(&theApp);
1617         theApp.DoWaitCursor(1);
1618
1619         if (PromptShown())
1620         {
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);
1625         }
1626         else
1627         {
1628                 CAppUtils::StartShowCompare(m_hWnd, path, rev2, path, rev1, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1629         }
1630
1631         theApp.DoWaitCursor(-1);
1632         EnableOKButton();
1633 #endif
1634 }
1635
1636 void CGitLogList::OnLvnOdfinditemLoglist(NMHDR *pNMHDR, LRESULT *pResult)
1637 {
1638         LPNMLVFINDITEM pFindInfo = reinterpret_cast<LPNMLVFINDITEM>(pNMHDR);
1639         *pResult = -1;
1640         
1641         if (pFindInfo->lvfi.flags & LVFI_PARAM)
1642                 return; 
1643         if ((pFindInfo->iStart < 0)||(pFindInfo->iStart >= m_arShownList.GetCount()))
1644                 return;
1645         if (pFindInfo->lvfi.psz == 0)
1646                 return;
1647 #if 0
1648         CString sCmp = pFindInfo->lvfi.psz;
1649         CString sRev;   
1650         for (int i=pFindInfo->iStart; i<m_arShownList.GetCount(); ++i)
1651         {
1652                 GitRev * pLogEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
1653                 sRev.Format(_T("%ld"), pLogEntry->Rev);
1654                 if (pFindInfo->lvfi.flags & LVFI_PARTIAL)
1655                 {
1656                         if (sCmp.Compare(sRev.Left(sCmp.GetLength()))==0)
1657                         {
1658                                 *pResult = i;
1659                                 return;
1660                         }
1661                 }
1662                 else
1663                 {
1664                         if (sCmp.Compare(sRev)==0)
1665                         {
1666                                 *pResult = i;
1667                                 return;
1668                         }
1669                 }
1670         }
1671         if (pFindInfo->lvfi.flags & LVFI_WRAP)
1672         {
1673                 for (int i=0; i<pFindInfo->iStart; ++i)
1674                 {
1675                         PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(i));
1676                         sRev.Format(_T("%ld"), pLogEntry->Rev);
1677                         if (pFindInfo->lvfi.flags & LVFI_PARTIAL)
1678                         {
1679                                 if (sCmp.Compare(sRev.Left(sCmp.GetLength()))==0)
1680                                 {
1681                                         *pResult = i;
1682                                         return;
1683                                 }
1684                         }
1685                         else
1686                         {
1687                                 if (sCmp.Compare(sRev)==0)
1688                                 {
1689                                         *pResult = i;
1690                                         return;
1691                                 }
1692                         }
1693                 }
1694         }
1695 #endif
1696         *pResult = -1;
1697 }
1698
1699 int CGitLogList::FillGitLog()
1700 {
1701         ClearText();
1702
1703         this->m_logEntries.ClearAll();
1704         this->m_logEntries.ParserFromLog();
1705         SetItemCountEx(this->m_logEntries.size());
1706
1707         this->m_arShownList.RemoveAll();
1708
1709         for(int i=0;i<m_logEntries.size();i++)
1710                 this->m_arShownList.Add(&m_logEntries[i]);
1711
1712         return 0;
1713 }
1714
1715 BOOL CGitLogList::PreTranslateMessage(MSG* pMsg)
1716 {
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')
1720         {
1721                 //if (GetFocus()==GetDlgItem(IDC_LOGLIST))
1722                 {
1723                         if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
1724                         {
1725                                 DiffSelectedRevWithPrevious();
1726                                 return TRUE;
1727                         }
1728                 }
1729 #if 0
1730                 if (GetFocus()==GetDlgItem(IDC_LOGMSG))
1731                 {
1732                         DiffSelectedFile();
1733                         return TRUE;
1734                 }
1735 #endif
1736         }
1737
1738 #if 0
1739         if (m_hAccel && !bSkipAccelerator)
1740         {
1741                 int ret = TranslateAccelerator(m_hWnd, m_hAccel, pMsg);
1742                 if (ret)
1743                         return TRUE;
1744         }
1745         
1746 #endif
1747         //m_tooltips.RelayEvent(pMsg);
1748         return __super::PreTranslateMessage(pMsg);
1749 }
1750
1751 void CGitLogList::OnNMDblclkLoglist(NMHDR * /*pNMHDR*/, LRESULT *pResult)
1752 {
1753         // a double click on an entry in the revision list has happened
1754         *pResult = 0;
1755
1756   if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
1757           DiffSelectedRevWithPrevious();
1758 }
1759
1760 int CGitLogList::FetchLogAsync(CALLBACK_PROCESS *proc,void * data)
1761 {
1762         m_ProcCallBack=proc;
1763         m_ProcData=data;
1764
1765         InterlockedExchange(&m_bThreadRunning, TRUE);
1766         InterlockedExchange(&m_bNoDispUpdates, TRUE);
1767         if (AfxBeginThread(LogThreadEntry, this)==NULL)
1768         {
1769                 InterlockedExchange(&m_bThreadRunning, FALSE);
1770                 InterlockedExchange(&m_bNoDispUpdates, FALSE);
1771                 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
1772                 return -1;
1773         }
1774         return 0;
1775 }
1776
1777 //this is the thread function which calls the subversion function
1778 UINT CGitLogList::LogThreadEntry(LPVOID pVoid)
1779 {
1780         return ((CGitLogList*)pVoid)->LogThread();
1781 }
1782
1783
1784 UINT CGitLogList::LogThread()
1785 {
1786
1787         if(m_ProcCallBack)
1788                 m_ProcCallBack(m_ProcData,GITLOG_START);
1789
1790         InterlockedExchange(&m_bThreadRunning, TRUE);
1791
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);
1795
1796         //disable the "Get All" button while we're receiving
1797         //log messages.
1798
1799         CString temp;
1800         temp.LoadString(IDS_PROGRESSWAIT);
1801         ShowText(temp, true);
1802
1803 //      git_revnum_t r = -1;
1804         
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);
1809
1810 //    m_sRepositoryRoot = rootpath.GetGitPathString();
1811 //    m_sURL = m_path.GetGitPathString();
1812
1813     // we need the UUID to unambigously identify the log cache
1814 //    if (logCachePool.IsEnabled())
1815 //        m_sUUID = logCachePool.GetRepositoryInfo().GetRepositoryUUID (rootpath);
1816
1817     // if the log dialog is started from a working copy, we need to turn that
1818     // local path into an url here
1819 //    if (succeeded)
1820 //    {
1821 //        if (!m_path.IsUrl())
1822 //        {
1823 //              m_sURL = GetURLFromPath(m_path);
1824
1825                 // The URL is escaped because Git::logReceiver
1826                 // returns the path in a native format
1827 //              m_sURL = CPathUtils::PathUnescape(m_sURL);
1828   //      }
1829 //        m_sRelativeRoot = m_sURL.Mid(CPathUtils::PathUnescape(m_sRepositoryRoot).GetLength());
1830 //              m_sSelfRelativeURL = m_sRelativeRoot;
1831   //  }
1832 #if 0
1833     if (succeeded && !m_mergePath.IsEmpty() && m_mergedRevs.empty())
1834     {
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)
1842             {
1843                     // now check the relative paths
1844                     apr_hash_index_t *hi;
1845                     const void *key;
1846                     void *val;
1847
1848                     if (mergeinfo)
1849                     {
1850                             for (hi = apr_hash_first(localpool, mergeinfo); hi; hi = apr_hash_next(hi))
1851                             {
1852                                     apr_hash_this(hi, &key, NULL, &val);
1853                                     if (m_sURL.Compare(CUnicodeUtils::GetUnicode((char*)key)) == 0)
1854                                     {
1855                                             apr_array_header_t * arr = (apr_array_header_t*)val;
1856                                             if (val)
1857                                             {
1858                                                     for (long i=0; i<arr->nelts; ++i)
1859                                                     {
1860                                                             git_merge_range_t * pRange = APR_ARRAY_IDX(arr, i, git_merge_range_t*);
1861                                                             if (pRange)
1862                                                             {
1863                                                                     for (git_revnum_t r=pRange->start+1; r<=pRange->end; ++r)
1864                                                                     {
1865                                                                             m_mergedRevs.insert(r);
1866                                                                     }
1867                                                             }
1868                                                     }
1869                                             }
1870                                             break;
1871                                     }
1872                             }
1873                     }
1874             }
1875     }
1876
1877     m_LogProgress.SetPos(1);
1878     if (m_startrev == GitRev::REV_HEAD)
1879     {
1880             m_startrev = r;
1881     }
1882     if (m_endrev == GitRev::REV_HEAD)
1883     {
1884             m_endrev = r;
1885     }
1886
1887     if (m_limit != 0)
1888     {
1889             m_limitcounter = m_limit;
1890             m_LogProgress.SetRange32(0, m_limit);
1891     }
1892     else
1893             m_LogProgress.SetRange32(m_endrev, m_startrev);
1894         
1895     if (!m_pegrev.IsValid())
1896             m_pegrev = m_startrev;
1897     size_t startcount = m_logEntries.size();
1898     m_lowestRev = -1;
1899     m_bStrictStopped = false;
1900
1901     if (succeeded)
1902     {
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()))
1905         {
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);
1909         }
1910     }
1911         m_LogList.ClearText();
1912     if (!succeeded)
1913         {
1914                 m_LogList.ShowText(GetLastErrorMessage(), true);
1915         }
1916         else
1917         {
1918                 if (!m_wcRev.IsValid())
1919                 {
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())
1923                                 revWCPath = m_path;
1924                         if (DWORD(CRegDWORD(_T("Software\\TortoiseGit\\RecursiveLogRev"), FALSE)))
1925                         {
1926                                 git_revnum_t minrev, maxrev;
1927                                 bool switched, modified, sparse;
1928                                 GetWCRevisionStatus(revWCPath, true, minrev, maxrev, switched, modified, sparse);
1929                                 if (maxrev)
1930                                         m_wcRev = maxrev;
1931                         }
1932                         else
1933                         {
1934                                 CTGitPath dummypath;
1935                                 GitStatus status;
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;
1941                         }
1942                 }
1943         }
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());
1947
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);
1954 #endif
1955         //DialogEnableWindow(IDC_GETALL, TRUE);
1956         FillGitLog();
1957         
1958         InterlockedExchange(&m_bThreadRunning, FALSE);
1959
1960         RedrawItems(0, m_arShownList.GetCount());
1961         SetRedraw(false);
1962         ResizeAllListCtrlCols();
1963         SetRedraw(true);
1964
1965         if ( m_pStoreSelection )
1966         {
1967                 // Deleting the instance will restore the
1968                 // selection of the CLogDlg.
1969                 delete m_pStoreSelection;
1970                 m_pStoreSelection = NULL;
1971         }
1972         else
1973         {
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 )
1977                 {
1978                         SetSelectionMark(0);
1979                         SetItemState(0, LVIS_SELECTED, LVIS_SELECTED);
1980                 }
1981         }
1982
1983         //RefreshCursor();
1984         // make sure the filter is applied (if any) now, after we refreshed/fetched
1985         // the log messages
1986
1987         InterlockedExchange(&m_bNoDispUpdates, FALSE);
1988
1989         if(m_ProcCallBack)
1990                 m_ProcCallBack(m_ProcData,GITLOG_END);
1991
1992         return 0;
1993 }
1994