1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2015 - TortoiseGit
4 // Copyright (C) 2005-2007 Marco Costalba
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 // GitLogList.cpp : implementation file
23 #include "TortoiseProc.h"
24 #include "GitLogList.h"
28 #include "GitProgressDlg.h"
29 #include "ProgressDlg.h"
30 #include "SysProgressDlg.h"
32 #include "MessageBox.h"
35 #include "StringUtils.h"
36 #include "UnicodeUtils.h"
38 #include "FileDiffDlg.h"
39 #include "CommitDlg.h"
40 #include "RebaseDlg.h"
42 #include "../TGitCache/CacheInterface.h"
44 IMPLEMENT_DYNAMIC(CGitLogList
, CHintListCtrl
)
46 int CGitLogList::RevertSelectedCommits(int parent
)
48 CSysProgressDlg progress
;
52 if(!g_Git
.CheckCleanWorkTree())
54 CMessageBox::Show(NULL
, IDS_PROC_NOCLEAN
, IDS_APPNAME
, MB_OK
);
59 if (this->GetSelectedCount() > 1)
61 progress
.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_REVERTCOMMIT
)));
62 progress
.SetAnimation(IDR_MOVEANI
);
63 progress
.SetTime(true);
64 progress
.ShowModeless(this);
67 CBlockCacheForPath
cacheBlock(g_Git
.m_CurrentDir
);
69 POSITION pos
= GetFirstSelectedItemPosition();
73 int index
= GetNextSelectedItem(pos
);
74 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(index
));
76 if (progress
.IsVisible())
78 progress
.FormatNonPathLine(1, IDS_PROC_REVERTCOMMIT
, r1
->m_CommitHash
.ToString());
79 progress
.FormatNonPathLine(2, _T("%s"), r1
->GetSubject());
80 progress
.SetProgress(i
, this->GetSelectedCount());
84 if(r1
->m_CommitHash
.IsEmpty())
87 if (g_Git
.GitRevert(parent
, r1
->m_CommitHash
))
90 str
.LoadString(IDS_SVNACTION_FAILEDREVERT
);
91 str
= g_Git
.GetGitLastErr(str
, CGit::GIT_CMD_REVERT
);
92 if( GetSelectedCount() == 1)
93 CMessageBox::Show(NULL
, str
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
96 if(CMessageBox::Show(NULL
, str
, _T("TortoiseGit"),2 , IDI_ERROR
, CString(MAKEINTRESOURCE(IDS_SKIPBUTTON
)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
))) == 2)
107 if (progress
.HasUserCancelled())
112 int CGitLogList::CherryPickFrom(CString from
, CString to
)
114 CLogDataVector
logs(&m_LogCache
);
116 range
.Format(_T("%s..%s"), from
, to
);
117 if (logs
.ParserFromLog(nullptr, -1, 0, &range
))
123 CSysProgressDlg progress
;
124 progress
.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_CHERRYPICK
)));
125 progress
.SetAnimation(IDR_MOVEANI
);
126 progress
.SetTime(true);
127 progress
.ShowModeless(this);
129 CBlockCacheForPath
cacheBlock(g_Git
.m_CurrentDir
);
131 for (int i
= (int)logs
.size() - 1; i
>= 0; i
--)
133 if (progress
.IsVisible())
135 progress
.FormatNonPathLine(1, IDS_PROC_PICK
, logs
.GetGitRevAt(i
).m_CommitHash
.ToString());
136 progress
.FormatNonPathLine(2, _T("%s"), logs
.GetGitRevAt(i
).GetSubject());
137 progress
.SetProgress64(logs
.size() - i
, logs
.size());
139 if (progress
.HasUserCancelled())
141 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED
))));
144 cmd
.Format(_T("git.exe cherry-pick %s"),logs
.GetGitRevAt(i
).m_CommitHash
.ToString());
146 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
148 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_CHERRYPICKFAILED
)) + _T(":\r\n\r\n") + out
));
155 void CGitLogList::ContextMenuAction(int cmd
,int FirstSelect
, int LastSelect
, CMenu
*popmenu
)
157 POSITION pos
= GetFirstSelectedItemPosition();
158 int indexNext
= GetNextSelectedItem(pos
);
162 GitRevLoglist
* pSelLogEntry
= reinterpret_cast<GitRevLoglist
*>(m_arShownList
.GetAt(indexNext
));
164 theApp
.DoWaitCursor(1);
169 CTGitPathList pathlist
;
170 CTGitPathList selectedlist
;
171 pathlist
.AddPath(this->m_Path
);
172 bool bSelectFilesForCommit
= !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE
));
174 CAppUtils::Commit(CString(),false,str
,
175 pathlist
,selectedlist
,bSelectFilesForCommit
);
177 this->GetParent()->PostMessage(WM_COMMAND
,ID_LOGDLG_REFRESH
,0);
182 if (CAppUtils::MergeAbort())
183 this->GetParent()->PostMessage(WM_COMMAND
,ID_LOGDLG_REFRESH
, 0);
186 case ID_GNUDIFF1
: // compare with WC, unified
188 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
189 bool bMerge
= false, bCombine
= false;
191 if(!r1
->m_CommitHash
.IsEmpty())
195 if( (cmd
&0xFFFF) == 0xFFFF)
197 else if((cmd
&0xFFFF) == 0xFFFE)
199 else if ((cmd
& 0xFFFF) == 0xFFFD)
201 CString tempfile
= GetTempFile();
202 CString cmd
= _T("git.exe diff-tree --cc ") + r1
->m_CommitHash
.ToString();
204 if (g_Git
.RunLogFile(cmd
, tempfile
, &lastErr
))
206 MessageBox(lastErr
, _T("TortoiseGit"), MB_ICONERROR
);
210 CAppUtils::StartUnifiedDiffViewer(tempfile
, _T("dd"));
215 if(cmd
> r1
->m_ParentHash
.size())
218 str
.Format(IDS_PROC_NOPARENT
, cmd
);
219 MessageBox(str
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
225 hash2
= r1
->m_ParentHash
[cmd
-1].ToString();
228 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), hash2
, CTGitPath(), r1
->m_CommitHash
.ToString(), false, false, false, bMerge
, bCombine
);
231 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), _T("HEAD"), CTGitPath(), GitRev::GetWorkingCopy(), false, false, false, bMerge
, bCombine
);
235 case ID_GNUDIFF2
: // compare two revisions, unified
237 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
238 GitRev
* r2
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
239 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), r2
->m_CommitHash
.ToString(), CTGitPath(), r1
->m_CommitHash
.ToString());
243 case ID_COMPARETWO
: // compare two revisions
245 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
246 GitRev
* r2
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
247 if (m_Path
.IsDirectory() || !(m_ShowMask
& CGit::LOG_INFO_FOLLOW
))
248 CGitDiff::DiffCommit(this->m_Path
, r1
,r2
);
251 CString path1
= m_Path
.GetGitPathString();
252 // start with 1 (0 = working copy changes)
253 for (int i
= 1; i
< FirstSelect
; ++i
)
255 GitRevLoglist
* first
= reinterpret_cast<GitRevLoglist
*>(m_arShownList
.GetAt(i
));
256 CTGitPathList list
= first
->GetFiles(NULL
);
257 CTGitPath
* file
= list
.LookForGitPath(path1
);
258 if (file
&& !file
->GetGitOldPathString().IsEmpty())
259 path1
= file
->GetGitOldPathString();
261 CString path2
= path1
;
262 for (int i
= FirstSelect
; i
< LastSelect
; ++i
)
264 GitRevLoglist
* first
= reinterpret_cast<GitRevLoglist
*>(m_arShownList
.GetAt(i
));
265 CTGitPathList list
= first
->GetFiles(NULL
);
266 CTGitPath
* file
= list
.LookForGitPath(path2
);
267 if (file
&& !file
->GetGitOldPathString().IsEmpty())
268 path2
= file
->GetGitOldPathString();
270 CGitDiff::DiffCommit(CTGitPath(path1
), CTGitPath(path2
), r1
, r2
);
276 case ID_COMPARE
: // compare revision with WC
278 GitRevLoglist
* r1
= &m_wcRev
;
279 GitRevLoglist
* r2
= pSelLogEntry
;
281 if (m_Path
.IsDirectory() || !(m_ShowMask
& CGit::LOG_INFO_FOLLOW
))
282 CGitDiff::DiffCommit(this->m_Path
, r1
,r2
);
285 CString path1
= m_Path
.GetGitPathString();
286 // start with 1 (0 = working copy changes)
287 for (int i
= 1; i
< FirstSelect
; ++i
)
289 GitRevLoglist
* first
= reinterpret_cast<GitRevLoglist
*>(m_arShownList
.GetAt(i
));
290 CTGitPathList list
= first
->GetFiles(NULL
);
291 CTGitPath
* file
= list
.LookForGitPath(path1
);
292 if (file
&& !file
->GetGitOldPathString().IsEmpty())
293 path1
= file
->GetGitOldPathString();
295 CGitDiff::DiffCommit(m_Path
, CTGitPath(path1
), r1
, r2
);
298 //user clicked on the menu item "compare with working copy"
301 // GitDiff diff(this, m_hWnd, true);
302 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
303 // diff.SetHEADPeg(m_LogRevision);
304 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
307 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
311 case ID_COMPAREWITHPREVIOUS
:
315 if (pSelLogEntry
->m_ParentHash
.empty())
317 if (pSelLogEntry
->GetParentFromHash(pSelLogEntry
->m_CommitHash
))
318 MessageBox(pSelLogEntry
->GetLastErr(), _T("TortoiseGit"), MB_ICONERROR
);
321 if (!pSelLogEntry
->m_ParentHash
.empty())
322 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
330 if (m_Path
.IsDirectory() || !(m_ShowMask
& CGit::LOG_INFO_FOLLOW
))
331 CGitDiff::DiffCommit(m_Path
, pSelLogEntry
->m_CommitHash
.ToString(), pSelLogEntry
->m_ParentHash
[cmd
- 1].ToString());
334 CString path1
= m_Path
.GetGitPathString();
335 // start with 1 (0 = working copy changes)
336 for (int i
= 1; i
< indexNext
; ++i
)
338 GitRevLoglist
* first
= reinterpret_cast<GitRevLoglist
*>(m_arShownList
.GetAt(i
));
339 CTGitPathList list
= first
->GetFiles(NULL
);
340 CTGitPath
* file
= list
.LookForGitPath(path1
);
341 if (file
&& !file
->GetGitOldPathString().IsEmpty())
342 path1
= file
->GetGitOldPathString();
344 CString path2
= path1
;
345 GitRevLoglist
* first
= reinterpret_cast<GitRevLoglist
*>(m_arShownList
.GetAt(indexNext
));
346 CTGitPathList list
= first
->GetFiles(NULL
);
347 CTGitPath
* file
= list
.LookForGitPath(path2
);
348 if (file
&& !file
->GetGitOldPathString().IsEmpty())
349 path2
= file
->GetGitOldPathString();
351 CGitDiff::DiffCommit(CTGitPath(path1
), CTGitPath(path2
), pSelLogEntry
->m_CommitHash
.ToString(), pSelLogEntry
->m_ParentHash
[cmd
- 1].ToString());
356 CMessageBox::Show(NULL
, IDS_PROC_NOPREVIOUSVERSION
, IDS_APPNAME
, MB_OK
);
360 // GitDiff diff(this, m_hWnd, true);
361 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
362 // diff.SetHEADPeg(m_LogRevision);
363 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
366 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
369 case ID_LOG_VIEWRANGE
:
370 case ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE
:
372 GitRev
* pLastEntry
= reinterpret_cast<GitRev
*>(m_arShownList
.SafeGetAt(LastSelect
));
374 CString sep
= _T("..");
375 if ((cmd
& 0xFFFF) == ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE
)
379 cmdline
.Format(_T("/command:log /path:\"%s\" /range:\"%s%s%s\""),
380 g_Git
.CombinePath(m_Path
), pLastEntry
->m_CommitHash
.ToString(), sep
, pSelLogEntry
->m_CommitHash
.ToString());
381 CAppUtils::RunTortoiseGitProc(cmdline
);
384 case ID_COPYCLIPBOARD
:
386 CopySelectionToClipBoard();
389 case ID_COPYCLIPBOARDMESSAGES
:
391 if ((GetAsyncKeyState(VK_SHIFT
) & 0x8000) != 0)
392 CopySelectionToClipBoard(ID_COPY_SUBJECT
);
394 CopySelectionToClipBoard(ID_COPY_MESSAGE
);
399 CopySelectionToClipBoard(ID_COPY_HASH
);
404 CString str
=pSelLogEntry
->m_CommitHash
.ToString();
405 // try to get the tag
406 for (size_t i
= 0; i
< m_HashMap
[pSelLogEntry
->m_CommitHash
].size(); ++i
)
408 if (m_HashMap
[pSelLogEntry
->m_CommitHash
][i
].Find(_T("refs/tags/")) == 0)
410 str
= m_HashMap
[pSelLogEntry
->m_CommitHash
][i
];
414 CAppUtils::Export(&str
, &m_Path
);
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 (size_t 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 m_pFindDialog
->RefreshList();
435 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
440 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
441 // try to guess remote branch in order to recommend good branch name and tracking
442 for (size_t i
= 0; i
< m_HashMap
[pSelLogEntry
->m_CommitHash
].size(); ++i
)
444 if (m_HashMap
[pSelLogEntry
->m_CommitHash
][i
].Find(_T("refs/remotes/")) == 0)
446 str
= m_HashMap
[pSelLogEntry
->m_CommitHash
][i
];
450 CAppUtils::Switch(str
);
454 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
456 case ID_SWITCHBRANCH
:
459 CString
*branch
= (CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
);
463 if(branch
->Find(_T("refs/heads/")) ==0 )
464 name
= branch
->Mid(11);
468 CAppUtils::PerformSwitch(name
);
472 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
477 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
478 if (CAppUtils::GitReset(&str
))
487 SetSelectedRebaseAction(LOGACTIONS_REBASE_PICK
);
490 SetSelectedRebaseAction(LOGACTIONS_REBASE_EDIT
);
492 case ID_REBASE_SQUASH
:
493 SetSelectedRebaseAction(LOGACTIONS_REBASE_SQUASH
);
496 SetSelectedRebaseAction(LOGACTIONS_REBASE_SKIP
);
498 case ID_COMBINE_COMMIT
:
502 CGitHash hashFirst
,hashLast
;
504 int headindex
=GetHeadIndex();
505 if(headindex
>=0) //incase show all branch, head is not the first commits.
507 head
.Format(_T("HEAD~%d"),FirstSelect
-headindex
);
508 if (g_Git
.GetHash(hashFirst
, head
))
510 MessageBox(g_Git
.GetGitLastErr(_T("Could not get hash of first selected revision.")), _T("TortoiseGit"), MB_ICONERROR
);
514 head
.Format(_T("HEAD~%d"),LastSelect
-headindex
);
515 if (g_Git
.GetHash(hashLast
, head
))
517 MessageBox(g_Git
.GetGitLastErr(_T("Could not get hash of last selected revision.")), _T("TortoiseGit"), MB_ICONERROR
);
522 GitRev
* pFirstEntry
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
523 GitRev
* pLastEntry
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
524 if(pFirstEntry
->m_CommitHash
!= hashFirst
|| pLastEntry
->m_CommitHash
!= hashLast
)
526 CMessageBox::Show(NULL
, IDS_PROC_CANNOTCOMBINE
, IDS_APPNAME
, MB_OK
);
531 if (lastRevision
.GetParentFromHash(hashLast
))
533 MessageBox(lastRevision
.GetLastErr(), _T("TortoiseGit"), MB_ICONERROR
);
537 if (g_Git
.GetHash(headhash
, _T("HEAD")))
539 MessageBox(g_Git
.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR
);
543 if(!g_Git
.CheckCleanWorkTree())
545 CMessageBox::Show(NULL
, IDS_PROC_NOCLEAN
, IDS_APPNAME
, MB_OK
);
550 //Use throw to abort this process (reset back to original HEAD)
553 sCmd
.Format(_T("git.exe reset --hard %s --"), pFirstEntry
->m_CommitHash
.ToString());
554 if(g_Git
.Run(sCmd
, &out
, CP_UTF8
))
556 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
557 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1
)) + _T("\r\n\r\n") + out
));
559 sCmd
.Format(_T("git.exe reset --mixed %s --"), hashLast
.ToString());
560 if(g_Git
.Run(sCmd
, &out
, CP_UTF8
))
562 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
563 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2
)) + _T("\r\n\r\n")+out
));
566 CTGitPathList PathList
;
567 /* don't why must add --stat to get action status*/
568 /* first -z will be omitted by gitdll*/
569 if(g_Git
.GetDiffPath(&PathList
,&pFirstEntry
->m_CommitHash
,&hashLast
,"-z --stat -r"))
571 CMessageBox::Show(NULL
,_T("Get Diff file list error"),_T("TortoiseGit"),MB_OK
);
572 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not get changed file list aborting...\r\n\r\n")+out
));
575 for (int i
= 0; i
< PathList
.GetCount(); ++i
)
577 if(PathList
[i
].m_Action
& CTGitPath::LOGACTIONS_ADDED
)
579 sCmd
.Format(_T("git.exe add -- \"%s\""), PathList
[i
].GetGitPathString());
580 if (g_Git
.Run(sCmd
, &out
, CP_UTF8
))
582 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
583 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not add new file aborting...\r\n\r\n")+out
));
587 if(PathList
[i
].m_Action
& CTGitPath::LOGACTIONS_DELETED
)
589 sCmd
.Format(_T("git.exe rm -- \"%s\""), PathList
[i
].GetGitPathString());
590 if (g_Git
.Run(sCmd
, &out
, CP_UTF8
))
592 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
593 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not rm file aborting...\r\n\r\n")+out
));
599 for (int i
= FirstSelect
; i
<= LastSelect
; ++i
)
601 GitRev
* pRev
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(i
));
602 dlg
.m_sLogMessage
+=pRev
->GetSubject()+_T("\n")+pRev
->GetBody();
603 dlg
.m_sLogMessage
+=_T("\n");
605 dlg
.m_bWholeProject
=true;
606 dlg
.m_bSelectFilesForCommit
= true;
607 dlg
.m_bForceCommitAmend
=true;
609 gpl
.AddPath(CTGitPath(g_Git
.m_CurrentDir
));
610 dlg
.m_pathList
= gpl
;
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 if(CherryPickFrom(pFirstEntry
->m_CommitHash
.ToString(),headhash
))
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
));
634 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED
))));
636 catch(std::exception
& e
)
638 CMessageBox::Show(NULL
, CUnicodeUtils::GetUnicode(CStringA(e
.what())), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
639 sCmd
.Format(_T("git.exe reset --hard %s --"), headhash
.ToString());
641 if(g_Git
.Run(sCmd
, &out
, CP_UTF8
))
643 CMessageBox::Show(NULL
, CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD
)) + _T("\r\n\r\n") + out
, _T("TortoiseGit"), MB_OK
);
652 if (m_bThreadRunning
)
654 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_LOG_ONLYONCE
, IDS_APPNAME
, MB_ICONEXCLAMATION
);
658 dlg
.m_IsCherryPick
= TRUE
;
659 dlg
.m_Upstream
= this->m_CurrentBranch
;
660 POSITION pos2
= GetFirstSelectedItemPosition();
663 int indexNext2
= GetNextSelectedItem(pos2
);
664 dlg
.m_CommitList
.m_logEntries
.push_back(((GitRevLoglist
*)m_arShownList
[indexNext2
])->m_CommitHash
);
665 dlg
.m_CommitList
.m_LogCache
.m_HashMap
[((GitRevLoglist
*)m_arShownList
[indexNext2
])->m_CommitHash
] = *(GitRevLoglist
*)m_arShownList
[indexNext2
];
666 dlg
.m_CommitList
.m_logEntries
.GetGitRevAt(dlg
.m_CommitList
.m_logEntries
.size() - 1).GetRebaseAction() |= LOGACTIONS_REBASE_PICK
;
669 if(dlg
.DoModal() == IDOK
)
675 case ID_REBASE_TO_VERSION
:
677 if (m_bThreadRunning
)
679 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_LOG_ONLYONCE
, IDS_APPNAME
, MB_ICONEXCLAMATION
);
683 auto refList
= m_HashMap
[pSelLogEntry
->m_CommitHash
];
684 dlg
.m_Upstream
= refList
.empty() ? pSelLogEntry
->m_CommitHash
.ToString() : refList
.front();
685 for (auto ref
: refList
)
687 if (ref
.Left(11) == _T("refs/heads/"))
689 // 11=len("refs/heads/")
690 dlg
.m_Upstream
= ref
.Mid(11);
695 if(dlg
.DoModal() == IDOK
)
704 if (CAppUtils::StashSave())
709 if (CAppUtils::StashPop())
714 CAppUtils::RunTortoiseGitProc(_T("/command:reflog /ref:refs/stash"));
717 case ID_REFLOG_STASH_APPLY
:
718 CAppUtils::StashApply(pSelLogEntry
->m_Ref
);
724 if (GetSelectedCount() > 1)
725 str
.Format(IDS_PROC_DELETENREFS
, GetSelectedCount());
727 str
.Format(IDS_PROC_DELETEREF
, pSelLogEntry
->m_Ref
);
729 if (CMessageBox::Show(NULL
, str
, _T("TortoiseGit"), 1, IDI_QUESTION
, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON
)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
))) == 2)
732 std::vector
<CString
> refsToDelete
;
733 POSITION pos2
= GetFirstSelectedItemPosition();
736 CString ref
= ((GitRevLoglist
*)m_arShownList
[GetNextSelectedItem(pos2
)])->m_Ref
;
737 if (ref
.Find(_T("refs/")) == 0)
739 int refpos
= ref
.ReverseFind('{');
740 if (refpos
> 0 && ref
.Mid(refpos
- 1, 2) != _T("@{"))
741 ref
= ref
.Left(refpos
) + _T("@")+ ref
.Mid(refpos
);
742 refsToDelete
.push_back(ref
);
745 for (auto revIt
= refsToDelete
.rbegin(); revIt
!= refsToDelete
.rend(); ++revIt
)
747 CString ref
= *revIt
;
749 if (ref
.Find(_T("stash")) == 0)
750 sCmd
.Format(_T("git.exe stash drop %s"), ref
);
752 sCmd
.Format(_T("git.exe reflog delete %s"), ref
);
754 if (g_Git
.Run(sCmd
, &out
, CP_UTF8
))
755 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
757 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
763 CString sCmd
= _T("/command:log");
764 sCmd
+= _T(" /path:\"") + g_Git
.m_CurrentDir
+ _T("\" ");
765 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
766 sCmd
+= _T(" /endrev:") + r1
->m_CommitHash
.ToString();
767 CAppUtils::RunTortoiseGitProc(sCmd
);
770 case ID_CREATE_PATCH
:
772 int select
=this->GetSelectedCount();
773 CString sCmd
= _T("/command:formatpatch");
774 sCmd
+= _T(" /path:\"") + g_Git
.m_CurrentDir
+ _T("\" ");
776 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
780 sCmd
+= _T(" /startrev:") + r1
->m_CommitHash
.ToString();
784 r2
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
785 if( this->m_IsOldFirst
)
787 sCmd
+= _T(" /startrev:") + r1
->m_CommitHash
.ToString() + _T("~1");
788 sCmd
+= _T(" /endrev:") + r2
->m_CommitHash
.ToString();
793 sCmd
+= _T(" /startrev:") + r2
->m_CommitHash
.ToString() + _T("~1");
794 sCmd
+= _T(" /endrev:") + r1
->m_CommitHash
.ToString();
799 CAppUtils::RunTortoiseGitProc(sCmd
);
804 GitRev
* first
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
805 GitRev
* last
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
806 ASSERT(first
!= NULL
&& last
!= NULL
);
808 CString firstBad
= first
->m_CommitHash
.ToString();
809 if (!m_HashMap
[first
->m_CommitHash
].empty())
810 firstBad
= m_HashMap
[first
->m_CommitHash
].at(0);
811 CString lastGood
= last
->m_CommitHash
.ToString();
812 if (!m_HashMap
[last
->m_CommitHash
].empty())
813 lastGood
= m_HashMap
[last
->m_CommitHash
].at(0);
815 if (CAppUtils::BisectStart(lastGood
, firstBad
))
822 sCmd
.Format(_T("/command:repobrowser /path:\"%s\" /rev:%s"), g_Git
.m_CurrentDir
, pSelLogEntry
->m_CommitHash
.ToString());
823 CAppUtils::RunTortoiseGitProc(sCmd
);
828 CString guessAssociatedBranch
= pSelLogEntry
->m_CommitHash
;
829 if (!m_HashMap
[pSelLogEntry
->m_CommitHash
].empty() && m_HashMap
[pSelLogEntry
->m_CommitHash
].at(0).Find(_T("refs/heads/")) == 0)
830 guessAssociatedBranch
= m_HashMap
[pSelLogEntry
->m_CommitHash
].at(0);
831 if (CAppUtils::Push(guessAssociatedBranch
))
837 if (CAppUtils::Pull())
843 if (CAppUtils::Fetch())
850 sCmd
.Format(_T("/command:cleanup /path:\"%s\""), g_Git
.m_CurrentDir
);
851 CAppUtils::RunTortoiseGitProc(sCmd
);
854 case ID_SUBMODULE_UPDATE
:
857 sCmd
.Format(_T("/command:subupdate /bkpath:\"%s\""), g_Git
.m_CurrentDir
);
858 CAppUtils::RunTortoiseGitProc(sCmd
);
861 case ID_SHOWBRANCHES
:
864 sCmd
.Format(_T("git.exe branch -a --contains %s"), pSelLogEntry
->m_CommitHash
.ToString());
865 CProgressDlg progress
;
866 progress
.m_AutoClose
= AUTOCLOSE_NO
;
867 progress
.m_GitCmd
= sCmd
;
873 CString
*branch
= (CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
);
876 CMessageBox::Show(NULL
,IDS_ERROR_NOREF
,IDS_APPNAME
,MB_OK
|MB_ICONERROR
);
880 if (CGit::GetShortName(*branch
, shortname
, _T("refs/remotes/")))
883 msg
.Format(IDS_PROC_DELETEREMOTEBRANCH
, *branch
);
884 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
)));
887 CString remoteName
= shortname
.Left(shortname
.Find('/'));
888 shortname
= shortname
.Mid(shortname
.Find('/') + 1);
889 if(CAppUtils::IsSSHPutty())
890 CAppUtils::LaunchPAgent(NULL
, &remoteName
);
892 CSysProgressDlg sysProgressDlg
;
893 sysProgressDlg
.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME
)));
894 sysProgressDlg
.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS
)));
895 sysProgressDlg
.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT
)));
896 sysProgressDlg
.SetShowProgressBar(false);
897 sysProgressDlg
.ShowModal(this, true);
899 list
.push_back(_T("refs/heads/") + shortname
);
900 if (g_Git
.DeleteRemoteRefs(remoteName
, list
))
901 CMessageBox::Show(NULL
, g_Git
.GetGitLastErr(_T("Could not delete remote ref."), CGit::GIT_CMD_PUSH
), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
902 sysProgressDlg
.Stop();
904 else if (result
== 2)
906 if (g_Git
.DeleteRef(*branch
))
908 CMessageBox::Show(nullptr, g_Git
.GetGitLastErr(L
"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH
), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
915 else if (CGit::GetShortName(*branch
, shortname
, _T("refs/stash")))
917 if (CMessageBox::Show(NULL
, IDS_PROC_DELETEALLSTASH
, IDS_APPNAME
, 2, IDI_QUESTION
, IDS_DELETEBUTTON
, IDS_ABORTBUTTON
) == 1)
920 sCmd
.Format(_T("git.exe stash clear"));
922 if (g_Git
.Run(sCmd
, &out
, CP_UTF8
))
923 CMessageBox::Show(NULL
, out
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
931 msg
.Format(IDS_PROC_DELETEBRANCHTAG
, *branch
);
932 if (CMessageBox::Show(NULL
, msg
, _T("TortoiseGit"), 2, IDI_QUESTION
, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON
)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
))) == 1)
934 if (g_Git
.DeleteRef(*branch
))
936 CMessageBox::Show(nullptr, g_Git
.GetGitLastErr(L
"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH
), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
941 this->ReloadHashMap();
943 m_pFindDialog
->RefreshList();
945 this->GetItemRect(FirstSelect
,&rect
,LVIR_BOUNDS
);
946 this->InvalidateRect(rect
);
952 m_nSearchIndex
= GetSelectionMark();
953 if (m_nSearchIndex
< 0)
961 m_pFindDialog
= new CFindDlg();
962 m_pFindDialog
->Create(this);
968 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
969 if (!m_HashMap
[pSelLogEntry
->m_CommitHash
].empty())
970 str
= m_HashMap
[pSelLogEntry
->m_CommitHash
].at(0);
971 // we need an URL to complete this command, so error out if we can't get an URL
972 if(CAppUtils::Merge(&str
))
981 if (GetSelectedCount() == 1)
984 if (parent
> pSelLogEntry
->m_ParentHash
.size())
987 str
.Format(IDS_PROC_NOPARENT
, parent
);
988 MessageBox(str
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
993 if (!this->RevertSelectedCommits(parent
))
995 if (CMessageBox::Show(m_hWnd
, IDS_REVREVERTED
, IDS_APPNAME
, 1, IDI_QUESTION
, IDS_OKBUTTON
, IDS_COMMITBUTTON
) == 2)
997 CTGitPathList pathlist
;
998 CTGitPathList selectedlist
;
999 pathlist
.AddPath(this->m_Path
);
1000 bool bSelectFilesForCommit
= !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE
));
1002 CAppUtils::Commit(CString(), false, str
, pathlist
, selectedlist
, bSelectFilesForCommit
);
1010 CAppUtils::EditNote(pSelLogEntry
);
1011 this->SetItemState(FirstSelect
, 0, LVIS_SELECTED
);
1012 this->SetItemState(FirstSelect
, LVIS_SELECTED
, LVIS_SELECTED
);
1016 //CMessageBox::Show(NULL,_T("Have not implemented"),_T("TortoiseGit"),MB_OK);
1020 case ID_BLAMECOMPARE
:
1022 //user clicked on the menu item "compare with working copy"
1023 //now first get the revision which is selected
1026 GitDiff
diff(this, this->m_hWnd
, true);
1027 diff
.SetHEADPeg(m_LogRevision
);
1028 diff
.ShowCompare(m_path
, GitRev::REV_BASE
, m_path
, revSelected
, GitRev(), false, true);
1031 CAppUtils::StartShowCompare(m_hWnd
, m_path
, GitRev::REV_BASE
, m_path
, revSelected
, GitRev(), m_LogRevision
, false, false, true);
1034 case ID_BLAMEWITHPREVIOUS
:
1036 //user clicked on the menu item "Compare and Blame with previous revision"
1039 GitDiff
diff(this, this->m_hWnd
, true);
1040 diff
.SetHEADPeg(m_LogRevision
);
1041 diff
.ShowCompare(CTGitPath(pathURL
), revPrevious
, CTGitPath(pathURL
), revSelected
, GitRev(), false, true);
1044 CAppUtils::StartShowCompare(m_hWnd
, CTGitPath(pathURL
), revPrevious
, CTGitPath(pathURL
), revSelected
, GitRev(), m_LogRevision
, false, false, true);
1052 CProgressDlg progDlg
;
1053 progDlg
.SetTitle(IDS_APPNAME
);
1054 progDlg
.SetAnimation(IDR_DOWNLOAD
);
1056 sInfoLine
.Format(IDS_PROGRESSGETFILEREVISION
, m_path
.GetWinPath(), (LPCTSTR
)revSelected
.ToString());
1057 progDlg
.SetLine(1, sInfoLine
, true);
1058 SetAndClearProgressInfo(&progDlg
);
1059 progDlg
.ShowModeless(m_hWnd
);
1060 CTGitPath tempfile
= CTempFiles::Instance().GetTempFilePath(false, m_path
, revSelected
);
1061 bool bSuccess
= true;
1062 if (!Cat(m_path
, GitRev(GitRev::REV_HEAD
), revSelected
, tempfile
))
1065 // try again, but with the selected revision as the peg revision
1066 if (!Cat(m_path
, revSelected
, revSelected
, tempfile
))
1069 SetAndClearProgressInfo((HWND
)NULL
);
1070 CMessageBox::Show(this->m_hWnd
, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR
);
1079 SetAndClearProgressInfo((HWND
)NULL
);
1080 SetFileAttributes(tempfile
.GetWinPath(), FILE_ATTRIBUTE_READONLY
);
1082 CAppUtils::ShellOpen(tempfile
.GetWinPath(), GetSafeHwnd());
1084 CAppUtils::ShowOpenWithDialog(tempfile
.GetWinPathString(), GetSafeHwnd());
1091 dlg
.EndRev
= revSelected
;
1092 if (dlg
.DoModal() == IDOK
)
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
);
1107 CString sParams
= _T("/path:\"") + m_path
.GetGitPathString() + _T("\" ");
1108 if(!CAppUtils::LaunchTortoiseBlame(tempfile
, logfile
, CPathUtils::GetFileNameFromPath(m_path
.GetFileOrDirectoryName()),sParams
))
1116 CMessageBox::Show(this->m_hWnd
, blame
.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR
);
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);
1132 CString url
= m_ProjectProperties
.sWebViewerRev
;
1133 url
= GetAbsoluteUrlFromRelativeUrl(url
);
1134 url
.Replace(_T("%REVISION%"), revSelected
.ToString());
1136 ShellExecute(this->m_hWnd
, _T("open"), url
, NULL
, NULL
, SW_SHOWDEFAULT
);
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
);
1149 ShellExecute(this->m_hWnd
, _T("open"), url
, NULL
, NULL
, SW_SHOWDEFAULT
);
1156 theApp
.DoWaitCursor(-1);
1159 void CGitLogList::SetSelectedRebaseAction(int action
)
1161 POSITION pos
= GetFirstSelectedItemPosition();
1166 index
= GetNextSelectedItem(pos
);
1167 if (((GitRevLoglist
*)m_arShownList
[index
])->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT
| LOGACTIONS_REBASE_DONE
) || (index
== GetItemCount() - 1 && action
== LOGACTIONS_REBASE_SQUASH
))
1169 ((GitRevLoglist
*)m_arShownList
[index
])->GetRebaseAction() = action
;
1171 this->GetItemRect(index
,&rect
,LVIR_BOUNDS
);
1172 this->InvalidateRect(rect
);
1175 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage
);
1178 void CGitLogList::SetUnselectedRebaseAction(int action
)
1180 POSITION pos
= GetFirstSelectedItemPosition();
1181 int index
= pos
? GetNextSelectedItem(pos
) : -1;
1182 for (int i
= 0; i
< GetItemCount(); i
++)
1186 index
= pos
? GetNextSelectedItem(pos
) : -1;
1190 if (((GitRevLoglist
*)m_arShownList
[i
])->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT
| LOGACTIONS_REBASE_DONE
) || (i
== GetItemCount() - 1 && action
== LOGACTIONS_REBASE_SQUASH
))
1192 ((GitRevLoglist
*)m_arShownList
[i
])->GetRebaseAction() = action
;
1194 this->GetItemRect(i
, &rect
, LVIR_BOUNDS
);
1195 this->InvalidateRect(rect
);
1198 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage
);
1201 void CGitLogList::ShiftSelectedRebaseAction()
1203 POSITION pos
= GetFirstSelectedItemPosition();
1207 index
= GetNextSelectedItem(pos
);
1208 int *action
= &((GitRevLoglist
*)m_arShownList
[index
])->GetRebaseAction();
1211 case LOGACTIONS_REBASE_PICK
:
1212 *action
= LOGACTIONS_REBASE_SKIP
;
1214 case LOGACTIONS_REBASE_SKIP
:
1215 *action
= LOGACTIONS_REBASE_EDIT
;
1217 case LOGACTIONS_REBASE_EDIT
:
1218 *action
= LOGACTIONS_REBASE_SQUASH
;
1219 if (index
== GetItemCount() - 1)
1220 *action
= LOGACTIONS_REBASE_PICK
;
1222 case LOGACTIONS_REBASE_SQUASH
:
1223 *action
= LOGACTIONS_REBASE_PICK
;
1227 this->GetItemRect(index
, &rect
, LVIR_BOUNDS
);
1228 this->InvalidateRect(rect
);