Allow to use libgit2 for unified diff
[TortoiseGit.git] / src / TortoiseProc / GitLogListAction.cpp
blob7d63c2a64adf7de36cb4b266d7a9979793f3bf0b
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2013 - TortoiseGit
4 // Copyright (C) 2011-2013 - 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 CString range;
142 range.Format(_T("%s..%s"), from, to);
143 if (logs.ParserFromLog(nullptr, -1, 0, &range))
144 return -1;
146 if (logs.empty())
147 return 0;
149 CSysProgressDlg progress;
150 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_CHERRYPICK)));
151 progress.SetAnimation(IDR_MOVEANI);
152 progress.SetTime(true);
153 progress.ShowModeless(this);
155 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
157 for (int i = (int)logs.size() - 1; i >= 0; i--)
159 if (progress.IsVisible())
161 progress.FormatNonPathLine(1, IDS_PROC_PICK, logs.GetGitRevAt(i).m_CommitHash.ToString());
162 progress.FormatNonPathLine(2, _T("%s"), logs.GetGitRevAt(i).GetSubject());
163 progress.SetProgress64(logs.size() - i, logs.size());
165 if (progress.HasUserCancelled())
167 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED))));
169 CString cmd,out;
170 cmd.Format(_T("git.exe cherry-pick %s"),logs.GetGitRevAt(i).m_CommitHash.ToString());
171 out.Empty();
172 if(g_Git.Run(cmd,&out,CP_UTF8))
174 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_CHERRYPICKFAILED)) + _T(":\r\n\r\n") + out));
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 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
209 bool bMerge = false, bCombine = false;
210 CString hash2;
211 if(!r1->m_CommitHash.IsEmpty())
213 CString merge;
214 cmd >>= 16;
215 if( (cmd&0xFFFF) == 0xFFFF)
216 bMerge = true;
218 else if((cmd&0xFFFF) == 0xFFFE)
219 bCombine = true;
220 else
222 if(cmd > r1->m_ParentHash.size())
224 CString str;
225 str.Format(IDS_PROC_NOPARENT, cmd);
226 MessageBox(str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
227 return;
229 else
231 if(cmd>0)
232 hash2 = r1->m_ParentHash[cmd-1].ToString();
236 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), r1->m_CommitHash.ToString(), CTGitPath(), hash2, false, false, false, bMerge, bCombine);
238 break;
240 case ID_GNUDIFF2: // compare two revisions, unified
242 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
243 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
244 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), r1->m_CommitHash.ToString(), CTGitPath(), r2->m_CommitHash.ToString());
246 break;
248 case ID_COMPARETWO: // compare two revisions
250 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
251 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
252 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
253 CGitDiff::DiffCommit(this->m_Path, r1,r2);
254 else
256 CString path1 = m_Path.GetGitPathString();
257 // start with 1 (0 = working copy changes)
258 for (int i = 1; i < FirstSelect; ++i)
260 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
261 CTGitPathList list = first->GetFiles(NULL);
262 CTGitPath * file = list.LookForGitPath(path1);
263 if (file && !file->GetGitOldPathString().IsEmpty())
264 path1 = file->GetGitOldPathString();
266 CString path2 = path1;
267 for (int i = FirstSelect; i < LastSelect; ++i)
269 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
270 CTGitPathList list = first->GetFiles(NULL);
271 CTGitPath * file = list.LookForGitPath(path2);
272 if (file && !file->GetGitOldPathString().IsEmpty())
273 path2 = file->GetGitOldPathString();
275 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), r1, r2);
279 break;
281 case ID_COMPARE: // compare revision with WC
283 GitRev * r1 = &m_wcRev;
284 GitRev * r2 = pSelLogEntry;
286 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
287 CGitDiff::DiffCommit(this->m_Path, r1,r2);
288 else
290 CString path1 = m_Path.GetGitPathString();
291 // start with 1 (0 = working copy changes)
292 for (int i = 1; i < FirstSelect; ++i)
294 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
295 CTGitPathList list = first->GetFiles(NULL);
296 CTGitPath * file = list.LookForGitPath(path1);
297 if (file && !file->GetGitOldPathString().IsEmpty())
298 path1 = file->GetGitOldPathString();
300 CGitDiff::DiffCommit(m_Path, CTGitPath(path1), r1, r2);
303 //user clicked on the menu item "compare with working copy"
304 //if (PromptShown())
306 // GitDiff diff(this, m_hWnd, true);
307 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
308 // diff.SetHEADPeg(m_LogRevision);
309 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
311 //else
312 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
314 break;
316 case ID_COMPAREWITHPREVIOUS:
318 CFileDiffDlg dlg;
320 if (!pSelLogEntry->m_ParentHash.empty())
321 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
323 cmd>>=16;
324 cmd&=0xFFFF;
326 if(cmd == 0)
327 cmd=1;
329 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
330 CGitDiff::DiffCommit(m_Path, pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString());
331 else
333 CString path1 = m_Path.GetGitPathString();
334 // start with 1 (0 = working copy changes)
335 for (int i = 1; i < indexNext; ++i)
337 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
338 CTGitPathList list = first->GetFiles(NULL);
339 CTGitPath * file = list.LookForGitPath(path1);
340 if (file && !file->GetGitOldPathString().IsEmpty())
341 path1 = file->GetGitOldPathString();
343 CString path2 = path1;
344 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(indexNext));
345 CTGitPathList list = first->GetFiles(NULL);
346 CTGitPath * file = list.LookForGitPath(path2);
347 if (file && !file->GetGitOldPathString().IsEmpty())
348 path2 = file->GetGitOldPathString();
350 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString());
353 else
355 CMessageBox::Show(NULL, IDS_PROC_NOPREVIOUSVERSION, IDS_APPNAME, MB_OK);
357 //if (PromptShown())
359 // GitDiff diff(this, m_hWnd, true);
360 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
361 // diff.SetHEADPeg(m_LogRevision);
362 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
364 //else
365 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
367 break;
368 case ID_COPYCLIPBOARD:
370 CopySelectionToClipBoard();
372 break;
373 case ID_COPYCLIPBOARDMESSAGES:
375 if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0)
376 CopySelectionToClipBoard(ID_COPY_SUBJECT);
377 else
378 CopySelectionToClipBoard(ID_COPY_MESSAGE);
380 break;
381 case ID_COPYHASH:
383 CopySelectionToClipBoard(ID_COPY_HASH);
385 break;
386 case ID_EXPORT:
388 CString str=pSelLogEntry->m_CommitHash.ToString();
389 CAppUtils::Export(&str);
391 break;
392 case ID_CREATE_BRANCH:
393 case ID_CREATE_TAG:
395 CString str = pSelLogEntry->m_CommitHash.ToString();
396 // try to guess remote branch in order to enable tracking
397 for (int i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
399 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/remotes/")) == 0)
401 str = m_HashMap[pSelLogEntry->m_CommitHash][i];
402 break;
405 CAppUtils::CreateBranchTag((cmd&0xFFFF) == ID_CREATE_TAG, &str);
406 ReloadHashMap();
407 Invalidate();
408 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
410 break;
411 case ID_SWITCHTOREV:
413 CString str = pSelLogEntry->m_CommitHash.ToString();
414 // try to guess remote branch in order to recommend good branch name and tracking
415 for (int i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
417 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/remotes/")) == 0)
419 str = m_HashMap[pSelLogEntry->m_CommitHash][i];
420 break;
423 CAppUtils::Switch(str);
425 ReloadHashMap();
426 Invalidate();
427 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
428 break;
429 case ID_SWITCHBRANCH:
430 if(popmenu)
432 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
433 if(branch)
435 CString name;
436 if(branch->Find(_T("refs/heads/")) ==0 )
437 name = branch->Mid(11);
438 else
439 name = *branch;
441 CAppUtils::PerformSwitch(name);
443 ReloadHashMap();
444 Invalidate();
445 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
447 break;
448 case ID_RESET:
450 CString str = pSelLogEntry->m_CommitHash.ToString();
451 if (CAppUtils::GitReset(&str))
453 ResetWcRev(true);
454 ReloadHashMap();
455 Invalidate();
458 break;
459 case ID_REBASE_PICK:
460 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_PICK);
461 break;
462 case ID_REBASE_EDIT:
463 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_EDIT);
464 break;
465 case ID_REBASE_SQUASH:
466 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_SQUASH);
467 break;
468 case ID_REBASE_SKIP:
469 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_SKIP);
470 break;
471 case ID_COMBINE_COMMIT:
473 CString head;
474 CGitHash headhash;
475 CGitHash hashFirst,hashLast;
477 int headindex=GetHeadIndex();
478 if(headindex>=0) //incase show all branch, head is not the first commits.
480 head.Format(_T("HEAD~%d"),FirstSelect-headindex);
481 if (g_Git.GetHash(hashFirst, head))
483 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of first selected revision.")), _T("TortoiseGit"), MB_ICONERROR);
484 break;
487 head.Format(_T("HEAD~%d"),LastSelect-headindex);
488 if (g_Git.GetHash(hashLast, head))
490 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of last selected revision.")), _T("TortoiseGit"), MB_ICONERROR);
491 break;
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 GitRev lastRevision;
506 lastRevision.GetParentFromHash(hashLast);
508 catch (char* msg)
510 CString err(msg);
511 MessageBox(_T("Could not get parent(s) of ") + hashLast.ToString() + _T(".\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
512 break;
515 if (g_Git.GetHash(headhash, _T("HEAD")))
517 MessageBox(g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
518 break;
521 if(!g_Git.CheckCleanWorkTree())
523 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
524 break;
526 CString cmd,out;
528 //Use throw to abort this process (reset back to original HEAD)
531 cmd.Format(_T("git.exe reset --hard %s"),pFirstEntry->m_CommitHash.ToString());
532 if(g_Git.Run(cmd,&out,CP_UTF8))
534 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
535 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1)) + _T("\r\n\r\n") + out));
537 cmd.Format(_T("git.exe reset --mixed %s"),hashLast.ToString());
538 if(g_Git.Run(cmd,&out,CP_UTF8))
540 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
541 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2)) + _T("\r\n\r\n")+out));
544 CTGitPathList PathList;
545 /* don't why must add --stat to get action status*/
546 /* first -z will be omitted by gitdll*/
547 if(g_Git.GetDiffPath(&PathList,&pFirstEntry->m_CommitHash,&hashLast,"-z --stat -r"))
549 CMessageBox::Show(NULL,_T("Get Diff file list error"),_T("TortoiseGit"),MB_OK);
550 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not get changed file list aborting...\r\n\r\n")+out));
553 for (int i = 0; i < PathList.GetCount(); ++i)
555 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_ADDED)
557 cmd.Format(_T("git.exe add -- \"%s\""), PathList[i].GetGitPathString());
558 if (g_Git.Run(cmd, &out, CP_UTF8))
560 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
561 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not add new file aborting...\r\n\r\n")+out));
565 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_DELETED)
567 cmd.Format(_T("git.exe rm -- \"%s\""), PathList[i].GetGitPathString());
568 if (g_Git.Run(cmd, &out, CP_UTF8))
570 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
571 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not rm file aborting...\r\n\r\n")+out));
576 CCommitDlg dlg;
577 for (int i = FirstSelect; i <= LastSelect; ++i)
579 GitRev* pRev = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
580 dlg.m_sLogMessage+=pRev->GetSubject()+_T("\n")+pRev->GetBody();
581 dlg.m_sLogMessage+=_T("\n");
583 dlg.m_bWholeProject=true;
584 dlg.m_bSelectFilesForCommit = true;
585 dlg.m_bForceCommitAmend=true;
586 if (lastRevision.ParentsCount() != 1)
588 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);
589 dlg.m_bAmendDiffToLastCommit = TRUE;
591 else
592 dlg.m_bAmendDiffToLastCommit = FALSE;
593 dlg.m_bNoPostActions=true;
594 dlg.m_AmendStr=dlg.m_sLogMessage;
596 if (dlg.DoModal() == IDOK)
598 if(pFirstEntry->m_CommitHash!=headhash)
600 //Commitrange firstEntry..headhash (from top of combine to original head) needs to be 'cherry-picked'
601 //on top of new commit.
602 //Use the rebase --onto command for it.
604 //All this can be done in one step using the following command:
605 //cmd.Format(_T("git.exe format-patch --stdout --binary --full-index -k %s..%s | git am -k -3"),
606 // pFirstEntry->m_CommitHash,
607 // headhash);
608 //But I am not sure if a '|' is going to work in a CreateProcess() call.
610 //Later the progress dialog could be used to execute these steps.
612 if(CherryPickFrom(pFirstEntry->m_CommitHash.ToString(),headhash))
614 CString msg;
615 msg.Format(_T("Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n"));
616 throw std::exception(CUnicodeUtils::GetUTF8(msg));
618 #if 0
619 CString currentBranch=g_Git.GetCurrentBranch();
620 cmd.Format(_T("git.exe rebase --onto \"%s\" %s %s"),
621 currentBranch,
622 pFirstEntry->m_CommitHash,
623 headhash);
624 if(g_Git.Run(cmd,&out,CP_UTF8)!=0)
626 CString msg;
627 msg.Format(_T("Error while rebasing commits on top of combined commits. Aborting.\r\n\r\n%s"),out);
628 // CMessageBox::Show(NULL,msg,_T("TortoiseGit"),MB_OK);
629 g_Git.Run(_T("git.exe rebase --abort"),&out,CP_UTF8);
630 throw std::exception(CUnicodeUtils::GetUTF8(msg));
633 //HEAD is now on <no branch>.
634 //The following steps are to get HEAD back on the original branch and reset the branch to the new HEAD
635 //To avoid 2 working copy changes, we could use git branch -f <original branch> <hash new head>
636 //And then git checkout <original branch>
637 //But I don't know if 'git branch -f' removes tracking options. So for now, do a checkout and a reset.
639 //Store new HEAD
640 CString newHead=g_Git.GetHash(CString(_T("HEAD")));
642 //Checkout working branch
643 cmd.Format(_T("git.exe checkout -f \"%s\""),currentBranch);
644 if(g_Git.Run(cmd,&out,CP_UTF8))
645 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not checkout original branch. Aborting...\r\n\r\n")+out));
647 //Reset to new HEAD
648 cmd.Format(_T("git.exe reset --hard %s"),newHead);
649 if(g_Git.Run(cmd,&out,CP_UTF8))
650 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not reset to new head. Aborting...\r\n\r\n")+out));
651 #endif
654 else
655 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED))));
657 catch(std::exception& e)
659 CMessageBox::Show(NULL, CUnicodeUtils::GetUnicode(CStringA(e.what())), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
660 cmd.Format(_T("git.exe reset --hard %s"),headhash.ToString());
661 out.Empty();
662 if(g_Git.Run(cmd,&out,CP_UTF8))
664 CMessageBox::Show(NULL, CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD)) + _T("\r\n\r\n") + out, _T("TortoiseGit"), MB_OK);
667 Refresh();
669 break;
671 case ID_CHERRY_PICK:
672 if(!g_Git.CheckCleanWorkTree())
674 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
677 else
679 CRebaseDlg dlg;
680 dlg.m_IsCherryPick = TRUE;
681 dlg.m_Upstream = this->m_CurrentBranch;
682 POSITION pos = GetFirstSelectedItemPosition();
683 while(pos)
685 int indexNext = GetNextSelectedItem(pos);
686 dlg.m_CommitList.m_logEntries.push_back( ((GitRev*)m_arShownList[indexNext])->m_CommitHash );
687 dlg.m_CommitList.m_LogCache.m_HashMap[((GitRev*)m_arShownList[indexNext])->m_CommitHash]=*(GitRev*)m_arShownList[indexNext];
688 dlg.m_CommitList.m_logEntries.GetGitRevAt(dlg.m_CommitList.m_logEntries.size()-1).GetAction(this) |= CTGitPath::LOGACTIONS_REBASE_PICK;
691 if(dlg.DoModal() == IDOK)
693 Refresh();
696 break;
697 case ID_REBASE_TO_VERSION:
698 if(!g_Git.CheckCleanWorkTree())
700 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
703 else
705 CRebaseDlg dlg;
706 dlg.m_Upstream = pSelLogEntry->m_CommitHash;
708 if(dlg.DoModal() == IDOK)
710 Refresh();
714 break;
716 case ID_STASH_SAVE:
717 if (CAppUtils::StashSave())
718 Refresh();
719 break;
721 case ID_STASH_POP:
722 if (CAppUtils::StashPop())
723 Refresh();
724 break;
726 case ID_STASH_LIST:
727 CAppUtils::RunTortoiseGitProc(_T("/command:reflog /ref:refs/stash"));
728 break;
730 case ID_REFLOG_STASH_APPLY:
731 CAppUtils::StashApply(pSelLogEntry->m_Ref);
732 break;
734 case ID_REFLOG_DEL:
736 CString str;
737 if (GetSelectedCount() > 1)
738 str.Format(IDS_PROC_DELETENREFS, GetSelectedCount());
739 else
740 str.Format(IDS_PROC_DELETEREF, pSelLogEntry->m_Ref);
742 if (CMessageBox::Show(NULL, str, _T("TortoiseGit"), 1, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
743 return;
745 std::vector<CString> refsToDelete;
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);
755 refsToDelete.push_back(ref);
758 for (auto revIt = refsToDelete.rbegin(); revIt != refsToDelete.rend(); ++revIt)
760 CString ref = *revIt;
761 CString cmd, out;
762 if (ref.Find(_T("stash")) == 0)
763 cmd.Format(_T("git.exe stash drop %s"), ref);
764 else
765 cmd.Format(_T("git.exe reflog delete %s"), ref);
767 if (g_Git.Run(cmd, &out, CP_UTF8))
768 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
770 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
773 break;
774 case ID_LOG:
776 CString cmd = _T("/command:log");
777 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
778 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
779 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
780 CAppUtils::RunTortoiseGitProc(cmd);
782 break;
783 case ID_CREATE_PATCH:
785 int select=this->GetSelectedCount();
786 CString cmd = _T("/command:formatpatch");
787 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
789 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
790 GitRev * r2 = NULL;
791 if(select == 1)
793 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString();
795 else
797 r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
798 if( this->m_IsOldFirst )
800 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString()+_T("~1");
801 cmd += _T(" /endrev:")+r2->m_CommitHash.ToString();
804 else
806 cmd += _T(" /startrev:")+r2->m_CommitHash.ToString()+_T("~1");
807 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
812 CAppUtils::RunTortoiseGitProc(cmd);
814 break;
815 case ID_BISECTSTART:
817 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
818 GitRev * last = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
819 ASSERT(first != NULL && last != NULL);
821 CString firstBad = first->m_CommitHash.ToString();
822 if (!m_HashMap[first->m_CommitHash].empty())
823 firstBad = m_HashMap[first->m_CommitHash].at(0);
824 CString lastGood = last->m_CommitHash.ToString();
825 if (!m_HashMap[last->m_CommitHash].empty())
826 lastGood = m_HashMap[last->m_CommitHash].at(0);
828 if (CAppUtils::BisectStart(lastGood, firstBad))
829 Refresh();
831 break;
832 case ID_REPOBROWSE:
834 CString sCmd;
835 sCmd.Format(_T("/command:repobrowser /path:\"%s\" /rev:%s"), g_Git.m_CurrentDir, pSelLogEntry->m_CommitHash.ToString());
836 CAppUtils::RunTortoiseGitProc(sCmd);
838 break;
839 case ID_PUSH:
841 CString guessAssociatedBranch;
842 if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
843 guessAssociatedBranch = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
844 if (CAppUtils::Push(guessAssociatedBranch))
845 Refresh();
847 break;
848 case ID_FETCH:
850 if (CAppUtils::Fetch(_T(""), true))
851 Refresh();
853 break;
854 case ID_SHOWBRANCHES:
856 CString cmd;
857 cmd.Format(_T("git.exe branch -a --contains %s"), pSelLogEntry->m_CommitHash.ToString());
858 CProgressDlg progress;
859 progress.m_GitCmd = cmd;
860 progress.DoModal();
862 break;
863 case ID_DELETE:
865 bool showProgress = false;
866 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
867 if (!branch)
869 CMessageBox::Show(NULL,IDS_ERROR_NOREF,IDS_APPNAME,MB_OK|MB_ICONERROR);
870 return;
872 CString shortname;
873 CString cmd;
874 if (CGit::GetShortName(*branch, shortname, _T("refs/remotes/")))
876 CString msg;
877 msg.Format(IDS_PROC_DELETEREMOTEBRANCH, *branch);
878 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)));
879 if (result == 1)
881 CString remoteName = shortname.Left(shortname.Find('/'));
882 shortname = shortname.Mid(shortname.Find('/') + 1);
883 if(CAppUtils::IsSSHPutty())
884 CAppUtils::LaunchPAgent(NULL, &remoteName);
886 cmd.Format(L"git.exe push \"%s\" :refs/heads/%s", remoteName, shortname);
887 showProgress = true;
889 else if (result == 2)
890 cmd.Format(_T("git.exe branch -r -D -- %s"), shortname);
891 else
892 return;
894 else if (CGit::GetShortName(*branch, shortname, _T("refs/stash")))
896 if (CMessageBox::Show(NULL, IDS_PROC_DELETEALLSTASH, IDS_APPNAME, 2, IDI_QUESTION, IDS_DELETEBUTTON, IDS_ABORTBUTTON) == 1)
897 cmd.Format(_T("git.exe stash clear"));
898 else
899 return;
901 else
903 CString msg;
904 msg.Format(IDS_PROC_DELETEBRANCHTAG, *branch);
905 if (CMessageBox::Show(NULL, msg, _T("TortoiseGit"), 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
907 if(CGit::GetShortName(*branch,shortname,_T("refs/heads/")))
909 cmd.Format(_T("git.exe branch -D -- %s"),shortname);
912 if(CGit::GetShortName(*branch,shortname,_T("refs/tags/")))
914 cmd.Format(_T("git.exe tag -d -- %s"),shortname);
918 if (!cmd.IsEmpty())
920 CSysProgressDlg sysProgressDlg;
921 if (showProgress)
923 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
924 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
925 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
926 sysProgressDlg.SetShowProgressBar(false);
927 sysProgressDlg.ShowModal(this, true);
929 CString out;
930 if(g_Git.Run(cmd,&out,CP_UTF8))
932 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
934 if (showProgress)
935 sysProgressDlg.Stop();
936 this->ReloadHashMap();
937 CRect rect;
938 this->GetItemRect(FirstSelect,&rect,LVIR_BOUNDS);
939 this->InvalidateRect(rect);
942 break;
944 case ID_FINDENTRY:
946 m_nSearchIndex = GetSelectionMark();
947 if (m_nSearchIndex < 0)
948 m_nSearchIndex = 0;
949 if (m_pFindDialog)
951 break;
953 else
955 m_pFindDialog = new CFindDlg();
956 m_pFindDialog->Create(this);
959 break;
960 case ID_MERGEREV:
962 CString str = pSelLogEntry->m_CommitHash.ToString();
963 if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
964 str = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
965 // we need an URL to complete this command, so error out if we can't get an URL
966 if(CAppUtils::Merge(&str))
968 this->Refresh();
971 break;
972 case ID_REVERTREV:
974 int parent = 0;
975 if (GetSelectedCount() == 1)
977 parent = cmd >> 16;
978 if (parent > pSelLogEntry->m_ParentHash.size())
980 CString str;
981 str.Format(IDS_PROC_NOPARENT, parent);
982 MessageBox(str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
983 return;
987 if (!this->RevertSelectedCommits(parent))
989 if (CMessageBox::Show(m_hWnd, IDS_REVREVERTED, IDS_APPNAME, 1, IDI_QUESTION, IDS_OKBUTTON, IDS_COMMITBUTTON) == 2)
991 CTGitPathList pathlist;
992 CTGitPathList selectedlist;
993 pathlist.AddPath(this->m_Path);
994 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
995 CString str;
996 CAppUtils::Commit(CString(), false, str, pathlist, selectedlist, bSelectFilesForCommit);
998 this->Refresh();
1001 break;
1002 case ID_EDITNOTE:
1004 CAppUtils::EditNote(pSelLogEntry);
1005 this->SetItemState(FirstSelect, 0, LVIS_SELECTED);
1006 this->SetItemState(FirstSelect, LVIS_SELECTED, LVIS_SELECTED);
1008 break;
1009 default:
1010 //CMessageBox::Show(NULL,_T("Have not implemented"),_T("TortoiseGit"),MB_OK);
1011 break;
1012 #if 0
1014 case ID_BLAMECOMPARE:
1016 //user clicked on the menu item "compare with working copy"
1017 //now first get the revision which is selected
1018 if (PromptShown())
1020 GitDiff diff(this, this->m_hWnd, true);
1021 diff.SetHEADPeg(m_LogRevision);
1022 diff.ShowCompare(m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), false, true);
1024 else
1025 CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), m_LogRevision, false, false, true);
1027 break;
1028 case ID_BLAMEWITHPREVIOUS:
1030 //user clicked on the menu item "Compare and Blame with previous revision"
1031 if (PromptShown())
1033 GitDiff diff(this, this->m_hWnd, true);
1034 diff.SetHEADPeg(m_LogRevision);
1035 diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), false, true);
1037 else
1038 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
1040 break;
1042 case ID_OPENWITH:
1043 bOpenWith = true;
1044 case ID_OPEN:
1046 CProgressDlg progDlg;
1047 progDlg.SetTitle(IDS_APPNAME);
1048 progDlg.SetAnimation(IDR_DOWNLOAD);
1049 CString sInfoLine;
1050 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, m_path.GetWinPath(), (LPCTSTR)revSelected.ToString());
1051 progDlg.SetLine(1, sInfoLine, true);
1052 SetAndClearProgressInfo(&progDlg);
1053 progDlg.ShowModeless(m_hWnd);
1054 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, m_path, revSelected);
1055 bool bSuccess = true;
1056 if (!Cat(m_path, GitRev(GitRev::REV_HEAD), revSelected, tempfile))
1058 bSuccess = false;
1059 // try again, but with the selected revision as the peg revision
1060 if (!Cat(m_path, revSelected, revSelected, tempfile))
1062 progDlg.Stop();
1063 SetAndClearProgressInfo((HWND)NULL);
1064 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1065 EnableOKButton();
1066 break;
1068 bSuccess = true;
1070 if (bSuccess)
1072 progDlg.Stop();
1073 SetAndClearProgressInfo((HWND)NULL);
1074 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1075 int ret = 0;
1076 if (!bOpenWith)
1077 ret = (int)ShellExecute(this->m_hWnd, NULL, tempfile.GetWinPath(), NULL, NULL, SW_SHOWNORMAL);
1078 if ((ret <= HINSTANCE_ERROR)||bOpenWith)
1080 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1081 cmd += tempfile.GetWinPathString() + _T(" ");
1082 CAppUtils::LaunchApplication(cmd, NULL, false);
1086 break;
1087 case ID_BLAME:
1089 CBlameDlg dlg;
1090 dlg.EndRev = revSelected;
1091 if (dlg.DoModal() == IDOK)
1093 CBlame blame;
1094 CString tempfile;
1095 CString logfile;
1096 tempfile = blame.BlameToTempFile(m_path, dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, _T(""), dlg.m_bIncludeMerge, TRUE, TRUE);
1097 if (!tempfile.IsEmpty())
1099 if (dlg.m_bTextView)
1101 //open the default text editor for the result file
1102 CAppUtils::StartTextViewer(tempfile);
1104 else
1106 CString sParams = _T("/path:\"") + m_path.GetGitPathString() + _T("\" ");
1107 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(m_path.GetFileOrDirectoryName()),sParams))
1109 break;
1113 else
1115 CMessageBox::Show(this->m_hWnd, blame.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1119 break;
1120 case ID_EXPORT:
1122 CString sCmd;
1123 sCmd.Format(_T("%s /command:export /path:\"%s\" /revision:%ld"),
1124 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseGitProc.exe")),
1125 (LPCTSTR)pathURL, (LONG)revSelected);
1126 CAppUtils::LaunchApplication(sCmd, NULL, false);
1128 break;
1129 case ID_VIEWREV:
1131 CString url = m_ProjectProperties.sWebViewerRev;
1132 url = GetAbsoluteUrlFromRelativeUrl(url);
1133 url.Replace(_T("%REVISION%"), revSelected.ToString());
1134 if (!url.IsEmpty())
1135 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1137 break;
1138 case ID_VIEWPATHREV:
1140 CString relurl = pathURL;
1141 CString sRoot = GetRepositoryRoot(CTGitPath(relurl));
1142 relurl = relurl.Mid(sRoot.GetLength());
1143 CString url = m_ProjectProperties.sWebViewerPathRev;
1144 url = GetAbsoluteUrlFromRelativeUrl(url);
1145 url.Replace(_T("%REVISION%"), revSelected.ToString());
1146 url.Replace(_T("%PATH%"), relurl);
1147 if (!url.IsEmpty())
1148 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1150 break;
1151 #endif
1153 } // switch (cmd)
1155 theApp.DoWaitCursor(-1);
1158 void CGitLogList::SetSelectedAction(int action)
1160 POSITION pos = GetFirstSelectedItemPosition();
1161 int index;
1162 while(pos)
1164 index = GetNextSelectedItem(pos);
1165 ((GitRev*)m_arShownList[index])->GetAction(this) = action;
1166 CRect rect;
1167 this->GetItemRect(index,&rect,LVIR_BOUNDS);
1168 this->InvalidateRect(rect);
1172 void CGitLogList::ShiftSelectedAction()
1174 POSITION pos = GetFirstSelectedItemPosition();
1175 int index;
1176 while(pos)
1178 index = GetNextSelectedItem(pos);
1179 int action = ((GitRev*)m_arShownList[index])->GetAction(this);
1180 switch (action)
1182 case CTGitPath::LOGACTIONS_REBASE_PICK:
1183 action = CTGitPath::LOGACTIONS_REBASE_SKIP;
1184 break;
1185 case CTGitPath::LOGACTIONS_REBASE_SKIP:
1186 action= CTGitPath::LOGACTIONS_REBASE_EDIT;
1187 break;
1188 case CTGitPath::LOGACTIONS_REBASE_EDIT:
1189 action = CTGitPath::LOGACTIONS_REBASE_SQUASH;
1190 break;
1191 case CTGitPath::LOGACTIONS_REBASE_SQUASH:
1192 action= CTGitPath::LOGACTIONS_REBASE_PICK;
1193 break;
1195 ((GitRev*)m_arShownList[index])->GetAction(this) = action;
1196 CRect rect;
1197 this->GetItemRect(index, &rect, LVIR_BOUNDS);
1198 this->InvalidateRect(rect);