1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2014 - 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"
29 #include "GitProgressDlg.h"
30 #include "ProgressDlg.h"
31 #include "SysProgressDlg.h"
33 #include "MessageBox.h"
36 #include "PathUtils.h"
37 #include "StringUtils.h"
38 #include "UnicodeUtils.h"
40 #include "FileDiffDlg.h"
41 #include "CommitDlg.h"
42 #include "RebaseDlg.h"
44 #include "../TGitCache/CacheInterface.h"
46 IMPLEMENT_DYNAMIC(CGitLogList
, CHintListCtrl
)
48 int CGitLogList::RevertSelectedCommits(int parent
)
50 CSysProgressDlg progress
;
54 if(!g_Git
.CheckCleanWorkTree())
56 CMessageBox::Show(NULL
, IDS_PROC_NOCLEAN
, IDS_APPNAME
, MB_OK
);
61 if (this->GetSelectedCount() > 1)
63 progress
.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_REVERTCOMMIT
)));
64 progress
.SetAnimation(IDR_MOVEANI
);
65 progress
.SetTime(true);
66 progress
.ShowModeless(this);
69 CBlockCacheForPath
cacheBlock(g_Git
.m_CurrentDir
);
71 POSITION pos
= GetFirstSelectedItemPosition();
75 int index
= GetNextSelectedItem(pos
);
76 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(index
));
78 if (progress
.IsVisible())
80 progress
.FormatNonPathLine(1, IDS_PROC_REVERTCOMMIT
, r1
->m_CommitHash
.ToString());
81 progress
.FormatNonPathLine(2, _T("%s"), r1
->GetSubject());
82 progress
.SetProgress(i
, this->GetSelectedCount());
86 if(r1
->m_CommitHash
.IsEmpty())
89 if (g_Git
.GitRevert(parent
, r1
->m_CommitHash
))
92 str
.LoadString(IDS_SVNACTION_FAILEDREVERT
);
93 str
= g_Git
.GetGitLastErr(str
, CGit::GIT_CMD_REVERT
);
94 if( GetSelectedCount() == 1)
95 CMessageBox::Show(NULL
, str
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
98 if(CMessageBox::Show(NULL
, str
, _T("TortoiseGit"),2 , IDI_ERROR
, CString(MAKEINTRESOURCE(IDS_SKIPBUTTON
)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
))) == 2)
109 if (progress
.HasUserCancelled())
114 int CGitLogList::CherryPickFrom(CString from
, CString to
)
116 CLogDataVector
logs(&m_LogCache
);
118 range
.Format(_T("%s..%s"), from
, to
);
119 if (logs
.ParserFromLog(nullptr, -1, 0, &range
))
125 CSysProgressDlg progress
;
126 progress
.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_CHERRYPICK
)));
127 progress
.SetAnimation(IDR_MOVEANI
);
128 progress
.SetTime(true);
129 progress
.ShowModeless(this);
131 CBlockCacheForPath
cacheBlock(g_Git
.m_CurrentDir
);
133 for (int i
= (int)logs
.size() - 1; i
>= 0; i
--)
135 if (progress
.IsVisible())
137 progress
.FormatNonPathLine(1, IDS_PROC_PICK
, logs
.GetGitRevAt(i
).m_CommitHash
.ToString());
138 progress
.FormatNonPathLine(2, _T("%s"), logs
.GetGitRevAt(i
).GetSubject());
139 progress
.SetProgress64(logs
.size() - i
, logs
.size());
141 if (progress
.HasUserCancelled())
143 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED
))));
146 cmd
.Format(_T("git.exe cherry-pick %s"),logs
.GetGitRevAt(i
).m_CommitHash
.ToString());
148 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
150 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_CHERRYPICKFAILED
)) + _T(":\r\n\r\n") + out
));
157 void CGitLogList::ContextMenuAction(int cmd
,int FirstSelect
, int LastSelect
, CMenu
*popmenu
)
159 POSITION pos
= GetFirstSelectedItemPosition();
160 int indexNext
= GetNextSelectedItem(pos
);
164 GitRev
* pSelLogEntry
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(indexNext
));
166 theApp
.DoWaitCursor(1);
171 CTGitPathList pathlist
;
172 CTGitPathList selectedlist
;
173 pathlist
.AddPath(this->m_Path
);
174 bool bSelectFilesForCommit
= !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE
));
176 CAppUtils::Commit(CString(),false,str
,
177 pathlist
,selectedlist
,bSelectFilesForCommit
);
179 this->GetParent()->PostMessage(WM_COMMAND
,ID_LOGDLG_REFRESH
,0);
184 if (CAppUtils::MergeAbort())
185 this->GetParent()->PostMessage(WM_COMMAND
,ID_LOGDLG_REFRESH
, 0);
188 case ID_GNUDIFF1
: // compare with WC, unified
190 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
191 bool bMerge
= false, bCombine
= false;
193 if(!r1
->m_CommitHash
.IsEmpty())
197 if( (cmd
&0xFFFF) == 0xFFFF)
200 else if((cmd
&0xFFFF) == 0xFFFE)
204 if(cmd
> r1
->m_ParentHash
.size())
207 str
.Format(IDS_PROC_NOPARENT
, cmd
);
208 MessageBox(str
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
214 hash2
= r1
->m_ParentHash
[cmd
-1].ToString();
217 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), hash2
, CTGitPath(), r1
->m_CommitHash
.ToString(), false, false, false, bMerge
, bCombine
);
220 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), _T("HEAD"), CTGitPath(), GitRev::GetWorkingCopy(), false, false, false, bMerge
, bCombine
);
224 case ID_GNUDIFF2
: // compare two revisions, unified
226 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
227 GitRev
* r2
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
228 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), r2
->m_CommitHash
.ToString(), CTGitPath(), r1
->m_CommitHash
.ToString());
232 case ID_COMPARETWO
: // compare two revisions
234 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
235 GitRev
* r2
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
236 if (m_Path
.IsDirectory() || !(m_ShowMask
& CGit::LOG_INFO_FOLLOW
))
237 CGitDiff::DiffCommit(this->m_Path
, r1
,r2
);
240 CString path1
= m_Path
.GetGitPathString();
241 // start with 1 (0 = working copy changes)
242 for (int i
= 1; i
< FirstSelect
; ++i
)
244 GitRev
* first
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(i
));
245 CTGitPathList list
= first
->GetFiles(NULL
);
246 CTGitPath
* file
= list
.LookForGitPath(path1
);
247 if (file
&& !file
->GetGitOldPathString().IsEmpty())
248 path1
= file
->GetGitOldPathString();
250 CString path2
= path1
;
251 for (int i
= FirstSelect
; i
< LastSelect
; ++i
)
253 GitRev
* first
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(i
));
254 CTGitPathList list
= first
->GetFiles(NULL
);
255 CTGitPath
* file
= list
.LookForGitPath(path2
);
256 if (file
&& !file
->GetGitOldPathString().IsEmpty())
257 path2
= file
->GetGitOldPathString();
259 CGitDiff::DiffCommit(CTGitPath(path1
), CTGitPath(path2
), r1
, r2
);
265 case ID_COMPARE
: // compare revision with WC
267 GitRev
* r1
= &m_wcRev
;
268 GitRev
* r2
= pSelLogEntry
;
270 if (m_Path
.IsDirectory() || !(m_ShowMask
& CGit::LOG_INFO_FOLLOW
))
271 CGitDiff::DiffCommit(this->m_Path
, r1
,r2
);
274 CString path1
= m_Path
.GetGitPathString();
275 // start with 1 (0 = working copy changes)
276 for (int i
= 1; i
< FirstSelect
; ++i
)
278 GitRev
* first
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(i
));
279 CTGitPathList list
= first
->GetFiles(NULL
);
280 CTGitPath
* file
= list
.LookForGitPath(path1
);
281 if (file
&& !file
->GetGitOldPathString().IsEmpty())
282 path1
= file
->GetGitOldPathString();
284 CGitDiff::DiffCommit(m_Path
, CTGitPath(path1
), r1
, r2
);
287 //user clicked on the menu item "compare with working copy"
290 // GitDiff diff(this, m_hWnd, true);
291 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
292 // diff.SetHEADPeg(m_LogRevision);
293 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
296 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
300 case ID_COMPAREWITHPREVIOUS
:
304 if (pSelLogEntry
->m_ParentHash
.empty())
308 pSelLogEntry
->GetParentFromHash(pSelLogEntry
->m_CommitHash
);
310 catch (const char* msg
)
312 MessageBox(_T("Could not get parent.\nlibgit reports:\n") + CString(msg
), _T("TortoiseGit"), MB_ICONERROR
);
316 if (!pSelLogEntry
->m_ParentHash
.empty())
317 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
325 if (m_Path
.IsDirectory() || !(m_ShowMask
& CGit::LOG_INFO_FOLLOW
))
326 CGitDiff::DiffCommit(m_Path
, pSelLogEntry
->m_CommitHash
.ToString(), pSelLogEntry
->m_ParentHash
[cmd
- 1].ToString());
329 CString path1
= m_Path
.GetGitPathString();
330 // start with 1 (0 = working copy changes)
331 for (int i
= 1; i
< indexNext
; ++i
)
333 GitRev
* first
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(i
));
334 CTGitPathList list
= first
->GetFiles(NULL
);
335 CTGitPath
* file
= list
.LookForGitPath(path1
);
336 if (file
&& !file
->GetGitOldPathString().IsEmpty())
337 path1
= file
->GetGitOldPathString();
339 CString path2
= path1
;
340 GitRev
* first
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(indexNext
));
341 CTGitPathList list
= first
->GetFiles(NULL
);
342 CTGitPath
* file
= list
.LookForGitPath(path2
);
343 if (file
&& !file
->GetGitOldPathString().IsEmpty())
344 path2
= file
->GetGitOldPathString();
346 CGitDiff::DiffCommit(CTGitPath(path1
), CTGitPath(path2
), pSelLogEntry
->m_CommitHash
.ToString(), pSelLogEntry
->m_ParentHash
[cmd
- 1].ToString());
351 CMessageBox::Show(NULL
, IDS_PROC_NOPREVIOUSVERSION
, IDS_APPNAME
, MB_OK
);
355 // GitDiff diff(this, m_hWnd, true);
356 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
357 // diff.SetHEADPeg(m_LogRevision);
358 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
361 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
364 case ID_LOG_VIEWRANGE
:
365 case ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE
:
367 GitRev
* pLastEntry
= reinterpret_cast<GitRev
*>(m_arShownList
.SafeGetAt(LastSelect
));
369 CString sep
= _T("..");
370 if ((cmd
& 0xFFFF) == ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE
)
373 CAppUtils::RunTortoiseGitProc(_T("/command:log /range:\"") + pLastEntry
->m_CommitHash
.ToString() + sep
+ pSelLogEntry
->m_CommitHash
.ToString() + _T("\""));
376 case ID_COPYCLIPBOARD
:
378 CopySelectionToClipBoard();
381 case ID_COPYCLIPBOARDMESSAGES
:
383 if ((GetAsyncKeyState(VK_SHIFT
) & 0x8000) != 0)
384 CopySelectionToClipBoard(ID_COPY_SUBJECT
);
386 CopySelectionToClipBoard(ID_COPY_MESSAGE
);
391 CopySelectionToClipBoard(ID_COPY_HASH
);
396 CString str
=pSelLogEntry
->m_CommitHash
.ToString();
397 CAppUtils::Export(&str
, &m_Path
);
400 case ID_CREATE_BRANCH
:
403 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
404 // try to guess remote branch in order to enable tracking
405 for (size_t i
= 0; i
< m_HashMap
[pSelLogEntry
->m_CommitHash
].size(); ++i
)
407 if (m_HashMap
[pSelLogEntry
->m_CommitHash
][i
].Find(_T("refs/remotes/")) == 0)
409 str
= m_HashMap
[pSelLogEntry
->m_CommitHash
][i
];
413 CAppUtils::CreateBranchTag((cmd
&0xFFFF) == ID_CREATE_TAG
, &str
);
416 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
421 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
422 // try to guess remote branch in order to recommend good branch name and tracking
423 for (size_t i
= 0; i
< m_HashMap
[pSelLogEntry
->m_CommitHash
].size(); ++i
)
425 if (m_HashMap
[pSelLogEntry
->m_CommitHash
][i
].Find(_T("refs/remotes/")) == 0)
427 str
= m_HashMap
[pSelLogEntry
->m_CommitHash
][i
];
431 CAppUtils::Switch(str
);
435 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
437 case ID_SWITCHBRANCH
:
440 CString
*branch
= (CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
);
444 if(branch
->Find(_T("refs/heads/")) ==0 )
445 name
= branch
->Mid(11);
449 CAppUtils::PerformSwitch(name
);
453 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
458 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
459 if (CAppUtils::GitReset(&str
))
468 SetSelectedRebaseAction(LOGACTIONS_REBASE_PICK
);
471 SetSelectedRebaseAction(LOGACTIONS_REBASE_EDIT
);
473 case ID_REBASE_SQUASH
:
474 SetSelectedRebaseAction(LOGACTIONS_REBASE_SQUASH
);
477 SetSelectedRebaseAction(LOGACTIONS_REBASE_SKIP
);
479 case ID_COMBINE_COMMIT
:
483 CGitHash hashFirst
,hashLast
;
485 int headindex
=GetHeadIndex();
486 if(headindex
>=0) //incase show all branch, head is not the first commits.
488 head
.Format(_T("HEAD~%d"),FirstSelect
-headindex
);
489 if (g_Git
.GetHash(hashFirst
, head
))
491 MessageBox(g_Git
.GetGitLastErr(_T("Could not get hash of first selected revision.")), _T("TortoiseGit"), MB_ICONERROR
);
495 head
.Format(_T("HEAD~%d"),LastSelect
-headindex
);
496 if (g_Git
.GetHash(hashLast
, head
))
498 MessageBox(g_Git
.GetGitLastErr(_T("Could not get hash of last selected revision.")), _T("TortoiseGit"), MB_ICONERROR
);
503 GitRev
* pFirstEntry
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
504 GitRev
* pLastEntry
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
505 if(pFirstEntry
->m_CommitHash
!= hashFirst
|| pLastEntry
->m_CommitHash
!= hashLast
)
507 CMessageBox::Show(NULL
, IDS_PROC_CANNOTCOMBINE
, IDS_APPNAME
, MB_OK
);
514 lastRevision
.GetParentFromHash(hashLast
);
519 MessageBox(_T("Could not get parent(s) of ") + hashLast
.ToString() + _T(".\nlibgit reports:\n") + err
, _T("TortoiseGit"), MB_ICONERROR
);
523 if (g_Git
.GetHash(headhash
, _T("HEAD")))
525 MessageBox(g_Git
.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR
);
529 if(!g_Git
.CheckCleanWorkTree())
531 CMessageBox::Show(NULL
, IDS_PROC_NOCLEAN
, IDS_APPNAME
, MB_OK
);
536 //Use throw to abort this process (reset back to original HEAD)
539 cmd
.Format(_T("git.exe reset --hard %s --"), pFirstEntry
->m_CommitHash
.ToString());
540 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
542 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
543 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1
)) + _T("\r\n\r\n") + out
));
545 cmd
.Format(_T("git.exe reset --mixed %s --"), hashLast
.ToString());
546 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
548 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
549 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2
)) + _T("\r\n\r\n")+out
));
552 CTGitPathList PathList
;
553 /* don't why must add --stat to get action status*/
554 /* first -z will be omitted by gitdll*/
555 if(g_Git
.GetDiffPath(&PathList
,&pFirstEntry
->m_CommitHash
,&hashLast
,"-z --stat -r"))
557 CMessageBox::Show(NULL
,_T("Get Diff file list error"),_T("TortoiseGit"),MB_OK
);
558 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not get changed file list aborting...\r\n\r\n")+out
));
561 for (int i
= 0; i
< PathList
.GetCount(); ++i
)
563 if(PathList
[i
].m_Action
& CTGitPath::LOGACTIONS_ADDED
)
565 cmd
.Format(_T("git.exe add -- \"%s\""), PathList
[i
].GetGitPathString());
566 if (g_Git
.Run(cmd
, &out
, CP_UTF8
))
568 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
569 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not add new file aborting...\r\n\r\n")+out
));
573 if(PathList
[i
].m_Action
& CTGitPath::LOGACTIONS_DELETED
)
575 cmd
.Format(_T("git.exe rm -- \"%s\""), PathList
[i
].GetGitPathString());
576 if (g_Git
.Run(cmd
, &out
, CP_UTF8
))
578 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
579 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not rm file aborting...\r\n\r\n")+out
));
585 for (int i
= FirstSelect
; i
<= LastSelect
; ++i
)
587 GitRev
* pRev
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(i
));
588 dlg
.m_sLogMessage
+=pRev
->GetSubject()+_T("\n")+pRev
->GetBody();
589 dlg
.m_sLogMessage
+=_T("\n");
591 dlg
.m_bWholeProject
=true;
592 dlg
.m_bSelectFilesForCommit
= true;
593 dlg
.m_bForceCommitAmend
=true;
595 gpl
.AddPath(CTGitPath(g_Git
.m_CurrentDir
));
596 dlg
.m_pathList
= gpl
;
597 if (lastRevision
.ParentsCount() != 1)
599 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
);
600 dlg
.m_bAmendDiffToLastCommit
= TRUE
;
603 dlg
.m_bAmendDiffToLastCommit
= FALSE
;
604 dlg
.m_bNoPostActions
=true;
605 dlg
.m_AmendStr
=dlg
.m_sLogMessage
;
607 if (dlg
.DoModal() == IDOK
)
609 if(pFirstEntry
->m_CommitHash
!=headhash
)
611 if(CherryPickFrom(pFirstEntry
->m_CommitHash
.ToString(),headhash
))
614 msg
.Format(_T("Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n"));
615 throw std::exception(CUnicodeUtils::GetUTF8(msg
));
620 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED
))));
622 catch(std::exception
& e
)
624 CMessageBox::Show(NULL
, CUnicodeUtils::GetUnicode(CStringA(e
.what())), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
625 cmd
.Format(_T("git.exe reset --hard %s --"), headhash
.ToString());
627 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
629 CMessageBox::Show(NULL
, CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD
)) + _T("\r\n\r\n") + out
, _T("TortoiseGit"), MB_OK
);
638 if (m_bThreadRunning
)
640 MessageBox(_T("This operation cannot be started while the log dialog is still loading commits."), _T("TortoiseGit"), MB_ICONEXCLAMATION
);
644 dlg
.m_IsCherryPick
= TRUE
;
645 dlg
.m_Upstream
= this->m_CurrentBranch
;
646 POSITION pos
= GetFirstSelectedItemPosition();
649 int indexNext
= GetNextSelectedItem(pos
);
650 dlg
.m_CommitList
.m_logEntries
.push_back( ((GitRev
*)m_arShownList
[indexNext
])->m_CommitHash
);
651 dlg
.m_CommitList
.m_LogCache
.m_HashMap
[((GitRev
*)m_arShownList
[indexNext
])->m_CommitHash
]=*(GitRev
*)m_arShownList
[indexNext
];
652 dlg
.m_CommitList
.m_logEntries
.GetGitRevAt(dlg
.m_CommitList
.m_logEntries
.size() - 1).GetRebaseAction() |= LOGACTIONS_REBASE_PICK
;
655 if(dlg
.DoModal() == IDOK
)
661 case ID_REBASE_TO_VERSION
:
663 if (m_bThreadRunning
)
665 MessageBox(_T("This operation cannot be started while the log dialog is still loading commits."), _T("TortoiseGit"), MB_ICONEXCLAMATION
);
669 auto refList
= m_HashMap
[pSelLogEntry
->m_CommitHash
];
670 dlg
.m_Upstream
= refList
.empty() ? pSelLogEntry
->m_CommitHash
.ToString() : refList
.front();
671 for (auto ref
: refList
)
673 if (ref
.Left(11) == _T("refs/heads/"))
675 // 11=len("refs/heads/")
676 dlg
.m_Upstream
= ref
.Mid(11);
681 if(dlg
.DoModal() == IDOK
)
690 if (CAppUtils::StashSave())
695 if (CAppUtils::StashPop())
700 CAppUtils::RunTortoiseGitProc(_T("/command:reflog /ref:refs/stash"));
703 case ID_REFLOG_STASH_APPLY
:
704 CAppUtils::StashApply(pSelLogEntry
->m_Ref
);
710 if (GetSelectedCount() > 1)
711 str
.Format(IDS_PROC_DELETENREFS
, GetSelectedCount());
713 str
.Format(IDS_PROC_DELETEREF
, pSelLogEntry
->m_Ref
);
715 if (CMessageBox::Show(NULL
, str
, _T("TortoiseGit"), 1, IDI_QUESTION
, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON
)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
))) == 2)
718 std::vector
<CString
> refsToDelete
;
719 POSITION pos
= GetFirstSelectedItemPosition();
722 CString ref
= ((GitRev
*)m_arShownList
[GetNextSelectedItem(pos
)])->m_Ref
;
723 if (ref
.Find(_T("refs/")) == 0)
725 int refpos
= ref
.ReverseFind('{');
726 if (refpos
> 0 && ref
.Mid(refpos
- 1, 2) != _T("@{"))
727 ref
= ref
.Left(refpos
) + _T("@")+ ref
.Mid(refpos
);
728 refsToDelete
.push_back(ref
);
731 for (auto revIt
= refsToDelete
.rbegin(); revIt
!= refsToDelete
.rend(); ++revIt
)
733 CString ref
= *revIt
;
735 if (ref
.Find(_T("stash")) == 0)
736 cmd
.Format(_T("git.exe stash drop %s"), ref
);
738 cmd
.Format(_T("git.exe reflog delete %s"), ref
);
740 if (g_Git
.Run(cmd
, &out
, CP_UTF8
))
741 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
743 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
749 CString cmd
= _T("/command:log");
750 cmd
+= _T(" /path:\"")+g_Git
.m_CurrentDir
+_T("\" ");
751 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
752 cmd
+= _T(" /endrev:")+r1
->m_CommitHash
.ToString();
753 CAppUtils::RunTortoiseGitProc(cmd
);
756 case ID_CREATE_PATCH
:
758 int select
=this->GetSelectedCount();
759 CString cmd
= _T("/command:formatpatch");
760 cmd
+= _T(" /path:\"")+g_Git
.m_CurrentDir
+_T("\" ");
762 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
766 cmd
+= _T(" /startrev:")+r1
->m_CommitHash
.ToString();
770 r2
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
771 if( this->m_IsOldFirst
)
773 cmd
+= _T(" /startrev:")+r1
->m_CommitHash
.ToString()+_T("~1");
774 cmd
+= _T(" /endrev:")+r2
->m_CommitHash
.ToString();
779 cmd
+= _T(" /startrev:")+r2
->m_CommitHash
.ToString()+_T("~1");
780 cmd
+= _T(" /endrev:")+r1
->m_CommitHash
.ToString();
785 CAppUtils::RunTortoiseGitProc(cmd
);
790 GitRev
* first
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
791 GitRev
* last
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
792 ASSERT(first
!= NULL
&& last
!= NULL
);
794 CString firstBad
= first
->m_CommitHash
.ToString();
795 if (!m_HashMap
[first
->m_CommitHash
].empty())
796 firstBad
= m_HashMap
[first
->m_CommitHash
].at(0);
797 CString lastGood
= last
->m_CommitHash
.ToString();
798 if (!m_HashMap
[last
->m_CommitHash
].empty())
799 lastGood
= m_HashMap
[last
->m_CommitHash
].at(0);
801 if (CAppUtils::BisectStart(lastGood
, firstBad
))
808 sCmd
.Format(_T("/command:repobrowser /path:\"%s\" /rev:%s"), g_Git
.m_CurrentDir
, pSelLogEntry
->m_CommitHash
.ToString());
809 CAppUtils::RunTortoiseGitProc(sCmd
);
814 CString guessAssociatedBranch
;
815 if (!m_HashMap
[pSelLogEntry
->m_CommitHash
].empty())
816 guessAssociatedBranch
= m_HashMap
[pSelLogEntry
->m_CommitHash
].at(0);
817 if (CAppUtils::Push(guessAssociatedBranch
))
823 if (CAppUtils::Pull())
829 if (CAppUtils::Fetch(_T(""), true))
833 case ID_SHOWBRANCHES
:
836 cmd
.Format(_T("git.exe branch -a --contains %s"), pSelLogEntry
->m_CommitHash
.ToString());
837 CProgressDlg progress
;
838 progress
.m_AutoClose
= AUTOCLOSE_NO
;
839 progress
.m_GitCmd
= cmd
;
845 bool showProgress
= false;
846 CString
*branch
= (CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
);
849 CMessageBox::Show(NULL
,IDS_ERROR_NOREF
,IDS_APPNAME
,MB_OK
|MB_ICONERROR
);
854 if (CGit::GetShortName(*branch
, shortname
, _T("refs/remotes/")))
857 msg
.Format(IDS_PROC_DELETEREMOTEBRANCH
, *branch
);
858 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
)));
861 CString remoteName
= shortname
.Left(shortname
.Find('/'));
862 shortname
= shortname
.Mid(shortname
.Find('/') + 1);
863 if(CAppUtils::IsSSHPutty())
864 CAppUtils::LaunchPAgent(NULL
, &remoteName
);
866 cmd
.Format(L
"git.exe push \"%s\" :refs/heads/%s", remoteName
, shortname
);
869 else if (result
== 2)
871 if (g_Git
.DeleteRef(*branch
))
873 CMessageBox::Show(nullptr, g_Git
.GetGitLastErr(L
"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH
), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
880 else if (CGit::GetShortName(*branch
, shortname
, _T("refs/stash")))
882 if (CMessageBox::Show(NULL
, IDS_PROC_DELETEALLSTASH
, IDS_APPNAME
, 2, IDI_QUESTION
, IDS_DELETEBUTTON
, IDS_ABORTBUTTON
) == 1)
883 cmd
.Format(_T("git.exe stash clear"));
890 msg
.Format(IDS_PROC_DELETEBRANCHTAG
, *branch
);
891 if (CMessageBox::Show(NULL
, msg
, _T("TortoiseGit"), 2, IDI_QUESTION
, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON
)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
))) == 1)
893 if (g_Git
.DeleteRef(*branch
))
895 CMessageBox::Show(nullptr, g_Git
.GetGitLastErr(L
"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH
), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
902 CSysProgressDlg sysProgressDlg
;
905 sysProgressDlg
.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME
)));
906 sysProgressDlg
.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS
)));
907 sysProgressDlg
.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT
)));
908 sysProgressDlg
.SetShowProgressBar(false);
909 sysProgressDlg
.ShowModal(this, true);
912 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
914 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
917 sysProgressDlg
.Stop();
919 this->ReloadHashMap();
921 this->GetItemRect(FirstSelect
,&rect
,LVIR_BOUNDS
);
922 this->InvalidateRect(rect
);
928 m_nSearchIndex
= GetSelectionMark();
929 if (m_nSearchIndex
< 0)
937 m_pFindDialog
= new CFindDlg();
938 m_pFindDialog
->Create(this);
944 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
945 if (!m_HashMap
[pSelLogEntry
->m_CommitHash
].empty())
946 str
= m_HashMap
[pSelLogEntry
->m_CommitHash
].at(0);
947 // we need an URL to complete this command, so error out if we can't get an URL
948 if(CAppUtils::Merge(&str
))
957 if (GetSelectedCount() == 1)
960 if (parent
> pSelLogEntry
->m_ParentHash
.size())
963 str
.Format(IDS_PROC_NOPARENT
, parent
);
964 MessageBox(str
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
969 if (!this->RevertSelectedCommits(parent
))
971 if (CMessageBox::Show(m_hWnd
, IDS_REVREVERTED
, IDS_APPNAME
, 1, IDI_QUESTION
, IDS_OKBUTTON
, IDS_COMMITBUTTON
) == 2)
973 CTGitPathList pathlist
;
974 CTGitPathList selectedlist
;
975 pathlist
.AddPath(this->m_Path
);
976 bool bSelectFilesForCommit
= !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE
));
978 CAppUtils::Commit(CString(), false, str
, pathlist
, selectedlist
, bSelectFilesForCommit
);
986 CAppUtils::EditNote(pSelLogEntry
);
987 this->SetItemState(FirstSelect
, 0, LVIS_SELECTED
);
988 this->SetItemState(FirstSelect
, LVIS_SELECTED
, LVIS_SELECTED
);
992 //CMessageBox::Show(NULL,_T("Have not implemented"),_T("TortoiseGit"),MB_OK);
996 case ID_BLAMECOMPARE
:
998 //user clicked on the menu item "compare with working copy"
999 //now first get the revision which is selected
1002 GitDiff
diff(this, this->m_hWnd
, true);
1003 diff
.SetHEADPeg(m_LogRevision
);
1004 diff
.ShowCompare(m_path
, GitRev::REV_BASE
, m_path
, revSelected
, GitRev(), false, true);
1007 CAppUtils::StartShowCompare(m_hWnd
, m_path
, GitRev::REV_BASE
, m_path
, revSelected
, GitRev(), m_LogRevision
, false, false, true);
1010 case ID_BLAMEWITHPREVIOUS
:
1012 //user clicked on the menu item "Compare and Blame with previous revision"
1015 GitDiff
diff(this, this->m_hWnd
, true);
1016 diff
.SetHEADPeg(m_LogRevision
);
1017 diff
.ShowCompare(CTGitPath(pathURL
), revPrevious
, CTGitPath(pathURL
), revSelected
, GitRev(), false, true);
1020 CAppUtils::StartShowCompare(m_hWnd
, CTGitPath(pathURL
), revPrevious
, CTGitPath(pathURL
), revSelected
, GitRev(), m_LogRevision
, false, false, true);
1028 CProgressDlg progDlg
;
1029 progDlg
.SetTitle(IDS_APPNAME
);
1030 progDlg
.SetAnimation(IDR_DOWNLOAD
);
1032 sInfoLine
.Format(IDS_PROGRESSGETFILEREVISION
, m_path
.GetWinPath(), (LPCTSTR
)revSelected
.ToString());
1033 progDlg
.SetLine(1, sInfoLine
, true);
1034 SetAndClearProgressInfo(&progDlg
);
1035 progDlg
.ShowModeless(m_hWnd
);
1036 CTGitPath tempfile
= CTempFiles::Instance().GetTempFilePath(false, m_path
, revSelected
);
1037 bool bSuccess
= true;
1038 if (!Cat(m_path
, GitRev(GitRev::REV_HEAD
), revSelected
, tempfile
))
1041 // try again, but with the selected revision as the peg revision
1042 if (!Cat(m_path
, revSelected
, revSelected
, tempfile
))
1045 SetAndClearProgressInfo((HWND
)NULL
);
1046 CMessageBox::Show(this->m_hWnd
, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR
);
1055 SetAndClearProgressInfo((HWND
)NULL
);
1056 SetFileAttributes(tempfile
.GetWinPath(), FILE_ATTRIBUTE_READONLY
);
1059 ret
= (int)ShellExecute(this->m_hWnd
, NULL
, tempfile
.GetWinPath(), NULL
, NULL
, SW_SHOWNORMAL
);
1060 if ((ret
<= HINSTANCE_ERROR
)||bOpenWith
)
1062 CString cmd
= _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1063 cmd
+= tempfile
.GetWinPathString() + _T(" ");
1064 CAppUtils::LaunchApplication(cmd
, NULL
, false);
1072 dlg
.EndRev
= revSelected
;
1073 if (dlg
.DoModal() == IDOK
)
1078 tempfile
= blame
.BlameToTempFile(m_path
, dlg
.StartRev
, dlg
.EndRev
, dlg
.EndRev
, logfile
, _T(""), dlg
.m_bIncludeMerge
, TRUE
, TRUE
);
1079 if (!tempfile
.IsEmpty())
1081 if (dlg
.m_bTextView
)
1083 //open the default text editor for the result file
1084 CAppUtils::StartTextViewer(tempfile
);
1088 CString sParams
= _T("/path:\"") + m_path
.GetGitPathString() + _T("\" ");
1089 if(!CAppUtils::LaunchTortoiseBlame(tempfile
, logfile
, CPathUtils::GetFileNameFromPath(m_path
.GetFileOrDirectoryName()),sParams
))
1097 CMessageBox::Show(this->m_hWnd
, blame
.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR
);
1105 sCmd
.Format(_T("%s /command:export /path:\"%s\" /revision:%ld"),
1106 (LPCTSTR
)(CPathUtils::GetAppDirectory()+_T("TortoiseGitProc.exe")),
1107 (LPCTSTR
)pathURL
, (LONG
)revSelected
);
1108 CAppUtils::LaunchApplication(sCmd
, NULL
, false);
1113 CString url
= m_ProjectProperties
.sWebViewerRev
;
1114 url
= GetAbsoluteUrlFromRelativeUrl(url
);
1115 url
.Replace(_T("%REVISION%"), revSelected
.ToString());
1117 ShellExecute(this->m_hWnd
, _T("open"), url
, NULL
, NULL
, SW_SHOWDEFAULT
);
1120 case ID_VIEWPATHREV
:
1122 CString relurl
= pathURL
;
1123 CString sRoot
= GetRepositoryRoot(CTGitPath(relurl
));
1124 relurl
= relurl
.Mid(sRoot
.GetLength());
1125 CString url
= m_ProjectProperties
.sWebViewerPathRev
;
1126 url
= GetAbsoluteUrlFromRelativeUrl(url
);
1127 url
.Replace(_T("%REVISION%"), revSelected
.ToString());
1128 url
.Replace(_T("%PATH%"), relurl
);
1130 ShellExecute(this->m_hWnd
, _T("open"), url
, NULL
, NULL
, SW_SHOWDEFAULT
);
1137 theApp
.DoWaitCursor(-1);
1140 void CGitLogList::SetSelectedRebaseAction(int action
)
1142 POSITION pos
= GetFirstSelectedItemPosition();
1147 index
= GetNextSelectedItem(pos
);
1148 if (((GitRev
*)m_arShownList
[index
])->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT
| LOGACTIONS_REBASE_DONE
))
1150 ((GitRev
*)m_arShownList
[index
])->GetRebaseAction() = action
;
1152 this->GetItemRect(index
,&rect
,LVIR_BOUNDS
);
1153 this->InvalidateRect(rect
);
1156 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage
);
1158 void CGitLogList::ShiftSelectedRebaseAction()
1160 POSITION pos
= GetFirstSelectedItemPosition();
1164 index
= GetNextSelectedItem(pos
);
1165 int dummyAction
= 0;
1166 int *action
= &dummyAction
;
1167 action
= &((GitRev
*)m_arShownList
[index
])->GetRebaseAction();
1170 case LOGACTIONS_REBASE_PICK
:
1171 *action
= LOGACTIONS_REBASE_SKIP
;
1173 case LOGACTIONS_REBASE_SKIP
:
1174 *action
= LOGACTIONS_REBASE_EDIT
;
1176 case LOGACTIONS_REBASE_EDIT
:
1177 *action
= LOGACTIONS_REBASE_SQUASH
;
1179 case LOGACTIONS_REBASE_SQUASH
:
1180 *action
= LOGACTIONS_REBASE_PICK
;
1184 this->GetItemRect(index
, &rect
, LVIR_BOUNDS
);
1185 this->InvalidateRect(rect
);