1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2012 - 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"
26 //#include "VssStyle.h"
31 #include "GITProgressDlg.h"
32 #include "ProgressDlg.h"
33 #include "SysProgressDlg.h"
34 //#include "RepositoryBrowser.h"
35 //#include "CopyDlg.h"
36 //#include "StatGraphDlg.h"
38 #include "MessageBox.h"
41 #include "PathUtils.h"
42 #include "StringUtils.h"
43 #include "UnicodeUtils.h"
45 //#include "GitInfo.h"
46 //#include "GitDiff.h"
47 //#include "RevisionRangeDlg.h"
48 //#include "BrowseFolder.h"
49 //#include "BlameDlg.h"
51 //#include "GitHelpers.h"
52 #include "GitStatus.h"
53 //#include "LogDlgHelper.h"
54 //#include "CachedLogInfo.h"
55 //#include "RepositoryInfo.h"
56 //#include "EditPropertiesDlg.h"
57 #include "FileDiffDlg.h"
58 #include "CommitDlg.h"
59 #include "RebaseDlg.h"
62 IMPLEMENT_DYNAMIC(CGitLogList
, CHintListCtrl
)
64 int CGitLogList::RevertSelectedCommits()
66 CSysProgressDlg progress
;
70 if(!g_Git
.CheckCleanWorkTree())
72 CMessageBox::Show(NULL
,_T("Revert requires a clean working tree"),_T("TortoiseGit"),MB_OK
);
77 if (progress
.IsValid() && (this->GetSelectedCount() > 1) )
79 progress
.SetTitle(_T("Revert Commit"));
80 progress
.SetAnimation(IDR_MOVEANI
);
81 progress
.SetTime(true);
82 progress
.ShowModeless(this);
85 POSITION pos
= GetFirstSelectedItemPosition();
89 int index
= GetNextSelectedItem(pos
);
90 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(index
));
92 if (progress
.IsValid() && (this->GetSelectedCount() > 1) )
94 progress
.FormatPathLine(1, _T("Revert %s"), r1
->m_CommitHash
.ToString());
95 progress
.FormatPathLine(2, _T("%s"), r1
->GetSubject());
96 progress
.SetProgress(i
, this->GetSelectedCount());
100 if(r1
->m_CommitHash
.IsEmpty())
104 cmd
.Format(_T("git.exe revert --no-edit --no-commit %s"), r1
->m_CommitHash
.ToString());
105 if(g_Git
.Run(cmd
, &output
, CP_ACP
))
108 str
=_T("Revert fail\n");
110 str
+= _T("\n")+output
;
111 if( GetSelectedCount() == 1)
112 CMessageBox::Show(NULL
,str
, _T("TortoiseGit"),MB_OK
|MB_ICONERROR
);
115 if(CMessageBox::Show(NULL
, str
, _T("TortoiseGit"),2 , IDI_ERROR
, _T("&Skip"), _T("&Abort")) == 2)
126 if ((progress
.IsValid())&&(progress
.HasUserCancelled()))
131 int CGitLogList::CherryPickFrom(CString from
, CString to
)
133 CLogDataVector
logs(&m_LogCache
);
134 if(logs
.ParserFromLog(NULL
,-1,0,&from
,&to
))
140 CSysProgressDlg progress
;
141 if (progress
.IsValid())
143 progress
.SetTitle(_T("Cherry Pick"));
144 progress
.SetAnimation(IDR_MOVEANI
);
145 progress
.SetTime(true);
146 progress
.ShowModeless(this);
149 for(int i
=logs
.size()-1;i
>=0;i
--)
151 if (progress
.IsValid())
153 progress
.FormatPathLine(1, _T("Pick up %s"), logs
.GetGitRevAt(i
).m_CommitHash
.ToString());
154 progress
.FormatPathLine(2, _T("%s"), logs
.GetGitRevAt(i
).GetSubject());
155 progress
.SetProgress(logs
.size()-i
, logs
.size());
157 if ((progress
.IsValid())&&(progress
.HasUserCancelled()))
159 //CMessageBox::Show(hwndExplorer, IDS_SVN_USERCANCELLED, IDS_APPNAME, MB_ICONINFORMATION);
160 throw std::exception(CUnicodeUtils::GetUTF8(_T("User canceled\r\n\r\n")));
164 cmd
.Format(_T("git.exe cherry-pick %s"),logs
.GetGitRevAt(i
).m_CommitHash
.ToString());
166 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
168 throw std::exception(CUnicodeUtils::GetUTF8(CString(_T("Cherry Pick Failure\r\n\r\n"))+out
));
176 void CGitLogList::ContextMenuAction(int cmd
,int FirstSelect
, int LastSelect
, CMenu
*popmenu
)
178 POSITION pos
= GetFirstSelectedItemPosition();
179 int indexNext
= GetNextSelectedItem(pos
);
183 GitRev
* pSelLogEntry
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(indexNext
));
185 theApp
.DoWaitCursor(1);
190 CTGitPathList pathlist
;
191 CTGitPathList selectedlist
;
192 pathlist
.AddPath(this->m_Path
);
193 bool bSelectFilesForCommit
= !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE
));
195 CAppUtils::Commit(CString(),false,str
,
196 pathlist
,selectedlist
,bSelectFilesForCommit
);
198 this->GetParent()->PostMessage(WM_COMMAND
,ID_LOGDLG_REFRESH
,0);
201 case ID_GNUDIFF1
: // compare with WC, unified
203 CString tempfile
=GetTempFile();
205 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
206 if(!r1
->m_CommitHash
.IsEmpty())
211 if( (cmd
&0xFFFF) == 0xFFFF)
215 else if((cmd
&0xFFFF) == 0xFFFE)
221 if(cmd
> r1
->m_ParentHash
.size())
224 str
.Format(_T("Parent %d does not exist"), cmd
);
225 CMessageBox::Show(NULL
,str
,_T("TortoiseGit"),MB_OK
|MB_ICONERROR
);
231 hash2
= r1
->m_ParentHash
[cmd
-1].ToString();
234 command
.Format(_T("git.exe diff-tree %s -r -p --stat %s %s"), merge
, hash2
, r1
->m_CommitHash
.ToString());
237 command
.Format(_T("git.exe diff -r -p --stat"));
239 g_Git
.RunLogFile(command
,tempfile
);
240 CAppUtils::StartUnifiedDiffViewer(tempfile
,r1
->m_CommitHash
.ToString().Left(6)+_T(":")+r1
->GetSubject());
244 case ID_GNUDIFF2
: // compare two revisions, unified
246 CString tempfile
=GetTempFile();
248 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
249 GitRev
* r2
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
251 if( r1
->m_CommitHash
.IsEmpty()) {
252 cmd
.Format(_T("git.exe diff -r -p --stat %s"),r2
->m_CommitHash
.ToString());
254 else if( r2
->m_CommitHash
.IsEmpty()) {
255 cmd
.Format(_T("git.exe diff -r -p --stat %s"),r1
->m_CommitHash
.ToString());
259 cmd
.Format(_T("git.exe diff-tree -r -p --stat %s %s"),r2
->m_CommitHash
.ToString(),r1
->m_CommitHash
.ToString());
262 g_Git
.RunLogFile(cmd
,tempfile
);
263 CAppUtils::StartUnifiedDiffViewer(tempfile
,r2
->m_CommitHash
.ToString().Left(6)+_T(":")+r1
->m_CommitHash
.ToString().Left(6));
268 case ID_COMPARETWO
: // compare two revisions
270 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
271 GitRev
* r2
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
272 CGitDiff::DiffCommit(this->m_Path
, r1
,r2
);
277 case ID_COMPARE
: // compare revision with WC
279 GitRev
* r1
= &m_wcRev
;
280 GitRev
* r2
= pSelLogEntry
;
282 CGitDiff::DiffCommit(this->m_Path
, r1
,r2
);
284 //user clicked on the menu item "compare with working copy"
287 // GitDiff diff(this, m_hWnd, true);
288 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
289 // diff.SetHEADPeg(m_LogRevision);
290 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
293 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
297 case ID_COMPAREWITHPREVIOUS
:
302 if(pSelLogEntry
->m_ParentHash
.size()>0)
303 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
311 CGitDiff::DiffCommit(this->m_Path
, pSelLogEntry
->m_CommitHash
.ToString(),pSelLogEntry
->m_ParentHash
[cmd
-1].ToString());
316 CMessageBox::Show(NULL
,_T("No previous version"),_T("TortoiseGit"),MB_OK
);
320 // GitDiff diff(this, m_hWnd, true);
321 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
322 // diff.SetHEADPeg(m_LogRevision);
323 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
326 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
329 case ID_COPYCLIPBOARD
:
331 CopySelectionToClipBoard();
336 CopySelectionToClipBoard(TRUE
);
341 CString str
=pSelLogEntry
->m_CommitHash
.ToString();
342 CAppUtils::Export(&str
);
345 case ID_CREATE_BRANCH
:
347 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
348 CAppUtils::CreateBranchTag(FALSE
,&str
);
355 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
356 CAppUtils::CreateBranchTag(TRUE
,&str
);
359 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
364 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
365 CAppUtils::Switch(&str
);
369 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
371 case ID_SWITCHBRANCH
:
374 CString
*branch
= (CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
);
378 if(branch
->Find(_T("refs/heads/")) ==0 )
379 name
= branch
->Mid(11);
383 CAppUtils::PerformSwitch(name
);
387 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
392 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
393 CAppUtils::GitReset(&str
);
399 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_PICK
);
402 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_EDIT
);
404 case ID_REBASE_SQUASH
:
405 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_SQUASH
);
408 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_SKIP
);
410 case ID_COMBINE_COMMIT
:
414 CGitHash hashFirst
,hashLast
;
416 int headindex
=GetHeadIndex();
417 if(headindex
>=0) //incase show all branch, head is not the first commits.
419 head
.Format(_T("HEAD~%d"),FirstSelect
-headindex
);
420 hashFirst
=g_Git
.GetHash(head
);
422 head
.Format(_T("HEAD~%d"),LastSelect
-headindex
);
423 hashLast
=g_Git
.GetHash(head
);
426 GitRev
* pFirstEntry
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
427 GitRev
* pLastEntry
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
428 if(pFirstEntry
->m_CommitHash
!= hashFirst
|| pLastEntry
->m_CommitHash
!= hashLast
)
430 CMessageBox::Show(NULL
,_T("Cannot combine commits now.\r\nMake sure you are viewing the log of your current branch and no filters are applied."),_T("TortoiseGit"),MB_OK
);
434 headhash
=g_Git
.GetHash(_T("HEAD"));
436 if(!g_Git
.CheckCleanWorkTree())
438 CMessageBox::Show(NULL
,_T("Combine needs a clean work tree"),_T("TortoiseGit"),MB_OK
);
443 //Use throw to abort this process (reset back to original HEAD)
446 cmd
.Format(_T("git.exe reset --hard %s"),pFirstEntry
->m_CommitHash
.ToString());
447 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
449 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
450 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not reset to first commit (first step) aborting...\r\n\r\n")+out
));
452 cmd
.Format(_T("git.exe reset --mixed %s"),hashLast
.ToString());
453 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
455 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
456 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not reset to last commit (second step) aborting...\r\n\r\n")+out
));
459 CTGitPathList PathList
;
460 /* don't why must add --stat to get action status*/
461 /* first -z will be omitted by gitdll*/
462 if(g_Git
.GetDiffPath(&PathList
,&pFirstEntry
->m_CommitHash
,&hashLast
,"-z --stat -r"))
464 CMessageBox::Show(NULL
,_T("Get Diff file list error"),_T("TortoiseGit"),MB_OK
);
465 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not get changed file list aborting...\r\n\r\n")+out
));
468 for(int i
=0;i
<PathList
.GetCount();i
++)
470 if(PathList
[i
].m_Action
& CTGitPath::LOGACTIONS_ADDED
)
472 cmd
.Format(_T("git.exe add -- \"%s\""), PathList
[i
].GetGitPathString());
473 if(g_Git
.Run(cmd
,&out
,CP_ACP
))
475 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
476 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not add new file aborting...\r\n\r\n")+out
));
480 if(PathList
[i
].m_Action
& CTGitPath::LOGACTIONS_DELETED
)
482 cmd
.Format(_T("git.exe rm -- \"%s\""), PathList
[i
].GetGitPathString());
483 if(g_Git
.Run(cmd
,&out
,CP_ACP
))
485 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
486 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not rm file aborting...\r\n\r\n")+out
));
492 for(int i
=FirstSelect
;i
<=LastSelect
;i
++)
494 GitRev
* pRev
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(i
));
495 dlg
.m_sLogMessage
+=pRev
->GetSubject()+_T("\n")+pRev
->GetBody();
496 dlg
.m_sLogMessage
+=_T("\n");
498 dlg
.m_bWholeProject
=true;
499 dlg
.m_bSelectFilesForCommit
= true;
500 dlg
.m_bCommitAmend
=true;
501 dlg
.m_bNoPostActions
=true;
502 dlg
.m_AmendStr
=dlg
.m_sLogMessage
;
504 if (dlg
.DoModal() == IDOK
)
506 if(pFirstEntry
->m_CommitHash
!=headhash
)
508 //Commitrange firstEntry..headhash (from top of combine to original head) needs to be 'cherry-picked'
509 //on top of new commit.
510 //Use the rebase --onto command for it.
512 //All this can be done in one step using the following command:
513 //cmd.Format(_T("git.exe format-patch --stdout --binary --full-index -k %s..%s | git am -k -3"),
514 // pFirstEntry->m_CommitHash,
516 //But I am not sure if a '|' is going to work in a CreateProcess() call.
518 //Later the progress dialog could be used to execute these steps.
520 if(CherryPickFrom(pFirstEntry
->m_CommitHash
.ToString(),headhash
))
523 msg
.Format(_T("Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n"));
524 throw std::exception(CUnicodeUtils::GetUTF8(msg
));
527 CString currentBranch
=g_Git
.GetCurrentBranch();
528 cmd
.Format(_T("git.exe rebase --onto \"%s\" %s %s"),
530 pFirstEntry
->m_CommitHash
,
532 if(g_Git
.Run(cmd
,&out
,CP_UTF8
)!=0)
535 msg
.Format(_T("Error while rebasing commits on top of combined commits. Aborting.\r\n\r\n%s"),out
);
536 // CMessageBox::Show(NULL,msg,_T("TortoiseGit"),MB_OK);
537 g_Git
.Run(_T("git.exe rebase --abort"),&out
,CP_UTF8
);
538 throw std::exception(CUnicodeUtils::GetUTF8(msg
));
541 //HEAD is now on <no branch>.
542 //The following steps are to get HEAD back on the original branch and reset the branch to the new HEAD
543 //To avoid 2 working copy changes, we could use git branch -f <original branch> <hash new head>
544 //And then git checkout <original branch>
545 //But I don't know if 'git branch -f' removes tracking options. So for now, do a checkout and a reset.
548 CString newHead
=g_Git
.GetHash(CString(_T("HEAD")));
550 //Checkout working branch
551 cmd
.Format(_T("git.exe checkout -f \"%s\""),currentBranch
);
552 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
553 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not checkout original branch. Aborting...\r\n\r\n")+out
));
556 cmd
.Format(_T("git.exe reset --hard %s"),newHead
);
557 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
558 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not reset to new head. Aborting...\r\n\r\n")+out
));
563 throw std::exception("User aborted the combine process");
565 catch(std::exception
& e
)
567 CMessageBox::Show(NULL
,CUnicodeUtils::GetUnicode(CStringA(e
.what())),_T("TortoiseGit: Combine error"),MB_OK
|MB_ICONERROR
);
568 cmd
.Format(_T("git.exe reset --hard %s"),headhash
.ToString());
570 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
572 CMessageBox::Show(NULL
,_T("Could not reset to original HEAD\r\n\r\n")+out
,_T("TortoiseGit"),MB_OK
);
580 if(!g_Git
.CheckCleanWorkTree())
582 CMessageBox::Show(NULL
,_T("Cherry Pick requires a clean working tree"),_T("TortoiseGit"),MB_OK
);
588 dlg
.m_IsCherryPick
= TRUE
;
589 dlg
.m_Upstream
= this->m_CurrentBranch
;
590 POSITION pos
= GetFirstSelectedItemPosition();
593 int indexNext
= GetNextSelectedItem(pos
);
594 dlg
.m_CommitList
.m_logEntries
.push_back( ((GitRev
*)m_arShownList
[indexNext
])->m_CommitHash
);
595 dlg
.m_CommitList
.m_LogCache
.m_HashMap
[((GitRev
*)m_arShownList
[indexNext
])->m_CommitHash
]=*(GitRev
*)m_arShownList
[indexNext
];
596 dlg
.m_CommitList
.m_logEntries
.GetGitRevAt(dlg
.m_CommitList
.m_logEntries
.size()-1).GetAction(this) |= CTGitPath::LOGACTIONS_REBASE_PICK
;
599 if(dlg
.DoModal() == IDOK
)
605 case ID_REBASE_TO_VERSION
:
606 if(!g_Git
.CheckCleanWorkTree())
608 CMessageBox::Show(NULL
,_T("Rebase requires a clean working tree"),_T("TortoiseGit"),MB_OK
);
614 dlg
.m_Upstream
= pSelLogEntry
->m_CommitHash
;
616 if(dlg
.DoModal() == IDOK
)
625 if (CAppUtils::StashSave())
630 if (CAppUtils::StashPop())
635 CAppUtils::RunTortoiseProc(_T("/command:reflog /ref:refs/stash"));
638 case ID_REFLOG_STASH_APPLY
:
639 CAppUtils::StashApply(pSelLogEntry
->m_Ref
);
645 if (GetSelectedCount() > 1)
646 str
.Format(_T("Do you really want to permanently delete the %d selected refs? It can <ct=0x0000FF><b>NOT</b></ct> be recovered!"), GetSelectedCount());
648 str
.Format(_T("Warning: \"%s\" will be permanently deleted. It can <ct=0x0000FF><b>NOT</b></ct> be recovered!\r\n\r\nDo you really want to continue?"), pSelLogEntry
->m_Ref
);
650 if (CMessageBox::Show(NULL
, str
, _T("TortoiseGit"), 1, IDI_QUESTION
, _T("&Delete"), _T("&Abort")) == 2)
653 POSITION pos
= GetFirstSelectedItemPosition();
656 CString ref
= ((GitRev
*)m_arShownList
[GetNextSelectedItem(pos
)])->m_Ref
;
657 if (ref
.Find(_T("refs/")) == 0)
659 int refpos
= ref
.ReverseFind('{');
660 if (refpos
> 0 && ref
.Mid(refpos
, 2) != _T("@{"))
661 ref
= ref
.Left(refpos
) + _T("@")+ ref
.Mid(refpos
);
664 if (ref
.Find(_T("stash")) == 0)
665 cmd
.Format(_T("git.exe stash drop %s"), ref
);
667 cmd
.Format(_T("git.exe reflog delete %s"), ref
);
669 if(g_Git
.Run(cmd
,&out
,CP_ACP
))
670 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
672 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
678 CString cmd
= _T("/command:log");
679 cmd
+= _T(" /path:\"")+g_Git
.m_CurrentDir
+_T("\" ");
680 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
681 cmd
+= _T(" /endrev:")+r1
->m_CommitHash
.ToString();
682 CAppUtils::RunTortoiseProc(cmd
);
685 case ID_CREATE_PATCH
:
687 int select
=this->GetSelectedCount();
688 CString cmd
= _T("/command:formatpatch");
689 cmd
+= _T(" /path:\"")+g_Git
.m_CurrentDir
+_T("\" ");
691 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
695 cmd
+= _T(" /startrev:")+r1
->m_CommitHash
.ToString();
699 r2
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
700 if( this->m_IsOldFirst
)
702 cmd
+= _T(" /startrev:")+r1
->m_CommitHash
.ToString()+_T("~1");
703 cmd
+= _T(" /endrev:")+r2
->m_CommitHash
.ToString();
708 cmd
+= _T(" /startrev:")+r2
->m_CommitHash
.ToString()+_T("~1");
709 cmd
+= _T(" /endrev:")+r1
->m_CommitHash
.ToString();
714 CAppUtils::RunTortoiseProc(cmd
);
719 CString guessAssociatedBranch
;
720 if (m_HashMap
[pSelLogEntry
->m_CommitHash
].size() > 0)
721 guessAssociatedBranch
= m_HashMap
[pSelLogEntry
->m_CommitHash
].at(0);
722 if (CAppUtils::Push(guessAssociatedBranch
))
728 CString
*branch
= (CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
);
731 CMessageBox::Show(NULL
,IDS_ERROR_NOREF
,IDS_APPNAME
,MB_OK
|MB_ICONERROR
);
735 msg
=CString(_T("Do you really want to <ct=0x0000FF>delete</ct> <b>")) + *branch
;
737 if( CMessageBox::Show(NULL
, msg
, _T("TortoiseGit"), 2, IDI_QUESTION
, _T("&Delete"), _T("&Abort")) == 1 )
741 if(this->GetShortName(*branch
,shortname
,_T("refs/heads/")))
743 cmd
.Format(_T("git.exe branch -D -- %s"),shortname
);
746 if(this->GetShortName(*branch
,shortname
,_T("refs/remotes/")))
748 cmd
.Format(_T("git.exe branch -r -D -- %s"),shortname
);
751 if(this->GetShortName(*branch
,shortname
,_T("refs/tags/")))
753 cmd
.Format(_T("git.exe tag -d -- %s"),shortname
);
756 if(this->GetShortName(*branch
,shortname
,_T("refs/stash")))
758 if(CMessageBox::Show(NULL
, _T("<ct=0x0000FF>Do you really want to delete <b>ALL</b> stash?</ct>"),
759 _T("TortoiseGit"), 2, IDI_QUESTION
, _T("&Delete"), _T("&Abort")) == 1)
760 cmd
.Format(_T("git.exe stash clear"));
766 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
768 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
770 this->ReloadHashMap();
772 this->GetItemRect(FirstSelect
,&rect
,LVIR_BOUNDS
);
773 this->InvalidateRect(rect
);
780 m_nSearchIndex
= GetSelectionMark();
781 if (m_nSearchIndex
< 0)
789 m_pFindDialog
= new CFindDlg();
790 m_pFindDialog
->Create(this);
796 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
797 if (m_HashMap
[pSelLogEntry
->m_CommitHash
].size() > 0)
798 str
= m_HashMap
[pSelLogEntry
->m_CommitHash
].at(0);
799 // we need an URL to complete this command, so error out if we can't get an URL
800 if(CAppUtils::Merge(&str
))
808 if(!this->RevertSelectedCommits())
814 CAppUtils::EditNote(pSelLogEntry
);
815 this->SetItemState(FirstSelect
, 0, LVIS_SELECTED
);
816 this->SetItemState(FirstSelect
, LVIS_SELECTED
, LVIS_SELECTED
);
820 //CMessageBox::Show(NULL,_T("Have not implemented"),_T("TortoiseGit"),MB_OK);
824 case ID_BLAMECOMPARE
:
826 //user clicked on the menu item "compare with working copy"
827 //now first get the revision which is selected
830 GitDiff
diff(this, this->m_hWnd
, true);
831 diff
.SetHEADPeg(m_LogRevision
);
832 diff
.ShowCompare(m_path
, GitRev::REV_BASE
, m_path
, revSelected
, GitRev(), false, true);
835 CAppUtils::StartShowCompare(m_hWnd
, m_path
, GitRev::REV_BASE
, m_path
, revSelected
, GitRev(), m_LogRevision
, false, false, true);
840 //user clicked on the menu item "compare and blame revisions"
843 GitDiff
diff(this, this->m_hWnd
, true);
844 diff
.SetHEADPeg(m_LogRevision
);
845 diff
.ShowCompare(CTGitPath(pathURL
), revSelected2
, CTGitPath(pathURL
), revSelected
, GitRev(), false, true);
848 CAppUtils::StartShowCompare(m_hWnd
, CTGitPath(pathURL
), revSelected2
, CTGitPath(pathURL
), revSelected
, GitRev(), m_LogRevision
, false, false, true);
851 case ID_BLAMEWITHPREVIOUS
:
853 //user clicked on the menu item "Compare and Blame with previous revision"
856 GitDiff
diff(this, this->m_hWnd
, true);
857 diff
.SetHEADPeg(m_LogRevision
);
858 diff
.ShowCompare(CTGitPath(pathURL
), revPrevious
, CTGitPath(pathURL
), revSelected
, GitRev(), false, true);
861 CAppUtils::StartShowCompare(m_hWnd
, CTGitPath(pathURL
), revPrevious
, CTGitPath(pathURL
), revSelected
, GitRev(), m_LogRevision
, false, false, true);
869 CProgressDlg progDlg
;
870 progDlg
.SetTitle(IDS_APPNAME
);
871 progDlg
.SetAnimation(IDR_DOWNLOAD
);
873 sInfoLine
.Format(IDS_PROGRESSGETFILEREVISION
, m_path
.GetWinPath(), (LPCTSTR
)revSelected
.ToString());
874 progDlg
.SetLine(1, sInfoLine
, true);
875 SetAndClearProgressInfo(&progDlg
);
876 progDlg
.ShowModeless(m_hWnd
);
877 CTGitPath tempfile
= CTempFiles::Instance().GetTempFilePath(false, m_path
, revSelected
);
878 bool bSuccess
= true;
879 if (!Cat(m_path
, GitRev(GitRev::REV_HEAD
), revSelected
, tempfile
))
882 // try again, but with the selected revision as the peg revision
883 if (!Cat(m_path
, revSelected
, revSelected
, tempfile
))
886 SetAndClearProgressInfo((HWND
)NULL
);
887 CMessageBox::Show(this->m_hWnd
, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR
);
896 SetAndClearProgressInfo((HWND
)NULL
);
897 SetFileAttributes(tempfile
.GetWinPath(), FILE_ATTRIBUTE_READONLY
);
900 ret
= (int)ShellExecute(this->m_hWnd
, NULL
, tempfile
.GetWinPath(), NULL
, NULL
, SW_SHOWNORMAL
);
901 if ((ret
<= HINSTANCE_ERROR
)||bOpenWith
)
903 CString cmd
= _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
904 cmd
+= tempfile
.GetWinPathString() + _T(" ");
905 CAppUtils::LaunchApplication(cmd
, NULL
, false);
913 dlg
.EndRev
= revSelected
;
914 if (dlg
.DoModal() == IDOK
)
919 tempfile
= blame
.BlameToTempFile(m_path
, dlg
.StartRev
, dlg
.EndRev
, dlg
.EndRev
, logfile
, _T(""), dlg
.m_bIncludeMerge
, TRUE
, TRUE
);
920 if (!tempfile
.IsEmpty())
924 //open the default text editor for the result file
925 CAppUtils::StartTextViewer(tempfile
);
929 CString sParams
= _T("/path:\"") + m_path
.GetGitPathString() + _T("\" ");
930 if(!CAppUtils::LaunchTortoiseBlame(tempfile
, logfile
, CPathUtils::GetFileNameFromPath(m_path
.GetFileOrDirectoryName()),sParams
))
938 CMessageBox::Show(this->m_hWnd
, blame
.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR
);
946 CString url
= _T("tgit:")+pathURL
;
947 sCmd
.Format(_T("%s /command:update /path:\"%s\" /rev:%ld"),
948 (LPCTSTR
)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
949 (LPCTSTR
)m_path
.GetWinPath(), (LONG
)revSelected
);
950 CAppUtils::LaunchApplication(sCmd
, NULL
, false);
956 EditLogMessage(selIndex
);
961 EditAuthor(selEntries
);
966 CEditPropertiesDlg dlg
;
967 dlg
.SetProjectProperties(&m_ProjectProperties
);
968 CTGitPathList escapedlist
;
969 dlg
.SetPathList(CTGitPathList(CTGitPath(pathURL
)));
970 dlg
.SetRevision(revSelected
);
979 sCmd
.Format(_T("%s /command:export /path:\"%s\" /revision:%ld"),
980 (LPCTSTR
)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
981 (LPCTSTR
)pathURL
, (LONG
)revSelected
);
982 CAppUtils::LaunchApplication(sCmd
, NULL
, false);
987 CString url
= m_ProjectProperties
.sWebViewerRev
;
988 url
= GetAbsoluteUrlFromRelativeUrl(url
);
989 url
.Replace(_T("%REVISION%"), revSelected
.ToString());
991 ShellExecute(this->m_hWnd
, _T("open"), url
, NULL
, NULL
, SW_SHOWDEFAULT
);
996 CString relurl
= pathURL
;
997 CString sRoot
= GetRepositoryRoot(CTGitPath(relurl
));
998 relurl
= relurl
.Mid(sRoot
.GetLength());
999 CString url
= m_ProjectProperties
.sWebViewerPathRev
;
1000 url
= GetAbsoluteUrlFromRelativeUrl(url
);
1001 url
.Replace(_T("%REVISION%"), revSelected
.ToString());
1002 url
.Replace(_T("%PATH%"), relurl
);
1004 ShellExecute(this->m_hWnd
, _T("open"), url
, NULL
, NULL
, SW_SHOWDEFAULT
);
1011 theApp
.DoWaitCursor(-1);
1014 void CGitLogList::SetSelectedAction(int action
)
1016 POSITION pos
= GetFirstSelectedItemPosition();
1020 index
= GetNextSelectedItem(pos
);
1021 ((GitRev
*)m_arShownList
[index
])->GetAction(this) = action
;
1023 this->GetItemRect(index
,&rect
,LVIR_BOUNDS
);
1024 this->InvalidateRect(rect
);
1028 void CGitLogList::ShiftSelectedAction()
1030 POSITION pos
= GetFirstSelectedItemPosition();
1034 index
= GetNextSelectedItem(pos
);
1035 int action
= ((GitRev
*)m_arShownList
[index
])->GetAction(this);
1038 case CTGitPath::LOGACTIONS_REBASE_PICK
:
1039 action
= CTGitPath::LOGACTIONS_REBASE_SKIP
;
1041 case CTGitPath::LOGACTIONS_REBASE_SKIP
:
1042 action
= CTGitPath::LOGACTIONS_REBASE_EDIT
;
1044 case CTGitPath::LOGACTIONS_REBASE_EDIT
:
1045 action
= CTGitPath::LOGACTIONS_REBASE_SQUASH
;
1047 case CTGitPath::LOGACTIONS_REBASE_SQUASH
:
1048 action
= CTGitPath::LOGACTIONS_REBASE_PICK
;
1051 ((GitRev
*)m_arShownList
[index
])->GetAction(this) = action
;
1053 this->GetItemRect(index
, &rect
, LVIR_BOUNDS
);
1054 this->InvalidateRect(rect
);