Fixed issue #1076: Error trying to delete remote branch named 1.0.0
[TortoiseGit.git] / src / TortoiseProc / GitLogListAction.cpp
blobb9d905d92c04d900deaa9dee0156724d71fb0a64
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(int parent)
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, merge;
110 if (parent)
111 merge.Format(_T("-m %d "), parent);
112 cmd.Format(_T("git.exe revert --no-edit --no-commit %s%s"), merge, r1->m_CommitHash.ToString());
113 if (g_Git.Run(cmd, &output, CP_UTF8))
115 CString str;
116 str.LoadString(IDS_SVNACTION_FAILEDREVERT);
117 str += _T("\n");
118 str+= cmd;
119 str+= _T("\n")+output;
120 if( GetSelectedCount() == 1)
121 CMessageBox::Show(NULL, str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
122 else
124 if(CMessageBox::Show(NULL, str, _T("TortoiseGit"),2 , IDI_ERROR, CString(MAKEINTRESOURCE(IDS_SKIPBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
126 return ret;
130 else
132 ret =0;
135 if ((progress.IsValid())&&(progress.HasUserCancelled()))
136 break;
138 return ret;
140 int CGitLogList::CherryPickFrom(CString from, CString to)
142 CLogDataVector logs(&m_LogCache);
143 if(logs.ParserFromLog(NULL,-1,0,&from,&to))
144 return -1;
146 if (logs.empty())
147 return 0;
149 CSysProgressDlg progress;
150 if (progress.IsValid())
152 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_CHERRYPICK)));
153 progress.SetAnimation(IDR_MOVEANI);
154 progress.SetTime(true);
155 progress.ShowModeless(this);
158 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
160 for (int i = (int)logs.size() - 1; i >= 0; i--)
162 if (progress.IsValid())
164 CString temp;
165 temp.Format(IDS_PROC_PICK, logs.GetGitRevAt(i).m_CommitHash.ToString());
166 progress.FormatPathLine(1, temp);
167 progress.FormatPathLine(2, _T("%s"), logs.GetGitRevAt(i).GetSubject());
168 progress.SetProgress(logs.size()-i, logs.size());
170 if ((progress.IsValid())&&(progress.HasUserCancelled()))
172 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED))));
173 return -1;
175 CString cmd,out;
176 cmd.Format(_T("git.exe cherry-pick %s"),logs.GetGitRevAt(i).m_CommitHash.ToString());
177 out.Empty();
178 if(g_Git.Run(cmd,&out,CP_UTF8))
180 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_CHERRYPICKFAILED)) + _T(":\r\n\r\n") + out));
181 return -1;
185 return 0;
188 void CGitLogList::ContextMenuAction(int cmd,int FirstSelect, int LastSelect, CMenu *popmenu)
190 POSITION pos = GetFirstSelectedItemPosition();
191 int indexNext = GetNextSelectedItem(pos);
192 if (indexNext < 0)
193 return;
195 GitRev* pSelLogEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(indexNext));
197 theApp.DoWaitCursor(1);
198 switch (cmd&0xFFFF)
200 case ID_COMMIT:
202 CTGitPathList pathlist;
203 CTGitPathList selectedlist;
204 pathlist.AddPath(this->m_Path);
205 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
206 CString str;
207 CAppUtils::Commit(CString(),false,str,
208 pathlist,selectedlist,bSelectFilesForCommit);
209 //this->Refresh();
210 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH,0);
212 break;
213 case ID_GNUDIFF1: // compare with WC, unified
215 CString tempfile=GetTempFile();
216 CString command;
217 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
218 if(!r1->m_CommitHash.IsEmpty())
220 CString merge;
221 CString hash2;
222 cmd >>= 16;
223 if( (cmd&0xFFFF) == 0xFFFF)
225 merge=_T("-m");
227 else if((cmd&0xFFFF) == 0xFFFE)
229 merge=_T("-c");
231 else
233 if(cmd > r1->m_ParentHash.size())
235 CString str;
236 str.Format(IDS_PROC_NOPARENT, cmd);
237 MessageBox(str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
238 return;
240 else
242 if(cmd>0)
243 hash2 = r1->m_ParentHash[cmd-1].ToString();
246 command.Format(_T("git.exe diff-tree %s -r -p --stat %s %s"), merge, hash2, r1->m_CommitHash.ToString());
248 else
249 command.Format(_T("git.exe diff -r -p --stat"));
251 g_Git.RunLogFile(command,tempfile);
252 CAppUtils::StartUnifiedDiffViewer(tempfile, r1->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()) + _T(":") + r1->GetSubject());
254 break;
256 case ID_GNUDIFF2: // compare two revisions, unified
258 CString tempfile=GetTempFile();
259 CString cmd;
260 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
261 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
263 if( r1->m_CommitHash.IsEmpty()) {
264 cmd.Format(_T("git.exe diff -r -p --stat %s"),r2->m_CommitHash.ToString());
266 else if( r2->m_CommitHash.IsEmpty()) {
267 cmd.Format(_T("git.exe diff -r -p --stat %s"),r1->m_CommitHash.ToString());
269 else
271 cmd.Format(_T("git.exe diff-tree -r -p --stat %s %s"),r2->m_CommitHash.ToString(),r1->m_CommitHash.ToString());
274 g_Git.RunLogFile(cmd,tempfile);
275 CAppUtils::StartUnifiedDiffViewer(tempfile, r2->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()) + _T(":") + r1->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()));
278 break;
280 case ID_COMPARETWO: // compare two revisions
282 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
283 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
284 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
285 CGitDiff::DiffCommit(this->m_Path, r1,r2);
286 else
288 CString path1 = m_Path.GetGitPathString();
289 // start with 1 (0 = working copy changes)
290 for (int i = 1; i < FirstSelect; i++)
292 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
293 CTGitPathList list = first->GetFiles(NULL);
294 CTGitPath * file = list.LookForGitPath(path1);
295 if (file && !file->GetGitOldPathString().IsEmpty())
296 path1 = file->GetGitOldPathString();
298 CString path2 = path1;
299 for (int i = FirstSelect; i < LastSelect; i++)
301 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
302 CTGitPathList list = first->GetFiles(NULL);
303 CTGitPath * file = list.LookForGitPath(path2);
304 if (file && !file->GetGitOldPathString().IsEmpty())
305 path2 = file->GetGitOldPathString();
307 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), r1, r2);
311 break;
313 case ID_COMPARE: // compare revision with WC
315 GitRev * r1 = &m_wcRev;
316 GitRev * r2 = pSelLogEntry;
318 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
319 CGitDiff::DiffCommit(this->m_Path, r1,r2);
320 else
322 CString path1 = m_Path.GetGitPathString();
323 // start with 1 (0 = working copy changes)
324 for (int i = 1; i < FirstSelect; i++)
326 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
327 CTGitPathList list = first->GetFiles(NULL);
328 CTGitPath * file = list.LookForGitPath(path1);
329 if (file && !file->GetGitOldPathString().IsEmpty())
330 path1 = file->GetGitOldPathString();
332 CGitDiff::DiffCommit(m_Path, CTGitPath(path1), r1, r2);
335 //user clicked on the menu item "compare with working copy"
336 //if (PromptShown())
338 // GitDiff diff(this, m_hWnd, true);
339 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
340 // diff.SetHEADPeg(m_LogRevision);
341 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
343 //else
344 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
346 break;
348 case ID_COMPAREWITHPREVIOUS:
350 CFileDiffDlg dlg;
352 if (!pSelLogEntry->m_ParentHash.empty())
353 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
355 cmd>>=16;
356 cmd&=0xFFFF;
358 if(cmd == 0)
359 cmd=1;
361 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
362 CGitDiff::DiffCommit(m_Path, pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString());
363 else
365 CString path1 = m_Path.GetGitPathString();
366 // start with 1 (0 = working copy changes)
367 for (int i = 1; i < indexNext; i++)
369 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
370 CTGitPathList list = first->GetFiles(NULL);
371 CTGitPath * file = list.LookForGitPath(path1);
372 if (file && !file->GetGitOldPathString().IsEmpty())
373 path1 = file->GetGitOldPathString();
375 CString path2 = path1;
376 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(indexNext));
377 CTGitPathList list = first->GetFiles(NULL);
378 CTGitPath * file = list.LookForGitPath(path2);
379 if (file && !file->GetGitOldPathString().IsEmpty())
380 path2 = file->GetGitOldPathString();
382 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString());
385 else
387 CMessageBox::Show(NULL, IDS_PROC_NOPREVIOUSVERSION, IDS_APPNAME, MB_OK);
389 //if (PromptShown())
391 // GitDiff diff(this, m_hWnd, true);
392 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
393 // diff.SetHEADPeg(m_LogRevision);
394 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
396 //else
397 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
399 break;
400 case ID_COPYCLIPBOARD:
402 CopySelectionToClipBoard();
404 break;
405 case ID_COPYCLIPBOARDMESSAGES:
407 if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0)
408 CopySelectionToClipBoard(ID_COPY_SUBJECT);
409 else
410 CopySelectionToClipBoard(ID_COPY_MESSAGE);
412 break;
413 case ID_COPYHASH:
415 CopySelectionToClipBoard(ID_COPY_HASH);
417 break;
418 case ID_EXPORT:
420 CString str=pSelLogEntry->m_CommitHash.ToString();
421 CAppUtils::Export(&str);
423 break;
424 case ID_CREATE_BRANCH:
425 case ID_CREATE_TAG:
427 CString str = pSelLogEntry->m_CommitHash.ToString();
428 // try to guess remote branch in order to enable tracking
429 for (int i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); i++)
431 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/remotes/")) == 0)
433 str = m_HashMap[pSelLogEntry->m_CommitHash][i];
434 break;
437 CAppUtils::CreateBranchTag((cmd&0xFFFF) == ID_CREATE_TAG, &str);
438 ReloadHashMap();
439 Invalidate();
440 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
442 break;
443 case ID_SWITCHTOREV:
445 CString str = pSelLogEntry->m_CommitHash.ToString();
446 // try to guess remote branch in order to recommend good branch name and tracking
447 for (int i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); i++)
449 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/remotes/")) == 0)
451 str = m_HashMap[pSelLogEntry->m_CommitHash][i];
452 break;
455 CAppUtils::Switch(str);
457 ReloadHashMap();
458 Invalidate();
459 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
460 break;
461 case ID_SWITCHBRANCH:
462 if(popmenu)
464 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
465 if(branch)
467 CString name;
468 if(branch->Find(_T("refs/heads/")) ==0 )
469 name = branch->Mid(11);
470 else
471 name = *branch;
473 CAppUtils::PerformSwitch(name);
475 ReloadHashMap();
476 Invalidate();
477 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
479 break;
480 case ID_RESET:
482 CString str = pSelLogEntry->m_CommitHash.ToString();
483 if (CAppUtils::GitReset(&str))
485 ResetWcRev(true);
486 ReloadHashMap();
487 Invalidate();
490 break;
491 case ID_REBASE_PICK:
492 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_PICK);
493 break;
494 case ID_REBASE_EDIT:
495 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_EDIT);
496 break;
497 case ID_REBASE_SQUASH:
498 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_SQUASH);
499 break;
500 case ID_REBASE_SKIP:
501 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_SKIP);
502 break;
503 case ID_COMBINE_COMMIT:
505 CString head;
506 CGitHash headhash;
507 CGitHash hashFirst,hashLast;
509 int headindex=GetHeadIndex();
510 if(headindex>=0) //incase show all branch, head is not the first commits.
512 head.Format(_T("HEAD~%d"),FirstSelect-headindex);
513 hashFirst=g_Git.GetHash(head);
515 head.Format(_T("HEAD~%d"),LastSelect-headindex);
516 hashLast=g_Git.GetHash(head);
519 GitRev* pFirstEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
520 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
521 if(pFirstEntry->m_CommitHash != hashFirst || pLastEntry->m_CommitHash != hashLast)
523 CMessageBox::Show(NULL, IDS_PROC_CANNOTCOMBINE, IDS_APPNAME, MB_OK);
524 break;
527 headhash=g_Git.GetHash(_T("HEAD"));
529 if(!g_Git.CheckCleanWorkTree())
531 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
532 break;
534 CString cmd,out;
536 //Use throw to abort this process (reset back to original HEAD)
539 cmd.Format(_T("git.exe reset --hard %s"),pFirstEntry->m_CommitHash.ToString());
540 if(g_Git.Run(cmd,&out,CP_UTF8))
542 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
543 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1)) + _T("\r\n\r\n") + out));
545 cmd.Format(_T("git.exe reset --mixed %s"),hashLast.ToString());
546 if(g_Git.Run(cmd,&out,CP_UTF8))
548 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
549 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2)) + _T("\r\n\r\n")+out));
552 CTGitPathList PathList;
553 /* don't why must add --stat to get action status*/
554 /* first -z will be omitted by gitdll*/
555 if(g_Git.GetDiffPath(&PathList,&pFirstEntry->m_CommitHash,&hashLast,"-z --stat -r"))
557 CMessageBox::Show(NULL,_T("Get Diff file list error"),_T("TortoiseGit"),MB_OK);
558 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not get changed file list aborting...\r\n\r\n")+out));
561 for(int i=0;i<PathList.GetCount();i++)
563 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_ADDED)
565 cmd.Format(_T("git.exe add -- \"%s\""), PathList[i].GetGitPathString());
566 if (g_Git.Run(cmd, &out, CP_UTF8))
568 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
569 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not add new file aborting...\r\n\r\n")+out));
573 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_DELETED)
575 cmd.Format(_T("git.exe rm -- \"%s\""), PathList[i].GetGitPathString());
576 if (g_Git.Run(cmd, &out, CP_UTF8))
578 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
579 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not rm file aborting...\r\n\r\n")+out));
584 CCommitDlg dlg;
585 for(int i=FirstSelect;i<=LastSelect;i++)
587 GitRev* pRev = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
588 dlg.m_sLogMessage+=pRev->GetSubject()+_T("\n")+pRev->GetBody();
589 dlg.m_sLogMessage+=_T("\n");
591 dlg.m_bWholeProject=true;
592 dlg.m_bSelectFilesForCommit = true;
593 dlg.m_bForceCommitAmend=true;
594 dlg.m_bNoPostActions=true;
595 dlg.m_AmendStr=dlg.m_sLogMessage;
597 if (dlg.DoModal() == IDOK)
599 if(pFirstEntry->m_CommitHash!=headhash)
601 //Commitrange firstEntry..headhash (from top of combine to original head) needs to be 'cherry-picked'
602 //on top of new commit.
603 //Use the rebase --onto command for it.
605 //All this can be done in one step using the following command:
606 //cmd.Format(_T("git.exe format-patch --stdout --binary --full-index -k %s..%s | git am -k -3"),
607 // pFirstEntry->m_CommitHash,
608 // headhash);
609 //But I am not sure if a '|' is going to work in a CreateProcess() call.
611 //Later the progress dialog could be used to execute these steps.
613 if(CherryPickFrom(pFirstEntry->m_CommitHash.ToString(),headhash))
615 CString msg;
616 msg.Format(_T("Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n"));
617 throw std::exception(CUnicodeUtils::GetUTF8(msg));
619 #if 0
620 CString currentBranch=g_Git.GetCurrentBranch();
621 cmd.Format(_T("git.exe rebase --onto \"%s\" %s %s"),
622 currentBranch,
623 pFirstEntry->m_CommitHash,
624 headhash);
625 if(g_Git.Run(cmd,&out,CP_UTF8)!=0)
627 CString msg;
628 msg.Format(_T("Error while rebasing commits on top of combined commits. Aborting.\r\n\r\n%s"),out);
629 // CMessageBox::Show(NULL,msg,_T("TortoiseGit"),MB_OK);
630 g_Git.Run(_T("git.exe rebase --abort"),&out,CP_UTF8);
631 throw std::exception(CUnicodeUtils::GetUTF8(msg));
634 //HEAD is now on <no branch>.
635 //The following steps are to get HEAD back on the original branch and reset the branch to the new HEAD
636 //To avoid 2 working copy changes, we could use git branch -f <original branch> <hash new head>
637 //And then git checkout <original branch>
638 //But I don't know if 'git branch -f' removes tracking options. So for now, do a checkout and a reset.
640 //Store new HEAD
641 CString newHead=g_Git.GetHash(CString(_T("HEAD")));
643 //Checkout working branch
644 cmd.Format(_T("git.exe checkout -f \"%s\""),currentBranch);
645 if(g_Git.Run(cmd,&out,CP_UTF8))
646 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not checkout original branch. Aborting...\r\n\r\n")+out));
648 //Reset to new HEAD
649 cmd.Format(_T("git.exe reset --hard %s"),newHead);
650 if(g_Git.Run(cmd,&out,CP_UTF8))
651 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not reset to new head. Aborting...\r\n\r\n")+out));
652 #endif
655 else
656 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED))));
658 catch(std::exception& e)
660 CMessageBox::Show(NULL, CUnicodeUtils::GetUnicode(CStringA(e.what())), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
661 cmd.Format(_T("git.exe reset --hard %s"),headhash.ToString());
662 out.Empty();
663 if(g_Git.Run(cmd,&out,CP_UTF8))
665 CMessageBox::Show(NULL, CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD)) + _T("\r\n\r\n") + out, _T("TortoiseGit"), MB_OK);
668 Refresh();
670 break;
672 case ID_CHERRY_PICK:
673 if(!g_Git.CheckCleanWorkTree())
675 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
678 else
680 CRebaseDlg dlg;
681 dlg.m_IsCherryPick = TRUE;
682 dlg.m_Upstream = this->m_CurrentBranch;
683 POSITION pos = GetFirstSelectedItemPosition();
684 while(pos)
686 int indexNext = GetNextSelectedItem(pos);
687 dlg.m_CommitList.m_logEntries.push_back( ((GitRev*)m_arShownList[indexNext])->m_CommitHash );
688 dlg.m_CommitList.m_LogCache.m_HashMap[((GitRev*)m_arShownList[indexNext])->m_CommitHash]=*(GitRev*)m_arShownList[indexNext];
689 dlg.m_CommitList.m_logEntries.GetGitRevAt(dlg.m_CommitList.m_logEntries.size()-1).GetAction(this) |= CTGitPath::LOGACTIONS_REBASE_PICK;
692 if(dlg.DoModal() == IDOK)
694 Refresh();
697 break;
698 case ID_REBASE_TO_VERSION:
699 if(!g_Git.CheckCleanWorkTree())
701 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
704 else
706 CRebaseDlg dlg;
707 dlg.m_Upstream = pSelLogEntry->m_CommitHash;
709 if(dlg.DoModal() == IDOK)
711 Refresh();
715 break;
717 case ID_STASH_SAVE:
718 if (CAppUtils::StashSave())
719 Refresh();
720 break;
722 case ID_STASH_POP:
723 if (CAppUtils::StashPop())
724 Refresh();
725 break;
727 case ID_STASH_LIST:
728 CAppUtils::RunTortoiseProc(_T("/command:reflog /ref:refs/stash"));
729 break;
731 case ID_REFLOG_STASH_APPLY:
732 CAppUtils::StashApply(pSelLogEntry->m_Ref);
733 break;
735 case ID_REFLOG_DEL:
737 CString str;
738 if (GetSelectedCount() > 1)
739 str.Format(IDS_PROC_DELETENREFS, GetSelectedCount());
740 else
741 str.Format(IDS_PROC_DELETEREF, pSelLogEntry->m_Ref);
743 if (CMessageBox::Show(NULL, str, _T("TortoiseGit"), 1, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
744 return;
746 POSITION pos = GetFirstSelectedItemPosition();
747 while (pos)
749 CString ref = ((GitRev *)m_arShownList[GetNextSelectedItem(pos)])->m_Ref;
750 if (ref.Find(_T("refs/")) == 0)
751 ref = ref.Mid(5);
752 int refpos = ref.ReverseFind('{');
753 if (refpos > 0 && ref.Mid(refpos, 2) != _T("@{"))
754 ref = ref.Left(refpos) + _T("@")+ ref.Mid(refpos);
756 CString cmd, out;
757 if (ref.Find(_T("stash")) == 0)
758 cmd.Format(_T("git.exe stash drop %s"), ref);
759 else
760 cmd.Format(_T("git.exe reflog delete %s"), ref);
762 if (g_Git.Run(cmd, &out, CP_UTF8))
763 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
765 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
768 break;
769 case ID_LOG:
771 CString cmd = _T("/command:log");
772 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
773 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
774 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
775 CAppUtils::RunTortoiseProc(cmd);
777 break;
778 case ID_CREATE_PATCH:
780 int select=this->GetSelectedCount();
781 CString cmd = _T("/command:formatpatch");
782 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
784 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
785 GitRev * r2 = NULL;
786 if(select == 1)
788 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString();
790 else
792 r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
793 if( this->m_IsOldFirst )
795 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString()+_T("~1");
796 cmd += _T(" /endrev:")+r2->m_CommitHash.ToString();
799 else
801 cmd += _T(" /startrev:")+r2->m_CommitHash.ToString()+_T("~1");
802 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
807 CAppUtils::RunTortoiseProc(cmd);
809 break;
810 case ID_REPOBROWSE:
812 CString sCmd;
813 sCmd.Format(_T("/command:repobrowser /path:\"%s\" /rev:%s"), g_Git.m_CurrentDir, pSelLogEntry->m_CommitHash.ToString());
814 CAppUtils::RunTortoiseProc(sCmd);
816 break;
817 case ID_PUSH:
819 CString guessAssociatedBranch;
820 if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
821 guessAssociatedBranch = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
822 if (CAppUtils::Push(guessAssociatedBranch))
823 Refresh();
825 break;
826 case ID_FETCH:
828 if (CAppUtils::Fetch(_T(""), true))
829 Refresh();
831 break;
832 case ID_SHOWBRANCHES:
834 CString cmd;
835 cmd.Format(_T("git.exe branch -a --contains %s"), pSelLogEntry->m_CommitHash.ToString());
836 CProgressDlg progress;
837 progress.m_GitCmd = cmd;
838 progress.DoModal();
840 break;
841 case ID_DELETE:
843 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
844 if (!branch)
846 CMessageBox::Show(NULL,IDS_ERROR_NOREF,IDS_APPNAME,MB_OK|MB_ICONERROR);
847 return;
849 CString shortname;
850 CString cmd;
851 if (this->GetShortName(*branch, shortname, _T("refs/remotes/")))
853 CString msg;
854 msg.Format(IDS_PROC_DELETEREMOTEBRANCH, *branch);
855 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)));
856 if (result == 1)
858 CString remoteName = shortname.Left(shortname.Find('/'));
859 shortname = shortname.Mid(shortname.Find('/') + 1);
860 if(CAppUtils::IsSSHPutty())
861 CAppUtils::LaunchPAgent(NULL, &remoteName);
863 cmd.Format(L"git.exe push \"%s\" :refs/heads/%s", remoteName, shortname);
865 else if (result == 2)
866 cmd.Format(_T("git.exe branch -r -D -- %s"), shortname);
867 else
868 return;
870 else if (this->GetShortName(*branch, shortname, _T("refs/stash")))
872 if (CMessageBox::Show(NULL, IDS_PROC_DELETEALLSTASH, IDS_APPNAME, 2, IDI_QUESTION, IDS_DELETEBUTTON, IDS_ABORTBUTTON) == 1)
873 cmd.Format(_T("git.exe stash clear"));
874 else
875 return;
877 else
879 CString msg;
880 msg.Format(IDS_PROC_DELETEBRANCHTAG, *branch);
881 if (CMessageBox::Show(NULL, msg, _T("TortoiseGit"), 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
883 if(this->GetShortName(*branch,shortname,_T("refs/heads/")))
885 cmd.Format(_T("git.exe branch -D -- %s"),shortname);
888 if(this->GetShortName(*branch,shortname,_T("refs/tags/")))
890 cmd.Format(_T("git.exe tag -d -- %s"),shortname);
894 if (!cmd.IsEmpty())
896 CString out;
897 if(g_Git.Run(cmd,&out,CP_UTF8))
899 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
901 this->ReloadHashMap();
902 CRect rect;
903 this->GetItemRect(FirstSelect,&rect,LVIR_BOUNDS);
904 this->InvalidateRect(rect);
907 break;
909 case ID_FINDENTRY:
911 m_nSearchIndex = GetSelectionMark();
912 if (m_nSearchIndex < 0)
913 m_nSearchIndex = 0;
914 if (m_pFindDialog)
916 break;
918 else
920 m_pFindDialog = new CFindDlg();
921 m_pFindDialog->Create(this);
924 break;
925 case ID_MERGEREV:
927 CString str = pSelLogEntry->m_CommitHash.ToString();
928 if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
929 str = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
930 // we need an URL to complete this command, so error out if we can't get an URL
931 if(CAppUtils::Merge(&str))
933 this->Refresh();
936 break;
937 case ID_REVERTREV:
939 int parent = 0;
940 if (GetSelectedCount() == 1)
942 parent = cmd >> 16;
943 if (parent > pSelLogEntry->m_ParentHash.size())
945 CString str;
946 str.Format(IDS_PROC_NOPARENT, parent);
947 MessageBox(str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
948 return;
952 if (!this->RevertSelectedCommits(parent))
954 if (CMessageBox::Show(m_hWnd, IDS_REVREVERTED, IDS_APPNAME, 1, IDI_QUESTION, IDS_OKBUTTON, IDS_COMMITBUTTON) == 2)
956 CTGitPathList pathlist;
957 CTGitPathList selectedlist;
958 pathlist.AddPath(this->m_Path);
959 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
960 CString str;
961 CAppUtils::Commit(CString(), false, str, pathlist, selectedlist, bSelectFilesForCommit);
963 this->Refresh();
966 break;
967 case ID_EDITNOTE:
969 CAppUtils::EditNote(pSelLogEntry);
970 this->SetItemState(FirstSelect, 0, LVIS_SELECTED);
971 this->SetItemState(FirstSelect, LVIS_SELECTED, LVIS_SELECTED);
973 break;
974 default:
975 //CMessageBox::Show(NULL,_T("Have not implemented"),_T("TortoiseGit"),MB_OK);
976 break;
977 #if 0
979 case ID_BLAMECOMPARE:
981 //user clicked on the menu item "compare with working copy"
982 //now first get the revision which is selected
983 if (PromptShown())
985 GitDiff diff(this, this->m_hWnd, true);
986 diff.SetHEADPeg(m_LogRevision);
987 diff.ShowCompare(m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), false, true);
989 else
990 CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), m_LogRevision, false, false, true);
992 break;
993 case ID_BLAMETWO:
995 //user clicked on the menu item "compare and blame revisions"
996 if (PromptShown())
998 GitDiff diff(this, this->m_hWnd, true);
999 diff.SetHEADPeg(m_LogRevision);
1000 diff.ShowCompare(CTGitPath(pathURL), revSelected2, CTGitPath(pathURL), revSelected, GitRev(), false, true);
1002 else
1003 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revSelected2, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
1005 break;
1006 case ID_BLAMEWITHPREVIOUS:
1008 //user clicked on the menu item "Compare and Blame with previous revision"
1009 if (PromptShown())
1011 GitDiff diff(this, this->m_hWnd, true);
1012 diff.SetHEADPeg(m_LogRevision);
1013 diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), false, true);
1015 else
1016 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
1018 break;
1020 case ID_OPENWITH:
1021 bOpenWith = true;
1022 case ID_OPEN:
1024 CProgressDlg progDlg;
1025 progDlg.SetTitle(IDS_APPNAME);
1026 progDlg.SetAnimation(IDR_DOWNLOAD);
1027 CString sInfoLine;
1028 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, m_path.GetWinPath(), (LPCTSTR)revSelected.ToString());
1029 progDlg.SetLine(1, sInfoLine, true);
1030 SetAndClearProgressInfo(&progDlg);
1031 progDlg.ShowModeless(m_hWnd);
1032 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, m_path, revSelected);
1033 bool bSuccess = true;
1034 if (!Cat(m_path, GitRev(GitRev::REV_HEAD), revSelected, tempfile))
1036 bSuccess = false;
1037 // try again, but with the selected revision as the peg revision
1038 if (!Cat(m_path, revSelected, revSelected, tempfile))
1040 progDlg.Stop();
1041 SetAndClearProgressInfo((HWND)NULL);
1042 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1043 EnableOKButton();
1044 break;
1046 bSuccess = true;
1048 if (bSuccess)
1050 progDlg.Stop();
1051 SetAndClearProgressInfo((HWND)NULL);
1052 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1053 int ret = 0;
1054 if (!bOpenWith)
1055 ret = (int)ShellExecute(this->m_hWnd, NULL, tempfile.GetWinPath(), NULL, NULL, SW_SHOWNORMAL);
1056 if ((ret <= HINSTANCE_ERROR)||bOpenWith)
1058 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1059 cmd += tempfile.GetWinPathString() + _T(" ");
1060 CAppUtils::LaunchApplication(cmd, NULL, false);
1064 break;
1065 case ID_BLAME:
1067 CBlameDlg dlg;
1068 dlg.EndRev = revSelected;
1069 if (dlg.DoModal() == IDOK)
1071 CBlame blame;
1072 CString tempfile;
1073 CString logfile;
1074 tempfile = blame.BlameToTempFile(m_path, dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, _T(""), dlg.m_bIncludeMerge, TRUE, TRUE);
1075 if (!tempfile.IsEmpty())
1077 if (dlg.m_bTextView)
1079 //open the default text editor for the result file
1080 CAppUtils::StartTextViewer(tempfile);
1082 else
1084 CString sParams = _T("/path:\"") + m_path.GetGitPathString() + _T("\" ");
1085 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(m_path.GetFileOrDirectoryName()),sParams))
1087 break;
1091 else
1093 CMessageBox::Show(this->m_hWnd, blame.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1097 break;
1098 case ID_UPDATE:
1100 CString sCmd;
1101 CString url = _T("tgit:")+pathURL;
1102 sCmd.Format(_T("%s /command:update /path:\"%s\" /rev:%ld"),
1103 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
1104 (LPCTSTR)m_path.GetWinPath(), (LONG)revSelected);
1105 CAppUtils::LaunchApplication(sCmd, NULL, false);
1107 break;
1109 case ID_EDITLOG:
1111 EditLogMessage(selIndex);
1113 break;
1114 case ID_EDITAUTHOR:
1116 EditAuthor(selEntries);
1118 break;
1119 case ID_REVPROPS:
1121 CEditPropertiesDlg dlg;
1122 dlg.SetProjectProperties(&m_ProjectProperties);
1123 CTGitPathList escapedlist;
1124 dlg.SetPathList(CTGitPathList(CTGitPath(pathURL)));
1125 dlg.SetRevision(revSelected);
1126 dlg.RevProps(true);
1127 dlg.DoModal();
1129 break;
1131 case ID_EXPORT:
1133 CString sCmd;
1134 sCmd.Format(_T("%s /command:export /path:\"%s\" /revision:%ld"),
1135 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
1136 (LPCTSTR)pathURL, (LONG)revSelected);
1137 CAppUtils::LaunchApplication(sCmd, NULL, false);
1139 break;
1140 case ID_VIEWREV:
1142 CString url = m_ProjectProperties.sWebViewerRev;
1143 url = GetAbsoluteUrlFromRelativeUrl(url);
1144 url.Replace(_T("%REVISION%"), revSelected.ToString());
1145 if (!url.IsEmpty())
1146 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1148 break;
1149 case ID_VIEWPATHREV:
1151 CString relurl = pathURL;
1152 CString sRoot = GetRepositoryRoot(CTGitPath(relurl));
1153 relurl = relurl.Mid(sRoot.GetLength());
1154 CString url = m_ProjectProperties.sWebViewerPathRev;
1155 url = GetAbsoluteUrlFromRelativeUrl(url);
1156 url.Replace(_T("%REVISION%"), revSelected.ToString());
1157 url.Replace(_T("%PATH%"), relurl);
1158 if (!url.IsEmpty())
1159 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1161 break;
1162 #endif
1164 } // switch (cmd)
1166 theApp.DoWaitCursor(-1);
1169 void CGitLogList::SetSelectedAction(int action)
1171 POSITION pos = GetFirstSelectedItemPosition();
1172 int index;
1173 while(pos)
1175 index = GetNextSelectedItem(pos);
1176 ((GitRev*)m_arShownList[index])->GetAction(this) = action;
1177 CRect rect;
1178 this->GetItemRect(index,&rect,LVIR_BOUNDS);
1179 this->InvalidateRect(rect);
1183 void CGitLogList::ShiftSelectedAction()
1185 POSITION pos = GetFirstSelectedItemPosition();
1186 int index;
1187 while(pos)
1189 index = GetNextSelectedItem(pos);
1190 int action = ((GitRev*)m_arShownList[index])->GetAction(this);
1191 switch (action)
1193 case CTGitPath::LOGACTIONS_REBASE_PICK:
1194 action = CTGitPath::LOGACTIONS_REBASE_SKIP;
1195 break;
1196 case CTGitPath::LOGACTIONS_REBASE_SKIP:
1197 action= CTGitPath::LOGACTIONS_REBASE_EDIT;
1198 break;
1199 case CTGitPath::LOGACTIONS_REBASE_EDIT:
1200 action = CTGitPath::LOGACTIONS_REBASE_SQUASH;
1201 break;
1202 case CTGitPath::LOGACTIONS_REBASE_SQUASH:
1203 action= CTGitPath::LOGACTIONS_REBASE_PICK;
1204 break;
1206 ((GitRev*)m_arShownList[index])->GetAction(this) = action;
1207 CRect rect;
1208 this->GetItemRect(index, &rect, LVIR_BOUNDS);
1209 this->InvalidateRect(rect);