FileDiffDlg: Switch Version 1 and Version 2 (Base)
[TortoiseGit.git] / src / TortoiseProc / AppUtils.cpp
blob003629daeecbcbb25a5961b65c5e6e463398b030
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2016 - 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 "SendmailPatch.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, int runRebase, const bool rebasePreserveMerges);
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 (dlg.m_bIncludeUntracked)
126 cmd += L" --include-untracked";
127 else if (dlg.m_bAll)
128 cmd += L" --all";
130 if (!dlg.m_sMessage.IsEmpty())
132 CString message = dlg.m_sMessage;
133 message.Replace(_T("\""), _T("\"\""));
134 cmd += _T(" -- \"") + message + _T("\"");
137 CProgressDlg progress;
138 progress.m_GitCmd = cmd;
139 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
141 if (status)
142 return;
144 if (showPull)
145 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&]{ CAppUtils::Pull(pullShowPush, true); });
146 if (showMerge)
147 postCmdList.emplace_back(IDI_MERGE, IDS_MENUMERGE, [&]{ CAppUtils::Merge(&mergeRev, true); });
149 return (progress.DoModal() == IDOK);
151 return false;
154 bool CAppUtils::StashApply(CString ref, bool showChanges /* true */)
156 CString cmd,out;
157 cmd = _T("git.exe stash apply ");
158 if (ref.Find(_T("refs/")) == 0)
159 ref = ref.Mid(5);
160 if (ref.Find(_T("stash{")) == 0)
161 ref = _T("stash@") + ref.Mid(5);
162 cmd += ref;
164 CSysProgressDlg sysProgressDlg;
165 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
166 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
167 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
168 sysProgressDlg.SetShowProgressBar(false);
169 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
170 sysProgressDlg.ShowModeless((HWND)nullptr, true);
172 int ret = g_Git.Run(cmd, &out, CP_UTF8);
174 sysProgressDlg.Stop();
176 bool hasConflicts = (out.Find(_T("CONFLICT")) >= 0);
177 if (ret && !(ret == 1 && hasConflicts))
178 CMessageBox::Show(nullptr, CString(MAKEINTRESOURCE(IDS_PROC_STASHAPPLYFAILED)) + _T("\n") + out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
179 else
181 CString message;
182 message.LoadString(IDS_PROC_STASHAPPLYSUCCESS);
183 if (hasConflicts)
184 message.LoadString(IDS_PROC_STASHAPPLYFAILEDCONFLICTS);
185 if (showChanges)
187 if (CMessageBox::Show(nullptr, message + _T("\n") + CString(MAKEINTRESOURCE(IDS_SEECHANGES)), _T("TortoiseGit"), MB_YESNO | MB_ICONINFORMATION) == IDYES)
189 CChangedDlg dlg;
190 dlg.m_pathList.AddPath(CTGitPath());
191 dlg.DoModal();
193 return true;
195 else
197 CMessageBox::Show(nullptr, message ,_T("TortoiseGit"), MB_OK | MB_ICONINFORMATION);
198 return true;
201 return false;
204 bool CAppUtils::StashPop(int showChanges /* = 1 */)
206 CString cmd,out;
207 cmd=_T("git.exe stash pop ");
209 CSysProgressDlg sysProgressDlg;
210 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
211 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
212 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
213 sysProgressDlg.SetShowProgressBar(false);
214 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
215 sysProgressDlg.ShowModeless((HWND)nullptr, true);
217 int ret = g_Git.Run(cmd, &out, CP_UTF8);
219 sysProgressDlg.Stop();
221 bool hasConflicts = (out.Find(_T("CONFLICT")) >= 0);
222 if (ret && !(ret == 1 && hasConflicts))
223 CMessageBox::Show(nullptr, CString(MAKEINTRESOURCE(IDS_PROC_STASHPOPFAILED)) + _T("\n") + out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
224 else
226 CString message;
227 message.LoadString(IDS_PROC_STASHPOPSUCCESS);
228 if (hasConflicts)
229 message.LoadString(IDS_PROC_STASHPOPFAILEDCONFLICTS);
230 if (showChanges == 1 || (showChanges == 0 && hasConflicts))
232 if (CMessageBox::Show(nullptr, CString(message + _T("\n") + CString(MAKEINTRESOURCE(IDS_SEECHANGES))), _T("TortoiseGit"), MB_YESNO | MB_ICONINFORMATION) == IDYES)
234 CChangedDlg dlg;
235 dlg.m_pathList.AddPath(CTGitPath());
236 dlg.DoModal();
238 return true;
240 else if (showChanges > 1)
242 CMessageBox::Show(nullptr, message, _T("TortoiseGit"), MB_OK | MB_ICONINFORMATION);
243 return true;
245 else if (showChanges == 0)
246 return true;
248 return false;
251 BOOL CAppUtils::StartExtMerge(
252 const CTGitPath& basefile, const CTGitPath& theirfile, const CTGitPath& yourfile, const CTGitPath& mergedfile,
253 const CString& basename, const CString& theirname, const CString& yourname, const CString& mergedname, bool bReadOnly,
254 HWND resolveMsgHwnd, bool bDeleteBaseTheirsMineOnClose)
256 CRegString regCom = CRegString(_T("Software\\TortoiseGit\\Merge"));
257 CString ext = mergedfile.GetFileExtension();
258 CString com = regCom;
259 bool bInternal = false;
261 if (!ext.IsEmpty())
263 // is there an extension specific merge tool?
264 CRegString mergetool(_T("Software\\TortoiseGit\\MergeTools\\") + ext.MakeLower());
265 if (!CString(mergetool).IsEmpty())
266 com = mergetool;
268 // is there a filename specific merge tool?
269 CRegString mergetool(_T("Software\\TortoiseGit\\MergeTools\\.") + mergedfile.GetFilename().MakeLower());
270 if (!CString(mergetool).IsEmpty())
271 com = mergetool;
273 if (com.IsEmpty()||(com.Left(1).Compare(_T("#"))==0))
275 // Maybe we should use TortoiseIDiff?
276 if ((ext == _T(".jpg")) || (ext == _T(".jpeg")) ||
277 (ext == _T(".bmp")) || (ext == _T(".gif")) ||
278 (ext == _T(".png")) || (ext == _T(".ico")) ||
279 (ext == _T(".tif")) || (ext == _T(".tiff")) ||
280 (ext == _T(".dib")) || (ext == _T(".emf")) ||
281 (ext == _T(".cur")))
283 com = CPathUtils::GetAppDirectory() + _T("TortoiseGitIDiff.exe");
284 com = _T("\"") + com + _T("\"");
285 com = com + _T(" /base:%base /theirs:%theirs /mine:%mine /result:%merged");
286 com = com + _T(" /basetitle:%bname /theirstitle:%tname /minetitle:%yname");
287 if (resolveMsgHwnd)
288 com.AppendFormat(L" /resolvemsghwnd:%I64d", (__int64)resolveMsgHwnd);
290 else
292 // use TortoiseGitMerge
293 bInternal = true;
294 com = CPathUtils::GetAppDirectory() + _T("TortoiseGitMerge.exe");
295 com = _T("\"") + com + _T("\"");
296 com = com + _T(" /base:%base /theirs:%theirs /mine:%mine /merged:%merged");
297 com = com + _T(" /basename:%bname /theirsname:%tname /minename:%yname /mergedname:%mname");
298 com += _T(" /saverequired");
299 if (resolveMsgHwnd)
300 com.AppendFormat(L" /resolvemsghwnd:%I64d", (__int64)resolveMsgHwnd);
301 if (bDeleteBaseTheirsMineOnClose)
302 com += _T(" /deletebasetheirsmineonclose");
304 if (!g_sGroupingUUID.IsEmpty())
306 com += L" /groupuuid:\"";
307 com += g_sGroupingUUID;
308 com += L"\"";
311 // check if the params are set. If not, just add the files to the command line
312 if ((com.Find(_T("%merged"))<0)&&(com.Find(_T("%base"))<0)&&(com.Find(_T("%theirs"))<0)&&(com.Find(_T("%mine"))<0))
314 com += _T(" \"")+basefile.GetWinPathString()+_T("\"");
315 com += _T(" \"")+theirfile.GetWinPathString()+_T("\"");
316 com += _T(" \"")+yourfile.GetWinPathString()+_T("\"");
317 com += _T(" \"")+mergedfile.GetWinPathString()+_T("\"");
319 if (basefile.IsEmpty())
321 com.Replace(_T("/base:%base"), _T(""));
322 com.Replace(_T("%base"), _T(""));
324 else
325 com.Replace(_T("%base"), _T("\"") + basefile.GetWinPathString() + _T("\""));
326 if (theirfile.IsEmpty())
328 com.Replace(_T("/theirs:%theirs"), _T(""));
329 com.Replace(_T("%theirs"), _T(""));
331 else
332 com.Replace(_T("%theirs"), _T("\"") + theirfile.GetWinPathString() + _T("\""));
333 if (yourfile.IsEmpty())
335 com.Replace(_T("/mine:%mine"), _T(""));
336 com.Replace(_T("%mine"), _T(""));
338 else
339 com.Replace(_T("%mine"), _T("\"") + yourfile.GetWinPathString() + _T("\""));
340 if (mergedfile.IsEmpty())
342 com.Replace(_T("/merged:%merged"), _T(""));
343 com.Replace(_T("%merged"), _T(""));
345 else
346 com.Replace(_T("%merged"), _T("\"") + mergedfile.GetWinPathString() + _T("\""));
347 if (basename.IsEmpty())
349 if (basefile.IsEmpty())
351 com.Replace(_T("/basename:%bname"), _T(""));
352 com.Replace(_T("%bname"), _T(""));
354 else
355 com.Replace(_T("%bname"), _T("\"") + basefile.GetUIFileOrDirectoryName() + _T("\""));
357 else
358 com.Replace(_T("%bname"), _T("\"") + basename + _T("\""));
359 if (theirname.IsEmpty())
361 if (theirfile.IsEmpty())
363 com.Replace(_T("/theirsname:%tname"), _T(""));
364 com.Replace(_T("%tname"), _T(""));
366 else
367 com.Replace(_T("%tname"), _T("\"") + theirfile.GetUIFileOrDirectoryName() + _T("\""));
369 else
370 com.Replace(_T("%tname"), _T("\"") + theirname + _T("\""));
371 if (yourname.IsEmpty())
373 if (yourfile.IsEmpty())
375 com.Replace(_T("/minename:%yname"), _T(""));
376 com.Replace(_T("%yname"), _T(""));
378 else
379 com.Replace(_T("%yname"), _T("\"") + yourfile.GetUIFileOrDirectoryName() + _T("\""));
381 else
382 com.Replace(_T("%yname"), _T("\"") + yourname + _T("\""));
383 if (mergedname.IsEmpty())
385 if (mergedfile.IsEmpty())
387 com.Replace(_T("/mergedname:%mname"), _T(""));
388 com.Replace(_T("%mname"), _T(""));
390 else
391 com.Replace(_T("%mname"), _T("\"") + mergedfile.GetUIFileOrDirectoryName() + _T("\""));
393 else
394 com.Replace(_T("%mname"), _T("\"") + mergedname + _T("\""));
396 if ((bReadOnly)&&(bInternal))
397 com += _T(" /readonly");
399 if(!LaunchApplication(com, IDS_ERR_EXTMERGESTART, false))
401 return FALSE;
404 return TRUE;
407 BOOL CAppUtils::StartExtPatch(const CTGitPath& patchfile, const CTGitPath& dir, const CString& sOriginalDescription, const CString& sPatchedDescription, BOOL bReversed, BOOL bWait)
409 CString viewer;
410 // use TortoiseGitMerge
411 viewer = CPathUtils::GetAppDirectory();
412 viewer += _T("TortoiseGitMerge.exe");
414 viewer = _T("\"") + viewer + _T("\"");
415 viewer = viewer + _T(" /diff:\"") + patchfile.GetWinPathString() + _T("\"");
416 viewer = viewer + _T(" /patchpath:\"") + dir.GetWinPathString() + _T("\"");
417 if (bReversed)
418 viewer += _T(" /reversedpatch");
419 if (!sOriginalDescription.IsEmpty())
420 viewer = viewer + _T(" /patchoriginal:\"") + sOriginalDescription + _T("\"");
421 if (!sPatchedDescription.IsEmpty())
422 viewer = viewer + _T(" /patchpatched:\"") + sPatchedDescription + _T("\"");
423 if (!g_sGroupingUUID.IsEmpty())
425 viewer += L" /groupuuid:\"";
426 viewer += g_sGroupingUUID;
427 viewer += L"\"";
429 if(!LaunchApplication(viewer, IDS_ERR_DIFFVIEWSTART, !!bWait))
430 return FALSE;
431 return TRUE;
434 CString CAppUtils::PickDiffTool(const CTGitPath& file1, const CTGitPath& file2)
436 CString difftool = CRegString(_T("Software\\TortoiseGit\\DiffTools\\") + file2.GetFilename().MakeLower());
437 if (!difftool.IsEmpty())
438 return difftool;
439 difftool = CRegString(_T("Software\\TortoiseGit\\DiffTools\\") + file1.GetFilename().MakeLower());
440 if (!difftool.IsEmpty())
441 return difftool;
443 // Is there an extension specific diff tool?
444 CString ext = file2.GetFileExtension().MakeLower();
445 if (!ext.IsEmpty())
447 difftool = CRegString(_T("Software\\TortoiseGit\\DiffTools\\") + ext);
448 if (!difftool.IsEmpty())
449 return difftool;
450 // Maybe we should use TortoiseIDiff?
451 if ((ext == _T(".jpg")) || (ext == _T(".jpeg")) ||
452 (ext == _T(".bmp")) || (ext == _T(".gif")) ||
453 (ext == _T(".png")) || (ext == _T(".ico")) ||
454 (ext == _T(".tif")) || (ext == _T(".tiff")) ||
455 (ext == _T(".dib")) || (ext == _T(".emf")) ||
456 (ext == _T(".cur")))
458 return
459 _T("\"") + CPathUtils::GetAppDirectory() + _T("TortoiseGitIDiff.exe") + _T("\"") +
460 _T(" /left:%base /right:%mine /lefttitle:%bname /righttitle:%yname") +
461 L" /groupuuid:\"" + g_sGroupingUUID + L"\"";
465 // Finally, pick a generic external diff tool
466 difftool = CRegString(_T("Software\\TortoiseGit\\Diff"));
467 return difftool;
470 bool CAppUtils::StartExtDiff(
471 const CString& file1, const CString& file2,
472 const CString& sName1, const CString& sName2,
473 const CString& originalFile1, const CString& originalFile2,
474 const git_revnum_t& hash1, const git_revnum_t& hash2,
475 const DiffFlags& flags, int jumpToLine)
477 CString viewer;
479 CRegDWORD blamediff(_T("Software\\TortoiseGit\\DiffBlamesWithTortoiseMerge"), FALSE);
480 if (!flags.bBlame || !(DWORD)blamediff)
482 viewer = PickDiffTool(file1, file2);
483 // If registry entry for a diff program is commented out, use TortoiseGitMerge.
484 bool bCommentedOut = viewer.Left(1) == _T("#");
485 if (flags.bAlternativeTool)
487 // Invert external vs. internal diff tool selection.
488 if (bCommentedOut)
489 viewer.Delete(0); // uncomment
490 else
491 viewer.Empty();
493 else if (bCommentedOut)
494 viewer.Empty();
497 bool bInternal = viewer.IsEmpty();
498 if (bInternal)
500 viewer =
501 _T("\"") + CPathUtils::GetAppDirectory() + _T("TortoiseGitMerge.exe") + _T("\"") +
502 _T(" /base:%base /mine:%mine /basename:%bname /minename:%yname") +
503 _T(" /basereflectedname:%bpath /minereflectedname:%ypath");
504 if (!g_sGroupingUUID.IsEmpty())
506 viewer += L" /groupuuid:\"";
507 viewer += g_sGroupingUUID;
508 viewer += L"\"";
510 if (flags.bBlame)
511 viewer += _T(" /blame");
513 // check if the params are set. If not, just add the files to the command line
514 if ((viewer.Find(_T("%base"))<0)&&(viewer.Find(_T("%mine"))<0))
516 viewer += _T(" \"")+file1+_T("\"");
517 viewer += _T(" \"")+file2+_T("\"");
519 if (viewer.Find(_T("%base")) >= 0)
520 viewer.Replace(_T("%base"), _T("\"")+file1+_T("\""));
521 if (viewer.Find(_T("%mine")) >= 0)
522 viewer.Replace(_T("%mine"), _T("\"")+file2+_T("\""));
524 if (sName1.IsEmpty())
525 viewer.Replace(_T("%bname"), _T("\"") + file1 + _T("\""));
526 else
527 viewer.Replace(_T("%bname"), _T("\"") + sName1 + _T("\""));
529 if (sName2.IsEmpty())
530 viewer.Replace(_T("%yname"), _T("\"") + file2 + _T("\""));
531 else
532 viewer.Replace(_T("%yname"), _T("\"") + sName2 + _T("\""));
534 viewer.Replace(_T("%bpath"), _T("\"") + originalFile1 + _T("\""));
535 viewer.Replace(_T("%ypath"), _T("\"") + originalFile2 + _T("\""));
537 viewer.Replace(_T("%brev"), _T("\"") + hash1 + _T("\""));
538 viewer.Replace(_T("%yrev"), _T("\"") + hash2 + _T("\""));
540 if (flags.bReadOnly && bInternal)
541 viewer += _T(" /readonly");
543 if (jumpToLine > 0)
544 viewer.AppendFormat(L" /line:%d", jumpToLine);
546 return LaunchApplication(viewer, IDS_ERR_EXTDIFFSTART, flags.bWait);
549 BOOL CAppUtils::StartUnifiedDiffViewer(const CString& patchfile, const CString& title, BOOL bWait)
551 CString viewer;
552 CRegString v = CRegString(_T("Software\\TortoiseGit\\DiffViewer"));
553 viewer = v;
554 if (viewer.IsEmpty() || (viewer.Left(1).Compare(_T("#"))==0))
556 // use TortoiseGitUDiff
557 viewer = CPathUtils::GetAppDirectory();
558 viewer += _T("TortoiseGitUDiff.exe");
559 // enquote the path to TortoiseGitUDiff
560 viewer = _T("\"") + viewer + _T("\"");
561 // add the params
562 viewer = viewer + _T(" /patchfile:%1 /title:\"%title\"");
563 if (!g_sGroupingUUID.IsEmpty())
565 viewer += L" /groupuuid:\"";
566 viewer += g_sGroupingUUID;
567 viewer += L"\"";
570 if (viewer.Find(_T("%1"))>=0)
572 if (viewer.Find(_T("\"%1\"")) >= 0)
573 viewer.Replace(_T("%1"), patchfile);
574 else
575 viewer.Replace(_T("%1"), _T("\"") + patchfile + _T("\""));
577 else
578 viewer += _T(" \"") + patchfile + _T("\"");
579 if (viewer.Find(_T("%title")) >= 0)
580 viewer.Replace(_T("%title"), title);
582 if(!LaunchApplication(viewer, IDS_ERR_DIFFVIEWSTART, !!bWait))
583 return FALSE;
584 return TRUE;
587 BOOL CAppUtils::StartTextViewer(CString file)
589 CString viewer;
590 CRegString txt = CRegString(_T(".txt\\"), _T(""), FALSE, HKEY_CLASSES_ROOT);
591 viewer = txt;
592 viewer = viewer + _T("\\Shell\\Open\\Command\\");
593 CRegString txtexe = CRegString(viewer, _T(""), FALSE, HKEY_CLASSES_ROOT);
594 viewer = txtexe;
596 DWORD len = ExpandEnvironmentStrings(viewer, nullptr, 0);
597 auto buf = std::make_unique<TCHAR[]>(len + 1);
598 ExpandEnvironmentStrings(viewer, buf.get(), len);
599 viewer = buf.get();
600 len = ExpandEnvironmentStrings(file, nullptr, 0);
601 auto buf2 = std::make_unique<TCHAR[]>(len + 1);
602 ExpandEnvironmentStrings(file, buf2.get(), len);
603 file = buf2.get();
604 file = _T("\"")+file+_T("\"");
605 if (viewer.IsEmpty())
606 return CAppUtils::ShowOpenWithDialog(file) ? TRUE : FALSE;
607 if (viewer.Find(_T("\"%1\"")) >= 0)
608 viewer.Replace(_T("\"%1\""), file);
609 else if (viewer.Find(_T("%1")) >= 0)
610 viewer.Replace(_T("%1"), file);
611 else
612 viewer += _T(" ");
613 viewer += file;
615 if(!LaunchApplication(viewer, IDS_ERR_TEXTVIEWSTART, false))
616 return FALSE;
617 return TRUE;
620 BOOL CAppUtils::CheckForEmptyDiff(const CTGitPath& sDiffPath)
622 DWORD length = 0;
623 CAutoFile hFile = ::CreateFile(sDiffPath.GetWinPath(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
624 if (!hFile)
625 return TRUE;
626 length = ::GetFileSize(hFile, nullptr);
627 if (length < 4)
628 return TRUE;
629 return FALSE;
632 void CAppUtils::CreateFontForLogs(CFont& fontToCreate)
634 LOGFONT logFont;
635 HDC hScreenDC = ::GetDC(nullptr);
636 logFont.lfHeight = -MulDiv((DWORD)CRegDWORD(_T("Software\\TortoiseGit\\LogFontSize"), 8), GetDeviceCaps(hScreenDC, LOGPIXELSY), 72);
637 ::ReleaseDC(nullptr, hScreenDC);
638 logFont.lfWidth = 0;
639 logFont.lfEscapement = 0;
640 logFont.lfOrientation = 0;
641 logFont.lfWeight = FW_NORMAL;
642 logFont.lfItalic = 0;
643 logFont.lfUnderline = 0;
644 logFont.lfStrikeOut = 0;
645 logFont.lfCharSet = DEFAULT_CHARSET;
646 logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
647 logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
648 logFont.lfQuality = DRAFT_QUALITY;
649 logFont.lfPitchAndFamily = FF_DONTCARE | FIXED_PITCH;
650 _tcscpy_s(logFont.lfFaceName, 32, (LPCTSTR)(CString)CRegString(_T("Software\\TortoiseGit\\LogFontName"), _T("Courier New")));
651 VERIFY(fontToCreate.CreateFontIndirect(&logFont));
654 bool CAppUtils::LaunchPAgent(const CString* keyfile, const CString* pRemote)
656 CString key,remote;
657 CString cmd,out;
658 if (!pRemote)
659 remote=_T("origin");
660 else
661 remote=*pRemote;
663 if (!keyfile)
665 cmd.Format(_T("remote.%s.puttykeyfile"), (LPCTSTR)remote);
666 key = g_Git.GetConfigValue(cmd);
668 else
669 key=*keyfile;
671 if(key.IsEmpty())
672 return false;
674 CString proc=CPathUtils::GetAppDirectory();
675 proc += _T("pageant.exe \"");
676 proc += key;
677 proc += _T("\"");
679 CString tempfile = GetTempFile();
680 ::DeleteFile(tempfile);
682 proc += _T(" -c \"");
683 proc += CPathUtils::GetAppDirectory();
684 proc += _T("tgittouch.exe\"");
685 proc += _T(" \"");
686 proc += tempfile;
687 proc += _T("\"");
689 CString appDir = CPathUtils::GetAppDirectory();
690 bool b = LaunchApplication(proc, IDS_ERR_PAGEANT, true, &appDir);
691 if(!b)
692 return b;
694 int i=0;
695 while(!::PathFileExists(tempfile))
697 Sleep(100);
698 ++i;
699 if(i>10*60*5)
700 break; //timeout 5 minutes
703 if( i== 10*60*5)
704 CMessageBox::Show(nullptr, IDS_ERR_PAEGENTTIMEOUT, IDS_APPNAME, MB_OK | MB_ICONERROR);
705 ::DeleteFile(tempfile);
706 return true;
708 bool CAppUtils::LaunchAlternativeEditor(const CString& filename, bool uac)
710 CString editTool = CRegString(_T("Software\\TortoiseGit\\AlternativeEditor"));
711 if (editTool.IsEmpty() || (editTool.Left(1).Compare(_T("#"))==0)) {
712 editTool = CPathUtils::GetAppDirectory() + _T("notepad2.exe");
715 CString sCmd;
716 sCmd.Format(_T("\"%s\" \"%s\""), (LPCTSTR)editTool, (LPCTSTR)filename);
718 LaunchApplication(sCmd, 0, false, nullptr, uac);
719 return true;
721 bool CAppUtils::LaunchRemoteSetting()
723 CTGitPath path(g_Git.m_CurrentDir);
724 CSettings dlg(IDS_PROC_SETTINGS_TITLE, &path);
725 dlg.SetTreeViewMode(TRUE, TRUE, TRUE);
726 dlg.SetTreeWidth(220);
727 dlg.m_DefaultPage = _T("gitremote");
729 dlg.DoModal();
730 dlg.HandleRestart();
731 return true;
734 * Launch the external blame viewer
736 bool CAppUtils::LaunchTortoiseBlame(const CString& sBlameFile, const CString& Rev, const CString& sParams)
738 CString viewer = _T("\"") + CPathUtils::GetAppDirectory();
739 viewer += _T("TortoiseGitBlame.exe");
740 viewer += _T("\" \"") + sBlameFile + _T("\"");
741 //viewer += _T(" \"") + sLogFile + _T("\"");
742 //viewer += _T(" \"") + sOriginalFile + _T("\"");
743 if(!Rev.IsEmpty() && Rev != GIT_REV_ZERO)
744 viewer += CString(_T(" /rev:"))+Rev;
745 if (!g_sGroupingUUID.IsEmpty())
747 viewer += L" /groupuuid:\"";
748 viewer += g_sGroupingUUID;
749 viewer += L"\"";
751 viewer += _T(" ")+sParams;
753 return LaunchApplication(viewer, IDS_ERR_TGITBLAME, false);
756 bool CAppUtils::FormatTextInRichEditControl(CWnd * pWnd)
758 CString sText;
759 if (!pWnd)
760 return false;
761 bool bStyled = false;
762 pWnd->GetWindowText(sText);
763 // the rich edit control doesn't count the CR char!
764 // to be exact: CRLF is treated as one char.
765 sText.Remove(_T('\r'));
767 // style each line separately
768 int offset = 0;
769 int nNewlinePos;
772 nNewlinePos = sText.Find('\n', offset);
773 CString sLine = nNewlinePos >= 0 ? sText.Mid(offset, nNewlinePos - offset) : sText.Mid(offset);
775 int start = 0;
776 int end = 0;
777 while (FindStyleChars(sLine, '*', start, end))
779 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
780 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
781 SetCharFormat(pWnd, CFM_BOLD, CFE_BOLD);
782 bStyled = true;
783 start = end;
785 start = 0;
786 end = 0;
787 while (FindStyleChars(sLine, '^', start, end))
789 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
790 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
791 SetCharFormat(pWnd, CFM_ITALIC, CFE_ITALIC);
792 bStyled = true;
793 start = end;
795 start = 0;
796 end = 0;
797 while (FindStyleChars(sLine, '_', start, end))
799 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
800 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
801 SetCharFormat(pWnd, CFM_UNDERLINE, CFE_UNDERLINE);
802 bStyled = true;
803 start = end;
805 offset = nNewlinePos+1;
806 } while(nNewlinePos>=0);
807 return bStyled;
810 bool CAppUtils::FindStyleChars(const CString& sText, TCHAR stylechar, int& start, int& end)
812 int i=start;
813 int last = sText.GetLength() - 1;
814 bool bFoundMarker = false;
815 TCHAR c = i == 0 ? _T('\0') : sText[i - 1];
816 TCHAR nextChar = i >= last ? _T('\0') : sText[i + 1];
818 // find a starting marker
819 while (i < last)
821 TCHAR prevChar = c;
822 c = nextChar;
823 nextChar = sText[i + 1];
825 // IsCharAlphaNumeric can be somewhat expensive.
826 // Long lines of "*****" or "----" will be pre-empted efficiently
827 // by the (c != nextChar) condition.
829 if ((c == stylechar) && (c != nextChar))
831 if (IsCharAlphaNumeric(nextChar) && !IsCharAlphaNumeric(prevChar))
833 start = ++i;
834 bFoundMarker = true;
835 break;
838 ++i;
840 if (!bFoundMarker)
841 return false;
843 // find ending marker
844 // c == sText[i - 1]
846 bFoundMarker = false;
847 while (i <= last)
849 TCHAR prevChar = c;
850 c = sText[i];
851 if (c == stylechar)
853 if ((i == last) || (!IsCharAlphaNumeric(sText[i + 1]) && IsCharAlphaNumeric(prevChar)))
855 end = i;
856 ++i;
857 bFoundMarker = true;
858 break;
861 ++i;
863 return bFoundMarker;
866 // from CSciEdit
867 namespace {
868 bool IsValidURLChar(wchar_t ch)
870 return iswalnum(ch) ||
871 ch == L'_' || ch == L'/' || ch == L';' || ch == L'?' || ch == L'&' || ch == L'=' ||
872 ch == L'%' || ch == L':' || ch == L'.' || ch == L'#' || ch == L'-' || ch == L'+' ||
873 ch == L'|' || ch == L'>' || ch == L'<';
876 bool IsUrl(const CString& sText)
878 if (!PathIsURLW(sText))
879 return false;
880 for (const CString& prefix : { L"http://", L"https://", L"git://", L"ftp://", L"file://", L"mailto:" })
882 if (sText.Find(prefix) == 0 && sText.GetLength() != prefix.GetLength())
883 return true;
885 return false;
889 BOOL CAppUtils::StyleURLs(const CString& msg, CWnd* pWnd)
891 std::vector<CHARRANGE> positions = FindURLMatches(msg);
892 CAppUtils::SetCharFormat(pWnd, CFM_LINK, CFE_LINK, positions);
894 return positions.empty() ? FALSE : TRUE;
898 * implements URL searching with the same logic as CSciEdit::StyleURLs
900 std::vector<CHARRANGE> CAppUtils::FindURLMatches(const CString& msg)
902 std::vector<CHARRANGE> result;
904 int len = msg.GetLength();
905 int starturl = -1;
907 for (int i = 0; i <= msg.GetLength(); ++i)
909 if ((i < len) && IsValidURLChar(msg[i]))
911 if (starturl < 0)
912 starturl = i;
914 else
916 if (starturl >= 0)
918 bool strip = true;
919 if (msg[starturl] == '<' && i < len) // try to detect and do not strip URLs put within <>
921 while (starturl <= i && msg[starturl] == '<') // strip leading '<'
922 ++starturl;
923 strip = false;
924 i = starturl;
925 while (i < len && msg[i] != '\r' && msg[i] != '\n' && msg[i] != '>') // find first '>' or new line after resetting i to start position
926 ++i;
928 if (!IsUrl(msg.Mid(starturl, i - starturl)))
930 starturl = -1;
931 continue;
934 int skipTrailing = 0;
935 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] == '<'))
936 ++skipTrailing;
938 CHARRANGE range = { starturl, i - skipTrailing };
939 result.push_back(range);
941 starturl = -1;
945 return result;
948 bool CAppUtils::StartShowUnifiedDiff(HWND hWnd, const CTGitPath& url1, const git_revnum_t& rev1,
949 const CTGitPath& /*url2*/, const git_revnum_t& rev2,
950 //const GitRev& peg /* = GitRev */, const GitRev& headpeg /* = GitRev */,
951 bool /*bAlternateDiff*/ /* = false */, bool /*bIgnoreAncestry*/ /* = false */,
952 bool /* blame = false */,
953 bool bMerge,
954 bool bCombine,
955 bool bNoPrefix)
957 int diffContext = g_Git.GetConfigValueInt32(L"diff.context", -1);
958 CString tempfile=GetTempFile();
959 if (g_Git.GetUnifiedDiff(url1, rev1, rev2, tempfile, bMerge, bCombine, diffContext, bNoPrefix))
961 CMessageBox::Show(hWnd, g_Git.GetGitLastErr(_T("Could not get unified diff."), CGit::GIT_CMD_DIFF), _T("TortoiseGit"), MB_OK);
962 return false;
964 CAppUtils::StartUnifiedDiffViewer(tempfile, rev1 + _T(":") + rev2);
966 #if 0
967 CString sCmd;
968 sCmd.Format(_T("%s /command:showcompare /unified"),
969 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseGitProc.exe")));
970 sCmd += _T(" /url1:\"") + url1.GetGitPathString() + _T("\"");
971 if (rev1.IsValid())
972 sCmd += _T(" /revision1:") + rev1.ToString();
973 sCmd += _T(" /url2:\"") + url2.GetGitPathString() + _T("\"");
974 if (rev2.IsValid())
975 sCmd += _T(" /revision2:") + rev2.ToString();
976 if (peg.IsValid())
977 sCmd += _T(" /pegrevision:") + peg.ToString();
978 if (headpeg.IsValid())
979 sCmd += _T(" /headpegrevision:") + headpeg.ToString();
981 if (bAlternateDiff)
982 sCmd += _T(" /alternatediff");
984 if (bIgnoreAncestry)
985 sCmd += _T(" /ignoreancestry");
987 if (hWnd)
989 sCmd += _T(" /hwnd:");
990 TCHAR buf[30];
991 _stprintf_s(buf, 30, _T("%p"), (void*)hWnd);
992 sCmd += buf;
995 return CAppUtils::LaunchApplication(sCmd, 0, false);
996 #endif
997 return TRUE;
1000 bool CAppUtils::SetupDiffScripts(bool force, const CString& type)
1002 CString scriptsdir = CPathUtils::GetAppParentDirectory();
1003 scriptsdir += _T("Diff-Scripts");
1004 CSimpleFileFind files(scriptsdir);
1005 while (files.FindNextFileNoDirectories())
1007 CString file = files.GetFilePath();
1008 CString filename = files.GetFileName();
1009 CString ext = file.Mid(file.ReverseFind('-') + 1);
1010 ext = _T(".") + ext.Left(ext.ReverseFind('.'));
1011 std::set<CString> extensions;
1012 extensions.insert(ext);
1013 CString kind;
1014 if (file.Right(3).CompareNoCase(_T("vbs"))==0)
1015 kind = _T(" //E:vbscript");
1016 if (file.Right(2).CompareNoCase(_T("js"))==0)
1017 kind = _T(" //E:javascript");
1018 // open the file, read the first line and find possible extensions
1019 // this script can handle
1022 CStdioFile f(file, CFile::modeRead | CFile::shareDenyNone);
1023 CString extline;
1024 if (f.ReadString(extline))
1026 if ((extline.GetLength() > 15 ) &&
1027 ((extline.Left(15).Compare(_T("// extensions: ")) == 0) ||
1028 (extline.Left(14).Compare(_T("' extensions: ")) == 0)))
1030 if (extline[0] == '/')
1031 extline = extline.Mid(15);
1032 else
1033 extline = extline.Mid(14);
1034 CString sToken;
1035 int curPos = 0;
1036 sToken = extline.Tokenize(_T(";"), curPos);
1037 while (!sToken.IsEmpty())
1039 if (!sToken.IsEmpty())
1041 if (sToken[0] != '.')
1042 sToken = _T(".") + sToken;
1043 extensions.insert(sToken);
1045 sToken = extline.Tokenize(_T(";"), curPos);
1049 f.Close();
1051 catch (CFileException* e)
1053 e->Delete();
1056 for (const auto& extension : extensions)
1058 if (type.IsEmpty() || (type.Compare(_T("Diff")) == 0))
1060 if (filename.Left(5).CompareNoCase(_T("diff-")) == 0)
1062 CRegString diffreg = CRegString(_T("Software\\TortoiseGit\\DiffTools\\") + extension);
1063 CString diffregstring = diffreg;
1064 if (force || (diffregstring.IsEmpty()) || (diffregstring.Find(filename) >= 0))
1065 diffreg = _T("wscript.exe \"") + file + _T("\" %base %mine") + kind;
1068 if (type.IsEmpty() || (type.Compare(_T("Merge"))==0))
1070 if (filename.Left(6).CompareNoCase(_T("merge-"))==0)
1072 CRegString diffreg = CRegString(_T("Software\\TortoiseGit\\MergeTools\\") + extension);
1073 CString diffregstring = diffreg;
1074 if (force || (diffregstring.IsEmpty()) || (diffregstring.Find(filename) >= 0))
1075 diffreg = _T("wscript.exe \"") + file + _T("\" %merged %theirs %mine %base") + kind;
1081 return true;
1084 bool CAppUtils::Export(const CString* BashHash, const CTGitPath* orgPath)
1086 // ask from where the export has to be done
1087 CExportDlg dlg;
1088 if(BashHash)
1089 dlg.m_initialRefName=*BashHash;
1090 if (orgPath)
1092 if (PathIsRelative(orgPath->GetWinPath()))
1093 dlg.m_orgPath = g_Git.CombinePath(orgPath);
1094 else
1095 dlg.m_orgPath = *orgPath;
1098 if (dlg.DoModal() == IDOK)
1100 CString cmd;
1101 cmd.Format(_T("git.exe archive --output=\"%s\" --format=zip --verbose %s --"),
1102 (LPCTSTR)dlg.m_strFile, (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName));
1104 CProgressDlg pro;
1105 pro.m_GitCmd=cmd;
1106 pro.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1108 if (status)
1109 return;
1110 postCmdList.emplace_back(IDI_EXPLORER, IDS_STATUSLIST_CONTEXT_EXPLORE, [&]{ CAppUtils::ExploreTo(hWndExplorer, dlg.m_strFile); });
1113 CGit git;
1114 if (!dlg.m_bWholeProject && !dlg.m_orgPath.IsEmpty() && PathIsDirectory(dlg.m_orgPath.GetWinPathString()))
1116 git.m_CurrentDir = dlg.m_orgPath.GetWinPathString();
1117 pro.m_Git = &git;
1119 return (pro.DoModal() == IDOK);
1121 return false;
1124 bool CAppUtils::UpdateBranchDescription(const CString& branch, CString description)
1126 if (branch.IsEmpty())
1127 return false;
1129 CString key;
1130 key.Format(L"branch.%s.description", (LPCTSTR)branch);
1131 description.Replace(L"\r", L"");
1132 description.Trim();
1133 if (description.IsEmpty())
1134 g_Git.UnsetConfigValue(key);
1135 else
1136 g_Git.SetConfigValue(key, description);
1138 return true;
1141 bool CAppUtils::CreateBranchTag(bool isTag /*true*/, const CString* commitHash /*nullptr*/, bool switchNewBranch /*false*/, LPCTSTR name /*nullptr*/)
1143 CCreateBranchTagDlg dlg;
1144 dlg.m_bIsTag = isTag;
1145 dlg.m_bSwitch = switchNewBranch;
1147 if (commitHash)
1148 dlg.m_initialRefName = *commitHash;
1150 if (name)
1151 dlg.m_BranchTagName = name;
1153 if(dlg.DoModal()==IDOK)
1155 CString cmd;
1156 CString force;
1157 CString track;
1158 if(dlg.m_bTrack == TRUE)
1159 track=_T(" --track ");
1160 else if(dlg.m_bTrack == FALSE)
1161 track=_T(" --no-track");
1163 if(dlg.m_bForce)
1164 force=_T(" -f ");
1166 if (isTag)
1168 CString sign;
1169 if(dlg.m_bSign)
1170 sign=_T("-s");
1172 cmd.Format(_T("git.exe tag %s %s %s %s"),
1173 (LPCTSTR)force,
1174 (LPCTSTR)sign,
1175 (LPCTSTR)dlg.m_BranchTagName,
1176 (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName)
1179 if(!dlg.m_Message.Trim().IsEmpty())
1181 CString tempfile = ::GetTempFile();
1182 if (CAppUtils::SaveCommitUnicodeFile(tempfile, dlg.m_Message))
1184 CMessageBox::Show(nullptr, _T("Could not save tag message"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1185 return FALSE;
1187 cmd += _T(" -F ")+tempfile;
1190 else
1192 cmd.Format(_T("git.exe branch %s %s %s %s"),
1193 (LPCTSTR)track,
1194 (LPCTSTR)force,
1195 (LPCTSTR)dlg.m_BranchTagName,
1196 (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName)
1199 CString out;
1200 if(g_Git.Run(cmd,&out,CP_UTF8))
1202 CMessageBox::Show(nullptr, out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1203 return FALSE;
1205 if (!isTag && dlg.m_bSwitch)
1207 // it is a new branch and the user has requested to switch to it
1208 PerformSwitch(dlg.m_BranchTagName);
1210 if (!isTag && !dlg.m_Message.IsEmpty())
1211 UpdateBranchDescription(dlg.m_BranchTagName, dlg.m_Message);
1213 return TRUE;
1215 return FALSE;
1218 bool CAppUtils::Switch(const CString& initialRefName)
1220 CGitSwitchDlg dlg;
1221 if(!initialRefName.IsEmpty())
1222 dlg.m_initialRefName = initialRefName;
1224 if (dlg.DoModal() == IDOK)
1226 CString branch;
1227 if (dlg.m_bBranch)
1228 branch = dlg.m_NewBranch;
1230 // if refs/heads/ is not stripped, checkout will detach HEAD
1231 // checkout prefers branches on name clashes (with tags)
1232 if (dlg.m_VersionName.Left(11) ==_T("refs/heads/") && dlg.m_bBranchOverride != TRUE)
1233 dlg.m_VersionName = dlg.m_VersionName.Mid(11);
1235 return PerformSwitch(dlg.m_VersionName, dlg.m_bForce == TRUE , branch, dlg.m_bBranchOverride == TRUE, dlg.m_bTrack, dlg.m_bMerge == TRUE);
1237 return FALSE;
1240 bool CAppUtils::PerformSwitch(const CString& ref, bool bForce /* false */, const CString& sNewBranch /* CString() */, bool bBranchOverride /* false */, BOOL bTrack /* 2 */, bool bMerge /* false */)
1242 CString cmd;
1243 CString track;
1244 CString force;
1245 CString branch;
1246 CString merge;
1248 if(!sNewBranch.IsEmpty()){
1249 if (bBranchOverride)
1250 branch.Format(_T("-B %s "), (LPCTSTR)sNewBranch);
1251 else
1252 branch.Format(_T("-b %s "), (LPCTSTR)sNewBranch);
1253 if (bTrack == TRUE)
1254 track = _T("--track ");
1255 else if (bTrack == FALSE)
1256 track = _T("--no-track ");
1258 if (bForce)
1259 force = _T("-f ");
1260 if (bMerge)
1261 merge = _T("--merge ");
1263 cmd.Format(_T("git.exe checkout %s%s%s%s%s --"),
1264 (LPCTSTR)force,
1265 (LPCTSTR)track,
1266 (LPCTSTR)merge,
1267 (LPCTSTR)branch,
1268 (LPCTSTR)g_Git.FixBranchName(ref));
1270 CProgressDlg progress;
1271 progress.m_GitCmd = cmd;
1273 CString currentBranch;
1274 bool hasBranch = CGit::GetCurrentBranchFromFile(g_Git.m_CurrentDir, currentBranch) == 0;
1275 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1277 if (!status)
1279 CTGitPath gitPath = g_Git.m_CurrentDir;
1280 if (gitPath.HasSubmodules())
1282 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1284 CString sCmd;
1285 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
1286 RunTortoiseGitProc(sCmd);
1289 if (hasBranch)
1290 postCmdList.emplace_back(IDI_MERGE, IDS_MENUMERGE, [&]{ Merge(&currentBranch); });
1293 CString newBranch;
1294 if (!CGit::GetCurrentBranchFromFile(g_Git.m_CurrentDir, newBranch))
1295 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&]{ Pull(); });
1297 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUCOMMIT, []{
1298 CTGitPathList pathlist;
1299 CTGitPathList selectedlist;
1300 pathlist.AddPath(CTGitPath());
1301 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
1302 CString str;
1303 Commit(CString(), false, str, pathlist, selectedlist, bSelectFilesForCommit);
1306 else
1308 if (bMerge && g_Git.HasWorkingTreeConflicts() > 0)
1310 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
1312 CString sCmd;
1313 sCmd.Format(_T("/command:commit /path:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
1314 CAppUtils::RunTortoiseGitProc(sCmd);
1317 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ PerformSwitch(ref, bForce, sNewBranch, bBranchOverride, bTrack, bMerge); });
1318 if (!bMerge)
1319 postCmdList.emplace_back(IDI_SWITCH, IDS_SWITCH_WITH_MERGE, [&]{ PerformSwitch(ref, bForce, sNewBranch, bBranchOverride, bTrack, true); });
1322 progress.m_PostExecCallback = [&](DWORD& exitCode, CString& extraMsg)
1324 if (bMerge && !exitCode && g_Git.HasWorkingTreeConflicts() > 0)
1326 exitCode = 1; // Treat it as failure
1327 extraMsg = _T("Has merge conflict");
1331 INT_PTR ret = progress.DoModal();
1333 return ret == IDOK;
1336 class CIgnoreFile : public CStdioFile
1338 public:
1339 STRING_VECTOR m_Items;
1340 CString m_eol;
1342 virtual BOOL ReadString(CString& rString)
1344 if (GetPosition() == 0)
1346 unsigned char utf8bom[] = { 0xEF, 0xBB, 0xBF };
1347 char buf[3] = { 0, 0, 0 };
1348 Read(buf, 3);
1349 if (memcpy(buf, utf8bom, sizeof(utf8bom)))
1350 SeekToBegin();
1353 CStringA strA;
1354 char lastChar = '\0';
1355 for (char c = '\0'; Read(&c, 1) == 1; lastChar = c)
1357 if (c == '\r')
1358 continue;
1359 if (c == '\n')
1361 m_eol = lastChar == '\r' ? _T("\r\n") : _T("\n");
1362 break;
1364 strA.AppendChar(c);
1366 if (strA.IsEmpty())
1367 return FALSE;
1369 rString = CUnicodeUtils::GetUnicode(strA);
1370 return TRUE;
1373 void ResetState()
1375 m_Items.clear();
1376 m_eol.Empty();
1380 bool CAppUtils::OpenIgnoreFile(CIgnoreFile &file, const CString& filename)
1382 file.ResetState();
1383 if (!file.Open(filename, CFile::modeCreate | CFile::modeReadWrite | CFile::modeNoTruncate | CFile::typeBinary))
1385 CMessageBox::Show(nullptr, filename + _T(" Open Failure"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1386 return false;
1389 if (file.GetLength() > 0)
1391 CString fileText;
1392 while (file.ReadString(fileText))
1393 file.m_Items.push_back(fileText);
1394 file.Seek(file.GetLength() - 1, 0);
1395 char lastchar[1] = { 0 };
1396 file.Read(lastchar, 1);
1397 file.SeekToEnd();
1398 if (lastchar[0] != '\n')
1400 CStringA eol = CStringA(file.m_eol.IsEmpty() ? _T("\n") : file.m_eol);
1401 file.Write(eol, eol.GetLength());
1404 else
1405 file.SeekToEnd();
1407 return true;
1410 bool CAppUtils::IgnoreFile(const CTGitPathList& path,bool IsMask)
1412 CIgnoreDlg ignoreDlg;
1413 if (ignoreDlg.DoModal() == IDOK)
1415 CString ignorefile;
1416 ignorefile = g_Git.m_CurrentDir + _T("\\");
1418 switch (ignoreDlg.m_IgnoreFile)
1420 case 0:
1421 ignorefile += _T(".gitignore");
1422 break;
1423 case 2:
1424 GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, ignorefile);
1425 ignorefile += _T("info");
1426 if (!PathFileExists(ignorefile))
1427 CreateDirectory(ignorefile, nullptr);
1428 ignorefile += _T("\\exclude");
1429 break;
1432 CIgnoreFile file;
1435 if (ignoreDlg.m_IgnoreFile != 1 && !OpenIgnoreFile(file, ignorefile))
1436 return false;
1438 for (int i = 0; i < path.GetCount(); ++i)
1440 if (ignoreDlg.m_IgnoreFile == 1)
1442 ignorefile = g_Git.CombinePath(path[i].GetContainingDirectory()) + _T("\\.gitignore");
1443 if (!OpenIgnoreFile(file, ignorefile))
1444 return false;
1447 CString ignorePattern;
1448 if (ignoreDlg.m_IgnoreType == 0)
1450 if (ignoreDlg.m_IgnoreFile != 1 && !path[i].GetContainingDirectory().GetGitPathString().IsEmpty())
1451 ignorePattern += _T("/") + path[i].GetContainingDirectory().GetGitPathString();
1453 ignorePattern += _T("/");
1455 if (IsMask)
1456 ignorePattern += _T("*") + path[i].GetFileExtension();
1457 else
1458 ignorePattern += path[i].GetFileOrDirectoryName();
1460 // escape [ and ] so that files get ignored correctly
1461 ignorePattern.Replace(_T("["), _T("\\["));
1462 ignorePattern.Replace(_T("]"), _T("\\]"));
1464 bool found = false;
1465 for (size_t j = 0; j < file.m_Items.size(); ++j)
1467 if (file.m_Items[j] == ignorePattern)
1469 found = true;
1470 break;
1473 if (!found)
1475 file.m_Items.push_back(ignorePattern);
1476 ignorePattern += file.m_eol.IsEmpty() ? _T("\n") : file.m_eol;
1477 CStringA ignorePatternA = CUnicodeUtils::GetUTF8(ignorePattern);
1478 file.Write(ignorePatternA, ignorePatternA.GetLength());
1481 if (ignoreDlg.m_IgnoreFile == 1)
1482 file.Close();
1485 if (ignoreDlg.m_IgnoreFile != 1)
1486 file.Close();
1488 catch(...)
1490 file.Abort();
1491 return false;
1494 return true;
1496 return false;
1499 static bool Reset(const CString& resetTo, int resetType)
1501 CString cmd;
1502 CString type;
1503 switch (resetType)
1505 case 0:
1506 type = _T("--soft");
1507 break;
1508 case 1:
1509 type = _T("--mixed");
1510 break;
1511 case 2:
1512 type = _T("--hard");
1513 break;
1514 default:
1515 resetType = 1;
1516 type = _T("--mixed");
1517 break;
1519 cmd.Format(_T("git.exe reset %s %s --"), (LPCTSTR)type, (LPCTSTR)resetTo);
1521 CProgressDlg progress;
1522 progress.m_GitCmd = cmd;
1524 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1526 if (status)
1528 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ Reset(resetTo, resetType); });
1529 return;
1532 CTGitPath gitPath = g_Git.m_CurrentDir;
1533 if (gitPath.HasSubmodules() && resetType == 2)
1535 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1537 CString sCmd;
1538 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
1539 CAppUtils::RunTortoiseGitProc(sCmd);
1544 INT_PTR ret;
1545 if (g_Git.UsingLibGit2(CGit::GIT_CMD_RESET))
1547 CGitProgressDlg gitdlg;
1548 ResetProgressCommand resetProgressCommand;
1549 gitdlg.SetCommand(&resetProgressCommand);
1550 resetProgressCommand.m_PostCmdCallback = progress.m_PostCmdCallback;
1551 resetProgressCommand.SetRevision(resetTo);
1552 resetProgressCommand.SetResetType(resetType);
1553 ret = gitdlg.DoModal();
1555 else
1556 ret = progress.DoModal();
1558 return ret == IDOK;
1561 bool CAppUtils::GitReset(const CString* CommitHash, int type)
1563 CResetDlg dlg;
1564 dlg.m_ResetType=type;
1565 dlg.m_ResetToVersion=*CommitHash;
1566 dlg.m_initialRefName = *CommitHash;
1567 if (dlg.DoModal() == IDOK)
1568 return Reset(dlg.m_ResetToVersion, dlg.m_ResetType);
1570 return false;
1573 void CAppUtils::DescribeConflictFile(bool mode, bool base,CString &descript)
1575 if(mode == FALSE)
1577 descript.LoadString(IDS_SVNACTION_DELETE);
1578 return;
1580 if(base)
1582 descript.LoadString(IDS_SVNACTION_MODIFIED);
1583 return;
1585 descript.LoadString(IDS_PROC_CREATED);
1588 void CAppUtils::RemoveTempMergeFile(const CTGitPath& path)
1590 ::DeleteFile(CAppUtils::GetMergeTempFile(_T("LOCAL"), path));
1591 ::DeleteFile(CAppUtils::GetMergeTempFile(_T("REMOTE"), path));
1592 ::DeleteFile(CAppUtils::GetMergeTempFile(_T("BASE"), path));
1594 CString CAppUtils::GetMergeTempFile(const CString& type, const CTGitPath &merge)
1596 CString file;
1597 file = g_Git.CombinePath(merge.GetWinPathString() + _T(".") + type + merge.GetFileExtension());
1599 return file;
1602 bool ParseHashesFromLsFile(const BYTE_VECTOR& out, CString& hash1, CString& hash2, CString& hash3)
1604 int pos = 0;
1605 CString one;
1606 CString part;
1608 while (pos >= 0 && pos < (int)out.size())
1610 one.Empty();
1612 CGit::StringAppend(&one, &out[pos], CP_UTF8);
1613 int tabstart = 0;
1614 one.Tokenize(_T("\t"), tabstart);
1616 tabstart = 0;
1617 part = one.Tokenize(_T(" "), tabstart); //Tag
1618 part = one.Tokenize(_T(" "), tabstart); //Mode
1619 part = one.Tokenize(_T(" "), tabstart); //Hash
1620 CString hash = part;
1621 part = one.Tokenize(_T("\t"), tabstart); //Stage
1622 int stage = _ttol(part);
1623 if (stage == 1)
1624 hash1 = hash;
1625 else if (stage == 2)
1626 hash2 = hash;
1627 else if (stage == 3)
1629 hash3 = hash;
1630 return true;
1633 pos = out.findNextString(pos);
1636 return false;
1639 bool CAppUtils::ConflictEdit(const CTGitPath& path, bool /*bAlternativeTool = false*/, bool revertTheirMy /*= false*/, HWND resolveMsgHwnd /*= nullptr*/)
1641 bool bRet = false;
1643 CTGitPath merge=path;
1644 CTGitPath directory = merge.GetDirectory();
1646 // we have the conflicted file (%merged)
1647 // now look for the other required files
1648 //GitStatus stat;
1649 //stat.GetStatus(merge);
1650 //if (stat.status == nullptr)
1651 // return false;
1653 BYTE_VECTOR vector;
1655 CString cmd;
1656 cmd.Format(_T("git.exe ls-files -u -t -z -- \"%s\""), (LPCTSTR)merge.GetGitPathString());
1658 if (g_Git.Run(cmd, &vector))
1659 return FALSE;
1661 if (merge.IsDirectory())
1663 CString baseHash, realBaseHash(GIT_REV_ZERO), localHash(GIT_REV_ZERO), remoteHash(GIT_REV_ZERO);
1664 if (merge.HasAdminDir()) {
1665 CGit subgit;
1666 subgit.m_CurrentDir = g_Git.CombinePath(merge);
1667 CGitHash hash;
1668 subgit.GetHash(hash, _T("HEAD"));
1669 baseHash = hash;
1671 if (ParseHashesFromLsFile(vector, realBaseHash, localHash, remoteHash)) // in base no submodule, but in remote submodule
1672 baseHash = realBaseHash;
1674 CGitDiff::ChangeType changeTypeMine = CGitDiff::Unknown;
1675 CGitDiff::ChangeType changeTypeTheirs = CGitDiff::Unknown;
1677 bool baseOK = false, mineOK = false, theirsOK = false;
1678 CString baseSubject, mineSubject, theirsSubject;
1679 if (merge.HasAdminDir())
1681 CGit subgit;
1682 subgit.m_CurrentDir = g_Git.CombinePath(merge);
1683 CGitDiff::GetSubmoduleChangeType(subgit, baseHash, localHash, baseOK, mineOK, changeTypeMine, baseSubject, mineSubject);
1684 CGitDiff::GetSubmoduleChangeType(subgit, baseHash, remoteHash, baseOK, theirsOK, changeTypeTheirs, baseSubject, theirsSubject);
1686 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)
1688 changeTypeMine = CGitDiff::Identical;
1689 changeTypeTheirs = CGitDiff::NewSubmodule;
1690 baseSubject = _T("no submodule");
1691 mineSubject = baseSubject;
1692 theirsSubject = _T("not initialized");
1694 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
1696 baseHash = localHash;
1697 baseSubject = _T("not initialized");
1698 mineSubject = baseSubject;
1699 theirsSubject = _T("not initialized");
1700 changeTypeMine = CGitDiff::Identical;
1701 changeTypeTheirs = CGitDiff::DeleteSubmodule;
1703 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
1705 baseSubject = _T("not initialized");
1706 mineSubject = baseSubject;
1707 theirsSubject = baseSubject;
1708 if (baseHash == localHash)
1709 changeTypeMine = CGitDiff::Identical;
1711 else
1712 return FALSE;
1714 CSubmoduleResolveConflictDlg resolveSubmoduleConflictDialog;
1715 resolveSubmoduleConflictDialog.SetDiff(merge.GetGitPathString(), revertTheirMy, baseHash, baseSubject, baseOK, localHash, mineSubject, mineOK, changeTypeMine, remoteHash, theirsSubject, theirsOK, changeTypeTheirs);
1716 resolveSubmoduleConflictDialog.DoModal();
1717 if (resolveSubmoduleConflictDialog.m_bResolved && resolveMsgHwnd)
1719 static UINT WM_REVERTMSG = RegisterWindowMessage(_T("GITSLNM_NEEDSREFRESH"));
1720 ::PostMessage(resolveMsgHwnd, WM_REVERTMSG, NULL, NULL);
1723 return TRUE;
1726 CTGitPathList list;
1727 if (list.ParserFromLsFile(vector))
1729 CMessageBox::Show(nullptr, _T("Parse ls-files failed!"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1730 return FALSE;
1733 if (list.IsEmpty())
1734 return FALSE;
1736 CTGitPath theirs;
1737 CTGitPath mine;
1738 CTGitPath base;
1740 if (revertTheirMy)
1742 mine.SetFromGit(GetMergeTempFile(_T("REMOTE"), merge));
1743 theirs.SetFromGit(GetMergeTempFile(_T("LOCAL"), merge));
1745 else
1747 mine.SetFromGit(GetMergeTempFile(_T("LOCAL"), merge));
1748 theirs.SetFromGit(GetMergeTempFile(_T("REMOTE"), merge));
1750 base.SetFromGit(GetMergeTempFile(_T("BASE"),merge));
1752 CString format;
1754 //format=_T("git.exe cat-file blob \":%d:%s\"");
1755 format = _T("git.exe checkout-index --temp --stage=%d -- \"%s\"");
1756 CFile tempfile;
1757 //create a empty file, incase stage is not three
1758 tempfile.Open(mine.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1759 tempfile.Close();
1760 tempfile.Open(theirs.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1761 tempfile.Close();
1762 tempfile.Open(base.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1763 tempfile.Close();
1765 bool b_base=false, b_local=false, b_remote=false;
1767 for (int i = 0; i< list.GetCount(); ++i)
1769 CString outfile;
1770 cmd.Empty();
1771 outfile.Empty();
1773 if( list[i].m_Stage == 1)
1775 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1776 b_base = true;
1777 outfile = base.GetWinPathString();
1780 if( list[i].m_Stage == 2 )
1782 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1783 b_local = true;
1784 outfile = mine.GetWinPathString();
1787 if( list[i].m_Stage == 3 )
1789 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1790 b_remote = true;
1791 outfile = theirs.GetWinPathString();
1793 CString output, err;
1794 if(!outfile.IsEmpty())
1795 if (!g_Git.Run(cmd, &output, &err, CP_UTF8))
1797 CString file;
1798 int start =0 ;
1799 file = output.Tokenize(_T("\t"), start);
1800 ::MoveFileEx(file,outfile,MOVEFILE_REPLACE_EXISTING|MOVEFILE_COPY_ALLOWED);
1802 else
1803 CMessageBox::Show(nullptr, output + L"\n" + err, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1806 if(b_local && b_remote )
1808 merge.SetFromWin(g_Git.CombinePath(merge));
1809 if( revertTheirMy )
1810 bRet = !!CAppUtils::StartExtMerge(base, mine, theirs, merge, _T("BASE"), _T("REMOTE"), _T("LOCAL"), CString(), false, resolveMsgHwnd, true);
1811 else
1812 bRet = !!CAppUtils::StartExtMerge(base, theirs, mine, merge, _T("BASE"), _T("REMOTE"), _T("LOCAL"), CString(), false, resolveMsgHwnd, true);
1814 else
1816 ::DeleteFile(mine.GetWinPathString());
1817 ::DeleteFile(theirs.GetWinPathString());
1818 ::DeleteFile(base.GetWinPathString());
1820 CDeleteConflictDlg dlg;
1821 DescribeConflictFile(b_local, b_base,dlg.m_LocalStatus);
1822 DescribeConflictFile(b_remote,b_base,dlg.m_RemoteStatus);
1823 CGitHash localHash, remoteHash;
1824 if (!g_Git.GetHash(localHash, _T("HEAD")))
1825 dlg.m_LocalHash = localHash.ToString();
1826 if (!g_Git.GetHash(remoteHash, _T("MERGE_HEAD")))
1827 dlg.m_RemoteHash = remoteHash.ToString();
1828 else if (!g_Git.GetHash(remoteHash, _T("rebase-apply/original-commit")))
1829 dlg.m_RemoteHash = remoteHash.ToString();
1830 else if (!g_Git.GetHash(remoteHash, _T("CHERRY_PICK_HEAD")))
1831 dlg.m_RemoteHash = remoteHash.ToString();
1832 else if (!g_Git.GetHash(remoteHash, _T("REVERT_HEAD")))
1833 dlg.m_RemoteHash = remoteHash.ToString();
1834 dlg.m_bShowModifiedButton=b_base;
1835 dlg.m_File=merge.GetGitPathString();
1836 if(dlg.DoModal() == IDOK)
1838 CString out;
1839 if(dlg.m_bIsDelete)
1840 cmd.Format(_T("git.exe rm -- \"%s\""), (LPCTSTR)merge.GetGitPathString());
1841 else
1842 cmd.Format(_T("git.exe add -- \"%s\""), (LPCTSTR)merge.GetGitPathString());
1844 if (g_Git.Run(cmd, &out, CP_UTF8))
1846 MessageBox(nullptr, out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1847 return FALSE;
1849 return TRUE;
1851 else
1852 return FALSE;
1855 #if 0
1856 CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool),
1857 base, theirs, mine, merge);
1858 #endif
1859 #if 0
1860 if (stat.status->text_status == svn_wc_status_conflicted)
1862 // we have a text conflict, use our merge tool to resolve the conflict
1864 CTSVNPath theirs(directory);
1865 CTSVNPath mine(directory);
1866 CTSVNPath base(directory);
1867 bool bConflictData = false;
1869 if ((stat.status->entry)&&(stat.status->entry->conflict_new))
1871 theirs.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->conflict_new));
1872 bConflictData = true;
1874 if ((stat.status->entry)&&(stat.status->entry->conflict_old))
1876 base.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->conflict_old));
1877 bConflictData = true;
1879 if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
1881 mine.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->conflict_wrk));
1882 bConflictData = true;
1884 else
1885 mine = merge;
1886 if (bConflictData)
1887 bRet = !!CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool),
1888 base, theirs, mine, merge);
1891 if (stat.status->prop_status == svn_wc_status_conflicted)
1893 // we have a property conflict
1894 CTSVNPath prej(directory);
1895 if ((stat.status->entry)&&(stat.status->entry->prejfile))
1897 prej.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->prejfile));
1898 // there's a problem: the prej file contains a _description_ of the conflict, and
1899 // that description string might be translated. That means we have no way of parsing
1900 // the file to find out the conflicting values.
1901 // The only thing we can do: show a dialog with the conflict description, then
1902 // let the user either accept the existing property or open the property edit dialog
1903 // to manually change the properties and values. And a button to mark the conflict as
1904 // resolved.
1905 CEditPropConflictDlg dlg;
1906 dlg.SetPrejFile(prej);
1907 dlg.SetConflictedItem(merge);
1908 bRet = (dlg.DoModal() != IDCANCEL);
1912 if (stat.status->tree_conflict)
1914 // we have a tree conflict
1915 SVNInfo info;
1916 const SVNInfoData * pInfoData = info.GetFirstFileInfo(merge, SVNRev(), SVNRev());
1917 if (pInfoData)
1919 if (pInfoData->treeconflict_kind == svn_wc_conflict_kind_text)
1921 CTSVNPath theirs(directory);
1922 CTSVNPath mine(directory);
1923 CTSVNPath base(directory);
1924 bool bConflictData = false;
1926 if (pInfoData->treeconflict_theirfile)
1928 theirs.AppendPathString(pInfoData->treeconflict_theirfile);
1929 bConflictData = true;
1931 if (pInfoData->treeconflict_basefile)
1933 base.AppendPathString(pInfoData->treeconflict_basefile);
1934 bConflictData = true;
1936 if (pInfoData->treeconflict_myfile)
1938 mine.AppendPathString(pInfoData->treeconflict_myfile);
1939 bConflictData = true;
1941 else
1942 mine = merge;
1943 if (bConflictData)
1944 bRet = !!CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool),
1945 base, theirs, mine, merge);
1947 else if (pInfoData->treeconflict_kind == svn_wc_conflict_kind_tree)
1949 CString sConflictAction;
1950 CString sConflictReason;
1951 CString sResolveTheirs;
1952 CString sResolveMine;
1953 CTSVNPath treeConflictPath = CTSVNPath(pInfoData->treeconflict_path);
1954 CString sItemName = treeConflictPath.GetUIFileOrDirectoryName();
1956 if (pInfoData->treeconflict_nodekind == svn_node_file)
1958 switch (pInfoData->treeconflict_operation)
1960 case svn_wc_operation_update:
1961 switch (pInfoData->treeconflict_action)
1963 case svn_wc_conflict_action_edit:
1964 sConflictAction.Format(IDS_TREECONFLICT_FILEUPDATEEDIT, (LPCTSTR)sItemName);
1965 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
1966 break;
1967 case svn_wc_conflict_action_add:
1968 sConflictAction.Format(IDS_TREECONFLICT_FILEUPDATEADD, (LPCTSTR)sItemName);
1969 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
1970 break;
1971 case svn_wc_conflict_action_delete:
1972 sConflictAction.Format(IDS_TREECONFLICT_FILEUPDATEDELETE, (LPCTSTR)sItemName);
1973 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
1974 break;
1976 break;
1977 case svn_wc_operation_switch:
1978 switch (pInfoData->treeconflict_action)
1980 case svn_wc_conflict_action_edit:
1981 sConflictAction.Format(IDS_TREECONFLICT_FILESWITCHEDIT, (LPCTSTR)sItemName);
1982 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
1983 break;
1984 case svn_wc_conflict_action_add:
1985 sConflictAction.Format(IDS_TREECONFLICT_FILESWITCHADD, (LPCTSTR)sItemName);
1986 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
1987 break;
1988 case svn_wc_conflict_action_delete:
1989 sConflictAction.Format(IDS_TREECONFLICT_FILESWITCHDELETE, (LPCTSTR)sItemName);
1990 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
1991 break;
1993 break;
1994 case svn_wc_operation_merge:
1995 switch (pInfoData->treeconflict_action)
1997 case svn_wc_conflict_action_edit:
1998 sConflictAction.Format(IDS_TREECONFLICT_FILEMERGEEDIT, (LPCTSTR)sItemName);
1999 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2000 break;
2001 case svn_wc_conflict_action_add:
2002 sResolveTheirs.Format(IDS_TREECONFLICT_FILEMERGEADD, (LPCTSTR)sItemName);
2003 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2004 break;
2005 case svn_wc_conflict_action_delete:
2006 sConflictAction.Format(IDS_TREECONFLICT_FILEMERGEDELETE, (LPCTSTR)sItemName);
2007 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2008 break;
2010 break;
2013 else if (pInfoData->treeconflict_nodekind == svn_node_dir)
2015 switch (pInfoData->treeconflict_operation)
2017 case svn_wc_operation_update:
2018 switch (pInfoData->treeconflict_action)
2020 case svn_wc_conflict_action_edit:
2021 sConflictAction.Format(IDS_TREECONFLICT_DIRUPDATEEDIT, (LPCTSTR)sItemName);
2022 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2023 break;
2024 case svn_wc_conflict_action_add:
2025 sConflictAction.Format(IDS_TREECONFLICT_DIRUPDATEADD, (LPCTSTR)sItemName);
2026 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2027 break;
2028 case svn_wc_conflict_action_delete:
2029 sConflictAction.Format(IDS_TREECONFLICT_DIRUPDATEDELETE, (LPCTSTR)sItemName);
2030 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR);
2031 break;
2033 break;
2034 case svn_wc_operation_switch:
2035 switch (pInfoData->treeconflict_action)
2037 case svn_wc_conflict_action_edit:
2038 sConflictAction.Format(IDS_TREECONFLICT_DIRSWITCHEDIT, (LPCTSTR)sItemName);
2039 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2040 break;
2041 case svn_wc_conflict_action_add:
2042 sConflictAction.Format(IDS_TREECONFLICT_DIRSWITCHADD, (LPCTSTR)sItemName);
2043 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2044 break;
2045 case svn_wc_conflict_action_delete:
2046 sConflictAction.Format(IDS_TREECONFLICT_DIRSWITCHDELETE, (LPCTSTR)sItemName);
2047 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR);
2048 break;
2050 break;
2051 case svn_wc_operation_merge:
2052 switch (pInfoData->treeconflict_action)
2054 case svn_wc_conflict_action_edit:
2055 sConflictAction.Format(IDS_TREECONFLICT_DIRMERGEEDIT, (LPCTSTR)sItemName);
2056 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2057 break;
2058 case svn_wc_conflict_action_add:
2059 sConflictAction.Format(IDS_TREECONFLICT_DIRMERGEADD, (LPCTSTR)sItemName);
2060 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2061 break;
2062 case svn_wc_conflict_action_delete:
2063 sConflictAction.Format(IDS_TREECONFLICT_DIRMERGEDELETE, (LPCTSTR)sItemName);
2064 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR);
2065 break;
2067 break;
2071 UINT uReasonID = 0;
2072 switch (pInfoData->treeconflict_reason)
2074 case svn_wc_conflict_reason_edited:
2075 uReasonID = IDS_TREECONFLICT_REASON_EDITED;
2076 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2077 break;
2078 case svn_wc_conflict_reason_obstructed:
2079 uReasonID = IDS_TREECONFLICT_REASON_OBSTRUCTED;
2080 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2081 break;
2082 case svn_wc_conflict_reason_deleted:
2083 uReasonID = IDS_TREECONFLICT_REASON_DELETED;
2084 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_REMOVEDIR : IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2085 break;
2086 case svn_wc_conflict_reason_added:
2087 uReasonID = IDS_TREECONFLICT_REASON_ADDED;
2088 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2089 break;
2090 case svn_wc_conflict_reason_missing:
2091 uReasonID = IDS_TREECONFLICT_REASON_MISSING;
2092 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_REMOVEDIR : IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2093 break;
2094 case svn_wc_conflict_reason_unversioned:
2095 uReasonID = IDS_TREECONFLICT_REASON_UNVERSIONED;
2096 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2097 break;
2099 sConflictReason.Format(uReasonID, (LPCTSTR)sConflictAction);
2101 CTreeConflictEditorDlg dlg;
2102 dlg.SetConflictInfoText(sConflictReason);
2103 dlg.SetResolveTexts(sResolveTheirs, sResolveMine);
2104 dlg.SetPath(treeConflictPath);
2105 INT_PTR dlgRet = dlg.DoModal();
2106 bRet = (dlgRet != IDCANCEL);
2110 #endif
2111 return bRet;
2114 bool CAppUtils::IsSSHPutty()
2116 CString sshclient=g_Git.m_Environment.GetEnv(_T("GIT_SSH"));
2117 sshclient=sshclient.MakeLower();
2118 if(sshclient.Find(_T("plink.exe"),0)>=0)
2119 return true;
2120 return false;
2123 CString CAppUtils::GetClipboardLink(const CString &skipGitPrefix, int paramsCount)
2125 if (!OpenClipboard(nullptr))
2126 return CString();
2128 CString sClipboardText;
2129 HGLOBAL hglb = GetClipboardData(CF_TEXT);
2130 if (hglb)
2132 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
2133 sClipboardText = CString(lpstr);
2134 GlobalUnlock(hglb);
2136 hglb = GetClipboardData(CF_UNICODETEXT);
2137 if (hglb)
2139 LPCTSTR lpstr = (LPCTSTR)GlobalLock(hglb);
2140 sClipboardText = lpstr;
2141 GlobalUnlock(hglb);
2143 CloseClipboard();
2145 if(!sClipboardText.IsEmpty())
2147 if(sClipboardText[0] == _T('\"') && sClipboardText[sClipboardText.GetLength()-1] == _T('\"'))
2148 sClipboardText=sClipboardText.Mid(1,sClipboardText.GetLength()-2);
2150 if(sClipboardText.Find( _T("http://")) == 0)
2151 return sClipboardText;
2153 if(sClipboardText.Find( _T("https://")) == 0)
2154 return sClipboardText;
2156 if(sClipboardText.Find( _T("git://")) == 0)
2157 return sClipboardText;
2159 if(sClipboardText.Find( _T("ssh://")) == 0)
2160 return sClipboardText;
2162 if (sClipboardText.Find(_T("git@")) == 0)
2163 return sClipboardText;
2165 if(sClipboardText.GetLength()>=2)
2166 if( sClipboardText[1] == _T(':') )
2167 if( (sClipboardText[0] >= 'A' && sClipboardText[0] <= 'Z')
2168 || (sClipboardText[0] >= 'a' && sClipboardText[0] <= 'z') )
2169 return sClipboardText;
2171 // trim prefixes like "git clone "
2172 if (!skipGitPrefix.IsEmpty() && sClipboardText.Find(skipGitPrefix) == 0)
2174 sClipboardText = sClipboardText.Mid(skipGitPrefix.GetLength()).Trim();
2175 int spacePos = -1;
2176 while (paramsCount >= 0)
2178 --paramsCount;
2179 spacePos = sClipboardText.Find(_T(' '), spacePos + 1);
2180 if (spacePos == -1)
2181 break;
2183 if (spacePos > 0 && paramsCount < 0)
2184 sClipboardText = sClipboardText.Left(spacePos);
2185 return sClipboardText;
2189 return CString();
2192 CString CAppUtils::ChooseRepository(const CString* path)
2194 CBrowseFolder browseFolder;
2195 CRegString regLastResopitory = CRegString(_T("Software\\TortoiseGit\\TortoiseProc\\LastRepo"),_T(""));
2197 browseFolder.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
2198 CString strCloneDirectory;
2199 if(path)
2200 strCloneDirectory=*path;
2201 else
2202 strCloneDirectory = regLastResopitory;
2204 CString title;
2205 title.LoadString(IDS_CHOOSE_REPOSITORY);
2207 browseFolder.SetInfo(title);
2209 if (browseFolder.Show(nullptr, strCloneDirectory) == CBrowseFolder::OK)
2211 regLastResopitory = strCloneDirectory;
2212 return strCloneDirectory;
2214 else
2215 return CString();
2218 bool CAppUtils::SendPatchMail(CTGitPathList& list, bool bIsMainWnd)
2220 CSendMailDlg dlg;
2222 dlg.m_PathList = list;
2224 if(dlg.DoModal()==IDOK)
2226 if (dlg.m_PathList.IsEmpty())
2227 return FALSE;
2229 CGitProgressDlg progDlg;
2230 if (bIsMainWnd)
2231 theApp.m_pMainWnd = &progDlg;
2232 SendMailProgressCommand sendMailProgressCommand;
2233 progDlg.SetCommand(&sendMailProgressCommand);
2235 sendMailProgressCommand.SetPathList(dlg.m_PathList);
2236 progDlg.SetItemCount(dlg.m_PathList.GetCount());
2238 CSendMailPatch sendMailPatch(dlg.m_To, dlg.m_CC, dlg.m_Subject, !!dlg.m_bAttachment, !!dlg.m_bCombine);
2239 sendMailProgressCommand.SetSendMailOption(&sendMailPatch);
2241 progDlg.DoModal();
2243 return true;
2245 return false;
2248 bool CAppUtils::SendPatchMail(const CString& cmd, const CString& formatpatchoutput, bool bIsMainWnd)
2250 CTGitPathList list;
2251 CString log=formatpatchoutput;
2252 int start=log.Find(cmd);
2253 if(start >=0)
2254 CString one=log.Tokenize(_T("\n"),start);
2255 else
2256 start = 0;
2258 while(start>=0)
2260 CString one=log.Tokenize(_T("\n"),start);
2261 one=one.Trim();
2262 if (one.IsEmpty() || one.Find(CString(MAKEINTRESOURCE(IDS_SUCCESS))) == 0)
2263 continue;
2264 one.Replace(_T('/'),_T('\\'));
2265 CTGitPath path;
2266 path.SetFromWin(one);
2267 list.AddPath(path);
2269 if (!list.IsEmpty())
2270 return SendPatchMail(list, bIsMainWnd);
2271 else
2273 CMessageBox::Show(nullptr, IDS_ERR_NOPATCHES, IDS_APPNAME, MB_ICONINFORMATION);
2274 return true;
2279 int CAppUtils::GetLogOutputEncode(CGit *pGit)
2281 CString output;
2282 output = pGit->GetConfigValue(_T("i18n.logOutputEncoding"));
2283 if(output.IsEmpty())
2284 return CUnicodeUtils::GetCPCode(pGit->GetConfigValue(_T("i18n.commitencoding")));
2285 else
2286 return CUnicodeUtils::GetCPCode(output);
2288 int CAppUtils::SaveCommitUnicodeFile(const CString& filename, CString &message)
2292 CFile file(filename, CFile::modeReadWrite | CFile::modeCreate);
2293 int cp = CUnicodeUtils::GetCPCode(g_Git.GetConfigValue(_T("i18n.commitencoding")));
2295 bool stripComments = (CRegDWORD(_T("Software\\TortoiseGit\\StripCommentedLines"), FALSE) == TRUE);
2296 TCHAR commentChar = L'#';
2297 if (stripComments)
2299 CString commentCharValue = g_Git.GetConfigValue(L"core.commentchar");
2300 if (!commentCharValue.IsEmpty())
2301 commentChar = commentCharValue[0];
2304 bool sanitize = (CRegDWORD(_T("Software\\TortoiseGit\\SanitizeCommitMsg"), TRUE) == TRUE);
2305 if (sanitize)
2306 message.Trim(L" \r\n");
2308 int len = message.GetLength();
2309 int start = 0;
2310 int emptyLineCnt = 0;
2311 while (start >= 0 && start < len)
2313 int oldStart = start;
2314 start = message.Find(L"\n", oldStart);
2315 CString line = message.Mid(oldStart);
2316 if (start != -1)
2318 line = line.Left(start - oldStart);
2319 ++start; // move forward so we don't find the same char again
2321 if (stripComments && (!line.IsEmpty() && line.GetAt(0) == commentChar) || (start < 0 && line.IsEmpty()))
2322 continue;
2323 line.TrimRight(L" \r");
2324 if (sanitize)
2326 if (line.IsEmpty())
2328 ++emptyLineCnt;
2329 continue;
2331 if (emptyLineCnt) // squash multiple newlines
2332 file.Write("\n", 1);
2333 emptyLineCnt = 0;
2335 CStringA lineA = CUnicodeUtils::GetMulti(line + L"\n", cp);
2336 file.Write((LPCSTR)lineA, lineA.GetLength());
2338 file.Close();
2339 return 0;
2341 catch (CFileException *e)
2343 e->Delete();
2344 return -1;
2348 bool CAppUtils::Pull(bool showPush, bool showStashPop)
2350 CPullFetchDlg dlg;
2351 dlg.m_IsPull = TRUE;
2352 if (dlg.DoModal() == IDOK)
2354 // "git.exe pull --rebase" is not supported, never and ever. So, adapting it to Fetch & Rebase.
2355 if (dlg.m_bRebase)
2356 return DoFetch(dlg.m_RemoteURL,
2357 FALSE, // Fetch all remotes
2358 dlg.m_bAutoLoad == BST_CHECKED,
2359 dlg.m_bPrune,
2360 dlg.m_bDepth == BST_CHECKED,
2361 dlg.m_nDepth,
2362 dlg.m_bFetchTags,
2363 dlg.m_RemoteBranchName,
2364 dlg.m_bRebaseActivatedInConfigForPull ? 2 : 1, // Rebase after fetching
2365 dlg.m_bRebasePreserveMerges == TRUE); // Preserve merges on rebase
2367 CString url = dlg.m_RemoteURL;
2369 if (dlg.m_bAutoLoad)
2370 CAppUtils::LaunchPAgent(nullptr, &dlg.m_RemoteURL);
2372 CString cmd;
2373 CGitHash hashOld;
2374 if (g_Git.GetHash(hashOld, _T("HEAD")))
2376 MessageBox(nullptr, g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
2377 return false;
2380 CString noff;
2381 CString ffonly;
2382 CString squash;
2383 CString nocommit;
2384 CString depth;
2385 CString notags;
2386 CString prune;
2388 if (!dlg.m_bFetchTags)
2389 notags = _T("--no-tags ");
2391 if (dlg.m_bFetchTags == TRUE)
2392 notags = _T("--tags ");
2394 if (dlg.m_bNoFF)
2395 noff=_T("--no-ff ");
2397 if (dlg.m_bFFonly)
2398 ffonly = _T("--ff-only ");
2400 if (dlg.m_bSquash)
2401 squash = _T("--squash ");
2403 if (dlg.m_bNoCommit)
2404 nocommit = _T("--no-commit ");
2406 if (dlg.m_bDepth)
2407 depth.Format(_T("--depth %d "), dlg.m_nDepth);
2409 if (dlg.m_bPrune == TRUE)
2410 prune = _T("--prune ");
2411 else if (dlg.m_bPrune == FALSE)
2412 prune = _T("--no-prune ");
2414 cmd.Format(_T("git.exe pull --progress%s -v %s%s%s%s%s%s%s\"%s\" %s"), CRegDWORD(L"Software\\TortoiseGit\\PullRebaseBehaviorLike1816", FALSE) == FALSE ? L" --no-rebase" : L"", (LPCTSTR)noff, (LPCTSTR)ffonly, (LPCTSTR)squash, (LPCTSTR)nocommit, (LPCTSTR)depth, (LPCTSTR)notags, (LPCTSTR)prune, (LPCTSTR)url, (LPCTSTR)dlg.m_RemoteBranchName);
2415 CProgressDlg progress;
2416 progress.m_GitCmd = cmd;
2418 CGitHash hashNew; // declare outside lambda, because it is captured by reference
2419 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2421 if (status)
2423 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&]{ Pull(); });
2424 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSTASHSAVE, [&]{ StashSave(_T(""), true); });
2425 return;
2428 if (showStashPop)
2429 postCmdList.emplace_back(IDI_RELOCATE, IDS_MENUSTASHPOP, []{ StashPop(); });
2431 if (g_Git.GetHash(hashNew, _T("HEAD")))
2432 MessageBox(nullptr, g_Git.GetGitLastErr(_T("Could not get HEAD hash after pulling.")), _T("TortoiseGit"), MB_ICONERROR);
2433 else
2435 postCmdList.emplace_back(IDI_DIFF, IDS_PROC_PULL_DIFFS, [&]
2437 CFileDiffDlg dlg;
2438 dlg.SetDiff(nullptr, hashOld.ToString(), hashNew.ToString());
2439 dlg.DoModal();
2441 postCmdList.emplace_back(IDI_LOG, IDS_PROC_PULL_LOG, [&]
2443 CLogDlg dlg;
2444 dlg.SetParams(CTGitPath(_T("")), CTGitPath(_T("")), _T(""), hashOld.ToString() + _T("..") + hashNew.ToString(), 0);
2445 dlg.DoModal();
2449 if (showPush)
2450 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, []{ Push(); });
2452 CTGitPath gitPath = g_Git.m_CurrentDir;
2453 if (gitPath.HasSubmodules())
2455 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
2457 CString sCmd;
2458 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
2459 CAppUtils::RunTortoiseGitProc(sCmd);
2464 INT_PTR ret = progress.DoModal();
2466 if (ret == IDOK && progress.m_GitStatus == 1 && progress.m_LogText.Find(_T("CONFLICT")) >= 0 && CMessageBox::Show(nullptr, IDS_SEECHANGES, IDS_APPNAME, MB_YESNO | MB_ICONINFORMATION) == IDYES)
2468 CChangedDlg changeddlg;
2469 changeddlg.m_pathList.AddPath(CTGitPath());
2470 changeddlg.DoModal();
2472 return true;
2475 return ret == IDOK;
2478 return false;
2481 bool CAppUtils::RebaseAfterFetch(const CString& upstream, int rebase, bool preserveMerges)
2483 while (true)
2485 CRebaseDlg dlg;
2486 if (!upstream.IsEmpty())
2487 dlg.m_Upstream = upstream;
2488 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENULOG)));
2489 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUPUSH)));
2490 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUDESSENDMAIL)));
2491 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUREBASE)));
2492 dlg.m_bRebaseAutoStart = (rebase == 2);
2493 dlg.m_bPreserveMerges = preserveMerges;
2494 INT_PTR response = dlg.DoModal();
2495 if (response == IDOK)
2496 return true;
2497 else if (response == IDC_REBASE_POST_BUTTON)
2499 CString cmd = _T("/command:log");
2500 cmd += _T(" /path:\"") + g_Git.m_CurrentDir + _T("\"");
2501 CAppUtils::RunTortoiseGitProc(cmd);
2502 return true;
2504 else if (response == IDC_REBASE_POST_BUTTON + 1)
2505 return Push();
2506 else if (response == IDC_REBASE_POST_BUTTON + 2)
2508 CString cmd, out, err;
2509 cmd.Format(_T("git.exe format-patch -o \"%s\" %s..%s"),
2510 (LPCTSTR)g_Git.m_CurrentDir,
2511 (LPCTSTR)g_Git.FixBranchName(dlg.m_Upstream),
2512 (LPCTSTR)g_Git.FixBranchName(dlg.m_Branch));
2513 if (g_Git.Run(cmd, &out, &err, CP_UTF8))
2515 CMessageBox::Show(nullptr, out + L"\n" + err, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
2516 return false;
2518 CAppUtils::SendPatchMail(cmd, out);
2519 return true;
2521 else if (response == IDC_REBASE_POST_BUTTON + 3)
2522 continue;
2523 else if (response == IDCANCEL)
2524 return false;
2525 return false;
2529 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, int runRebase, const bool rebasePreserveMerges)
2531 if (loadPuttyAgent)
2533 if (fetchAllRemotes)
2535 STRING_VECTOR list;
2536 g_Git.GetRemoteList(list);
2538 for (const auto& remote : list)
2539 CAppUtils::LaunchPAgent(nullptr, &remote);
2541 else
2542 CAppUtils::LaunchPAgent(nullptr, &url);
2545 CString upstream = _T("FETCH_HEAD");
2546 CGitHash oldUpstreamHash;
2547 if (runRebase)
2549 STRING_VECTOR list;
2550 g_Git.GetRemoteList(list);
2551 for (auto it = list.cbegin(); it != list.cend(); ++it)
2553 if (url == *it)
2555 CString remote, trackedBranch;
2556 g_Git.GetRemoteTrackedBranchForHEAD(remote, trackedBranch);
2557 if (!remote.IsEmpty() && !trackedBranch.IsEmpty())
2559 upstream = L"remotes/" + remote + L"/" + trackedBranch;
2560 g_Git.GetHash(oldUpstreamHash, upstream);
2562 break;
2567 CString cmd, arg;
2568 arg = _T(" --progress");
2570 if (bDepth)
2571 arg.AppendFormat(_T(" --depth %d"), nDepth);
2573 if (prune == TRUE)
2574 arg += _T(" --prune");
2575 else if (prune == FALSE)
2576 arg += _T(" --no-prune");
2578 if (fetchTags == 1)
2579 arg += _T(" --tags");
2580 else if (fetchTags == 0)
2581 arg += _T(" --no-tags");
2583 if (fetchAllRemotes)
2584 cmd.Format(_T("git.exe fetch --all -v%s"), (LPCTSTR)arg);
2585 else
2586 cmd.Format(_T("git.exe fetch -v%s \"%s\" %s"), (LPCTSTR)arg, (LPCTSTR)url, (LPCTSTR)remoteBranch);
2588 CProgressDlg progress;
2589 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2591 if (status)
2593 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ DoFetch(url, fetchAllRemotes, loadPuttyAgent, prune, bDepth, nDepth, fetchTags, remoteBranch, runRebase, rebasePreserveMerges); });
2594 return;
2597 postCmdList.emplace_back(IDI_LOG, IDS_MENULOG, []
2599 CString cmd = _T("/command:log");
2600 cmd += _T(" /path:\"") + g_Git.m_CurrentDir + _T("\"");
2601 CAppUtils::RunTortoiseGitProc(cmd);
2604 postCmdList.emplace_back(IDI_REVERT, IDS_PROC_RESET, []
2606 CString pullRemote, pullBranch;
2607 g_Git.GetRemoteTrackedBranchForHEAD(pullRemote, pullBranch);
2608 CString defaultUpstream;
2609 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
2610 defaultUpstream.Format(_T("remotes/%s/%s"), (LPCTSTR)pullRemote, (LPCTSTR)pullBranch);
2611 CAppUtils::GitReset(&defaultUpstream, 2);
2614 postCmdList.emplace_back(IDI_PULL, IDS_MENUFETCH, []{ CAppUtils::Fetch(); });
2616 if (!runRebase && !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir))
2617 postCmdList.emplace_back(IDI_REBASE, IDS_MENUREBASE, [&]{ runRebase = false; CAppUtils::RebaseAfterFetch(); });
2620 progress.m_GitCmd = cmd;
2622 if (g_Git.UsingLibGit2(CGit::GIT_CMD_FETCH))
2624 CGitProgressDlg gitdlg;
2625 FetchProgressCommand fetchProgressCommand;
2626 if (!fetchAllRemotes)
2627 fetchProgressCommand.SetUrl(url);
2628 gitdlg.SetCommand(&fetchProgressCommand);
2629 fetchProgressCommand.m_PostCmdCallback = progress.m_PostCmdCallback;
2630 fetchProgressCommand.SetAutoTag(fetchTags == 1 ? GIT_REMOTE_DOWNLOAD_TAGS_ALL : fetchTags == 2 ? GIT_REMOTE_DOWNLOAD_TAGS_AUTO : GIT_REMOTE_DOWNLOAD_TAGS_NONE);
2631 if (!fetchAllRemotes)
2632 fetchProgressCommand.SetRefSpec(remoteBranch);
2633 return gitdlg.DoModal() == IDOK;
2636 progress.m_PostExecCallback = [&](DWORD& exitCode, CString&)
2638 if (exitCode || !runRebase)
2639 return;
2641 CGitHash remoteBranchHash;
2642 g_Git.GetHash(remoteBranchHash, upstream);
2643 if (runRebase == 1 && remoteBranchHash == oldUpstreamHash && !oldUpstreamHash.IsEmpty() && CMessageBox::ShowCheck(nullptr, IDS_REBASE_BRANCH_UNCHANGED, IDS_APPNAME, MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2, L"OpenRebaseRemoteBranchUnchanged", IDS_MSGBOX_DONOTSHOWAGAIN) == IDNO)
2644 return;
2646 if (runRebase == 1 && g_Git.IsFastForward(L"HEAD", upstream))
2648 UINT ret = CMessageBox::ShowCheck(nullptr, IDS_REBASE_BRANCH_FF, IDS_APPNAME, 2, IDI_QUESTION, IDS_MERGEBUTTON, IDS_REBASEBUTTON, IDS_ABORTBUTTON, L"OpenRebaseRemoteBranchFastForwards", IDS_MSGBOX_DONOTSHOWAGAIN);
2649 if (ret == 3)
2650 return;
2651 if (ret == 1)
2653 CProgressDlg mergeProgress;
2654 mergeProgress.m_GitCmd = L"git.exe merge --ff-only " + upstream;
2655 mergeProgress.m_AutoClose = AUTOCLOSE_IF_NO_ERRORS;
2656 mergeProgress.m_PostCmdCallback = [](DWORD status, PostCmdList& postCmdList)
2658 if (status && g_Git.HasWorkingTreeConflicts())
2660 // there are conflict files
2661 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
2663 CString sCmd;
2664 sCmd.Format(L"/command:commit /path:\"%s\"", g_Git.m_CurrentDir);
2665 CAppUtils::RunTortoiseGitProc(sCmd);
2669 mergeProgress.DoModal();
2670 return;
2674 CAppUtils::RebaseAfterFetch(upstream, runRebase, rebasePreserveMerges);
2677 return progress.DoModal() == IDOK;
2680 bool CAppUtils::Fetch(const CString& remoteName, bool allRemotes)
2682 CPullFetchDlg dlg;
2683 dlg.m_PreSelectRemote = remoteName;
2684 dlg.m_IsPull=FALSE;
2685 dlg.m_bAllRemotes = allRemotes;
2687 if(dlg.DoModal()==IDOK)
2688 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 ? 1 : 0, FALSE);
2690 return false;
2693 bool CAppUtils::DoPush(bool autoloadKey, bool pack, bool tags, bool allRemotes, bool allBranches, bool force, bool forceWithLease, const CString& localBranch, const CString& remote, const CString& remoteBranch, bool setUpstream, int recurseSubmodules)
2695 CString error;
2696 DWORD exitcode = 0xFFFFFFFF;
2697 if (CHooks::Instance().PrePush(g_Git.m_CurrentDir, exitcode, error))
2699 if (exitcode)
2701 CString temp;
2702 temp.Format(IDS_ERR_HOOKFAILED, (LPCTSTR)error);
2703 CMessageBox::Show(nullptr, temp, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
2704 return false;
2708 int iRecurseSubmodules = 0;
2709 if (GetMsysgitVersion() >= 0x02070000)
2711 CString sRecurseSubmodules = g_Git.GetConfigValue(_T("push.recurseSubmodules"));
2712 if (sRecurseSubmodules == _T("check"))
2713 iRecurseSubmodules = 1;
2714 else if (sRecurseSubmodules == _T("on-demand"))
2715 iRecurseSubmodules = 2;
2718 CString arg;
2719 if (pack)
2720 arg += _T("--thin ");
2721 if (tags && !allBranches)
2722 arg += _T("--tags ");
2723 if (force)
2724 arg += _T("--force ");
2725 if (forceWithLease)
2726 arg += _T("--force-with-lease ");
2727 if (setUpstream)
2728 arg += _T("--set-upstream ");
2729 if (recurseSubmodules == 0 && recurseSubmodules != iRecurseSubmodules)
2730 arg += _T("--recurse-submodules=no ");
2731 if (recurseSubmodules == 1 && recurseSubmodules != iRecurseSubmodules)
2732 arg += _T("--recurse-submodules=check ");
2733 if (recurseSubmodules == 2 && recurseSubmodules != iRecurseSubmodules)
2734 arg += _T("--recurse-submodules=on-demand ");
2736 arg += _T("--progress ");
2738 CProgressDlg progress;
2740 STRING_VECTOR remotesList;
2741 if (allRemotes)
2742 g_Git.GetRemoteList(remotesList);
2743 else
2744 remotesList.push_back(remote);
2746 for (unsigned int i = 0; i < remotesList.size(); ++i)
2748 if (autoloadKey)
2749 CAppUtils::LaunchPAgent(nullptr, &remotesList[i]);
2751 CString cmd;
2752 if (allBranches)
2754 cmd.Format(_T("git.exe push --all %s\"%s\""),
2755 (LPCTSTR)arg,
2756 (LPCTSTR)remotesList[i]);
2758 if (tags)
2760 progress.m_GitCmdList.push_back(cmd);
2761 cmd.Format(_T("git.exe push --tags %s\"%s\""), (LPCTSTR)arg, (LPCTSTR)remotesList[i]);
2764 else
2766 cmd.Format(_T("git.exe push %s\"%s\" %s"),
2767 (LPCTSTR)arg,
2768 (LPCTSTR)remotesList[i],
2769 (LPCTSTR)localBranch);
2770 if (!remoteBranch.IsEmpty())
2772 cmd += L":";
2773 cmd += remoteBranch;
2776 progress.m_GitCmdList.push_back(cmd);
2778 if (!allBranches && !!CRegDWORD(_T("Software\\TortoiseGit\\ShowBranchRevisionNumber"), FALSE))
2780 cmd.Format(_T("git.exe rev-list --count --first-parent %s"), (LPCTSTR)localBranch);
2781 progress.m_GitCmdList.push_back(cmd);
2785 CString superprojectRoot;
2786 GitAdminDir::HasAdminDir(g_Git.m_CurrentDir, false, &superprojectRoot);
2787 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2789 // need to execute hooks as those might be needed by post action commands
2790 DWORD exitcode = 0xFFFFFFFF;
2791 CString error;
2792 if (CHooks::Instance().PostPush(g_Git.m_CurrentDir, exitcode, error))
2794 if (exitcode)
2796 CString temp;
2797 temp.Format(IDS_ERR_HOOKFAILED, (LPCTSTR)error);
2798 MessageBox(nullptr, temp, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
2802 if (status)
2804 bool rejected = progress.GetLogText().Find(_T("! [rejected]")) > 0;
2805 if (rejected)
2807 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, []{ Pull(true); });
2808 postCmdList.emplace_back(IDI_PULL, IDS_MENUFETCH, [&]{ Fetch(allRemotes ? _T("") : remote, allRemotes); });
2810 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, [&]{ Push(localBranch); });
2811 return;
2814 postCmdList.emplace_back(IDS_PROC_REQUESTPULL, [&]{ RequestPull(remoteBranch); });
2815 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, [&]{ Push(localBranch); });
2816 postCmdList.emplace_back(IDI_SWITCH, IDS_MENUSWITCH, [&]{ Switch(); });
2817 if (!superprojectRoot.IsEmpty())
2819 postCmdList.emplace_back(IDI_COMMIT, IDS_PROC_COMMIT_SUPERPROJECT, [&]
2821 CString sCmd;
2822 sCmd.Format(_T("/command:commit /path:\"%s\""), (LPCTSTR)superprojectRoot);
2823 RunTortoiseGitProc(sCmd);
2828 INT_PTR ret = progress.DoModal();
2829 return ret == IDOK;
2832 bool CAppUtils::Push(const CString& selectLocalBranch)
2834 CPushDlg dlg;
2835 dlg.m_BranchSourceName = selectLocalBranch;
2837 if (dlg.DoModal() == IDOK)
2838 return DoPush(!!dlg.m_bAutoLoad, !!dlg.m_bPack, !!dlg.m_bTags, !!dlg.m_bPushAllRemotes, !!dlg.m_bPushAllBranches, !!dlg.m_bForce, !!dlg.m_bForceWithLease, dlg.m_BranchSourceName, dlg.m_URL, dlg.m_BranchRemoteName, !!dlg.m_bSetUpstream, dlg.m_RecurseSubmodules);
2840 return FALSE;
2843 bool CAppUtils::RequestPull(const CString& endrevision, const CString& repositoryUrl, bool bIsMainWnd)
2845 CRequestPullDlg dlg;
2846 dlg.m_RepositoryURL = repositoryUrl;
2847 dlg.m_EndRevision = endrevision;
2848 if (dlg.DoModal()==IDOK)
2850 CString cmd;
2851 cmd.Format(_T("git.exe request-pull %s \"%s\" %s"), (LPCTSTR)dlg.m_StartRevision, (LPCTSTR)dlg.m_RepositoryURL, (LPCTSTR)dlg.m_EndRevision);
2853 CSysProgressDlg sysProgressDlg;
2854 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
2855 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_CREATINGPULLREUQEST)));
2856 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
2857 sysProgressDlg.SetShowProgressBar(false);
2858 sysProgressDlg.ShowModeless((HWND)nullptr, true);
2860 CString tempFileName = GetTempFile();
2861 CString err;
2862 DeleteFile(tempFileName);
2863 CreateDirectory(tempFileName, nullptr);
2864 tempFileName += _T("\\pullrequest.txt");
2865 if (g_Git.RunLogFile(cmd, tempFileName, &err))
2867 CString msg;
2868 msg.LoadString(IDS_ERR_PULLREUQESTFAILED);
2869 MessageBox(nullptr, msg + _T("\n") + err, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
2870 return false;
2873 if (sysProgressDlg.HasUserCancelled())
2875 CMessageBox::Show(nullptr, IDS_USERCANCELLED, IDS_APPNAME, MB_OK);
2876 ::DeleteFile(tempFileName);
2877 return false;
2880 sysProgressDlg.Stop();
2882 if (dlg.m_bSendMail)
2884 CSendMailDlg sendmaildlg;
2885 sendmaildlg.m_PathList = CTGitPathList(CTGitPath(tempFileName));
2886 sendmaildlg.m_bCustomSubject = true;
2888 if (sendmaildlg.DoModal() == IDOK)
2890 if (sendmaildlg.m_PathList.IsEmpty())
2891 return FALSE;
2893 CGitProgressDlg progDlg;
2894 if (bIsMainWnd)
2895 theApp.m_pMainWnd = &progDlg;
2896 SendMailProgressCommand sendMailProgressCommand;
2897 progDlg.SetCommand(&sendMailProgressCommand);
2899 sendMailProgressCommand.SetPathList(sendmaildlg.m_PathList);
2900 progDlg.SetItemCount(sendmaildlg.m_PathList.GetCount());
2902 CSendMailCombineable sendMailCombineable(sendmaildlg.m_To, sendmaildlg.m_CC, sendmaildlg.m_Subject, !!sendmaildlg.m_bAttachment, !!sendmaildlg.m_bCombine);
2903 sendMailProgressCommand.SetSendMailOption(&sendMailCombineable);
2905 progDlg.DoModal();
2907 return true;
2909 return false;
2912 CAppUtils::LaunchAlternativeEditor(tempFileName);
2914 return true;
2917 void CAppUtils::RemoveTrailSlash(CString &path)
2919 if(path.IsEmpty())
2920 return ;
2922 // For URL, do not trim the slash just after the host name component.
2923 int index = path.Find(_T("://"));
2924 if (index >= 0)
2926 index += 4;
2927 index = path.Find(_T('/'), index);
2928 if (index == path.GetLength() - 1)
2929 return;
2932 while(path[path.GetLength()-1] == _T('\\') || path[path.GetLength()-1] == _T('/' ) )
2934 path=path.Left(path.GetLength()-1);
2935 if(path.IsEmpty())
2936 return;
2940 bool CAppUtils::CheckUserData()
2942 while(g_Git.GetUserName().IsEmpty() || g_Git.GetUserEmail().IsEmpty())
2944 if (CMessageBox::Show(nullptr, IDS_PROC_NOUSERDATA, IDS_APPNAME, MB_YESNO | MB_ICONERROR) == IDYES)
2946 CTGitPath path(g_Git.m_CurrentDir);
2947 CSettings dlg(IDS_PROC_SETTINGS_TITLE,&path);
2948 dlg.SetTreeViewMode(TRUE, TRUE, TRUE);
2949 dlg.SetTreeWidth(220);
2950 dlg.m_DefaultPage = _T("gitconfig");
2952 dlg.DoModal();
2953 dlg.HandleRestart();
2956 else
2957 return false;
2960 return true;
2963 BOOL CAppUtils::Commit(const CString& bugid, BOOL bWholeProject, CString &sLogMsg,
2964 CTGitPathList &pathList,
2965 CTGitPathList &selectedList,
2966 bool bSelectFilesForCommit)
2968 bool bFailed = true;
2970 if (!CheckUserData())
2971 return false;
2973 while (bFailed)
2975 bFailed = false;
2976 CCommitDlg dlg;
2977 dlg.m_sBugID = bugid;
2979 dlg.m_bWholeProject = bWholeProject;
2981 dlg.m_sLogMessage = sLogMsg;
2982 dlg.m_pathList = pathList;
2983 dlg.m_checkedPathList = selectedList;
2984 dlg.m_bSelectFilesForCommit = bSelectFilesForCommit;
2985 if (dlg.DoModal() == IDOK)
2987 if (dlg.m_pathList.IsEmpty())
2988 return false;
2989 // if the user hasn't changed the list of selected items
2990 // we don't use that list. Because if we would use the list
2991 // of pre-checked items, the dialog would show different
2992 // checked items on the next startup: it would only try
2993 // to check the parent folder (which might not even show)
2994 // instead, we simply use an empty list and let the
2995 // default checking do its job.
2996 if (!dlg.m_pathList.IsEqual(pathList))
2997 selectedList = dlg.m_pathList;
2998 pathList = dlg.m_updatedPathList;
2999 sLogMsg = dlg.m_sLogMessage;
3000 bSelectFilesForCommit = true;
3002 switch (dlg.m_PostCmd)
3004 case GIT_POSTCOMMIT_CMD_DCOMMIT:
3005 CAppUtils::SVNDCommit();
3006 break;
3007 case GIT_POSTCOMMIT_CMD_PUSH:
3008 CAppUtils::Push();
3009 break;
3010 case GIT_POSTCOMMIT_CMD_CREATETAG:
3011 CAppUtils::CreateBranchTag(TRUE);
3012 break;
3013 case GIT_POSTCOMMIT_CMD_PULL:
3014 CAppUtils::Pull(true);
3015 break;
3016 default:
3017 break;
3020 // CGitProgressDlg progDlg;
3021 // progDlg.SetChangeList(dlg.m_sChangeList, !!dlg.m_bKeepChangeList);
3022 // if (parser.HasVal(_T("closeonend")))
3023 // progDlg.SetAutoClose(parser.GetLongVal(_T("closeonend")));
3024 // progDlg.SetCommand(CGitProgressDlg::GitProgress_Commit);
3025 // progDlg.SetOptions(dlg.m_bKeepLocks ? ProgOptKeeplocks : ProgOptNone);
3026 // progDlg.SetPathList(dlg.m_pathList);
3027 // progDlg.SetCommitMessage(dlg.m_sLogMessage);
3028 // progDlg.SetDepth(dlg.m_bRecursive ? Git_depth_infinity : svn_depth_empty);
3029 // progDlg.SetSelectedList(dlg.m_selectedPathList);
3030 // progDlg.SetItemCount(dlg.m_itemsCount);
3031 // progDlg.SetBugTraqProvider(dlg.m_BugTraqProvider);
3032 // progDlg.DoModal();
3033 // CRegDWORD err = CRegDWORD(_T("Software\\TortoiseGit\\ErrorOccurred"), FALSE);
3034 // err = (DWORD)progDlg.DidErrorsOccur();
3035 // bFailed = progDlg.DidErrorsOccur();
3036 // bRet = progDlg.DidErrorsOccur();
3037 // CRegDWORD bFailRepeat = CRegDWORD(_T("Software\\TortoiseGit\\CommitReopen"), FALSE);
3038 // if (DWORD(bFailRepeat)==0)
3039 // bFailed = false; // do not repeat if the user chose not to in the settings.
3042 return true;
3045 BOOL CAppUtils::SVNDCommit()
3047 CSVNDCommitDlg dcommitdlg;
3048 CString gitSetting = g_Git.GetConfigValue(_T("svn.rmdir"));
3049 if (gitSetting.IsEmpty()) {
3050 if (dcommitdlg.DoModal() != IDOK)
3051 return false;
3052 else
3054 if (dcommitdlg.m_remember)
3056 if (dcommitdlg.m_rmdir)
3057 gitSetting = _T("true");
3058 else
3059 gitSetting = _T("false");
3060 if(g_Git.SetConfigValue(_T("svn.rmdir"),gitSetting))
3062 CString msg;
3063 msg.Format(IDS_PROC_SAVECONFIGFAILED, _T("svn.rmdir"), gitSetting);
3064 CMessageBox::Show(nullptr, msg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
3070 BOOL IsStash = false;
3071 if(!g_Git.CheckCleanWorkTree())
3073 if (CMessageBox::Show(nullptr, IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
3075 CSysProgressDlg sysProgressDlg;
3076 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3077 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3078 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3079 sysProgressDlg.SetShowProgressBar(false);
3080 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3081 sysProgressDlg.ShowModeless((HWND)nullptr, true);
3083 CString cmd,out;
3084 cmd=_T("git.exe stash");
3085 if (g_Git.Run(cmd, &out, CP_UTF8))
3087 sysProgressDlg.Stop();
3088 MessageBox(nullptr, out,_T("TortoiseGit"), MB_OK | MB_ICONERROR);
3089 return false;
3091 sysProgressDlg.Stop();
3093 IsStash =true;
3095 else
3096 return false;
3099 CProgressDlg progress;
3100 if (dcommitdlg.m_rmdir)
3101 progress.m_GitCmd=_T("git.exe svn dcommit --rmdir");
3102 else
3103 progress.m_GitCmd=_T("git.exe svn dcommit");
3104 if(progress.DoModal()==IDOK && progress.m_GitStatus == 0)
3106 if( IsStash)
3108 if (CMessageBox::Show(nullptr, IDS_DCOMMIT_STASH_POP, IDS_APPNAME, MB_YESNO | MB_ICONINFORMATION) == IDYES)
3110 CSysProgressDlg sysProgressDlg;
3111 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3112 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3113 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3114 sysProgressDlg.SetShowProgressBar(false);
3115 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3116 sysProgressDlg.ShowModeless((HWND)nullptr, true);
3118 CString cmd,out;
3119 cmd=_T("git.exe stash pop");
3120 if (g_Git.Run(cmd, &out, CP_UTF8))
3122 sysProgressDlg.Stop();
3123 MessageBox(nullptr, out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
3124 return false;
3126 sysProgressDlg.Stop();
3128 else
3129 return false;
3131 return TRUE;
3133 return FALSE;
3136 BOOL CAppUtils::Merge(const CString* commit, bool showStashPop)
3138 if (!CheckUserData())
3139 return FALSE;
3141 CMergeDlg dlg;
3142 if(commit)
3143 dlg.m_initialRefName = *commit;
3145 if(dlg.DoModal()==IDOK)
3147 CString cmd;
3148 CString args;
3150 if(dlg.m_bNoFF)
3151 args += _T(" --no-ff");
3152 else if (dlg.m_bFFonly)
3153 args += _T(" --ff-only");
3155 if(dlg.m_bSquash)
3156 args += _T(" --squash");
3158 if(dlg.m_bNoCommit)
3159 args += _T(" --no-commit");
3161 if (dlg.m_bLog)
3162 args.AppendFormat(L" --log=%d", dlg.m_nLog);
3164 if (!dlg.m_MergeStrategy.IsEmpty())
3166 args += _T(" --strategy=") + dlg.m_MergeStrategy;
3167 if (!dlg.m_StrategyOption.IsEmpty())
3169 args += _T(" --strategy-option=") + dlg.m_StrategyOption;
3170 if (!dlg.m_StrategyParam.IsEmpty())
3171 args += _T("=") + dlg.m_StrategyParam;
3175 if(!dlg.m_strLogMesage.IsEmpty())
3177 CString logmsg = dlg.m_strLogMesage;
3178 logmsg.Replace(_T("\\\""), _T("\\\\\""));
3179 logmsg.Replace(_T("\""), _T("\\\""));
3180 args += _T(" -m \"") + logmsg + _T("\"");
3182 cmd.Format(_T("git.exe merge%s %s"), (LPCTSTR)args, (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName));
3184 CProgressDlg Prodlg;
3185 Prodlg.m_GitCmd = cmd;
3187 Prodlg.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3189 if (status)
3191 int hasConflicts = g_Git.HasWorkingTreeConflicts();
3192 if (hasConflicts < 0)
3193 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(L"Checking for conflicts failed.", CGit::GIT_CMD_CHECKCONFLICTS), _T("TortoiseGit"), MB_ICONEXCLAMATION);
3194 else if (hasConflicts)
3196 // there are conflict files
3198 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
3200 CString sCmd;
3201 sCmd.Format(_T("/command:commit /path:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
3202 CAppUtils::RunTortoiseGitProc(sCmd);
3206 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSTASHSAVE, [&]{ CAppUtils::StashSave(_T(""), false, false, true, g_Git.FixBranchName(dlg.m_VersionName)); });
3207 return;
3210 if (showStashPop)
3211 postCmdList.emplace_back(IDI_RELOCATE, IDS_MENUSTASHPOP, []{ StashPop(); });
3213 if (dlg.m_bNoCommit)
3215 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUCOMMIT, []
3217 CString sCmd;
3218 sCmd.Format(_T("/command:commit /path:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
3219 CAppUtils::RunTortoiseGitProc(sCmd);
3221 return;
3224 if (dlg.m_bIsBranch && dlg.m_VersionName.Find(L"remotes/") == -1) // do not ask to remove remote branches
3226 postCmdList.emplace_back(IDI_DELETE, IDS_PROC_REMOVEBRANCH, [&]
3228 CString msg;
3229 msg.Format(IDS_PROC_DELETEBRANCHTAG, dlg.m_VersionName);
3230 if (CMessageBox::Show(nullptr, msg, _T("TortoiseGit"), 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
3232 CString cmd, out;
3233 cmd.Format(_T("git.exe branch -D -- %s"), (LPCTSTR)dlg.m_VersionName);
3234 if (g_Git.Run(cmd, &out, CP_UTF8))
3235 MessageBox(nullptr, out, _T("TortoiseGit"), MB_OK);
3239 if (dlg.m_bIsBranch)
3240 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, []{ Push(); });
3242 BOOL hasGitSVN = CTGitPath(g_Git.m_CurrentDir).GetAdminDirMask() & ITEMIS_GITSVN;
3243 if (hasGitSVN)
3244 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSVNDCOMMIT, []{ SVNDCommit(); });
3247 Prodlg.DoModal();
3248 return !Prodlg.m_GitStatus;
3250 return false;
3253 BOOL CAppUtils::MergeAbort()
3255 CMergeAbortDlg dlg;
3256 if (dlg.DoModal() == IDOK)
3257 return Reset(_T("HEAD"), dlg.m_ResetType + 1);
3259 return FALSE;
3262 void CAppUtils::EditNote(GitRevLoglist* rev)
3264 if (!CheckUserData())
3265 return;
3267 CInputDlg dlg;
3268 dlg.m_sHintText = CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES));
3269 dlg.m_sInputText = rev->m_Notes;
3270 dlg.m_sTitle = CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES));
3271 //dlg.m_pProjectProperties = &m_ProjectProperties;
3272 dlg.m_bUseLogWidth = true;
3273 if(dlg.DoModal() == IDOK)
3275 CString cmd,output;
3276 cmd=_T("notes add -f -F \"");
3278 CString tempfile=::GetTempFile();
3279 if (CAppUtils::SaveCommitUnicodeFile(tempfile, dlg.m_sInputText))
3281 CMessageBox::Show(nullptr, IDS_PROC_FAILEDSAVINGNOTES, IDS_APPNAME, MB_OK | MB_ICONERROR);
3282 return;
3284 cmd += tempfile;
3285 cmd += _T("\" ");
3286 cmd += rev->m_CommitHash.ToString();
3290 if (git_run_cmd("notes", CUnicodeUtils::GetMulti(cmd, CP_UTF8).GetBuffer()))
3291 CMessageBox::Show(nullptr, IDS_PROC_FAILEDSAVINGNOTES, IDS_APPNAME, MB_OK | MB_ICONERROR);
3292 else
3293 rev->m_Notes = dlg.m_sInputText;
3294 }catch(...)
3296 CMessageBox::Show(nullptr, IDS_PROC_FAILEDSAVINGNOTES, IDS_APPNAME, MB_OK | MB_ICONERROR);
3298 ::DeleteFile(tempfile);
3303 int CAppUtils::GetMsysgitVersion()
3305 if (g_Git.ms_LastMsysGitVersion)
3306 return g_Git.ms_LastMsysGitVersion;
3308 CString cmd;
3309 CString versiondebug;
3310 CString version;
3312 CRegDWORD regTime = CRegDWORD(_T("Software\\TortoiseGit\\git_file_time"));
3313 CRegDWORD regVersion = CRegDWORD(_T("Software\\TortoiseGit\\git_cached_version"));
3315 CString gitpath = CGit::ms_LastMsysGitDir+_T("\\git.exe");
3317 __int64 time=0;
3318 if (!CGit::GetFileModifyTime(gitpath, &time))
3320 if((DWORD)time == regTime)
3322 g_Git.ms_LastMsysGitVersion = regVersion;
3323 return regVersion;
3327 CString err;
3328 int ver = g_Git.GetGitVersion(&versiondebug, &err);
3329 if (ver < 0)
3331 CMessageBox::Show(nullptr, _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);
3332 return -1;
3336 if (!ver)
3338 CMessageBox::Show(nullptr, _T("Could not parse git.exe version number: \"") + versiondebug + _T("\""), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
3339 return -1;
3343 regTime = time&0xFFFFFFFF;
3344 regVersion = ver;
3345 g_Git.ms_LastMsysGitVersion = ver;
3347 return ver;
3350 void CAppUtils::MarkWindowAsUnpinnable(HWND hWnd)
3352 typedef HRESULT (WINAPI *SHGPSFW) (HWND hwnd,REFIID riid,void** ppv);
3354 CAutoLibrary hShell = AtlLoadSystemLibraryUsingFullPath(_T("Shell32.dll"));
3356 if (hShell.IsValid()) {
3357 SHGPSFW pfnSHGPSFW = (SHGPSFW)::GetProcAddress(hShell, "SHGetPropertyStoreForWindow");
3358 if (pfnSHGPSFW) {
3359 IPropertyStore *pps;
3360 HRESULT hr = pfnSHGPSFW(hWnd, IID_PPV_ARGS(&pps));
3361 if (SUCCEEDED(hr)) {
3362 PROPVARIANT var;
3363 var.vt = VT_BOOL;
3364 var.boolVal = VARIANT_TRUE;
3365 pps->SetValue(PKEY_AppUserModel_PreventPinning, var);
3366 pps->Release();
3372 void CAppUtils::SetWindowTitle(HWND hWnd, const CString& urlorpath, const CString& dialogname)
3374 ASSERT(dialogname.GetLength() < 70);
3375 ASSERT(urlorpath.GetLength() < MAX_PATH);
3376 WCHAR pathbuf[MAX_PATH] = {0};
3378 PathCompactPathEx(pathbuf, urlorpath, 70 - dialogname.GetLength(), 0);
3380 wcscat_s(pathbuf, L" - ");
3381 wcscat_s(pathbuf, dialogname);
3382 wcscat_s(pathbuf, L" - ");
3383 wcscat_s(pathbuf, CString(MAKEINTRESOURCE(IDS_APPNAME)));
3384 SetWindowText(hWnd, pathbuf);
3387 bool CAppUtils::BisectStart(const CString& lastGood, const CString& firstBad, bool bIsMainWnd)
3389 if (!g_Git.CheckCleanWorkTree())
3391 if (CMessageBox::Show(nullptr, IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
3393 CSysProgressDlg sysProgressDlg;
3394 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3395 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3396 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3397 sysProgressDlg.SetShowProgressBar(false);
3398 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3399 sysProgressDlg.ShowModeless((HWND)nullptr, true);
3401 CString cmd, out;
3402 cmd = _T("git.exe stash");
3403 if (g_Git.Run(cmd, &out, CP_UTF8))
3405 sysProgressDlg.Stop();
3406 MessageBox(nullptr, out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
3407 return false;
3409 sysProgressDlg.Stop();
3411 else
3412 return false;
3415 CBisectStartDlg bisectStartDlg;
3417 if (!lastGood.IsEmpty())
3418 bisectStartDlg.m_sLastGood = lastGood;
3419 if (!firstBad.IsEmpty())
3420 bisectStartDlg.m_sFirstBad = firstBad;
3422 if (bisectStartDlg.DoModal() == IDOK)
3424 CProgressDlg progress;
3425 if (bIsMainWnd)
3426 theApp.m_pMainWnd = &progress;
3427 progress.m_GitCmdList.push_back(_T("git.exe bisect start"));
3428 progress.m_GitCmdList.push_back(_T("git.exe bisect good ") + bisectStartDlg.m_LastGoodRevision);
3429 progress.m_GitCmdList.push_back(_T("git.exe bisect bad ") + bisectStartDlg.m_FirstBadRevision);
3431 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3433 if (status)
3434 return;
3436 CTGitPath path(g_Git.m_CurrentDir);
3437 if (path.HasSubmodules())
3439 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
3441 CString sCmd;
3442 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
3443 CAppUtils::RunTortoiseGitProc(sCmd);
3448 INT_PTR ret = progress.DoModal();
3449 return ret == IDOK;
3452 return false;
3455 bool CAppUtils::BisectOperation(const CString& op, const CString& ref, bool bIsMainWnd)
3457 CString cmd = _T("git.exe bisect ") + op;
3459 if (!ref.IsEmpty())
3461 cmd += _T(" ");
3462 cmd += ref;
3465 CProgressDlg progress;
3466 if (bIsMainWnd)
3467 theApp.m_pMainWnd = &progress;
3468 progress.m_GitCmd = cmd;
3470 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3472 if (status)
3473 return;
3475 CTGitPath path = g_Git.m_CurrentDir;
3476 if (path.HasSubmodules())
3478 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
3480 CString sCmd;
3481 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
3482 CAppUtils::RunTortoiseGitProc(sCmd);
3486 if (op != _T("reset"))
3487 postCmdList.emplace_back(IDS_MENUBISECTRESET, []{ CAppUtils::RunTortoiseGitProc(_T("/command:bisect /reset")); });
3490 INT_PTR ret = progress.DoModal();
3491 return ret == IDOK;
3494 int CAppUtils::Git2GetUserPassword(git_cred **out, const char *url, const char *username_from_url, unsigned int /*allowed_types*/, void * /*payload*/)
3496 CUserPassword dlg;
3497 dlg.m_URL = CUnicodeUtils::GetUnicode(url, CP_UTF8);
3498 if (username_from_url)
3499 dlg.m_UserName = CUnicodeUtils::GetUnicode(username_from_url, CP_UTF8);
3501 CStringA username, password;
3502 if (dlg.DoModal() == IDOK)
3504 username = CUnicodeUtils::GetMulti(dlg.m_UserName, CP_UTF8);
3505 password = CUnicodeUtils::GetMulti(dlg.m_Password, CP_UTF8);
3506 return git_cred_userpass_plaintext_new(out, username, password);
3508 giterr_set_str(GITERR_NONE, "User cancelled.");
3509 return GIT_EUSER;
3512 int CAppUtils::Git2CertificateCheck(git_cert* base_cert, int /*valid*/, const char* host, void* /*payload*/)
3514 if (base_cert->cert_type == GIT_CERT_X509)
3516 git_cert_x509* cert = (git_cert_x509*)base_cert;
3518 if (last_accepted_cert.cmp(cert))
3519 return 0;
3521 PCCERT_CONTEXT pServerCert = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, (BYTE*)cert->data, (DWORD)cert->len);
3522 SCOPE_EXIT { CertFreeCertificateContext(pServerCert); };
3524 DWORD verificationError = VerifyServerCertificate(pServerCert, CUnicodeUtils::GetUnicode(host).GetBuffer(), 0);
3525 if (!verificationError)
3527 last_accepted_cert.set(cert);
3528 return 0;
3531 CString servernameInCert;
3532 CertGetNameString(pServerCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nullptr, CStrBuf(servernameInCert, 128), 128);
3534 CString issuer;
3535 CertGetNameString(pServerCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, CERT_NAME_ISSUER_FLAG, nullptr, CStrBuf(issuer, 128), 128);
3537 CCheckCertificateDlg dlg;
3538 dlg.cert = cert;
3539 dlg.m_sCertificateCN = servernameInCert;
3540 dlg.m_sCertificateIssuer = issuer;
3541 dlg.m_sHostname = CUnicodeUtils::GetUnicode(host);
3542 dlg.m_sError = CFormatMessageWrapper(verificationError);
3543 if (dlg.DoModal() == IDOK)
3545 last_accepted_cert.set(cert);
3546 return 0;
3549 return GIT_ECERTIFICATE;
3552 int CAppUtils::ExploreTo(HWND hwnd, CString path)
3554 if (PathFileExists(path))
3556 HRESULT ret = -1;
3557 ITEMIDLIST __unaligned * pidl = ILCreateFromPath(path);
3558 if (pidl)
3560 ret = SHOpenFolderAndSelectItems(pidl, 0, 0, 0);
3561 ILFree(pidl);
3563 return SUCCEEDED(ret) ? 0 : -1;
3565 // if filepath does not exist any more, navigate to closest matching folder
3568 int pos = path.ReverseFind(_T('\\'));
3569 if (pos <= 3)
3570 break;
3571 path = path.Left(pos);
3572 } while (!PathFileExists(path));
3573 return (INT_PTR)ShellExecute(hwnd, _T("explore"), path, nullptr, nullptr, SW_SHOW) > 32 ? 0 : -1;
3576 int CAppUtils::ResolveConflict(CTGitPath& path, resolve_with resolveWith)
3578 bool b_local = false, b_remote = false;
3579 BYTE_VECTOR vector;
3581 CString cmd;
3582 cmd.Format(_T("git.exe ls-files -u -t -z -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3583 if (g_Git.Run(cmd, &vector))
3585 CMessageBox::Show(nullptr, _T("git ls-files failed!"), _T("TortoiseGit"), MB_OK);
3586 return -1;
3589 CTGitPathList list;
3590 if (list.ParserFromLsFile(vector))
3592 CMessageBox::Show(nullptr, _T("Parse ls-files failed!"), _T("TortoiseGit"), MB_OK);
3593 return -1;
3596 if (list.IsEmpty())
3597 return 0;
3598 for (int i = 0; i < list.GetCount(); ++i)
3600 if (list[i].m_Stage == 2)
3601 b_local = true;
3602 if (list[i].m_Stage == 3)
3603 b_remote = true;
3607 CBlockCacheForPath block(g_Git.m_CurrentDir);
3608 if (path.IsDirectory()) // is submodule conflict
3610 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.");
3611 if (b_local && b_remote)
3613 if (!path.HasAdminDir()) // check if submodule is initialized
3615 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).");
3616 MessageBox(nullptr, err, _T("TortoiseGit"), MB_ICONERROR);
3617 return -1;
3619 CGit subgit;
3620 subgit.m_CurrentDir = g_Git.CombinePath(path);
3621 CGitHash submoduleHead;
3622 if (subgit.GetHash(submoduleHead, _T("HEAD")))
3624 MessageBox(nullptr, err, _T("TortoiseGit"), MB_ICONERROR);
3625 return -1;
3627 CString baseHash, localHash, remoteHash;
3628 ParseHashesFromLsFile(vector, baseHash, localHash, remoteHash);
3629 if (resolveWith == RESOLVE_WITH_THEIRS && submoduleHead.ToString() != remoteHash)
3631 CString origPath = g_Git.m_CurrentDir;
3632 g_Git.m_CurrentDir = g_Git.CombinePath(path);
3633 if (!GitReset(&remoteHash))
3635 g_Git.m_CurrentDir = origPath;
3636 return -1;
3638 g_Git.m_CurrentDir = origPath;
3640 else if (resolveWith == RESOLVE_WITH_MINE && submoduleHead.ToString() != localHash)
3642 CString origPath = g_Git.m_CurrentDir;
3643 g_Git.m_CurrentDir = g_Git.CombinePath(path);
3644 if (!GitReset(&localHash))
3646 g_Git.m_CurrentDir = origPath;
3647 return -1;
3649 g_Git.m_CurrentDir = origPath;
3652 else
3654 MessageBox(nullptr, err, _T("TortoiseGit"), MB_ICONERROR);
3655 return -1;
3659 if (resolveWith == RESOLVE_WITH_THEIRS)
3661 CString gitcmd, output;
3662 if (b_local && b_remote)
3663 gitcmd.Format(_T("git.exe checkout-index -f --stage=3 -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3664 else if (b_remote)
3665 gitcmd.Format(_T("git.exe add -f -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3666 else if (b_local)
3667 gitcmd.Format(_T("git.exe rm -f -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3668 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3670 CMessageBox::Show(nullptr, output, _T("TortoiseGit"), MB_ICONERROR);
3671 return -1;
3674 else if (resolveWith == RESOLVE_WITH_MINE)
3676 CString gitcmd, output;
3677 if (b_local && b_remote)
3678 gitcmd.Format(_T("git.exe checkout-index -f --stage=2 -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3679 else if (b_local)
3680 gitcmd.Format(_T("git.exe add -f -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3681 else if (b_remote)
3682 gitcmd.Format(_T("git.exe rm -f -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3683 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3685 CMessageBox::Show(nullptr, output, _T("TortoiseGit"), MB_ICONERROR);
3686 return -1;
3690 if (b_local && b_remote && path.m_Action & CTGitPath::LOGACTIONS_UNMERGED)
3692 CString gitcmd, output;
3693 gitcmd.Format(_T("git.exe add -f -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3694 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3695 CMessageBox::Show(nullptr, output, _T("TortoiseGit"), MB_ICONERROR);
3696 else
3698 path.m_Action |= CTGitPath::LOGACTIONS_MODIFIED;
3699 path.m_Action &= ~CTGitPath::LOGACTIONS_UNMERGED;
3703 RemoveTempMergeFile(path);
3704 return 0;
3707 bool CAppUtils::ShellOpen(const CString& file, HWND hwnd /*= nullptr */)
3709 if ((INT_PTR)ShellExecute(hwnd, nullptr, file, nullptr, nullptr, SW_SHOW) > HINSTANCE_ERROR)
3710 return true;
3712 return ShowOpenWithDialog(file, hwnd);
3715 bool CAppUtils::ShowOpenWithDialog(const CString& file, HWND hwnd /*= nullptr */)
3717 OPENASINFO oi = { 0 };
3718 oi.pcszFile = file;
3719 oi.oaifInFlags = OAIF_EXEC;
3720 return SUCCEEDED(SHOpenWithDialog(hwnd, &oi));