1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2014 - TortoiseGit
4 // Copyright (C) 2003-2011, 2013-2014 - TortoiseSVN
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.
21 #include "TortoiseProc.h"
22 #include "PathUtils.h"
24 #include "StringUtils.h"
25 #include "MessageBox.h"
29 #include "UnicodeUtils.h"
30 #include "ExportDlg.h"
31 #include "ProgressDlg.h"
32 #include "GitAdminDir.h"
33 #include "ProgressDlg.h"
34 #include "BrowseFolder.h"
35 #include "DirFileEnum.h"
36 #include "MessageBox.h"
37 #include "GitStatus.h"
38 #include "CreateBranchTagDlg.h"
39 #include "GitSwitchDlg.h"
41 #include "DeleteConflictDlg.h"
42 #include "ChangedDlg.h"
43 #include "SendMailDlg.h"
44 #include "GitProgressDlg.h"
46 #include "CommitDlg.h"
48 #include "MergeAbortDlg.h"
50 #include "..\Settings\Settings.h"
52 #include "SVNDCommitDlg.h"
53 #include "requestpulldlg.h"
54 #include "PullFetchDlg.h"
55 #include "FileDiffDlg.h"
56 #include "RebaseDlg.h"
58 #include "StashSave.h"
59 #include "IgnoreDlg.h"
60 #include "FormatMessageWrapper.h"
61 #include "SmartHandle.h"
62 #include "BisectStartDlg.h"
63 #include "SysProgressDlg.h"
64 #include "UserPassword.h"
68 CAppUtils::CAppUtils(void)
72 CAppUtils::~CAppUtils(void)
76 bool CAppUtils::StashSave()
80 if (dlg
.DoModal() == IDOK
)
83 cmd
= _T("git.exe stash save");
85 if (CAppUtils::GetMsysgitVersion() >= 0x01070700)
87 if (dlg
.m_bIncludeUntracked
)
88 cmd
+= _T(" --include-untracked");
93 if (!dlg
.m_sMessage
.IsEmpty())
95 CString message
= dlg
.m_sMessage
;
96 message
.Replace(_T("\""), _T("\"\""));
97 cmd
+= _T(" -- \"") + message
+ _T("\"");
100 CProgressDlg progress
;
101 progress
.m_GitCmd
= cmd
;
102 return (progress
.DoModal() == IDOK
);
107 bool CAppUtils::StashApply(CString ref
, bool showChanges
/* true */)
110 cmd
= _T("git.exe stash apply ");
111 if (ref
.Find(_T("refs/")) == 0)
113 if (ref
.Find(_T("stash{")) == 0)
114 ref
= _T("stash@") + ref
.Mid(5);
117 CSysProgressDlg sysProgressDlg
;
118 sysProgressDlg
.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME
)));
119 sysProgressDlg
.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING
)));
120 sysProgressDlg
.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT
)));
121 sysProgressDlg
.SetShowProgressBar(false);
122 sysProgressDlg
.SetCancelMsg(IDS_PROGRS_INFOFAILED
);
123 sysProgressDlg
.ShowModeless((HWND
)NULL
, true);
125 int ret
= g_Git
.Run(cmd
, &out
, CP_UTF8
);
127 sysProgressDlg
.Stop();
129 bool hasConflicts
= (out
.Find(_T("CONFLICT")) >= 0);
130 if (ret
&& !(ret
== 1 && hasConflicts
))
132 CMessageBox::Show(NULL
, CString(MAKEINTRESOURCE(IDS_PROC_STASHAPPLYFAILED
)) + _T("\n") + out
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
137 message
.LoadString(IDS_PROC_STASHAPPLYSUCCESS
);
139 message
.LoadString(IDS_PROC_STASHAPPLYFAILEDCONFLICTS
);
142 if(CMessageBox::Show(NULL
,message
+ _T("\n") + CString(MAKEINTRESOURCE(IDS_SEECHANGES
))
143 ,_T("TortoiseGit"),MB_YESNO
|MB_ICONINFORMATION
) == IDYES
)
146 dlg
.m_pathList
.AddPath(CTGitPath());
153 CMessageBox::Show(NULL
, message
,_T("TortoiseGit"), MB_OK
| MB_ICONINFORMATION
);
160 bool CAppUtils::StashPop(bool showChanges
/* true */)
163 cmd
=_T("git.exe stash pop ");
165 CSysProgressDlg sysProgressDlg
;
166 sysProgressDlg
.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME
)));
167 sysProgressDlg
.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING
)));
168 sysProgressDlg
.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT
)));
169 sysProgressDlg
.SetShowProgressBar(false);
170 sysProgressDlg
.SetCancelMsg(IDS_PROGRS_INFOFAILED
);
171 sysProgressDlg
.ShowModeless((HWND
)NULL
, true);
173 int ret
= g_Git
.Run(cmd
, &out
, CP_UTF8
);
175 sysProgressDlg
.Stop();
177 bool hasConflicts
= (out
.Find(_T("CONFLICT")) >= 0);
178 if (ret
&& !(ret
== 1 && hasConflicts
))
180 CMessageBox::Show(NULL
,CString(MAKEINTRESOURCE(IDS_PROC_STASHPOPFAILED
)) + _T("\n") + out
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
185 message
.LoadString(IDS_PROC_STASHPOPSUCCESS
);
187 message
.LoadString(IDS_PROC_STASHPOPFAILEDCONFLICTS
);
190 if(CMessageBox::Show(NULL
,CString(message
+ _T("\n") + CString(MAKEINTRESOURCE(IDS_SEECHANGES
)))
191 ,_T("TortoiseGit"),MB_YESNO
|MB_ICONINFORMATION
) == IDYES
)
194 dlg
.m_pathList
.AddPath(CTGitPath());
201 CMessageBox::Show(NULL
, message
,_T("TortoiseGit"), MB_OK
| MB_ICONINFORMATION
);
208 BOOL
CAppUtils::StartExtMerge(
209 const CTGitPath
& basefile
, const CTGitPath
& theirfile
, const CTGitPath
& yourfile
, const CTGitPath
& mergedfile
,
210 const CString
& basename
, const CString
& theirname
, const CString
& yourname
, const CString
& mergedname
, bool bReadOnly
,
214 CRegString regCom
= CRegString(_T("Software\\TortoiseGit\\Merge"));
215 CString ext
= mergedfile
.GetFileExtension();
216 CString com
= regCom
;
217 bool bInternal
= false;
221 // is there an extension specific merge tool?
222 CRegString
mergetool(_T("Software\\TortoiseGit\\MergeTools\\") + ext
.MakeLower());
223 if (!CString(mergetool
).IsEmpty())
228 // is there a filename specific merge tool?
229 CRegString
mergetool(_T("Software\\TortoiseGit\\MergeTools\\.") + mergedfile
.GetFilename().MakeLower());
230 if (!CString(mergetool
).IsEmpty())
235 if (com
.IsEmpty()||(com
.Left(1).Compare(_T("#"))==0))
237 // Maybe we should use TortoiseIDiff?
238 if ((ext
== _T(".jpg")) || (ext
== _T(".jpeg")) ||
239 (ext
== _T(".bmp")) || (ext
== _T(".gif")) ||
240 (ext
== _T(".png")) || (ext
== _T(".ico")) ||
241 (ext
== _T(".tif")) || (ext
== _T(".tiff")) ||
242 (ext
== _T(".dib")) || (ext
== _T(".emf")) ||
245 com
= CPathUtils::GetAppDirectory() + _T("TortoiseGitIDiff.exe");
246 com
= _T("\"") + com
+ _T("\"");
247 com
= com
+ _T(" /base:%base /theirs:%theirs /mine:%mine /result:%merged");
248 com
= com
+ _T(" /basetitle:%bname /theirstitle:%tname /minetitle:%yname");
252 // use TortoiseGitMerge
254 com
= CPathUtils::GetAppDirectory() + _T("TortoiseGitMerge.exe");
255 com
= _T("\"") + com
+ _T("\"");
256 com
= com
+ _T(" /base:%base /theirs:%theirs /mine:%mine /merged:%merged");
257 com
= com
+ _T(" /basename:%bname /theirsname:%tname /minename:%yname /mergedname:%mname");
258 com
+= _T(" /saverequired");
262 s
.Format(L
" /resolvemsghwnd:%I64d", (__int64
)resolveMsgHwnd
);
266 if (!g_sGroupingUUID
.IsEmpty())
268 com
+= L
" /groupuuid:\"";
269 com
+= g_sGroupingUUID
;
273 // check if the params are set. If not, just add the files to the command line
274 if ((com
.Find(_T("%merged"))<0)&&(com
.Find(_T("%base"))<0)&&(com
.Find(_T("%theirs"))<0)&&(com
.Find(_T("%mine"))<0))
276 com
+= _T(" \"")+basefile
.GetWinPathString()+_T("\"");
277 com
+= _T(" \"")+theirfile
.GetWinPathString()+_T("\"");
278 com
+= _T(" \"")+yourfile
.GetWinPathString()+_T("\"");
279 com
+= _T(" \"")+mergedfile
.GetWinPathString()+_T("\"");
281 if (basefile
.IsEmpty())
283 com
.Replace(_T("/base:%base"), _T(""));
284 com
.Replace(_T("%base"), _T(""));
287 com
.Replace(_T("%base"), _T("\"") + basefile
.GetWinPathString() + _T("\""));
288 if (theirfile
.IsEmpty())
290 com
.Replace(_T("/theirs:%theirs"), _T(""));
291 com
.Replace(_T("%theirs"), _T(""));
294 com
.Replace(_T("%theirs"), _T("\"") + theirfile
.GetWinPathString() + _T("\""));
295 if (yourfile
.IsEmpty())
297 com
.Replace(_T("/mine:%mine"), _T(""));
298 com
.Replace(_T("%mine"), _T(""));
301 com
.Replace(_T("%mine"), _T("\"") + yourfile
.GetWinPathString() + _T("\""));
302 if (mergedfile
.IsEmpty())
304 com
.Replace(_T("/merged:%merged"), _T(""));
305 com
.Replace(_T("%merged"), _T(""));
308 com
.Replace(_T("%merged"), _T("\"") + mergedfile
.GetWinPathString() + _T("\""));
309 if (basename
.IsEmpty())
311 if (basefile
.IsEmpty())
313 com
.Replace(_T("/basename:%bname"), _T(""));
314 com
.Replace(_T("%bname"), _T(""));
318 com
.Replace(_T("%bname"), _T("\"") + basefile
.GetUIFileOrDirectoryName() + _T("\""));
322 com
.Replace(_T("%bname"), _T("\"") + basename
+ _T("\""));
323 if (theirname
.IsEmpty())
325 if (theirfile
.IsEmpty())
327 com
.Replace(_T("/theirsname:%tname"), _T(""));
328 com
.Replace(_T("%tname"), _T(""));
332 com
.Replace(_T("%tname"), _T("\"") + theirfile
.GetUIFileOrDirectoryName() + _T("\""));
336 com
.Replace(_T("%tname"), _T("\"") + theirname
+ _T("\""));
337 if (yourname
.IsEmpty())
339 if (yourfile
.IsEmpty())
341 com
.Replace(_T("/minename:%yname"), _T(""));
342 com
.Replace(_T("%yname"), _T(""));
346 com
.Replace(_T("%yname"), _T("\"") + yourfile
.GetUIFileOrDirectoryName() + _T("\""));
350 com
.Replace(_T("%yname"), _T("\"") + yourname
+ _T("\""));
351 if (mergedname
.IsEmpty())
353 if (mergedfile
.IsEmpty())
355 com
.Replace(_T("/mergedname:%mname"), _T(""));
356 com
.Replace(_T("%mname"), _T(""));
360 com
.Replace(_T("%mname"), _T("\"") + mergedfile
.GetUIFileOrDirectoryName() + _T("\""));
364 com
.Replace(_T("%mname"), _T("\"") + mergedname
+ _T("\""));
366 if ((bReadOnly
)&&(bInternal
))
367 com
+= _T(" /readonly");
369 if(!LaunchApplication(com
, IDS_ERR_EXTMERGESTART
, false))
377 BOOL
CAppUtils::StartExtPatch(const CTGitPath
& patchfile
, const CTGitPath
& dir
, const CString
& sOriginalDescription
, const CString
& sPatchedDescription
, BOOL bReversed
, BOOL bWait
)
380 // use TortoiseGitMerge
381 viewer
= CPathUtils::GetAppDirectory();
382 viewer
+= _T("TortoiseGitMerge.exe");
384 viewer
= _T("\"") + viewer
+ _T("\"");
385 viewer
= viewer
+ _T(" /diff:\"") + patchfile
.GetWinPathString() + _T("\"");
386 viewer
= viewer
+ _T(" /patchpath:\"") + dir
.GetWinPathString() + _T("\"");
388 viewer
+= _T(" /reversedpatch");
389 if (!sOriginalDescription
.IsEmpty())
390 viewer
= viewer
+ _T(" /patchoriginal:\"") + sOriginalDescription
+ _T("\"");
391 if (!sPatchedDescription
.IsEmpty())
392 viewer
= viewer
+ _T(" /patchpatched:\"") + sPatchedDescription
+ _T("\"");
393 if (!g_sGroupingUUID
.IsEmpty())
395 viewer
+= L
" /groupuuid:\"";
396 viewer
+= g_sGroupingUUID
;
399 if(!LaunchApplication(viewer
, IDS_ERR_DIFFVIEWSTART
, !!bWait
))
406 CString
CAppUtils::PickDiffTool(const CTGitPath
& file1
, const CTGitPath
& file2
)
408 CString difftool
= CRegString(_T("Software\\TortoiseGit\\DiffTools\\.") + file2
.GetFilename().MakeLower());
409 if (!difftool
.IsEmpty())
411 difftool
= CRegString(_T("Software\\TortoiseGit\\DiffTools\\.") + file1
.GetFilename().MakeLower());
412 if (!difftool
.IsEmpty())
415 // Is there an extension specific diff tool?
416 CString ext
= file2
.GetFileExtension().MakeLower();
419 difftool
= CRegString(_T("Software\\TortoiseGit\\DiffTools\\") + ext
);
420 if (!difftool
.IsEmpty())
422 // Maybe we should use TortoiseIDiff?
423 if ((ext
== _T(".jpg")) || (ext
== _T(".jpeg")) ||
424 (ext
== _T(".bmp")) || (ext
== _T(".gif")) ||
425 (ext
== _T(".png")) || (ext
== _T(".ico")) ||
426 (ext
== _T(".dib")) || (ext
== _T(".emf")) ||
430 _T("\"") + CPathUtils::GetAppDirectory() + _T("TortoiseGitIDiff.exe") + _T("\"") +
431 _T(" /left:%base /right:%mine /lefttitle:%bname /righttitle:%yname") +
432 L
" /groupuuid:\"" + g_sGroupingUUID
+ L
"\"";
436 // Finally, pick a generic external diff tool
437 difftool
= CRegString(_T("Software\\TortoiseGit\\Diff"));
441 bool CAppUtils::StartExtDiff(
442 const CString
& file1
, const CString
& file2
,
443 const CString
& sName1
, const CString
& sName2
,
444 const CString
& originalFile1
, const CString
& originalFile2
,
445 const git_revnum_t
& hash1
, const git_revnum_t
& hash2
,
446 const DiffFlags
& flags
, int jumpToLine
)
450 CRegDWORD
blamediff(_T("Software\\TortoiseGit\\DiffBlamesWithTortoiseMerge"), FALSE
);
451 if (!flags
.bBlame
|| !(DWORD
)blamediff
)
453 viewer
= PickDiffTool(file1
, file2
);
454 // If registry entry for a diff program is commented out, use TortoiseGitMerge.
455 bool bCommentedOut
= viewer
.Left(1) == _T("#");
456 if (flags
.bAlternativeTool
)
458 // Invert external vs. internal diff tool selection.
460 viewer
.Delete(0); // uncomment
464 else if (bCommentedOut
)
468 bool bInternal
= viewer
.IsEmpty();
472 _T("\"") + CPathUtils::GetAppDirectory() + _T("TortoiseGitMerge.exe") + _T("\"") +
473 _T(" /base:%base /mine:%mine /basename:%bname /minename:%yname") +
474 _T(" /basereflectedname:%bpath /minereflectedname:%ypath");
475 if (!g_sGroupingUUID
.IsEmpty())
477 viewer
+= L
" /groupuuid:\"";
478 viewer
+= g_sGroupingUUID
;
482 viewer
+= _T(" /blame");
484 // check if the params are set. If not, just add the files to the command line
485 if ((viewer
.Find(_T("%base"))<0)&&(viewer
.Find(_T("%mine"))<0))
487 viewer
+= _T(" \"")+file1
+_T("\"");
488 viewer
+= _T(" \"")+file2
+_T("\"");
490 if (viewer
.Find(_T("%base")) >= 0)
492 viewer
.Replace(_T("%base"), _T("\"")+file1
+_T("\""));
494 if (viewer
.Find(_T("%mine")) >= 0)
496 viewer
.Replace(_T("%mine"), _T("\"")+file2
+_T("\""));
499 if (sName1
.IsEmpty())
500 viewer
.Replace(_T("%bname"), _T("\"") + file1
+ _T("\""));
502 viewer
.Replace(_T("%bname"), _T("\"") + sName1
+ _T("\""));
504 if (sName2
.IsEmpty())
505 viewer
.Replace(_T("%yname"), _T("\"") + file2
+ _T("\""));
507 viewer
.Replace(_T("%yname"), _T("\"") + sName2
+ _T("\""));
509 viewer
.Replace(_T("%bpath"), _T("\"") + originalFile1
+ _T("\""));
510 viewer
.Replace(_T("%ypath"), _T("\"") + originalFile2
+ _T("\""));
512 viewer
.Replace(_T("%brev"), _T("\"") + hash1
+ _T("\""));
513 viewer
.Replace(_T("%yrev"), _T("\"") + hash2
+ _T("\""));
515 if (flags
.bReadOnly
&& bInternal
)
516 viewer
+= _T(" /readonly");
521 temp
.Format(_T(" /line:%d"), jumpToLine
);
525 return LaunchApplication(viewer
, IDS_ERR_EXTDIFFSTART
, flags
.bWait
);
528 BOOL
CAppUtils::StartUnifiedDiffViewer(const CString
& patchfile
, const CString
& title
, BOOL bWait
)
531 CRegString v
= CRegString(_T("Software\\TortoiseGit\\DiffViewer"));
533 if (viewer
.IsEmpty() || (viewer
.Left(1).Compare(_T("#"))==0))
535 // use TortoiseGitUDiff
536 viewer
= CPathUtils::GetAppDirectory();
537 viewer
+= _T("TortoiseGitUDiff.exe");
538 // enquote the path to TortoiseGitUDiff
539 viewer
= _T("\"") + viewer
+ _T("\"");
541 viewer
= viewer
+ _T(" /patchfile:%1 /title:\"%title\"");
542 if (!g_sGroupingUUID
.IsEmpty())
544 viewer
+= L
" /groupuuid:\"";
545 viewer
+= g_sGroupingUUID
;
549 if (viewer
.Find(_T("%1"))>=0)
551 if (viewer
.Find(_T("\"%1\"")) >= 0)
552 viewer
.Replace(_T("%1"), patchfile
);
554 viewer
.Replace(_T("%1"), _T("\"") + patchfile
+ _T("\""));
557 viewer
+= _T(" \"") + patchfile
+ _T("\"");
558 if (viewer
.Find(_T("%title")) >= 0)
560 viewer
.Replace(_T("%title"), title
);
563 if(!LaunchApplication(viewer
, IDS_ERR_DIFFVIEWSTART
, !!bWait
))
570 BOOL
CAppUtils::StartTextViewer(CString file
)
573 CRegString txt
= CRegString(_T(".txt\\"), _T(""), FALSE
, HKEY_CLASSES_ROOT
);
575 viewer
= viewer
+ _T("\\Shell\\Open\\Command\\");
576 CRegString txtexe
= CRegString(viewer
, _T(""), FALSE
, HKEY_CLASSES_ROOT
);
579 DWORD len
= ExpandEnvironmentStrings(viewer
, NULL
, 0);
580 std::unique_ptr
<TCHAR
[]> buf(new TCHAR
[len
+ 1]);
581 ExpandEnvironmentStrings(viewer
, buf
.get(), len
);
583 len
= ExpandEnvironmentStrings(file
, NULL
, 0);
584 std::unique_ptr
<TCHAR
[]> buf2(new TCHAR
[len
+ 1]);
585 ExpandEnvironmentStrings(file
, buf2
.get(), len
);
587 file
= _T("\"")+file
+_T("\"");
588 if (viewer
.IsEmpty())
590 viewer
= _T("RUNDLL32 Shell32,OpenAs_RunDLL");
592 if (viewer
.Find(_T("\"%1\"")) >= 0)
594 viewer
.Replace(_T("\"%1\""), file
);
596 else if (viewer
.Find(_T("%1")) >= 0)
598 viewer
.Replace(_T("%1"), file
);
606 if(!LaunchApplication(viewer
, IDS_ERR_TEXTVIEWSTART
, false))
613 BOOL
CAppUtils::CheckForEmptyDiff(const CTGitPath
& sDiffPath
)
616 CAutoFile hFile
= ::CreateFile(sDiffPath
.GetWinPath(), GENERIC_READ
, FILE_SHARE_READ
| FILE_SHARE_WRITE
, NULL
, OPEN_EXISTING
, NULL
, NULL
);
619 length
= ::GetFileSize(hFile
, NULL
);
626 void CAppUtils::CreateFontForLogs(CFont
& fontToCreate
)
629 HDC hScreenDC
= ::GetDC(NULL
);
630 logFont
.lfHeight
= -MulDiv((DWORD
)CRegDWORD(_T("Software\\TortoiseGit\\LogFontSize"), 8), GetDeviceCaps(hScreenDC
, LOGPIXELSY
), 72);
631 ::ReleaseDC(NULL
, hScreenDC
);
633 logFont
.lfEscapement
= 0;
634 logFont
.lfOrientation
= 0;
635 logFont
.lfWeight
= FW_NORMAL
;
636 logFont
.lfItalic
= 0;
637 logFont
.lfUnderline
= 0;
638 logFont
.lfStrikeOut
= 0;
639 logFont
.lfCharSet
= DEFAULT_CHARSET
;
640 logFont
.lfOutPrecision
= OUT_DEFAULT_PRECIS
;
641 logFont
.lfClipPrecision
= CLIP_DEFAULT_PRECIS
;
642 logFont
.lfQuality
= DRAFT_QUALITY
;
643 logFont
.lfPitchAndFamily
= FF_DONTCARE
| FIXED_PITCH
;
644 _tcscpy_s(logFont
.lfFaceName
, 32, (LPCTSTR
)(CString
)CRegString(_T("Software\\TortoiseGit\\LogFontName"), _T("Courier New")));
645 VERIFY(fontToCreate
.CreateFontIndirect(&logFont
));
648 bool CAppUtils::LaunchPAgent(CString
*keyfile
,CString
* pRemote
)
662 cmd
.Format(_T("remote.%s.puttykeyfile"),remote
);
663 key
= g_Git
.GetConfigValue(cmd
);
671 CString proc
=CPathUtils::GetAppDirectory();
672 proc
+= _T("pageant.exe \"");
676 CString tempfile
= GetTempFile();
677 ::DeleteFile(tempfile
);
679 proc
+= _T(" -c \"");
680 proc
+= CPathUtils::GetAppDirectory();
681 proc
+= _T("tgittouch.exe\"");
686 CString appDir
= CPathUtils::GetAppDirectory();
687 bool b
= LaunchApplication(proc
, IDS_ERR_PAGEANT
, true, &appDir
);
692 while(!::PathFileExists(tempfile
))
697 break; //timeout 5 minutes
702 CMessageBox::Show(NULL
, IDS_ERR_PAEGENTTIMEOUT
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
704 ::DeleteFile(tempfile
);
707 bool CAppUtils::LaunchAlternativeEditor(const CString
& filename
, bool uac
)
709 CString editTool
= CRegString(_T("Software\\TortoiseGit\\AlternativeEditor"));
710 if (editTool
.IsEmpty() || (editTool
.Left(1).Compare(_T("#"))==0)) {
711 editTool
= CPathUtils::GetAppDirectory() + _T("notepad2.exe");
715 sCmd
.Format(_T("\"%s\" \"%s\""), editTool
, filename
);
717 LaunchApplication(sCmd
, NULL
, false, NULL
, uac
);
720 bool CAppUtils::LaunchRemoteSetting()
722 CTGitPath
path(g_Git
.m_CurrentDir
);
723 CSettings
dlg(IDS_PROC_SETTINGS_TITLE
, &path
);
724 dlg
.SetTreeViewMode(TRUE
, TRUE
, TRUE
);
725 dlg
.SetTreeWidth(220);
726 dlg
.m_DefaultPage
= _T("gitremote");
733 * Launch the external blame viewer
735 bool CAppUtils::LaunchTortoiseBlame(const CString
& sBlameFile
,CString Rev
,const CString
& sParams
)
737 CString viewer
= _T("\"") + CPathUtils::GetAppDirectory();
738 viewer
+= _T("TortoiseGitBlame.exe");
739 viewer
+= _T("\" \"") + sBlameFile
+ _T("\"");
740 //viewer += _T(" \"") + sLogFile + _T("\"");
741 //viewer += _T(" \"") + sOriginalFile + _T("\"");
742 if(!Rev
.IsEmpty() && Rev
!= GIT_REV_ZERO
)
743 viewer
+= CString(_T(" /rev:"))+Rev
;
744 if (!g_sGroupingUUID
.IsEmpty())
746 viewer
+= L
" /groupuuid:\"";
747 viewer
+= g_sGroupingUUID
;
750 viewer
+= _T(" ")+sParams
;
752 return LaunchApplication(viewer
, IDS_ERR_TGITBLAME
, false);
755 bool CAppUtils::FormatTextInRichEditControl(CWnd
* pWnd
)
760 bool bStyled
= false;
761 pWnd
->GetWindowText(sText
);
762 // the rich edit control doesn't count the CR char!
763 // to be exact: CRLF is treated as one char.
764 sText
.Remove(_T('\r'));
766 // style each line separately
771 nNewlinePos
= sText
.Find('\n', offset
);
772 CString sLine
= nNewlinePos
>= 0 ? sText
.Mid(offset
, nNewlinePos
- offset
) : sText
.Mid(offset
);
776 while (FindStyleChars(sLine
, '*', start
, end
))
778 CHARRANGE range
= {(LONG
)start
+offset
, (LONG
)end
+offset
};
779 pWnd
->SendMessage(EM_EXSETSEL
, NULL
, (LPARAM
)&range
);
780 SetCharFormat(pWnd
, CFM_BOLD
, CFE_BOLD
);
786 while (FindStyleChars(sLine
, '^', start
, end
))
788 CHARRANGE range
= {(LONG
)start
+offset
, (LONG
)end
+offset
};
789 pWnd
->SendMessage(EM_EXSETSEL
, NULL
, (LPARAM
)&range
);
790 SetCharFormat(pWnd
, CFM_ITALIC
, CFE_ITALIC
);
796 while (FindStyleChars(sLine
, '_', start
, end
))
798 CHARRANGE range
= {(LONG
)start
+offset
, (LONG
)end
+offset
};
799 pWnd
->SendMessage(EM_EXSETSEL
, NULL
, (LPARAM
)&range
);
800 SetCharFormat(pWnd
, CFM_UNDERLINE
, CFE_UNDERLINE
);
804 offset
= nNewlinePos
+1;
805 } while(nNewlinePos
>=0);
809 bool CAppUtils::FindStyleChars(const CString
& sText
, TCHAR stylechar
, int& start
, int& end
)
812 int last
= sText
.GetLength() - 1;
813 bool bFoundMarker
= false;
814 TCHAR c
= i
== 0 ? _T('\0') : sText
[i
- 1];
815 TCHAR nextChar
= i
>= last
? _T('\0') : sText
[i
+ 1];
817 // find a starting marker
822 nextChar
= sText
[i
+ 1];
824 // IsCharAlphaNumeric can be somewhat expensive.
825 // Long lines of "*****" or "----" will be pre-empted efficiently
826 // by the (c != nextChar) condition.
828 if ((c
== stylechar
) && (c
!= nextChar
))
830 if (IsCharAlphaNumeric(nextChar
) && !IsCharAlphaNumeric(prevChar
))
842 // find ending marker
845 bFoundMarker
= false;
852 if ((i
== last
) || (!IsCharAlphaNumeric(sText
[i
+ 1]) && IsCharAlphaNumeric(prevChar
)))
865 bool CAppUtils::StartShowUnifiedDiff(HWND hWnd
, const CTGitPath
& url1
, const git_revnum_t
& rev1
,
866 const CTGitPath
& /*url2*/, const git_revnum_t
& rev2
,
867 //const GitRev& peg /* = GitRev */, const GitRev& headpeg /* = GitRev */,
868 bool /*bAlternateDiff*/ /* = false */, bool /*bIgnoreAncestry*/ /* = false */,
869 bool /* blame = false */,
874 if (GetMsysgitVersion() > 0x01080100)
875 diffContext
= g_Git
.GetConfigValueInt32(_T("diff.context"), -1);
876 CString tempfile
=GetTempFile();
877 if (g_Git
.GetUnifiedDiff(url1
, rev1
, rev2
, tempfile
, bMerge
, bCombine
, diffContext
))
879 CMessageBox::Show(hWnd
, g_Git
.GetGitLastErr(_T("Could not get unified diff."), CGit::GIT_CMD_DIFF
), _T("TortoiseGit"), MB_OK
);
882 CAppUtils::StartUnifiedDiffViewer(tempfile
, rev1
+ _T(":") + rev2
);
886 sCmd
.Format(_T("%s /command:showcompare /unified"),
887 (LPCTSTR
)(CPathUtils::GetAppDirectory()+_T("TortoiseGitProc.exe")));
888 sCmd
+= _T(" /url1:\"") + url1
.GetGitPathString() + _T("\"");
890 sCmd
+= _T(" /revision1:") + rev1
.ToString();
891 sCmd
+= _T(" /url2:\"") + url2
.GetGitPathString() + _T("\"");
893 sCmd
+= _T(" /revision2:") + rev2
.ToString();
895 sCmd
+= _T(" /pegrevision:") + peg
.ToString();
896 if (headpeg
.IsValid())
897 sCmd
+= _T(" /headpegrevision:") + headpeg
.ToString();
900 sCmd
+= _T(" /alternatediff");
903 sCmd
+= _T(" /ignoreancestry");
907 sCmd
+= _T(" /hwnd:");
909 _stprintf_s(buf
, 30, _T("%p"), (void*)hWnd
);
913 return CAppUtils::LaunchApplication(sCmd
, NULL
, false);
918 bool CAppUtils::SetupDiffScripts(bool force
, const CString
& type
)
920 CString scriptsdir
= CPathUtils::GetAppParentDirectory();
921 scriptsdir
+= _T("Diff-Scripts");
922 CSimpleFileFind
files(scriptsdir
);
923 while (files
.FindNextFileNoDirectories())
925 CString file
= files
.GetFilePath();
926 CString filename
= files
.GetFileName();
927 CString ext
= file
.Mid(file
.ReverseFind('-') + 1);
928 ext
= _T(".") + ext
.Left(ext
.ReverseFind('.'));
929 std::set
<CString
> extensions
;
930 extensions
.insert(ext
);
932 if (file
.Right(3).CompareNoCase(_T("vbs"))==0)
934 kind
= _T(" //E:vbscript");
936 if (file
.Right(2).CompareNoCase(_T("js"))==0)
938 kind
= _T(" //E:javascript");
940 // open the file, read the first line and find possible extensions
941 // this script can handle
944 CStdioFile
f(file
, CFile::modeRead
| CFile::shareDenyNone
);
946 if (f
.ReadString(extline
))
948 if ((extline
.GetLength() > 15 ) &&
949 ((extline
.Left(15).Compare(_T("// extensions: ")) == 0) ||
950 (extline
.Left(14).Compare(_T("' extensions: ")) == 0)))
952 if (extline
[0] == '/')
953 extline
= extline
.Mid(15);
955 extline
= extline
.Mid(14);
958 sToken
= extline
.Tokenize(_T(";"), curPos
);
959 while (!sToken
.IsEmpty())
961 if (!sToken
.IsEmpty())
963 if (sToken
[0] != '.')
964 sToken
= _T(".") + sToken
;
965 extensions
.insert(sToken
);
967 sToken
= extline
.Tokenize(_T(";"), curPos
);
973 catch (CFileException
* e
)
978 for (std::set
<CString
>::const_iterator it
= extensions
.begin(); it
!= extensions
.end(); ++it
)
980 if (type
.IsEmpty() || (type
.Compare(_T("Diff")) == 0))
982 if (filename
.Left(5).CompareNoCase(_T("diff-")) == 0)
984 CRegString diffreg
= CRegString(_T("Software\\TortoiseGit\\DiffTools\\") + *it
);
985 CString diffregstring
= diffreg
;
986 if (force
|| (diffregstring
.IsEmpty()) || (diffregstring
.Find(filename
) >= 0))
987 diffreg
= _T("wscript.exe \"") + file
+ _T("\" %base %mine") + kind
;
990 if (type
.IsEmpty() || (type
.Compare(_T("Merge"))==0))
992 if (filename
.Left(6).CompareNoCase(_T("merge-"))==0)
994 CRegString diffreg
= CRegString(_T("Software\\TortoiseGit\\MergeTools\\") + *it
);
995 CString diffregstring
= diffreg
;
996 if (force
|| (diffregstring
.IsEmpty()) || (diffregstring
.Find(filename
) >= 0))
997 diffreg
= _T("wscript.exe \"") + file
+ _T("\" %merged %theirs %mine %base") + kind
;
1006 bool CAppUtils::Export(CString
*BashHash
, const CTGitPath
*orgPath
)
1008 // ask from where the export has to be done
1011 dlg
.m_Revision
=*BashHash
;
1014 if (PathIsRelative(orgPath
->GetWinPath()))
1015 dlg
.m_orgPath
= g_Git
.CombinePath(orgPath
);
1017 dlg
.m_orgPath
= *orgPath
;
1020 if (dlg
.DoModal() == IDOK
)
1023 cmd
.Format(_T("git.exe archive --output=\"%s\" --format=zip --verbose %s --"),
1024 dlg
.m_strFile
, g_Git
.FixBranchName(dlg
.m_VersionName
));
1029 if (!dlg
.m_bWholeProject
&& !dlg
.m_orgPath
.IsEmpty() && PathIsDirectory(dlg
.m_orgPath
.GetWinPathString()))
1031 git
.m_CurrentDir
= dlg
.m_orgPath
.GetWinPathString();
1034 return (pro
.DoModal() == IDOK
);
1039 bool CAppUtils::CreateBranchTag(bool IsTag
,CString
*CommitHash
, bool switch_new_brach
)
1041 CCreateBranchTagDlg dlg
;
1043 dlg
.m_bSwitch
=switch_new_brach
;
1046 dlg
.m_initialRefName
= *CommitHash
;
1048 if(dlg
.DoModal()==IDOK
)
1053 if(dlg
.m_bTrack
== TRUE
)
1054 track
=_T(" --track ");
1055 else if(dlg
.m_bTrack
== FALSE
)
1056 track
=_T(" --no-track");
1067 cmd
.Format(_T("git.exe tag %s %s %s %s"),
1070 dlg
.m_BranchTagName
,
1071 g_Git
.FixBranchName(dlg
.m_VersionName
)
1074 CString tempfile
=::GetTempFile();
1075 if(!dlg
.m_Message
.Trim().IsEmpty())
1077 CAppUtils::SaveCommitUnicodeFile(tempfile
,dlg
.m_Message
);
1078 cmd
+= _T(" -F ")+tempfile
;
1083 cmd
.Format(_T("git.exe branch %s %s %s %s"),
1086 dlg
.m_BranchTagName
,
1087 g_Git
.FixBranchName(dlg
.m_VersionName
)
1091 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
1093 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
1096 if( !IsTag
&& dlg
.m_bSwitch
)
1098 // it is a new branch and the user has requested to switch to it
1099 PerformSwitch(dlg
.m_BranchTagName
);
1107 bool CAppUtils::Switch(CString initialRefName
)
1110 if(!initialRefName
.IsEmpty())
1111 dlg
.m_initialRefName
= initialRefName
;
1113 if (dlg
.DoModal() == IDOK
)
1117 branch
= dlg
.m_NewBranch
;
1119 // if refs/heads/ is not stripped, checkout will detach HEAD
1120 // checkout prefers branches on name clashes (with tags)
1121 if (dlg
.m_VersionName
.Left(11) ==_T("refs/heads/") && dlg
.m_bBranchOverride
!= TRUE
)
1122 dlg
.m_VersionName
= dlg
.m_VersionName
.Mid(11);
1124 return PerformSwitch(dlg
.m_VersionName
, dlg
.m_bForce
== TRUE
, branch
, dlg
.m_bBranchOverride
== TRUE
, dlg
.m_bTrack
, dlg
.m_bMerge
== TRUE
);
1129 bool CAppUtils::PerformSwitch(CString ref
, bool bForce
/* false */, CString sNewBranch
/* CString() */, bool bBranchOverride
/* false */, BOOL bTrack
/* 2 */, bool bMerge
/* false */)
1137 if(!sNewBranch
.IsEmpty()){
1138 if (bBranchOverride
)
1140 branch
.Format(_T("-B %s"), sNewBranch
);
1144 branch
.Format(_T("-b %s"), sNewBranch
);
1147 track
= _T("--track");
1148 else if (bTrack
== FALSE
)
1149 track
= _T("--no-track");
1154 merge
= _T("--merge");
1156 cmd
.Format(_T("git.exe checkout %s %s %s %s %s --"),
1161 g_Git
.FixBranchName(ref
));
1165 CProgressDlg progress
;
1166 progress
.m_GitCmd
= cmd
;
1168 INT_PTR idPull
= -1;
1169 INT_PTR idSubmoduleUpdate
= -1;
1170 INT_PTR idMerge
= -1;
1172 CTGitPath gitPath
= g_Git
.m_CurrentDir
;
1173 if (gitPath
.HasSubmodules())
1174 idSubmoduleUpdate
= progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_PROC_SUBMODULESUPDATE
)));
1175 CString currentBranch
;
1176 bool hasBranch
= CGit::GetCurrentBranchFromFile(g_Git
.m_CurrentDir
, currentBranch
) == 0;
1178 idMerge
= progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_MENUMERGE
)));
1180 progress
.m_PostCmdCallback
= [&] ()
1182 if (!progress
.m_GitStatus
)
1185 if (!CGit::GetCurrentBranchFromFile(g_Git
.m_CurrentDir
, newBranch
))
1186 idPull
= progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_MENUPULL
)));
1190 progress
.m_PostCmdList
.RemoveAll();
1191 progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_MSGBOX_RETRY
)));
1192 progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_SWITCH_WITH_MERGE
)));
1196 INT_PTR ret
= progress
.DoModal();
1197 if (progress
.m_GitStatus
== 0)
1199 if (idSubmoduleUpdate
>= 0 && ret
== IDC_PROGRESS_BUTTON1
+ idSubmoduleUpdate
)
1202 sCmd
.Format(_T("/command:subupdate /bkpath:\"%s\""), g_Git
.m_CurrentDir
);
1204 RunTortoiseGitProc(sCmd
);
1207 else if (ret
== IDC_PROGRESS_BUTTON1
+ idPull
)
1212 else if (ret
== IDC_PROGRESS_BUTTON1
+ idMerge
)
1214 Merge(¤tBranch
);
1217 else if (ret
== IDOK
)
1220 else if (ret
== IDC_PROGRESS_BUTTON1
)
1222 else if (ret
== IDC_PROGRESS_BUTTON1
+ 1)
1224 merge
= _T("--merge");
1225 cmd
.Format(_T("git.exe checkout %s %s %s %s %s --"),
1226 force
, track
, merge
, branch
, g_Git
.FixBranchName(ref
));
1234 class CIgnoreFile
: public CStdioFile
1237 STRING_VECTOR m_Items
;
1240 virtual BOOL
ReadString(CString
& rString
)
1242 if (GetPosition() == 0)
1244 unsigned char utf8bom
[] = { 0xEF, 0xBB, 0xBF };
1245 char buf
[3] = { 0, 0, 0 };
1247 if (memcpy(buf
, utf8bom
, sizeof(utf8bom
)))
1254 char lastChar
= '\0';
1255 for (char c
= '\0'; Read(&c
, 1) == 1; lastChar
= c
)
1261 m_eol
= lastChar
== '\r' ? _T("\r\n") : _T("\n");
1269 rString
= CUnicodeUtils::GetUnicode(strA
);
1280 bool CAppUtils::OpenIgnoreFile(CIgnoreFile
&file
, const CString
& filename
)
1283 if (!file
.Open(filename
, CFile::modeCreate
| CFile::modeReadWrite
| CFile::modeNoTruncate
| CFile::typeBinary
))
1285 CMessageBox::Show(NULL
, filename
+ _T(" Open Failure"), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
1289 if (file
.GetLength() > 0)
1292 while (file
.ReadString(fileText
))
1293 file
.m_Items
.push_back(fileText
);
1294 file
.Seek(file
.GetLength() - 1, 0);
1295 char lastchar
[1] = { 0 };
1296 file
.Read(lastchar
, 1);
1298 if (lastchar
[0] != '\n')
1300 CStringA eol
= CStringA(file
.m_eol
.IsEmpty() ? _T("\n") : file
.m_eol
);
1301 file
.Write(eol
, eol
.GetLength());
1310 bool CAppUtils::IgnoreFile(CTGitPathList
&path
,bool IsMask
)
1312 CIgnoreDlg ignoreDlg
;
1313 if (ignoreDlg
.DoModal() == IDOK
)
1316 ignorefile
= g_Git
.m_CurrentDir
+ _T("\\");
1318 switch (ignoreDlg
.m_IgnoreFile
)
1321 ignorefile
+= _T(".gitignore");
1324 g_GitAdminDir
.GetAdminDirPath(g_Git
.m_CurrentDir
, ignorefile
);
1325 ignorefile
+= _T("info/exclude");
1332 if (ignoreDlg
.m_IgnoreFile
!= 1 && !OpenIgnoreFile(file
, ignorefile
))
1335 for (int i
= 0; i
< path
.GetCount(); ++i
)
1337 if (ignoreDlg
.m_IgnoreFile
== 1)
1339 ignorefile
= g_Git
.CombinePath(path
[i
].GetContainingDirectory()) + _T("\\.gitignore");
1340 if (!OpenIgnoreFile(file
, ignorefile
))
1344 CString ignorePattern
;
1345 if (ignoreDlg
.m_IgnoreType
== 0)
1347 if (ignoreDlg
.m_IgnoreFile
!= 1 && !path
[i
].GetContainingDirectory().GetGitPathString().IsEmpty())
1348 ignorePattern
+= _T("/") + path
[i
].GetContainingDirectory().GetGitPathString();
1350 ignorePattern
+= _T("/");
1354 ignorePattern
+= _T("*") + path
[i
].GetFileExtension();
1358 ignorePattern
+= path
[i
].GetFileOrDirectoryName();
1361 // escape [ and ] so that files get ignored correctly
1362 ignorePattern
.Replace(_T("["), _T("\\["));
1363 ignorePattern
.Replace(_T("]"), _T("\\]"));
1366 for (size_t j
= 0; j
< file
.m_Items
.size(); ++j
)
1368 if (file
.m_Items
[j
] == ignorePattern
)
1376 file
.m_Items
.push_back(ignorePattern
);
1377 ignorePattern
+= file
.m_eol
.IsEmpty() ? _T("\n") : file
.m_eol
;
1378 CStringA ignorePatternA
= CUnicodeUtils::GetUTF8(ignorePattern
);
1379 file
.Write(ignorePatternA
, ignorePatternA
.GetLength());
1382 if (ignoreDlg
.m_IgnoreFile
== 1)
1386 if (ignoreDlg
.m_IgnoreFile
!= 1)
1401 bool CAppUtils::GitReset(CString
*CommitHash
,int type
)
1404 dlg
.m_ResetType
=type
;
1405 dlg
.m_ResetToVersion
=*CommitHash
;
1406 dlg
.m_initialRefName
= *CommitHash
;
1407 if (dlg
.DoModal() == IDOK
)
1411 switch(dlg
.m_ResetType
)
1423 dlg
.m_ResetType
= 1;
1427 cmd
.Format(_T("git.exe reset %s %s --"),type
, dlg
.m_ResetToVersion
);
1431 CProgressDlg progress
;
1432 progress
.m_GitCmd
=cmd
;
1434 CTGitPath gitPath
= g_Git
.m_CurrentDir
;
1435 if (gitPath
.HasSubmodules() && dlg
.m_ResetType
== 2)
1436 progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_PROC_SUBMODULESUPDATE
)));
1438 progress
.m_PostFailCmdList
.Add(CString(MAKEINTRESOURCE(IDS_MSGBOX_RETRY
)));
1441 if (g_Git
.UsingLibGit2(CGit::GIT_CMD_RESET
))
1443 CGitProgressDlg gitdlg
;
1444 gitdlg
.SetCommand(CGitProgressList::GitProgress_Reset
);
1445 gitdlg
.SetRevision(dlg
.m_ResetToVersion
);
1446 gitdlg
.SetResetType(dlg
.m_ResetType
);
1447 ret
= gitdlg
.DoModal();
1450 ret
= progress
.DoModal();
1452 if (progress
.m_GitStatus
== 0 && gitPath
.HasSubmodules() && dlg
.m_ResetType
== 2 && ret
== IDC_PROGRESS_BUTTON1
)
1455 sCmd
.Format(_T("/command:subupdate /bkpath:\"%s\""), g_Git
.m_CurrentDir
);
1457 RunTortoiseGitProc(sCmd
);
1460 else if (progress
.m_GitStatus
!= 0 && ret
== IDC_PROGRESS_BUTTON1
)
1462 else if (ret
== IDOK
)
1471 void CAppUtils::DescribeConflictFile(bool mode
, bool base
,CString
&descript
)
1475 descript
= CString(MAKEINTRESOURCE(IDS_SVNACTION_DELETE
));
1480 descript
= CString(MAKEINTRESOURCE(IDS_SVNACTION_MODIFIED
));
1483 descript
= CString(MAKEINTRESOURCE(IDS_PROC_CREATED
));
1487 void CAppUtils::RemoveTempMergeFile(CTGitPath
&path
)
1489 ::DeleteFile(CAppUtils::GetMergeTempFile(_T("LOCAL"), path
));
1490 ::DeleteFile(CAppUtils::GetMergeTempFile(_T("REMOTE"), path
));
1491 ::DeleteFile(CAppUtils::GetMergeTempFile(_T("BASE"), path
));
1493 CString
CAppUtils::GetMergeTempFile(CString type
,CTGitPath
&merge
)
1496 file
= g_Git
.CombinePath(merge
.GetWinPathString() + _T(".") + type
+ merge
.GetFileExtension());
1501 bool ParseHashesFromLsFile(BYTE_VECTOR
&out
, CString
&hash1
, CString
&hash2
, CString
&hash3
)
1503 unsigned int pos
= 0;
1507 while (pos
>= 0 && pos
< out
.size())
1511 g_Git
.StringAppend(&one
, &out
[pos
], CP_UTF8
);
1513 one
.Tokenize(_T("\t"), tabstart
);
1516 part
= one
.Tokenize(_T(" "), tabstart
); //Tag
1517 part
= one
.Tokenize(_T(" "), tabstart
); //Mode
1518 part
= one
.Tokenize(_T(" "), tabstart
); //Hash
1519 CString hash
= part
;
1520 part
= one
.Tokenize(_T("\t"), tabstart
); //Stage
1521 int stage
= _ttol(part
);
1524 else if (stage
== 2)
1526 else if (stage
== 3)
1532 pos
= out
.findNextString(pos
);
1538 bool CAppUtils::ConflictEdit(CTGitPath
& path
, bool /*bAlternativeTool = false*/, bool revertTheirMy
/*= false*/, HWND resolveMsgHwnd
/*= nullptr*/)
1542 CTGitPath merge
=path
;
1543 CTGitPath directory
= merge
.GetDirectory();
1545 // we have the conflicted file (%merged)
1546 // now look for the other required files
1548 //stat.GetStatus(merge);
1549 //if (stat.status == NULL)
1555 cmd
.Format(_T("git.exe ls-files -u -t -z -- \"%s\""),merge
.GetGitPathString());
1557 if (g_Git
.Run(cmd
, &vector
))
1562 if (merge
.IsDirectory())
1564 CString baseHash
, localHash
, remoteHash
;
1565 if (!ParseHashesFromLsFile(vector
, baseHash
, localHash
, remoteHash
))
1569 msg
.Format(_T("BASE: %s\nLOCAL: %s\nREMOTE: %s"), baseHash
, localHash
, remoteHash
);
1570 CMessageBox::Show(NULL
, msg
, _T("TortoiseGit"), MB_OK
);
1575 if (list
.ParserFromLsFile(vector
))
1577 CMessageBox::Show(NULL
, _T("Parse ls-files failed!"), _T("TortoiseGit"), MB_OK
);
1590 mine
.SetFromGit(GetMergeTempFile(_T("REMOTE"), merge
));
1591 theirs
.SetFromGit(GetMergeTempFile(_T("LOCAL"), merge
));
1595 mine
.SetFromGit(GetMergeTempFile(_T("LOCAL"), merge
));
1596 theirs
.SetFromGit(GetMergeTempFile(_T("REMOTE"), merge
));
1598 base
.SetFromGit(GetMergeTempFile(_T("BASE"),merge
));
1602 //format=_T("git.exe cat-file blob \":%d:%s\"");
1603 format
= _T("git.exe checkout-index --temp --stage=%d -- \"%s\"");
1605 //create a empty file, incase stage is not three
1606 tempfile
.Open(mine
.GetWinPathString(),CFile::modeCreate
|CFile::modeReadWrite
);
1608 tempfile
.Open(theirs
.GetWinPathString(),CFile::modeCreate
|CFile::modeReadWrite
);
1610 tempfile
.Open(base
.GetWinPathString(),CFile::modeCreate
|CFile::modeReadWrite
);
1613 bool b_base
=false, b_local
=false, b_remote
=false;
1615 for (int i
= 0; i
< list
.GetCount(); ++i
)
1622 if( list
[i
].m_Stage
== 1)
1624 cmd
.Format(format
, list
[i
].m_Stage
, list
[i
].GetGitPathString());
1626 outfile
= base
.GetWinPathString();
1629 if( list
[i
].m_Stage
== 2 )
1631 cmd
.Format(format
, list
[i
].m_Stage
, list
[i
].GetGitPathString());
1633 outfile
= mine
.GetWinPathString();
1636 if( list
[i
].m_Stage
== 3 )
1638 cmd
.Format(format
, list
[i
].m_Stage
, list
[i
].GetGitPathString());
1640 outfile
= theirs
.GetWinPathString();
1642 CString output
, err
;
1643 if(!outfile
.IsEmpty())
1644 if (!g_Git
.Run(cmd
, &output
, &err
, CP_UTF8
))
1648 file
= output
.Tokenize(_T("\t"), start
);
1649 ::MoveFileEx(file
,outfile
,MOVEFILE_REPLACE_EXISTING
|MOVEFILE_COPY_ALLOWED
);
1653 CMessageBox::Show(NULL
, output
+ L
"\n" + err
, _T("TortoiseGit"), MB_OK
|MB_ICONERROR
);
1657 if(b_local
&& b_remote
)
1659 merge
.SetFromWin(g_Git
.CombinePath(merge
));
1661 bRet
= !!CAppUtils::StartExtMerge(base
, mine
, theirs
, merge
, _T("BASE"), _T("REMOTE"), _T("LOCAL"), CString(), false, resolveMsgHwnd
);
1663 bRet
= !!CAppUtils::StartExtMerge(base
, theirs
, mine
, merge
, _T("BASE"), _T("REMOTE"), _T("LOCAL"), CString(), false, resolveMsgHwnd
);
1668 ::DeleteFile(mine
.GetWinPathString());
1669 ::DeleteFile(theirs
.GetWinPathString());
1670 ::DeleteFile(base
.GetWinPathString());
1672 CDeleteConflictDlg dlg
;
1673 DescribeConflictFile(b_local
, b_base
,dlg
.m_LocalStatus
);
1674 DescribeConflictFile(b_remote
,b_base
,dlg
.m_RemoteStatus
);
1675 CGitHash localHash
, remoteHash
;
1676 if (!g_Git
.GetHash(localHash
, _T("HEAD")))
1677 dlg
.m_LocalHash
= localHash
.ToString();
1678 if (!g_Git
.GetHash(remoteHash
, _T("MERGE_HEAD")))
1679 dlg
.m_RemoteHash
= remoteHash
.ToString();
1680 else if (!g_Git
.GetHash(remoteHash
, _T("rebase-apply/original-commit")))
1681 dlg
.m_RemoteHash
= remoteHash
.ToString();
1682 else if (!g_Git
.GetHash(remoteHash
, _T("CHERRY_PICK_HEAD")))
1683 dlg
.m_RemoteHash
= remoteHash
.ToString();
1684 else if (!g_Git
.GetHash(remoteHash
, _T("REVERT_HEAD")))
1685 dlg
.m_RemoteHash
= remoteHash
.ToString();
1686 dlg
.m_bShowModifiedButton
=b_base
;
1687 dlg
.m_File
=merge
.GetGitPathString();
1688 if(dlg
.DoModal() == IDOK
)
1693 cmd
.Format(_T("git.exe rm -- \"%s\""),merge
.GetGitPathString());
1696 cmd
.Format(_T("git.exe add -- \"%s\""),merge
.GetGitPathString());
1698 if (g_Git
.Run(cmd
, &out
, CP_UTF8
))
1700 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
1710 CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool
),
1711 base
, theirs
, mine
, merge
);
1714 if (stat
.status
->text_status
== svn_wc_status_conflicted
)
1716 // we have a text conflict, use our merge tool to resolve the conflict
1718 CTSVNPath
theirs(directory
);
1719 CTSVNPath
mine(directory
);
1720 CTSVNPath
base(directory
);
1721 bool bConflictData
= false;
1723 if ((stat
.status
->entry
)&&(stat
.status
->entry
->conflict_new
))
1725 theirs
.AppendPathString(CUnicodeUtils::GetUnicode(stat
.status
->entry
->conflict_new
));
1726 bConflictData
= true;
1728 if ((stat
.status
->entry
)&&(stat
.status
->entry
->conflict_old
))
1730 base
.AppendPathString(CUnicodeUtils::GetUnicode(stat
.status
->entry
->conflict_old
));
1731 bConflictData
= true;
1733 if ((stat
.status
->entry
)&&(stat
.status
->entry
->conflict_wrk
))
1735 mine
.AppendPathString(CUnicodeUtils::GetUnicode(stat
.status
->entry
->conflict_wrk
));
1736 bConflictData
= true;
1743 bRet
= !!CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool
),
1744 base
, theirs
, mine
, merge
);
1747 if (stat
.status
->prop_status
== svn_wc_status_conflicted
)
1749 // we have a property conflict
1750 CTSVNPath
prej(directory
);
1751 if ((stat
.status
->entry
)&&(stat
.status
->entry
->prejfile
))
1753 prej
.AppendPathString(CUnicodeUtils::GetUnicode(stat
.status
->entry
->prejfile
));
1754 // there's a problem: the prej file contains a _description_ of the conflict, and
1755 // that description string might be translated. That means we have no way of parsing
1756 // the file to find out the conflicting values.
1757 // The only thing we can do: show a dialog with the conflict description, then
1758 // let the user either accept the existing property or open the property edit dialog
1759 // to manually change the properties and values. And a button to mark the conflict as
1761 CEditPropConflictDlg dlg
;
1762 dlg
.SetPrejFile(prej
);
1763 dlg
.SetConflictedItem(merge
);
1764 bRet
= (dlg
.DoModal() != IDCANCEL
);
1768 if (stat
.status
->tree_conflict
)
1770 // we have a tree conflict
1772 const SVNInfoData
* pInfoData
= info
.GetFirstFileInfo(merge
, SVNRev(), SVNRev());
1775 if (pInfoData
->treeconflict_kind
== svn_wc_conflict_kind_text
)
1777 CTSVNPath
theirs(directory
);
1778 CTSVNPath
mine(directory
);
1779 CTSVNPath
base(directory
);
1780 bool bConflictData
= false;
1782 if (pInfoData
->treeconflict_theirfile
)
1784 theirs
.AppendPathString(pInfoData
->treeconflict_theirfile
);
1785 bConflictData
= true;
1787 if (pInfoData
->treeconflict_basefile
)
1789 base
.AppendPathString(pInfoData
->treeconflict_basefile
);
1790 bConflictData
= true;
1792 if (pInfoData
->treeconflict_myfile
)
1794 mine
.AppendPathString(pInfoData
->treeconflict_myfile
);
1795 bConflictData
= true;
1802 bRet
= !!CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool
),
1803 base
, theirs
, mine
, merge
);
1805 else if (pInfoData
->treeconflict_kind
== svn_wc_conflict_kind_tree
)
1807 CString sConflictAction
;
1808 CString sConflictReason
;
1809 CString sResolveTheirs
;
1810 CString sResolveMine
;
1811 CTSVNPath treeConflictPath
= CTSVNPath(pInfoData
->treeconflict_path
);
1812 CString sItemName
= treeConflictPath
.GetUIFileOrDirectoryName();
1814 if (pInfoData
->treeconflict_nodekind
== svn_node_file
)
1816 switch (pInfoData
->treeconflict_operation
)
1818 case svn_wc_operation_update
:
1819 switch (pInfoData
->treeconflict_action
)
1821 case svn_wc_conflict_action_edit
:
1822 sConflictAction
.Format(IDS_TREECONFLICT_FILEUPDATEEDIT
, (LPCTSTR
)sItemName
);
1823 sResolveTheirs
.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE
);
1825 case svn_wc_conflict_action_add
:
1826 sConflictAction
.Format(IDS_TREECONFLICT_FILEUPDATEADD
, (LPCTSTR
)sItemName
);
1827 sResolveTheirs
.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE
);
1829 case svn_wc_conflict_action_delete
:
1830 sConflictAction
.Format(IDS_TREECONFLICT_FILEUPDATEDELETE
, (LPCTSTR
)sItemName
);
1831 sResolveTheirs
.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE
);
1835 case svn_wc_operation_switch
:
1836 switch (pInfoData
->treeconflict_action
)
1838 case svn_wc_conflict_action_edit
:
1839 sConflictAction
.Format(IDS_TREECONFLICT_FILESWITCHEDIT
, (LPCTSTR
)sItemName
);
1840 sResolveTheirs
.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE
);
1842 case svn_wc_conflict_action_add
:
1843 sConflictAction
.Format(IDS_TREECONFLICT_FILESWITCHADD
, (LPCTSTR
)sItemName
);
1844 sResolveTheirs
.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE
);
1846 case svn_wc_conflict_action_delete
:
1847 sConflictAction
.Format(IDS_TREECONFLICT_FILESWITCHDELETE
, (LPCTSTR
)sItemName
);
1848 sResolveTheirs
.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE
);
1852 case svn_wc_operation_merge
:
1853 switch (pInfoData
->treeconflict_action
)
1855 case svn_wc_conflict_action_edit
:
1856 sConflictAction
.Format(IDS_TREECONFLICT_FILEMERGEEDIT
, (LPCTSTR
)sItemName
);
1857 sResolveTheirs
.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE
);
1859 case svn_wc_conflict_action_add
:
1860 sResolveTheirs
.Format(IDS_TREECONFLICT_FILEMERGEADD
, (LPCTSTR
)sItemName
);
1861 sResolveTheirs
.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE
);
1863 case svn_wc_conflict_action_delete
:
1864 sConflictAction
.Format(IDS_TREECONFLICT_FILEMERGEDELETE
, (LPCTSTR
)sItemName
);
1865 sResolveTheirs
.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE
);
1871 else if (pInfoData
->treeconflict_nodekind
== svn_node_dir
)
1873 switch (pInfoData
->treeconflict_operation
)
1875 case svn_wc_operation_update
:
1876 switch (pInfoData
->treeconflict_action
)
1878 case svn_wc_conflict_action_edit
:
1879 sConflictAction
.Format(IDS_TREECONFLICT_DIRUPDATEEDIT
, (LPCTSTR
)sItemName
);
1880 sResolveTheirs
.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR
);
1882 case svn_wc_conflict_action_add
:
1883 sConflictAction
.Format(IDS_TREECONFLICT_DIRUPDATEADD
, (LPCTSTR
)sItemName
);
1884 sResolveTheirs
.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR
);
1886 case svn_wc_conflict_action_delete
:
1887 sConflictAction
.Format(IDS_TREECONFLICT_DIRUPDATEDELETE
, (LPCTSTR
)sItemName
);
1888 sResolveTheirs
.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR
);
1892 case svn_wc_operation_switch
:
1893 switch (pInfoData
->treeconflict_action
)
1895 case svn_wc_conflict_action_edit
:
1896 sConflictAction
.Format(IDS_TREECONFLICT_DIRSWITCHEDIT
, (LPCTSTR
)sItemName
);
1897 sResolveTheirs
.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR
);
1899 case svn_wc_conflict_action_add
:
1900 sConflictAction
.Format(IDS_TREECONFLICT_DIRSWITCHADD
, (LPCTSTR
)sItemName
);
1901 sResolveTheirs
.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR
);
1903 case svn_wc_conflict_action_delete
:
1904 sConflictAction
.Format(IDS_TREECONFLICT_DIRSWITCHDELETE
, (LPCTSTR
)sItemName
);
1905 sResolveTheirs
.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR
);
1909 case svn_wc_operation_merge
:
1910 switch (pInfoData
->treeconflict_action
)
1912 case svn_wc_conflict_action_edit
:
1913 sConflictAction
.Format(IDS_TREECONFLICT_DIRMERGEEDIT
, (LPCTSTR
)sItemName
);
1914 sResolveTheirs
.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR
);
1916 case svn_wc_conflict_action_add
:
1917 sConflictAction
.Format(IDS_TREECONFLICT_DIRMERGEADD
, (LPCTSTR
)sItemName
);
1918 sResolveTheirs
.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR
);
1920 case svn_wc_conflict_action_delete
:
1921 sConflictAction
.Format(IDS_TREECONFLICT_DIRMERGEDELETE
, (LPCTSTR
)sItemName
);
1922 sResolveTheirs
.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR
);
1930 switch (pInfoData
->treeconflict_reason
)
1932 case svn_wc_conflict_reason_edited
:
1933 uReasonID
= IDS_TREECONFLICT_REASON_EDITED
;
1934 sResolveMine
.LoadString(pInfoData
->treeconflict_nodekind
== svn_node_dir
? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR
: IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE
);
1936 case svn_wc_conflict_reason_obstructed
:
1937 uReasonID
= IDS_TREECONFLICT_REASON_OBSTRUCTED
;
1938 sResolveMine
.LoadString(pInfoData
->treeconflict_nodekind
== svn_node_dir
? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR
: IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE
);
1940 case svn_wc_conflict_reason_deleted
:
1941 uReasonID
= IDS_TREECONFLICT_REASON_DELETED
;
1942 sResolveMine
.LoadString(pInfoData
->treeconflict_nodekind
== svn_node_dir
? IDS_TREECONFLICT_RESOLVE_REMOVEDIR
: IDS_TREECONFLICT_RESOLVE_REMOVEFILE
);
1944 case svn_wc_conflict_reason_added
:
1945 uReasonID
= IDS_TREECONFLICT_REASON_ADDED
;
1946 sResolveMine
.LoadString(pInfoData
->treeconflict_nodekind
== svn_node_dir
? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR
: IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE
);
1948 case svn_wc_conflict_reason_missing
:
1949 uReasonID
= IDS_TREECONFLICT_REASON_MISSING
;
1950 sResolveMine
.LoadString(pInfoData
->treeconflict_nodekind
== svn_node_dir
? IDS_TREECONFLICT_RESOLVE_REMOVEDIR
: IDS_TREECONFLICT_RESOLVE_REMOVEFILE
);
1952 case svn_wc_conflict_reason_unversioned
:
1953 uReasonID
= IDS_TREECONFLICT_REASON_UNVERSIONED
;
1954 sResolveMine
.LoadString(pInfoData
->treeconflict_nodekind
== svn_node_dir
? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR
: IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE
);
1957 sConflictReason
.Format(uReasonID
, (LPCTSTR
)sConflictAction
);
1959 CTreeConflictEditorDlg dlg
;
1960 dlg
.SetConflictInfoText(sConflictReason
);
1961 dlg
.SetResolveTexts(sResolveTheirs
, sResolveMine
);
1962 dlg
.SetPath(treeConflictPath
);
1963 INT_PTR dlgRet
= dlg
.DoModal();
1964 bRet
= (dlgRet
!= IDCANCEL
);
1972 bool CAppUtils::IsSSHPutty()
1974 CString sshclient
=g_Git
.m_Environment
.GetEnv(_T("GIT_SSH"));
1975 sshclient
=sshclient
.MakeLower();
1976 if(sshclient
.Find(_T("plink.exe"),0)>=0)
1983 CString
CAppUtils::GetClipboardLink(const CString
&skipGitPrefix
, int paramsCount
)
1985 if (!OpenClipboard(NULL
))
1988 CString sClipboardText
;
1989 HGLOBAL hglb
= GetClipboardData(CF_TEXT
);
1992 LPCSTR lpstr
= (LPCSTR
)GlobalLock(hglb
);
1993 sClipboardText
= CString(lpstr
);
1996 hglb
= GetClipboardData(CF_UNICODETEXT
);
1999 LPCTSTR lpstr
= (LPCTSTR
)GlobalLock(hglb
);
2000 sClipboardText
= lpstr
;
2005 if(!sClipboardText
.IsEmpty())
2007 if(sClipboardText
[0] == _T('\"') && sClipboardText
[sClipboardText
.GetLength()-1] == _T('\"'))
2008 sClipboardText
=sClipboardText
.Mid(1,sClipboardText
.GetLength()-2);
2010 if(sClipboardText
.Find( _T("http://")) == 0)
2011 return sClipboardText
;
2013 if(sClipboardText
.Find( _T("https://")) == 0)
2014 return sClipboardText
;
2016 if(sClipboardText
.Find( _T("git://")) == 0)
2017 return sClipboardText
;
2019 if(sClipboardText
.Find( _T("ssh://")) == 0)
2020 return sClipboardText
;
2022 if(sClipboardText
.GetLength()>=2)
2023 if( sClipboardText
[1] == _T(':') )
2024 if( (sClipboardText
[0] >= 'A' && sClipboardText
[0] <= 'Z')
2025 || (sClipboardText
[0] >= 'a' && sClipboardText
[0] <= 'z') )
2026 return sClipboardText
;
2028 // trim prefixes like "git clone "
2029 if (!skipGitPrefix
.IsEmpty() && sClipboardText
.Find(skipGitPrefix
) == 0)
2031 sClipboardText
= sClipboardText
.Mid(skipGitPrefix
.GetLength()).Trim();
2033 while (paramsCount
>= 0)
2036 spacePos
= sClipboardText
.Find(_T(' '), spacePos
+ 1);
2040 if (spacePos
> 0 && paramsCount
< 0)
2041 sClipboardText
= sClipboardText
.Left(spacePos
);
2042 return sClipboardText
;
2046 return CString(_T(""));
2049 CString
CAppUtils::ChooseRepository(CString
*path
)
2051 CBrowseFolder browseFolder
;
2052 CRegString regLastResopitory
= CRegString(_T("Software\\TortoiseGit\\TortoiseProc\\LastRepo"),_T(""));
2054 browseFolder
.m_style
= BIF_EDITBOX
| BIF_NEWDIALOGSTYLE
| BIF_RETURNFSANCESTORS
| BIF_RETURNONLYFSDIRS
;
2055 CString strCloneDirectory
;
2057 strCloneDirectory
=*path
;
2060 strCloneDirectory
= regLastResopitory
;
2064 title
.LoadString(IDS_CHOOSE_REPOSITORY
);
2066 browseFolder
.SetInfo(title
);
2068 if (browseFolder
.Show(NULL
, strCloneDirectory
) == CBrowseFolder::OK
)
2070 regLastResopitory
= strCloneDirectory
;
2071 return strCloneDirectory
;
2079 bool CAppUtils::SendPatchMail(CTGitPathList
& list
)
2083 dlg
.m_PathList
= list
;
2085 if(dlg
.DoModal()==IDOK
)
2087 if (dlg
.m_PathList
.IsEmpty())
2090 CGitProgressDlg progDlg
;
2092 theApp
.m_pMainWnd
= &progDlg
;
2093 progDlg
.SetCommand(CGitProgressList::GitProgress_SendMail
);
2095 progDlg
.SetPathList(dlg
.m_PathList
);
2096 //ProjectProperties props;
2097 //props.ReadPropsPathList(dlg.m_pathList);
2098 //progDlg.SetProjectProperties(props);
2099 progDlg
.SetItemCount(dlg
.m_PathList
.GetCount());
2101 CSendMailPatch
sendMailPatch(dlg
.m_To
, dlg
.m_CC
, dlg
.m_Subject
, !!dlg
.m_bAttachment
, !!dlg
.m_bCombine
);
2102 progDlg
.SetSendMailOption(&sendMailPatch
);
2111 bool CAppUtils::SendPatchMail(CString
&cmd
,CString
&formatpatchoutput
)
2114 CString log
=formatpatchoutput
;
2115 int start
=log
.Find(cmd
);
2117 CString one
=log
.Tokenize(_T("\n"),start
);
2123 CString one
=log
.Tokenize(_T("\n"),start
);
2125 if(one
.IsEmpty() || one
.Find(_T("Success")) == 0)
2127 one
.Replace(_T('/'),_T('\\'));
2129 path
.SetFromWin(one
);
2132 if (!list
.IsEmpty())
2134 return SendPatchMail(list
);
2138 CMessageBox::Show(NULL
, IDS_ERR_NOPATCHES
, IDS_APPNAME
, MB_ICONINFORMATION
);
2144 int CAppUtils::GetLogOutputEncode(CGit
*pGit
)
2147 output
= pGit
->GetConfigValue(_T("i18n.logOutputEncoding"));
2148 if(output
.IsEmpty())
2149 return CUnicodeUtils::GetCPCode(pGit
->GetConfigValue(_T("i18n.commitencoding")));
2152 return CUnicodeUtils::GetCPCode(output
);
2155 int CAppUtils::SaveCommitUnicodeFile(CString
&filename
, CString
&message
)
2157 CFile
file(filename
,CFile::modeReadWrite
|CFile::modeCreate
);
2158 int cp
= CUnicodeUtils::GetCPCode(g_Git
.GetConfigValue(_T("i18n.commitencoding")));
2160 bool stripComments
= (CRegDWORD(_T("Software\\TortoiseGit\\StripCommentedLines"), FALSE
) == TRUE
);
2162 if (CRegDWORD(_T("Software\\TortoiseGit\\SanitizeCommitMsg"), TRUE
) == TRUE
)
2163 message
.TrimRight(L
" \r\n");
2165 int len
= message
.GetLength();
2167 while (start
>= 0 && start
< len
)
2169 int oldStart
= start
;
2170 start
= message
.Find(L
"\n", oldStart
);
2171 CString line
= message
.Mid(oldStart
);
2174 line
= line
.Left(start
- oldStart
);
2175 ++start
; // move forward so we don't find the same char again
2177 if (stripComments
&& (line
.GetLength() >= 1 && line
.GetAt(0) == '#') || (start
< 0 && line
.IsEmpty()))
2179 line
.TrimRight(L
" \r");
2180 CStringA lineA
= CUnicodeUtils::GetMulti(line
+ L
"\n", cp
);
2181 file
.Write(lineA
.GetBuffer(), lineA
.GetLength());
2187 bool CAppUtils::Pull(bool showPush
)
2190 dlg
.m_IsPull
= TRUE
;
2191 if (dlg
.DoModal() == IDOK
)
2193 CString url
= dlg
.m_RemoteURL
;
2195 if (dlg
.m_bAutoLoad
)
2197 CAppUtils::LaunchPAgent(NULL
, &dlg
.m_RemoteURL
);
2202 if (g_Git
.GetHash(hashOld
, _T("HEAD")))
2204 MessageBox(NULL
, g_Git
.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR
);
2218 cmdRebase
= "--rebase ";
2220 if (!dlg
.m_bFetchTags
)
2221 notags
= _T("--no-tags");
2227 ffonly
= _T("--ff-only");
2230 squash
= _T("--squash");
2232 if (dlg
.m_bNoCommit
)
2233 nocommit
= _T("--no-commit");
2236 depth
.Format(_T("--depth %d "), dlg
.m_nDepth
);
2238 int ver
= CAppUtils::GetMsysgitVersion();
2240 if (dlg
.m_bPrune
== TRUE
)
2241 prune
= _T("--prune ");
2242 else if (dlg
.m_bPrune
== FALSE
&& ver
>= 0x01080500)
2243 prune
= _T("--no-prune ");
2245 if(ver
>= 0x01070203) //above 1.7.0.2
2246 cmdRebase
+= _T("--progress ");
2248 cmd
.Format(_T("git.exe pull -v %s %s %s %s %s %s %s %s \"%s\" %s"), cmdRebase
, noff
, ffonly
, squash
, nocommit
, depth
, notags
, prune
, url
, dlg
.m_RemoteBranchName
);
2249 CProgressDlg progress
;
2250 progress
.m_GitCmd
= cmd
;
2251 progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_PROC_PULL_DIFFS
)));
2252 progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_PROC_PULL_LOG
)));
2253 INT_PTR pushButton
= showPush
? progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_MENUPUSH
))) + IDC_PROGRESS_BUTTON1
: -1;
2255 CTGitPath gitPath
= g_Git
.m_CurrentDir
;
2256 INT_PTR smUpdateButton
= gitPath
.HasSubmodules() ? progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_PROC_SUBMODULESUPDATE
))) + IDC_PROGRESS_BUTTON1
: -1;
2258 INT_PTR ret
= progress
.DoModal();
2260 if (ret
== IDOK
&& progress
.m_GitStatus
== 1 && progress
.m_LogText
.Find(_T("CONFLICT")) >= 0 && CMessageBox::Show(NULL
, IDS_SEECHANGES
, IDS_APPNAME
, MB_YESNO
| MB_ICONINFORMATION
) == IDYES
)
2263 dlg
.m_pathList
.AddPath(CTGitPath());
2270 if (g_Git
.GetHash(hashNew
, _T("HEAD")))
2272 MessageBox(NULL
, g_Git
.GetGitLastErr(_T("Could not get HEAD hash after pulling.")), _T("TortoiseGit"), MB_ICONERROR
);
2276 if( ret
== IDC_PROGRESS_BUTTON1
)
2278 if(hashOld
== hashNew
)
2280 if(progress
.m_GitStatus
== 0)
2281 CMessageBox::Show(NULL
, IDS_UPTODATE
, IDS_APPNAME
, MB_OK
| MB_ICONINFORMATION
);
2286 dlg
.SetDiff(NULL
, hashNew
.ToString(), hashOld
.ToString());
2291 else if ( ret
== IDC_PROGRESS_BUTTON1
+1 )
2293 if(hashOld
== hashNew
)
2295 if(progress
.m_GitStatus
== 0)
2296 CMessageBox::Show(NULL
, IDS_UPTODATE
, IDS_APPNAME
, MB_OK
| MB_ICONINFORMATION
);
2301 dlg
.SetParams(CTGitPath(_T("")), CTGitPath(_T("")), _T(""), hashOld
.ToString() + _T("..") + hashNew
.ToString(), 0);
2304 else if (ret
== pushButton
)
2309 else if (ret
== smUpdateButton
)
2312 sCmd
.Format(_T("/command:subupdate /bkpath:\"%s\""), g_Git
.m_CurrentDir
);
2314 CAppUtils::RunTortoiseGitProc(sCmd
);
2322 bool CAppUtils::Fetch(CString remoteName
, bool allowRebase
, bool allRemotes
)
2325 dlg
.m_PreSelectRemote
= remoteName
;
2326 dlg
.m_bAllowRebase
= allowRebase
;
2328 dlg
.m_bAllRemotes
= allRemotes
;
2330 if(dlg
.DoModal()==IDOK
)
2334 if (dlg
.m_bAllRemotes
)
2337 g_Git
.GetRemoteList(list
);
2339 STRING_VECTOR::const_iterator it
= list
.begin();
2340 while (it
!= list
.end())
2342 CString
remote(*it
);
2343 CAppUtils::LaunchPAgent(NULL
, &remote
);
2348 CAppUtils::LaunchPAgent(NULL
, &dlg
.m_RemoteURL
);
2352 url
=dlg
.m_RemoteURL
;
2356 int ver
= CAppUtils::GetMsysgitVersion();
2358 if(ver
>= 0x01070203) //above 1.7.0.2
2359 arg
= _T("--progress ");
2362 arg
.AppendFormat(_T("--depth %d "), dlg
.m_nDepth
);
2364 if (dlg
.m_bPrune
== TRUE
)
2365 arg
+= _T("--prune ");
2366 else if (dlg
.m_bPrune
== FALSE
&& ver
>= 0x01080500)
2367 arg
+= _T("--no-prune ");
2369 if (dlg
.m_bFetchTags
== 1)
2371 arg
+= _T("--tags ");
2373 else if (dlg
.m_bFetchTags
== 0)
2375 arg
+= _T("--no-tags ");
2378 if (dlg
.m_bAllRemotes
)
2379 cmd
.Format(_T("git.exe fetch --all -v %s"), arg
);
2381 cmd
.Format(_T("git.exe fetch -v %s \"%s\" %s"), arg
, url
, dlg
.m_RemoteBranchName
);
2385 CProgressDlg progress
;
2387 progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_MENULOG
)));
2388 progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_PROC_RESET
)));
2390 if (!dlg
.m_bRebase
&& !g_GitAdminDir
.IsBareRepo(g_Git
.m_CurrentDir
))
2391 progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_MENUREBASE
)));
2393 progress
.m_PostFailCmdList
.Add(CString(MAKEINTRESOURCE(IDS_MSGBOX_RETRY
)));
2395 progress
.m_GitCmd
=cmd
;
2396 INT_PTR userResponse
;
2398 if (g_Git
.UsingLibGit2(CGit::GIT_CMD_FETCH
))
2400 CGitProgressDlg gitdlg
;
2401 if (!dlg
.m_bAllRemotes
)
2403 gitdlg
.SetCommand(CGitProgressList::GitProgress_Fetch
);
2404 gitdlg
.SetAutoTag(dlg
.m_bFetchTags
== 1 ? GIT_REMOTE_DOWNLOAD_TAGS_ALL
: dlg
.m_bFetchTags
== 2 ? GIT_REMOTE_DOWNLOAD_TAGS_AUTO
: GIT_REMOTE_DOWNLOAD_TAGS_NONE
);
2405 if (!dlg
.m_bAllRemotes
)
2406 gitdlg
.SetRefSpec(dlg
.m_RemoteBranchName
);
2407 userResponse
= gitdlg
.DoModal();
2410 userResponse
= progress
.DoModal();
2412 if (!progress
.m_GitStatus
)
2414 if (userResponse
== IDC_PROGRESS_BUTTON1
)
2416 CString cmd
= _T("/command:log");
2417 cmd
+= _T(" /path:\"") + g_Git
.m_CurrentDir
+ _T("\"");
2418 RunTortoiseGitProc(cmd
);
2421 else if (userResponse
== IDC_PROGRESS_BUTTON1
+ 1)
2423 CString pullRemote
, pullBranch
;
2424 g_Git
.GetRemoteTrackedBranchForHEAD(pullRemote
, pullBranch
);
2426 CString defaultUpstream
;
2427 if (!pullRemote
.IsEmpty() && !pullBranch
.IsEmpty())
2428 defaultUpstream
.Format(_T("remotes/%s/%s"), pullRemote
, pullBranch
);
2429 GitReset(&defaultUpstream
, 2);
2432 else if ((userResponse
== IDC_PROGRESS_BUTTON1
+ 2) || dlg
.m_bRebase
)
2437 dlg
.m_PostButtonTexts
.Add(CString(MAKEINTRESOURCE(IDS_MENULOG
)));
2438 dlg
.m_PostButtonTexts
.Add(CString(MAKEINTRESOURCE(IDS_MENUDESSENDMAIL
)));
2439 dlg
.m_PostButtonTexts
.Add(CString(MAKEINTRESOURCE(IDS_MENUREBASE
)));
2440 INT_PTR response
= dlg
.DoModal();
2441 if(response
== IDOK
)
2445 else if (response
== IDC_REBASE_POST_BUTTON
)
2447 CString cmd
= _T("/command:log");
2448 cmd
+= _T(" /path:\"") + g_Git
.m_CurrentDir
+ _T("\"");
2449 RunTortoiseGitProc(cmd
);
2452 else if (response
== IDC_REBASE_POST_BUTTON
+ 1)
2454 CString cmd
, out
, err
;
2455 cmd
.Format(_T("git.exe format-patch -o \"%s\" %s..%s"),
2457 g_Git
.FixBranchName(dlg
.m_Upstream
),
2458 g_Git
.FixBranchName(dlg
.m_Branch
));
2459 if (g_Git
.Run(cmd
, &out
, &err
, CP_UTF8
))
2461 CMessageBox::Show(NULL
, out
+ L
"\n" + err
, _T("TortoiseGit"), MB_OK
|MB_ICONERROR
);
2465 CAppUtils::SendPatchMail(cmd
,out
);
2468 else if (response
== IDC_REBASE_POST_BUTTON
+ 2)
2470 else if(response
== IDCANCEL
)
2475 else if (userResponse
!= IDCANCEL
)
2481 if (userResponse
== IDC_PROGRESS_BUTTON1
)
2490 bool CAppUtils::Push(CString selectLocalBranch
)
2493 dlg
.m_BranchSourceName
= selectLocalBranch
;
2495 if (dlg
.DoModal() == IDOK
)
2498 DWORD exitcode
= 0xFFFFFFFF;
2499 if (CHooks::Instance().PrePush(g_Git
.m_CurrentDir
, exitcode
, error
))
2504 temp
.Format(IDS_ERR_HOOKFAILED
, (LPCTSTR
)error
);
2505 CMessageBox::Show(nullptr, temp
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
2513 arg
+= _T("--thin ");
2514 if(dlg
.m_bTags
&& !dlg
.m_bPushAllBranches
)
2515 arg
+= _T("--tags ");
2517 arg
+= _T("--force ");
2518 if (dlg
.m_bSetUpstream
)
2519 arg
+= _T("--set-upstream ");
2520 if (dlg
.m_RecurseSubmodules
== 1)
2521 arg
+= _T("--recurse-submodules=check ");
2522 if (dlg
.m_RecurseSubmodules
== 2)
2523 arg
+= _T("--recurse-submodules=on-demand ");
2525 int ver
= CAppUtils::GetMsysgitVersion();
2527 if(ver
>= 0x01070203) //above 1.7.0.2
2528 arg
+= _T("--progress ");
2530 CProgressDlg progress
;
2532 STRING_VECTOR remotesList
;
2533 if (dlg
.m_bPushAllRemotes
)
2534 g_Git
.GetRemoteList(remotesList
);
2536 remotesList
.push_back(dlg
.m_URL
);
2538 for (unsigned int i
= 0; i
< remotesList
.size(); ++i
)
2540 if (dlg
.m_bAutoLoad
)
2541 CAppUtils::LaunchPAgent(NULL
, &remotesList
[i
]);
2544 if (dlg
.m_bPushAllBranches
)
2546 cmd
.Format(_T("git.exe push --all %s \"%s\""),
2552 progress
.m_GitCmdList
.push_back(cmd
);
2553 cmd
.Format(_T("git.exe push --tags %s \"%s\""), arg
, remotesList
[i
]);
2558 cmd
.Format(_T("git.exe push %s \"%s\" %s"),
2561 dlg
.m_BranchSourceName
);
2562 if (!dlg
.m_BranchRemoteName
.IsEmpty())
2564 cmd
+= _T(":") + dlg
.m_BranchRemoteName
;
2567 progress
.m_GitCmdList
.push_back(cmd
);
2570 progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_PROC_REQUESTPULL
)));
2571 progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_MENUPUSH
)));
2572 bool rejected
= false;
2573 progress
.m_PostCmdCallback
= [&] ()
2575 if (progress
.m_GitStatus
)
2577 progress
.m_PostCmdList
.RemoveAll();
2578 rejected
= progress
.GetLogText().Find(_T("! [rejected]")) > 0;
2581 progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_MENUPULL
)));
2582 progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_MENUFETCH
)));
2584 progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_MENUPUSH
)));
2588 INT_PTR ret
= progress
.DoModal();
2590 exitcode
= 0xFFFFFFFF;
2592 if (CHooks::Instance().PostPush(g_Git
.m_CurrentDir
, exitcode
, error
))
2597 temp
.Format(IDS_ERR_HOOKFAILED
, (LPCTSTR
)error
);
2598 CMessageBox::Show(nullptr, temp
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
2603 if (!progress
.m_GitStatus
)
2605 if(ret
== IDC_PROGRESS_BUTTON1
)
2607 RequestPull(dlg
.m_BranchRemoteName
);
2609 else if(ret
== IDC_PROGRESS_BUTTON1
+ 1)
2611 Push(selectLocalBranch
);
2619 // failed, pull first
2620 if (ret
== IDC_PROGRESS_BUTTON1
)
2624 else if (ret
== IDC_PROGRESS_BUTTON1
+ 1)
2626 Fetch(dlg
.m_bPushAllRemotes
? _T("") : dlg
.m_URL
, true, !!dlg
.m_bPushAllRemotes
);
2628 else if (ret
== IDC_PROGRESS_BUTTON1
+ 2)
2630 Push(selectLocalBranch
);
2635 if (ret
== IDC_PROGRESS_BUTTON1
)
2637 Push(selectLocalBranch
);
2645 bool CAppUtils::RequestPull(CString endrevision
, CString repositoryUrl
)
2647 CRequestPullDlg dlg
;
2648 dlg
.m_RepositoryURL
= repositoryUrl
;
2649 dlg
.m_EndRevision
= endrevision
;
2650 if (dlg
.DoModal()==IDOK
)
2653 cmd
.Format(_T("git.exe request-pull %s \"%s\" %s"), dlg
.m_StartRevision
, dlg
.m_RepositoryURL
, dlg
.m_EndRevision
);
2655 CSysProgressDlg sysProgressDlg
;
2656 sysProgressDlg
.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME
)));
2657 sysProgressDlg
.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_CREATINGPULLREUQEST
)));
2658 sysProgressDlg
.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT
)));
2659 sysProgressDlg
.SetShowProgressBar(false);
2660 sysProgressDlg
.ShowModeless((HWND
)NULL
, true);
2662 CString tempFileName
= GetTempFile();
2664 DeleteFile(tempFileName
);
2665 CreateDirectory(tempFileName
, NULL
);
2666 tempFileName
+= _T("\\pullrequest.txt");
2667 if (g_Git
.RunLogFile(cmd
, tempFileName
, &err
))
2670 msg
.LoadString(IDS_ERR_PULLREUQESTFAILED
);
2671 CMessageBox::Show(NULL
, msg
+ _T("\n") + err
, _T("TortoiseGit"), MB_OK
);
2675 if (sysProgressDlg
.HasUserCancelled())
2677 CMessageBox::Show(NULL
, IDS_SVN_USERCANCELLED
, IDS_APPNAME
, MB_OK
);
2678 ::DeleteFile(tempFileName
);
2682 sysProgressDlg
.Stop();
2684 if (dlg
.m_bSendMail
)
2687 dlg
.m_PathList
= CTGitPathList(CTGitPath(tempFileName
));
2688 dlg
.m_bCustomSubject
= true;
2690 if (dlg
.DoModal() == IDOK
)
2692 if (dlg
.m_PathList
.IsEmpty())
2695 CGitProgressDlg progDlg
;
2697 theApp
.m_pMainWnd
= &progDlg
;
2698 progDlg
.SetCommand(CGitProgressList::GitProgress_SendMail
);
2700 progDlg
.SetPathList(dlg
.m_PathList
);
2701 progDlg
.SetItemCount(dlg
.m_PathList
.GetCount());
2703 CSendMailCombineable
sendMailCombineable(dlg
.m_To
, dlg
.m_CC
, dlg
.m_Subject
, !!dlg
.m_bAttachment
, !!dlg
.m_bCombine
);
2704 progDlg
.SetSendMailOption(&sendMailCombineable
);
2713 CAppUtils::LaunchAlternativeEditor(tempFileName
);
2718 bool CAppUtils::CreateMultipleDirectory(const CString
& szPath
)
2720 CString
strDir(szPath
);
2721 if (strDir
.GetAt(strDir
.GetLength()-1)!=_T('\\'))
2723 strDir
.AppendChar(_T('\\'));
2725 std::vector
<CString
> vPath
;
2727 bool bSuccess
= false;
2729 for (int i
=0;i
<strDir
.GetLength();++i
)
2731 if (strDir
.GetAt(i
) != _T('\\'))
2733 strTemp
.AppendChar(strDir
.GetAt(i
));
2737 vPath
.push_back(strTemp
);
2738 strTemp
.AppendChar(_T('\\'));
2742 for (auto vIter
= vPath
.begin(); vIter
!= vPath
.end(); ++vIter
)
2744 bSuccess
= CreateDirectory(*vIter
, NULL
) ? true : false;
2750 void CAppUtils::RemoveTrailSlash(CString
&path
)
2755 // For URL, do not trim the slash just after the host name component.
2756 int index
= path
.Find(_T("://"));
2760 index
= path
.Find(_T('/'), index
);
2761 if (index
== path
.GetLength() - 1)
2765 while(path
[path
.GetLength()-1] == _T('\\') || path
[path
.GetLength()-1] == _T('/' ) )
2767 path
=path
.Left(path
.GetLength()-1);
2773 bool CAppUtils::CheckUserData()
2775 while(g_Git
.GetUserName().IsEmpty() || g_Git
.GetUserEmail().IsEmpty())
2777 if(CMessageBox::Show(NULL
, IDS_PROC_NOUSERDATA
, IDS_APPNAME
, MB_YESNO
| MB_ICONERROR
) == IDYES
)
2779 CTGitPath
path(g_Git
.m_CurrentDir
);
2780 CSettings
dlg(IDS_PROC_SETTINGS_TITLE
,&path
);
2781 dlg
.SetTreeViewMode(TRUE
, TRUE
, TRUE
);
2782 dlg
.SetTreeWidth(220);
2783 dlg
.m_DefaultPage
= _T("gitconfig");
2786 dlg
.HandleRestart();
2796 BOOL
CAppUtils::Commit(CString bugid
,BOOL bWholeProject
,CString
&sLogMsg
,
2797 CTGitPathList
&pathList
,
2798 CTGitPathList
&selectedList
,
2799 bool bSelectFilesForCommit
)
2801 bool bFailed
= true;
2803 if (!CheckUserData())
2810 dlg
.m_sBugID
= bugid
;
2812 dlg
.m_bWholeProject
= bWholeProject
;
2814 dlg
.m_sLogMessage
= sLogMsg
;
2815 dlg
.m_pathList
= pathList
;
2816 dlg
.m_checkedPathList
= selectedList
;
2817 dlg
.m_bSelectFilesForCommit
= bSelectFilesForCommit
;
2818 if (dlg
.DoModal() == IDOK
)
2820 if (dlg
.m_pathList
.IsEmpty())
2822 // if the user hasn't changed the list of selected items
2823 // we don't use that list. Because if we would use the list
2824 // of pre-checked items, the dialog would show different
2825 // checked items on the next startup: it would only try
2826 // to check the parent folder (which might not even show)
2827 // instead, we simply use an empty list and let the
2828 // default checking do its job.
2829 if (!dlg
.m_pathList
.IsEqual(pathList
))
2830 selectedList
= dlg
.m_pathList
;
2831 pathList
= dlg
.m_updatedPathList
;
2832 sLogMsg
= dlg
.m_sLogMessage
;
2833 bSelectFilesForCommit
= true;
2835 switch (dlg
.m_PostCmd
)
2837 case GIT_POSTCOMMIT_CMD_DCOMMIT
:
2838 CAppUtils::SVNDCommit();
2840 case GIT_POSTCOMMIT_CMD_PUSH
:
2843 case GIT_POSTCOMMIT_CMD_CREATETAG
:
2844 CAppUtils::CreateBranchTag(TRUE
);
2846 case GIT_POSTCOMMIT_CMD_PULL
:
2847 CAppUtils::Pull(true);
2853 // CGitProgressDlg progDlg;
2854 // progDlg.SetChangeList(dlg.m_sChangeList, !!dlg.m_bKeepChangeList);
2855 // if (parser.HasVal(_T("closeonend")))
2856 // progDlg.SetAutoClose(parser.GetLongVal(_T("closeonend")));
2857 // progDlg.SetCommand(CGitProgressDlg::GitProgress_Commit);
2858 // progDlg.SetOptions(dlg.m_bKeepLocks ? ProgOptKeeplocks : ProgOptNone);
2859 // progDlg.SetPathList(dlg.m_pathList);
2860 // progDlg.SetCommitMessage(dlg.m_sLogMessage);
2861 // progDlg.SetDepth(dlg.m_bRecursive ? Git_depth_infinity : svn_depth_empty);
2862 // progDlg.SetSelectedList(dlg.m_selectedPathList);
2863 // progDlg.SetItemCount(dlg.m_itemsCount);
2864 // progDlg.SetBugTraqProvider(dlg.m_BugTraqProvider);
2865 // progDlg.DoModal();
2866 // CRegDWORD err = CRegDWORD(_T("Software\\TortoiseGit\\ErrorOccurred"), FALSE);
2867 // err = (DWORD)progDlg.DidErrorsOccur();
2868 // bFailed = progDlg.DidErrorsOccur();
2869 // bRet = progDlg.DidErrorsOccur();
2870 // CRegDWORD bFailRepeat = CRegDWORD(_T("Software\\TortoiseGit\\CommitReopen"), FALSE);
2871 // if (DWORD(bFailRepeat)==0)
2872 // bFailed = false; // do not repeat if the user chose not to in the settings.
2879 BOOL
CAppUtils::SVNDCommit()
2881 CSVNDCommitDlg dcommitdlg
;
2882 CString gitSetting
= g_Git
.GetConfigValue(_T("svn.rmdir"));
2883 if (gitSetting
== _T("")) {
2884 if (dcommitdlg
.DoModal() != IDOK
)
2890 if (dcommitdlg
.m_remember
)
2892 if (dcommitdlg
.m_rmdir
)
2894 gitSetting
= _T("true");
2898 gitSetting
= _T("false");
2900 if(g_Git
.SetConfigValue(_T("svn.rmdir"),gitSetting
))
2903 msg
.Format(IDS_PROC_SAVECONFIGFAILED
, _T("svn.rmdir"), gitSetting
);
2904 CMessageBox::Show(NULL
, msg
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
2910 BOOL IsStash
= false;
2911 if(!g_Git
.CheckCleanWorkTree())
2913 if (CMessageBox::Show(NULL
, IDS_ERROR_NOCLEAN_STASH
, IDS_APPNAME
, 1, IDI_QUESTION
, IDS_STASHBUTTON
, IDS_ABORTBUTTON
) == 1)
2915 CSysProgressDlg sysProgressDlg
;
2916 sysProgressDlg
.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME
)));
2917 sysProgressDlg
.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING
)));
2918 sysProgressDlg
.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT
)));
2919 sysProgressDlg
.SetShowProgressBar(false);
2920 sysProgressDlg
.SetCancelMsg(IDS_PROGRS_INFOFAILED
);
2921 sysProgressDlg
.ShowModeless((HWND
)NULL
, true);
2924 cmd
=_T("git.exe stash");
2925 if (g_Git
.Run(cmd
, &out
, CP_UTF8
))
2927 sysProgressDlg
.Stop();
2928 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
2931 sysProgressDlg
.Stop();
2941 CProgressDlg progress
;
2942 if (dcommitdlg
.m_rmdir
)
2944 progress
.m_GitCmd
=_T("git.exe svn dcommit --rmdir");
2948 progress
.m_GitCmd
=_T("git.exe svn dcommit");
2950 if(progress
.DoModal()==IDOK
&& progress
.m_GitStatus
== 0)
2952 ::DeleteFile(g_Git
.m_CurrentDir
+ _T("\\sys$command"));
2955 if(CMessageBox::Show(NULL
,IDS_DCOMMIT_STASH_POP
,IDS_APPNAME
,MB_YESNO
|MB_ICONINFORMATION
)==IDYES
)
2957 CSysProgressDlg sysProgressDlg
;
2958 sysProgressDlg
.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME
)));
2959 sysProgressDlg
.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING
)));
2960 sysProgressDlg
.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT
)));
2961 sysProgressDlg
.SetShowProgressBar(false);
2962 sysProgressDlg
.SetCancelMsg(IDS_PROGRS_INFOFAILED
);
2963 sysProgressDlg
.ShowModeless((HWND
)NULL
, true);
2966 cmd
=_T("git.exe stash pop");
2967 if (g_Git
.Run(cmd
, &out
, CP_UTF8
))
2969 sysProgressDlg
.Stop();
2970 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
2973 sysProgressDlg
.Stop();
2985 BOOL
CAppUtils::Merge(CString
*commit
)
2987 if (!CheckUserData())
2992 dlg
.m_initialRefName
= *commit
;
2994 if(dlg
.DoModal()==IDOK
)
3000 args
+= _T(" --no-ff");
3003 args
+= _T(" --squash");
3006 args
+= _T(" --no-commit");
3011 fmt
.Format(_T(" --log=%d"), dlg
.m_nLog
);
3015 if (!dlg
.m_MergeStrategy
.IsEmpty())
3017 args
+= _T(" --strategy=") + dlg
.m_MergeStrategy
;
3018 if (!dlg
.m_StrategyOption
.IsEmpty())
3020 args
+= _T(" --strategy-option=") + dlg
.m_StrategyOption
;
3021 if (!dlg
.m_StrategyParam
.IsEmpty())
3022 args
+= _T("=") + dlg
.m_StrategyParam
;
3026 if(!dlg
.m_strLogMesage
.IsEmpty())
3028 CString logmsg
= dlg
.m_strLogMesage
;
3029 logmsg
.Replace(_T("\""), _T("\\\""));
3030 args
+= _T(" -m \"") + logmsg
+ _T("\"");
3032 cmd
.Format(_T("git.exe merge %s %s"), args
, g_Git
.FixBranchName(dlg
.m_VersionName
));
3034 CProgressDlg Prodlg
;
3035 Prodlg
.m_GitCmd
= cmd
;
3037 INT_PTR idCommit
= -1;
3038 INT_PTR idRemoveBranch
= -1;
3039 INT_PTR idPush
= -1;
3040 INT_PTR idSVNDCommit
= -1;
3041 INT_PTR idResolve
= -1;
3042 BOOL hasGitSVN
= CTGitPath(g_Git
.m_CurrentDir
).GetAdminDirMask() & ITEMIS_GITSVN
;
3043 if (dlg
.m_bNoCommit
)
3044 idCommit
= Prodlg
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_MENUCOMMIT
)));
3047 if (dlg
.m_bIsBranch
)
3049 idRemoveBranch
= Prodlg
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_PROC_REMOVEBRANCH
)));
3050 idPush
= Prodlg
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_MENUPUSH
)));
3054 idSVNDCommit
= Prodlg
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_MENUSVNDCOMMIT
)));
3057 Prodlg
.m_PostCmdCallback
= [&] ()
3059 if (Prodlg
.m_GitStatus
)
3061 Prodlg
.m_PostCmdList
.RemoveAll();
3064 if (!g_Git
.ListConflictFile(list
) && !list
.IsEmpty())
3066 // there are conflict files
3067 idResolve
= Prodlg
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_PROGRS_CMD_RESOLVE
)));
3072 INT_PTR ret
= Prodlg
.DoModal();
3073 if (Prodlg
.m_GitStatus
&& (ret
== IDC_PROGRESS_BUTTON1
+ idResolve
))
3075 CTGitPathList pathlist
;
3076 CTGitPathList selectedlist
;
3077 pathlist
.AddPath(g_Git
.m_CurrentDir
);
3078 bool bSelectFilesForCommit
= !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE
));
3080 CAppUtils::Commit(CString(), false, str
, pathlist
, selectedlist
, bSelectFilesForCommit
);
3082 else if (!Prodlg
.m_GitStatus
)
3084 if (ret
== IDC_PROGRESS_BUTTON1
+ idCommit
)
3087 CTGitPathList pathList
;
3088 CTGitPathList selectedList
;
3089 return Commit(_T(""), TRUE
, sLogMsg
, pathList
, selectedList
, true);
3091 else if (ret
== IDC_PROGRESS_BUTTON1
+ idRemoveBranch
)
3094 msg
.Format(IDS_PROC_DELETEBRANCHTAG
, dlg
.m_VersionName
);
3095 if (CMessageBox::Show(NULL
, msg
, _T("TortoiseGit"), 2, IDI_QUESTION
, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON
)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
))) == 1)
3098 cmd
.Format(_T("git.exe branch -D -- %s"), dlg
.m_VersionName
);
3099 if (g_Git
.Run(cmd
, &out
, CP_UTF8
))
3100 MessageBox(NULL
, out
, _T("TortoiseGit"), MB_OK
);
3103 else if (ret
== IDC_PROGRESS_BUTTON1
+ idPush
)
3105 else if (ret
== IDC_PROGRESS_BUTTON1
+ idSVNDCommit
)
3106 CAppUtils::SVNDCommit();
3109 return !Prodlg
.m_GitStatus
;
3114 BOOL
CAppUtils::MergeAbort()
3117 if (dlg
.DoModal() == IDOK
)
3121 switch (dlg
.m_ResetType
)
3124 type
= _T("--mixed");
3127 type
= _T("--hard");
3130 dlg
.m_ResetType
= 0;
3131 type
= _T("--mixed");
3134 cmd
.Format(_T("git.exe reset %s HEAD --"), type
);
3138 CProgressDlg progress
;
3139 progress
.m_GitCmd
= cmd
;
3141 CTGitPath gitPath
= g_Git
.m_CurrentDir
;
3142 if (gitPath
.HasSubmodules() && dlg
.m_ResetType
== 1)
3143 progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_PROC_SUBMODULESUPDATE
)));
3145 progress
.m_PostFailCmdList
.Add(CString(MAKEINTRESOURCE(IDS_MSGBOX_RETRY
)));
3148 if (g_Git
.UsingLibGit2(CGit::GIT_CMD_RESET
))
3150 CGitProgressDlg gitdlg
;
3151 gitdlg
.SetCommand(CGitProgressList::GitProgress_Reset
);
3152 gitdlg
.SetRevision(_T("HEAD"));
3153 gitdlg
.SetResetType(dlg
.m_ResetType
+ 1);
3154 ret
= gitdlg
.DoModal();
3157 ret
= progress
.DoModal();
3159 if (progress
.m_GitStatus
== 0 && gitPath
.HasSubmodules() && dlg
.m_ResetType
== 1 && ret
== IDC_PROGRESS_BUTTON1
)
3162 sCmd
.Format(_T("/command:subupdate /bkpath:\"%s\""), g_Git
.m_CurrentDir
);
3164 CCommonAppUtils::RunTortoiseGitProc(sCmd
);
3167 else if (progress
.m_GitStatus
!= 0 && ret
== IDC_PROGRESS_BUTTON1
)
3169 else if (ret
== IDOK
)
3178 void CAppUtils::EditNote(GitRev
*rev
)
3180 if (!CheckUserData())
3184 dlg
.m_sHintText
= CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES
));
3185 dlg
.m_sInputText
= rev
->m_Notes
;
3186 dlg
.m_sTitle
= CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES
));
3187 //dlg.m_pProjectProperties = &m_ProjectProperties;
3188 dlg
.m_bUseLogWidth
= true;
3189 if(dlg
.DoModal() == IDOK
)
3192 cmd
=_T("notes add -f -F \"");
3194 CString tempfile
=::GetTempFile();
3195 CAppUtils::SaveCommitUnicodeFile(tempfile
,dlg
.m_sInputText
);
3198 cmd
+= rev
->m_CommitHash
.ToString();
3202 if (git_run_cmd("notes", CUnicodeUtils::GetMulti(cmd
, CP_UTF8
).GetBuffer()))
3204 CMessageBox::Show(NULL
, IDS_PROC_FAILEDSAVINGNOTES
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
3209 rev
->m_Notes
= dlg
.m_sInputText
;
3213 CMessageBox::Show(NULL
, IDS_PROC_FAILEDSAVINGNOTES
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
3215 ::DeleteFile(tempfile
);
3220 int CAppUtils::GetMsysgitVersion()
3222 if (g_Git
.ms_LastMsysGitVersion
)
3223 return g_Git
.ms_LastMsysGitVersion
;
3226 CString versiondebug
;
3229 CRegDWORD regTime
= CRegDWORD(_T("Software\\TortoiseGit\\git_file_time"));
3230 CRegDWORD regVersion
= CRegDWORD(_T("Software\\TortoiseGit\\git_cached_version"));
3232 CString gitpath
= CGit::ms_LastMsysGitDir
+_T("\\git.exe");
3235 if (!g_Git
.GetFileModifyTime(gitpath
, &time
))
3237 if((DWORD
)time
== regTime
)
3239 g_Git
.ms_LastMsysGitVersion
= regVersion
;
3245 cmd
= _T("git.exe --version");
3246 if (g_Git
.Run(cmd
, &version
, &err
, CP_UTF8
))
3248 CMessageBox::Show(NULL
, _T("git.exe not correctly set up (") + err
+ _T(")\nCheck TortoiseGit settings and consult help file for \"Git.exe Path\"."), _T("TortoiseGit"), MB_OK
|MB_ICONERROR
);
3255 versiondebug
= version
;
3259 CString str
=version
.Tokenize(_T("."), start
);
3260 int space
= str
.ReverseFind(_T(' '));
3261 str
= str
.Mid(space
+1,start
);
3265 version
= version
.Mid(start
);
3268 str
= version
.Tokenize(_T("."), start
);
3270 ver
|= (_ttol(str
) & 0xFF) << 16;
3272 str
= version
.Tokenize(_T("."), start
);
3273 ver
|= (_ttol(str
) & 0xFF) << 8;
3275 str
= version
.Tokenize(_T("."), start
);
3276 ver
|= (_ttol(str
) & 0xFF);
3282 CMessageBox::Show(NULL
, _T("Could not parse git.exe version number: \"") + versiondebug
+ _T("\""), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
3287 regTime
= time
&0xFFFFFFFF;
3289 g_Git
.ms_LastMsysGitVersion
= ver
;
3294 void CAppUtils::MarkWindowAsUnpinnable(HWND hWnd
)
3296 typedef HRESULT (WINAPI
*SHGPSFW
) (HWND hwnd
,REFIID riid
,void** ppv
);
3298 CAutoLibrary hShell
= AtlLoadSystemLibraryUsingFullPath(_T("Shell32.dll"));
3300 if (hShell
.IsValid()) {
3301 SHGPSFW pfnSHGPSFW
= (SHGPSFW
)::GetProcAddress(hShell
, "SHGetPropertyStoreForWindow");
3303 IPropertyStore
*pps
;
3304 HRESULT hr
= pfnSHGPSFW(hWnd
, IID_PPV_ARGS(&pps
));
3305 if (SUCCEEDED(hr
)) {
3308 var
.boolVal
= VARIANT_TRUE
;
3309 hr
= pps
->SetValue(PKEY_AppUserModel_PreventPinning
, var
);
3316 void CAppUtils::SetWindowTitle(HWND hWnd
, const CString
& urlorpath
, const CString
& dialogname
)
3318 ASSERT(dialogname
.GetLength() < 70);
3319 ASSERT(urlorpath
.GetLength() < MAX_PATH
);
3320 WCHAR pathbuf
[MAX_PATH
] = {0};
3322 PathCompactPathEx(pathbuf
, urlorpath
, 70 - dialogname
.GetLength(), 0);
3324 wcscat_s(pathbuf
, L
" - ");
3325 wcscat_s(pathbuf
, dialogname
);
3326 wcscat_s(pathbuf
, L
" - ");
3327 wcscat_s(pathbuf
, CString(MAKEINTRESOURCE(IDS_APPNAME
)));
3328 SetWindowText(hWnd
, pathbuf
);
3331 bool CAppUtils::BisectStart(CString lastGood
, CString firstBad
)
3333 if (!g_Git
.CheckCleanWorkTree())
3335 if (CMessageBox::Show(NULL
, IDS_ERROR_NOCLEAN_STASH
, IDS_APPNAME
, 1, IDI_QUESTION
, IDS_STASHBUTTON
, IDS_ABORTBUTTON
) == 1)
3337 CSysProgressDlg sysProgressDlg
;
3338 sysProgressDlg
.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME
)));
3339 sysProgressDlg
.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING
)));
3340 sysProgressDlg
.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT
)));
3341 sysProgressDlg
.SetShowProgressBar(false);
3342 sysProgressDlg
.SetCancelMsg(IDS_PROGRS_INFOFAILED
);
3343 sysProgressDlg
.ShowModeless((HWND
)NULL
, true);
3346 cmd
= _T("git.exe stash");
3347 if (g_Git
.Run(cmd
, &out
, CP_UTF8
))
3349 sysProgressDlg
.Stop();
3350 CMessageBox::Show(NULL
, out
, _T("TortoiseGit"), MB_OK
);
3353 sysProgressDlg
.Stop();
3359 CBisectStartDlg bisectStartDlg
;
3361 if (!lastGood
.IsEmpty())
3362 bisectStartDlg
.m_sLastGood
= lastGood
;
3363 if (!firstBad
.IsEmpty())
3364 bisectStartDlg
.m_sFirstBad
= firstBad
;
3366 if (bisectStartDlg
.DoModal() == IDOK
)
3368 CProgressDlg progress
;
3369 theApp
.m_pMainWnd
= &progress
;
3370 progress
.m_GitCmdList
.push_back(_T("git.exe bisect start"));
3371 progress
.m_GitCmdList
.push_back(_T("git.exe bisect good ") + bisectStartDlg
.m_LastGoodRevision
);
3372 progress
.m_GitCmdList
.push_back(_T("git.exe bisect bad ") + bisectStartDlg
.m_FirstBadRevision
);
3374 CTGitPath
path(g_Git
.m_CurrentDir
);
3376 if (path
.HasSubmodules())
3377 progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_PROC_SUBMODULESUPDATE
)));
3379 INT_PTR ret
= progress
.DoModal();
3380 if (path
.HasSubmodules() && ret
== IDC_PROGRESS_BUTTON1
)
3383 sCmd
.Format(_T("/command:subupdate /bkpath:\"%s\""), g_Git
.m_CurrentDir
);
3385 CAppUtils::RunTortoiseGitProc(sCmd
);
3388 else if (ret
== IDOK
)
3395 int CAppUtils::Git2GetUserPassword(git_cred
**out
, const char *url
, const char *username_from_url
, unsigned int /*allowed_types*/, void * /*payload*/)
3398 dlg
.m_URL
= CUnicodeUtils::GetUnicode(url
, CP_UTF8
);
3399 if (username_from_url
)
3400 dlg
.m_UserName
= CUnicodeUtils::GetUnicode(username_from_url
, CP_UTF8
);
3402 CStringA username
, password
;
3403 if (dlg
.DoModal() == IDOK
)
3405 username
= CUnicodeUtils::GetMulti(dlg
.m_UserName
, CP_UTF8
);
3406 password
= CUnicodeUtils::GetMulti(dlg
.m_Password
, CP_UTF8
);
3407 return git_cred_userpass_plaintext_new(out
, username
, password
);