Do not quit TortoiseGitProc after running Bisect Start from log list
[TortoiseGit.git] / src / TortoiseProc / GitLogListAction.cpp
blob801a0135158a7eea071782f8987cd3dadcfb6873
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 CAppUtils::RunTortoiseGitProc(_T("/command:log /range:\"") + pLastEntry->m_CommitHash.ToString() + sep + pSelLogEntry->m_CommitHash.ToString() + _T("\""));
375 break;
376 case ID_COPYCLIPBOARD:
378 CopySelectionToClipBoard();
380 break;
381 case ID_COPYCLIPBOARDMESSAGES:
383 if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0)
384 CopySelectionToClipBoard(ID_COPY_SUBJECT);
385 else
386 CopySelectionToClipBoard(ID_COPY_MESSAGE);
388 break;
389 case ID_COPYHASH:
391 CopySelectionToClipBoard(ID_COPY_HASH);
393 break;
394 case ID_EXPORT:
396 CString str=pSelLogEntry->m_CommitHash.ToString();
397 CAppUtils::Export(&str, &m_Path);
399 break;
400 case ID_CREATE_BRANCH:
401 case ID_CREATE_TAG:
403 CString str = pSelLogEntry->m_CommitHash.ToString();
404 // try to guess remote branch in order to enable tracking
405 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
407 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/remotes/")) == 0)
409 str = m_HashMap[pSelLogEntry->m_CommitHash][i];
410 break;
413 CAppUtils::CreateBranchTag((cmd&0xFFFF) == ID_CREATE_TAG, &str);
414 ReloadHashMap();
415 Invalidate();
416 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
418 break;
419 case ID_SWITCHTOREV:
421 CString str = pSelLogEntry->m_CommitHash.ToString();
422 // try to guess remote branch in order to recommend good branch name and tracking
423 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
425 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/remotes/")) == 0)
427 str = m_HashMap[pSelLogEntry->m_CommitHash][i];
428 break;
431 CAppUtils::Switch(str);
433 ReloadHashMap();
434 Invalidate();
435 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
436 break;
437 case ID_SWITCHBRANCH:
438 if(popmenu)
440 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
441 if(branch)
443 CString name;
444 if(branch->Find(_T("refs/heads/")) ==0 )
445 name = branch->Mid(11);
446 else
447 name = *branch;
449 CAppUtils::PerformSwitch(name);
451 ReloadHashMap();
452 Invalidate();
453 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
455 break;
456 case ID_RESET:
458 CString str = pSelLogEntry->m_CommitHash.ToString();
459 if (CAppUtils::GitReset(&str))
461 ResetWcRev(true);
462 ReloadHashMap();
463 Invalidate();
466 break;
467 case ID_REBASE_PICK:
468 SetSelectedRebaseAction(LOGACTIONS_REBASE_PICK);
469 break;
470 case ID_REBASE_EDIT:
471 SetSelectedRebaseAction(LOGACTIONS_REBASE_EDIT);
472 break;
473 case ID_REBASE_SQUASH:
474 SetSelectedRebaseAction(LOGACTIONS_REBASE_SQUASH);
475 break;
476 case ID_REBASE_SKIP:
477 SetSelectedRebaseAction(LOGACTIONS_REBASE_SKIP);
478 break;
479 case ID_COMBINE_COMMIT:
481 CString head;
482 CGitHash headhash;
483 CGitHash hashFirst,hashLast;
485 int headindex=GetHeadIndex();
486 if(headindex>=0) //incase show all branch, head is not the first commits.
488 head.Format(_T("HEAD~%d"),FirstSelect-headindex);
489 if (g_Git.GetHash(hashFirst, head))
491 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of first selected revision.")), _T("TortoiseGit"), MB_ICONERROR);
492 break;
495 head.Format(_T("HEAD~%d"),LastSelect-headindex);
496 if (g_Git.GetHash(hashLast, head))
498 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of last selected revision.")), _T("TortoiseGit"), MB_ICONERROR);
499 break;
503 GitRev* pFirstEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
504 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
505 if(pFirstEntry->m_CommitHash != hashFirst || pLastEntry->m_CommitHash != hashLast)
507 CMessageBox::Show(NULL, IDS_PROC_CANNOTCOMBINE, IDS_APPNAME, MB_OK);
508 break;
511 GitRev lastRevision;
514 lastRevision.GetParentFromHash(hashLast);
516 catch (char* msg)
518 CString err(msg);
519 MessageBox(_T("Could not get parent(s) of ") + hashLast.ToString() + _T(".\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
520 break;
523 if (g_Git.GetHash(headhash, _T("HEAD")))
525 MessageBox(g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
526 break;
529 if(!g_Git.CheckCleanWorkTree())
531 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
532 break;
534 CString cmd,out;
536 //Use throw to abort this process (reset back to original HEAD)
539 cmd.Format(_T("git.exe reset --hard %s --"), pFirstEntry->m_CommitHash.ToString());
540 if(g_Git.Run(cmd,&out,CP_UTF8))
542 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
543 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1)) + _T("\r\n\r\n") + out));
545 cmd.Format(_T("git.exe reset --mixed %s --"), hashLast.ToString());
546 if(g_Git.Run(cmd,&out,CP_UTF8))
548 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
549 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2)) + _T("\r\n\r\n")+out));
552 CTGitPathList PathList;
553 /* don't why must add --stat to get action status*/
554 /* first -z will be omitted by gitdll*/
555 if(g_Git.GetDiffPath(&PathList,&pFirstEntry->m_CommitHash,&hashLast,"-z --stat -r"))
557 CMessageBox::Show(NULL,_T("Get Diff file list error"),_T("TortoiseGit"),MB_OK);
558 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not get changed file list aborting...\r\n\r\n")+out));
561 for (int i = 0; i < PathList.GetCount(); ++i)
563 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_ADDED)
565 cmd.Format(_T("git.exe add -- \"%s\""), PathList[i].GetGitPathString());
566 if (g_Git.Run(cmd, &out, CP_UTF8))
568 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
569 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not add new file aborting...\r\n\r\n")+out));
573 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_DELETED)
575 cmd.Format(_T("git.exe rm -- \"%s\""), PathList[i].GetGitPathString());
576 if (g_Git.Run(cmd, &out, CP_UTF8))
578 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
579 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not rm file aborting...\r\n\r\n")+out));
584 CCommitDlg dlg;
585 for (int i = FirstSelect; i <= LastSelect; ++i)
587 GitRev* pRev = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
588 dlg.m_sLogMessage+=pRev->GetSubject()+_T("\n")+pRev->GetBody();
589 dlg.m_sLogMessage+=_T("\n");
591 dlg.m_bWholeProject=true;
592 dlg.m_bSelectFilesForCommit = true;
593 dlg.m_bForceCommitAmend=true;
594 CTGitPathList gpl;
595 gpl.AddPath(CTGitPath(g_Git.m_CurrentDir));
596 dlg.m_pathList = gpl;
597 if (lastRevision.ParentsCount() != 1)
599 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);
600 dlg.m_bAmendDiffToLastCommit = TRUE;
602 else
603 dlg.m_bAmendDiffToLastCommit = FALSE;
604 dlg.m_bNoPostActions=true;
605 dlg.m_AmendStr=dlg.m_sLogMessage;
607 if (dlg.DoModal() == IDOK)
609 if(pFirstEntry->m_CommitHash!=headhash)
611 if(CherryPickFrom(pFirstEntry->m_CommitHash.ToString(),headhash))
613 CString msg;
614 msg.Format(_T("Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n"));
615 throw std::exception(CUnicodeUtils::GetUTF8(msg));
619 else
620 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED))));
622 catch(std::exception& e)
624 CMessageBox::Show(NULL, CUnicodeUtils::GetUnicode(CStringA(e.what())), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
625 cmd.Format(_T("git.exe reset --hard %s --"), headhash.ToString());
626 out.Empty();
627 if(g_Git.Run(cmd,&out,CP_UTF8))
629 CMessageBox::Show(NULL, CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD)) + _T("\r\n\r\n") + out, _T("TortoiseGit"), MB_OK);
632 Refresh();
634 break;
636 case ID_CHERRY_PICK:
638 if (m_bThreadRunning)
640 MessageBox(_T("This operation cannot be started while the log dialog is still loading commits."), _T("TortoiseGit"), MB_ICONEXCLAMATION);
641 break;
643 CRebaseDlg dlg;
644 dlg.m_IsCherryPick = TRUE;
645 dlg.m_Upstream = this->m_CurrentBranch;
646 POSITION pos = GetFirstSelectedItemPosition();
647 while(pos)
649 int indexNext = GetNextSelectedItem(pos);
650 dlg.m_CommitList.m_logEntries.push_back( ((GitRev*)m_arShownList[indexNext])->m_CommitHash );
651 dlg.m_CommitList.m_LogCache.m_HashMap[((GitRev*)m_arShownList[indexNext])->m_CommitHash]=*(GitRev*)m_arShownList[indexNext];
652 dlg.m_CommitList.m_logEntries.GetGitRevAt(dlg.m_CommitList.m_logEntries.size() - 1).GetRebaseAction() |= LOGACTIONS_REBASE_PICK;
655 if(dlg.DoModal() == IDOK)
657 Refresh();
660 break;
661 case ID_REBASE_TO_VERSION:
663 if (m_bThreadRunning)
665 MessageBox(_T("This operation cannot be started while the log dialog is still loading commits."), _T("TortoiseGit"), MB_ICONEXCLAMATION);
666 break;
668 CRebaseDlg dlg;
669 auto refList = m_HashMap[pSelLogEntry->m_CommitHash];
670 dlg.m_Upstream = refList.empty() ? pSelLogEntry->m_CommitHash.ToString() : refList.front();
671 for (auto ref : refList)
673 if (ref.Left(11) == _T("refs/heads/"))
675 // 11=len("refs/heads/")
676 dlg.m_Upstream = ref.Mid(11);
677 break;
681 if(dlg.DoModal() == IDOK)
683 Refresh();
687 break;
689 case ID_STASH_SAVE:
690 if (CAppUtils::StashSave())
691 Refresh();
692 break;
694 case ID_STASH_POP:
695 if (CAppUtils::StashPop())
696 Refresh();
697 break;
699 case ID_STASH_LIST:
700 CAppUtils::RunTortoiseGitProc(_T("/command:reflog /ref:refs/stash"));
701 break;
703 case ID_REFLOG_STASH_APPLY:
704 CAppUtils::StashApply(pSelLogEntry->m_Ref);
705 break;
707 case ID_REFLOG_DEL:
709 CString str;
710 if (GetSelectedCount() > 1)
711 str.Format(IDS_PROC_DELETENREFS, GetSelectedCount());
712 else
713 str.Format(IDS_PROC_DELETEREF, pSelLogEntry->m_Ref);
715 if (CMessageBox::Show(NULL, str, _T("TortoiseGit"), 1, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
716 return;
718 std::vector<CString> refsToDelete;
719 POSITION pos = GetFirstSelectedItemPosition();
720 while (pos)
722 CString ref = ((GitRev *)m_arShownList[GetNextSelectedItem(pos)])->m_Ref;
723 if (ref.Find(_T("refs/")) == 0)
724 ref = ref.Mid(5);
725 int refpos = ref.ReverseFind('{');
726 if (refpos > 0 && ref.Mid(refpos - 1, 2) != _T("@{"))
727 ref = ref.Left(refpos) + _T("@")+ ref.Mid(refpos);
728 refsToDelete.push_back(ref);
731 for (auto revIt = refsToDelete.rbegin(); revIt != refsToDelete.rend(); ++revIt)
733 CString ref = *revIt;
734 CString cmd, out;
735 if (ref.Find(_T("stash")) == 0)
736 cmd.Format(_T("git.exe stash drop %s"), ref);
737 else
738 cmd.Format(_T("git.exe reflog delete %s"), ref);
740 if (g_Git.Run(cmd, &out, CP_UTF8))
741 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
743 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
746 break;
747 case ID_LOG:
749 CString cmd = _T("/command:log");
750 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
751 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
752 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
753 CAppUtils::RunTortoiseGitProc(cmd);
755 break;
756 case ID_CREATE_PATCH:
758 int select=this->GetSelectedCount();
759 CString cmd = _T("/command:formatpatch");
760 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
762 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
763 GitRev * r2 = NULL;
764 if(select == 1)
766 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString();
768 else
770 r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
771 if( this->m_IsOldFirst )
773 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString()+_T("~1");
774 cmd += _T(" /endrev:")+r2->m_CommitHash.ToString();
777 else
779 cmd += _T(" /startrev:")+r2->m_CommitHash.ToString()+_T("~1");
780 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
785 CAppUtils::RunTortoiseGitProc(cmd);
787 break;
788 case ID_BISECTSTART:
790 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
791 GitRev * last = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
792 ASSERT(first != NULL && last != NULL);
794 CString firstBad = first->m_CommitHash.ToString();
795 if (!m_HashMap[first->m_CommitHash].empty())
796 firstBad = m_HashMap[first->m_CommitHash].at(0);
797 CString lastGood = last->m_CommitHash.ToString();
798 if (!m_HashMap[last->m_CommitHash].empty())
799 lastGood = m_HashMap[last->m_CommitHash].at(0);
801 if (CAppUtils::BisectStart(lastGood, firstBad))
802 Refresh();
804 break;
805 case ID_REPOBROWSE:
807 CString sCmd;
808 sCmd.Format(_T("/command:repobrowser /path:\"%s\" /rev:%s"), g_Git.m_CurrentDir, pSelLogEntry->m_CommitHash.ToString());
809 CAppUtils::RunTortoiseGitProc(sCmd);
811 break;
812 case ID_PUSH:
814 CString guessAssociatedBranch;
815 if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
816 guessAssociatedBranch = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
817 if (CAppUtils::Push(guessAssociatedBranch))
818 Refresh();
820 break;
821 case ID_PULL:
823 if (CAppUtils::Pull())
824 Refresh();
826 break;
827 case ID_FETCH:
829 if (CAppUtils::Fetch(_T(""), true))
830 Refresh();
832 break;
833 case ID_SHOWBRANCHES:
835 CString cmd;
836 cmd.Format(_T("git.exe branch -a --contains %s"), pSelLogEntry->m_CommitHash.ToString());
837 CProgressDlg progress;
838 progress.m_AutoClose = AUTOCLOSE_NO;
839 progress.m_GitCmd = cmd;
840 progress.DoModal();
842 break;
843 case ID_DELETE:
845 bool showProgress = false;
846 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
847 if (!branch)
849 CMessageBox::Show(NULL,IDS_ERROR_NOREF,IDS_APPNAME,MB_OK|MB_ICONERROR);
850 return;
852 CString shortname;
853 CString cmd;
854 if (CGit::GetShortName(*branch, shortname, _T("refs/remotes/")))
856 CString msg;
857 msg.Format(IDS_PROC_DELETEREMOTEBRANCH, *branch);
858 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)));
859 if (result == 1)
861 CString remoteName = shortname.Left(shortname.Find('/'));
862 shortname = shortname.Mid(shortname.Find('/') + 1);
863 if(CAppUtils::IsSSHPutty())
864 CAppUtils::LaunchPAgent(NULL, &remoteName);
866 cmd.Format(L"git.exe push \"%s\" :refs/heads/%s", remoteName, shortname);
867 showProgress = true;
869 else if (result == 2)
871 if (g_Git.DeleteRef(*branch))
873 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
874 return;
877 else
878 return;
880 else if (CGit::GetShortName(*branch, shortname, _T("refs/stash")))
882 if (CMessageBox::Show(NULL, IDS_PROC_DELETEALLSTASH, IDS_APPNAME, 2, IDI_QUESTION, IDS_DELETEBUTTON, IDS_ABORTBUTTON) == 1)
883 cmd.Format(_T("git.exe stash clear"));
884 else
885 return;
887 else
889 CString msg;
890 msg.Format(IDS_PROC_DELETEBRANCHTAG, *branch);
891 if (CMessageBox::Show(NULL, msg, _T("TortoiseGit"), 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
893 if (g_Git.DeleteRef(*branch))
895 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
896 return;
900 if (!cmd.IsEmpty())
902 CSysProgressDlg sysProgressDlg;
903 if (showProgress)
905 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
906 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
907 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
908 sysProgressDlg.SetShowProgressBar(false);
909 sysProgressDlg.ShowModal(this, true);
911 CString out;
912 if(g_Git.Run(cmd,&out,CP_UTF8))
914 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
916 if (showProgress)
917 sysProgressDlg.Stop();
919 this->ReloadHashMap();
920 CRect rect;
921 this->GetItemRect(FirstSelect,&rect,LVIR_BOUNDS);
922 this->InvalidateRect(rect);
924 break;
926 case ID_FINDENTRY:
928 m_nSearchIndex = GetSelectionMark();
929 if (m_nSearchIndex < 0)
930 m_nSearchIndex = 0;
931 if (m_pFindDialog)
933 break;
935 else
937 m_pFindDialog = new CFindDlg();
938 m_pFindDialog->Create(this);
941 break;
942 case ID_MERGEREV:
944 CString str = pSelLogEntry->m_CommitHash.ToString();
945 if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
946 str = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
947 // we need an URL to complete this command, so error out if we can't get an URL
948 if(CAppUtils::Merge(&str))
950 this->Refresh();
953 break;
954 case ID_REVERTREV:
956 int parent = 0;
957 if (GetSelectedCount() == 1)
959 parent = cmd >> 16;
960 if (parent > pSelLogEntry->m_ParentHash.size())
962 CString str;
963 str.Format(IDS_PROC_NOPARENT, parent);
964 MessageBox(str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
965 return;
969 if (!this->RevertSelectedCommits(parent))
971 if (CMessageBox::Show(m_hWnd, IDS_REVREVERTED, IDS_APPNAME, 1, IDI_QUESTION, IDS_OKBUTTON, IDS_COMMITBUTTON) == 2)
973 CTGitPathList pathlist;
974 CTGitPathList selectedlist;
975 pathlist.AddPath(this->m_Path);
976 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
977 CString str;
978 CAppUtils::Commit(CString(), false, str, pathlist, selectedlist, bSelectFilesForCommit);
980 this->Refresh();
983 break;
984 case ID_EDITNOTE:
986 CAppUtils::EditNote(pSelLogEntry);
987 this->SetItemState(FirstSelect, 0, LVIS_SELECTED);
988 this->SetItemState(FirstSelect, LVIS_SELECTED, LVIS_SELECTED);
990 break;
991 default:
992 //CMessageBox::Show(NULL,_T("Have not implemented"),_T("TortoiseGit"),MB_OK);
993 break;
994 #if 0
996 case ID_BLAMECOMPARE:
998 //user clicked on the menu item "compare with working copy"
999 //now first get the revision which is selected
1000 if (PromptShown())
1002 GitDiff diff(this, this->m_hWnd, true);
1003 diff.SetHEADPeg(m_LogRevision);
1004 diff.ShowCompare(m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), false, true);
1006 else
1007 CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), m_LogRevision, false, false, true);
1009 break;
1010 case ID_BLAMEWITHPREVIOUS:
1012 //user clicked on the menu item "Compare and Blame with previous revision"
1013 if (PromptShown())
1015 GitDiff diff(this, this->m_hWnd, true);
1016 diff.SetHEADPeg(m_LogRevision);
1017 diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), false, true);
1019 else
1020 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
1022 break;
1024 case ID_OPENWITH:
1025 bOpenWith = true;
1026 case ID_OPEN:
1028 CProgressDlg progDlg;
1029 progDlg.SetTitle(IDS_APPNAME);
1030 progDlg.SetAnimation(IDR_DOWNLOAD);
1031 CString sInfoLine;
1032 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, m_path.GetWinPath(), (LPCTSTR)revSelected.ToString());
1033 progDlg.SetLine(1, sInfoLine, true);
1034 SetAndClearProgressInfo(&progDlg);
1035 progDlg.ShowModeless(m_hWnd);
1036 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, m_path, revSelected);
1037 bool bSuccess = true;
1038 if (!Cat(m_path, GitRev(GitRev::REV_HEAD), revSelected, tempfile))
1040 bSuccess = false;
1041 // try again, but with the selected revision as the peg revision
1042 if (!Cat(m_path, revSelected, revSelected, tempfile))
1044 progDlg.Stop();
1045 SetAndClearProgressInfo((HWND)NULL);
1046 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1047 EnableOKButton();
1048 break;
1050 bSuccess = true;
1052 if (bSuccess)
1054 progDlg.Stop();
1055 SetAndClearProgressInfo((HWND)NULL);
1056 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1057 int ret = 0;
1058 if (!bOpenWith)
1059 ret = (int)ShellExecute(this->m_hWnd, NULL, tempfile.GetWinPath(), NULL, NULL, SW_SHOWNORMAL);
1060 if ((ret <= HINSTANCE_ERROR)||bOpenWith)
1062 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1063 cmd += tempfile.GetWinPathString() + _T(" ");
1064 CAppUtils::LaunchApplication(cmd, NULL, false);
1068 break;
1069 case ID_BLAME:
1071 CBlameDlg dlg;
1072 dlg.EndRev = revSelected;
1073 if (dlg.DoModal() == IDOK)
1075 CBlame blame;
1076 CString tempfile;
1077 CString logfile;
1078 tempfile = blame.BlameToTempFile(m_path, dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, _T(""), dlg.m_bIncludeMerge, TRUE, TRUE);
1079 if (!tempfile.IsEmpty())
1081 if (dlg.m_bTextView)
1083 //open the default text editor for the result file
1084 CAppUtils::StartTextViewer(tempfile);
1086 else
1088 CString sParams = _T("/path:\"") + m_path.GetGitPathString() + _T("\" ");
1089 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(m_path.GetFileOrDirectoryName()),sParams))
1091 break;
1095 else
1097 CMessageBox::Show(this->m_hWnd, blame.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1101 break;
1102 case ID_EXPORT:
1104 CString sCmd;
1105 sCmd.Format(_T("%s /command:export /path:\"%s\" /revision:%ld"),
1106 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseGitProc.exe")),
1107 (LPCTSTR)pathURL, (LONG)revSelected);
1108 CAppUtils::LaunchApplication(sCmd, NULL, false);
1110 break;
1111 case ID_VIEWREV:
1113 CString url = m_ProjectProperties.sWebViewerRev;
1114 url = GetAbsoluteUrlFromRelativeUrl(url);
1115 url.Replace(_T("%REVISION%"), revSelected.ToString());
1116 if (!url.IsEmpty())
1117 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1119 break;
1120 case ID_VIEWPATHREV:
1122 CString relurl = pathURL;
1123 CString sRoot = GetRepositoryRoot(CTGitPath(relurl));
1124 relurl = relurl.Mid(sRoot.GetLength());
1125 CString url = m_ProjectProperties.sWebViewerPathRev;
1126 url = GetAbsoluteUrlFromRelativeUrl(url);
1127 url.Replace(_T("%REVISION%"), revSelected.ToString());
1128 url.Replace(_T("%PATH%"), relurl);
1129 if (!url.IsEmpty())
1130 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1132 break;
1133 #endif
1135 } // switch (cmd)
1137 theApp.DoWaitCursor(-1);
1140 void CGitLogList::SetSelectedRebaseAction(int action)
1142 POSITION pos = GetFirstSelectedItemPosition();
1143 if (!pos) return;
1144 int index;
1145 while(pos)
1147 index = GetNextSelectedItem(pos);
1148 if (((GitRev*)m_arShownList[index])->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT | LOGACTIONS_REBASE_DONE))
1149 continue;
1150 ((GitRev*)m_arShownList[index])->GetRebaseAction() = action;
1151 CRect rect;
1152 this->GetItemRect(index,&rect,LVIR_BOUNDS);
1153 this->InvalidateRect(rect);
1156 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage);
1158 void CGitLogList::ShiftSelectedRebaseAction()
1160 POSITION pos = GetFirstSelectedItemPosition();
1161 int index;
1162 while(pos)
1164 index = GetNextSelectedItem(pos);
1165 int dummyAction = 0;
1166 int *action = &dummyAction;
1167 action = &((GitRev*)m_arShownList[index])->GetRebaseAction();
1168 switch (*action)
1170 case LOGACTIONS_REBASE_PICK:
1171 *action = LOGACTIONS_REBASE_SKIP;
1172 break;
1173 case LOGACTIONS_REBASE_SKIP:
1174 *action= LOGACTIONS_REBASE_EDIT;
1175 break;
1176 case LOGACTIONS_REBASE_EDIT:
1177 *action = LOGACTIONS_REBASE_SQUASH;
1178 break;
1179 case LOGACTIONS_REBASE_SQUASH:
1180 *action= LOGACTIONS_REBASE_PICK;
1181 break;
1183 CRect rect;
1184 this->GetItemRect(index, &rect, LVIR_BOUNDS);
1185 this->InvalidateRect(rect);