CommitDlg: Disable amend buttons while refreshing
[TortoiseGit.git] / src / TortoiseProc / GitLogListAction.cpp
blob47a2469ea2eedd9b4cba57a6a89187fc3b0e4c4f
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2012 - TortoiseGit
4 // Copyright (C) 2011-2012 - 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 "VssStyle.h"
28 #include "IconMenu.h"
29 // CGitLogList
30 #include "cursor.h"
31 #include "InputDlg.h"
32 #include "GITProgressDlg.h"
33 #include "ProgressDlg.h"
34 #include "SysProgressDlg.h"
35 //#include "RepositoryBrowser.h"
36 //#include "CopyDlg.h"
37 //#include "StatGraphDlg.h"
38 #include "Logdlg.h"
39 #include "MessageBox.h"
40 #include "Registry.h"
41 #include "AppUtils.h"
42 #include "PathUtils.h"
43 #include "StringUtils.h"
44 #include "UnicodeUtils.h"
45 #include "TempFile.h"
46 //#include "GitInfo.h"
47 //#include "GitDiff.h"
48 //#include "RevisionRangeDlg.h"
49 //#include "BrowseFolder.h"
50 //#include "BlameDlg.h"
51 //#include "Blame.h"
52 //#include "GitHelpers.h"
53 #include "GitStatus.h"
54 //#include "LogDlgHelper.h"
55 //#include "CachedLogInfo.h"
56 //#include "RepositoryInfo.h"
57 //#include "EditPropertiesDlg.h"
58 #include "FileDiffDlg.h"
59 #include "CommitDlg.h"
60 #include "RebaseDlg.h"
61 #include "GitDiff.h"
62 #include "../TGitCache/CacheInterface.h"
64 IMPLEMENT_DYNAMIC(CGitLogList, CHintListCtrl)
66 int CGitLogList::RevertSelectedCommits()
68 CSysProgressDlg progress;
69 int ret = -1;
71 #if 0
72 if(!g_Git.CheckCleanWorkTree())
74 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
77 #endif
79 if (progress.IsValid() && (this->GetSelectedCount() > 1) )
81 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_REVERTCOMMIT)));
82 progress.SetAnimation(IDR_MOVEANI);
83 progress.SetTime(true);
84 progress.ShowModeless(this);
87 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
89 POSITION pos = GetFirstSelectedItemPosition();
90 int i=0;
91 while(pos)
93 int index = GetNextSelectedItem(pos);
94 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(index));
96 if (progress.IsValid() && (this->GetSelectedCount() > 1) )
98 CString temp;
99 temp.Format(IDS_PROC_REVERTCOMMIT, r1->m_CommitHash.ToString());
100 progress.FormatPathLine(1, temp);
101 progress.FormatPathLine(2, _T("%s"), r1->GetSubject());
102 progress.SetProgress(i, this->GetSelectedCount());
104 i++;
106 if(r1->m_CommitHash.IsEmpty())
107 continue;
109 CString cmd, output;
110 cmd.Format(_T("git.exe revert --no-edit --no-commit %s"), r1->m_CommitHash.ToString());
111 if (g_Git.Run(cmd, &output, CP_UTF8))
113 CString str;
114 str.LoadString(IDS_SVNACTION_FAILEDREVERT);
115 str += _T("\n");
116 str+= cmd;
117 str+= _T("\n")+output;
118 if( GetSelectedCount() == 1)
119 CMessageBox::Show(NULL, str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
120 else
122 if(CMessageBox::Show(NULL, str, _T("TortoiseGit"),2 , IDI_ERROR, CString(MAKEINTRESOURCE(IDS_SKIPBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
124 return ret;
128 else
130 ret =0;
133 if ((progress.IsValid())&&(progress.HasUserCancelled()))
134 break;
136 return ret;
138 int CGitLogList::CherryPickFrom(CString from, CString to)
140 CLogDataVector logs(&m_LogCache);
141 if(logs.ParserFromLog(NULL,-1,0,&from,&to))
142 return -1;
144 if(logs.size() == 0)
145 return 0;
147 CSysProgressDlg progress;
148 if (progress.IsValid())
150 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_CHERRYPICK)));
151 progress.SetAnimation(IDR_MOVEANI);
152 progress.SetTime(true);
153 progress.ShowModeless(this);
156 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
158 for(int i=logs.size()-1;i>=0;i--)
160 if (progress.IsValid())
162 CString temp;
163 temp.Format(IDS_PROC_PICK, logs.GetGitRevAt(i).m_CommitHash.ToString());
164 progress.FormatPathLine(1, temp);
165 progress.FormatPathLine(2, _T("%s"), logs.GetGitRevAt(i).GetSubject());
166 progress.SetProgress(logs.size()-i, logs.size());
168 if ((progress.IsValid())&&(progress.HasUserCancelled()))
170 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED))));
171 return -1;
173 CString cmd,out;
174 cmd.Format(_T("git.exe cherry-pick %s"),logs.GetGitRevAt(i).m_CommitHash.ToString());
175 out.Empty();
176 if(g_Git.Run(cmd,&out,CP_UTF8))
178 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_CHERRYPICKFAILED)) + _T(":\r\n\r\n") + out));
179 return -1;
183 return 0;
186 void CGitLogList::ContextMenuAction(int cmd,int FirstSelect, int LastSelect, CMenu *popmenu)
188 POSITION pos = GetFirstSelectedItemPosition();
189 int indexNext = GetNextSelectedItem(pos);
190 if (indexNext < 0)
191 return;
193 GitRev* pSelLogEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(indexNext));
195 theApp.DoWaitCursor(1);
196 switch (cmd&0xFFFF)
198 case ID_COMMIT:
200 CTGitPathList pathlist;
201 CTGitPathList selectedlist;
202 pathlist.AddPath(this->m_Path);
203 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
204 CString str;
205 CAppUtils::Commit(CString(),false,str,
206 pathlist,selectedlist,bSelectFilesForCommit);
207 //this->Refresh();
208 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH,0);
210 break;
211 case ID_GNUDIFF1: // compare with WC, unified
213 CString tempfile=GetTempFile();
214 CString command;
215 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
216 if(!r1->m_CommitHash.IsEmpty())
218 CString merge;
219 CString hash2;
220 cmd >>= 16;
221 if( (cmd&0xFFFF) == 0xFFFF)
223 merge=_T("-m");
225 else if((cmd&0xFFFF) == 0xFFFE)
227 merge=_T("-c");
229 else
231 if(cmd > r1->m_ParentHash.size())
233 CString str;
234 str.Format(IDS_PROC_NOPARENT, cmd);
235 MessageBox(str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
236 return;
238 else
240 if(cmd>0)
241 hash2 = r1->m_ParentHash[cmd-1].ToString();
244 command.Format(_T("git.exe diff-tree %s -r -p --stat %s %s"), merge, hash2, r1->m_CommitHash.ToString());
246 else
247 command.Format(_T("git.exe diff -r -p --stat"));
249 g_Git.RunLogFile(command,tempfile);
250 CAppUtils::StartUnifiedDiffViewer(tempfile, r1->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()) + _T(":") + r1->GetSubject());
252 break;
254 case ID_GNUDIFF2: // compare two revisions, unified
256 CString tempfile=GetTempFile();
257 CString cmd;
258 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
259 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
261 if( r1->m_CommitHash.IsEmpty()) {
262 cmd.Format(_T("git.exe diff -r -p --stat %s"),r2->m_CommitHash.ToString());
264 else if( r2->m_CommitHash.IsEmpty()) {
265 cmd.Format(_T("git.exe diff -r -p --stat %s"),r1->m_CommitHash.ToString());
267 else
269 cmd.Format(_T("git.exe diff-tree -r -p --stat %s %s"),r2->m_CommitHash.ToString(),r1->m_CommitHash.ToString());
272 g_Git.RunLogFile(cmd,tempfile);
273 CAppUtils::StartUnifiedDiffViewer(tempfile, r2->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()) + _T(":") + r1->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()));
276 break;
278 case ID_COMPARETWO: // compare two revisions
280 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
281 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
282 if (m_Path.IsDirectory() || ! m_ShowMask & CGit::LOG_INFO_FOLLOW)
283 CGitDiff::DiffCommit(this->m_Path, r1,r2);
284 else
286 CString path1 = m_Path.GetGitPathString();
287 // start with 1 (0 = working copy changes)
288 for (int i = 1; i < FirstSelect; i++)
290 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
291 CTGitPathList list = first->GetFiles(NULL);
292 CTGitPath * file = list.LookForGitPath(path1);
293 if (file && !file->GetGitOldPathString().IsEmpty())
294 path1 = file->GetGitOldPathString();
296 CString path2 = path1;
297 for (int i = FirstSelect; i < LastSelect; i++)
299 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
300 CTGitPathList list = first->GetFiles(NULL);
301 CTGitPath * file = list.LookForGitPath(path2);
302 if (file && !file->GetGitOldPathString().IsEmpty())
303 path2 = file->GetGitOldPathString();
305 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), r1, r2);
309 break;
311 case ID_COMPARE: // compare revision with WC
313 GitRev * r1 = &m_wcRev;
314 GitRev * r2 = pSelLogEntry;
316 if (m_Path.IsDirectory() || ! m_ShowMask & CGit::LOG_INFO_FOLLOW)
317 CGitDiff::DiffCommit(this->m_Path, r1,r2);
318 else
320 CString path1 = m_Path.GetGitPathString();
321 // start with 1 (0 = working copy changes)
322 for (int i = 1; i < FirstSelect; i++)
324 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
325 CTGitPathList list = first->GetFiles(NULL);
326 CTGitPath * file = list.LookForGitPath(path1);
327 if (file && !file->GetGitOldPathString().IsEmpty())
328 path1 = file->GetGitOldPathString();
330 CGitDiff::DiffCommit(m_Path, CTGitPath(path1), r1, r2);
333 //user clicked on the menu item "compare with working copy"
334 //if (PromptShown())
336 // GitDiff diff(this, m_hWnd, true);
337 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
338 // diff.SetHEADPeg(m_LogRevision);
339 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
341 //else
342 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
344 break;
346 case ID_COMPAREWITHPREVIOUS:
348 CFileDiffDlg dlg;
350 if(pSelLogEntry->m_ParentHash.size()>0)
351 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
353 cmd>>=16;
354 cmd&=0xFFFF;
356 if(cmd == 0)
357 cmd=1;
359 if (m_Path.IsDirectory() || ! m_ShowMask & CGit::LOG_INFO_FOLLOW)
360 CGitDiff::DiffCommit(m_Path, pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString());
361 else
363 CString path1 = m_Path.GetGitPathString();
364 // start with 1 (0 = working copy changes)
365 for (int i = 1; i < indexNext; i++)
367 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
368 CTGitPathList list = first->GetFiles(NULL);
369 CTGitPath * file = list.LookForGitPath(path1);
370 if (file && !file->GetGitOldPathString().IsEmpty())
371 path1 = file->GetGitOldPathString();
373 CString path2 = path1;
374 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(indexNext));
375 CTGitPathList list = first->GetFiles(NULL);
376 CTGitPath * file = list.LookForGitPath(path2);
377 if (file && !file->GetGitOldPathString().IsEmpty())
378 path2 = file->GetGitOldPathString();
380 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString());
383 else
385 CMessageBox::Show(NULL, IDS_PROC_NOPREVIOUSVERSION, IDS_APPNAME, MB_OK);
387 //if (PromptShown())
389 // GitDiff diff(this, m_hWnd, true);
390 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
391 // diff.SetHEADPeg(m_LogRevision);
392 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
394 //else
395 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
397 break;
398 case ID_COPYCLIPBOARD:
400 CopySelectionToClipBoard();
402 break;
403 case ID_COPYHASH:
405 CopySelectionToClipBoard(TRUE);
407 break;
408 case ID_EXPORT:
410 CString str=pSelLogEntry->m_CommitHash.ToString();
411 CAppUtils::Export(&str);
413 break;
414 case ID_CREATE_BRANCH:
416 CString str = pSelLogEntry->m_CommitHash.ToString();
417 CAppUtils::CreateBranchTag(FALSE,&str);
418 ReloadHashMap();
419 Invalidate();
421 break;
422 case ID_CREATE_TAG:
424 CString str = pSelLogEntry->m_CommitHash.ToString();
425 CAppUtils::CreateBranchTag(TRUE,&str);
426 ReloadHashMap();
427 Invalidate();
428 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
430 break;
431 case ID_SWITCHTOREV:
433 CString str = pSelLogEntry->m_CommitHash.ToString();
434 CAppUtils::Switch(&str);
436 ReloadHashMap();
437 Invalidate();
438 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
439 break;
440 case ID_SWITCHBRANCH:
441 if(popmenu)
443 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
444 if(branch)
446 CString name;
447 if(branch->Find(_T("refs/heads/")) ==0 )
448 name = branch->Mid(11);
449 else
450 name = *branch;
452 CAppUtils::PerformSwitch(name);
454 ReloadHashMap();
455 Invalidate();
456 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
458 break;
459 case ID_RESET:
461 CString str = pSelLogEntry->m_CommitHash.ToString();
462 CAppUtils::GitReset(&str);
463 ReloadHashMap();
464 Invalidate();
466 break;
467 case ID_REBASE_PICK:
468 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_PICK);
469 break;
470 case ID_REBASE_EDIT:
471 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_EDIT);
472 break;
473 case ID_REBASE_SQUASH:
474 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_SQUASH);
475 break;
476 case ID_REBASE_SKIP:
477 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_SKIP);
478 break;
479 case ID_COMBINE_COMMIT:
481 CString head;
482 CGitHash headhash;
483 CGitHash hashFirst,hashLast;
485 int headindex=GetHeadIndex();
486 if(headindex>=0) //incase show all branch, head is not the first commits.
488 head.Format(_T("HEAD~%d"),FirstSelect-headindex);
489 hashFirst=g_Git.GetHash(head);
491 head.Format(_T("HEAD~%d"),LastSelect-headindex);
492 hashLast=g_Git.GetHash(head);
495 GitRev* pFirstEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
496 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
497 if(pFirstEntry->m_CommitHash != hashFirst || pLastEntry->m_CommitHash != hashLast)
499 CMessageBox::Show(NULL, IDS_PROC_CANNOTCOMBINE, IDS_APPNAME, MB_OK);
500 break;
503 headhash=g_Git.GetHash(_T("HEAD"));
505 if(!g_Git.CheckCleanWorkTree())
507 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
508 break;
510 CString cmd,out;
512 //Use throw to abort this process (reset back to original HEAD)
515 cmd.Format(_T("git.exe reset --hard %s"),pFirstEntry->m_CommitHash.ToString());
516 if(g_Git.Run(cmd,&out,CP_UTF8))
518 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
519 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1)) + _T("\r\n\r\n") + out));
521 cmd.Format(_T("git.exe reset --mixed %s"),hashLast.ToString());
522 if(g_Git.Run(cmd,&out,CP_UTF8))
524 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
525 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2)) + _T("\r\n\r\n")+out));
528 CTGitPathList PathList;
529 /* don't why must add --stat to get action status*/
530 /* first -z will be omitted by gitdll*/
531 if(g_Git.GetDiffPath(&PathList,&pFirstEntry->m_CommitHash,&hashLast,"-z --stat -r"))
533 CMessageBox::Show(NULL,_T("Get Diff file list error"),_T("TortoiseGit"),MB_OK);
534 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not get changed file list aborting...\r\n\r\n")+out));
537 for(int i=0;i<PathList.GetCount();i++)
539 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_ADDED)
541 cmd.Format(_T("git.exe add -- \"%s\""), PathList[i].GetGitPathString());
542 if (g_Git.Run(cmd, &out, CP_UTF8))
544 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
545 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not add new file aborting...\r\n\r\n")+out));
549 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_DELETED)
551 cmd.Format(_T("git.exe rm -- \"%s\""), PathList[i].GetGitPathString());
552 if (g_Git.Run(cmd, &out, CP_UTF8))
554 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
555 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not rm file aborting...\r\n\r\n")+out));
560 CCommitDlg dlg;
561 for(int i=FirstSelect;i<=LastSelect;i++)
563 GitRev* pRev = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
564 dlg.m_sLogMessage+=pRev->GetSubject()+_T("\n")+pRev->GetBody();
565 dlg.m_sLogMessage+=_T("\n");
567 dlg.m_bWholeProject=true;
568 dlg.m_bSelectFilesForCommit = true;
569 dlg.m_bForceCommitAmend=true;
570 dlg.m_bNoPostActions=true;
571 dlg.m_AmendStr=dlg.m_sLogMessage;
573 if (dlg.DoModal() == IDOK)
575 if(pFirstEntry->m_CommitHash!=headhash)
577 //Commitrange firstEntry..headhash (from top of combine to original head) needs to be 'cherry-picked'
578 //on top of new commit.
579 //Use the rebase --onto command for it.
581 //All this can be done in one step using the following command:
582 //cmd.Format(_T("git.exe format-patch --stdout --binary --full-index -k %s..%s | git am -k -3"),
583 // pFirstEntry->m_CommitHash,
584 // headhash);
585 //But I am not sure if a '|' is going to work in a CreateProcess() call.
587 //Later the progress dialog could be used to execute these steps.
589 if(CherryPickFrom(pFirstEntry->m_CommitHash.ToString(),headhash))
591 CString msg;
592 msg.Format(_T("Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n"));
593 throw std::exception(CUnicodeUtils::GetUTF8(msg));
595 #if 0
596 CString currentBranch=g_Git.GetCurrentBranch();
597 cmd.Format(_T("git.exe rebase --onto \"%s\" %s %s"),
598 currentBranch,
599 pFirstEntry->m_CommitHash,
600 headhash);
601 if(g_Git.Run(cmd,&out,CP_UTF8)!=0)
603 CString msg;
604 msg.Format(_T("Error while rebasing commits on top of combined commits. Aborting.\r\n\r\n%s"),out);
605 // CMessageBox::Show(NULL,msg,_T("TortoiseGit"),MB_OK);
606 g_Git.Run(_T("git.exe rebase --abort"),&out,CP_UTF8);
607 throw std::exception(CUnicodeUtils::GetUTF8(msg));
610 //HEAD is now on <no branch>.
611 //The following steps are to get HEAD back on the original branch and reset the branch to the new HEAD
612 //To avoid 2 working copy changes, we could use git branch -f <original branch> <hash new head>
613 //And then git checkout <original branch>
614 //But I don't know if 'git branch -f' removes tracking options. So for now, do a checkout and a reset.
616 //Store new HEAD
617 CString newHead=g_Git.GetHash(CString(_T("HEAD")));
619 //Checkout working branch
620 cmd.Format(_T("git.exe checkout -f \"%s\""),currentBranch);
621 if(g_Git.Run(cmd,&out,CP_UTF8))
622 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not checkout original branch. Aborting...\r\n\r\n")+out));
624 //Reset to new HEAD
625 cmd.Format(_T("git.exe reset --hard %s"),newHead);
626 if(g_Git.Run(cmd,&out,CP_UTF8))
627 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not reset to new head. Aborting...\r\n\r\n")+out));
628 #endif
631 else
632 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED))));
634 catch(std::exception& e)
636 CMessageBox::Show(NULL, CUnicodeUtils::GetUnicode(CStringA(e.what())), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
637 cmd.Format(_T("git.exe reset --hard %s"),headhash.ToString());
638 out.Empty();
639 if(g_Git.Run(cmd,&out,CP_UTF8))
641 CMessageBox::Show(NULL, CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD)) + _T("\r\n\r\n") + out, _T("TortoiseGit"), MB_OK);
644 Refresh();
646 break;
648 case ID_CHERRY_PICK:
649 if(!g_Git.CheckCleanWorkTree())
651 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
654 else
656 CRebaseDlg dlg;
657 dlg.m_IsCherryPick = TRUE;
658 dlg.m_Upstream = this->m_CurrentBranch;
659 POSITION pos = GetFirstSelectedItemPosition();
660 while(pos)
662 int indexNext = GetNextSelectedItem(pos);
663 dlg.m_CommitList.m_logEntries.push_back( ((GitRev*)m_arShownList[indexNext])->m_CommitHash );
664 dlg.m_CommitList.m_LogCache.m_HashMap[((GitRev*)m_arShownList[indexNext])->m_CommitHash]=*(GitRev*)m_arShownList[indexNext];
665 dlg.m_CommitList.m_logEntries.GetGitRevAt(dlg.m_CommitList.m_logEntries.size()-1).GetAction(this) |= CTGitPath::LOGACTIONS_REBASE_PICK;
668 if(dlg.DoModal() == IDOK)
670 Refresh();
673 break;
674 case ID_REBASE_TO_VERSION:
675 if(!g_Git.CheckCleanWorkTree())
677 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
680 else
682 CRebaseDlg dlg;
683 dlg.m_Upstream = pSelLogEntry->m_CommitHash;
685 if(dlg.DoModal() == IDOK)
687 Refresh();
691 break;
693 case ID_STASH_SAVE:
694 if (CAppUtils::StashSave())
695 Refresh();
696 break;
698 case ID_STASH_POP:
699 if (CAppUtils::StashPop())
700 Refresh();
701 break;
703 case ID_STASH_LIST:
704 CAppUtils::RunTortoiseProc(_T("/command:reflog /ref:refs/stash"));
705 break;
707 case ID_REFLOG_STASH_APPLY:
708 CAppUtils::StashApply(pSelLogEntry->m_Ref);
709 break;
711 case ID_REFLOG_DEL:
713 CString str;
714 if (GetSelectedCount() > 1)
715 str.Format(IDS_PROC_DELETENREFS, GetSelectedCount());
716 else
717 str.Format(IDS_PROC_DELETEREF, pSelLogEntry->m_Ref);
719 if (CMessageBox::Show(NULL, str, _T("TortoiseGit"), 1, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
720 return;
722 POSITION pos = GetFirstSelectedItemPosition();
723 while (pos)
725 CString ref = ((GitRev *)m_arShownList[GetNextSelectedItem(pos)])->m_Ref;
726 if (ref.Find(_T("refs/")) == 0)
727 ref = ref.Mid(5);
728 int refpos = ref.ReverseFind('{');
729 if (refpos > 0 && ref.Mid(refpos, 2) != _T("@{"))
730 ref = ref.Left(refpos) + _T("@")+ ref.Mid(refpos);
732 CString cmd, out;
733 if (ref.Find(_T("stash")) == 0)
734 cmd.Format(_T("git.exe stash drop %s"), ref);
735 else
736 cmd.Format(_T("git.exe reflog delete %s"), ref);
738 if (g_Git.Run(cmd, &out, CP_UTF8))
739 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
741 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
744 break;
745 case ID_LOG:
747 CString cmd = _T("/command:log");
748 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
749 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
750 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
751 CAppUtils::RunTortoiseProc(cmd);
753 break;
754 case ID_CREATE_PATCH:
756 int select=this->GetSelectedCount();
757 CString cmd = _T("/command:formatpatch");
758 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
760 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
761 GitRev * r2 = NULL;
762 if(select == 1)
764 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString();
766 else
768 r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
769 if( this->m_IsOldFirst )
771 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString()+_T("~1");
772 cmd += _T(" /endrev:")+r2->m_CommitHash.ToString();
775 else
777 cmd += _T(" /startrev:")+r2->m_CommitHash.ToString()+_T("~1");
778 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
783 CAppUtils::RunTortoiseProc(cmd);
785 break;
786 case ID_REPOBROWSE:
788 CString sCmd;
789 sCmd.Format(_T("/command:repobrowser /path:\"%s\" /rev:%s"), g_Git.m_CurrentDir, pSelLogEntry->m_CommitHash.ToString());
790 CAppUtils::RunTortoiseProc(sCmd);
792 break;
793 case ID_PUSH:
795 CString guessAssociatedBranch;
796 if (m_HashMap[pSelLogEntry->m_CommitHash].size() > 0)
797 guessAssociatedBranch = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
798 if (CAppUtils::Push(guessAssociatedBranch))
799 Refresh();
801 break;
802 case ID_FETCH:
804 if (CAppUtils::Fetch(_T(""), true))
805 Refresh();
807 break;
808 case ID_SHOWBRANCHES:
810 CString cmd;
811 cmd.Format(_T("git.exe branch -a --contains %s"), pSelLogEntry->m_CommitHash.ToString());
812 CProgressDlg progress;
813 progress.m_GitCmd = cmd;
814 progress.DoModal();
816 break;
817 case ID_DELETE:
819 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
820 if (!branch)
822 CMessageBox::Show(NULL,IDS_ERROR_NOREF,IDS_APPNAME,MB_OK|MB_ICONERROR);
823 return;
825 CString shortname;
826 CString cmd;
827 if (this->GetShortName(*branch, shortname, _T("refs/remotes/")))
829 CString msg;
830 msg.Format(IDS_PROC_DELETEREMOTEBRANCH, *branch);
831 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)));
832 if (result == 1)
834 CString remoteName = shortname.Left(shortname.Find('/'));
835 shortname = shortname.Mid(shortname.Find('/') + 1);
836 if(CAppUtils::IsSSHPutty())
837 CAppUtils::LaunchPAgent(NULL, &remoteName);
839 cmd.Format(L"git.exe push \"%s\" :%s", remoteName, shortname);
841 else if (result == 2)
842 cmd.Format(_T("git.exe branch -r -D -- %s"), shortname);
843 else
844 return;
846 else if (this->GetShortName(*branch, shortname, _T("refs/stash")))
848 if (CMessageBox::Show(NULL, IDS_PROC_DELETEALLSTASH, IDS_APPNAME, 2, IDI_QUESTION, IDS_DELETEBUTTON, IDS_ABORTBUTTON) == 1)
849 cmd.Format(_T("git.exe stash clear"));
850 else
851 return;
853 else
855 CString msg;
856 msg.Format(IDS_PROC_DELETEBRANCHTAG, *branch);
857 if (CMessageBox::Show(NULL, msg, _T("TortoiseGit"), 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
859 if(this->GetShortName(*branch,shortname,_T("refs/heads/")))
861 cmd.Format(_T("git.exe branch -D -- %s"),shortname);
864 if(this->GetShortName(*branch,shortname,_T("refs/tags/")))
866 cmd.Format(_T("git.exe tag -d -- %s"),shortname);
870 if (!cmd.IsEmpty())
872 CString out;
873 if(g_Git.Run(cmd,&out,CP_UTF8))
875 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
877 this->ReloadHashMap();
878 CRect rect;
879 this->GetItemRect(FirstSelect,&rect,LVIR_BOUNDS);
880 this->InvalidateRect(rect);
883 break;
885 case ID_FINDENTRY:
887 m_nSearchIndex = GetSelectionMark();
888 if (m_nSearchIndex < 0)
889 m_nSearchIndex = 0;
890 if (m_pFindDialog)
892 break;
894 else
896 m_pFindDialog = new CFindDlg();
897 m_pFindDialog->Create(this);
900 break;
901 case ID_MERGEREV:
903 CString str = pSelLogEntry->m_CommitHash.ToString();
904 if (m_HashMap[pSelLogEntry->m_CommitHash].size() > 0)
905 str = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
906 // we need an URL to complete this command, so error out if we can't get an URL
907 if(CAppUtils::Merge(&str))
909 this->Refresh();
912 break;
913 case ID_REVERTREV:
915 if(!this->RevertSelectedCommits())
916 this->Refresh();
918 break;
919 case ID_EDITNOTE:
921 CAppUtils::EditNote(pSelLogEntry);
922 this->SetItemState(FirstSelect, 0, LVIS_SELECTED);
923 this->SetItemState(FirstSelect, LVIS_SELECTED, LVIS_SELECTED);
925 break;
926 default:
927 //CMessageBox::Show(NULL,_T("Have not implemented"),_T("TortoiseGit"),MB_OK);
928 break;
929 #if 0
931 case ID_BLAMECOMPARE:
933 //user clicked on the menu item "compare with working copy"
934 //now first get the revision which is selected
935 if (PromptShown())
937 GitDiff diff(this, this->m_hWnd, true);
938 diff.SetHEADPeg(m_LogRevision);
939 diff.ShowCompare(m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), false, true);
941 else
942 CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), m_LogRevision, false, false, true);
944 break;
945 case ID_BLAMETWO:
947 //user clicked on the menu item "compare and blame revisions"
948 if (PromptShown())
950 GitDiff diff(this, this->m_hWnd, true);
951 diff.SetHEADPeg(m_LogRevision);
952 diff.ShowCompare(CTGitPath(pathURL), revSelected2, CTGitPath(pathURL), revSelected, GitRev(), false, true);
954 else
955 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revSelected2, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
957 break;
958 case ID_BLAMEWITHPREVIOUS:
960 //user clicked on the menu item "Compare and Blame with previous revision"
961 if (PromptShown())
963 GitDiff diff(this, this->m_hWnd, true);
964 diff.SetHEADPeg(m_LogRevision);
965 diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), false, true);
967 else
968 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
970 break;
972 case ID_OPENWITH:
973 bOpenWith = true;
974 case ID_OPEN:
976 CProgressDlg progDlg;
977 progDlg.SetTitle(IDS_APPNAME);
978 progDlg.SetAnimation(IDR_DOWNLOAD);
979 CString sInfoLine;
980 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, m_path.GetWinPath(), (LPCTSTR)revSelected.ToString());
981 progDlg.SetLine(1, sInfoLine, true);
982 SetAndClearProgressInfo(&progDlg);
983 progDlg.ShowModeless(m_hWnd);
984 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, m_path, revSelected);
985 bool bSuccess = true;
986 if (!Cat(m_path, GitRev(GitRev::REV_HEAD), revSelected, tempfile))
988 bSuccess = false;
989 // try again, but with the selected revision as the peg revision
990 if (!Cat(m_path, revSelected, revSelected, tempfile))
992 progDlg.Stop();
993 SetAndClearProgressInfo((HWND)NULL);
994 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
995 EnableOKButton();
996 break;
998 bSuccess = true;
1000 if (bSuccess)
1002 progDlg.Stop();
1003 SetAndClearProgressInfo((HWND)NULL);
1004 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1005 int ret = 0;
1006 if (!bOpenWith)
1007 ret = (int)ShellExecute(this->m_hWnd, NULL, tempfile.GetWinPath(), NULL, NULL, SW_SHOWNORMAL);
1008 if ((ret <= HINSTANCE_ERROR)||bOpenWith)
1010 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1011 cmd += tempfile.GetWinPathString() + _T(" ");
1012 CAppUtils::LaunchApplication(cmd, NULL, false);
1016 break;
1017 case ID_BLAME:
1019 CBlameDlg dlg;
1020 dlg.EndRev = revSelected;
1021 if (dlg.DoModal() == IDOK)
1023 CBlame blame;
1024 CString tempfile;
1025 CString logfile;
1026 tempfile = blame.BlameToTempFile(m_path, dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, _T(""), dlg.m_bIncludeMerge, TRUE, TRUE);
1027 if (!tempfile.IsEmpty())
1029 if (dlg.m_bTextView)
1031 //open the default text editor for the result file
1032 CAppUtils::StartTextViewer(tempfile);
1034 else
1036 CString sParams = _T("/path:\"") + m_path.GetGitPathString() + _T("\" ");
1037 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(m_path.GetFileOrDirectoryName()),sParams))
1039 break;
1043 else
1045 CMessageBox::Show(this->m_hWnd, blame.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1049 break;
1050 case ID_UPDATE:
1052 CString sCmd;
1053 CString url = _T("tgit:")+pathURL;
1054 sCmd.Format(_T("%s /command:update /path:\"%s\" /rev:%ld"),
1055 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
1056 (LPCTSTR)m_path.GetWinPath(), (LONG)revSelected);
1057 CAppUtils::LaunchApplication(sCmd, NULL, false);
1059 break;
1061 case ID_EDITLOG:
1063 EditLogMessage(selIndex);
1065 break;
1066 case ID_EDITAUTHOR:
1068 EditAuthor(selEntries);
1070 break;
1071 case ID_REVPROPS:
1073 CEditPropertiesDlg dlg;
1074 dlg.SetProjectProperties(&m_ProjectProperties);
1075 CTGitPathList escapedlist;
1076 dlg.SetPathList(CTGitPathList(CTGitPath(pathURL)));
1077 dlg.SetRevision(revSelected);
1078 dlg.RevProps(true);
1079 dlg.DoModal();
1081 break;
1083 case ID_EXPORT:
1085 CString sCmd;
1086 sCmd.Format(_T("%s /command:export /path:\"%s\" /revision:%ld"),
1087 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
1088 (LPCTSTR)pathURL, (LONG)revSelected);
1089 CAppUtils::LaunchApplication(sCmd, NULL, false);
1091 break;
1092 case ID_VIEWREV:
1094 CString url = m_ProjectProperties.sWebViewerRev;
1095 url = GetAbsoluteUrlFromRelativeUrl(url);
1096 url.Replace(_T("%REVISION%"), revSelected.ToString());
1097 if (!url.IsEmpty())
1098 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1100 break;
1101 case ID_VIEWPATHREV:
1103 CString relurl = pathURL;
1104 CString sRoot = GetRepositoryRoot(CTGitPath(relurl));
1105 relurl = relurl.Mid(sRoot.GetLength());
1106 CString url = m_ProjectProperties.sWebViewerPathRev;
1107 url = GetAbsoluteUrlFromRelativeUrl(url);
1108 url.Replace(_T("%REVISION%"), revSelected.ToString());
1109 url.Replace(_T("%PATH%"), relurl);
1110 if (!url.IsEmpty())
1111 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1113 break;
1114 #endif
1116 } // switch (cmd)
1118 theApp.DoWaitCursor(-1);
1121 void CGitLogList::SetSelectedAction(int action)
1123 POSITION pos = GetFirstSelectedItemPosition();
1124 int index;
1125 while(pos)
1127 index = GetNextSelectedItem(pos);
1128 ((GitRev*)m_arShownList[index])->GetAction(this) = action;
1129 CRect rect;
1130 this->GetItemRect(index,&rect,LVIR_BOUNDS);
1131 this->InvalidateRect(rect);
1135 void CGitLogList::ShiftSelectedAction()
1137 POSITION pos = GetFirstSelectedItemPosition();
1138 int index;
1139 while(pos)
1141 index = GetNextSelectedItem(pos);
1142 int action = ((GitRev*)m_arShownList[index])->GetAction(this);
1143 switch (action)
1145 case CTGitPath::LOGACTIONS_REBASE_PICK:
1146 action = CTGitPath::LOGACTIONS_REBASE_SKIP;
1147 break;
1148 case CTGitPath::LOGACTIONS_REBASE_SKIP:
1149 action= CTGitPath::LOGACTIONS_REBASE_EDIT;
1150 break;
1151 case CTGitPath::LOGACTIONS_REBASE_EDIT:
1152 action = CTGitPath::LOGACTIONS_REBASE_SQUASH;
1153 break;
1154 case CTGitPath::LOGACTIONS_REBASE_SQUASH:
1155 action= CTGitPath::LOGACTIONS_REBASE_PICK;
1156 break;
1158 ((GitRev*)m_arShownList[index])->GetAction(this) = action;
1159 CRect rect;
1160 this->GetItemRect(index, &rect, LVIR_BOUNDS);
1161 this->InvalidateRect(rect);