Allow to start a new pull after a failed one
[TortoiseGit.git] / src / TortoiseProc / AppUtils.cpp
blob6e256f1c864298d93a247d442dfc2ef60efd5d75
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.
20 #include "stdafx.h"
21 #include "TortoiseProc.h"
22 #include "PathUtils.h"
23 #include "AppUtils.h"
24 #include "StringUtils.h"
25 #include "MessageBox.h"
26 #include "registry.h"
27 #include "TGitPath.h"
28 #include "Git.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"
40 #include "ResetDlg.h"
41 #include "DeleteConflictDlg.h"
42 #include "ChangedDlg.h"
43 #include "SendMailDlg.h"
44 #include "GitProgressDlg.h"
45 #include "PushDlg.h"
46 #include "CommitDlg.h"
47 #include "MergeDlg.h"
48 #include "MergeAbortDlg.h"
49 #include "Hooks.h"
50 #include "..\Settings\Settings.h"
51 #include "InputDlg.h"
52 #include "SVNDCommitDlg.h"
53 #include "requestpulldlg.h"
54 #include "PullFetchDlg.h"
55 #include "FileDiffDlg.h"
56 #include "RebaseDlg.h"
57 #include "PropKey.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"
65 #include "Patch.h"
66 #include "Globals.h"
67 #include "ProgressCommands/ResetProgressCommand.h"
68 #include "ProgressCommands/FetchProgressCommand.h"
69 #include "ProgressCommands/SendMailProgressCommand.h"
71 CAppUtils::CAppUtils(void)
75 CAppUtils::~CAppUtils(void)
79 bool CAppUtils::StashSave()
81 CStashSaveDlg dlg;
83 if (dlg.DoModal() == IDOK)
85 CString cmd;
86 cmd = _T("git.exe stash save");
88 if (CAppUtils::GetMsysgitVersion() >= 0x01070700)
90 if (dlg.m_bIncludeUntracked)
91 cmd += _T(" --include-untracked");
92 else if (dlg.m_bAll)
93 cmd += _T(" --all");
96 if (!dlg.m_sMessage.IsEmpty())
98 CString message = dlg.m_sMessage;
99 message.Replace(_T("\""), _T("\"\""));
100 cmd += _T(" -- \"") + message + _T("\"");
103 CProgressDlg progress;
104 progress.m_GitCmd = cmd;
105 return (progress.DoModal() == IDOK);
107 return false;
110 bool CAppUtils::StashApply(CString ref, bool showChanges /* true */)
112 CString cmd,out;
113 cmd = _T("git.exe stash apply ");
114 if (ref.Find(_T("refs/")) == 0)
115 ref = ref.Mid(5);
116 if (ref.Find(_T("stash{")) == 0)
117 ref = _T("stash@") + ref.Mid(5);
118 cmd += ref;
120 CSysProgressDlg sysProgressDlg;
121 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
122 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
123 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
124 sysProgressDlg.SetShowProgressBar(false);
125 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
126 sysProgressDlg.ShowModeless((HWND)NULL, true);
128 int ret = g_Git.Run(cmd, &out, CP_UTF8);
130 sysProgressDlg.Stop();
132 bool hasConflicts = (out.Find(_T("CONFLICT")) >= 0);
133 if (ret && !(ret == 1 && hasConflicts))
135 CMessageBox::Show(NULL, CString(MAKEINTRESOURCE(IDS_PROC_STASHAPPLYFAILED)) + _T("\n") + out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
137 else
139 CString message;
140 message.LoadString(IDS_PROC_STASHAPPLYSUCCESS);
141 if (hasConflicts)
142 message.LoadString(IDS_PROC_STASHAPPLYFAILEDCONFLICTS);
143 if (showChanges)
145 if(CMessageBox::Show(NULL,message + _T("\n") + CString(MAKEINTRESOURCE(IDS_SEECHANGES))
146 ,_T("TortoiseGit"),MB_YESNO|MB_ICONINFORMATION) == IDYES)
148 CChangedDlg dlg;
149 dlg.m_pathList.AddPath(CTGitPath());
150 dlg.DoModal();
152 return true;
154 else
156 CMessageBox::Show(NULL, message ,_T("TortoiseGit"), MB_OK | MB_ICONINFORMATION);
157 return true;
160 return false;
163 bool CAppUtils::StashPop(bool showChanges /* true */)
165 CString cmd,out;
166 cmd=_T("git.exe stash pop ");
168 CSysProgressDlg sysProgressDlg;
169 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
170 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
171 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
172 sysProgressDlg.SetShowProgressBar(false);
173 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
174 sysProgressDlg.ShowModeless((HWND)NULL, true);
176 int ret = g_Git.Run(cmd, &out, CP_UTF8);
178 sysProgressDlg.Stop();
180 bool hasConflicts = (out.Find(_T("CONFLICT")) >= 0);
181 if (ret && !(ret == 1 && hasConflicts))
183 CMessageBox::Show(NULL,CString(MAKEINTRESOURCE(IDS_PROC_STASHPOPFAILED)) + _T("\n") + out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
185 else
187 CString message;
188 message.LoadString(IDS_PROC_STASHPOPSUCCESS);
189 if (hasConflicts)
190 message.LoadString(IDS_PROC_STASHPOPFAILEDCONFLICTS);
191 if (showChanges)
193 if(CMessageBox::Show(NULL,CString(message + _T("\n") + CString(MAKEINTRESOURCE(IDS_SEECHANGES)))
194 ,_T("TortoiseGit"),MB_YESNO|MB_ICONINFORMATION) == IDYES)
196 CChangedDlg dlg;
197 dlg.m_pathList.AddPath(CTGitPath());
198 dlg.DoModal();
200 return true;
202 else
204 CMessageBox::Show(NULL, message ,_T("TortoiseGit"), MB_OK | MB_ICONINFORMATION);
205 return true;
208 return false;
211 BOOL CAppUtils::StartExtMerge(
212 const CTGitPath& basefile, const CTGitPath& theirfile, const CTGitPath& yourfile, const CTGitPath& mergedfile,
213 const CString& basename, const CString& theirname, const CString& yourname, const CString& mergedname, bool bReadOnly,
214 HWND resolveMsgHwnd)
217 CRegString regCom = CRegString(_T("Software\\TortoiseGit\\Merge"));
218 CString ext = mergedfile.GetFileExtension();
219 CString com = regCom;
220 bool bInternal = false;
222 if (!ext.IsEmpty())
224 // is there an extension specific merge tool?
225 CRegString mergetool(_T("Software\\TortoiseGit\\MergeTools\\") + ext.MakeLower());
226 if (!CString(mergetool).IsEmpty())
228 com = mergetool;
231 // is there a filename specific merge tool?
232 CRegString mergetool(_T("Software\\TortoiseGit\\MergeTools\\.") + mergedfile.GetFilename().MakeLower());
233 if (!CString(mergetool).IsEmpty())
235 com = mergetool;
238 if (com.IsEmpty()||(com.Left(1).Compare(_T("#"))==0))
240 // Maybe we should use TortoiseIDiff?
241 if ((ext == _T(".jpg")) || (ext == _T(".jpeg")) ||
242 (ext == _T(".bmp")) || (ext == _T(".gif")) ||
243 (ext == _T(".png")) || (ext == _T(".ico")) ||
244 (ext == _T(".tif")) || (ext == _T(".tiff")) ||
245 (ext == _T(".dib")) || (ext == _T(".emf")) ||
246 (ext == _T(".cur")))
248 com = CPathUtils::GetAppDirectory() + _T("TortoiseGitIDiff.exe");
249 com = _T("\"") + com + _T("\"");
250 com = com + _T(" /base:%base /theirs:%theirs /mine:%mine /result:%merged");
251 com = com + _T(" /basetitle:%bname /theirstitle:%tname /minetitle:%yname");
253 else
255 // use TortoiseGitMerge
256 bInternal = true;
257 com = CPathUtils::GetAppDirectory() + _T("TortoiseGitMerge.exe");
258 com = _T("\"") + com + _T("\"");
259 com = com + _T(" /base:%base /theirs:%theirs /mine:%mine /merged:%merged");
260 com = com + _T(" /basename:%bname /theirsname:%tname /minename:%yname /mergedname:%mname");
261 com += _T(" /saverequired");
262 if (resolveMsgHwnd)
264 CString s;
265 s.Format(L" /resolvemsghwnd:%I64d", (__int64)resolveMsgHwnd);
266 com += s;
269 if (!g_sGroupingUUID.IsEmpty())
271 com += L" /groupuuid:\"";
272 com += g_sGroupingUUID;
273 com += L"\"";
276 // check if the params are set. If not, just add the files to the command line
277 if ((com.Find(_T("%merged"))<0)&&(com.Find(_T("%base"))<0)&&(com.Find(_T("%theirs"))<0)&&(com.Find(_T("%mine"))<0))
279 com += _T(" \"")+basefile.GetWinPathString()+_T("\"");
280 com += _T(" \"")+theirfile.GetWinPathString()+_T("\"");
281 com += _T(" \"")+yourfile.GetWinPathString()+_T("\"");
282 com += _T(" \"")+mergedfile.GetWinPathString()+_T("\"");
284 if (basefile.IsEmpty())
286 com.Replace(_T("/base:%base"), _T(""));
287 com.Replace(_T("%base"), _T(""));
289 else
290 com.Replace(_T("%base"), _T("\"") + basefile.GetWinPathString() + _T("\""));
291 if (theirfile.IsEmpty())
293 com.Replace(_T("/theirs:%theirs"), _T(""));
294 com.Replace(_T("%theirs"), _T(""));
296 else
297 com.Replace(_T("%theirs"), _T("\"") + theirfile.GetWinPathString() + _T("\""));
298 if (yourfile.IsEmpty())
300 com.Replace(_T("/mine:%mine"), _T(""));
301 com.Replace(_T("%mine"), _T(""));
303 else
304 com.Replace(_T("%mine"), _T("\"") + yourfile.GetWinPathString() + _T("\""));
305 if (mergedfile.IsEmpty())
307 com.Replace(_T("/merged:%merged"), _T(""));
308 com.Replace(_T("%merged"), _T(""));
310 else
311 com.Replace(_T("%merged"), _T("\"") + mergedfile.GetWinPathString() + _T("\""));
312 if (basename.IsEmpty())
314 if (basefile.IsEmpty())
316 com.Replace(_T("/basename:%bname"), _T(""));
317 com.Replace(_T("%bname"), _T(""));
319 else
321 com.Replace(_T("%bname"), _T("\"") + basefile.GetUIFileOrDirectoryName() + _T("\""));
324 else
325 com.Replace(_T("%bname"), _T("\"") + basename + _T("\""));
326 if (theirname.IsEmpty())
328 if (theirfile.IsEmpty())
330 com.Replace(_T("/theirsname:%tname"), _T(""));
331 com.Replace(_T("%tname"), _T(""));
333 else
335 com.Replace(_T("%tname"), _T("\"") + theirfile.GetUIFileOrDirectoryName() + _T("\""));
338 else
339 com.Replace(_T("%tname"), _T("\"") + theirname + _T("\""));
340 if (yourname.IsEmpty())
342 if (yourfile.IsEmpty())
344 com.Replace(_T("/minename:%yname"), _T(""));
345 com.Replace(_T("%yname"), _T(""));
347 else
349 com.Replace(_T("%yname"), _T("\"") + yourfile.GetUIFileOrDirectoryName() + _T("\""));
352 else
353 com.Replace(_T("%yname"), _T("\"") + yourname + _T("\""));
354 if (mergedname.IsEmpty())
356 if (mergedfile.IsEmpty())
358 com.Replace(_T("/mergedname:%mname"), _T(""));
359 com.Replace(_T("%mname"), _T(""));
361 else
363 com.Replace(_T("%mname"), _T("\"") + mergedfile.GetUIFileOrDirectoryName() + _T("\""));
366 else
367 com.Replace(_T("%mname"), _T("\"") + mergedname + _T("\""));
369 if ((bReadOnly)&&(bInternal))
370 com += _T(" /readonly");
372 if(!LaunchApplication(com, IDS_ERR_EXTMERGESTART, false))
374 return FALSE;
377 return TRUE;
380 BOOL CAppUtils::StartExtPatch(const CTGitPath& patchfile, const CTGitPath& dir, const CString& sOriginalDescription, const CString& sPatchedDescription, BOOL bReversed, BOOL bWait)
382 CString viewer;
383 // use TortoiseGitMerge
384 viewer = CPathUtils::GetAppDirectory();
385 viewer += _T("TortoiseGitMerge.exe");
387 viewer = _T("\"") + viewer + _T("\"");
388 viewer = viewer + _T(" /diff:\"") + patchfile.GetWinPathString() + _T("\"");
389 viewer = viewer + _T(" /patchpath:\"") + dir.GetWinPathString() + _T("\"");
390 if (bReversed)
391 viewer += _T(" /reversedpatch");
392 if (!sOriginalDescription.IsEmpty())
393 viewer = viewer + _T(" /patchoriginal:\"") + sOriginalDescription + _T("\"");
394 if (!sPatchedDescription.IsEmpty())
395 viewer = viewer + _T(" /patchpatched:\"") + sPatchedDescription + _T("\"");
396 if (!g_sGroupingUUID.IsEmpty())
398 viewer += L" /groupuuid:\"";
399 viewer += g_sGroupingUUID;
400 viewer += L"\"";
402 if(!LaunchApplication(viewer, IDS_ERR_DIFFVIEWSTART, !!bWait))
404 return FALSE;
406 return TRUE;
409 CString CAppUtils::PickDiffTool(const CTGitPath& file1, const CTGitPath& file2)
411 CString difftool = CRegString(_T("Software\\TortoiseGit\\DiffTools\\.") + file2.GetFilename().MakeLower());
412 if (!difftool.IsEmpty())
413 return difftool;
414 difftool = CRegString(_T("Software\\TortoiseGit\\DiffTools\\.") + file1.GetFilename().MakeLower());
415 if (!difftool.IsEmpty())
416 return difftool;
418 // Is there an extension specific diff tool?
419 CString ext = file2.GetFileExtension().MakeLower();
420 if (!ext.IsEmpty())
422 difftool = CRegString(_T("Software\\TortoiseGit\\DiffTools\\") + ext);
423 if (!difftool.IsEmpty())
424 return difftool;
425 // Maybe we should use TortoiseIDiff?
426 if ((ext == _T(".jpg")) || (ext == _T(".jpeg")) ||
427 (ext == _T(".bmp")) || (ext == _T(".gif")) ||
428 (ext == _T(".png")) || (ext == _T(".ico")) ||
429 (ext == _T(".dib")) || (ext == _T(".emf")) ||
430 (ext == _T(".cur")))
432 return
433 _T("\"") + CPathUtils::GetAppDirectory() + _T("TortoiseGitIDiff.exe") + _T("\"") +
434 _T(" /left:%base /right:%mine /lefttitle:%bname /righttitle:%yname") +
435 L" /groupuuid:\"" + g_sGroupingUUID + L"\"";
439 // Finally, pick a generic external diff tool
440 difftool = CRegString(_T("Software\\TortoiseGit\\Diff"));
441 return difftool;
444 bool CAppUtils::StartExtDiff(
445 const CString& file1, const CString& file2,
446 const CString& sName1, const CString& sName2,
447 const CString& originalFile1, const CString& originalFile2,
448 const git_revnum_t& hash1, const git_revnum_t& hash2,
449 const DiffFlags& flags, int jumpToLine)
451 CString viewer;
453 CRegDWORD blamediff(_T("Software\\TortoiseGit\\DiffBlamesWithTortoiseMerge"), FALSE);
454 if (!flags.bBlame || !(DWORD)blamediff)
456 viewer = PickDiffTool(file1, file2);
457 // If registry entry for a diff program is commented out, use TortoiseGitMerge.
458 bool bCommentedOut = viewer.Left(1) == _T("#");
459 if (flags.bAlternativeTool)
461 // Invert external vs. internal diff tool selection.
462 if (bCommentedOut)
463 viewer.Delete(0); // uncomment
464 else
465 viewer = "";
467 else if (bCommentedOut)
468 viewer = "";
471 bool bInternal = viewer.IsEmpty();
472 if (bInternal)
474 viewer =
475 _T("\"") + CPathUtils::GetAppDirectory() + _T("TortoiseGitMerge.exe") + _T("\"") +
476 _T(" /base:%base /mine:%mine /basename:%bname /minename:%yname") +
477 _T(" /basereflectedname:%bpath /minereflectedname:%ypath");
478 if (!g_sGroupingUUID.IsEmpty())
480 viewer += L" /groupuuid:\"";
481 viewer += g_sGroupingUUID;
482 viewer += L"\"";
484 if (flags.bBlame)
485 viewer += _T(" /blame");
487 // check if the params are set. If not, just add the files to the command line
488 if ((viewer.Find(_T("%base"))<0)&&(viewer.Find(_T("%mine"))<0))
490 viewer += _T(" \"")+file1+_T("\"");
491 viewer += _T(" \"")+file2+_T("\"");
493 if (viewer.Find(_T("%base")) >= 0)
495 viewer.Replace(_T("%base"), _T("\"")+file1+_T("\""));
497 if (viewer.Find(_T("%mine")) >= 0)
499 viewer.Replace(_T("%mine"), _T("\"")+file2+_T("\""));
502 if (sName1.IsEmpty())
503 viewer.Replace(_T("%bname"), _T("\"") + file1 + _T("\""));
504 else
505 viewer.Replace(_T("%bname"), _T("\"") + sName1 + _T("\""));
507 if (sName2.IsEmpty())
508 viewer.Replace(_T("%yname"), _T("\"") + file2 + _T("\""));
509 else
510 viewer.Replace(_T("%yname"), _T("\"") + sName2 + _T("\""));
512 viewer.Replace(_T("%bpath"), _T("\"") + originalFile1 + _T("\""));
513 viewer.Replace(_T("%ypath"), _T("\"") + originalFile2 + _T("\""));
515 viewer.Replace(_T("%brev"), _T("\"") + hash1 + _T("\""));
516 viewer.Replace(_T("%yrev"), _T("\"") + hash2 + _T("\""));
518 if (flags.bReadOnly && bInternal)
519 viewer += _T(" /readonly");
521 if (jumpToLine > 0)
523 CString temp;
524 temp.Format(_T(" /line:%d"), jumpToLine);
525 viewer += temp;
528 return LaunchApplication(viewer, IDS_ERR_EXTDIFFSTART, flags.bWait);
531 BOOL CAppUtils::StartUnifiedDiffViewer(const CString& patchfile, const CString& title, BOOL bWait)
533 CString viewer;
534 CRegString v = CRegString(_T("Software\\TortoiseGit\\DiffViewer"));
535 viewer = v;
536 if (viewer.IsEmpty() || (viewer.Left(1).Compare(_T("#"))==0))
538 // use TortoiseGitUDiff
539 viewer = CPathUtils::GetAppDirectory();
540 viewer += _T("TortoiseGitUDiff.exe");
541 // enquote the path to TortoiseGitUDiff
542 viewer = _T("\"") + viewer + _T("\"");
543 // add the params
544 viewer = viewer + _T(" /patchfile:%1 /title:\"%title\"");
545 if (!g_sGroupingUUID.IsEmpty())
547 viewer += L" /groupuuid:\"";
548 viewer += g_sGroupingUUID;
549 viewer += L"\"";
552 if (viewer.Find(_T("%1"))>=0)
554 if (viewer.Find(_T("\"%1\"")) >= 0)
555 viewer.Replace(_T("%1"), patchfile);
556 else
557 viewer.Replace(_T("%1"), _T("\"") + patchfile + _T("\""));
559 else
560 viewer += _T(" \"") + patchfile + _T("\"");
561 if (viewer.Find(_T("%title")) >= 0)
563 viewer.Replace(_T("%title"), title);
566 if(!LaunchApplication(viewer, IDS_ERR_DIFFVIEWSTART, !!bWait))
568 return FALSE;
570 return TRUE;
573 BOOL CAppUtils::StartTextViewer(CString file)
575 CString viewer;
576 CRegString txt = CRegString(_T(".txt\\"), _T(""), FALSE, HKEY_CLASSES_ROOT);
577 viewer = txt;
578 viewer = viewer + _T("\\Shell\\Open\\Command\\");
579 CRegString txtexe = CRegString(viewer, _T(""), FALSE, HKEY_CLASSES_ROOT);
580 viewer = txtexe;
582 DWORD len = ExpandEnvironmentStrings(viewer, NULL, 0);
583 std::unique_ptr<TCHAR[]> buf(new TCHAR[len + 1]);
584 ExpandEnvironmentStrings(viewer, buf.get(), len);
585 viewer = buf.get();
586 len = ExpandEnvironmentStrings(file, NULL, 0);
587 std::unique_ptr<TCHAR[]> buf2(new TCHAR[len + 1]);
588 ExpandEnvironmentStrings(file, buf2.get(), len);
589 file = buf2.get();
590 file = _T("\"")+file+_T("\"");
591 if (viewer.IsEmpty())
593 viewer = _T("RUNDLL32 Shell32,OpenAs_RunDLL");
595 if (viewer.Find(_T("\"%1\"")) >= 0)
597 viewer.Replace(_T("\"%1\""), file);
599 else if (viewer.Find(_T("%1")) >= 0)
601 viewer.Replace(_T("%1"), file);
603 else
605 viewer += _T(" ");
606 viewer += file;
609 if(!LaunchApplication(viewer, IDS_ERR_TEXTVIEWSTART, false))
611 return FALSE;
613 return TRUE;
616 BOOL CAppUtils::CheckForEmptyDiff(const CTGitPath& sDiffPath)
618 DWORD length = 0;
619 CAutoFile hFile = ::CreateFile(sDiffPath.GetWinPath(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL);
620 if (!hFile)
621 return TRUE;
622 length = ::GetFileSize(hFile, NULL);
623 if (length < 4)
624 return TRUE;
625 return FALSE;
629 void CAppUtils::CreateFontForLogs(CFont& fontToCreate)
631 LOGFONT logFont;
632 HDC hScreenDC = ::GetDC(NULL);
633 logFont.lfHeight = -MulDiv((DWORD)CRegDWORD(_T("Software\\TortoiseGit\\LogFontSize"), 8), GetDeviceCaps(hScreenDC, LOGPIXELSY), 72);
634 ::ReleaseDC(NULL, hScreenDC);
635 logFont.lfWidth = 0;
636 logFont.lfEscapement = 0;
637 logFont.lfOrientation = 0;
638 logFont.lfWeight = FW_NORMAL;
639 logFont.lfItalic = 0;
640 logFont.lfUnderline = 0;
641 logFont.lfStrikeOut = 0;
642 logFont.lfCharSet = DEFAULT_CHARSET;
643 logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
644 logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
645 logFont.lfQuality = DRAFT_QUALITY;
646 logFont.lfPitchAndFamily = FF_DONTCARE | FIXED_PITCH;
647 _tcscpy_s(logFont.lfFaceName, 32, (LPCTSTR)(CString)CRegString(_T("Software\\TortoiseGit\\LogFontName"), _T("Courier New")));
648 VERIFY(fontToCreate.CreateFontIndirect(&logFont));
651 bool CAppUtils::LaunchPAgent(const CString* keyfile, const CString* pRemote)
653 CString key,remote;
654 CString cmd,out;
655 if( pRemote == NULL)
657 remote=_T("origin");
659 else
661 remote=*pRemote;
663 if(keyfile == NULL)
665 cmd.Format(_T("remote.%s.puttykeyfile"),remote);
666 key = g_Git.GetConfigValue(cmd);
668 else
669 key=*keyfile;
671 if(key.IsEmpty())
672 return false;
674 CString proc=CPathUtils::GetAppDirectory();
675 proc += _T("pageant.exe \"");
676 proc += key;
677 proc += _T("\"");
679 CString tempfile = GetTempFile();
680 ::DeleteFile(tempfile);
682 proc += _T(" -c \"");
683 proc += CPathUtils::GetAppDirectory();
684 proc += _T("tgittouch.exe\"");
685 proc += _T(" \"");
686 proc += tempfile;
687 proc += _T("\"");
689 CString appDir = CPathUtils::GetAppDirectory();
690 bool b = LaunchApplication(proc, IDS_ERR_PAGEANT, true, &appDir);
691 if(!b)
692 return b;
694 int i=0;
695 while(!::PathFileExists(tempfile))
697 Sleep(100);
698 ++i;
699 if(i>10*60*5)
700 break; //timeout 5 minutes
703 if( i== 10*60*5)
705 CMessageBox::Show(NULL, IDS_ERR_PAEGENTTIMEOUT, IDS_APPNAME, MB_OK | MB_ICONERROR);
707 ::DeleteFile(tempfile);
708 return true;
710 bool CAppUtils::LaunchAlternativeEditor(const CString& filename, bool uac)
712 CString editTool = CRegString(_T("Software\\TortoiseGit\\AlternativeEditor"));
713 if (editTool.IsEmpty() || (editTool.Left(1).Compare(_T("#"))==0)) {
714 editTool = CPathUtils::GetAppDirectory() + _T("notepad2.exe");
717 CString sCmd;
718 sCmd.Format(_T("\"%s\" \"%s\""), editTool, filename);
720 LaunchApplication(sCmd, NULL, false, NULL, uac);
721 return true;
723 bool CAppUtils::LaunchRemoteSetting()
725 CTGitPath path(g_Git.m_CurrentDir);
726 CSettings dlg(IDS_PROC_SETTINGS_TITLE, &path);
727 dlg.SetTreeViewMode(TRUE, TRUE, TRUE);
728 dlg.SetTreeWidth(220);
729 dlg.m_DefaultPage = _T("gitremote");
731 dlg.DoModal();
732 dlg.HandleRestart();
733 return true;
736 * Launch the external blame viewer
738 bool CAppUtils::LaunchTortoiseBlame(const CString& sBlameFile,CString Rev,const CString& sParams)
740 CString viewer = _T("\"") + CPathUtils::GetAppDirectory();
741 viewer += _T("TortoiseGitBlame.exe");
742 viewer += _T("\" \"") + sBlameFile + _T("\"");
743 //viewer += _T(" \"") + sLogFile + _T("\"");
744 //viewer += _T(" \"") + sOriginalFile + _T("\"");
745 if(!Rev.IsEmpty() && Rev != GIT_REV_ZERO)
746 viewer += CString(_T(" /rev:"))+Rev;
747 if (!g_sGroupingUUID.IsEmpty())
749 viewer += L" /groupuuid:\"";
750 viewer += g_sGroupingUUID;
751 viewer += L"\"";
753 viewer += _T(" ")+sParams;
755 return LaunchApplication(viewer, IDS_ERR_TGITBLAME, false);
758 bool CAppUtils::FormatTextInRichEditControl(CWnd * pWnd)
760 CString sText;
761 if (pWnd == NULL)
762 return false;
763 bool bStyled = false;
764 pWnd->GetWindowText(sText);
765 // the rich edit control doesn't count the CR char!
766 // to be exact: CRLF is treated as one char.
767 sText.Remove(_T('\r'));
769 // style each line separately
770 int offset = 0;
771 int nNewlinePos;
774 nNewlinePos = sText.Find('\n', offset);
775 CString sLine = nNewlinePos >= 0 ? sText.Mid(offset, nNewlinePos - offset) : sText.Mid(offset);
777 int start = 0;
778 int end = 0;
779 while (FindStyleChars(sLine, '*', start, end))
781 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
782 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
783 SetCharFormat(pWnd, CFM_BOLD, CFE_BOLD);
784 bStyled = true;
785 start = end;
787 start = 0;
788 end = 0;
789 while (FindStyleChars(sLine, '^', start, end))
791 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
792 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
793 SetCharFormat(pWnd, CFM_ITALIC, CFE_ITALIC);
794 bStyled = true;
795 start = end;
797 start = 0;
798 end = 0;
799 while (FindStyleChars(sLine, '_', start, end))
801 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
802 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
803 SetCharFormat(pWnd, CFM_UNDERLINE, CFE_UNDERLINE);
804 bStyled = true;
805 start = end;
807 offset = nNewlinePos+1;
808 } while(nNewlinePos>=0);
809 return bStyled;
812 bool CAppUtils::FindStyleChars(const CString& sText, TCHAR stylechar, int& start, int& end)
814 int i=start;
815 int last = sText.GetLength() - 1;
816 bool bFoundMarker = false;
817 TCHAR c = i == 0 ? _T('\0') : sText[i - 1];
818 TCHAR nextChar = i >= last ? _T('\0') : sText[i + 1];
820 // find a starting marker
821 while (i < last)
823 TCHAR prevChar = c;
824 c = nextChar;
825 nextChar = sText[i + 1];
827 // IsCharAlphaNumeric can be somewhat expensive.
828 // Long lines of "*****" or "----" will be pre-empted efficiently
829 // by the (c != nextChar) condition.
831 if ((c == stylechar) && (c != nextChar))
833 if (IsCharAlphaNumeric(nextChar) && !IsCharAlphaNumeric(prevChar))
835 start = ++i;
836 bFoundMarker = true;
837 break;
840 ++i;
842 if (!bFoundMarker)
843 return false;
845 // find ending marker
846 // c == sText[i - 1]
848 bFoundMarker = false;
849 while (i <= last)
851 TCHAR prevChar = c;
852 c = sText[i];
853 if (c == stylechar)
855 if ((i == last) || (!IsCharAlphaNumeric(sText[i + 1]) && IsCharAlphaNumeric(prevChar)))
857 end = i;
858 ++i;
859 bFoundMarker = true;
860 break;
863 ++i;
865 return bFoundMarker;
868 bool CAppUtils::StartShowUnifiedDiff(HWND hWnd, const CTGitPath& url1, const git_revnum_t& rev1,
869 const CTGitPath& /*url2*/, const git_revnum_t& rev2,
870 //const GitRev& peg /* = GitRev */, const GitRev& headpeg /* = GitRev */,
871 bool /*bAlternateDiff*/ /* = false */, bool /*bIgnoreAncestry*/ /* = false */,
872 bool /* blame = false */,
873 bool bMerge,
874 bool bCombine)
876 int diffContext = 0;
877 if (GetMsysgitVersion() > 0x01080100)
878 diffContext = g_Git.GetConfigValueInt32(_T("diff.context"), -1);
879 CString tempfile=GetTempFile();
880 if (g_Git.GetUnifiedDiff(url1, rev1, rev2, tempfile, bMerge, bCombine, diffContext))
882 CMessageBox::Show(hWnd, g_Git.GetGitLastErr(_T("Could not get unified diff."), CGit::GIT_CMD_DIFF), _T("TortoiseGit"), MB_OK);
883 return false;
885 CAppUtils::StartUnifiedDiffViewer(tempfile, rev1 + _T(":") + rev2);
887 #if 0
888 CString sCmd;
889 sCmd.Format(_T("%s /command:showcompare /unified"),
890 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseGitProc.exe")));
891 sCmd += _T(" /url1:\"") + url1.GetGitPathString() + _T("\"");
892 if (rev1.IsValid())
893 sCmd += _T(" /revision1:") + rev1.ToString();
894 sCmd += _T(" /url2:\"") + url2.GetGitPathString() + _T("\"");
895 if (rev2.IsValid())
896 sCmd += _T(" /revision2:") + rev2.ToString();
897 if (peg.IsValid())
898 sCmd += _T(" /pegrevision:") + peg.ToString();
899 if (headpeg.IsValid())
900 sCmd += _T(" /headpegrevision:") + headpeg.ToString();
902 if (bAlternateDiff)
903 sCmd += _T(" /alternatediff");
905 if (bIgnoreAncestry)
906 sCmd += _T(" /ignoreancestry");
908 if (hWnd)
910 sCmd += _T(" /hwnd:");
911 TCHAR buf[30];
912 _stprintf_s(buf, 30, _T("%p"), (void*)hWnd);
913 sCmd += buf;
916 return CAppUtils::LaunchApplication(sCmd, NULL, false);
917 #endif
918 return TRUE;
921 bool CAppUtils::SetupDiffScripts(bool force, const CString& type)
923 CString scriptsdir = CPathUtils::GetAppParentDirectory();
924 scriptsdir += _T("Diff-Scripts");
925 CSimpleFileFind files(scriptsdir);
926 while (files.FindNextFileNoDirectories())
928 CString file = files.GetFilePath();
929 CString filename = files.GetFileName();
930 CString ext = file.Mid(file.ReverseFind('-') + 1);
931 ext = _T(".") + ext.Left(ext.ReverseFind('.'));
932 std::set<CString> extensions;
933 extensions.insert(ext);
934 CString kind;
935 if (file.Right(3).CompareNoCase(_T("vbs"))==0)
937 kind = _T(" //E:vbscript");
939 if (file.Right(2).CompareNoCase(_T("js"))==0)
941 kind = _T(" //E:javascript");
943 // open the file, read the first line and find possible extensions
944 // this script can handle
947 CStdioFile f(file, CFile::modeRead | CFile::shareDenyNone);
948 CString extline;
949 if (f.ReadString(extline))
951 if ((extline.GetLength() > 15 ) &&
952 ((extline.Left(15).Compare(_T("// extensions: ")) == 0) ||
953 (extline.Left(14).Compare(_T("' extensions: ")) == 0)))
955 if (extline[0] == '/')
956 extline = extline.Mid(15);
957 else
958 extline = extline.Mid(14);
959 CString sToken;
960 int curPos = 0;
961 sToken = extline.Tokenize(_T(";"), curPos);
962 while (!sToken.IsEmpty())
964 if (!sToken.IsEmpty())
966 if (sToken[0] != '.')
967 sToken = _T(".") + sToken;
968 extensions.insert(sToken);
970 sToken = extline.Tokenize(_T(";"), curPos);
974 f.Close();
976 catch (CFileException* e)
978 e->Delete();
981 for (std::set<CString>::const_iterator it = extensions.begin(); it != extensions.end(); ++it)
983 if (type.IsEmpty() || (type.Compare(_T("Diff")) == 0))
985 if (filename.Left(5).CompareNoCase(_T("diff-")) == 0)
987 CRegString diffreg = CRegString(_T("Software\\TortoiseGit\\DiffTools\\") + *it);
988 CString diffregstring = diffreg;
989 if (force || (diffregstring.IsEmpty()) || (diffregstring.Find(filename) >= 0))
990 diffreg = _T("wscript.exe \"") + file + _T("\" %base %mine") + kind;
993 if (type.IsEmpty() || (type.Compare(_T("Merge"))==0))
995 if (filename.Left(6).CompareNoCase(_T("merge-"))==0)
997 CRegString diffreg = CRegString(_T("Software\\TortoiseGit\\MergeTools\\") + *it);
998 CString diffregstring = diffreg;
999 if (force || (diffregstring.IsEmpty()) || (diffregstring.Find(filename) >= 0))
1000 diffreg = _T("wscript.exe \"") + file + _T("\" %merged %theirs %mine %base") + kind;
1006 return true;
1009 bool CAppUtils::Export(CString *BashHash, const CTGitPath *orgPath)
1011 // ask from where the export has to be done
1012 CExportDlg dlg;
1013 if(BashHash)
1014 dlg.m_Revision=*BashHash;
1015 if (orgPath)
1017 if (PathIsRelative(orgPath->GetWinPath()))
1018 dlg.m_orgPath = g_Git.CombinePath(orgPath);
1019 else
1020 dlg.m_orgPath = *orgPath;
1023 if (dlg.DoModal() == IDOK)
1025 CString cmd;
1026 cmd.Format(_T("git.exe archive --output=\"%s\" --format=zip --verbose %s --"),
1027 dlg.m_strFile, g_Git.FixBranchName(dlg.m_VersionName));
1029 CProgressDlg pro;
1030 pro.m_GitCmd=cmd;
1031 CGit git;
1032 if (!dlg.m_bWholeProject && !dlg.m_orgPath.IsEmpty() && PathIsDirectory(dlg.m_orgPath.GetWinPathString()))
1034 git.m_CurrentDir = dlg.m_orgPath.GetWinPathString();
1035 pro.m_Git = &git;
1037 return (pro.DoModal() == IDOK);
1039 return false;
1042 bool CAppUtils::CreateBranchTag(bool IsTag,CString *CommitHash, bool switch_new_brach)
1044 CCreateBranchTagDlg dlg;
1045 dlg.m_bIsTag=IsTag;
1046 dlg.m_bSwitch=switch_new_brach;
1048 if(CommitHash)
1049 dlg.m_initialRefName = *CommitHash;
1051 if(dlg.DoModal()==IDOK)
1053 CString cmd;
1054 CString force;
1055 CString track;
1056 if(dlg.m_bTrack == TRUE)
1057 track=_T(" --track ");
1058 else if(dlg.m_bTrack == FALSE)
1059 track=_T(" --no-track");
1061 if(dlg.m_bForce)
1062 force=_T(" -f ");
1064 if(IsTag)
1066 CString sign;
1067 if(dlg.m_bSign)
1068 sign=_T("-s");
1070 cmd.Format(_T("git.exe tag %s %s %s %s"),
1071 force,
1072 sign,
1073 dlg.m_BranchTagName,
1074 g_Git.FixBranchName(dlg.m_VersionName)
1077 CString tempfile=::GetTempFile();
1078 if(!dlg.m_Message.Trim().IsEmpty())
1080 CAppUtils::SaveCommitUnicodeFile(tempfile,dlg.m_Message);
1081 cmd += _T(" -F ")+tempfile;
1084 else
1086 cmd.Format(_T("git.exe branch %s %s %s %s"),
1087 track,
1088 force,
1089 dlg.m_BranchTagName,
1090 g_Git.FixBranchName(dlg.m_VersionName)
1093 CString out;
1094 if(g_Git.Run(cmd,&out,CP_UTF8))
1096 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
1097 return FALSE;
1099 if( !IsTag && dlg.m_bSwitch )
1101 // it is a new branch and the user has requested to switch to it
1102 PerformSwitch(dlg.m_BranchTagName);
1105 return TRUE;
1107 return FALSE;
1110 bool CAppUtils::Switch(CString initialRefName)
1112 CGitSwitchDlg dlg;
1113 if(!initialRefName.IsEmpty())
1114 dlg.m_initialRefName = initialRefName;
1116 if (dlg.DoModal() == IDOK)
1118 CString branch;
1119 if (dlg.m_bBranch)
1120 branch = dlg.m_NewBranch;
1122 // if refs/heads/ is not stripped, checkout will detach HEAD
1123 // checkout prefers branches on name clashes (with tags)
1124 if (dlg.m_VersionName.Left(11) ==_T("refs/heads/") && dlg.m_bBranchOverride != TRUE)
1125 dlg.m_VersionName = dlg.m_VersionName.Mid(11);
1127 return PerformSwitch(dlg.m_VersionName, dlg.m_bForce == TRUE , branch, dlg.m_bBranchOverride == TRUE, dlg.m_bTrack, dlg.m_bMerge == TRUE);
1129 return FALSE;
1132 bool CAppUtils::PerformSwitch(CString ref, bool bForce /* false */, CString sNewBranch /* CString() */, bool bBranchOverride /* false */, BOOL bTrack /* 2 */, bool bMerge /* false */)
1134 CString cmd;
1135 CString track;
1136 CString force;
1137 CString branch;
1138 CString merge;
1140 if(!sNewBranch.IsEmpty()){
1141 if (bBranchOverride)
1143 branch.Format(_T("-B %s"), sNewBranch);
1145 else
1147 branch.Format(_T("-b %s"), sNewBranch);
1149 if (bTrack == TRUE)
1150 track = _T("--track");
1151 else if (bTrack == FALSE)
1152 track = _T("--no-track");
1154 if (bForce)
1155 force = _T("-f");
1156 if (bMerge)
1157 merge = _T("--merge");
1159 cmd.Format(_T("git.exe checkout %s %s %s %s %s --"),
1160 force,
1161 track,
1162 merge,
1163 branch,
1164 g_Git.FixBranchName(ref));
1166 CProgressDlg progress;
1167 progress.m_GitCmd = cmd;
1169 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1171 if (!status)
1173 CTGitPath gitPath = g_Git.m_CurrentDir;
1174 if (gitPath.HasSubmodules())
1176 postCmdList.push_back(PostCmd(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1178 CString sCmd;
1179 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), g_Git.m_CurrentDir);
1180 RunTortoiseGitProc(sCmd);
1181 }));
1183 CString currentBranch;
1184 bool hasBranch = CGit::GetCurrentBranchFromFile(g_Git.m_CurrentDir, currentBranch) == 0;
1185 if (hasBranch)
1186 postCmdList.push_back(PostCmd(IDI_MERGE, IDS_MENUMERGE, [&]{ Merge(&currentBranch); }));
1189 CString newBranch;
1190 if (!CGit::GetCurrentBranchFromFile(g_Git.m_CurrentDir, newBranch))
1191 postCmdList.push_back(PostCmd(IDI_PULL, IDS_MENUPULL, [&]{ Pull(); }));
1193 else
1195 postCmdList.push_back(PostCmd(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ PerformSwitch(ref, bForce, sNewBranch, bBranchOverride, bTrack, bMerge); }));
1196 if (!bMerge)
1197 postCmdList.push_back(PostCmd(IDI_SWITCH, IDS_SWITCH_WITH_MERGE, [&]{ PerformSwitch(ref, bForce, sNewBranch, bBranchOverride, bTrack, true); }));
1201 INT_PTR ret = progress.DoModal();
1203 return ret == IDOK;
1206 class CIgnoreFile : public CStdioFile
1208 public:
1209 STRING_VECTOR m_Items;
1210 CString m_eol;
1212 virtual BOOL ReadString(CString& rString)
1214 if (GetPosition() == 0)
1216 unsigned char utf8bom[] = { 0xEF, 0xBB, 0xBF };
1217 char buf[3] = { 0, 0, 0 };
1218 Read(buf, 3);
1219 if (memcpy(buf, utf8bom, sizeof(utf8bom)))
1221 SeekToBegin();
1225 CStringA strA;
1226 char lastChar = '\0';
1227 for (char c = '\0'; Read(&c, 1) == 1; lastChar = c)
1229 if (c == '\r')
1230 continue;
1231 if (c == '\n')
1233 m_eol = lastChar == '\r' ? _T("\r\n") : _T("\n");
1234 break;
1236 strA.AppendChar(c);
1238 if (strA.IsEmpty())
1239 return FALSE;
1241 rString = CUnicodeUtils::GetUnicode(strA);
1242 return TRUE;
1245 void ResetState()
1247 m_Items.clear();
1248 m_eol = _T("");
1252 bool CAppUtils::OpenIgnoreFile(CIgnoreFile &file, const CString& filename)
1254 file.ResetState();
1255 if (!file.Open(filename, CFile::modeCreate | CFile::modeReadWrite | CFile::modeNoTruncate | CFile::typeBinary))
1257 CMessageBox::Show(NULL, filename + _T(" Open Failure"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1258 return false;
1261 if (file.GetLength() > 0)
1263 CString fileText;
1264 while (file.ReadString(fileText))
1265 file.m_Items.push_back(fileText);
1266 file.Seek(file.GetLength() - 1, 0);
1267 char lastchar[1] = { 0 };
1268 file.Read(lastchar, 1);
1269 file.SeekToEnd();
1270 if (lastchar[0] != '\n')
1272 CStringA eol = CStringA(file.m_eol.IsEmpty() ? _T("\n") : file.m_eol);
1273 file.Write(eol, eol.GetLength());
1276 else
1277 file.SeekToEnd();
1279 return true;
1282 bool CAppUtils::IgnoreFile(CTGitPathList &path,bool IsMask)
1284 CIgnoreDlg ignoreDlg;
1285 if (ignoreDlg.DoModal() == IDOK)
1287 CString ignorefile;
1288 ignorefile = g_Git.m_CurrentDir + _T("\\");
1290 switch (ignoreDlg.m_IgnoreFile)
1292 case 0:
1293 ignorefile += _T(".gitignore");
1294 break;
1295 case 2:
1296 g_GitAdminDir.GetAdminDirPath(g_Git.m_CurrentDir, ignorefile);
1297 ignorefile += _T("info/exclude");
1298 break;
1301 CIgnoreFile file;
1304 if (ignoreDlg.m_IgnoreFile != 1 && !OpenIgnoreFile(file, ignorefile))
1305 return false;
1307 for (int i = 0; i < path.GetCount(); ++i)
1309 if (ignoreDlg.m_IgnoreFile == 1)
1311 ignorefile = g_Git.CombinePath(path[i].GetContainingDirectory()) + _T("\\.gitignore");
1312 if (!OpenIgnoreFile(file, ignorefile))
1313 return false;
1316 CString ignorePattern;
1317 if (ignoreDlg.m_IgnoreType == 0)
1319 if (ignoreDlg.m_IgnoreFile != 1 && !path[i].GetContainingDirectory().GetGitPathString().IsEmpty())
1320 ignorePattern += _T("/") + path[i].GetContainingDirectory().GetGitPathString();
1322 ignorePattern += _T("/");
1324 if (IsMask)
1326 ignorePattern += _T("*") + path[i].GetFileExtension();
1328 else
1330 ignorePattern += path[i].GetFileOrDirectoryName();
1333 // escape [ and ] so that files get ignored correctly
1334 ignorePattern.Replace(_T("["), _T("\\["));
1335 ignorePattern.Replace(_T("]"), _T("\\]"));
1337 bool found = false;
1338 for (size_t j = 0; j < file.m_Items.size(); ++j)
1340 if (file.m_Items[j] == ignorePattern)
1342 found = true;
1343 break;
1346 if (!found)
1348 file.m_Items.push_back(ignorePattern);
1349 ignorePattern += file.m_eol.IsEmpty() ? _T("\n") : file.m_eol;
1350 CStringA ignorePatternA = CUnicodeUtils::GetUTF8(ignorePattern);
1351 file.Write(ignorePatternA, ignorePatternA.GetLength());
1354 if (ignoreDlg.m_IgnoreFile == 1)
1355 file.Close();
1358 if (ignoreDlg.m_IgnoreFile != 1)
1359 file.Close();
1361 catch(...)
1363 file.Abort();
1364 return false;
1367 return true;
1369 return false;
1372 static bool Reset(const CString& resetTo, int resetType)
1374 CString cmd;
1375 CString type;
1376 switch (resetType)
1378 case 0:
1379 type = _T("--soft");
1380 break;
1381 case 1:
1382 type = _T("--mixed");
1383 break;
1384 case 2:
1385 type = _T("--hard");
1386 break;
1387 default:
1388 resetType = 1;
1389 type = _T("--mixed");
1390 break;
1392 cmd.Format(_T("git.exe reset %s %s --"), type, resetTo);
1394 CProgressDlg progress;
1395 progress.m_GitCmd = cmd;
1397 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1399 if (status)
1401 postCmdList.push_back(PostCmd(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ Reset(resetTo, resetType); }));
1402 return;
1405 CTGitPath gitPath = g_Git.m_CurrentDir;
1406 if (gitPath.HasSubmodules() && resetType == 2)
1408 postCmdList.push_back(PostCmd(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1410 CString sCmd;
1411 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), g_Git.m_CurrentDir);
1412 CAppUtils::RunTortoiseGitProc(sCmd);
1413 }));
1417 INT_PTR ret;
1418 if (g_Git.UsingLibGit2(CGit::GIT_CMD_RESET))
1420 CGitProgressDlg gitdlg;
1421 ResetProgressCommand resetProgressCommand;
1422 gitdlg.SetCommand(&resetProgressCommand);
1423 resetProgressCommand.m_PostCmdCallback = progress.m_PostCmdCallback;
1424 resetProgressCommand.SetRevision(resetTo);
1425 resetProgressCommand.SetResetType(resetType);
1426 ret = gitdlg.DoModal();
1428 else
1429 ret = progress.DoModal();
1431 return ret == IDOK;
1434 bool CAppUtils::GitReset(CString *CommitHash,int type)
1436 CResetDlg dlg;
1437 dlg.m_ResetType=type;
1438 dlg.m_ResetToVersion=*CommitHash;
1439 dlg.m_initialRefName = *CommitHash;
1440 if (dlg.DoModal() == IDOK)
1441 return Reset(dlg.m_ResetToVersion, dlg.m_ResetType);
1443 return false;
1446 void CAppUtils::DescribeConflictFile(bool mode, bool base,CString &descript)
1448 if(mode == FALSE)
1450 descript = CString(MAKEINTRESOURCE(IDS_SVNACTION_DELETE));
1451 return;
1453 if(base)
1455 descript = CString(MAKEINTRESOURCE(IDS_SVNACTION_MODIFIED));
1456 return;
1458 descript = CString(MAKEINTRESOURCE(IDS_PROC_CREATED));
1459 return;
1462 void CAppUtils::RemoveTempMergeFile(CTGitPath &path)
1464 ::DeleteFile(CAppUtils::GetMergeTempFile(_T("LOCAL"), path));
1465 ::DeleteFile(CAppUtils::GetMergeTempFile(_T("REMOTE"), path));
1466 ::DeleteFile(CAppUtils::GetMergeTempFile(_T("BASE"), path));
1468 CString CAppUtils::GetMergeTempFile(CString type,CTGitPath &merge)
1470 CString file;
1471 file = g_Git.CombinePath(merge.GetWinPathString() + _T(".") + type + merge.GetFileExtension());
1473 return file;
1476 bool ParseHashesFromLsFile(BYTE_VECTOR &out, CString &hash1, CString &hash2, CString &hash3)
1478 unsigned int pos = 0;
1479 CString one;
1480 CString part;
1482 while (pos >= 0 && pos < out.size())
1484 one.Empty();
1486 g_Git.StringAppend(&one, &out[pos], CP_UTF8);
1487 int tabstart = 0;
1488 one.Tokenize(_T("\t"), tabstart);
1490 tabstart = 0;
1491 part = one.Tokenize(_T(" "), tabstart); //Tag
1492 part = one.Tokenize(_T(" "), tabstart); //Mode
1493 part = one.Tokenize(_T(" "), tabstart); //Hash
1494 CString hash = part;
1495 part = one.Tokenize(_T("\t"), tabstart); //Stage
1496 int stage = _ttol(part);
1497 if (stage == 1)
1498 hash1 = hash;
1499 else if (stage == 2)
1500 hash2 = hash;
1501 else if (stage == 3)
1503 hash3 = hash;
1504 return true;
1507 pos = out.findNextString(pos);
1510 return false;
1513 bool CAppUtils::ConflictEdit(CTGitPath& path, bool /*bAlternativeTool = false*/, bool revertTheirMy /*= false*/, HWND resolveMsgHwnd /*= nullptr*/)
1515 bool bRet = false;
1517 CTGitPath merge=path;
1518 CTGitPath directory = merge.GetDirectory();
1520 // we have the conflicted file (%merged)
1521 // now look for the other required files
1522 //GitStatus stat;
1523 //stat.GetStatus(merge);
1524 //if (stat.status == NULL)
1525 // return false;
1527 BYTE_VECTOR vector;
1529 CString cmd;
1530 cmd.Format(_T("git.exe ls-files -u -t -z -- \"%s\""),merge.GetGitPathString());
1532 if (g_Git.Run(cmd, &vector))
1534 return FALSE;
1537 if (merge.IsDirectory())
1539 CString baseHash, localHash, remoteHash;
1540 if (!ParseHashesFromLsFile(vector, baseHash, localHash, remoteHash))
1541 return FALSE;
1543 CString msg;
1544 msg.Format(_T("BASE: %s\nLOCAL: %s\nREMOTE: %s"), baseHash, localHash, remoteHash);
1545 CMessageBox::Show(NULL, msg, _T("TortoiseGit"), MB_OK);
1546 return TRUE;
1549 CTGitPathList list;
1550 if (list.ParserFromLsFile(vector))
1552 CMessageBox::Show(NULL, _T("Parse ls-files failed!"), _T("TortoiseGit"), MB_OK);
1553 return FALSE;
1556 if (list.IsEmpty())
1557 return FALSE;
1559 CTGitPath theirs;
1560 CTGitPath mine;
1561 CTGitPath base;
1563 if (revertTheirMy)
1565 mine.SetFromGit(GetMergeTempFile(_T("REMOTE"), merge));
1566 theirs.SetFromGit(GetMergeTempFile(_T("LOCAL"), merge));
1568 else
1570 mine.SetFromGit(GetMergeTempFile(_T("LOCAL"), merge));
1571 theirs.SetFromGit(GetMergeTempFile(_T("REMOTE"), merge));
1573 base.SetFromGit(GetMergeTempFile(_T("BASE"),merge));
1575 CString format;
1577 //format=_T("git.exe cat-file blob \":%d:%s\"");
1578 format = _T("git.exe checkout-index --temp --stage=%d -- \"%s\"");
1579 CFile tempfile;
1580 //create a empty file, incase stage is not three
1581 tempfile.Open(mine.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1582 tempfile.Close();
1583 tempfile.Open(theirs.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1584 tempfile.Close();
1585 tempfile.Open(base.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1586 tempfile.Close();
1588 bool b_base=false, b_local=false, b_remote=false;
1590 for (int i = 0; i< list.GetCount(); ++i)
1592 CString cmd;
1593 CString outfile;
1594 cmd.Empty();
1595 outfile.Empty();
1597 if( list[i].m_Stage == 1)
1599 cmd.Format(format, list[i].m_Stage, list[i].GetGitPathString());
1600 b_base = true;
1601 outfile = base.GetWinPathString();
1604 if( list[i].m_Stage == 2 )
1606 cmd.Format(format, list[i].m_Stage, list[i].GetGitPathString());
1607 b_local = true;
1608 outfile = mine.GetWinPathString();
1611 if( list[i].m_Stage == 3 )
1613 cmd.Format(format, list[i].m_Stage, list[i].GetGitPathString());
1614 b_remote = true;
1615 outfile = theirs.GetWinPathString();
1617 CString output, err;
1618 if(!outfile.IsEmpty())
1619 if (!g_Git.Run(cmd, &output, &err, CP_UTF8))
1621 CString file;
1622 int start =0 ;
1623 file = output.Tokenize(_T("\t"), start);
1624 ::MoveFileEx(file,outfile,MOVEFILE_REPLACE_EXISTING|MOVEFILE_COPY_ALLOWED);
1626 else
1628 CMessageBox::Show(NULL, output + L"\n" + err, _T("TortoiseGit"), MB_OK|MB_ICONERROR);
1632 if(b_local && b_remote )
1634 merge.SetFromWin(g_Git.CombinePath(merge));
1635 if( revertTheirMy )
1636 bRet = !!CAppUtils::StartExtMerge(base, mine, theirs, merge, _T("BASE"), _T("REMOTE"), _T("LOCAL"), CString(), false, resolveMsgHwnd);
1637 else
1638 bRet = !!CAppUtils::StartExtMerge(base, theirs, mine, merge, _T("BASE"), _T("REMOTE"), _T("LOCAL"), CString(), false, resolveMsgHwnd);
1641 else
1643 ::DeleteFile(mine.GetWinPathString());
1644 ::DeleteFile(theirs.GetWinPathString());
1645 ::DeleteFile(base.GetWinPathString());
1647 CDeleteConflictDlg dlg;
1648 DescribeConflictFile(b_local, b_base,dlg.m_LocalStatus);
1649 DescribeConflictFile(b_remote,b_base,dlg.m_RemoteStatus);
1650 CGitHash localHash, remoteHash;
1651 if (!g_Git.GetHash(localHash, _T("HEAD")))
1652 dlg.m_LocalHash = localHash.ToString();
1653 if (!g_Git.GetHash(remoteHash, _T("MERGE_HEAD")))
1654 dlg.m_RemoteHash = remoteHash.ToString();
1655 else if (!g_Git.GetHash(remoteHash, _T("rebase-apply/original-commit")))
1656 dlg.m_RemoteHash = remoteHash.ToString();
1657 else if (!g_Git.GetHash(remoteHash, _T("CHERRY_PICK_HEAD")))
1658 dlg.m_RemoteHash = remoteHash.ToString();
1659 else if (!g_Git.GetHash(remoteHash, _T("REVERT_HEAD")))
1660 dlg.m_RemoteHash = remoteHash.ToString();
1661 dlg.m_bShowModifiedButton=b_base;
1662 dlg.m_File=merge.GetGitPathString();
1663 if(dlg.DoModal() == IDOK)
1665 CString cmd,out;
1666 if(dlg.m_bIsDelete)
1668 cmd.Format(_T("git.exe rm -- \"%s\""),merge.GetGitPathString());
1670 else
1671 cmd.Format(_T("git.exe add -- \"%s\""),merge.GetGitPathString());
1673 if (g_Git.Run(cmd, &out, CP_UTF8))
1675 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
1676 return FALSE;
1678 return TRUE;
1680 else
1681 return FALSE;
1684 #if 0
1685 CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool),
1686 base, theirs, mine, merge);
1687 #endif
1688 #if 0
1689 if (stat.status->text_status == svn_wc_status_conflicted)
1691 // we have a text conflict, use our merge tool to resolve the conflict
1693 CTSVNPath theirs(directory);
1694 CTSVNPath mine(directory);
1695 CTSVNPath base(directory);
1696 bool bConflictData = false;
1698 if ((stat.status->entry)&&(stat.status->entry->conflict_new))
1700 theirs.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->conflict_new));
1701 bConflictData = true;
1703 if ((stat.status->entry)&&(stat.status->entry->conflict_old))
1705 base.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->conflict_old));
1706 bConflictData = true;
1708 if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
1710 mine.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->conflict_wrk));
1711 bConflictData = true;
1713 else
1715 mine = merge;
1717 if (bConflictData)
1718 bRet = !!CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool),
1719 base, theirs, mine, merge);
1722 if (stat.status->prop_status == svn_wc_status_conflicted)
1724 // we have a property conflict
1725 CTSVNPath prej(directory);
1726 if ((stat.status->entry)&&(stat.status->entry->prejfile))
1728 prej.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->prejfile));
1729 // there's a problem: the prej file contains a _description_ of the conflict, and
1730 // that description string might be translated. That means we have no way of parsing
1731 // the file to find out the conflicting values.
1732 // The only thing we can do: show a dialog with the conflict description, then
1733 // let the user either accept the existing property or open the property edit dialog
1734 // to manually change the properties and values. And a button to mark the conflict as
1735 // resolved.
1736 CEditPropConflictDlg dlg;
1737 dlg.SetPrejFile(prej);
1738 dlg.SetConflictedItem(merge);
1739 bRet = (dlg.DoModal() != IDCANCEL);
1743 if (stat.status->tree_conflict)
1745 // we have a tree conflict
1746 SVNInfo info;
1747 const SVNInfoData * pInfoData = info.GetFirstFileInfo(merge, SVNRev(), SVNRev());
1748 if (pInfoData)
1750 if (pInfoData->treeconflict_kind == svn_wc_conflict_kind_text)
1752 CTSVNPath theirs(directory);
1753 CTSVNPath mine(directory);
1754 CTSVNPath base(directory);
1755 bool bConflictData = false;
1757 if (pInfoData->treeconflict_theirfile)
1759 theirs.AppendPathString(pInfoData->treeconflict_theirfile);
1760 bConflictData = true;
1762 if (pInfoData->treeconflict_basefile)
1764 base.AppendPathString(pInfoData->treeconflict_basefile);
1765 bConflictData = true;
1767 if (pInfoData->treeconflict_myfile)
1769 mine.AppendPathString(pInfoData->treeconflict_myfile);
1770 bConflictData = true;
1772 else
1774 mine = merge;
1776 if (bConflictData)
1777 bRet = !!CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool),
1778 base, theirs, mine, merge);
1780 else if (pInfoData->treeconflict_kind == svn_wc_conflict_kind_tree)
1782 CString sConflictAction;
1783 CString sConflictReason;
1784 CString sResolveTheirs;
1785 CString sResolveMine;
1786 CTSVNPath treeConflictPath = CTSVNPath(pInfoData->treeconflict_path);
1787 CString sItemName = treeConflictPath.GetUIFileOrDirectoryName();
1789 if (pInfoData->treeconflict_nodekind == svn_node_file)
1791 switch (pInfoData->treeconflict_operation)
1793 case svn_wc_operation_update:
1794 switch (pInfoData->treeconflict_action)
1796 case svn_wc_conflict_action_edit:
1797 sConflictAction.Format(IDS_TREECONFLICT_FILEUPDATEEDIT, (LPCTSTR)sItemName);
1798 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
1799 break;
1800 case svn_wc_conflict_action_add:
1801 sConflictAction.Format(IDS_TREECONFLICT_FILEUPDATEADD, (LPCTSTR)sItemName);
1802 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
1803 break;
1804 case svn_wc_conflict_action_delete:
1805 sConflictAction.Format(IDS_TREECONFLICT_FILEUPDATEDELETE, (LPCTSTR)sItemName);
1806 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
1807 break;
1809 break;
1810 case svn_wc_operation_switch:
1811 switch (pInfoData->treeconflict_action)
1813 case svn_wc_conflict_action_edit:
1814 sConflictAction.Format(IDS_TREECONFLICT_FILESWITCHEDIT, (LPCTSTR)sItemName);
1815 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
1816 break;
1817 case svn_wc_conflict_action_add:
1818 sConflictAction.Format(IDS_TREECONFLICT_FILESWITCHADD, (LPCTSTR)sItemName);
1819 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
1820 break;
1821 case svn_wc_conflict_action_delete:
1822 sConflictAction.Format(IDS_TREECONFLICT_FILESWITCHDELETE, (LPCTSTR)sItemName);
1823 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
1824 break;
1826 break;
1827 case svn_wc_operation_merge:
1828 switch (pInfoData->treeconflict_action)
1830 case svn_wc_conflict_action_edit:
1831 sConflictAction.Format(IDS_TREECONFLICT_FILEMERGEEDIT, (LPCTSTR)sItemName);
1832 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
1833 break;
1834 case svn_wc_conflict_action_add:
1835 sResolveTheirs.Format(IDS_TREECONFLICT_FILEMERGEADD, (LPCTSTR)sItemName);
1836 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
1837 break;
1838 case svn_wc_conflict_action_delete:
1839 sConflictAction.Format(IDS_TREECONFLICT_FILEMERGEDELETE, (LPCTSTR)sItemName);
1840 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
1841 break;
1843 break;
1846 else if (pInfoData->treeconflict_nodekind == svn_node_dir)
1848 switch (pInfoData->treeconflict_operation)
1850 case svn_wc_operation_update:
1851 switch (pInfoData->treeconflict_action)
1853 case svn_wc_conflict_action_edit:
1854 sConflictAction.Format(IDS_TREECONFLICT_DIRUPDATEEDIT, (LPCTSTR)sItemName);
1855 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
1856 break;
1857 case svn_wc_conflict_action_add:
1858 sConflictAction.Format(IDS_TREECONFLICT_DIRUPDATEADD, (LPCTSTR)sItemName);
1859 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
1860 break;
1861 case svn_wc_conflict_action_delete:
1862 sConflictAction.Format(IDS_TREECONFLICT_DIRUPDATEDELETE, (LPCTSTR)sItemName);
1863 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR);
1864 break;
1866 break;
1867 case svn_wc_operation_switch:
1868 switch (pInfoData->treeconflict_action)
1870 case svn_wc_conflict_action_edit:
1871 sConflictAction.Format(IDS_TREECONFLICT_DIRSWITCHEDIT, (LPCTSTR)sItemName);
1872 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
1873 break;
1874 case svn_wc_conflict_action_add:
1875 sConflictAction.Format(IDS_TREECONFLICT_DIRSWITCHADD, (LPCTSTR)sItemName);
1876 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
1877 break;
1878 case svn_wc_conflict_action_delete:
1879 sConflictAction.Format(IDS_TREECONFLICT_DIRSWITCHDELETE, (LPCTSTR)sItemName);
1880 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR);
1881 break;
1883 break;
1884 case svn_wc_operation_merge:
1885 switch (pInfoData->treeconflict_action)
1887 case svn_wc_conflict_action_edit:
1888 sConflictAction.Format(IDS_TREECONFLICT_DIRMERGEEDIT, (LPCTSTR)sItemName);
1889 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
1890 break;
1891 case svn_wc_conflict_action_add:
1892 sConflictAction.Format(IDS_TREECONFLICT_DIRMERGEADD, (LPCTSTR)sItemName);
1893 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
1894 break;
1895 case svn_wc_conflict_action_delete:
1896 sConflictAction.Format(IDS_TREECONFLICT_DIRMERGEDELETE, (LPCTSTR)sItemName);
1897 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR);
1898 break;
1900 break;
1904 UINT uReasonID = 0;
1905 switch (pInfoData->treeconflict_reason)
1907 case svn_wc_conflict_reason_edited:
1908 uReasonID = IDS_TREECONFLICT_REASON_EDITED;
1909 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
1910 break;
1911 case svn_wc_conflict_reason_obstructed:
1912 uReasonID = IDS_TREECONFLICT_REASON_OBSTRUCTED;
1913 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
1914 break;
1915 case svn_wc_conflict_reason_deleted:
1916 uReasonID = IDS_TREECONFLICT_REASON_DELETED;
1917 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_REMOVEDIR : IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
1918 break;
1919 case svn_wc_conflict_reason_added:
1920 uReasonID = IDS_TREECONFLICT_REASON_ADDED;
1921 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
1922 break;
1923 case svn_wc_conflict_reason_missing:
1924 uReasonID = IDS_TREECONFLICT_REASON_MISSING;
1925 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_REMOVEDIR : IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
1926 break;
1927 case svn_wc_conflict_reason_unversioned:
1928 uReasonID = IDS_TREECONFLICT_REASON_UNVERSIONED;
1929 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
1930 break;
1932 sConflictReason.Format(uReasonID, (LPCTSTR)sConflictAction);
1934 CTreeConflictEditorDlg dlg;
1935 dlg.SetConflictInfoText(sConflictReason);
1936 dlg.SetResolveTexts(sResolveTheirs, sResolveMine);
1937 dlg.SetPath(treeConflictPath);
1938 INT_PTR dlgRet = dlg.DoModal();
1939 bRet = (dlgRet != IDCANCEL);
1943 #endif
1944 return bRet;
1947 bool CAppUtils::IsSSHPutty()
1949 CString sshclient=g_Git.m_Environment.GetEnv(_T("GIT_SSH"));
1950 sshclient=sshclient.MakeLower();
1951 if(sshclient.Find(_T("plink.exe"),0)>=0)
1953 return true;
1955 return false;
1958 CString CAppUtils::GetClipboardLink(const CString &skipGitPrefix, int paramsCount)
1960 if (!OpenClipboard(NULL))
1961 return CString();
1963 CString sClipboardText;
1964 HGLOBAL hglb = GetClipboardData(CF_TEXT);
1965 if (hglb)
1967 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
1968 sClipboardText = CString(lpstr);
1969 GlobalUnlock(hglb);
1971 hglb = GetClipboardData(CF_UNICODETEXT);
1972 if (hglb)
1974 LPCTSTR lpstr = (LPCTSTR)GlobalLock(hglb);
1975 sClipboardText = lpstr;
1976 GlobalUnlock(hglb);
1978 CloseClipboard();
1980 if(!sClipboardText.IsEmpty())
1982 if(sClipboardText[0] == _T('\"') && sClipboardText[sClipboardText.GetLength()-1] == _T('\"'))
1983 sClipboardText=sClipboardText.Mid(1,sClipboardText.GetLength()-2);
1985 if(sClipboardText.Find( _T("http://")) == 0)
1986 return sClipboardText;
1988 if(sClipboardText.Find( _T("https://")) == 0)
1989 return sClipboardText;
1991 if(sClipboardText.Find( _T("git://")) == 0)
1992 return sClipboardText;
1994 if(sClipboardText.Find( _T("ssh://")) == 0)
1995 return sClipboardText;
1997 if(sClipboardText.GetLength()>=2)
1998 if( sClipboardText[1] == _T(':') )
1999 if( (sClipboardText[0] >= 'A' && sClipboardText[0] <= 'Z')
2000 || (sClipboardText[0] >= 'a' && sClipboardText[0] <= 'z') )
2001 return sClipboardText;
2003 // trim prefixes like "git clone "
2004 if (!skipGitPrefix.IsEmpty() && sClipboardText.Find(skipGitPrefix) == 0)
2006 sClipboardText = sClipboardText.Mid(skipGitPrefix.GetLength()).Trim();
2007 int spacePos = -1;
2008 while (paramsCount >= 0)
2010 --paramsCount;
2011 spacePos = sClipboardText.Find(_T(' '), spacePos + 1);
2012 if (spacePos == -1)
2013 break;
2015 if (spacePos > 0 && paramsCount < 0)
2016 sClipboardText = sClipboardText.Left(spacePos);
2017 return sClipboardText;
2021 return CString(_T(""));
2024 CString CAppUtils::ChooseRepository(CString *path)
2026 CBrowseFolder browseFolder;
2027 CRegString regLastResopitory = CRegString(_T("Software\\TortoiseGit\\TortoiseProc\\LastRepo"),_T(""));
2029 browseFolder.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
2030 CString strCloneDirectory;
2031 if(path)
2032 strCloneDirectory=*path;
2033 else
2035 strCloneDirectory = regLastResopitory;
2038 CString title;
2039 title.LoadString(IDS_CHOOSE_REPOSITORY);
2041 browseFolder.SetInfo(title);
2043 if (browseFolder.Show(NULL, strCloneDirectory) == CBrowseFolder::OK)
2045 regLastResopitory = strCloneDirectory;
2046 return strCloneDirectory;
2048 else
2050 return CString();
2054 bool CAppUtils::SendPatchMail(CTGitPathList& list)
2056 CSendMailDlg dlg;
2058 dlg.m_PathList = list;
2060 if(dlg.DoModal()==IDOK)
2062 if (dlg.m_PathList.IsEmpty())
2063 return FALSE;
2065 CGitProgressDlg progDlg;
2067 theApp.m_pMainWnd = &progDlg;
2068 SendMailProgressCommand sendMailProgressCommand;
2069 progDlg.SetCommand(&sendMailProgressCommand);
2071 sendMailProgressCommand.SetPathList(dlg.m_PathList);
2072 progDlg.SetItemCount(dlg.m_PathList.GetCount());
2074 CSendMailPatch sendMailPatch(dlg.m_To, dlg.m_CC, dlg.m_Subject, !!dlg.m_bAttachment, !!dlg.m_bCombine);
2075 sendMailProgressCommand.SetSendMailOption(&sendMailPatch);
2077 progDlg.DoModal();
2079 return true;
2081 return false;
2084 bool CAppUtils::SendPatchMail(CString &cmd,CString &formatpatchoutput)
2086 CTGitPathList list;
2087 CString log=formatpatchoutput;
2088 int start=log.Find(cmd);
2089 if(start >=0)
2090 CString one=log.Tokenize(_T("\n"),start);
2091 else
2092 start = 0;
2094 while(start>=0)
2096 CString one=log.Tokenize(_T("\n"),start);
2097 one=one.Trim();
2098 if(one.IsEmpty() || one.Find(_T("Success")) == 0)
2099 continue;
2100 one.Replace(_T('/'),_T('\\'));
2101 CTGitPath path;
2102 path.SetFromWin(one);
2103 list.AddPath(path);
2105 if (!list.IsEmpty())
2107 return SendPatchMail(list);
2109 else
2111 CMessageBox::Show(NULL, IDS_ERR_NOPATCHES, IDS_APPNAME, MB_ICONINFORMATION);
2112 return true;
2117 int CAppUtils::GetLogOutputEncode(CGit *pGit)
2119 CString output;
2120 output = pGit->GetConfigValue(_T("i18n.logOutputEncoding"));
2121 if(output.IsEmpty())
2122 return CUnicodeUtils::GetCPCode(pGit->GetConfigValue(_T("i18n.commitencoding")));
2123 else
2125 return CUnicodeUtils::GetCPCode(output);
2128 int CAppUtils::SaveCommitUnicodeFile(CString &filename, CString &message)
2130 CFile file(filename,CFile::modeReadWrite|CFile::modeCreate );
2131 int cp = CUnicodeUtils::GetCPCode(g_Git.GetConfigValue(_T("i18n.commitencoding")));
2133 bool stripComments = (CRegDWORD(_T("Software\\TortoiseGit\\StripCommentedLines"), FALSE) == TRUE);
2135 if (CRegDWORD(_T("Software\\TortoiseGit\\SanitizeCommitMsg"), TRUE) == TRUE)
2136 message.TrimRight(L" \r\n");
2138 int len = message.GetLength();
2139 int start = 0;
2140 while (start >= 0 && start < len)
2142 int oldStart = start;
2143 start = message.Find(L"\n", oldStart);
2144 CString line = message.Mid(oldStart);
2145 if (start != -1)
2147 line = line.Left(start - oldStart);
2148 ++start; // move forward so we don't find the same char again
2150 if (stripComments && (line.GetLength() >= 1 && line.GetAt(0) == '#') || (start < 0 && line.IsEmpty()))
2151 continue;
2152 line.TrimRight(L" \r");
2153 CStringA lineA = CUnicodeUtils::GetMulti(line + L"\n", cp);
2154 file.Write(lineA.GetBuffer(), lineA.GetLength());
2156 file.Close();
2157 return 0;
2160 bool CAppUtils::Pull(bool showPush)
2162 CPullFetchDlg dlg;
2163 dlg.m_IsPull = TRUE;
2164 if (dlg.DoModal() == IDOK)
2166 CString url = dlg.m_RemoteURL;
2168 if (dlg.m_bAutoLoad)
2170 CAppUtils::LaunchPAgent(NULL, &dlg.m_RemoteURL);
2173 CString cmd;
2174 CGitHash hashOld;
2175 if (g_Git.GetHash(hashOld, _T("HEAD")))
2177 MessageBox(NULL, g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
2178 return false;
2181 CString cmdRebase;
2182 CString noff;
2183 CString ffonly;
2184 CString squash;
2185 CString nocommit;
2186 CString depth;
2187 CString notags;
2188 CString prune;
2190 if (dlg.m_bRebase)
2191 cmdRebase = "--rebase ";
2193 if (!dlg.m_bFetchTags)
2194 notags = _T("--no-tags");
2196 if (dlg.m_bNoFF)
2197 noff=_T("--no-ff");
2199 if (dlg.m_bFFonly)
2200 ffonly = _T("--ff-only");
2202 if (dlg.m_bSquash)
2203 squash = _T("--squash");
2205 if (dlg.m_bNoCommit)
2206 nocommit = _T("--no-commit");
2208 if (dlg.m_bDepth)
2209 depth.Format(_T("--depth %d "), dlg.m_nDepth);
2211 int ver = CAppUtils::GetMsysgitVersion();
2213 if (dlg.m_bPrune == TRUE)
2214 prune = _T("--prune ");
2215 else if (dlg.m_bPrune == FALSE && ver >= 0x01080500)
2216 prune = _T("--no-prune ");
2218 if(ver >= 0x01070203) //above 1.7.0.2
2219 cmdRebase += _T("--progress ");
2221 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);
2222 CProgressDlg progress;
2223 progress.m_GitCmd = cmd;
2225 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2227 if (status)
2229 postCmdList.push_back(PostCmd(IDI_PULL, IDS_MENUPULL, [&]{ Pull(); }));
2230 return;
2233 CGitHash hashNew;
2234 if (g_Git.GetHash(hashNew, _T("HEAD")))
2235 MessageBox(nullptr, g_Git.GetGitLastErr(_T("Could not get HEAD hash after pulling.")), _T("TortoiseGit"), MB_ICONERROR);
2236 else
2238 postCmdList.push_back(PostCmd(IDI_DIFF, IDS_PROC_PULL_DIFFS, [&]
2240 CFileDiffDlg dlg;
2241 dlg.SetDiff(NULL, hashNew.ToString(), hashOld.ToString());
2242 dlg.DoModal();
2243 }));
2244 postCmdList.push_back(PostCmd(IDI_LOG, IDS_PROC_PULL_LOG, [&]
2246 CLogDlg dlg;
2247 dlg.SetParams(CTGitPath(_T("")), CTGitPath(_T("")), _T(""), hashOld.ToString() + _T("..") + hashNew.ToString(), 0);
2248 dlg.DoModal();
2249 }));
2252 if (showPush)
2253 postCmdList.push_back(PostCmd(IDI_PUSH, IDS_MENUPUSH, []{ Push(_T("")); }));
2255 CTGitPath gitPath = g_Git.m_CurrentDir;
2256 if (gitPath.HasSubmodules())
2258 postCmdList.push_back(PostCmd(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
2260 CString sCmd;
2261 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), g_Git.m_CurrentDir);
2262 CAppUtils::RunTortoiseGitProc(sCmd);
2263 }));
2267 INT_PTR ret = progress.DoModal();
2269 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)
2271 CChangedDlg dlg;
2272 dlg.m_pathList.AddPath(CTGitPath());
2273 dlg.DoModal();
2275 return true;
2278 return ret == IDOK;
2281 return false;
2284 static bool RebaseAfterFetch()
2286 while (true)
2288 CRebaseDlg dlg;
2289 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENULOG)));
2290 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUDESSENDMAIL)));
2291 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUREBASE)));
2292 INT_PTR response = dlg.DoModal();
2293 if (response == IDOK)
2295 return true;
2297 else if (response == IDC_REBASE_POST_BUTTON)
2299 CString cmd = _T("/command:log");
2300 cmd += _T(" /path:\"") + g_Git.m_CurrentDir + _T("\"");
2301 CAppUtils::RunTortoiseGitProc(cmd);
2302 return true;
2304 else if (response == IDC_REBASE_POST_BUTTON + 1)
2306 CString cmd, out, err;
2307 cmd.Format(_T("git.exe format-patch -o \"%s\" %s..%s"),
2308 g_Git.m_CurrentDir,
2309 g_Git.FixBranchName(dlg.m_Upstream),
2310 g_Git.FixBranchName(dlg.m_Branch));
2311 if (g_Git.Run(cmd, &out, &err, CP_UTF8))
2313 CMessageBox::Show(NULL, out + L"\n" + err, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
2314 return false;
2316 CAppUtils::SendPatchMail(cmd, out);
2317 return true;
2319 else if (response == IDC_REBASE_POST_BUTTON + 2)
2320 continue;
2321 else if (response == IDCANCEL)
2322 return false;
2323 return false;
2327 static bool DoFetch(const CString& url, const bool fetchAllRemotes, const bool loadPuttyAgent, const int prune, const bool bDepth, const int nDepth, const int fetchTags, const CString& remoteBranch, const boolean runRebase)
2329 if (loadPuttyAgent)
2331 if (fetchAllRemotes)
2333 STRING_VECTOR list;
2334 g_Git.GetRemoteList(list);
2336 STRING_VECTOR::const_iterator it = list.begin();
2337 while (it != list.end())
2339 CString remote(*it);
2340 CAppUtils::LaunchPAgent(NULL, &remote);
2341 ++it;
2344 else
2345 CAppUtils::LaunchPAgent(NULL, &url);
2348 CString cmd, arg;
2349 int ver = CAppUtils::GetMsysgitVersion();
2351 if (bDepth)
2352 arg.AppendFormat(_T(" --depth %d"), nDepth);
2354 if (prune == TRUE)
2355 arg += _T(" --prune");
2356 else if (prune == FALSE && ver >= 0x01080500)
2357 arg += _T(" --no-prune");
2359 if (fetchTags == 1)
2360 arg += _T(" --tags");
2361 else if (fetchTags == 0)
2362 arg += _T(" --no-tags");
2364 if (fetchAllRemotes)
2365 cmd.Format(_T("git.exe fetch --all -v%s"), arg);
2366 else
2367 cmd.Format(_T("git.exe fetch -v%s \"%s\" %s"), arg, url, remoteBranch);
2369 CProgressDlg progress;
2370 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2372 if (status)
2374 postCmdList.push_back(PostCmd(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ DoFetch(url, fetchAllRemotes, loadPuttyAgent, prune, bDepth, nDepth, fetchTags, remoteBranch, runRebase); }));
2375 return;
2378 postCmdList.push_back(PostCmd(IDI_LOG, IDS_MENULOG, []
2380 CString cmd = _T("/command:log");
2381 cmd += _T(" /path:\"") + g_Git.m_CurrentDir + _T("\"");
2382 CAppUtils::RunTortoiseGitProc(cmd);
2383 }));
2385 postCmdList.push_back(PostCmd(IDS_PROC_RESET, []
2387 CString pullRemote, pullBranch;
2388 g_Git.GetRemoteTrackedBranchForHEAD(pullRemote, pullBranch);
2389 CString defaultUpstream;
2390 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
2391 defaultUpstream.Format(_T("remotes/%s/%s"), pullRemote, pullBranch);
2392 CAppUtils::GitReset(&defaultUpstream, 2);
2393 }));
2395 postCmdList.push_back(PostCmd(IDI_PULL, IDS_MENUFETCH, []{ CAppUtils::Fetch(); }));
2397 if (!runRebase && !g_GitAdminDir.IsBareRepo(g_Git.m_CurrentDir))
2398 postCmdList.push_back(PostCmd(IDI_REBASE, IDS_MENUREBASE, []{ RebaseAfterFetch(); }));
2401 progress.m_GitCmd = cmd;
2402 INT_PTR userResponse;
2404 if (g_Git.UsingLibGit2(CGit::GIT_CMD_FETCH))
2406 CGitProgressDlg gitdlg;
2407 FetchProgressCommand fetchProgressCommand;
2408 if (!fetchAllRemotes)
2409 fetchProgressCommand.SetUrl(url);
2410 gitdlg.SetCommand(&fetchProgressCommand);
2411 fetchProgressCommand.m_PostCmdCallback = progress.m_PostCmdCallback;
2412 fetchProgressCommand.SetAutoTag(fetchTags == 1 ? GIT_REMOTE_DOWNLOAD_TAGS_ALL : fetchTags == 2 ? GIT_REMOTE_DOWNLOAD_TAGS_AUTO : GIT_REMOTE_DOWNLOAD_TAGS_NONE);
2413 if (!fetchAllRemotes)
2414 fetchProgressCommand.SetRefSpec(remoteBranch);
2415 userResponse = gitdlg.DoModal();
2416 return userResponse == IDOK;
2419 userResponse = progress.DoModal();
2420 if (!progress.m_GitStatus)
2422 if (runRebase)
2423 return RebaseAfterFetch();
2426 return userResponse == IDOK;
2429 bool CAppUtils::Fetch(CString remoteName, bool allowRebase, bool allRemotes)
2431 CPullFetchDlg dlg;
2432 dlg.m_PreSelectRemote = remoteName;
2433 dlg.m_bAllowRebase = allowRebase;
2434 dlg.m_IsPull=FALSE;
2435 dlg.m_bAllRemotes = allRemotes;
2437 if(dlg.DoModal()==IDOK)
2438 return DoFetch(dlg.m_RemoteURL, dlg.m_bAllRemotes == BST_CHECKED, dlg.m_bAutoLoad == BST_CHECKED, dlg.m_bPrune, dlg.m_bDepth == BST_CHECKED, dlg.m_nDepth, dlg.m_bFetchTags, dlg.m_RemoteBranchName, dlg.m_bRebase == BST_CHECKED);
2440 return false;
2443 bool CAppUtils::Push(CString selectLocalBranch)
2445 CPushDlg dlg;
2446 dlg.m_BranchSourceName = selectLocalBranch;
2448 if (dlg.DoModal() == IDOK)
2450 CString error;
2451 DWORD exitcode = 0xFFFFFFFF;
2452 if (CHooks::Instance().PrePush(g_Git.m_CurrentDir, exitcode, error))
2454 if (exitcode)
2456 CString temp;
2457 temp.Format(IDS_ERR_HOOKFAILED, (LPCTSTR)error);
2458 CMessageBox::Show(nullptr, temp, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
2459 return false;
2463 CString arg;
2465 if(dlg.m_bPack)
2466 arg += _T("--thin ");
2467 if(dlg.m_bTags && !dlg.m_bPushAllBranches)
2468 arg += _T("--tags ");
2469 if(dlg.m_bForce)
2470 arg += _T("--force ");
2471 if (dlg.m_bSetUpstream)
2472 arg += _T("--set-upstream ");
2473 if (dlg.m_RecurseSubmodules == 1)
2474 arg += _T("--recurse-submodules=check ");
2475 if (dlg.m_RecurseSubmodules == 2)
2476 arg += _T("--recurse-submodules=on-demand ");
2478 int ver = CAppUtils::GetMsysgitVersion();
2480 if(ver >= 0x01070203) //above 1.7.0.2
2481 arg += _T("--progress ");
2483 CProgressDlg progress;
2485 STRING_VECTOR remotesList;
2486 if (dlg.m_bPushAllRemotes)
2487 g_Git.GetRemoteList(remotesList);
2488 else
2489 remotesList.push_back(dlg.m_URL);
2491 for (unsigned int i = 0; i < remotesList.size(); ++i)
2493 if (dlg.m_bAutoLoad)
2494 CAppUtils::LaunchPAgent(NULL, &remotesList[i]);
2496 CString cmd;
2497 if (dlg.m_bPushAllBranches)
2499 cmd.Format(_T("git.exe push --all %s \"%s\""),
2500 arg,
2501 remotesList[i]);
2503 if (dlg.m_bTags)
2505 progress.m_GitCmdList.push_back(cmd);
2506 cmd.Format(_T("git.exe push --tags %s \"%s\""), arg, remotesList[i]);
2509 else
2511 cmd.Format(_T("git.exe push %s \"%s\" %s"),
2512 arg,
2513 remotesList[i],
2514 dlg.m_BranchSourceName);
2515 if (!dlg.m_BranchRemoteName.IsEmpty())
2517 cmd += _T(":") + dlg.m_BranchRemoteName;
2520 progress.m_GitCmdList.push_back(cmd);
2523 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2525 // need to execute hooks as those might be needed by post action commands
2526 DWORD exitcode = 0xFFFFFFFF;
2527 CString error;
2528 if (CHooks::Instance().PostPush(g_Git.m_CurrentDir, exitcode, error))
2530 if (exitcode)
2532 CString temp;
2533 temp.Format(IDS_ERR_HOOKFAILED, (LPCTSTR)error);
2534 MessageBox(nullptr, temp, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
2538 if (status)
2540 bool rejected = progress.GetLogText().Find(_T("! [rejected]")) > 0;
2541 if (rejected)
2543 postCmdList.push_back(PostCmd(IDI_PULL, IDS_MENUPULL, []{ Pull(true); }));
2544 postCmdList.push_back(PostCmd(IDI_PULL, IDS_MENUFETCH, [&]{ Fetch(dlg.m_bPushAllRemotes ? _T("") : dlg.m_URL, true, !!dlg.m_bPushAllRemotes); }));
2546 postCmdList.push_back(PostCmd(IDI_PUSH, IDS_MENUPUSH, [&]{ Push(selectLocalBranch); }));
2547 return;
2550 postCmdList.push_back(PostCmd(IDS_PROC_REQUESTPULL, [&]{ RequestPull(dlg.m_BranchRemoteName); }));
2551 postCmdList.push_back(PostCmd(IDI_PUSH, IDS_MENUPUSH, [&]{ Push(selectLocalBranch); }));
2552 postCmdList.push_back(PostCmd(IDI_SWITCH, IDS_MENUSWITCH, [&]{ Switch(); }));
2555 INT_PTR ret = progress.DoModal();
2556 return ret == IDOK;
2558 return FALSE;
2561 bool CAppUtils::RequestPull(CString endrevision, CString repositoryUrl)
2563 CRequestPullDlg dlg;
2564 dlg.m_RepositoryURL = repositoryUrl;
2565 dlg.m_EndRevision = endrevision;
2566 if (dlg.DoModal()==IDOK)
2568 CString cmd;
2569 cmd.Format(_T("git.exe request-pull %s \"%s\" %s"), dlg.m_StartRevision, dlg.m_RepositoryURL, dlg.m_EndRevision);
2571 CSysProgressDlg sysProgressDlg;
2572 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
2573 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_CREATINGPULLREUQEST)));
2574 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
2575 sysProgressDlg.SetShowProgressBar(false);
2576 sysProgressDlg.ShowModeless((HWND)NULL, true);
2578 CString tempFileName = GetTempFile();
2579 CString err;
2580 DeleteFile(tempFileName);
2581 CreateDirectory(tempFileName, NULL);
2582 tempFileName += _T("\\pullrequest.txt");
2583 if (g_Git.RunLogFile(cmd, tempFileName, &err))
2585 CString msg;
2586 msg.LoadString(IDS_ERR_PULLREUQESTFAILED);
2587 CMessageBox::Show(NULL, msg + _T("\n") + err, _T("TortoiseGit"), MB_OK);
2588 return false;
2591 if (sysProgressDlg.HasUserCancelled())
2593 CMessageBox::Show(NULL, IDS_SVN_USERCANCELLED, IDS_APPNAME, MB_OK);
2594 ::DeleteFile(tempFileName);
2595 return false;
2598 sysProgressDlg.Stop();
2600 if (dlg.m_bSendMail)
2602 CSendMailDlg dlg;
2603 dlg.m_PathList = CTGitPathList(CTGitPath(tempFileName));
2604 dlg.m_bCustomSubject = true;
2606 if (dlg.DoModal() == IDOK)
2608 if (dlg.m_PathList.IsEmpty())
2609 return FALSE;
2611 CGitProgressDlg progDlg;
2613 theApp.m_pMainWnd = &progDlg;
2614 SendMailProgressCommand sendMailProgressCommand;
2615 progDlg.SetCommand(&sendMailProgressCommand);
2617 sendMailProgressCommand.SetPathList(dlg.m_PathList);
2618 progDlg.SetItemCount(dlg.m_PathList.GetCount());
2620 CSendMailCombineable sendMailCombineable(dlg.m_To, dlg.m_CC, dlg.m_Subject, !!dlg.m_bAttachment, !!dlg.m_bCombine);
2621 sendMailProgressCommand.SetSendMailOption(&sendMailCombineable);
2623 progDlg.DoModal();
2625 return true;
2627 return false;
2630 CAppUtils::LaunchAlternativeEditor(tempFileName);
2632 return true;
2635 bool CAppUtils::CreateMultipleDirectory(const CString& szPath)
2637 CString strDir(szPath);
2638 if (strDir.GetAt(strDir.GetLength()-1)!=_T('\\'))
2640 strDir.AppendChar(_T('\\'));
2642 std::vector<CString> vPath;
2643 CString strTemp;
2644 bool bSuccess = false;
2646 for (int i=0;i<strDir.GetLength();++i)
2648 if (strDir.GetAt(i) != _T('\\'))
2650 strTemp.AppendChar(strDir.GetAt(i));
2652 else
2654 vPath.push_back(strTemp);
2655 strTemp.AppendChar(_T('\\'));
2659 for (auto vIter = vPath.begin(); vIter != vPath.end(); ++vIter)
2661 bSuccess = CreateDirectory(*vIter, NULL) ? true : false;
2664 return bSuccess;
2667 void CAppUtils::RemoveTrailSlash(CString &path)
2669 if(path.IsEmpty())
2670 return ;
2672 // For URL, do not trim the slash just after the host name component.
2673 int index = path.Find(_T("://"));
2674 if (index >= 0)
2676 index += 4;
2677 index = path.Find(_T('/'), index);
2678 if (index == path.GetLength() - 1)
2679 return;
2682 while(path[path.GetLength()-1] == _T('\\') || path[path.GetLength()-1] == _T('/' ) )
2684 path=path.Left(path.GetLength()-1);
2685 if(path.IsEmpty())
2686 return;
2690 bool CAppUtils::CheckUserData()
2692 while(g_Git.GetUserName().IsEmpty() || g_Git.GetUserEmail().IsEmpty())
2694 if(CMessageBox::Show(NULL, IDS_PROC_NOUSERDATA, IDS_APPNAME, MB_YESNO| MB_ICONERROR) == IDYES)
2696 CTGitPath path(g_Git.m_CurrentDir);
2697 CSettings dlg(IDS_PROC_SETTINGS_TITLE,&path);
2698 dlg.SetTreeViewMode(TRUE, TRUE, TRUE);
2699 dlg.SetTreeWidth(220);
2700 dlg.m_DefaultPage = _T("gitconfig");
2702 dlg.DoModal();
2703 dlg.HandleRestart();
2706 else
2707 return false;
2710 return true;
2713 BOOL CAppUtils::Commit(CString bugid,BOOL bWholeProject,CString &sLogMsg,
2714 CTGitPathList &pathList,
2715 CTGitPathList &selectedList,
2716 bool bSelectFilesForCommit)
2718 bool bFailed = true;
2720 if (!CheckUserData())
2721 return false;
2723 while (bFailed)
2725 bFailed = false;
2726 CCommitDlg dlg;
2727 dlg.m_sBugID = bugid;
2729 dlg.m_bWholeProject = bWholeProject;
2731 dlg.m_sLogMessage = sLogMsg;
2732 dlg.m_pathList = pathList;
2733 dlg.m_checkedPathList = selectedList;
2734 dlg.m_bSelectFilesForCommit = bSelectFilesForCommit;
2735 if (dlg.DoModal() == IDOK)
2737 if (dlg.m_pathList.IsEmpty())
2738 return false;
2739 // if the user hasn't changed the list of selected items
2740 // we don't use that list. Because if we would use the list
2741 // of pre-checked items, the dialog would show different
2742 // checked items on the next startup: it would only try
2743 // to check the parent folder (which might not even show)
2744 // instead, we simply use an empty list and let the
2745 // default checking do its job.
2746 if (!dlg.m_pathList.IsEqual(pathList))
2747 selectedList = dlg.m_pathList;
2748 pathList = dlg.m_updatedPathList;
2749 sLogMsg = dlg.m_sLogMessage;
2750 bSelectFilesForCommit = true;
2752 switch (dlg.m_PostCmd)
2754 case GIT_POSTCOMMIT_CMD_DCOMMIT:
2755 CAppUtils::SVNDCommit();
2756 break;
2757 case GIT_POSTCOMMIT_CMD_PUSH:
2758 CAppUtils::Push();
2759 break;
2760 case GIT_POSTCOMMIT_CMD_CREATETAG:
2761 CAppUtils::CreateBranchTag(TRUE);
2762 break;
2763 case GIT_POSTCOMMIT_CMD_PULL:
2764 CAppUtils::Pull(true);
2765 break;
2766 default:
2767 break;
2770 // CGitProgressDlg progDlg;
2771 // progDlg.SetChangeList(dlg.m_sChangeList, !!dlg.m_bKeepChangeList);
2772 // if (parser.HasVal(_T("closeonend")))
2773 // progDlg.SetAutoClose(parser.GetLongVal(_T("closeonend")));
2774 // progDlg.SetCommand(CGitProgressDlg::GitProgress_Commit);
2775 // progDlg.SetOptions(dlg.m_bKeepLocks ? ProgOptKeeplocks : ProgOptNone);
2776 // progDlg.SetPathList(dlg.m_pathList);
2777 // progDlg.SetCommitMessage(dlg.m_sLogMessage);
2778 // progDlg.SetDepth(dlg.m_bRecursive ? Git_depth_infinity : svn_depth_empty);
2779 // progDlg.SetSelectedList(dlg.m_selectedPathList);
2780 // progDlg.SetItemCount(dlg.m_itemsCount);
2781 // progDlg.SetBugTraqProvider(dlg.m_BugTraqProvider);
2782 // progDlg.DoModal();
2783 // CRegDWORD err = CRegDWORD(_T("Software\\TortoiseGit\\ErrorOccurred"), FALSE);
2784 // err = (DWORD)progDlg.DidErrorsOccur();
2785 // bFailed = progDlg.DidErrorsOccur();
2786 // bRet = progDlg.DidErrorsOccur();
2787 // CRegDWORD bFailRepeat = CRegDWORD(_T("Software\\TortoiseGit\\CommitReopen"), FALSE);
2788 // if (DWORD(bFailRepeat)==0)
2789 // bFailed = false; // do not repeat if the user chose not to in the settings.
2792 return true;
2796 BOOL CAppUtils::SVNDCommit()
2798 CSVNDCommitDlg dcommitdlg;
2799 CString gitSetting = g_Git.GetConfigValue(_T("svn.rmdir"));
2800 if (gitSetting == _T("")) {
2801 if (dcommitdlg.DoModal() != IDOK)
2803 return false;
2805 else
2807 if (dcommitdlg.m_remember)
2809 if (dcommitdlg.m_rmdir)
2811 gitSetting = _T("true");
2813 else
2815 gitSetting = _T("false");
2817 if(g_Git.SetConfigValue(_T("svn.rmdir"),gitSetting))
2819 CString msg;
2820 msg.Format(IDS_PROC_SAVECONFIGFAILED, _T("svn.rmdir"), gitSetting);
2821 CMessageBox::Show(NULL, msg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
2827 BOOL IsStash = false;
2828 if(!g_Git.CheckCleanWorkTree())
2830 if (CMessageBox::Show(NULL, IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
2832 CSysProgressDlg sysProgressDlg;
2833 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
2834 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
2835 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
2836 sysProgressDlg.SetShowProgressBar(false);
2837 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
2838 sysProgressDlg.ShowModeless((HWND)NULL, true);
2840 CString cmd,out;
2841 cmd=_T("git.exe stash");
2842 if (g_Git.Run(cmd, &out, CP_UTF8))
2844 sysProgressDlg.Stop();
2845 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
2846 return false;
2848 sysProgressDlg.Stop();
2850 IsStash =true;
2852 else
2854 return false;
2858 CProgressDlg progress;
2859 if (dcommitdlg.m_rmdir)
2861 progress.m_GitCmd=_T("git.exe svn dcommit --rmdir");
2863 else
2865 progress.m_GitCmd=_T("git.exe svn dcommit");
2867 if(progress.DoModal()==IDOK && progress.m_GitStatus == 0)
2869 ::DeleteFile(g_Git.m_CurrentDir + _T("\\sys$command"));
2870 if( IsStash)
2872 if(CMessageBox::Show(NULL,IDS_DCOMMIT_STASH_POP,IDS_APPNAME,MB_YESNO|MB_ICONINFORMATION)==IDYES)
2874 CSysProgressDlg sysProgressDlg;
2875 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
2876 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
2877 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
2878 sysProgressDlg.SetShowProgressBar(false);
2879 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
2880 sysProgressDlg.ShowModeless((HWND)NULL, true);
2882 CString cmd,out;
2883 cmd=_T("git.exe stash pop");
2884 if (g_Git.Run(cmd, &out, CP_UTF8))
2886 sysProgressDlg.Stop();
2887 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
2888 return false;
2890 sysProgressDlg.Stop();
2892 else
2894 return false;
2897 return TRUE;
2899 return FALSE;
2902 BOOL CAppUtils::Merge(CString *commit)
2904 if (!CheckUserData())
2905 return FALSE;
2907 CMergeDlg dlg;
2908 if(commit)
2909 dlg.m_initialRefName = *commit;
2911 if(dlg.DoModal()==IDOK)
2913 CString cmd;
2914 CString args;
2916 if(dlg.m_bNoFF)
2917 args += _T(" --no-ff");
2919 if(dlg.m_bSquash)
2920 args += _T(" --squash");
2922 if(dlg.m_bNoCommit)
2923 args += _T(" --no-commit");
2925 if (dlg.m_bLog)
2927 CString fmt;
2928 fmt.Format(_T(" --log=%d"), dlg.m_nLog);
2929 args += fmt;
2932 if (!dlg.m_MergeStrategy.IsEmpty())
2934 args += _T(" --strategy=") + dlg.m_MergeStrategy;
2935 if (!dlg.m_StrategyOption.IsEmpty())
2937 args += _T(" --strategy-option=") + dlg.m_StrategyOption;
2938 if (!dlg.m_StrategyParam.IsEmpty())
2939 args += _T("=") + dlg.m_StrategyParam;
2943 if(!dlg.m_strLogMesage.IsEmpty())
2945 CString logmsg = dlg.m_strLogMesage;
2946 logmsg.Replace(_T("\""), _T("\\\""));
2947 args += _T(" -m \"") + logmsg + _T("\"");
2949 cmd.Format(_T("git.exe merge %s %s"), args, g_Git.FixBranchName(dlg.m_VersionName));
2951 CProgressDlg Prodlg;
2952 Prodlg.m_GitCmd = cmd;
2954 Prodlg.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2956 if (status)
2958 CTGitPathList list;
2959 if (!g_Git.ListConflictFile(list) && !list.IsEmpty())
2961 // there are conflict files
2963 postCmdList.push_back(PostCmd(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
2965 CString sCmd;
2966 sCmd.Format(_T("/command:commit /path:\"%s\""), g_Git.m_CurrentDir);
2967 CAppUtils::RunTortoiseGitProc(sCmd);
2968 }));
2970 return;
2973 if (dlg.m_bNoCommit)
2975 postCmdList.push_back(PostCmd(IDI_COMMIT, IDS_MENUCOMMIT, []
2977 CString sCmd;
2978 sCmd.Format(_T("/command:commit /path:\"%s\""), g_Git.m_CurrentDir);
2979 CAppUtils::RunTortoiseGitProc(sCmd);
2980 }));
2981 return;
2984 if (dlg.m_bIsBranch && dlg.m_VersionName.Find(L"remotes/") == -1) // do not ask to remove remote branches
2986 postCmdList.push_back(PostCmd(IDI_DELETE, IDS_PROC_REMOVEBRANCH, [&]
2988 CString msg;
2989 msg.Format(IDS_PROC_DELETEBRANCHTAG, dlg.m_VersionName);
2990 if (CMessageBox::Show(nullptr, msg, _T("TortoiseGit"), 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
2992 CString cmd, out;
2993 cmd.Format(_T("git.exe branch -D -- %s"), dlg.m_VersionName);
2994 if (g_Git.Run(cmd, &out, CP_UTF8))
2995 MessageBox(nullptr, out, _T("TortoiseGit"), MB_OK);
2997 }));
2999 if (dlg.m_bIsBranch)
3000 postCmdList.push_back(PostCmd(IDI_PUSH, IDS_MENUPUSH, []{ Push(); }));
3002 BOOL hasGitSVN = CTGitPath(g_Git.m_CurrentDir).GetAdminDirMask() & ITEMIS_GITSVN;
3003 if (hasGitSVN)
3004 postCmdList.push_back(PostCmd(IDI_COMMIT, IDS_MENUSVNDCOMMIT, []{ SVNDCommit(); }));
3007 Prodlg.DoModal();
3008 return !Prodlg.m_GitStatus;
3010 return false;
3013 BOOL CAppUtils::MergeAbort()
3015 CMergeAbortDlg dlg;
3016 if (dlg.DoModal() == IDOK)
3017 return Reset(_T("HEAD"), dlg.m_ResetType + 1);
3019 return FALSE;
3022 void CAppUtils::EditNote(GitRev *rev)
3024 if (!CheckUserData())
3025 return;
3027 CInputDlg dlg;
3028 dlg.m_sHintText = CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES));
3029 dlg.m_sInputText = rev->m_Notes;
3030 dlg.m_sTitle = CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES));
3031 //dlg.m_pProjectProperties = &m_ProjectProperties;
3032 dlg.m_bUseLogWidth = true;
3033 if(dlg.DoModal() == IDOK)
3035 CString cmd,output;
3036 cmd=_T("notes add -f -F \"");
3038 CString tempfile=::GetTempFile();
3039 CAppUtils::SaveCommitUnicodeFile(tempfile,dlg.m_sInputText);
3040 cmd += tempfile;
3041 cmd += _T("\" ");
3042 cmd += rev->m_CommitHash.ToString();
3046 if (git_run_cmd("notes", CUnicodeUtils::GetMulti(cmd, CP_UTF8).GetBuffer()))
3048 CMessageBox::Show(NULL, IDS_PROC_FAILEDSAVINGNOTES, IDS_APPNAME, MB_OK | MB_ICONERROR);
3051 else
3053 rev->m_Notes = dlg.m_sInputText;
3055 }catch(...)
3057 CMessageBox::Show(NULL, IDS_PROC_FAILEDSAVINGNOTES, IDS_APPNAME, MB_OK | MB_ICONERROR);
3059 ::DeleteFile(tempfile);
3064 int CAppUtils::GetMsysgitVersion()
3066 if (g_Git.ms_LastMsysGitVersion)
3067 return g_Git.ms_LastMsysGitVersion;
3069 CString cmd;
3070 CString versiondebug;
3071 CString version;
3073 CRegDWORD regTime = CRegDWORD(_T("Software\\TortoiseGit\\git_file_time"));
3074 CRegDWORD regVersion = CRegDWORD(_T("Software\\TortoiseGit\\git_cached_version"));
3076 CString gitpath = CGit::ms_LastMsysGitDir+_T("\\git.exe");
3078 __int64 time=0;
3079 if (!g_Git.GetFileModifyTime(gitpath, &time))
3081 if((DWORD)time == regTime)
3083 g_Git.ms_LastMsysGitVersion = regVersion;
3084 return regVersion;
3088 CString err;
3089 cmd = _T("git.exe --version");
3090 if (g_Git.Run(cmd, &version, &err, CP_UTF8))
3092 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);
3093 return -1;
3096 int start=0;
3097 int ver = 0;
3099 versiondebug = version;
3103 CString str=version.Tokenize(_T("."), start);
3104 int space = str.ReverseFind(_T(' '));
3105 str = str.Mid(space+1,start);
3106 ver = _ttol(str);
3107 ver <<=24;
3109 version = version.Mid(start);
3110 start = 0;
3112 str = version.Tokenize(_T("."), start);
3114 ver |= (_ttol(str) & 0xFF) << 16;
3116 str = version.Tokenize(_T("."), start);
3117 ver |= (_ttol(str) & 0xFF) << 8;
3119 str = version.Tokenize(_T("."), start);
3120 ver |= (_ttol(str) & 0xFF);
3122 catch(...)
3124 if (!ver)
3126 CMessageBox::Show(NULL, _T("Could not parse git.exe version number: \"") + versiondebug + _T("\""), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
3127 return -1;
3131 regTime = time&0xFFFFFFFF;
3132 regVersion = ver;
3133 g_Git.ms_LastMsysGitVersion = ver;
3135 return ver;
3138 void CAppUtils::MarkWindowAsUnpinnable(HWND hWnd)
3140 typedef HRESULT (WINAPI *SHGPSFW) (HWND hwnd,REFIID riid,void** ppv);
3142 CAutoLibrary hShell = AtlLoadSystemLibraryUsingFullPath(_T("Shell32.dll"));
3144 if (hShell.IsValid()) {
3145 SHGPSFW pfnSHGPSFW = (SHGPSFW)::GetProcAddress(hShell, "SHGetPropertyStoreForWindow");
3146 if (pfnSHGPSFW) {
3147 IPropertyStore *pps;
3148 HRESULT hr = pfnSHGPSFW(hWnd, IID_PPV_ARGS(&pps));
3149 if (SUCCEEDED(hr)) {
3150 PROPVARIANT var;
3151 var.vt = VT_BOOL;
3152 var.boolVal = VARIANT_TRUE;
3153 hr = pps->SetValue(PKEY_AppUserModel_PreventPinning, var);
3154 pps->Release();
3160 void CAppUtils::SetWindowTitle(HWND hWnd, const CString& urlorpath, const CString& dialogname)
3162 ASSERT(dialogname.GetLength() < 70);
3163 ASSERT(urlorpath.GetLength() < MAX_PATH);
3164 WCHAR pathbuf[MAX_PATH] = {0};
3166 PathCompactPathEx(pathbuf, urlorpath, 70 - dialogname.GetLength(), 0);
3168 wcscat_s(pathbuf, L" - ");
3169 wcscat_s(pathbuf, dialogname);
3170 wcscat_s(pathbuf, L" - ");
3171 wcscat_s(pathbuf, CString(MAKEINTRESOURCE(IDS_APPNAME)));
3172 SetWindowText(hWnd, pathbuf);
3175 bool CAppUtils::BisectStart(CString lastGood, CString firstBad)
3177 if (!g_Git.CheckCleanWorkTree())
3179 if (CMessageBox::Show(NULL, IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
3181 CSysProgressDlg sysProgressDlg;
3182 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3183 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3184 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3185 sysProgressDlg.SetShowProgressBar(false);
3186 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3187 sysProgressDlg.ShowModeless((HWND)NULL, true);
3189 CString cmd, out;
3190 cmd = _T("git.exe stash");
3191 if (g_Git.Run(cmd, &out, CP_UTF8))
3193 sysProgressDlg.Stop();
3194 CMessageBox::Show(NULL, out, _T("TortoiseGit"), MB_OK);
3195 return false;
3197 sysProgressDlg.Stop();
3199 else
3200 return false;
3203 CBisectStartDlg bisectStartDlg;
3205 if (!lastGood.IsEmpty())
3206 bisectStartDlg.m_sLastGood = lastGood;
3207 if (!firstBad.IsEmpty())
3208 bisectStartDlg.m_sFirstBad = firstBad;
3210 if (bisectStartDlg.DoModal() == IDOK)
3212 CProgressDlg progress;
3213 theApp.m_pMainWnd = &progress;
3214 progress.m_GitCmdList.push_back(_T("git.exe bisect start"));
3215 progress.m_GitCmdList.push_back(_T("git.exe bisect good ") + bisectStartDlg.m_LastGoodRevision);
3216 progress.m_GitCmdList.push_back(_T("git.exe bisect bad ") + bisectStartDlg.m_FirstBadRevision);
3218 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3220 if (status)
3221 return;
3223 CTGitPath path(g_Git.m_CurrentDir);
3224 if (path.HasSubmodules())
3226 postCmdList.push_back(PostCmd(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
3228 CString sCmd;
3229 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), g_Git.m_CurrentDir);
3230 CAppUtils::RunTortoiseGitProc(sCmd);
3231 }));
3237 INT_PTR ret = progress.DoModal();
3238 return ret == IDOK;
3241 return false;
3244 int CAppUtils::Git2GetUserPassword(git_cred **out, const char *url, const char *username_from_url, unsigned int /*allowed_types*/, void * /*payload*/)
3246 CUserPassword dlg;
3247 dlg.m_URL = CUnicodeUtils::GetUnicode(url, CP_UTF8);
3248 if (username_from_url)
3249 dlg.m_UserName = CUnicodeUtils::GetUnicode(username_from_url, CP_UTF8);
3251 CStringA username, password;
3252 if (dlg.DoModal() == IDOK)
3254 username = CUnicodeUtils::GetMulti(dlg.m_UserName, CP_UTF8);
3255 password = CUnicodeUtils::GetMulti(dlg.m_Password, CP_UTF8);
3256 return git_cred_userpass_plaintext_new(out, username, password);
3258 return -1;
3261 void CAppUtils::ExploreTo(HWND hwnd, CString path)
3263 if (PathFileExists(path))
3265 ITEMIDLIST __unaligned * pidl = ILCreateFromPath(path);
3266 if (pidl)
3268 SHOpenFolderAndSelectItems(pidl, 0, 0, 0);
3269 ILFree(pidl);
3271 return;
3273 // if filepath does not exist any more, navigate to closest matching folder
3276 int pos = path.ReverseFind(_T('\\'));
3277 if (pos <= 3)
3278 break;
3279 path = path.Left(pos);
3280 } while (!PathFileExists(path));
3281 ShellExecute(hwnd, _T("explore"), path, nullptr, nullptr, SW_SHOW);