Fixed issue #3304: Double click on stash list item does nothing, show log instead
[TortoiseGit.git] / src / TortoiseProc / GitLogListAction.cpp
blob312fa2515f44b2bc3150c583f5ee39b8a8ca7482
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2018 - 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 "CommitIsOnRefsDlg.h"
41 #include "GitDiff.h"
42 #include "../TGitCache/CacheInterface.h"
44 IMPLEMENT_DYNAMIC(CGitLogList, CHintCtrl<CListCtrl>)
46 static bool GetFirstEntryStartingWith(STRING_VECTOR& heystack, const CString& needle, CString& result)
48 auto it = std::find_if(heystack.cbegin(), heystack.cend(), [&needle](const CString& entry) { return CStringUtils::StartsWith(entry, needle); });
49 if (it == heystack.cend())
50 return false;
51 result = *it;
52 return true;
55 int CGitLogList::RevertSelectedCommits(int parent)
57 CSysProgressDlg progress;
58 int ret = -1;
60 #if 0
61 if(!g_Git.CheckCleanWorkTree())
62 CMessageBox::Show(nullptr, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
63 #endif
65 if (this->GetSelectedCount() > 1)
67 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_REVERTCOMMIT)));
68 progress.SetTime(true);
69 progress.ShowModeless(this);
72 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
74 POSITION pos = GetFirstSelectedItemPosition();
75 int i=0;
76 while(pos)
78 int index = GetNextSelectedItem(pos);
79 GitRev* r1 = m_arShownList.SafeGetAt(index);
81 if (progress.IsVisible())
83 progress.FormatNonPathLine(1, IDS_PROC_REVERTCOMMIT, (LPCTSTR)r1->m_CommitHash.ToString());
84 progress.FormatNonPathLine(2, L"%s", (LPCTSTR)r1->GetSubject());
85 progress.SetProgress(i, this->GetSelectedCount());
87 ++i;
89 if(r1->m_CommitHash.IsEmpty())
90 continue;
92 if (g_Git.GitRevert(parent, r1->m_CommitHash))
94 CString str;
95 str.LoadString(IDS_SVNACTION_FAILEDREVERT);
96 str = g_Git.GetGitLastErr(str, CGit::GIT_CMD_REVERT);
97 if( GetSelectedCount() == 1)
98 CMessageBox::Show(GetParentHWND(), str, L"TortoiseGit", MB_OK | MB_ICONERROR);
99 else if (CMessageBox::Show(GetParentHWND(), str, L"TortoiseGit", 2, IDI_ERROR, CString(MAKEINTRESOURCE(IDS_SKIPBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
100 return ret;
102 else
103 ret =0;
105 if (progress.HasUserCancelled())
106 break;
108 return ret;
110 int CGitLogList::CherryPickFrom(CString from, CString to)
112 CLogDataVector logs(&m_LogCache);
113 CString range;
114 range.Format(L"%s..%s", (LPCTSTR)from, (LPCTSTR)to);
115 if (logs.ParserFromLog(nullptr, 0, 0, &range))
116 return -1;
118 if (logs.empty())
119 return 0;
121 CSysProgressDlg progress;
122 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_CHERRYPICK)));
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, (LPCTSTR)logs.GetGitRevAt(i).m_CommitHash.ToString());
133 progress.FormatNonPathLine(2, L"%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(L"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)) + L":\r\n\r\n" + out));
144 return 0;
147 void CGitLogList::ContextMenuAction(int cmd,int FirstSelect, int LastSelect, CMenu *popmenu)
149 POSITION pos = GetFirstSelectedItemPosition();
150 int indexNext = GetNextSelectedItem(pos);
151 if (indexNext < 0)
152 return;
154 GitRevLoglist* pSelLogEntry = m_arShownList.SafeGetAt(indexNext);
156 bool bShiftPressed = !!(GetAsyncKeyState(VK_SHIFT) & 0x8000);
158 theApp.DoWaitCursor(1);
159 switch (cmd&0xFFFF)
161 case ID_COMMIT:
163 CTGitPathList pathlist;
164 pathlist.AddPath(this->m_Path);
165 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(L"Software\\TortoiseGit\\SelectFilesForCommit", TRUE));
166 CString str;
167 CAppUtils::Commit(GetParentHWND(), CString(), false, str,
168 pathlist, bSelectFilesForCommit);
169 //this->Refresh();
170 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH,0);
172 break;
173 case ID_MERGE_ABORT:
175 if (CAppUtils::MergeAbort(GetParentHWND()))
176 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH, 0);
178 break;
179 case ID_GNUDIFF1: // compare with WC, unified
181 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
182 if(!r1->m_CommitHash.IsEmpty())
184 CString hash2;
185 bool bMerge = false, bCombine = false;
186 CString merge;
187 cmd >>= 16;
188 if( (cmd&0xFFFF) == 0xFFFF)
189 bMerge = true;
190 else if((cmd&0xFFFF) == 0xFFFE)
191 bCombine = true;
192 else if ((cmd & 0xFFFF) == 0xFFFD)
194 CString tempfile = GetTempFile();
195 CString gitcmd = L"git.exe diff-tree --cc " + r1->m_CommitHash.ToString();
196 CString lastErr;
197 if (g_Git.RunLogFile(gitcmd, tempfile, &lastErr))
199 MessageBox(lastErr, L"TortoiseGit", MB_ICONERROR);
200 break;
205 CStdioFile file(tempfile, CFile::typeText | CFile::modeRead | CFile::shareDenyWrite);
206 CString strLine;
207 bool isHash = file.ReadString(strLine) && r1->m_CommitHash.ToString() == strLine;
208 bool more = isHash && file.ReadString(strLine) && !strLine.IsEmpty();
209 if (!more)
211 CMessageBox::Show(GetParentHWND(), IDS_NOCHANGEAFTERMERGE, IDS_APPNAME, MB_OK);
212 break;
215 catch (CFileException* e)
217 e->Delete();
220 CAppUtils::StartUnifiedDiffViewer(tempfile, r1->m_CommitHash.ToString());
221 break;
223 else
225 if (cmd == 0)
226 cmd = 1;
227 if ((size_t)cmd > r1->m_ParentHash.size())
229 CString str;
230 str.Format(IDS_PROC_NOPARENT, cmd);
231 MessageBox(str, L"TortoiseGit", MB_OK | MB_ICONERROR);
232 return;
234 else
236 if(cmd>0)
237 hash2 = r1->m_ParentHash[cmd-1].ToString();
240 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
241 CAppUtils::StartShowUnifiedDiff(nullptr, m_Path, hash2, m_Path, r1->m_CommitHash.ToString(), bShiftPressed, false, false, bMerge, bCombine);
242 else
244 CString path = m_Path.GetGitPathString();
245 // start with 1 (0 = working copy changes)
246 for (int i = m_bShowWC ? 1 : 0; i < FirstSelect; ++i)
248 GitRevLoglist* first = m_arShownList.SafeGetAt(i);
249 CTGitPathList list = first->GetFiles(nullptr);
250 const CTGitPath* file = list.LookForGitPath(path);
251 if (file && !file->GetGitOldPathString().IsEmpty())
252 path = file->GetGitOldPathString();
254 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(path), hash2, CTGitPath(path), r1->m_CommitHash.ToString(), bShiftPressed, false, false, bMerge, bCombine);
257 else
258 CAppUtils::StartShowUnifiedDiff(nullptr, m_Path, L"HEAD", m_Path, GitRev::GetWorkingCopy(), bShiftPressed);
260 break;
262 case ID_GNUDIFF2: // compare two revisions, unified
264 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
265 GitRev* r2 = m_arShownList.SafeGetAt(LastSelect);
266 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
267 CAppUtils::StartShowUnifiedDiff(nullptr, m_Path, r2->m_CommitHash.ToString(), m_Path, r1->m_CommitHash.ToString(), bShiftPressed);
268 else
270 CString path = m_Path.GetGitPathString();
271 // start with 1 (0 = working copy changes)
272 for (int i = m_bShowWC ? 1 : 0; i < FirstSelect; ++i)
274 GitRevLoglist* first = m_arShownList.SafeGetAt(i);
275 CTGitPathList list = first->GetFiles(nullptr);
276 const CTGitPath* file = list.LookForGitPath(path);
277 if (file && !file->GetGitOldPathString().IsEmpty())
278 path = file->GetGitOldPathString();
280 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(path), r2->m_CommitHash.ToString(), CTGitPath(path), r1->m_CommitHash.ToString(), bShiftPressed);
283 break;
285 case ID_COMPARETWO: // compare two revisions
287 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
288 GitRev* r2 = m_arShownList.SafeGetAt(LastSelect);
289 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
290 CGitDiff::DiffCommit(GetParentHWND(), m_Path, r1, r2, bShiftPressed);
291 else
293 CString path1 = m_Path.GetGitPathString();
294 // start with 1 (0 = working copy changes)
295 for (int i = m_bShowWC ? 1 : 0; i < FirstSelect; ++i)
297 GitRevLoglist* first = m_arShownList.SafeGetAt(i);
298 CTGitPathList list = first->GetFiles(nullptr);
299 const CTGitPath* file = list.LookForGitPath(path1);
300 if (file && !file->GetGitOldPathString().IsEmpty())
301 path1 = file->GetGitOldPathString();
303 CString path2 = path1;
304 for (int i = FirstSelect; i < LastSelect; ++i)
306 GitRevLoglist* first = m_arShownList.SafeGetAt(i);
307 CTGitPathList list = first->GetFiles(nullptr);
308 const CTGitPath* file = list.LookForGitPath(path2);
309 if (file && !file->GetGitOldPathString().IsEmpty())
310 path2 = file->GetGitOldPathString();
312 CGitDiff::DiffCommit(GetParentHWND(), CTGitPath(path1), CTGitPath(path2), r1, r2, bShiftPressed);
316 break;
318 case ID_COMPARE: // compare revision with WC
320 GitRevLoglist* r1 = &m_wcRev;
321 GitRevLoglist* r2 = pSelLogEntry;
323 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
324 CGitDiff::DiffCommit(GetParentHWND(), m_Path, r1, r2, bShiftPressed);
325 else
327 CString path1 = m_Path.GetGitPathString();
328 // start with 1 (0 = working copy changes)
329 for (int i = m_bShowWC ? 1 : 0; i < FirstSelect; ++i)
331 GitRevLoglist* first = m_arShownList.SafeGetAt(i);
332 CTGitPathList list = first->GetFiles(nullptr);
333 const CTGitPath* file = list.LookForGitPath(path1);
334 if (file && !file->GetGitOldPathString().IsEmpty())
335 path1 = file->GetGitOldPathString();
337 CGitDiff::DiffCommit(GetParentHWND(), m_Path, CTGitPath(path1), r1, r2, bShiftPressed);
340 //user clicked on the menu item "compare with working copy"
341 //if (PromptShown())
343 // GitDiff diff(this, m_hWnd, true);
344 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
345 // diff.SetHEADPeg(m_LogRevision);
346 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
348 //else
349 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
351 break;
353 case ID_COMPAREWITHPREVIOUS:
355 if (pSelLogEntry->m_ParentHash.empty())
357 if (pSelLogEntry->GetParentFromHash(pSelLogEntry->m_CommitHash))
358 MessageBox(pSelLogEntry->GetLastErr(), L"TortoiseGit", MB_ICONERROR);
361 if (!pSelLogEntry->m_ParentHash.empty())
362 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
364 cmd>>=16;
365 cmd&=0xFFFF;
367 if(cmd == 0)
368 cmd=1;
370 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
371 CGitDiff::DiffCommit(GetParentHWND(), m_Path, pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString(), bShiftPressed);
372 else
374 CString path1 = m_Path.GetGitPathString();
375 // start with 1 (0 = working copy changes)
376 for (int i = m_bShowWC ? 1 : 0; i < indexNext; ++i)
378 GitRevLoglist* first = m_arShownList.SafeGetAt(i);
379 CTGitPathList list = first->GetFiles(nullptr);
380 const CTGitPath* file = list.LookForGitPath(path1);
381 if (file && !file->GetGitOldPathString().IsEmpty())
382 path1 = file->GetGitOldPathString();
384 CString path2 = path1;
385 GitRevLoglist* first = m_arShownList.SafeGetAt(indexNext);
386 CTGitPathList list = first->GetFiles(nullptr);
387 const CTGitPath* file = list.LookForGitPath(path2);
388 if (file && !file->GetGitOldPathString().IsEmpty())
389 path2 = file->GetGitOldPathString();
391 CGitDiff::DiffCommit(GetParentHWND(), CTGitPath(path1), CTGitPath(path2), pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString(), bShiftPressed);
394 else
396 CMessageBox::Show(GetParentHWND(), IDS_PROC_NOPREVIOUSVERSION, IDS_APPNAME, MB_OK);
398 //if (PromptShown())
400 // GitDiff diff(this, m_hWnd, true);
401 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
402 // diff.SetHEADPeg(m_LogRevision);
403 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
405 //else
406 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
408 break;
409 case ID_COMPARETWOCOMMITCHANGES:
411 auto pFirstEntry = m_arShownList.SafeGetAt(FirstSelect);
412 auto pLastEntry = m_arShownList.SafeGetAt(LastSelect);
413 CString patch1 = GetTempFile();
414 CString patch2 = GetTempFile();
415 CString err;
416 if (g_Git.RunLogFile(L"git.exe format-patch --stdout " + pFirstEntry->m_CommitHash.ToString() + L"~1.." + pFirstEntry->m_CommitHash.ToString() + L"", patch1, &err))
418 MessageBox(L"Could not generate patch for commit " + pFirstEntry->m_CommitHash.ToString() + L".\n" + err, L"TortoiseGit", MB_ICONERROR);
419 break;
421 if (g_Git.RunLogFile(L"git.exe format-patch --stdout " + pLastEntry->m_CommitHash.ToString() + L"~1.." + pLastEntry->m_CommitHash.ToString() + L"", patch2, &err))
423 MessageBox(L"Could not generate patch for commit " + pLastEntry->m_CommitHash.ToString() + L".\n" + err, L"TortoiseGit", MB_ICONERROR);
424 break;
426 CAppUtils::DiffFlags flags;
427 CAppUtils::StartExtDiff(patch1, patch2, pFirstEntry->m_CommitHash.ToString(), pLastEntry->m_CommitHash.ToString(), pFirstEntry->m_CommitHash.ToString() + L".patch", pLastEntry->m_CommitHash.ToString() + L".patch", pFirstEntry->m_CommitHash.ToString(), pLastEntry->m_CommitHash.ToString(), flags);
429 break;
430 case ID_LOG_VIEWRANGE:
431 case ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE:
433 GitRev* pLastEntry = m_arShownList.SafeGetAt(LastSelect);
435 CString sep = L"..";
436 if ((cmd & 0xFFFF) == ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE)
437 sep = L"...";
439 CString cmdline;
440 cmdline.Format(L"/command:log /path:\"%s\" /range:\"%s%s%s\"",
441 (LPCTSTR)g_Git.CombinePath(m_Path), (LPCTSTR)pLastEntry->m_CommitHash.ToString(), (LPCTSTR)sep, (LPCTSTR)pSelLogEntry->m_CommitHash.ToString());
442 CAppUtils::RunTortoiseGitProc(cmdline);
444 break;
445 case ID_COPYCLIPBOARDFULL:
446 case ID_COPYCLIPBOARDFULLNOPATHS:
447 case ID_COPYCLIPBOARDHASH:
448 case ID_COPYCLIPBOARDAUTHORSFULL:
449 case ID_COPYCLIPBOARDAUTHORSNAME:
450 case ID_COPYCLIPBOARDAUTHORSEMAIL:
451 case ID_COPYCLIPBOARDSUBJECTS:
452 case ID_COPYCLIPBOARDMESSAGES:
454 CopySelectionToClipBoard(cmd & 0xFFFF);
456 break;
457 case ID_COPYCLIPBOARDBRANCHTAG:
459 if (!popmenu)
460 break;
461 auto selectedBranch = reinterpret_cast<const CString*>(reinterpret_cast<CIconMenu*>(popmenu)->GetMenuItemData(cmd));
462 CString sClipboard;
463 if (selectedBranch)
465 if (CStringUtils::StartsWith(*selectedBranch, L"refs/tags/"))
466 sClipboard = selectedBranch->Mid((int)wcslen(L"refs/tags/")).TrimRight(L"^{}");
467 else
468 sClipboard = CGit::StripRefName(*selectedBranch);
470 else
472 for (const auto& ref : m_HashMap[pSelLogEntry->m_CommitHash])
474 sClipboard += ref;
475 sClipboard += L"\r\n";
478 CStringUtils::WriteAsciiStringToClipboard(sClipboard, GetSafeHwnd());
480 break;
481 case ID_EXPORT:
483 CString str=pSelLogEntry->m_CommitHash.ToString();
484 // try to get the tag
485 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], L"refs/tags/", str);
486 CAppUtils::Export(GetParentHWND(), &str, &m_Path);
488 break;
489 case ID_CREATE_BRANCH:
490 case ID_CREATE_TAG:
492 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd & 0xFFFF) : nullptr;
493 CString str = pSelLogEntry->m_CommitHash.ToString();
494 if (branch)
495 str = *branch;
496 else // try to guess remote branch in order to enable tracking
497 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], L"refs/remotes/", str);
499 CAppUtils::CreateBranchTag(GetParentHWND(), (cmd & 0xFFFF) == ID_CREATE_TAG, &str);
500 ReloadHashMap();
501 if (m_pFindDialog)
502 m_pFindDialog->RefreshList();
503 Invalidate();
504 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
506 break;
507 case ID_SWITCHTOREV:
509 CString str = pSelLogEntry->m_CommitHash.ToString();
510 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd & 0xFFFF) : nullptr;
511 if (branch)
512 str = *branch;
513 else // try to guess remote branch in order to recommend good branch name and tracking
514 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], L"refs/remotes/", str);
516 CAppUtils::Switch(GetParentHWND(), str);
518 ReloadHashMap();
519 Invalidate();
520 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
521 break;
522 case ID_SWITCHBRANCH:
523 if(popmenu)
525 const CString* branch = (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
526 if(branch)
528 CString name = *branch;
529 CGit::GetShortName(*branch, name, L"refs/heads/");
530 CAppUtils::PerformSwitch(GetParentHWND(), name);
532 ReloadHashMap();
533 Invalidate();
534 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
536 break;
537 case ID_RESET:
539 CString str = pSelLogEntry->m_CommitHash.ToString();
540 if (CAppUtils::GitReset(GetParentHWND(), &str))
542 ResetWcRev(true);
543 ReloadHashMap();
544 Invalidate();
547 break;
548 case ID_REBASE_PICK:
549 SetSelectedRebaseAction(LOGACTIONS_REBASE_PICK);
550 break;
551 case ID_REBASE_EDIT:
552 SetSelectedRebaseAction(LOGACTIONS_REBASE_EDIT);
553 break;
554 case ID_REBASE_SQUASH:
555 SetSelectedRebaseAction(LOGACTIONS_REBASE_SQUASH);
556 break;
557 case ID_REBASE_SKIP:
558 SetSelectedRebaseAction(LOGACTIONS_REBASE_SKIP);
559 break;
560 case ID_COMBINE_COMMIT:
562 CString head;
563 CGitHash headhash;
564 CGitHash hashFirst,hashLast;
566 int headindex=GetHeadIndex();
567 if(headindex>=0) //incase show all branch, head is not the first commits.
569 head.Format(L"HEAD~%d", FirstSelect - headindex);
570 if (g_Git.GetHash(hashFirst, head))
572 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of first selected revision."), L"TortoiseGit", MB_ICONERROR);
573 break;
576 head.Format(L"HEAD~%d", LastSelect - headindex);
577 if (g_Git.GetHash(hashLast, head))
579 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of last selected revision."), L"TortoiseGit", MB_ICONERROR);
580 break;
584 GitRev* pFirstEntry = m_arShownList.SafeGetAt(FirstSelect);
585 GitRev* pLastEntry = m_arShownList.SafeGetAt(LastSelect);
586 if(pFirstEntry->m_CommitHash != hashFirst || pLastEntry->m_CommitHash != hashLast)
588 CMessageBox::Show(GetParentHWND(), IDS_PROC_CANNOTCOMBINE, IDS_APPNAME, MB_OK | MB_ICONEXCLAMATION);
589 break;
592 GitRev lastRevision;
593 if (lastRevision.GetParentFromHash(hashLast))
595 MessageBox(lastRevision.GetLastErr(), L"TortoiseGit", MB_ICONERROR);
596 break;
599 if (g_Git.GetHash(headhash, L"HEAD"))
601 MessageBox(g_Git.GetGitLastErr(L"Could not get HEAD hash."), L"TortoiseGit", MB_ICONERROR);
602 break;
605 if(!g_Git.CheckCleanWorkTree())
607 CMessageBox::Show(GetParentHWND(), IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK | MB_ICONEXCLAMATION);
608 break;
610 CString sCmd, out;
612 //Use throw to abort this process (reset back to original HEAD)
615 sCmd.Format(L"git.exe reset --hard %s --", (LPCTSTR)pFirstEntry->m_CommitHash.ToString());
616 if(g_Git.Run(sCmd, &out, CP_UTF8))
618 MessageBox(out, L"TortoiseGit", MB_OK | MB_ICONERROR);
619 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1)) + L"\r\n\r\n" + out));
621 sCmd.Format(L"git.exe reset --soft %s --", (LPCTSTR)hashLast.ToString());
622 if(g_Git.Run(sCmd, &out, CP_UTF8))
624 MessageBox(out, L"TortoiseGit", MB_OK | MB_ICONERROR);
625 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2)) + L"\r\n\r\n"+out));
628 CCommitDlg dlg;
629 for (int i = FirstSelect; i <= LastSelect; ++i)
631 GitRev* pRev = m_arShownList.SafeGetAt(i);
632 dlg.m_sLogMessage += pRev->GetSubject() + L'\n' + pRev->GetBody();
633 dlg.m_sLogMessage += L'\n';
635 dlg.m_bWholeProject=true;
636 dlg.m_bSelectFilesForCommit = true;
637 dlg.m_bForceCommitAmend=true;
638 int squashDate = (int)CRegDWORD(L"Software\\TortoiseGit\\SquashDate", 0);
639 if (squashDate == 1)
640 dlg.SetTime(m_arShownList.SafeGetAt(FirstSelect)->GetAuthorDate());
641 else if (squashDate == 2)
642 dlg.SetTime(CTime::GetCurrentTime());
643 else
644 dlg.SetTime(m_arShownList.SafeGetAt(LastSelect)->GetAuthorDate());
645 CTGitPathList gpl;
646 gpl.AddPath(CTGitPath(g_Git.m_CurrentDir));
647 dlg.m_pathList = gpl;
648 if (lastRevision.ParentsCount() != 1)
650 MessageBox(L"The following commit dialog can only show changes of oldest commit if it has exactly one parent. This is not the case right now.", L"TortoiseGit", MB_OK | MB_ICONINFORMATION);
651 dlg.m_bAmendDiffToLastCommit = TRUE;
653 else
654 dlg.m_bAmendDiffToLastCommit = FALSE;
655 dlg.m_bNoPostActions=true;
656 dlg.m_AmendStr=dlg.m_sLogMessage;
658 if (dlg.DoModal() == IDOK)
660 if(pFirstEntry->m_CommitHash!=headhash)
662 if (CherryPickFrom(pFirstEntry->m_CommitHash.ToString(), headhash.ToString()))
664 CString msg;
665 msg.Format(L"Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n");
666 throw std::exception(CUnicodeUtils::GetUTF8(msg));
670 else
671 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_USERCANCELLED))));
673 catch(std::exception& e)
675 CMessageBox::Show(GetParentHWND(), CUnicodeUtils::GetUnicode(CStringA(e.what())), L"TortoiseGit", MB_OK | MB_ICONERROR);
676 sCmd.Format(L"git.exe reset --hard %s --", (LPCTSTR)headhash.ToString());
677 out.Empty();
678 if(g_Git.Run(sCmd, &out, CP_UTF8))
679 MessageBox(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD)) + L"\r\n\r\n" + out, L"TortoiseGit", MB_OK | MB_ICONERROR);
681 Refresh();
683 break;
685 case ID_CHERRY_PICK:
687 if (m_bThreadRunning)
689 CMessageBox::Show(GetParentHWND(), IDS_PROC_LOG_ONLYONCE, IDS_APPNAME, MB_ICONEXCLAMATION);
690 break;
692 CRebaseDlg dlg;
693 dlg.m_IsCherryPick = TRUE;
694 dlg.m_Upstream = this->m_CurrentBranch;
695 POSITION pos2 = GetFirstSelectedItemPosition();
696 while(pos2)
698 int indexNext2 = GetNextSelectedItem(pos2);
699 dlg.m_CommitList.m_logEntries.push_back(m_arShownList.SafeGetAt(indexNext2)->m_CommitHash);
700 dlg.m_CommitList.m_LogCache.m_HashMap[m_arShownList.SafeGetAt(indexNext2)->m_CommitHash] = *m_arShownList.SafeGetAt(indexNext2);
701 dlg.m_CommitList.m_logEntries.GetGitRevAt(dlg.m_CommitList.m_logEntries.size() - 1).GetRebaseAction() |= LOGACTIONS_REBASE_PICK;
704 if(dlg.DoModal() == IDOK)
706 Refresh();
709 break;
710 case ID_REBASE_TO_VERSION:
712 if (m_bThreadRunning)
714 CMessageBox::Show(GetParentHWND(), IDS_PROC_LOG_ONLYONCE, IDS_APPNAME, MB_ICONEXCLAMATION);
715 break;
717 CRebaseDlg dlg;
718 auto refList = m_HashMap[pSelLogEntry->m_CommitHash];
719 dlg.m_Upstream = refList.empty() ? pSelLogEntry->m_CommitHash.ToString() : refList.front();
720 for (const auto& ref : refList)
721 if (CGit::GetShortName(ref, dlg.m_Upstream, L"refs/heads/"))
722 break;
724 if(dlg.DoModal() == IDOK)
726 Refresh();
730 break;
732 case ID_STASH_SAVE:
733 if (CAppUtils::StashSave(GetParentHWND()))
734 Refresh();
735 break;
737 case ID_STASH_POP:
738 if (CAppUtils::StashPop(GetParentHWND()))
739 Refresh();
740 break;
742 case ID_STASH_LIST:
743 CAppUtils::RunTortoiseGitProc(L"/command:reflog /ref:refs/stash");
744 break;
746 case ID_REFLOG_STASH_APPLY:
747 CAppUtils::StashApply(GetParentHWND(), pSelLogEntry->m_Ref);
748 break;
750 case ID_REFLOG_DEL:
752 CString str;
753 if (GetSelectedCount() > 1)
754 str.Format(IDS_PROC_DELETENREFS, GetSelectedCount());
755 else
756 str.Format(IDS_PROC_DELETEREF, (LPCTSTR)pSelLogEntry->m_Ref);
758 if (CMessageBox::Show(GetParentHWND(), str, L"TortoiseGit", 1, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
759 return;
761 std::vector<CString> refsToDelete;
762 POSITION pos2 = GetFirstSelectedItemPosition();
763 while (pos2)
765 CString ref = m_arShownList.SafeGetAt(GetNextSelectedItem(pos2))->m_Ref;
766 if (CStringUtils::StartsWith(ref, L"refs/"))
767 ref = ref.Mid((int)wcslen(L"refs/"));
768 int refpos = ref.ReverseFind(L'{');
769 if (refpos > 0 && ref.Mid(refpos - 1, 2) != L"@{")
770 ref = ref.Left(refpos) + L'@'+ ref.Mid(refpos);
771 refsToDelete.push_back(ref);
774 for (auto revIt = refsToDelete.crbegin(); revIt != refsToDelete.crend(); ++revIt)
776 CString ref = *revIt;
777 CString sCmd, out;
778 if (CStringUtils::StartsWith(ref, L"stash"))
779 sCmd.Format(L"git.exe stash drop %s", (LPCTSTR)ref);
780 else
781 sCmd.Format(L"git.exe reflog delete %s", (LPCTSTR)ref);
783 if (g_Git.Run(sCmd, &out, CP_UTF8))
784 MessageBox(out, L"TortoiseGit", MB_OK | MB_ICONERROR);
786 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
789 break;
790 case ID_LOG:
792 CString sCmd = L"/command:log";
793 sCmd += L" /path:\"" + g_Git.m_CurrentDir + L"\" ";
794 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
795 sCmd += L" /endrev:" + r1->m_CommitHash.ToString();
796 CAppUtils::RunTortoiseGitProc(sCmd);
798 break;
799 case ID_CREATE_PATCH:
801 int select=this->GetSelectedCount();
802 CString sCmd = L"/command:formatpatch";
803 sCmd += L" /path:\"" + g_Git.m_CurrentDir + L"\" ";
805 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
806 GitRev* r2 = nullptr;
807 if(select == 1)
809 sCmd += L" /startrev:" + r1->m_CommitHash.ToString();
811 else
813 r2 = m_arShownList.SafeGetAt(LastSelect);
814 if( this->m_IsOldFirst )
816 sCmd += L" /startrev:" + r1->m_CommitHash.ToString() + L"~1";
817 sCmd += L" /endrev:" + r2->m_CommitHash.ToString();
820 else
822 sCmd += L" /startrev:" + r2->m_CommitHash.ToString() + L"~1";
823 sCmd += L" /endrev:" + r1->m_CommitHash.ToString();
828 CAppUtils::RunTortoiseGitProc(sCmd);
830 break;
831 case ID_BISECTSTART:
833 GitRev* first = m_arShownList.SafeGetAt(FirstSelect);
834 GitRev* last = m_arShownList.SafeGetAt(LastSelect);
835 ASSERT(first && last);
837 CString firstBad = first->m_CommitHash.ToString();
838 if (!m_HashMap[first->m_CommitHash].empty())
839 firstBad = m_HashMap[first->m_CommitHash].at(0);
840 CString lastGood = last->m_CommitHash.ToString();
841 if (!m_HashMap[last->m_CommitHash].empty())
842 lastGood = m_HashMap[last->m_CommitHash].at(0);
844 if (CAppUtils::BisectStart(GetParentHWND(), lastGood, firstBad))
845 Refresh();
847 break;
848 case ID_BISECTGOOD:
850 GitRev* first = m_arShownList.SafeGetAt(FirstSelect);
851 if (CAppUtils::BisectOperation(GetParentHWND(), L"good", !first->m_CommitHash.IsEmpty() ? first->m_CommitHash.ToString() : L""))
852 Refresh();
854 break;
855 case ID_BISECTBAD:
857 GitRev* first = m_arShownList.SafeGetAt(FirstSelect);
858 if (CAppUtils::BisectOperation(GetParentHWND(), L"bad", !first->m_CommitHash.IsEmpty() ? first->m_CommitHash.ToString() : L""))
859 Refresh();
861 break;
862 case ID_BISECTSKIP:
864 CString refs;
865 POSITION pos2 = GetFirstSelectedItemPosition();
866 while (pos2)
868 int indexNext2 = GetNextSelectedItem(pos2);
869 auto rev = m_arShownList.SafeGetAt(indexNext2);
870 if (!rev->m_CommitHash.IsEmpty())
871 refs.AppendFormat(L" %s", (LPCTSTR)rev->m_CommitHash.ToString());
873 if (CAppUtils::BisectOperation(GetParentHWND(), L"skip", refs))
874 Refresh();
876 break;
877 case ID_BISECTRESET:
879 if (CAppUtils::BisectOperation(GetParentHWND(), L"reset"))
880 Refresh();
882 break;
883 case ID_REPOBROWSE:
885 CString sCmd;
886 sCmd.Format(L"/command:repobrowser /path:\"%s\" /rev:%s", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)pSelLogEntry->m_CommitHash.ToString());
887 CAppUtils::RunTortoiseGitProc(sCmd);
889 break;
890 case ID_PUSH:
892 CString guessAssociatedBranch = pSelLogEntry->m_CommitHash.ToString();
893 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd) : nullptr;
894 if (branch && !CStringUtils::StartsWith(*branch, L"refs/remotes/"))
895 guessAssociatedBranch = *branch;
896 else if (!GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], L"refs/heads/", guessAssociatedBranch))
897 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], L"refs/tags/", guessAssociatedBranch);
899 guessAssociatedBranch.Replace(L"^{}", L"");
901 if (CAppUtils::Push(GetParentHWND(), guessAssociatedBranch))
902 Refresh();
904 break;
905 case ID_PULL:
907 if (CAppUtils::Pull(GetParentHWND()))
908 Refresh();
910 break;
911 case ID_FETCH:
913 if (CAppUtils::Fetch(GetParentHWND()))
914 Refresh();
916 break;
917 case ID_SVNDCOMMIT:
919 if (CAppUtils::SVNDCommit(GetParentHWND()))
920 Refresh();
922 break;
923 case ID_CLEANUP:
925 CString sCmd;
926 sCmd.Format(L"/command:cleanup /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
927 CAppUtils::RunTortoiseGitProc(sCmd);
929 break;
930 case ID_SUBMODULE_UPDATE:
932 CString sCmd;
933 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
934 CAppUtils::RunTortoiseGitProc(sCmd);
936 break;
937 case ID_SHOWBRANCHES:
939 CCommitIsOnRefsDlg* dlg = new CCommitIsOnRefsDlg(this);
940 dlg->m_Rev = (LPCTSTR)pSelLogEntry->m_CommitHash.ToString();
941 dlg->Create(this);
942 // pointer won't leak as it is destroyed within PostNcDestroy()
944 break;
945 case ID_DELETE:
947 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd) : nullptr;
948 if (!branch)
950 CMessageBox::Show(GetParentHWND(), IDS_ERROR_NOREF, IDS_APPNAME, MB_OK | MB_ICONERROR);
951 return;
953 CString shortname;
954 if (branch == (CString*)MAKEINTRESOURCE(IDS_ALL))
956 CString currentBranch = L"refs/heads/" + m_CurrentBranch;
957 bool nothingDeleted = true;
958 for (const auto& ref : m_HashMap[pSelLogEntry->m_CommitHash])
960 if (ref == currentBranch)
961 continue;
962 if (!CAppUtils::DeleteRef(this, ref))
963 break;
964 nothingDeleted = false;
966 if (nothingDeleted)
967 return;
969 else if (!CAppUtils::DeleteRef(this, *branch))
970 return;
971 this->ReloadHashMap();
972 if (m_pFindDialog)
973 m_pFindDialog->RefreshList();
974 CRect rect;
975 this->GetItemRect(FirstSelect,&rect,LVIR_BOUNDS);
976 this->InvalidateRect(rect);
978 break;
980 case ID_FINDENTRY:
982 m_nSearchIndex = GetSelectionMark();
983 if (m_nSearchIndex < 0)
984 m_nSearchIndex = 0;
985 if (m_pFindDialog)
986 break;
987 else
989 m_pFindDialog = new CFindDlg();
990 m_pFindDialog->Create(this);
993 break;
994 case ID_MERGEREV:
996 CString str = pSelLogEntry->m_CommitHash.ToString();
997 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd & 0xFFFF) : nullptr;
998 if (branch)
999 str = *branch;
1000 else if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
1001 str = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
1002 // we need an URL to complete this command, so error out if we can't get an URL
1003 if (CAppUtils::Merge(GetParentHWND(), &str))
1005 this->Refresh();
1008 break;
1009 case ID_REVERTREV:
1011 int parent = 0;
1012 if (GetSelectedCount() == 1)
1014 parent = cmd >> 16;
1015 if ((size_t)parent > pSelLogEntry->m_ParentHash.size())
1017 CString str;
1018 str.Format(IDS_PROC_NOPARENT, parent);
1019 MessageBox(str, L"TortoiseGit", MB_OK | MB_ICONERROR);
1020 return;
1024 if (!this->RevertSelectedCommits(parent))
1026 if (CMessageBox::Show(m_hWnd, IDS_REVREVERTED, IDS_APPNAME, 1, IDI_QUESTION, IDS_OKBUTTON, IDS_COMMITBUTTON) == 2)
1028 CTGitPathList pathlist;
1029 pathlist.AddPath(this->m_Path);
1030 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(L"Software\\TortoiseGit\\SelectFilesForCommit", TRUE));
1031 CString str;
1032 CAppUtils::Commit(GetParentHWND(), CString(), false, str, pathlist, bSelectFilesForCommit);
1034 this->Refresh();
1037 break;
1038 case ID_EDITNOTE:
1040 CAppUtils::EditNote(GetParentHWND(), pSelLogEntry, &m_ProjectProperties);
1041 this->SetItemState(FirstSelect, 0, LVIS_SELECTED);
1042 this->SetItemState(FirstSelect, LVIS_SELECTED, LVIS_SELECTED);
1044 break;
1045 default:
1046 //CMessageBox::Show(nullptr, L"Have not implemented", L"TortoiseGit", MB_OK);
1047 break;
1048 #if 0
1050 case ID_BLAMECOMPARE:
1052 //user clicked on the menu item "compare with working copy"
1053 //now first get the revision which is selected
1054 if (PromptShown())
1056 GitDiff diff(this, this->m_hWnd, true);
1057 diff.SetHEADPeg(m_LogRevision);
1058 diff.ShowCompare(m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), false, true);
1060 else
1061 CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), m_LogRevision, false, false, true);
1063 break;
1064 case ID_BLAMEWITHPREVIOUS:
1066 //user clicked on the menu item "Compare and Blame with previous revision"
1067 if (PromptShown())
1069 GitDiff diff(this, this->m_hWnd, true);
1070 diff.SetHEADPeg(m_LogRevision);
1071 diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), false, true);
1073 else
1074 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
1076 break;
1078 case ID_OPENWITH:
1079 bOpenWith = true;
1080 case ID_OPEN:
1082 CProgressDlg progDlg;
1083 progDlg.SetTitle(IDS_APPNAME);
1084 progDlg.SetAnimation(IDR_DOWNLOAD);
1085 CString sInfoLine;
1086 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, m_path.GetWinPath(), (LPCTSTR)revSelected.ToString());
1087 progDlg.SetLine(1, sInfoLine, true);
1088 SetAndClearProgressInfo(&progDlg);
1089 progDlg.ShowModeless(m_hWnd);
1090 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, m_path, revSelected);
1091 bool bSuccess = true;
1092 if (!Cat(m_path, GitRev(GitRev::REV_HEAD), revSelected, tempfile))
1094 bSuccess = false;
1095 // try again, but with the selected revision as the peg revision
1096 if (!Cat(m_path, revSelected, revSelected, tempfile))
1098 progDlg.Stop();
1099 SetAndClearProgressInfo(nullptr);
1100 CMessageBox::Show(GetSafeHwnd(), GetLastErrorMessage(), L"TortoiseGit", MB_ICONERROR);
1101 EnableOKButton();
1102 break;
1104 bSuccess = true;
1106 if (bSuccess)
1108 progDlg.Stop();
1109 SetAndClearProgressInfo(nullptr);
1110 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1111 if (!bOpenWith)
1112 CAppUtils::ShellOpen(tempfile.GetWinPath(), GetSafeHwnd());
1113 else
1114 CAppUtils::ShowOpenWithDialog(tempfile.GetWinPathString(), GetSafeHwnd());
1117 break;
1118 case ID_BLAME:
1120 CBlameDlg dlg;
1121 dlg.EndRev = revSelected;
1122 if (dlg.DoModal() == IDOK)
1124 CBlame blame;
1125 CString tempfile;
1126 CString logfile;
1127 tempfile = blame.BlameToTempFile(m_path, dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, L"", dlg.m_bIncludeMerge, TRUE, TRUE);
1128 if (!tempfile.IsEmpty())
1130 if (dlg.m_bTextView)
1132 //open the default text editor for the result file
1133 CAppUtils::StartTextViewer(tempfile);
1135 else
1137 CString sParams = L"/path:\"" + m_path.GetGitPathString() + L"\" ";
1138 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(m_path.GetFileOrDirectoryName()),sParams))
1140 break;
1144 else
1146 CMessageBox::Show(GetSafeHwnd(), blame.GetLastErrorMessage(), L"TortoiseGit", MB_ICONERROR);
1150 break;
1151 case ID_EXPORT:
1153 CString sCmd;
1154 sCmd.Format(L"%s /command:export /path:\"%s\" /revision:%ld",
1155 (LPCTSTR)(CPathUtils::GetAppDirectory() + L"TortoiseGitProc.exe"),
1156 (LPCTSTR)pathURL, (LONG)revSelected);
1157 CAppUtils::LaunchApplication(sCmd, nullptr, false);
1159 break;
1160 case ID_VIEWREV:
1162 CString url = m_ProjectProperties.sWebViewerRev;
1163 url = GetAbsoluteUrlFromRelativeUrl(url);
1164 url.Replace(L"%REVISION%", revSelected.ToString());
1165 if (!url.IsEmpty())
1166 ShellExecute(GetSafeHwnd(), L"open", url, nullptr, nullptr, SW_SHOWDEFAULT);
1168 break;
1169 case ID_VIEWPATHREV:
1171 CString relurl = pathURL;
1172 CString sRoot = GetRepositoryRoot(CTGitPath(relurl));
1173 relurl = relurl.Mid(sRoot.GetLength());
1174 CString url = m_ProjectProperties.sWebViewerPathRev;
1175 url = GetAbsoluteUrlFromRelativeUrl(url);
1176 url.Replace(L"%REVISION%", revSelected.ToString());
1177 url.Replace(L"%PATH%", relurl);
1178 if (!url.IsEmpty())
1179 ShellExecute(GetSafeHwnd(), L"open", url, nullptr, nullptr, SW_SHOWDEFAULT);
1181 break;
1182 #endif
1184 } // switch (cmd)
1186 theApp.DoWaitCursor(-1);
1189 void CGitLogList::SetSelectedRebaseAction(int action)
1191 POSITION pos = GetFirstSelectedItemPosition();
1192 if (!pos) return;
1193 int index;
1194 while(pos)
1196 index = GetNextSelectedItem(pos);
1197 if (m_arShownList.SafeGetAt(index)->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT | LOGACTIONS_REBASE_DONE) || (index == GetItemCount() - 1 && action == LOGACTIONS_REBASE_SQUASH))
1198 continue;
1199 if (!m_bIsCherryPick && m_arShownList.SafeGetAt(index)->ParentsCount() > 1 && action == LOGACTIONS_REBASE_SQUASH)
1200 continue;
1201 m_arShownList.SafeGetAt(index)->GetRebaseAction() = action;
1202 CRect rect;
1203 this->GetItemRect(index,&rect,LVIR_BOUNDS);
1204 this->InvalidateRect(rect);
1207 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage);
1210 void CGitLogList::SetUnselectedRebaseAction(int action)
1212 POSITION pos = GetFirstSelectedItemPosition();
1213 int index = pos ? GetNextSelectedItem(pos) : -1;
1214 for (int i = 0; i < GetItemCount(); i++)
1216 if (i == index)
1218 index = pos ? GetNextSelectedItem(pos) : -1;
1219 continue;
1222 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))
1223 continue;
1224 m_arShownList.SafeGetAt(i)->GetRebaseAction() = action;
1225 CRect rect;
1226 this->GetItemRect(i, &rect, LVIR_BOUNDS);
1227 this->InvalidateRect(rect);
1230 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage);
1233 void CGitLogList::ShiftSelectedRebaseAction()
1235 POSITION pos = GetFirstSelectedItemPosition();
1236 int index;
1237 while(pos)
1239 index = GetNextSelectedItem(pos);
1240 int* action = &(m_arShownList.SafeGetAt(index))->GetRebaseAction();
1241 switch (*action)
1243 case LOGACTIONS_REBASE_PICK:
1244 *action = LOGACTIONS_REBASE_SKIP;
1245 break;
1246 case LOGACTIONS_REBASE_SKIP:
1247 *action= LOGACTIONS_REBASE_EDIT;
1248 break;
1249 case LOGACTIONS_REBASE_EDIT:
1250 *action = LOGACTIONS_REBASE_SQUASH;
1251 if (index == GetItemCount() - 1 && (m_bIsCherryPick || m_arShownList.SafeGetAt(index)->m_ParentHash.size() == 1))
1252 *action = LOGACTIONS_REBASE_PICK;
1253 break;
1254 case LOGACTIONS_REBASE_SQUASH:
1255 *action= LOGACTIONS_REBASE_PICK;
1256 break;
1258 CRect rect;
1259 this->GetItemRect(index, &rect, LVIR_BOUNDS);
1260 this->InvalidateRect(rect);