No need to beep after displaying Commit Not Visible message
[TortoiseGit.git] / src / TortoiseProc / GitLogListAction.cpp
blob0efe3ed26799dfb3f84639a23ed3689b99e5f71f
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2014 - TortoiseGit
4 // Copyright (C) 2011-2013 - Sven Strickroth <email@cs-ware.de>
5 // Copyright (C) 2005-2007 Marco Costalba
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 2
10 // of the License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software Foundation,
19 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 // GitLogList.cpp : implementation file
23 #include "stdafx.h"
24 #include "TortoiseProc.h"
25 #include "GitLogList.h"
26 #include "GitRev.h"
27 #include "IconMenu.h"
28 #include "cursor.h"
29 #include "GitProgressDlg.h"
30 #include "ProgressDlg.h"
31 #include "SysProgressDlg.h"
32 #include "LogDlg.h"
33 #include "MessageBox.h"
34 #include "registry.h"
35 #include "AppUtils.h"
36 #include "PathUtils.h"
37 #include "StringUtils.h"
38 #include "UnicodeUtils.h"
39 #include "TempFile.h"
40 #include "FileDiffDlg.h"
41 #include "CommitDlg.h"
42 #include "RebaseDlg.h"
43 #include "GitDiff.h"
44 #include "../TGitCache/CacheInterface.h"
46 IMPLEMENT_DYNAMIC(CGitLogList, CHintListCtrl)
48 int CGitLogList::RevertSelectedCommits(int parent)
50 CSysProgressDlg progress;
51 int ret = -1;
53 #if 0
54 if(!g_Git.CheckCleanWorkTree())
56 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
59 #endif
61 if (this->GetSelectedCount() > 1)
63 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_REVERTCOMMIT)));
64 progress.SetAnimation(IDR_MOVEANI);
65 progress.SetTime(true);
66 progress.ShowModeless(this);
69 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
71 POSITION pos = GetFirstSelectedItemPosition();
72 int i=0;
73 while(pos)
75 int index = GetNextSelectedItem(pos);
76 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(index));
78 if (progress.IsVisible())
80 progress.FormatNonPathLine(1, IDS_PROC_REVERTCOMMIT, r1->m_CommitHash.ToString());
81 progress.FormatNonPathLine(2, _T("%s"), r1->GetSubject());
82 progress.SetProgress(i, this->GetSelectedCount());
84 ++i;
86 if(r1->m_CommitHash.IsEmpty())
87 continue;
89 if (g_Git.GitRevert(parent, r1->m_CommitHash))
91 CString str;
92 str.LoadString(IDS_SVNACTION_FAILEDREVERT);
93 str = g_Git.GetGitLastErr(str, CGit::GIT_CMD_REVERT);
94 if( GetSelectedCount() == 1)
95 CMessageBox::Show(NULL, str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
96 else
98 if(CMessageBox::Show(NULL, str, _T("TortoiseGit"),2 , IDI_ERROR, CString(MAKEINTRESOURCE(IDS_SKIPBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
100 return ret;
104 else
106 ret =0;
109 if (progress.HasUserCancelled())
110 break;
112 return ret;
114 int CGitLogList::CherryPickFrom(CString from, CString to)
116 CLogDataVector logs(&m_LogCache);
117 CString range;
118 range.Format(_T("%s..%s"), from, to);
119 if (logs.ParserFromLog(nullptr, -1, 0, &range))
120 return -1;
122 if (logs.empty())
123 return 0;
125 CSysProgressDlg progress;
126 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_CHERRYPICK)));
127 progress.SetAnimation(IDR_MOVEANI);
128 progress.SetTime(true);
129 progress.ShowModeless(this);
131 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
133 for (int i = (int)logs.size() - 1; i >= 0; i--)
135 if (progress.IsVisible())
137 progress.FormatNonPathLine(1, IDS_PROC_PICK, logs.GetGitRevAt(i).m_CommitHash.ToString());
138 progress.FormatNonPathLine(2, _T("%s"), logs.GetGitRevAt(i).GetSubject());
139 progress.SetProgress64(logs.size() - i, logs.size());
141 if (progress.HasUserCancelled())
143 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED))));
145 CString cmd,out;
146 cmd.Format(_T("git.exe cherry-pick %s"),logs.GetGitRevAt(i).m_CommitHash.ToString());
147 out.Empty();
148 if(g_Git.Run(cmd,&out,CP_UTF8))
150 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_CHERRYPICKFAILED)) + _T(":\r\n\r\n") + out));
154 return 0;
157 void CGitLogList::ContextMenuAction(int cmd,int FirstSelect, int LastSelect, CMenu *popmenu)
159 POSITION pos = GetFirstSelectedItemPosition();
160 int indexNext = GetNextSelectedItem(pos);
161 if (indexNext < 0)
162 return;
164 GitRev* pSelLogEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(indexNext));
166 theApp.DoWaitCursor(1);
167 switch (cmd&0xFFFF)
169 case ID_COMMIT:
171 CTGitPathList pathlist;
172 CTGitPathList selectedlist;
173 pathlist.AddPath(this->m_Path);
174 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
175 CString str;
176 CAppUtils::Commit(CString(),false,str,
177 pathlist,selectedlist,bSelectFilesForCommit);
178 //this->Refresh();
179 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH,0);
181 break;
182 case ID_MERGE_ABORT:
184 if (CAppUtils::MergeAbort())
185 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH, 0);
187 break;
188 case ID_GNUDIFF1: // compare with WC, unified
190 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
191 bool bMerge = false, bCombine = false;
192 CString hash2;
193 if(!r1->m_CommitHash.IsEmpty())
195 CString merge;
196 cmd >>= 16;
197 if( (cmd&0xFFFF) == 0xFFFF)
198 bMerge = true;
200 else if((cmd&0xFFFF) == 0xFFFE)
201 bCombine = true;
202 else
204 if(cmd > r1->m_ParentHash.size())
206 CString str;
207 str.Format(IDS_PROC_NOPARENT, cmd);
208 MessageBox(str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
209 return;
211 else
213 if(cmd>0)
214 hash2 = r1->m_ParentHash[cmd-1].ToString();
217 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), hash2, CTGitPath(), r1->m_CommitHash.ToString(), false, false, false, bMerge, bCombine);
219 else
220 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), _T("HEAD"), CTGitPath(), GitRev::GetWorkingCopy(), false, false, false, bMerge, bCombine);
222 break;
224 case ID_GNUDIFF2: // compare two revisions, unified
226 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
227 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
228 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), r2->m_CommitHash.ToString(), CTGitPath(), r1->m_CommitHash.ToString());
230 break;
232 case ID_COMPARETWO: // compare two revisions
234 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
235 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
236 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
237 CGitDiff::DiffCommit(this->m_Path, r1,r2);
238 else
240 CString path1 = m_Path.GetGitPathString();
241 // start with 1 (0 = working copy changes)
242 for (int i = 1; i < FirstSelect; ++i)
244 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
245 CTGitPathList list = first->GetFiles(NULL);
246 CTGitPath * file = list.LookForGitPath(path1);
247 if (file && !file->GetGitOldPathString().IsEmpty())
248 path1 = file->GetGitOldPathString();
250 CString path2 = path1;
251 for (int i = FirstSelect; i < LastSelect; ++i)
253 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
254 CTGitPathList list = first->GetFiles(NULL);
255 CTGitPath * file = list.LookForGitPath(path2);
256 if (file && !file->GetGitOldPathString().IsEmpty())
257 path2 = file->GetGitOldPathString();
259 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), r1, r2);
263 break;
265 case ID_COMPARE: // compare revision with WC
267 GitRev * r1 = &m_wcRev;
268 GitRev * r2 = pSelLogEntry;
270 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
271 CGitDiff::DiffCommit(this->m_Path, r1,r2);
272 else
274 CString path1 = m_Path.GetGitPathString();
275 // start with 1 (0 = working copy changes)
276 for (int i = 1; i < FirstSelect; ++i)
278 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
279 CTGitPathList list = first->GetFiles(NULL);
280 CTGitPath * file = list.LookForGitPath(path1);
281 if (file && !file->GetGitOldPathString().IsEmpty())
282 path1 = file->GetGitOldPathString();
284 CGitDiff::DiffCommit(m_Path, CTGitPath(path1), r1, r2);
287 //user clicked on the menu item "compare with working copy"
288 //if (PromptShown())
290 // GitDiff diff(this, m_hWnd, true);
291 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
292 // diff.SetHEADPeg(m_LogRevision);
293 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
295 //else
296 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
298 break;
300 case ID_COMPAREWITHPREVIOUS:
302 CFileDiffDlg dlg;
304 if (pSelLogEntry->m_ParentHash.empty())
308 pSelLogEntry->GetParentFromHash(pSelLogEntry->m_CommitHash);
310 catch (const char* msg)
312 MessageBox(_T("Could not get parent.\nlibgit reports:\n") + CString(msg), _T("TortoiseGit"), MB_ICONERROR);
316 if (!pSelLogEntry->m_ParentHash.empty())
317 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
319 cmd>>=16;
320 cmd&=0xFFFF;
322 if(cmd == 0)
323 cmd=1;
325 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
326 CGitDiff::DiffCommit(m_Path, pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString());
327 else
329 CString path1 = m_Path.GetGitPathString();
330 // start with 1 (0 = working copy changes)
331 for (int i = 1; i < indexNext; ++i)
333 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
334 CTGitPathList list = first->GetFiles(NULL);
335 CTGitPath * file = list.LookForGitPath(path1);
336 if (file && !file->GetGitOldPathString().IsEmpty())
337 path1 = file->GetGitOldPathString();
339 CString path2 = path1;
340 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(indexNext));
341 CTGitPathList list = first->GetFiles(NULL);
342 CTGitPath * file = list.LookForGitPath(path2);
343 if (file && !file->GetGitOldPathString().IsEmpty())
344 path2 = file->GetGitOldPathString();
346 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString());
349 else
351 CMessageBox::Show(NULL, IDS_PROC_NOPREVIOUSVERSION, IDS_APPNAME, MB_OK);
353 //if (PromptShown())
355 // GitDiff diff(this, m_hWnd, true);
356 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
357 // diff.SetHEADPeg(m_LogRevision);
358 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
360 //else
361 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
363 break;
364 case ID_LOG_VIEWRANGE:
365 case ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE:
367 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(LastSelect));
369 CString sep = _T("..");
370 if ((cmd & 0xFFFF) == ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE)
371 sep = _T("...");
373 CString cmdline;
374 cmdline.Format(_T("/command:log /path:\"%s\" /range:\"%s%s%s\""),
375 g_Git.CombinePath(m_Path), pLastEntry->m_CommitHash.ToString(), sep, pSelLogEntry->m_CommitHash.ToString());
376 CAppUtils::RunTortoiseGitProc(cmdline);
378 break;
379 case ID_COPYCLIPBOARD:
381 CopySelectionToClipBoard();
383 break;
384 case ID_COPYCLIPBOARDMESSAGES:
386 if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0)
387 CopySelectionToClipBoard(ID_COPY_SUBJECT);
388 else
389 CopySelectionToClipBoard(ID_COPY_MESSAGE);
391 break;
392 case ID_COPYHASH:
394 CopySelectionToClipBoard(ID_COPY_HASH);
396 break;
397 case ID_EXPORT:
399 CString str=pSelLogEntry->m_CommitHash.ToString();
400 // try to get the tag
401 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
403 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/tags/")) == 0)
405 str = m_HashMap[pSelLogEntry->m_CommitHash][i];
406 break;
409 CAppUtils::Export(&str, &m_Path);
411 break;
412 case ID_CREATE_BRANCH:
413 case ID_CREATE_TAG:
415 CString str = pSelLogEntry->m_CommitHash.ToString();
416 // try to guess remote branch in order to enable tracking
417 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
419 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/remotes/")) == 0)
421 str = m_HashMap[pSelLogEntry->m_CommitHash][i];
422 break;
425 CAppUtils::CreateBranchTag((cmd&0xFFFF) == ID_CREATE_TAG, &str);
426 ReloadHashMap();
427 Invalidate();
428 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
430 break;
431 case ID_SWITCHTOREV:
433 CString str = pSelLogEntry->m_CommitHash.ToString();
434 // try to guess remote branch in order to recommend good branch name and tracking
435 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
437 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/remotes/")) == 0)
439 str = m_HashMap[pSelLogEntry->m_CommitHash][i];
440 break;
443 CAppUtils::Switch(str);
445 ReloadHashMap();
446 Invalidate();
447 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
448 break;
449 case ID_SWITCHBRANCH:
450 if(popmenu)
452 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
453 if(branch)
455 CString name;
456 if(branch->Find(_T("refs/heads/")) ==0 )
457 name = branch->Mid(11);
458 else
459 name = *branch;
461 CAppUtils::PerformSwitch(name);
463 ReloadHashMap();
464 Invalidate();
465 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
467 break;
468 case ID_RESET:
470 CString str = pSelLogEntry->m_CommitHash.ToString();
471 if (CAppUtils::GitReset(&str))
473 ResetWcRev(true);
474 ReloadHashMap();
475 Invalidate();
478 break;
479 case ID_REBASE_PICK:
480 SetSelectedRebaseAction(LOGACTIONS_REBASE_PICK);
481 break;
482 case ID_REBASE_EDIT:
483 SetSelectedRebaseAction(LOGACTIONS_REBASE_EDIT);
484 break;
485 case ID_REBASE_SQUASH:
486 SetSelectedRebaseAction(LOGACTIONS_REBASE_SQUASH);
487 break;
488 case ID_REBASE_SKIP:
489 SetSelectedRebaseAction(LOGACTIONS_REBASE_SKIP);
490 break;
491 case ID_COMBINE_COMMIT:
493 CString head;
494 CGitHash headhash;
495 CGitHash hashFirst,hashLast;
497 int headindex=GetHeadIndex();
498 if(headindex>=0) //incase show all branch, head is not the first commits.
500 head.Format(_T("HEAD~%d"),FirstSelect-headindex);
501 if (g_Git.GetHash(hashFirst, head))
503 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of first selected revision.")), _T("TortoiseGit"), MB_ICONERROR);
504 break;
507 head.Format(_T("HEAD~%d"),LastSelect-headindex);
508 if (g_Git.GetHash(hashLast, head))
510 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of last selected revision.")), _T("TortoiseGit"), MB_ICONERROR);
511 break;
515 GitRev* pFirstEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
516 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
517 if(pFirstEntry->m_CommitHash != hashFirst || pLastEntry->m_CommitHash != hashLast)
519 CMessageBox::Show(NULL, IDS_PROC_CANNOTCOMBINE, IDS_APPNAME, MB_OK);
520 break;
523 GitRev lastRevision;
526 lastRevision.GetParentFromHash(hashLast);
528 catch (char* msg)
530 CString err(msg);
531 MessageBox(_T("Could not get parent(s) of ") + hashLast.ToString() + _T(".\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
532 break;
535 if (g_Git.GetHash(headhash, _T("HEAD")))
537 MessageBox(g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
538 break;
541 if(!g_Git.CheckCleanWorkTree())
543 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
544 break;
546 CString cmd,out;
548 //Use throw to abort this process (reset back to original HEAD)
551 cmd.Format(_T("git.exe reset --hard %s --"), pFirstEntry->m_CommitHash.ToString());
552 if(g_Git.Run(cmd,&out,CP_UTF8))
554 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
555 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1)) + _T("\r\n\r\n") + out));
557 cmd.Format(_T("git.exe reset --mixed %s --"), hashLast.ToString());
558 if(g_Git.Run(cmd,&out,CP_UTF8))
560 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
561 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2)) + _T("\r\n\r\n")+out));
564 CTGitPathList PathList;
565 /* don't why must add --stat to get action status*/
566 /* first -z will be omitted by gitdll*/
567 if(g_Git.GetDiffPath(&PathList,&pFirstEntry->m_CommitHash,&hashLast,"-z --stat -r"))
569 CMessageBox::Show(NULL,_T("Get Diff file list error"),_T("TortoiseGit"),MB_OK);
570 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not get changed file list aborting...\r\n\r\n")+out));
573 for (int i = 0; i < PathList.GetCount(); ++i)
575 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_ADDED)
577 cmd.Format(_T("git.exe add -- \"%s\""), PathList[i].GetGitPathString());
578 if (g_Git.Run(cmd, &out, CP_UTF8))
580 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
581 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not add new file aborting...\r\n\r\n")+out));
585 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_DELETED)
587 cmd.Format(_T("git.exe rm -- \"%s\""), PathList[i].GetGitPathString());
588 if (g_Git.Run(cmd, &out, CP_UTF8))
590 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
591 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not rm file aborting...\r\n\r\n")+out));
596 CCommitDlg dlg;
597 for (int i = FirstSelect; i <= LastSelect; ++i)
599 GitRev* pRev = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
600 dlg.m_sLogMessage+=pRev->GetSubject()+_T("\n")+pRev->GetBody();
601 dlg.m_sLogMessage+=_T("\n");
603 dlg.m_bWholeProject=true;
604 dlg.m_bSelectFilesForCommit = true;
605 dlg.m_bForceCommitAmend=true;
606 CTGitPathList gpl;
607 gpl.AddPath(CTGitPath(g_Git.m_CurrentDir));
608 dlg.m_pathList = gpl;
609 if (lastRevision.ParentsCount() != 1)
611 CMessageBox::Show(NULL, _T("The following commit dialog can only show changes of oldest commit if it has exactly one parent. This is not the case right now."), _T("TortoiseGit"),MB_OK);
612 dlg.m_bAmendDiffToLastCommit = TRUE;
614 else
615 dlg.m_bAmendDiffToLastCommit = FALSE;
616 dlg.m_bNoPostActions=true;
617 dlg.m_AmendStr=dlg.m_sLogMessage;
619 if (dlg.DoModal() == IDOK)
621 if(pFirstEntry->m_CommitHash!=headhash)
623 if(CherryPickFrom(pFirstEntry->m_CommitHash.ToString(),headhash))
625 CString msg;
626 msg.Format(_T("Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n"));
627 throw std::exception(CUnicodeUtils::GetUTF8(msg));
631 else
632 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED))));
634 catch(std::exception& e)
636 CMessageBox::Show(NULL, CUnicodeUtils::GetUnicode(CStringA(e.what())), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
637 cmd.Format(_T("git.exe reset --hard %s --"), headhash.ToString());
638 out.Empty();
639 if(g_Git.Run(cmd,&out,CP_UTF8))
641 CMessageBox::Show(NULL, CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD)) + _T("\r\n\r\n") + out, _T("TortoiseGit"), MB_OK);
644 Refresh();
646 break;
648 case ID_CHERRY_PICK:
650 if (m_bThreadRunning)
652 MessageBox(_T("This operation cannot be started while the log dialog is still loading commits."), _T("TortoiseGit"), MB_ICONEXCLAMATION);
653 break;
655 CRebaseDlg dlg;
656 dlg.m_IsCherryPick = TRUE;
657 dlg.m_Upstream = this->m_CurrentBranch;
658 POSITION pos = GetFirstSelectedItemPosition();
659 while(pos)
661 int indexNext = GetNextSelectedItem(pos);
662 dlg.m_CommitList.m_logEntries.push_back( ((GitRev*)m_arShownList[indexNext])->m_CommitHash );
663 dlg.m_CommitList.m_LogCache.m_HashMap[((GitRev*)m_arShownList[indexNext])->m_CommitHash]=*(GitRev*)m_arShownList[indexNext];
664 dlg.m_CommitList.m_logEntries.GetGitRevAt(dlg.m_CommitList.m_logEntries.size() - 1).GetRebaseAction() |= LOGACTIONS_REBASE_PICK;
667 if(dlg.DoModal() == IDOK)
669 Refresh();
672 break;
673 case ID_REBASE_TO_VERSION:
675 if (m_bThreadRunning)
677 MessageBox(_T("This operation cannot be started while the log dialog is still loading commits."), _T("TortoiseGit"), MB_ICONEXCLAMATION);
678 break;
680 CRebaseDlg dlg;
681 auto refList = m_HashMap[pSelLogEntry->m_CommitHash];
682 dlg.m_Upstream = refList.empty() ? pSelLogEntry->m_CommitHash.ToString() : refList.front();
683 for (auto ref : refList)
685 if (ref.Left(11) == _T("refs/heads/"))
687 // 11=len("refs/heads/")
688 dlg.m_Upstream = ref.Mid(11);
689 break;
693 if(dlg.DoModal() == IDOK)
695 Refresh();
699 break;
701 case ID_STASH_SAVE:
702 if (CAppUtils::StashSave())
703 Refresh();
704 break;
706 case ID_STASH_POP:
707 if (CAppUtils::StashPop())
708 Refresh();
709 break;
711 case ID_STASH_LIST:
712 CAppUtils::RunTortoiseGitProc(_T("/command:reflog /ref:refs/stash"));
713 break;
715 case ID_REFLOG_STASH_APPLY:
716 CAppUtils::StashApply(pSelLogEntry->m_Ref);
717 break;
719 case ID_REFLOG_DEL:
721 CString str;
722 if (GetSelectedCount() > 1)
723 str.Format(IDS_PROC_DELETENREFS, GetSelectedCount());
724 else
725 str.Format(IDS_PROC_DELETEREF, pSelLogEntry->m_Ref);
727 if (CMessageBox::Show(NULL, str, _T("TortoiseGit"), 1, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
728 return;
730 std::vector<CString> refsToDelete;
731 POSITION pos = GetFirstSelectedItemPosition();
732 while (pos)
734 CString ref = ((GitRev *)m_arShownList[GetNextSelectedItem(pos)])->m_Ref;
735 if (ref.Find(_T("refs/")) == 0)
736 ref = ref.Mid(5);
737 int refpos = ref.ReverseFind('{');
738 if (refpos > 0 && ref.Mid(refpos - 1, 2) != _T("@{"))
739 ref = ref.Left(refpos) + _T("@")+ ref.Mid(refpos);
740 refsToDelete.push_back(ref);
743 for (auto revIt = refsToDelete.rbegin(); revIt != refsToDelete.rend(); ++revIt)
745 CString ref = *revIt;
746 CString cmd, out;
747 if (ref.Find(_T("stash")) == 0)
748 cmd.Format(_T("git.exe stash drop %s"), ref);
749 else
750 cmd.Format(_T("git.exe reflog delete %s"), ref);
752 if (g_Git.Run(cmd, &out, CP_UTF8))
753 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
755 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
758 break;
759 case ID_LOG:
761 CString cmd = _T("/command:log");
762 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
763 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
764 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
765 CAppUtils::RunTortoiseGitProc(cmd);
767 break;
768 case ID_CREATE_PATCH:
770 int select=this->GetSelectedCount();
771 CString cmd = _T("/command:formatpatch");
772 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
774 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
775 GitRev * r2 = NULL;
776 if(select == 1)
778 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString();
780 else
782 r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
783 if( this->m_IsOldFirst )
785 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString()+_T("~1");
786 cmd += _T(" /endrev:")+r2->m_CommitHash.ToString();
789 else
791 cmd += _T(" /startrev:")+r2->m_CommitHash.ToString()+_T("~1");
792 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
797 CAppUtils::RunTortoiseGitProc(cmd);
799 break;
800 case ID_BISECTSTART:
802 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
803 GitRev * last = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
804 ASSERT(first != NULL && last != NULL);
806 CString firstBad = first->m_CommitHash.ToString();
807 if (!m_HashMap[first->m_CommitHash].empty())
808 firstBad = m_HashMap[first->m_CommitHash].at(0);
809 CString lastGood = last->m_CommitHash.ToString();
810 if (!m_HashMap[last->m_CommitHash].empty())
811 lastGood = m_HashMap[last->m_CommitHash].at(0);
813 if (CAppUtils::BisectStart(lastGood, firstBad))
814 Refresh();
816 break;
817 case ID_REPOBROWSE:
819 CString sCmd;
820 sCmd.Format(_T("/command:repobrowser /path:\"%s\" /rev:%s"), g_Git.m_CurrentDir, pSelLogEntry->m_CommitHash.ToString());
821 CAppUtils::RunTortoiseGitProc(sCmd);
823 break;
824 case ID_PUSH:
826 CString guessAssociatedBranch;
827 if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
828 guessAssociatedBranch = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
829 if (CAppUtils::Push(guessAssociatedBranch))
830 Refresh();
832 break;
833 case ID_PULL:
835 if (CAppUtils::Pull())
836 Refresh();
838 break;
839 case ID_FETCH:
841 if (CAppUtils::Fetch(_T(""), true))
842 Refresh();
844 break;
845 case ID_CLEANUP:
847 CString sCmd;
848 sCmd.Format(_T("/command:cleanup /path:\"%s\""), g_Git.m_CurrentDir);
849 CAppUtils::RunTortoiseGitProc(sCmd);
851 break;
852 case ID_SUBMODULE_UPDATE:
854 CString sCmd;
855 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), g_Git.m_CurrentDir);
856 CAppUtils::RunTortoiseGitProc(sCmd);
858 break;
859 case ID_SHOWBRANCHES:
861 CString cmd;
862 cmd.Format(_T("git.exe branch -a --contains %s"), pSelLogEntry->m_CommitHash.ToString());
863 CProgressDlg progress;
864 progress.m_AutoClose = AUTOCLOSE_NO;
865 progress.m_GitCmd = cmd;
866 progress.DoModal();
868 break;
869 case ID_DELETE:
871 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
872 if (!branch)
874 CMessageBox::Show(NULL,IDS_ERROR_NOREF,IDS_APPNAME,MB_OK|MB_ICONERROR);
875 return;
877 CString shortname;
878 if (CGit::GetShortName(*branch, shortname, _T("refs/remotes/")))
880 CString msg;
881 msg.Format(IDS_PROC_DELETEREMOTEBRANCH, *branch);
882 int result = CMessageBox::Show(NULL, msg, _T("TortoiseGit"), 3, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_PROC_DELETEREMOTEBRANCH_LOCALREMOTE)), CString(MAKEINTRESOURCE(IDS_PROC_DELETEREMOTEBRANCH_LOCAL)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON)));
883 if (result == 1)
885 CString remoteName = shortname.Left(shortname.Find('/'));
886 shortname = shortname.Mid(shortname.Find('/') + 1);
887 if(CAppUtils::IsSSHPutty())
888 CAppUtils::LaunchPAgent(NULL, &remoteName);
890 CSysProgressDlg sysProgressDlg;
891 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
892 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
893 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
894 sysProgressDlg.SetShowProgressBar(false);
895 sysProgressDlg.ShowModal(this, true);
896 STRING_VECTOR list;
897 list.push_back(_T("refs/heads/") + shortname);
898 if (g_Git.DeleteRemoteRefs(remoteName, list))
899 CMessageBox::Show(NULL, g_Git.GetGitLastErr(_T("Could not delete remote ref."), CGit::GIT_CMD_PUSH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
900 sysProgressDlg.Stop();
902 else if (result == 2)
904 if (g_Git.DeleteRef(*branch))
906 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
907 return;
910 else
911 return;
913 else if (CGit::GetShortName(*branch, shortname, _T("refs/stash")))
915 if (CMessageBox::Show(NULL, IDS_PROC_DELETEALLSTASH, IDS_APPNAME, 2, IDI_QUESTION, IDS_DELETEBUTTON, IDS_ABORTBUTTON) == 1)
917 CString cmd;
918 cmd.Format(_T("git.exe stash clear"));
919 CString out;
920 if (g_Git.Run(cmd, &out, CP_UTF8))
921 CMessageBox::Show(NULL, out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
923 else
924 return;
926 else
928 CString msg;
929 msg.Format(IDS_PROC_DELETEBRANCHTAG, *branch);
930 if (CMessageBox::Show(NULL, msg, _T("TortoiseGit"), 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
932 if (g_Git.DeleteRef(*branch))
934 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
935 return;
939 this->ReloadHashMap();
940 CRect rect;
941 this->GetItemRect(FirstSelect,&rect,LVIR_BOUNDS);
942 this->InvalidateRect(rect);
944 break;
946 case ID_FINDENTRY:
948 m_nSearchIndex = GetSelectionMark();
949 if (m_nSearchIndex < 0)
950 m_nSearchIndex = 0;
951 if (m_pFindDialog)
953 break;
955 else
957 m_pFindDialog = new CFindDlg();
958 m_pFindDialog->Create(this);
961 break;
962 case ID_MERGEREV:
964 CString str = pSelLogEntry->m_CommitHash.ToString();
965 if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
966 str = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
967 // we need an URL to complete this command, so error out if we can't get an URL
968 if(CAppUtils::Merge(&str))
970 this->Refresh();
973 break;
974 case ID_REVERTREV:
976 int parent = 0;
977 if (GetSelectedCount() == 1)
979 parent = cmd >> 16;
980 if (parent > pSelLogEntry->m_ParentHash.size())
982 CString str;
983 str.Format(IDS_PROC_NOPARENT, parent);
984 MessageBox(str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
985 return;
989 if (!this->RevertSelectedCommits(parent))
991 if (CMessageBox::Show(m_hWnd, IDS_REVREVERTED, IDS_APPNAME, 1, IDI_QUESTION, IDS_OKBUTTON, IDS_COMMITBUTTON) == 2)
993 CTGitPathList pathlist;
994 CTGitPathList selectedlist;
995 pathlist.AddPath(this->m_Path);
996 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
997 CString str;
998 CAppUtils::Commit(CString(), false, str, pathlist, selectedlist, bSelectFilesForCommit);
1000 this->Refresh();
1003 break;
1004 case ID_EDITNOTE:
1006 CAppUtils::EditNote(pSelLogEntry);
1007 this->SetItemState(FirstSelect, 0, LVIS_SELECTED);
1008 this->SetItemState(FirstSelect, LVIS_SELECTED, LVIS_SELECTED);
1010 break;
1011 default:
1012 //CMessageBox::Show(NULL,_T("Have not implemented"),_T("TortoiseGit"),MB_OK);
1013 break;
1014 #if 0
1016 case ID_BLAMECOMPARE:
1018 //user clicked on the menu item "compare with working copy"
1019 //now first get the revision which is selected
1020 if (PromptShown())
1022 GitDiff diff(this, this->m_hWnd, true);
1023 diff.SetHEADPeg(m_LogRevision);
1024 diff.ShowCompare(m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), false, true);
1026 else
1027 CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), m_LogRevision, false, false, true);
1029 break;
1030 case ID_BLAMEWITHPREVIOUS:
1032 //user clicked on the menu item "Compare and Blame with previous revision"
1033 if (PromptShown())
1035 GitDiff diff(this, this->m_hWnd, true);
1036 diff.SetHEADPeg(m_LogRevision);
1037 diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), false, true);
1039 else
1040 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
1042 break;
1044 case ID_OPENWITH:
1045 bOpenWith = true;
1046 case ID_OPEN:
1048 CProgressDlg progDlg;
1049 progDlg.SetTitle(IDS_APPNAME);
1050 progDlg.SetAnimation(IDR_DOWNLOAD);
1051 CString sInfoLine;
1052 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, m_path.GetWinPath(), (LPCTSTR)revSelected.ToString());
1053 progDlg.SetLine(1, sInfoLine, true);
1054 SetAndClearProgressInfo(&progDlg);
1055 progDlg.ShowModeless(m_hWnd);
1056 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, m_path, revSelected);
1057 bool bSuccess = true;
1058 if (!Cat(m_path, GitRev(GitRev::REV_HEAD), revSelected, tempfile))
1060 bSuccess = false;
1061 // try again, but with the selected revision as the peg revision
1062 if (!Cat(m_path, revSelected, revSelected, tempfile))
1064 progDlg.Stop();
1065 SetAndClearProgressInfo((HWND)NULL);
1066 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1067 EnableOKButton();
1068 break;
1070 bSuccess = true;
1072 if (bSuccess)
1074 progDlg.Stop();
1075 SetAndClearProgressInfo((HWND)NULL);
1076 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1077 int ret = 0;
1078 if (!bOpenWith)
1079 ret = (int)ShellExecute(this->m_hWnd, NULL, tempfile.GetWinPath(), NULL, NULL, SW_SHOWNORMAL);
1080 if ((ret <= HINSTANCE_ERROR)||bOpenWith)
1082 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1083 cmd += tempfile.GetWinPathString() + _T(" ");
1084 CAppUtils::LaunchApplication(cmd, NULL, false);
1088 break;
1089 case ID_BLAME:
1091 CBlameDlg dlg;
1092 dlg.EndRev = revSelected;
1093 if (dlg.DoModal() == IDOK)
1095 CBlame blame;
1096 CString tempfile;
1097 CString logfile;
1098 tempfile = blame.BlameToTempFile(m_path, dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, _T(""), dlg.m_bIncludeMerge, TRUE, TRUE);
1099 if (!tempfile.IsEmpty())
1101 if (dlg.m_bTextView)
1103 //open the default text editor for the result file
1104 CAppUtils::StartTextViewer(tempfile);
1106 else
1108 CString sParams = _T("/path:\"") + m_path.GetGitPathString() + _T("\" ");
1109 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(m_path.GetFileOrDirectoryName()),sParams))
1111 break;
1115 else
1117 CMessageBox::Show(this->m_hWnd, blame.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1121 break;
1122 case ID_EXPORT:
1124 CString sCmd;
1125 sCmd.Format(_T("%s /command:export /path:\"%s\" /revision:%ld"),
1126 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseGitProc.exe")),
1127 (LPCTSTR)pathURL, (LONG)revSelected);
1128 CAppUtils::LaunchApplication(sCmd, NULL, false);
1130 break;
1131 case ID_VIEWREV:
1133 CString url = m_ProjectProperties.sWebViewerRev;
1134 url = GetAbsoluteUrlFromRelativeUrl(url);
1135 url.Replace(_T("%REVISION%"), revSelected.ToString());
1136 if (!url.IsEmpty())
1137 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1139 break;
1140 case ID_VIEWPATHREV:
1142 CString relurl = pathURL;
1143 CString sRoot = GetRepositoryRoot(CTGitPath(relurl));
1144 relurl = relurl.Mid(sRoot.GetLength());
1145 CString url = m_ProjectProperties.sWebViewerPathRev;
1146 url = GetAbsoluteUrlFromRelativeUrl(url);
1147 url.Replace(_T("%REVISION%"), revSelected.ToString());
1148 url.Replace(_T("%PATH%"), relurl);
1149 if (!url.IsEmpty())
1150 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1152 break;
1153 #endif
1155 } // switch (cmd)
1157 theApp.DoWaitCursor(-1);
1160 void CGitLogList::SetSelectedRebaseAction(int action)
1162 POSITION pos = GetFirstSelectedItemPosition();
1163 if (!pos) return;
1164 int index;
1165 while(pos)
1167 index = GetNextSelectedItem(pos);
1168 if (((GitRev*)m_arShownList[index])->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT | LOGACTIONS_REBASE_DONE))
1169 continue;
1170 ((GitRev*)m_arShownList[index])->GetRebaseAction() = action;
1171 CRect rect;
1172 this->GetItemRect(index,&rect,LVIR_BOUNDS);
1173 this->InvalidateRect(rect);
1176 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage);
1178 void CGitLogList::ShiftSelectedRebaseAction()
1180 POSITION pos = GetFirstSelectedItemPosition();
1181 int index;
1182 while(pos)
1184 index = GetNextSelectedItem(pos);
1185 int dummyAction = 0;
1186 int *action = &dummyAction;
1187 action = &((GitRev*)m_arShownList[index])->GetRebaseAction();
1188 switch (*action)
1190 case LOGACTIONS_REBASE_PICK:
1191 *action = LOGACTIONS_REBASE_SKIP;
1192 break;
1193 case LOGACTIONS_REBASE_SKIP:
1194 *action= LOGACTIONS_REBASE_EDIT;
1195 break;
1196 case LOGACTIONS_REBASE_EDIT:
1197 *action = LOGACTIONS_REBASE_SQUASH;
1198 break;
1199 case LOGACTIONS_REBASE_SQUASH:
1200 *action= LOGACTIONS_REBASE_PICK;
1201 break;
1203 CRect rect;
1204 this->GetItemRect(index, &rect, LVIR_BOUNDS);
1205 this->InvalidateRect(rect);