Fixed issue #1899: Support cherry-picking merge revisions
[TortoiseGit.git] / src / TortoiseProc / GitLogListAction.cpp
blob79d33c2739b83b534efbfc4a62c0169c0685e2c3
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2016 - 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 static void GetFirstEntryStartingWith(STRING_VECTOR& heystack, const CString& needle, CString& result)
48 auto it = std::find_if(heystack.cbegin(), heystack.cend(), [&needle](const CString& entry) { return entry.Find(needle) == 0; });
49 if (it == heystack.cend())
50 return;
51 result = *it;
54 int CGitLogList::RevertSelectedCommits(int parent)
56 CSysProgressDlg progress;
57 int ret = -1;
59 #if 0
60 if(!g_Git.CheckCleanWorkTree())
62 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
65 #endif
67 if (this->GetSelectedCount() > 1)
69 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_REVERTCOMMIT)));
70 progress.SetAnimation(IDR_MOVEANI);
71 progress.SetTime(true);
72 progress.ShowModeless(this);
75 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
77 POSITION pos = GetFirstSelectedItemPosition();
78 int i=0;
79 while(pos)
81 int index = GetNextSelectedItem(pos);
82 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(index));
84 if (progress.IsVisible())
86 progress.FormatNonPathLine(1, IDS_PROC_REVERTCOMMIT, r1->m_CommitHash.ToString());
87 progress.FormatNonPathLine(2, _T("%s"), (LPCTSTR)r1->GetSubject());
88 progress.SetProgress(i, this->GetSelectedCount());
90 ++i;
92 if(r1->m_CommitHash.IsEmpty())
93 continue;
95 if (g_Git.GitRevert(parent, r1->m_CommitHash))
97 CString str;
98 str.LoadString(IDS_SVNACTION_FAILEDREVERT);
99 str = g_Git.GetGitLastErr(str, CGit::GIT_CMD_REVERT);
100 if( GetSelectedCount() == 1)
101 CMessageBox::Show(NULL, str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
102 else
104 if(CMessageBox::Show(NULL, str, _T("TortoiseGit"),2 , IDI_ERROR, CString(MAKEINTRESOURCE(IDS_SKIPBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
106 return ret;
110 else
112 ret =0;
115 if (progress.HasUserCancelled())
116 break;
118 return ret;
120 int CGitLogList::CherryPickFrom(CString from, CString to)
122 CLogDataVector logs(&m_LogCache);
123 CString range;
124 range.Format(_T("%s..%s"), (LPCTSTR)from, (LPCTSTR)to);
125 if (logs.ParserFromLog(nullptr, 0, 0, &range))
126 return -1;
128 if (logs.empty())
129 return 0;
131 CSysProgressDlg progress;
132 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_CHERRYPICK)));
133 progress.SetAnimation(IDR_MOVEANI);
134 progress.SetTime(true);
135 progress.ShowModeless(this);
137 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
139 for (int i = (int)logs.size() - 1; i >= 0; i--)
141 if (progress.IsVisible())
143 progress.FormatNonPathLine(1, IDS_PROC_PICK, logs.GetGitRevAt(i).m_CommitHash.ToString());
144 progress.FormatNonPathLine(2, _T("%s"), (LPCTSTR)logs.GetGitRevAt(i).GetSubject());
145 progress.SetProgress64(logs.size() - i, logs.size());
147 if (progress.HasUserCancelled())
149 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_USERCANCELLED))));
151 CString cmd,out;
152 cmd.Format(_T("git.exe cherry-pick %s"), (LPCTSTR)logs.GetGitRevAt(i).m_CommitHash.ToString());
153 out.Empty();
154 if(g_Git.Run(cmd,&out,CP_UTF8))
156 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_CHERRYPICKFAILED)) + _T(":\r\n\r\n") + out));
160 return 0;
163 int CGitLogList::DeleteRef(const CString& ref)
165 CString shortname;
166 if (CGit::GetShortName(ref, shortname, _T("refs/remotes/")))
168 CString msg;
169 msg.Format(IDS_PROC_DELETEREMOTEBRANCH, (LPCTSTR)ref);
170 int result = CMessageBox::Show(nullptr, msg, _T("TortoiseGit"), 3, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_PROC_DELETEREMOTEBRANCH_LOCALREMOTE)), CString(MAKEINTRESOURCE(IDS_PROC_DELETEREMOTEBRANCH_LOCAL)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON)));
171 if (result == 1)
173 CString remoteName = shortname.Left(shortname.Find(L'/'));
174 shortname = shortname.Mid(shortname.Find(L'/') + 1);
175 if (CAppUtils::IsSSHPutty())
176 CAppUtils::LaunchPAgent(nullptr, &remoteName);
178 CSysProgressDlg sysProgressDlg;
179 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
180 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
181 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
182 sysProgressDlg.SetShowProgressBar(false);
183 sysProgressDlg.ShowModal(this, true);
184 STRING_VECTOR list;
185 list.push_back(_T("refs/heads/") + shortname);
186 if (g_Git.DeleteRemoteRefs(remoteName, list))
187 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(_T("Could not delete remote ref."), CGit::GIT_CMD_PUSH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
188 sysProgressDlg.Stop();
189 return TRUE;
191 else if (result == 2)
193 if (g_Git.DeleteRef(ref))
195 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
196 return FALSE;
198 return TRUE;
200 return FALSE;
202 else if (CGit::GetShortName(ref, shortname, _T("refs/stash")))
204 CString err;
205 std::vector<GitRevLoglist> stashList;
206 size_t count = !GitRevLoglist::GetRefLog(ref, stashList, err) ? stashList.size() : 0;
207 CString msg;
208 msg.Format(IDS_PROC_DELETEALLSTASH, count);
209 if (CMessageBox::Show(nullptr, msg, _T("TortoiseGit"), 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
211 CString sCmd;
212 sCmd.Format(_T("git.exe stash clear"));
213 CString out;
214 if (g_Git.Run(sCmd, &out, CP_UTF8))
215 CMessageBox::Show(nullptr, out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
216 return TRUE;
218 return FALSE;
221 CString msg;
222 msg.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)ref);
223 if (CMessageBox::Show(nullptr, msg, _T("TortoiseGit"), 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
225 if (g_Git.DeleteRef(ref))
227 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
228 return FALSE;
230 return TRUE;
232 return FALSE;
235 void CGitLogList::ContextMenuAction(int cmd,int FirstSelect, int LastSelect, CMenu *popmenu)
237 POSITION pos = GetFirstSelectedItemPosition();
238 int indexNext = GetNextSelectedItem(pos);
239 if (indexNext < 0)
240 return;
242 GitRevLoglist* pSelLogEntry = reinterpret_cast<GitRevLoglist*>(m_arShownList.GetAt(indexNext));
244 theApp.DoWaitCursor(1);
245 switch (cmd&0xFFFF)
247 case ID_COMMIT:
249 CTGitPathList pathlist;
250 CTGitPathList selectedlist;
251 pathlist.AddPath(this->m_Path);
252 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
253 CString str;
254 CAppUtils::Commit(CString(),false,str,
255 pathlist,selectedlist,bSelectFilesForCommit);
256 //this->Refresh();
257 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH,0);
259 break;
260 case ID_MERGE_ABORT:
262 if (CAppUtils::MergeAbort())
263 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH, 0);
265 break;
266 case ID_GNUDIFF1: // compare with WC, unified
268 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
269 bool bMerge = false, bCombine = false;
270 CString hash2;
271 if(!r1->m_CommitHash.IsEmpty())
273 CString merge;
274 cmd >>= 16;
275 if( (cmd&0xFFFF) == 0xFFFF)
276 bMerge = true;
277 else if((cmd&0xFFFF) == 0xFFFE)
278 bCombine = true;
279 else if ((cmd & 0xFFFF) == 0xFFFD)
281 CString tempfile = GetTempFile();
282 CString gitcmd = _T("git.exe diff-tree --cc ") + r1->m_CommitHash.ToString();
283 CString lastErr;
284 if (g_Git.RunLogFile(gitcmd, tempfile, &lastErr))
286 MessageBox(lastErr, _T("TortoiseGit"), MB_ICONERROR);
287 break;
292 CStdioFile file(tempfile, CFile::typeText | CFile::modeRead | CFile::shareDenyWrite);
293 CString strLine;
294 bool isHash = file.ReadString(strLine) && r1->m_CommitHash.ToString() == strLine;
295 bool more = isHash && file.ReadString(strLine) && !strLine.IsEmpty();
296 if (!more)
298 CMessageBox::Show(nullptr, IDS_NOCHANGEAFTERMERGE, IDS_APPNAME, MB_OK);
299 break;
302 catch (CFileException* e)
304 e->Delete();
307 CAppUtils::StartUnifiedDiffViewer(tempfile, _T("dd"));
308 break;
310 else
312 if ((size_t)cmd > r1->m_ParentHash.size())
314 CString str;
315 str.Format(IDS_PROC_NOPARENT, cmd);
316 MessageBox(str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
317 return;
319 else
321 if(cmd>0)
322 hash2 = r1->m_ParentHash[cmd-1].ToString();
325 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), hash2, CTGitPath(), r1->m_CommitHash.ToString(), false, false, false, bMerge, bCombine);
327 else
328 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), _T("HEAD"), CTGitPath(), GitRev::GetWorkingCopy(), false, false, false, bMerge, bCombine);
330 break;
332 case ID_GNUDIFF2: // compare two revisions, unified
334 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
335 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
336 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), r2->m_CommitHash.ToString(), CTGitPath(), r1->m_CommitHash.ToString());
338 break;
340 case ID_COMPARETWO: // compare two revisions
342 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
343 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
344 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
345 CGitDiff::DiffCommit(this->m_Path, r1,r2);
346 else
348 CString path1 = m_Path.GetGitPathString();
349 // start with 1 (0 = working copy changes)
350 for (int i = 1; i < FirstSelect; ++i)
352 GitRevLoglist* first = reinterpret_cast<GitRevLoglist*>(m_arShownList.GetAt(i));
353 CTGitPathList list = first->GetFiles(NULL);
354 const CTGitPath* file = list.LookForGitPath(path1);
355 if (file && !file->GetGitOldPathString().IsEmpty())
356 path1 = file->GetGitOldPathString();
358 CString path2 = path1;
359 for (int i = FirstSelect; i < LastSelect; ++i)
361 GitRevLoglist* first = reinterpret_cast<GitRevLoglist*>(m_arShownList.GetAt(i));
362 CTGitPathList list = first->GetFiles(NULL);
363 const CTGitPath* file = list.LookForGitPath(path2);
364 if (file && !file->GetGitOldPathString().IsEmpty())
365 path2 = file->GetGitOldPathString();
367 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), r1, r2);
371 break;
373 case ID_COMPARE: // compare revision with WC
375 GitRevLoglist* r1 = &m_wcRev;
376 GitRevLoglist* r2 = pSelLogEntry;
378 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
379 CGitDiff::DiffCommit(this->m_Path, r1,r2);
380 else
382 CString path1 = m_Path.GetGitPathString();
383 // start with 1 (0 = working copy changes)
384 for (int i = 1; i < FirstSelect; ++i)
386 GitRevLoglist* first = reinterpret_cast<GitRevLoglist*>(m_arShownList.GetAt(i));
387 CTGitPathList list = first->GetFiles(NULL);
388 const CTGitPath* file = list.LookForGitPath(path1);
389 if (file && !file->GetGitOldPathString().IsEmpty())
390 path1 = file->GetGitOldPathString();
392 CGitDiff::DiffCommit(m_Path, CTGitPath(path1), r1, r2);
395 //user clicked on the menu item "compare with working copy"
396 //if (PromptShown())
398 // GitDiff diff(this, m_hWnd, true);
399 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
400 // diff.SetHEADPeg(m_LogRevision);
401 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
403 //else
404 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
406 break;
408 case ID_COMPAREWITHPREVIOUS:
410 CFileDiffDlg dlg;
412 if (pSelLogEntry->m_ParentHash.empty())
414 if (pSelLogEntry->GetParentFromHash(pSelLogEntry->m_CommitHash))
415 MessageBox(pSelLogEntry->GetLastErr(), _T("TortoiseGit"), MB_ICONERROR);
418 if (!pSelLogEntry->m_ParentHash.empty())
419 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
421 cmd>>=16;
422 cmd&=0xFFFF;
424 if(cmd == 0)
425 cmd=1;
427 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
428 CGitDiff::DiffCommit(m_Path, pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString());
429 else
431 CString path1 = m_Path.GetGitPathString();
432 // start with 1 (0 = working copy changes)
433 for (int i = 1; i < indexNext; ++i)
435 GitRevLoglist* first = reinterpret_cast<GitRevLoglist*>(m_arShownList.GetAt(i));
436 CTGitPathList list = first->GetFiles(NULL);
437 const CTGitPath* file = list.LookForGitPath(path1);
438 if (file && !file->GetGitOldPathString().IsEmpty())
439 path1 = file->GetGitOldPathString();
441 CString path2 = path1;
442 GitRevLoglist* first = reinterpret_cast<GitRevLoglist*>(m_arShownList.GetAt(indexNext));
443 CTGitPathList list = first->GetFiles(NULL);
444 const CTGitPath* file = list.LookForGitPath(path2);
445 if (file && !file->GetGitOldPathString().IsEmpty())
446 path2 = file->GetGitOldPathString();
448 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString());
451 else
453 CMessageBox::Show(NULL, IDS_PROC_NOPREVIOUSVERSION, IDS_APPNAME, MB_OK);
455 //if (PromptShown())
457 // GitDiff diff(this, m_hWnd, true);
458 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
459 // diff.SetHEADPeg(m_LogRevision);
460 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
462 //else
463 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
465 break;
466 case ID_LOG_VIEWRANGE:
467 case ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE:
469 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(LastSelect));
471 CString sep = _T("..");
472 if ((cmd & 0xFFFF) == ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE)
473 sep = _T("...");
475 CString cmdline;
476 cmdline.Format(_T("/command:log /path:\"%s\" /range:\"%s%s%s\""),
477 (LPCTSTR)g_Git.CombinePath(m_Path), (LPCTSTR)pLastEntry->m_CommitHash.ToString(), (LPCTSTR)sep, (LPCTSTR)pSelLogEntry->m_CommitHash.ToString());
478 CAppUtils::RunTortoiseGitProc(cmdline);
480 break;
481 case ID_COPYCLIPBOARD:
483 CopySelectionToClipBoard();
485 break;
486 case ID_COPYCLIPBOARDMESSAGES:
488 if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0)
489 CopySelectionToClipBoard(ID_COPY_SUBJECT);
490 else
491 CopySelectionToClipBoard(ID_COPY_MESSAGE);
493 break;
494 case ID_COPYHASH:
496 CopySelectionToClipBoard(ID_COPY_HASH);
498 break;
499 case ID_EXPORT:
501 CString str=pSelLogEntry->m_CommitHash.ToString();
502 // try to get the tag
503 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], _T("refs/tags/"), str);
504 CAppUtils::Export(&str, &m_Path);
506 break;
507 case ID_CREATE_BRANCH:
508 case ID_CREATE_TAG:
510 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd & 0xFFFF) : nullptr;
511 CString str = pSelLogEntry->m_CommitHash.ToString();
512 if (branch)
513 str = *branch;
514 else // try to guess remote branch in order to enable tracking
515 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], _T("refs/remotes/"), str);
517 CAppUtils::CreateBranchTag((cmd&0xFFFF) == ID_CREATE_TAG, &str);
518 ReloadHashMap();
519 if (m_pFindDialog)
520 m_pFindDialog->RefreshList();
521 Invalidate();
522 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
524 break;
525 case ID_SWITCHTOREV:
527 CString str = pSelLogEntry->m_CommitHash.ToString();
528 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd & 0xFFFF) : nullptr;
529 if (branch)
530 str = *branch;
531 else // try to guess remote branch in order to recommend good branch name and tracking
532 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], _T("refs/remotes/"), str);
534 CAppUtils::Switch(str);
536 ReloadHashMap();
537 Invalidate();
538 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
539 break;
540 case ID_SWITCHBRANCH:
541 if(popmenu)
543 const CString* branch = (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
544 if(branch)
546 CString name = *branch;
547 CGit::GetShortName(*branch, name, L"refs/heads/");
548 CAppUtils::PerformSwitch(name);
550 ReloadHashMap();
551 Invalidate();
552 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
554 break;
555 case ID_RESET:
557 CString str = pSelLogEntry->m_CommitHash.ToString();
558 if (CAppUtils::GitReset(&str))
560 ResetWcRev(true);
561 ReloadHashMap();
562 Invalidate();
565 break;
566 case ID_REBASE_PICK:
567 SetSelectedRebaseAction(LOGACTIONS_REBASE_PICK);
568 break;
569 case ID_REBASE_EDIT:
570 SetSelectedRebaseAction(LOGACTIONS_REBASE_EDIT);
571 break;
572 case ID_REBASE_SQUASH:
573 SetSelectedRebaseAction(LOGACTIONS_REBASE_SQUASH);
574 break;
575 case ID_REBASE_SKIP:
576 SetSelectedRebaseAction(LOGACTIONS_REBASE_SKIP);
577 break;
578 case ID_COMBINE_COMMIT:
580 CString head;
581 CGitHash headhash;
582 CGitHash hashFirst,hashLast;
584 int headindex=GetHeadIndex();
585 if(headindex>=0) //incase show all branch, head is not the first commits.
587 head.Format(_T("HEAD~%d"),FirstSelect-headindex);
588 if (g_Git.GetHash(hashFirst, head))
590 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of first selected revision.")), _T("TortoiseGit"), MB_ICONERROR);
591 break;
594 head.Format(_T("HEAD~%d"),LastSelect-headindex);
595 if (g_Git.GetHash(hashLast, head))
597 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of last selected revision.")), _T("TortoiseGit"), MB_ICONERROR);
598 break;
602 GitRev* pFirstEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
603 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
604 if(pFirstEntry->m_CommitHash != hashFirst || pLastEntry->m_CommitHash != hashLast)
606 CMessageBox::Show(NULL, IDS_PROC_CANNOTCOMBINE, IDS_APPNAME, MB_OK);
607 break;
610 GitRev lastRevision;
611 if (lastRevision.GetParentFromHash(hashLast))
613 MessageBox(lastRevision.GetLastErr(), _T("TortoiseGit"), MB_ICONERROR);
614 break;
617 if (g_Git.GetHash(headhash, _T("HEAD")))
619 MessageBox(g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
620 break;
623 if(!g_Git.CheckCleanWorkTree())
625 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
626 break;
628 CString sCmd, out;
630 //Use throw to abort this process (reset back to original HEAD)
633 sCmd.Format(_T("git.exe reset --hard %s --"), (LPCTSTR)pFirstEntry->m_CommitHash.ToString());
634 if(g_Git.Run(sCmd, &out, CP_UTF8))
636 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
637 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1)) + _T("\r\n\r\n") + out));
639 sCmd.Format(_T("git.exe reset --mixed %s --"), (LPCTSTR)hashLast.ToString());
640 if(g_Git.Run(sCmd, &out, CP_UTF8))
642 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
643 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2)) + _T("\r\n\r\n")+out));
646 CTGitPathList PathList;
647 /* don't why must add --stat to get action status*/
648 /* first -z will be omitted by gitdll*/
649 if(g_Git.GetDiffPath(&PathList,&pFirstEntry->m_CommitHash,&hashLast,"-z --stat -r"))
651 CMessageBox::Show(NULL,_T("Get Diff file list error"),_T("TortoiseGit"),MB_OK);
652 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not get changed file list aborting...\r\n\r\n")+out));
655 for (int i = 0; i < PathList.GetCount(); ++i)
657 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_ADDED)
659 sCmd.Format(_T("git.exe add -- \"%s\""), (LPCTSTR)PathList[i].GetGitPathString());
660 if (g_Git.Run(sCmd, &out, CP_UTF8))
662 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
663 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not add new file aborting...\r\n\r\n")+out));
667 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_DELETED)
669 sCmd.Format(_T("git.exe rm -- \"%s\""), (LPCTSTR)PathList[i].GetGitPathString());
670 if (g_Git.Run(sCmd, &out, CP_UTF8))
672 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
673 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not rm file aborting...\r\n\r\n")+out));
678 CCommitDlg dlg;
679 for (int i = FirstSelect; i <= LastSelect; ++i)
681 GitRev* pRev = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
682 dlg.m_sLogMessage+=pRev->GetSubject()+_T("\n")+pRev->GetBody();
683 dlg.m_sLogMessage+=_T("\n");
685 dlg.m_bWholeProject=true;
686 dlg.m_bSelectFilesForCommit = true;
687 dlg.m_bForceCommitAmend=true;
688 CTGitPathList gpl;
689 gpl.AddPath(CTGitPath(g_Git.m_CurrentDir));
690 dlg.m_pathList = gpl;
691 if (lastRevision.ParentsCount() != 1)
693 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);
694 dlg.m_bAmendDiffToLastCommit = TRUE;
696 else
697 dlg.m_bAmendDiffToLastCommit = FALSE;
698 dlg.m_bNoPostActions=true;
699 dlg.m_AmendStr=dlg.m_sLogMessage;
701 if (dlg.DoModal() == IDOK)
703 if(pFirstEntry->m_CommitHash!=headhash)
705 if(CherryPickFrom(pFirstEntry->m_CommitHash.ToString(),headhash))
707 CString msg;
708 msg.Format(_T("Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n"));
709 throw std::exception(CUnicodeUtils::GetUTF8(msg));
713 else
714 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_USERCANCELLED))));
716 catch(std::exception& e)
718 CMessageBox::Show(NULL, CUnicodeUtils::GetUnicode(CStringA(e.what())), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
719 sCmd.Format(_T("git.exe reset --hard %s --"), (LPCTSTR)headhash.ToString());
720 out.Empty();
721 if(g_Git.Run(sCmd, &out, CP_UTF8))
723 CMessageBox::Show(NULL, CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD)) + _T("\r\n\r\n") + out, _T("TortoiseGit"), MB_OK);
726 Refresh();
728 break;
730 case ID_CHERRY_PICK:
732 if (m_bThreadRunning)
734 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_LOG_ONLYONCE, IDS_APPNAME, MB_ICONEXCLAMATION);
735 break;
737 CRebaseDlg dlg;
738 dlg.m_IsCherryPick = TRUE;
739 dlg.m_Upstream = this->m_CurrentBranch;
740 POSITION pos2 = GetFirstSelectedItemPosition();
741 while(pos2)
743 int indexNext2 = GetNextSelectedItem(pos2);
744 dlg.m_CommitList.m_logEntries.push_back(((GitRevLoglist*)m_arShownList[indexNext2])->m_CommitHash);
745 dlg.m_CommitList.m_LogCache.m_HashMap[((GitRevLoglist*)m_arShownList[indexNext2])->m_CommitHash] = *(GitRevLoglist*)m_arShownList[indexNext2];
746 dlg.m_CommitList.m_logEntries.GetGitRevAt(dlg.m_CommitList.m_logEntries.size() - 1).GetRebaseAction() |= LOGACTIONS_REBASE_PICK;
749 if(dlg.DoModal() == IDOK)
751 Refresh();
754 break;
755 case ID_REBASE_TO_VERSION:
757 if (m_bThreadRunning)
759 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_LOG_ONLYONCE, IDS_APPNAME, MB_ICONEXCLAMATION);
760 break;
762 CRebaseDlg dlg;
763 auto refList = m_HashMap[pSelLogEntry->m_CommitHash];
764 dlg.m_Upstream = refList.empty() ? pSelLogEntry->m_CommitHash.ToString() : refList.front();
765 for (const auto& ref : refList)
766 if (CGit::GetShortName(ref, dlg.m_Upstream, L"refs/heads/"))
767 break;
769 if(dlg.DoModal() == IDOK)
771 Refresh();
775 break;
777 case ID_STASH_SAVE:
778 if (CAppUtils::StashSave())
779 Refresh();
780 break;
782 case ID_STASH_POP:
783 if (CAppUtils::StashPop())
784 Refresh();
785 break;
787 case ID_STASH_LIST:
788 CAppUtils::RunTortoiseGitProc(_T("/command:reflog /ref:refs/stash"));
789 break;
791 case ID_REFLOG_STASH_APPLY:
792 CAppUtils::StashApply(pSelLogEntry->m_Ref);
793 break;
795 case ID_REFLOG_DEL:
797 CString str;
798 if (GetSelectedCount() > 1)
799 str.Format(IDS_PROC_DELETENREFS, GetSelectedCount());
800 else
801 str.Format(IDS_PROC_DELETEREF, (LPCTSTR)pSelLogEntry->m_Ref);
803 if (CMessageBox::Show(NULL, str, _T("TortoiseGit"), 1, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
804 return;
806 std::vector<CString> refsToDelete;
807 POSITION pos2 = GetFirstSelectedItemPosition();
808 while (pos2)
810 CString ref = ((GitRevLoglist*)m_arShownList[GetNextSelectedItem(pos2)])->m_Ref;
811 if (ref.Find(_T("refs/")) == 0)
812 ref = ref.Mid(5);
813 int refpos = ref.ReverseFind('{');
814 if (refpos > 0 && ref.Mid(refpos - 1, 2) != _T("@{"))
815 ref = ref.Left(refpos) + _T("@")+ ref.Mid(refpos);
816 refsToDelete.push_back(ref);
819 for (auto revIt = refsToDelete.crbegin(); revIt != refsToDelete.crend(); ++revIt)
821 CString ref = *revIt;
822 CString sCmd, out;
823 if (ref.Find(_T("stash")) == 0)
824 sCmd.Format(_T("git.exe stash drop %s"), (LPCTSTR)ref);
825 else
826 sCmd.Format(_T("git.exe reflog delete %s"), (LPCTSTR)ref);
828 if (g_Git.Run(sCmd, &out, CP_UTF8))
829 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
831 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
834 break;
835 case ID_LOG:
837 CString sCmd = _T("/command:log");
838 sCmd += _T(" /path:\"") + g_Git.m_CurrentDir + _T("\" ");
839 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
840 sCmd += _T(" /endrev:") + r1->m_CommitHash.ToString();
841 CAppUtils::RunTortoiseGitProc(sCmd);
843 break;
844 case ID_CREATE_PATCH:
846 int select=this->GetSelectedCount();
847 CString sCmd = _T("/command:formatpatch");
848 sCmd += _T(" /path:\"") + g_Git.m_CurrentDir + _T("\" ");
850 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
851 GitRev * r2 = NULL;
852 if(select == 1)
854 sCmd += _T(" /startrev:") + r1->m_CommitHash.ToString();
856 else
858 r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
859 if( this->m_IsOldFirst )
861 sCmd += _T(" /startrev:") + r1->m_CommitHash.ToString() + _T("~1");
862 sCmd += _T(" /endrev:") + r2->m_CommitHash.ToString();
865 else
867 sCmd += _T(" /startrev:") + r2->m_CommitHash.ToString() + _T("~1");
868 sCmd += _T(" /endrev:") + r1->m_CommitHash.ToString();
873 CAppUtils::RunTortoiseGitProc(sCmd);
875 break;
876 case ID_BISECTSTART:
878 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
879 GitRev * last = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
880 ASSERT(first != NULL && last != NULL);
882 CString firstBad = first->m_CommitHash.ToString();
883 if (!m_HashMap[first->m_CommitHash].empty())
884 firstBad = m_HashMap[first->m_CommitHash].at(0);
885 CString lastGood = last->m_CommitHash.ToString();
886 if (!m_HashMap[last->m_CommitHash].empty())
887 lastGood = m_HashMap[last->m_CommitHash].at(0);
889 if (CAppUtils::BisectStart(lastGood, firstBad))
890 Refresh();
892 break;
893 case ID_BISECTGOOD:
895 GitRev *first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
896 if (CAppUtils::BisectOperation(_T("good"), !first->m_CommitHash.IsEmpty() ? first->m_CommitHash.ToString() : _T("")))
897 Refresh();
899 break;
900 case ID_BISECTBAD:
902 GitRev *first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
903 if (CAppUtils::BisectOperation(_T("bad"), !first->m_CommitHash.IsEmpty() ? first->m_CommitHash.ToString() : _T("")))
904 Refresh();
906 break;
907 case ID_BISECTRESET:
909 if (CAppUtils::BisectOperation(_T("reset")))
910 Refresh();
912 break;
913 case ID_REPOBROWSE:
915 CString sCmd;
916 sCmd.Format(_T("/command:repobrowser /path:\"%s\" /rev:%s"), (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)pSelLogEntry->m_CommitHash.ToString());
917 CAppUtils::RunTortoiseGitProc(sCmd);
919 break;
920 case ID_PUSH:
922 CString guessAssociatedBranch = pSelLogEntry->m_CommitHash;
923 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd) : nullptr;
924 if (branch)
925 guessAssociatedBranch = *branch;
926 else
927 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], _T("refs/heads/"), guessAssociatedBranch);
929 if (CAppUtils::Push(guessAssociatedBranch))
930 Refresh();
932 break;
933 case ID_PULL:
935 if (CAppUtils::Pull())
936 Refresh();
938 break;
939 case ID_FETCH:
941 if (CAppUtils::Fetch())
942 Refresh();
944 break;
945 case ID_CLEANUP:
947 CString sCmd;
948 sCmd.Format(_T("/command:cleanup /path:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
949 CAppUtils::RunTortoiseGitProc(sCmd);
951 break;
952 case ID_SUBMODULE_UPDATE:
954 CString sCmd;
955 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
956 CAppUtils::RunTortoiseGitProc(sCmd);
958 break;
959 case ID_SHOWBRANCHES:
961 CString sCmd;
962 sCmd.Format(_T("git.exe branch -a --contains %s"), (LPCTSTR)pSelLogEntry->m_CommitHash.ToString());
963 CProgressDlg progress;
964 progress.m_AutoClose = AUTOCLOSE_NO;
965 progress.m_GitCmd = sCmd;
966 progress.DoModal();
968 break;
969 case ID_DELETE:
971 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd) : nullptr;
972 if (!branch)
974 CMessageBox::Show(NULL,IDS_ERROR_NOREF,IDS_APPNAME,MB_OK|MB_ICONERROR);
975 return;
977 CString shortname;
978 if (branch == (CString*)MAKEINTRESOURCE(IDS_ALL))
980 CString currentBranch = L"refs/heads/" + m_CurrentBranch;
981 bool nothingDeleted = true;
982 for (const auto& ref : m_HashMap[pSelLogEntry->m_CommitHash])
984 if (ref == currentBranch)
985 continue;
986 if (!DeleteRef(ref))
987 break;
988 nothingDeleted = false;
990 if (nothingDeleted)
991 return;
993 else if (!DeleteRef(*branch))
994 return;
995 this->ReloadHashMap();
996 if (m_pFindDialog)
997 m_pFindDialog->RefreshList();
998 CRect rect;
999 this->GetItemRect(FirstSelect,&rect,LVIR_BOUNDS);
1000 this->InvalidateRect(rect);
1002 break;
1004 case ID_FINDENTRY:
1006 m_nSearchIndex = GetSelectionMark();
1007 if (m_nSearchIndex < 0)
1008 m_nSearchIndex = 0;
1009 if (m_pFindDialog)
1011 break;
1013 else
1015 m_pFindDialog = new CFindDlg();
1016 m_pFindDialog->Create(this);
1019 break;
1020 case ID_MERGEREV:
1022 CString str = pSelLogEntry->m_CommitHash.ToString();
1023 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd & 0xFFFF) : nullptr;
1024 if (branch)
1025 str = *branch;
1026 else if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
1027 str = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
1028 // we need an URL to complete this command, so error out if we can't get an URL
1029 if(CAppUtils::Merge(&str))
1031 this->Refresh();
1034 break;
1035 case ID_REVERTREV:
1037 int parent = 0;
1038 if (GetSelectedCount() == 1)
1040 parent = cmd >> 16;
1041 if ((size_t)parent > pSelLogEntry->m_ParentHash.size())
1043 CString str;
1044 str.Format(IDS_PROC_NOPARENT, parent);
1045 MessageBox(str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1046 return;
1050 if (!this->RevertSelectedCommits(parent))
1052 if (CMessageBox::Show(m_hWnd, IDS_REVREVERTED, IDS_APPNAME, 1, IDI_QUESTION, IDS_OKBUTTON, IDS_COMMITBUTTON) == 2)
1054 CTGitPathList pathlist;
1055 CTGitPathList selectedlist;
1056 pathlist.AddPath(this->m_Path);
1057 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
1058 CString str;
1059 CAppUtils::Commit(CString(), false, str, pathlist, selectedlist, bSelectFilesForCommit);
1061 this->Refresh();
1064 break;
1065 case ID_EDITNOTE:
1067 CAppUtils::EditNote(pSelLogEntry);
1068 this->SetItemState(FirstSelect, 0, LVIS_SELECTED);
1069 this->SetItemState(FirstSelect, LVIS_SELECTED, LVIS_SELECTED);
1071 break;
1072 default:
1073 //CMessageBox::Show(NULL,_T("Have not implemented"),_T("TortoiseGit"),MB_OK);
1074 break;
1075 #if 0
1077 case ID_BLAMECOMPARE:
1079 //user clicked on the menu item "compare with working copy"
1080 //now first get the revision which is selected
1081 if (PromptShown())
1083 GitDiff diff(this, this->m_hWnd, true);
1084 diff.SetHEADPeg(m_LogRevision);
1085 diff.ShowCompare(m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), false, true);
1087 else
1088 CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), m_LogRevision, false, false, true);
1090 break;
1091 case ID_BLAMEWITHPREVIOUS:
1093 //user clicked on the menu item "Compare and Blame with previous revision"
1094 if (PromptShown())
1096 GitDiff diff(this, this->m_hWnd, true);
1097 diff.SetHEADPeg(m_LogRevision);
1098 diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), false, true);
1100 else
1101 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
1103 break;
1105 case ID_OPENWITH:
1106 bOpenWith = true;
1107 case ID_OPEN:
1109 CProgressDlg progDlg;
1110 progDlg.SetTitle(IDS_APPNAME);
1111 progDlg.SetAnimation(IDR_DOWNLOAD);
1112 CString sInfoLine;
1113 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, m_path.GetWinPath(), (LPCTSTR)revSelected.ToString());
1114 progDlg.SetLine(1, sInfoLine, true);
1115 SetAndClearProgressInfo(&progDlg);
1116 progDlg.ShowModeless(m_hWnd);
1117 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, m_path, revSelected);
1118 bool bSuccess = true;
1119 if (!Cat(m_path, GitRev(GitRev::REV_HEAD), revSelected, tempfile))
1121 bSuccess = false;
1122 // try again, but with the selected revision as the peg revision
1123 if (!Cat(m_path, revSelected, revSelected, tempfile))
1125 progDlg.Stop();
1126 SetAndClearProgressInfo((HWND)NULL);
1127 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1128 EnableOKButton();
1129 break;
1131 bSuccess = true;
1133 if (bSuccess)
1135 progDlg.Stop();
1136 SetAndClearProgressInfo((HWND)NULL);
1137 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1138 if (!bOpenWith)
1139 CAppUtils::ShellOpen(tempfile.GetWinPath(), GetSafeHwnd());
1140 else
1141 CAppUtils::ShowOpenWithDialog(tempfile.GetWinPathString(), GetSafeHwnd());
1144 break;
1145 case ID_BLAME:
1147 CBlameDlg dlg;
1148 dlg.EndRev = revSelected;
1149 if (dlg.DoModal() == IDOK)
1151 CBlame blame;
1152 CString tempfile;
1153 CString logfile;
1154 tempfile = blame.BlameToTempFile(m_path, dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, _T(""), dlg.m_bIncludeMerge, TRUE, TRUE);
1155 if (!tempfile.IsEmpty())
1157 if (dlg.m_bTextView)
1159 //open the default text editor for the result file
1160 CAppUtils::StartTextViewer(tempfile);
1162 else
1164 CString sParams = _T("/path:\"") + m_path.GetGitPathString() + _T("\" ");
1165 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(m_path.GetFileOrDirectoryName()),sParams))
1167 break;
1171 else
1173 CMessageBox::Show(this->m_hWnd, blame.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1177 break;
1178 case ID_EXPORT:
1180 CString sCmd;
1181 sCmd.Format(_T("%s /command:export /path:\"%s\" /revision:%ld"),
1182 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseGitProc.exe")),
1183 (LPCTSTR)pathURL, (LONG)revSelected);
1184 CAppUtils::LaunchApplication(sCmd, NULL, false);
1186 break;
1187 case ID_VIEWREV:
1189 CString url = m_ProjectProperties.sWebViewerRev;
1190 url = GetAbsoluteUrlFromRelativeUrl(url);
1191 url.Replace(_T("%REVISION%"), revSelected.ToString());
1192 if (!url.IsEmpty())
1193 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1195 break;
1196 case ID_VIEWPATHREV:
1198 CString relurl = pathURL;
1199 CString sRoot = GetRepositoryRoot(CTGitPath(relurl));
1200 relurl = relurl.Mid(sRoot.GetLength());
1201 CString url = m_ProjectProperties.sWebViewerPathRev;
1202 url = GetAbsoluteUrlFromRelativeUrl(url);
1203 url.Replace(_T("%REVISION%"), revSelected.ToString());
1204 url.Replace(_T("%PATH%"), relurl);
1205 if (!url.IsEmpty())
1206 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1208 break;
1209 #endif
1211 } // switch (cmd)
1213 theApp.DoWaitCursor(-1);
1216 void CGitLogList::SetSelectedRebaseAction(int action)
1218 POSITION pos = GetFirstSelectedItemPosition();
1219 if (!pos) return;
1220 int index;
1221 while(pos)
1223 index = GetNextSelectedItem(pos);
1224 if (((GitRevLoglist*)m_arShownList[index])->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT | LOGACTIONS_REBASE_DONE) || (index == GetItemCount() - 1 && action == LOGACTIONS_REBASE_SQUASH))
1225 continue;
1226 if (!m_bIsCherryPick && ((GitRevLoglist*)m_arShownList[index])->ParentsCount() > 1 && action == LOGACTIONS_REBASE_SQUASH)
1227 continue;
1228 ((GitRevLoglist*)m_arShownList[index])->GetRebaseAction() = action;
1229 CRect rect;
1230 this->GetItemRect(index,&rect,LVIR_BOUNDS);
1231 this->InvalidateRect(rect);
1234 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage);
1237 void CGitLogList::SetUnselectedRebaseAction(int action)
1239 POSITION pos = GetFirstSelectedItemPosition();
1240 int index = pos ? GetNextSelectedItem(pos) : -1;
1241 for (int i = 0; i < GetItemCount(); i++)
1243 if (i == index)
1245 index = pos ? GetNextSelectedItem(pos) : -1;
1246 continue;
1249 if (((GitRevLoglist*)m_arShownList[i])->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT | LOGACTIONS_REBASE_DONE) || (i == GetItemCount() - 1 && action == LOGACTIONS_REBASE_SQUASH) || (!m_bIsCherryPick && action == LOGACTIONS_REBASE_SQUASH && ((GitRevLoglist*)m_arShownList[i])->ParentsCount() != 1))
1250 continue;
1251 ((GitRevLoglist*)m_arShownList[i])->GetRebaseAction() = action;
1252 CRect rect;
1253 this->GetItemRect(i, &rect, LVIR_BOUNDS);
1254 this->InvalidateRect(rect);
1257 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage);
1260 void CGitLogList::ShiftSelectedRebaseAction()
1262 POSITION pos = GetFirstSelectedItemPosition();
1263 int index;
1264 while(pos)
1266 index = GetNextSelectedItem(pos);
1267 int *action = &((GitRevLoglist*)m_arShownList[index])->GetRebaseAction();
1268 switch (*action)
1270 case LOGACTIONS_REBASE_PICK:
1271 *action = LOGACTIONS_REBASE_SKIP;
1272 break;
1273 case LOGACTIONS_REBASE_SKIP:
1274 *action= LOGACTIONS_REBASE_EDIT;
1275 break;
1276 case LOGACTIONS_REBASE_EDIT:
1277 *action = LOGACTIONS_REBASE_SQUASH;
1278 if (index == GetItemCount() - 1 && (m_bIsCherryPick || ((GitRevLoglist*)m_arShownList[index])->m_ParentHash.size() == 1))
1279 *action = LOGACTIONS_REBASE_PICK;
1280 break;
1281 case LOGACTIONS_REBASE_SQUASH:
1282 *action= LOGACTIONS_REBASE_PICK;
1283 break;
1285 CRect rect;
1286 this->GetItemRect(index, &rect, LVIR_BOUNDS);
1287 this->InvalidateRect(rect);