Fixed issue #2272: Export Dialog auto select tag if the revision has tag
[TortoiseGit.git] / src / TortoiseProc / GitLogListAction.cpp
blob2cc5f9e8793af5f12f1969293394bed22f9af1bb
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2014 - TortoiseGit
4 // Copyright (C) 2011-2013 - Sven Strickroth <email@cs-ware.de>
5 // Copyright (C) 2005-2007 Marco Costalba
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 2
10 // of the License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software Foundation,
19 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 // GitLogList.cpp : implementation file
23 #include "stdafx.h"
24 #include "TortoiseProc.h"
25 #include "GitLogList.h"
26 #include "GitRev.h"
27 #include "IconMenu.h"
28 #include "cursor.h"
29 #include "GitProgressDlg.h"
30 #include "ProgressDlg.h"
31 #include "SysProgressDlg.h"
32 #include "LogDlg.h"
33 #include "MessageBox.h"
34 #include "registry.h"
35 #include "AppUtils.h"
36 #include "PathUtils.h"
37 #include "StringUtils.h"
38 #include "UnicodeUtils.h"
39 #include "TempFile.h"
40 #include "FileDiffDlg.h"
41 #include "CommitDlg.h"
42 #include "RebaseDlg.h"
43 #include "GitDiff.h"
44 #include "../TGitCache/CacheInterface.h"
46 IMPLEMENT_DYNAMIC(CGitLogList, CHintListCtrl)
48 int CGitLogList::RevertSelectedCommits(int parent)
50 CSysProgressDlg progress;
51 int ret = -1;
53 #if 0
54 if(!g_Git.CheckCleanWorkTree())
56 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
59 #endif
61 if (this->GetSelectedCount() > 1)
63 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_REVERTCOMMIT)));
64 progress.SetAnimation(IDR_MOVEANI);
65 progress.SetTime(true);
66 progress.ShowModeless(this);
69 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
71 POSITION pos = GetFirstSelectedItemPosition();
72 int i=0;
73 while(pos)
75 int index = GetNextSelectedItem(pos);
76 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(index));
78 if (progress.IsVisible())
80 progress.FormatNonPathLine(1, IDS_PROC_REVERTCOMMIT, r1->m_CommitHash.ToString());
81 progress.FormatNonPathLine(2, _T("%s"), r1->GetSubject());
82 progress.SetProgress(i, this->GetSelectedCount());
84 ++i;
86 if(r1->m_CommitHash.IsEmpty())
87 continue;
89 if (g_Git.GitRevert(parent, r1->m_CommitHash))
91 CString str;
92 str.LoadString(IDS_SVNACTION_FAILEDREVERT);
93 str = g_Git.GetGitLastErr(str, CGit::GIT_CMD_REVERT);
94 if( GetSelectedCount() == 1)
95 CMessageBox::Show(NULL, str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
96 else
98 if(CMessageBox::Show(NULL, str, _T("TortoiseGit"),2 , IDI_ERROR, CString(MAKEINTRESOURCE(IDS_SKIPBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
100 return ret;
104 else
106 ret =0;
109 if (progress.HasUserCancelled())
110 break;
112 return ret;
114 int CGitLogList::CherryPickFrom(CString from, CString to)
116 CLogDataVector logs(&m_LogCache);
117 CString range;
118 range.Format(_T("%s..%s"), from, to);
119 if (logs.ParserFromLog(nullptr, -1, 0, &range))
120 return -1;
122 if (logs.empty())
123 return 0;
125 CSysProgressDlg progress;
126 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_CHERRYPICK)));
127 progress.SetAnimation(IDR_MOVEANI);
128 progress.SetTime(true);
129 progress.ShowModeless(this);
131 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
133 for (int i = (int)logs.size() - 1; i >= 0; i--)
135 if (progress.IsVisible())
137 progress.FormatNonPathLine(1, IDS_PROC_PICK, logs.GetGitRevAt(i).m_CommitHash.ToString());
138 progress.FormatNonPathLine(2, _T("%s"), logs.GetGitRevAt(i).GetSubject());
139 progress.SetProgress64(logs.size() - i, logs.size());
141 if (progress.HasUserCancelled())
143 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED))));
145 CString cmd,out;
146 cmd.Format(_T("git.exe cherry-pick %s"),logs.GetGitRevAt(i).m_CommitHash.ToString());
147 out.Empty();
148 if(g_Git.Run(cmd,&out,CP_UTF8))
150 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_CHERRYPICKFAILED)) + _T(":\r\n\r\n") + out));
154 return 0;
157 void CGitLogList::ContextMenuAction(int cmd,int FirstSelect, int LastSelect, CMenu *popmenu)
159 POSITION pos = GetFirstSelectedItemPosition();
160 int indexNext = GetNextSelectedItem(pos);
161 if (indexNext < 0)
162 return;
164 GitRev* pSelLogEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(indexNext));
166 theApp.DoWaitCursor(1);
167 switch (cmd&0xFFFF)
169 case ID_COMMIT:
171 CTGitPathList pathlist;
172 CTGitPathList selectedlist;
173 pathlist.AddPath(this->m_Path);
174 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
175 CString str;
176 CAppUtils::Commit(CString(),false,str,
177 pathlist,selectedlist,bSelectFilesForCommit);
178 //this->Refresh();
179 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH,0);
181 break;
182 case ID_MERGE_ABORT:
184 if (CAppUtils::MergeAbort())
185 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH, 0);
187 break;
188 case ID_GNUDIFF1: // compare with WC, unified
190 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
191 bool bMerge = false, bCombine = false;
192 CString hash2;
193 if(!r1->m_CommitHash.IsEmpty())
195 CString merge;
196 cmd >>= 16;
197 if( (cmd&0xFFFF) == 0xFFFF)
198 bMerge = true;
200 else if((cmd&0xFFFF) == 0xFFFE)
201 bCombine = true;
202 else
204 if(cmd > r1->m_ParentHash.size())
206 CString str;
207 str.Format(IDS_PROC_NOPARENT, cmd);
208 MessageBox(str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
209 return;
211 else
213 if(cmd>0)
214 hash2 = r1->m_ParentHash[cmd-1].ToString();
217 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), hash2, CTGitPath(), r1->m_CommitHash.ToString(), false, false, false, bMerge, bCombine);
219 else
220 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), _T("HEAD"), CTGitPath(), GitRev::GetWorkingCopy(), false, false, false, bMerge, bCombine);
222 break;
224 case ID_GNUDIFF2: // compare two revisions, unified
226 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
227 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
228 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), r2->m_CommitHash.ToString(), CTGitPath(), r1->m_CommitHash.ToString());
230 break;
232 case ID_COMPARETWO: // compare two revisions
234 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
235 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
236 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
237 CGitDiff::DiffCommit(this->m_Path, r1,r2);
238 else
240 CString path1 = m_Path.GetGitPathString();
241 // start with 1 (0 = working copy changes)
242 for (int i = 1; i < FirstSelect; ++i)
244 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
245 CTGitPathList list = first->GetFiles(NULL);
246 CTGitPath * file = list.LookForGitPath(path1);
247 if (file && !file->GetGitOldPathString().IsEmpty())
248 path1 = file->GetGitOldPathString();
250 CString path2 = path1;
251 for (int i = FirstSelect; i < LastSelect; ++i)
253 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
254 CTGitPathList list = first->GetFiles(NULL);
255 CTGitPath * file = list.LookForGitPath(path2);
256 if (file && !file->GetGitOldPathString().IsEmpty())
257 path2 = file->GetGitOldPathString();
259 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), r1, r2);
263 break;
265 case ID_COMPARE: // compare revision with WC
267 GitRev * r1 = &m_wcRev;
268 GitRev * r2 = pSelLogEntry;
270 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
271 CGitDiff::DiffCommit(this->m_Path, r1,r2);
272 else
274 CString path1 = m_Path.GetGitPathString();
275 // start with 1 (0 = working copy changes)
276 for (int i = 1; i < FirstSelect; ++i)
278 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
279 CTGitPathList list = first->GetFiles(NULL);
280 CTGitPath * file = list.LookForGitPath(path1);
281 if (file && !file->GetGitOldPathString().IsEmpty())
282 path1 = file->GetGitOldPathString();
284 CGitDiff::DiffCommit(m_Path, CTGitPath(path1), r1, r2);
287 //user clicked on the menu item "compare with working copy"
288 //if (PromptShown())
290 // GitDiff diff(this, m_hWnd, true);
291 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
292 // diff.SetHEADPeg(m_LogRevision);
293 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
295 //else
296 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
298 break;
300 case ID_COMPAREWITHPREVIOUS:
302 CFileDiffDlg dlg;
304 if (pSelLogEntry->m_ParentHash.empty())
308 pSelLogEntry->GetParentFromHash(pSelLogEntry->m_CommitHash);
310 catch (const char* msg)
312 MessageBox(_T("Could not get parent.\nlibgit reports:\n") + CString(msg), _T("TortoiseGit"), MB_ICONERROR);
316 if (!pSelLogEntry->m_ParentHash.empty())
317 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
319 cmd>>=16;
320 cmd&=0xFFFF;
322 if(cmd == 0)
323 cmd=1;
325 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
326 CGitDiff::DiffCommit(m_Path, pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString());
327 else
329 CString path1 = m_Path.GetGitPathString();
330 // start with 1 (0 = working copy changes)
331 for (int i = 1; i < indexNext; ++i)
333 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
334 CTGitPathList list = first->GetFiles(NULL);
335 CTGitPath * file = list.LookForGitPath(path1);
336 if (file && !file->GetGitOldPathString().IsEmpty())
337 path1 = file->GetGitOldPathString();
339 CString path2 = path1;
340 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(indexNext));
341 CTGitPathList list = first->GetFiles(NULL);
342 CTGitPath * file = list.LookForGitPath(path2);
343 if (file && !file->GetGitOldPathString().IsEmpty())
344 path2 = file->GetGitOldPathString();
346 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString());
349 else
351 CMessageBox::Show(NULL, IDS_PROC_NOPREVIOUSVERSION, IDS_APPNAME, MB_OK);
353 //if (PromptShown())
355 // GitDiff diff(this, m_hWnd, true);
356 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
357 // diff.SetHEADPeg(m_LogRevision);
358 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
360 //else
361 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
363 break;
364 case ID_LOG_VIEWRANGE:
365 case ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE:
367 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(LastSelect));
369 CString sep = _T("..");
370 if ((cmd & 0xFFFF) == ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE)
371 sep = _T("...");
373 CAppUtils::RunTortoiseGitProc(_T("/command:log /range:\"") + pLastEntry->m_CommitHash.ToString() + sep + pSelLogEntry->m_CommitHash.ToString() + _T("\""));
375 break;
376 case ID_COPYCLIPBOARD:
378 CopySelectionToClipBoard();
380 break;
381 case ID_COPYCLIPBOARDMESSAGES:
383 if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0)
384 CopySelectionToClipBoard(ID_COPY_SUBJECT);
385 else
386 CopySelectionToClipBoard(ID_COPY_MESSAGE);
388 break;
389 case ID_COPYHASH:
391 CopySelectionToClipBoard(ID_COPY_HASH);
393 break;
394 case ID_EXPORT:
396 CString str=pSelLogEntry->m_CommitHash.ToString();
397 // try to get the tag
398 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
400 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/tags/")) == 0)
402 str = m_HashMap[pSelLogEntry->m_CommitHash][i];
403 break;
406 CAppUtils::Export(&str, &m_Path);
408 break;
409 case ID_CREATE_BRANCH:
410 case ID_CREATE_TAG:
412 CString str = pSelLogEntry->m_CommitHash.ToString();
413 // try to guess remote branch in order to enable tracking
414 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
416 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/remotes/")) == 0)
418 str = m_HashMap[pSelLogEntry->m_CommitHash][i];
419 break;
422 CAppUtils::CreateBranchTag((cmd&0xFFFF) == ID_CREATE_TAG, &str);
423 ReloadHashMap();
424 Invalidate();
425 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
427 break;
428 case ID_SWITCHTOREV:
430 CString str = pSelLogEntry->m_CommitHash.ToString();
431 // try to guess remote branch in order to recommend good branch name and tracking
432 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
434 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/remotes/")) == 0)
436 str = m_HashMap[pSelLogEntry->m_CommitHash][i];
437 break;
440 CAppUtils::Switch(str);
442 ReloadHashMap();
443 Invalidate();
444 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
445 break;
446 case ID_SWITCHBRANCH:
447 if(popmenu)
449 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
450 if(branch)
452 CString name;
453 if(branch->Find(_T("refs/heads/")) ==0 )
454 name = branch->Mid(11);
455 else
456 name = *branch;
458 CAppUtils::PerformSwitch(name);
460 ReloadHashMap();
461 Invalidate();
462 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
464 break;
465 case ID_RESET:
467 CString str = pSelLogEntry->m_CommitHash.ToString();
468 if (CAppUtils::GitReset(&str))
470 ResetWcRev(true);
471 ReloadHashMap();
472 Invalidate();
475 break;
476 case ID_REBASE_PICK:
477 SetSelectedRebaseAction(LOGACTIONS_REBASE_PICK);
478 break;
479 case ID_REBASE_EDIT:
480 SetSelectedRebaseAction(LOGACTIONS_REBASE_EDIT);
481 break;
482 case ID_REBASE_SQUASH:
483 SetSelectedRebaseAction(LOGACTIONS_REBASE_SQUASH);
484 break;
485 case ID_REBASE_SKIP:
486 SetSelectedRebaseAction(LOGACTIONS_REBASE_SKIP);
487 break;
488 case ID_COMBINE_COMMIT:
490 CString head;
491 CGitHash headhash;
492 CGitHash hashFirst,hashLast;
494 int headindex=GetHeadIndex();
495 if(headindex>=0) //incase show all branch, head is not the first commits.
497 head.Format(_T("HEAD~%d"),FirstSelect-headindex);
498 if (g_Git.GetHash(hashFirst, head))
500 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of first selected revision.")), _T("TortoiseGit"), MB_ICONERROR);
501 break;
504 head.Format(_T("HEAD~%d"),LastSelect-headindex);
505 if (g_Git.GetHash(hashLast, head))
507 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of last selected revision.")), _T("TortoiseGit"), MB_ICONERROR);
508 break;
512 GitRev* pFirstEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
513 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
514 if(pFirstEntry->m_CommitHash != hashFirst || pLastEntry->m_CommitHash != hashLast)
516 CMessageBox::Show(NULL, IDS_PROC_CANNOTCOMBINE, IDS_APPNAME, MB_OK);
517 break;
520 GitRev lastRevision;
523 lastRevision.GetParentFromHash(hashLast);
525 catch (char* msg)
527 CString err(msg);
528 MessageBox(_T("Could not get parent(s) of ") + hashLast.ToString() + _T(".\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
529 break;
532 if (g_Git.GetHash(headhash, _T("HEAD")))
534 MessageBox(g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
535 break;
538 if(!g_Git.CheckCleanWorkTree())
540 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
541 break;
543 CString cmd,out;
545 //Use throw to abort this process (reset back to original HEAD)
548 cmd.Format(_T("git.exe reset --hard %s --"), pFirstEntry->m_CommitHash.ToString());
549 if(g_Git.Run(cmd,&out,CP_UTF8))
551 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
552 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1)) + _T("\r\n\r\n") + out));
554 cmd.Format(_T("git.exe reset --mixed %s --"), hashLast.ToString());
555 if(g_Git.Run(cmd,&out,CP_UTF8))
557 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
558 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2)) + _T("\r\n\r\n")+out));
561 CTGitPathList PathList;
562 /* don't why must add --stat to get action status*/
563 /* first -z will be omitted by gitdll*/
564 if(g_Git.GetDiffPath(&PathList,&pFirstEntry->m_CommitHash,&hashLast,"-z --stat -r"))
566 CMessageBox::Show(NULL,_T("Get Diff file list error"),_T("TortoiseGit"),MB_OK);
567 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not get changed file list aborting...\r\n\r\n")+out));
570 for (int i = 0; i < PathList.GetCount(); ++i)
572 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_ADDED)
574 cmd.Format(_T("git.exe add -- \"%s\""), PathList[i].GetGitPathString());
575 if (g_Git.Run(cmd, &out, CP_UTF8))
577 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
578 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not add new file aborting...\r\n\r\n")+out));
582 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_DELETED)
584 cmd.Format(_T("git.exe rm -- \"%s\""), PathList[i].GetGitPathString());
585 if (g_Git.Run(cmd, &out, CP_UTF8))
587 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
588 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not rm file aborting...\r\n\r\n")+out));
593 CCommitDlg dlg;
594 for (int i = FirstSelect; i <= LastSelect; ++i)
596 GitRev* pRev = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
597 dlg.m_sLogMessage+=pRev->GetSubject()+_T("\n")+pRev->GetBody();
598 dlg.m_sLogMessage+=_T("\n");
600 dlg.m_bWholeProject=true;
601 dlg.m_bSelectFilesForCommit = true;
602 dlg.m_bForceCommitAmend=true;
603 CTGitPathList gpl;
604 gpl.AddPath(CTGitPath(g_Git.m_CurrentDir));
605 dlg.m_pathList = gpl;
606 if (lastRevision.ParentsCount() != 1)
608 CMessageBox::Show(NULL, _T("The following commit dialog can only show changes of oldest commit if it has exactly one parent. This is not the case right now."), _T("TortoiseGit"),MB_OK);
609 dlg.m_bAmendDiffToLastCommit = TRUE;
611 else
612 dlg.m_bAmendDiffToLastCommit = FALSE;
613 dlg.m_bNoPostActions=true;
614 dlg.m_AmendStr=dlg.m_sLogMessage;
616 if (dlg.DoModal() == IDOK)
618 if(pFirstEntry->m_CommitHash!=headhash)
620 if(CherryPickFrom(pFirstEntry->m_CommitHash.ToString(),headhash))
622 CString msg;
623 msg.Format(_T("Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n"));
624 throw std::exception(CUnicodeUtils::GetUTF8(msg));
628 else
629 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED))));
631 catch(std::exception& e)
633 CMessageBox::Show(NULL, CUnicodeUtils::GetUnicode(CStringA(e.what())), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
634 cmd.Format(_T("git.exe reset --hard %s --"), headhash.ToString());
635 out.Empty();
636 if(g_Git.Run(cmd,&out,CP_UTF8))
638 CMessageBox::Show(NULL, CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD)) + _T("\r\n\r\n") + out, _T("TortoiseGit"), MB_OK);
641 Refresh();
643 break;
645 case ID_CHERRY_PICK:
647 if (m_bThreadRunning)
649 MessageBox(_T("This operation cannot be started while the log dialog is still loading commits."), _T("TortoiseGit"), MB_ICONEXCLAMATION);
650 break;
652 CRebaseDlg dlg;
653 dlg.m_IsCherryPick = TRUE;
654 dlg.m_Upstream = this->m_CurrentBranch;
655 POSITION pos = GetFirstSelectedItemPosition();
656 while(pos)
658 int indexNext = GetNextSelectedItem(pos);
659 dlg.m_CommitList.m_logEntries.push_back( ((GitRev*)m_arShownList[indexNext])->m_CommitHash );
660 dlg.m_CommitList.m_LogCache.m_HashMap[((GitRev*)m_arShownList[indexNext])->m_CommitHash]=*(GitRev*)m_arShownList[indexNext];
661 dlg.m_CommitList.m_logEntries.GetGitRevAt(dlg.m_CommitList.m_logEntries.size() - 1).GetRebaseAction() |= LOGACTIONS_REBASE_PICK;
664 if(dlg.DoModal() == IDOK)
666 Refresh();
669 break;
670 case ID_REBASE_TO_VERSION:
672 if (m_bThreadRunning)
674 MessageBox(_T("This operation cannot be started while the log dialog is still loading commits."), _T("TortoiseGit"), MB_ICONEXCLAMATION);
675 break;
677 CRebaseDlg dlg;
678 auto refList = m_HashMap[pSelLogEntry->m_CommitHash];
679 dlg.m_Upstream = refList.empty() ? pSelLogEntry->m_CommitHash.ToString() : refList.front();
680 for (auto ref : refList)
682 if (ref.Left(11) == _T("refs/heads/"))
684 // 11=len("refs/heads/")
685 dlg.m_Upstream = ref.Mid(11);
686 break;
690 if(dlg.DoModal() == IDOK)
692 Refresh();
696 break;
698 case ID_STASH_SAVE:
699 if (CAppUtils::StashSave())
700 Refresh();
701 break;
703 case ID_STASH_POP:
704 if (CAppUtils::StashPop())
705 Refresh();
706 break;
708 case ID_STASH_LIST:
709 CAppUtils::RunTortoiseGitProc(_T("/command:reflog /ref:refs/stash"));
710 break;
712 case ID_REFLOG_STASH_APPLY:
713 CAppUtils::StashApply(pSelLogEntry->m_Ref);
714 break;
716 case ID_REFLOG_DEL:
718 CString str;
719 if (GetSelectedCount() > 1)
720 str.Format(IDS_PROC_DELETENREFS, GetSelectedCount());
721 else
722 str.Format(IDS_PROC_DELETEREF, pSelLogEntry->m_Ref);
724 if (CMessageBox::Show(NULL, str, _T("TortoiseGit"), 1, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
725 return;
727 std::vector<CString> refsToDelete;
728 POSITION pos = GetFirstSelectedItemPosition();
729 while (pos)
731 CString ref = ((GitRev *)m_arShownList[GetNextSelectedItem(pos)])->m_Ref;
732 if (ref.Find(_T("refs/")) == 0)
733 ref = ref.Mid(5);
734 int refpos = ref.ReverseFind('{');
735 if (refpos > 0 && ref.Mid(refpos - 1, 2) != _T("@{"))
736 ref = ref.Left(refpos) + _T("@")+ ref.Mid(refpos);
737 refsToDelete.push_back(ref);
740 for (auto revIt = refsToDelete.rbegin(); revIt != refsToDelete.rend(); ++revIt)
742 CString ref = *revIt;
743 CString cmd, out;
744 if (ref.Find(_T("stash")) == 0)
745 cmd.Format(_T("git.exe stash drop %s"), ref);
746 else
747 cmd.Format(_T("git.exe reflog delete %s"), ref);
749 if (g_Git.Run(cmd, &out, CP_UTF8))
750 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
752 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
755 break;
756 case ID_LOG:
758 CString cmd = _T("/command:log");
759 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
760 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
761 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
762 CAppUtils::RunTortoiseGitProc(cmd);
764 break;
765 case ID_CREATE_PATCH:
767 int select=this->GetSelectedCount();
768 CString cmd = _T("/command:formatpatch");
769 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
771 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
772 GitRev * r2 = NULL;
773 if(select == 1)
775 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString();
777 else
779 r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
780 if( this->m_IsOldFirst )
782 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString()+_T("~1");
783 cmd += _T(" /endrev:")+r2->m_CommitHash.ToString();
786 else
788 cmd += _T(" /startrev:")+r2->m_CommitHash.ToString()+_T("~1");
789 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
794 CAppUtils::RunTortoiseGitProc(cmd);
796 break;
797 case ID_BISECTSTART:
799 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
800 GitRev * last = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
801 ASSERT(first != NULL && last != NULL);
803 CString firstBad = first->m_CommitHash.ToString();
804 if (!m_HashMap[first->m_CommitHash].empty())
805 firstBad = m_HashMap[first->m_CommitHash].at(0);
806 CString lastGood = last->m_CommitHash.ToString();
807 if (!m_HashMap[last->m_CommitHash].empty())
808 lastGood = m_HashMap[last->m_CommitHash].at(0);
810 if (CAppUtils::BisectStart(lastGood, firstBad))
811 Refresh();
813 break;
814 case ID_REPOBROWSE:
816 CString sCmd;
817 sCmd.Format(_T("/command:repobrowser /path:\"%s\" /rev:%s"), g_Git.m_CurrentDir, pSelLogEntry->m_CommitHash.ToString());
818 CAppUtils::RunTortoiseGitProc(sCmd);
820 break;
821 case ID_PUSH:
823 CString guessAssociatedBranch;
824 if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
825 guessAssociatedBranch = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
826 if (CAppUtils::Push(guessAssociatedBranch))
827 Refresh();
829 break;
830 case ID_PULL:
832 if (CAppUtils::Pull())
833 Refresh();
835 break;
836 case ID_FETCH:
838 if (CAppUtils::Fetch(_T(""), true))
839 Refresh();
841 break;
842 case ID_SHOWBRANCHES:
844 CString cmd;
845 cmd.Format(_T("git.exe branch -a --contains %s"), pSelLogEntry->m_CommitHash.ToString());
846 CProgressDlg progress;
847 progress.m_AutoClose = AUTOCLOSE_NO;
848 progress.m_GitCmd = cmd;
849 progress.DoModal();
851 break;
852 case ID_DELETE:
854 bool showProgress = false;
855 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
856 if (!branch)
858 CMessageBox::Show(NULL,IDS_ERROR_NOREF,IDS_APPNAME,MB_OK|MB_ICONERROR);
859 return;
861 CString shortname;
862 CString cmd;
863 if (CGit::GetShortName(*branch, shortname, _T("refs/remotes/")))
865 CString msg;
866 msg.Format(IDS_PROC_DELETEREMOTEBRANCH, *branch);
867 int result = CMessageBox::Show(NULL, msg, _T("TortoiseGit"), 3, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_PROC_DELETEREMOTEBRANCH_LOCALREMOTE)), CString(MAKEINTRESOURCE(IDS_PROC_DELETEREMOTEBRANCH_LOCAL)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON)));
868 if (result == 1)
870 CString remoteName = shortname.Left(shortname.Find('/'));
871 shortname = shortname.Mid(shortname.Find('/') + 1);
872 if(CAppUtils::IsSSHPutty())
873 CAppUtils::LaunchPAgent(NULL, &remoteName);
875 cmd.Format(L"git.exe push \"%s\" :refs/heads/%s", remoteName, shortname);
876 showProgress = true;
878 else if (result == 2)
880 if (g_Git.DeleteRef(*branch))
882 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
883 return;
886 else
887 return;
889 else if (CGit::GetShortName(*branch, shortname, _T("refs/stash")))
891 if (CMessageBox::Show(NULL, IDS_PROC_DELETEALLSTASH, IDS_APPNAME, 2, IDI_QUESTION, IDS_DELETEBUTTON, IDS_ABORTBUTTON) == 1)
892 cmd.Format(_T("git.exe stash clear"));
893 else
894 return;
896 else
898 CString msg;
899 msg.Format(IDS_PROC_DELETEBRANCHTAG, *branch);
900 if (CMessageBox::Show(NULL, msg, _T("TortoiseGit"), 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
902 if (g_Git.DeleteRef(*branch))
904 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
905 return;
909 if (!cmd.IsEmpty())
911 CSysProgressDlg sysProgressDlg;
912 if (showProgress)
914 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
915 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
916 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
917 sysProgressDlg.SetShowProgressBar(false);
918 sysProgressDlg.ShowModal(this, true);
920 CString out;
921 if(g_Git.Run(cmd,&out,CP_UTF8))
923 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
925 if (showProgress)
926 sysProgressDlg.Stop();
928 this->ReloadHashMap();
929 CRect rect;
930 this->GetItemRect(FirstSelect,&rect,LVIR_BOUNDS);
931 this->InvalidateRect(rect);
933 break;
935 case ID_FINDENTRY:
937 m_nSearchIndex = GetSelectionMark();
938 if (m_nSearchIndex < 0)
939 m_nSearchIndex = 0;
940 if (m_pFindDialog)
942 break;
944 else
946 m_pFindDialog = new CFindDlg();
947 m_pFindDialog->Create(this);
950 break;
951 case ID_MERGEREV:
953 CString str = pSelLogEntry->m_CommitHash.ToString();
954 if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
955 str = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
956 // we need an URL to complete this command, so error out if we can't get an URL
957 if(CAppUtils::Merge(&str))
959 this->Refresh();
962 break;
963 case ID_REVERTREV:
965 int parent = 0;
966 if (GetSelectedCount() == 1)
968 parent = cmd >> 16;
969 if (parent > pSelLogEntry->m_ParentHash.size())
971 CString str;
972 str.Format(IDS_PROC_NOPARENT, parent);
973 MessageBox(str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
974 return;
978 if (!this->RevertSelectedCommits(parent))
980 if (CMessageBox::Show(m_hWnd, IDS_REVREVERTED, IDS_APPNAME, 1, IDI_QUESTION, IDS_OKBUTTON, IDS_COMMITBUTTON) == 2)
982 CTGitPathList pathlist;
983 CTGitPathList selectedlist;
984 pathlist.AddPath(this->m_Path);
985 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
986 CString str;
987 CAppUtils::Commit(CString(), false, str, pathlist, selectedlist, bSelectFilesForCommit);
989 this->Refresh();
992 break;
993 case ID_EDITNOTE:
995 CAppUtils::EditNote(pSelLogEntry);
996 this->SetItemState(FirstSelect, 0, LVIS_SELECTED);
997 this->SetItemState(FirstSelect, LVIS_SELECTED, LVIS_SELECTED);
999 break;
1000 default:
1001 //CMessageBox::Show(NULL,_T("Have not implemented"),_T("TortoiseGit"),MB_OK);
1002 break;
1003 #if 0
1005 case ID_BLAMECOMPARE:
1007 //user clicked on the menu item "compare with working copy"
1008 //now first get the revision which is selected
1009 if (PromptShown())
1011 GitDiff diff(this, this->m_hWnd, true);
1012 diff.SetHEADPeg(m_LogRevision);
1013 diff.ShowCompare(m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), false, true);
1015 else
1016 CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), m_LogRevision, false, false, true);
1018 break;
1019 case ID_BLAMEWITHPREVIOUS:
1021 //user clicked on the menu item "Compare and Blame with previous revision"
1022 if (PromptShown())
1024 GitDiff diff(this, this->m_hWnd, true);
1025 diff.SetHEADPeg(m_LogRevision);
1026 diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), false, true);
1028 else
1029 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
1031 break;
1033 case ID_OPENWITH:
1034 bOpenWith = true;
1035 case ID_OPEN:
1037 CProgressDlg progDlg;
1038 progDlg.SetTitle(IDS_APPNAME);
1039 progDlg.SetAnimation(IDR_DOWNLOAD);
1040 CString sInfoLine;
1041 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, m_path.GetWinPath(), (LPCTSTR)revSelected.ToString());
1042 progDlg.SetLine(1, sInfoLine, true);
1043 SetAndClearProgressInfo(&progDlg);
1044 progDlg.ShowModeless(m_hWnd);
1045 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, m_path, revSelected);
1046 bool bSuccess = true;
1047 if (!Cat(m_path, GitRev(GitRev::REV_HEAD), revSelected, tempfile))
1049 bSuccess = false;
1050 // try again, but with the selected revision as the peg revision
1051 if (!Cat(m_path, revSelected, revSelected, tempfile))
1053 progDlg.Stop();
1054 SetAndClearProgressInfo((HWND)NULL);
1055 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1056 EnableOKButton();
1057 break;
1059 bSuccess = true;
1061 if (bSuccess)
1063 progDlg.Stop();
1064 SetAndClearProgressInfo((HWND)NULL);
1065 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1066 int ret = 0;
1067 if (!bOpenWith)
1068 ret = (int)ShellExecute(this->m_hWnd, NULL, tempfile.GetWinPath(), NULL, NULL, SW_SHOWNORMAL);
1069 if ((ret <= HINSTANCE_ERROR)||bOpenWith)
1071 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1072 cmd += tempfile.GetWinPathString() + _T(" ");
1073 CAppUtils::LaunchApplication(cmd, NULL, false);
1077 break;
1078 case ID_BLAME:
1080 CBlameDlg dlg;
1081 dlg.EndRev = revSelected;
1082 if (dlg.DoModal() == IDOK)
1084 CBlame blame;
1085 CString tempfile;
1086 CString logfile;
1087 tempfile = blame.BlameToTempFile(m_path, dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, _T(""), dlg.m_bIncludeMerge, TRUE, TRUE);
1088 if (!tempfile.IsEmpty())
1090 if (dlg.m_bTextView)
1092 //open the default text editor for the result file
1093 CAppUtils::StartTextViewer(tempfile);
1095 else
1097 CString sParams = _T("/path:\"") + m_path.GetGitPathString() + _T("\" ");
1098 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(m_path.GetFileOrDirectoryName()),sParams))
1100 break;
1104 else
1106 CMessageBox::Show(this->m_hWnd, blame.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1110 break;
1111 case ID_EXPORT:
1113 CString sCmd;
1114 sCmd.Format(_T("%s /command:export /path:\"%s\" /revision:%ld"),
1115 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseGitProc.exe")),
1116 (LPCTSTR)pathURL, (LONG)revSelected);
1117 CAppUtils::LaunchApplication(sCmd, NULL, false);
1119 break;
1120 case ID_VIEWREV:
1122 CString url = m_ProjectProperties.sWebViewerRev;
1123 url = GetAbsoluteUrlFromRelativeUrl(url);
1124 url.Replace(_T("%REVISION%"), revSelected.ToString());
1125 if (!url.IsEmpty())
1126 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1128 break;
1129 case ID_VIEWPATHREV:
1131 CString relurl = pathURL;
1132 CString sRoot = GetRepositoryRoot(CTGitPath(relurl));
1133 relurl = relurl.Mid(sRoot.GetLength());
1134 CString url = m_ProjectProperties.sWebViewerPathRev;
1135 url = GetAbsoluteUrlFromRelativeUrl(url);
1136 url.Replace(_T("%REVISION%"), revSelected.ToString());
1137 url.Replace(_T("%PATH%"), relurl);
1138 if (!url.IsEmpty())
1139 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1141 break;
1142 #endif
1144 } // switch (cmd)
1146 theApp.DoWaitCursor(-1);
1149 void CGitLogList::SetSelectedRebaseAction(int action)
1151 POSITION pos = GetFirstSelectedItemPosition();
1152 if (!pos) return;
1153 int index;
1154 while(pos)
1156 index = GetNextSelectedItem(pos);
1157 if (((GitRev*)m_arShownList[index])->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT | LOGACTIONS_REBASE_DONE))
1158 continue;
1159 ((GitRev*)m_arShownList[index])->GetRebaseAction() = action;
1160 CRect rect;
1161 this->GetItemRect(index,&rect,LVIR_BOUNDS);
1162 this->InvalidateRect(rect);
1165 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage);
1167 void CGitLogList::ShiftSelectedRebaseAction()
1169 POSITION pos = GetFirstSelectedItemPosition();
1170 int index;
1171 while(pos)
1173 index = GetNextSelectedItem(pos);
1174 int dummyAction = 0;
1175 int *action = &dummyAction;
1176 action = &((GitRev*)m_arShownList[index])->GetRebaseAction();
1177 switch (*action)
1179 case LOGACTIONS_REBASE_PICK:
1180 *action = LOGACTIONS_REBASE_SKIP;
1181 break;
1182 case LOGACTIONS_REBASE_SKIP:
1183 *action= LOGACTIONS_REBASE_EDIT;
1184 break;
1185 case LOGACTIONS_REBASE_EDIT:
1186 *action = LOGACTIONS_REBASE_SQUASH;
1187 break;
1188 case LOGACTIONS_REBASE_SQUASH:
1189 *action= LOGACTIONS_REBASE_PICK;
1190 break;
1192 CRect rect;
1193 this->GetItemRect(index, &rect, LVIR_BOUNDS);
1194 this->InvalidateRect(rect);