Use wcsncmp instead of Find for StartsWith checks
[TortoiseGit.git] / src / TortoiseProc / GitLogListAction.cpp
blob2add0964a70745cbbca158868e62be4a92eb7a6d
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 "CommitDlg.h"
39 #include "RebaseDlg.h"
40 #include "GitDiff.h"
41 #include "../TGitCache/CacheInterface.h"
43 IMPLEMENT_DYNAMIC(CGitLogList, CHintCtrl<CListCtrl>)
45 static void GetFirstEntryStartingWith(STRING_VECTOR& heystack, const CString& needle, CString& result)
47 auto it = std::find_if(heystack.cbegin(), heystack.cend(), [&needle](const CString& entry) { return wcsncmp(entry, needle, needle.GetLength()) == 0; });
48 if (it == heystack.cend())
49 return;
50 result = *it;
53 int CGitLogList::RevertSelectedCommits(int parent)
55 CSysProgressDlg progress;
56 int ret = -1;
58 #if 0
59 if(!g_Git.CheckCleanWorkTree())
60 CMessageBox::Show(nullptr, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
61 #endif
63 if (this->GetSelectedCount() > 1)
65 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_REVERTCOMMIT)));
66 progress.SetAnimation(IDR_MOVEANI);
67 progress.SetTime(true);
68 progress.ShowModeless(this);
71 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
73 POSITION pos = GetFirstSelectedItemPosition();
74 int i=0;
75 while(pos)
77 int index = GetNextSelectedItem(pos);
78 GitRev* r1 = m_arShownList.SafeGetAt(index);
80 if (progress.IsVisible())
82 progress.FormatNonPathLine(1, IDS_PROC_REVERTCOMMIT, r1->m_CommitHash.ToString());
83 progress.FormatNonPathLine(2, _T("%s"), (LPCTSTR)r1->GetSubject());
84 progress.SetProgress(i, this->GetSelectedCount());
86 ++i;
88 if(r1->m_CommitHash.IsEmpty())
89 continue;
91 if (g_Git.GitRevert(parent, r1->m_CommitHash))
93 CString str;
94 str.LoadString(IDS_SVNACTION_FAILEDREVERT);
95 str = g_Git.GetGitLastErr(str, CGit::GIT_CMD_REVERT);
96 if( GetSelectedCount() == 1)
97 CMessageBox::Show(GetSafeHwnd(), str, L"TortoiseGit", MB_OK | MB_ICONERROR);
98 else if (CMessageBox::Show(GetSafeHwnd(), str, L"TortoiseGit", 2, IDI_ERROR, CString(MAKEINTRESOURCE(IDS_SKIPBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
99 return ret;
101 else
102 ret =0;
104 if (progress.HasUserCancelled())
105 break;
107 return ret;
109 int CGitLogList::CherryPickFrom(CString from, CString to)
111 CLogDataVector logs(&m_LogCache);
112 CString range;
113 range.Format(_T("%s..%s"), (LPCTSTR)from, (LPCTSTR)to);
114 if (logs.ParserFromLog(nullptr, 0, 0, &range))
115 return -1;
117 if (logs.empty())
118 return 0;
120 CSysProgressDlg progress;
121 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_CHERRYPICK)));
122 progress.SetAnimation(IDR_MOVEANI);
123 progress.SetTime(true);
124 progress.ShowModeless(this);
126 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
128 for (int i = (int)logs.size() - 1; i >= 0; i--)
130 if (progress.IsVisible())
132 progress.FormatNonPathLine(1, IDS_PROC_PICK, logs.GetGitRevAt(i).m_CommitHash.ToString());
133 progress.FormatNonPathLine(2, _T("%s"), (LPCTSTR)logs.GetGitRevAt(i).GetSubject());
134 progress.SetProgress64(logs.size() - i, logs.size());
136 if (progress.HasUserCancelled())
137 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_USERCANCELLED))));
138 CString cmd,out;
139 cmd.Format(_T("git.exe cherry-pick %s"), (LPCTSTR)logs.GetGitRevAt(i).m_CommitHash.ToString());
140 if(g_Git.Run(cmd,&out,CP_UTF8))
141 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_CHERRYPICKFAILED)) + _T(":\r\n\r\n") + out));
144 return 0;
147 int CGitLogList::DeleteRef(const CString& ref)
149 CString shortname;
150 if (CGit::GetShortName(ref, shortname, _T("refs/remotes/")))
152 CString msg;
153 msg.Format(IDS_PROC_DELETEREMOTEBRANCH, (LPCTSTR)ref);
154 int result = CMessageBox::Show(GetSafeHwnd(), msg, L"TortoiseGit", 3, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_PROC_DELETEREMOTEBRANCH_LOCALREMOTE)), CString(MAKEINTRESOURCE(IDS_PROC_DELETEREMOTEBRANCH_LOCAL)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON)));
155 if (result == 1)
157 CString remoteName = shortname.Left(shortname.Find(L'/'));
158 shortname = shortname.Mid(shortname.Find(L'/') + 1);
159 if (CAppUtils::IsSSHPutty())
160 CAppUtils::LaunchPAgent(nullptr, &remoteName);
162 CSysProgressDlg sysProgressDlg;
163 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
164 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
165 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
166 sysProgressDlg.SetShowProgressBar(false);
167 sysProgressDlg.ShowModal(this, true);
168 STRING_VECTOR list;
169 list.push_back(_T("refs/heads/") + shortname);
170 if (g_Git.DeleteRemoteRefs(remoteName, list))
171 CMessageBox::Show(GetSafeHwnd(), g_Git.GetGitLastErr(L"Could not delete remote ref.", CGit::GIT_CMD_PUSH), L"TortoiseGit", MB_OK | MB_ICONERROR);
172 sysProgressDlg.Stop();
173 return TRUE;
175 else if (result == 2)
177 if (g_Git.DeleteRef(ref))
179 CMessageBox::Show(GetSafeHwnd(), g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), L"TortoiseGit", MB_OK | MB_ICONERROR);
180 return FALSE;
182 return TRUE;
184 return FALSE;
186 else if (CGit::GetShortName(ref, shortname, _T("refs/stash")))
188 CString err;
189 std::vector<GitRevLoglist> stashList;
190 size_t count = !GitRevLoglist::GetRefLog(ref, stashList, err) ? stashList.size() : 0;
191 CString msg;
192 msg.Format(IDS_PROC_DELETEALLSTASH, count);
193 int choose = CMessageBox::Show(GetSafeHwnd(), msg, L"TortoiseGit", 3, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_DROPONESTASH)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON)));
194 if (choose == 1)
196 CString sCmd;
197 sCmd.Format(_T("git.exe stash clear"));
198 CString out;
199 if (g_Git.Run(sCmd, &out, CP_UTF8))
200 CMessageBox::Show(GetSafeHwnd(), out, L"TortoiseGit", MB_OK | MB_ICONERROR);
201 return TRUE;
203 else if (choose == 2)
205 CString sCmd;
206 sCmd.Format(_T("git.exe stash drop refs/stash@{0}"));
207 CString out;
208 if (g_Git.Run(sCmd, &out, CP_UTF8))
209 CMessageBox::Show(GetSafeHwnd(), out, L"TortoiseGit", MB_OK | MB_ICONERROR);
210 return TRUE;
212 return FALSE;
215 CString msg;
216 msg.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)ref);
217 if (CMessageBox::Show(GetSafeHwnd(), msg, L"TortoiseGit", 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
219 if (g_Git.DeleteRef(ref))
221 CMessageBox::Show(GetSafeHwnd(), g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), L"TortoiseGit", MB_OK | MB_ICONERROR);
222 return FALSE;
224 return TRUE;
226 return FALSE;
229 void CGitLogList::ContextMenuAction(int cmd,int FirstSelect, int LastSelect, CMenu *popmenu)
231 POSITION pos = GetFirstSelectedItemPosition();
232 int indexNext = GetNextSelectedItem(pos);
233 if (indexNext < 0)
234 return;
236 GitRevLoglist* pSelLogEntry = m_arShownList.SafeGetAt(indexNext);
238 bool bShiftPressed = !!(GetAsyncKeyState(VK_SHIFT) & 0x8000);
240 theApp.DoWaitCursor(1);
241 switch (cmd&0xFFFF)
243 case ID_COMMIT:
245 CTGitPathList pathlist;
246 CTGitPathList selectedlist;
247 pathlist.AddPath(this->m_Path);
248 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
249 CString str;
250 CAppUtils::Commit(CString(),false,str,
251 pathlist,selectedlist,bSelectFilesForCommit);
252 //this->Refresh();
253 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH,0);
255 break;
256 case ID_MERGE_ABORT:
258 if (CAppUtils::MergeAbort())
259 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH, 0);
261 break;
262 case ID_GNUDIFF1: // compare with WC, unified
264 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
265 bool bMerge = false, bCombine = false;
266 CString hash2;
267 if(!r1->m_CommitHash.IsEmpty())
269 CString merge;
270 cmd >>= 16;
271 if( (cmd&0xFFFF) == 0xFFFF)
272 bMerge = true;
273 else if((cmd&0xFFFF) == 0xFFFE)
274 bCombine = true;
275 else if ((cmd & 0xFFFF) == 0xFFFD)
277 CString tempfile = GetTempFile();
278 CString gitcmd = _T("git.exe diff-tree --cc ") + r1->m_CommitHash.ToString();
279 CString lastErr;
280 if (g_Git.RunLogFile(gitcmd, tempfile, &lastErr))
282 MessageBox(lastErr, _T("TortoiseGit"), MB_ICONERROR);
283 break;
288 CStdioFile file(tempfile, CFile::typeText | CFile::modeRead | CFile::shareDenyWrite);
289 CString strLine;
290 bool isHash = file.ReadString(strLine) && r1->m_CommitHash.ToString() == strLine;
291 bool more = isHash && file.ReadString(strLine) && !strLine.IsEmpty();
292 if (!more)
294 CMessageBox::Show(GetSafeHwnd(), IDS_NOCHANGEAFTERMERGE, IDS_APPNAME, MB_OK);
295 break;
298 catch (CFileException* e)
300 e->Delete();
303 CAppUtils::StartUnifiedDiffViewer(tempfile, _T("dd"));
304 break;
306 else
308 if ((size_t)cmd > r1->m_ParentHash.size())
310 CString str;
311 str.Format(IDS_PROC_NOPARENT, cmd);
312 MessageBox(str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
313 return;
315 else
317 if(cmd>0)
318 hash2 = r1->m_ParentHash[cmd-1].ToString();
321 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), hash2, CTGitPath(), r1->m_CommitHash.ToString(), bShiftPressed, false, false, bMerge, bCombine);
323 else
324 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), _T("HEAD"), CTGitPath(), GitRev::GetWorkingCopy(), bShiftPressed, false, false, bMerge, bCombine);
326 break;
328 case ID_GNUDIFF2: // compare two revisions, unified
330 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
331 GitRev* r2 = m_arShownList.SafeGetAt(LastSelect);
332 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), r2->m_CommitHash.ToString(), CTGitPath(), r1->m_CommitHash.ToString(), bShiftPressed);
334 break;
336 case ID_COMPARETWO: // compare two revisions
338 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
339 GitRev* r2 = m_arShownList.SafeGetAt(LastSelect);
340 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
341 CGitDiff::DiffCommit(m_Path, r1, r2, bShiftPressed);
342 else
344 CString path1 = m_Path.GetGitPathString();
345 // start with 1 (0 = working copy changes)
346 for (int i = m_bShowWC ? 1 : 0; i < FirstSelect; ++i)
348 GitRevLoglist* first = m_arShownList.SafeGetAt(i);
349 CTGitPathList list = first->GetFiles(nullptr);
350 const CTGitPath* file = list.LookForGitPath(path1);
351 if (file && !file->GetGitOldPathString().IsEmpty())
352 path1 = file->GetGitOldPathString();
354 CString path2 = path1;
355 for (int i = FirstSelect; i < LastSelect; ++i)
357 GitRevLoglist* first = m_arShownList.SafeGetAt(i);
358 CTGitPathList list = first->GetFiles(nullptr);
359 const CTGitPath* file = list.LookForGitPath(path2);
360 if (file && !file->GetGitOldPathString().IsEmpty())
361 path2 = file->GetGitOldPathString();
363 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), r1, r2, bShiftPressed);
367 break;
369 case ID_COMPARE: // compare revision with WC
371 GitRevLoglist* r1 = &m_wcRev;
372 GitRevLoglist* r2 = pSelLogEntry;
374 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
375 CGitDiff::DiffCommit(m_Path, r1, r2, bShiftPressed);
376 else
378 CString path1 = m_Path.GetGitPathString();
379 // start with 1 (0 = working copy changes)
380 for (int i = m_bShowWC ? 1 : 0; i < FirstSelect; ++i)
382 GitRevLoglist* first = m_arShownList.SafeGetAt(i);
383 CTGitPathList list = first->GetFiles(nullptr);
384 const CTGitPath* file = list.LookForGitPath(path1);
385 if (file && !file->GetGitOldPathString().IsEmpty())
386 path1 = file->GetGitOldPathString();
388 CGitDiff::DiffCommit(m_Path, CTGitPath(path1), r1, r2, bShiftPressed);
391 //user clicked on the menu item "compare with working copy"
392 //if (PromptShown())
394 // GitDiff diff(this, m_hWnd, true);
395 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
396 // diff.SetHEADPeg(m_LogRevision);
397 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
399 //else
400 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
402 break;
404 case ID_COMPAREWITHPREVIOUS:
406 if (pSelLogEntry->m_ParentHash.empty())
408 if (pSelLogEntry->GetParentFromHash(pSelLogEntry->m_CommitHash))
409 MessageBox(pSelLogEntry->GetLastErr(), _T("TortoiseGit"), MB_ICONERROR);
412 if (!pSelLogEntry->m_ParentHash.empty())
413 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
415 cmd>>=16;
416 cmd&=0xFFFF;
418 if(cmd == 0)
419 cmd=1;
421 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
422 CGitDiff::DiffCommit(m_Path, pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString(), bShiftPressed);
423 else
425 CString path1 = m_Path.GetGitPathString();
426 // start with 1 (0 = working copy changes)
427 for (int i = m_bShowWC ? 1 : 0; i < indexNext; ++i)
429 GitRevLoglist* first = m_arShownList.SafeGetAt(i);
430 CTGitPathList list = first->GetFiles(nullptr);
431 const CTGitPath* file = list.LookForGitPath(path1);
432 if (file && !file->GetGitOldPathString().IsEmpty())
433 path1 = file->GetGitOldPathString();
435 CString path2 = path1;
436 GitRevLoglist* first = m_arShownList.SafeGetAt(indexNext);
437 CTGitPathList list = first->GetFiles(nullptr);
438 const CTGitPath* file = list.LookForGitPath(path2);
439 if (file && !file->GetGitOldPathString().IsEmpty())
440 path2 = file->GetGitOldPathString();
442 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString(), bShiftPressed);
445 else
447 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_NOPREVIOUSVERSION, IDS_APPNAME, MB_OK);
449 //if (PromptShown())
451 // GitDiff diff(this, m_hWnd, true);
452 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
453 // diff.SetHEADPeg(m_LogRevision);
454 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
456 //else
457 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
459 break;
460 case ID_LOG_VIEWRANGE:
461 case ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE:
463 GitRev* pLastEntry = m_arShownList.SafeGetAt(LastSelect);
465 CString sep = _T("..");
466 if ((cmd & 0xFFFF) == ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE)
467 sep = _T("...");
469 CString cmdline;
470 cmdline.Format(_T("/command:log /path:\"%s\" /range:\"%s%s%s\""),
471 (LPCTSTR)g_Git.CombinePath(m_Path), (LPCTSTR)pLastEntry->m_CommitHash.ToString(), (LPCTSTR)sep, (LPCTSTR)pSelLogEntry->m_CommitHash.ToString());
472 CAppUtils::RunTortoiseGitProc(cmdline);
474 break;
475 case ID_COPYCLIPBOARD:
477 CopySelectionToClipBoard();
479 break;
480 case ID_COPYCLIPBOARDMESSAGES:
482 if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0)
483 CopySelectionToClipBoard(ID_COPY_SUBJECT);
484 else
485 CopySelectionToClipBoard(ID_COPY_MESSAGE);
487 break;
488 case ID_COPYHASH:
490 CopySelectionToClipBoard(ID_COPY_HASH);
492 break;
493 case ID_EXPORT:
495 CString str=pSelLogEntry->m_CommitHash.ToString();
496 // try to get the tag
497 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], _T("refs/tags/"), str);
498 CAppUtils::Export(&str, &m_Path);
500 break;
501 case ID_CREATE_BRANCH:
502 case ID_CREATE_TAG:
504 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd & 0xFFFF) : nullptr;
505 CString str = pSelLogEntry->m_CommitHash.ToString();
506 if (branch)
507 str = *branch;
508 else // try to guess remote branch in order to enable tracking
509 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], _T("refs/remotes/"), str);
511 CAppUtils::CreateBranchTag((cmd&0xFFFF) == ID_CREATE_TAG, &str);
512 ReloadHashMap();
513 if (m_pFindDialog)
514 m_pFindDialog->RefreshList();
515 Invalidate();
516 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
518 break;
519 case ID_SWITCHTOREV:
521 CString str = pSelLogEntry->m_CommitHash.ToString();
522 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd & 0xFFFF) : nullptr;
523 if (branch)
524 str = *branch;
525 else // try to guess remote branch in order to recommend good branch name and tracking
526 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], _T("refs/remotes/"), str);
528 CAppUtils::Switch(str);
530 ReloadHashMap();
531 Invalidate();
532 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
533 break;
534 case ID_SWITCHBRANCH:
535 if(popmenu)
537 const CString* branch = (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
538 if(branch)
540 CString name = *branch;
541 CGit::GetShortName(*branch, name, L"refs/heads/");
542 CAppUtils::PerformSwitch(name);
544 ReloadHashMap();
545 Invalidate();
546 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
548 break;
549 case ID_RESET:
551 CString str = pSelLogEntry->m_CommitHash.ToString();
552 if (CAppUtils::GitReset(&str))
554 ResetWcRev(true);
555 ReloadHashMap();
556 Invalidate();
559 break;
560 case ID_REBASE_PICK:
561 SetSelectedRebaseAction(LOGACTIONS_REBASE_PICK);
562 break;
563 case ID_REBASE_EDIT:
564 SetSelectedRebaseAction(LOGACTIONS_REBASE_EDIT);
565 break;
566 case ID_REBASE_SQUASH:
567 SetSelectedRebaseAction(LOGACTIONS_REBASE_SQUASH);
568 break;
569 case ID_REBASE_SKIP:
570 SetSelectedRebaseAction(LOGACTIONS_REBASE_SKIP);
571 break;
572 case ID_COMBINE_COMMIT:
574 CString head;
575 CGitHash headhash;
576 CGitHash hashFirst,hashLast;
578 int headindex=GetHeadIndex();
579 if(headindex>=0) //incase show all branch, head is not the first commits.
581 head.Format(_T("HEAD~%d"),FirstSelect-headindex);
582 if (g_Git.GetHash(hashFirst, head))
584 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of first selected revision.")), _T("TortoiseGit"), MB_ICONERROR);
585 break;
588 head.Format(_T("HEAD~%d"),LastSelect-headindex);
589 if (g_Git.GetHash(hashLast, head))
591 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of last selected revision.")), _T("TortoiseGit"), MB_ICONERROR);
592 break;
596 GitRev* pFirstEntry = m_arShownList.SafeGetAt(FirstSelect);
597 GitRev* pLastEntry = m_arShownList.SafeGetAt(LastSelect);
598 if(pFirstEntry->m_CommitHash != hashFirst || pLastEntry->m_CommitHash != hashLast)
600 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_CANNOTCOMBINE, IDS_APPNAME, MB_OK | MB_ICONEXCLAMATION);
601 break;
604 GitRev lastRevision;
605 if (lastRevision.GetParentFromHash(hashLast))
607 MessageBox(lastRevision.GetLastErr(), _T("TortoiseGit"), MB_ICONERROR);
608 break;
611 if (g_Git.GetHash(headhash, _T("HEAD")))
613 MessageBox(g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
614 break;
617 if(!g_Git.CheckCleanWorkTree())
619 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK | MB_ICONEXCLAMATION);
620 break;
622 CString sCmd, out;
624 //Use throw to abort this process (reset back to original HEAD)
627 sCmd.Format(_T("git.exe reset --hard %s --"), (LPCTSTR)pFirstEntry->m_CommitHash.ToString());
628 if(g_Git.Run(sCmd, &out, CP_UTF8))
630 MessageBox(out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
631 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1)) + _T("\r\n\r\n") + out));
633 sCmd.Format(_T("git.exe reset --mixed %s --"), (LPCTSTR)hashLast.ToString());
634 if(g_Git.Run(sCmd, &out, CP_UTF8))
636 MessageBox(out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
637 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2)) + _T("\r\n\r\n")+out));
640 CTGitPathList PathList;
641 /* don't why must add --stat to get action status*/
642 /* first -z will be omitted by gitdll*/
643 if(g_Git.GetDiffPath(&PathList,&pFirstEntry->m_CommitHash,&hashLast,"-z --stat -r"))
645 MessageBox(_T("Get Diff file list error"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
646 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not get changed file list aborting...\r\n\r\n")+out));
649 for (int i = 0; i < PathList.GetCount(); ++i)
651 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_ADDED)
653 sCmd.Format(_T("git.exe add -- \"%s\""), (LPCTSTR)PathList[i].GetGitPathString());
654 if (g_Git.Run(sCmd, &out, CP_UTF8))
656 MessageBox(out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
657 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not add new file aborting...\r\n\r\n")+out));
660 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_DELETED)
662 sCmd.Format(_T("git.exe rm -- \"%s\""), (LPCTSTR)PathList[i].GetGitPathString());
663 if (g_Git.Run(sCmd, &out, CP_UTF8))
665 MessageBox(out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
666 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not rm file aborting...\r\n\r\n")+out));
671 CCommitDlg dlg;
672 for (int i = FirstSelect; i <= LastSelect; ++i)
674 GitRev* pRev = m_arShownList.SafeGetAt(i);
675 dlg.m_sLogMessage+=pRev->GetSubject()+_T("\n")+pRev->GetBody();
676 dlg.m_sLogMessage+=_T("\n");
678 dlg.m_bWholeProject=true;
679 dlg.m_bSelectFilesForCommit = true;
680 dlg.m_bForceCommitAmend=true;
681 CTGitPathList gpl;
682 gpl.AddPath(CTGitPath(g_Git.m_CurrentDir));
683 dlg.m_pathList = gpl;
684 if (lastRevision.ParentsCount() != 1)
686 MessageBox(_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 | MB_ICONINFORMATION);
687 dlg.m_bAmendDiffToLastCommit = TRUE;
689 else
690 dlg.m_bAmendDiffToLastCommit = FALSE;
691 dlg.m_bNoPostActions=true;
692 dlg.m_AmendStr=dlg.m_sLogMessage;
694 if (dlg.DoModal() == IDOK)
696 if(pFirstEntry->m_CommitHash!=headhash)
698 if(CherryPickFrom(pFirstEntry->m_CommitHash.ToString(),headhash))
700 CString msg;
701 msg.Format(_T("Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n"));
702 throw std::exception(CUnicodeUtils::GetUTF8(msg));
706 else
707 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_USERCANCELLED))));
709 catch(std::exception& e)
711 CMessageBox::Show(GetSafeHwnd(), CUnicodeUtils::GetUnicode(CStringA(e.what())), L"TortoiseGit", MB_OK | MB_ICONERROR);
712 sCmd.Format(_T("git.exe reset --hard %s --"), (LPCTSTR)headhash.ToString());
713 out.Empty();
714 if(g_Git.Run(sCmd, &out, CP_UTF8))
715 MessageBox(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD)) + _T("\r\n\r\n") + out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
717 Refresh();
719 break;
721 case ID_CHERRY_PICK:
723 if (m_bThreadRunning)
725 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_LOG_ONLYONCE, IDS_APPNAME, MB_ICONEXCLAMATION);
726 break;
728 CRebaseDlg dlg;
729 dlg.m_IsCherryPick = TRUE;
730 dlg.m_Upstream = this->m_CurrentBranch;
731 POSITION pos2 = GetFirstSelectedItemPosition();
732 while(pos2)
734 int indexNext2 = GetNextSelectedItem(pos2);
735 dlg.m_CommitList.m_logEntries.push_back(m_arShownList.SafeGetAt(indexNext2)->m_CommitHash);
736 dlg.m_CommitList.m_LogCache.m_HashMap[m_arShownList.SafeGetAt(indexNext2)->m_CommitHash] = *m_arShownList.SafeGetAt(indexNext2);
737 dlg.m_CommitList.m_logEntries.GetGitRevAt(dlg.m_CommitList.m_logEntries.size() - 1).GetRebaseAction() |= LOGACTIONS_REBASE_PICK;
740 if(dlg.DoModal() == IDOK)
742 Refresh();
745 break;
746 case ID_REBASE_TO_VERSION:
748 if (m_bThreadRunning)
750 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_LOG_ONLYONCE, IDS_APPNAME, MB_ICONEXCLAMATION);
751 break;
753 CRebaseDlg dlg;
754 auto refList = m_HashMap[pSelLogEntry->m_CommitHash];
755 dlg.m_Upstream = refList.empty() ? pSelLogEntry->m_CommitHash.ToString() : refList.front();
756 for (const auto& ref : refList)
757 if (CGit::GetShortName(ref, dlg.m_Upstream, L"refs/heads/"))
758 break;
760 if(dlg.DoModal() == IDOK)
762 Refresh();
766 break;
768 case ID_STASH_SAVE:
769 if (CAppUtils::StashSave())
770 Refresh();
771 break;
773 case ID_STASH_POP:
774 if (CAppUtils::StashPop())
775 Refresh();
776 break;
778 case ID_STASH_LIST:
779 CAppUtils::RunTortoiseGitProc(_T("/command:reflog /ref:refs/stash"));
780 break;
782 case ID_REFLOG_STASH_APPLY:
783 CAppUtils::StashApply(pSelLogEntry->m_Ref);
784 break;
786 case ID_REFLOG_DEL:
788 CString str;
789 if (GetSelectedCount() > 1)
790 str.Format(IDS_PROC_DELETENREFS, GetSelectedCount());
791 else
792 str.Format(IDS_PROC_DELETEREF, (LPCTSTR)pSelLogEntry->m_Ref);
794 if (CMessageBox::Show(GetSafeHwnd(), str, L"TortoiseGit", 1, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
795 return;
797 std::vector<CString> refsToDelete;
798 POSITION pos2 = GetFirstSelectedItemPosition();
799 while (pos2)
801 CString ref = m_arShownList.SafeGetAt(GetNextSelectedItem(pos2))->m_Ref;
802 if (wcsncmp(ref, L"refs/", 5) == 0)
803 ref = ref.Mid(5);
804 int refpos = ref.ReverseFind('{');
805 if (refpos > 0 && ref.Mid(refpos - 1, 2) != _T("@{"))
806 ref = ref.Left(refpos) + _T("@")+ ref.Mid(refpos);
807 refsToDelete.push_back(ref);
810 for (auto revIt = refsToDelete.crbegin(); revIt != refsToDelete.crend(); ++revIt)
812 CString ref = *revIt;
813 CString sCmd, out;
814 if (wcsncmp(ref, L"stash", 5) == 0)
815 sCmd.Format(_T("git.exe stash drop %s"), (LPCTSTR)ref);
816 else
817 sCmd.Format(_T("git.exe reflog delete %s"), (LPCTSTR)ref);
819 if (g_Git.Run(sCmd, &out, CP_UTF8))
820 MessageBox(out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
822 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
825 break;
826 case ID_LOG:
828 CString sCmd = _T("/command:log");
829 sCmd += _T(" /path:\"") + g_Git.m_CurrentDir + _T("\" ");
830 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
831 sCmd += _T(" /endrev:") + r1->m_CommitHash.ToString();
832 CAppUtils::RunTortoiseGitProc(sCmd);
834 break;
835 case ID_CREATE_PATCH:
837 int select=this->GetSelectedCount();
838 CString sCmd = _T("/command:formatpatch");
839 sCmd += _T(" /path:\"") + g_Git.m_CurrentDir + _T("\" ");
841 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
842 GitRev* r2 = nullptr;
843 if(select == 1)
845 sCmd += _T(" /startrev:") + r1->m_CommitHash.ToString();
847 else
849 r2 = m_arShownList.SafeGetAt(LastSelect);
850 if( this->m_IsOldFirst )
852 sCmd += _T(" /startrev:") + r1->m_CommitHash.ToString() + _T("~1");
853 sCmd += _T(" /endrev:") + r2->m_CommitHash.ToString();
856 else
858 sCmd += _T(" /startrev:") + r2->m_CommitHash.ToString() + _T("~1");
859 sCmd += _T(" /endrev:") + r1->m_CommitHash.ToString();
864 CAppUtils::RunTortoiseGitProc(sCmd);
866 break;
867 case ID_BISECTSTART:
869 GitRev* first = m_arShownList.SafeGetAt(FirstSelect);
870 GitRev* last = m_arShownList.SafeGetAt(LastSelect);
871 ASSERT(first && last);
873 CString firstBad = first->m_CommitHash.ToString();
874 if (!m_HashMap[first->m_CommitHash].empty())
875 firstBad = m_HashMap[first->m_CommitHash].at(0);
876 CString lastGood = last->m_CommitHash.ToString();
877 if (!m_HashMap[last->m_CommitHash].empty())
878 lastGood = m_HashMap[last->m_CommitHash].at(0);
880 if (CAppUtils::BisectStart(lastGood, firstBad))
881 Refresh();
883 break;
884 case ID_BISECTGOOD:
886 GitRev* first = m_arShownList.SafeGetAt(FirstSelect);
887 if (CAppUtils::BisectOperation(_T("good"), !first->m_CommitHash.IsEmpty() ? first->m_CommitHash.ToString() : _T("")))
888 Refresh();
890 break;
891 case ID_BISECTBAD:
893 GitRev* first = m_arShownList.SafeGetAt(FirstSelect);
894 if (CAppUtils::BisectOperation(_T("bad"), !first->m_CommitHash.IsEmpty() ? first->m_CommitHash.ToString() : _T("")))
895 Refresh();
897 break;
898 case ID_BISECTRESET:
900 if (CAppUtils::BisectOperation(_T("reset")))
901 Refresh();
903 break;
904 case ID_REPOBROWSE:
906 CString sCmd;
907 sCmd.Format(_T("/command:repobrowser /path:\"%s\" /rev:%s"), (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)pSelLogEntry->m_CommitHash.ToString());
908 CAppUtils::RunTortoiseGitProc(sCmd);
910 break;
911 case ID_PUSH:
913 CString guessAssociatedBranch = pSelLogEntry->m_CommitHash;
914 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd) : nullptr;
915 if (branch)
916 guessAssociatedBranch = *branch;
917 else
918 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], _T("refs/heads/"), guessAssociatedBranch);
920 if (CAppUtils::Push(guessAssociatedBranch))
921 Refresh();
923 break;
924 case ID_PULL:
926 if (CAppUtils::Pull())
927 Refresh();
929 break;
930 case ID_FETCH:
932 if (CAppUtils::Fetch())
933 Refresh();
935 break;
936 case ID_CLEANUP:
938 CString sCmd;
939 sCmd.Format(_T("/command:cleanup /path:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
940 CAppUtils::RunTortoiseGitProc(sCmd);
942 break;
943 case ID_SUBMODULE_UPDATE:
945 CString sCmd;
946 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
947 CAppUtils::RunTortoiseGitProc(sCmd);
949 break;
950 case ID_SHOWBRANCHES:
952 CString sCmd;
953 sCmd.Format(_T("git.exe branch -a --contains %s"), (LPCTSTR)pSelLogEntry->m_CommitHash.ToString());
954 CProgressDlg progress;
955 progress.m_AutoClose = AUTOCLOSE_NO;
956 progress.m_GitCmd = sCmd;
957 progress.DoModal();
959 break;
960 case ID_DELETE:
962 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd) : nullptr;
963 if (!branch)
965 CMessageBox::Show(GetSafeHwnd(), IDS_ERROR_NOREF, IDS_APPNAME, MB_OK | MB_ICONERROR);
966 return;
968 CString shortname;
969 if (branch == (CString*)MAKEINTRESOURCE(IDS_ALL))
971 CString currentBranch = L"refs/heads/" + m_CurrentBranch;
972 bool nothingDeleted = true;
973 for (const auto& ref : m_HashMap[pSelLogEntry->m_CommitHash])
975 if (ref == currentBranch)
976 continue;
977 if (!DeleteRef(ref))
978 break;
979 nothingDeleted = false;
981 if (nothingDeleted)
982 return;
984 else if (!DeleteRef(*branch))
985 return;
986 this->ReloadHashMap();
987 if (m_pFindDialog)
988 m_pFindDialog->RefreshList();
989 CRect rect;
990 this->GetItemRect(FirstSelect,&rect,LVIR_BOUNDS);
991 this->InvalidateRect(rect);
993 break;
995 case ID_FINDENTRY:
997 m_nSearchIndex = GetSelectionMark();
998 if (m_nSearchIndex < 0)
999 m_nSearchIndex = 0;
1000 if (m_pFindDialog)
1001 break;
1002 else
1004 m_pFindDialog = new CFindDlg();
1005 m_pFindDialog->Create(this);
1008 break;
1009 case ID_MERGEREV:
1011 CString str = pSelLogEntry->m_CommitHash.ToString();
1012 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd & 0xFFFF) : nullptr;
1013 if (branch)
1014 str = *branch;
1015 else if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
1016 str = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
1017 // we need an URL to complete this command, so error out if we can't get an URL
1018 if(CAppUtils::Merge(&str))
1020 this->Refresh();
1023 break;
1024 case ID_REVERTREV:
1026 int parent = 0;
1027 if (GetSelectedCount() == 1)
1029 parent = cmd >> 16;
1030 if ((size_t)parent > pSelLogEntry->m_ParentHash.size())
1032 CString str;
1033 str.Format(IDS_PROC_NOPARENT, parent);
1034 MessageBox(str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1035 return;
1039 if (!this->RevertSelectedCommits(parent))
1041 if (CMessageBox::Show(m_hWnd, IDS_REVREVERTED, IDS_APPNAME, 1, IDI_QUESTION, IDS_OKBUTTON, IDS_COMMITBUTTON) == 2)
1043 CTGitPathList pathlist;
1044 CTGitPathList selectedlist;
1045 pathlist.AddPath(this->m_Path);
1046 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
1047 CString str;
1048 CAppUtils::Commit(CString(), false, str, pathlist, selectedlist, bSelectFilesForCommit);
1050 this->Refresh();
1053 break;
1054 case ID_EDITNOTE:
1056 CAppUtils::EditNote(pSelLogEntry);
1057 this->SetItemState(FirstSelect, 0, LVIS_SELECTED);
1058 this->SetItemState(FirstSelect, LVIS_SELECTED, LVIS_SELECTED);
1060 break;
1061 default:
1062 //CMessageBox::Show(nullptr, _T("Have not implemented"), _T("TortoiseGit"), MB_OK);
1063 break;
1064 #if 0
1066 case ID_BLAMECOMPARE:
1068 //user clicked on the menu item "compare with working copy"
1069 //now first get the revision which is selected
1070 if (PromptShown())
1072 GitDiff diff(this, this->m_hWnd, true);
1073 diff.SetHEADPeg(m_LogRevision);
1074 diff.ShowCompare(m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), false, true);
1076 else
1077 CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), m_LogRevision, false, false, true);
1079 break;
1080 case ID_BLAMEWITHPREVIOUS:
1082 //user clicked on the menu item "Compare and Blame with previous revision"
1083 if (PromptShown())
1085 GitDiff diff(this, this->m_hWnd, true);
1086 diff.SetHEADPeg(m_LogRevision);
1087 diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), false, true);
1089 else
1090 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
1092 break;
1094 case ID_OPENWITH:
1095 bOpenWith = true;
1096 case ID_OPEN:
1098 CProgressDlg progDlg;
1099 progDlg.SetTitle(IDS_APPNAME);
1100 progDlg.SetAnimation(IDR_DOWNLOAD);
1101 CString sInfoLine;
1102 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, m_path.GetWinPath(), (LPCTSTR)revSelected.ToString());
1103 progDlg.SetLine(1, sInfoLine, true);
1104 SetAndClearProgressInfo(&progDlg);
1105 progDlg.ShowModeless(m_hWnd);
1106 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, m_path, revSelected);
1107 bool bSuccess = true;
1108 if (!Cat(m_path, GitRev(GitRev::REV_HEAD), revSelected, tempfile))
1110 bSuccess = false;
1111 // try again, but with the selected revision as the peg revision
1112 if (!Cat(m_path, revSelected, revSelected, tempfile))
1114 progDlg.Stop();
1115 SetAndClearProgressInfo(nullptr);
1116 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1117 EnableOKButton();
1118 break;
1120 bSuccess = true;
1122 if (bSuccess)
1124 progDlg.Stop();
1125 SetAndClearProgressInfo(nullptr);
1126 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1127 if (!bOpenWith)
1128 CAppUtils::ShellOpen(tempfile.GetWinPath(), GetSafeHwnd());
1129 else
1130 CAppUtils::ShowOpenWithDialog(tempfile.GetWinPathString(), GetSafeHwnd());
1133 break;
1134 case ID_BLAME:
1136 CBlameDlg dlg;
1137 dlg.EndRev = revSelected;
1138 if (dlg.DoModal() == IDOK)
1140 CBlame blame;
1141 CString tempfile;
1142 CString logfile;
1143 tempfile = blame.BlameToTempFile(m_path, dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, _T(""), dlg.m_bIncludeMerge, TRUE, TRUE);
1144 if (!tempfile.IsEmpty())
1146 if (dlg.m_bTextView)
1148 //open the default text editor for the result file
1149 CAppUtils::StartTextViewer(tempfile);
1151 else
1153 CString sParams = _T("/path:\"") + m_path.GetGitPathString() + _T("\" ");
1154 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(m_path.GetFileOrDirectoryName()),sParams))
1156 break;
1160 else
1162 CMessageBox::Show(this->m_hWnd, blame.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1166 break;
1167 case ID_EXPORT:
1169 CString sCmd;
1170 sCmd.Format(_T("%s /command:export /path:\"%s\" /revision:%ld"),
1171 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseGitProc.exe")),
1172 (LPCTSTR)pathURL, (LONG)revSelected);
1173 CAppUtils::LaunchApplication(sCmd, nullptr, false);
1175 break;
1176 case ID_VIEWREV:
1178 CString url = m_ProjectProperties.sWebViewerRev;
1179 url = GetAbsoluteUrlFromRelativeUrl(url);
1180 url.Replace(_T("%REVISION%"), revSelected.ToString());
1181 if (!url.IsEmpty())
1182 ShellExecute(this->m_hWnd, _T("open"), url, nullptr, nullptr, SW_SHOWDEFAULT);
1184 break;
1185 case ID_VIEWPATHREV:
1187 CString relurl = pathURL;
1188 CString sRoot = GetRepositoryRoot(CTGitPath(relurl));
1189 relurl = relurl.Mid(sRoot.GetLength());
1190 CString url = m_ProjectProperties.sWebViewerPathRev;
1191 url = GetAbsoluteUrlFromRelativeUrl(url);
1192 url.Replace(_T("%REVISION%"), revSelected.ToString());
1193 url.Replace(_T("%PATH%"), relurl);
1194 if (!url.IsEmpty())
1195 ShellExecute(this->m_hWnd, _T("open"), url, nullptr, nullptr, SW_SHOWDEFAULT);
1197 break;
1198 #endif
1200 } // switch (cmd)
1202 theApp.DoWaitCursor(-1);
1205 void CGitLogList::SetSelectedRebaseAction(int action)
1207 POSITION pos = GetFirstSelectedItemPosition();
1208 if (!pos) return;
1209 int index;
1210 while(pos)
1212 index = GetNextSelectedItem(pos);
1213 if (m_arShownList.SafeGetAt(index)->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT | LOGACTIONS_REBASE_DONE) || (index == GetItemCount() - 1 && action == LOGACTIONS_REBASE_SQUASH))
1214 continue;
1215 if (!m_bIsCherryPick && m_arShownList.SafeGetAt(index)->ParentsCount() > 1 && action == LOGACTIONS_REBASE_SQUASH)
1216 continue;
1217 m_arShownList.SafeGetAt(index)->GetRebaseAction() = action;
1218 CRect rect;
1219 this->GetItemRect(index,&rect,LVIR_BOUNDS);
1220 this->InvalidateRect(rect);
1223 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage);
1226 void CGitLogList::SetUnselectedRebaseAction(int action)
1228 POSITION pos = GetFirstSelectedItemPosition();
1229 int index = pos ? GetNextSelectedItem(pos) : -1;
1230 for (int i = 0; i < GetItemCount(); i++)
1232 if (i == index)
1234 index = pos ? GetNextSelectedItem(pos) : -1;
1235 continue;
1238 if (m_arShownList.SafeGetAt(i)->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT | LOGACTIONS_REBASE_DONE) || (i == GetItemCount() - 1 && action == LOGACTIONS_REBASE_SQUASH) || (!m_bIsCherryPick && action == LOGACTIONS_REBASE_SQUASH && m_arShownList.SafeGetAt(i)->ParentsCount() != 1))
1239 continue;
1240 m_arShownList.SafeGetAt(i)->GetRebaseAction() = action;
1241 CRect rect;
1242 this->GetItemRect(i, &rect, LVIR_BOUNDS);
1243 this->InvalidateRect(rect);
1246 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage);
1249 void CGitLogList::ShiftSelectedRebaseAction()
1251 POSITION pos = GetFirstSelectedItemPosition();
1252 int index;
1253 while(pos)
1255 index = GetNextSelectedItem(pos);
1256 int* action = &(m_arShownList.SafeGetAt(index))->GetRebaseAction();
1257 switch (*action)
1259 case LOGACTIONS_REBASE_PICK:
1260 *action = LOGACTIONS_REBASE_SKIP;
1261 break;
1262 case LOGACTIONS_REBASE_SKIP:
1263 *action= LOGACTIONS_REBASE_EDIT;
1264 break;
1265 case LOGACTIONS_REBASE_EDIT:
1266 *action = LOGACTIONS_REBASE_SQUASH;
1267 if (index == GetItemCount() - 1 && (m_bIsCherryPick || m_arShownList.SafeGetAt(index)->m_ParentHash.size() == 1))
1268 *action = LOGACTIONS_REBASE_PICK;
1269 break;
1270 case LOGACTIONS_REBASE_SQUASH:
1271 *action= LOGACTIONS_REBASE_PICK;
1272 break;
1274 CRect rect;
1275 this->GetItemRect(index, &rect, LVIR_BOUNDS);
1276 this->InvalidateRect(rect);