Fixed issue #2219: "add cherry picked from" is not applied when squashing/editing...
[TortoiseGit.git] / src / TortoiseProc / GitLogListAction.cpp
blobe850c8cdcb4fcc88bbf2c2ff3c5e9b6d029cf373
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 CRebaseDlg dlg;
639 dlg.m_IsCherryPick = TRUE;
640 dlg.m_Upstream = this->m_CurrentBranch;
641 POSITION pos = GetFirstSelectedItemPosition();
642 while(pos)
644 int indexNext = GetNextSelectedItem(pos);
645 dlg.m_CommitList.m_logEntries.push_back( ((GitRev*)m_arShownList[indexNext])->m_CommitHash );
646 dlg.m_CommitList.m_LogCache.m_HashMap[((GitRev*)m_arShownList[indexNext])->m_CommitHash]=*(GitRev*)m_arShownList[indexNext];
647 dlg.m_CommitList.m_logEntries.GetGitRevAt(dlg.m_CommitList.m_logEntries.size() - 1).GetRebaseAction() |= LOGACTIONS_REBASE_PICK;
650 if(dlg.DoModal() == IDOK)
652 Refresh();
655 break;
656 case ID_REBASE_TO_VERSION:
658 CRebaseDlg dlg;
659 auto refList = m_HashMap[pSelLogEntry->m_CommitHash];
660 dlg.m_Upstream = refList.empty() ? pSelLogEntry->m_CommitHash.ToString() : refList.front();
661 for (auto ref : refList)
663 if (ref.Left(11) == _T("refs/heads/"))
665 // 11=len("refs/heads/")
666 dlg.m_Upstream = ref.Mid(11);
667 break;
671 if(dlg.DoModal() == IDOK)
673 Refresh();
677 break;
679 case ID_STASH_SAVE:
680 if (CAppUtils::StashSave())
681 Refresh();
682 break;
684 case ID_STASH_POP:
685 if (CAppUtils::StashPop())
686 Refresh();
687 break;
689 case ID_STASH_LIST:
690 CAppUtils::RunTortoiseGitProc(_T("/command:reflog /ref:refs/stash"));
691 break;
693 case ID_REFLOG_STASH_APPLY:
694 CAppUtils::StashApply(pSelLogEntry->m_Ref);
695 break;
697 case ID_REFLOG_DEL:
699 CString str;
700 if (GetSelectedCount() > 1)
701 str.Format(IDS_PROC_DELETENREFS, GetSelectedCount());
702 else
703 str.Format(IDS_PROC_DELETEREF, pSelLogEntry->m_Ref);
705 if (CMessageBox::Show(NULL, str, _T("TortoiseGit"), 1, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
706 return;
708 std::vector<CString> refsToDelete;
709 POSITION pos = GetFirstSelectedItemPosition();
710 while (pos)
712 CString ref = ((GitRev *)m_arShownList[GetNextSelectedItem(pos)])->m_Ref;
713 if (ref.Find(_T("refs/")) == 0)
714 ref = ref.Mid(5);
715 int refpos = ref.ReverseFind('{');
716 if (refpos > 0 && ref.Mid(refpos - 1, 2) != _T("@{"))
717 ref = ref.Left(refpos) + _T("@")+ ref.Mid(refpos);
718 refsToDelete.push_back(ref);
721 for (auto revIt = refsToDelete.rbegin(); revIt != refsToDelete.rend(); ++revIt)
723 CString ref = *revIt;
724 CString cmd, out;
725 if (ref.Find(_T("stash")) == 0)
726 cmd.Format(_T("git.exe stash drop %s"), ref);
727 else
728 cmd.Format(_T("git.exe reflog delete %s"), ref);
730 if (g_Git.Run(cmd, &out, CP_UTF8))
731 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
733 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
736 break;
737 case ID_LOG:
739 CString cmd = _T("/command:log");
740 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
741 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
742 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
743 CAppUtils::RunTortoiseGitProc(cmd);
745 break;
746 case ID_CREATE_PATCH:
748 int select=this->GetSelectedCount();
749 CString cmd = _T("/command:formatpatch");
750 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
752 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
753 GitRev * r2 = NULL;
754 if(select == 1)
756 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString();
758 else
760 r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
761 if( this->m_IsOldFirst )
763 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString()+_T("~1");
764 cmd += _T(" /endrev:")+r2->m_CommitHash.ToString();
767 else
769 cmd += _T(" /startrev:")+r2->m_CommitHash.ToString()+_T("~1");
770 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
775 CAppUtils::RunTortoiseGitProc(cmd);
777 break;
778 case ID_BISECTSTART:
780 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
781 GitRev * last = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
782 ASSERT(first != NULL && last != NULL);
784 CString firstBad = first->m_CommitHash.ToString();
785 if (!m_HashMap[first->m_CommitHash].empty())
786 firstBad = m_HashMap[first->m_CommitHash].at(0);
787 CString lastGood = last->m_CommitHash.ToString();
788 if (!m_HashMap[last->m_CommitHash].empty())
789 lastGood = m_HashMap[last->m_CommitHash].at(0);
791 if (CAppUtils::BisectStart(lastGood, firstBad))
792 Refresh();
794 break;
795 case ID_REPOBROWSE:
797 CString sCmd;
798 sCmd.Format(_T("/command:repobrowser /path:\"%s\" /rev:%s"), g_Git.m_CurrentDir, pSelLogEntry->m_CommitHash.ToString());
799 CAppUtils::RunTortoiseGitProc(sCmd);
801 break;
802 case ID_PUSH:
804 CString guessAssociatedBranch;
805 if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
806 guessAssociatedBranch = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
807 if (CAppUtils::Push(guessAssociatedBranch))
808 Refresh();
810 break;
811 case ID_PULL:
813 if (CAppUtils::Pull())
814 Refresh();
816 break;
817 case ID_FETCH:
819 if (CAppUtils::Fetch(_T(""), true))
820 Refresh();
822 break;
823 case ID_SHOWBRANCHES:
825 CString cmd;
826 cmd.Format(_T("git.exe branch -a --contains %s"), pSelLogEntry->m_CommitHash.ToString());
827 CProgressDlg progress;
828 progress.m_AutoClose = AUTOCLOSE_NO;
829 progress.m_GitCmd = cmd;
830 progress.DoModal();
832 break;
833 case ID_DELETE:
835 bool showProgress = false;
836 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
837 if (!branch)
839 CMessageBox::Show(NULL,IDS_ERROR_NOREF,IDS_APPNAME,MB_OK|MB_ICONERROR);
840 return;
842 CString shortname;
843 CString cmd;
844 if (CGit::GetShortName(*branch, shortname, _T("refs/remotes/")))
846 CString msg;
847 msg.Format(IDS_PROC_DELETEREMOTEBRANCH, *branch);
848 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)));
849 if (result == 1)
851 CString remoteName = shortname.Left(shortname.Find('/'));
852 shortname = shortname.Mid(shortname.Find('/') + 1);
853 if(CAppUtils::IsSSHPutty())
854 CAppUtils::LaunchPAgent(NULL, &remoteName);
856 cmd.Format(L"git.exe push \"%s\" :refs/heads/%s", remoteName, shortname);
857 showProgress = true;
859 else if (result == 2)
861 if (g_Git.DeleteRef(*branch))
863 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
864 return;
867 else
868 return;
870 else if (CGit::GetShortName(*branch, shortname, _T("refs/stash")))
872 if (CMessageBox::Show(NULL, IDS_PROC_DELETEALLSTASH, IDS_APPNAME, 2, IDI_QUESTION, IDS_DELETEBUTTON, IDS_ABORTBUTTON) == 1)
873 cmd.Format(_T("git.exe stash clear"));
874 else
875 return;
877 else
879 CString msg;
880 msg.Format(IDS_PROC_DELETEBRANCHTAG, *branch);
881 if (CMessageBox::Show(NULL, msg, _T("TortoiseGit"), 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
883 if (g_Git.DeleteRef(*branch))
885 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
886 return;
890 if (!cmd.IsEmpty())
892 CSysProgressDlg sysProgressDlg;
893 if (showProgress)
895 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
896 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
897 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
898 sysProgressDlg.SetShowProgressBar(false);
899 sysProgressDlg.ShowModal(this, true);
901 CString out;
902 if(g_Git.Run(cmd,&out,CP_UTF8))
904 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
906 if (showProgress)
907 sysProgressDlg.Stop();
909 this->ReloadHashMap();
910 CRect rect;
911 this->GetItemRect(FirstSelect,&rect,LVIR_BOUNDS);
912 this->InvalidateRect(rect);
914 break;
916 case ID_FINDENTRY:
918 m_nSearchIndex = GetSelectionMark();
919 if (m_nSearchIndex < 0)
920 m_nSearchIndex = 0;
921 if (m_pFindDialog)
923 break;
925 else
927 m_pFindDialog = new CFindDlg();
928 m_pFindDialog->Create(this);
931 break;
932 case ID_MERGEREV:
934 CString str = pSelLogEntry->m_CommitHash.ToString();
935 if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
936 str = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
937 // we need an URL to complete this command, so error out if we can't get an URL
938 if(CAppUtils::Merge(&str))
940 this->Refresh();
943 break;
944 case ID_REVERTREV:
946 int parent = 0;
947 if (GetSelectedCount() == 1)
949 parent = cmd >> 16;
950 if (parent > pSelLogEntry->m_ParentHash.size())
952 CString str;
953 str.Format(IDS_PROC_NOPARENT, parent);
954 MessageBox(str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
955 return;
959 if (!this->RevertSelectedCommits(parent))
961 if (CMessageBox::Show(m_hWnd, IDS_REVREVERTED, IDS_APPNAME, 1, IDI_QUESTION, IDS_OKBUTTON, IDS_COMMITBUTTON) == 2)
963 CTGitPathList pathlist;
964 CTGitPathList selectedlist;
965 pathlist.AddPath(this->m_Path);
966 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
967 CString str;
968 CAppUtils::Commit(CString(), false, str, pathlist, selectedlist, bSelectFilesForCommit);
970 this->Refresh();
973 break;
974 case ID_EDITNOTE:
976 CAppUtils::EditNote(pSelLogEntry);
977 this->SetItemState(FirstSelect, 0, LVIS_SELECTED);
978 this->SetItemState(FirstSelect, LVIS_SELECTED, LVIS_SELECTED);
980 break;
981 default:
982 //CMessageBox::Show(NULL,_T("Have not implemented"),_T("TortoiseGit"),MB_OK);
983 break;
984 #if 0
986 case ID_BLAMECOMPARE:
988 //user clicked on the menu item "compare with working copy"
989 //now first get the revision which is selected
990 if (PromptShown())
992 GitDiff diff(this, this->m_hWnd, true);
993 diff.SetHEADPeg(m_LogRevision);
994 diff.ShowCompare(m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), false, true);
996 else
997 CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), m_LogRevision, false, false, true);
999 break;
1000 case ID_BLAMEWITHPREVIOUS:
1002 //user clicked on the menu item "Compare and Blame with previous revision"
1003 if (PromptShown())
1005 GitDiff diff(this, this->m_hWnd, true);
1006 diff.SetHEADPeg(m_LogRevision);
1007 diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), false, true);
1009 else
1010 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
1012 break;
1014 case ID_OPENWITH:
1015 bOpenWith = true;
1016 case ID_OPEN:
1018 CProgressDlg progDlg;
1019 progDlg.SetTitle(IDS_APPNAME);
1020 progDlg.SetAnimation(IDR_DOWNLOAD);
1021 CString sInfoLine;
1022 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, m_path.GetWinPath(), (LPCTSTR)revSelected.ToString());
1023 progDlg.SetLine(1, sInfoLine, true);
1024 SetAndClearProgressInfo(&progDlg);
1025 progDlg.ShowModeless(m_hWnd);
1026 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, m_path, revSelected);
1027 bool bSuccess = true;
1028 if (!Cat(m_path, GitRev(GitRev::REV_HEAD), revSelected, tempfile))
1030 bSuccess = false;
1031 // try again, but with the selected revision as the peg revision
1032 if (!Cat(m_path, revSelected, revSelected, tempfile))
1034 progDlg.Stop();
1035 SetAndClearProgressInfo((HWND)NULL);
1036 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1037 EnableOKButton();
1038 break;
1040 bSuccess = true;
1042 if (bSuccess)
1044 progDlg.Stop();
1045 SetAndClearProgressInfo((HWND)NULL);
1046 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1047 int ret = 0;
1048 if (!bOpenWith)
1049 ret = (int)ShellExecute(this->m_hWnd, NULL, tempfile.GetWinPath(), NULL, NULL, SW_SHOWNORMAL);
1050 if ((ret <= HINSTANCE_ERROR)||bOpenWith)
1052 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1053 cmd += tempfile.GetWinPathString() + _T(" ");
1054 CAppUtils::LaunchApplication(cmd, NULL, false);
1058 break;
1059 case ID_BLAME:
1061 CBlameDlg dlg;
1062 dlg.EndRev = revSelected;
1063 if (dlg.DoModal() == IDOK)
1065 CBlame blame;
1066 CString tempfile;
1067 CString logfile;
1068 tempfile = blame.BlameToTempFile(m_path, dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, _T(""), dlg.m_bIncludeMerge, TRUE, TRUE);
1069 if (!tempfile.IsEmpty())
1071 if (dlg.m_bTextView)
1073 //open the default text editor for the result file
1074 CAppUtils::StartTextViewer(tempfile);
1076 else
1078 CString sParams = _T("/path:\"") + m_path.GetGitPathString() + _T("\" ");
1079 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(m_path.GetFileOrDirectoryName()),sParams))
1081 break;
1085 else
1087 CMessageBox::Show(this->m_hWnd, blame.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1091 break;
1092 case ID_EXPORT:
1094 CString sCmd;
1095 sCmd.Format(_T("%s /command:export /path:\"%s\" /revision:%ld"),
1096 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseGitProc.exe")),
1097 (LPCTSTR)pathURL, (LONG)revSelected);
1098 CAppUtils::LaunchApplication(sCmd, NULL, false);
1100 break;
1101 case ID_VIEWREV:
1103 CString url = m_ProjectProperties.sWebViewerRev;
1104 url = GetAbsoluteUrlFromRelativeUrl(url);
1105 url.Replace(_T("%REVISION%"), revSelected.ToString());
1106 if (!url.IsEmpty())
1107 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1109 break;
1110 case ID_VIEWPATHREV:
1112 CString relurl = pathURL;
1113 CString sRoot = GetRepositoryRoot(CTGitPath(relurl));
1114 relurl = relurl.Mid(sRoot.GetLength());
1115 CString url = m_ProjectProperties.sWebViewerPathRev;
1116 url = GetAbsoluteUrlFromRelativeUrl(url);
1117 url.Replace(_T("%REVISION%"), revSelected.ToString());
1118 url.Replace(_T("%PATH%"), relurl);
1119 if (!url.IsEmpty())
1120 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1122 break;
1123 #endif
1125 } // switch (cmd)
1127 theApp.DoWaitCursor(-1);
1130 void CGitLogList::SetSelectedRebaseAction(int action)
1132 POSITION pos = GetFirstSelectedItemPosition();
1133 if (!pos) return;
1134 int index;
1135 while(pos)
1137 index = GetNextSelectedItem(pos);
1138 if (((GitRev*)m_arShownList[index])->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT | LOGACTIONS_REBASE_DONE))
1139 continue;
1140 ((GitRev*)m_arShownList[index])->GetRebaseAction() = action;
1141 CRect rect;
1142 this->GetItemRect(index,&rect,LVIR_BOUNDS);
1143 this->InvalidateRect(rect);
1146 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage);
1148 void CGitLogList::ShiftSelectedRebaseAction()
1150 POSITION pos = GetFirstSelectedItemPosition();
1151 int index;
1152 while(pos)
1154 index = GetNextSelectedItem(pos);
1155 int dummyAction = 0;
1156 int *action = &dummyAction;
1157 action = &((GitRev*)m_arShownList[index])->GetRebaseAction();
1158 switch (*action)
1160 case LOGACTIONS_REBASE_PICK:
1161 *action = LOGACTIONS_REBASE_SKIP;
1162 break;
1163 case LOGACTIONS_REBASE_SKIP:
1164 *action= LOGACTIONS_REBASE_EDIT;
1165 break;
1166 case LOGACTIONS_REBASE_EDIT:
1167 *action = LOGACTIONS_REBASE_SQUASH;
1168 break;
1169 case LOGACTIONS_REBASE_SQUASH:
1170 *action= LOGACTIONS_REBASE_PICK;
1171 break;
1173 CRect rect;
1174 this->GetItemRect(index, &rect, LVIR_BOUNDS);
1175 this->InvalidateRect(rect);