1 // GitLogList.cpp : implementation file
4 Description: qgit revision list view
6 Author: Marco Costalba (C) 2005-2007
8 Copyright: See COPYING file that comes with this distribution
12 #include "TortoiseProc.h"
13 #include "GitLogList.h"
15 //#include "VssStyle.h"
21 #include "SVNProgressDlg.h"
22 #include "ProgressDlg.h"
23 //#include "RepositoryBrowser.h"
24 //#include "CopyDlg.h"
25 //#include "StatGraphDlg.h"
27 #include "MessageBox.h"
30 #include "PathUtils.h"
31 #include "StringUtils.h"
32 #include "UnicodeUtils.h"
34 //#include "GitInfo.h"
35 //#include "GitDiff.h"
37 //#include "RevisionRangeDlg.h"
38 //#include "BrowseFolder.h"
39 //#include "BlameDlg.h"
41 //#include "GitHelpers.h"
42 #include "GitStatus.h"
43 //#include "LogDlgHelper.h"
44 //#include "CachedLogInfo.h"
45 //#include "RepositoryInfo.h"
46 //#include "EditPropertiesDlg.h"
47 #include "FileDiffDlg.h"
48 #include "CommitDlg.h"
49 #include "RebaseDlg.h"
51 IMPLEMENT_DYNAMIC(CGitLogList
, CHintListCtrl
)
53 void CGitLogList::ContextMenuAction(int cmd
,int FirstSelect
, int LastSelect
)
55 POSITION pos
= GetFirstSelectedItemPosition();
56 int indexNext
= GetNextSelectedItem(pos
);
60 GitRev
* pSelLogEntry
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(indexNext
));
62 theApp
.DoWaitCursor(1);
63 bool bOpenWith
= false;
68 CString tempfile
=GetTempFile();
70 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
71 cmd
.Format(_T("git.exe diff-tree -r -p --stat %s"),r1
->m_CommitHash
);
72 g_Git
.RunLogFile(cmd
,tempfile
);
73 CAppUtils::StartUnifiedDiffViewer(tempfile
,r1
->m_CommitHash
.Left(6)+_T(":")+r1
->m_Subject
);
79 CString tempfile
=GetTempFile();
81 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
82 GitRev
* r2
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
83 cmd
.Format(_T("git.exe diff-tree -r -p --stat %s %s"),r1
->m_CommitHash
,r2
->m_CommitHash
);
84 g_Git
.RunLogFile(cmd
,tempfile
);
85 CAppUtils::StartUnifiedDiffViewer(tempfile
,r1
->m_CommitHash
.Left(6)+_T(":")+r2
->m_CommitHash
.Left(6));
92 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
93 GitRev
* r2
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
95 dlg
.SetDiff(NULL
,*r1
,*r2
);
104 GitRev
* r1
= &m_wcRev
;
105 GitRev
* r2
= pSelLogEntry
;
107 dlg
.SetDiff(NULL
,*r1
,*r2
);
110 //user clicked on the menu item "compare with working copy"
113 // GitDiff diff(this, m_hWnd, true);
114 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
115 // diff.SetHEADPeg(m_LogRevision);
116 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
119 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
123 case ID_COMPAREWITHPREVIOUS
:
128 if(pSelLogEntry
->m_ParentHash
.size()>0)
129 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
131 dlg
.SetDiff(NULL
,pSelLogEntry
->m_CommitHash
,pSelLogEntry
->m_ParentHash
[0]);
135 CMessageBox::Show(NULL
,_T("No previous version"),_T("TortoiseGit"),MB_OK
);
139 // GitDiff diff(this, m_hWnd, true);
140 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
141 // diff.SetHEADPeg(m_LogRevision);
142 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
145 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
148 case ID_COPYCLIPBOARD
:
150 CopySelectionToClipBoard();
155 CopySelectionToClipBoard(TRUE
);
159 CAppUtils::Export(&pSelLogEntry
->m_CommitHash
);
161 case ID_CREATE_BRANCH
:
162 CAppUtils::CreateBranchTag(FALSE
,&pSelLogEntry
->m_CommitHash
);
167 CAppUtils::CreateBranchTag(TRUE
,&pSelLogEntry
->m_CommitHash
);
172 CAppUtils::Switch(&pSelLogEntry
->m_CommitHash
);
177 CAppUtils::GitReset(&pSelLogEntry
->m_CommitHash
);
182 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_PICK
);
185 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_EDIT
);
187 case ID_REBASE_SQUASH
:
188 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_SQUASH
);
191 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_SKIP
);
193 case ID_COMBINE_COMMIT
:
198 head
.Format(_T("HEAD~%d"),LastSelect
);
199 CString hash
=g_Git
.GetHash(head
);
202 headhash
=g_Git
.GetHash(CString(_T("HEAD")));
203 headhash
=headhash
.Left(40);
205 GitRev
* pLastEntry
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
206 if(pLastEntry
->m_CommitHash
!= hash
)
208 CMessageBox::Show(NULL
,_T("Only combine top continuous commit"),_T("TortoiseGit"),MB_OK
);
210 if(!g_Git
.CheckCleanWorkTree())
212 CMessageBox::Show(NULL
,_T("Combine need clean work tree"),_T("TortoiseGit"),MB_OK
);
217 cmd
.Format(_T("git.exe reset --mixed %s"),hash
);
218 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
220 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
223 for(int i
=FirstSelect
;i
<=LastSelect
;i
++)
225 GitRev
* pRev
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(i
));
226 dlg
.m_sLogMessage
+=pRev
->m_Subject
+_T("\n")+pRev
->m_Body
;
227 dlg
.m_sLogMessage
+=_T("\n");
229 dlg
.m_bWholeProject
=true;
230 dlg
.m_bSelectFilesForCommit
= true;
231 dlg
.m_bCommitAmend
=true;
232 dlg
.m_AmendStr
=dlg
.m_sLogMessage
;
234 if (dlg
.DoModal() == IDOK
)
239 cmd
.Format(_T("git.exe reset --hard %s"),headhash
);
241 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
243 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
251 if(!g_Git
.CheckCleanWorkTree())
253 CMessageBox::Show(NULL
,_T("Cherry Pick Require Clean Working Tree"),_T("TortoiseGit"),MB_OK
);
258 dlg
.m_IsCherryPick
= TRUE
;
259 dlg
.m_Upstream
= this->m_CurrentBranch
;
260 POSITION pos
= GetFirstSelectedItemPosition();
263 int indexNext
= GetNextSelectedItem(pos
);
264 dlg
.m_CommitList
.m_logEntries
.push_back(*(GitRev
*)m_arShownList
[indexNext
]);
265 dlg
.m_CommitList
.m_logEntries
.at(dlg
.m_CommitList
.m_logEntries
.size()-1).m_Action
|= CTGitPath::LOGACTIONS_REBASE_PICK
;
268 if(dlg
.DoModal() == IDOK
)
274 case ID_REBASE_TO_VERSION
:
275 if(!g_Git
.CheckCleanWorkTree())
277 CMessageBox::Show(NULL
,_T("Rebase Require Clean Working Tree"),_T("TortoiseGit"),MB_OK
);
282 dlg
.m_Upstream
= pSelLogEntry
->m_CommitHash
;
284 if(dlg
.DoModal() == IDOK
)
293 //CMessageBox::Show(NULL,_T("Have not implemented"),_T("TortoiseGit"),MB_OK);
299 // we need an URL to complete this command, so error out if we can't get an URL
300 if (pathURL
.IsEmpty())
303 strMessage
.Format(IDS_ERR_NOURLOFFILE
, (LPCTSTR
)(m_path
.GetUIPathString()));
304 CMessageBox::Show(this->m_hWnd
, strMessage
, _T("TortoiseGit"), MB_ICONERROR
);
305 TRACE(_T("could not retrieve the URL of the folder!\n"));
309 msg
.Format(IDS_LOG_REVERT_CONFIRM
, m_path
.GetWinPath());
310 if (CMessageBox::Show(this->m_hWnd
, msg
, _T("TortoiseGit"), MB_YESNO
| MB_ICONQUESTION
) == IDYES
)
313 dlg
.SetCommand(CGitProgressDlg::GitProgress_Merge
);
314 dlg
.SetPathList(CTGitPathList(m_path
));
316 dlg
.SetSecondUrl(pathURL
);
317 revisionRanges
.AdjustForMerge(true);
318 dlg
.SetRevisionRanges(revisionRanges
);
319 dlg
.SetPegRevision(m_LogRevision
);
326 // we need an URL to complete this command, so error out if we can't get an URL
327 if (pathURL
.IsEmpty())
330 strMessage
.Format(IDS_ERR_NOURLOFFILE
, (LPCTSTR
)(m_path
.GetUIPathString()));
331 CMessageBox::Show(this->m_hWnd
, strMessage
, _T("TortoiseGit"), MB_ICONERROR
);
332 TRACE(_T("could not retrieve the URL of the folder!\n"));
336 CString path
= m_path
.GetWinPathString();
337 bool bGotSavePath
= false;
338 if ((GetSelectedCount() == 1)&&(!m_path
.IsDirectory()))
340 bGotSavePath
= CAppUtils::FileOpenSave(path
, NULL
, IDS_LOG_MERGETO
, IDS_COMMONFILEFILTER
, true, GetSafeHwnd());
344 CBrowseFolder folderBrowser
;
345 folderBrowser
.SetInfo(CString(MAKEINTRESOURCE(IDS_LOG_MERGETO
)));
346 bGotSavePath
= (folderBrowser
.Show(GetSafeHwnd(), path
, path
) == CBrowseFolder::OK
);
351 dlg
.SetCommand(CGitProgressDlg::GitProgress_Merge
);
352 dlg
.SetPathList(CTGitPathList(CTGitPath(path
)));
354 dlg
.SetSecondUrl(pathURL
);
355 revisionRanges
.AdjustForMerge(false);
356 dlg
.SetRevisionRanges(revisionRanges
);
357 dlg
.SetPegRevision(m_LogRevision
);
364 // we need an URL to complete this command, so error out if we can't get an URL
365 if (pathURL
.IsEmpty())
368 strMessage
.Format(IDS_ERR_NOURLOFFILE
, (LPCTSTR
)(m_path
.GetUIPathString()));
369 CMessageBox::Show(this->m_hWnd
, strMessage
, _T("TortoiseGit"), MB_ICONERROR
);
370 TRACE(_T("could not retrieve the URL of the folder!\n"));
375 msg
.Format(IDS_LOG_REVERTTOREV_CONFIRM
, m_path
.GetWinPath());
376 if (CMessageBox::Show(this->m_hWnd
, msg
, _T("TortoiseGit"), MB_YESNO
| MB_ICONQUESTION
) == IDYES
)
379 dlg
.SetCommand(CGitProgressDlg::GitProgress_Merge
);
380 dlg
.SetPathList(CTGitPathList(m_path
));
382 dlg
.SetSecondUrl(pathURL
);
383 GitRevRangeArray revarray
;
384 revarray
.AddRevRange(GitRev::REV_HEAD
, revSelected
);
385 dlg
.SetRevisionRanges(revarray
);
386 dlg
.SetPegRevision(m_LogRevision
);
394 case ID_BLAMECOMPARE
:
396 //user clicked on the menu item "compare with working copy"
397 //now first get the revision which is selected
400 GitDiff
diff(this, this->m_hWnd
, true);
401 diff
.SetHEADPeg(m_LogRevision
);
402 diff
.ShowCompare(m_path
, GitRev::REV_BASE
, m_path
, revSelected
, GitRev(), false, true);
405 CAppUtils::StartShowCompare(m_hWnd
, m_path
, GitRev::REV_BASE
, m_path
, revSelected
, GitRev(), m_LogRevision
, false, false, true);
410 //user clicked on the menu item "compare and blame revisions"
413 GitDiff
diff(this, this->m_hWnd
, true);
414 diff
.SetHEADPeg(m_LogRevision
);
415 diff
.ShowCompare(CTGitPath(pathURL
), revSelected2
, CTGitPath(pathURL
), revSelected
, GitRev(), false, true);
418 CAppUtils::StartShowCompare(m_hWnd
, CTGitPath(pathURL
), revSelected2
, CTGitPath(pathURL
), revSelected
, GitRev(), m_LogRevision
, false, false, true);
421 case ID_BLAMEWITHPREVIOUS
:
423 //user clicked on the menu item "Compare and Blame with previous revision"
426 GitDiff
diff(this, this->m_hWnd
, true);
427 diff
.SetHEADPeg(m_LogRevision
);
428 diff
.ShowCompare(CTGitPath(pathURL
), revPrevious
, CTGitPath(pathURL
), revSelected
, GitRev(), false, true);
431 CAppUtils::StartShowCompare(m_hWnd
, CTGitPath(pathURL
), revPrevious
, CTGitPath(pathURL
), revSelected
, GitRev(), m_LogRevision
, false, false, true);
439 CProgressDlg progDlg
;
440 progDlg
.SetTitle(IDS_APPNAME
);
441 progDlg
.SetAnimation(IDR_DOWNLOAD
);
443 sInfoLine
.Format(IDS_PROGRESSGETFILEREVISION
, m_path
.GetWinPath(), (LPCTSTR
)revSelected
.ToString());
444 progDlg
.SetLine(1, sInfoLine
, true);
445 SetAndClearProgressInfo(&progDlg
);
446 progDlg
.ShowModeless(m_hWnd
);
447 CTGitPath tempfile
= CTempFiles::Instance().GetTempFilePath(false, m_path
, revSelected
);
448 bool bSuccess
= true;
449 if (!Cat(m_path
, GitRev(GitRev::REV_HEAD
), revSelected
, tempfile
))
452 // try again, but with the selected revision as the peg revision
453 if (!Cat(m_path
, revSelected
, revSelected
, tempfile
))
456 SetAndClearProgressInfo((HWND
)NULL
);
457 CMessageBox::Show(this->m_hWnd
, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR
);
466 SetAndClearProgressInfo((HWND
)NULL
);
467 SetFileAttributes(tempfile
.GetWinPath(), FILE_ATTRIBUTE_READONLY
);
470 ret
= (int)ShellExecute(this->m_hWnd
, NULL
, tempfile
.GetWinPath(), NULL
, NULL
, SW_SHOWNORMAL
);
471 if ((ret
<= HINSTANCE_ERROR
)||bOpenWith
)
473 CString cmd
= _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
474 cmd
+= tempfile
.GetWinPathString() + _T(" ");
475 CAppUtils::LaunchApplication(cmd
, NULL
, false);
483 dlg
.EndRev
= revSelected
;
484 if (dlg
.DoModal() == IDOK
)
489 tempfile
= blame
.BlameToTempFile(m_path
, dlg
.StartRev
, dlg
.EndRev
, dlg
.EndRev
, logfile
, _T(""), dlg
.m_bIncludeMerge
, TRUE
, TRUE
);
490 if (!tempfile
.IsEmpty())
494 //open the default text editor for the result file
495 CAppUtils::StartTextViewer(tempfile
);
499 CString sParams
= _T("/path:\"") + m_path
.GetGitPathString() + _T("\" ");
500 if(!CAppUtils::LaunchTortoiseBlame(tempfile
, logfile
, CPathUtils::GetFileNameFromPath(m_path
.GetFileOrDirectoryName()),sParams
))
508 CMessageBox::Show(this->m_hWnd
, blame
.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR
);
516 CString url
= _T("tgit:")+pathURL
;
517 sCmd
.Format(_T("%s /command:update /path:\"%s\" /rev:%ld"),
518 (LPCTSTR
)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
519 (LPCTSTR
)m_path
.GetWinPath(), (LONG
)revSelected
);
520 CAppUtils::LaunchApplication(sCmd
, NULL
, false);
525 m_nSearchIndex
= GetSelectionMark();
526 if (m_nSearchIndex
< 0)
534 m_pFindDialog
= new CFindReplaceDialog();
535 m_pFindDialog
->Create(TRUE
, NULL
, NULL
, FR_HIDEUPDOWN
| FR_HIDEWHOLEWORD
, this);
542 sCmd
.Format(_T("%s /command:repobrowser /path:\"%s\" /rev:%s"),
543 (LPCTSTR
)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
544 (LPCTSTR
)pathURL
, (LPCTSTR
)revSelected
.ToString());
546 CAppUtils::LaunchApplication(sCmd
, NULL
, false);
551 EditLogMessage(selIndex
);
556 EditAuthor(selEntries
);
561 CEditPropertiesDlg dlg
;
562 dlg
.SetProjectProperties(&m_ProjectProperties
);
563 CTGitPathList escapedlist
;
564 dlg
.SetPathList(CTGitPathList(CTGitPath(pathURL
)));
565 dlg
.SetRevision(revSelected
);
574 sCmd
.Format(_T("%s /command:export /path:\"%s\" /revision:%ld"),
575 (LPCTSTR
)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
576 (LPCTSTR
)pathURL
, (LONG
)revSelected
);
577 CAppUtils::LaunchApplication(sCmd
, NULL
, false);
583 CString url
= _T("tgit:")+pathURL
;
584 sCmd
.Format(_T("%s /command:checkout /url:\"%s\" /revision:%ld"),
585 (LPCTSTR
)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
586 (LPCTSTR
)url
, (LONG
)revSelected
);
587 CAppUtils::LaunchApplication(sCmd
, NULL
, false);
592 CString url
= m_ProjectProperties
.sWebViewerRev
;
593 url
= GetAbsoluteUrlFromRelativeUrl(url
);
594 url
.Replace(_T("%REVISION%"), revSelected
.ToString());
596 ShellExecute(this->m_hWnd
, _T("open"), url
, NULL
, NULL
, SW_SHOWDEFAULT
);
601 CString relurl
= pathURL
;
602 CString sRoot
= GetRepositoryRoot(CTGitPath(relurl
));
603 relurl
= relurl
.Mid(sRoot
.GetLength());
604 CString url
= m_ProjectProperties
.sWebViewerPathRev
;
605 url
= GetAbsoluteUrlFromRelativeUrl(url
);
606 url
.Replace(_T("%REVISION%"), revSelected
.ToString());
607 url
.Replace(_T("%PATH%"), relurl
);
609 ShellExecute(this->m_hWnd
, _T("open"), url
, NULL
, NULL
, SW_SHOWDEFAULT
);
616 theApp
.DoWaitCursor(-1);
619 void CGitLogList::SetSelectedAction(int action
)
621 POSITION pos
= GetFirstSelectedItemPosition();
625 index
= GetNextSelectedItem(pos
);
626 ((GitRev
*)m_arShownList
[index
])->m_Action
= action
;
628 this->GetItemRect(index
,&rect
,LVIR_BOUNDS
);
629 this->InvalidateRect(rect
);