1 // GitLogList.cpp : implementation file
\r
4 Description: qgit revision list view
\r
6 Author: Marco Costalba (C) 2005-2007
\r
8 Copyright: See COPYING file that comes with this distribution
\r
12 #include "TortoiseProc.h"
\r
13 #include "GitLogList.h"
\r
15 //#include "VssStyle.h"
\r
16 #include "IconMenu.h"
\r
19 #include "InputDlg.h"
\r
20 #include "PropDlg.h"
\r
21 #include "SVNProgressDlg.h"
\r
22 #include "ProgressDlg.h"
\r
23 //#include "RepositoryBrowser.h"
\r
24 //#include "CopyDlg.h"
\r
25 //#include "StatGraphDlg.h"
\r
27 #include "MessageBox.h"
\r
28 #include "Registry.h"
\r
29 #include "AppUtils.h"
\r
30 #include "PathUtils.h"
\r
31 #include "StringUtils.h"
\r
32 #include "UnicodeUtils.h"
\r
33 #include "TempFile.h"
\r
34 //#include "GitInfo.h"
\r
35 //#include "GitDiff.h"
\r
36 #include "IconMenu.h"
\r
37 //#include "RevisionRangeDlg.h"
\r
38 //#include "BrowseFolder.h"
\r
39 //#include "BlameDlg.h"
\r
40 //#include "Blame.h"
\r
41 //#include "GitHelpers.h"
\r
42 #include "GitStatus.h"
\r
43 //#include "LogDlgHelper.h"
\r
44 //#include "CachedLogInfo.h"
\r
45 //#include "RepositoryInfo.h"
\r
46 //#include "EditPropertiesDlg.h"
\r
47 #include "FileDiffDlg.h"
\r
48 #include "CommitDlg.h"
\r
50 IMPLEMENT_DYNAMIC(CGitLogList, CHintListCtrl)
\r
52 void CGitLogList::ContextMenuAction(int cmd,int FirstSelect, int LastSelect)
\r
54 POSITION pos = GetFirstSelectedItemPosition();
\r
55 int indexNext = GetNextSelectedItem(pos);
\r
59 GitRev* pSelLogEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(indexNext));
\r
61 theApp.DoWaitCursor(1);
\r
62 bool bOpenWith = false;
\r
67 CString tempfile=GetTempFile();
\r
69 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
\r
70 cmd.Format(_T("git.exe diff-tree -r -p --stat %s"),r1->m_CommitHash);
\r
71 g_Git.RunLogFile(cmd,tempfile);
\r
72 CAppUtils::StartUnifiedDiffViewer(tempfile,r1->m_CommitHash.Left(6)+_T(":")+r1->m_Subject);
\r
78 CString tempfile=GetTempFile();
\r
80 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
\r
81 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
\r
82 cmd.Format(_T("git.exe diff-tree -r -p --stat %s %s"),r1->m_CommitHash,r2->m_CommitHash);
\r
83 g_Git.RunLogFile(cmd,tempfile);
\r
84 CAppUtils::StartUnifiedDiffViewer(tempfile,r1->m_CommitHash.Left(6)+_T(":")+r2->m_CommitHash.Left(6));
\r
91 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
\r
92 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
\r
94 dlg.SetDiff(NULL,*r1,*r2);
\r
103 GitRev * r1 = &m_wcRev;
\r
104 GitRev * r2 = pSelLogEntry;
\r
106 dlg.SetDiff(NULL,*r1,*r2);
\r
109 //user clicked on the menu item "compare with working copy"
\r
110 //if (PromptShown())
\r
112 // GitDiff diff(this, m_hWnd, true);
\r
113 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
\r
114 // diff.SetHEADPeg(m_LogRevision);
\r
115 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
\r
118 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
\r
122 case ID_COMPAREWITHPREVIOUS:
\r
127 if(pSelLogEntry->m_ParentHash.size()>0)
\r
128 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
\r
130 dlg.SetDiff(NULL,pSelLogEntry->m_CommitHash,pSelLogEntry->m_ParentHash[0]);
\r
134 CMessageBox::Show(NULL,_T("No previous version"),_T("TortoiseGit"),MB_OK);
\r
136 //if (PromptShown())
\r
138 // GitDiff diff(this, m_hWnd, true);
\r
139 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
\r
140 // diff.SetHEADPeg(m_LogRevision);
\r
141 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
\r
144 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
\r
147 case ID_COPYCLIPBOARD:
\r
149 CopySelectionToClipBoard();
\r
154 CopySelectionToClipBoard(TRUE);
\r
158 CAppUtils::Export(&pSelLogEntry->m_CommitHash);
\r
160 case ID_CREATE_BRANCH:
\r
161 CAppUtils::CreateBranchTag(FALSE,&pSelLogEntry->m_CommitHash);
\r
165 case ID_CREATE_TAG:
\r
166 CAppUtils::CreateBranchTag(TRUE,&pSelLogEntry->m_CommitHash);
\r
170 case ID_SWITCHTOREV:
\r
171 CAppUtils::Switch(&pSelLogEntry->m_CommitHash);
\r
176 CAppUtils::GitReset(&pSelLogEntry->m_CommitHash);
\r
180 case ID_REBASE_PICK:
\r
181 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_PICK);
\r
183 case ID_REBASE_EDIT:
\r
184 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_EDIT);
\r
186 case ID_REBASE_SQUASH:
\r
187 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_SQUASH);
\r
189 case ID_REBASE_SKIP:
\r
190 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_SKIP);
\r
192 case ID_COMBINE_COMMIT:
\r
197 head.Format(_T("HEAD~%d"),LastSelect);
\r
198 CString hash=g_Git.GetHash(head);
\r
199 hash=hash.Left(40);
\r
201 headhash=g_Git.GetHash(CString(_T("HEAD")));
\r
202 headhash=headhash.Left(40);
\r
204 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
\r
205 if(pLastEntry->m_CommitHash != hash)
\r
207 CMessageBox::Show(NULL,_T("Only combine top continuous commit"),_T("TortoiseGit"),MB_OK);
\r
209 if(!g_Git.CheckCleanWorkTree())
\r
211 CMessageBox::Show(NULL,_T("Combine need clean work tree"),_T("TortoiseGit"),MB_OK);
\r
216 cmd.Format(_T("git.exe reset --mixed %s"),hash);
\r
217 if(g_Git.Run(cmd,&out,CP_UTF8))
\r
219 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
\r
222 for(int i=FirstSelect;i<=LastSelect;i++)
\r
224 GitRev* pRev = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
\r
225 dlg.m_sLogMessage+=pRev->m_Subject+_T("\n")+pRev->m_Body;
\r
226 dlg.m_sLogMessage+=_T("\n");
\r
228 dlg.m_bWholeProject=true;
\r
229 dlg.m_bSelectFilesForCommit = true;
\r
230 dlg.m_bCommitAmend=true;
\r
231 dlg.m_AmendStr=dlg.m_sLogMessage;
\r
233 if (dlg.DoModal() == IDOK)
\r
238 cmd.Format(_T("git.exe reset --hard %s"),headhash);
\r
240 if(g_Git.Run(cmd,&out,CP_UTF8))
\r
242 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
\r
249 //CMessageBox::Show(NULL,_T("Have not implemented"),_T("TortoiseGit"),MB_OK);
\r
255 // we need an URL to complete this command, so error out if we can't get an URL
\r
256 if (pathURL.IsEmpty())
\r
258 CString strMessage;
\r
259 strMessage.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)(m_path.GetUIPathString()));
\r
260 CMessageBox::Show(this->m_hWnd, strMessage, _T("TortoiseGit"), MB_ICONERROR);
\r
261 TRACE(_T("could not retrieve the URL of the folder!\n"));
\r
265 msg.Format(IDS_LOG_REVERT_CONFIRM, m_path.GetWinPath());
\r
266 if (CMessageBox::Show(this->m_hWnd, msg, _T("TortoiseGit"), MB_YESNO | MB_ICONQUESTION) == IDYES)
\r
268 CGitProgressDlg dlg;
\r
269 dlg.SetCommand(CGitProgressDlg::GitProgress_Merge);
\r
270 dlg.SetPathList(CTGitPathList(m_path));
\r
271 dlg.SetUrl(pathURL);
\r
272 dlg.SetSecondUrl(pathURL);
\r
273 revisionRanges.AdjustForMerge(true);
\r
274 dlg.SetRevisionRanges(revisionRanges);
\r
275 dlg.SetPegRevision(m_LogRevision);
\r
282 // we need an URL to complete this command, so error out if we can't get an URL
\r
283 if (pathURL.IsEmpty())
\r
285 CString strMessage;
\r
286 strMessage.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)(m_path.GetUIPathString()));
\r
287 CMessageBox::Show(this->m_hWnd, strMessage, _T("TortoiseGit"), MB_ICONERROR);
\r
288 TRACE(_T("could not retrieve the URL of the folder!\n"));
\r
292 CString path = m_path.GetWinPathString();
\r
293 bool bGotSavePath = false;
\r
294 if ((GetSelectedCount() == 1)&&(!m_path.IsDirectory()))
\r
296 bGotSavePath = CAppUtils::FileOpenSave(path, NULL, IDS_LOG_MERGETO, IDS_COMMONFILEFILTER, true, GetSafeHwnd());
\r
300 CBrowseFolder folderBrowser;
\r
301 folderBrowser.SetInfo(CString(MAKEINTRESOURCE(IDS_LOG_MERGETO)));
\r
302 bGotSavePath = (folderBrowser.Show(GetSafeHwnd(), path, path) == CBrowseFolder::OK);
\r
306 CGitProgressDlg dlg;
\r
307 dlg.SetCommand(CGitProgressDlg::GitProgress_Merge);
\r
308 dlg.SetPathList(CTGitPathList(CTGitPath(path)));
\r
309 dlg.SetUrl(pathURL);
\r
310 dlg.SetSecondUrl(pathURL);
\r
311 revisionRanges.AdjustForMerge(false);
\r
312 dlg.SetRevisionRanges(revisionRanges);
\r
313 dlg.SetPegRevision(m_LogRevision);
\r
318 case ID_REVERTTOREV:
\r
320 // we need an URL to complete this command, so error out if we can't get an URL
\r
321 if (pathURL.IsEmpty())
\r
323 CString strMessage;
\r
324 strMessage.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)(m_path.GetUIPathString()));
\r
325 CMessageBox::Show(this->m_hWnd, strMessage, _T("TortoiseGit"), MB_ICONERROR);
\r
326 TRACE(_T("could not retrieve the URL of the folder!\n"));
\r
331 msg.Format(IDS_LOG_REVERTTOREV_CONFIRM, m_path.GetWinPath());
\r
332 if (CMessageBox::Show(this->m_hWnd, msg, _T("TortoiseGit"), MB_YESNO | MB_ICONQUESTION) == IDYES)
\r
334 CGitProgressDlg dlg;
\r
335 dlg.SetCommand(CGitProgressDlg::GitProgress_Merge);
\r
336 dlg.SetPathList(CTGitPathList(m_path));
\r
337 dlg.SetUrl(pathURL);
\r
338 dlg.SetSecondUrl(pathURL);
\r
339 GitRevRangeArray revarray;
\r
340 revarray.AddRevRange(GitRev::REV_HEAD, revSelected);
\r
341 dlg.SetRevisionRanges(revarray);
\r
342 dlg.SetPegRevision(m_LogRevision);
\r
350 case ID_BLAMECOMPARE:
\r
352 //user clicked on the menu item "compare with working copy"
\r
353 //now first get the revision which is selected
\r
356 GitDiff diff(this, this->m_hWnd, true);
\r
357 diff.SetHEADPeg(m_LogRevision);
\r
358 diff.ShowCompare(m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), false, true);
\r
361 CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), m_LogRevision, false, false, true);
\r
366 //user clicked on the menu item "compare and blame revisions"
\r
369 GitDiff diff(this, this->m_hWnd, true);
\r
370 diff.SetHEADPeg(m_LogRevision);
\r
371 diff.ShowCompare(CTGitPath(pathURL), revSelected2, CTGitPath(pathURL), revSelected, GitRev(), false, true);
\r
374 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revSelected2, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
\r
377 case ID_BLAMEWITHPREVIOUS:
\r
379 //user clicked on the menu item "Compare and Blame with previous revision"
\r
382 GitDiff diff(this, this->m_hWnd, true);
\r
383 diff.SetHEADPeg(m_LogRevision);
\r
384 diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), false, true);
\r
387 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
\r
395 CProgressDlg progDlg;
\r
396 progDlg.SetTitle(IDS_APPNAME);
\r
397 progDlg.SetAnimation(IDR_DOWNLOAD);
\r
399 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, m_path.GetWinPath(), (LPCTSTR)revSelected.ToString());
\r
400 progDlg.SetLine(1, sInfoLine, true);
\r
401 SetAndClearProgressInfo(&progDlg);
\r
402 progDlg.ShowModeless(m_hWnd);
\r
403 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, m_path, revSelected);
\r
404 bool bSuccess = true;
\r
405 if (!Cat(m_path, GitRev(GitRev::REV_HEAD), revSelected, tempfile))
\r
408 // try again, but with the selected revision as the peg revision
\r
409 if (!Cat(m_path, revSelected, revSelected, tempfile))
\r
412 SetAndClearProgressInfo((HWND)NULL);
\r
413 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
\r
422 SetAndClearProgressInfo((HWND)NULL);
\r
423 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
\r
426 ret = (int)ShellExecute(this->m_hWnd, NULL, tempfile.GetWinPath(), NULL, NULL, SW_SHOWNORMAL);
\r
427 if ((ret <= HINSTANCE_ERROR)||bOpenWith)
\r
429 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
\r
430 cmd += tempfile.GetWinPathString() + _T(" ");
\r
431 CAppUtils::LaunchApplication(cmd, NULL, false);
\r
439 dlg.EndRev = revSelected;
\r
440 if (dlg.DoModal() == IDOK)
\r
445 tempfile = blame.BlameToTempFile(m_path, dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, _T(""), dlg.m_bIncludeMerge, TRUE, TRUE);
\r
446 if (!tempfile.IsEmpty())
\r
448 if (dlg.m_bTextView)
\r
450 //open the default text editor for the result file
\r
451 CAppUtils::StartTextViewer(tempfile);
\r
455 CString sParams = _T("/path:\"") + m_path.GetGitPathString() + _T("\" ");
\r
456 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(m_path.GetFileOrDirectoryName()),sParams))
\r
464 CMessageBox::Show(this->m_hWnd, blame.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
\r
472 CString url = _T("tgit:")+pathURL;
\r
473 sCmd.Format(_T("%s /command:update /path:\"%s\" /rev:%ld"),
\r
474 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
\r
475 (LPCTSTR)m_path.GetWinPath(), (LONG)revSelected);
\r
476 CAppUtils::LaunchApplication(sCmd, NULL, false);
\r
481 m_nSearchIndex = GetSelectionMark();
\r
482 if (m_nSearchIndex < 0)
\r
483 m_nSearchIndex = 0;
\r
490 m_pFindDialog = new CFindReplaceDialog();
\r
491 m_pFindDialog->Create(TRUE, NULL, NULL, FR_HIDEUPDOWN | FR_HIDEWHOLEWORD, this);
\r
495 case ID_REPOBROWSE:
\r
498 sCmd.Format(_T("%s /command:repobrowser /path:\"%s\" /rev:%s"),
\r
499 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
\r
500 (LPCTSTR)pathURL, (LPCTSTR)revSelected.ToString());
\r
502 CAppUtils::LaunchApplication(sCmd, NULL, false);
\r
507 EditLogMessage(selIndex);
\r
510 case ID_EDITAUTHOR:
\r
512 EditAuthor(selEntries);
\r
517 CEditPropertiesDlg dlg;
\r
518 dlg.SetProjectProperties(&m_ProjectProperties);
\r
519 CTGitPathList escapedlist;
\r
520 dlg.SetPathList(CTGitPathList(CTGitPath(pathURL)));
\r
521 dlg.SetRevision(revSelected);
\r
522 dlg.RevProps(true);
\r
530 sCmd.Format(_T("%s /command:export /path:\"%s\" /revision:%ld"),
\r
531 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
\r
532 (LPCTSTR)pathURL, (LONG)revSelected);
\r
533 CAppUtils::LaunchApplication(sCmd, NULL, false);
\r
539 CString url = _T("tgit:")+pathURL;
\r
540 sCmd.Format(_T("%s /command:checkout /url:\"%s\" /revision:%ld"),
\r
541 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
\r
542 (LPCTSTR)url, (LONG)revSelected);
\r
543 CAppUtils::LaunchApplication(sCmd, NULL, false);
\r
548 CString url = m_ProjectProperties.sWebViewerRev;
\r
549 url = GetAbsoluteUrlFromRelativeUrl(url);
\r
550 url.Replace(_T("%REVISION%"), revSelected.ToString());
\r
551 if (!url.IsEmpty())
\r
552 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
\r
555 case ID_VIEWPATHREV:
\r
557 CString relurl = pathURL;
\r
558 CString sRoot = GetRepositoryRoot(CTGitPath(relurl));
\r
559 relurl = relurl.Mid(sRoot.GetLength());
\r
560 CString url = m_ProjectProperties.sWebViewerPathRev;
\r
561 url = GetAbsoluteUrlFromRelativeUrl(url);
\r
562 url.Replace(_T("%REVISION%"), revSelected.ToString());
\r
563 url.Replace(_T("%PATH%"), relurl);
\r
564 if (!url.IsEmpty())
\r
565 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
\r
572 theApp.DoWaitCursor(-1);
\r
575 void CGitLogList::SetSelectedAction(int action)
\r
577 POSITION pos = GetFirstSelectedItemPosition();
\r
581 index = GetNextSelectedItem(pos);
\r
582 ((GitRev*)m_arShownList[index])->m_Action = action;
\r
584 this->GetItemRect(index,&rect,LVIR_BOUNDS);
\r
585 this->InvalidateRect(rect);
\r