Don't delete files which contain of one line without a line ending
[TortoiseGit.git] / src / TortoiseProc / GitLogListAction.cpp
bloba30a448fbf2032f6e128c8c9cd9e32c96a6fa881
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2013 - TortoiseGit
4 // Copyright (C) 2011-2013 - Sven Strickroth <email@cs-ware.de>
5 // Copyright (C) 2005-2007 Marco Costalba
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 2
10 // of the License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software Foundation,
19 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 // GitLogList.cpp : implementation file
23 #include "stdafx.h"
24 #include "TortoiseProc.h"
25 #include "GitLogList.h"
26 #include "GitRev.h"
27 //#include "VssStyle.h"
28 #include "IconMenu.h"
29 // CGitLogList
30 #include "cursor.h"
31 #include "InputDlg.h"
32 #include "GITProgressDlg.h"
33 #include "ProgressDlg.h"
34 #include "SysProgressDlg.h"
35 //#include "RepositoryBrowser.h"
36 //#include "CopyDlg.h"
37 //#include "StatGraphDlg.h"
38 #include "Logdlg.h"
39 #include "MessageBox.h"
40 #include "registry.h"
41 #include "AppUtils.h"
42 #include "PathUtils.h"
43 #include "StringUtils.h"
44 #include "UnicodeUtils.h"
45 #include "TempFile.h"
46 //#include "GitInfo.h"
47 //#include "GitDiff.h"
48 //#include "RevisionRangeDlg.h"
49 //#include "BrowseFolder.h"
50 //#include "BlameDlg.h"
51 //#include "Blame.h"
52 //#include "GitHelpers.h"
53 #include "GitStatus.h"
54 //#include "LogDlgHelper.h"
55 //#include "CachedLogInfo.h"
56 //#include "RepositoryInfo.h"
57 //#include "EditPropertiesDlg.h"
58 #include "FileDiffDlg.h"
59 #include "CommitDlg.h"
60 #include "RebaseDlg.h"
61 #include "GitDiff.h"
62 #include "../TGitCache/CacheInterface.h"
64 IMPLEMENT_DYNAMIC(CGitLogList, CHintListCtrl)
66 int CGitLogList::RevertSelectedCommits(int parent)
68 CSysProgressDlg progress;
69 int ret = -1;
71 #if 0
72 if(!g_Git.CheckCleanWorkTree())
74 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
77 #endif
79 if (this->GetSelectedCount() > 1)
81 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_REVERTCOMMIT)));
82 progress.SetAnimation(IDR_MOVEANI);
83 progress.SetTime(true);
84 progress.ShowModeless(this);
87 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
89 POSITION pos = GetFirstSelectedItemPosition();
90 int i=0;
91 while(pos)
93 int index = GetNextSelectedItem(pos);
94 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(index));
96 if (progress.IsVisible())
98 progress.FormatNonPathLine(1, IDS_PROC_REVERTCOMMIT, r1->m_CommitHash.ToString());
99 progress.FormatNonPathLine(2, _T("%s"), r1->GetSubject());
100 progress.SetProgress(i, this->GetSelectedCount());
102 i++;
104 if(r1->m_CommitHash.IsEmpty())
105 continue;
107 CString cmd, output, merge;
108 if (parent)
109 merge.Format(_T("-m %d "), parent);
110 cmd.Format(_T("git.exe revert --no-edit --no-commit %s%s"), merge, r1->m_CommitHash.ToString());
111 if (g_Git.Run(cmd, &output, CP_UTF8))
113 CString str;
114 str.LoadString(IDS_SVNACTION_FAILEDREVERT);
115 str += _T("\n");
116 str+= cmd;
117 str+= _T("\n")+output;
118 if( GetSelectedCount() == 1)
119 CMessageBox::Show(NULL, str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
120 else
122 if(CMessageBox::Show(NULL, str, _T("TortoiseGit"),2 , IDI_ERROR, CString(MAKEINTRESOURCE(IDS_SKIPBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
124 return ret;
128 else
130 ret =0;
133 if (progress.HasUserCancelled())
134 break;
136 return ret;
138 int CGitLogList::CherryPickFrom(CString from, CString to)
140 CLogDataVector logs(&m_LogCache);
141 if(logs.ParserFromLog(NULL,-1,0,&from,&to))
142 return -1;
144 if (logs.empty())
145 return 0;
147 CSysProgressDlg progress;
148 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_CHERRYPICK)));
149 progress.SetAnimation(IDR_MOVEANI);
150 progress.SetTime(true);
151 progress.ShowModeless(this);
153 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
155 for (int i = (int)logs.size() - 1; i >= 0; i--)
157 if (progress.IsVisible())
159 progress.FormatNonPathLine(1, IDS_PROC_PICK, logs.GetGitRevAt(i).m_CommitHash.ToString());
160 progress.FormatNonPathLine(2, _T("%s"), logs.GetGitRevAt(i).GetSubject());
161 progress.SetProgress64(logs.size() - i, logs.size());
163 if (progress.HasUserCancelled())
165 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED))));
167 CString cmd,out;
168 cmd.Format(_T("git.exe cherry-pick %s"),logs.GetGitRevAt(i).m_CommitHash.ToString());
169 out.Empty();
170 if(g_Git.Run(cmd,&out,CP_UTF8))
172 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_CHERRYPICKFAILED)) + _T(":\r\n\r\n") + out));
176 return 0;
179 void CGitLogList::ContextMenuAction(int cmd,int FirstSelect, int LastSelect, CMenu *popmenu)
181 POSITION pos = GetFirstSelectedItemPosition();
182 int indexNext = GetNextSelectedItem(pos);
183 if (indexNext < 0)
184 return;
186 GitRev* pSelLogEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(indexNext));
188 theApp.DoWaitCursor(1);
189 switch (cmd&0xFFFF)
191 case ID_COMMIT:
193 CTGitPathList pathlist;
194 CTGitPathList selectedlist;
195 pathlist.AddPath(this->m_Path);
196 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
197 CString str;
198 CAppUtils::Commit(CString(),false,str,
199 pathlist,selectedlist,bSelectFilesForCommit);
200 //this->Refresh();
201 this->GetParent()->PostMessage(WM_COMMAND,ID_LOGDLG_REFRESH,0);
203 break;
204 case ID_GNUDIFF1: // compare with WC, unified
206 CString tempfile=GetTempFile();
207 CString command;
208 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
209 if(!r1->m_CommitHash.IsEmpty())
211 CString merge;
212 CString hash2;
213 cmd >>= 16;
214 if( (cmd&0xFFFF) == 0xFFFF)
216 merge=_T("-m");
218 else if((cmd&0xFFFF) == 0xFFFE)
220 merge=_T("-c");
222 else
224 if(cmd > r1->m_ParentHash.size())
226 CString str;
227 str.Format(IDS_PROC_NOPARENT, cmd);
228 MessageBox(str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
229 return;
231 else
233 if(cmd>0)
234 hash2 = r1->m_ParentHash[cmd-1].ToString();
237 command.Format(_T("git.exe diff-tree %s -r -p --stat %s %s"), merge, hash2, r1->m_CommitHash.ToString());
239 else
240 command.Format(_T("git.exe diff -r -p --stat"));
242 g_Git.RunLogFile(command,tempfile);
243 CAppUtils::StartUnifiedDiffViewer(tempfile, r1->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()) + _T(":") + r1->GetSubject());
245 break;
247 case ID_GNUDIFF2: // compare two revisions, unified
249 CString tempfile=GetTempFile();
250 CString cmd;
251 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
252 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
254 if( r1->m_CommitHash.IsEmpty()) {
255 cmd.Format(_T("git.exe diff -r -p --stat %s"),r2->m_CommitHash.ToString());
257 else if( r2->m_CommitHash.IsEmpty()) {
258 cmd.Format(_T("git.exe diff -r -p --stat %s"),r1->m_CommitHash.ToString());
260 else
262 cmd.Format(_T("git.exe diff-tree -r -p --stat %s %s"),r2->m_CommitHash.ToString(),r1->m_CommitHash.ToString());
265 g_Git.RunLogFile(cmd,tempfile);
266 CAppUtils::StartUnifiedDiffViewer(tempfile, r2->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()) + _T(":") + r1->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()));
269 break;
271 case ID_COMPARETWO: // compare two revisions
273 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
274 GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
275 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
276 CGitDiff::DiffCommit(this->m_Path, r1,r2);
277 else
279 CString path1 = m_Path.GetGitPathString();
280 // start with 1 (0 = working copy changes)
281 for (int i = 1; i < FirstSelect; i++)
283 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
284 CTGitPathList list = first->GetFiles(NULL);
285 CTGitPath * file = list.LookForGitPath(path1);
286 if (file && !file->GetGitOldPathString().IsEmpty())
287 path1 = file->GetGitOldPathString();
289 CString path2 = path1;
290 for (int i = FirstSelect; i < LastSelect; i++)
292 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
293 CTGitPathList list = first->GetFiles(NULL);
294 CTGitPath * file = list.LookForGitPath(path2);
295 if (file && !file->GetGitOldPathString().IsEmpty())
296 path2 = file->GetGitOldPathString();
298 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), r1, r2);
302 break;
304 case ID_COMPARE: // compare revision with WC
306 GitRev * r1 = &m_wcRev;
307 GitRev * r2 = pSelLogEntry;
309 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
310 CGitDiff::DiffCommit(this->m_Path, r1,r2);
311 else
313 CString path1 = m_Path.GetGitPathString();
314 // start with 1 (0 = working copy changes)
315 for (int i = 1; i < FirstSelect; i++)
317 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
318 CTGitPathList list = first->GetFiles(NULL);
319 CTGitPath * file = list.LookForGitPath(path1);
320 if (file && !file->GetGitOldPathString().IsEmpty())
321 path1 = file->GetGitOldPathString();
323 CGitDiff::DiffCommit(m_Path, CTGitPath(path1), r1, r2);
326 //user clicked on the menu item "compare with working copy"
327 //if (PromptShown())
329 // GitDiff diff(this, m_hWnd, true);
330 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
331 // diff.SetHEADPeg(m_LogRevision);
332 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
334 //else
335 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
337 break;
339 case ID_COMPAREWITHPREVIOUS:
341 CFileDiffDlg dlg;
343 if (!pSelLogEntry->m_ParentHash.empty())
344 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
346 cmd>>=16;
347 cmd&=0xFFFF;
349 if(cmd == 0)
350 cmd=1;
352 if (m_Path.IsDirectory() || !(m_ShowMask & CGit::LOG_INFO_FOLLOW))
353 CGitDiff::DiffCommit(m_Path, pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString());
354 else
356 CString path1 = m_Path.GetGitPathString();
357 // start with 1 (0 = working copy changes)
358 for (int i = 1; i < indexNext; i++)
360 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
361 CTGitPathList list = first->GetFiles(NULL);
362 CTGitPath * file = list.LookForGitPath(path1);
363 if (file && !file->GetGitOldPathString().IsEmpty())
364 path1 = file->GetGitOldPathString();
366 CString path2 = path1;
367 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(indexNext));
368 CTGitPathList list = first->GetFiles(NULL);
369 CTGitPath * file = list.LookForGitPath(path2);
370 if (file && !file->GetGitOldPathString().IsEmpty())
371 path2 = file->GetGitOldPathString();
373 CGitDiff::DiffCommit(CTGitPath(path1), CTGitPath(path2), pSelLogEntry->m_CommitHash.ToString(), pSelLogEntry->m_ParentHash[cmd - 1].ToString());
376 else
378 CMessageBox::Show(NULL, IDS_PROC_NOPREVIOUSVERSION, IDS_APPNAME, MB_OK);
380 //if (PromptShown())
382 // GitDiff diff(this, m_hWnd, true);
383 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
384 // diff.SetHEADPeg(m_LogRevision);
385 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
387 //else
388 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
390 break;
391 case ID_COPYCLIPBOARD:
393 CopySelectionToClipBoard();
395 break;
396 case ID_COPYCLIPBOARDMESSAGES:
398 if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0)
399 CopySelectionToClipBoard(ID_COPY_SUBJECT);
400 else
401 CopySelectionToClipBoard(ID_COPY_MESSAGE);
403 break;
404 case ID_COPYHASH:
406 CopySelectionToClipBoard(ID_COPY_HASH);
408 break;
409 case ID_EXPORT:
411 CString str=pSelLogEntry->m_CommitHash.ToString();
412 CAppUtils::Export(&str);
414 break;
415 case ID_CREATE_BRANCH:
416 case ID_CREATE_TAG:
418 CString str = pSelLogEntry->m_CommitHash.ToString();
419 // try to guess remote branch in order to enable tracking
420 for (int i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); i++)
422 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/remotes/")) == 0)
424 str = m_HashMap[pSelLogEntry->m_CommitHash][i];
425 break;
428 CAppUtils::CreateBranchTag((cmd&0xFFFF) == ID_CREATE_TAG, &str);
429 ReloadHashMap();
430 Invalidate();
431 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
433 break;
434 case ID_SWITCHTOREV:
436 CString str = pSelLogEntry->m_CommitHash.ToString();
437 // try to guess remote branch in order to recommend good branch name and tracking
438 for (int i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); i++)
440 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/remotes/")) == 0)
442 str = m_HashMap[pSelLogEntry->m_CommitHash][i];
443 break;
446 CAppUtils::Switch(str);
448 ReloadHashMap();
449 Invalidate();
450 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
451 break;
452 case ID_SWITCHBRANCH:
453 if(popmenu)
455 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
456 if(branch)
458 CString name;
459 if(branch->Find(_T("refs/heads/")) ==0 )
460 name = branch->Mid(11);
461 else
462 name = *branch;
464 CAppUtils::PerformSwitch(name);
466 ReloadHashMap();
467 Invalidate();
468 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
470 break;
471 case ID_RESET:
473 CString str = pSelLogEntry->m_CommitHash.ToString();
474 if (CAppUtils::GitReset(&str))
476 ResetWcRev(true);
477 ReloadHashMap();
478 Invalidate();
481 break;
482 case ID_REBASE_PICK:
483 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_PICK);
484 break;
485 case ID_REBASE_EDIT:
486 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_EDIT);
487 break;
488 case ID_REBASE_SQUASH:
489 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_SQUASH);
490 break;
491 case ID_REBASE_SKIP:
492 SetSelectedAction(CTGitPath::LOGACTIONS_REBASE_SKIP);
493 break;
494 case ID_COMBINE_COMMIT:
496 CString head;
497 CGitHash headhash;
498 CGitHash hashFirst,hashLast;
500 int headindex=GetHeadIndex();
501 if(headindex>=0) //incase show all branch, head is not the first commits.
503 head.Format(_T("HEAD~%d"),FirstSelect-headindex);
504 if (g_Git.GetHash(hashFirst, head))
506 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of first selected revision.")), _T("TortoiseGit"), MB_ICONERROR);
507 break;
510 head.Format(_T("HEAD~%d"),LastSelect-headindex);
511 if (g_Git.GetHash(hashLast, head))
513 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of last selected revision.")), _T("TortoiseGit"), MB_ICONERROR);
514 break;
518 GitRev* pFirstEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
519 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
520 if(pFirstEntry->m_CommitHash != hashFirst || pLastEntry->m_CommitHash != hashLast)
522 CMessageBox::Show(NULL, IDS_PROC_CANNOTCOMBINE, IDS_APPNAME, MB_OK);
523 break;
526 GitRev lastRevision;
529 lastRevision.GetParentFromHash(hashLast);
531 catch (char* msg)
533 CString err(msg);
534 MessageBox(_T("Could not get parent(s) of ") + hashLast.ToString() + _T(".\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
535 break;
538 if (g_Git.GetHash(headhash, _T("HEAD")))
540 MessageBox(g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
541 break;
544 if(!g_Git.CheckCleanWorkTree())
546 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
547 break;
549 CString cmd,out;
551 //Use throw to abort this process (reset back to original HEAD)
554 cmd.Format(_T("git.exe reset --hard %s"),pFirstEntry->m_CommitHash.ToString());
555 if(g_Git.Run(cmd,&out,CP_UTF8))
557 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
558 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1)) + _T("\r\n\r\n") + out));
560 cmd.Format(_T("git.exe reset --mixed %s"),hashLast.ToString());
561 if(g_Git.Run(cmd,&out,CP_UTF8))
563 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
564 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2)) + _T("\r\n\r\n")+out));
567 CTGitPathList PathList;
568 /* don't why must add --stat to get action status*/
569 /* first -z will be omitted by gitdll*/
570 if(g_Git.GetDiffPath(&PathList,&pFirstEntry->m_CommitHash,&hashLast,"-z --stat -r"))
572 CMessageBox::Show(NULL,_T("Get Diff file list error"),_T("TortoiseGit"),MB_OK);
573 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not get changed file list aborting...\r\n\r\n")+out));
576 for(int i=0;i<PathList.GetCount();i++)
578 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_ADDED)
580 cmd.Format(_T("git.exe add -- \"%s\""), PathList[i].GetGitPathString());
581 if (g_Git.Run(cmd, &out, CP_UTF8))
583 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
584 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not add new file aborting...\r\n\r\n")+out));
588 if(PathList[i].m_Action & CTGitPath::LOGACTIONS_DELETED)
590 cmd.Format(_T("git.exe rm -- \"%s\""), PathList[i].GetGitPathString());
591 if (g_Git.Run(cmd, &out, CP_UTF8))
593 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
594 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not rm file aborting...\r\n\r\n")+out));
599 CCommitDlg dlg;
600 for(int i=FirstSelect;i<=LastSelect;i++)
602 GitRev* pRev = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));
603 dlg.m_sLogMessage+=pRev->GetSubject()+_T("\n")+pRev->GetBody();
604 dlg.m_sLogMessage+=_T("\n");
606 dlg.m_bWholeProject=true;
607 dlg.m_bSelectFilesForCommit = true;
608 dlg.m_bForceCommitAmend=true;
609 if (lastRevision.ParentsCount() != 1)
611 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);
612 dlg.m_bAmendDiffToLastCommit = TRUE;
614 else
615 dlg.m_bAmendDiffToLastCommit = FALSE;
616 dlg.m_bNoPostActions=true;
617 dlg.m_AmendStr=dlg.m_sLogMessage;
619 if (dlg.DoModal() == IDOK)
621 if(pFirstEntry->m_CommitHash!=headhash)
623 //Commitrange firstEntry..headhash (from top of combine to original head) needs to be 'cherry-picked'
624 //on top of new commit.
625 //Use the rebase --onto command for it.
627 //All this can be done in one step using the following command:
628 //cmd.Format(_T("git.exe format-patch --stdout --binary --full-index -k %s..%s | git am -k -3"),
629 // pFirstEntry->m_CommitHash,
630 // headhash);
631 //But I am not sure if a '|' is going to work in a CreateProcess() call.
633 //Later the progress dialog could be used to execute these steps.
635 if(CherryPickFrom(pFirstEntry->m_CommitHash.ToString(),headhash))
637 CString msg;
638 msg.Format(_T("Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n"));
639 throw std::exception(CUnicodeUtils::GetUTF8(msg));
641 #if 0
642 CString currentBranch=g_Git.GetCurrentBranch();
643 cmd.Format(_T("git.exe rebase --onto \"%s\" %s %s"),
644 currentBranch,
645 pFirstEntry->m_CommitHash,
646 headhash);
647 if(g_Git.Run(cmd,&out,CP_UTF8)!=0)
649 CString msg;
650 msg.Format(_T("Error while rebasing commits on top of combined commits. Aborting.\r\n\r\n%s"),out);
651 // CMessageBox::Show(NULL,msg,_T("TortoiseGit"),MB_OK);
652 g_Git.Run(_T("git.exe rebase --abort"),&out,CP_UTF8);
653 throw std::exception(CUnicodeUtils::GetUTF8(msg));
656 //HEAD is now on <no branch>.
657 //The following steps are to get HEAD back on the original branch and reset the branch to the new HEAD
658 //To avoid 2 working copy changes, we could use git branch -f <original branch> <hash new head>
659 //And then git checkout <original branch>
660 //But I don't know if 'git branch -f' removes tracking options. So for now, do a checkout and a reset.
662 //Store new HEAD
663 CString newHead=g_Git.GetHash(CString(_T("HEAD")));
665 //Checkout working branch
666 cmd.Format(_T("git.exe checkout -f \"%s\""),currentBranch);
667 if(g_Git.Run(cmd,&out,CP_UTF8))
668 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not checkout original branch. Aborting...\r\n\r\n")+out));
670 //Reset to new HEAD
671 cmd.Format(_T("git.exe reset --hard %s"),newHead);
672 if(g_Git.Run(cmd,&out,CP_UTF8))
673 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not reset to new head. Aborting...\r\n\r\n")+out));
674 #endif
677 else
678 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED))));
680 catch(std::exception& e)
682 CMessageBox::Show(NULL, CUnicodeUtils::GetUnicode(CStringA(e.what())), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
683 cmd.Format(_T("git.exe reset --hard %s"),headhash.ToString());
684 out.Empty();
685 if(g_Git.Run(cmd,&out,CP_UTF8))
687 CMessageBox::Show(NULL, CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD)) + _T("\r\n\r\n") + out, _T("TortoiseGit"), MB_OK);
690 Refresh();
692 break;
694 case ID_CHERRY_PICK:
695 if(!g_Git.CheckCleanWorkTree())
697 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
700 else
702 CRebaseDlg dlg;
703 dlg.m_IsCherryPick = TRUE;
704 dlg.m_Upstream = this->m_CurrentBranch;
705 POSITION pos = GetFirstSelectedItemPosition();
706 while(pos)
708 int indexNext = GetNextSelectedItem(pos);
709 dlg.m_CommitList.m_logEntries.push_back( ((GitRev*)m_arShownList[indexNext])->m_CommitHash );
710 dlg.m_CommitList.m_LogCache.m_HashMap[((GitRev*)m_arShownList[indexNext])->m_CommitHash]=*(GitRev*)m_arShownList[indexNext];
711 dlg.m_CommitList.m_logEntries.GetGitRevAt(dlg.m_CommitList.m_logEntries.size()-1).GetAction(this) |= CTGitPath::LOGACTIONS_REBASE_PICK;
714 if(dlg.DoModal() == IDOK)
716 Refresh();
719 break;
720 case ID_REBASE_TO_VERSION:
721 if(!g_Git.CheckCleanWorkTree())
723 CMessageBox::Show(NULL, IDS_PROC_NOCLEAN, IDS_APPNAME, MB_OK);
726 else
728 CRebaseDlg dlg;
729 dlg.m_Upstream = pSelLogEntry->m_CommitHash;
731 if(dlg.DoModal() == IDOK)
733 Refresh();
737 break;
739 case ID_STASH_SAVE:
740 if (CAppUtils::StashSave())
741 Refresh();
742 break;
744 case ID_STASH_POP:
745 if (CAppUtils::StashPop())
746 Refresh();
747 break;
749 case ID_STASH_LIST:
750 CAppUtils::RunTortoiseGitProc(_T("/command:reflog /ref:refs/stash"));
751 break;
753 case ID_REFLOG_STASH_APPLY:
754 if (CAppUtils::StashApply(pSelLogEntry->m_Ref))
755 Refresh();
756 break;
758 case ID_REFLOG_DEL:
760 CString str;
761 if (GetSelectedCount() > 1)
762 str.Format(IDS_PROC_DELETENREFS, GetSelectedCount());
763 else
764 str.Format(IDS_PROC_DELETEREF, pSelLogEntry->m_Ref);
766 if (CMessageBox::Show(NULL, str, _T("TortoiseGit"), 1, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
767 return;
769 std::vector<CString> refsToDelete;
770 POSITION pos = GetFirstSelectedItemPosition();
771 while (pos)
773 CString ref = ((GitRev *)m_arShownList[GetNextSelectedItem(pos)])->m_Ref;
774 if (ref.Find(_T("refs/")) == 0)
775 ref = ref.Mid(5);
776 int refpos = ref.ReverseFind('{');
777 if (refpos > 0 && ref.Mid(refpos, 2) != _T("@{"))
778 ref = ref.Left(refpos) + _T("@")+ ref.Mid(refpos);
779 refsToDelete.push_back(ref);
782 for (std::vector<CString>::const_reverse_iterator revIt = refsToDelete.rbegin(); revIt != refsToDelete.rend(); revIt++)
784 CString ref = *revIt;
785 CString cmd, out;
786 if (ref.Find(_T("stash")) == 0)
787 cmd.Format(_T("git.exe stash drop %s"), ref);
788 else
789 cmd.Format(_T("git.exe reflog delete %s"), ref);
791 if (g_Git.Run(cmd, &out, CP_UTF8))
792 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
794 ::PostMessage(this->GetParent()->m_hWnd,MSG_REFLOG_CHANGED,0,0);
797 break;
798 case ID_LOG:
800 CString cmd = _T("/command:log");
801 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
802 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
803 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
804 CAppUtils::RunTortoiseGitProc(cmd);
806 break;
807 case ID_CREATE_PATCH:
809 int select=this->GetSelectedCount();
810 CString cmd = _T("/command:formatpatch");
811 cmd += _T(" /path:\"")+g_Git.m_CurrentDir+_T("\" ");
813 GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
814 GitRev * r2 = NULL;
815 if(select == 1)
817 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString();
819 else
821 r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
822 if( this->m_IsOldFirst )
824 cmd += _T(" /startrev:")+r1->m_CommitHash.ToString()+_T("~1");
825 cmd += _T(" /endrev:")+r2->m_CommitHash.ToString();
828 else
830 cmd += _T(" /startrev:")+r2->m_CommitHash.ToString()+_T("~1");
831 cmd += _T(" /endrev:")+r1->m_CommitHash.ToString();
836 CAppUtils::RunTortoiseGitProc(cmd);
838 break;
839 case ID_BISECTSTART:
841 GitRev * first = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));
842 GitRev * last = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));
843 ASSERT(first != NULL && last != NULL);
845 CString firstBad = first->m_CommitHash.ToString();
846 if (!m_HashMap[first->m_CommitHash].empty())
847 firstBad = m_HashMap[first->m_CommitHash].at(0);
848 CString lastGood = last->m_CommitHash.ToString();
849 if (!m_HashMap[last->m_CommitHash].empty())
850 lastGood = m_HashMap[last->m_CommitHash].at(0);
852 if (CAppUtils::BisectStart(lastGood, firstBad))
853 Refresh();
855 break;
856 case ID_REPOBROWSE:
858 CString sCmd;
859 sCmd.Format(_T("/command:repobrowser /path:\"%s\" /rev:%s"), g_Git.m_CurrentDir, pSelLogEntry->m_CommitHash.ToString());
860 CAppUtils::RunTortoiseGitProc(sCmd);
862 break;
863 case ID_PUSH:
865 CString guessAssociatedBranch;
866 if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
867 guessAssociatedBranch = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
868 if (CAppUtils::Push(guessAssociatedBranch))
869 Refresh();
871 break;
872 case ID_FETCH:
874 if (CAppUtils::Fetch(_T(""), true))
875 Refresh();
877 break;
878 case ID_SHOWBRANCHES:
880 CString cmd;
881 cmd.Format(_T("git.exe branch -a --contains %s"), pSelLogEntry->m_CommitHash.ToString());
882 CProgressDlg progress;
883 progress.m_GitCmd = cmd;
884 progress.DoModal();
886 break;
887 case ID_DELETE:
889 CString *branch = (CString*)((CIconMenu*)popmenu)->GetMenuItemData(cmd);
890 if (!branch)
892 CMessageBox::Show(NULL,IDS_ERROR_NOREF,IDS_APPNAME,MB_OK|MB_ICONERROR);
893 return;
895 CString shortname;
896 CString cmd;
897 if (CGit::GetShortName(*branch, shortname, _T("refs/remotes/")))
899 CString msg;
900 msg.Format(IDS_PROC_DELETEREMOTEBRANCH, *branch);
901 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)));
902 if (result == 1)
904 CString remoteName = shortname.Left(shortname.Find('/'));
905 shortname = shortname.Mid(shortname.Find('/') + 1);
906 if(CAppUtils::IsSSHPutty())
907 CAppUtils::LaunchPAgent(NULL, &remoteName);
909 cmd.Format(L"git.exe push \"%s\" :refs/heads/%s", remoteName, shortname);
911 else if (result == 2)
912 cmd.Format(_T("git.exe branch -r -D -- %s"), shortname);
913 else
914 return;
916 else if (CGit::GetShortName(*branch, shortname, _T("refs/stash")))
918 if (CMessageBox::Show(NULL, IDS_PROC_DELETEALLSTASH, IDS_APPNAME, 2, IDI_QUESTION, IDS_DELETEBUTTON, IDS_ABORTBUTTON) == 1)
919 cmd.Format(_T("git.exe stash clear"));
920 else
921 return;
923 else
925 CString msg;
926 msg.Format(IDS_PROC_DELETEBRANCHTAG, *branch);
927 if (CMessageBox::Show(NULL, msg, _T("TortoiseGit"), 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
929 if(CGit::GetShortName(*branch,shortname,_T("refs/heads/")))
931 cmd.Format(_T("git.exe branch -D -- %s"),shortname);
934 if(CGit::GetShortName(*branch,shortname,_T("refs/tags/")))
936 cmd.Format(_T("git.exe tag -d -- %s"),shortname);
940 if (!cmd.IsEmpty())
942 CString out;
943 if(g_Git.Run(cmd,&out,CP_UTF8))
945 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
947 this->ReloadHashMap();
948 CRect rect;
949 this->GetItemRect(FirstSelect,&rect,LVIR_BOUNDS);
950 this->InvalidateRect(rect);
953 break;
955 case ID_FINDENTRY:
957 m_nSearchIndex = GetSelectionMark();
958 if (m_nSearchIndex < 0)
959 m_nSearchIndex = 0;
960 if (m_pFindDialog)
962 break;
964 else
966 m_pFindDialog = new CFindDlg();
967 m_pFindDialog->Create(this);
970 break;
971 case ID_MERGEREV:
973 CString str = pSelLogEntry->m_CommitHash.ToString();
974 if (!m_HashMap[pSelLogEntry->m_CommitHash].empty())
975 str = m_HashMap[pSelLogEntry->m_CommitHash].at(0);
976 // we need an URL to complete this command, so error out if we can't get an URL
977 if(CAppUtils::Merge(&str))
979 this->Refresh();
982 break;
983 case ID_REVERTREV:
985 int parent = 0;
986 if (GetSelectedCount() == 1)
988 parent = cmd >> 16;
989 if (parent > pSelLogEntry->m_ParentHash.size())
991 CString str;
992 str.Format(IDS_PROC_NOPARENT, parent);
993 MessageBox(str, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
994 return;
998 if (!this->RevertSelectedCommits(parent))
1000 if (CMessageBox::Show(m_hWnd, IDS_REVREVERTED, IDS_APPNAME, 1, IDI_QUESTION, IDS_OKBUTTON, IDS_COMMITBUTTON) == 2)
1002 CTGitPathList pathlist;
1003 CTGitPathList selectedlist;
1004 pathlist.AddPath(this->m_Path);
1005 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
1006 CString str;
1007 CAppUtils::Commit(CString(), false, str, pathlist, selectedlist, bSelectFilesForCommit);
1009 this->Refresh();
1012 break;
1013 case ID_EDITNOTE:
1015 CAppUtils::EditNote(pSelLogEntry);
1016 this->SetItemState(FirstSelect, 0, LVIS_SELECTED);
1017 this->SetItemState(FirstSelect, LVIS_SELECTED, LVIS_SELECTED);
1019 break;
1020 default:
1021 //CMessageBox::Show(NULL,_T("Have not implemented"),_T("TortoiseGit"),MB_OK);
1022 break;
1023 #if 0
1025 case ID_BLAMECOMPARE:
1027 //user clicked on the menu item "compare with working copy"
1028 //now first get the revision which is selected
1029 if (PromptShown())
1031 GitDiff diff(this, this->m_hWnd, true);
1032 diff.SetHEADPeg(m_LogRevision);
1033 diff.ShowCompare(m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), false, true);
1035 else
1036 CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_BASE, m_path, revSelected, GitRev(), m_LogRevision, false, false, true);
1038 break;
1039 case ID_BLAMEWITHPREVIOUS:
1041 //user clicked on the menu item "Compare and Blame with previous revision"
1042 if (PromptShown())
1044 GitDiff diff(this, this->m_hWnd, true);
1045 diff.SetHEADPeg(m_LogRevision);
1046 diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), false, true);
1048 else
1049 CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, false, false, true);
1051 break;
1053 case ID_OPENWITH:
1054 bOpenWith = true;
1055 case ID_OPEN:
1057 CProgressDlg progDlg;
1058 progDlg.SetTitle(IDS_APPNAME);
1059 progDlg.SetAnimation(IDR_DOWNLOAD);
1060 CString sInfoLine;
1061 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, m_path.GetWinPath(), (LPCTSTR)revSelected.ToString());
1062 progDlg.SetLine(1, sInfoLine, true);
1063 SetAndClearProgressInfo(&progDlg);
1064 progDlg.ShowModeless(m_hWnd);
1065 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, m_path, revSelected);
1066 bool bSuccess = true;
1067 if (!Cat(m_path, GitRev(GitRev::REV_HEAD), revSelected, tempfile))
1069 bSuccess = false;
1070 // try again, but with the selected revision as the peg revision
1071 if (!Cat(m_path, revSelected, revSelected, tempfile))
1073 progDlg.Stop();
1074 SetAndClearProgressInfo((HWND)NULL);
1075 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1076 EnableOKButton();
1077 break;
1079 bSuccess = true;
1081 if (bSuccess)
1083 progDlg.Stop();
1084 SetAndClearProgressInfo((HWND)NULL);
1085 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1086 int ret = 0;
1087 if (!bOpenWith)
1088 ret = (int)ShellExecute(this->m_hWnd, NULL, tempfile.GetWinPath(), NULL, NULL, SW_SHOWNORMAL);
1089 if ((ret <= HINSTANCE_ERROR)||bOpenWith)
1091 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1092 cmd += tempfile.GetWinPathString() + _T(" ");
1093 CAppUtils::LaunchApplication(cmd, NULL, false);
1097 break;
1098 case ID_BLAME:
1100 CBlameDlg dlg;
1101 dlg.EndRev = revSelected;
1102 if (dlg.DoModal() == IDOK)
1104 CBlame blame;
1105 CString tempfile;
1106 CString logfile;
1107 tempfile = blame.BlameToTempFile(m_path, dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, _T(""), dlg.m_bIncludeMerge, TRUE, TRUE);
1108 if (!tempfile.IsEmpty())
1110 if (dlg.m_bTextView)
1112 //open the default text editor for the result file
1113 CAppUtils::StartTextViewer(tempfile);
1115 else
1117 CString sParams = _T("/path:\"") + m_path.GetGitPathString() + _T("\" ");
1118 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(m_path.GetFileOrDirectoryName()),sParams))
1120 break;
1124 else
1126 CMessageBox::Show(this->m_hWnd, blame.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1130 break;
1131 case ID_EXPORT:
1133 CString sCmd;
1134 sCmd.Format(_T("%s /command:export /path:\"%s\" /revision:%ld"),
1135 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseGitProc.exe")),
1136 (LPCTSTR)pathURL, (LONG)revSelected);
1137 CAppUtils::LaunchApplication(sCmd, NULL, false);
1139 break;
1140 case ID_VIEWREV:
1142 CString url = m_ProjectProperties.sWebViewerRev;
1143 url = GetAbsoluteUrlFromRelativeUrl(url);
1144 url.Replace(_T("%REVISION%"), revSelected.ToString());
1145 if (!url.IsEmpty())
1146 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1148 break;
1149 case ID_VIEWPATHREV:
1151 CString relurl = pathURL;
1152 CString sRoot = GetRepositoryRoot(CTGitPath(relurl));
1153 relurl = relurl.Mid(sRoot.GetLength());
1154 CString url = m_ProjectProperties.sWebViewerPathRev;
1155 url = GetAbsoluteUrlFromRelativeUrl(url);
1156 url.Replace(_T("%REVISION%"), revSelected.ToString());
1157 url.Replace(_T("%PATH%"), relurl);
1158 if (!url.IsEmpty())
1159 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1161 break;
1162 #endif
1164 } // switch (cmd)
1166 theApp.DoWaitCursor(-1);
1169 void CGitLogList::SetSelectedAction(int action)
1171 POSITION pos = GetFirstSelectedItemPosition();
1172 int index;
1173 while(pos)
1175 index = GetNextSelectedItem(pos);
1176 ((GitRev*)m_arShownList[index])->GetAction(this) = action;
1177 CRect rect;
1178 this->GetItemRect(index,&rect,LVIR_BOUNDS);
1179 this->InvalidateRect(rect);
1183 void CGitLogList::ShiftSelectedAction()
1185 POSITION pos = GetFirstSelectedItemPosition();
1186 int index;
1187 while(pos)
1189 index = GetNextSelectedItem(pos);
1190 int action = ((GitRev*)m_arShownList[index])->GetAction(this);
1191 switch (action)
1193 case CTGitPath::LOGACTIONS_REBASE_PICK:
1194 action = CTGitPath::LOGACTIONS_REBASE_SKIP;
1195 break;
1196 case CTGitPath::LOGACTIONS_REBASE_SKIP:
1197 action= CTGitPath::LOGACTIONS_REBASE_EDIT;
1198 break;
1199 case CTGitPath::LOGACTIONS_REBASE_EDIT:
1200 action = CTGitPath::LOGACTIONS_REBASE_SQUASH;
1201 break;
1202 case CTGitPath::LOGACTIONS_REBASE_SQUASH:
1203 action= CTGitPath::LOGACTIONS_REBASE_PICK;
1204 break;
1206 ((GitRev*)m_arShownList[index])->GetAction(this) = action;
1207 CRect rect;
1208 this->GetItemRect(index, &rect, LVIR_BOUNDS);
1209 this->InvalidateRect(rect);