Refactor bisect operations into CAppUtils
[TortoiseGit.git] / src / TortoiseProc / AppUtils.cpp
blob49d3ef4b5cebb0f0e9abb197fe79305121e1b8cf
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2015 - 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 "MessageBox.h"
25 #include "registry.h"
26 #include "TGitPath.h"
27 #include "Git.h"
28 #include "UnicodeUtils.h"
29 #include "ExportDlg.h"
30 #include "ProgressDlg.h"
31 #include "GitAdminDir.h"
32 #include "ProgressDlg.h"
33 #include "BrowseFolder.h"
34 #include "DirFileEnum.h"
35 #include "MessageBox.h"
36 #include "GitStatus.h"
37 #include "CreateBranchTagDlg.h"
38 #include "GitSwitchDlg.h"
39 #include "ResetDlg.h"
40 #include "DeleteConflictDlg.h"
41 #include "ChangedDlg.h"
42 #include "SendMailDlg.h"
43 #include "GitProgressDlg.h"
44 #include "PushDlg.h"
45 #include "CommitDlg.h"
46 #include "MergeDlg.h"
47 #include "MergeAbortDlg.h"
48 #include "Hooks.h"
49 #include "..\Settings\Settings.h"
50 #include "InputDlg.h"
51 #include "SVNDCommitDlg.h"
52 #include "requestpulldlg.h"
53 #include "PullFetchDlg.h"
54 #include "FileDiffDlg.h"
55 #include "RebaseDlg.h"
56 #include "PropKey.h"
57 #include "StashSave.h"
58 #include "IgnoreDlg.h"
59 #include "FormatMessageWrapper.h"
60 #include "SmartHandle.h"
61 #include "BisectStartDlg.h"
62 #include "SysProgressDlg.h"
63 #include "UserPassword.h"
64 #include "Patch.h"
65 #include "Globals.h"
66 #include "ProgressCommands/ResetProgressCommand.h"
67 #include "ProgressCommands/FetchProgressCommand.h"
68 #include "ProgressCommands/SendMailProgressCommand.h"
69 #include "CertificateValidationHelper.h"
70 #include "CheckCertificateDlg.h"
71 #include "SubmoduleResolveConflictDlg.h"
72 #include "GitDiff.h"
73 #include "../TGitCache/CacheInterface.h"
75 static struct last_accepted_cert {
76 BYTE* data;
77 size_t len;
79 last_accepted_cert()
80 : data(nullptr)
81 , len(0)
84 ~last_accepted_cert()
86 free(data);
88 boolean cmp(git_cert_x509* cert)
90 return len > 0 && len == cert->len && memcmp(data, cert->data, len) == 0;
92 void set(git_cert_x509* cert)
94 free(data);
95 len = cert->len;
96 if (len == 0)
98 data = nullptr;
99 return;
101 data = new BYTE[len];
102 memcpy(data, cert->data, len);
104 } last_accepted_cert;
106 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, boolean runRebase);
108 CAppUtils::CAppUtils(void)
112 CAppUtils::~CAppUtils(void)
116 bool CAppUtils::StashSave(const CString& msg, bool showPull, bool pullShowPush, bool showMerge, const CString& mergeRev)
118 CStashSaveDlg dlg;
119 dlg.m_sMessage = msg;
120 if (dlg.DoModal() == IDOK)
122 CString cmd;
123 cmd = _T("git.exe stash save");
125 if (CAppUtils::GetMsysgitVersion() >= 0x01070700)
127 if (dlg.m_bIncludeUntracked)
128 cmd += _T(" --include-untracked");
129 else if (dlg.m_bAll)
130 cmd += _T(" --all");
133 if (!dlg.m_sMessage.IsEmpty())
135 CString message = dlg.m_sMessage;
136 message.Replace(_T("\""), _T("\"\""));
137 cmd += _T(" -- \"") + message + _T("\"");
140 CProgressDlg progress;
141 progress.m_GitCmd = cmd;
142 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
144 if (status)
145 return;
147 if (showPull)
148 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&]{ CAppUtils::Pull(pullShowPush, true); });
149 if (showMerge)
150 postCmdList.emplace_back(IDI_MERGE, IDS_MENUMERGE, [&]{ CAppUtils::Merge(&mergeRev, true); });
152 return (progress.DoModal() == IDOK);
154 return false;
157 bool CAppUtils::StashApply(CString ref, bool showChanges /* true */)
159 CString cmd,out;
160 cmd = _T("git.exe stash apply ");
161 if (ref.Find(_T("refs/")) == 0)
162 ref = ref.Mid(5);
163 if (ref.Find(_T("stash{")) == 0)
164 ref = _T("stash@") + ref.Mid(5);
165 cmd += ref;
167 CSysProgressDlg sysProgressDlg;
168 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
169 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
170 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
171 sysProgressDlg.SetShowProgressBar(false);
172 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
173 sysProgressDlg.ShowModeless((HWND)NULL, true);
175 int ret = g_Git.Run(cmd, &out, CP_UTF8);
177 sysProgressDlg.Stop();
179 bool hasConflicts = (out.Find(_T("CONFLICT")) >= 0);
180 if (ret && !(ret == 1 && hasConflicts))
182 CMessageBox::Show(NULL, CString(MAKEINTRESOURCE(IDS_PROC_STASHAPPLYFAILED)) + _T("\n") + out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
184 else
186 CString message;
187 message.LoadString(IDS_PROC_STASHAPPLYSUCCESS);
188 if (hasConflicts)
189 message.LoadString(IDS_PROC_STASHAPPLYFAILEDCONFLICTS);
190 if (showChanges)
192 if(CMessageBox::Show(NULL,message + _T("\n") + CString(MAKEINTRESOURCE(IDS_SEECHANGES))
193 ,_T("TortoiseGit"),MB_YESNO|MB_ICONINFORMATION) == IDYES)
195 CChangedDlg dlg;
196 dlg.m_pathList.AddPath(CTGitPath());
197 dlg.DoModal();
199 return true;
201 else
203 CMessageBox::Show(NULL, message ,_T("TortoiseGit"), MB_OK | MB_ICONINFORMATION);
204 return true;
207 return false;
210 bool CAppUtils::StashPop(bool showChanges /* true */)
212 CString cmd,out;
213 cmd=_T("git.exe stash pop ");
215 CSysProgressDlg sysProgressDlg;
216 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
217 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
218 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
219 sysProgressDlg.SetShowProgressBar(false);
220 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
221 sysProgressDlg.ShowModeless((HWND)NULL, true);
223 int ret = g_Git.Run(cmd, &out, CP_UTF8);
225 sysProgressDlg.Stop();
227 bool hasConflicts = (out.Find(_T("CONFLICT")) >= 0);
228 if (ret && !(ret == 1 && hasConflicts))
230 CMessageBox::Show(NULL,CString(MAKEINTRESOURCE(IDS_PROC_STASHPOPFAILED)) + _T("\n") + out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
232 else
234 CString message;
235 message.LoadString(IDS_PROC_STASHPOPSUCCESS);
236 if (hasConflicts)
237 message.LoadString(IDS_PROC_STASHPOPFAILEDCONFLICTS);
238 if (showChanges)
240 if(CMessageBox::Show(NULL,CString(message + _T("\n") + CString(MAKEINTRESOURCE(IDS_SEECHANGES)))
241 ,_T("TortoiseGit"),MB_YESNO|MB_ICONINFORMATION) == IDYES)
243 CChangedDlg dlg;
244 dlg.m_pathList.AddPath(CTGitPath());
245 dlg.DoModal();
247 return true;
249 else
251 CMessageBox::Show(NULL, message ,_T("TortoiseGit"), MB_OK | MB_ICONINFORMATION);
252 return true;
255 return false;
258 BOOL CAppUtils::StartExtMerge(
259 const CTGitPath& basefile, const CTGitPath& theirfile, const CTGitPath& yourfile, const CTGitPath& mergedfile,
260 const CString& basename, const CString& theirname, const CString& yourname, const CString& mergedname, bool bReadOnly,
261 HWND resolveMsgHwnd, bool bDeleteBaseTheirsMineOnClose)
264 CRegString regCom = CRegString(_T("Software\\TortoiseGit\\Merge"));
265 CString ext = mergedfile.GetFileExtension();
266 CString com = regCom;
267 bool bInternal = false;
269 if (!ext.IsEmpty())
271 // is there an extension specific merge tool?
272 CRegString mergetool(_T("Software\\TortoiseGit\\MergeTools\\") + ext.MakeLower());
273 if (!CString(mergetool).IsEmpty())
275 com = mergetool;
278 // is there a filename specific merge tool?
279 CRegString mergetool(_T("Software\\TortoiseGit\\MergeTools\\.") + mergedfile.GetFilename().MakeLower());
280 if (!CString(mergetool).IsEmpty())
282 com = mergetool;
285 if (com.IsEmpty()||(com.Left(1).Compare(_T("#"))==0))
287 // Maybe we should use TortoiseIDiff?
288 if ((ext == _T(".jpg")) || (ext == _T(".jpeg")) ||
289 (ext == _T(".bmp")) || (ext == _T(".gif")) ||
290 (ext == _T(".png")) || (ext == _T(".ico")) ||
291 (ext == _T(".tif")) || (ext == _T(".tiff")) ||
292 (ext == _T(".dib")) || (ext == _T(".emf")) ||
293 (ext == _T(".cur")))
295 com = CPathUtils::GetAppDirectory() + _T("TortoiseGitIDiff.exe");
296 com = _T("\"") + com + _T("\"");
297 com = com + _T(" /base:%base /theirs:%theirs /mine:%mine /result:%merged");
298 com = com + _T(" /basetitle:%bname /theirstitle:%tname /minetitle:%yname");
299 if (resolveMsgHwnd)
301 CString s;
302 s.Format(L" /resolvemsghwnd:%I64d", (__int64)resolveMsgHwnd);
303 com += s;
306 else
308 // use TortoiseGitMerge
309 bInternal = true;
310 com = CPathUtils::GetAppDirectory() + _T("TortoiseGitMerge.exe");
311 com = _T("\"") + com + _T("\"");
312 com = com + _T(" /base:%base /theirs:%theirs /mine:%mine /merged:%merged");
313 com = com + _T(" /basename:%bname /theirsname:%tname /minename:%yname /mergedname:%mname");
314 com += _T(" /saverequired");
315 if (resolveMsgHwnd)
317 CString s;
318 s.Format(L" /resolvemsghwnd:%I64d", (__int64)resolveMsgHwnd);
319 com += s;
321 if (bDeleteBaseTheirsMineOnClose)
322 com += _T(" /deletebasetheirsmineonclose");
324 if (!g_sGroupingUUID.IsEmpty())
326 com += L" /groupuuid:\"";
327 com += g_sGroupingUUID;
328 com += L"\"";
331 // check if the params are set. If not, just add the files to the command line
332 if ((com.Find(_T("%merged"))<0)&&(com.Find(_T("%base"))<0)&&(com.Find(_T("%theirs"))<0)&&(com.Find(_T("%mine"))<0))
334 com += _T(" \"")+basefile.GetWinPathString()+_T("\"");
335 com += _T(" \"")+theirfile.GetWinPathString()+_T("\"");
336 com += _T(" \"")+yourfile.GetWinPathString()+_T("\"");
337 com += _T(" \"")+mergedfile.GetWinPathString()+_T("\"");
339 if (basefile.IsEmpty())
341 com.Replace(_T("/base:%base"), _T(""));
342 com.Replace(_T("%base"), _T(""));
344 else
345 com.Replace(_T("%base"), _T("\"") + basefile.GetWinPathString() + _T("\""));
346 if (theirfile.IsEmpty())
348 com.Replace(_T("/theirs:%theirs"), _T(""));
349 com.Replace(_T("%theirs"), _T(""));
351 else
352 com.Replace(_T("%theirs"), _T("\"") + theirfile.GetWinPathString() + _T("\""));
353 if (yourfile.IsEmpty())
355 com.Replace(_T("/mine:%mine"), _T(""));
356 com.Replace(_T("%mine"), _T(""));
358 else
359 com.Replace(_T("%mine"), _T("\"") + yourfile.GetWinPathString() + _T("\""));
360 if (mergedfile.IsEmpty())
362 com.Replace(_T("/merged:%merged"), _T(""));
363 com.Replace(_T("%merged"), _T(""));
365 else
366 com.Replace(_T("%merged"), _T("\"") + mergedfile.GetWinPathString() + _T("\""));
367 if (basename.IsEmpty())
369 if (basefile.IsEmpty())
371 com.Replace(_T("/basename:%bname"), _T(""));
372 com.Replace(_T("%bname"), _T(""));
374 else
376 com.Replace(_T("%bname"), _T("\"") + basefile.GetUIFileOrDirectoryName() + _T("\""));
379 else
380 com.Replace(_T("%bname"), _T("\"") + basename + _T("\""));
381 if (theirname.IsEmpty())
383 if (theirfile.IsEmpty())
385 com.Replace(_T("/theirsname:%tname"), _T(""));
386 com.Replace(_T("%tname"), _T(""));
388 else
390 com.Replace(_T("%tname"), _T("\"") + theirfile.GetUIFileOrDirectoryName() + _T("\""));
393 else
394 com.Replace(_T("%tname"), _T("\"") + theirname + _T("\""));
395 if (yourname.IsEmpty())
397 if (yourfile.IsEmpty())
399 com.Replace(_T("/minename:%yname"), _T(""));
400 com.Replace(_T("%yname"), _T(""));
402 else
404 com.Replace(_T("%yname"), _T("\"") + yourfile.GetUIFileOrDirectoryName() + _T("\""));
407 else
408 com.Replace(_T("%yname"), _T("\"") + yourname + _T("\""));
409 if (mergedname.IsEmpty())
411 if (mergedfile.IsEmpty())
413 com.Replace(_T("/mergedname:%mname"), _T(""));
414 com.Replace(_T("%mname"), _T(""));
416 else
418 com.Replace(_T("%mname"), _T("\"") + mergedfile.GetUIFileOrDirectoryName() + _T("\""));
421 else
422 com.Replace(_T("%mname"), _T("\"") + mergedname + _T("\""));
424 if ((bReadOnly)&&(bInternal))
425 com += _T(" /readonly");
427 if(!LaunchApplication(com, IDS_ERR_EXTMERGESTART, false))
429 return FALSE;
432 return TRUE;
435 BOOL CAppUtils::StartExtPatch(const CTGitPath& patchfile, const CTGitPath& dir, const CString& sOriginalDescription, const CString& sPatchedDescription, BOOL bReversed, BOOL bWait)
437 CString viewer;
438 // use TortoiseGitMerge
439 viewer = CPathUtils::GetAppDirectory();
440 viewer += _T("TortoiseGitMerge.exe");
442 viewer = _T("\"") + viewer + _T("\"");
443 viewer = viewer + _T(" /diff:\"") + patchfile.GetWinPathString() + _T("\"");
444 viewer = viewer + _T(" /patchpath:\"") + dir.GetWinPathString() + _T("\"");
445 if (bReversed)
446 viewer += _T(" /reversedpatch");
447 if (!sOriginalDescription.IsEmpty())
448 viewer = viewer + _T(" /patchoriginal:\"") + sOriginalDescription + _T("\"");
449 if (!sPatchedDescription.IsEmpty())
450 viewer = viewer + _T(" /patchpatched:\"") + sPatchedDescription + _T("\"");
451 if (!g_sGroupingUUID.IsEmpty())
453 viewer += L" /groupuuid:\"";
454 viewer += g_sGroupingUUID;
455 viewer += L"\"";
457 if(!LaunchApplication(viewer, IDS_ERR_DIFFVIEWSTART, !!bWait))
459 return FALSE;
461 return TRUE;
464 CString CAppUtils::PickDiffTool(const CTGitPath& file1, const CTGitPath& file2)
466 CString difftool = CRegString(_T("Software\\TortoiseGit\\DiffTools\\") + file2.GetFilename().MakeLower());
467 if (!difftool.IsEmpty())
468 return difftool;
469 difftool = CRegString(_T("Software\\TortoiseGit\\DiffTools\\") + file1.GetFilename().MakeLower());
470 if (!difftool.IsEmpty())
471 return difftool;
473 // Is there an extension specific diff tool?
474 CString ext = file2.GetFileExtension().MakeLower();
475 if (!ext.IsEmpty())
477 difftool = CRegString(_T("Software\\TortoiseGit\\DiffTools\\") + ext);
478 if (!difftool.IsEmpty())
479 return difftool;
480 // Maybe we should use TortoiseIDiff?
481 if ((ext == _T(".jpg")) || (ext == _T(".jpeg")) ||
482 (ext == _T(".bmp")) || (ext == _T(".gif")) ||
483 (ext == _T(".png")) || (ext == _T(".ico")) ||
484 (ext == _T(".tif")) || (ext == _T(".tiff")) ||
485 (ext == _T(".dib")) || (ext == _T(".emf")) ||
486 (ext == _T(".cur")))
488 return
489 _T("\"") + CPathUtils::GetAppDirectory() + _T("TortoiseGitIDiff.exe") + _T("\"") +
490 _T(" /left:%base /right:%mine /lefttitle:%bname /righttitle:%yname") +
491 L" /groupuuid:\"" + g_sGroupingUUID + L"\"";
495 // Finally, pick a generic external diff tool
496 difftool = CRegString(_T("Software\\TortoiseGit\\Diff"));
497 return difftool;
500 bool CAppUtils::StartExtDiff(
501 const CString& file1, const CString& file2,
502 const CString& sName1, const CString& sName2,
503 const CString& originalFile1, const CString& originalFile2,
504 const git_revnum_t& hash1, const git_revnum_t& hash2,
505 const DiffFlags& flags, int jumpToLine)
507 CString viewer;
509 CRegDWORD blamediff(_T("Software\\TortoiseGit\\DiffBlamesWithTortoiseMerge"), FALSE);
510 if (!flags.bBlame || !(DWORD)blamediff)
512 viewer = PickDiffTool(file1, file2);
513 // If registry entry for a diff program is commented out, use TortoiseGitMerge.
514 bool bCommentedOut = viewer.Left(1) == _T("#");
515 if (flags.bAlternativeTool)
517 // Invert external vs. internal diff tool selection.
518 if (bCommentedOut)
519 viewer.Delete(0); // uncomment
520 else
521 viewer = "";
523 else if (bCommentedOut)
524 viewer = "";
527 bool bInternal = viewer.IsEmpty();
528 if (bInternal)
530 viewer =
531 _T("\"") + CPathUtils::GetAppDirectory() + _T("TortoiseGitMerge.exe") + _T("\"") +
532 _T(" /base:%base /mine:%mine /basename:%bname /minename:%yname") +
533 _T(" /basereflectedname:%bpath /minereflectedname:%ypath");
534 if (!g_sGroupingUUID.IsEmpty())
536 viewer += L" /groupuuid:\"";
537 viewer += g_sGroupingUUID;
538 viewer += L"\"";
540 if (flags.bBlame)
541 viewer += _T(" /blame");
543 // check if the params are set. If not, just add the files to the command line
544 if ((viewer.Find(_T("%base"))<0)&&(viewer.Find(_T("%mine"))<0))
546 viewer += _T(" \"")+file1+_T("\"");
547 viewer += _T(" \"")+file2+_T("\"");
549 if (viewer.Find(_T("%base")) >= 0)
551 viewer.Replace(_T("%base"), _T("\"")+file1+_T("\""));
553 if (viewer.Find(_T("%mine")) >= 0)
555 viewer.Replace(_T("%mine"), _T("\"")+file2+_T("\""));
558 if (sName1.IsEmpty())
559 viewer.Replace(_T("%bname"), _T("\"") + file1 + _T("\""));
560 else
561 viewer.Replace(_T("%bname"), _T("\"") + sName1 + _T("\""));
563 if (sName2.IsEmpty())
564 viewer.Replace(_T("%yname"), _T("\"") + file2 + _T("\""));
565 else
566 viewer.Replace(_T("%yname"), _T("\"") + sName2 + _T("\""));
568 viewer.Replace(_T("%bpath"), _T("\"") + originalFile1 + _T("\""));
569 viewer.Replace(_T("%ypath"), _T("\"") + originalFile2 + _T("\""));
571 viewer.Replace(_T("%brev"), _T("\"") + hash1 + _T("\""));
572 viewer.Replace(_T("%yrev"), _T("\"") + hash2 + _T("\""));
574 if (flags.bReadOnly && bInternal)
575 viewer += _T(" /readonly");
577 if (jumpToLine > 0)
579 CString temp;
580 temp.Format(_T(" /line:%d"), jumpToLine);
581 viewer += temp;
584 return LaunchApplication(viewer, IDS_ERR_EXTDIFFSTART, flags.bWait);
587 BOOL CAppUtils::StartUnifiedDiffViewer(const CString& patchfile, const CString& title, BOOL bWait)
589 CString viewer;
590 CRegString v = CRegString(_T("Software\\TortoiseGit\\DiffViewer"));
591 viewer = v;
592 if (viewer.IsEmpty() || (viewer.Left(1).Compare(_T("#"))==0))
594 // use TortoiseGitUDiff
595 viewer = CPathUtils::GetAppDirectory();
596 viewer += _T("TortoiseGitUDiff.exe");
597 // enquote the path to TortoiseGitUDiff
598 viewer = _T("\"") + viewer + _T("\"");
599 // add the params
600 viewer = viewer + _T(" /patchfile:%1 /title:\"%title\"");
601 if (!g_sGroupingUUID.IsEmpty())
603 viewer += L" /groupuuid:\"";
604 viewer += g_sGroupingUUID;
605 viewer += L"\"";
608 if (viewer.Find(_T("%1"))>=0)
610 if (viewer.Find(_T("\"%1\"")) >= 0)
611 viewer.Replace(_T("%1"), patchfile);
612 else
613 viewer.Replace(_T("%1"), _T("\"") + patchfile + _T("\""));
615 else
616 viewer += _T(" \"") + patchfile + _T("\"");
617 if (viewer.Find(_T("%title")) >= 0)
619 viewer.Replace(_T("%title"), title);
622 if(!LaunchApplication(viewer, IDS_ERR_DIFFVIEWSTART, !!bWait))
624 return FALSE;
626 return TRUE;
629 BOOL CAppUtils::StartTextViewer(CString file)
631 CString viewer;
632 CRegString txt = CRegString(_T(".txt\\"), _T(""), FALSE, HKEY_CLASSES_ROOT);
633 viewer = txt;
634 viewer = viewer + _T("\\Shell\\Open\\Command\\");
635 CRegString txtexe = CRegString(viewer, _T(""), FALSE, HKEY_CLASSES_ROOT);
636 viewer = txtexe;
638 DWORD len = ExpandEnvironmentStrings(viewer, NULL, 0);
639 std::unique_ptr<TCHAR[]> buf(new TCHAR[len + 1]);
640 ExpandEnvironmentStrings(viewer, buf.get(), len);
641 viewer = buf.get();
642 len = ExpandEnvironmentStrings(file, NULL, 0);
643 std::unique_ptr<TCHAR[]> buf2(new TCHAR[len + 1]);
644 ExpandEnvironmentStrings(file, buf2.get(), len);
645 file = buf2.get();
646 file = _T("\"")+file+_T("\"");
647 if (viewer.IsEmpty())
649 return CAppUtils::ShowOpenWithDialog(file) ? TRUE : FALSE;
651 if (viewer.Find(_T("\"%1\"")) >= 0)
653 viewer.Replace(_T("\"%1\""), file);
655 else if (viewer.Find(_T("%1")) >= 0)
657 viewer.Replace(_T("%1"), file);
659 else
661 viewer += _T(" ");
662 viewer += file;
665 if(!LaunchApplication(viewer, IDS_ERR_TEXTVIEWSTART, false))
667 return FALSE;
669 return TRUE;
672 BOOL CAppUtils::CheckForEmptyDiff(const CTGitPath& sDiffPath)
674 DWORD length = 0;
675 CAutoFile hFile = ::CreateFile(sDiffPath.GetWinPath(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL);
676 if (!hFile)
677 return TRUE;
678 length = ::GetFileSize(hFile, NULL);
679 if (length < 4)
680 return TRUE;
681 return FALSE;
685 void CAppUtils::CreateFontForLogs(CFont& fontToCreate)
687 LOGFONT logFont;
688 HDC hScreenDC = ::GetDC(NULL);
689 logFont.lfHeight = -MulDiv((DWORD)CRegDWORD(_T("Software\\TortoiseGit\\LogFontSize"), 8), GetDeviceCaps(hScreenDC, LOGPIXELSY), 72);
690 ::ReleaseDC(NULL, hScreenDC);
691 logFont.lfWidth = 0;
692 logFont.lfEscapement = 0;
693 logFont.lfOrientation = 0;
694 logFont.lfWeight = FW_NORMAL;
695 logFont.lfItalic = 0;
696 logFont.lfUnderline = 0;
697 logFont.lfStrikeOut = 0;
698 logFont.lfCharSet = DEFAULT_CHARSET;
699 logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
700 logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
701 logFont.lfQuality = DRAFT_QUALITY;
702 logFont.lfPitchAndFamily = FF_DONTCARE | FIXED_PITCH;
703 _tcscpy_s(logFont.lfFaceName, 32, (LPCTSTR)(CString)CRegString(_T("Software\\TortoiseGit\\LogFontName"), _T("Courier New")));
704 VERIFY(fontToCreate.CreateFontIndirect(&logFont));
707 bool CAppUtils::LaunchPAgent(const CString* keyfile, const CString* pRemote)
709 CString key,remote;
710 CString cmd,out;
711 if( pRemote == NULL)
713 remote=_T("origin");
715 else
717 remote=*pRemote;
719 if(keyfile == NULL)
721 cmd.Format(_T("remote.%s.puttykeyfile"), (LPCTSTR)remote);
722 key = g_Git.GetConfigValue(cmd);
724 else
725 key=*keyfile;
727 if(key.IsEmpty())
728 return false;
730 CString proc=CPathUtils::GetAppDirectory();
731 proc += _T("pageant.exe \"");
732 proc += key;
733 proc += _T("\"");
735 CString tempfile = GetTempFile();
736 ::DeleteFile(tempfile);
738 proc += _T(" -c \"");
739 proc += CPathUtils::GetAppDirectory();
740 proc += _T("tgittouch.exe\"");
741 proc += _T(" \"");
742 proc += tempfile;
743 proc += _T("\"");
745 CString appDir = CPathUtils::GetAppDirectory();
746 bool b = LaunchApplication(proc, IDS_ERR_PAGEANT, true, &appDir);
747 if(!b)
748 return b;
750 int i=0;
751 while(!::PathFileExists(tempfile))
753 Sleep(100);
754 ++i;
755 if(i>10*60*5)
756 break; //timeout 5 minutes
759 if( i== 10*60*5)
761 CMessageBox::Show(NULL, IDS_ERR_PAEGENTTIMEOUT, IDS_APPNAME, MB_OK | MB_ICONERROR);
763 ::DeleteFile(tempfile);
764 return true;
766 bool CAppUtils::LaunchAlternativeEditor(const CString& filename, bool uac)
768 CString editTool = CRegString(_T("Software\\TortoiseGit\\AlternativeEditor"));
769 if (editTool.IsEmpty() || (editTool.Left(1).Compare(_T("#"))==0)) {
770 editTool = CPathUtils::GetAppDirectory() + _T("notepad2.exe");
773 CString sCmd;
774 sCmd.Format(_T("\"%s\" \"%s\""), (LPCTSTR)editTool, (LPCTSTR)filename);
776 LaunchApplication(sCmd, NULL, false, NULL, uac);
777 return true;
779 bool CAppUtils::LaunchRemoteSetting()
781 CTGitPath path(g_Git.m_CurrentDir);
782 CSettings dlg(IDS_PROC_SETTINGS_TITLE, &path);
783 dlg.SetTreeViewMode(TRUE, TRUE, TRUE);
784 dlg.SetTreeWidth(220);
785 dlg.m_DefaultPage = _T("gitremote");
787 dlg.DoModal();
788 dlg.HandleRestart();
789 return true;
792 * Launch the external blame viewer
794 bool CAppUtils::LaunchTortoiseBlame(const CString& sBlameFile, const CString& Rev, const CString& sParams)
796 CString viewer = _T("\"") + CPathUtils::GetAppDirectory();
797 viewer += _T("TortoiseGitBlame.exe");
798 viewer += _T("\" \"") + sBlameFile + _T("\"");
799 //viewer += _T(" \"") + sLogFile + _T("\"");
800 //viewer += _T(" \"") + sOriginalFile + _T("\"");
801 if(!Rev.IsEmpty() && Rev != GIT_REV_ZERO)
802 viewer += CString(_T(" /rev:"))+Rev;
803 if (!g_sGroupingUUID.IsEmpty())
805 viewer += L" /groupuuid:\"";
806 viewer += g_sGroupingUUID;
807 viewer += L"\"";
809 viewer += _T(" ")+sParams;
811 return LaunchApplication(viewer, IDS_ERR_TGITBLAME, false);
814 bool CAppUtils::FormatTextInRichEditControl(CWnd * pWnd)
816 CString sText;
817 if (pWnd == NULL)
818 return false;
819 bool bStyled = false;
820 pWnd->GetWindowText(sText);
821 // the rich edit control doesn't count the CR char!
822 // to be exact: CRLF is treated as one char.
823 sText.Remove(_T('\r'));
825 // style each line separately
826 int offset = 0;
827 int nNewlinePos;
830 nNewlinePos = sText.Find('\n', offset);
831 CString sLine = nNewlinePos >= 0 ? sText.Mid(offset, nNewlinePos - offset) : sText.Mid(offset);
833 int start = 0;
834 int end = 0;
835 while (FindStyleChars(sLine, '*', start, end))
837 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
838 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
839 SetCharFormat(pWnd, CFM_BOLD, CFE_BOLD);
840 bStyled = true;
841 start = end;
843 start = 0;
844 end = 0;
845 while (FindStyleChars(sLine, '^', start, end))
847 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
848 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
849 SetCharFormat(pWnd, CFM_ITALIC, CFE_ITALIC);
850 bStyled = true;
851 start = end;
853 start = 0;
854 end = 0;
855 while (FindStyleChars(sLine, '_', start, end))
857 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
858 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
859 SetCharFormat(pWnd, CFM_UNDERLINE, CFE_UNDERLINE);
860 bStyled = true;
861 start = end;
863 offset = nNewlinePos+1;
864 } while(nNewlinePos>=0);
865 return bStyled;
868 bool CAppUtils::FindStyleChars(const CString& sText, TCHAR stylechar, int& start, int& end)
870 int i=start;
871 int last = sText.GetLength() - 1;
872 bool bFoundMarker = false;
873 TCHAR c = i == 0 ? _T('\0') : sText[i - 1];
874 TCHAR nextChar = i >= last ? _T('\0') : sText[i + 1];
876 // find a starting marker
877 while (i < last)
879 TCHAR prevChar = c;
880 c = nextChar;
881 nextChar = sText[i + 1];
883 // IsCharAlphaNumeric can be somewhat expensive.
884 // Long lines of "*****" or "----" will be pre-empted efficiently
885 // by the (c != nextChar) condition.
887 if ((c == stylechar) && (c != nextChar))
889 if (IsCharAlphaNumeric(nextChar) && !IsCharAlphaNumeric(prevChar))
891 start = ++i;
892 bFoundMarker = true;
893 break;
896 ++i;
898 if (!bFoundMarker)
899 return false;
901 // find ending marker
902 // c == sText[i - 1]
904 bFoundMarker = false;
905 while (i <= last)
907 TCHAR prevChar = c;
908 c = sText[i];
909 if (c == stylechar)
911 if ((i == last) || (!IsCharAlphaNumeric(sText[i + 1]) && IsCharAlphaNumeric(prevChar)))
913 end = i;
914 ++i;
915 bFoundMarker = true;
916 break;
919 ++i;
921 return bFoundMarker;
924 // from CSciEdit
925 namespace {
926 bool IsValidURLChar(wchar_t ch)
928 return iswalnum(ch) ||
929 ch == L'_' || ch == L'/' || ch == L';' || ch == L'?' || ch == L'&' || ch == L'=' ||
930 ch == L'%' || ch == L':' || ch == L'.' || ch == L'#' || ch == L'-' || ch == L'+' ||
931 ch == L'|' || ch == L'>' || ch == L'<';
934 bool IsUrl(const CString& sText)
936 if (!PathIsURLW(sText))
937 return false;
938 static const CString prefixes[] = { L"http://", L"https://", L"git://", L"ftp://", L"file://", L"mailto:" };
939 for (const auto& prefix : prefixes)
941 if (sText.Find(prefix) == 0 && sText.GetLength() != prefix.GetLength())
942 return true;
944 return false;
948 BOOL CAppUtils::StyleURLs(const CString& msg, CWnd* pWnd)
950 std::vector<CHARRANGE> positions = FindURLMatches(msg);
951 CAppUtils::SetCharFormat(pWnd, CFM_LINK, CFE_LINK, positions);
953 return positions.empty() ? FALSE : TRUE;
957 * implements URL searching with the same logic as CSciEdit::StyleURLs
959 std::vector<CHARRANGE> CAppUtils::FindURLMatches(const CString& msg)
961 std::vector<CHARRANGE> result;
963 int len = msg.GetLength();
964 int starturl = -1;
966 for (int i = 0; i <= msg.GetLength(); ++i)
968 if ((i < len) && IsValidURLChar(msg[i]))
970 if (starturl < 0)
971 starturl = i;
973 else
975 if (starturl >= 0)
977 bool strip = true;
978 if (msg[starturl] == '<' && i < len) // try to detect and do not strip URLs put within <>
980 while (starturl <= i && msg[starturl] == '<') // strip leading '<'
981 ++starturl;
982 strip = false;
983 i = starturl;
984 while (i < len && msg[i] != '\r' && msg[i] != '\n' && msg[i] != '>') // find first '>' or new line after resetting i to start position
985 ++i;
987 if (!IsUrl(msg.Mid(starturl, i - starturl)))
989 starturl = -1;
990 continue;
993 int skipTrailing = 0;
994 while (strip && i - skipTrailing - 1 > starturl && (msg[i - skipTrailing - 1] == '.' || msg[i - skipTrailing - 1] == '-' || msg[i - skipTrailing - 1] == '?' || msg[i - skipTrailing - 1] == ';' || msg[i - skipTrailing - 1] == ':' || msg[i - skipTrailing - 1] == '>' || msg[i - skipTrailing - 1] == '<'))
995 ++skipTrailing;
997 CHARRANGE range = { starturl, i - skipTrailing };
998 result.push_back(range);
1000 starturl = -1;
1004 return result;
1007 bool CAppUtils::StartShowUnifiedDiff(HWND hWnd, const CTGitPath& url1, const git_revnum_t& rev1,
1008 const CTGitPath& /*url2*/, const git_revnum_t& rev2,
1009 //const GitRev& peg /* = GitRev */, const GitRev& headpeg /* = GitRev */,
1010 bool /*bAlternateDiff*/ /* = false */, bool /*bIgnoreAncestry*/ /* = false */,
1011 bool /* blame = false */,
1012 bool bMerge,
1013 bool bCombine)
1015 int diffContext = 0;
1016 if (GetMsysgitVersion() > 0x01080100)
1017 diffContext = g_Git.GetConfigValueInt32(_T("diff.context"), -1);
1018 CString tempfile=GetTempFile();
1019 if (g_Git.GetUnifiedDiff(url1, rev1, rev2, tempfile, bMerge, bCombine, diffContext))
1021 CMessageBox::Show(hWnd, g_Git.GetGitLastErr(_T("Could not get unified diff."), CGit::GIT_CMD_DIFF), _T("TortoiseGit"), MB_OK);
1022 return false;
1024 CAppUtils::StartUnifiedDiffViewer(tempfile, rev1 + _T(":") + rev2);
1026 #if 0
1027 CString sCmd;
1028 sCmd.Format(_T("%s /command:showcompare /unified"),
1029 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseGitProc.exe")));
1030 sCmd += _T(" /url1:\"") + url1.GetGitPathString() + _T("\"");
1031 if (rev1.IsValid())
1032 sCmd += _T(" /revision1:") + rev1.ToString();
1033 sCmd += _T(" /url2:\"") + url2.GetGitPathString() + _T("\"");
1034 if (rev2.IsValid())
1035 sCmd += _T(" /revision2:") + rev2.ToString();
1036 if (peg.IsValid())
1037 sCmd += _T(" /pegrevision:") + peg.ToString();
1038 if (headpeg.IsValid())
1039 sCmd += _T(" /headpegrevision:") + headpeg.ToString();
1041 if (bAlternateDiff)
1042 sCmd += _T(" /alternatediff");
1044 if (bIgnoreAncestry)
1045 sCmd += _T(" /ignoreancestry");
1047 if (hWnd)
1049 sCmd += _T(" /hwnd:");
1050 TCHAR buf[30];
1051 _stprintf_s(buf, 30, _T("%p"), (void*)hWnd);
1052 sCmd += buf;
1055 return CAppUtils::LaunchApplication(sCmd, NULL, false);
1056 #endif
1057 return TRUE;
1060 bool CAppUtils::SetupDiffScripts(bool force, const CString& type)
1062 CString scriptsdir = CPathUtils::GetAppParentDirectory();
1063 scriptsdir += _T("Diff-Scripts");
1064 CSimpleFileFind files(scriptsdir);
1065 while (files.FindNextFileNoDirectories())
1067 CString file = files.GetFilePath();
1068 CString filename = files.GetFileName();
1069 CString ext = file.Mid(file.ReverseFind('-') + 1);
1070 ext = _T(".") + ext.Left(ext.ReverseFind('.'));
1071 std::set<CString> extensions;
1072 extensions.insert(ext);
1073 CString kind;
1074 if (file.Right(3).CompareNoCase(_T("vbs"))==0)
1076 kind = _T(" //E:vbscript");
1078 if (file.Right(2).CompareNoCase(_T("js"))==0)
1080 kind = _T(" //E:javascript");
1082 // open the file, read the first line and find possible extensions
1083 // this script can handle
1086 CStdioFile f(file, CFile::modeRead | CFile::shareDenyNone);
1087 CString extline;
1088 if (f.ReadString(extline))
1090 if ((extline.GetLength() > 15 ) &&
1091 ((extline.Left(15).Compare(_T("// extensions: ")) == 0) ||
1092 (extline.Left(14).Compare(_T("' extensions: ")) == 0)))
1094 if (extline[0] == '/')
1095 extline = extline.Mid(15);
1096 else
1097 extline = extline.Mid(14);
1098 CString sToken;
1099 int curPos = 0;
1100 sToken = extline.Tokenize(_T(";"), curPos);
1101 while (!sToken.IsEmpty())
1103 if (!sToken.IsEmpty())
1105 if (sToken[0] != '.')
1106 sToken = _T(".") + sToken;
1107 extensions.insert(sToken);
1109 sToken = extline.Tokenize(_T(";"), curPos);
1113 f.Close();
1115 catch (CFileException* e)
1117 e->Delete();
1120 for (const auto& extension : extensions)
1122 if (type.IsEmpty() || (type.Compare(_T("Diff")) == 0))
1124 if (filename.Left(5).CompareNoCase(_T("diff-")) == 0)
1126 CRegString diffreg = CRegString(_T("Software\\TortoiseGit\\DiffTools\\") + extension);
1127 CString diffregstring = diffreg;
1128 if (force || (diffregstring.IsEmpty()) || (diffregstring.Find(filename) >= 0))
1129 diffreg = _T("wscript.exe \"") + file + _T("\" %base %mine") + kind;
1132 if (type.IsEmpty() || (type.Compare(_T("Merge"))==0))
1134 if (filename.Left(6).CompareNoCase(_T("merge-"))==0)
1136 CRegString diffreg = CRegString(_T("Software\\TortoiseGit\\MergeTools\\") + extension);
1137 CString diffregstring = diffreg;
1138 if (force || (diffregstring.IsEmpty()) || (diffregstring.Find(filename) >= 0))
1139 diffreg = _T("wscript.exe \"") + file + _T("\" %merged %theirs %mine %base") + kind;
1145 return true;
1148 bool CAppUtils::Export(const CString* BashHash, const CTGitPath* orgPath)
1150 // ask from where the export has to be done
1151 CExportDlg dlg;
1152 if(BashHash)
1153 dlg.m_initialRefName=*BashHash;
1154 if (orgPath)
1156 if (PathIsRelative(orgPath->GetWinPath()))
1157 dlg.m_orgPath = g_Git.CombinePath(orgPath);
1158 else
1159 dlg.m_orgPath = *orgPath;
1162 if (dlg.DoModal() == IDOK)
1164 CString cmd;
1165 cmd.Format(_T("git.exe archive --output=\"%s\" --format=zip --verbose %s --"),
1166 (LPCTSTR)dlg.m_strFile, (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName));
1168 CProgressDlg pro;
1169 pro.m_GitCmd=cmd;
1170 pro.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1172 if (status)
1173 return;
1174 postCmdList.emplace_back(IDI_EXPLORER, IDS_STATUSLIST_CONTEXT_EXPLORE, [&]{ CAppUtils::ExploreTo(hWndExplorer, dlg.m_strFile); });
1177 CGit git;
1178 if (!dlg.m_bWholeProject && !dlg.m_orgPath.IsEmpty() && PathIsDirectory(dlg.m_orgPath.GetWinPathString()))
1180 git.m_CurrentDir = dlg.m_orgPath.GetWinPathString();
1181 pro.m_Git = &git;
1183 return (pro.DoModal() == IDOK);
1185 return false;
1188 bool CAppUtils::CreateBranchTag(bool isTag /*true*/, const CString* commitHash /*nullptr*/, bool switchNewBranch /*false*/, LPCTSTR name /*nullptr*/)
1190 CCreateBranchTagDlg dlg;
1191 dlg.m_bIsTag = isTag;
1192 dlg.m_bSwitch = switchNewBranch;
1194 if (commitHash)
1195 dlg.m_initialRefName = *commitHash;
1197 if (name)
1198 dlg.m_BranchTagName = name;
1200 if(dlg.DoModal()==IDOK)
1202 CString cmd;
1203 CString force;
1204 CString track;
1205 if(dlg.m_bTrack == TRUE)
1206 track=_T(" --track ");
1207 else if(dlg.m_bTrack == FALSE)
1208 track=_T(" --no-track");
1210 if(dlg.m_bForce)
1211 force=_T(" -f ");
1213 if (isTag)
1215 CString sign;
1216 if(dlg.m_bSign)
1217 sign=_T("-s");
1219 cmd.Format(_T("git.exe tag %s %s %s %s"),
1220 (LPCTSTR)force,
1221 (LPCTSTR)sign,
1222 (LPCTSTR)dlg.m_BranchTagName,
1223 (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName)
1226 if(!dlg.m_Message.Trim().IsEmpty())
1228 CString tempfile = ::GetTempFile();
1229 if (CAppUtils::SaveCommitUnicodeFile(tempfile, dlg.m_Message))
1231 CMessageBox::Show(nullptr, _T("Could not save tag message"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1232 return FALSE;
1234 cmd += _T(" -F ")+tempfile;
1237 else
1239 cmd.Format(_T("git.exe branch %s %s %s %s"),
1240 (LPCTSTR)track,
1241 (LPCTSTR)force,
1242 (LPCTSTR)dlg.m_BranchTagName,
1243 (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName)
1246 CString out;
1247 if(g_Git.Run(cmd,&out,CP_UTF8))
1249 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
1250 return FALSE;
1252 if (!isTag && dlg.m_bSwitch)
1254 // it is a new branch and the user has requested to switch to it
1255 PerformSwitch(dlg.m_BranchTagName);
1258 return TRUE;
1260 return FALSE;
1263 bool CAppUtils::Switch(const CString& initialRefName)
1265 CGitSwitchDlg dlg;
1266 if(!initialRefName.IsEmpty())
1267 dlg.m_initialRefName = initialRefName;
1269 if (dlg.DoModal() == IDOK)
1271 CString branch;
1272 if (dlg.m_bBranch)
1273 branch = dlg.m_NewBranch;
1275 // if refs/heads/ is not stripped, checkout will detach HEAD
1276 // checkout prefers branches on name clashes (with tags)
1277 if (dlg.m_VersionName.Left(11) ==_T("refs/heads/") && dlg.m_bBranchOverride != TRUE)
1278 dlg.m_VersionName = dlg.m_VersionName.Mid(11);
1280 return PerformSwitch(dlg.m_VersionName, dlg.m_bForce == TRUE , branch, dlg.m_bBranchOverride == TRUE, dlg.m_bTrack, dlg.m_bMerge == TRUE);
1282 return FALSE;
1285 bool CAppUtils::PerformSwitch(const CString& ref, bool bForce /* false */, const CString& sNewBranch /* CString() */, bool bBranchOverride /* false */, BOOL bTrack /* 2 */, bool bMerge /* false */)
1287 CString cmd;
1288 CString track;
1289 CString force;
1290 CString branch;
1291 CString merge;
1293 if(!sNewBranch.IsEmpty()){
1294 if (bBranchOverride)
1296 branch.Format(_T("-B %s "), (LPCTSTR)sNewBranch);
1298 else
1300 branch.Format(_T("-b %s "), (LPCTSTR)sNewBranch);
1302 if (bTrack == TRUE)
1303 track = _T("--track ");
1304 else if (bTrack == FALSE)
1305 track = _T("--no-track ");
1307 if (bForce)
1308 force = _T("-f ");
1309 if (bMerge)
1310 merge = _T("--merge ");
1312 cmd.Format(_T("git.exe checkout %s%s%s%s%s --"),
1313 (LPCTSTR)force,
1314 (LPCTSTR)track,
1315 (LPCTSTR)merge,
1316 (LPCTSTR)branch,
1317 (LPCTSTR)g_Git.FixBranchName(ref));
1319 CProgressDlg progress;
1320 progress.m_GitCmd = cmd;
1322 CString currentBranch;
1323 bool hasBranch = CGit::GetCurrentBranchFromFile(g_Git.m_CurrentDir, currentBranch) == 0;
1324 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1326 if (!status)
1328 CTGitPath gitPath = g_Git.m_CurrentDir;
1329 if (gitPath.HasSubmodules())
1331 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1333 CString sCmd;
1334 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
1335 RunTortoiseGitProc(sCmd);
1338 if (hasBranch)
1339 postCmdList.emplace_back(IDI_MERGE, IDS_MENUMERGE, [&]{ Merge(&currentBranch); });
1342 CString newBranch;
1343 if (!CGit::GetCurrentBranchFromFile(g_Git.m_CurrentDir, newBranch))
1344 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&]{ Pull(); });
1346 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUCOMMIT, []{
1347 CTGitPathList pathlist;
1348 CTGitPathList selectedlist;
1349 pathlist.AddPath(CTGitPath());
1350 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
1351 CString str;
1352 Commit(CString(), false, str, pathlist, selectedlist, bSelectFilesForCommit);
1355 else
1357 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ PerformSwitch(ref, bForce, sNewBranch, bBranchOverride, bTrack, bMerge); });
1358 if (!bMerge)
1359 postCmdList.emplace_back(IDI_SWITCH, IDS_SWITCH_WITH_MERGE, [&]{ PerformSwitch(ref, bForce, sNewBranch, bBranchOverride, bTrack, true); });
1363 INT_PTR ret = progress.DoModal();
1365 return ret == IDOK;
1368 class CIgnoreFile : public CStdioFile
1370 public:
1371 STRING_VECTOR m_Items;
1372 CString m_eol;
1374 virtual BOOL ReadString(CString& rString)
1376 if (GetPosition() == 0)
1378 unsigned char utf8bom[] = { 0xEF, 0xBB, 0xBF };
1379 char buf[3] = { 0, 0, 0 };
1380 Read(buf, 3);
1381 if (memcpy(buf, utf8bom, sizeof(utf8bom)))
1383 SeekToBegin();
1387 CStringA strA;
1388 char lastChar = '\0';
1389 for (char c = '\0'; Read(&c, 1) == 1; lastChar = c)
1391 if (c == '\r')
1392 continue;
1393 if (c == '\n')
1395 m_eol = lastChar == '\r' ? _T("\r\n") : _T("\n");
1396 break;
1398 strA.AppendChar(c);
1400 if (strA.IsEmpty())
1401 return FALSE;
1403 rString = CUnicodeUtils::GetUnicode(strA);
1404 return TRUE;
1407 void ResetState()
1409 m_Items.clear();
1410 m_eol.Empty();
1414 bool CAppUtils::OpenIgnoreFile(CIgnoreFile &file, const CString& filename)
1416 file.ResetState();
1417 if (!file.Open(filename, CFile::modeCreate | CFile::modeReadWrite | CFile::modeNoTruncate | CFile::typeBinary))
1419 CMessageBox::Show(NULL, filename + _T(" Open Failure"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1420 return false;
1423 if (file.GetLength() > 0)
1425 CString fileText;
1426 while (file.ReadString(fileText))
1427 file.m_Items.push_back(fileText);
1428 file.Seek(file.GetLength() - 1, 0);
1429 char lastchar[1] = { 0 };
1430 file.Read(lastchar, 1);
1431 file.SeekToEnd();
1432 if (lastchar[0] != '\n')
1434 CStringA eol = CStringA(file.m_eol.IsEmpty() ? _T("\n") : file.m_eol);
1435 file.Write(eol, eol.GetLength());
1438 else
1439 file.SeekToEnd();
1441 return true;
1444 bool CAppUtils::IgnoreFile(const CTGitPathList& path,bool IsMask)
1446 CIgnoreDlg ignoreDlg;
1447 if (ignoreDlg.DoModal() == IDOK)
1449 CString ignorefile;
1450 ignorefile = g_Git.m_CurrentDir + _T("\\");
1452 switch (ignoreDlg.m_IgnoreFile)
1454 case 0:
1455 ignorefile += _T(".gitignore");
1456 break;
1457 case 2:
1458 GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, ignorefile);
1459 ignorefile += _T("info/exclude");
1460 break;
1463 CIgnoreFile file;
1466 if (ignoreDlg.m_IgnoreFile != 1 && !OpenIgnoreFile(file, ignorefile))
1467 return false;
1469 for (int i = 0; i < path.GetCount(); ++i)
1471 if (ignoreDlg.m_IgnoreFile == 1)
1473 ignorefile = g_Git.CombinePath(path[i].GetContainingDirectory()) + _T("\\.gitignore");
1474 if (!OpenIgnoreFile(file, ignorefile))
1475 return false;
1478 CString ignorePattern;
1479 if (ignoreDlg.m_IgnoreType == 0)
1481 if (ignoreDlg.m_IgnoreFile != 1 && !path[i].GetContainingDirectory().GetGitPathString().IsEmpty())
1482 ignorePattern += _T("/") + path[i].GetContainingDirectory().GetGitPathString();
1484 ignorePattern += _T("/");
1486 if (IsMask)
1488 ignorePattern += _T("*") + path[i].GetFileExtension();
1490 else
1492 ignorePattern += path[i].GetFileOrDirectoryName();
1495 // escape [ and ] so that files get ignored correctly
1496 ignorePattern.Replace(_T("["), _T("\\["));
1497 ignorePattern.Replace(_T("]"), _T("\\]"));
1499 bool found = false;
1500 for (size_t j = 0; j < file.m_Items.size(); ++j)
1502 if (file.m_Items[j] == ignorePattern)
1504 found = true;
1505 break;
1508 if (!found)
1510 file.m_Items.push_back(ignorePattern);
1511 ignorePattern += file.m_eol.IsEmpty() ? _T("\n") : file.m_eol;
1512 CStringA ignorePatternA = CUnicodeUtils::GetUTF8(ignorePattern);
1513 file.Write(ignorePatternA, ignorePatternA.GetLength());
1516 if (ignoreDlg.m_IgnoreFile == 1)
1517 file.Close();
1520 if (ignoreDlg.m_IgnoreFile != 1)
1521 file.Close();
1523 catch(...)
1525 file.Abort();
1526 return false;
1529 return true;
1531 return false;
1534 static bool Reset(const CString& resetTo, int resetType)
1536 CString cmd;
1537 CString type;
1538 switch (resetType)
1540 case 0:
1541 type = _T("--soft");
1542 break;
1543 case 1:
1544 type = _T("--mixed");
1545 break;
1546 case 2:
1547 type = _T("--hard");
1548 break;
1549 default:
1550 resetType = 1;
1551 type = _T("--mixed");
1552 break;
1554 cmd.Format(_T("git.exe reset %s %s --"), (LPCTSTR)type, (LPCTSTR)resetTo);
1556 CProgressDlg progress;
1557 progress.m_GitCmd = cmd;
1559 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1561 if (status)
1563 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ Reset(resetTo, resetType); });
1564 return;
1567 CTGitPath gitPath = g_Git.m_CurrentDir;
1568 if (gitPath.HasSubmodules() && resetType == 2)
1570 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1572 CString sCmd;
1573 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
1574 CAppUtils::RunTortoiseGitProc(sCmd);
1579 INT_PTR ret;
1580 if (g_Git.UsingLibGit2(CGit::GIT_CMD_RESET))
1582 CGitProgressDlg gitdlg;
1583 ResetProgressCommand resetProgressCommand;
1584 gitdlg.SetCommand(&resetProgressCommand);
1585 resetProgressCommand.m_PostCmdCallback = progress.m_PostCmdCallback;
1586 resetProgressCommand.SetRevision(resetTo);
1587 resetProgressCommand.SetResetType(resetType);
1588 ret = gitdlg.DoModal();
1590 else
1591 ret = progress.DoModal();
1593 return ret == IDOK;
1596 bool CAppUtils::GitReset(const CString* CommitHash, int type)
1598 CResetDlg dlg;
1599 dlg.m_ResetType=type;
1600 dlg.m_ResetToVersion=*CommitHash;
1601 dlg.m_initialRefName = *CommitHash;
1602 if (dlg.DoModal() == IDOK)
1603 return Reset(dlg.m_ResetToVersion, dlg.m_ResetType);
1605 return false;
1608 void CAppUtils::DescribeConflictFile(bool mode, bool base,CString &descript)
1610 if(mode == FALSE)
1612 descript.LoadString(IDS_SVNACTION_DELETE);
1613 return;
1615 if(base)
1617 descript.LoadString(IDS_SVNACTION_MODIFIED);
1618 return;
1620 descript.LoadString(IDS_PROC_CREATED);
1623 void CAppUtils::RemoveTempMergeFile(const CTGitPath& path)
1625 ::DeleteFile(CAppUtils::GetMergeTempFile(_T("LOCAL"), path));
1626 ::DeleteFile(CAppUtils::GetMergeTempFile(_T("REMOTE"), path));
1627 ::DeleteFile(CAppUtils::GetMergeTempFile(_T("BASE"), path));
1629 CString CAppUtils::GetMergeTempFile(const CString& type, const CTGitPath &merge)
1631 CString file;
1632 file = g_Git.CombinePath(merge.GetWinPathString() + _T(".") + type + merge.GetFileExtension());
1634 return file;
1637 bool ParseHashesFromLsFile(const BYTE_VECTOR& out, CString& hash1, CString& hash2, CString& hash3)
1639 int pos = 0;
1640 CString one;
1641 CString part;
1643 while (pos >= 0 && pos < (int)out.size())
1645 one.Empty();
1647 CGit::StringAppend(&one, &out[pos], CP_UTF8);
1648 int tabstart = 0;
1649 one.Tokenize(_T("\t"), tabstart);
1651 tabstart = 0;
1652 part = one.Tokenize(_T(" "), tabstart); //Tag
1653 part = one.Tokenize(_T(" "), tabstart); //Mode
1654 part = one.Tokenize(_T(" "), tabstart); //Hash
1655 CString hash = part;
1656 part = one.Tokenize(_T("\t"), tabstart); //Stage
1657 int stage = _ttol(part);
1658 if (stage == 1)
1659 hash1 = hash;
1660 else if (stage == 2)
1661 hash2 = hash;
1662 else if (stage == 3)
1664 hash3 = hash;
1665 return true;
1668 pos = out.findNextString(pos);
1671 return false;
1674 bool CAppUtils::ConflictEdit(const CTGitPath& path, bool /*bAlternativeTool = false*/, bool revertTheirMy /*= false*/, HWND resolveMsgHwnd /*= nullptr*/)
1676 bool bRet = false;
1678 CTGitPath merge=path;
1679 CTGitPath directory = merge.GetDirectory();
1681 // we have the conflicted file (%merged)
1682 // now look for the other required files
1683 //GitStatus stat;
1684 //stat.GetStatus(merge);
1685 //if (stat.status == NULL)
1686 // return false;
1688 BYTE_VECTOR vector;
1690 CString cmd;
1691 cmd.Format(_T("git.exe ls-files -u -t -z -- \"%s\""), (LPCTSTR)merge.GetGitPathString());
1693 if (g_Git.Run(cmd, &vector))
1695 return FALSE;
1698 if (merge.IsDirectory())
1700 CString baseHash, realBaseHash(GIT_REV_ZERO), localHash(GIT_REV_ZERO), remoteHash(GIT_REV_ZERO);
1701 if (merge.HasAdminDir()) {
1702 CGit subgit;
1703 subgit.m_CurrentDir = g_Git.CombinePath(merge);
1704 CGitHash hash;
1705 subgit.GetHash(hash, _T("HEAD"));
1706 baseHash = hash;
1708 if (ParseHashesFromLsFile(vector, realBaseHash, localHash, remoteHash)) // in base no submodule, but in remote submodule
1709 baseHash = realBaseHash;
1711 CGitDiff::ChangeType changeTypeMine = CGitDiff::Unknown;
1712 CGitDiff::ChangeType changeTypeTheirs = CGitDiff::Unknown;
1714 bool baseOK = false, mineOK = false, theirsOK = false;
1715 CString baseSubject, mineSubject, theirsSubject;
1716 if (merge.HasAdminDir())
1718 CGit subgit;
1719 subgit.m_CurrentDir = g_Git.CombinePath(merge);
1720 CGitDiff::GetSubmoduleChangeType(subgit, baseHash, localHash, baseOK, mineOK, changeTypeMine, baseSubject, mineSubject);
1721 CGitDiff::GetSubmoduleChangeType(subgit, baseHash, remoteHash, baseOK, theirsOK, changeTypeTheirs, baseSubject, theirsSubject);
1723 else if (baseHash == GIT_REV_ZERO && localHash == GIT_REV_ZERO && remoteHash != GIT_REV_ZERO) // merge conflict with no submodule, but submodule in merged revision (not initialized)
1725 changeTypeMine = CGitDiff::Identical;
1726 changeTypeTheirs = CGitDiff::NewSubmodule;
1727 baseSubject = _T("no submodule");
1728 mineSubject = baseSubject;
1729 theirsSubject = _T("not initialized");
1731 else if (baseHash.IsEmpty() && localHash != GIT_REV_ZERO && remoteHash == GIT_REV_ZERO) // merge conflict with no submodule initialized, but submodule exists in base and folder with no submodule is merged
1733 baseHash = localHash;
1734 baseSubject = _T("not initialized");
1735 mineSubject = baseSubject;
1736 theirsSubject = _T("not initialized");
1737 changeTypeMine = CGitDiff::Identical;
1738 changeTypeTheirs = CGitDiff::DeleteSubmodule;
1740 else if (baseHash != GIT_REV_ZERO && localHash != GIT_REV_ZERO && remoteHash != GIT_REV_ZERO) // base has submodule, mine has submodule and theirs also, but not initialized
1742 baseSubject = _T("not initialized");
1743 mineSubject = baseSubject;
1744 theirsSubject = baseSubject;
1745 if (baseHash == localHash)
1746 changeTypeMine = CGitDiff::Identical;
1748 else
1749 return FALSE;
1751 CSubmoduleResolveConflictDlg resolveSubmoduleConflictDialog;
1752 resolveSubmoduleConflictDialog.SetDiff(merge.GetGitPathString(), revertTheirMy, baseHash, baseSubject, baseOK, localHash, mineSubject, mineOK, changeTypeMine, remoteHash, theirsSubject, theirsOK, changeTypeTheirs);
1753 resolveSubmoduleConflictDialog.DoModal();
1754 if (resolveSubmoduleConflictDialog.m_bResolved && resolveMsgHwnd)
1756 static UINT WM_REVERTMSG = RegisterWindowMessage(_T("GITSLNM_NEEDSREFRESH"));
1757 ::PostMessage(resolveMsgHwnd, WM_REVERTMSG, NULL, NULL);
1760 return TRUE;
1763 CTGitPathList list;
1764 if (list.ParserFromLsFile(vector))
1766 CMessageBox::Show(NULL, _T("Parse ls-files failed!"), _T("TortoiseGit"), MB_OK);
1767 return FALSE;
1770 if (list.IsEmpty())
1771 return FALSE;
1773 CTGitPath theirs;
1774 CTGitPath mine;
1775 CTGitPath base;
1777 if (revertTheirMy)
1779 mine.SetFromGit(GetMergeTempFile(_T("REMOTE"), merge));
1780 theirs.SetFromGit(GetMergeTempFile(_T("LOCAL"), merge));
1782 else
1784 mine.SetFromGit(GetMergeTempFile(_T("LOCAL"), merge));
1785 theirs.SetFromGit(GetMergeTempFile(_T("REMOTE"), merge));
1787 base.SetFromGit(GetMergeTempFile(_T("BASE"),merge));
1789 CString format;
1791 //format=_T("git.exe cat-file blob \":%d:%s\"");
1792 format = _T("git.exe checkout-index --temp --stage=%d -- \"%s\"");
1793 CFile tempfile;
1794 //create a empty file, incase stage is not three
1795 tempfile.Open(mine.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1796 tempfile.Close();
1797 tempfile.Open(theirs.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1798 tempfile.Close();
1799 tempfile.Open(base.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1800 tempfile.Close();
1802 bool b_base=false, b_local=false, b_remote=false;
1804 for (int i = 0; i< list.GetCount(); ++i)
1806 CString outfile;
1807 cmd.Empty();
1808 outfile.Empty();
1810 if( list[i].m_Stage == 1)
1812 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1813 b_base = true;
1814 outfile = base.GetWinPathString();
1817 if( list[i].m_Stage == 2 )
1819 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1820 b_local = true;
1821 outfile = mine.GetWinPathString();
1824 if( list[i].m_Stage == 3 )
1826 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1827 b_remote = true;
1828 outfile = theirs.GetWinPathString();
1830 CString output, err;
1831 if(!outfile.IsEmpty())
1832 if (!g_Git.Run(cmd, &output, &err, CP_UTF8))
1834 CString file;
1835 int start =0 ;
1836 file = output.Tokenize(_T("\t"), start);
1837 ::MoveFileEx(file,outfile,MOVEFILE_REPLACE_EXISTING|MOVEFILE_COPY_ALLOWED);
1839 else
1841 CMessageBox::Show(NULL, output + L"\n" + err, _T("TortoiseGit"), MB_OK|MB_ICONERROR);
1845 if(b_local && b_remote )
1847 merge.SetFromWin(g_Git.CombinePath(merge));
1848 if( revertTheirMy )
1849 bRet = !!CAppUtils::StartExtMerge(base, mine, theirs, merge, _T("BASE"), _T("REMOTE"), _T("LOCAL"), CString(), false, resolveMsgHwnd, true);
1850 else
1851 bRet = !!CAppUtils::StartExtMerge(base, theirs, mine, merge, _T("BASE"), _T("REMOTE"), _T("LOCAL"), CString(), false, resolveMsgHwnd, true);
1854 else
1856 ::DeleteFile(mine.GetWinPathString());
1857 ::DeleteFile(theirs.GetWinPathString());
1858 ::DeleteFile(base.GetWinPathString());
1860 CDeleteConflictDlg dlg;
1861 DescribeConflictFile(b_local, b_base,dlg.m_LocalStatus);
1862 DescribeConflictFile(b_remote,b_base,dlg.m_RemoteStatus);
1863 CGitHash localHash, remoteHash;
1864 if (!g_Git.GetHash(localHash, _T("HEAD")))
1865 dlg.m_LocalHash = localHash.ToString();
1866 if (!g_Git.GetHash(remoteHash, _T("MERGE_HEAD")))
1867 dlg.m_RemoteHash = remoteHash.ToString();
1868 else if (!g_Git.GetHash(remoteHash, _T("rebase-apply/original-commit")))
1869 dlg.m_RemoteHash = remoteHash.ToString();
1870 else if (!g_Git.GetHash(remoteHash, _T("CHERRY_PICK_HEAD")))
1871 dlg.m_RemoteHash = remoteHash.ToString();
1872 else if (!g_Git.GetHash(remoteHash, _T("REVERT_HEAD")))
1873 dlg.m_RemoteHash = remoteHash.ToString();
1874 dlg.m_bShowModifiedButton=b_base;
1875 dlg.m_File=merge.GetGitPathString();
1876 if(dlg.DoModal() == IDOK)
1878 CString out;
1879 if(dlg.m_bIsDelete)
1881 cmd.Format(_T("git.exe rm -- \"%s\""), (LPCTSTR)merge.GetGitPathString());
1883 else
1884 cmd.Format(_T("git.exe add -- \"%s\""), (LPCTSTR)merge.GetGitPathString());
1886 if (g_Git.Run(cmd, &out, CP_UTF8))
1888 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
1889 return FALSE;
1891 return TRUE;
1893 else
1894 return FALSE;
1897 #if 0
1898 CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool),
1899 base, theirs, mine, merge);
1900 #endif
1901 #if 0
1902 if (stat.status->text_status == svn_wc_status_conflicted)
1904 // we have a text conflict, use our merge tool to resolve the conflict
1906 CTSVNPath theirs(directory);
1907 CTSVNPath mine(directory);
1908 CTSVNPath base(directory);
1909 bool bConflictData = false;
1911 if ((stat.status->entry)&&(stat.status->entry->conflict_new))
1913 theirs.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->conflict_new));
1914 bConflictData = true;
1916 if ((stat.status->entry)&&(stat.status->entry->conflict_old))
1918 base.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->conflict_old));
1919 bConflictData = true;
1921 if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
1923 mine.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->conflict_wrk));
1924 bConflictData = true;
1926 else
1928 mine = merge;
1930 if (bConflictData)
1931 bRet = !!CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool),
1932 base, theirs, mine, merge);
1935 if (stat.status->prop_status == svn_wc_status_conflicted)
1937 // we have a property conflict
1938 CTSVNPath prej(directory);
1939 if ((stat.status->entry)&&(stat.status->entry->prejfile))
1941 prej.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->prejfile));
1942 // there's a problem: the prej file contains a _description_ of the conflict, and
1943 // that description string might be translated. That means we have no way of parsing
1944 // the file to find out the conflicting values.
1945 // The only thing we can do: show a dialog with the conflict description, then
1946 // let the user either accept the existing property or open the property edit dialog
1947 // to manually change the properties and values. And a button to mark the conflict as
1948 // resolved.
1949 CEditPropConflictDlg dlg;
1950 dlg.SetPrejFile(prej);
1951 dlg.SetConflictedItem(merge);
1952 bRet = (dlg.DoModal() != IDCANCEL);
1956 if (stat.status->tree_conflict)
1958 // we have a tree conflict
1959 SVNInfo info;
1960 const SVNInfoData * pInfoData = info.GetFirstFileInfo(merge, SVNRev(), SVNRev());
1961 if (pInfoData)
1963 if (pInfoData->treeconflict_kind == svn_wc_conflict_kind_text)
1965 CTSVNPath theirs(directory);
1966 CTSVNPath mine(directory);
1967 CTSVNPath base(directory);
1968 bool bConflictData = false;
1970 if (pInfoData->treeconflict_theirfile)
1972 theirs.AppendPathString(pInfoData->treeconflict_theirfile);
1973 bConflictData = true;
1975 if (pInfoData->treeconflict_basefile)
1977 base.AppendPathString(pInfoData->treeconflict_basefile);
1978 bConflictData = true;
1980 if (pInfoData->treeconflict_myfile)
1982 mine.AppendPathString(pInfoData->treeconflict_myfile);
1983 bConflictData = true;
1985 else
1987 mine = merge;
1989 if (bConflictData)
1990 bRet = !!CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool),
1991 base, theirs, mine, merge);
1993 else if (pInfoData->treeconflict_kind == svn_wc_conflict_kind_tree)
1995 CString sConflictAction;
1996 CString sConflictReason;
1997 CString sResolveTheirs;
1998 CString sResolveMine;
1999 CTSVNPath treeConflictPath = CTSVNPath(pInfoData->treeconflict_path);
2000 CString sItemName = treeConflictPath.GetUIFileOrDirectoryName();
2002 if (pInfoData->treeconflict_nodekind == svn_node_file)
2004 switch (pInfoData->treeconflict_operation)
2006 case svn_wc_operation_update:
2007 switch (pInfoData->treeconflict_action)
2009 case svn_wc_conflict_action_edit:
2010 sConflictAction.Format(IDS_TREECONFLICT_FILEUPDATEEDIT, (LPCTSTR)sItemName);
2011 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2012 break;
2013 case svn_wc_conflict_action_add:
2014 sConflictAction.Format(IDS_TREECONFLICT_FILEUPDATEADD, (LPCTSTR)sItemName);
2015 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2016 break;
2017 case svn_wc_conflict_action_delete:
2018 sConflictAction.Format(IDS_TREECONFLICT_FILEUPDATEDELETE, (LPCTSTR)sItemName);
2019 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2020 break;
2022 break;
2023 case svn_wc_operation_switch:
2024 switch (pInfoData->treeconflict_action)
2026 case svn_wc_conflict_action_edit:
2027 sConflictAction.Format(IDS_TREECONFLICT_FILESWITCHEDIT, (LPCTSTR)sItemName);
2028 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2029 break;
2030 case svn_wc_conflict_action_add:
2031 sConflictAction.Format(IDS_TREECONFLICT_FILESWITCHADD, (LPCTSTR)sItemName);
2032 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2033 break;
2034 case svn_wc_conflict_action_delete:
2035 sConflictAction.Format(IDS_TREECONFLICT_FILESWITCHDELETE, (LPCTSTR)sItemName);
2036 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2037 break;
2039 break;
2040 case svn_wc_operation_merge:
2041 switch (pInfoData->treeconflict_action)
2043 case svn_wc_conflict_action_edit:
2044 sConflictAction.Format(IDS_TREECONFLICT_FILEMERGEEDIT, (LPCTSTR)sItemName);
2045 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2046 break;
2047 case svn_wc_conflict_action_add:
2048 sResolveTheirs.Format(IDS_TREECONFLICT_FILEMERGEADD, (LPCTSTR)sItemName);
2049 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2050 break;
2051 case svn_wc_conflict_action_delete:
2052 sConflictAction.Format(IDS_TREECONFLICT_FILEMERGEDELETE, (LPCTSTR)sItemName);
2053 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2054 break;
2056 break;
2059 else if (pInfoData->treeconflict_nodekind == svn_node_dir)
2061 switch (pInfoData->treeconflict_operation)
2063 case svn_wc_operation_update:
2064 switch (pInfoData->treeconflict_action)
2066 case svn_wc_conflict_action_edit:
2067 sConflictAction.Format(IDS_TREECONFLICT_DIRUPDATEEDIT, (LPCTSTR)sItemName);
2068 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2069 break;
2070 case svn_wc_conflict_action_add:
2071 sConflictAction.Format(IDS_TREECONFLICT_DIRUPDATEADD, (LPCTSTR)sItemName);
2072 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2073 break;
2074 case svn_wc_conflict_action_delete:
2075 sConflictAction.Format(IDS_TREECONFLICT_DIRUPDATEDELETE, (LPCTSTR)sItemName);
2076 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR);
2077 break;
2079 break;
2080 case svn_wc_operation_switch:
2081 switch (pInfoData->treeconflict_action)
2083 case svn_wc_conflict_action_edit:
2084 sConflictAction.Format(IDS_TREECONFLICT_DIRSWITCHEDIT, (LPCTSTR)sItemName);
2085 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2086 break;
2087 case svn_wc_conflict_action_add:
2088 sConflictAction.Format(IDS_TREECONFLICT_DIRSWITCHADD, (LPCTSTR)sItemName);
2089 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2090 break;
2091 case svn_wc_conflict_action_delete:
2092 sConflictAction.Format(IDS_TREECONFLICT_DIRSWITCHDELETE, (LPCTSTR)sItemName);
2093 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR);
2094 break;
2096 break;
2097 case svn_wc_operation_merge:
2098 switch (pInfoData->treeconflict_action)
2100 case svn_wc_conflict_action_edit:
2101 sConflictAction.Format(IDS_TREECONFLICT_DIRMERGEEDIT, (LPCTSTR)sItemName);
2102 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2103 break;
2104 case svn_wc_conflict_action_add:
2105 sConflictAction.Format(IDS_TREECONFLICT_DIRMERGEADD, (LPCTSTR)sItemName);
2106 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2107 break;
2108 case svn_wc_conflict_action_delete:
2109 sConflictAction.Format(IDS_TREECONFLICT_DIRMERGEDELETE, (LPCTSTR)sItemName);
2110 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR);
2111 break;
2113 break;
2117 UINT uReasonID = 0;
2118 switch (pInfoData->treeconflict_reason)
2120 case svn_wc_conflict_reason_edited:
2121 uReasonID = IDS_TREECONFLICT_REASON_EDITED;
2122 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2123 break;
2124 case svn_wc_conflict_reason_obstructed:
2125 uReasonID = IDS_TREECONFLICT_REASON_OBSTRUCTED;
2126 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2127 break;
2128 case svn_wc_conflict_reason_deleted:
2129 uReasonID = IDS_TREECONFLICT_REASON_DELETED;
2130 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_REMOVEDIR : IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2131 break;
2132 case svn_wc_conflict_reason_added:
2133 uReasonID = IDS_TREECONFLICT_REASON_ADDED;
2134 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2135 break;
2136 case svn_wc_conflict_reason_missing:
2137 uReasonID = IDS_TREECONFLICT_REASON_MISSING;
2138 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_REMOVEDIR : IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2139 break;
2140 case svn_wc_conflict_reason_unversioned:
2141 uReasonID = IDS_TREECONFLICT_REASON_UNVERSIONED;
2142 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2143 break;
2145 sConflictReason.Format(uReasonID, (LPCTSTR)sConflictAction);
2147 CTreeConflictEditorDlg dlg;
2148 dlg.SetConflictInfoText(sConflictReason);
2149 dlg.SetResolveTexts(sResolveTheirs, sResolveMine);
2150 dlg.SetPath(treeConflictPath);
2151 INT_PTR dlgRet = dlg.DoModal();
2152 bRet = (dlgRet != IDCANCEL);
2156 #endif
2157 return bRet;
2160 bool CAppUtils::IsSSHPutty()
2162 CString sshclient=g_Git.m_Environment.GetEnv(_T("GIT_SSH"));
2163 sshclient=sshclient.MakeLower();
2164 if(sshclient.Find(_T("plink.exe"),0)>=0)
2166 return true;
2168 return false;
2171 CString CAppUtils::GetClipboardLink(const CString &skipGitPrefix, int paramsCount)
2173 if (!OpenClipboard(NULL))
2174 return CString();
2176 CString sClipboardText;
2177 HGLOBAL hglb = GetClipboardData(CF_TEXT);
2178 if (hglb)
2180 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
2181 sClipboardText = CString(lpstr);
2182 GlobalUnlock(hglb);
2184 hglb = GetClipboardData(CF_UNICODETEXT);
2185 if (hglb)
2187 LPCTSTR lpstr = (LPCTSTR)GlobalLock(hglb);
2188 sClipboardText = lpstr;
2189 GlobalUnlock(hglb);
2191 CloseClipboard();
2193 if(!sClipboardText.IsEmpty())
2195 if(sClipboardText[0] == _T('\"') && sClipboardText[sClipboardText.GetLength()-1] == _T('\"'))
2196 sClipboardText=sClipboardText.Mid(1,sClipboardText.GetLength()-2);
2198 if(sClipboardText.Find( _T("http://")) == 0)
2199 return sClipboardText;
2201 if(sClipboardText.Find( _T("https://")) == 0)
2202 return sClipboardText;
2204 if(sClipboardText.Find( _T("git://")) == 0)
2205 return sClipboardText;
2207 if(sClipboardText.Find( _T("ssh://")) == 0)
2208 return sClipboardText;
2210 if (sClipboardText.Find(_T("git@")) == 0)
2211 return sClipboardText;
2213 if(sClipboardText.GetLength()>=2)
2214 if( sClipboardText[1] == _T(':') )
2215 if( (sClipboardText[0] >= 'A' && sClipboardText[0] <= 'Z')
2216 || (sClipboardText[0] >= 'a' && sClipboardText[0] <= 'z') )
2217 return sClipboardText;
2219 // trim prefixes like "git clone "
2220 if (!skipGitPrefix.IsEmpty() && sClipboardText.Find(skipGitPrefix) == 0)
2222 sClipboardText = sClipboardText.Mid(skipGitPrefix.GetLength()).Trim();
2223 int spacePos = -1;
2224 while (paramsCount >= 0)
2226 --paramsCount;
2227 spacePos = sClipboardText.Find(_T(' '), spacePos + 1);
2228 if (spacePos == -1)
2229 break;
2231 if (spacePos > 0 && paramsCount < 0)
2232 sClipboardText = sClipboardText.Left(spacePos);
2233 return sClipboardText;
2237 return CString();
2240 CString CAppUtils::ChooseRepository(const CString* path)
2242 CBrowseFolder browseFolder;
2243 CRegString regLastResopitory = CRegString(_T("Software\\TortoiseGit\\TortoiseProc\\LastRepo"),_T(""));
2245 browseFolder.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
2246 CString strCloneDirectory;
2247 if(path)
2248 strCloneDirectory=*path;
2249 else
2251 strCloneDirectory = regLastResopitory;
2254 CString title;
2255 title.LoadString(IDS_CHOOSE_REPOSITORY);
2257 browseFolder.SetInfo(title);
2259 if (browseFolder.Show(NULL, strCloneDirectory) == CBrowseFolder::OK)
2261 regLastResopitory = strCloneDirectory;
2262 return strCloneDirectory;
2264 else
2266 return CString();
2270 bool CAppUtils::SendPatchMail(CTGitPathList& list, bool bIsMainWnd)
2272 CSendMailDlg dlg;
2274 dlg.m_PathList = list;
2276 if(dlg.DoModal()==IDOK)
2278 if (dlg.m_PathList.IsEmpty())
2279 return FALSE;
2281 CGitProgressDlg progDlg;
2282 if (bIsMainWnd)
2283 theApp.m_pMainWnd = &progDlg;
2284 SendMailProgressCommand sendMailProgressCommand;
2285 progDlg.SetCommand(&sendMailProgressCommand);
2287 sendMailProgressCommand.SetPathList(dlg.m_PathList);
2288 progDlg.SetItemCount(dlg.m_PathList.GetCount());
2290 CSendMailPatch sendMailPatch(dlg.m_To, dlg.m_CC, dlg.m_Subject, !!dlg.m_bAttachment, !!dlg.m_bCombine);
2291 sendMailProgressCommand.SetSendMailOption(&sendMailPatch);
2293 progDlg.DoModal();
2295 return true;
2297 return false;
2300 bool CAppUtils::SendPatchMail(const CString& cmd, const CString& formatpatchoutput, bool bIsMainWnd)
2302 CTGitPathList list;
2303 CString log=formatpatchoutput;
2304 int start=log.Find(cmd);
2305 if(start >=0)
2306 CString one=log.Tokenize(_T("\n"),start);
2307 else
2308 start = 0;
2310 while(start>=0)
2312 CString one=log.Tokenize(_T("\n"),start);
2313 one=one.Trim();
2314 if (one.IsEmpty() || one.Find(CString(MAKEINTRESOURCE(IDS_SUCCESS))) == 0)
2315 continue;
2316 one.Replace(_T('/'),_T('\\'));
2317 CTGitPath path;
2318 path.SetFromWin(one);
2319 list.AddPath(path);
2321 if (!list.IsEmpty())
2323 return SendPatchMail(list, bIsMainWnd);
2325 else
2327 CMessageBox::Show(NULL, IDS_ERR_NOPATCHES, IDS_APPNAME, MB_ICONINFORMATION);
2328 return true;
2333 int CAppUtils::GetLogOutputEncode(CGit *pGit)
2335 CString output;
2336 output = pGit->GetConfigValue(_T("i18n.logOutputEncoding"));
2337 if(output.IsEmpty())
2338 return CUnicodeUtils::GetCPCode(pGit->GetConfigValue(_T("i18n.commitencoding")));
2339 else
2341 return CUnicodeUtils::GetCPCode(output);
2344 int CAppUtils::SaveCommitUnicodeFile(const CString& filename, CString &message)
2348 CFile file(filename, CFile::modeReadWrite | CFile::modeCreate);
2349 int cp = CUnicodeUtils::GetCPCode(g_Git.GetConfigValue(_T("i18n.commitencoding")));
2351 bool stripComments = (CRegDWORD(_T("Software\\TortoiseGit\\StripCommentedLines"), FALSE) == TRUE);
2353 if (CRegDWORD(_T("Software\\TortoiseGit\\SanitizeCommitMsg"), TRUE) == TRUE)
2354 message.TrimRight(L" \r\n");
2356 int len = message.GetLength();
2357 int start = 0;
2358 while (start >= 0 && start < len)
2360 int oldStart = start;
2361 start = message.Find(L"\n", oldStart);
2362 CString line = message.Mid(oldStart);
2363 if (start != -1)
2365 line = line.Left(start - oldStart);
2366 ++start; // move forward so we don't find the same char again
2368 if (stripComments && (!line.IsEmpty() && line.GetAt(0) == '#') || (start < 0 && line.IsEmpty()))
2369 continue;
2370 line.TrimRight(L" \r");
2371 CStringA lineA = CUnicodeUtils::GetMulti(line + L"\n", cp);
2372 file.Write((LPCSTR)lineA, lineA.GetLength());
2374 file.Close();
2375 return 0;
2377 catch (CFileException *e)
2379 e->Delete();
2380 return -1;
2384 bool CAppUtils::Pull(bool showPush, bool showStashPop)
2386 CPullFetchDlg dlg;
2387 dlg.m_IsPull = TRUE;
2388 if (dlg.DoModal() == IDOK)
2390 // "git.exe pull --rebase" is not supported, never and ever. So, adapting it to Fetch & Rebase.
2391 if (dlg.m_bRebase)
2392 return DoFetch(dlg.m_RemoteURL,
2393 FALSE, // Fetch all remotes
2394 dlg.m_bAutoLoad == BST_CHECKED,
2395 dlg.m_bPrune,
2396 dlg.m_bDepth == BST_CHECKED,
2397 dlg.m_nDepth,
2398 dlg.m_bFetchTags,
2399 dlg.m_RemoteBranchName,
2400 TRUE); // Rebase after fetching
2402 CString url = dlg.m_RemoteURL;
2404 if (dlg.m_bAutoLoad)
2406 CAppUtils::LaunchPAgent(NULL, &dlg.m_RemoteURL);
2409 CString cmd;
2410 CGitHash hashOld;
2411 if (g_Git.GetHash(hashOld, _T("HEAD")))
2413 MessageBox(NULL, g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
2414 return false;
2417 CString cmdRebase;
2418 CString noff;
2419 CString ffonly;
2420 CString squash;
2421 CString nocommit;
2422 CString depth;
2423 CString notags;
2424 CString prune;
2426 if (!dlg.m_bFetchTags)
2427 notags = _T("--no-tags ");
2429 if (dlg.m_bFetchTags == TRUE)
2430 notags = _T("--tags ");
2432 if (dlg.m_bNoFF)
2433 noff=_T("--no-ff ");
2435 if (dlg.m_bFFonly)
2436 ffonly = _T("--ff-only ");
2438 if (dlg.m_bSquash)
2439 squash = _T("--squash ");
2441 if (dlg.m_bNoCommit)
2442 nocommit = _T("--no-commit ");
2444 if (dlg.m_bDepth)
2445 depth.Format(_T("--depth %d "), dlg.m_nDepth);
2447 int ver = CAppUtils::GetMsysgitVersion();
2449 if (dlg.m_bPrune == TRUE)
2450 prune = _T("--prune ");
2451 else if (dlg.m_bPrune == FALSE && ver >= 0x01080500)
2452 prune = _T("--no-prune ");
2454 if(ver >= 0x01070203) //above 1.7.0.2
2455 cmdRebase += _T("--progress ");
2457 cmd.Format(_T("git.exe pull -v %s%s%s%s%s%s%s%s\"%s\" %s"), (LPCTSTR)cmdRebase, (LPCTSTR)noff, (LPCTSTR)ffonly, (LPCTSTR)squash, (LPCTSTR)nocommit, (LPCTSTR)depth, (LPCTSTR)notags, (LPCTSTR)prune, (LPCTSTR)url, (LPCTSTR)dlg.m_RemoteBranchName);
2458 CProgressDlg progress;
2459 progress.m_GitCmd = cmd;
2461 CGitHash hashNew; // declare outside lambda, because it is captured by reference
2462 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2464 if (status)
2466 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&]{ Pull(); });
2467 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSTASHSAVE, [&]{ StashSave(_T(""), true); });
2468 return;
2471 if (showStashPop)
2472 postCmdList.emplace_back(IDI_RELOCATE, IDS_MENUSTASHPOP, []{ StashPop(); });
2474 if (g_Git.GetHash(hashNew, _T("HEAD")))
2475 MessageBox(nullptr, g_Git.GetGitLastErr(_T("Could not get HEAD hash after pulling.")), _T("TortoiseGit"), MB_ICONERROR);
2476 else
2478 postCmdList.emplace_back(IDI_DIFF, IDS_PROC_PULL_DIFFS, [&]
2480 CFileDiffDlg dlg;
2481 dlg.SetDiff(NULL, hashNew.ToString(), hashOld.ToString());
2482 dlg.DoModal();
2484 postCmdList.emplace_back(IDI_LOG, IDS_PROC_PULL_LOG, [&]
2486 CLogDlg dlg;
2487 dlg.SetParams(CTGitPath(_T("")), CTGitPath(_T("")), _T(""), hashOld.ToString() + _T("..") + hashNew.ToString(), 0);
2488 dlg.DoModal();
2492 if (showPush)
2493 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, []{ Push(); });
2495 CTGitPath gitPath = g_Git.m_CurrentDir;
2496 if (gitPath.HasSubmodules())
2498 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
2500 CString sCmd;
2501 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
2502 CAppUtils::RunTortoiseGitProc(sCmd);
2507 INT_PTR ret = progress.DoModal();
2509 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)
2511 CChangedDlg changeddlg;
2512 changeddlg.m_pathList.AddPath(CTGitPath());
2513 changeddlg.DoModal();
2515 return true;
2518 return ret == IDOK;
2521 return false;
2524 bool CAppUtils::RebaseAfterFetch(const CString& upstream)
2526 while (true)
2528 CRebaseDlg dlg;
2529 if (!upstream.IsEmpty())
2530 dlg.m_Upstream = upstream;
2531 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENULOG)));
2532 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUPUSH)));
2533 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUDESSENDMAIL)));
2534 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUREBASE)));
2535 INT_PTR response = dlg.DoModal();
2536 if (response == IDOK)
2538 return true;
2540 else if (response == IDC_REBASE_POST_BUTTON)
2542 CString cmd = _T("/command:log");
2543 cmd += _T(" /path:\"") + g_Git.m_CurrentDir + _T("\"");
2544 CAppUtils::RunTortoiseGitProc(cmd);
2545 return true;
2547 else if (response == IDC_REBASE_POST_BUTTON + 1)
2548 return Push();
2549 else if (response == IDC_REBASE_POST_BUTTON + 2)
2551 CString cmd, out, err;
2552 cmd.Format(_T("git.exe format-patch -o \"%s\" %s..%s"),
2553 (LPCTSTR)g_Git.m_CurrentDir,
2554 (LPCTSTR)g_Git.FixBranchName(dlg.m_Upstream),
2555 (LPCTSTR)g_Git.FixBranchName(dlg.m_Branch));
2556 if (g_Git.Run(cmd, &out, &err, CP_UTF8))
2558 CMessageBox::Show(NULL, out + L"\n" + err, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
2559 return false;
2561 CAppUtils::SendPatchMail(cmd, out);
2562 return true;
2564 else if (response == IDC_REBASE_POST_BUTTON + 3)
2565 continue;
2566 else if (response == IDCANCEL)
2567 return false;
2568 return false;
2572 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, boolean runRebase)
2574 if (loadPuttyAgent)
2576 if (fetchAllRemotes)
2578 STRING_VECTOR list;
2579 g_Git.GetRemoteList(list);
2581 for (const auto& remote : list)
2583 CAppUtils::LaunchPAgent(NULL, &remote);
2586 else
2587 CAppUtils::LaunchPAgent(NULL, &url);
2590 CString upstream = _T("FETCH_HEAD");
2591 CGitHash oldUpstreamHash;
2592 if (runRebase)
2594 STRING_VECTOR list;
2595 g_Git.GetRemoteList(list);
2596 for (auto it = list.cbegin(); it != list.cend(); ++it)
2598 if (url == *it)
2600 CString remote, trackedBranch;
2601 g_Git.GetRemoteTrackedBranchForHEAD(remote, trackedBranch);
2602 if (!remote.IsEmpty() && !trackedBranch.IsEmpty())
2604 upstream = remote + _T("/") + trackedBranch;
2605 g_Git.GetHash(oldUpstreamHash, upstream);
2607 break;
2612 CString cmd, arg;
2613 int ver = CAppUtils::GetMsysgitVersion();
2614 if (ver >= 0x01070203) //above 1.7.0.2
2615 arg += _T(" --progress");
2617 if (bDepth)
2618 arg.AppendFormat(_T(" --depth %d"), nDepth);
2620 if (prune == TRUE)
2621 arg += _T(" --prune");
2622 else if (prune == FALSE && ver >= 0x01080500)
2623 arg += _T(" --no-prune");
2625 if (fetchTags == 1)
2626 arg += _T(" --tags");
2627 else if (fetchTags == 0)
2628 arg += _T(" --no-tags");
2630 if (fetchAllRemotes)
2631 cmd.Format(_T("git.exe fetch --all -v%s"), (LPCTSTR)arg);
2632 else
2633 cmd.Format(_T("git.exe fetch -v%s \"%s\" %s"), (LPCTSTR)arg, (LPCTSTR)url, (LPCTSTR)remoteBranch);
2635 CProgressDlg progress;
2636 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2638 if (status)
2640 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ DoFetch(url, fetchAllRemotes, loadPuttyAgent, prune, bDepth, nDepth, fetchTags, remoteBranch, runRebase); });
2641 return;
2644 postCmdList.emplace_back(IDI_LOG, IDS_MENULOG, []
2646 CString cmd = _T("/command:log");
2647 cmd += _T(" /path:\"") + g_Git.m_CurrentDir + _T("\"");
2648 CAppUtils::RunTortoiseGitProc(cmd);
2651 postCmdList.emplace_back(IDI_REVERT, IDS_PROC_RESET, []
2653 CString pullRemote, pullBranch;
2654 g_Git.GetRemoteTrackedBranchForHEAD(pullRemote, pullBranch);
2655 CString defaultUpstream;
2656 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
2657 defaultUpstream.Format(_T("remotes/%s/%s"), (LPCTSTR)pullRemote, (LPCTSTR)pullBranch);
2658 CAppUtils::GitReset(&defaultUpstream, 2);
2661 postCmdList.emplace_back(IDI_PULL, IDS_MENUFETCH, []{ CAppUtils::Fetch(); });
2663 if (!runRebase && !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir))
2664 postCmdList.emplace_back(IDI_REBASE, IDS_MENUREBASE, [&]{ runRebase = false; CAppUtils::RebaseAfterFetch(); });
2667 progress.m_GitCmd = cmd;
2668 INT_PTR userResponse;
2670 if (g_Git.UsingLibGit2(CGit::GIT_CMD_FETCH))
2672 CGitProgressDlg gitdlg;
2673 FetchProgressCommand fetchProgressCommand;
2674 if (!fetchAllRemotes)
2675 fetchProgressCommand.SetUrl(url);
2676 gitdlg.SetCommand(&fetchProgressCommand);
2677 fetchProgressCommand.m_PostCmdCallback = progress.m_PostCmdCallback;
2678 fetchProgressCommand.SetAutoTag(fetchTags == 1 ? GIT_REMOTE_DOWNLOAD_TAGS_ALL : fetchTags == 2 ? GIT_REMOTE_DOWNLOAD_TAGS_AUTO : GIT_REMOTE_DOWNLOAD_TAGS_NONE);
2679 if (!fetchAllRemotes)
2680 fetchProgressCommand.SetRefSpec(remoteBranch);
2681 userResponse = gitdlg.DoModal();
2682 return userResponse == IDOK;
2685 userResponse = progress.DoModal();
2686 if (!progress.m_GitStatus)
2688 if (runRebase)
2690 CGitHash remoteBranchHash;
2691 g_Git.GetHash(remoteBranchHash, upstream);
2692 if (remoteBranchHash == oldUpstreamHash && !oldUpstreamHash.IsEmpty() && CMessageBox::ShowCheck(nullptr, IDS_REBASE_BRANCH_UNCHANGED, IDS_APPNAME, MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2, _T("OpenRebaseRemoteBranchUnchanged"), IDS_MSGBOX_DONOTSHOWAGAIN) == IDNO)
2693 return userResponse == IDOK;
2695 if (g_Git.IsFastForward(_T("HEAD"), upstream))
2697 UINT ret = CMessageBox::ShowCheck(nullptr, IDS_REBASE_BRANCH_FF, IDS_APPNAME, 2, IDI_QUESTION, IDS_MERGEBUTTON, IDS_REBASEBUTTON, IDS_ABORTBUTTON, _T("OpenRebaseRemoteBranchFastForwards"), IDS_MSGBOX_DONOTSHOWAGAIN);
2698 if (ret == 3)
2699 return userResponse == IDOK;
2700 if (ret == 1)
2702 CProgressDlg mergeProgress;
2703 mergeProgress.m_GitCmd = _T("git.exe merge --ff-only ") + upstream;
2704 mergeProgress.m_AutoClose = AUTOCLOSE_IF_NO_ERRORS;
2705 mergeProgress.m_PostCmdCallback = [](DWORD status, PostCmdList& postCmdList)
2707 if (status && g_Git.HasWorkingTreeConflicts())
2709 // there are conflict files
2710 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
2712 CString sCmd;
2713 sCmd.Format(_T("/command:commit /path:\"%s\""), g_Git.m_CurrentDir);
2714 CAppUtils::RunTortoiseGitProc(sCmd);
2718 return mergeProgress.DoModal() == IDOK;
2722 return CAppUtils::RebaseAfterFetch(upstream);
2726 return userResponse == IDOK;
2729 bool CAppUtils::Fetch(const CString& remoteName, bool allRemotes)
2731 CPullFetchDlg dlg;
2732 dlg.m_PreSelectRemote = remoteName;
2733 dlg.m_IsPull=FALSE;
2734 dlg.m_bAllRemotes = allRemotes;
2736 if(dlg.DoModal()==IDOK)
2737 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);
2739 return false;
2742 bool CAppUtils::Push(const CString& selectLocalBranch)
2744 CPushDlg dlg;
2745 dlg.m_BranchSourceName = selectLocalBranch;
2747 if (dlg.DoModal() == IDOK)
2749 CString error;
2750 DWORD exitcode = 0xFFFFFFFF;
2751 if (CHooks::Instance().PrePush(g_Git.m_CurrentDir, exitcode, error))
2753 if (exitcode)
2755 CString temp;
2756 temp.Format(IDS_ERR_HOOKFAILED, (LPCTSTR)error);
2757 CMessageBox::Show(nullptr, temp, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
2758 return false;
2762 CString arg;
2764 if(dlg.m_bPack)
2765 arg += _T("--thin ");
2766 if(dlg.m_bTags && !dlg.m_bPushAllBranches)
2767 arg += _T("--tags ");
2768 if(dlg.m_bForce)
2769 arg += _T("--force ");
2770 if (dlg.m_bForceWithLease)
2771 arg += _T("--force-with-lease ");
2772 if (dlg.m_bSetUpstream)
2773 arg += _T("--set-upstream ");
2774 if (dlg.m_RecurseSubmodules == 1)
2775 arg += _T("--recurse-submodules=check ");
2776 if (dlg.m_RecurseSubmodules == 2)
2777 arg += _T("--recurse-submodules=on-demand ");
2779 int ver = CAppUtils::GetMsysgitVersion();
2781 if(ver >= 0x01070203) //above 1.7.0.2
2782 arg += _T("--progress ");
2784 CProgressDlg progress;
2786 STRING_VECTOR remotesList;
2787 if (dlg.m_bPushAllRemotes)
2788 g_Git.GetRemoteList(remotesList);
2789 else
2790 remotesList.push_back(dlg.m_URL);
2792 for (unsigned int i = 0; i < remotesList.size(); ++i)
2794 if (dlg.m_bAutoLoad)
2795 CAppUtils::LaunchPAgent(NULL, &remotesList[i]);
2797 CString cmd;
2798 if (dlg.m_bPushAllBranches)
2800 cmd.Format(_T("git.exe push --all %s\"%s\""),
2801 (LPCTSTR)arg,
2802 (LPCTSTR)remotesList[i]);
2804 if (dlg.m_bTags)
2806 progress.m_GitCmdList.push_back(cmd);
2807 cmd.Format(_T("git.exe push --tags %s\"%s\""), (LPCTSTR)arg, (LPCTSTR)remotesList[i]);
2810 else
2812 cmd.Format(_T("git.exe push %s\"%s\" %s"),
2813 (LPCTSTR)arg,
2814 (LPCTSTR)remotesList[i],
2815 (LPCTSTR)dlg.m_BranchSourceName);
2816 if (!dlg.m_BranchRemoteName.IsEmpty())
2818 cmd += _T(":") + dlg.m_BranchRemoteName;
2821 progress.m_GitCmdList.push_back(cmd);
2823 if (!dlg.m_bPushAllBranches && !!CRegDWORD(_T("Software\\TortoiseGit\\ShowBranchRevisionNumber"), FALSE))
2825 cmd.Format(_T("git.exe rev-list --count --first-parent %s"), (LPCTSTR)dlg.m_BranchSourceName);
2826 progress.m_GitCmdList.push_back(cmd);
2830 CString superprojectRoot;
2831 GitAdminDir::HasAdminDir(g_Git.m_CurrentDir, false, &superprojectRoot);
2832 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2834 // need to execute hooks as those might be needed by post action commands
2835 DWORD exitcode = 0xFFFFFFFF;
2836 CString error;
2837 if (CHooks::Instance().PostPush(g_Git.m_CurrentDir, exitcode, error))
2839 if (exitcode)
2841 CString temp;
2842 temp.Format(IDS_ERR_HOOKFAILED, (LPCTSTR)error);
2843 MessageBox(nullptr, temp, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
2847 if (status)
2849 bool rejected = progress.GetLogText().Find(_T("! [rejected]")) > 0;
2850 if (rejected)
2852 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, []{ Pull(true); });
2853 postCmdList.emplace_back(IDI_PULL, IDS_MENUFETCH, [&]{ Fetch(dlg.m_bPushAllRemotes ? _T("") : dlg.m_URL, !!dlg.m_bPushAllRemotes); });
2855 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, [&]{ Push(selectLocalBranch); });
2856 return;
2859 postCmdList.emplace_back(IDS_PROC_REQUESTPULL, [&]{ RequestPull(dlg.m_BranchRemoteName); });
2860 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, [&]{ Push(selectLocalBranch); });
2861 postCmdList.emplace_back(IDI_SWITCH, IDS_MENUSWITCH, [&]{ Switch(); });
2862 if (!superprojectRoot.IsEmpty())
2864 postCmdList.emplace_back(IDI_COMMIT, IDS_PROC_COMMIT_SUPERPROJECT, [&]
2866 CString sCmd;
2867 sCmd.Format(_T("/command:commit /path:\"%s\""), (LPCTSTR)superprojectRoot);
2868 RunTortoiseGitProc(sCmd);
2873 INT_PTR ret = progress.DoModal();
2874 return ret == IDOK;
2876 return FALSE;
2879 bool CAppUtils::RequestPull(const CString& endrevision, const CString& repositoryUrl, bool bIsMainWnd)
2881 CRequestPullDlg dlg;
2882 dlg.m_RepositoryURL = repositoryUrl;
2883 dlg.m_EndRevision = endrevision;
2884 if (dlg.DoModal()==IDOK)
2886 CString cmd;
2887 cmd.Format(_T("git.exe request-pull %s \"%s\" %s"), (LPCTSTR)dlg.m_StartRevision, (LPCTSTR)dlg.m_RepositoryURL, (LPCTSTR)dlg.m_EndRevision);
2889 CSysProgressDlg sysProgressDlg;
2890 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
2891 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_CREATINGPULLREUQEST)));
2892 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
2893 sysProgressDlg.SetShowProgressBar(false);
2894 sysProgressDlg.ShowModeless((HWND)NULL, true);
2896 CString tempFileName = GetTempFile();
2897 CString err;
2898 DeleteFile(tempFileName);
2899 CreateDirectory(tempFileName, NULL);
2900 tempFileName += _T("\\pullrequest.txt");
2901 if (g_Git.RunLogFile(cmd, tempFileName, &err))
2903 CString msg;
2904 msg.LoadString(IDS_ERR_PULLREUQESTFAILED);
2905 CMessageBox::Show(NULL, msg + _T("\n") + err, _T("TortoiseGit"), MB_OK);
2906 return false;
2909 if (sysProgressDlg.HasUserCancelled())
2911 CMessageBox::Show(NULL, IDS_USERCANCELLED, IDS_APPNAME, MB_OK);
2912 ::DeleteFile(tempFileName);
2913 return false;
2916 sysProgressDlg.Stop();
2918 if (dlg.m_bSendMail)
2920 CSendMailDlg sendmaildlg;
2921 sendmaildlg.m_PathList = CTGitPathList(CTGitPath(tempFileName));
2922 sendmaildlg.m_bCustomSubject = true;
2924 if (sendmaildlg.DoModal() == IDOK)
2926 if (sendmaildlg.m_PathList.IsEmpty())
2927 return FALSE;
2929 CGitProgressDlg progDlg;
2930 if (bIsMainWnd)
2931 theApp.m_pMainWnd = &progDlg;
2932 SendMailProgressCommand sendMailProgressCommand;
2933 progDlg.SetCommand(&sendMailProgressCommand);
2935 sendMailProgressCommand.SetPathList(sendmaildlg.m_PathList);
2936 progDlg.SetItemCount(sendmaildlg.m_PathList.GetCount());
2938 CSendMailCombineable sendMailCombineable(sendmaildlg.m_To, sendmaildlg.m_CC, sendmaildlg.m_Subject, !!sendmaildlg.m_bAttachment, !!sendmaildlg.m_bCombine);
2939 sendMailProgressCommand.SetSendMailOption(&sendMailCombineable);
2941 progDlg.DoModal();
2943 return true;
2945 return false;
2948 CAppUtils::LaunchAlternativeEditor(tempFileName);
2950 return true;
2953 void CAppUtils::RemoveTrailSlash(CString &path)
2955 if(path.IsEmpty())
2956 return ;
2958 // For URL, do not trim the slash just after the host name component.
2959 int index = path.Find(_T("://"));
2960 if (index >= 0)
2962 index += 4;
2963 index = path.Find(_T('/'), index);
2964 if (index == path.GetLength() - 1)
2965 return;
2968 while(path[path.GetLength()-1] == _T('\\') || path[path.GetLength()-1] == _T('/' ) )
2970 path=path.Left(path.GetLength()-1);
2971 if(path.IsEmpty())
2972 return;
2976 bool CAppUtils::CheckUserData()
2978 while(g_Git.GetUserName().IsEmpty() || g_Git.GetUserEmail().IsEmpty())
2980 if(CMessageBox::Show(NULL, IDS_PROC_NOUSERDATA, IDS_APPNAME, MB_YESNO| MB_ICONERROR) == IDYES)
2982 CTGitPath path(g_Git.m_CurrentDir);
2983 CSettings dlg(IDS_PROC_SETTINGS_TITLE,&path);
2984 dlg.SetTreeViewMode(TRUE, TRUE, TRUE);
2985 dlg.SetTreeWidth(220);
2986 dlg.m_DefaultPage = _T("gitconfig");
2988 dlg.DoModal();
2989 dlg.HandleRestart();
2992 else
2993 return false;
2996 return true;
2999 BOOL CAppUtils::Commit(const CString& bugid, BOOL bWholeProject, CString &sLogMsg,
3000 CTGitPathList &pathList,
3001 CTGitPathList &selectedList,
3002 bool bSelectFilesForCommit)
3004 bool bFailed = true;
3006 if (!CheckUserData())
3007 return false;
3009 while (bFailed)
3011 bFailed = false;
3012 CCommitDlg dlg;
3013 dlg.m_sBugID = bugid;
3015 dlg.m_bWholeProject = bWholeProject;
3017 dlg.m_sLogMessage = sLogMsg;
3018 dlg.m_pathList = pathList;
3019 dlg.m_checkedPathList = selectedList;
3020 dlg.m_bSelectFilesForCommit = bSelectFilesForCommit;
3021 if (dlg.DoModal() == IDOK)
3023 if (dlg.m_pathList.IsEmpty())
3024 return false;
3025 // if the user hasn't changed the list of selected items
3026 // we don't use that list. Because if we would use the list
3027 // of pre-checked items, the dialog would show different
3028 // checked items on the next startup: it would only try
3029 // to check the parent folder (which might not even show)
3030 // instead, we simply use an empty list and let the
3031 // default checking do its job.
3032 if (!dlg.m_pathList.IsEqual(pathList))
3033 selectedList = dlg.m_pathList;
3034 pathList = dlg.m_updatedPathList;
3035 sLogMsg = dlg.m_sLogMessage;
3036 bSelectFilesForCommit = true;
3038 switch (dlg.m_PostCmd)
3040 case GIT_POSTCOMMIT_CMD_DCOMMIT:
3041 CAppUtils::SVNDCommit();
3042 break;
3043 case GIT_POSTCOMMIT_CMD_PUSH:
3044 CAppUtils::Push();
3045 break;
3046 case GIT_POSTCOMMIT_CMD_CREATETAG:
3047 CAppUtils::CreateBranchTag(TRUE);
3048 break;
3049 case GIT_POSTCOMMIT_CMD_PULL:
3050 CAppUtils::Pull(true);
3051 break;
3052 default:
3053 break;
3056 // CGitProgressDlg progDlg;
3057 // progDlg.SetChangeList(dlg.m_sChangeList, !!dlg.m_bKeepChangeList);
3058 // if (parser.HasVal(_T("closeonend")))
3059 // progDlg.SetAutoClose(parser.GetLongVal(_T("closeonend")));
3060 // progDlg.SetCommand(CGitProgressDlg::GitProgress_Commit);
3061 // progDlg.SetOptions(dlg.m_bKeepLocks ? ProgOptKeeplocks : ProgOptNone);
3062 // progDlg.SetPathList(dlg.m_pathList);
3063 // progDlg.SetCommitMessage(dlg.m_sLogMessage);
3064 // progDlg.SetDepth(dlg.m_bRecursive ? Git_depth_infinity : svn_depth_empty);
3065 // progDlg.SetSelectedList(dlg.m_selectedPathList);
3066 // progDlg.SetItemCount(dlg.m_itemsCount);
3067 // progDlg.SetBugTraqProvider(dlg.m_BugTraqProvider);
3068 // progDlg.DoModal();
3069 // CRegDWORD err = CRegDWORD(_T("Software\\TortoiseGit\\ErrorOccurred"), FALSE);
3070 // err = (DWORD)progDlg.DidErrorsOccur();
3071 // bFailed = progDlg.DidErrorsOccur();
3072 // bRet = progDlg.DidErrorsOccur();
3073 // CRegDWORD bFailRepeat = CRegDWORD(_T("Software\\TortoiseGit\\CommitReopen"), FALSE);
3074 // if (DWORD(bFailRepeat)==0)
3075 // bFailed = false; // do not repeat if the user chose not to in the settings.
3078 return true;
3082 BOOL CAppUtils::SVNDCommit()
3084 CSVNDCommitDlg dcommitdlg;
3085 CString gitSetting = g_Git.GetConfigValue(_T("svn.rmdir"));
3086 if (gitSetting.IsEmpty()) {
3087 if (dcommitdlg.DoModal() != IDOK)
3089 return false;
3091 else
3093 if (dcommitdlg.m_remember)
3095 if (dcommitdlg.m_rmdir)
3097 gitSetting = _T("true");
3099 else
3101 gitSetting = _T("false");
3103 if(g_Git.SetConfigValue(_T("svn.rmdir"),gitSetting))
3105 CString msg;
3106 msg.Format(IDS_PROC_SAVECONFIGFAILED, _T("svn.rmdir"), gitSetting);
3107 CMessageBox::Show(NULL, msg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
3113 BOOL IsStash = false;
3114 if(!g_Git.CheckCleanWorkTree())
3116 if (CMessageBox::Show(NULL, IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
3118 CSysProgressDlg sysProgressDlg;
3119 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3120 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3121 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3122 sysProgressDlg.SetShowProgressBar(false);
3123 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3124 sysProgressDlg.ShowModeless((HWND)NULL, true);
3126 CString cmd,out;
3127 cmd=_T("git.exe stash");
3128 if (g_Git.Run(cmd, &out, CP_UTF8))
3130 sysProgressDlg.Stop();
3131 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
3132 return false;
3134 sysProgressDlg.Stop();
3136 IsStash =true;
3138 else
3140 return false;
3144 CProgressDlg progress;
3145 if (dcommitdlg.m_rmdir)
3147 progress.m_GitCmd=_T("git.exe svn dcommit --rmdir");
3149 else
3151 progress.m_GitCmd=_T("git.exe svn dcommit");
3153 if(progress.DoModal()==IDOK && progress.m_GitStatus == 0)
3155 ::DeleteFile(g_Git.m_CurrentDir + _T("\\sys$command"));
3156 if( IsStash)
3158 if(CMessageBox::Show(NULL,IDS_DCOMMIT_STASH_POP,IDS_APPNAME,MB_YESNO|MB_ICONINFORMATION)==IDYES)
3160 CSysProgressDlg sysProgressDlg;
3161 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3162 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3163 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3164 sysProgressDlg.SetShowProgressBar(false);
3165 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3166 sysProgressDlg.ShowModeless((HWND)NULL, true);
3168 CString cmd,out;
3169 cmd=_T("git.exe stash pop");
3170 if (g_Git.Run(cmd, &out, CP_UTF8))
3172 sysProgressDlg.Stop();
3173 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
3174 return false;
3176 sysProgressDlg.Stop();
3178 else
3180 return false;
3183 return TRUE;
3185 return FALSE;
3188 BOOL CAppUtils::Merge(const CString* commit, bool showStashPop)
3190 if (!CheckUserData())
3191 return FALSE;
3193 CMergeDlg dlg;
3194 if(commit)
3195 dlg.m_initialRefName = *commit;
3197 if(dlg.DoModal()==IDOK)
3199 CString cmd;
3200 CString args;
3202 if(dlg.m_bNoFF)
3203 args += _T(" --no-ff");
3204 else if (dlg.m_bFFonly)
3205 args += _T(" --ff-only");
3207 if(dlg.m_bSquash)
3208 args += _T(" --squash");
3210 if(dlg.m_bNoCommit)
3211 args += _T(" --no-commit");
3213 if (dlg.m_bLog)
3215 CString fmt;
3216 fmt.Format(_T(" --log=%d"), dlg.m_nLog);
3217 args += fmt;
3220 if (!dlg.m_MergeStrategy.IsEmpty())
3222 args += _T(" --strategy=") + dlg.m_MergeStrategy;
3223 if (!dlg.m_StrategyOption.IsEmpty())
3225 args += _T(" --strategy-option=") + dlg.m_StrategyOption;
3226 if (!dlg.m_StrategyParam.IsEmpty())
3227 args += _T("=") + dlg.m_StrategyParam;
3231 if(!dlg.m_strLogMesage.IsEmpty())
3233 CString logmsg = dlg.m_strLogMesage;
3234 logmsg.Replace(_T("\\\""), _T("\\\\\""));
3235 logmsg.Replace(_T("\""), _T("\\\""));
3236 args += _T(" -m \"") + logmsg + _T("\"");
3238 cmd.Format(_T("git.exe merge%s %s"), (LPCTSTR)args, (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName));
3240 CProgressDlg Prodlg;
3241 Prodlg.m_GitCmd = cmd;
3243 Prodlg.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3245 if (status)
3247 int hasConflicts = g_Git.HasWorkingTreeConflicts();
3248 if (hasConflicts < 0)
3249 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(L"Checking for conflicts failed.", CGit::GIT_CMD_CHECKCONFLICTS), _T("TortoiseGit"), MB_ICONEXCLAMATION);
3250 else if (hasConflicts)
3252 // there are conflict files
3254 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
3256 CString sCmd;
3257 sCmd.Format(_T("/command:commit /path:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
3258 CAppUtils::RunTortoiseGitProc(sCmd);
3262 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSTASHSAVE, [&]{ CAppUtils::StashSave(_T(""), false, false, true, g_Git.FixBranchName(dlg.m_VersionName)); });
3263 return;
3266 if (showStashPop)
3267 postCmdList.emplace_back(IDI_RELOCATE, IDS_MENUSTASHPOP, []{ StashPop(); });
3269 if (dlg.m_bNoCommit)
3271 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUCOMMIT, []
3273 CString sCmd;
3274 sCmd.Format(_T("/command:commit /path:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
3275 CAppUtils::RunTortoiseGitProc(sCmd);
3277 return;
3280 if (dlg.m_bIsBranch && dlg.m_VersionName.Find(L"remotes/") == -1) // do not ask to remove remote branches
3282 postCmdList.emplace_back(IDI_DELETE, IDS_PROC_REMOVEBRANCH, [&]
3284 CString msg;
3285 msg.Format(IDS_PROC_DELETEBRANCHTAG, dlg.m_VersionName);
3286 if (CMessageBox::Show(nullptr, msg, _T("TortoiseGit"), 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
3288 CString cmd, out;
3289 cmd.Format(_T("git.exe branch -D -- %s"), (LPCTSTR)dlg.m_VersionName);
3290 if (g_Git.Run(cmd, &out, CP_UTF8))
3291 MessageBox(nullptr, out, _T("TortoiseGit"), MB_OK);
3295 if (dlg.m_bIsBranch)
3296 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, []{ Push(); });
3298 BOOL hasGitSVN = CTGitPath(g_Git.m_CurrentDir).GetAdminDirMask() & ITEMIS_GITSVN;
3299 if (hasGitSVN)
3300 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSVNDCOMMIT, []{ SVNDCommit(); });
3303 Prodlg.DoModal();
3304 return !Prodlg.m_GitStatus;
3306 return false;
3309 BOOL CAppUtils::MergeAbort()
3311 CMergeAbortDlg dlg;
3312 if (dlg.DoModal() == IDOK)
3313 return Reset(_T("HEAD"), dlg.m_ResetType + 1);
3315 return FALSE;
3318 void CAppUtils::EditNote(GitRevLoglist* rev)
3320 if (!CheckUserData())
3321 return;
3323 CInputDlg dlg;
3324 dlg.m_sHintText = CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES));
3325 dlg.m_sInputText = rev->m_Notes;
3326 dlg.m_sTitle = CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES));
3327 //dlg.m_pProjectProperties = &m_ProjectProperties;
3328 dlg.m_bUseLogWidth = true;
3329 if(dlg.DoModal() == IDOK)
3331 CString cmd,output;
3332 cmd=_T("notes add -f -F \"");
3334 CString tempfile=::GetTempFile();
3335 if (CAppUtils::SaveCommitUnicodeFile(tempfile, dlg.m_sInputText))
3337 CMessageBox::Show(nullptr, IDS_PROC_FAILEDSAVINGNOTES, IDS_APPNAME, MB_OK | MB_ICONERROR);
3338 return;
3340 cmd += tempfile;
3341 cmd += _T("\" ");
3342 cmd += rev->m_CommitHash.ToString();
3346 if (git_run_cmd("notes", CUnicodeUtils::GetMulti(cmd, CP_UTF8).GetBuffer()))
3348 CMessageBox::Show(NULL, IDS_PROC_FAILEDSAVINGNOTES, IDS_APPNAME, MB_OK | MB_ICONERROR);
3351 else
3353 rev->m_Notes = dlg.m_sInputText;
3355 }catch(...)
3357 CMessageBox::Show(NULL, IDS_PROC_FAILEDSAVINGNOTES, IDS_APPNAME, MB_OK | MB_ICONERROR);
3359 ::DeleteFile(tempfile);
3364 int CAppUtils::GetMsysgitVersion()
3366 if (g_Git.ms_LastMsysGitVersion)
3367 return g_Git.ms_LastMsysGitVersion;
3369 CString cmd;
3370 CString versiondebug;
3371 CString version;
3373 CRegDWORD regTime = CRegDWORD(_T("Software\\TortoiseGit\\git_file_time"));
3374 CRegDWORD regVersion = CRegDWORD(_T("Software\\TortoiseGit\\git_cached_version"));
3376 CString gitpath = CGit::ms_LastMsysGitDir+_T("\\git.exe");
3378 __int64 time=0;
3379 if (!CGit::GetFileModifyTime(gitpath, &time))
3381 if((DWORD)time == regTime)
3383 g_Git.ms_LastMsysGitVersion = regVersion;
3384 return regVersion;
3388 CString err;
3389 cmd = _T("git.exe --version");
3390 if (g_Git.Run(cmd, &version, &err, CP_UTF8))
3392 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);
3393 return -1;
3396 int start=0;
3397 int ver = 0;
3399 versiondebug = version;
3403 CString str=version.Tokenize(_T("."), start);
3404 int space = str.ReverseFind(_T(' '));
3405 str = str.Mid(space+1,start);
3406 ver = _ttol(str);
3407 ver <<=24;
3409 version = version.Mid(start);
3410 start = 0;
3412 str = version.Tokenize(_T("."), start);
3414 ver |= (_ttol(str) & 0xFF) << 16;
3416 str = version.Tokenize(_T("."), start);
3417 ver |= (_ttol(str) & 0xFF) << 8;
3419 str = version.Tokenize(_T("."), start);
3420 ver |= (_ttol(str) & 0xFF);
3422 catch(...)
3424 if (!ver)
3426 CMessageBox::Show(NULL, _T("Could not parse git.exe version number: \"") + versiondebug + _T("\""), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
3427 return -1;
3431 regTime = time&0xFFFFFFFF;
3432 regVersion = ver;
3433 g_Git.ms_LastMsysGitVersion = ver;
3435 return ver;
3438 void CAppUtils::MarkWindowAsUnpinnable(HWND hWnd)
3440 typedef HRESULT (WINAPI *SHGPSFW) (HWND hwnd,REFIID riid,void** ppv);
3442 CAutoLibrary hShell = AtlLoadSystemLibraryUsingFullPath(_T("Shell32.dll"));
3444 if (hShell.IsValid()) {
3445 SHGPSFW pfnSHGPSFW = (SHGPSFW)::GetProcAddress(hShell, "SHGetPropertyStoreForWindow");
3446 if (pfnSHGPSFW) {
3447 IPropertyStore *pps;
3448 HRESULT hr = pfnSHGPSFW(hWnd, IID_PPV_ARGS(&pps));
3449 if (SUCCEEDED(hr)) {
3450 PROPVARIANT var;
3451 var.vt = VT_BOOL;
3452 var.boolVal = VARIANT_TRUE;
3453 pps->SetValue(PKEY_AppUserModel_PreventPinning, var);
3454 pps->Release();
3460 void CAppUtils::SetWindowTitle(HWND hWnd, const CString& urlorpath, const CString& dialogname)
3462 ASSERT(dialogname.GetLength() < 70);
3463 ASSERT(urlorpath.GetLength() < MAX_PATH);
3464 WCHAR pathbuf[MAX_PATH] = {0};
3466 PathCompactPathEx(pathbuf, urlorpath, 70 - dialogname.GetLength(), 0);
3468 wcscat_s(pathbuf, L" - ");
3469 wcscat_s(pathbuf, dialogname);
3470 wcscat_s(pathbuf, L" - ");
3471 wcscat_s(pathbuf, CString(MAKEINTRESOURCE(IDS_APPNAME)));
3472 SetWindowText(hWnd, pathbuf);
3475 bool CAppUtils::BisectStart(const CString& lastGood, const CString& firstBad, bool bIsMainWnd)
3477 if (!g_Git.CheckCleanWorkTree())
3479 if (CMessageBox::Show(NULL, IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
3481 CSysProgressDlg sysProgressDlg;
3482 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3483 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3484 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3485 sysProgressDlg.SetShowProgressBar(false);
3486 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3487 sysProgressDlg.ShowModeless((HWND)NULL, true);
3489 CString cmd, out;
3490 cmd = _T("git.exe stash");
3491 if (g_Git.Run(cmd, &out, CP_UTF8))
3493 sysProgressDlg.Stop();
3494 CMessageBox::Show(NULL, out, _T("TortoiseGit"), MB_OK);
3495 return false;
3497 sysProgressDlg.Stop();
3499 else
3500 return false;
3503 CBisectStartDlg bisectStartDlg;
3505 if (!lastGood.IsEmpty())
3506 bisectStartDlg.m_sLastGood = lastGood;
3507 if (!firstBad.IsEmpty())
3508 bisectStartDlg.m_sFirstBad = firstBad;
3510 if (bisectStartDlg.DoModal() == IDOK)
3512 CProgressDlg progress;
3513 if (bIsMainWnd)
3514 theApp.m_pMainWnd = &progress;
3515 progress.m_GitCmdList.push_back(_T("git.exe bisect start"));
3516 progress.m_GitCmdList.push_back(_T("git.exe bisect good ") + bisectStartDlg.m_LastGoodRevision);
3517 progress.m_GitCmdList.push_back(_T("git.exe bisect bad ") + bisectStartDlg.m_FirstBadRevision);
3519 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3521 if (status)
3522 return;
3524 CTGitPath path(g_Git.m_CurrentDir);
3525 if (path.HasSubmodules())
3527 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
3529 CString sCmd;
3530 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
3531 CAppUtils::RunTortoiseGitProc(sCmd);
3538 INT_PTR ret = progress.DoModal();
3539 return ret == IDOK;
3542 return false;
3545 bool CAppUtils::BisectOperation(const CString& op, const CString& ref, bool bIsMainWnd)
3547 CString cmd = _T("git.exe bisect ") + op;
3549 if (!ref.IsEmpty())
3551 cmd += _T(" ");
3552 cmd += ref;
3555 CProgressDlg progress;
3556 if (bIsMainWnd)
3557 theApp.m_pMainWnd = &progress;
3558 progress.m_GitCmd = cmd;
3560 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3562 if (status)
3563 return;
3565 CTGitPath path = g_Git.m_CurrentDir;
3566 if (path.HasSubmodules())
3568 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
3570 CString sCmd;
3571 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
3572 CAppUtils::RunTortoiseGitProc(sCmd);
3576 if (op != _T("reset"))
3577 postCmdList.emplace_back(IDS_MENUBISECTRESET, []{ CAppUtils::RunTortoiseGitProc(_T("/command:bisect /reset")); });
3580 INT_PTR ret = progress.DoModal();
3581 return ret == IDOK;
3584 int CAppUtils::Git2GetUserPassword(git_cred **out, const char *url, const char *username_from_url, unsigned int /*allowed_types*/, void * /*payload*/)
3586 CUserPassword dlg;
3587 dlg.m_URL = CUnicodeUtils::GetUnicode(url, CP_UTF8);
3588 if (username_from_url)
3589 dlg.m_UserName = CUnicodeUtils::GetUnicode(username_from_url, CP_UTF8);
3591 CStringA username, password;
3592 if (dlg.DoModal() == IDOK)
3594 username = CUnicodeUtils::GetMulti(dlg.m_UserName, CP_UTF8);
3595 password = CUnicodeUtils::GetMulti(dlg.m_Password, CP_UTF8);
3596 return git_cred_userpass_plaintext_new(out, username, password);
3598 giterr_set_str(GITERR_NONE, "User cancelled.");
3599 return GIT_EUSER;
3602 int CAppUtils::Git2CertificateCheck(git_cert* base_cert, int /*valid*/, const char* host, void* /*payload*/)
3604 if (base_cert->cert_type == GIT_CERT_X509)
3606 git_cert_x509* cert = (git_cert_x509*)base_cert;
3608 if (last_accepted_cert.cmp(cert))
3609 return 0;
3611 PCCERT_CONTEXT pServerCert = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, (BYTE*)cert->data, (DWORD)cert->len);
3613 DWORD verificationError = VerifyServerCertificate(pServerCert, CUnicodeUtils::GetUnicode(host).GetBuffer(), 0);
3614 if (!verificationError)
3616 last_accepted_cert.set(cert);
3617 CertFreeCertificateContext(pServerCert);
3618 return 0;
3621 CString servernameInCert;
3622 CertGetNameString(pServerCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nullptr, CStrBuf(servernameInCert, 128), 128);
3624 CString issuer;
3625 CertGetNameString(pServerCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, CERT_NAME_ISSUER_FLAG, nullptr, CStrBuf(issuer, 128), 128);
3627 CertFreeCertificateContext(pServerCert);
3629 CCheckCertificateDlg dlg;
3630 dlg.cert = cert;
3631 dlg.m_sCertificateCN = servernameInCert;
3632 dlg.m_sCertificateIssuer = issuer;
3633 dlg.m_sHostname = CUnicodeUtils::GetUnicode(host);
3634 dlg.m_sError = CFormatMessageWrapper(verificationError);
3635 if (dlg.DoModal() == IDOK)
3637 last_accepted_cert.set(cert);
3638 return 0;
3641 return GIT_ECERTIFICATE;
3644 int CAppUtils::ExploreTo(HWND hwnd, CString path)
3646 if (PathFileExists(path))
3648 HRESULT ret = -1;
3649 ITEMIDLIST __unaligned * pidl = ILCreateFromPath(path);
3650 if (pidl)
3652 ret = SHOpenFolderAndSelectItems(pidl, 0, 0, 0);
3653 ILFree(pidl);
3655 return SUCCEEDED(ret) ? 0 : -1;
3657 // if filepath does not exist any more, navigate to closest matching folder
3660 int pos = path.ReverseFind(_T('\\'));
3661 if (pos <= 3)
3662 break;
3663 path = path.Left(pos);
3664 } while (!PathFileExists(path));
3665 return (INT_PTR)ShellExecute(hwnd, _T("explore"), path, nullptr, nullptr, SW_SHOW) > 32 ? 0 : -1;
3668 int CAppUtils::ResolveConflict(CTGitPath& path, resolve_with resolveWith)
3670 bool b_local = false, b_remote = false;
3671 BYTE_VECTOR vector;
3673 CString cmd;
3674 cmd.Format(_T("git.exe ls-files -u -t -z -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3675 if (g_Git.Run(cmd, &vector))
3677 CMessageBox::Show(nullptr, _T("git ls-files failed!"), _T("TortoiseGit"), MB_OK);
3678 return -1;
3681 CTGitPathList list;
3682 if (list.ParserFromLsFile(vector))
3684 CMessageBox::Show(nullptr, _T("Parse ls-files failed!"), _T("TortoiseGit"), MB_OK);
3685 return -1;
3688 if (list.IsEmpty())
3689 return 0;
3690 for (int i = 0; i < list.GetCount(); ++i)
3692 if (list[i].m_Stage == 2)
3693 b_local = true;
3694 if (list[i].m_Stage == 3)
3695 b_remote = true;
3699 CBlockCacheForPath block(g_Git.m_CurrentDir);
3700 if (path.IsDirectory()) // is submodule conflict
3702 CString err = _T("We're sorry, but you hit a very rare conflict condition with a submodule which cannot be resolved by TortoiseGit. You have to use the command line git for this.");
3703 if (b_local && b_remote)
3705 if (!path.HasAdminDir()) // check if submodule is initialized
3707 err += _T("\n\nYou have to checkout the submodule manually into \"") + path.GetGitPathString() + _T("\" and then reset HEAD to the right commit (see resolve submodule conflict dialog for this).");
3708 MessageBox(nullptr, err, _T("TortoiseGit"), MB_ICONERROR);
3709 return -1;
3711 CGit subgit;
3712 subgit.m_CurrentDir = g_Git.CombinePath(path);
3713 CGitHash submoduleHead;
3714 if (subgit.GetHash(submoduleHead, _T("HEAD")))
3716 MessageBox(nullptr, err, _T("TortoiseGit"), MB_ICONERROR);
3717 return -1;
3719 CString baseHash, localHash, remoteHash;
3720 ParseHashesFromLsFile(vector, baseHash, localHash, remoteHash);
3721 if (resolveWith == RESOLVE_WITH_THEIRS && submoduleHead.ToString() != remoteHash)
3723 CString origPath = g_Git.m_CurrentDir;
3724 g_Git.m_CurrentDir = g_Git.CombinePath(path);
3725 if (!GitReset(&remoteHash))
3727 g_Git.m_CurrentDir = origPath;
3728 return -1;
3730 g_Git.m_CurrentDir = origPath;
3732 else if (resolveWith == RESOLVE_WITH_MINE && submoduleHead.ToString() != localHash)
3734 CString origPath = g_Git.m_CurrentDir;
3735 g_Git.m_CurrentDir = g_Git.CombinePath(path);
3736 if (!GitReset(&localHash))
3738 g_Git.m_CurrentDir = origPath;
3739 return -1;
3741 g_Git.m_CurrentDir = origPath;
3744 else
3746 MessageBox(nullptr, err, _T("TortoiseGit"), MB_ICONERROR);
3747 return -1;
3751 if (resolveWith == RESOLVE_WITH_THEIRS)
3753 CString gitcmd, output;
3754 if (b_local && b_remote)
3755 gitcmd.Format(_T("git.exe checkout-index -f --stage=3 -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3756 else if (b_remote)
3757 gitcmd.Format(_T("git.exe add -f -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3758 else if (b_local)
3759 gitcmd.Format(_T("git.exe rm -f -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3760 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3762 CMessageBox::Show(nullptr, output, _T("TortoiseGit"), MB_ICONERROR);
3763 return -1;
3766 else if (resolveWith == RESOLVE_WITH_MINE)
3768 CString gitcmd, output;
3769 if (b_local && b_remote)
3770 gitcmd.Format(_T("git.exe checkout-index -f --stage=2 -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3771 else if (b_local)
3772 gitcmd.Format(_T("git.exe add -f -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3773 else if (b_remote)
3774 gitcmd.Format(_T("git.exe rm -f -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3775 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3777 CMessageBox::Show(nullptr, output, _T("TortoiseGit"), MB_ICONERROR);
3778 return -1;
3782 if (b_local && b_remote && path.m_Action & CTGitPath::LOGACTIONS_UNMERGED)
3784 CString gitcmd, output;
3785 gitcmd.Format(_T("git.exe add -f -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3786 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3787 CMessageBox::Show(nullptr, output, _T("TortoiseGit"), MB_ICONERROR);
3788 else
3790 path.m_Action |= CTGitPath::LOGACTIONS_MODIFIED;
3791 path.m_Action &= ~CTGitPath::LOGACTIONS_UNMERGED;
3795 RemoveTempMergeFile(path);
3796 return 0;
3799 bool CAppUtils::ShellOpen(const CString& file, HWND hwnd /*= nullptr */)
3801 if ((INT_PTR)ShellExecute(hwnd, NULL, file, NULL, NULL, SW_SHOW) > HINSTANCE_ERROR)
3802 return true;
3804 return ShowOpenWithDialog(file, hwnd);
3807 bool CAppUtils::ShowOpenWithDialog(const CString& file, HWND hwnd /*= nullptr */)
3809 CAutoLibrary hShell = AtlLoadSystemLibraryUsingFullPath(_T("shell32.dll"));
3810 if (hShell)
3812 typedef HRESULT STDAPICALLTYPE SHOpenWithDialoFN(_In_opt_ HWND hwndParent, _In_ const OPENASINFO *poainfo);
3813 SHOpenWithDialoFN *pfnSHOpenWithDialog = (SHOpenWithDialoFN*)GetProcAddress(hShell, "SHOpenWithDialog");
3814 if (pfnSHOpenWithDialog)
3816 OPENASINFO oi = { 0 };
3817 oi.pcszFile = file;
3818 oi.oaifInFlags = OAIF_EXEC;
3819 return SUCCEEDED(pfnSHOpenWithDialog(hwnd, &oi));
3822 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
3823 cmd += file;
3824 return CAppUtils::LaunchApplication(cmd, NULL, false);