Make sure member variables are initialized correctly
[TortoiseGit.git] / src / TortoiseProc / GitLogListAction.cpp
blob62f57bf739ef696b7fe1ad163938a4c3c3af45b3
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 if (m_pFindDialog)
428 m_pFindDialog->RefreshList();
429 Invalidate();
430 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
432 break;
433 case ID_SWITCHTOREV:
435 CString str = pSelLogEntry->m_CommitHash.ToString();
436 // try to guess remote branch in order to recommend good branch name and tracking
437 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
439 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/remotes/")) == 0)
441 str = m_HashMap[pSelLogEntry->m_CommitHash][i];
442 break;
445 CAppUtils::Switch(str);
447 ReloadHashMap();
448 Invalidate();
449 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
450 break;
451 case ID_SWITCHBRANCH:
452 if(popmenu)
454 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
455 if(branch)
457 CString name;
458 if(branch->Find(_T("refs/heads/")) ==0 )
459 name = branch->Mid(11);
460 else
461 name = *branch;
463 CAppUtils::PerformSwitch(name);
465 ReloadHashMap();
466 Invalidate();
467 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
469 break;
470 case ID_RESET:
472 CString str = pSelLogEntry->m_CommitHash.ToString();
473 if (CAppUtils::GitReset(&str))
475 ResetWcRev(true);
476 ReloadHashMap();
477 Invalidate();
480 break;
481 case ID_REBASE_PICK:
482 SetSelectedRebaseAction(LOGACTIONS_REBASE_PICK);
483 break;
484 case ID_REBASE_EDIT:
485 SetSelectedRebaseAction(LOGACTIONS_REBASE_EDIT);
486 break;
487 case ID_REBASE_SQUASH:
488 SetSelectedRebaseAction(LOGACTIONS_REBASE_SQUASH);
489 break;
490 case ID_REBASE_SKIP:
491 SetSelectedRebaseAction(LOGACTIONS_REBASE_SKIP);
492 break;
493 case ID_COMBINE_COMMIT:
495 CString head;
496 CGitHash headhash;
497 CGitHash hashFirst,hashLast;
499 int headindex=GetHeadIndex();
500 if(headindex>=0) //incase show all branch, head is not the first commits.
502 head.Format(_T("HEAD~%d"),FirstSelect-headindex);
503 if (g_Git.GetHash(hashFirst, head))
505 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of first selected revision.")), _T("TortoiseGit"), MB_ICONERROR);
506 break;
509 head.Format(_T("HEAD~%d"),LastSelect-headindex);
510 if (g_Git.GetHash(hashLast, head))
512 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of last selected revision.")), _T("TortoiseGit"), MB_ICONERROR);
513 break;
517 GitRev* pFirstEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
518 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
519 if(pFirstEntry->m_CommitHash != hashFirst || pLastEntry->m_CommitHash != hashLast)
521 CMessageBox::Show(NULL, IDS_PROC_CANNOTCOMBINE, IDS_APPNAME, MB_OK);
522 break;
525 GitRev lastRevision;
528 lastRevision.GetParentFromHash(hashLast);
530 catch (char* msg)
532 CString err(msg);
533 MessageBox(_T("Could not get parent(s) of ") + hashLast.ToString() + _T(".\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
534 break;
537 if (g_Git.GetHash(headhash, _T("HEAD")))
539 MessageBox(g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
540 break;
543 if(!g_Git.CheckCleanWorkTree())
545 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
546 break;
548 CString cmd,out;
550 //Use throw to abort this process (reset back to original HEAD)
553 cmd.Format(_T("git.exe reset --hard %s --"), pFirstEntry->m_CommitHash.ToString());
554 if(g_Git.Run(cmd,&out,CP_UTF8))
556 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
557 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1)) + _T("\r\n\r\n") + out));
559 cmd.Format(_T("git.exe reset --mixed %s --"), hashLast.ToString());
560 if(g_Git.Run(cmd,&out,CP_UTF8))
562 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
563 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2)) + _T("\r\n\r\n")+out));
566 CTGitPathList PathList;
567 /* don't why must add --stat to get action status*/
568 /* first -z will be omitted by gitdll*/
569 if(g_Git.GetDiffPath(&PathList,&pFirstEntry->m_CommitHash,&hashLast,"-z --stat -r"))
571 CMessageBox::Show(NULL,_T("Get Diff file list error"),_T("TortoiseGit"),MB_OK);
572 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not get changed file list aborting...\r\n\r\n")+out));
575 for (int i = 0; i < PathList.GetCount(); ++i)
577 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_ADDED)
579 cmd.Format(_T("git.exe add -- \"%s\""), PathList[i].GetGitPathString());
580 if (g_Git.Run(cmd, &out, CP_UTF8))
582 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
583 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not add new file aborting...\r\n\r\n")+out));
587 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_DELETED)
589 cmd.Format(_T("git.exe rm -- \"%s\""), PathList[i].GetGitPathString());
590 if (g_Git.Run(cmd, &out, CP_UTF8))
592 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
593 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not rm file aborting...\r\n\r\n")+out));
598 CCommitDlg dlg;
599 for (int i = FirstSelect; i <= LastSelect; ++i)
601 GitRev* pRev = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
602 dlg.m_sLogMessage+=pRev->GetSubject()+_T("\n")+pRev->GetBody();
603 dlg.m_sLogMessage+=_T("\n");
605 dlg.m_bWholeProject=true;
606 dlg.m_bSelectFilesForCommit = true;
607 dlg.m_bForceCommitAmend=true;
608 CTGitPathList gpl;
609 gpl.AddPath(CTGitPath(g_Git.m_CurrentDir));
610 dlg.m_pathList = gpl;
611 if (lastRevision.ParentsCount() != 1)
613 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);
614 dlg.m_bAmendDiffToLastCommit = TRUE;
616 else
617 dlg.m_bAmendDiffToLastCommit = FALSE;
618 dlg.m_bNoPostActions=true;
619 dlg.m_AmendStr=dlg.m_sLogMessage;
621 if (dlg.DoModal() == IDOK)
623 if(pFirstEntry->m_CommitHash!=headhash)
625 if(CherryPickFrom(pFirstEntry->m_CommitHash.ToString(),headhash))
627 CString msg;
628 msg.Format(_T("Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n"));
629 throw std::exception(CUnicodeUtils::GetUTF8(msg));
633 else
634 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED))));
636 catch(std::exception& e)
638 CMessageBox::Show(NULL, CUnicodeUtils::GetUnicode(CStringA(e.what())), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
639 cmd.Format(_T("git.exe reset --hard %s --"), headhash.ToString());
640 out.Empty();
641 if(g_Git.Run(cmd,&out,CP_UTF8))
643 CMessageBox::Show(NULL, CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD)) + _T("\r\n\r\n") + out, _T("TortoiseGit"), MB_OK);
646 Refresh();
648 break;
650 case ID_CHERRY_PICK:
652 if (m_bThreadRunning)
654 MessageBox(_T("This operation cannot be started while the log dialog is still loading commits."), _T("TortoiseGit"), MB_ICONEXCLAMATION);
655 break;
657 CRebaseDlg dlg;
658 dlg.m_IsCherryPick = TRUE;
659 dlg.m_Upstream = this->m_CurrentBranch;
660 POSITION pos = GetFirstSelectedItemPosition();
661 while(pos)
663 int indexNext = GetNextSelectedItem(pos);
664 dlg.m_CommitList.m_logEntries.push_back( ((GitRev*)m_arShownList[indexNext])->m_CommitHash );
665 dlg.m_CommitList.m_LogCache.m_HashMap[((GitRev*)m_arShownList[indexNext])->m_CommitHash]=*(GitRev*)m_arShownList[indexNext];
666 dlg.m_CommitList.m_logEntries.GetGitRevAt(dlg.m_CommitList.m_logEntries.size() - 1).GetRebaseAction() |= LOGACTIONS_REBASE_PICK;
669 if(dlg.DoModal() == IDOK)
671 Refresh();
674 break;
675 case ID_REBASE_TO_VERSION:
677 if (m_bThreadRunning)
679 MessageBox(_T("This operation cannot be started while the log dialog is still loading commits."), _T("TortoiseGit"), MB_ICONEXCLAMATION);
680 break;
682 CRebaseDlg dlg;
683 auto refList = m_HashMap[pSelLogEntry->m_CommitHash];
684 dlg.m_Upstream = refList.empty() ? pSelLogEntry->m_CommitHash.ToString() : refList.front();
685 for (auto ref : refList)
687 if (ref.Left(11) == _T("refs/heads/"))
689 // 11=len("refs/heads/")
690 dlg.m_Upstream = ref.Mid(11);
691 break;
695 if(dlg.DoModal() == IDOK)
697 Refresh();
701 break;
703 case ID_STASH_SAVE:
704 if (CAppUtils::StashSave())
705 Refresh();
706 break;
708 case ID_STASH_POP:
709 if (CAppUtils::StashPop())
710 Refresh();
711 break;
713 case ID_STASH_LIST:
714 CAppUtils::RunTortoiseGitProc(_T("/command:reflog /ref:refs/stash"));
715 break;
717 case ID_REFLOG_STASH_APPLY:
718 CAppUtils::StashApply(pSelLogEntry->m_Ref);
719 break;
721 case ID_REFLOG_DEL:
723 CString str;
724 if (GetSelectedCount() > 1)
725 str.Format(IDS_PROC_DELETENREFS, GetSelectedCount());
726 else
727 str.Format(IDS_PROC_DELETEREF, pSelLogEntry->m_Ref);
729 if (CMessageBox::Show(NULL, str, _T("TortoiseGit"), 1, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
730 return;
732 std::vector<CString> refsToDelete;
733 POSITION pos = GetFirstSelectedItemPosition();
734 while (pos)
736 CString ref = ((GitRev *)m_arShownList[GetNextSelectedItem(pos)])->m_Ref;
737 if (ref.Find(_T("refs/")) == 0)
738 ref = ref.Mid(5);
739 int refpos = ref.ReverseFind('{');
740 if (refpos > 0 && ref.Mid(refpos - 1, 2) != _T("@{"))
741 ref = ref.Left(refpos) + _T("@")+ ref.Mid(refpos);
742 refsToDelete.push_back(ref);
745 for (auto revIt = refsToDelete.rbegin(); revIt != refsToDelete.rend(); ++revIt)
747 CString ref = *revIt;
748 CString cmd, out;
749 if (ref.Find(_T("stash")) == 0)
750 cmd.Format(_T("git.exe stash drop %s"), ref);
751 else
752 cmd.Format(_T("git.exe reflog delete %s"), ref);
754 if (g_Git.Run(cmd, &out, CP_UTF8))
755 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
757 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
760 break;
761 case ID_LOG:
763 CString cmd = _T("/command:log");
764 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
765 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
766 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
767 CAppUtils::RunTortoiseGitProc(cmd);
769 break;
770 case ID_CREATE_PATCH:
772 int select=this->GetSelectedCount();
773 CString cmd = _T("/command:formatpatch");
774 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
776 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
777 GitRev * r2 = NULL;
778 if(select == 1)
780 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString();
782 else
784 r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
785 if( this->m_IsOldFirst )
787 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString()+_T("~1");
788 cmd += _T(" /endrev:")+r2->m_CommitHash.ToString();
791 else
793 cmd += _T(" /startrev:")+r2->m_CommitHash.ToString()+_T("~1");
794 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
799 CAppUtils::RunTortoiseGitProc(cmd);
801 break;
802 case ID_BISECTSTART:
804 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
805 GitRev * last = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
806 ASSERT(first != NULL && last != NULL);
808 CString firstBad = first->m_CommitHash.ToString();
809 if (!m_HashMap[first->m_CommitHash].empty())
810 firstBad = m_HashMap[first->m_CommitHash].at(0);
811 CString lastGood = last->m_CommitHash.ToString();
812 if (!m_HashMap[last->m_CommitHash].empty())
813 lastGood = m_HashMap[last->m_CommitHash].at(0);
815 if (CAppUtils::BisectStart(lastGood, firstBad))
816 Refresh();
818 break;
819 case ID_REPOBROWSE:
821 CString sCmd;
822 sCmd.Format(_T("/command:repobrowser /path:\"%s\" /rev:%s"), g_Git.m_CurrentDir, pSelLogEntry->m_CommitHash.ToString());
823 CAppUtils::RunTortoiseGitProc(sCmd);
825 break;
826 case ID_PUSH:
828 CString guessAssociatedBranch;
829 if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
830 guessAssociatedBranch = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
831 if (CAppUtils::Push(guessAssociatedBranch))
832 Refresh();
834 break;
835 case ID_PULL:
837 if (CAppUtils::Pull())
838 Refresh();
840 break;
841 case ID_FETCH:
843 if (CAppUtils::Fetch())
844 Refresh();
846 break;
847 case ID_CLEANUP:
849 CString sCmd;
850 sCmd.Format(_T("/command:cleanup /path:\"%s\""), g_Git.m_CurrentDir);
851 CAppUtils::RunTortoiseGitProc(sCmd);
853 break;
854 case ID_SUBMODULE_UPDATE:
856 CString sCmd;
857 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), g_Git.m_CurrentDir);
858 CAppUtils::RunTortoiseGitProc(sCmd);
860 break;
861 case ID_SHOWBRANCHES:
863 CString cmd;
864 cmd.Format(_T("git.exe branch -a --contains %s"), pSelLogEntry->m_CommitHash.ToString());
865 CProgressDlg progress;
866 progress.m_AutoClose = AUTOCLOSE_NO;
867 progress.m_GitCmd = cmd;
868 progress.DoModal();
870 break;
871 case ID_DELETE:
873 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
874 if (!branch)
876 CMessageBox::Show(NULL,IDS_ERROR_NOREF,IDS_APPNAME,MB_OK|MB_ICONERROR);
877 return;
879 CString shortname;
880 if (CGit::GetShortName(*branch, shortname, _T("refs/remotes/")))
882 CString msg;
883 msg.Format(IDS_PROC_DELETEREMOTEBRANCH, *branch);
884 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)));
885 if (result == 1)
887 CString remoteName = shortname.Left(shortname.Find('/'));
888 shortname = shortname.Mid(shortname.Find('/') + 1);
889 if(CAppUtils::IsSSHPutty())
890 CAppUtils::LaunchPAgent(NULL, &remoteName);
892 CSysProgressDlg sysProgressDlg;
893 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
894 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
895 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
896 sysProgressDlg.SetShowProgressBar(false);
897 sysProgressDlg.ShowModal(this, true);
898 STRING_VECTOR list;
899 list.push_back(_T("refs/heads/") + shortname);
900 if (g_Git.DeleteRemoteRefs(remoteName, list))
901 CMessageBox::Show(NULL, g_Git.GetGitLastErr(_T("Could not delete remote ref."), CGit::GIT_CMD_PUSH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
902 sysProgressDlg.Stop();
904 else if (result == 2)
906 if (g_Git.DeleteRef(*branch))
908 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
909 return;
912 else
913 return;
915 else if (CGit::GetShortName(*branch, shortname, _T("refs/stash")))
917 if (CMessageBox::Show(NULL, IDS_PROC_DELETEALLSTASH, IDS_APPNAME, 2, IDI_QUESTION, IDS_DELETEBUTTON, IDS_ABORTBUTTON) == 1)
919 CString cmd;
920 cmd.Format(_T("git.exe stash clear"));
921 CString out;
922 if (g_Git.Run(cmd, &out, CP_UTF8))
923 CMessageBox::Show(NULL, out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
925 else
926 return;
928 else
930 CString msg;
931 msg.Format(IDS_PROC_DELETEBRANCHTAG, *branch);
932 if (CMessageBox::Show(NULL, msg, _T("TortoiseGit"), 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
934 if (g_Git.DeleteRef(*branch))
936 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
937 return;
941 this->ReloadHashMap();
942 if (m_pFindDialog)
943 m_pFindDialog->RefreshList();
944 CRect rect;
945 this->GetItemRect(FirstSelect,&rect,LVIR_BOUNDS);
946 this->InvalidateRect(rect);
948 break;
950 case ID_FINDENTRY:
952 m_nSearchIndex = GetSelectionMark();
953 if (m_nSearchIndex < 0)
954 m_nSearchIndex = 0;
955 if (m_pFindDialog)
957 break;
959 else
961 m_pFindDialog = new CFindDlg();
962 m_pFindDialog->Create(this);
965 break;
966 case ID_MERGEREV:
968 CString str = pSelLogEntry->m_CommitHash.ToString();
969 if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
970 str = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
971 // we need an URL to complete this command, so error out if we can't get an URL
972 if(CAppUtils::Merge(&str))
974 this->Refresh();
977 break;
978 case ID_REVERTREV:
980 int parent = 0;
981 if (GetSelectedCount() == 1)
983 parent = cmd >> 16;
984 if (parent > pSelLogEntry->m_ParentHash.size())
986 CString str;
987 str.Format(IDS_PROC_NOPARENT, parent);
988 MessageBox(str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
989 return;
993 if (!this->RevertSelectedCommits(parent))
995 if (CMessageBox::Show(m_hWnd, IDS_REVREVERTED, IDS_APPNAME, 1, IDI_QUESTION, IDS_OKBUTTON, IDS_COMMITBUTTON) == 2)
997 CTGitPathList pathlist;
998 CTGitPathList selectedlist;
999 pathlist.AddPath(this->m_Path);
1000 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
1001 CString str;
1002 CAppUtils::Commit(CString(), false, str, pathlist, selectedlist, bSelectFilesForCommit);
1004 this->Refresh();
1007 break;
1008 case ID_EDITNOTE:
1010 CAppUtils::EditNote(pSelLogEntry);
1011 this->SetItemState(FirstSelect, 0, LVIS_SELECTED);
1012 this->SetItemState(FirstSelect, LVIS_SELECTED, LVIS_SELECTED);
1014 break;
1015 default:
1016 //CMessageBox::Show(NULL,_T("Have not implemented"),_T("TortoiseGit"),MB_OK);
1017 break;
1018 #if 0
1020 case ID_BLAMECOMPARE:
1022 //user clicked on the menu item "compare with working copy"
1023 //now first get the revision which is selected
1024 if (PromptShown())
1026 GitDiff diff(this, this->m_hWnd, true);
1027 diff.SetHEADPeg(m_LogRevision);
1028 diff.ShowCompare(m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), false, true);
1030 else
1031 CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), m_LogRevision, false, false, true);
1033 break;
1034 case ID_BLAMEWITHPREVIOUS:
1036 //user clicked on the menu item "Compare and Blame with previous revision"
1037 if (PromptShown())
1039 GitDiff diff(this, this->m_hWnd, true);
1040 diff.SetHEADPeg(m_LogRevision);
1041 diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), false, true);
1043 else
1044 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
1046 break;
1048 case ID_OPENWITH:
1049 bOpenWith = true;
1050 case ID_OPEN:
1052 CProgressDlg progDlg;
1053 progDlg.SetTitle(IDS_APPNAME);
1054 progDlg.SetAnimation(IDR_DOWNLOAD);
1055 CString sInfoLine;
1056 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, m_path.GetWinPath(), (LPCTSTR)revSelected.ToString());
1057 progDlg.SetLine(1, sInfoLine, true);
1058 SetAndClearProgressInfo(&progDlg);
1059 progDlg.ShowModeless(m_hWnd);
1060 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, m_path, revSelected);
1061 bool bSuccess = true;
1062 if (!Cat(m_path, GitRev(GitRev::REV_HEAD), revSelected, tempfile))
1064 bSuccess = false;
1065 // try again, but with the selected revision as the peg revision
1066 if (!Cat(m_path, revSelected, revSelected, tempfile))
1068 progDlg.Stop();
1069 SetAndClearProgressInfo((HWND)NULL);
1070 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1071 EnableOKButton();
1072 break;
1074 bSuccess = true;
1076 if (bSuccess)
1078 progDlg.Stop();
1079 SetAndClearProgressInfo((HWND)NULL);
1080 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1081 if (!bOpenWith)
1082 CAppUtils::ShellOpen(tempfile.GetWinPath(), GetSafeHwnd());
1083 else
1084 CAppUtils::ShowOpenWithDialog(tempfile.GetWinPathString(), GetSafeHwnd());
1087 break;
1088 case ID_BLAME:
1090 CBlameDlg dlg;
1091 dlg.EndRev = revSelected;
1092 if (dlg.DoModal() == IDOK)
1094 CBlame blame;
1095 CString tempfile;
1096 CString logfile;
1097 tempfile = blame.BlameToTempFile(m_path, dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, _T(""), dlg.m_bIncludeMerge, TRUE, TRUE);
1098 if (!tempfile.IsEmpty())
1100 if (dlg.m_bTextView)
1102 //open the default text editor for the result file
1103 CAppUtils::StartTextViewer(tempfile);
1105 else
1107 CString sParams = _T("/path:\"") + m_path.GetGitPathString() + _T("\" ");
1108 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(m_path.GetFileOrDirectoryName()),sParams))
1110 break;
1114 else
1116 CMessageBox::Show(this->m_hWnd, blame.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1120 break;
1121 case ID_EXPORT:
1123 CString sCmd;
1124 sCmd.Format(_T("%s /command:export /path:\"%s\" /revision:%ld"),
1125 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseGitProc.exe")),
1126 (LPCTSTR)pathURL, (LONG)revSelected);
1127 CAppUtils::LaunchApplication(sCmd, NULL, false);
1129 break;
1130 case ID_VIEWREV:
1132 CString url = m_ProjectProperties.sWebViewerRev;
1133 url = GetAbsoluteUrlFromRelativeUrl(url);
1134 url.Replace(_T("%REVISION%"), revSelected.ToString());
1135 if (!url.IsEmpty())
1136 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1138 break;
1139 case ID_VIEWPATHREV:
1141 CString relurl = pathURL;
1142 CString sRoot = GetRepositoryRoot(CTGitPath(relurl));
1143 relurl = relurl.Mid(sRoot.GetLength());
1144 CString url = m_ProjectProperties.sWebViewerPathRev;
1145 url = GetAbsoluteUrlFromRelativeUrl(url);
1146 url.Replace(_T("%REVISION%"), revSelected.ToString());
1147 url.Replace(_T("%PATH%"), relurl);
1148 if (!url.IsEmpty())
1149 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1151 break;
1152 #endif
1154 } // switch (cmd)
1156 theApp.DoWaitCursor(-1);
1159 void CGitLogList::SetSelectedRebaseAction(int action)
1161 POSITION pos = GetFirstSelectedItemPosition();
1162 if (!pos) return;
1163 int index;
1164 while(pos)
1166 index = GetNextSelectedItem(pos);
1167 if (((GitRev*)m_arShownList[index])->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT | LOGACTIONS_REBASE_DONE))
1168 continue;
1169 ((GitRev*)m_arShownList[index])->GetRebaseAction() = action;
1170 CRect rect;
1171 this->GetItemRect(index,&rect,LVIR_BOUNDS);
1172 this->InvalidateRect(rect);
1175 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage);
1177 void CGitLogList::ShiftSelectedRebaseAction()
1179 POSITION pos = GetFirstSelectedItemPosition();
1180 int index;
1181 while(pos)
1183 index = GetNextSelectedItem(pos);
1184 int dummyAction = 0;
1185 int *action = &dummyAction;
1186 action = &((GitRev*)m_arShownList[index])->GetRebaseAction();
1187 switch (*action)
1189 case LOGACTIONS_REBASE_PICK:
1190 *action = LOGACTIONS_REBASE_SKIP;
1191 break;
1192 case LOGACTIONS_REBASE_SKIP:
1193 *action= LOGACTIONS_REBASE_EDIT;
1194 break;
1195 case LOGACTIONS_REBASE_EDIT:
1196 *action = LOGACTIONS_REBASE_SQUASH;
1197 break;
1198 case LOGACTIONS_REBASE_SQUASH:
1199 *action= LOGACTIONS_REBASE_PICK;
1200 break;
1202 CRect rect;
1203 this->GetItemRect(index, &rect, LVIR_BOUNDS);
1204 this->InvalidateRect(rect);