SyncDlg: Disallow in/out changes to include local context menu
[TortoiseGit.git] / src / TortoiseProc / GitLogListAction.cpp
blobcf9f6e5616ced9ee260c7a53d0ddb06a92f4c9e5
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2016 - TortoiseGit
4 // Copyright (C) 2005-2007 Marco Costalba
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 // GitLogList.cpp : implementation file
22 #include "stdafx.h"
23 #include "TortoiseProc.h"
24 #include "GitLogList.h"
25 #include "GitRev.h"
26 #include "IconMenu.h"
27 #include "cursor.h"
28 #include "GitProgressDlg.h"
29 #include "ProgressDlg.h"
30 #include "SysProgressDlg.h"
31 #include "LogDlg.h"
32 #include "MessageBox.h"
33 #include "registry.h"
34 #include "AppUtils.h"
35 #include "StringUtils.h"
36 #include "UnicodeUtils.h"
37 #include "TempFile.h"
38 #include "CommitDlg.h"
39 #include "RebaseDlg.h"
40 #include "CommitIsOnRefsDlg.h"
41 #include "GitDiff.h"
42 #include "../TGitCache/CacheInterface.h"
44 IMPLEMENT_DYNAMIC(CGitLogList, CHintCtrl<CListCtrl>)
46 static void GetFirstEntryStartingWith(STRING_VECTOR& heystack, const CString& needle, CString& result)
48 auto it = std::find_if(heystack.cbegin(), heystack.cend(), [&needle](const CString& entry) { return CStringUtils::StartsWith(entry, needle); });
49 if (it == heystack.cend())
50 return;
51 result = *it;
54 int CGitLogList::RevertSelectedCommits(int parent)
56 CSysProgressDlg progress;
57 int ret = -1;
59 #if 0
60 if(!g_Git.CheckCleanWorkTree())
61 CMessageBox::Show(nullptr, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
62 #endif
64 if (this->GetSelectedCount() > 1)
66 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_REVERTCOMMIT)));
67 progress.SetTime(true);
68 progress.ShowModeless(this);
71 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
73 POSITION pos = GetFirstSelectedItemPosition();
74 int i=0;
75 while(pos)
77 int index = GetNextSelectedItem(pos);
78 GitRev* r1 = m_arShownList.SafeGetAt(index);
80 if (progress.IsVisible())
82 progress.FormatNonPathLine(1, IDS_PROC_REVERTCOMMIT, r1->m_CommitHash.ToString());
83 progress.FormatNonPathLine(2, L"%s", (LPCTSTR)r1->GetSubject());
84 progress.SetProgress(i, this->GetSelectedCount());
86 ++i;
88 if(r1->m_CommitHash.IsEmpty())
89 continue;
91 if (g_Git.GitRevert(parent, r1->m_CommitHash))
93 CString str;
94 str.LoadString(IDS_SVNACTION_FAILEDREVERT);
95 str = g_Git.GetGitLastErr(str, CGit::GIT_CMD_REVERT);
96 if( GetSelectedCount() == 1)
97 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), str, L"TortoiseGit", MB_OK | MB_ICONERROR);
98 else if (CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), str, L"TortoiseGit", 2, IDI_ERROR, CString(MAKEINTRESOURCE(IDS_SKIPBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
99 return ret;
101 else
102 ret =0;
104 if (progress.HasUserCancelled())
105 break;
107 return ret;
109 int CGitLogList::CherryPickFrom(CString from, CString to)
111 CLogDataVector logs(&m_LogCache);
112 CString range;
113 range.Format(L"%s..%s", (LPCTSTR)from, (LPCTSTR)to);
114 if (logs.ParserFromLog(nullptr, 0, 0, &range))
115 return -1;
117 if (logs.empty())
118 return 0;
120 CSysProgressDlg progress;
121 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_CHERRYPICK)));
122 progress.SetTime(true);
123 progress.ShowModeless(this);
125 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
127 for (int i = (int)logs.size() - 1; i >= 0; i--)
129 if (progress.IsVisible())
131 progress.FormatNonPathLine(1, IDS_PROC_PICK, logs.GetGitRevAt(i).m_CommitHash.ToString());
132 progress.FormatNonPathLine(2, L"%s", (LPCTSTR)logs.GetGitRevAt(i).GetSubject());
133 progress.SetProgress64(logs.size() - i, logs.size());
135 if (progress.HasUserCancelled())
136 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_USERCANCELLED))));
137 CString cmd,out;
138 cmd.Format(L"git.exe cherry-pick %s", (LPCTSTR)logs.GetGitRevAt(i).m_CommitHash.ToString());
139 if(g_Git.Run(cmd,&out,CP_UTF8))
140 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_CHERRYPICKFAILED)) + L":\r\n\r\n" + out));
143 return 0;
146 int CGitLogList::DeleteRef(const CString& ref)
148 CString shortname;
149 if (CGit::GetShortName(ref, shortname, L"refs/remotes/"))
151 CString msg;
152 msg.Format(IDS_PROC_DELETEREMOTEBRANCH, (LPCTSTR)ref);
153 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)));
154 if (result == 1)
156 CString remoteName = shortname.Left(shortname.Find(L'/'));
157 shortname = shortname.Mid(shortname.Find(L'/') + 1);
158 if (CAppUtils::IsSSHPutty())
159 CAppUtils::LaunchPAgent(nullptr, &remoteName);
161 CSysProgressDlg sysProgressDlg;
162 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
163 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
164 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
165 sysProgressDlg.SetShowProgressBar(false);
166 sysProgressDlg.ShowModal(this, true);
167 STRING_VECTOR list;
168 list.push_back(L"refs/heads/" + shortname);
169 if (g_Git.DeleteRemoteRefs(remoteName, list))
170 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), g_Git.GetGitLastErr(L"Could not delete remote ref.", CGit::GIT_CMD_PUSH), L"TortoiseGit", MB_OK | MB_ICONERROR);
171 sysProgressDlg.Stop();
172 return TRUE;
174 else if (result == 2)
176 if (g_Git.DeleteRef(ref))
178 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), L"TortoiseGit", MB_OK | MB_ICONERROR);
179 return FALSE;
181 return TRUE;
183 return FALSE;
185 else if (CGit::GetShortName(ref, shortname, L"refs/stash"))
187 CString err;
188 std::vector<GitRevLoglist> stashList;
189 size_t count = !GitRevLoglist::GetRefLog(ref, stashList, err) ? stashList.size() : 0;
190 CString msg;
191 msg.Format(IDS_PROC_DELETEALLSTASH, count);
192 int choose = CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), msg, L"TortoiseGit", 3, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_DROPONESTASH)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON)));
193 if (choose == 1)
195 CString out;
196 if (g_Git.Run(L"git.exe stash clear", &out, CP_UTF8))
197 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), out, L"TortoiseGit", MB_OK | MB_ICONERROR);
198 return TRUE;
200 else if (choose == 2)
202 CString out;
203 if (g_Git.Run(L"git.exe stash drop refs/stash@{0}", &out, CP_UTF8))
204 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), out, L"TortoiseGit", MB_OK | MB_ICONERROR);
205 return TRUE;
207 return FALSE;
210 CString msg;
211 msg.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)ref);
212 if (CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), msg, L"TortoiseGit", 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
214 if (g_Git.DeleteRef(ref))
216 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), L"TortoiseGit", MB_OK | MB_ICONERROR);
217 return FALSE;
219 return TRUE;
221 return FALSE;
224 void CGitLogList::ContextMenuAction(int cmd,int FirstSelect, int LastSelect, CMenu *popmenu)
226 POSITION pos = GetFirstSelectedItemPosition();
227 int indexNext = GetNextSelectedItem(pos);
228 if (indexNext < 0)
229 return;
231 GitRevLoglist* pSelLogEntry = m_arShownList.SafeGetAt(indexNext);
233 bool bShiftPressed = !!(GetAsyncKeyState(VK_SHIFT) & 0x8000);
235 theApp.DoWaitCursor(1);
236 switch (cmd&0xFFFF)
238 case ID_COMMIT:
240 CTGitPathList pathlist;
241 CTGitPathList selectedlist;
242 pathlist.AddPath(this->m_Path);
243 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(L"Software\\TortoiseGit\\SelectFilesForCommit", TRUE));
244 CString str;
245 CAppUtils::Commit(CString(),false,str,
246 pathlist,selectedlist,bSelectFilesForCommit);
247 //this->Refresh();
248 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH,0);
250 break;
251 case ID_MERGE_ABORT:
253 if (CAppUtils::MergeAbort())
254 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH, 0);
256 break;
257 case ID_GNUDIFF1: // compare with WC, unified
259 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
260 bool bMerge = false, bCombine = false;
261 CString hash2;
262 if(!r1->m_CommitHash.IsEmpty())
264 CString merge;
265 cmd >>= 16;
266 if( (cmd&0xFFFF) == 0xFFFF)
267 bMerge = true;
268 else if((cmd&0xFFFF) == 0xFFFE)
269 bCombine = true;
270 else if ((cmd & 0xFFFF) == 0xFFFD)
272 CString tempfile = GetTempFile();
273 CString gitcmd = L"git.exe diff-tree --cc " + r1->m_CommitHash.ToString();
274 CString lastErr;
275 if (g_Git.RunLogFile(gitcmd, tempfile, &lastErr))
277 MessageBox(lastErr, L"TortoiseGit", MB_ICONERROR);
278 break;
283 CStdioFile file(tempfile, CFile::typeText | CFile::modeRead | CFile::shareDenyWrite);
284 CString strLine;
285 bool isHash = file.ReadString(strLine) && r1->m_CommitHash.ToString() == strLine;
286 bool more = isHash && file.ReadString(strLine) && !strLine.IsEmpty();
287 if (!more)
289 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_NOCHANGEAFTERMERGE, IDS_APPNAME, MB_OK);
290 break;
293 catch (CFileException* e)
295 e->Delete();
298 CAppUtils::StartUnifiedDiffViewer(tempfile, r1->m_CommitHash.ToString());
299 break;
301 else
303 if (cmd == 0)
304 cmd = 1;
305 if ((size_t)cmd > r1->m_ParentHash.size())
307 CString str;
308 str.Format(IDS_PROC_NOPARENT, cmd);
309 MessageBox(str, L"TortoiseGit", MB_OK | MB_ICONERROR);
310 return;
312 else
314 if(cmd>0)
315 hash2 = r1->m_ParentHash[cmd-1].ToString();
318 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), hash2, CTGitPath(), r1->m_CommitHash.ToString(), bShiftPressed, false, false, bMerge, bCombine);
320 else
321 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), L"HEAD", CTGitPath(), GitRev::GetWorkingCopy(), bShiftPressed, false, false, bMerge, bCombine);
323 break;
325 case ID_GNUDIFF2: // compare two revisions, unified
327 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
328 GitRev* r2 = m_arShownList.SafeGetAt(LastSelect);
329 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), r2->m_CommitHash.ToString(), CTGitPath(), r1->m_CommitHash.ToString(), bShiftPressed);
331 break;
333 case ID_COMPARETWO: // compare two revisions
335 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
336 GitRev* r2 = m_arShownList.SafeGetAt(LastSelect);
337 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
338 CGitDiff::DiffCommit(m_Path, r1, r2, bShiftPressed);
339 else
341 CString path1 = m_Path.GetGitPathString();
342 // start with 1 (0 = working copy changes)
343 for (int i = m_bShowWC ? 1 : 0; i < FirstSelect; ++i)
345 GitRevLoglist* first = m_arShownList.SafeGetAt(i);
346 CTGitPathList list = first->GetFiles(nullptr);
347 const CTGitPath* file = list.LookForGitPath(path1);
348 if (file && !file->GetGitOldPathString().IsEmpty())
349 path1 = file->GetGitOldPathString();
351 CString path2 = path1;
352 for (int i = FirstSelect; i < LastSelect; ++i)
354 GitRevLoglist* first = m_arShownList.SafeGetAt(i);
355 CTGitPathList list = first->GetFiles(nullptr);
356 const CTGitPath* file = list.LookForGitPath(path2);
357 if (file && !file->GetGitOldPathString().IsEmpty())
358 path2 = file->GetGitOldPathString();
360 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), r1, r2, bShiftPressed);
364 break;
366 case ID_COMPARE: // compare revision with WC
368 GitRevLoglist* r1 = &m_wcRev;
369 GitRevLoglist* r2 = pSelLogEntry;
371 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
372 CGitDiff::DiffCommit(m_Path, r1, r2, bShiftPressed);
373 else
375 CString path1 = m_Path.GetGitPathString();
376 // start with 1 (0 = working copy changes)
377 for (int i = m_bShowWC ? 1 : 0; i < FirstSelect; ++i)
379 GitRevLoglist* first = m_arShownList.SafeGetAt(i);
380 CTGitPathList list = first->GetFiles(nullptr);
381 const CTGitPath* file = list.LookForGitPath(path1);
382 if (file && !file->GetGitOldPathString().IsEmpty())
383 path1 = file->GetGitOldPathString();
385 CGitDiff::DiffCommit(m_Path, CTGitPath(path1), r1, r2, bShiftPressed);
388 //user clicked on the menu item "compare with working copy"
389 //if (PromptShown())
391 // GitDiff diff(this, m_hWnd, true);
392 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
393 // diff.SetHEADPeg(m_LogRevision);
394 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
396 //else
397 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
399 break;
401 case ID_COMPAREWITHPREVIOUS:
403 if (pSelLogEntry->m_ParentHash.empty())
405 if (pSelLogEntry->GetParentFromHash(pSelLogEntry->m_CommitHash))
406 MessageBox(pSelLogEntry->GetLastErr(), L"TortoiseGit", MB_ICONERROR);
409 if (!pSelLogEntry->m_ParentHash.empty())
410 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
412 cmd>>=16;
413 cmd&=0xFFFF;
415 if(cmd == 0)
416 cmd=1;
418 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
419 CGitDiff::DiffCommit(m_Path, pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString(), bShiftPressed);
420 else
422 CString path1 = m_Path.GetGitPathString();
423 // start with 1 (0 = working copy changes)
424 for (int i = m_bShowWC ? 1 : 0; i < indexNext; ++i)
426 GitRevLoglist* first = m_arShownList.SafeGetAt(i);
427 CTGitPathList list = first->GetFiles(nullptr);
428 const CTGitPath* file = list.LookForGitPath(path1);
429 if (file && !file->GetGitOldPathString().IsEmpty())
430 path1 = file->GetGitOldPathString();
432 CString path2 = path1;
433 GitRevLoglist* first = m_arShownList.SafeGetAt(indexNext);
434 CTGitPathList list = first->GetFiles(nullptr);
435 const CTGitPath* file = list.LookForGitPath(path2);
436 if (file && !file->GetGitOldPathString().IsEmpty())
437 path2 = file->GetGitOldPathString();
439 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString(), bShiftPressed);
442 else
444 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_PROC_NOPREVIOUSVERSION, IDS_APPNAME, MB_OK);
446 //if (PromptShown())
448 // GitDiff diff(this, m_hWnd, true);
449 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
450 // diff.SetHEADPeg(m_LogRevision);
451 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
453 //else
454 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
456 break;
457 case ID_COMPARETWOCOMMITCHANGES:
459 auto pFirstEntry = m_arShownList.SafeGetAt(FirstSelect);
460 auto pLastEntry = m_arShownList.SafeGetAt(LastSelect);
461 CString patch1 = GetTempFile();
462 CString patch2 = GetTempFile();
463 CString err;
464 if (g_Git.RunLogFile(L"git.exe format-patch --stdout " + pFirstEntry->m_CommitHash.ToString() + L"~1.." + pFirstEntry->m_CommitHash.ToString() + L"", patch1, &err))
466 MessageBox(L"Could not generate patch for commit " + pFirstEntry->m_CommitHash.ToString() + L".\n" + err, L"TortoiseGit", MB_ICONERROR);
467 break;
469 if (g_Git.RunLogFile(L"git.exe format-patch --stdout " + pLastEntry->m_CommitHash.ToString() + L"~1.." + pLastEntry->m_CommitHash.ToString() + L"", patch2, &err))
471 MessageBox(L"Could not generate patch for commit " + pLastEntry->m_CommitHash.ToString() + L".\n" + err, L"TortoiseGit", MB_ICONERROR);
472 break;
474 CAppUtils::DiffFlags flags;
475 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);
477 break;
478 case ID_LOG_VIEWRANGE:
479 case ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE:
481 GitRev* pLastEntry = m_arShownList.SafeGetAt(LastSelect);
483 CString sep = L"..";
484 if ((cmd & 0xFFFF) == ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE)
485 sep = L"...";
487 CString cmdline;
488 cmdline.Format(L"/command:log /path:\"%s\" /range:\"%s%s%s\"",
489 (LPCTSTR)g_Git.CombinePath(m_Path), (LPCTSTR)pLastEntry->m_CommitHash.ToString(), (LPCTSTR)sep, (LPCTSTR)pSelLogEntry->m_CommitHash.ToString());
490 CAppUtils::RunTortoiseGitProc(cmdline);
492 break;
493 case ID_COPYCLIPBOARD:
495 CopySelectionToClipBoard();
497 break;
498 case ID_COPYCLIPBOARDMESSAGES:
500 if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0)
501 CopySelectionToClipBoard(ID_COPY_SUBJECT);
502 else
503 CopySelectionToClipBoard(ID_COPY_MESSAGE);
505 break;
506 case ID_COPYHASH:
508 CopySelectionToClipBoard(ID_COPY_HASH);
510 break;
511 case ID_EXPORT:
513 CString str=pSelLogEntry->m_CommitHash.ToString();
514 // try to get the tag
515 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], L"refs/tags/", str);
516 CAppUtils::Export(&str, &m_Path);
518 break;
519 case ID_CREATE_BRANCH:
520 case ID_CREATE_TAG:
522 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd & 0xFFFF) : nullptr;
523 CString str = pSelLogEntry->m_CommitHash.ToString();
524 if (branch)
525 str = *branch;
526 else // try to guess remote branch in order to enable tracking
527 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], L"refs/remotes/", str);
529 CAppUtils::CreateBranchTag((cmd&0xFFFF) == ID_CREATE_TAG, &str);
530 ReloadHashMap();
531 if (m_pFindDialog)
532 m_pFindDialog->RefreshList();
533 Invalidate();
534 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
536 break;
537 case ID_SWITCHTOREV:
539 CString str = pSelLogEntry->m_CommitHash.ToString();
540 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd & 0xFFFF) : nullptr;
541 if (branch)
542 str = *branch;
543 else // try to guess remote branch in order to recommend good branch name and tracking
544 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], L"refs/remotes/", str);
546 CAppUtils::Switch(str);
548 ReloadHashMap();
549 Invalidate();
550 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
551 break;
552 case ID_SWITCHBRANCH:
553 if(popmenu)
555 const CString* branch = (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
556 if(branch)
558 CString name = *branch;
559 CGit::GetShortName(*branch, name, L"refs/heads/");
560 CAppUtils::PerformSwitch(name);
562 ReloadHashMap();
563 Invalidate();
564 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
566 break;
567 case ID_RESET:
569 CString str = pSelLogEntry->m_CommitHash.ToString();
570 if (CAppUtils::GitReset(&str))
572 ResetWcRev(true);
573 ReloadHashMap();
574 Invalidate();
577 break;
578 case ID_REBASE_PICK:
579 SetSelectedRebaseAction(LOGACTIONS_REBASE_PICK);
580 break;
581 case ID_REBASE_EDIT:
582 SetSelectedRebaseAction(LOGACTIONS_REBASE_EDIT);
583 break;
584 case ID_REBASE_SQUASH:
585 SetSelectedRebaseAction(LOGACTIONS_REBASE_SQUASH);
586 break;
587 case ID_REBASE_SKIP:
588 SetSelectedRebaseAction(LOGACTIONS_REBASE_SKIP);
589 break;
590 case ID_COMBINE_COMMIT:
592 CString head;
593 CGitHash headhash;
594 CGitHash hashFirst,hashLast;
596 int headindex=GetHeadIndex();
597 if(headindex>=0) //incase show all branch, head is not the first commits.
599 head.Format(L"HEAD~%d", FirstSelect - headindex);
600 if (g_Git.GetHash(hashFirst, head))
602 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of first selected revision."), L"TortoiseGit", MB_ICONERROR);
603 break;
606 head.Format(L"HEAD~%d", LastSelect - headindex);
607 if (g_Git.GetHash(hashLast, head))
609 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of last selected revision."), L"TortoiseGit", MB_ICONERROR);
610 break;
614 GitRev* pFirstEntry = m_arShownList.SafeGetAt(FirstSelect);
615 GitRev* pLastEntry = m_arShownList.SafeGetAt(LastSelect);
616 if(pFirstEntry->m_CommitHash != hashFirst || pLastEntry->m_CommitHash != hashLast)
618 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_PROC_CANNOTCOMBINE, IDS_APPNAME, MB_OK | MB_ICONEXCLAMATION);
619 break;
622 GitRev lastRevision;
623 if (lastRevision.GetParentFromHash(hashLast))
625 MessageBox(lastRevision.GetLastErr(), L"TortoiseGit", MB_ICONERROR);
626 break;
629 if (g_Git.GetHash(headhash, L"HEAD"))
631 MessageBox(g_Git.GetGitLastErr(L"Could not get HEAD hash."), L"TortoiseGit", MB_ICONERROR);
632 break;
635 if(!g_Git.CheckCleanWorkTree())
637 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK | MB_ICONEXCLAMATION);
638 break;
640 CString sCmd, out;
642 //Use throw to abort this process (reset back to original HEAD)
645 sCmd.Format(L"git.exe reset --hard %s --", (LPCTSTR)pFirstEntry->m_CommitHash.ToString());
646 if(g_Git.Run(sCmd, &out, CP_UTF8))
648 MessageBox(out, L"TortoiseGit", MB_OK | MB_ICONERROR);
649 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1)) + L"\r\n\r\n" + out));
651 sCmd.Format(L"git.exe reset --soft %s --", (LPCTSTR)hashLast.ToString());
652 if(g_Git.Run(sCmd, &out, CP_UTF8))
654 MessageBox(out, L"TortoiseGit", MB_OK | MB_ICONERROR);
655 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2)) + L"\r\n\r\n"+out));
658 CCommitDlg dlg;
659 for (int i = FirstSelect; i <= LastSelect; ++i)
661 GitRev* pRev = m_arShownList.SafeGetAt(i);
662 dlg.m_sLogMessage += pRev->GetSubject() + L'\n' + pRev->GetBody();
663 dlg.m_sLogMessage += L'\n';
665 dlg.m_bWholeProject=true;
666 dlg.m_bSelectFilesForCommit = true;
667 dlg.m_bForceCommitAmend=true;
668 int squashDate = (int)CRegDWORD(L"Software\\TortoiseGit\\SquashDate", 0);
669 if (squashDate == 1)
670 dlg.SetTime(m_arShownList.SafeGetAt(FirstSelect)->GetAuthorDate());
671 else if (squashDate == 2)
672 dlg.SetTime(CTime::GetCurrentTime());
673 else
674 dlg.SetTime(m_arShownList.SafeGetAt(LastSelect)->GetAuthorDate());
675 CTGitPathList gpl;
676 gpl.AddPath(CTGitPath(g_Git.m_CurrentDir));
677 dlg.m_pathList = gpl;
678 if (lastRevision.ParentsCount() != 1)
680 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);
681 dlg.m_bAmendDiffToLastCommit = TRUE;
683 else
684 dlg.m_bAmendDiffToLastCommit = FALSE;
685 dlg.m_bNoPostActions=true;
686 dlg.m_AmendStr=dlg.m_sLogMessage;
688 if (dlg.DoModal() == IDOK)
690 if(pFirstEntry->m_CommitHash!=headhash)
692 if(CherryPickFrom(pFirstEntry->m_CommitHash.ToString(),headhash))
694 CString msg;
695 msg.Format(L"Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n");
696 throw std::exception(CUnicodeUtils::GetUTF8(msg));
700 else
701 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_USERCANCELLED))));
703 catch(std::exception& e)
705 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), CUnicodeUtils::GetUnicode(CStringA(e.what())), L"TortoiseGit", MB_OK | MB_ICONERROR);
706 sCmd.Format(L"git.exe reset --hard %s --", (LPCTSTR)headhash.ToString());
707 out.Empty();
708 if(g_Git.Run(sCmd, &out, CP_UTF8))
709 MessageBox(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD)) + L"\r\n\r\n" + out, L"TortoiseGit", MB_OK | MB_ICONERROR);
711 Refresh();
713 break;
715 case ID_CHERRY_PICK:
717 if (m_bThreadRunning)
719 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_PROC_LOG_ONLYONCE, IDS_APPNAME, MB_ICONEXCLAMATION);
720 break;
722 CRebaseDlg dlg;
723 dlg.m_IsCherryPick = TRUE;
724 dlg.m_Upstream = this->m_CurrentBranch;
725 POSITION pos2 = GetFirstSelectedItemPosition();
726 while(pos2)
728 int indexNext2 = GetNextSelectedItem(pos2);
729 dlg.m_CommitList.m_logEntries.push_back(m_arShownList.SafeGetAt(indexNext2)->m_CommitHash);
730 dlg.m_CommitList.m_LogCache.m_HashMap[m_arShownList.SafeGetAt(indexNext2)->m_CommitHash] = *m_arShownList.SafeGetAt(indexNext2);
731 dlg.m_CommitList.m_logEntries.GetGitRevAt(dlg.m_CommitList.m_logEntries.size() - 1).GetRebaseAction() |= LOGACTIONS_REBASE_PICK;
734 if(dlg.DoModal() == IDOK)
736 Refresh();
739 break;
740 case ID_REBASE_TO_VERSION:
742 if (m_bThreadRunning)
744 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_PROC_LOG_ONLYONCE, IDS_APPNAME, MB_ICONEXCLAMATION);
745 break;
747 CRebaseDlg dlg;
748 auto refList = m_HashMap[pSelLogEntry->m_CommitHash];
749 dlg.m_Upstream = refList.empty() ? pSelLogEntry->m_CommitHash.ToString() : refList.front();
750 for (const auto& ref : refList)
751 if (CGit::GetShortName(ref, dlg.m_Upstream, L"refs/heads/"))
752 break;
754 if(dlg.DoModal() == IDOK)
756 Refresh();
760 break;
762 case ID_STASH_SAVE:
763 if (CAppUtils::StashSave())
764 Refresh();
765 break;
767 case ID_STASH_POP:
768 if (CAppUtils::StashPop())
769 Refresh();
770 break;
772 case ID_STASH_LIST:
773 CAppUtils::RunTortoiseGitProc(L"/command:reflog /ref:refs/stash");
774 break;
776 case ID_REFLOG_STASH_APPLY:
777 CAppUtils::StashApply(pSelLogEntry->m_Ref);
778 break;
780 case ID_REFLOG_DEL:
782 CString str;
783 if (GetSelectedCount() > 1)
784 str.Format(IDS_PROC_DELETENREFS, GetSelectedCount());
785 else
786 str.Format(IDS_PROC_DELETEREF, (LPCTSTR)pSelLogEntry->m_Ref);
788 if (CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), str, L"TortoiseGit", 1, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
789 return;
791 std::vector<CString> refsToDelete;
792 POSITION pos2 = GetFirstSelectedItemPosition();
793 while (pos2)
795 CString ref = m_arShownList.SafeGetAt(GetNextSelectedItem(pos2))->m_Ref;
796 if (CStringUtils::StartsWith(ref, L"refs/"))
797 ref = ref.Mid(5);
798 int refpos = ref.ReverseFind(L'{');
799 if (refpos > 0 && ref.Mid(refpos - 1, 2) != L"@{")
800 ref = ref.Left(refpos) + L'@'+ ref.Mid(refpos);
801 refsToDelete.push_back(ref);
804 for (auto revIt = refsToDelete.crbegin(); revIt != refsToDelete.crend(); ++revIt)
806 CString ref = *revIt;
807 CString sCmd, out;
808 if (CStringUtils::StartsWith(ref, L"stash"))
809 sCmd.Format(L"git.exe stash drop %s", (LPCTSTR)ref);
810 else
811 sCmd.Format(L"git.exe reflog delete %s", (LPCTSTR)ref);
813 if (g_Git.Run(sCmd, &out, CP_UTF8))
814 MessageBox(out, L"TortoiseGit", MB_OK | MB_ICONERROR);
816 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
819 break;
820 case ID_LOG:
822 CString sCmd = L"/command:log";
823 sCmd += L" /path:\"" + g_Git.m_CurrentDir + L"\" ";
824 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
825 sCmd += L" /endrev:" + r1->m_CommitHash.ToString();
826 CAppUtils::RunTortoiseGitProc(sCmd);
828 break;
829 case ID_CREATE_PATCH:
831 int select=this->GetSelectedCount();
832 CString sCmd = L"/command:formatpatch";
833 sCmd += L" /path:\"" + g_Git.m_CurrentDir + L"\" ";
835 GitRev* r1 = m_arShownList.SafeGetAt(FirstSelect);
836 GitRev* r2 = nullptr;
837 if(select == 1)
839 sCmd += L" /startrev:" + r1->m_CommitHash.ToString();
841 else
843 r2 = m_arShownList.SafeGetAt(LastSelect);
844 if( this->m_IsOldFirst )
846 sCmd += L" /startrev:" + r1->m_CommitHash.ToString() + L"~1";
847 sCmd += L" /endrev:" + r2->m_CommitHash.ToString();
850 else
852 sCmd += L" /startrev:" + r2->m_CommitHash.ToString() + L"~1";
853 sCmd += L" /endrev:" + r1->m_CommitHash.ToString();
858 CAppUtils::RunTortoiseGitProc(sCmd);
860 break;
861 case ID_BISECTSTART:
863 GitRev* first = m_arShownList.SafeGetAt(FirstSelect);
864 GitRev* last = m_arShownList.SafeGetAt(LastSelect);
865 ASSERT(first && last);
867 CString firstBad = first->m_CommitHash.ToString();
868 if (!m_HashMap[first->m_CommitHash].empty())
869 firstBad = m_HashMap[first->m_CommitHash].at(0);
870 CString lastGood = last->m_CommitHash.ToString();
871 if (!m_HashMap[last->m_CommitHash].empty())
872 lastGood = m_HashMap[last->m_CommitHash].at(0);
874 if (CAppUtils::BisectStart(lastGood, firstBad))
875 Refresh();
877 break;
878 case ID_BISECTGOOD:
880 GitRev* first = m_arShownList.SafeGetAt(FirstSelect);
881 if (CAppUtils::BisectOperation(L"good", !first->m_CommitHash.IsEmpty() ? first->m_CommitHash.ToString() : L""))
882 Refresh();
884 break;
885 case ID_BISECTBAD:
887 GitRev* first = m_arShownList.SafeGetAt(FirstSelect);
888 if (CAppUtils::BisectOperation(L"bad", !first->m_CommitHash.IsEmpty() ? first->m_CommitHash.ToString() : L""))
889 Refresh();
891 break;
892 case ID_BISECTSKIP:
894 CString refs;
895 POSITION pos2 = GetFirstSelectedItemPosition();
896 while (pos2)
898 int indexNext2 = GetNextSelectedItem(pos2);
899 auto rev = m_arShownList.SafeGetAt(indexNext2);
900 if (!rev->m_CommitHash.IsEmpty())
901 refs.AppendFormat(L" %s", (LPCTSTR)rev->m_CommitHash.ToString());
903 if (CAppUtils::BisectOperation(L"skip", refs))
904 Refresh();
906 break;
907 case ID_BISECTRESET:
909 if (CAppUtils::BisectOperation(L"reset"))
910 Refresh();
912 break;
913 case ID_REPOBROWSE:
915 CString sCmd;
916 sCmd.Format(L"/command:repobrowser /path:\"%s\" /rev:%s", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)pSelLogEntry->m_CommitHash.ToString());
917 CAppUtils::RunTortoiseGitProc(sCmd);
919 break;
920 case ID_PUSH:
922 CString guessAssociatedBranch = pSelLogEntry->m_CommitHash;
923 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd) : nullptr;
924 if (branch)
925 guessAssociatedBranch = *branch;
926 else
927 GetFirstEntryStartingWith(m_HashMap[pSelLogEntry->m_CommitHash], L"refs/heads/", guessAssociatedBranch);
929 if (CAppUtils::Push(guessAssociatedBranch))
930 Refresh();
932 break;
933 case ID_PULL:
935 if (CAppUtils::Pull())
936 Refresh();
938 break;
939 case ID_FETCH:
941 if (CAppUtils::Fetch())
942 Refresh();
944 break;
945 case ID_SVNDCOMMIT:
947 if (CAppUtils::SVNDCommit())
948 Refresh();
950 break;
951 case ID_CLEANUP:
953 CString sCmd;
954 sCmd.Format(L"/command:cleanup /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
955 CAppUtils::RunTortoiseGitProc(sCmd);
957 break;
958 case ID_SUBMODULE_UPDATE:
960 CString sCmd;
961 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
962 CAppUtils::RunTortoiseGitProc(sCmd);
964 break;
965 case ID_SHOWBRANCHES:
967 CCommitIsOnRefsDlg* dlg = new CCommitIsOnRefsDlg(this);
968 dlg->m_Rev = (LPCTSTR)pSelLogEntry->m_CommitHash.ToString();
969 dlg->Create(this);
970 // pointer won't leak as it is destroyed within PostNcDestroy()
972 break;
973 case ID_DELETE:
975 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd) : nullptr;
976 if (!branch)
978 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_ERROR_NOREF, IDS_APPNAME, MB_OK | MB_ICONERROR);
979 return;
981 CString shortname;
982 if (branch == (CString*)MAKEINTRESOURCE(IDS_ALL))
984 CString currentBranch = L"refs/heads/" + m_CurrentBranch;
985 bool nothingDeleted = true;
986 for (const auto& ref : m_HashMap[pSelLogEntry->m_CommitHash])
988 if (ref == currentBranch)
989 continue;
990 if (!DeleteRef(ref))
991 break;
992 nothingDeleted = false;
994 if (nothingDeleted)
995 return;
997 else if (!DeleteRef(*branch))
998 return;
999 this->ReloadHashMap();
1000 if (m_pFindDialog)
1001 m_pFindDialog->RefreshList();
1002 CRect rect;
1003 this->GetItemRect(FirstSelect,&rect,LVIR_BOUNDS);
1004 this->InvalidateRect(rect);
1006 break;
1008 case ID_FINDENTRY:
1010 m_nSearchIndex = GetSelectionMark();
1011 if (m_nSearchIndex < 0)
1012 m_nSearchIndex = 0;
1013 if (m_pFindDialog)
1014 break;
1015 else
1017 m_pFindDialog = new CFindDlg();
1018 m_pFindDialog->Create(this);
1021 break;
1022 case ID_MERGEREV:
1024 CString str = pSelLogEntry->m_CommitHash.ToString();
1025 const CString* branch = popmenu ? (const CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd & 0xFFFF) : nullptr;
1026 if (branch)
1027 str = *branch;
1028 else if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
1029 str = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
1030 // we need an URL to complete this command, so error out if we can't get an URL
1031 if(CAppUtils::Merge(&str))
1033 this->Refresh();
1036 break;
1037 case ID_REVERTREV:
1039 int parent = 0;
1040 if (GetSelectedCount() == 1)
1042 parent = cmd >> 16;
1043 if ((size_t)parent > pSelLogEntry->m_ParentHash.size())
1045 CString str;
1046 str.Format(IDS_PROC_NOPARENT, parent);
1047 MessageBox(str, L"TortoiseGit", MB_OK | MB_ICONERROR);
1048 return;
1052 if (!this->RevertSelectedCommits(parent))
1054 if (CMessageBox::Show(m_hWnd, IDS_REVREVERTED, IDS_APPNAME, 1, IDI_QUESTION, IDS_OKBUTTON, IDS_COMMITBUTTON) == 2)
1056 CTGitPathList pathlist;
1057 CTGitPathList selectedlist;
1058 pathlist.AddPath(this->m_Path);
1059 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(L"Software\\TortoiseGit\\SelectFilesForCommit", TRUE));
1060 CString str;
1061 CAppUtils::Commit(CString(), false, str, pathlist, selectedlist, bSelectFilesForCommit);
1063 this->Refresh();
1066 break;
1067 case ID_EDITNOTE:
1069 CAppUtils::EditNote(pSelLogEntry, &m_ProjectProperties);
1070 this->SetItemState(FirstSelect, 0, LVIS_SELECTED);
1071 this->SetItemState(FirstSelect, LVIS_SELECTED, LVIS_SELECTED);
1073 break;
1074 default:
1075 //CMessageBox::Show(nullptr, L"Have not implemented", L"TortoiseGit", MB_OK);
1076 break;
1077 #if 0
1079 case ID_BLAMECOMPARE:
1081 //user clicked on the menu item "compare with working copy"
1082 //now first get the revision which is selected
1083 if (PromptShown())
1085 GitDiff diff(this, this->m_hWnd, true);
1086 diff.SetHEADPeg(m_LogRevision);
1087 diff.ShowCompare(m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), false, true);
1089 else
1090 CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), m_LogRevision, false, false, true);
1092 break;
1093 case ID_BLAMEWITHPREVIOUS:
1095 //user clicked on the menu item "Compare and Blame with previous revision"
1096 if (PromptShown())
1098 GitDiff diff(this, this->m_hWnd, true);
1099 diff.SetHEADPeg(m_LogRevision);
1100 diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), false, true);
1102 else
1103 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
1105 break;
1107 case ID_OPENWITH:
1108 bOpenWith = true;
1109 case ID_OPEN:
1111 CProgressDlg progDlg;
1112 progDlg.SetTitle(IDS_APPNAME);
1113 progDlg.SetAnimation(IDR_DOWNLOAD);
1114 CString sInfoLine;
1115 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, m_path.GetWinPath(), (LPCTSTR)revSelected.ToString());
1116 progDlg.SetLine(1, sInfoLine, true);
1117 SetAndClearProgressInfo(&progDlg);
1118 progDlg.ShowModeless(m_hWnd);
1119 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, m_path, revSelected);
1120 bool bSuccess = true;
1121 if (!Cat(m_path, GitRev(GitRev::REV_HEAD), revSelected, tempfile))
1123 bSuccess = false;
1124 // try again, but with the selected revision as the peg revision
1125 if (!Cat(m_path, revSelected, revSelected, tempfile))
1127 progDlg.Stop();
1128 SetAndClearProgressInfo(nullptr);
1129 CMessageBox::Show(GetSafeHwnd(), GetLastErrorMessage(), L"TortoiseGit", MB_ICONERROR);
1130 EnableOKButton();
1131 break;
1133 bSuccess = true;
1135 if (bSuccess)
1137 progDlg.Stop();
1138 SetAndClearProgressInfo(nullptr);
1139 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1140 if (!bOpenWith)
1141 CAppUtils::ShellOpen(tempfile.GetWinPath(), GetSafeHwnd());
1142 else
1143 CAppUtils::ShowOpenWithDialog(tempfile.GetWinPathString(), GetSafeHwnd());
1146 break;
1147 case ID_BLAME:
1149 CBlameDlg dlg;
1150 dlg.EndRev = revSelected;
1151 if (dlg.DoModal() == IDOK)
1153 CBlame blame;
1154 CString tempfile;
1155 CString logfile;
1156 tempfile = blame.BlameToTempFile(m_path, dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, L"", dlg.m_bIncludeMerge, TRUE, TRUE);
1157 if (!tempfile.IsEmpty())
1159 if (dlg.m_bTextView)
1161 //open the default text editor for the result file
1162 CAppUtils::StartTextViewer(tempfile);
1164 else
1166 CString sParams = L"/path:\"" + m_path.GetGitPathString() + L"\" ";
1167 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(m_path.GetFileOrDirectoryName()),sParams))
1169 break;
1173 else
1175 CMessageBox::Show(GetSafeHwnd(), blame.GetLastErrorMessage(), L"TortoiseGit", MB_ICONERROR);
1179 break;
1180 case ID_EXPORT:
1182 CString sCmd;
1183 sCmd.Format(L"%s /command:export /path:\"%s\" /revision:%ld",
1184 (LPCTSTR)(CPathUtils::GetAppDirectory() + L"TortoiseGitProc.exe"),
1185 (LPCTSTR)pathURL, (LONG)revSelected);
1186 CAppUtils::LaunchApplication(sCmd, nullptr, false);
1188 break;
1189 case ID_VIEWREV:
1191 CString url = m_ProjectProperties.sWebViewerRev;
1192 url = GetAbsoluteUrlFromRelativeUrl(url);
1193 url.Replace(L"%REVISION%", revSelected.ToString());
1194 if (!url.IsEmpty())
1195 ShellExecute(GetSafeHwnd(), L"open", url, nullptr, nullptr, SW_SHOWDEFAULT);
1197 break;
1198 case ID_VIEWPATHREV:
1200 CString relurl = pathURL;
1201 CString sRoot = GetRepositoryRoot(CTGitPath(relurl));
1202 relurl = relurl.Mid(sRoot.GetLength());
1203 CString url = m_ProjectProperties.sWebViewerPathRev;
1204 url = GetAbsoluteUrlFromRelativeUrl(url);
1205 url.Replace(L"%REVISION%", revSelected.ToString());
1206 url.Replace(L"%PATH%", relurl);
1207 if (!url.IsEmpty())
1208 ShellExecute(GetSafeHwnd(), L"open", url, nullptr, nullptr, SW_SHOWDEFAULT);
1210 break;
1211 #endif
1213 } // switch (cmd)
1215 theApp.DoWaitCursor(-1);
1218 void CGitLogList::SetSelectedRebaseAction(int action)
1220 POSITION pos = GetFirstSelectedItemPosition();
1221 if (!pos) return;
1222 int index;
1223 while(pos)
1225 index = GetNextSelectedItem(pos);
1226 if (m_arShownList.SafeGetAt(index)->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT | LOGACTIONS_REBASE_DONE) || (index == GetItemCount() - 1 && action == LOGACTIONS_REBASE_SQUASH))
1227 continue;
1228 if (!m_bIsCherryPick && m_arShownList.SafeGetAt(index)->ParentsCount() > 1 && action == LOGACTIONS_REBASE_SQUASH)
1229 continue;
1230 m_arShownList.SafeGetAt(index)->GetRebaseAction() = action;
1231 CRect rect;
1232 this->GetItemRect(index,&rect,LVIR_BOUNDS);
1233 this->InvalidateRect(rect);
1236 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage);
1239 void CGitLogList::SetUnselectedRebaseAction(int action)
1241 POSITION pos = GetFirstSelectedItemPosition();
1242 int index = pos ? GetNextSelectedItem(pos) : -1;
1243 for (int i = 0; i < GetItemCount(); i++)
1245 if (i == index)
1247 index = pos ? GetNextSelectedItem(pos) : -1;
1248 continue;
1251 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))
1252 continue;
1253 m_arShownList.SafeGetAt(i)->GetRebaseAction() = action;
1254 CRect rect;
1255 this->GetItemRect(i, &rect, LVIR_BOUNDS);
1256 this->InvalidateRect(rect);
1259 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage);
1262 void CGitLogList::ShiftSelectedRebaseAction()
1264 POSITION pos = GetFirstSelectedItemPosition();
1265 int index;
1266 while(pos)
1268 index = GetNextSelectedItem(pos);
1269 int* action = &(m_arShownList.SafeGetAt(index))->GetRebaseAction();
1270 switch (*action)
1272 case LOGACTIONS_REBASE_PICK:
1273 *action = LOGACTIONS_REBASE_SKIP;
1274 break;
1275 case LOGACTIONS_REBASE_SKIP:
1276 *action= LOGACTIONS_REBASE_EDIT;
1277 break;
1278 case LOGACTIONS_REBASE_EDIT:
1279 *action = LOGACTIONS_REBASE_SQUASH;
1280 if (index == GetItemCount() - 1 && (m_bIsCherryPick || m_arShownList.SafeGetAt(index)->m_ParentHash.size() == 1))
1281 *action = LOGACTIONS_REBASE_PICK;
1282 break;
1283 case LOGACTIONS_REBASE_SQUASH:
1284 *action= LOGACTIONS_REBASE_PICK;
1285 break;
1287 CRect rect;
1288 this->GetItemRect(index, &rect, LVIR_BOUNDS);
1289 this->InvalidateRect(rect);