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
24 #include "TortoiseProc.h"
25 #include "GitLogList.h"
27 //#include "VssStyle.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"
39 #include "MessageBox.h"
42 #include "PathUtils.h"
43 #include "StringUtils.h"
44 #include "UnicodeUtils.h"
46 //#include "GitInfo.h"
47 //#include "GitDiff.h"
48 //#include "RevisionRangeDlg.h"
49 //#include "BrowseFolder.h"
50 //#include "BlameDlg.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"
62 #include "../TGitCache/CacheInterface.h"
64 IMPLEMENT_DYNAMIC(CGitLogList
, CHintListCtrl
)
66 int CGitLogList::RevertSelectedCommits(int parent
)
68 CSysProgressDlg progress
;
72 if(!g_Git
.CheckCleanWorkTree())
74 CMessageBox::Show(NULL
, IDS_PROC_NOCLEAN
, IDS_APPNAME
, MB_OK
);
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();
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());
104 if(r1
->m_CommitHash
.IsEmpty())
107 CString cmd
, output
, merge
;
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
))
114 str
.LoadString(IDS_SVNACTION_FAILEDREVERT
);
117 str
+= _T("\n")+output
;
118 if( GetSelectedCount() == 1)
119 CMessageBox::Show(NULL
, str
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
122 if(CMessageBox::Show(NULL
, str
, _T("TortoiseGit"),2 , IDI_ERROR
, CString(MAKEINTRESOURCE(IDS_SKIPBUTTON
)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
))) == 2)
133 if (progress
.HasUserCancelled())
138 int CGitLogList::CherryPickFrom(CString from
, CString to
)
140 CLogDataVector
logs(&m_LogCache
);
142 range
.Format(_T("%s..%s"), from
, to
);
143 if (logs
.ParserFromLog(nullptr, -1, 0, &range
))
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
))));
170 cmd
.Format(_T("git.exe cherry-pick %s"),logs
.GetGitRevAt(i
).m_CommitHash
.ToString());
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
));
181 void CGitLogList::ContextMenuAction(int cmd
,int FirstSelect
, int LastSelect
, CMenu
*popmenu
)
183 POSITION pos
= GetFirstSelectedItemPosition();
184 int indexNext
= GetNextSelectedItem(pos
);
188 GitRev
* pSelLogEntry
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(indexNext
));
190 theApp
.DoWaitCursor(1);
195 CTGitPathList pathlist
;
196 CTGitPathList selectedlist
;
197 pathlist
.AddPath(this->m_Path
);
198 bool bSelectFilesForCommit
= !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE
));
200 CAppUtils::Commit(CString(),false,str
,
201 pathlist
,selectedlist
,bSelectFilesForCommit
);
203 this->GetParent()->PostMessage(WM_COMMAND
,ID_LOGDLG_REFRESH
,0);
206 case ID_GNUDIFF1
: // compare with WC, unified
208 CString tempfile
=GetTempFile();
210 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
211 if(!r1
->m_CommitHash
.IsEmpty())
216 if( (cmd
&0xFFFF) == 0xFFFF)
220 else if((cmd
&0xFFFF) == 0xFFFE)
226 if(cmd
> r1
->m_ParentHash
.size())
229 str
.Format(IDS_PROC_NOPARENT
, cmd
);
230 MessageBox(str
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
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());
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());
249 case ID_GNUDIFF2
: // compare two revisions, unified
251 CString tempfile
=GetTempFile();
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());
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()));
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
);
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
);
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
);
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"
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);
337 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
341 case ID_COMPAREWITHPREVIOUS
:
345 if (!pSelLogEntry
->m_ParentHash
.empty())
346 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
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());
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());
380 CMessageBox::Show(NULL
, IDS_PROC_NOPREVIOUSVERSION
, IDS_APPNAME
, MB_OK
);
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);
390 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
393 case ID_COPYCLIPBOARD
:
395 CopySelectionToClipBoard();
398 case ID_COPYCLIPBOARDMESSAGES
:
400 if ((GetAsyncKeyState(VK_SHIFT
) & 0x8000) != 0)
401 CopySelectionToClipBoard(ID_COPY_SUBJECT
);
403 CopySelectionToClipBoard(ID_COPY_MESSAGE
);
408 CopySelectionToClipBoard(ID_COPY_HASH
);
413 CString str
=pSelLogEntry
->m_CommitHash
.ToString();
414 CAppUtils::Export(&str
);
417 case ID_CREATE_BRANCH
:
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
];
430 CAppUtils::CreateBranchTag((cmd
&0xFFFF) == ID_CREATE_TAG
, &str
);
433 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
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
];
448 CAppUtils::Switch(str
);
452 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
454 case ID_SWITCHBRANCH
:
457 CString
*branch
= (CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
);
461 if(branch
->Find(_T("refs/heads/")) ==0 )
462 name
= branch
->Mid(11);
466 CAppUtils::PerformSwitch(name
);
470 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
475 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
476 if (CAppUtils::GitReset(&str
))
485 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_PICK
);
488 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_EDIT
);
490 case ID_REBASE_SQUASH
:
491 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_SQUASH
);
494 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_SKIP
);
496 case ID_COMBINE_COMMIT
:
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 if (g_Git
.GetHash(hashFirst
, head
))
508 MessageBox(g_Git
.GetGitLastErr(_T("Could not get hash of first selected revision.")), _T("TortoiseGit"), MB_ICONERROR
);
512 head
.Format(_T("HEAD~%d"),LastSelect
-headindex
);
513 if (g_Git
.GetHash(hashLast
, head
))
515 MessageBox(g_Git
.GetGitLastErr(_T("Could not get hash of last selected revision.")), _T("TortoiseGit"), MB_ICONERROR
);
520 GitRev
* pFirstEntry
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
521 GitRev
* pLastEntry
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
522 if(pFirstEntry
->m_CommitHash
!= hashFirst
|| pLastEntry
->m_CommitHash
!= hashLast
)
524 CMessageBox::Show(NULL
, IDS_PROC_CANNOTCOMBINE
, IDS_APPNAME
, MB_OK
);
531 lastRevision
.GetParentFromHash(hashLast
);
536 MessageBox(_T("Could not get parent(s) of ") + hashLast
.ToString() + _T(".\nlibgit reports:\n") + err
, _T("TortoiseGit"), MB_ICONERROR
);
540 if (g_Git
.GetHash(headhash
, _T("HEAD")))
542 MessageBox(g_Git
.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR
);
546 if(!g_Git
.CheckCleanWorkTree())
548 CMessageBox::Show(NULL
, IDS_PROC_NOCLEAN
, IDS_APPNAME
, MB_OK
);
553 //Use throw to abort this process (reset back to original HEAD)
556 cmd
.Format(_T("git.exe reset --hard %s"),pFirstEntry
->m_CommitHash
.ToString());
557 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
559 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
560 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1
)) + _T("\r\n\r\n") + out
));
562 cmd
.Format(_T("git.exe reset --mixed %s"),hashLast
.ToString());
563 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
565 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
566 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2
)) + _T("\r\n\r\n")+out
));
569 CTGitPathList PathList
;
570 /* don't why must add --stat to get action status*/
571 /* first -z will be omitted by gitdll*/
572 if(g_Git
.GetDiffPath(&PathList
,&pFirstEntry
->m_CommitHash
,&hashLast
,"-z --stat -r"))
574 CMessageBox::Show(NULL
,_T("Get Diff file list error"),_T("TortoiseGit"),MB_OK
);
575 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not get changed file list aborting...\r\n\r\n")+out
));
578 for (int i
= 0; i
< PathList
.GetCount(); ++i
)
580 if(PathList
[i
].m_Action
& CTGitPath::LOGACTIONS_ADDED
)
582 cmd
.Format(_T("git.exe add -- \"%s\""), PathList
[i
].GetGitPathString());
583 if (g_Git
.Run(cmd
, &out
, CP_UTF8
))
585 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
586 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not add new file aborting...\r\n\r\n")+out
));
590 if(PathList
[i
].m_Action
& CTGitPath::LOGACTIONS_DELETED
)
592 cmd
.Format(_T("git.exe rm -- \"%s\""), PathList
[i
].GetGitPathString());
593 if (g_Git
.Run(cmd
, &out
, CP_UTF8
))
595 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
596 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not rm file aborting...\r\n\r\n")+out
));
602 for (int i
= FirstSelect
; i
<= LastSelect
; ++i
)
604 GitRev
* pRev
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(i
));
605 dlg
.m_sLogMessage
+=pRev
->GetSubject()+_T("\n")+pRev
->GetBody();
606 dlg
.m_sLogMessage
+=_T("\n");
608 dlg
.m_bWholeProject
=true;
609 dlg
.m_bSelectFilesForCommit
= true;
610 dlg
.m_bForceCommitAmend
=true;
611 if (lastRevision
.ParentsCount() != 1)
613 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
);
614 dlg
.m_bAmendDiffToLastCommit
= TRUE
;
617 dlg
.m_bAmendDiffToLastCommit
= FALSE
;
618 dlg
.m_bNoPostActions
=true;
619 dlg
.m_AmendStr
=dlg
.m_sLogMessage
;
621 if (dlg
.DoModal() == IDOK
)
623 if(pFirstEntry
->m_CommitHash
!=headhash
)
625 //Commitrange firstEntry..headhash (from top of combine to original head) needs to be 'cherry-picked'
626 //on top of new commit.
627 //Use the rebase --onto command for it.
629 //All this can be done in one step using the following command:
630 //cmd.Format(_T("git.exe format-patch --stdout --binary --full-index -k %s..%s | git am -k -3"),
631 // pFirstEntry->m_CommitHash,
633 //But I am not sure if a '|' is going to work in a CreateProcess() call.
635 //Later the progress dialog could be used to execute these steps.
637 if(CherryPickFrom(pFirstEntry
->m_CommitHash
.ToString(),headhash
))
640 msg
.Format(_T("Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n"));
641 throw std::exception(CUnicodeUtils::GetUTF8(msg
));
644 CString currentBranch
=g_Git
.GetCurrentBranch();
645 cmd
.Format(_T("git.exe rebase --onto \"%s\" %s %s"),
647 pFirstEntry
->m_CommitHash
,
649 if(g_Git
.Run(cmd
,&out
,CP_UTF8
)!=0)
652 msg
.Format(_T("Error while rebasing commits on top of combined commits. Aborting.\r\n\r\n%s"),out
);
653 // CMessageBox::Show(NULL,msg,_T("TortoiseGit"),MB_OK);
654 g_Git
.Run(_T("git.exe rebase --abort"),&out
,CP_UTF8
);
655 throw std::exception(CUnicodeUtils::GetUTF8(msg
));
658 //HEAD is now on <no branch>.
659 //The following steps are to get HEAD back on the original branch and reset the branch to the new HEAD
660 //To avoid 2 working copy changes, we could use git branch -f <original branch> <hash new head>
661 //And then git checkout <original branch>
662 //But I don't know if 'git branch -f' removes tracking options. So for now, do a checkout and a reset.
665 CString newHead
=g_Git
.GetHash(CString(_T("HEAD")));
667 //Checkout working branch
668 cmd
.Format(_T("git.exe checkout -f \"%s\""),currentBranch
);
669 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
670 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not checkout original branch. Aborting...\r\n\r\n")+out
));
673 cmd
.Format(_T("git.exe reset --hard %s"),newHead
);
674 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
675 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not reset to new head. Aborting...\r\n\r\n")+out
));
680 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED
))));
682 catch(std::exception
& e
)
684 CMessageBox::Show(NULL
, CUnicodeUtils::GetUnicode(CStringA(e
.what())), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
685 cmd
.Format(_T("git.exe reset --hard %s"),headhash
.ToString());
687 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
689 CMessageBox::Show(NULL
, CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD
)) + _T("\r\n\r\n") + out
, _T("TortoiseGit"), MB_OK
);
697 if(!g_Git
.CheckCleanWorkTree())
699 CMessageBox::Show(NULL
, IDS_PROC_NOCLEAN
, IDS_APPNAME
, MB_OK
);
705 dlg
.m_IsCherryPick
= TRUE
;
706 dlg
.m_Upstream
= this->m_CurrentBranch
;
707 POSITION pos
= GetFirstSelectedItemPosition();
710 int indexNext
= GetNextSelectedItem(pos
);
711 dlg
.m_CommitList
.m_logEntries
.push_back( ((GitRev
*)m_arShownList
[indexNext
])->m_CommitHash
);
712 dlg
.m_CommitList
.m_LogCache
.m_HashMap
[((GitRev
*)m_arShownList
[indexNext
])->m_CommitHash
]=*(GitRev
*)m_arShownList
[indexNext
];
713 dlg
.m_CommitList
.m_logEntries
.GetGitRevAt(dlg
.m_CommitList
.m_logEntries
.size()-1).GetAction(this) |= CTGitPath::LOGACTIONS_REBASE_PICK
;
716 if(dlg
.DoModal() == IDOK
)
722 case ID_REBASE_TO_VERSION
:
723 if(!g_Git
.CheckCleanWorkTree())
725 CMessageBox::Show(NULL
, IDS_PROC_NOCLEAN
, IDS_APPNAME
, MB_OK
);
731 dlg
.m_Upstream
= pSelLogEntry
->m_CommitHash
;
733 if(dlg
.DoModal() == IDOK
)
742 if (CAppUtils::StashSave())
747 if (CAppUtils::StashPop())
752 CAppUtils::RunTortoiseGitProc(_T("/command:reflog /ref:refs/stash"));
755 case ID_REFLOG_STASH_APPLY
:
756 CAppUtils::StashApply(pSelLogEntry
->m_Ref
);
762 if (GetSelectedCount() > 1)
763 str
.Format(IDS_PROC_DELETENREFS
, GetSelectedCount());
765 str
.Format(IDS_PROC_DELETEREF
, pSelLogEntry
->m_Ref
);
767 if (CMessageBox::Show(NULL
, str
, _T("TortoiseGit"), 1, IDI_QUESTION
, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON
)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
))) == 2)
770 std::vector
<CString
> refsToDelete
;
771 POSITION pos
= GetFirstSelectedItemPosition();
774 CString ref
= ((GitRev
*)m_arShownList
[GetNextSelectedItem(pos
)])->m_Ref
;
775 if (ref
.Find(_T("refs/")) == 0)
777 int refpos
= ref
.ReverseFind('{');
778 if (refpos
> 0 && ref
.Mid(refpos
, 2) != _T("@{"))
779 ref
= ref
.Left(refpos
) + _T("@")+ ref
.Mid(refpos
);
780 refsToDelete
.push_back(ref
);
783 for (auto revIt
= refsToDelete
.rbegin(); revIt
!= refsToDelete
.rend(); ++revIt
)
785 CString ref
= *revIt
;
787 if (ref
.Find(_T("stash")) == 0)
788 cmd
.Format(_T("git.exe stash drop %s"), ref
);
790 cmd
.Format(_T("git.exe reflog delete %s"), ref
);
792 if (g_Git
.Run(cmd
, &out
, CP_UTF8
))
793 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
795 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
801 CString cmd
= _T("/command:log");
802 cmd
+= _T(" /path:\"")+g_Git
.m_CurrentDir
+_T("\" ");
803 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
804 cmd
+= _T(" /endrev:")+r1
->m_CommitHash
.ToString();
805 CAppUtils::RunTortoiseGitProc(cmd
);
808 case ID_CREATE_PATCH
:
810 int select
=this->GetSelectedCount();
811 CString cmd
= _T("/command:formatpatch");
812 cmd
+= _T(" /path:\"")+g_Git
.m_CurrentDir
+_T("\" ");
814 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
818 cmd
+= _T(" /startrev:")+r1
->m_CommitHash
.ToString();
822 r2
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
823 if( this->m_IsOldFirst
)
825 cmd
+= _T(" /startrev:")+r1
->m_CommitHash
.ToString()+_T("~1");
826 cmd
+= _T(" /endrev:")+r2
->m_CommitHash
.ToString();
831 cmd
+= _T(" /startrev:")+r2
->m_CommitHash
.ToString()+_T("~1");
832 cmd
+= _T(" /endrev:")+r1
->m_CommitHash
.ToString();
837 CAppUtils::RunTortoiseGitProc(cmd
);
842 GitRev
* first
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
843 GitRev
* last
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
844 ASSERT(first
!= NULL
&& last
!= NULL
);
846 CString firstBad
= first
->m_CommitHash
.ToString();
847 if (!m_HashMap
[first
->m_CommitHash
].empty())
848 firstBad
= m_HashMap
[first
->m_CommitHash
].at(0);
849 CString lastGood
= last
->m_CommitHash
.ToString();
850 if (!m_HashMap
[last
->m_CommitHash
].empty())
851 lastGood
= m_HashMap
[last
->m_CommitHash
].at(0);
853 if (CAppUtils::BisectStart(lastGood
, firstBad
))
860 sCmd
.Format(_T("/command:repobrowser /path:\"%s\" /rev:%s"), g_Git
.m_CurrentDir
, pSelLogEntry
->m_CommitHash
.ToString());
861 CAppUtils::RunTortoiseGitProc(sCmd
);
866 CString guessAssociatedBranch
;
867 if (!m_HashMap
[pSelLogEntry
->m_CommitHash
].empty())
868 guessAssociatedBranch
= m_HashMap
[pSelLogEntry
->m_CommitHash
].at(0);
869 if (CAppUtils::Push(guessAssociatedBranch
))
875 if (CAppUtils::Fetch(_T(""), true))
879 case ID_SHOWBRANCHES
:
882 cmd
.Format(_T("git.exe branch -a --contains %s"), pSelLogEntry
->m_CommitHash
.ToString());
883 CProgressDlg progress
;
884 progress
.m_GitCmd
= cmd
;
890 bool showProgress
= false;
891 CString
*branch
= (CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
);
894 CMessageBox::Show(NULL
,IDS_ERROR_NOREF
,IDS_APPNAME
,MB_OK
|MB_ICONERROR
);
899 if (CGit::GetShortName(*branch
, shortname
, _T("refs/remotes/")))
902 msg
.Format(IDS_PROC_DELETEREMOTEBRANCH
, *branch
);
903 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
)));
906 CString remoteName
= shortname
.Left(shortname
.Find('/'));
907 shortname
= shortname
.Mid(shortname
.Find('/') + 1);
908 if(CAppUtils::IsSSHPutty())
909 CAppUtils::LaunchPAgent(NULL
, &remoteName
);
911 cmd
.Format(L
"git.exe push \"%s\" :refs/heads/%s", remoteName
, shortname
);
914 else if (result
== 2)
915 cmd
.Format(_T("git.exe branch -r -D -- %s"), shortname
);
919 else if (CGit::GetShortName(*branch
, shortname
, _T("refs/stash")))
921 if (CMessageBox::Show(NULL
, IDS_PROC_DELETEALLSTASH
, IDS_APPNAME
, 2, IDI_QUESTION
, IDS_DELETEBUTTON
, IDS_ABORTBUTTON
) == 1)
922 cmd
.Format(_T("git.exe stash clear"));
929 msg
.Format(IDS_PROC_DELETEBRANCHTAG
, *branch
);
930 if (CMessageBox::Show(NULL
, msg
, _T("TortoiseGit"), 2, IDI_QUESTION
, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON
)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
))) == 1)
932 if(CGit::GetShortName(*branch
,shortname
,_T("refs/heads/")))
934 cmd
.Format(_T("git.exe branch -D -- %s"),shortname
);
937 if(CGit::GetShortName(*branch
,shortname
,_T("refs/tags/")))
939 cmd
.Format(_T("git.exe tag -d -- %s"),shortname
);
945 CSysProgressDlg sysProgressDlg
;
948 sysProgressDlg
.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME
)));
949 sysProgressDlg
.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS
)));
950 sysProgressDlg
.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT
)));
951 sysProgressDlg
.SetShowProgressBar(false);
952 sysProgressDlg
.ShowModal(this, true);
955 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
957 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
960 sysProgressDlg
.Stop();
961 this->ReloadHashMap();
963 this->GetItemRect(FirstSelect
,&rect
,LVIR_BOUNDS
);
964 this->InvalidateRect(rect
);
971 m_nSearchIndex
= GetSelectionMark();
972 if (m_nSearchIndex
< 0)
980 m_pFindDialog
= new CFindDlg();
981 m_pFindDialog
->Create(this);
987 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
988 if (!m_HashMap
[pSelLogEntry
->m_CommitHash
].empty())
989 str
= m_HashMap
[pSelLogEntry
->m_CommitHash
].at(0);
990 // we need an URL to complete this command, so error out if we can't get an URL
991 if(CAppUtils::Merge(&str
))
1000 if (GetSelectedCount() == 1)
1003 if (parent
> pSelLogEntry
->m_ParentHash
.size())
1006 str
.Format(IDS_PROC_NOPARENT
, parent
);
1007 MessageBox(str
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
1012 if (!this->RevertSelectedCommits(parent
))
1014 if (CMessageBox::Show(m_hWnd
, IDS_REVREVERTED
, IDS_APPNAME
, 1, IDI_QUESTION
, IDS_OKBUTTON
, IDS_COMMITBUTTON
) == 2)
1016 CTGitPathList pathlist
;
1017 CTGitPathList selectedlist
;
1018 pathlist
.AddPath(this->m_Path
);
1019 bool bSelectFilesForCommit
= !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE
));
1021 CAppUtils::Commit(CString(), false, str
, pathlist
, selectedlist
, bSelectFilesForCommit
);
1029 CAppUtils::EditNote(pSelLogEntry
);
1030 this->SetItemState(FirstSelect
, 0, LVIS_SELECTED
);
1031 this->SetItemState(FirstSelect
, LVIS_SELECTED
, LVIS_SELECTED
);
1035 //CMessageBox::Show(NULL,_T("Have not implemented"),_T("TortoiseGit"),MB_OK);
1039 case ID_BLAMECOMPARE
:
1041 //user clicked on the menu item "compare with working copy"
1042 //now first get the revision which is selected
1045 GitDiff
diff(this, this->m_hWnd
, true);
1046 diff
.SetHEADPeg(m_LogRevision
);
1047 diff
.ShowCompare(m_path
, GitRev::REV_BASE
, m_path
, revSelected
, GitRev(), false, true);
1050 CAppUtils::StartShowCompare(m_hWnd
, m_path
, GitRev::REV_BASE
, m_path
, revSelected
, GitRev(), m_LogRevision
, false, false, true);
1053 case ID_BLAMEWITHPREVIOUS
:
1055 //user clicked on the menu item "Compare and Blame with previous revision"
1058 GitDiff
diff(this, this->m_hWnd
, true);
1059 diff
.SetHEADPeg(m_LogRevision
);
1060 diff
.ShowCompare(CTGitPath(pathURL
), revPrevious
, CTGitPath(pathURL
), revSelected
, GitRev(), false, true);
1063 CAppUtils::StartShowCompare(m_hWnd
, CTGitPath(pathURL
), revPrevious
, CTGitPath(pathURL
), revSelected
, GitRev(), m_LogRevision
, false, false, true);
1071 CProgressDlg progDlg
;
1072 progDlg
.SetTitle(IDS_APPNAME
);
1073 progDlg
.SetAnimation(IDR_DOWNLOAD
);
1075 sInfoLine
.Format(IDS_PROGRESSGETFILEREVISION
, m_path
.GetWinPath(), (LPCTSTR
)revSelected
.ToString());
1076 progDlg
.SetLine(1, sInfoLine
, true);
1077 SetAndClearProgressInfo(&progDlg
);
1078 progDlg
.ShowModeless(m_hWnd
);
1079 CTGitPath tempfile
= CTempFiles::Instance().GetTempFilePath(false, m_path
, revSelected
);
1080 bool bSuccess
= true;
1081 if (!Cat(m_path
, GitRev(GitRev::REV_HEAD
), revSelected
, tempfile
))
1084 // try again, but with the selected revision as the peg revision
1085 if (!Cat(m_path
, revSelected
, revSelected
, tempfile
))
1088 SetAndClearProgressInfo((HWND
)NULL
);
1089 CMessageBox::Show(this->m_hWnd
, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR
);
1098 SetAndClearProgressInfo((HWND
)NULL
);
1099 SetFileAttributes(tempfile
.GetWinPath(), FILE_ATTRIBUTE_READONLY
);
1102 ret
= (int)ShellExecute(this->m_hWnd
, NULL
, tempfile
.GetWinPath(), NULL
, NULL
, SW_SHOWNORMAL
);
1103 if ((ret
<= HINSTANCE_ERROR
)||bOpenWith
)
1105 CString cmd
= _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1106 cmd
+= tempfile
.GetWinPathString() + _T(" ");
1107 CAppUtils::LaunchApplication(cmd
, NULL
, false);
1115 dlg
.EndRev
= revSelected
;
1116 if (dlg
.DoModal() == IDOK
)
1121 tempfile
= blame
.BlameToTempFile(m_path
, dlg
.StartRev
, dlg
.EndRev
, dlg
.EndRev
, logfile
, _T(""), dlg
.m_bIncludeMerge
, TRUE
, TRUE
);
1122 if (!tempfile
.IsEmpty())
1124 if (dlg
.m_bTextView
)
1126 //open the default text editor for the result file
1127 CAppUtils::StartTextViewer(tempfile
);
1131 CString sParams
= _T("/path:\"") + m_path
.GetGitPathString() + _T("\" ");
1132 if(!CAppUtils::LaunchTortoiseBlame(tempfile
, logfile
, CPathUtils::GetFileNameFromPath(m_path
.GetFileOrDirectoryName()),sParams
))
1140 CMessageBox::Show(this->m_hWnd
, blame
.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR
);
1148 sCmd
.Format(_T("%s /command:export /path:\"%s\" /revision:%ld"),
1149 (LPCTSTR
)(CPathUtils::GetAppDirectory()+_T("TortoiseGitProc.exe")),
1150 (LPCTSTR
)pathURL
, (LONG
)revSelected
);
1151 CAppUtils::LaunchApplication(sCmd
, NULL
, false);
1156 CString url
= m_ProjectProperties
.sWebViewerRev
;
1157 url
= GetAbsoluteUrlFromRelativeUrl(url
);
1158 url
.Replace(_T("%REVISION%"), revSelected
.ToString());
1160 ShellExecute(this->m_hWnd
, _T("open"), url
, NULL
, NULL
, SW_SHOWDEFAULT
);
1163 case ID_VIEWPATHREV
:
1165 CString relurl
= pathURL
;
1166 CString sRoot
= GetRepositoryRoot(CTGitPath(relurl
));
1167 relurl
= relurl
.Mid(sRoot
.GetLength());
1168 CString url
= m_ProjectProperties
.sWebViewerPathRev
;
1169 url
= GetAbsoluteUrlFromRelativeUrl(url
);
1170 url
.Replace(_T("%REVISION%"), revSelected
.ToString());
1171 url
.Replace(_T("%PATH%"), relurl
);
1173 ShellExecute(this->m_hWnd
, _T("open"), url
, NULL
, NULL
, SW_SHOWDEFAULT
);
1180 theApp
.DoWaitCursor(-1);
1183 void CGitLogList::SetSelectedAction(int action
)
1185 POSITION pos
= GetFirstSelectedItemPosition();
1189 index
= GetNextSelectedItem(pos
);
1190 ((GitRev
*)m_arShownList
[index
])->GetAction(this) = action
;
1192 this->GetItemRect(index
,&rect
,LVIR_BOUNDS
);
1193 this->InvalidateRect(rect
);
1197 void CGitLogList::ShiftSelectedAction()
1199 POSITION pos
= GetFirstSelectedItemPosition();
1203 index
= GetNextSelectedItem(pos
);
1204 int action
= ((GitRev
*)m_arShownList
[index
])->GetAction(this);
1207 case CTGitPath::LOGACTIONS_REBASE_PICK
:
1208 action
= CTGitPath::LOGACTIONS_REBASE_SKIP
;
1210 case CTGitPath::LOGACTIONS_REBASE_SKIP
:
1211 action
= CTGitPath::LOGACTIONS_REBASE_EDIT
;
1213 case CTGitPath::LOGACTIONS_REBASE_EDIT
:
1214 action
= CTGitPath::LOGACTIONS_REBASE_SQUASH
;
1216 case CTGitPath::LOGACTIONS_REBASE_SQUASH
:
1217 action
= CTGitPath::LOGACTIONS_REBASE_PICK
;
1220 ((GitRev
*)m_arShownList
[index
])->GetAction(this) = action
;
1222 this->GetItemRect(index
, &rect
, LVIR_BOUNDS
);
1223 this->InvalidateRect(rect
);