Allow to delete multiple refs at once
[TortoiseGit.git] / src / TortoiseProc / GitLogListAction.cpp
blob01bf4798068d7ce24cb9265ab27f1168f58a775e
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2012 - 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 "VssStyle.h"
27 #include "IconMenu.h"
28 // CGitLogList
29 #include "cursor.h"
30 #include "InputDlg.h"
31 #include "GITProgressDlg.h"
32 #include "ProgressDlg.h"
33 #include "SysProgressDlg.h"
34 //#include "RepositoryBrowser.h"
35 //#include "CopyDlg.h"
36 //#include "StatGraphDlg.h"
37 #include "Logdlg.h"
38 #include "MessageBox.h"
39 #include "Registry.h"
40 #include "AppUtils.h"
41 #include "PathUtils.h"
42 #include "StringUtils.h"
43 #include "UnicodeUtils.h"
44 #include "TempFile.h"
45 //#include "GitInfo.h"
46 //#include "GitDiff.h"
47 //#include "RevisionRangeDlg.h"
48 //#include "BrowseFolder.h"
49 //#include "BlameDlg.h"
50 //#include "Blame.h"
51 //#include "GitHelpers.h"
52 #include "GitStatus.h"
53 //#include "LogDlgHelper.h"
54 //#include "CachedLogInfo.h"
55 //#include "RepositoryInfo.h"
56 //#include "EditPropertiesDlg.h"
57 #include "FileDiffDlg.h"
58 #include "CommitDlg.h"
59 #include "RebaseDlg.h"
60 #include "GitDiff.h"
62 IMPLEMENT_DYNAMIC(CGitLogList, CHintListCtrl)
64 int CGitLogList::RevertSelectedCommits()
66 CSysProgressDlg progress;
67 int ret = -1;
69 #if 0
70 if(!g_Git.CheckCleanWorkTree())
72 CMessageBox::Show(NULL,_T("Revert requires a clean working tree"),_T("TortoiseGit"),MB_OK);
75 #endif
77 if (progress.IsValid() && (this->GetSelectedCount() > 1) )
79 progress.SetTitle(_T("Revert Commit"));
80 progress.SetAnimation(IDR_MOVEANI);
81 progress.SetTime(true);
82 progress.ShowModeless(this);
85 POSITION pos = GetFirstSelectedItemPosition();
86 int i=0;
87 while(pos)
89 int index = GetNextSelectedItem(pos);
90 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(index));
92 if (progress.IsValid() && (this->GetSelectedCount() > 1) )
94 progress.FormatPathLine(1, _T("Revert %s"), r1->m_CommitHash.ToString());
95 progress.FormatPathLine(2, _T("%s"), r1->GetSubject());
96 progress.SetProgress(i, this->GetSelectedCount());
98 i++;
100 if(r1->m_CommitHash.IsEmpty())
101 continue;
103 CString cmd, output;
104 cmd.Format(_T("git.exe revert --no-edit --no-commit %s"), r1->m_CommitHash.ToString());
105 if(g_Git.Run(cmd, &output, CP_ACP))
107 CString str;
108 str=_T("Revert fail\n");
109 str+= cmd;
110 str+= _T("\n")+output;
111 if( GetSelectedCount() == 1)
112 CMessageBox::Show(NULL,str, _T("TortoiseGit"),MB_OK|MB_ICONERROR);
113 else
115 if(CMessageBox::Show(NULL, str, _T("TortoiseGit"),2 , IDI_ERROR, _T("&Skip"), _T("&Abort")) == 2)
117 return ret;
121 else
123 ret =0;
126 if ((progress.IsValid())&&(progress.HasUserCancelled()))
127 break;
129 return ret;
131 int CGitLogList::CherryPickFrom(CString from, CString to)
133 CLogDataVector logs(&m_LogCache);
134 if(logs.ParserFromLog(NULL,-1,0,&from,&to))
135 return -1;
137 if(logs.size() == 0)
138 return 0;
140 CSysProgressDlg progress;
141 if (progress.IsValid())
143 progress.SetTitle(_T("Cherry Pick"));
144 progress.SetAnimation(IDR_MOVEANI);
145 progress.SetTime(true);
146 progress.ShowModeless(this);
149 for(int i=logs.size()-1;i>=0;i--)
151 if (progress.IsValid())
153 progress.FormatPathLine(1, _T("Pick up %s"), logs.GetGitRevAt(i).m_CommitHash.ToString());
154 progress.FormatPathLine(2, _T("%s"), logs.GetGitRevAt(i).GetSubject());
155 progress.SetProgress(logs.size()-i, logs.size());
157 if ((progress.IsValid())&&(progress.HasUserCancelled()))
159 //CMessageBox::Show(hwndExplorer, IDS_SVN_USERCANCELLED, IDS_APPNAME, MB_ICONINFORMATION);
160 throw std::exception(CUnicodeUtils::GetUTF8(_T("User canceled\r\n\r\n")));
161 return -1;
163 CString cmd,out;
164 cmd.Format(_T("git.exe cherry-pick %s"),logs.GetGitRevAt(i).m_CommitHash.ToString());
165 out.Empty();
166 if(g_Git.Run(cmd,&out,CP_UTF8))
168 throw std::exception(CUnicodeUtils::GetUTF8(CString(_T("Cherry Pick Failure\r\n\r\n"))+out));
169 return -1;
173 return 0;
176 void CGitLogList::ContextMenuAction(int cmd,int FirstSelect, int LastSelect, CMenu *popmenu)
178 POSITION pos = GetFirstSelectedItemPosition();
179 int indexNext = GetNextSelectedItem(pos);
180 if (indexNext < 0)
181 return;
183 GitRev* pSelLogEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(indexNext));
185 theApp.DoWaitCursor(1);
186 switch (cmd&0xFFFF)
188 case ID_COMMIT:
190 CTGitPathList pathlist;
191 CTGitPathList selectedlist;
192 pathlist.AddPath(this->m_Path);
193 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
194 CString str;
195 CAppUtils::Commit(CString(),false,str,
196 pathlist,selectedlist,bSelectFilesForCommit);
197 //this->Refresh();
198 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH,0);
200 break;
201 case ID_GNUDIFF1: // compare with WC, unified
203 CString tempfile=GetTempFile();
204 CString command;
205 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
206 if(!r1->m_CommitHash.IsEmpty())
208 CString merge;
209 CString hash2;
210 cmd >>= 16;
211 if( (cmd&0xFFFF) == 0xFFFF)
213 merge=_T("-m");
215 else if((cmd&0xFFFF) == 0xFFFE)
217 merge=_T("-c");
219 else
221 if(cmd > r1->m_ParentHash.size())
223 CString str;
224 str.Format(_T("Parent %d does not exist"), cmd);
225 CMessageBox::Show(NULL,str,_T("TortoiseGit"),MB_OK|MB_ICONERROR);
226 return;
228 else
230 if(cmd>0)
231 hash2 = r1->m_ParentHash[cmd-1].ToString();
234 command.Format(_T("git.exe diff-tree %s -r -p --stat %s %s"), merge, hash2, r1->m_CommitHash.ToString());
236 else
237 command.Format(_T("git.exe diff -r -p --stat"));
239 g_Git.RunLogFile(command,tempfile);
240 CAppUtils::StartUnifiedDiffViewer(tempfile,r1->m_CommitHash.ToString().Left(6)+_T(":")+r1->GetSubject());
242 break;
244 case ID_GNUDIFF2: // compare two revisions, unified
246 CString tempfile=GetTempFile();
247 CString cmd;
248 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
249 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
251 if( r1->m_CommitHash.IsEmpty()) {
252 cmd.Format(_T("git.exe diff -r -p --stat %s"),r2->m_CommitHash.ToString());
254 else if( r2->m_CommitHash.IsEmpty()) {
255 cmd.Format(_T("git.exe diff -r -p --stat %s"),r1->m_CommitHash.ToString());
257 else
259 cmd.Format(_T("git.exe diff-tree -r -p --stat %s %s"),r2->m_CommitHash.ToString(),r1->m_CommitHash.ToString());
262 g_Git.RunLogFile(cmd,tempfile);
263 CAppUtils::StartUnifiedDiffViewer(tempfile,r2->m_CommitHash.ToString().Left(6)+_T(":")+r1->m_CommitHash.ToString().Left(6));
266 break;
268 case ID_COMPARETWO: // compare two revisions
270 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
271 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
272 CGitDiff::DiffCommit(this->m_Path, r1,r2);
275 break;
277 case ID_COMPARE: // compare revision with WC
279 GitRev * r1 = &m_wcRev;
280 GitRev * r2 = pSelLogEntry;
282 CGitDiff::DiffCommit(this->m_Path, r1,r2);
284 //user clicked on the menu item "compare with working copy"
285 //if (PromptShown())
287 // GitDiff diff(this, m_hWnd, true);
288 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
289 // diff.SetHEADPeg(m_LogRevision);
290 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
292 //else
293 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
295 break;
297 case ID_COMPAREWITHPREVIOUS:
300 CFileDiffDlg dlg;
302 if(pSelLogEntry->m_ParentHash.size()>0)
303 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
305 cmd>>=16;
306 cmd&=0xFFFF;
308 if(cmd == 0)
309 cmd=1;
311 CGitDiff::DiffCommit(this->m_Path, pSelLogEntry->m_CommitHash.ToString(),pSelLogEntry->m_ParentHash[cmd-1].ToString());
314 else
316 CMessageBox::Show(NULL,_T("No previous version"),_T("TortoiseGit"),MB_OK);
318 //if (PromptShown())
320 // GitDiff diff(this, m_hWnd, true);
321 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
322 // diff.SetHEADPeg(m_LogRevision);
323 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
325 //else
326 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
328 break;
329 case ID_COPYCLIPBOARD:
331 CopySelectionToClipBoard();
333 break;
334 case ID_COPYHASH:
336 CopySelectionToClipBoard(TRUE);
338 break;
339 case ID_EXPORT:
341 CString str=pSelLogEntry->m_CommitHash.ToString();
342 CAppUtils::Export(&str);
344 break;
345 case ID_CREATE_BRANCH:
347 CString str = pSelLogEntry->m_CommitHash.ToString();
348 CAppUtils::CreateBranchTag(FALSE,&str);
349 ReloadHashMap();
350 Invalidate();
352 break;
353 case ID_CREATE_TAG:
355 CString str = pSelLogEntry->m_CommitHash.ToString();
356 CAppUtils::CreateBranchTag(TRUE,&str);
357 ReloadHashMap();
358 Invalidate();
359 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
361 break;
362 case ID_SWITCHTOREV:
364 CString str = pSelLogEntry->m_CommitHash.ToString();
365 CAppUtils::Switch(&str);
367 ReloadHashMap();
368 Invalidate();
369 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
370 break;
371 case ID_SWITCHBRANCH:
372 if(popmenu)
374 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
375 if(branch)
377 CString name;
378 if(branch->Find(_T("refs/heads/")) ==0 )
379 name = branch->Mid(11);
380 else
381 name = *branch;
383 CAppUtils::PerformSwitch(name);
385 ReloadHashMap();
386 Invalidate();
387 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
389 break;
390 case ID_RESET:
392 CString str = pSelLogEntry->m_CommitHash.ToString();
393 CAppUtils::GitReset(&str);
394 ReloadHashMap();
395 Invalidate();
397 break;
398 case ID_REBASE_PICK:
399 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_PICK);
400 break;
401 case ID_REBASE_EDIT:
402 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_EDIT);
403 break;
404 case ID_REBASE_SQUASH:
405 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_SQUASH);
406 break;
407 case ID_REBASE_SKIP:
408 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_SKIP);
409 break;
410 case ID_COMBINE_COMMIT:
412 CString head;
413 CGitHash headhash;
414 CGitHash hashFirst,hashLast;
416 int headindex=GetHeadIndex();
417 if(headindex>=0) //incase show all branch, head is not the first commits.
419 head.Format(_T("HEAD~%d"),FirstSelect-headindex);
420 hashFirst=g_Git.GetHash(head);
422 head.Format(_T("HEAD~%d"),LastSelect-headindex);
423 hashLast=g_Git.GetHash(head);
426 GitRev* pFirstEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
427 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
428 if(pFirstEntry->m_CommitHash != hashFirst || pLastEntry->m_CommitHash != hashLast)
430 CMessageBox::Show(NULL,_T("Cannot combine commits now.\r\nMake sure you are viewing the log of your current branch and no filters are applied."),_T("TortoiseGit"),MB_OK);
431 break;
434 headhash=g_Git.GetHash(_T("HEAD"));
436 if(!g_Git.CheckCleanWorkTree())
438 CMessageBox::Show(NULL,_T("Combine needs a clean work tree"),_T("TortoiseGit"),MB_OK);
439 break;
441 CString cmd,out;
443 //Use throw to abort this process (reset back to original HEAD)
446 cmd.Format(_T("git.exe reset --hard %s"),pFirstEntry->m_CommitHash.ToString());
447 if(g_Git.Run(cmd,&out,CP_UTF8))
449 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
450 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not reset to first commit (first step) aborting...\r\n\r\n")+out));
452 cmd.Format(_T("git.exe reset --mixed %s"),hashLast.ToString());
453 if(g_Git.Run(cmd,&out,CP_UTF8))
455 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
456 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not reset to last commit (second step) aborting...\r\n\r\n")+out));
459 CTGitPathList PathList;
460 /* don't why must add --stat to get action status*/
461 /* first -z will be omitted by gitdll*/
462 if(g_Git.GetDiffPath(&PathList,&pFirstEntry->m_CommitHash,&hashLast,"-z --stat -r"))
464 CMessageBox::Show(NULL,_T("Get Diff file list error"),_T("TortoiseGit"),MB_OK);
465 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not get changed file list aborting...\r\n\r\n")+out));
468 for(int i=0;i<PathList.GetCount();i++)
470 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_ADDED)
472 cmd.Format(_T("git.exe add -- \"%s\""), PathList[i].GetGitPathString());
473 if(g_Git.Run(cmd,&out,CP_ACP))
475 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
476 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not add new file aborting...\r\n\r\n")+out));
480 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_DELETED)
482 cmd.Format(_T("git.exe rm -- \"%s\""), PathList[i].GetGitPathString());
483 if(g_Git.Run(cmd,&out,CP_ACP))
485 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
486 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not rm file aborting...\r\n\r\n")+out));
491 CCommitDlg dlg;
492 for(int i=FirstSelect;i<=LastSelect;i++)
494 GitRev* pRev = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
495 dlg.m_sLogMessage+=pRev->GetSubject()+_T("\n")+pRev->GetBody();
496 dlg.m_sLogMessage+=_T("\n");
498 dlg.m_bWholeProject=true;
499 dlg.m_bSelectFilesForCommit = true;
500 dlg.m_bCommitAmend=true;
501 dlg.m_bNoPostActions=true;
502 dlg.m_AmendStr=dlg.m_sLogMessage;
504 if (dlg.DoModal() == IDOK)
506 if(pFirstEntry->m_CommitHash!=headhash)
508 //Commitrange firstEntry..headhash (from top of combine to original head) needs to be 'cherry-picked'
509 //on top of new commit.
510 //Use the rebase --onto command for it.
512 //All this can be done in one step using the following command:
513 //cmd.Format(_T("git.exe format-patch --stdout --binary --full-index -k %s..%s | git am -k -3"),
514 // pFirstEntry->m_CommitHash,
515 // headhash);
516 //But I am not sure if a '|' is going to work in a CreateProcess() call.
518 //Later the progress dialog could be used to execute these steps.
520 if(CherryPickFrom(pFirstEntry->m_CommitHash.ToString(),headhash))
522 CString msg;
523 msg.Format(_T("Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n"));
524 throw std::exception(CUnicodeUtils::GetUTF8(msg));
526 #if 0
527 CString currentBranch=g_Git.GetCurrentBranch();
528 cmd.Format(_T("git.exe rebase --onto \"%s\" %s %s"),
529 currentBranch,
530 pFirstEntry->m_CommitHash,
531 headhash);
532 if(g_Git.Run(cmd,&out,CP_UTF8)!=0)
534 CString msg;
535 msg.Format(_T("Error while rebasing commits on top of combined commits. Aborting.\r\n\r\n%s"),out);
536 // CMessageBox::Show(NULL,msg,_T("TortoiseGit"),MB_OK);
537 g_Git.Run(_T("git.exe rebase --abort"),&out,CP_UTF8);
538 throw std::exception(CUnicodeUtils::GetUTF8(msg));
541 //HEAD is now on <no branch>.
542 //The following steps are to get HEAD back on the original branch and reset the branch to the new HEAD
543 //To avoid 2 working copy changes, we could use git branch -f <original branch> <hash new head>
544 //And then git checkout <original branch>
545 //But I don't know if 'git branch -f' removes tracking options. So for now, do a checkout and a reset.
547 //Store new HEAD
548 CString newHead=g_Git.GetHash(CString(_T("HEAD")));
550 //Checkout working branch
551 cmd.Format(_T("git.exe checkout -f \"%s\""),currentBranch);
552 if(g_Git.Run(cmd,&out,CP_UTF8))
553 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not checkout original branch. Aborting...\r\n\r\n")+out));
555 //Reset to new HEAD
556 cmd.Format(_T("git.exe reset --hard %s"),newHead);
557 if(g_Git.Run(cmd,&out,CP_UTF8))
558 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not reset to new head. Aborting...\r\n\r\n")+out));
559 #endif
562 else
563 throw std::exception("User aborted the combine process");
565 catch(std::exception& e)
567 CMessageBox::Show(NULL,CUnicodeUtils::GetUnicode(CStringA(e.what())),_T("TortoiseGit: Combine error"),MB_OK|MB_ICONERROR);
568 cmd.Format(_T("git.exe reset --hard %s"),headhash.ToString());
569 out.Empty();
570 if(g_Git.Run(cmd,&out,CP_UTF8))
572 CMessageBox::Show(NULL,_T("Could not reset to original HEAD\r\n\r\n")+out,_T("TortoiseGit"),MB_OK);
575 Refresh();
577 break;
579 case ID_CHERRY_PICK:
580 if(!g_Git.CheckCleanWorkTree())
582 CMessageBox::Show(NULL,_T("Cherry Pick requires a clean working tree"),_T("TortoiseGit"),MB_OK);
585 else
587 CRebaseDlg dlg;
588 dlg.m_IsCherryPick = TRUE;
589 dlg.m_Upstream = this->m_CurrentBranch;
590 POSITION pos = GetFirstSelectedItemPosition();
591 while(pos)
593 int indexNext = GetNextSelectedItem(pos);
594 dlg.m_CommitList.m_logEntries.push_back( ((GitRev*)m_arShownList[indexNext])->m_CommitHash );
595 dlg.m_CommitList.m_LogCache.m_HashMap[((GitRev*)m_arShownList[indexNext])->m_CommitHash]=*(GitRev*)m_arShownList[indexNext];
596 dlg.m_CommitList.m_logEntries.GetGitRevAt(dlg.m_CommitList.m_logEntries.size()-1).GetAction(this) |= CTGitPath::LOGACTIONS_REBASE_PICK;
599 if(dlg.DoModal() == IDOK)
601 Refresh();
604 break;
605 case ID_REBASE_TO_VERSION:
606 if(!g_Git.CheckCleanWorkTree())
608 CMessageBox::Show(NULL,_T("Rebase requires a clean working tree"),_T("TortoiseGit"),MB_OK);
611 else
613 CRebaseDlg dlg;
614 dlg.m_Upstream = pSelLogEntry->m_CommitHash;
616 if(dlg.DoModal() == IDOK)
618 Refresh();
622 break;
624 case ID_STASH_SAVE:
625 if (CAppUtils::StashSave())
626 Refresh();
627 break;
629 case ID_STASH_POP:
630 if (CAppUtils::StashPop())
631 Refresh();
632 break;
634 case ID_STASH_LIST:
635 CAppUtils::RunTortoiseProc(_T("/command:reflog /ref:refs/stash"));
636 break;
638 case ID_REFLOG_STASH_APPLY:
639 CAppUtils::StashApply(pSelLogEntry->m_Ref);
640 break;
642 case ID_REFLOG_DEL:
644 CString str;
645 if (GetSelectedCount() > 1)
646 str.Format(_T("Do you really want to permanently delete the %d selected refs? It can <ct=0x0000FF><b>NOT</b></ct> be recovered!"), GetSelectedCount());
647 else
648 str.Format(_T("Warning: \"%s\" will be permanently deleted. It can <ct=0x0000FF><b>NOT</b></ct> be recovered!\r\n\r\nDo you really want to continue?"), pSelLogEntry->m_Ref);
650 if (CMessageBox::Show(NULL, str, _T("TortoiseGit"), 1, IDI_QUESTION, _T("&Delete"), _T("&Abort")) == 2)
651 return;
653 POSITION pos = GetFirstSelectedItemPosition();
654 while (pos)
656 CString ref = ((GitRev *)m_arShownList[GetNextSelectedItem(pos)])->m_Ref;
657 if (ref.Find(_T("refs/")) == 0)
658 ref = ref.Mid(5);
659 int refpos = ref.ReverseFind('{');
660 if (refpos > 0 && ref.Mid(refpos, 2) != _T("@{"))
661 ref = ref.Left(refpos) + _T("@")+ ref.Mid(refpos);
663 CString cmd, out;
664 if (ref.Find(_T("stash")) == 0)
665 cmd.Format(_T("git.exe stash drop %s"), ref);
666 else
667 cmd.Format(_T("git.exe reflog delete %s"), ref);
669 if(g_Git.Run(cmd,&out,CP_ACP))
670 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
672 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
675 break;
676 case ID_LOG:
678 CString cmd = _T("/command:log");
679 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
680 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
681 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
682 CAppUtils::RunTortoiseProc(cmd);
684 break;
685 case ID_CREATE_PATCH:
687 int select=this->GetSelectedCount();
688 CString cmd = _T("/command:formatpatch");
689 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
691 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
692 GitRev * r2 = NULL;
693 if(select == 1)
695 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString();
697 else
699 r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
700 if( this->m_IsOldFirst )
702 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString()+_T("~1");
703 cmd += _T(" /endrev:")+r2->m_CommitHash.ToString();
706 else
708 cmd += _T(" /startrev:")+r2->m_CommitHash.ToString()+_T("~1");
709 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
714 CAppUtils::RunTortoiseProc(cmd);
716 break;
717 case ID_PUSH:
719 CString guessAssociatedBranch;
720 if (m_HashMap[pSelLogEntry->m_CommitHash].size() > 0)
721 guessAssociatedBranch = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
722 if (CAppUtils::Push(guessAssociatedBranch))
723 Refresh();
725 break;
726 case ID_DELETE:
728 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
729 if (!branch)
731 CMessageBox::Show(NULL,IDS_ERROR_NOREF,IDS_APPNAME,MB_OK|MB_ICONERROR);
732 return;
734 CString msg;
735 msg=CString(_T("Do you really want to <ct=0x0000FF>delete</ct> <b>")) + *branch;
736 msg+=_T("</b>?");
737 if( CMessageBox::Show(NULL, msg, _T("TortoiseGit"), 2, IDI_QUESTION, _T("&Delete"), _T("&Abort")) == 1 )
739 CString shortname;
740 CString cmd;
741 if(this->GetShortName(*branch,shortname,_T("refs/heads/")))
743 cmd.Format(_T("git.exe branch -D -- %s"),shortname);
746 if(this->GetShortName(*branch,shortname,_T("refs/remotes/")))
748 cmd.Format(_T("git.exe branch -r -D -- %s"),shortname);
751 if(this->GetShortName(*branch,shortname,_T("refs/tags/")))
753 cmd.Format(_T("git.exe tag -d -- %s"),shortname);
756 if(this->GetShortName(*branch,shortname,_T("refs/stash")))
758 if(CMessageBox::Show(NULL, _T("<ct=0x0000FF>Do you really want to delete <b>ALL</b> stash?</ct>"),
759 _T("TortoiseGit"), 2, IDI_QUESTION, _T("&Delete"), _T("&Abort")) == 1)
760 cmd.Format(_T("git.exe stash clear"));
761 else
762 return;
765 CString out;
766 if(g_Git.Run(cmd,&out,CP_UTF8))
768 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
770 this->ReloadHashMap();
771 CRect rect;
772 this->GetItemRect(FirstSelect,&rect,LVIR_BOUNDS);
773 this->InvalidateRect(rect);
776 break;
778 case ID_FINDENTRY:
780 m_nSearchIndex = GetSelectionMark();
781 if (m_nSearchIndex < 0)
782 m_nSearchIndex = 0;
783 if (m_pFindDialog)
785 break;
787 else
789 m_pFindDialog = new CFindDlg();
790 m_pFindDialog->Create(this);
793 break;
794 case ID_MERGEREV:
796 CString str = pSelLogEntry->m_CommitHash.ToString();
797 if (m_HashMap[pSelLogEntry->m_CommitHash].size() > 0)
798 str = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
799 // we need an URL to complete this command, so error out if we can't get an URL
800 if(CAppUtils::Merge(&str))
802 this->Refresh();
805 break;
806 case ID_REVERTREV:
808 if(!this->RevertSelectedCommits())
809 this->Refresh();
811 break;
812 case ID_EDITNOTE:
814 CAppUtils::EditNote(pSelLogEntry);
815 this->SetItemState(FirstSelect, 0, LVIS_SELECTED);
816 this->SetItemState(FirstSelect, LVIS_SELECTED, LVIS_SELECTED);
818 break;
819 default:
820 //CMessageBox::Show(NULL,_T("Have not implemented"),_T("TortoiseGit"),MB_OK);
821 break;
822 #if 0
824 case ID_BLAMECOMPARE:
826 //user clicked on the menu item "compare with working copy"
827 //now first get the revision which is selected
828 if (PromptShown())
830 GitDiff diff(this, this->m_hWnd, true);
831 diff.SetHEADPeg(m_LogRevision);
832 diff.ShowCompare(m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), false, true);
834 else
835 CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), m_LogRevision, false, false, true);
837 break;
838 case ID_BLAMETWO:
840 //user clicked on the menu item "compare and blame revisions"
841 if (PromptShown())
843 GitDiff diff(this, this->m_hWnd, true);
844 diff.SetHEADPeg(m_LogRevision);
845 diff.ShowCompare(CTGitPath(pathURL), revSelected2, CTGitPath(pathURL), revSelected, GitRev(), false, true);
847 else
848 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revSelected2, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
850 break;
851 case ID_BLAMEWITHPREVIOUS:
853 //user clicked on the menu item "Compare and Blame with previous revision"
854 if (PromptShown())
856 GitDiff diff(this, this->m_hWnd, true);
857 diff.SetHEADPeg(m_LogRevision);
858 diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), false, true);
860 else
861 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
863 break;
865 case ID_OPENWITH:
866 bOpenWith = true;
867 case ID_OPEN:
869 CProgressDlg progDlg;
870 progDlg.SetTitle(IDS_APPNAME);
871 progDlg.SetAnimation(IDR_DOWNLOAD);
872 CString sInfoLine;
873 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, m_path.GetWinPath(), (LPCTSTR)revSelected.ToString());
874 progDlg.SetLine(1, sInfoLine, true);
875 SetAndClearProgressInfo(&progDlg);
876 progDlg.ShowModeless(m_hWnd);
877 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, m_path, revSelected);
878 bool bSuccess = true;
879 if (!Cat(m_path, GitRev(GitRev::REV_HEAD), revSelected, tempfile))
881 bSuccess = false;
882 // try again, but with the selected revision as the peg revision
883 if (!Cat(m_path, revSelected, revSelected, tempfile))
885 progDlg.Stop();
886 SetAndClearProgressInfo((HWND)NULL);
887 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
888 EnableOKButton();
889 break;
891 bSuccess = true;
893 if (bSuccess)
895 progDlg.Stop();
896 SetAndClearProgressInfo((HWND)NULL);
897 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
898 int ret = 0;
899 if (!bOpenWith)
900 ret = (int)ShellExecute(this->m_hWnd, NULL, tempfile.GetWinPath(), NULL, NULL, SW_SHOWNORMAL);
901 if ((ret <= HINSTANCE_ERROR)||bOpenWith)
903 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
904 cmd += tempfile.GetWinPathString() + _T(" ");
905 CAppUtils::LaunchApplication(cmd, NULL, false);
909 break;
910 case ID_BLAME:
912 CBlameDlg dlg;
913 dlg.EndRev = revSelected;
914 if (dlg.DoModal() == IDOK)
916 CBlame blame;
917 CString tempfile;
918 CString logfile;
919 tempfile = blame.BlameToTempFile(m_path, dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, _T(""), dlg.m_bIncludeMerge, TRUE, TRUE);
920 if (!tempfile.IsEmpty())
922 if (dlg.m_bTextView)
924 //open the default text editor for the result file
925 CAppUtils::StartTextViewer(tempfile);
927 else
929 CString sParams = _T("/path:\"") + m_path.GetGitPathString() + _T("\" ");
930 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(m_path.GetFileOrDirectoryName()),sParams))
932 break;
936 else
938 CMessageBox::Show(this->m_hWnd, blame.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
942 break;
943 case ID_UPDATE:
945 CString sCmd;
946 CString url = _T("tgit:")+pathURL;
947 sCmd.Format(_T("%s /command:update /path:\"%s\" /rev:%ld"),
948 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
949 (LPCTSTR)m_path.GetWinPath(), (LONG)revSelected);
950 CAppUtils::LaunchApplication(sCmd, NULL, false);
952 break;
954 case ID_EDITLOG:
956 EditLogMessage(selIndex);
958 break;
959 case ID_EDITAUTHOR:
961 EditAuthor(selEntries);
963 break;
964 case ID_REVPROPS:
966 CEditPropertiesDlg dlg;
967 dlg.SetProjectProperties(&m_ProjectProperties);
968 CTGitPathList escapedlist;
969 dlg.SetPathList(CTGitPathList(CTGitPath(pathURL)));
970 dlg.SetRevision(revSelected);
971 dlg.RevProps(true);
972 dlg.DoModal();
974 break;
976 case ID_EXPORT:
978 CString sCmd;
979 sCmd.Format(_T("%s /command:export /path:\"%s\" /revision:%ld"),
980 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
981 (LPCTSTR)pathURL, (LONG)revSelected);
982 CAppUtils::LaunchApplication(sCmd, NULL, false);
984 break;
985 case ID_VIEWREV:
987 CString url = m_ProjectProperties.sWebViewerRev;
988 url = GetAbsoluteUrlFromRelativeUrl(url);
989 url.Replace(_T("%REVISION%"), revSelected.ToString());
990 if (!url.IsEmpty())
991 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
993 break;
994 case ID_VIEWPATHREV:
996 CString relurl = pathURL;
997 CString sRoot = GetRepositoryRoot(CTGitPath(relurl));
998 relurl = relurl.Mid(sRoot.GetLength());
999 CString url = m_ProjectProperties.sWebViewerPathRev;
1000 url = GetAbsoluteUrlFromRelativeUrl(url);
1001 url.Replace(_T("%REVISION%"), revSelected.ToString());
1002 url.Replace(_T("%PATH%"), relurl);
1003 if (!url.IsEmpty())
1004 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1006 break;
1007 #endif
1009 } // switch (cmd)
1011 theApp.DoWaitCursor(-1);
1014 void CGitLogList::SetSelectedAction(int action)
1016 POSITION pos = GetFirstSelectedItemPosition();
1017 int index;
1018 while(pos)
1020 index = GetNextSelectedItem(pos);
1021 ((GitRev*)m_arShownList[index])->GetAction(this) = action;
1022 CRect rect;
1023 this->GetItemRect(index,&rect,LVIR_BOUNDS);
1024 this->InvalidateRect(rect);
1028 void CGitLogList::ShiftSelectedAction()
1030 POSITION pos = GetFirstSelectedItemPosition();
1031 int index;
1032 while(pos)
1034 index = GetNextSelectedItem(pos);
1035 int action = ((GitRev*)m_arShownList[index])->GetAction(this);
1036 switch (action)
1038 case CTGitPath::LOGACTIONS_REBASE_PICK:
1039 action = CTGitPath::LOGACTIONS_REBASE_SKIP;
1040 break;
1041 case CTGitPath::LOGACTIONS_REBASE_SKIP:
1042 action= CTGitPath::LOGACTIONS_REBASE_EDIT;
1043 break;
1044 case CTGitPath::LOGACTIONS_REBASE_EDIT:
1045 action = CTGitPath::LOGACTIONS_REBASE_SQUASH;
1046 break;
1047 case CTGitPath::LOGACTIONS_REBASE_SQUASH:
1048 action= CTGitPath::LOGACTIONS_REBASE_PICK;
1049 break;
1051 ((GitRev*)m_arShownList[index])->GetAction(this) = action;
1052 CRect rect;
1053 this->GetItemRect(index, &rect, LVIR_BOUNDS);
1054 this->InvalidateRect(rect);