Moved some more strings to resources
[TortoiseGit.git] / src / TortoiseProc / GitLogListAction.cpp
blob5b836b38100b8ed894e5d57c8e3df46254c77b75
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"
61 #include "../TGitCache/CacheInterface.h"
63 IMPLEMENT_DYNAMIC(CGitLogList, CHintListCtrl)
65 int CGitLogList::RevertSelectedCommits()
67 CSysProgressDlg progress;
68 int ret = -1;
70 #if 0
71 if(!g_Git.CheckCleanWorkTree())
73 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
76 #endif
78 if (progress.IsValid() && (this->GetSelectedCount() > 1) )
80 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_REVERTCOMMIT)));
81 progress.SetAnimation(IDR_MOVEANI);
82 progress.SetTime(true);
83 progress.ShowModeless(this);
86 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
88 POSITION pos = GetFirstSelectedItemPosition();
89 int i=0;
90 while(pos)
92 int index = GetNextSelectedItem(pos);
93 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(index));
95 if (progress.IsValid() && (this->GetSelectedCount() > 1) )
97 CString temp;
98 temp.Format(IDS_PROC_REVERTCOMMIT, r1->m_CommitHash.ToString());
99 progress.FormatPathLine(1, temp);
100 progress.FormatPathLine(2, _T("%s"), r1->GetSubject());
101 progress.SetProgress(i, this->GetSelectedCount());
103 i++;
105 if(r1->m_CommitHash.IsEmpty())
106 continue;
108 CString cmd, output;
109 cmd.Format(_T("git.exe revert --no-edit --no-commit %s"), r1->m_CommitHash.ToString());
110 if (g_Git.Run(cmd, &output, CP_UTF8))
112 CString str;
113 str.LoadString(IDS_SVNACTION_FAILEDREVERT);
114 str += _T("\n");
115 str+= cmd;
116 str+= _T("\n")+output;
117 if( GetSelectedCount() == 1)
118 CMessageBox::Show(NULL, str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
119 else
121 if(CMessageBox::Show(NULL, str, _T("TortoiseGit"),2 , IDI_ERROR, CString(MAKEINTRESOURCE(IDS_SKIPBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
123 return ret;
127 else
129 ret =0;
132 if ((progress.IsValid())&&(progress.HasUserCancelled()))
133 break;
135 return ret;
137 int CGitLogList::CherryPickFrom(CString from, CString to)
139 CLogDataVector logs(&m_LogCache);
140 if(logs.ParserFromLog(NULL,-1,0,&from,&to))
141 return -1;
143 if(logs.size() == 0)
144 return 0;
146 CSysProgressDlg progress;
147 if (progress.IsValid())
149 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_CHERRYPICK)));
150 progress.SetAnimation(IDR_MOVEANI);
151 progress.SetTime(true);
152 progress.ShowModeless(this);
155 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
157 for(int i=logs.size()-1;i>=0;i--)
159 if (progress.IsValid())
161 CString temp;
162 temp.Format(IDS_PROC_PICK, logs.GetGitRevAt(i).m_CommitHash.ToString());
163 progress.FormatPathLine(1, temp);
164 progress.FormatPathLine(2, _T("%s"), logs.GetGitRevAt(i).GetSubject());
165 progress.SetProgress(logs.size()-i, logs.size());
167 if ((progress.IsValid())&&(progress.HasUserCancelled()))
169 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED))));
170 return -1;
172 CString cmd,out;
173 cmd.Format(_T("git.exe cherry-pick %s"),logs.GetGitRevAt(i).m_CommitHash.ToString());
174 out.Empty();
175 if(g_Git.Run(cmd,&out,CP_UTF8))
177 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_CHERRYPICKFAILED)) + _T(":\r\n\r\n") + out));
178 return -1;
182 return 0;
185 void CGitLogList::ContextMenuAction(int cmd,int FirstSelect, int LastSelect, CMenu *popmenu)
187 POSITION pos = GetFirstSelectedItemPosition();
188 int indexNext = GetNextSelectedItem(pos);
189 if (indexNext < 0)
190 return;
192 GitRev* pSelLogEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(indexNext));
194 theApp.DoWaitCursor(1);
195 switch (cmd&0xFFFF)
197 case ID_COMMIT:
199 CTGitPathList pathlist;
200 CTGitPathList selectedlist;
201 pathlist.AddPath(this->m_Path);
202 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
203 CString str;
204 CAppUtils::Commit(CString(),false,str,
205 pathlist,selectedlist,bSelectFilesForCommit);
206 //this->Refresh();
207 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH,0);
209 break;
210 case ID_GNUDIFF1: // compare with WC, unified
212 CString tempfile=GetTempFile();
213 CString command;
214 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
215 if(!r1->m_CommitHash.IsEmpty())
217 CString merge;
218 CString hash2;
219 cmd >>= 16;
220 if( (cmd&0xFFFF) == 0xFFFF)
222 merge=_T("-m");
224 else if((cmd&0xFFFF) == 0xFFFE)
226 merge=_T("-c");
228 else
230 if(cmd > r1->m_ParentHash.size())
232 CString str;
233 str.Format(IDS_PROC_NOPARENT, cmd);
234 MessageBox(str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
235 return;
237 else
239 if(cmd>0)
240 hash2 = r1->m_ParentHash[cmd-1].ToString();
243 command.Format(_T("git.exe diff-tree %s -r -p --stat %s %s"), merge, hash2, r1->m_CommitHash.ToString());
245 else
246 command.Format(_T("git.exe diff -r -p --stat"));
248 g_Git.RunLogFile(command,tempfile);
249 CAppUtils::StartUnifiedDiffViewer(tempfile,r1->m_CommitHash.ToString().Left(6)+_T(":")+r1->GetSubject());
251 break;
253 case ID_GNUDIFF2: // compare two revisions, unified
255 CString tempfile=GetTempFile();
256 CString cmd;
257 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
258 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
260 if( r1->m_CommitHash.IsEmpty()) {
261 cmd.Format(_T("git.exe diff -r -p --stat %s"),r2->m_CommitHash.ToString());
263 else if( r2->m_CommitHash.IsEmpty()) {
264 cmd.Format(_T("git.exe diff -r -p --stat %s"),r1->m_CommitHash.ToString());
266 else
268 cmd.Format(_T("git.exe diff-tree -r -p --stat %s %s"),r2->m_CommitHash.ToString(),r1->m_CommitHash.ToString());
271 g_Git.RunLogFile(cmd,tempfile);
272 CAppUtils::StartUnifiedDiffViewer(tempfile,r2->m_CommitHash.ToString().Left(6)+_T(":")+r1->m_CommitHash.ToString().Left(6));
275 break;
277 case ID_COMPARETWO: // compare two revisions
279 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
280 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
281 CGitDiff::DiffCommit(this->m_Path, r1,r2);
284 break;
286 case ID_COMPARE: // compare revision with WC
288 GitRev * r1 = &m_wcRev;
289 GitRev * r2 = pSelLogEntry;
291 CGitDiff::DiffCommit(this->m_Path, r1,r2);
293 //user clicked on the menu item "compare with working copy"
294 //if (PromptShown())
296 // GitDiff diff(this, m_hWnd, true);
297 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
298 // diff.SetHEADPeg(m_LogRevision);
299 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
301 //else
302 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
304 break;
306 case ID_COMPAREWITHPREVIOUS:
309 CFileDiffDlg dlg;
311 if(pSelLogEntry->m_ParentHash.size()>0)
312 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
314 cmd>>=16;
315 cmd&=0xFFFF;
317 if(cmd == 0)
318 cmd=1;
320 CGitDiff::DiffCommit(this->m_Path, pSelLogEntry->m_CommitHash.ToString(),pSelLogEntry->m_ParentHash[cmd-1].ToString());
323 else
325 CMessageBox::Show(NULL, IDS_PROC_NOPREVIOUSVERSION, IDS_APPNAME, MB_OK);
327 //if (PromptShown())
329 // GitDiff diff(this, m_hWnd, true);
330 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
331 // diff.SetHEADPeg(m_LogRevision);
332 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
334 //else
335 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
337 break;
338 case ID_COPYCLIPBOARD:
340 CopySelectionToClipBoard();
342 break;
343 case ID_COPYHASH:
345 CopySelectionToClipBoard(TRUE);
347 break;
348 case ID_EXPORT:
350 CString str=pSelLogEntry->m_CommitHash.ToString();
351 CAppUtils::Export(&str);
353 break;
354 case ID_CREATE_BRANCH:
356 CString str = pSelLogEntry->m_CommitHash.ToString();
357 CAppUtils::CreateBranchTag(FALSE,&str);
358 ReloadHashMap();
359 Invalidate();
361 break;
362 case ID_CREATE_TAG:
364 CString str = pSelLogEntry->m_CommitHash.ToString();
365 CAppUtils::CreateBranchTag(TRUE,&str);
366 ReloadHashMap();
367 Invalidate();
368 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
370 break;
371 case ID_SWITCHTOREV:
373 CString str = pSelLogEntry->m_CommitHash.ToString();
374 CAppUtils::Switch(&str);
376 ReloadHashMap();
377 Invalidate();
378 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
379 break;
380 case ID_SWITCHBRANCH:
381 if(popmenu)
383 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
384 if(branch)
386 CString name;
387 if(branch->Find(_T("refs/heads/")) ==0 )
388 name = branch->Mid(11);
389 else
390 name = *branch;
392 CAppUtils::PerformSwitch(name);
394 ReloadHashMap();
395 Invalidate();
396 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
398 break;
399 case ID_RESET:
401 CString str = pSelLogEntry->m_CommitHash.ToString();
402 CAppUtils::GitReset(&str);
403 ReloadHashMap();
404 Invalidate();
406 break;
407 case ID_REBASE_PICK:
408 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_PICK);
409 break;
410 case ID_REBASE_EDIT:
411 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_EDIT);
412 break;
413 case ID_REBASE_SQUASH:
414 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_SQUASH);
415 break;
416 case ID_REBASE_SKIP:
417 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_SKIP);
418 break;
419 case ID_COMBINE_COMMIT:
421 CString head;
422 CGitHash headhash;
423 CGitHash hashFirst,hashLast;
425 int headindex=GetHeadIndex();
426 if(headindex>=0) //incase show all branch, head is not the first commits.
428 head.Format(_T("HEAD~%d"),FirstSelect-headindex);
429 hashFirst=g_Git.GetHash(head);
431 head.Format(_T("HEAD~%d"),LastSelect-headindex);
432 hashLast=g_Git.GetHash(head);
435 GitRev* pFirstEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
436 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
437 if(pFirstEntry->m_CommitHash != hashFirst || pLastEntry->m_CommitHash != hashLast)
439 CMessageBox::Show(NULL, IDS_PROC_CANNOTCOMBINE, IDS_APPNAME, MB_OK);
440 break;
443 headhash=g_Git.GetHash(_T("HEAD"));
445 if(!g_Git.CheckCleanWorkTree())
447 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
448 break;
450 CString cmd,out;
452 //Use throw to abort this process (reset back to original HEAD)
455 cmd.Format(_T("git.exe reset --hard %s"),pFirstEntry->m_CommitHash.ToString());
456 if(g_Git.Run(cmd,&out,CP_UTF8))
458 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
459 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1)) + _T("\r\n\r\n") + out));
461 cmd.Format(_T("git.exe reset --mixed %s"),hashLast.ToString());
462 if(g_Git.Run(cmd,&out,CP_UTF8))
464 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
465 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2)) + _T("\r\n\r\n")+out));
468 CTGitPathList PathList;
469 /* don't why must add --stat to get action status*/
470 /* first -z will be omitted by gitdll*/
471 if(g_Git.GetDiffPath(&PathList,&pFirstEntry->m_CommitHash,&hashLast,"-z --stat -r"))
473 CMessageBox::Show(NULL,_T("Get Diff file list error"),_T("TortoiseGit"),MB_OK);
474 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not get changed file list aborting...\r\n\r\n")+out));
477 for(int i=0;i<PathList.GetCount();i++)
479 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_ADDED)
481 cmd.Format(_T("git.exe add -- \"%s\""), PathList[i].GetGitPathString());
482 if (g_Git.Run(cmd, &out, CP_UTF8))
484 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
485 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not add new file aborting...\r\n\r\n")+out));
489 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_DELETED)
491 cmd.Format(_T("git.exe rm -- \"%s\""), PathList[i].GetGitPathString());
492 if (g_Git.Run(cmd, &out, CP_UTF8))
494 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
495 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not rm file aborting...\r\n\r\n")+out));
500 CCommitDlg dlg;
501 for(int i=FirstSelect;i<=LastSelect;i++)
503 GitRev* pRev = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
504 dlg.m_sLogMessage+=pRev->GetSubject()+_T("\n")+pRev->GetBody();
505 dlg.m_sLogMessage+=_T("\n");
507 dlg.m_bWholeProject=true;
508 dlg.m_bSelectFilesForCommit = true;
509 dlg.m_bCommitAmend=true;
510 dlg.m_bNoPostActions=true;
511 dlg.m_AmendStr=dlg.m_sLogMessage;
513 if (dlg.DoModal() == IDOK)
515 if(pFirstEntry->m_CommitHash!=headhash)
517 //Commitrange firstEntry..headhash (from top of combine to original head) needs to be 'cherry-picked'
518 //on top of new commit.
519 //Use the rebase --onto command for it.
521 //All this can be done in one step using the following command:
522 //cmd.Format(_T("git.exe format-patch --stdout --binary --full-index -k %s..%s | git am -k -3"),
523 // pFirstEntry->m_CommitHash,
524 // headhash);
525 //But I am not sure if a '|' is going to work in a CreateProcess() call.
527 //Later the progress dialog could be used to execute these steps.
529 if(CherryPickFrom(pFirstEntry->m_CommitHash.ToString(),headhash))
531 CString msg;
532 msg.Format(_T("Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n"));
533 throw std::exception(CUnicodeUtils::GetUTF8(msg));
535 #if 0
536 CString currentBranch=g_Git.GetCurrentBranch();
537 cmd.Format(_T("git.exe rebase --onto \"%s\" %s %s"),
538 currentBranch,
539 pFirstEntry->m_CommitHash,
540 headhash);
541 if(g_Git.Run(cmd,&out,CP_UTF8)!=0)
543 CString msg;
544 msg.Format(_T("Error while rebasing commits on top of combined commits. Aborting.\r\n\r\n%s"),out);
545 // CMessageBox::Show(NULL,msg,_T("TortoiseGit"),MB_OK);
546 g_Git.Run(_T("git.exe rebase --abort"),&out,CP_UTF8);
547 throw std::exception(CUnicodeUtils::GetUTF8(msg));
550 //HEAD is now on <no branch>.
551 //The following steps are to get HEAD back on the original branch and reset the branch to the new HEAD
552 //To avoid 2 working copy changes, we could use git branch -f <original branch> <hash new head>
553 //And then git checkout <original branch>
554 //But I don't know if 'git branch -f' removes tracking options. So for now, do a checkout and a reset.
556 //Store new HEAD
557 CString newHead=g_Git.GetHash(CString(_T("HEAD")));
559 //Checkout working branch
560 cmd.Format(_T("git.exe checkout -f \"%s\""),currentBranch);
561 if(g_Git.Run(cmd,&out,CP_UTF8))
562 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not checkout original branch. Aborting...\r\n\r\n")+out));
564 //Reset to new HEAD
565 cmd.Format(_T("git.exe reset --hard %s"),newHead);
566 if(g_Git.Run(cmd,&out,CP_UTF8))
567 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not reset to new head. Aborting...\r\n\r\n")+out));
568 #endif
571 else
572 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED))));
574 catch(std::exception& e)
576 CMessageBox::Show(NULL, CUnicodeUtils::GetUnicode(CStringA(e.what())), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
577 cmd.Format(_T("git.exe reset --hard %s"),headhash.ToString());
578 out.Empty();
579 if(g_Git.Run(cmd,&out,CP_UTF8))
581 CMessageBox::Show(NULL, CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD)) + _T("\r\n\r\n") + out, _T("TortoiseGit"), MB_OK);
584 Refresh();
586 break;
588 case ID_CHERRY_PICK:
589 if(!g_Git.CheckCleanWorkTree())
591 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
594 else
596 CRebaseDlg dlg;
597 dlg.m_IsCherryPick = TRUE;
598 dlg.m_Upstream = this->m_CurrentBranch;
599 POSITION pos = GetFirstSelectedItemPosition();
600 while(pos)
602 int indexNext = GetNextSelectedItem(pos);
603 dlg.m_CommitList.m_logEntries.push_back( ((GitRev*)m_arShownList[indexNext])->m_CommitHash );
604 dlg.m_CommitList.m_LogCache.m_HashMap[((GitRev*)m_arShownList[indexNext])->m_CommitHash]=*(GitRev*)m_arShownList[indexNext];
605 dlg.m_CommitList.m_logEntries.GetGitRevAt(dlg.m_CommitList.m_logEntries.size()-1).GetAction(this) |= CTGitPath::LOGACTIONS_REBASE_PICK;
608 if(dlg.DoModal() == IDOK)
610 Refresh();
613 break;
614 case ID_REBASE_TO_VERSION:
615 if(!g_Git.CheckCleanWorkTree())
617 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
620 else
622 CRebaseDlg dlg;
623 dlg.m_Upstream = pSelLogEntry->m_CommitHash;
625 if(dlg.DoModal() == IDOK)
627 Refresh();
631 break;
633 case ID_STASH_SAVE:
634 if (CAppUtils::StashSave())
635 Refresh();
636 break;
638 case ID_STASH_POP:
639 if (CAppUtils::StashPop())
640 Refresh();
641 break;
643 case ID_STASH_LIST:
644 CAppUtils::RunTortoiseProc(_T("/command:reflog /ref:refs/stash"));
645 break;
647 case ID_REFLOG_STASH_APPLY:
648 CAppUtils::StashApply(pSelLogEntry->m_Ref);
649 break;
651 case ID_REFLOG_DEL:
653 CString str;
654 if (GetSelectedCount() > 1)
655 str.Format(IDS_PROC_DELETENREFS, GetSelectedCount());
656 else
657 str.Format(IDS_PROC_DELETEREF, pSelLogEntry->m_Ref);
659 if (CMessageBox::Show(NULL, str, _T("TortoiseGit"), 1, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
660 return;
662 POSITION pos = GetFirstSelectedItemPosition();
663 while (pos)
665 CString ref = ((GitRev *)m_arShownList[GetNextSelectedItem(pos)])->m_Ref;
666 if (ref.Find(_T("refs/")) == 0)
667 ref = ref.Mid(5);
668 int refpos = ref.ReverseFind('{');
669 if (refpos > 0 && ref.Mid(refpos, 2) != _T("@{"))
670 ref = ref.Left(refpos) + _T("@")+ ref.Mid(refpos);
672 CString cmd, out;
673 if (ref.Find(_T("stash")) == 0)
674 cmd.Format(_T("git.exe stash drop %s"), ref);
675 else
676 cmd.Format(_T("git.exe reflog delete %s"), ref);
678 if (g_Git.Run(cmd, &out, CP_UTF8))
679 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
681 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
684 break;
685 case ID_LOG:
687 CString cmd = _T("/command:log");
688 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
689 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
690 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
691 CAppUtils::RunTortoiseProc(cmd);
693 break;
694 case ID_CREATE_PATCH:
696 int select=this->GetSelectedCount();
697 CString cmd = _T("/command:formatpatch");
698 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
700 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
701 GitRev * r2 = NULL;
702 if(select == 1)
704 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString();
706 else
708 r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
709 if( this->m_IsOldFirst )
711 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString()+_T("~1");
712 cmd += _T(" /endrev:")+r2->m_CommitHash.ToString();
715 else
717 cmd += _T(" /startrev:")+r2->m_CommitHash.ToString()+_T("~1");
718 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
723 CAppUtils::RunTortoiseProc(cmd);
725 break;
726 case ID_PUSH:
728 CString guessAssociatedBranch;
729 if (m_HashMap[pSelLogEntry->m_CommitHash].size() > 0)
730 guessAssociatedBranch = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
731 if (CAppUtils::Push(guessAssociatedBranch))
732 Refresh();
734 break;
735 case ID_FETCH:
737 if (CAppUtils::Fetch(_T(""), true))
738 Refresh();
740 break;
741 case ID_DELETE:
743 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
744 if (!branch)
746 CMessageBox::Show(NULL,IDS_ERROR_NOREF,IDS_APPNAME,MB_OK|MB_ICONERROR);
747 return;
749 CString shortname;
750 CString cmd;
751 if (this->GetShortName(*branch, shortname, _T("refs/remotes/")))
753 CString msg;
754 msg.Format(IDS_PROC_DELETEREMOTEBRANCH, *branch);
755 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)));
756 if (result == 1)
758 CString remoteName = shortname.Left(shortname.Find('/'));
759 shortname = shortname.Mid(shortname.Find('/') + 1);
760 if(CAppUtils::IsSSHPutty())
761 CAppUtils::LaunchPAgent(NULL, &remoteName);
763 cmd.Format(L"git.exe push \"%s\" :%s", remoteName, shortname);
765 else if (result == 2)
766 cmd.Format(_T("git.exe branch -r -D -- %s"), shortname);
767 else
768 return;
770 else if (this->GetShortName(*branch, shortname, _T("refs/stash")))
772 if (CMessageBox::Show(NULL, IDS_PROC_DELETEALLSTASH, IDS_APPNAME, 2, IDI_QUESTION, IDS_DELETEBUTTON, IDS_ABORTBUTTON) == 1)
773 cmd.Format(_T("git.exe stash clear"));
774 else
775 return;
777 else
779 CString msg;
780 msg.Format(IDS_PROC_DELETEBRANCHTAG, *branch);
781 if (CMessageBox::Show(NULL, msg, _T("TortoiseGit"), 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
783 if(this->GetShortName(*branch,shortname,_T("refs/heads/")))
785 cmd.Format(_T("git.exe branch -D -- %s"),shortname);
788 if(this->GetShortName(*branch,shortname,_T("refs/tags/")))
790 cmd.Format(_T("git.exe tag -d -- %s"),shortname);
794 if (!cmd.IsEmpty())
796 CString out;
797 if(g_Git.Run(cmd,&out,CP_UTF8))
799 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
801 this->ReloadHashMap();
802 CRect rect;
803 this->GetItemRect(FirstSelect,&rect,LVIR_BOUNDS);
804 this->InvalidateRect(rect);
807 break;
809 case ID_FINDENTRY:
811 m_nSearchIndex = GetSelectionMark();
812 if (m_nSearchIndex < 0)
813 m_nSearchIndex = 0;
814 if (m_pFindDialog)
816 break;
818 else
820 m_pFindDialog = new CFindDlg();
821 m_pFindDialog->Create(this);
824 break;
825 case ID_MERGEREV:
827 CString str = pSelLogEntry->m_CommitHash.ToString();
828 if (m_HashMap[pSelLogEntry->m_CommitHash].size() > 0)
829 str = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
830 // we need an URL to complete this command, so error out if we can't get an URL
831 if(CAppUtils::Merge(&str))
833 this->Refresh();
836 break;
837 case ID_REVERTREV:
839 if(!this->RevertSelectedCommits())
840 this->Refresh();
842 break;
843 case ID_EDITNOTE:
845 CAppUtils::EditNote(pSelLogEntry);
846 this->SetItemState(FirstSelect, 0, LVIS_SELECTED);
847 this->SetItemState(FirstSelect, LVIS_SELECTED, LVIS_SELECTED);
849 break;
850 default:
851 //CMessageBox::Show(NULL,_T("Have not implemented"),_T("TortoiseGit"),MB_OK);
852 break;
853 #if 0
855 case ID_BLAMECOMPARE:
857 //user clicked on the menu item "compare with working copy"
858 //now first get the revision which is selected
859 if (PromptShown())
861 GitDiff diff(this, this->m_hWnd, true);
862 diff.SetHEADPeg(m_LogRevision);
863 diff.ShowCompare(m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), false, true);
865 else
866 CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), m_LogRevision, false, false, true);
868 break;
869 case ID_BLAMETWO:
871 //user clicked on the menu item "compare and blame revisions"
872 if (PromptShown())
874 GitDiff diff(this, this->m_hWnd, true);
875 diff.SetHEADPeg(m_LogRevision);
876 diff.ShowCompare(CTGitPath(pathURL), revSelected2, CTGitPath(pathURL), revSelected, GitRev(), false, true);
878 else
879 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revSelected2, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
881 break;
882 case ID_BLAMEWITHPREVIOUS:
884 //user clicked on the menu item "Compare and Blame with previous revision"
885 if (PromptShown())
887 GitDiff diff(this, this->m_hWnd, true);
888 diff.SetHEADPeg(m_LogRevision);
889 diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), false, true);
891 else
892 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
894 break;
896 case ID_OPENWITH:
897 bOpenWith = true;
898 case ID_OPEN:
900 CProgressDlg progDlg;
901 progDlg.SetTitle(IDS_APPNAME);
902 progDlg.SetAnimation(IDR_DOWNLOAD);
903 CString sInfoLine;
904 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, m_path.GetWinPath(), (LPCTSTR)revSelected.ToString());
905 progDlg.SetLine(1, sInfoLine, true);
906 SetAndClearProgressInfo(&progDlg);
907 progDlg.ShowModeless(m_hWnd);
908 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, m_path, revSelected);
909 bool bSuccess = true;
910 if (!Cat(m_path, GitRev(GitRev::REV_HEAD), revSelected, tempfile))
912 bSuccess = false;
913 // try again, but with the selected revision as the peg revision
914 if (!Cat(m_path, revSelected, revSelected, tempfile))
916 progDlg.Stop();
917 SetAndClearProgressInfo((HWND)NULL);
918 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
919 EnableOKButton();
920 break;
922 bSuccess = true;
924 if (bSuccess)
926 progDlg.Stop();
927 SetAndClearProgressInfo((HWND)NULL);
928 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
929 int ret = 0;
930 if (!bOpenWith)
931 ret = (int)ShellExecute(this->m_hWnd, NULL, tempfile.GetWinPath(), NULL, NULL, SW_SHOWNORMAL);
932 if ((ret <= HINSTANCE_ERROR)||bOpenWith)
934 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
935 cmd += tempfile.GetWinPathString() + _T(" ");
936 CAppUtils::LaunchApplication(cmd, NULL, false);
940 break;
941 case ID_BLAME:
943 CBlameDlg dlg;
944 dlg.EndRev = revSelected;
945 if (dlg.DoModal() == IDOK)
947 CBlame blame;
948 CString tempfile;
949 CString logfile;
950 tempfile = blame.BlameToTempFile(m_path, dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, _T(""), dlg.m_bIncludeMerge, TRUE, TRUE);
951 if (!tempfile.IsEmpty())
953 if (dlg.m_bTextView)
955 //open the default text editor for the result file
956 CAppUtils::StartTextViewer(tempfile);
958 else
960 CString sParams = _T("/path:\"") + m_path.GetGitPathString() + _T("\" ");
961 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(m_path.GetFileOrDirectoryName()),sParams))
963 break;
967 else
969 CMessageBox::Show(this->m_hWnd, blame.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
973 break;
974 case ID_UPDATE:
976 CString sCmd;
977 CString url = _T("tgit:")+pathURL;
978 sCmd.Format(_T("%s /command:update /path:\"%s\" /rev:%ld"),
979 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
980 (LPCTSTR)m_path.GetWinPath(), (LONG)revSelected);
981 CAppUtils::LaunchApplication(sCmd, NULL, false);
983 break;
985 case ID_EDITLOG:
987 EditLogMessage(selIndex);
989 break;
990 case ID_EDITAUTHOR:
992 EditAuthor(selEntries);
994 break;
995 case ID_REVPROPS:
997 CEditPropertiesDlg dlg;
998 dlg.SetProjectProperties(&m_ProjectProperties);
999 CTGitPathList escapedlist;
1000 dlg.SetPathList(CTGitPathList(CTGitPath(pathURL)));
1001 dlg.SetRevision(revSelected);
1002 dlg.RevProps(true);
1003 dlg.DoModal();
1005 break;
1007 case ID_EXPORT:
1009 CString sCmd;
1010 sCmd.Format(_T("%s /command:export /path:\"%s\" /revision:%ld"),
1011 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
1012 (LPCTSTR)pathURL, (LONG)revSelected);
1013 CAppUtils::LaunchApplication(sCmd, NULL, false);
1015 break;
1016 case ID_VIEWREV:
1018 CString url = m_ProjectProperties.sWebViewerRev;
1019 url = GetAbsoluteUrlFromRelativeUrl(url);
1020 url.Replace(_T("%REVISION%"), revSelected.ToString());
1021 if (!url.IsEmpty())
1022 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1024 break;
1025 case ID_VIEWPATHREV:
1027 CString relurl = pathURL;
1028 CString sRoot = GetRepositoryRoot(CTGitPath(relurl));
1029 relurl = relurl.Mid(sRoot.GetLength());
1030 CString url = m_ProjectProperties.sWebViewerPathRev;
1031 url = GetAbsoluteUrlFromRelativeUrl(url);
1032 url.Replace(_T("%REVISION%"), revSelected.ToString());
1033 url.Replace(_T("%PATH%"), relurl);
1034 if (!url.IsEmpty())
1035 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1037 break;
1038 #endif
1040 } // switch (cmd)
1042 theApp.DoWaitCursor(-1);
1045 void CGitLogList::SetSelectedAction(int action)
1047 POSITION pos = GetFirstSelectedItemPosition();
1048 int index;
1049 while(pos)
1051 index = GetNextSelectedItem(pos);
1052 ((GitRev*)m_arShownList[index])->GetAction(this) = action;
1053 CRect rect;
1054 this->GetItemRect(index,&rect,LVIR_BOUNDS);
1055 this->InvalidateRect(rect);
1059 void CGitLogList::ShiftSelectedAction()
1061 POSITION pos = GetFirstSelectedItemPosition();
1062 int index;
1063 while(pos)
1065 index = GetNextSelectedItem(pos);
1066 int action = ((GitRev*)m_arShownList[index])->GetAction(this);
1067 switch (action)
1069 case CTGitPath::LOGACTIONS_REBASE_PICK:
1070 action = CTGitPath::LOGACTIONS_REBASE_SKIP;
1071 break;
1072 case CTGitPath::LOGACTIONS_REBASE_SKIP:
1073 action= CTGitPath::LOGACTIONS_REBASE_EDIT;
1074 break;
1075 case CTGitPath::LOGACTIONS_REBASE_EDIT:
1076 action = CTGitPath::LOGACTIONS_REBASE_SQUASH;
1077 break;
1078 case CTGitPath::LOGACTIONS_REBASE_SQUASH:
1079 action= CTGitPath::LOGACTIONS_REBASE_PICK;
1080 break;
1082 ((GitRev*)m_arShownList[index])->GetAction(this) = action;
1083 CRect rect;
1084 this->GetItemRect(index, &rect, LVIR_BOUNDS);
1085 this->InvalidateRect(rect);