Includes cleanup
[TortoiseGit.git] / src / TortoiseProc / GitLogListAction.cpp
blob30bfc96225495816c69447f1aa322e392eeba2fb
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2015 - TortoiseGit
4 // Copyright (C) 2005-2007 Marco Costalba
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 // GitLogList.cpp : implementation file
22 #include "stdafx.h"
23 #include "TortoiseProc.h"
24 #include "GitLogList.h"
25 #include "GitRev.h"
26 #include "IconMenu.h"
27 #include "cursor.h"
28 #include "GitProgressDlg.h"
29 #include "ProgressDlg.h"
30 #include "SysProgressDlg.h"
31 #include "LogDlg.h"
32 #include "MessageBox.h"
33 #include "registry.h"
34 #include "AppUtils.h"
35 #include "StringUtils.h"
36 #include "UnicodeUtils.h"
37 #include "TempFile.h"
38 #include "FileDiffDlg.h"
39 #include "CommitDlg.h"
40 #include "RebaseDlg.h"
41 #include "GitDiff.h"
42 #include "../TGitCache/CacheInterface.h"
44 IMPLEMENT_DYNAMIC(CGitLogList, CHintListCtrl)
46 int CGitLogList::RevertSelectedCommits(int parent)
48 CSysProgressDlg progress;
49 int ret = -1;
51 #if 0
52 if(!g_Git.CheckCleanWorkTree())
54 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
57 #endif
59 if (this->GetSelectedCount() > 1)
61 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_REVERTCOMMIT)));
62 progress.SetAnimation(IDR_MOVEANI);
63 progress.SetTime(true);
64 progress.ShowModeless(this);
67 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
69 POSITION pos = GetFirstSelectedItemPosition();
70 int i=0;
71 while(pos)
73 int index = GetNextSelectedItem(pos);
74 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(index));
76 if (progress.IsVisible())
78 progress.FormatNonPathLine(1, IDS_PROC_REVERTCOMMIT, r1->m_CommitHash.ToString());
79 progress.FormatNonPathLine(2, _T("%s"), r1->GetSubject());
80 progress.SetProgress(i, this->GetSelectedCount());
82 ++i;
84 if(r1->m_CommitHash.IsEmpty())
85 continue;
87 if (g_Git.GitRevert(parent, r1->m_CommitHash))
89 CString str;
90 str.LoadString(IDS_SVNACTION_FAILEDREVERT);
91 str = g_Git.GetGitLastErr(str, CGit::GIT_CMD_REVERT);
92 if( GetSelectedCount() == 1)
93 CMessageBox::Show(NULL, str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
94 else
96 if(CMessageBox::Show(NULL, str, _T("TortoiseGit"),2 , IDI_ERROR, CString(MAKEINTRESOURCE(IDS_SKIPBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
98 return ret;
102 else
104 ret =0;
107 if (progress.HasUserCancelled())
108 break;
110 return ret;
112 int CGitLogList::CherryPickFrom(CString from, CString to)
114 CLogDataVector logs(&m_LogCache);
115 CString range;
116 range.Format(_T("%s..%s"), from, to);
117 if (logs.ParserFromLog(nullptr, -1, 0, &range))
118 return -1;
120 if (logs.empty())
121 return 0;
123 CSysProgressDlg progress;
124 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_CHERRYPICK)));
125 progress.SetAnimation(IDR_MOVEANI);
126 progress.SetTime(true);
127 progress.ShowModeless(this);
129 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
131 for (int i = (int)logs.size() - 1; i >= 0; i--)
133 if (progress.IsVisible())
135 progress.FormatNonPathLine(1, IDS_PROC_PICK, logs.GetGitRevAt(i).m_CommitHash.ToString());
136 progress.FormatNonPathLine(2, _T("%s"), logs.GetGitRevAt(i).GetSubject());
137 progress.SetProgress64(logs.size() - i, logs.size());
139 if (progress.HasUserCancelled())
141 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED))));
143 CString cmd,out;
144 cmd.Format(_T("git.exe cherry-pick %s"),logs.GetGitRevAt(i).m_CommitHash.ToString());
145 out.Empty();
146 if(g_Git.Run(cmd,&out,CP_UTF8))
148 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_CHERRYPICKFAILED)) + _T(":\r\n\r\n") + out));
152 return 0;
155 void CGitLogList::ContextMenuAction(int cmd,int FirstSelect, int LastSelect, CMenu *popmenu)
157 POSITION pos = GetFirstSelectedItemPosition();
158 int indexNext = GetNextSelectedItem(pos);
159 if (indexNext < 0)
160 return;
162 GitRevLoglist* pSelLogEntry = reinterpret_cast<GitRevLoglist*>(m_arShownList.GetAt(indexNext));
164 theApp.DoWaitCursor(1);
165 switch (cmd&0xFFFF)
167 case ID_COMMIT:
169 CTGitPathList pathlist;
170 CTGitPathList selectedlist;
171 pathlist.AddPath(this->m_Path);
172 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
173 CString str;
174 CAppUtils::Commit(CString(),false,str,
175 pathlist,selectedlist,bSelectFilesForCommit);
176 //this->Refresh();
177 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH,0);
179 break;
180 case ID_MERGE_ABORT:
182 if (CAppUtils::MergeAbort())
183 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH, 0);
185 break;
186 case ID_GNUDIFF1: // compare with WC, unified
188 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
189 bool bMerge = false, bCombine = false;
190 CString hash2;
191 if(!r1->m_CommitHash.IsEmpty())
193 CString merge;
194 cmd >>= 16;
195 if( (cmd&0xFFFF) == 0xFFFF)
196 bMerge = true;
198 else if((cmd&0xFFFF) == 0xFFFE)
199 bCombine = true;
200 else
202 if(cmd > r1->m_ParentHash.size())
204 CString str;
205 str.Format(IDS_PROC_NOPARENT, cmd);
206 MessageBox(str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
207 return;
209 else
211 if(cmd>0)
212 hash2 = r1->m_ParentHash[cmd-1].ToString();
215 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), hash2, CTGitPath(), r1->m_CommitHash.ToString(), false, false, false, bMerge, bCombine);
217 else
218 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), _T("HEAD"), CTGitPath(), GitRev::GetWorkingCopy(), false, false, false, bMerge, bCombine);
220 break;
222 case ID_GNUDIFF2: // compare two revisions, unified
224 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
225 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
226 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), r2->m_CommitHash.ToString(), CTGitPath(), r1->m_CommitHash.ToString());
228 break;
230 case ID_COMPARETWO: // compare two revisions
232 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
233 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
234 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
235 CGitDiff::DiffCommit(this->m_Path, r1,r2);
236 else
238 CString path1 = m_Path.GetGitPathString();
239 // start with 1 (0 = working copy changes)
240 for (int i = 1; i < FirstSelect; ++i)
242 GitRevLoglist* first = reinterpret_cast<GitRevLoglist*>(m_arShownList.GetAt(i));
243 CTGitPathList list = first->GetFiles(NULL);
244 CTGitPath * file = list.LookForGitPath(path1);
245 if (file && !file->GetGitOldPathString().IsEmpty())
246 path1 = file->GetGitOldPathString();
248 CString path2 = path1;
249 for (int i = FirstSelect; i < LastSelect; ++i)
251 GitRevLoglist* first = reinterpret_cast<GitRevLoglist*>(m_arShownList.GetAt(i));
252 CTGitPathList list = first->GetFiles(NULL);
253 CTGitPath * file = list.LookForGitPath(path2);
254 if (file && !file->GetGitOldPathString().IsEmpty())
255 path2 = file->GetGitOldPathString();
257 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), r1, r2);
261 break;
263 case ID_COMPARE: // compare revision with WC
265 GitRevLoglist* r1 = &m_wcRev;
266 GitRevLoglist* r2 = pSelLogEntry;
268 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
269 CGitDiff::DiffCommit(this->m_Path, r1,r2);
270 else
272 CString path1 = m_Path.GetGitPathString();
273 // start with 1 (0 = working copy changes)
274 for (int i = 1; i < FirstSelect; ++i)
276 GitRevLoglist* first = reinterpret_cast<GitRevLoglist*>(m_arShownList.GetAt(i));
277 CTGitPathList list = first->GetFiles(NULL);
278 CTGitPath * file = list.LookForGitPath(path1);
279 if (file && !file->GetGitOldPathString().IsEmpty())
280 path1 = file->GetGitOldPathString();
282 CGitDiff::DiffCommit(m_Path, CTGitPath(path1), r1, r2);
285 //user clicked on the menu item "compare with working copy"
286 //if (PromptShown())
288 // GitDiff diff(this, m_hWnd, true);
289 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
290 // diff.SetHEADPeg(m_LogRevision);
291 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
293 //else
294 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
296 break;
298 case ID_COMPAREWITHPREVIOUS:
300 CFileDiffDlg dlg;
302 if (pSelLogEntry->m_ParentHash.empty())
304 if (pSelLogEntry->GetParentFromHash(pSelLogEntry->m_CommitHash))
305 MessageBox(pSelLogEntry->GetLastErr(), _T("TortoiseGit"), MB_ICONERROR);
308 if (!pSelLogEntry->m_ParentHash.empty())
309 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
311 cmd>>=16;
312 cmd&=0xFFFF;
314 if(cmd == 0)
315 cmd=1;
317 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
318 CGitDiff::DiffCommit(m_Path, pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString());
319 else
321 CString path1 = m_Path.GetGitPathString();
322 // start with 1 (0 = working copy changes)
323 for (int i = 1; i < indexNext; ++i)
325 GitRevLoglist* first = reinterpret_cast<GitRevLoglist*>(m_arShownList.GetAt(i));
326 CTGitPathList list = first->GetFiles(NULL);
327 CTGitPath * file = list.LookForGitPath(path1);
328 if (file && !file->GetGitOldPathString().IsEmpty())
329 path1 = file->GetGitOldPathString();
331 CString path2 = path1;
332 GitRevLoglist* first = reinterpret_cast<GitRevLoglist*>(m_arShownList.GetAt(indexNext));
333 CTGitPathList list = first->GetFiles(NULL);
334 CTGitPath * file = list.LookForGitPath(path2);
335 if (file && !file->GetGitOldPathString().IsEmpty())
336 path2 = file->GetGitOldPathString();
338 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString());
341 else
343 CMessageBox::Show(NULL, IDS_PROC_NOPREVIOUSVERSION, IDS_APPNAME, MB_OK);
345 //if (PromptShown())
347 // GitDiff diff(this, m_hWnd, true);
348 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
349 // diff.SetHEADPeg(m_LogRevision);
350 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
352 //else
353 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
355 break;
356 case ID_LOG_VIEWRANGE:
357 case ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE:
359 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(LastSelect));
361 CString sep = _T("..");
362 if ((cmd & 0xFFFF) == ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE)
363 sep = _T("...");
365 CString cmdline;
366 cmdline.Format(_T("/command:log /path:\"%s\" /range:\"%s%s%s\""),
367 g_Git.CombinePath(m_Path), pLastEntry->m_CommitHash.ToString(), sep, pSelLogEntry->m_CommitHash.ToString());
368 CAppUtils::RunTortoiseGitProc(cmdline);
370 break;
371 case ID_COPYCLIPBOARD:
373 CopySelectionToClipBoard();
375 break;
376 case ID_COPYCLIPBOARDMESSAGES:
378 if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0)
379 CopySelectionToClipBoard(ID_COPY_SUBJECT);
380 else
381 CopySelectionToClipBoard(ID_COPY_MESSAGE);
383 break;
384 case ID_COPYHASH:
386 CopySelectionToClipBoard(ID_COPY_HASH);
388 break;
389 case ID_EXPORT:
391 CString str=pSelLogEntry->m_CommitHash.ToString();
392 // try to get the tag
393 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
395 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/tags/")) == 0)
397 str = m_HashMap[pSelLogEntry->m_CommitHash][i];
398 break;
401 CAppUtils::Export(&str, &m_Path);
403 break;
404 case ID_CREATE_BRANCH:
405 case ID_CREATE_TAG:
407 CString str = pSelLogEntry->m_CommitHash.ToString();
408 // try to guess remote branch in order to enable tracking
409 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
411 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/remotes/")) == 0)
413 str = m_HashMap[pSelLogEntry->m_CommitHash][i];
414 break;
417 CAppUtils::CreateBranchTag((cmd&0xFFFF) == ID_CREATE_TAG, &str);
418 ReloadHashMap();
419 if (m_pFindDialog)
420 m_pFindDialog->RefreshList();
421 Invalidate();
422 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
424 break;
425 case ID_SWITCHTOREV:
427 CString str = pSelLogEntry->m_CommitHash.ToString();
428 // try to guess remote branch in order to recommend good branch name and tracking
429 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
431 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/remotes/")) == 0)
433 str = m_HashMap[pSelLogEntry->m_CommitHash][i];
434 break;
437 CAppUtils::Switch(str);
439 ReloadHashMap();
440 Invalidate();
441 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
442 break;
443 case ID_SWITCHBRANCH:
444 if(popmenu)
446 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
447 if(branch)
449 CString name;
450 if(branch->Find(_T("refs/heads/")) ==0 )
451 name = branch->Mid(11);
452 else
453 name = *branch;
455 CAppUtils::PerformSwitch(name);
457 ReloadHashMap();
458 Invalidate();
459 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
461 break;
462 case ID_RESET:
464 CString str = pSelLogEntry->m_CommitHash.ToString();
465 if (CAppUtils::GitReset(&str))
467 ResetWcRev(true);
468 ReloadHashMap();
469 Invalidate();
472 break;
473 case ID_REBASE_PICK:
474 SetSelectedRebaseAction(LOGACTIONS_REBASE_PICK);
475 break;
476 case ID_REBASE_EDIT:
477 SetSelectedRebaseAction(LOGACTIONS_REBASE_EDIT);
478 break;
479 case ID_REBASE_SQUASH:
480 SetSelectedRebaseAction(LOGACTIONS_REBASE_SQUASH);
481 break;
482 case ID_REBASE_SKIP:
483 SetSelectedRebaseAction(LOGACTIONS_REBASE_SKIP);
484 break;
485 case ID_COMBINE_COMMIT:
487 CString head;
488 CGitHash headhash;
489 CGitHash hashFirst,hashLast;
491 int headindex=GetHeadIndex();
492 if(headindex>=0) //incase show all branch, head is not the first commits.
494 head.Format(_T("HEAD~%d"),FirstSelect-headindex);
495 if (g_Git.GetHash(hashFirst, head))
497 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of first selected revision.")), _T("TortoiseGit"), MB_ICONERROR);
498 break;
501 head.Format(_T("HEAD~%d"),LastSelect-headindex);
502 if (g_Git.GetHash(hashLast, head))
504 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of last selected revision.")), _T("TortoiseGit"), MB_ICONERROR);
505 break;
509 GitRev* pFirstEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
510 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
511 if(pFirstEntry->m_CommitHash != hashFirst || pLastEntry->m_CommitHash != hashLast)
513 CMessageBox::Show(NULL, IDS_PROC_CANNOTCOMBINE, IDS_APPNAME, MB_OK);
514 break;
517 GitRev lastRevision;
518 if (lastRevision.GetParentFromHash(hashLast))
520 MessageBox(lastRevision.GetLastErr(), _T("TortoiseGit"), MB_ICONERROR);
521 break;
524 if (g_Git.GetHash(headhash, _T("HEAD")))
526 MessageBox(g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
527 break;
530 if(!g_Git.CheckCleanWorkTree())
532 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
533 break;
535 CString cmd,out;
537 //Use throw to abort this process (reset back to original HEAD)
540 cmd.Format(_T("git.exe reset --hard %s --"), pFirstEntry->m_CommitHash.ToString());
541 if(g_Git.Run(cmd,&out,CP_UTF8))
543 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
544 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1)) + _T("\r\n\r\n") + out));
546 cmd.Format(_T("git.exe reset --mixed %s --"), hashLast.ToString());
547 if(g_Git.Run(cmd,&out,CP_UTF8))
549 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
550 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2)) + _T("\r\n\r\n")+out));
553 CTGitPathList PathList;
554 /* don't why must add --stat to get action status*/
555 /* first -z will be omitted by gitdll*/
556 if(g_Git.GetDiffPath(&PathList,&pFirstEntry->m_CommitHash,&hashLast,"-z --stat -r"))
558 CMessageBox::Show(NULL,_T("Get Diff file list error"),_T("TortoiseGit"),MB_OK);
559 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not get changed file list aborting...\r\n\r\n")+out));
562 for (int i = 0; i < PathList.GetCount(); ++i)
564 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_ADDED)
566 cmd.Format(_T("git.exe add -- \"%s\""), PathList[i].GetGitPathString());
567 if (g_Git.Run(cmd, &out, CP_UTF8))
569 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
570 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not add new file aborting...\r\n\r\n")+out));
574 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_DELETED)
576 cmd.Format(_T("git.exe rm -- \"%s\""), PathList[i].GetGitPathString());
577 if (g_Git.Run(cmd, &out, CP_UTF8))
579 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
580 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not rm file aborting...\r\n\r\n")+out));
585 CCommitDlg dlg;
586 for (int i = FirstSelect; i <= LastSelect; ++i)
588 GitRev* pRev = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
589 dlg.m_sLogMessage+=pRev->GetSubject()+_T("\n")+pRev->GetBody();
590 dlg.m_sLogMessage+=_T("\n");
592 dlg.m_bWholeProject=true;
593 dlg.m_bSelectFilesForCommit = true;
594 dlg.m_bForceCommitAmend=true;
595 CTGitPathList gpl;
596 gpl.AddPath(CTGitPath(g_Git.m_CurrentDir));
597 dlg.m_pathList = gpl;
598 if (lastRevision.ParentsCount() != 1)
600 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);
601 dlg.m_bAmendDiffToLastCommit = TRUE;
603 else
604 dlg.m_bAmendDiffToLastCommit = FALSE;
605 dlg.m_bNoPostActions=true;
606 dlg.m_AmendStr=dlg.m_sLogMessage;
608 if (dlg.DoModal() == IDOK)
610 if(pFirstEntry->m_CommitHash!=headhash)
612 if(CherryPickFrom(pFirstEntry->m_CommitHash.ToString(),headhash))
614 CString msg;
615 msg.Format(_T("Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n"));
616 throw std::exception(CUnicodeUtils::GetUTF8(msg));
620 else
621 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED))));
623 catch(std::exception& e)
625 CMessageBox::Show(NULL, CUnicodeUtils::GetUnicode(CStringA(e.what())), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
626 cmd.Format(_T("git.exe reset --hard %s --"), headhash.ToString());
627 out.Empty();
628 if(g_Git.Run(cmd,&out,CP_UTF8))
630 CMessageBox::Show(NULL, CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD)) + _T("\r\n\r\n") + out, _T("TortoiseGit"), MB_OK);
633 Refresh();
635 break;
637 case ID_CHERRY_PICK:
639 if (m_bThreadRunning)
641 MessageBox(_T("This operation cannot be started while the log dialog is still loading commits."), _T("TortoiseGit"), MB_ICONEXCLAMATION);
642 break;
644 CRebaseDlg dlg;
645 dlg.m_IsCherryPick = TRUE;
646 dlg.m_Upstream = this->m_CurrentBranch;
647 POSITION pos = GetFirstSelectedItemPosition();
648 while(pos)
650 int indexNext = GetNextSelectedItem(pos);
651 dlg.m_CommitList.m_logEntries.push_back(((GitRevLoglist*)m_arShownList[indexNext])->m_CommitHash);
652 dlg.m_CommitList.m_LogCache.m_HashMap[((GitRevLoglist*)m_arShownList[indexNext])->m_CommitHash] = *(GitRevLoglist*)m_arShownList[indexNext];
653 dlg.m_CommitList.m_logEntries.GetGitRevAt(dlg.m_CommitList.m_logEntries.size() - 1).GetRebaseAction() |= LOGACTIONS_REBASE_PICK;
656 if(dlg.DoModal() == IDOK)
658 Refresh();
661 break;
662 case ID_REBASE_TO_VERSION:
664 if (m_bThreadRunning)
666 MessageBox(_T("This operation cannot be started while the log dialog is still loading commits."), _T("TortoiseGit"), MB_ICONEXCLAMATION);
667 break;
669 CRebaseDlg dlg;
670 auto refList = m_HashMap[pSelLogEntry->m_CommitHash];
671 dlg.m_Upstream = refList.empty() ? pSelLogEntry->m_CommitHash.ToString() : refList.front();
672 for (auto ref : refList)
674 if (ref.Left(11) == _T("refs/heads/"))
676 // 11=len("refs/heads/")
677 dlg.m_Upstream = ref.Mid(11);
678 break;
682 if(dlg.DoModal() == IDOK)
684 Refresh();
688 break;
690 case ID_STASH_SAVE:
691 if (CAppUtils::StashSave())
692 Refresh();
693 break;
695 case ID_STASH_POP:
696 if (CAppUtils::StashPop())
697 Refresh();
698 break;
700 case ID_STASH_LIST:
701 CAppUtils::RunTortoiseGitProc(_T("/command:reflog /ref:refs/stash"));
702 break;
704 case ID_REFLOG_STASH_APPLY:
705 CAppUtils::StashApply(pSelLogEntry->m_Ref);
706 break;
708 case ID_REFLOG_DEL:
710 CString str;
711 if (GetSelectedCount() > 1)
712 str.Format(IDS_PROC_DELETENREFS, GetSelectedCount());
713 else
714 str.Format(IDS_PROC_DELETEREF, pSelLogEntry->m_Ref);
716 if (CMessageBox::Show(NULL, str, _T("TortoiseGit"), 1, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
717 return;
719 std::vector<CString> refsToDelete;
720 POSITION pos = GetFirstSelectedItemPosition();
721 while (pos)
723 CString ref = ((GitRevLoglist*)m_arShownList[GetNextSelectedItem(pos)])->m_Ref;
724 if (ref.Find(_T("refs/")) == 0)
725 ref = ref.Mid(5);
726 int refpos = ref.ReverseFind('{');
727 if (refpos > 0 && ref.Mid(refpos - 1, 2) != _T("@{"))
728 ref = ref.Left(refpos) + _T("@")+ ref.Mid(refpos);
729 refsToDelete.push_back(ref);
732 for (auto revIt = refsToDelete.rbegin(); revIt != refsToDelete.rend(); ++revIt)
734 CString ref = *revIt;
735 CString cmd, out;
736 if (ref.Find(_T("stash")) == 0)
737 cmd.Format(_T("git.exe stash drop %s"), ref);
738 else
739 cmd.Format(_T("git.exe reflog delete %s"), ref);
741 if (g_Git.Run(cmd, &out, CP_UTF8))
742 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
744 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
747 break;
748 case ID_LOG:
750 CString cmd = _T("/command:log");
751 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
752 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
753 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
754 CAppUtils::RunTortoiseGitProc(cmd);
756 break;
757 case ID_CREATE_PATCH:
759 int select=this->GetSelectedCount();
760 CString cmd = _T("/command:formatpatch");
761 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
763 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
764 GitRev * r2 = NULL;
765 if(select == 1)
767 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString();
769 else
771 r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
772 if( this->m_IsOldFirst )
774 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString()+_T("~1");
775 cmd += _T(" /endrev:")+r2->m_CommitHash.ToString();
778 else
780 cmd += _T(" /startrev:")+r2->m_CommitHash.ToString()+_T("~1");
781 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
786 CAppUtils::RunTortoiseGitProc(cmd);
788 break;
789 case ID_BISECTSTART:
791 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
792 GitRev * last = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
793 ASSERT(first != NULL && last != NULL);
795 CString firstBad = first->m_CommitHash.ToString();
796 if (!m_HashMap[first->m_CommitHash].empty())
797 firstBad = m_HashMap[first->m_CommitHash].at(0);
798 CString lastGood = last->m_CommitHash.ToString();
799 if (!m_HashMap[last->m_CommitHash].empty())
800 lastGood = m_HashMap[last->m_CommitHash].at(0);
802 if (CAppUtils::BisectStart(lastGood, firstBad))
803 Refresh();
805 break;
806 case ID_REPOBROWSE:
808 CString sCmd;
809 sCmd.Format(_T("/command:repobrowser /path:\"%s\" /rev:%s"), g_Git.m_CurrentDir, pSelLogEntry->m_CommitHash.ToString());
810 CAppUtils::RunTortoiseGitProc(sCmd);
812 break;
813 case ID_PUSH:
815 CString guessAssociatedBranch = pSelLogEntry->m_CommitHash;
816 if (!m_HashMap[pSelLogEntry->m_CommitHash].empty() && m_HashMap[pSelLogEntry->m_CommitHash].at(0).Find(_T("refs/heads/")) == 0)
817 guessAssociatedBranch = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
818 if (CAppUtils::Push(guessAssociatedBranch))
819 Refresh();
821 break;
822 case ID_PULL:
824 if (CAppUtils::Pull())
825 Refresh();
827 break;
828 case ID_FETCH:
830 if (CAppUtils::Fetch())
831 Refresh();
833 break;
834 case ID_CLEANUP:
836 CString sCmd;
837 sCmd.Format(_T("/command:cleanup /path:\"%s\""), g_Git.m_CurrentDir);
838 CAppUtils::RunTortoiseGitProc(sCmd);
840 break;
841 case ID_SUBMODULE_UPDATE:
843 CString sCmd;
844 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), g_Git.m_CurrentDir);
845 CAppUtils::RunTortoiseGitProc(sCmd);
847 break;
848 case ID_SHOWBRANCHES:
850 CString cmd;
851 cmd.Format(_T("git.exe branch -a --contains %s"), pSelLogEntry->m_CommitHash.ToString());
852 CProgressDlg progress;
853 progress.m_AutoClose = AUTOCLOSE_NO;
854 progress.m_GitCmd = cmd;
855 progress.DoModal();
857 break;
858 case ID_DELETE:
860 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
861 if (!branch)
863 CMessageBox::Show(NULL,IDS_ERROR_NOREF,IDS_APPNAME,MB_OK|MB_ICONERROR);
864 return;
866 CString shortname;
867 if (CGit::GetShortName(*branch, shortname, _T("refs/remotes/")))
869 CString msg;
870 msg.Format(IDS_PROC_DELETEREMOTEBRANCH, *branch);
871 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)));
872 if (result == 1)
874 CString remoteName = shortname.Left(shortname.Find('/'));
875 shortname = shortname.Mid(shortname.Find('/') + 1);
876 if(CAppUtils::IsSSHPutty())
877 CAppUtils::LaunchPAgent(NULL, &remoteName);
879 CSysProgressDlg sysProgressDlg;
880 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
881 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
882 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
883 sysProgressDlg.SetShowProgressBar(false);
884 sysProgressDlg.ShowModal(this, true);
885 STRING_VECTOR list;
886 list.push_back(_T("refs/heads/") + shortname);
887 if (g_Git.DeleteRemoteRefs(remoteName, list))
888 CMessageBox::Show(NULL, g_Git.GetGitLastErr(_T("Could not delete remote ref."), CGit::GIT_CMD_PUSH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
889 sysProgressDlg.Stop();
891 else if (result == 2)
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;
899 else
900 return;
902 else if (CGit::GetShortName(*branch, shortname, _T("refs/stash")))
904 if (CMessageBox::Show(NULL, IDS_PROC_DELETEALLSTASH, IDS_APPNAME, 2, IDI_QUESTION, IDS_DELETEBUTTON, IDS_ABORTBUTTON) == 1)
906 CString cmd;
907 cmd.Format(_T("git.exe stash clear"));
908 CString out;
909 if (g_Git.Run(cmd, &out, CP_UTF8))
910 CMessageBox::Show(NULL, out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
912 else
913 return;
915 else
917 CString msg;
918 msg.Format(IDS_PROC_DELETEBRANCHTAG, *branch);
919 if (CMessageBox::Show(NULL, msg, _T("TortoiseGit"), 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
921 if (g_Git.DeleteRef(*branch))
923 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
924 return;
928 this->ReloadHashMap();
929 if (m_pFindDialog)
930 m_pFindDialog->RefreshList();
931 CRect rect;
932 this->GetItemRect(FirstSelect,&rect,LVIR_BOUNDS);
933 this->InvalidateRect(rect);
935 break;
937 case ID_FINDENTRY:
939 m_nSearchIndex = GetSelectionMark();
940 if (m_nSearchIndex < 0)
941 m_nSearchIndex = 0;
942 if (m_pFindDialog)
944 break;
946 else
948 m_pFindDialog = new CFindDlg();
949 m_pFindDialog->Create(this);
952 break;
953 case ID_MERGEREV:
955 CString str = pSelLogEntry->m_CommitHash.ToString();
956 if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
957 str = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
958 // we need an URL to complete this command, so error out if we can't get an URL
959 if(CAppUtils::Merge(&str))
961 this->Refresh();
964 break;
965 case ID_REVERTREV:
967 int parent = 0;
968 if (GetSelectedCount() == 1)
970 parent = cmd >> 16;
971 if (parent > pSelLogEntry->m_ParentHash.size())
973 CString str;
974 str.Format(IDS_PROC_NOPARENT, parent);
975 MessageBox(str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
976 return;
980 if (!this->RevertSelectedCommits(parent))
982 if (CMessageBox::Show(m_hWnd, IDS_REVREVERTED, IDS_APPNAME, 1, IDI_QUESTION, IDS_OKBUTTON, IDS_COMMITBUTTON) == 2)
984 CTGitPathList pathlist;
985 CTGitPathList selectedlist;
986 pathlist.AddPath(this->m_Path);
987 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
988 CString str;
989 CAppUtils::Commit(CString(), false, str, pathlist, selectedlist, bSelectFilesForCommit);
991 this->Refresh();
994 break;
995 case ID_EDITNOTE:
997 CAppUtils::EditNote(pSelLogEntry);
998 this->SetItemState(FirstSelect, 0, LVIS_SELECTED);
999 this->SetItemState(FirstSelect, LVIS_SELECTED, LVIS_SELECTED);
1001 break;
1002 default:
1003 //CMessageBox::Show(NULL,_T("Have not implemented"),_T("TortoiseGit"),MB_OK);
1004 break;
1005 #if 0
1007 case ID_BLAMECOMPARE:
1009 //user clicked on the menu item "compare with working copy"
1010 //now first get the revision which is selected
1011 if (PromptShown())
1013 GitDiff diff(this, this->m_hWnd, true);
1014 diff.SetHEADPeg(m_LogRevision);
1015 diff.ShowCompare(m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), false, true);
1017 else
1018 CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), m_LogRevision, false, false, true);
1020 break;
1021 case ID_BLAMEWITHPREVIOUS:
1023 //user clicked on the menu item "Compare and Blame with previous revision"
1024 if (PromptShown())
1026 GitDiff diff(this, this->m_hWnd, true);
1027 diff.SetHEADPeg(m_LogRevision);
1028 diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), false, true);
1030 else
1031 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
1033 break;
1035 case ID_OPENWITH:
1036 bOpenWith = true;
1037 case ID_OPEN:
1039 CProgressDlg progDlg;
1040 progDlg.SetTitle(IDS_APPNAME);
1041 progDlg.SetAnimation(IDR_DOWNLOAD);
1042 CString sInfoLine;
1043 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, m_path.GetWinPath(), (LPCTSTR)revSelected.ToString());
1044 progDlg.SetLine(1, sInfoLine, true);
1045 SetAndClearProgressInfo(&progDlg);
1046 progDlg.ShowModeless(m_hWnd);
1047 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, m_path, revSelected);
1048 bool bSuccess = true;
1049 if (!Cat(m_path, GitRev(GitRev::REV_HEAD), revSelected, tempfile))
1051 bSuccess = false;
1052 // try again, but with the selected revision as the peg revision
1053 if (!Cat(m_path, revSelected, revSelected, tempfile))
1055 progDlg.Stop();
1056 SetAndClearProgressInfo((HWND)NULL);
1057 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1058 EnableOKButton();
1059 break;
1061 bSuccess = true;
1063 if (bSuccess)
1065 progDlg.Stop();
1066 SetAndClearProgressInfo((HWND)NULL);
1067 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1068 if (!bOpenWith)
1069 CAppUtils::ShellOpen(tempfile.GetWinPath(), GetSafeHwnd());
1070 else
1071 CAppUtils::ShowOpenWithDialog(tempfile.GetWinPathString(), GetSafeHwnd());
1074 break;
1075 case ID_BLAME:
1077 CBlameDlg dlg;
1078 dlg.EndRev = revSelected;
1079 if (dlg.DoModal() == IDOK)
1081 CBlame blame;
1082 CString tempfile;
1083 CString logfile;
1084 tempfile = blame.BlameToTempFile(m_path, dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, _T(""), dlg.m_bIncludeMerge, TRUE, TRUE);
1085 if (!tempfile.IsEmpty())
1087 if (dlg.m_bTextView)
1089 //open the default text editor for the result file
1090 CAppUtils::StartTextViewer(tempfile);
1092 else
1094 CString sParams = _T("/path:\"") + m_path.GetGitPathString() + _T("\" ");
1095 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(m_path.GetFileOrDirectoryName()),sParams))
1097 break;
1101 else
1103 CMessageBox::Show(this->m_hWnd, blame.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1107 break;
1108 case ID_EXPORT:
1110 CString sCmd;
1111 sCmd.Format(_T("%s /command:export /path:\"%s\" /revision:%ld"),
1112 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseGitProc.exe")),
1113 (LPCTSTR)pathURL, (LONG)revSelected);
1114 CAppUtils::LaunchApplication(sCmd, NULL, false);
1116 break;
1117 case ID_VIEWREV:
1119 CString url = m_ProjectProperties.sWebViewerRev;
1120 url = GetAbsoluteUrlFromRelativeUrl(url);
1121 url.Replace(_T("%REVISION%"), revSelected.ToString());
1122 if (!url.IsEmpty())
1123 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1125 break;
1126 case ID_VIEWPATHREV:
1128 CString relurl = pathURL;
1129 CString sRoot = GetRepositoryRoot(CTGitPath(relurl));
1130 relurl = relurl.Mid(sRoot.GetLength());
1131 CString url = m_ProjectProperties.sWebViewerPathRev;
1132 url = GetAbsoluteUrlFromRelativeUrl(url);
1133 url.Replace(_T("%REVISION%"), revSelected.ToString());
1134 url.Replace(_T("%PATH%"), relurl);
1135 if (!url.IsEmpty())
1136 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1138 break;
1139 #endif
1141 } // switch (cmd)
1143 theApp.DoWaitCursor(-1);
1146 void CGitLogList::SetSelectedRebaseAction(int action)
1148 POSITION pos = GetFirstSelectedItemPosition();
1149 if (!pos) return;
1150 int index;
1151 while(pos)
1153 index = GetNextSelectedItem(pos);
1154 if (((GitRevLoglist*)m_arShownList[index])->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT | LOGACTIONS_REBASE_DONE))
1155 continue;
1156 ((GitRevLoglist*)m_arShownList[index])->GetRebaseAction() = action;
1157 CRect rect;
1158 this->GetItemRect(index,&rect,LVIR_BOUNDS);
1159 this->InvalidateRect(rect);
1162 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage);
1164 void CGitLogList::ShiftSelectedRebaseAction()
1166 POSITION pos = GetFirstSelectedItemPosition();
1167 int index;
1168 while(pos)
1170 index = GetNextSelectedItem(pos);
1171 int dummyAction = 0;
1172 int *action = &dummyAction;
1173 action = &((GitRevLoglist*)m_arShownList[index])->GetRebaseAction();
1174 switch (*action)
1176 case LOGACTIONS_REBASE_PICK:
1177 *action = LOGACTIONS_REBASE_SKIP;
1178 break;
1179 case LOGACTIONS_REBASE_SKIP:
1180 *action= LOGACTIONS_REBASE_EDIT;
1181 break;
1182 case LOGACTIONS_REBASE_EDIT:
1183 *action = LOGACTIONS_REBASE_SQUASH;
1184 break;
1185 case LOGACTIONS_REBASE_SQUASH:
1186 *action= LOGACTIONS_REBASE_PICK;
1187 break;
1189 CRect rect;
1190 this->GetItemRect(index, &rect, LVIR_BOUNDS);
1191 this->InvalidateRect(rect);