Renamed Tortoise*.exe to TortoiseGit*.exe
[TortoiseGit.git] / src / TortoiseProc / GitLogListAction.cpp
blob3d1a0a5b9f9be930b6f67f2f5e1bee09b5e7709c
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 (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.IsVisible())
98 progress.FormatNonPathLine(1, IDS_PROC_REVERTCOMMIT, r1->m_CommitHash.ToString());
99 progress.FormatNonPathLine(2, _T("%s"), r1->GetSubject());
100 progress.SetProgress(i, this->GetSelectedCount());
102 i++;
104 if(r1->m_CommitHash.IsEmpty())
105 continue;
107 CString cmd, output, merge;
108 if (parent)
109 merge.Format(_T("-m %d "), parent);
110 cmd.Format(_T("git.exe revert --no-edit --no-commit %s%s"), merge, 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.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.empty())
145 return 0;
147 CSysProgressDlg progress;
148 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_CHERRYPICK)));
149 progress.SetAnimation(IDR_MOVEANI);
150 progress.SetTime(true);
151 progress.ShowModeless(this);
153 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
155 for (int i = (int)logs.size() - 1; i >= 0; i--)
157 if (progress.IsVisible())
159 progress.FormatNonPathLine(1, IDS_PROC_PICK, logs.GetGitRevAt(i).m_CommitHash.ToString());
160 progress.FormatNonPathLine(2, _T("%s"), logs.GetGitRevAt(i).GetSubject());
161 progress.SetProgress64(logs.size() - i, logs.size());
163 if (progress.HasUserCancelled())
165 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED))));
166 return -1;
168 CString cmd,out;
169 cmd.Format(_T("git.exe cherry-pick %s"),logs.GetGitRevAt(i).m_CommitHash.ToString());
170 out.Empty();
171 if(g_Git.Run(cmd,&out,CP_UTF8))
173 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_CHERRYPICKFAILED)) + _T(":\r\n\r\n") + out));
174 return -1;
178 return 0;
181 void CGitLogList::ContextMenuAction(int cmd,int FirstSelect, int LastSelect, CMenu *popmenu)
183 POSITION pos = GetFirstSelectedItemPosition();
184 int indexNext = GetNextSelectedItem(pos);
185 if (indexNext < 0)
186 return;
188 GitRev* pSelLogEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(indexNext));
190 theApp.DoWaitCursor(1);
191 switch (cmd&0xFFFF)
193 case ID_COMMIT:
195 CTGitPathList pathlist;
196 CTGitPathList selectedlist;
197 pathlist.AddPath(this->m_Path);
198 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
199 CString str;
200 CAppUtils::Commit(CString(),false,str,
201 pathlist,selectedlist,bSelectFilesForCommit);
202 //this->Refresh();
203 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH,0);
205 break;
206 case ID_GNUDIFF1: // compare with WC, unified
208 CString tempfile=GetTempFile();
209 CString command;
210 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
211 if(!r1->m_CommitHash.IsEmpty())
213 CString merge;
214 CString hash2;
215 cmd >>= 16;
216 if( (cmd&0xFFFF) == 0xFFFF)
218 merge=_T("-m");
220 else if((cmd&0xFFFF) == 0xFFFE)
222 merge=_T("-c");
224 else
226 if(cmd > r1->m_ParentHash.size())
228 CString str;
229 str.Format(IDS_PROC_NOPARENT, cmd);
230 MessageBox(str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
231 return;
233 else
235 if(cmd>0)
236 hash2 = r1->m_ParentHash[cmd-1].ToString();
239 command.Format(_T("git.exe diff-tree %s -r -p --stat %s %s"), merge, hash2, r1->m_CommitHash.ToString());
241 else
242 command.Format(_T("git.exe diff -r -p --stat"));
244 g_Git.RunLogFile(command,tempfile);
245 CAppUtils::StartUnifiedDiffViewer(tempfile, r1->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()) + _T(":") + r1->GetSubject());
247 break;
249 case ID_GNUDIFF2: // compare two revisions, unified
251 CString tempfile=GetTempFile();
252 CString cmd;
253 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
254 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
256 if( r1->m_CommitHash.IsEmpty()) {
257 cmd.Format(_T("git.exe diff -r -p --stat %s"),r2->m_CommitHash.ToString());
259 else if( r2->m_CommitHash.IsEmpty()) {
260 cmd.Format(_T("git.exe diff -r -p --stat %s"),r1->m_CommitHash.ToString());
262 else
264 cmd.Format(_T("git.exe diff-tree -r -p --stat %s %s"),r2->m_CommitHash.ToString(),r1->m_CommitHash.ToString());
267 g_Git.RunLogFile(cmd,tempfile);
268 CAppUtils::StartUnifiedDiffViewer(tempfile, r2->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()) + _T(":") + r1->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()));
271 break;
273 case ID_COMPARETWO: // compare two revisions
275 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
276 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
277 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
278 CGitDiff::DiffCommit(this->m_Path, r1,r2);
279 else
281 CString path1 = m_Path.GetGitPathString();
282 // start with 1 (0 = working copy changes)
283 for (int i = 1; i < FirstSelect; i++)
285 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
286 CTGitPathList list = first->GetFiles(NULL);
287 CTGitPath * file = list.LookForGitPath(path1);
288 if (file && !file->GetGitOldPathString().IsEmpty())
289 path1 = file->GetGitOldPathString();
291 CString path2 = path1;
292 for (int i = FirstSelect; i < LastSelect; i++)
294 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
295 CTGitPathList list = first->GetFiles(NULL);
296 CTGitPath * file = list.LookForGitPath(path2);
297 if (file && !file->GetGitOldPathString().IsEmpty())
298 path2 = file->GetGitOldPathString();
300 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), r1, r2);
304 break;
306 case ID_COMPARE: // compare revision with WC
308 GitRev * r1 = &m_wcRev;
309 GitRev * r2 = pSelLogEntry;
311 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
312 CGitDiff::DiffCommit(this->m_Path, r1,r2);
313 else
315 CString path1 = m_Path.GetGitPathString();
316 // start with 1 (0 = working copy changes)
317 for (int i = 1; i < FirstSelect; i++)
319 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
320 CTGitPathList list = first->GetFiles(NULL);
321 CTGitPath * file = list.LookForGitPath(path1);
322 if (file && !file->GetGitOldPathString().IsEmpty())
323 path1 = file->GetGitOldPathString();
325 CGitDiff::DiffCommit(m_Path, CTGitPath(path1), r1, r2);
328 //user clicked on the menu item "compare with working copy"
329 //if (PromptShown())
331 // GitDiff diff(this, m_hWnd, true);
332 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
333 // diff.SetHEADPeg(m_LogRevision);
334 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
336 //else
337 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
339 break;
341 case ID_COMPAREWITHPREVIOUS:
343 CFileDiffDlg dlg;
345 if (!pSelLogEntry->m_ParentHash.empty())
346 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
348 cmd>>=16;
349 cmd&=0xFFFF;
351 if(cmd == 0)
352 cmd=1;
354 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
355 CGitDiff::DiffCommit(m_Path, pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString());
356 else
358 CString path1 = m_Path.GetGitPathString();
359 // start with 1 (0 = working copy changes)
360 for (int i = 1; i < indexNext; i++)
362 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
363 CTGitPathList list = first->GetFiles(NULL);
364 CTGitPath * file = list.LookForGitPath(path1);
365 if (file && !file->GetGitOldPathString().IsEmpty())
366 path1 = file->GetGitOldPathString();
368 CString path2 = path1;
369 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(indexNext));
370 CTGitPathList list = first->GetFiles(NULL);
371 CTGitPath * file = list.LookForGitPath(path2);
372 if (file && !file->GetGitOldPathString().IsEmpty())
373 path2 = file->GetGitOldPathString();
375 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString());
378 else
380 CMessageBox::Show(NULL, IDS_PROC_NOPREVIOUSVERSION, IDS_APPNAME, MB_OK);
382 //if (PromptShown())
384 // GitDiff diff(this, m_hWnd, true);
385 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
386 // diff.SetHEADPeg(m_LogRevision);
387 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
389 //else
390 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
392 break;
393 case ID_COPYCLIPBOARD:
395 CopySelectionToClipBoard();
397 break;
398 case ID_COPYCLIPBOARDMESSAGES:
400 if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0)
401 CopySelectionToClipBoard(ID_COPY_SUBJECT);
402 else
403 CopySelectionToClipBoard(ID_COPY_MESSAGE);
405 break;
406 case ID_COPYHASH:
408 CopySelectionToClipBoard(ID_COPY_HASH);
410 break;
411 case ID_EXPORT:
413 CString str=pSelLogEntry->m_CommitHash.ToString();
414 CAppUtils::Export(&str);
416 break;
417 case ID_CREATE_BRANCH:
418 case ID_CREATE_TAG:
420 CString str = pSelLogEntry->m_CommitHash.ToString();
421 // try to guess remote branch in order to enable tracking
422 for (int i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); i++)
424 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/remotes/")) == 0)
426 str = m_HashMap[pSelLogEntry->m_CommitHash][i];
427 break;
430 CAppUtils::CreateBranchTag((cmd&0xFFFF) == ID_CREATE_TAG, &str);
431 ReloadHashMap();
432 Invalidate();
433 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
435 break;
436 case ID_SWITCHTOREV:
438 CString str = pSelLogEntry->m_CommitHash.ToString();
439 // try to guess remote branch in order to recommend good branch name and tracking
440 for (int i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); i++)
442 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/remotes/")) == 0)
444 str = m_HashMap[pSelLogEntry->m_CommitHash][i];
445 break;
448 CAppUtils::Switch(str);
450 ReloadHashMap();
451 Invalidate();
452 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
453 break;
454 case ID_SWITCHBRANCH:
455 if(popmenu)
457 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
458 if(branch)
460 CString name;
461 if(branch->Find(_T("refs/heads/")) ==0 )
462 name = branch->Mid(11);
463 else
464 name = *branch;
466 CAppUtils::PerformSwitch(name);
468 ReloadHashMap();
469 Invalidate();
470 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
472 break;
473 case ID_RESET:
475 CString str = pSelLogEntry->m_CommitHash.ToString();
476 if (CAppUtils::GitReset(&str))
478 ResetWcRev(true);
479 ReloadHashMap();
480 Invalidate();
483 break;
484 case ID_REBASE_PICK:
485 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_PICK);
486 break;
487 case ID_REBASE_EDIT:
488 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_EDIT);
489 break;
490 case ID_REBASE_SQUASH:
491 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_SQUASH);
492 break;
493 case ID_REBASE_SKIP:
494 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_SKIP);
495 break;
496 case ID_COMBINE_COMMIT:
498 CString head;
499 CGitHash headhash;
500 CGitHash hashFirst,hashLast;
502 int headindex=GetHeadIndex();
503 if(headindex>=0) //incase show all branch, head is not the first commits.
505 head.Format(_T("HEAD~%d"),FirstSelect-headindex);
506 hashFirst=g_Git.GetHash(head);
508 head.Format(_T("HEAD~%d"),LastSelect-headindex);
509 hashLast=g_Git.GetHash(head);
512 GitRev* pFirstEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
513 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
514 if(pFirstEntry->m_CommitHash != hashFirst || pLastEntry->m_CommitHash != hashLast)
516 CMessageBox::Show(NULL, IDS_PROC_CANNOTCOMBINE, IDS_APPNAME, MB_OK);
517 break;
520 GitRev lastRevision;
523 lastRevision.GetParentFromHash(hashLast);
525 catch (char* msg)
527 CString err(msg);
528 MessageBox(_T("Could not get parent(s) of ") + hashLast.ToString() + _T(".\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
529 break;
532 headhash=g_Git.GetHash(_T("HEAD"));
534 if(!g_Git.CheckCleanWorkTree())
536 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
537 break;
539 CString cmd,out;
541 //Use throw to abort this process (reset back to original HEAD)
544 cmd.Format(_T("git.exe reset --hard %s"),pFirstEntry->m_CommitHash.ToString());
545 if(g_Git.Run(cmd,&out,CP_UTF8))
547 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
548 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1)) + _T("\r\n\r\n") + out));
550 cmd.Format(_T("git.exe reset --mixed %s"),hashLast.ToString());
551 if(g_Git.Run(cmd,&out,CP_UTF8))
553 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
554 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2)) + _T("\r\n\r\n")+out));
557 CTGitPathList PathList;
558 /* don't why must add --stat to get action status*/
559 /* first -z will be omitted by gitdll*/
560 if(g_Git.GetDiffPath(&PathList,&pFirstEntry->m_CommitHash,&hashLast,"-z --stat -r"))
562 CMessageBox::Show(NULL,_T("Get Diff file list error"),_T("TortoiseGit"),MB_OK);
563 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not get changed file list aborting...\r\n\r\n")+out));
566 for(int i=0;i<PathList.GetCount();i++)
568 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_ADDED)
570 cmd.Format(_T("git.exe add -- \"%s\""), PathList[i].GetGitPathString());
571 if (g_Git.Run(cmd, &out, CP_UTF8))
573 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
574 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not add new file aborting...\r\n\r\n")+out));
578 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_DELETED)
580 cmd.Format(_T("git.exe rm -- \"%s\""), PathList[i].GetGitPathString());
581 if (g_Git.Run(cmd, &out, CP_UTF8))
583 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
584 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not rm file aborting...\r\n\r\n")+out));
589 CCommitDlg dlg;
590 for(int i=FirstSelect;i<=LastSelect;i++)
592 GitRev* pRev = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
593 dlg.m_sLogMessage+=pRev->GetSubject()+_T("\n")+pRev->GetBody();
594 dlg.m_sLogMessage+=_T("\n");
596 dlg.m_bWholeProject=true;
597 dlg.m_bSelectFilesForCommit = true;
598 dlg.m_bForceCommitAmend=true;
599 if (lastRevision.ParentsCount() != 1)
601 CMessageBox::Show(NULL, _T("The following commit dialog can only show changes of oldest commit if it has exactly one parent. This is not the case right now."), _T("TortoiseGit"),MB_OK);
602 dlg.m_bAmendDiffToLastCommit = TRUE;
604 else
605 dlg.m_bAmendDiffToLastCommit = FALSE;
606 dlg.m_bNoPostActions=true;
607 dlg.m_AmendStr=dlg.m_sLogMessage;
609 if (dlg.DoModal() == IDOK)
611 if(pFirstEntry->m_CommitHash!=headhash)
613 //Commitrange firstEntry..headhash (from top of combine to original head) needs to be 'cherry-picked'
614 //on top of new commit.
615 //Use the rebase --onto command for it.
617 //All this can be done in one step using the following command:
618 //cmd.Format(_T("git.exe format-patch --stdout --binary --full-index -k %s..%s | git am -k -3"),
619 // pFirstEntry->m_CommitHash,
620 // headhash);
621 //But I am not sure if a '|' is going to work in a CreateProcess() call.
623 //Later the progress dialog could be used to execute these steps.
625 if(CherryPickFrom(pFirstEntry->m_CommitHash.ToString(),headhash))
627 CString msg;
628 msg.Format(_T("Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n"));
629 throw std::exception(CUnicodeUtils::GetUTF8(msg));
631 #if 0
632 CString currentBranch=g_Git.GetCurrentBranch();
633 cmd.Format(_T("git.exe rebase --onto \"%s\" %s %s"),
634 currentBranch,
635 pFirstEntry->m_CommitHash,
636 headhash);
637 if(g_Git.Run(cmd,&out,CP_UTF8)!=0)
639 CString msg;
640 msg.Format(_T("Error while rebasing commits on top of combined commits. Aborting.\r\n\r\n%s"),out);
641 // CMessageBox::Show(NULL,msg,_T("TortoiseGit"),MB_OK);
642 g_Git.Run(_T("git.exe rebase --abort"),&out,CP_UTF8);
643 throw std::exception(CUnicodeUtils::GetUTF8(msg));
646 //HEAD is now on <no branch>.
647 //The following steps are to get HEAD back on the original branch and reset the branch to the new HEAD
648 //To avoid 2 working copy changes, we could use git branch -f <original branch> <hash new head>
649 //And then git checkout <original branch>
650 //But I don't know if 'git branch -f' removes tracking options. So for now, do a checkout and a reset.
652 //Store new HEAD
653 CString newHead=g_Git.GetHash(CString(_T("HEAD")));
655 //Checkout working branch
656 cmd.Format(_T("git.exe checkout -f \"%s\""),currentBranch);
657 if(g_Git.Run(cmd,&out,CP_UTF8))
658 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not checkout original branch. Aborting...\r\n\r\n")+out));
660 //Reset to new HEAD
661 cmd.Format(_T("git.exe reset --hard %s"),newHead);
662 if(g_Git.Run(cmd,&out,CP_UTF8))
663 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not reset to new head. Aborting...\r\n\r\n")+out));
664 #endif
667 else
668 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED))));
670 catch(std::exception& e)
672 CMessageBox::Show(NULL, CUnicodeUtils::GetUnicode(CStringA(e.what())), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
673 cmd.Format(_T("git.exe reset --hard %s"),headhash.ToString());
674 out.Empty();
675 if(g_Git.Run(cmd,&out,CP_UTF8))
677 CMessageBox::Show(NULL, CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD)) + _T("\r\n\r\n") + out, _T("TortoiseGit"), MB_OK);
680 Refresh();
682 break;
684 case ID_CHERRY_PICK:
685 if(!g_Git.CheckCleanWorkTree())
687 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
690 else
692 CRebaseDlg dlg;
693 dlg.m_IsCherryPick = TRUE;
694 dlg.m_Upstream = this->m_CurrentBranch;
695 POSITION pos = GetFirstSelectedItemPosition();
696 while(pos)
698 int indexNext = GetNextSelectedItem(pos);
699 dlg.m_CommitList.m_logEntries.push_back( ((GitRev*)m_arShownList[indexNext])->m_CommitHash );
700 dlg.m_CommitList.m_LogCache.m_HashMap[((GitRev*)m_arShownList[indexNext])->m_CommitHash]=*(GitRev*)m_arShownList[indexNext];
701 dlg.m_CommitList.m_logEntries.GetGitRevAt(dlg.m_CommitList.m_logEntries.size()-1).GetAction(this) |= CTGitPath::LOGACTIONS_REBASE_PICK;
704 if(dlg.DoModal() == IDOK)
706 Refresh();
709 break;
710 case ID_REBASE_TO_VERSION:
711 if(!g_Git.CheckCleanWorkTree())
713 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
716 else
718 CRebaseDlg dlg;
719 dlg.m_Upstream = pSelLogEntry->m_CommitHash;
721 if(dlg.DoModal() == IDOK)
723 Refresh();
727 break;
729 case ID_STASH_SAVE:
730 if (CAppUtils::StashSave())
731 Refresh();
732 break;
734 case ID_STASH_POP:
735 if (CAppUtils::StashPop())
736 Refresh();
737 break;
739 case ID_STASH_LIST:
740 CAppUtils::RunTortoiseGitProc(_T("/command:reflog /ref:refs/stash"));
741 break;
743 case ID_REFLOG_STASH_APPLY:
744 if (CAppUtils::StashApply(pSelLogEntry->m_Ref))
745 Refresh();
746 break;
748 case ID_REFLOG_DEL:
750 CString str;
751 if (GetSelectedCount() > 1)
752 str.Format(IDS_PROC_DELETENREFS, GetSelectedCount());
753 else
754 str.Format(IDS_PROC_DELETEREF, pSelLogEntry->m_Ref);
756 if (CMessageBox::Show(NULL, str, _T("TortoiseGit"), 1, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
757 return;
759 std::vector<CString> refsToDelete;
760 POSITION pos = GetFirstSelectedItemPosition();
761 while (pos)
763 CString ref = ((GitRev *)m_arShownList[GetNextSelectedItem(pos)])->m_Ref;
764 if (ref.Find(_T("refs/")) == 0)
765 ref = ref.Mid(5);
766 int refpos = ref.ReverseFind('{');
767 if (refpos > 0 && ref.Mid(refpos, 2) != _T("@{"))
768 ref = ref.Left(refpos) + _T("@")+ ref.Mid(refpos);
769 refsToDelete.push_back(ref);
772 for (std::vector<CString>::const_reverse_iterator revIt = refsToDelete.rbegin(); revIt != refsToDelete.rend(); revIt++)
774 CString ref = *revIt;
775 CString cmd, out;
776 if (ref.Find(_T("stash")) == 0)
777 cmd.Format(_T("git.exe stash drop %s"), ref);
778 else
779 cmd.Format(_T("git.exe reflog delete %s"), ref);
781 if (g_Git.Run(cmd, &out, CP_UTF8))
782 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
784 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
787 break;
788 case ID_LOG:
790 CString cmd = _T("/command:log");
791 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
792 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
793 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
794 CAppUtils::RunTortoiseGitProc(cmd);
796 break;
797 case ID_CREATE_PATCH:
799 int select=this->GetSelectedCount();
800 CString cmd = _T("/command:formatpatch");
801 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
803 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
804 GitRev * r2 = NULL;
805 if(select == 1)
807 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString();
809 else
811 r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
812 if( this->m_IsOldFirst )
814 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString()+_T("~1");
815 cmd += _T(" /endrev:")+r2->m_CommitHash.ToString();
818 else
820 cmd += _T(" /startrev:")+r2->m_CommitHash.ToString()+_T("~1");
821 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
826 CAppUtils::RunTortoiseGitProc(cmd);
828 break;
829 case ID_BISECTSTART:
831 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
832 GitRev * last = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
833 ASSERT(first != NULL && last != NULL);
835 CString firstBad = first->m_CommitHash.ToString();
836 if (!m_HashMap[first->m_CommitHash].empty())
837 firstBad = m_HashMap[first->m_CommitHash].at(0);
838 CString lastGood = last->m_CommitHash.ToString();
839 if (!m_HashMap[last->m_CommitHash].empty())
840 lastGood = m_HashMap[last->m_CommitHash].at(0);
842 if (CAppUtils::BisectStart(lastGood, firstBad))
843 Refresh();
845 break;
846 case ID_REPOBROWSE:
848 CString sCmd;
849 sCmd.Format(_T("/command:repobrowser /path:\"%s\" /rev:%s"), g_Git.m_CurrentDir, pSelLogEntry->m_CommitHash.ToString());
850 CAppUtils::RunTortoiseGitProc(sCmd);
852 break;
853 case ID_PUSH:
855 CString guessAssociatedBranch;
856 if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
857 guessAssociatedBranch = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
858 if (CAppUtils::Push(guessAssociatedBranch))
859 Refresh();
861 break;
862 case ID_FETCH:
864 if (CAppUtils::Fetch(_T(""), true))
865 Refresh();
867 break;
868 case ID_SHOWBRANCHES:
870 CString cmd;
871 cmd.Format(_T("git.exe branch -a --contains %s"), pSelLogEntry->m_CommitHash.ToString());
872 CProgressDlg progress;
873 progress.m_GitCmd = cmd;
874 progress.DoModal();
876 break;
877 case ID_DELETE:
879 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
880 if (!branch)
882 CMessageBox::Show(NULL,IDS_ERROR_NOREF,IDS_APPNAME,MB_OK|MB_ICONERROR);
883 return;
885 CString shortname;
886 CString cmd;
887 if (CGit::GetShortName(*branch, shortname, _T("refs/remotes/")))
889 CString msg;
890 msg.Format(IDS_PROC_DELETEREMOTEBRANCH, *branch);
891 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)));
892 if (result == 1)
894 CString remoteName = shortname.Left(shortname.Find('/'));
895 shortname = shortname.Mid(shortname.Find('/') + 1);
896 if(CAppUtils::IsSSHPutty())
897 CAppUtils::LaunchPAgent(NULL, &remoteName);
899 cmd.Format(L"git.exe push \"%s\" :refs/heads/%s", remoteName, shortname);
901 else if (result == 2)
902 cmd.Format(_T("git.exe branch -r -D -- %s"), shortname);
903 else
904 return;
906 else if (CGit::GetShortName(*branch, shortname, _T("refs/stash")))
908 if (CMessageBox::Show(NULL, IDS_PROC_DELETEALLSTASH, IDS_APPNAME, 2, IDI_QUESTION, IDS_DELETEBUTTON, IDS_ABORTBUTTON) == 1)
909 cmd.Format(_T("git.exe stash clear"));
910 else
911 return;
913 else
915 CString msg;
916 msg.Format(IDS_PROC_DELETEBRANCHTAG, *branch);
917 if (CMessageBox::Show(NULL, msg, _T("TortoiseGit"), 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
919 if(CGit::GetShortName(*branch,shortname,_T("refs/heads/")))
921 cmd.Format(_T("git.exe branch -D -- %s"),shortname);
924 if(CGit::GetShortName(*branch,shortname,_T("refs/tags/")))
926 cmd.Format(_T("git.exe tag -d -- %s"),shortname);
930 if (!cmd.IsEmpty())
932 CString out;
933 if(g_Git.Run(cmd,&out,CP_UTF8))
935 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
937 this->ReloadHashMap();
938 CRect rect;
939 this->GetItemRect(FirstSelect,&rect,LVIR_BOUNDS);
940 this->InvalidateRect(rect);
943 break;
945 case ID_FINDENTRY:
947 m_nSearchIndex = GetSelectionMark();
948 if (m_nSearchIndex < 0)
949 m_nSearchIndex = 0;
950 if (m_pFindDialog)
952 break;
954 else
956 m_pFindDialog = new CFindDlg();
957 m_pFindDialog->Create(this);
960 break;
961 case ID_MERGEREV:
963 CString str = pSelLogEntry->m_CommitHash.ToString();
964 if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
965 str = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
966 // we need an URL to complete this command, so error out if we can't get an URL
967 if(CAppUtils::Merge(&str))
969 this->Refresh();
972 break;
973 case ID_REVERTREV:
975 int parent = 0;
976 if (GetSelectedCount() == 1)
978 parent = cmd >> 16;
979 if (parent > pSelLogEntry->m_ParentHash.size())
981 CString str;
982 str.Format(IDS_PROC_NOPARENT, parent);
983 MessageBox(str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
984 return;
988 if (!this->RevertSelectedCommits(parent))
990 if (CMessageBox::Show(m_hWnd, IDS_REVREVERTED, IDS_APPNAME, 1, IDI_QUESTION, IDS_OKBUTTON, IDS_COMMITBUTTON) == 2)
992 CTGitPathList pathlist;
993 CTGitPathList selectedlist;
994 pathlist.AddPath(this->m_Path);
995 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
996 CString str;
997 CAppUtils::Commit(CString(), false, str, pathlist, selectedlist, bSelectFilesForCommit);
999 this->Refresh();
1002 break;
1003 case ID_EDITNOTE:
1005 CAppUtils::EditNote(pSelLogEntry);
1006 this->SetItemState(FirstSelect, 0, LVIS_SELECTED);
1007 this->SetItemState(FirstSelect, LVIS_SELECTED, LVIS_SELECTED);
1009 break;
1010 default:
1011 //CMessageBox::Show(NULL,_T("Have not implemented"),_T("TortoiseGit"),MB_OK);
1012 break;
1013 #if 0
1015 case ID_BLAMECOMPARE:
1017 //user clicked on the menu item "compare with working copy"
1018 //now first get the revision which is selected
1019 if (PromptShown())
1021 GitDiff diff(this, this->m_hWnd, true);
1022 diff.SetHEADPeg(m_LogRevision);
1023 diff.ShowCompare(m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), false, true);
1025 else
1026 CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), m_LogRevision, false, false, true);
1028 break;
1029 case ID_BLAMEWITHPREVIOUS:
1031 //user clicked on the menu item "Compare and Blame with previous revision"
1032 if (PromptShown())
1034 GitDiff diff(this, this->m_hWnd, true);
1035 diff.SetHEADPeg(m_LogRevision);
1036 diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), false, true);
1038 else
1039 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
1041 break;
1043 case ID_OPENWITH:
1044 bOpenWith = true;
1045 case ID_OPEN:
1047 CProgressDlg progDlg;
1048 progDlg.SetTitle(IDS_APPNAME);
1049 progDlg.SetAnimation(IDR_DOWNLOAD);
1050 CString sInfoLine;
1051 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, m_path.GetWinPath(), (LPCTSTR)revSelected.ToString());
1052 progDlg.SetLine(1, sInfoLine, true);
1053 SetAndClearProgressInfo(&progDlg);
1054 progDlg.ShowModeless(m_hWnd);
1055 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, m_path, revSelected);
1056 bool bSuccess = true;
1057 if (!Cat(m_path, GitRev(GitRev::REV_HEAD), revSelected, tempfile))
1059 bSuccess = false;
1060 // try again, but with the selected revision as the peg revision
1061 if (!Cat(m_path, revSelected, revSelected, tempfile))
1063 progDlg.Stop();
1064 SetAndClearProgressInfo((HWND)NULL);
1065 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1066 EnableOKButton();
1067 break;
1069 bSuccess = true;
1071 if (bSuccess)
1073 progDlg.Stop();
1074 SetAndClearProgressInfo((HWND)NULL);
1075 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1076 int ret = 0;
1077 if (!bOpenWith)
1078 ret = (int)ShellExecute(this->m_hWnd, NULL, tempfile.GetWinPath(), NULL, NULL, SW_SHOWNORMAL);
1079 if ((ret <= HINSTANCE_ERROR)||bOpenWith)
1081 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1082 cmd += tempfile.GetWinPathString() + _T(" ");
1083 CAppUtils::LaunchApplication(cmd, NULL, false);
1087 break;
1088 case ID_BLAME:
1090 CBlameDlg dlg;
1091 dlg.EndRev = revSelected;
1092 if (dlg.DoModal() == IDOK)
1094 CBlame blame;
1095 CString tempfile;
1096 CString logfile;
1097 tempfile = blame.BlameToTempFile(m_path, dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, _T(""), dlg.m_bIncludeMerge, TRUE, TRUE);
1098 if (!tempfile.IsEmpty())
1100 if (dlg.m_bTextView)
1102 //open the default text editor for the result file
1103 CAppUtils::StartTextViewer(tempfile);
1105 else
1107 CString sParams = _T("/path:\"") + m_path.GetGitPathString() + _T("\" ");
1108 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(m_path.GetFileOrDirectoryName()),sParams))
1110 break;
1114 else
1116 CMessageBox::Show(this->m_hWnd, blame.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1120 break;
1121 case ID_EXPORT:
1123 CString sCmd;
1124 sCmd.Format(_T("%s /command:export /path:\"%s\" /revision:%ld"),
1125 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseGitProc.exe")),
1126 (LPCTSTR)pathURL, (LONG)revSelected);
1127 CAppUtils::LaunchApplication(sCmd, NULL, false);
1129 break;
1130 case ID_VIEWREV:
1132 CString url = m_ProjectProperties.sWebViewerRev;
1133 url = GetAbsoluteUrlFromRelativeUrl(url);
1134 url.Replace(_T("%REVISION%"), revSelected.ToString());
1135 if (!url.IsEmpty())
1136 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1138 break;
1139 case ID_VIEWPATHREV:
1141 CString relurl = pathURL;
1142 CString sRoot = GetRepositoryRoot(CTGitPath(relurl));
1143 relurl = relurl.Mid(sRoot.GetLength());
1144 CString url = m_ProjectProperties.sWebViewerPathRev;
1145 url = GetAbsoluteUrlFromRelativeUrl(url);
1146 url.Replace(_T("%REVISION%"), revSelected.ToString());
1147 url.Replace(_T("%PATH%"), relurl);
1148 if (!url.IsEmpty())
1149 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1151 break;
1152 #endif
1154 } // switch (cmd)
1156 theApp.DoWaitCursor(-1);
1159 void CGitLogList::SetSelectedAction(int action)
1161 POSITION pos = GetFirstSelectedItemPosition();
1162 int index;
1163 while(pos)
1165 index = GetNextSelectedItem(pos);
1166 ((GitRev*)m_arShownList[index])->GetAction(this) = action;
1167 CRect rect;
1168 this->GetItemRect(index,&rect,LVIR_BOUNDS);
1169 this->InvalidateRect(rect);
1173 void CGitLogList::ShiftSelectedAction()
1175 POSITION pos = GetFirstSelectedItemPosition();
1176 int index;
1177 while(pos)
1179 index = GetNextSelectedItem(pos);
1180 int action = ((GitRev*)m_arShownList[index])->GetAction(this);
1181 switch (action)
1183 case CTGitPath::LOGACTIONS_REBASE_PICK:
1184 action = CTGitPath::LOGACTIONS_REBASE_SKIP;
1185 break;
1186 case CTGitPath::LOGACTIONS_REBASE_SKIP:
1187 action= CTGitPath::LOGACTIONS_REBASE_EDIT;
1188 break;
1189 case CTGitPath::LOGACTIONS_REBASE_EDIT:
1190 action = CTGitPath::LOGACTIONS_REBASE_SQUASH;
1191 break;
1192 case CTGitPath::LOGACTIONS_REBASE_SQUASH:
1193 action= CTGitPath::LOGACTIONS_REBASE_PICK;
1194 break;
1196 ((GitRev*)m_arShownList[index])->GetAction(this) = action;
1197 CRect rect;
1198 this->GetItemRect(index, &rect, LVIR_BOUNDS);
1199 this->InvalidateRect(rect);