Fixed issue #3116: Revision graph: add ability to delete branches
[TortoiseGit.git] / src / TortoiseProc / GitLogListAction.cpp
blob13bf31b422ba010284aca4e83aa1204cf237f8d8
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2017 - 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(GetSafeOwner()->GetSafeHwnd(), str, L"TortoiseGit", MB_OK | MB_ICONERROR);
99 else if (CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), 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 CTGitPathList selectedlist;
165 pathlist.AddPath(this->m_Path);
166 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(L"Software\\TortoiseGit\\SelectFilesForCommit", TRUE));
167 CString str;
168 CAppUtils::Commit(CString(),false,str,
169 pathlist,selectedlist,bSelectFilesForCommit);
170 //this->Refresh();
171 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH,0);
173 break;
174 case ID_MERGE_ABORT:
176 if (CAppUtils::MergeAbort())
177 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH, 0);
179 break;
180 case ID_GNUDIFF1: // compare with WC, unified
182 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
183 bool bMerge = false, bCombine = false;
184 CString hash2;
185 if(!r1->m_CommitHash.IsEmpty())
187 CString merge;
188 cmd >>= 16;
189 if( (cmd&0xFFFF) == 0xFFFF)
190 bMerge = true;
191 else if((cmd&0xFFFF) == 0xFFFE)
192 bCombine = true;
193 else if ((cmd & 0xFFFF) == 0xFFFD)
195 CString tempfile = GetTempFile();
196 CString gitcmd = L"git.exe diff-tree --cc " + r1->m_CommitHash.ToString();
197 CString lastErr;
198 if (g_Git.RunLogFile(gitcmd, tempfile, &lastErr))
200 MessageBox(lastErr, L"TortoiseGit", MB_ICONERROR);
201 break;
206 CStdioFile file(tempfile, CFile::typeText | CFile::modeRead | CFile::shareDenyWrite);
207 CString strLine;
208 bool isHash = file.ReadString(strLine) && r1->m_CommitHash.ToString() == strLine;
209 bool more = isHash && file.ReadString(strLine) && !strLine.IsEmpty();
210 if (!more)
212 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_NOCHANGEAFTERMERGE, IDS_APPNAME, MB_OK);
213 break;
216 catch (CFileException* e)
218 e->Delete();
221 CAppUtils::StartUnifiedDiffViewer(tempfile, r1->m_CommitHash.ToString());
222 break;
224 else
226 if (cmd == 0)
227 cmd = 1;
228 if ((size_t)cmd > r1->m_ParentHash.size())
230 CString str;
231 str.Format(IDS_PROC_NOPARENT, cmd);
232 MessageBox(str, L"TortoiseGit", MB_OK | MB_ICONERROR);
233 return;
235 else
237 if(cmd>0)
238 hash2 = r1->m_ParentHash[cmd-1].ToString();
241 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), hash2, CTGitPath(), r1->m_CommitHash.ToString(), bShiftPressed, false, false, bMerge, bCombine);
243 else
244 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), L"HEAD", CTGitPath(), GitRev::GetWorkingCopy(), bShiftPressed, false, false, bMerge, bCombine);
246 break;
248 case ID_GNUDIFF2: // compare two revisions, unified
250 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
251 GitRev* r2 = m_arShownList.SafeGetAt(LastSelect);
252 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), r2->m_CommitHash.ToString(), CTGitPath(), r1->m_CommitHash.ToString(), bShiftPressed);
254 break;
256 case ID_COMPARETWO: // compare two revisions
258 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
259 GitRev* r2 = m_arShownList.SafeGetAt(LastSelect);
260 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
261 CGitDiff::DiffCommit(m_Path, r1, r2, bShiftPressed);
262 else
264 CString path1 = m_Path.GetGitPathString();
265 // start with 1 (0 = working copy changes)
266 for (int i = m_bShowWC ? 1 : 0; i < FirstSelect; ++i)
268 GitRevLoglist* first = m_arShownList.SafeGetAt(i);
269 CTGitPathList list = first->GetFiles(nullptr);
270 const CTGitPath* file = list.LookForGitPath(path1);
271 if (file && !file->GetGitOldPathString().IsEmpty())
272 path1 = file->GetGitOldPathString();
274 CString path2 = path1;
275 for (int i = FirstSelect; i < LastSelect; ++i)
277 GitRevLoglist* first = m_arShownList.SafeGetAt(i);
278 CTGitPathList list = first->GetFiles(nullptr);
279 const CTGitPath* file = list.LookForGitPath(path2);
280 if (file && !file->GetGitOldPathString().IsEmpty())
281 path2 = file->GetGitOldPathString();
283 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), r1, r2, bShiftPressed);
287 break;
289 case ID_COMPARE: // compare revision with WC
291 GitRevLoglist* r1 = &m_wcRev;
292 GitRevLoglist* r2 = pSelLogEntry;
294 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
295 CGitDiff::DiffCommit(m_Path, r1, r2, bShiftPressed);
296 else
298 CString path1 = m_Path.GetGitPathString();
299 // start with 1 (0 = working copy changes)
300 for (int i = m_bShowWC ? 1 : 0; i < FirstSelect; ++i)
302 GitRevLoglist* first = m_arShownList.SafeGetAt(i);
303 CTGitPathList list = first->GetFiles(nullptr);
304 const CTGitPath* file = list.LookForGitPath(path1);
305 if (file && !file->GetGitOldPathString().IsEmpty())
306 path1 = file->GetGitOldPathString();
308 CGitDiff::DiffCommit(m_Path, CTGitPath(path1), r1, r2, bShiftPressed);
311 //user clicked on the menu item "compare with working copy"
312 //if (PromptShown())
314 // GitDiff diff(this, m_hWnd, true);
315 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
316 // diff.SetHEADPeg(m_LogRevision);
317 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
319 //else
320 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
322 break;
324 case ID_COMPAREWITHPREVIOUS:
326 if (pSelLogEntry->m_ParentHash.empty())
328 if (pSelLogEntry->GetParentFromHash(pSelLogEntry->m_CommitHash))
329 MessageBox(pSelLogEntry->GetLastErr(), L"TortoiseGit", MB_ICONERROR);
332 if (!pSelLogEntry->m_ParentHash.empty())
333 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
335 cmd>>=16;
336 cmd&=0xFFFF;
338 if(cmd == 0)
339 cmd=1;
341 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
342 CGitDiff::DiffCommit(m_Path, pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString(), bShiftPressed);
343 else
345 CString path1 = m_Path.GetGitPathString();
346 // start with 1 (0 = working copy changes)
347 for (int i = m_bShowWC ? 1 : 0; i < indexNext; ++i)
349 GitRevLoglist* first = m_arShownList.SafeGetAt(i);
350 CTGitPathList list = first->GetFiles(nullptr);
351 const CTGitPath* file = list.LookForGitPath(path1);
352 if (file && !file->GetGitOldPathString().IsEmpty())
353 path1 = file->GetGitOldPathString();
355 CString path2 = path1;
356 GitRevLoglist* first = m_arShownList.SafeGetAt(indexNext);
357 CTGitPathList list = first->GetFiles(nullptr);
358 const CTGitPath* file = list.LookForGitPath(path2);
359 if (file && !file->GetGitOldPathString().IsEmpty())
360 path2 = file->GetGitOldPathString();
362 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString(), bShiftPressed);
365 else
367 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_PROC_NOPREVIOUSVERSION, IDS_APPNAME, MB_OK);
369 //if (PromptShown())
371 // GitDiff diff(this, m_hWnd, true);
372 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
373 // diff.SetHEADPeg(m_LogRevision);
374 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
376 //else
377 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
379 break;
380 case ID_COMPARETWOCOMMITCHANGES:
382 auto pFirstEntry = m_arShownList.SafeGetAt(FirstSelect);
383 auto pLastEntry = m_arShownList.SafeGetAt(LastSelect);
384 CString patch1 = GetTempFile();
385 CString patch2 = GetTempFile();
386 CString err;
387 if (g_Git.RunLogFile(L"git.exe format-patch --stdout " + pFirstEntry->m_CommitHash.ToString() + L"~1.." + pFirstEntry->m_CommitHash.ToString() + L"", patch1, &err))
389 MessageBox(L"Could not generate patch for commit " + pFirstEntry->m_CommitHash.ToString() + L".\n" + err, L"TortoiseGit", MB_ICONERROR);
390 break;
392 if (g_Git.RunLogFile(L"git.exe format-patch --stdout " + pLastEntry->m_CommitHash.ToString() + L"~1.." + pLastEntry->m_CommitHash.ToString() + L"", patch2, &err))
394 MessageBox(L"Could not generate patch for commit " + pLastEntry->m_CommitHash.ToString() + L".\n" + err, L"TortoiseGit", MB_ICONERROR);
395 break;
397 CAppUtils::DiffFlags flags;
398 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);
400 break;
401 case ID_LOG_VIEWRANGE:
402 case ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE:
404 GitRev* pLastEntry = m_arShownList.SafeGetAt(LastSelect);
406 CString sep = L"..";
407 if ((cmd & 0xFFFF) == ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE)
408 sep = L"...";
410 CString cmdline;
411 cmdline.Format(L"/command:log /path:\"%s\" /range:\"%s%s%s\"",
412 (LPCTSTR)g_Git.CombinePath(m_Path), (LPCTSTR)pLastEntry->m_CommitHash.ToString(), (LPCTSTR)sep, (LPCTSTR)pSelLogEntry->m_CommitHash.ToString());
413 CAppUtils::RunTortoiseGitProc(cmdline);
415 break;
416 case ID_COPYCLIPBOARDFULL:
417 case ID_COPYCLIPBOARDFULLNOPATHS:
418 case ID_COPYCLIPBOARDHASH:
419 case ID_COPYCLIPBOARDAUTHORSFULL:
420 case ID_COPYCLIPBOARDAUTHORSNAME:
421 case ID_COPYCLIPBOARDAUTHORSEMAIL:
422 case ID_COPYCLIPBOARDSUBJECTS:
423 case ID_COPYCLIPBOARDMESSAGES:
425 CopySelectionToClipBoard(cmd & 0xFFFF);
427 break;
428 case ID_COPYCLIPBOARDBRANCHTAG:
430 if (!popmenu)
431 break;
432 auto selectedBranch = reinterpret_cast<const CString*>(reinterpret_cast<CIconMenu*>(popmenu)->GetMenuItemData(cmd));
433 CString sClipboard;
434 if (selectedBranch)
436 if (CStringUtils::StartsWith(*selectedBranch, L"refs/tags/"))
437 sClipboard = selectedBranch->Mid((int)wcslen(L"refs/tags/")).TrimRight(L"^{}");
438 else
439 sClipboard = CGit::StripRefName(*selectedBranch);
441 else
443 for (const auto& ref : m_HashMap[pSelLogEntry->m_CommitHash])
445 sClipboard += ref;
446 sClipboard += L"\r\n";
449 CStringUtils::WriteAsciiStringToClipboard(sClipboard, GetSafeHwnd());
451 break;
452 case ID_EXPORT:
454 CString str=pSelLogEntry->m_CommitHash.ToString();
455 // try to get the tag
456 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], L"refs/tags/", str);
457 CAppUtils::Export(&str, &m_Path);
459 break;
460 case ID_CREATE_BRANCH:
461 case ID_CREATE_TAG:
463 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd & 0xFFFF) : nullptr;
464 CString str = pSelLogEntry->m_CommitHash.ToString();
465 if (branch)
466 str = *branch;
467 else // try to guess remote branch in order to enable tracking
468 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], L"refs/remotes/", str);
470 CAppUtils::CreateBranchTag((cmd&0xFFFF) == ID_CREATE_TAG, &str);
471 ReloadHashMap();
472 if (m_pFindDialog)
473 m_pFindDialog->RefreshList();
474 Invalidate();
475 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
477 break;
478 case ID_SWITCHTOREV:
480 CString str = pSelLogEntry->m_CommitHash.ToString();
481 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd & 0xFFFF) : nullptr;
482 if (branch)
483 str = *branch;
484 else // try to guess remote branch in order to recommend good branch name and tracking
485 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], L"refs/remotes/", str);
487 CAppUtils::Switch(str);
489 ReloadHashMap();
490 Invalidate();
491 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
492 break;
493 case ID_SWITCHBRANCH:
494 if(popmenu)
496 const CString* branch = (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
497 if(branch)
499 CString name = *branch;
500 CGit::GetShortName(*branch, name, L"refs/heads/");
501 CAppUtils::PerformSwitch(name);
503 ReloadHashMap();
504 Invalidate();
505 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
507 break;
508 case ID_RESET:
510 CString str = pSelLogEntry->m_CommitHash.ToString();
511 if (CAppUtils::GitReset(&str))
513 ResetWcRev(true);
514 ReloadHashMap();
515 Invalidate();
518 break;
519 case ID_REBASE_PICK:
520 SetSelectedRebaseAction(LOGACTIONS_REBASE_PICK);
521 break;
522 case ID_REBASE_EDIT:
523 SetSelectedRebaseAction(LOGACTIONS_REBASE_EDIT);
524 break;
525 case ID_REBASE_SQUASH:
526 SetSelectedRebaseAction(LOGACTIONS_REBASE_SQUASH);
527 break;
528 case ID_REBASE_SKIP:
529 SetSelectedRebaseAction(LOGACTIONS_REBASE_SKIP);
530 break;
531 case ID_COMBINE_COMMIT:
533 CString head;
534 CGitHash headhash;
535 CGitHash hashFirst,hashLast;
537 int headindex=GetHeadIndex();
538 if(headindex>=0) //incase show all branch, head is not the first commits.
540 head.Format(L"HEAD~%d", FirstSelect - headindex);
541 if (g_Git.GetHash(hashFirst, head))
543 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of first selected revision."), L"TortoiseGit", MB_ICONERROR);
544 break;
547 head.Format(L"HEAD~%d", LastSelect - headindex);
548 if (g_Git.GetHash(hashLast, head))
550 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of last selected revision."), L"TortoiseGit", MB_ICONERROR);
551 break;
555 GitRev* pFirstEntry = m_arShownList.SafeGetAt(FirstSelect);
556 GitRev* pLastEntry = m_arShownList.SafeGetAt(LastSelect);
557 if(pFirstEntry->m_CommitHash != hashFirst || pLastEntry->m_CommitHash != hashLast)
559 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_PROC_CANNOTCOMBINE, IDS_APPNAME, MB_OK | MB_ICONEXCLAMATION);
560 break;
563 GitRev lastRevision;
564 if (lastRevision.GetParentFromHash(hashLast))
566 MessageBox(lastRevision.GetLastErr(), L"TortoiseGit", MB_ICONERROR);
567 break;
570 if (g_Git.GetHash(headhash, L"HEAD"))
572 MessageBox(g_Git.GetGitLastErr(L"Could not get HEAD hash."), L"TortoiseGit", MB_ICONERROR);
573 break;
576 if(!g_Git.CheckCleanWorkTree())
578 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK | MB_ICONEXCLAMATION);
579 break;
581 CString sCmd, out;
583 //Use throw to abort this process (reset back to original HEAD)
586 sCmd.Format(L"git.exe reset --hard %s --", (LPCTSTR)pFirstEntry->m_CommitHash.ToString());
587 if(g_Git.Run(sCmd, &out, CP_UTF8))
589 MessageBox(out, L"TortoiseGit", MB_OK | MB_ICONERROR);
590 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1)) + L"\r\n\r\n" + out));
592 sCmd.Format(L"git.exe reset --soft %s --", (LPCTSTR)hashLast.ToString());
593 if(g_Git.Run(sCmd, &out, CP_UTF8))
595 MessageBox(out, L"TortoiseGit", MB_OK | MB_ICONERROR);
596 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2)) + L"\r\n\r\n"+out));
599 CCommitDlg dlg;
600 for (int i = FirstSelect; i <= LastSelect; ++i)
602 GitRev* pRev = m_arShownList.SafeGetAt(i);
603 dlg.m_sLogMessage += pRev->GetSubject() + L'\n' + pRev->GetBody();
604 dlg.m_sLogMessage += L'\n';
606 dlg.m_bWholeProject=true;
607 dlg.m_bSelectFilesForCommit = true;
608 dlg.m_bForceCommitAmend=true;
609 int squashDate = (int)CRegDWORD(L"Software\\TortoiseGit\\SquashDate", 0);
610 if (squashDate == 1)
611 dlg.SetTime(m_arShownList.SafeGetAt(FirstSelect)->GetAuthorDate());
612 else if (squashDate == 2)
613 dlg.SetTime(CTime::GetCurrentTime());
614 else
615 dlg.SetTime(m_arShownList.SafeGetAt(LastSelect)->GetAuthorDate());
616 CTGitPathList gpl;
617 gpl.AddPath(CTGitPath(g_Git.m_CurrentDir));
618 dlg.m_pathList = gpl;
619 if (lastRevision.ParentsCount() != 1)
621 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);
622 dlg.m_bAmendDiffToLastCommit = TRUE;
624 else
625 dlg.m_bAmendDiffToLastCommit = FALSE;
626 dlg.m_bNoPostActions=true;
627 dlg.m_AmendStr=dlg.m_sLogMessage;
629 if (dlg.DoModal() == IDOK)
631 if(pFirstEntry->m_CommitHash!=headhash)
633 if(CherryPickFrom(pFirstEntry->m_CommitHash.ToString(),headhash))
635 CString msg;
636 msg.Format(L"Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n");
637 throw std::exception(CUnicodeUtils::GetUTF8(msg));
641 else
642 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_USERCANCELLED))));
644 catch(std::exception& e)
646 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), CUnicodeUtils::GetUnicode(CStringA(e.what())), L"TortoiseGit", MB_OK | MB_ICONERROR);
647 sCmd.Format(L"git.exe reset --hard %s --", (LPCTSTR)headhash.ToString());
648 out.Empty();
649 if(g_Git.Run(sCmd, &out, CP_UTF8))
650 MessageBox(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD)) + L"\r\n\r\n" + out, L"TortoiseGit", MB_OK | MB_ICONERROR);
652 Refresh();
654 break;
656 case ID_CHERRY_PICK:
658 if (m_bThreadRunning)
660 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_PROC_LOG_ONLYONCE, IDS_APPNAME, MB_ICONEXCLAMATION);
661 break;
663 CRebaseDlg dlg;
664 dlg.m_IsCherryPick = TRUE;
665 dlg.m_Upstream = this->m_CurrentBranch;
666 POSITION pos2 = GetFirstSelectedItemPosition();
667 while(pos2)
669 int indexNext2 = GetNextSelectedItem(pos2);
670 dlg.m_CommitList.m_logEntries.push_back(m_arShownList.SafeGetAt(indexNext2)->m_CommitHash);
671 dlg.m_CommitList.m_LogCache.m_HashMap[m_arShownList.SafeGetAt(indexNext2)->m_CommitHash] = *m_arShownList.SafeGetAt(indexNext2);
672 dlg.m_CommitList.m_logEntries.GetGitRevAt(dlg.m_CommitList.m_logEntries.size() - 1).GetRebaseAction() |= LOGACTIONS_REBASE_PICK;
675 if(dlg.DoModal() == IDOK)
677 Refresh();
680 break;
681 case ID_REBASE_TO_VERSION:
683 if (m_bThreadRunning)
685 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_PROC_LOG_ONLYONCE, IDS_APPNAME, MB_ICONEXCLAMATION);
686 break;
688 CRebaseDlg dlg;
689 auto refList = m_HashMap[pSelLogEntry->m_CommitHash];
690 dlg.m_Upstream = refList.empty() ? pSelLogEntry->m_CommitHash.ToString() : refList.front();
691 for (const auto& ref : refList)
692 if (CGit::GetShortName(ref, dlg.m_Upstream, L"refs/heads/"))
693 break;
695 if(dlg.DoModal() == IDOK)
697 Refresh();
701 break;
703 case ID_STASH_SAVE:
704 if (CAppUtils::StashSave())
705 Refresh();
706 break;
708 case ID_STASH_POP:
709 if (CAppUtils::StashPop())
710 Refresh();
711 break;
713 case ID_STASH_LIST:
714 CAppUtils::RunTortoiseGitProc(L"/command:reflog /ref:refs/stash");
715 break;
717 case ID_REFLOG_STASH_APPLY:
718 CAppUtils::StashApply(pSelLogEntry->m_Ref);
719 break;
721 case ID_REFLOG_DEL:
723 CString str;
724 if (GetSelectedCount() > 1)
725 str.Format(IDS_PROC_DELETENREFS, GetSelectedCount());
726 else
727 str.Format(IDS_PROC_DELETEREF, (LPCTSTR)pSelLogEntry->m_Ref);
729 if (CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), str, L"TortoiseGit", 1, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
730 return;
732 std::vector<CString> refsToDelete;
733 POSITION pos2 = GetFirstSelectedItemPosition();
734 while (pos2)
736 CString ref = m_arShownList.SafeGetAt(GetNextSelectedItem(pos2))->m_Ref;
737 if (CStringUtils::StartsWith(ref, L"refs/"))
738 ref = ref.Mid(5);
739 int refpos = ref.ReverseFind(L'{');
740 if (refpos > 0 && ref.Mid(refpos - 1, 2) != L"@{")
741 ref = ref.Left(refpos) + L'@'+ ref.Mid(refpos);
742 refsToDelete.push_back(ref);
745 for (auto revIt = refsToDelete.crbegin(); revIt != refsToDelete.crend(); ++revIt)
747 CString ref = *revIt;
748 CString sCmd, out;
749 if (CStringUtils::StartsWith(ref, L"stash"))
750 sCmd.Format(L"git.exe stash drop %s", (LPCTSTR)ref);
751 else
752 sCmd.Format(L"git.exe reflog delete %s", (LPCTSTR)ref);
754 if (g_Git.Run(sCmd, &out, CP_UTF8))
755 MessageBox(out, L"TortoiseGit", MB_OK | MB_ICONERROR);
757 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
760 break;
761 case ID_LOG:
763 CString sCmd = L"/command:log";
764 sCmd += L" /path:\"" + g_Git.m_CurrentDir + L"\" ";
765 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
766 sCmd += L" /endrev:" + r1->m_CommitHash.ToString();
767 CAppUtils::RunTortoiseGitProc(sCmd);
769 break;
770 case ID_CREATE_PATCH:
772 int select=this->GetSelectedCount();
773 CString sCmd = L"/command:formatpatch";
774 sCmd += L" /path:\"" + g_Git.m_CurrentDir + L"\" ";
776 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
777 GitRev* r2 = nullptr;
778 if(select == 1)
780 sCmd += L" /startrev:" + r1->m_CommitHash.ToString();
782 else
784 r2 = m_arShownList.SafeGetAt(LastSelect);
785 if( this->m_IsOldFirst )
787 sCmd += L" /startrev:" + r1->m_CommitHash.ToString() + L"~1";
788 sCmd += L" /endrev:" + r2->m_CommitHash.ToString();
791 else
793 sCmd += L" /startrev:" + r2->m_CommitHash.ToString() + L"~1";
794 sCmd += L" /endrev:" + r1->m_CommitHash.ToString();
799 CAppUtils::RunTortoiseGitProc(sCmd);
801 break;
802 case ID_BISECTSTART:
804 GitRev* first = m_arShownList.SafeGetAt(FirstSelect);
805 GitRev* last = m_arShownList.SafeGetAt(LastSelect);
806 ASSERT(first && last);
808 CString firstBad = first->m_CommitHash.ToString();
809 if (!m_HashMap[first->m_CommitHash].empty())
810 firstBad = m_HashMap[first->m_CommitHash].at(0);
811 CString lastGood = last->m_CommitHash.ToString();
812 if (!m_HashMap[last->m_CommitHash].empty())
813 lastGood = m_HashMap[last->m_CommitHash].at(0);
815 if (CAppUtils::BisectStart(lastGood, firstBad))
816 Refresh();
818 break;
819 case ID_BISECTGOOD:
821 GitRev* first = m_arShownList.SafeGetAt(FirstSelect);
822 if (CAppUtils::BisectOperation(L"good", !first->m_CommitHash.IsEmpty() ? first->m_CommitHash.ToString() : L""))
823 Refresh();
825 break;
826 case ID_BISECTBAD:
828 GitRev* first = m_arShownList.SafeGetAt(FirstSelect);
829 if (CAppUtils::BisectOperation(L"bad", !first->m_CommitHash.IsEmpty() ? first->m_CommitHash.ToString() : L""))
830 Refresh();
832 break;
833 case ID_BISECTSKIP:
835 CString refs;
836 POSITION pos2 = GetFirstSelectedItemPosition();
837 while (pos2)
839 int indexNext2 = GetNextSelectedItem(pos2);
840 auto rev = m_arShownList.SafeGetAt(indexNext2);
841 if (!rev->m_CommitHash.IsEmpty())
842 refs.AppendFormat(L" %s", (LPCTSTR)rev->m_CommitHash.ToString());
844 if (CAppUtils::BisectOperation(L"skip", refs))
845 Refresh();
847 break;
848 case ID_BISECTRESET:
850 if (CAppUtils::BisectOperation(L"reset"))
851 Refresh();
853 break;
854 case ID_REPOBROWSE:
856 CString sCmd;
857 sCmd.Format(L"/command:repobrowser /path:\"%s\" /rev:%s", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)pSelLogEntry->m_CommitHash.ToString());
858 CAppUtils::RunTortoiseGitProc(sCmd);
860 break;
861 case ID_PUSH:
863 CString guessAssociatedBranch = pSelLogEntry->m_CommitHash;
864 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd) : nullptr;
865 if (branch && !CStringUtils::StartsWith(*branch, L"refs/remotes/"))
866 guessAssociatedBranch = *branch;
867 else if (!GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], L"refs/heads/", guessAssociatedBranch))
868 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], L"refs/tags/", guessAssociatedBranch);
870 guessAssociatedBranch.Replace(L"^{}", L"");
872 if (CAppUtils::Push(guessAssociatedBranch))
873 Refresh();
875 break;
876 case ID_PULL:
878 if (CAppUtils::Pull())
879 Refresh();
881 break;
882 case ID_FETCH:
884 if (CAppUtils::Fetch())
885 Refresh();
887 break;
888 case ID_SVNDCOMMIT:
890 if (CAppUtils::SVNDCommit())
891 Refresh();
893 break;
894 case ID_CLEANUP:
896 CString sCmd;
897 sCmd.Format(L"/command:cleanup /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
898 CAppUtils::RunTortoiseGitProc(sCmd);
900 break;
901 case ID_SUBMODULE_UPDATE:
903 CString sCmd;
904 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
905 CAppUtils::RunTortoiseGitProc(sCmd);
907 break;
908 case ID_SHOWBRANCHES:
910 CCommitIsOnRefsDlg* dlg = new CCommitIsOnRefsDlg(this);
911 dlg->m_Rev = (LPCTSTR)pSelLogEntry->m_CommitHash.ToString();
912 dlg->Create(this);
913 // pointer won't leak as it is destroyed within PostNcDestroy()
915 break;
916 case ID_DELETE:
918 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd) : nullptr;
919 if (!branch)
921 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_ERROR_NOREF, IDS_APPNAME, MB_OK | MB_ICONERROR);
922 return;
924 CString shortname;
925 if (branch == (CString*)MAKEINTRESOURCE(IDS_ALL))
927 CString currentBranch = L"refs/heads/" + m_CurrentBranch;
928 bool nothingDeleted = true;
929 for (const auto& ref : m_HashMap[pSelLogEntry->m_CommitHash])
931 if (ref == currentBranch)
932 continue;
933 if (!CAppUtils::DeleteRef(this, ref))
934 break;
935 nothingDeleted = false;
937 if (nothingDeleted)
938 return;
940 else if (!CAppUtils::DeleteRef(this, *branch))
941 return;
942 this->ReloadHashMap();
943 if (m_pFindDialog)
944 m_pFindDialog->RefreshList();
945 CRect rect;
946 this->GetItemRect(FirstSelect,&rect,LVIR_BOUNDS);
947 this->InvalidateRect(rect);
949 break;
951 case ID_FINDENTRY:
953 m_nSearchIndex = GetSelectionMark();
954 if (m_nSearchIndex < 0)
955 m_nSearchIndex = 0;
956 if (m_pFindDialog)
957 break;
958 else
960 m_pFindDialog = new CFindDlg();
961 m_pFindDialog->Create(this);
964 break;
965 case ID_MERGEREV:
967 CString str = pSelLogEntry->m_CommitHash.ToString();
968 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd & 0xFFFF) : nullptr;
969 if (branch)
970 str = *branch;
971 else if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
972 str = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
973 // we need an URL to complete this command, so error out if we can't get an URL
974 if(CAppUtils::Merge(&str))
976 this->Refresh();
979 break;
980 case ID_REVERTREV:
982 int parent = 0;
983 if (GetSelectedCount() == 1)
985 parent = cmd >> 16;
986 if ((size_t)parent > pSelLogEntry->m_ParentHash.size())
988 CString str;
989 str.Format(IDS_PROC_NOPARENT, parent);
990 MessageBox(str, L"TortoiseGit", MB_OK | MB_ICONERROR);
991 return;
995 if (!this->RevertSelectedCommits(parent))
997 if (CMessageBox::Show(m_hWnd, IDS_REVREVERTED, IDS_APPNAME, 1, IDI_QUESTION, IDS_OKBUTTON, IDS_COMMITBUTTON) == 2)
999 CTGitPathList pathlist;
1000 CTGitPathList selectedlist;
1001 pathlist.AddPath(this->m_Path);
1002 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(L"Software\\TortoiseGit\\SelectFilesForCommit", TRUE));
1003 CString str;
1004 CAppUtils::Commit(CString(), false, str, pathlist, selectedlist, bSelectFilesForCommit);
1006 this->Refresh();
1009 break;
1010 case ID_EDITNOTE:
1012 CAppUtils::EditNote(pSelLogEntry, &m_ProjectProperties);
1013 this->SetItemState(FirstSelect, 0, LVIS_SELECTED);
1014 this->SetItemState(FirstSelect, LVIS_SELECTED, LVIS_SELECTED);
1016 break;
1017 default:
1018 //CMessageBox::Show(nullptr, L"Have not implemented", L"TortoiseGit", MB_OK);
1019 break;
1020 #if 0
1022 case ID_BLAMECOMPARE:
1024 //user clicked on the menu item "compare with working copy"
1025 //now first get the revision which is selected
1026 if (PromptShown())
1028 GitDiff diff(this, this->m_hWnd, true);
1029 diff.SetHEADPeg(m_LogRevision);
1030 diff.ShowCompare(m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), false, true);
1032 else
1033 CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), m_LogRevision, false, false, true);
1035 break;
1036 case ID_BLAMEWITHPREVIOUS:
1038 //user clicked on the menu item "Compare and Blame with previous revision"
1039 if (PromptShown())
1041 GitDiff diff(this, this->m_hWnd, true);
1042 diff.SetHEADPeg(m_LogRevision);
1043 diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), false, true);
1045 else
1046 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
1048 break;
1050 case ID_OPENWITH:
1051 bOpenWith = true;
1052 case ID_OPEN:
1054 CProgressDlg progDlg;
1055 progDlg.SetTitle(IDS_APPNAME);
1056 progDlg.SetAnimation(IDR_DOWNLOAD);
1057 CString sInfoLine;
1058 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, m_path.GetWinPath(), (LPCTSTR)revSelected.ToString());
1059 progDlg.SetLine(1, sInfoLine, true);
1060 SetAndClearProgressInfo(&progDlg);
1061 progDlg.ShowModeless(m_hWnd);
1062 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, m_path, revSelected);
1063 bool bSuccess = true;
1064 if (!Cat(m_path, GitRev(GitRev::REV_HEAD), revSelected, tempfile))
1066 bSuccess = false;
1067 // try again, but with the selected revision as the peg revision
1068 if (!Cat(m_path, revSelected, revSelected, tempfile))
1070 progDlg.Stop();
1071 SetAndClearProgressInfo(nullptr);
1072 CMessageBox::Show(GetSafeHwnd(), GetLastErrorMessage(), L"TortoiseGit", MB_ICONERROR);
1073 EnableOKButton();
1074 break;
1076 bSuccess = true;
1078 if (bSuccess)
1080 progDlg.Stop();
1081 SetAndClearProgressInfo(nullptr);
1082 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1083 if (!bOpenWith)
1084 CAppUtils::ShellOpen(tempfile.GetWinPath(), GetSafeHwnd());
1085 else
1086 CAppUtils::ShowOpenWithDialog(tempfile.GetWinPathString(), GetSafeHwnd());
1089 break;
1090 case ID_BLAME:
1092 CBlameDlg dlg;
1093 dlg.EndRev = revSelected;
1094 if (dlg.DoModal() == IDOK)
1096 CBlame blame;
1097 CString tempfile;
1098 CString logfile;
1099 tempfile = blame.BlameToTempFile(m_path, dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, L"", dlg.m_bIncludeMerge, TRUE, TRUE);
1100 if (!tempfile.IsEmpty())
1102 if (dlg.m_bTextView)
1104 //open the default text editor for the result file
1105 CAppUtils::StartTextViewer(tempfile);
1107 else
1109 CString sParams = L"/path:\"" + m_path.GetGitPathString() + L"\" ";
1110 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(m_path.GetFileOrDirectoryName()),sParams))
1112 break;
1116 else
1118 CMessageBox::Show(GetSafeHwnd(), blame.GetLastErrorMessage(), L"TortoiseGit", MB_ICONERROR);
1122 break;
1123 case ID_EXPORT:
1125 CString sCmd;
1126 sCmd.Format(L"%s /command:export /path:\"%s\" /revision:%ld",
1127 (LPCTSTR)(CPathUtils::GetAppDirectory() + L"TortoiseGitProc.exe"),
1128 (LPCTSTR)pathURL, (LONG)revSelected);
1129 CAppUtils::LaunchApplication(sCmd, nullptr, false);
1131 break;
1132 case ID_VIEWREV:
1134 CString url = m_ProjectProperties.sWebViewerRev;
1135 url = GetAbsoluteUrlFromRelativeUrl(url);
1136 url.Replace(L"%REVISION%", revSelected.ToString());
1137 if (!url.IsEmpty())
1138 ShellExecute(GetSafeHwnd(), L"open", url, nullptr, nullptr, SW_SHOWDEFAULT);
1140 break;
1141 case ID_VIEWPATHREV:
1143 CString relurl = pathURL;
1144 CString sRoot = GetRepositoryRoot(CTGitPath(relurl));
1145 relurl = relurl.Mid(sRoot.GetLength());
1146 CString url = m_ProjectProperties.sWebViewerPathRev;
1147 url = GetAbsoluteUrlFromRelativeUrl(url);
1148 url.Replace(L"%REVISION%", revSelected.ToString());
1149 url.Replace(L"%PATH%", relurl);
1150 if (!url.IsEmpty())
1151 ShellExecute(GetSafeHwnd(), L"open", url, nullptr, nullptr, SW_SHOWDEFAULT);
1153 break;
1154 #endif
1156 } // switch (cmd)
1158 theApp.DoWaitCursor(-1);
1161 void CGitLogList::SetSelectedRebaseAction(int action)
1163 POSITION pos = GetFirstSelectedItemPosition();
1164 if (!pos) return;
1165 int index;
1166 while(pos)
1168 index = GetNextSelectedItem(pos);
1169 if (m_arShownList.SafeGetAt(index)->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT | LOGACTIONS_REBASE_DONE) || (index == GetItemCount() - 1 && action == LOGACTIONS_REBASE_SQUASH))
1170 continue;
1171 if (!m_bIsCherryPick && m_arShownList.SafeGetAt(index)->ParentsCount() > 1 && action == LOGACTIONS_REBASE_SQUASH)
1172 continue;
1173 m_arShownList.SafeGetAt(index)->GetRebaseAction() = action;
1174 CRect rect;
1175 this->GetItemRect(index,&rect,LVIR_BOUNDS);
1176 this->InvalidateRect(rect);
1179 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage);
1182 void CGitLogList::SetUnselectedRebaseAction(int action)
1184 POSITION pos = GetFirstSelectedItemPosition();
1185 int index = pos ? GetNextSelectedItem(pos) : -1;
1186 for (int i = 0; i < GetItemCount(); i++)
1188 if (i == index)
1190 index = pos ? GetNextSelectedItem(pos) : -1;
1191 continue;
1194 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))
1195 continue;
1196 m_arShownList.SafeGetAt(i)->GetRebaseAction() = action;
1197 CRect rect;
1198 this->GetItemRect(i, &rect, LVIR_BOUNDS);
1199 this->InvalidateRect(rect);
1202 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage);
1205 void CGitLogList::ShiftSelectedRebaseAction()
1207 POSITION pos = GetFirstSelectedItemPosition();
1208 int index;
1209 while(pos)
1211 index = GetNextSelectedItem(pos);
1212 int* action = &(m_arShownList.SafeGetAt(index))->GetRebaseAction();
1213 switch (*action)
1215 case LOGACTIONS_REBASE_PICK:
1216 *action = LOGACTIONS_REBASE_SKIP;
1217 break;
1218 case LOGACTIONS_REBASE_SKIP:
1219 *action= LOGACTIONS_REBASE_EDIT;
1220 break;
1221 case LOGACTIONS_REBASE_EDIT:
1222 *action = LOGACTIONS_REBASE_SQUASH;
1223 if (index == GetItemCount() - 1 && (m_bIsCherryPick || m_arShownList.SafeGetAt(index)->m_ParentHash.size() == 1))
1224 *action = LOGACTIONS_REBASE_PICK;
1225 break;
1226 case LOGACTIONS_REBASE_SQUASH:
1227 *action= LOGACTIONS_REBASE_PICK;
1228 break;
1230 CRect rect;
1231 this->GetItemRect(index, &rect, LVIR_BOUNDS);
1232 this->InvalidateRect(rect);