Prevent to set branch.<branch>.pushbranch and branch.<branch>.pushremote local config...
[TortoiseGit.git] / src / TortoiseProc / GitLogListAction.cpp
blob4181340020d10e5bb595c5502c240d2fd7c178ba
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 int CGitLogList::DeleteRef(const CString& ref)
149 CString shortname;
150 if (CGit::GetShortName(ref, shortname, L"refs/remotes/"))
152 CString msg;
153 msg.Format(IDS_PROC_DELETEREMOTEBRANCH, (LPCTSTR)ref);
154 int result = CMessageBox::Show(GetSafeOwner()->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(L"refs/heads/" + shortname);
170 if (g_Git.DeleteRemoteRefs(remoteName, list))
171 CMessageBox::Show(GetSafeOwner()->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(GetSafeOwner()->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, L"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(GetSafeOwner()->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 out;
197 if (g_Git.Run(L"git.exe stash clear", &out, CP_UTF8))
198 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), out, L"TortoiseGit", MB_OK | MB_ICONERROR);
199 return TRUE;
201 else if (choose == 2)
203 CString out;
204 if (g_Git.Run(L"git.exe stash drop refs/stash@{0}", &out, CP_UTF8))
205 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), out, L"TortoiseGit", MB_OK | MB_ICONERROR);
206 return TRUE;
208 return FALSE;
211 CString msg;
212 msg.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)ref);
213 if (CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), msg, L"TortoiseGit", 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
215 if (g_Git.DeleteRef(ref))
217 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), L"TortoiseGit", MB_OK | MB_ICONERROR);
218 return FALSE;
220 return TRUE;
222 return FALSE;
225 void CGitLogList::ContextMenuAction(int cmd,int FirstSelect, int LastSelect, CMenu *popmenu)
227 POSITION pos = GetFirstSelectedItemPosition();
228 int indexNext = GetNextSelectedItem(pos);
229 if (indexNext < 0)
230 return;
232 GitRevLoglist* pSelLogEntry = m_arShownList.SafeGetAt(indexNext);
234 bool bShiftPressed = !!(GetAsyncKeyState(VK_SHIFT) & 0x8000);
236 theApp.DoWaitCursor(1);
237 switch (cmd&0xFFFF)
239 case ID_COMMIT:
241 CTGitPathList pathlist;
242 CTGitPathList selectedlist;
243 pathlist.AddPath(this->m_Path);
244 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(L"Software\\TortoiseGit\\SelectFilesForCommit", TRUE));
245 CString str;
246 CAppUtils::Commit(CString(),false,str,
247 pathlist,selectedlist,bSelectFilesForCommit);
248 //this->Refresh();
249 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH,0);
251 break;
252 case ID_MERGE_ABORT:
254 if (CAppUtils::MergeAbort())
255 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH, 0);
257 break;
258 case ID_GNUDIFF1: // compare with WC, unified
260 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
261 bool bMerge = false, bCombine = false;
262 CString hash2;
263 if(!r1->m_CommitHash.IsEmpty())
265 CString merge;
266 cmd >>= 16;
267 if( (cmd&0xFFFF) == 0xFFFF)
268 bMerge = true;
269 else if((cmd&0xFFFF) == 0xFFFE)
270 bCombine = true;
271 else if ((cmd & 0xFFFF) == 0xFFFD)
273 CString tempfile = GetTempFile();
274 CString gitcmd = L"git.exe diff-tree --cc " + r1->m_CommitHash.ToString();
275 CString lastErr;
276 if (g_Git.RunLogFile(gitcmd, tempfile, &lastErr))
278 MessageBox(lastErr, L"TortoiseGit", MB_ICONERROR);
279 break;
284 CStdioFile file(tempfile, CFile::typeText | CFile::modeRead | CFile::shareDenyWrite);
285 CString strLine;
286 bool isHash = file.ReadString(strLine) && r1->m_CommitHash.ToString() == strLine;
287 bool more = isHash && file.ReadString(strLine) && !strLine.IsEmpty();
288 if (!more)
290 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_NOCHANGEAFTERMERGE, IDS_APPNAME, MB_OK);
291 break;
294 catch (CFileException* e)
296 e->Delete();
299 CAppUtils::StartUnifiedDiffViewer(tempfile, r1->m_CommitHash.ToString());
300 break;
302 else
304 if (cmd == 0)
305 cmd = 1;
306 if ((size_t)cmd > r1->m_ParentHash.size())
308 CString str;
309 str.Format(IDS_PROC_NOPARENT, cmd);
310 MessageBox(str, L"TortoiseGit", MB_OK | MB_ICONERROR);
311 return;
313 else
315 if(cmd>0)
316 hash2 = r1->m_ParentHash[cmd-1].ToString();
319 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), hash2, CTGitPath(), r1->m_CommitHash.ToString(), bShiftPressed, false, false, bMerge, bCombine);
321 else
322 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), L"HEAD", CTGitPath(), GitRev::GetWorkingCopy(), bShiftPressed, false, false, bMerge, bCombine);
324 break;
326 case ID_GNUDIFF2: // compare two revisions, unified
328 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
329 GitRev* r2 = m_arShownList.SafeGetAt(LastSelect);
330 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), r2->m_CommitHash.ToString(), CTGitPath(), r1->m_CommitHash.ToString(), bShiftPressed);
332 break;
334 case ID_COMPARETWO: // compare two revisions
336 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
337 GitRev* r2 = m_arShownList.SafeGetAt(LastSelect);
338 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
339 CGitDiff::DiffCommit(m_Path, r1, r2, bShiftPressed);
340 else
342 CString path1 = m_Path.GetGitPathString();
343 // start with 1 (0 = working copy changes)
344 for (int i = m_bShowWC ? 1 : 0; i < FirstSelect; ++i)
346 GitRevLoglist* first = m_arShownList.SafeGetAt(i);
347 CTGitPathList list = first->GetFiles(nullptr);
348 const CTGitPath* file = list.LookForGitPath(path1);
349 if (file && !file->GetGitOldPathString().IsEmpty())
350 path1 = file->GetGitOldPathString();
352 CString path2 = path1;
353 for (int i = FirstSelect; i < LastSelect; ++i)
355 GitRevLoglist* first = m_arShownList.SafeGetAt(i);
356 CTGitPathList list = first->GetFiles(nullptr);
357 const CTGitPath* file = list.LookForGitPath(path2);
358 if (file && !file->GetGitOldPathString().IsEmpty())
359 path2 = file->GetGitOldPathString();
361 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), r1, r2, bShiftPressed);
365 break;
367 case ID_COMPARE: // compare revision with WC
369 GitRevLoglist* r1 = &m_wcRev;
370 GitRevLoglist* r2 = pSelLogEntry;
372 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
373 CGitDiff::DiffCommit(m_Path, r1, r2, bShiftPressed);
374 else
376 CString path1 = m_Path.GetGitPathString();
377 // start with 1 (0 = working copy changes)
378 for (int i = m_bShowWC ? 1 : 0; i < FirstSelect; ++i)
380 GitRevLoglist* first = m_arShownList.SafeGetAt(i);
381 CTGitPathList list = first->GetFiles(nullptr);
382 const CTGitPath* file = list.LookForGitPath(path1);
383 if (file && !file->GetGitOldPathString().IsEmpty())
384 path1 = file->GetGitOldPathString();
386 CGitDiff::DiffCommit(m_Path, CTGitPath(path1), r1, r2, bShiftPressed);
389 //user clicked on the menu item "compare with working copy"
390 //if (PromptShown())
392 // GitDiff diff(this, m_hWnd, true);
393 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
394 // diff.SetHEADPeg(m_LogRevision);
395 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
397 //else
398 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
400 break;
402 case ID_COMPAREWITHPREVIOUS:
404 if (pSelLogEntry->m_ParentHash.empty())
406 if (pSelLogEntry->GetParentFromHash(pSelLogEntry->m_CommitHash))
407 MessageBox(pSelLogEntry->GetLastErr(), L"TortoiseGit", MB_ICONERROR);
410 if (!pSelLogEntry->m_ParentHash.empty())
411 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
413 cmd>>=16;
414 cmd&=0xFFFF;
416 if(cmd == 0)
417 cmd=1;
419 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
420 CGitDiff::DiffCommit(m_Path, pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString(), bShiftPressed);
421 else
423 CString path1 = m_Path.GetGitPathString();
424 // start with 1 (0 = working copy changes)
425 for (int i = m_bShowWC ? 1 : 0; i < indexNext; ++i)
427 GitRevLoglist* first = m_arShownList.SafeGetAt(i);
428 CTGitPathList list = first->GetFiles(nullptr);
429 const CTGitPath* file = list.LookForGitPath(path1);
430 if (file && !file->GetGitOldPathString().IsEmpty())
431 path1 = file->GetGitOldPathString();
433 CString path2 = path1;
434 GitRevLoglist* first = m_arShownList.SafeGetAt(indexNext);
435 CTGitPathList list = first->GetFiles(nullptr);
436 const CTGitPath* file = list.LookForGitPath(path2);
437 if (file && !file->GetGitOldPathString().IsEmpty())
438 path2 = file->GetGitOldPathString();
440 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString(), bShiftPressed);
443 else
445 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_PROC_NOPREVIOUSVERSION, IDS_APPNAME, MB_OK);
447 //if (PromptShown())
449 // GitDiff diff(this, m_hWnd, true);
450 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
451 // diff.SetHEADPeg(m_LogRevision);
452 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
454 //else
455 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
457 break;
458 case ID_COMPARETWOCOMMITCHANGES:
460 auto pFirstEntry = m_arShownList.SafeGetAt(FirstSelect);
461 auto pLastEntry = m_arShownList.SafeGetAt(LastSelect);
462 CString patch1 = GetTempFile();
463 CString patch2 = GetTempFile();
464 CString err;
465 if (g_Git.RunLogFile(L"git.exe format-patch --stdout " + pFirstEntry->m_CommitHash.ToString() + L"~1.." + pFirstEntry->m_CommitHash.ToString() + L"", patch1, &err))
467 MessageBox(L"Could not generate patch for commit " + pFirstEntry->m_CommitHash.ToString() + L".\n" + err, L"TortoiseGit", MB_ICONERROR);
468 break;
470 if (g_Git.RunLogFile(L"git.exe format-patch --stdout " + pLastEntry->m_CommitHash.ToString() + L"~1.." + pLastEntry->m_CommitHash.ToString() + L"", patch2, &err))
472 MessageBox(L"Could not generate patch for commit " + pLastEntry->m_CommitHash.ToString() + L".\n" + err, L"TortoiseGit", MB_ICONERROR);
473 break;
475 CAppUtils::DiffFlags flags;
476 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);
478 break;
479 case ID_LOG_VIEWRANGE:
480 case ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE:
482 GitRev* pLastEntry = m_arShownList.SafeGetAt(LastSelect);
484 CString sep = L"..";
485 if ((cmd & 0xFFFF) == ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE)
486 sep = L"...";
488 CString cmdline;
489 cmdline.Format(L"/command:log /path:\"%s\" /range:\"%s%s%s\"",
490 (LPCTSTR)g_Git.CombinePath(m_Path), (LPCTSTR)pLastEntry->m_CommitHash.ToString(), (LPCTSTR)sep, (LPCTSTR)pSelLogEntry->m_CommitHash.ToString());
491 CAppUtils::RunTortoiseGitProc(cmdline);
493 break;
494 case ID_COPYCLIPBOARDFULL:
495 case ID_COPYCLIPBOARDFULLNOPATHS:
496 case ID_COPYCLIPBOARDHASH:
497 case ID_COPYCLIPBOARDAUTHORSFULL:
498 case ID_COPYCLIPBOARDAUTHORSNAME:
499 case ID_COPYCLIPBOARDAUTHORSEMAIL:
500 case ID_COPYCLIPBOARDSUBJECTS:
501 case ID_COPYCLIPBOARDMESSAGES:
503 CopySelectionToClipBoard(cmd & 0xFFFF);
505 break;
506 case ID_COPYCLIPBOARDBRANCHTAG:
508 if (!popmenu)
509 break;
510 auto selectedBranch = reinterpret_cast<const CString*>(reinterpret_cast<CIconMenu*>(popmenu)->GetMenuItemData(cmd));
511 CString sClipboard;
512 if (selectedBranch)
514 if (CStringUtils::StartsWith(*selectedBranch, L"refs/tags/"))
515 sClipboard = selectedBranch->Mid((int)wcslen(L"refs/tags/")).TrimRight(L"^{}");
516 else
517 sClipboard = CGit::StripRefName(*selectedBranch);
519 else
521 for (const auto& ref : m_HashMap[pSelLogEntry->m_CommitHash])
523 sClipboard += ref;
524 sClipboard += L"\r\n";
527 CStringUtils::WriteAsciiStringToClipboard(sClipboard, GetSafeHwnd());
529 break;
530 case ID_EXPORT:
532 CString str=pSelLogEntry->m_CommitHash.ToString();
533 // try to get the tag
534 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], L"refs/tags/", str);
535 CAppUtils::Export(&str, &m_Path);
537 break;
538 case ID_CREATE_BRANCH:
539 case ID_CREATE_TAG:
541 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd & 0xFFFF) : nullptr;
542 CString str = pSelLogEntry->m_CommitHash.ToString();
543 if (branch)
544 str = *branch;
545 else // try to guess remote branch in order to enable tracking
546 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], L"refs/remotes/", str);
548 CAppUtils::CreateBranchTag((cmd&0xFFFF) == ID_CREATE_TAG, &str);
549 ReloadHashMap();
550 if (m_pFindDialog)
551 m_pFindDialog->RefreshList();
552 Invalidate();
553 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
555 break;
556 case ID_SWITCHTOREV:
558 CString str = pSelLogEntry->m_CommitHash.ToString();
559 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd & 0xFFFF) : nullptr;
560 if (branch)
561 str = *branch;
562 else // try to guess remote branch in order to recommend good branch name and tracking
563 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], L"refs/remotes/", str);
565 CAppUtils::Switch(str);
567 ReloadHashMap();
568 Invalidate();
569 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
570 break;
571 case ID_SWITCHBRANCH:
572 if(popmenu)
574 const CString* branch = (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
575 if(branch)
577 CString name = *branch;
578 CGit::GetShortName(*branch, name, L"refs/heads/");
579 CAppUtils::PerformSwitch(name);
581 ReloadHashMap();
582 Invalidate();
583 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
585 break;
586 case ID_RESET:
588 CString str = pSelLogEntry->m_CommitHash.ToString();
589 if (CAppUtils::GitReset(&str))
591 ResetWcRev(true);
592 ReloadHashMap();
593 Invalidate();
596 break;
597 case ID_REBASE_PICK:
598 SetSelectedRebaseAction(LOGACTIONS_REBASE_PICK);
599 break;
600 case ID_REBASE_EDIT:
601 SetSelectedRebaseAction(LOGACTIONS_REBASE_EDIT);
602 break;
603 case ID_REBASE_SQUASH:
604 SetSelectedRebaseAction(LOGACTIONS_REBASE_SQUASH);
605 break;
606 case ID_REBASE_SKIP:
607 SetSelectedRebaseAction(LOGACTIONS_REBASE_SKIP);
608 break;
609 case ID_COMBINE_COMMIT:
611 CString head;
612 CGitHash headhash;
613 CGitHash hashFirst,hashLast;
615 int headindex=GetHeadIndex();
616 if(headindex>=0) //incase show all branch, head is not the first commits.
618 head.Format(L"HEAD~%d", FirstSelect - headindex);
619 if (g_Git.GetHash(hashFirst, head))
621 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of first selected revision."), L"TortoiseGit", MB_ICONERROR);
622 break;
625 head.Format(L"HEAD~%d", LastSelect - headindex);
626 if (g_Git.GetHash(hashLast, head))
628 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of last selected revision."), L"TortoiseGit", MB_ICONERROR);
629 break;
633 GitRev* pFirstEntry = m_arShownList.SafeGetAt(FirstSelect);
634 GitRev* pLastEntry = m_arShownList.SafeGetAt(LastSelect);
635 if(pFirstEntry->m_CommitHash != hashFirst || pLastEntry->m_CommitHash != hashLast)
637 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_PROC_CANNOTCOMBINE, IDS_APPNAME, MB_OK | MB_ICONEXCLAMATION);
638 break;
641 GitRev lastRevision;
642 if (lastRevision.GetParentFromHash(hashLast))
644 MessageBox(lastRevision.GetLastErr(), L"TortoiseGit", MB_ICONERROR);
645 break;
648 if (g_Git.GetHash(headhash, L"HEAD"))
650 MessageBox(g_Git.GetGitLastErr(L"Could not get HEAD hash."), L"TortoiseGit", MB_ICONERROR);
651 break;
654 if(!g_Git.CheckCleanWorkTree())
656 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK | MB_ICONEXCLAMATION);
657 break;
659 CString sCmd, out;
661 //Use throw to abort this process (reset back to original HEAD)
664 sCmd.Format(L"git.exe reset --hard %s --", (LPCTSTR)pFirstEntry->m_CommitHash.ToString());
665 if(g_Git.Run(sCmd, &out, CP_UTF8))
667 MessageBox(out, L"TortoiseGit", MB_OK | MB_ICONERROR);
668 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1)) + L"\r\n\r\n" + out));
670 sCmd.Format(L"git.exe reset --soft %s --", (LPCTSTR)hashLast.ToString());
671 if(g_Git.Run(sCmd, &out, CP_UTF8))
673 MessageBox(out, L"TortoiseGit", MB_OK | MB_ICONERROR);
674 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2)) + L"\r\n\r\n"+out));
677 CCommitDlg dlg;
678 for (int i = FirstSelect; i <= LastSelect; ++i)
680 GitRev* pRev = m_arShownList.SafeGetAt(i);
681 dlg.m_sLogMessage += pRev->GetSubject() + L'\n' + pRev->GetBody();
682 dlg.m_sLogMessage += L'\n';
684 dlg.m_bWholeProject=true;
685 dlg.m_bSelectFilesForCommit = true;
686 dlg.m_bForceCommitAmend=true;
687 int squashDate = (int)CRegDWORD(L"Software\\TortoiseGit\\SquashDate", 0);
688 if (squashDate == 1)
689 dlg.SetTime(m_arShownList.SafeGetAt(FirstSelect)->GetAuthorDate());
690 else if (squashDate == 2)
691 dlg.SetTime(CTime::GetCurrentTime());
692 else
693 dlg.SetTime(m_arShownList.SafeGetAt(LastSelect)->GetAuthorDate());
694 CTGitPathList gpl;
695 gpl.AddPath(CTGitPath(g_Git.m_CurrentDir));
696 dlg.m_pathList = gpl;
697 if (lastRevision.ParentsCount() != 1)
699 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);
700 dlg.m_bAmendDiffToLastCommit = TRUE;
702 else
703 dlg.m_bAmendDiffToLastCommit = FALSE;
704 dlg.m_bNoPostActions=true;
705 dlg.m_AmendStr=dlg.m_sLogMessage;
707 if (dlg.DoModal() == IDOK)
709 if(pFirstEntry->m_CommitHash!=headhash)
711 if(CherryPickFrom(pFirstEntry->m_CommitHash.ToString(),headhash))
713 CString msg;
714 msg.Format(L"Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n");
715 throw std::exception(CUnicodeUtils::GetUTF8(msg));
719 else
720 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_USERCANCELLED))));
722 catch(std::exception& e)
724 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), CUnicodeUtils::GetUnicode(CStringA(e.what())), L"TortoiseGit", MB_OK | MB_ICONERROR);
725 sCmd.Format(L"git.exe reset --hard %s --", (LPCTSTR)headhash.ToString());
726 out.Empty();
727 if(g_Git.Run(sCmd, &out, CP_UTF8))
728 MessageBox(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD)) + L"\r\n\r\n" + out, L"TortoiseGit", MB_OK | MB_ICONERROR);
730 Refresh();
732 break;
734 case ID_CHERRY_PICK:
736 if (m_bThreadRunning)
738 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_PROC_LOG_ONLYONCE, IDS_APPNAME, MB_ICONEXCLAMATION);
739 break;
741 CRebaseDlg dlg;
742 dlg.m_IsCherryPick = TRUE;
743 dlg.m_Upstream = this->m_CurrentBranch;
744 POSITION pos2 = GetFirstSelectedItemPosition();
745 while(pos2)
747 int indexNext2 = GetNextSelectedItem(pos2);
748 dlg.m_CommitList.m_logEntries.push_back(m_arShownList.SafeGetAt(indexNext2)->m_CommitHash);
749 dlg.m_CommitList.m_LogCache.m_HashMap[m_arShownList.SafeGetAt(indexNext2)->m_CommitHash] = *m_arShownList.SafeGetAt(indexNext2);
750 dlg.m_CommitList.m_logEntries.GetGitRevAt(dlg.m_CommitList.m_logEntries.size() - 1).GetRebaseAction() |= LOGACTIONS_REBASE_PICK;
753 if(dlg.DoModal() == IDOK)
755 Refresh();
758 break;
759 case ID_REBASE_TO_VERSION:
761 if (m_bThreadRunning)
763 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_PROC_LOG_ONLYONCE, IDS_APPNAME, MB_ICONEXCLAMATION);
764 break;
766 CRebaseDlg dlg;
767 auto refList = m_HashMap[pSelLogEntry->m_CommitHash];
768 dlg.m_Upstream = refList.empty() ? pSelLogEntry->m_CommitHash.ToString() : refList.front();
769 for (const auto& ref : refList)
770 if (CGit::GetShortName(ref, dlg.m_Upstream, L"refs/heads/"))
771 break;
773 if(dlg.DoModal() == IDOK)
775 Refresh();
779 break;
781 case ID_STASH_SAVE:
782 if (CAppUtils::StashSave())
783 Refresh();
784 break;
786 case ID_STASH_POP:
787 if (CAppUtils::StashPop())
788 Refresh();
789 break;
791 case ID_STASH_LIST:
792 CAppUtils::RunTortoiseGitProc(L"/command:reflog /ref:refs/stash");
793 break;
795 case ID_REFLOG_STASH_APPLY:
796 CAppUtils::StashApply(pSelLogEntry->m_Ref);
797 break;
799 case ID_REFLOG_DEL:
801 CString str;
802 if (GetSelectedCount() > 1)
803 str.Format(IDS_PROC_DELETENREFS, GetSelectedCount());
804 else
805 str.Format(IDS_PROC_DELETEREF, (LPCTSTR)pSelLogEntry->m_Ref);
807 if (CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), str, L"TortoiseGit", 1, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
808 return;
810 std::vector<CString> refsToDelete;
811 POSITION pos2 = GetFirstSelectedItemPosition();
812 while (pos2)
814 CString ref = m_arShownList.SafeGetAt(GetNextSelectedItem(pos2))->m_Ref;
815 if (CStringUtils::StartsWith(ref, L"refs/"))
816 ref = ref.Mid(5);
817 int refpos = ref.ReverseFind(L'{');
818 if (refpos > 0 && ref.Mid(refpos - 1, 2) != L"@{")
819 ref = ref.Left(refpos) + L'@'+ ref.Mid(refpos);
820 refsToDelete.push_back(ref);
823 for (auto revIt = refsToDelete.crbegin(); revIt != refsToDelete.crend(); ++revIt)
825 CString ref = *revIt;
826 CString sCmd, out;
827 if (CStringUtils::StartsWith(ref, L"stash"))
828 sCmd.Format(L"git.exe stash drop %s", (LPCTSTR)ref);
829 else
830 sCmd.Format(L"git.exe reflog delete %s", (LPCTSTR)ref);
832 if (g_Git.Run(sCmd, &out, CP_UTF8))
833 MessageBox(out, L"TortoiseGit", MB_OK | MB_ICONERROR);
835 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
838 break;
839 case ID_LOG:
841 CString sCmd = L"/command:log";
842 sCmd += L" /path:\"" + g_Git.m_CurrentDir + L"\" ";
843 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
844 sCmd += L" /endrev:" + r1->m_CommitHash.ToString();
845 CAppUtils::RunTortoiseGitProc(sCmd);
847 break;
848 case ID_CREATE_PATCH:
850 int select=this->GetSelectedCount();
851 CString sCmd = L"/command:formatpatch";
852 sCmd += L" /path:\"" + g_Git.m_CurrentDir + L"\" ";
854 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
855 GitRev* r2 = nullptr;
856 if(select == 1)
858 sCmd += L" /startrev:" + r1->m_CommitHash.ToString();
860 else
862 r2 = m_arShownList.SafeGetAt(LastSelect);
863 if( this->m_IsOldFirst )
865 sCmd += L" /startrev:" + r1->m_CommitHash.ToString() + L"~1";
866 sCmd += L" /endrev:" + r2->m_CommitHash.ToString();
869 else
871 sCmd += L" /startrev:" + r2->m_CommitHash.ToString() + L"~1";
872 sCmd += L" /endrev:" + r1->m_CommitHash.ToString();
877 CAppUtils::RunTortoiseGitProc(sCmd);
879 break;
880 case ID_BISECTSTART:
882 GitRev* first = m_arShownList.SafeGetAt(FirstSelect);
883 GitRev* last = m_arShownList.SafeGetAt(LastSelect);
884 ASSERT(first && last);
886 CString firstBad = first->m_CommitHash.ToString();
887 if (!m_HashMap[first->m_CommitHash].empty())
888 firstBad = m_HashMap[first->m_CommitHash].at(0);
889 CString lastGood = last->m_CommitHash.ToString();
890 if (!m_HashMap[last->m_CommitHash].empty())
891 lastGood = m_HashMap[last->m_CommitHash].at(0);
893 if (CAppUtils::BisectStart(lastGood, firstBad))
894 Refresh();
896 break;
897 case ID_BISECTGOOD:
899 GitRev* first = m_arShownList.SafeGetAt(FirstSelect);
900 if (CAppUtils::BisectOperation(L"good", !first->m_CommitHash.IsEmpty() ? first->m_CommitHash.ToString() : L""))
901 Refresh();
903 break;
904 case ID_BISECTBAD:
906 GitRev* first = m_arShownList.SafeGetAt(FirstSelect);
907 if (CAppUtils::BisectOperation(L"bad", !first->m_CommitHash.IsEmpty() ? first->m_CommitHash.ToString() : L""))
908 Refresh();
910 break;
911 case ID_BISECTSKIP:
913 CString refs;
914 POSITION pos2 = GetFirstSelectedItemPosition();
915 while (pos2)
917 int indexNext2 = GetNextSelectedItem(pos2);
918 auto rev = m_arShownList.SafeGetAt(indexNext2);
919 if (!rev->m_CommitHash.IsEmpty())
920 refs.AppendFormat(L" %s", (LPCTSTR)rev->m_CommitHash.ToString());
922 if (CAppUtils::BisectOperation(L"skip", refs))
923 Refresh();
925 break;
926 case ID_BISECTRESET:
928 if (CAppUtils::BisectOperation(L"reset"))
929 Refresh();
931 break;
932 case ID_REPOBROWSE:
934 CString sCmd;
935 sCmd.Format(L"/command:repobrowser /path:\"%s\" /rev:%s", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)pSelLogEntry->m_CommitHash.ToString());
936 CAppUtils::RunTortoiseGitProc(sCmd);
938 break;
939 case ID_PUSH:
941 CString guessAssociatedBranch = pSelLogEntry->m_CommitHash;
942 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd) : nullptr;
943 if (branch)
944 guessAssociatedBranch = *branch;
945 else if (!GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], L"refs/heads/", guessAssociatedBranch))
946 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], L"refs/tags/", guessAssociatedBranch);
948 guessAssociatedBranch.Replace(L"^{}", L"");
950 if (CAppUtils::Push(guessAssociatedBranch))
951 Refresh();
953 break;
954 case ID_PULL:
956 if (CAppUtils::Pull())
957 Refresh();
959 break;
960 case ID_FETCH:
962 if (CAppUtils::Fetch())
963 Refresh();
965 break;
966 case ID_SVNDCOMMIT:
968 if (CAppUtils::SVNDCommit())
969 Refresh();
971 break;
972 case ID_CLEANUP:
974 CString sCmd;
975 sCmd.Format(L"/command:cleanup /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
976 CAppUtils::RunTortoiseGitProc(sCmd);
978 break;
979 case ID_SUBMODULE_UPDATE:
981 CString sCmd;
982 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
983 CAppUtils::RunTortoiseGitProc(sCmd);
985 break;
986 case ID_SHOWBRANCHES:
988 CCommitIsOnRefsDlg* dlg = new CCommitIsOnRefsDlg(this);
989 dlg->m_Rev = (LPCTSTR)pSelLogEntry->m_CommitHash.ToString();
990 dlg->Create(this);
991 // pointer won't leak as it is destroyed within PostNcDestroy()
993 break;
994 case ID_DELETE:
996 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd) : nullptr;
997 if (!branch)
999 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_ERROR_NOREF, IDS_APPNAME, MB_OK | MB_ICONERROR);
1000 return;
1002 CString shortname;
1003 if (branch == (CString*)MAKEINTRESOURCE(IDS_ALL))
1005 CString currentBranch = L"refs/heads/" + m_CurrentBranch;
1006 bool nothingDeleted = true;
1007 for (const auto& ref : m_HashMap[pSelLogEntry->m_CommitHash])
1009 if (ref == currentBranch)
1010 continue;
1011 if (!DeleteRef(ref))
1012 break;
1013 nothingDeleted = false;
1015 if (nothingDeleted)
1016 return;
1018 else if (!DeleteRef(*branch))
1019 return;
1020 this->ReloadHashMap();
1021 if (m_pFindDialog)
1022 m_pFindDialog->RefreshList();
1023 CRect rect;
1024 this->GetItemRect(FirstSelect,&rect,LVIR_BOUNDS);
1025 this->InvalidateRect(rect);
1027 break;
1029 case ID_FINDENTRY:
1031 m_nSearchIndex = GetSelectionMark();
1032 if (m_nSearchIndex < 0)
1033 m_nSearchIndex = 0;
1034 if (m_pFindDialog)
1035 break;
1036 else
1038 m_pFindDialog = new CFindDlg();
1039 m_pFindDialog->Create(this);
1042 break;
1043 case ID_MERGEREV:
1045 CString str = pSelLogEntry->m_CommitHash.ToString();
1046 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd & 0xFFFF) : nullptr;
1047 if (branch)
1048 str = *branch;
1049 else if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
1050 str = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
1051 // we need an URL to complete this command, so error out if we can't get an URL
1052 if(CAppUtils::Merge(&str))
1054 this->Refresh();
1057 break;
1058 case ID_REVERTREV:
1060 int parent = 0;
1061 if (GetSelectedCount() == 1)
1063 parent = cmd >> 16;
1064 if ((size_t)parent > pSelLogEntry->m_ParentHash.size())
1066 CString str;
1067 str.Format(IDS_PROC_NOPARENT, parent);
1068 MessageBox(str, L"TortoiseGit", MB_OK | MB_ICONERROR);
1069 return;
1073 if (!this->RevertSelectedCommits(parent))
1075 if (CMessageBox::Show(m_hWnd, IDS_REVREVERTED, IDS_APPNAME, 1, IDI_QUESTION, IDS_OKBUTTON, IDS_COMMITBUTTON) == 2)
1077 CTGitPathList pathlist;
1078 CTGitPathList selectedlist;
1079 pathlist.AddPath(this->m_Path);
1080 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(L"Software\\TortoiseGit\\SelectFilesForCommit", TRUE));
1081 CString str;
1082 CAppUtils::Commit(CString(), false, str, pathlist, selectedlist, bSelectFilesForCommit);
1084 this->Refresh();
1087 break;
1088 case ID_EDITNOTE:
1090 CAppUtils::EditNote(pSelLogEntry, &m_ProjectProperties);
1091 this->SetItemState(FirstSelect, 0, LVIS_SELECTED);
1092 this->SetItemState(FirstSelect, LVIS_SELECTED, LVIS_SELECTED);
1094 break;
1095 default:
1096 //CMessageBox::Show(nullptr, L"Have not implemented", L"TortoiseGit", MB_OK);
1097 break;
1098 #if 0
1100 case ID_BLAMECOMPARE:
1102 //user clicked on the menu item "compare with working copy"
1103 //now first get the revision which is selected
1104 if (PromptShown())
1106 GitDiff diff(this, this->m_hWnd, true);
1107 diff.SetHEADPeg(m_LogRevision);
1108 diff.ShowCompare(m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), false, true);
1110 else
1111 CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), m_LogRevision, false, false, true);
1113 break;
1114 case ID_BLAMEWITHPREVIOUS:
1116 //user clicked on the menu item "Compare and Blame with previous revision"
1117 if (PromptShown())
1119 GitDiff diff(this, this->m_hWnd, true);
1120 diff.SetHEADPeg(m_LogRevision);
1121 diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), false, true);
1123 else
1124 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
1126 break;
1128 case ID_OPENWITH:
1129 bOpenWith = true;
1130 case ID_OPEN:
1132 CProgressDlg progDlg;
1133 progDlg.SetTitle(IDS_APPNAME);
1134 progDlg.SetAnimation(IDR_DOWNLOAD);
1135 CString sInfoLine;
1136 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, m_path.GetWinPath(), (LPCTSTR)revSelected.ToString());
1137 progDlg.SetLine(1, sInfoLine, true);
1138 SetAndClearProgressInfo(&progDlg);
1139 progDlg.ShowModeless(m_hWnd);
1140 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, m_path, revSelected);
1141 bool bSuccess = true;
1142 if (!Cat(m_path, GitRev(GitRev::REV_HEAD), revSelected, tempfile))
1144 bSuccess = false;
1145 // try again, but with the selected revision as the peg revision
1146 if (!Cat(m_path, revSelected, revSelected, tempfile))
1148 progDlg.Stop();
1149 SetAndClearProgressInfo(nullptr);
1150 CMessageBox::Show(GetSafeHwnd(), GetLastErrorMessage(), L"TortoiseGit", MB_ICONERROR);
1151 EnableOKButton();
1152 break;
1154 bSuccess = true;
1156 if (bSuccess)
1158 progDlg.Stop();
1159 SetAndClearProgressInfo(nullptr);
1160 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1161 if (!bOpenWith)
1162 CAppUtils::ShellOpen(tempfile.GetWinPath(), GetSafeHwnd());
1163 else
1164 CAppUtils::ShowOpenWithDialog(tempfile.GetWinPathString(), GetSafeHwnd());
1167 break;
1168 case ID_BLAME:
1170 CBlameDlg dlg;
1171 dlg.EndRev = revSelected;
1172 if (dlg.DoModal() == IDOK)
1174 CBlame blame;
1175 CString tempfile;
1176 CString logfile;
1177 tempfile = blame.BlameToTempFile(m_path, dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, L"", dlg.m_bIncludeMerge, TRUE, TRUE);
1178 if (!tempfile.IsEmpty())
1180 if (dlg.m_bTextView)
1182 //open the default text editor for the result file
1183 CAppUtils::StartTextViewer(tempfile);
1185 else
1187 CString sParams = L"/path:\"" + m_path.GetGitPathString() + L"\" ";
1188 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(m_path.GetFileOrDirectoryName()),sParams))
1190 break;
1194 else
1196 CMessageBox::Show(GetSafeHwnd(), blame.GetLastErrorMessage(), L"TortoiseGit", MB_ICONERROR);
1200 break;
1201 case ID_EXPORT:
1203 CString sCmd;
1204 sCmd.Format(L"%s /command:export /path:\"%s\" /revision:%ld",
1205 (LPCTSTR)(CPathUtils::GetAppDirectory() + L"TortoiseGitProc.exe"),
1206 (LPCTSTR)pathURL, (LONG)revSelected);
1207 CAppUtils::LaunchApplication(sCmd, nullptr, false);
1209 break;
1210 case ID_VIEWREV:
1212 CString url = m_ProjectProperties.sWebViewerRev;
1213 url = GetAbsoluteUrlFromRelativeUrl(url);
1214 url.Replace(L"%REVISION%", revSelected.ToString());
1215 if (!url.IsEmpty())
1216 ShellExecute(GetSafeHwnd(), L"open", url, nullptr, nullptr, SW_SHOWDEFAULT);
1218 break;
1219 case ID_VIEWPATHREV:
1221 CString relurl = pathURL;
1222 CString sRoot = GetRepositoryRoot(CTGitPath(relurl));
1223 relurl = relurl.Mid(sRoot.GetLength());
1224 CString url = m_ProjectProperties.sWebViewerPathRev;
1225 url = GetAbsoluteUrlFromRelativeUrl(url);
1226 url.Replace(L"%REVISION%", revSelected.ToString());
1227 url.Replace(L"%PATH%", relurl);
1228 if (!url.IsEmpty())
1229 ShellExecute(GetSafeHwnd(), L"open", url, nullptr, nullptr, SW_SHOWDEFAULT);
1231 break;
1232 #endif
1234 } // switch (cmd)
1236 theApp.DoWaitCursor(-1);
1239 void CGitLogList::SetSelectedRebaseAction(int action)
1241 POSITION pos = GetFirstSelectedItemPosition();
1242 if (!pos) return;
1243 int index;
1244 while(pos)
1246 index = GetNextSelectedItem(pos);
1247 if (m_arShownList.SafeGetAt(index)->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT | LOGACTIONS_REBASE_DONE) || (index == GetItemCount() - 1 && action == LOGACTIONS_REBASE_SQUASH))
1248 continue;
1249 if (!m_bIsCherryPick && m_arShownList.SafeGetAt(index)->ParentsCount() > 1 && action == LOGACTIONS_REBASE_SQUASH)
1250 continue;
1251 m_arShownList.SafeGetAt(index)->GetRebaseAction() = action;
1252 CRect rect;
1253 this->GetItemRect(index,&rect,LVIR_BOUNDS);
1254 this->InvalidateRect(rect);
1257 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage);
1260 void CGitLogList::SetUnselectedRebaseAction(int action)
1262 POSITION pos = GetFirstSelectedItemPosition();
1263 int index = pos ? GetNextSelectedItem(pos) : -1;
1264 for (int i = 0; i < GetItemCount(); i++)
1266 if (i == index)
1268 index = pos ? GetNextSelectedItem(pos) : -1;
1269 continue;
1272 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))
1273 continue;
1274 m_arShownList.SafeGetAt(i)->GetRebaseAction() = action;
1275 CRect rect;
1276 this->GetItemRect(i, &rect, LVIR_BOUNDS);
1277 this->InvalidateRect(rect);
1280 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage);
1283 void CGitLogList::ShiftSelectedRebaseAction()
1285 POSITION pos = GetFirstSelectedItemPosition();
1286 int index;
1287 while(pos)
1289 index = GetNextSelectedItem(pos);
1290 int* action = &(m_arShownList.SafeGetAt(index))->GetRebaseAction();
1291 switch (*action)
1293 case LOGACTIONS_REBASE_PICK:
1294 *action = LOGACTIONS_REBASE_SKIP;
1295 break;
1296 case LOGACTIONS_REBASE_SKIP:
1297 *action= LOGACTIONS_REBASE_EDIT;
1298 break;
1299 case LOGACTIONS_REBASE_EDIT:
1300 *action = LOGACTIONS_REBASE_SQUASH;
1301 if (index == GetItemCount() - 1 && (m_bIsCherryPick || m_arShownList.SafeGetAt(index)->m_ParentHash.size() == 1))
1302 *action = LOGACTIONS_REBASE_PICK;
1303 break;
1304 case LOGACTIONS_REBASE_SQUASH:
1305 *action= LOGACTIONS_REBASE_PICK;
1306 break;
1308 CRect rect;
1309 this->GetItemRect(index, &rect, LVIR_BOUNDS);
1310 this->InvalidateRect(rect);