Make using alternative merge tool work by pressing the shift key
[TortoiseGit.git] / src / TortoiseProc / AppUtils.cpp
blobbe1e6f7f1e6a1020b217bbb8fa55bdf2ff0ce75e
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(bool bAlternative,
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 (bAlternative && !com.IsEmpty())
275 if (com.Left(1).Compare(L"#") == 0)
276 com.Delete(0);
277 else
278 com.Empty();
281 if (com.IsEmpty()||(com.Left(1).Compare(_T("#"))==0))
283 // Maybe we should use TortoiseIDiff?
284 if ((ext == _T(".jpg")) || (ext == _T(".jpeg")) ||
285 (ext == _T(".bmp")) || (ext == _T(".gif")) ||
286 (ext == _T(".png")) || (ext == _T(".ico")) ||
287 (ext == _T(".tif")) || (ext == _T(".tiff")) ||
288 (ext == _T(".dib")) || (ext == _T(".emf")) ||
289 (ext == _T(".cur")))
291 com = CPathUtils::GetAppDirectory() + _T("TortoiseGitIDiff.exe");
292 com = _T("\"") + com + _T("\"");
293 com = com + _T(" /base:%base /theirs:%theirs /mine:%mine /result:%merged");
294 com = com + _T(" /basetitle:%bname /theirstitle:%tname /minetitle:%yname");
295 if (resolveMsgHwnd)
296 com.AppendFormat(L" /resolvemsghwnd:%I64d", (__int64)resolveMsgHwnd);
298 else
300 // use TortoiseGitMerge
301 bInternal = true;
302 com = CPathUtils::GetAppDirectory() + _T("TortoiseGitMerge.exe");
303 com = _T("\"") + com + _T("\"");
304 com = com + _T(" /base:%base /theirs:%theirs /mine:%mine /merged:%merged");
305 com = com + _T(" /basename:%bname /theirsname:%tname /minename:%yname /mergedname:%mname");
306 com += _T(" /saverequired");
307 if (resolveMsgHwnd)
308 com.AppendFormat(L" /resolvemsghwnd:%I64d", (__int64)resolveMsgHwnd);
309 if (bDeleteBaseTheirsMineOnClose)
310 com += _T(" /deletebasetheirsmineonclose");
312 if (!g_sGroupingUUID.IsEmpty())
314 com += L" /groupuuid:\"";
315 com += g_sGroupingUUID;
316 com += L"\"";
319 // check if the params are set. If not, just add the files to the command line
320 if ((com.Find(_T("%merged"))<0)&&(com.Find(_T("%base"))<0)&&(com.Find(_T("%theirs"))<0)&&(com.Find(_T("%mine"))<0))
322 com += _T(" \"")+basefile.GetWinPathString()+_T("\"");
323 com += _T(" \"")+theirfile.GetWinPathString()+_T("\"");
324 com += _T(" \"")+yourfile.GetWinPathString()+_T("\"");
325 com += _T(" \"")+mergedfile.GetWinPathString()+_T("\"");
327 if (basefile.IsEmpty())
329 com.Replace(_T("/base:%base"), _T(""));
330 com.Replace(_T("%base"), _T(""));
332 else
333 com.Replace(_T("%base"), _T("\"") + basefile.GetWinPathString() + _T("\""));
334 if (theirfile.IsEmpty())
336 com.Replace(_T("/theirs:%theirs"), _T(""));
337 com.Replace(_T("%theirs"), _T(""));
339 else
340 com.Replace(_T("%theirs"), _T("\"") + theirfile.GetWinPathString() + _T("\""));
341 if (yourfile.IsEmpty())
343 com.Replace(_T("/mine:%mine"), _T(""));
344 com.Replace(_T("%mine"), _T(""));
346 else
347 com.Replace(_T("%mine"), _T("\"") + yourfile.GetWinPathString() + _T("\""));
348 if (mergedfile.IsEmpty())
350 com.Replace(_T("/merged:%merged"), _T(""));
351 com.Replace(_T("%merged"), _T(""));
353 else
354 com.Replace(_T("%merged"), _T("\"") + mergedfile.GetWinPathString() + _T("\""));
355 if (basename.IsEmpty())
357 if (basefile.IsEmpty())
359 com.Replace(_T("/basename:%bname"), _T(""));
360 com.Replace(_T("%bname"), _T(""));
362 else
363 com.Replace(_T("%bname"), _T("\"") + basefile.GetUIFileOrDirectoryName() + _T("\""));
365 else
366 com.Replace(_T("%bname"), _T("\"") + basename + _T("\""));
367 if (theirname.IsEmpty())
369 if (theirfile.IsEmpty())
371 com.Replace(_T("/theirsname:%tname"), _T(""));
372 com.Replace(_T("%tname"), _T(""));
374 else
375 com.Replace(_T("%tname"), _T("\"") + theirfile.GetUIFileOrDirectoryName() + _T("\""));
377 else
378 com.Replace(_T("%tname"), _T("\"") + theirname + _T("\""));
379 if (yourname.IsEmpty())
381 if (yourfile.IsEmpty())
383 com.Replace(_T("/minename:%yname"), _T(""));
384 com.Replace(_T("%yname"), _T(""));
386 else
387 com.Replace(_T("%yname"), _T("\"") + yourfile.GetUIFileOrDirectoryName() + _T("\""));
389 else
390 com.Replace(_T("%yname"), _T("\"") + yourname + _T("\""));
391 if (mergedname.IsEmpty())
393 if (mergedfile.IsEmpty())
395 com.Replace(_T("/mergedname:%mname"), _T(""));
396 com.Replace(_T("%mname"), _T(""));
398 else
399 com.Replace(_T("%mname"), _T("\"") + mergedfile.GetUIFileOrDirectoryName() + _T("\""));
401 else
402 com.Replace(_T("%mname"), _T("\"") + mergedname + _T("\""));
404 if ((bReadOnly)&&(bInternal))
405 com += _T(" /readonly");
407 if(!LaunchApplication(com, IDS_ERR_EXTMERGESTART, false))
409 return FALSE;
412 return TRUE;
415 BOOL CAppUtils::StartExtPatch(const CTGitPath& patchfile, const CTGitPath& dir, const CString& sOriginalDescription, const CString& sPatchedDescription, BOOL bReversed, BOOL bWait)
417 CString viewer;
418 // use TortoiseGitMerge
419 viewer = CPathUtils::GetAppDirectory();
420 viewer += _T("TortoiseGitMerge.exe");
422 viewer = _T("\"") + viewer + _T("\"");
423 viewer = viewer + _T(" /diff:\"") + patchfile.GetWinPathString() + _T("\"");
424 viewer = viewer + _T(" /patchpath:\"") + dir.GetWinPathString() + _T("\"");
425 if (bReversed)
426 viewer += _T(" /reversedpatch");
427 if (!sOriginalDescription.IsEmpty())
428 viewer = viewer + _T(" /patchoriginal:\"") + sOriginalDescription + _T("\"");
429 if (!sPatchedDescription.IsEmpty())
430 viewer = viewer + _T(" /patchpatched:\"") + sPatchedDescription + _T("\"");
431 if (!g_sGroupingUUID.IsEmpty())
433 viewer += L" /groupuuid:\"";
434 viewer += g_sGroupingUUID;
435 viewer += L"\"";
437 if(!LaunchApplication(viewer, IDS_ERR_DIFFVIEWSTART, !!bWait))
438 return FALSE;
439 return TRUE;
442 CString CAppUtils::PickDiffTool(const CTGitPath& file1, const CTGitPath& file2)
444 CString difftool = CRegString(_T("Software\\TortoiseGit\\DiffTools\\") + file2.GetFilename().MakeLower());
445 if (!difftool.IsEmpty())
446 return difftool;
447 difftool = CRegString(_T("Software\\TortoiseGit\\DiffTools\\") + file1.GetFilename().MakeLower());
448 if (!difftool.IsEmpty())
449 return difftool;
451 // Is there an extension specific diff tool?
452 CString ext = file2.GetFileExtension().MakeLower();
453 if (!ext.IsEmpty())
455 difftool = CRegString(_T("Software\\TortoiseGit\\DiffTools\\") + ext);
456 if (!difftool.IsEmpty())
457 return difftool;
458 // Maybe we should use TortoiseIDiff?
459 if ((ext == _T(".jpg")) || (ext == _T(".jpeg")) ||
460 (ext == _T(".bmp")) || (ext == _T(".gif")) ||
461 (ext == _T(".png")) || (ext == _T(".ico")) ||
462 (ext == _T(".tif")) || (ext == _T(".tiff")) ||
463 (ext == _T(".dib")) || (ext == _T(".emf")) ||
464 (ext == _T(".cur")))
466 return
467 _T("\"") + CPathUtils::GetAppDirectory() + _T("TortoiseGitIDiff.exe") + _T("\"") +
468 _T(" /left:%base /right:%mine /lefttitle:%bname /righttitle:%yname") +
469 L" /groupuuid:\"" + g_sGroupingUUID + L"\"";
473 // Finally, pick a generic external diff tool
474 difftool = CRegString(_T("Software\\TortoiseGit\\Diff"));
475 return difftool;
478 bool CAppUtils::StartExtDiff(
479 const CString& file1, const CString& file2,
480 const CString& sName1, const CString& sName2,
481 const CString& originalFile1, const CString& originalFile2,
482 const git_revnum_t& hash1, const git_revnum_t& hash2,
483 const DiffFlags& flags, int jumpToLine)
485 CString viewer;
487 CRegDWORD blamediff(_T("Software\\TortoiseGit\\DiffBlamesWithTortoiseMerge"), FALSE);
488 if (!flags.bBlame || !(DWORD)blamediff)
490 viewer = PickDiffTool(file1, file2);
491 // If registry entry for a diff program is commented out, use TortoiseGitMerge.
492 bool bCommentedOut = viewer.Left(1) == _T("#");
493 if (flags.bAlternativeTool)
495 // Invert external vs. internal diff tool selection.
496 if (bCommentedOut)
497 viewer.Delete(0); // uncomment
498 else
499 viewer.Empty();
501 else if (bCommentedOut)
502 viewer.Empty();
505 bool bInternal = viewer.IsEmpty();
506 if (bInternal)
508 viewer =
509 _T("\"") + CPathUtils::GetAppDirectory() + _T("TortoiseGitMerge.exe") + _T("\"") +
510 _T(" /base:%base /mine:%mine /basename:%bname /minename:%yname") +
511 _T(" /basereflectedname:%bpath /minereflectedname:%ypath");
512 if (!g_sGroupingUUID.IsEmpty())
514 viewer += L" /groupuuid:\"";
515 viewer += g_sGroupingUUID;
516 viewer += L"\"";
518 if (flags.bBlame)
519 viewer += _T(" /blame");
521 // check if the params are set. If not, just add the files to the command line
522 if ((viewer.Find(_T("%base"))<0)&&(viewer.Find(_T("%mine"))<0))
524 viewer += _T(" \"")+file1+_T("\"");
525 viewer += _T(" \"")+file2+_T("\"");
527 if (viewer.Find(_T("%base")) >= 0)
528 viewer.Replace(_T("%base"), _T("\"")+file1+_T("\""));
529 if (viewer.Find(_T("%mine")) >= 0)
530 viewer.Replace(_T("%mine"), _T("\"")+file2+_T("\""));
532 if (sName1.IsEmpty())
533 viewer.Replace(_T("%bname"), _T("\"") + file1 + _T("\""));
534 else
535 viewer.Replace(_T("%bname"), _T("\"") + sName1 + _T("\""));
537 if (sName2.IsEmpty())
538 viewer.Replace(_T("%yname"), _T("\"") + file2 + _T("\""));
539 else
540 viewer.Replace(_T("%yname"), _T("\"") + sName2 + _T("\""));
542 viewer.Replace(_T("%bpath"), _T("\"") + originalFile1 + _T("\""));
543 viewer.Replace(_T("%ypath"), _T("\"") + originalFile2 + _T("\""));
545 viewer.Replace(_T("%brev"), _T("\"") + hash1 + _T("\""));
546 viewer.Replace(_T("%yrev"), _T("\"") + hash2 + _T("\""));
548 if (flags.bReadOnly && bInternal)
549 viewer += _T(" /readonly");
551 if (jumpToLine > 0)
552 viewer.AppendFormat(L" /line:%d", jumpToLine);
554 return LaunchApplication(viewer, IDS_ERR_EXTDIFFSTART, flags.bWait);
557 BOOL CAppUtils::StartUnifiedDiffViewer(const CString& patchfile, const CString& title, BOOL bWait, bool bAlternativeTool)
559 CString viewer;
560 CRegString v = CRegString(_T("Software\\TortoiseGit\\DiffViewer"));
561 viewer = v;
563 // If registry entry for a diff program is commented out, use TortoiseGitMerge.
564 bool bCommentedOut = viewer.Left(1) == _T("#");
565 if (bAlternativeTool)
567 // Invert external vs. internal diff tool selection.
568 if (bCommentedOut)
569 viewer.Delete(0); // uncomment
570 else
571 viewer.Empty();
573 else if (bCommentedOut)
574 viewer.Empty();
576 if (viewer.IsEmpty())
578 // use TortoiseGitUDiff
579 viewer = CPathUtils::GetAppDirectory();
580 viewer += _T("TortoiseGitUDiff.exe");
581 // enquote the path to TortoiseGitUDiff
582 viewer = _T("\"") + viewer + _T("\"");
583 // add the params
584 viewer = viewer + _T(" /patchfile:%1 /title:\"%title\"");
585 if (!g_sGroupingUUID.IsEmpty())
587 viewer += L" /groupuuid:\"";
588 viewer += g_sGroupingUUID;
589 viewer += L"\"";
592 if (viewer.Find(_T("%1"))>=0)
594 if (viewer.Find(_T("\"%1\"")) >= 0)
595 viewer.Replace(_T("%1"), patchfile);
596 else
597 viewer.Replace(_T("%1"), _T("\"") + patchfile + _T("\""));
599 else
600 viewer += _T(" \"") + patchfile + _T("\"");
601 if (viewer.Find(_T("%title")) >= 0)
602 viewer.Replace(_T("%title"), title);
604 if(!LaunchApplication(viewer, IDS_ERR_DIFFVIEWSTART, !!bWait))
605 return FALSE;
606 return TRUE;
609 BOOL CAppUtils::StartTextViewer(CString file)
611 CString viewer;
612 CRegString txt = CRegString(_T(".txt\\"), _T(""), FALSE, HKEY_CLASSES_ROOT);
613 viewer = txt;
614 viewer = viewer + _T("\\Shell\\Open\\Command\\");
615 CRegString txtexe = CRegString(viewer, _T(""), FALSE, HKEY_CLASSES_ROOT);
616 viewer = txtexe;
618 DWORD len = ExpandEnvironmentStrings(viewer, nullptr, 0);
619 auto buf = std::make_unique<TCHAR[]>(len + 1);
620 ExpandEnvironmentStrings(viewer, buf.get(), len);
621 viewer = buf.get();
622 len = ExpandEnvironmentStrings(file, nullptr, 0);
623 auto buf2 = std::make_unique<TCHAR[]>(len + 1);
624 ExpandEnvironmentStrings(file, buf2.get(), len);
625 file = buf2.get();
626 file = _T("\"")+file+_T("\"");
627 if (viewer.IsEmpty())
628 return CAppUtils::ShowOpenWithDialog(file) ? TRUE : FALSE;
629 if (viewer.Find(_T("\"%1\"")) >= 0)
630 viewer.Replace(_T("\"%1\""), file);
631 else if (viewer.Find(_T("%1")) >= 0)
632 viewer.Replace(_T("%1"), file);
633 else
634 viewer += _T(" ");
635 viewer += file;
637 if(!LaunchApplication(viewer, IDS_ERR_TEXTVIEWSTART, false))
638 return FALSE;
639 return TRUE;
642 BOOL CAppUtils::CheckForEmptyDiff(const CTGitPath& sDiffPath)
644 DWORD length = 0;
645 CAutoFile hFile = ::CreateFile(sDiffPath.GetWinPath(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
646 if (!hFile)
647 return TRUE;
648 length = ::GetFileSize(hFile, nullptr);
649 if (length < 4)
650 return TRUE;
651 return FALSE;
654 void CAppUtils::CreateFontForLogs(CFont& fontToCreate)
656 LOGFONT logFont;
657 HDC hScreenDC = ::GetDC(nullptr);
658 logFont.lfHeight = -MulDiv((DWORD)CRegDWORD(_T("Software\\TortoiseGit\\LogFontSize"), 8), GetDeviceCaps(hScreenDC, LOGPIXELSY), 72);
659 ::ReleaseDC(nullptr, hScreenDC);
660 logFont.lfWidth = 0;
661 logFont.lfEscapement = 0;
662 logFont.lfOrientation = 0;
663 logFont.lfWeight = FW_NORMAL;
664 logFont.lfItalic = 0;
665 logFont.lfUnderline = 0;
666 logFont.lfStrikeOut = 0;
667 logFont.lfCharSet = DEFAULT_CHARSET;
668 logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
669 logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
670 logFont.lfQuality = DRAFT_QUALITY;
671 logFont.lfPitchAndFamily = FF_DONTCARE | FIXED_PITCH;
672 _tcscpy_s(logFont.lfFaceName, 32, (LPCTSTR)(CString)CRegString(_T("Software\\TortoiseGit\\LogFontName"), _T("Courier New")));
673 VERIFY(fontToCreate.CreateFontIndirect(&logFont));
676 bool CAppUtils::LaunchPAgent(const CString* keyfile, const CString* pRemote)
678 CString key,remote;
679 CString cmd,out;
680 if (!pRemote)
681 remote=_T("origin");
682 else
683 remote=*pRemote;
685 if (!keyfile)
687 cmd.Format(_T("remote.%s.puttykeyfile"), (LPCTSTR)remote);
688 key = g_Git.GetConfigValue(cmd);
690 else
691 key=*keyfile;
693 if(key.IsEmpty())
694 return false;
696 CString proc=CPathUtils::GetAppDirectory();
697 proc += _T("pageant.exe \"");
698 proc += key;
699 proc += _T("\"");
701 CString tempfile = GetTempFile();
702 ::DeleteFile(tempfile);
704 proc += _T(" -c \"");
705 proc += CPathUtils::GetAppDirectory();
706 proc += _T("tgittouch.exe\"");
707 proc += _T(" \"");
708 proc += tempfile;
709 proc += _T("\"");
711 CString appDir = CPathUtils::GetAppDirectory();
712 bool b = LaunchApplication(proc, IDS_ERR_PAGEANT, true, &appDir);
713 if(!b)
714 return b;
716 int i=0;
717 while(!::PathFileExists(tempfile))
719 Sleep(100);
720 ++i;
721 if(i>10*60*5)
722 break; //timeout 5 minutes
725 if( i== 10*60*5)
726 CMessageBox::Show(nullptr, IDS_ERR_PAEGENTTIMEOUT, IDS_APPNAME, MB_OK | MB_ICONERROR);
727 ::DeleteFile(tempfile);
728 return true;
730 bool CAppUtils::LaunchAlternativeEditor(const CString& filename, bool uac)
732 CString editTool = CRegString(_T("Software\\TortoiseGit\\AlternativeEditor"));
733 if (editTool.IsEmpty() || (editTool.Left(1).Compare(_T("#"))==0)) {
734 editTool = CPathUtils::GetAppDirectory() + _T("notepad2.exe");
737 CString sCmd;
738 sCmd.Format(_T("\"%s\" \"%s\""), (LPCTSTR)editTool, (LPCTSTR)filename);
740 LaunchApplication(sCmd, 0, false, nullptr, uac);
741 return true;
743 bool CAppUtils::LaunchRemoteSetting()
745 CTGitPath path(g_Git.m_CurrentDir);
746 CSettings dlg(IDS_PROC_SETTINGS_TITLE, &path);
747 dlg.SetTreeViewMode(TRUE, TRUE, TRUE);
748 dlg.SetTreeWidth(220);
749 dlg.m_DefaultPage = _T("gitremote");
751 dlg.DoModal();
752 dlg.HandleRestart();
753 return true;
756 * Launch the external blame viewer
758 bool CAppUtils::LaunchTortoiseBlame(const CString& sBlameFile, const CString& Rev, const CString& sParams)
760 CString viewer = _T("\"") + CPathUtils::GetAppDirectory();
761 viewer += _T("TortoiseGitBlame.exe");
762 viewer += _T("\" \"") + sBlameFile + _T("\"");
763 //viewer += _T(" \"") + sLogFile + _T("\"");
764 //viewer += _T(" \"") + sOriginalFile + _T("\"");
765 if(!Rev.IsEmpty() && Rev != GIT_REV_ZERO)
766 viewer += CString(_T(" /rev:"))+Rev;
767 if (!g_sGroupingUUID.IsEmpty())
769 viewer += L" /groupuuid:\"";
770 viewer += g_sGroupingUUID;
771 viewer += L"\"";
773 viewer += _T(" ")+sParams;
775 return LaunchApplication(viewer, IDS_ERR_TGITBLAME, false);
778 bool CAppUtils::FormatTextInRichEditControl(CWnd * pWnd)
780 CString sText;
781 if (!pWnd)
782 return false;
783 bool bStyled = false;
784 pWnd->GetWindowText(sText);
785 // the rich edit control doesn't count the CR char!
786 // to be exact: CRLF is treated as one char.
787 sText.Remove(_T('\r'));
789 // style each line separately
790 int offset = 0;
791 int nNewlinePos;
794 nNewlinePos = sText.Find('\n', offset);
795 CString sLine = nNewlinePos >= 0 ? sText.Mid(offset, nNewlinePos - offset) : sText.Mid(offset);
797 int start = 0;
798 int end = 0;
799 while (FindStyleChars(sLine, '*', start, end))
801 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
802 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
803 SetCharFormat(pWnd, CFM_BOLD, CFE_BOLD);
804 bStyled = true;
805 start = end;
807 start = 0;
808 end = 0;
809 while (FindStyleChars(sLine, '^', start, end))
811 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
812 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
813 SetCharFormat(pWnd, CFM_ITALIC, CFE_ITALIC);
814 bStyled = true;
815 start = end;
817 start = 0;
818 end = 0;
819 while (FindStyleChars(sLine, '_', start, end))
821 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
822 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
823 SetCharFormat(pWnd, CFM_UNDERLINE, CFE_UNDERLINE);
824 bStyled = true;
825 start = end;
827 offset = nNewlinePos+1;
828 } while(nNewlinePos>=0);
829 return bStyled;
832 bool CAppUtils::FindStyleChars(const CString& sText, TCHAR stylechar, int& start, int& end)
834 int i=start;
835 int last = sText.GetLength() - 1;
836 bool bFoundMarker = false;
837 TCHAR c = i == 0 ? _T('\0') : sText[i - 1];
838 TCHAR nextChar = i >= last ? _T('\0') : sText[i + 1];
840 // find a starting marker
841 while (i < last)
843 TCHAR prevChar = c;
844 c = nextChar;
845 nextChar = sText[i + 1];
847 // IsCharAlphaNumeric can be somewhat expensive.
848 // Long lines of "*****" or "----" will be pre-empted efficiently
849 // by the (c != nextChar) condition.
851 if ((c == stylechar) && (c != nextChar))
853 if (IsCharAlphaNumeric(nextChar) && !IsCharAlphaNumeric(prevChar))
855 start = ++i;
856 bFoundMarker = true;
857 break;
860 ++i;
862 if (!bFoundMarker)
863 return false;
865 // find ending marker
866 // c == sText[i - 1]
868 bFoundMarker = false;
869 while (i <= last)
871 TCHAR prevChar = c;
872 c = sText[i];
873 if (c == stylechar)
875 if ((i == last) || (!IsCharAlphaNumeric(sText[i + 1]) && IsCharAlphaNumeric(prevChar)))
877 end = i;
878 ++i;
879 bFoundMarker = true;
880 break;
883 ++i;
885 return bFoundMarker;
888 // from CSciEdit
889 namespace {
890 bool IsValidURLChar(wchar_t ch)
892 return iswalnum(ch) ||
893 ch == L'_' || ch == L'/' || ch == L';' || ch == L'?' || ch == L'&' || ch == L'=' ||
894 ch == L'%' || ch == L':' || ch == L'.' || ch == L'#' || ch == L'-' || ch == L'+' ||
895 ch == L'|' || ch == L'>' || ch == L'<';
898 bool IsUrl(const CString& sText)
900 if (!PathIsURLW(sText))
901 return false;
902 for (const CString& prefix : { L"http://", L"https://", L"git://", L"ftp://", L"file://", L"mailto:" })
904 if (sText.Find(prefix) == 0 && sText.GetLength() != prefix.GetLength())
905 return true;
907 return false;
911 BOOL CAppUtils::StyleURLs(const CString& msg, CWnd* pWnd)
913 std::vector<CHARRANGE> positions = FindURLMatches(msg);
914 CAppUtils::SetCharFormat(pWnd, CFM_LINK, CFE_LINK, positions);
916 return positions.empty() ? FALSE : TRUE;
920 * implements URL searching with the same logic as CSciEdit::StyleURLs
922 std::vector<CHARRANGE> CAppUtils::FindURLMatches(const CString& msg)
924 std::vector<CHARRANGE> result;
926 int len = msg.GetLength();
927 int starturl = -1;
929 for (int i = 0; i <= msg.GetLength(); ++i)
931 if ((i < len) && IsValidURLChar(msg[i]))
933 if (starturl < 0)
934 starturl = i;
936 else
938 if (starturl >= 0)
940 bool strip = true;
941 if (msg[starturl] == '<' && i < len) // try to detect and do not strip URLs put within <>
943 while (starturl <= i && msg[starturl] == '<') // strip leading '<'
944 ++starturl;
945 strip = false;
946 i = starturl;
947 while (i < len && msg[i] != '\r' && msg[i] != '\n' && msg[i] != '>') // find first '>' or new line after resetting i to start position
948 ++i;
950 if (!IsUrl(msg.Mid(starturl, i - starturl)))
952 starturl = -1;
953 continue;
956 int skipTrailing = 0;
957 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] == '<'))
958 ++skipTrailing;
960 CHARRANGE range = { starturl, i - skipTrailing };
961 result.push_back(range);
963 starturl = -1;
967 return result;
970 bool CAppUtils::StartShowUnifiedDiff(HWND hWnd, const CTGitPath& url1, const git_revnum_t& rev1,
971 const CTGitPath& /*url2*/, const git_revnum_t& rev2,
972 //const GitRev& peg /* = GitRev */, const GitRev& headpeg /* = GitRev */,
973 bool bAlternateDiff /* = false */, bool /*bIgnoreAncestry*/ /* = false */,
974 bool /* blame = false */,
975 bool bMerge,
976 bool bCombine,
977 bool bNoPrefix)
979 int diffContext = g_Git.GetConfigValueInt32(L"diff.context", -1);
980 CString tempfile=GetTempFile();
981 if (g_Git.GetUnifiedDiff(url1, rev1, rev2, tempfile, bMerge, bCombine, diffContext, bNoPrefix))
983 CMessageBox::Show(hWnd, g_Git.GetGitLastErr(_T("Could not get unified diff."), CGit::GIT_CMD_DIFF), _T("TortoiseGit"), MB_OK);
984 return false;
986 CAppUtils::StartUnifiedDiffViewer(tempfile, rev1 + L":" + rev2, FALSE, bAlternateDiff);
988 #if 0
989 CString sCmd;
990 sCmd.Format(_T("%s /command:showcompare /unified"),
991 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseGitProc.exe")));
992 sCmd += _T(" /url1:\"") + url1.GetGitPathString() + _T("\"");
993 if (rev1.IsValid())
994 sCmd += _T(" /revision1:") + rev1.ToString();
995 sCmd += _T(" /url2:\"") + url2.GetGitPathString() + _T("\"");
996 if (rev2.IsValid())
997 sCmd += _T(" /revision2:") + rev2.ToString();
998 if (peg.IsValid())
999 sCmd += _T(" /pegrevision:") + peg.ToString();
1000 if (headpeg.IsValid())
1001 sCmd += _T(" /headpegrevision:") + headpeg.ToString();
1003 if (bAlternateDiff)
1004 sCmd += _T(" /alternatediff");
1006 if (bIgnoreAncestry)
1007 sCmd += _T(" /ignoreancestry");
1009 if (hWnd)
1011 sCmd += _T(" /hwnd:");
1012 TCHAR buf[30];
1013 _stprintf_s(buf, 30, _T("%p"), (void*)hWnd);
1014 sCmd += buf;
1017 return CAppUtils::LaunchApplication(sCmd, 0, false);
1018 #endif
1019 return TRUE;
1022 bool CAppUtils::SetupDiffScripts(bool force, const CString& type)
1024 CString scriptsdir = CPathUtils::GetAppParentDirectory();
1025 scriptsdir += _T("Diff-Scripts");
1026 CSimpleFileFind files(scriptsdir);
1027 while (files.FindNextFileNoDirectories())
1029 CString file = files.GetFilePath();
1030 CString filename = files.GetFileName();
1031 CString ext = file.Mid(file.ReverseFind('-') + 1);
1032 ext = _T(".") + ext.Left(ext.ReverseFind('.'));
1033 std::set<CString> extensions;
1034 extensions.insert(ext);
1035 CString kind;
1036 if (file.Right(3).CompareNoCase(_T("vbs"))==0)
1037 kind = _T(" //E:vbscript");
1038 if (file.Right(2).CompareNoCase(_T("js"))==0)
1039 kind = _T(" //E:javascript");
1040 // open the file, read the first line and find possible extensions
1041 // this script can handle
1044 CStdioFile f(file, CFile::modeRead | CFile::shareDenyNone);
1045 CString extline;
1046 if (f.ReadString(extline))
1048 if ((extline.GetLength() > 15 ) &&
1049 ((extline.Left(15).Compare(_T("// extensions: ")) == 0) ||
1050 (extline.Left(14).Compare(_T("' extensions: ")) == 0)))
1052 if (extline[0] == '/')
1053 extline = extline.Mid(15);
1054 else
1055 extline = extline.Mid(14);
1056 CString sToken;
1057 int curPos = 0;
1058 sToken = extline.Tokenize(_T(";"), curPos);
1059 while (!sToken.IsEmpty())
1061 if (!sToken.IsEmpty())
1063 if (sToken[0] != '.')
1064 sToken = _T(".") + sToken;
1065 extensions.insert(sToken);
1067 sToken = extline.Tokenize(_T(";"), curPos);
1071 f.Close();
1073 catch (CFileException* e)
1075 e->Delete();
1078 for (const auto& extension : extensions)
1080 if (type.IsEmpty() || (type.Compare(_T("Diff")) == 0))
1082 if (filename.Left(5).CompareNoCase(_T("diff-")) == 0)
1084 CRegString diffreg = CRegString(_T("Software\\TortoiseGit\\DiffTools\\") + extension);
1085 CString diffregstring = diffreg;
1086 if (force || (diffregstring.IsEmpty()) || (diffregstring.Find(filename) >= 0))
1087 diffreg = _T("wscript.exe \"") + file + _T("\" %base %mine") + kind;
1090 if (type.IsEmpty() || (type.Compare(_T("Merge"))==0))
1092 if (filename.Left(6).CompareNoCase(_T("merge-"))==0)
1094 CRegString diffreg = CRegString(_T("Software\\TortoiseGit\\MergeTools\\") + extension);
1095 CString diffregstring = diffreg;
1096 if (force || (diffregstring.IsEmpty()) || (diffregstring.Find(filename) >= 0))
1097 diffreg = _T("wscript.exe \"") + file + _T("\" %merged %theirs %mine %base") + kind;
1103 return true;
1106 bool CAppUtils::Export(const CString* BashHash, const CTGitPath* orgPath)
1108 // ask from where the export has to be done
1109 CExportDlg dlg;
1110 if(BashHash)
1111 dlg.m_initialRefName=*BashHash;
1112 if (orgPath)
1114 if (PathIsRelative(orgPath->GetWinPath()))
1115 dlg.m_orgPath = g_Git.CombinePath(orgPath);
1116 else
1117 dlg.m_orgPath = *orgPath;
1120 if (dlg.DoModal() == IDOK)
1122 CString cmd;
1123 cmd.Format(_T("git.exe archive --output=\"%s\" --format=zip --verbose %s --"),
1124 (LPCTSTR)dlg.m_strFile, (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName));
1126 CProgressDlg pro;
1127 pro.m_GitCmd=cmd;
1128 pro.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1130 if (status)
1131 return;
1132 postCmdList.emplace_back(IDI_EXPLORER, IDS_STATUSLIST_CONTEXT_EXPLORE, [&]{ CAppUtils::ExploreTo(hWndExplorer, dlg.m_strFile); });
1135 CGit git;
1136 if (!dlg.m_bWholeProject && !dlg.m_orgPath.IsEmpty() && PathIsDirectory(dlg.m_orgPath.GetWinPathString()))
1138 git.m_CurrentDir = dlg.m_orgPath.GetWinPathString();
1139 pro.m_Git = &git;
1141 return (pro.DoModal() == IDOK);
1143 return false;
1146 bool CAppUtils::UpdateBranchDescription(const CString& branch, CString description)
1148 if (branch.IsEmpty())
1149 return false;
1151 CString key;
1152 key.Format(L"branch.%s.description", (LPCTSTR)branch);
1153 description.Replace(L"\r", L"");
1154 description.Trim();
1155 if (description.IsEmpty())
1156 g_Git.UnsetConfigValue(key);
1157 else
1158 g_Git.SetConfigValue(key, description);
1160 return true;
1163 bool CAppUtils::CreateBranchTag(bool isTag /*true*/, const CString* commitHash /*nullptr*/, bool switchNewBranch /*false*/, LPCTSTR name /*nullptr*/)
1165 CCreateBranchTagDlg dlg;
1166 dlg.m_bIsTag = isTag;
1167 dlg.m_bSwitch = switchNewBranch;
1169 if (commitHash)
1170 dlg.m_initialRefName = *commitHash;
1172 if (name)
1173 dlg.m_BranchTagName = name;
1175 if(dlg.DoModal()==IDOK)
1177 CString cmd;
1178 CString force;
1179 CString track;
1180 if(dlg.m_bTrack == TRUE)
1181 track=_T(" --track ");
1182 else if(dlg.m_bTrack == FALSE)
1183 track=_T(" --no-track");
1185 if(dlg.m_bForce)
1186 force=_T(" -f ");
1188 if (isTag)
1190 CString sign;
1191 if(dlg.m_bSign)
1192 sign=_T("-s");
1194 cmd.Format(_T("git.exe tag %s %s %s %s"),
1195 (LPCTSTR)force,
1196 (LPCTSTR)sign,
1197 (LPCTSTR)dlg.m_BranchTagName,
1198 (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName)
1201 if(!dlg.m_Message.Trim().IsEmpty())
1203 CString tempfile = ::GetTempFile();
1204 if (CAppUtils::SaveCommitUnicodeFile(tempfile, dlg.m_Message))
1206 CMessageBox::Show(nullptr, _T("Could not save tag message"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1207 return FALSE;
1209 cmd += _T(" -F ")+tempfile;
1212 else
1214 cmd.Format(_T("git.exe branch %s %s %s %s"),
1215 (LPCTSTR)track,
1216 (LPCTSTR)force,
1217 (LPCTSTR)dlg.m_BranchTagName,
1218 (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName)
1221 CString out;
1222 if(g_Git.Run(cmd,&out,CP_UTF8))
1224 CMessageBox::Show(nullptr, out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1225 return FALSE;
1227 if (!isTag && dlg.m_bSwitch)
1229 // it is a new branch and the user has requested to switch to it
1230 PerformSwitch(dlg.m_BranchTagName);
1232 if (!isTag && !dlg.m_Message.IsEmpty())
1233 UpdateBranchDescription(dlg.m_BranchTagName, dlg.m_Message);
1235 return TRUE;
1237 return FALSE;
1240 bool CAppUtils::Switch(const CString& initialRefName)
1242 CGitSwitchDlg dlg;
1243 if(!initialRefName.IsEmpty())
1244 dlg.m_initialRefName = initialRefName;
1246 if (dlg.DoModal() == IDOK)
1248 CString branch;
1249 if (dlg.m_bBranch)
1250 branch = dlg.m_NewBranch;
1252 // if refs/heads/ is not stripped, checkout will detach HEAD
1253 // checkout prefers branches on name clashes (with tags)
1254 if (dlg.m_VersionName.Left(11) ==_T("refs/heads/") && dlg.m_bBranchOverride != TRUE)
1255 dlg.m_VersionName = dlg.m_VersionName.Mid(11);
1257 return PerformSwitch(dlg.m_VersionName, dlg.m_bForce == TRUE , branch, dlg.m_bBranchOverride == TRUE, dlg.m_bTrack, dlg.m_bMerge == TRUE);
1259 return FALSE;
1262 bool CAppUtils::PerformSwitch(const CString& ref, bool bForce /* false */, const CString& sNewBranch /* CString() */, bool bBranchOverride /* false */, BOOL bTrack /* 2 */, bool bMerge /* false */)
1264 CString cmd;
1265 CString track;
1266 CString force;
1267 CString branch;
1268 CString merge;
1270 if(!sNewBranch.IsEmpty()){
1271 if (bBranchOverride)
1272 branch.Format(_T("-B %s "), (LPCTSTR)sNewBranch);
1273 else
1274 branch.Format(_T("-b %s "), (LPCTSTR)sNewBranch);
1275 if (bTrack == TRUE)
1276 track = _T("--track ");
1277 else if (bTrack == FALSE)
1278 track = _T("--no-track ");
1280 if (bForce)
1281 force = _T("-f ");
1282 if (bMerge)
1283 merge = _T("--merge ");
1285 cmd.Format(_T("git.exe checkout %s%s%s%s%s --"),
1286 (LPCTSTR)force,
1287 (LPCTSTR)track,
1288 (LPCTSTR)merge,
1289 (LPCTSTR)branch,
1290 (LPCTSTR)g_Git.FixBranchName(ref));
1292 CProgressDlg progress;
1293 progress.m_GitCmd = cmd;
1295 CString currentBranch;
1296 bool hasBranch = CGit::GetCurrentBranchFromFile(g_Git.m_CurrentDir, currentBranch) == 0;
1297 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1299 if (!status)
1301 CTGitPath gitPath = g_Git.m_CurrentDir;
1302 if (gitPath.HasSubmodules())
1304 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1306 CString sCmd;
1307 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
1308 RunTortoiseGitProc(sCmd);
1311 if (hasBranch)
1312 postCmdList.emplace_back(IDI_MERGE, IDS_MENUMERGE, [&]{ Merge(&currentBranch); });
1315 CString newBranch;
1316 if (!CGit::GetCurrentBranchFromFile(g_Git.m_CurrentDir, newBranch))
1317 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&]{ Pull(); });
1319 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUCOMMIT, []{
1320 CTGitPathList pathlist;
1321 CTGitPathList selectedlist;
1322 pathlist.AddPath(CTGitPath());
1323 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
1324 CString str;
1325 Commit(CString(), false, str, pathlist, selectedlist, bSelectFilesForCommit);
1328 else
1330 if (bMerge && g_Git.HasWorkingTreeConflicts() > 0)
1332 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
1334 CString sCmd;
1335 sCmd.Format(_T("/command:commit /path:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
1336 CAppUtils::RunTortoiseGitProc(sCmd);
1339 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ PerformSwitch(ref, bForce, sNewBranch, bBranchOverride, bTrack, bMerge); });
1340 if (!bMerge)
1341 postCmdList.emplace_back(IDI_SWITCH, IDS_SWITCH_WITH_MERGE, [&]{ PerformSwitch(ref, bForce, sNewBranch, bBranchOverride, bTrack, true); });
1344 progress.m_PostExecCallback = [&](DWORD& exitCode, CString& extraMsg)
1346 if (bMerge && !exitCode && g_Git.HasWorkingTreeConflicts() > 0)
1348 exitCode = 1; // Treat it as failure
1349 extraMsg = _T("Has merge conflict");
1353 INT_PTR ret = progress.DoModal();
1355 return ret == IDOK;
1358 class CIgnoreFile : public CStdioFile
1360 public:
1361 STRING_VECTOR m_Items;
1362 CString m_eol;
1364 virtual BOOL ReadString(CString& rString)
1366 if (GetPosition() == 0)
1368 unsigned char utf8bom[] = { 0xEF, 0xBB, 0xBF };
1369 char buf[3] = { 0, 0, 0 };
1370 Read(buf, 3);
1371 if (memcpy(buf, utf8bom, sizeof(utf8bom)))
1372 SeekToBegin();
1375 CStringA strA;
1376 char lastChar = '\0';
1377 for (char c = '\0'; Read(&c, 1) == 1; lastChar = c)
1379 if (c == '\r')
1380 continue;
1381 if (c == '\n')
1383 m_eol = lastChar == '\r' ? _T("\r\n") : _T("\n");
1384 break;
1386 strA.AppendChar(c);
1388 if (strA.IsEmpty())
1389 return FALSE;
1391 rString = CUnicodeUtils::GetUnicode(strA);
1392 return TRUE;
1395 void ResetState()
1397 m_Items.clear();
1398 m_eol.Empty();
1402 bool CAppUtils::OpenIgnoreFile(CIgnoreFile &file, const CString& filename)
1404 file.ResetState();
1405 if (!file.Open(filename, CFile::modeCreate | CFile::modeReadWrite | CFile::modeNoTruncate | CFile::typeBinary))
1407 CMessageBox::Show(nullptr, filename + _T(" Open Failure"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1408 return false;
1411 if (file.GetLength() > 0)
1413 CString fileText;
1414 while (file.ReadString(fileText))
1415 file.m_Items.push_back(fileText);
1416 file.Seek(file.GetLength() - 1, 0);
1417 char lastchar[1] = { 0 };
1418 file.Read(lastchar, 1);
1419 file.SeekToEnd();
1420 if (lastchar[0] != '\n')
1422 CStringA eol = CStringA(file.m_eol.IsEmpty() ? _T("\n") : file.m_eol);
1423 file.Write(eol, eol.GetLength());
1426 else
1427 file.SeekToEnd();
1429 return true;
1432 bool CAppUtils::IgnoreFile(const CTGitPathList& path,bool IsMask)
1434 CIgnoreDlg ignoreDlg;
1435 if (ignoreDlg.DoModal() == IDOK)
1437 CString ignorefile;
1438 ignorefile = g_Git.m_CurrentDir + _T("\\");
1440 switch (ignoreDlg.m_IgnoreFile)
1442 case 0:
1443 ignorefile += _T(".gitignore");
1444 break;
1445 case 2:
1446 GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, ignorefile);
1447 ignorefile += _T("info");
1448 if (!PathFileExists(ignorefile))
1449 CreateDirectory(ignorefile, nullptr);
1450 ignorefile += _T("\\exclude");
1451 break;
1454 CIgnoreFile file;
1457 if (ignoreDlg.m_IgnoreFile != 1 && !OpenIgnoreFile(file, ignorefile))
1458 return false;
1460 for (int i = 0; i < path.GetCount(); ++i)
1462 if (ignoreDlg.m_IgnoreFile == 1)
1464 ignorefile = g_Git.CombinePath(path[i].GetContainingDirectory()) + _T("\\.gitignore");
1465 if (!OpenIgnoreFile(file, ignorefile))
1466 return false;
1469 CString ignorePattern;
1470 if (ignoreDlg.m_IgnoreType == 0)
1472 if (ignoreDlg.m_IgnoreFile != 1 && !path[i].GetContainingDirectory().GetGitPathString().IsEmpty())
1473 ignorePattern += _T("/") + path[i].GetContainingDirectory().GetGitPathString();
1475 ignorePattern += _T("/");
1477 if (IsMask)
1478 ignorePattern += _T("*") + path[i].GetFileExtension();
1479 else
1480 ignorePattern += path[i].GetFileOrDirectoryName();
1482 // escape [ and ] so that files get ignored correctly
1483 ignorePattern.Replace(_T("["), _T("\\["));
1484 ignorePattern.Replace(_T("]"), _T("\\]"));
1486 bool found = false;
1487 for (size_t j = 0; j < file.m_Items.size(); ++j)
1489 if (file.m_Items[j] == ignorePattern)
1491 found = true;
1492 break;
1495 if (!found)
1497 file.m_Items.push_back(ignorePattern);
1498 ignorePattern += file.m_eol.IsEmpty() ? _T("\n") : file.m_eol;
1499 CStringA ignorePatternA = CUnicodeUtils::GetUTF8(ignorePattern);
1500 file.Write(ignorePatternA, ignorePatternA.GetLength());
1503 if (ignoreDlg.m_IgnoreFile == 1)
1504 file.Close();
1507 if (ignoreDlg.m_IgnoreFile != 1)
1508 file.Close();
1510 catch(...)
1512 file.Abort();
1513 return false;
1516 return true;
1518 return false;
1521 static bool Reset(const CString& resetTo, int resetType)
1523 CString cmd;
1524 CString type;
1525 switch (resetType)
1527 case 0:
1528 type = _T("--soft");
1529 break;
1530 case 1:
1531 type = _T("--mixed");
1532 break;
1533 case 2:
1534 type = _T("--hard");
1535 break;
1536 default:
1537 resetType = 1;
1538 type = _T("--mixed");
1539 break;
1541 cmd.Format(_T("git.exe reset %s %s --"), (LPCTSTR)type, (LPCTSTR)resetTo);
1543 CProgressDlg progress;
1544 progress.m_GitCmd = cmd;
1546 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1548 if (status)
1550 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ Reset(resetTo, resetType); });
1551 return;
1554 CTGitPath gitPath = g_Git.m_CurrentDir;
1555 if (gitPath.HasSubmodules() && resetType == 2)
1557 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1559 CString sCmd;
1560 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
1561 CAppUtils::RunTortoiseGitProc(sCmd);
1566 INT_PTR ret;
1567 if (g_Git.UsingLibGit2(CGit::GIT_CMD_RESET))
1569 CGitProgressDlg gitdlg;
1570 ResetProgressCommand resetProgressCommand;
1571 gitdlg.SetCommand(&resetProgressCommand);
1572 resetProgressCommand.m_PostCmdCallback = progress.m_PostCmdCallback;
1573 resetProgressCommand.SetRevision(resetTo);
1574 resetProgressCommand.SetResetType(resetType);
1575 ret = gitdlg.DoModal();
1577 else
1578 ret = progress.DoModal();
1580 return ret == IDOK;
1583 bool CAppUtils::GitReset(const CString* CommitHash, int type)
1585 CResetDlg dlg;
1586 dlg.m_ResetType=type;
1587 dlg.m_ResetToVersion=*CommitHash;
1588 dlg.m_initialRefName = *CommitHash;
1589 if (dlg.DoModal() == IDOK)
1590 return Reset(dlg.m_ResetToVersion, dlg.m_ResetType);
1592 return false;
1595 void CAppUtils::DescribeConflictFile(bool mode, bool base,CString &descript)
1597 if(mode == FALSE)
1599 descript.LoadString(IDS_SVNACTION_DELETE);
1600 return;
1602 if(base)
1604 descript.LoadString(IDS_SVNACTION_MODIFIED);
1605 return;
1607 descript.LoadString(IDS_PROC_CREATED);
1610 void CAppUtils::RemoveTempMergeFile(const CTGitPath& path)
1612 ::DeleteFile(CAppUtils::GetMergeTempFile(_T("LOCAL"), path));
1613 ::DeleteFile(CAppUtils::GetMergeTempFile(_T("REMOTE"), path));
1614 ::DeleteFile(CAppUtils::GetMergeTempFile(_T("BASE"), path));
1616 CString CAppUtils::GetMergeTempFile(const CString& type, const CTGitPath &merge)
1618 CString file;
1619 file = g_Git.CombinePath(merge.GetWinPathString() + _T(".") + type + merge.GetFileExtension());
1621 return file;
1624 bool ParseHashesFromLsFile(const BYTE_VECTOR& out, CString& hash1, CString& hash2, CString& hash3)
1626 int pos = 0;
1627 CString one;
1628 CString part;
1630 while (pos >= 0 && pos < (int)out.size())
1632 one.Empty();
1634 CGit::StringAppend(&one, &out[pos], CP_UTF8);
1635 int tabstart = 0;
1636 one.Tokenize(_T("\t"), tabstart);
1638 tabstart = 0;
1639 part = one.Tokenize(_T(" "), tabstart); //Tag
1640 part = one.Tokenize(_T(" "), tabstart); //Mode
1641 part = one.Tokenize(_T(" "), tabstart); //Hash
1642 CString hash = part;
1643 part = one.Tokenize(_T("\t"), tabstart); //Stage
1644 int stage = _ttol(part);
1645 if (stage == 1)
1646 hash1 = hash;
1647 else if (stage == 2)
1648 hash2 = hash;
1649 else if (stage == 3)
1651 hash3 = hash;
1652 return true;
1655 pos = out.findNextString(pos);
1658 return false;
1661 bool CAppUtils::ConflictEdit(const CTGitPath& path, bool bAlternativeTool /*= false*/, bool revertTheirMy /*= false*/, HWND resolveMsgHwnd /*= nullptr*/)
1663 bool bRet = false;
1665 CTGitPath merge=path;
1666 CTGitPath directory = merge.GetDirectory();
1668 // we have the conflicted file (%merged)
1669 // now look for the other required files
1670 //GitStatus stat;
1671 //stat.GetStatus(merge);
1672 //if (stat.status == nullptr)
1673 // return false;
1675 BYTE_VECTOR vector;
1677 CString cmd;
1678 cmd.Format(_T("git.exe ls-files -u -t -z -- \"%s\""), (LPCTSTR)merge.GetGitPathString());
1680 if (g_Git.Run(cmd, &vector))
1681 return FALSE;
1683 if (merge.IsDirectory())
1685 CString baseHash, realBaseHash(GIT_REV_ZERO), localHash(GIT_REV_ZERO), remoteHash(GIT_REV_ZERO);
1686 if (merge.HasAdminDir()) {
1687 CGit subgit;
1688 subgit.m_CurrentDir = g_Git.CombinePath(merge);
1689 CGitHash hash;
1690 subgit.GetHash(hash, _T("HEAD"));
1691 baseHash = hash;
1693 if (ParseHashesFromLsFile(vector, realBaseHash, localHash, remoteHash)) // in base no submodule, but in remote submodule
1694 baseHash = realBaseHash;
1696 CGitDiff::ChangeType changeTypeMine = CGitDiff::Unknown;
1697 CGitDiff::ChangeType changeTypeTheirs = CGitDiff::Unknown;
1699 bool baseOK = false, mineOK = false, theirsOK = false;
1700 CString baseSubject, mineSubject, theirsSubject;
1701 if (merge.HasAdminDir())
1703 CGit subgit;
1704 subgit.m_CurrentDir = g_Git.CombinePath(merge);
1705 CGitDiff::GetSubmoduleChangeType(subgit, baseHash, localHash, baseOK, mineOK, changeTypeMine, baseSubject, mineSubject);
1706 CGitDiff::GetSubmoduleChangeType(subgit, baseHash, remoteHash, baseOK, theirsOK, changeTypeTheirs, baseSubject, theirsSubject);
1708 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)
1710 changeTypeMine = CGitDiff::Identical;
1711 changeTypeTheirs = CGitDiff::NewSubmodule;
1712 baseSubject = _T("no submodule");
1713 mineSubject = baseSubject;
1714 theirsSubject = _T("not initialized");
1716 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
1718 baseHash = localHash;
1719 baseSubject = _T("not initialized");
1720 mineSubject = baseSubject;
1721 theirsSubject = _T("not initialized");
1722 changeTypeMine = CGitDiff::Identical;
1723 changeTypeTheirs = CGitDiff::DeleteSubmodule;
1725 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
1727 baseSubject = _T("not initialized");
1728 mineSubject = baseSubject;
1729 theirsSubject = baseSubject;
1730 if (baseHash == localHash)
1731 changeTypeMine = CGitDiff::Identical;
1733 else
1734 return FALSE;
1736 CSubmoduleResolveConflictDlg resolveSubmoduleConflictDialog;
1737 resolveSubmoduleConflictDialog.SetDiff(merge.GetGitPathString(), revertTheirMy, baseHash, baseSubject, baseOK, localHash, mineSubject, mineOK, changeTypeMine, remoteHash, theirsSubject, theirsOK, changeTypeTheirs);
1738 resolveSubmoduleConflictDialog.DoModal();
1739 if (resolveSubmoduleConflictDialog.m_bResolved && resolveMsgHwnd)
1741 static UINT WM_REVERTMSG = RegisterWindowMessage(_T("GITSLNM_NEEDSREFRESH"));
1742 ::PostMessage(resolveMsgHwnd, WM_REVERTMSG, NULL, NULL);
1745 return TRUE;
1748 CTGitPathList list;
1749 if (list.ParserFromLsFile(vector))
1751 CMessageBox::Show(nullptr, _T("Parse ls-files failed!"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1752 return FALSE;
1755 if (list.IsEmpty())
1756 return FALSE;
1758 CTGitPath theirs;
1759 CTGitPath mine;
1760 CTGitPath base;
1762 if (revertTheirMy)
1764 mine.SetFromGit(GetMergeTempFile(_T("REMOTE"), merge));
1765 theirs.SetFromGit(GetMergeTempFile(_T("LOCAL"), merge));
1767 else
1769 mine.SetFromGit(GetMergeTempFile(_T("LOCAL"), merge));
1770 theirs.SetFromGit(GetMergeTempFile(_T("REMOTE"), merge));
1772 base.SetFromGit(GetMergeTempFile(_T("BASE"),merge));
1774 CString format;
1776 //format=_T("git.exe cat-file blob \":%d:%s\"");
1777 format = _T("git.exe checkout-index --temp --stage=%d -- \"%s\"");
1778 CFile tempfile;
1779 //create a empty file, incase stage is not three
1780 tempfile.Open(mine.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1781 tempfile.Close();
1782 tempfile.Open(theirs.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1783 tempfile.Close();
1784 tempfile.Open(base.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1785 tempfile.Close();
1787 bool b_base=false, b_local=false, b_remote=false;
1789 for (int i = 0; i< list.GetCount(); ++i)
1791 CString outfile;
1792 cmd.Empty();
1793 outfile.Empty();
1795 if( list[i].m_Stage == 1)
1797 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1798 b_base = true;
1799 outfile = base.GetWinPathString();
1802 if( list[i].m_Stage == 2 )
1804 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1805 b_local = true;
1806 outfile = mine.GetWinPathString();
1809 if( list[i].m_Stage == 3 )
1811 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1812 b_remote = true;
1813 outfile = theirs.GetWinPathString();
1815 CString output, err;
1816 if(!outfile.IsEmpty())
1817 if (!g_Git.Run(cmd, &output, &err, CP_UTF8))
1819 CString file;
1820 int start =0 ;
1821 file = output.Tokenize(_T("\t"), start);
1822 ::MoveFileEx(file,outfile,MOVEFILE_REPLACE_EXISTING|MOVEFILE_COPY_ALLOWED);
1824 else
1825 CMessageBox::Show(nullptr, output + L"\n" + err, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1828 if(b_local && b_remote )
1830 merge.SetFromWin(g_Git.CombinePath(merge));
1831 if( revertTheirMy )
1832 bRet = !!CAppUtils::StartExtMerge(bAlternativeTool, base, mine, theirs, merge, L"BASE", L"REMOTE", L"LOCAL", CString(), false, resolveMsgHwnd, true);
1833 else
1834 bRet = !!CAppUtils::StartExtMerge(bAlternativeTool, base, theirs, mine, merge, L"BASE", L"REMOTE", L"LOCAL", CString(), false, resolveMsgHwnd, true);
1836 else
1838 ::DeleteFile(mine.GetWinPathString());
1839 ::DeleteFile(theirs.GetWinPathString());
1840 ::DeleteFile(base.GetWinPathString());
1842 CDeleteConflictDlg dlg;
1843 DescribeConflictFile(b_local, b_base,dlg.m_LocalStatus);
1844 DescribeConflictFile(b_remote,b_base,dlg.m_RemoteStatus);
1845 CGitHash localHash, remoteHash;
1846 if (!g_Git.GetHash(localHash, _T("HEAD")))
1847 dlg.m_LocalHash = localHash.ToString();
1848 if (!g_Git.GetHash(remoteHash, _T("MERGE_HEAD")))
1849 dlg.m_RemoteHash = remoteHash.ToString();
1850 else if (!g_Git.GetHash(remoteHash, _T("rebase-apply/original-commit")))
1851 dlg.m_RemoteHash = remoteHash.ToString();
1852 else if (!g_Git.GetHash(remoteHash, _T("CHERRY_PICK_HEAD")))
1853 dlg.m_RemoteHash = remoteHash.ToString();
1854 else if (!g_Git.GetHash(remoteHash, _T("REVERT_HEAD")))
1855 dlg.m_RemoteHash = remoteHash.ToString();
1856 dlg.m_bShowModifiedButton=b_base;
1857 dlg.m_File=merge.GetGitPathString();
1858 if(dlg.DoModal() == IDOK)
1860 CString out;
1861 if(dlg.m_bIsDelete)
1862 cmd.Format(_T("git.exe rm -- \"%s\""), (LPCTSTR)merge.GetGitPathString());
1863 else
1864 cmd.Format(_T("git.exe add -- \"%s\""), (LPCTSTR)merge.GetGitPathString());
1866 if (g_Git.Run(cmd, &out, CP_UTF8))
1868 MessageBox(nullptr, out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1869 return FALSE;
1871 return TRUE;
1873 else
1874 return FALSE;
1877 #if 0
1878 CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool),
1879 base, theirs, mine, merge);
1880 #endif
1881 #if 0
1882 if (stat.status->text_status == svn_wc_status_conflicted)
1884 // we have a text conflict, use our merge tool to resolve the conflict
1886 CTSVNPath theirs(directory);
1887 CTSVNPath mine(directory);
1888 CTSVNPath base(directory);
1889 bool bConflictData = false;
1891 if ((stat.status->entry)&&(stat.status->entry->conflict_new))
1893 theirs.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->conflict_new));
1894 bConflictData = true;
1896 if ((stat.status->entry)&&(stat.status->entry->conflict_old))
1898 base.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->conflict_old));
1899 bConflictData = true;
1901 if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
1903 mine.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->conflict_wrk));
1904 bConflictData = true;
1906 else
1907 mine = merge;
1908 if (bConflictData)
1909 bRet = !!CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool),
1910 base, theirs, mine, merge);
1913 if (stat.status->prop_status == svn_wc_status_conflicted)
1915 // we have a property conflict
1916 CTSVNPath prej(directory);
1917 if ((stat.status->entry)&&(stat.status->entry->prejfile))
1919 prej.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->prejfile));
1920 // there's a problem: the prej file contains a _description_ of the conflict, and
1921 // that description string might be translated. That means we have no way of parsing
1922 // the file to find out the conflicting values.
1923 // The only thing we can do: show a dialog with the conflict description, then
1924 // let the user either accept the existing property or open the property edit dialog
1925 // to manually change the properties and values. And a button to mark the conflict as
1926 // resolved.
1927 CEditPropConflictDlg dlg;
1928 dlg.SetPrejFile(prej);
1929 dlg.SetConflictedItem(merge);
1930 bRet = (dlg.DoModal() != IDCANCEL);
1934 if (stat.status->tree_conflict)
1936 // we have a tree conflict
1937 SVNInfo info;
1938 const SVNInfoData * pInfoData = info.GetFirstFileInfo(merge, SVNRev(), SVNRev());
1939 if (pInfoData)
1941 if (pInfoData->treeconflict_kind == svn_wc_conflict_kind_text)
1943 CTSVNPath theirs(directory);
1944 CTSVNPath mine(directory);
1945 CTSVNPath base(directory);
1946 bool bConflictData = false;
1948 if (pInfoData->treeconflict_theirfile)
1950 theirs.AppendPathString(pInfoData->treeconflict_theirfile);
1951 bConflictData = true;
1953 if (pInfoData->treeconflict_basefile)
1955 base.AppendPathString(pInfoData->treeconflict_basefile);
1956 bConflictData = true;
1958 if (pInfoData->treeconflict_myfile)
1960 mine.AppendPathString(pInfoData->treeconflict_myfile);
1961 bConflictData = true;
1963 else
1964 mine = merge;
1965 if (bConflictData)
1966 bRet = !!CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool),
1967 base, theirs, mine, merge);
1969 else if (pInfoData->treeconflict_kind == svn_wc_conflict_kind_tree)
1971 CString sConflictAction;
1972 CString sConflictReason;
1973 CString sResolveTheirs;
1974 CString sResolveMine;
1975 CTSVNPath treeConflictPath = CTSVNPath(pInfoData->treeconflict_path);
1976 CString sItemName = treeConflictPath.GetUIFileOrDirectoryName();
1978 if (pInfoData->treeconflict_nodekind == svn_node_file)
1980 switch (pInfoData->treeconflict_operation)
1982 case svn_wc_operation_update:
1983 switch (pInfoData->treeconflict_action)
1985 case svn_wc_conflict_action_edit:
1986 sConflictAction.Format(IDS_TREECONFLICT_FILEUPDATEEDIT, (LPCTSTR)sItemName);
1987 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
1988 break;
1989 case svn_wc_conflict_action_add:
1990 sConflictAction.Format(IDS_TREECONFLICT_FILEUPDATEADD, (LPCTSTR)sItemName);
1991 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
1992 break;
1993 case svn_wc_conflict_action_delete:
1994 sConflictAction.Format(IDS_TREECONFLICT_FILEUPDATEDELETE, (LPCTSTR)sItemName);
1995 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
1996 break;
1998 break;
1999 case svn_wc_operation_switch:
2000 switch (pInfoData->treeconflict_action)
2002 case svn_wc_conflict_action_edit:
2003 sConflictAction.Format(IDS_TREECONFLICT_FILESWITCHEDIT, (LPCTSTR)sItemName);
2004 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2005 break;
2006 case svn_wc_conflict_action_add:
2007 sConflictAction.Format(IDS_TREECONFLICT_FILESWITCHADD, (LPCTSTR)sItemName);
2008 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2009 break;
2010 case svn_wc_conflict_action_delete:
2011 sConflictAction.Format(IDS_TREECONFLICT_FILESWITCHDELETE, (LPCTSTR)sItemName);
2012 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2013 break;
2015 break;
2016 case svn_wc_operation_merge:
2017 switch (pInfoData->treeconflict_action)
2019 case svn_wc_conflict_action_edit:
2020 sConflictAction.Format(IDS_TREECONFLICT_FILEMERGEEDIT, (LPCTSTR)sItemName);
2021 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2022 break;
2023 case svn_wc_conflict_action_add:
2024 sResolveTheirs.Format(IDS_TREECONFLICT_FILEMERGEADD, (LPCTSTR)sItemName);
2025 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2026 break;
2027 case svn_wc_conflict_action_delete:
2028 sConflictAction.Format(IDS_TREECONFLICT_FILEMERGEDELETE, (LPCTSTR)sItemName);
2029 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2030 break;
2032 break;
2035 else if (pInfoData->treeconflict_nodekind == svn_node_dir)
2037 switch (pInfoData->treeconflict_operation)
2039 case svn_wc_operation_update:
2040 switch (pInfoData->treeconflict_action)
2042 case svn_wc_conflict_action_edit:
2043 sConflictAction.Format(IDS_TREECONFLICT_DIRUPDATEEDIT, (LPCTSTR)sItemName);
2044 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2045 break;
2046 case svn_wc_conflict_action_add:
2047 sConflictAction.Format(IDS_TREECONFLICT_DIRUPDATEADD, (LPCTSTR)sItemName);
2048 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2049 break;
2050 case svn_wc_conflict_action_delete:
2051 sConflictAction.Format(IDS_TREECONFLICT_DIRUPDATEDELETE, (LPCTSTR)sItemName);
2052 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR);
2053 break;
2055 break;
2056 case svn_wc_operation_switch:
2057 switch (pInfoData->treeconflict_action)
2059 case svn_wc_conflict_action_edit:
2060 sConflictAction.Format(IDS_TREECONFLICT_DIRSWITCHEDIT, (LPCTSTR)sItemName);
2061 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2062 break;
2063 case svn_wc_conflict_action_add:
2064 sConflictAction.Format(IDS_TREECONFLICT_DIRSWITCHADD, (LPCTSTR)sItemName);
2065 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2066 break;
2067 case svn_wc_conflict_action_delete:
2068 sConflictAction.Format(IDS_TREECONFLICT_DIRSWITCHDELETE, (LPCTSTR)sItemName);
2069 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR);
2070 break;
2072 break;
2073 case svn_wc_operation_merge:
2074 switch (pInfoData->treeconflict_action)
2076 case svn_wc_conflict_action_edit:
2077 sConflictAction.Format(IDS_TREECONFLICT_DIRMERGEEDIT, (LPCTSTR)sItemName);
2078 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2079 break;
2080 case svn_wc_conflict_action_add:
2081 sConflictAction.Format(IDS_TREECONFLICT_DIRMERGEADD, (LPCTSTR)sItemName);
2082 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2083 break;
2084 case svn_wc_conflict_action_delete:
2085 sConflictAction.Format(IDS_TREECONFLICT_DIRMERGEDELETE, (LPCTSTR)sItemName);
2086 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR);
2087 break;
2089 break;
2093 UINT uReasonID = 0;
2094 switch (pInfoData->treeconflict_reason)
2096 case svn_wc_conflict_reason_edited:
2097 uReasonID = IDS_TREECONFLICT_REASON_EDITED;
2098 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2099 break;
2100 case svn_wc_conflict_reason_obstructed:
2101 uReasonID = IDS_TREECONFLICT_REASON_OBSTRUCTED;
2102 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2103 break;
2104 case svn_wc_conflict_reason_deleted:
2105 uReasonID = IDS_TREECONFLICT_REASON_DELETED;
2106 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_REMOVEDIR : IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2107 break;
2108 case svn_wc_conflict_reason_added:
2109 uReasonID = IDS_TREECONFLICT_REASON_ADDED;
2110 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2111 break;
2112 case svn_wc_conflict_reason_missing:
2113 uReasonID = IDS_TREECONFLICT_REASON_MISSING;
2114 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_REMOVEDIR : IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2115 break;
2116 case svn_wc_conflict_reason_unversioned:
2117 uReasonID = IDS_TREECONFLICT_REASON_UNVERSIONED;
2118 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2119 break;
2121 sConflictReason.Format(uReasonID, (LPCTSTR)sConflictAction);
2123 CTreeConflictEditorDlg dlg;
2124 dlg.SetConflictInfoText(sConflictReason);
2125 dlg.SetResolveTexts(sResolveTheirs, sResolveMine);
2126 dlg.SetPath(treeConflictPath);
2127 INT_PTR dlgRet = dlg.DoModal();
2128 bRet = (dlgRet != IDCANCEL);
2132 #endif
2133 return bRet;
2136 bool CAppUtils::IsSSHPutty()
2138 CString sshclient=g_Git.m_Environment.GetEnv(_T("GIT_SSH"));
2139 sshclient=sshclient.MakeLower();
2140 if(sshclient.Find(_T("plink.exe"),0)>=0)
2141 return true;
2142 return false;
2145 CString CAppUtils::GetClipboardLink(const CString &skipGitPrefix, int paramsCount)
2147 if (!OpenClipboard(nullptr))
2148 return CString();
2150 CString sClipboardText;
2151 HGLOBAL hglb = GetClipboardData(CF_TEXT);
2152 if (hglb)
2154 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
2155 sClipboardText = CString(lpstr);
2156 GlobalUnlock(hglb);
2158 hglb = GetClipboardData(CF_UNICODETEXT);
2159 if (hglb)
2161 LPCTSTR lpstr = (LPCTSTR)GlobalLock(hglb);
2162 sClipboardText = lpstr;
2163 GlobalUnlock(hglb);
2165 CloseClipboard();
2167 if(!sClipboardText.IsEmpty())
2169 if(sClipboardText[0] == _T('\"') && sClipboardText[sClipboardText.GetLength()-1] == _T('\"'))
2170 sClipboardText=sClipboardText.Mid(1,sClipboardText.GetLength()-2);
2172 if(sClipboardText.Find( _T("http://")) == 0)
2173 return sClipboardText;
2175 if(sClipboardText.Find( _T("https://")) == 0)
2176 return sClipboardText;
2178 if(sClipboardText.Find( _T("git://")) == 0)
2179 return sClipboardText;
2181 if(sClipboardText.Find( _T("ssh://")) == 0)
2182 return sClipboardText;
2184 if (sClipboardText.Find(_T("git@")) == 0)
2185 return sClipboardText;
2187 if(sClipboardText.GetLength()>=2)
2188 if( sClipboardText[1] == _T(':') )
2189 if( (sClipboardText[0] >= 'A' && sClipboardText[0] <= 'Z')
2190 || (sClipboardText[0] >= 'a' && sClipboardText[0] <= 'z') )
2191 return sClipboardText;
2193 // trim prefixes like "git clone "
2194 if (!skipGitPrefix.IsEmpty() && sClipboardText.Find(skipGitPrefix) == 0)
2196 sClipboardText = sClipboardText.Mid(skipGitPrefix.GetLength()).Trim();
2197 int spacePos = -1;
2198 while (paramsCount >= 0)
2200 --paramsCount;
2201 spacePos = sClipboardText.Find(_T(' '), spacePos + 1);
2202 if (spacePos == -1)
2203 break;
2205 if (spacePos > 0 && paramsCount < 0)
2206 sClipboardText = sClipboardText.Left(spacePos);
2207 return sClipboardText;
2211 return CString();
2214 CString CAppUtils::ChooseRepository(const CString* path)
2216 CBrowseFolder browseFolder;
2217 CRegString regLastResopitory = CRegString(_T("Software\\TortoiseGit\\TortoiseProc\\LastRepo"),_T(""));
2219 browseFolder.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
2220 CString strCloneDirectory;
2221 if(path)
2222 strCloneDirectory=*path;
2223 else
2224 strCloneDirectory = regLastResopitory;
2226 CString title;
2227 title.LoadString(IDS_CHOOSE_REPOSITORY);
2229 browseFolder.SetInfo(title);
2231 if (browseFolder.Show(nullptr, strCloneDirectory) == CBrowseFolder::OK)
2233 regLastResopitory = strCloneDirectory;
2234 return strCloneDirectory;
2236 else
2237 return CString();
2240 bool CAppUtils::SendPatchMail(CTGitPathList& list, bool bIsMainWnd)
2242 CSendMailDlg dlg;
2244 dlg.m_PathList = list;
2246 if(dlg.DoModal()==IDOK)
2248 if (dlg.m_PathList.IsEmpty())
2249 return FALSE;
2251 CGitProgressDlg progDlg;
2252 if (bIsMainWnd)
2253 theApp.m_pMainWnd = &progDlg;
2254 SendMailProgressCommand sendMailProgressCommand;
2255 progDlg.SetCommand(&sendMailProgressCommand);
2257 sendMailProgressCommand.SetPathList(dlg.m_PathList);
2258 progDlg.SetItemCount(dlg.m_PathList.GetCount());
2260 CSendMailPatch sendMailPatch(dlg.m_To, dlg.m_CC, dlg.m_Subject, !!dlg.m_bAttachment, !!dlg.m_bCombine);
2261 sendMailProgressCommand.SetSendMailOption(&sendMailPatch);
2263 progDlg.DoModal();
2265 return true;
2267 return false;
2270 bool CAppUtils::SendPatchMail(const CString& cmd, const CString& formatpatchoutput, bool bIsMainWnd)
2272 CTGitPathList list;
2273 CString log=formatpatchoutput;
2274 int start=log.Find(cmd);
2275 if(start >=0)
2276 CString one=log.Tokenize(_T("\n"),start);
2277 else
2278 start = 0;
2280 while(start>=0)
2282 CString one=log.Tokenize(_T("\n"),start);
2283 one=one.Trim();
2284 if (one.IsEmpty() || one.Find(CString(MAKEINTRESOURCE(IDS_SUCCESS))) == 0)
2285 continue;
2286 one.Replace(_T('/'),_T('\\'));
2287 CTGitPath path;
2288 path.SetFromWin(one);
2289 list.AddPath(path);
2291 if (!list.IsEmpty())
2292 return SendPatchMail(list, bIsMainWnd);
2293 else
2295 CMessageBox::Show(nullptr, IDS_ERR_NOPATCHES, IDS_APPNAME, MB_ICONINFORMATION);
2296 return true;
2301 int CAppUtils::GetLogOutputEncode(CGit *pGit)
2303 CString output;
2304 output = pGit->GetConfigValue(_T("i18n.logOutputEncoding"));
2305 if(output.IsEmpty())
2306 return CUnicodeUtils::GetCPCode(pGit->GetConfigValue(_T("i18n.commitencoding")));
2307 else
2308 return CUnicodeUtils::GetCPCode(output);
2310 int CAppUtils::SaveCommitUnicodeFile(const CString& filename, CString &message)
2314 CFile file(filename, CFile::modeReadWrite | CFile::modeCreate);
2315 int cp = CUnicodeUtils::GetCPCode(g_Git.GetConfigValue(_T("i18n.commitencoding")));
2317 bool stripComments = (CRegDWORD(_T("Software\\TortoiseGit\\StripCommentedLines"), FALSE) == TRUE);
2318 TCHAR commentChar = L'#';
2319 if (stripComments)
2321 CString commentCharValue = g_Git.GetConfigValue(L"core.commentchar");
2322 if (!commentCharValue.IsEmpty())
2323 commentChar = commentCharValue[0];
2326 bool sanitize = (CRegDWORD(_T("Software\\TortoiseGit\\SanitizeCommitMsg"), TRUE) == TRUE);
2327 if (sanitize)
2328 message.Trim(L" \r\n");
2330 int len = message.GetLength();
2331 int start = 0;
2332 int emptyLineCnt = 0;
2333 while (start >= 0 && start < len)
2335 int oldStart = start;
2336 start = message.Find(L"\n", oldStart);
2337 CString line = message.Mid(oldStart);
2338 if (start != -1)
2340 line = line.Left(start - oldStart);
2341 ++start; // move forward so we don't find the same char again
2343 if (stripComments && (!line.IsEmpty() && line.GetAt(0) == commentChar) || (start < 0 && line.IsEmpty()))
2344 continue;
2345 line.TrimRight(L" \r");
2346 if (sanitize)
2348 if (line.IsEmpty())
2350 ++emptyLineCnt;
2351 continue;
2353 if (emptyLineCnt) // squash multiple newlines
2354 file.Write("\n", 1);
2355 emptyLineCnt = 0;
2357 CStringA lineA = CUnicodeUtils::GetMulti(line + L"\n", cp);
2358 file.Write((LPCSTR)lineA, lineA.GetLength());
2360 file.Close();
2361 return 0;
2363 catch (CFileException *e)
2365 e->Delete();
2366 return -1;
2370 bool CAppUtils::Pull(bool showPush, bool showStashPop)
2372 if (IsTGitRebaseActive())
2373 return false;
2375 CPullFetchDlg dlg;
2376 dlg.m_IsPull = TRUE;
2377 if (dlg.DoModal() == IDOK)
2379 // "git.exe pull --rebase" is not supported, never and ever. So, adapting it to Fetch & Rebase.
2380 if (dlg.m_bRebase)
2381 return DoFetch(dlg.m_RemoteURL,
2382 FALSE, // Fetch all remotes
2383 dlg.m_bAutoLoad == BST_CHECKED,
2384 dlg.m_bPrune,
2385 dlg.m_bDepth == BST_CHECKED,
2386 dlg.m_nDepth,
2387 dlg.m_bFetchTags,
2388 dlg.m_RemoteBranchName,
2389 dlg.m_bRebaseActivatedInConfigForPull ? 2 : 1, // Rebase after fetching
2390 dlg.m_bRebasePreserveMerges == TRUE); // Preserve merges on rebase
2392 CString url = dlg.m_RemoteURL;
2394 if (dlg.m_bAutoLoad)
2395 CAppUtils::LaunchPAgent(nullptr, &dlg.m_RemoteURL);
2397 CString cmd;
2398 CGitHash hashOld;
2399 if (g_Git.GetHash(hashOld, _T("HEAD")))
2401 MessageBox(nullptr, g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
2402 return false;
2405 CString noff;
2406 CString ffonly;
2407 CString squash;
2408 CString nocommit;
2409 CString depth;
2410 CString notags;
2411 CString prune;
2413 if (!dlg.m_bFetchTags)
2414 notags = _T("--no-tags ");
2416 if (dlg.m_bFetchTags == TRUE)
2417 notags = _T("--tags ");
2419 if (dlg.m_bNoFF)
2420 noff=_T("--no-ff ");
2422 if (dlg.m_bFFonly)
2423 ffonly = _T("--ff-only ");
2425 if (dlg.m_bSquash)
2426 squash = _T("--squash ");
2428 if (dlg.m_bNoCommit)
2429 nocommit = _T("--no-commit ");
2431 if (dlg.m_bDepth)
2432 depth.Format(_T("--depth %d "), dlg.m_nDepth);
2434 if (dlg.m_bPrune == TRUE)
2435 prune = _T("--prune ");
2436 else if (dlg.m_bPrune == FALSE)
2437 prune = _T("--no-prune ");
2439 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);
2440 CProgressDlg progress;
2441 progress.m_GitCmd = cmd;
2443 CGitHash hashNew; // declare outside lambda, because it is captured by reference
2444 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2446 if (status)
2448 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&]{ Pull(); });
2449 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSTASHSAVE, [&]{ StashSave(_T(""), true); });
2450 return;
2453 if (showStashPop)
2454 postCmdList.emplace_back(IDI_RELOCATE, IDS_MENUSTASHPOP, []{ StashPop(); });
2456 if (g_Git.GetHash(hashNew, _T("HEAD")))
2457 MessageBox(nullptr, g_Git.GetGitLastErr(_T("Could not get HEAD hash after pulling.")), _T("TortoiseGit"), MB_ICONERROR);
2458 else
2460 postCmdList.emplace_back(IDI_DIFF, IDS_PROC_PULL_DIFFS, [&]
2462 CFileDiffDlg dlg;
2463 dlg.SetDiff(nullptr, hashOld.ToString(), hashNew.ToString());
2464 dlg.DoModal();
2466 postCmdList.emplace_back(IDI_LOG, IDS_PROC_PULL_LOG, [&]
2468 CLogDlg dlg;
2469 dlg.SetParams(CTGitPath(_T("")), CTGitPath(_T("")), _T(""), hashOld.ToString() + _T("..") + hashNew.ToString(), 0);
2470 dlg.DoModal();
2474 if (showPush)
2475 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, []{ Push(); });
2477 CTGitPath gitPath = g_Git.m_CurrentDir;
2478 if (gitPath.HasSubmodules())
2480 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
2482 CString sCmd;
2483 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
2484 CAppUtils::RunTortoiseGitProc(sCmd);
2489 INT_PTR ret = progress.DoModal();
2491 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)
2493 CChangedDlg changeddlg;
2494 changeddlg.m_pathList.AddPath(CTGitPath());
2495 changeddlg.DoModal();
2497 return true;
2500 return ret == IDOK;
2503 return false;
2506 bool CAppUtils::RebaseAfterFetch(const CString& upstream, int rebase, bool preserveMerges)
2508 while (true)
2510 CRebaseDlg dlg;
2511 if (!upstream.IsEmpty())
2512 dlg.m_Upstream = upstream;
2513 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENULOG)));
2514 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUPUSH)));
2515 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUDESSENDMAIL)));
2516 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUREBASE)));
2517 dlg.m_bRebaseAutoStart = (rebase == 2);
2518 dlg.m_bPreserveMerges = preserveMerges;
2519 INT_PTR response = dlg.DoModal();
2520 if (response == IDOK)
2521 return true;
2522 else if (response == IDC_REBASE_POST_BUTTON)
2524 CString cmd = _T("/command:log");
2525 cmd += _T(" /path:\"") + g_Git.m_CurrentDir + _T("\"");
2526 CAppUtils::RunTortoiseGitProc(cmd);
2527 return true;
2529 else if (response == IDC_REBASE_POST_BUTTON + 1)
2530 return Push();
2531 else if (response == IDC_REBASE_POST_BUTTON + 2)
2533 CString cmd, out, err;
2534 cmd.Format(_T("git.exe format-patch -o \"%s\" %s..%s"),
2535 (LPCTSTR)g_Git.m_CurrentDir,
2536 (LPCTSTR)g_Git.FixBranchName(dlg.m_Upstream),
2537 (LPCTSTR)g_Git.FixBranchName(dlg.m_Branch));
2538 if (g_Git.Run(cmd, &out, &err, CP_UTF8))
2540 CMessageBox::Show(nullptr, out + L"\n" + err, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
2541 return false;
2543 CAppUtils::SendPatchMail(cmd, out);
2544 return true;
2546 else if (response == IDC_REBASE_POST_BUTTON + 3)
2547 continue;
2548 else if (response == IDCANCEL)
2549 return false;
2550 return false;
2554 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)
2556 if (loadPuttyAgent)
2558 if (fetchAllRemotes)
2560 STRING_VECTOR list;
2561 g_Git.GetRemoteList(list);
2563 for (const auto& remote : list)
2564 CAppUtils::LaunchPAgent(nullptr, &remote);
2566 else
2567 CAppUtils::LaunchPAgent(nullptr, &url);
2570 CString upstream = _T("FETCH_HEAD");
2571 CGitHash oldUpstreamHash;
2572 if (runRebase)
2574 STRING_VECTOR list;
2575 g_Git.GetRemoteList(list);
2576 for (auto it = list.cbegin(); it != list.cend(); ++it)
2578 if (url == *it)
2580 CString remote, trackedBranch;
2581 g_Git.GetRemoteTrackedBranchForHEAD(remote, trackedBranch);
2582 if (!remote.IsEmpty() && !trackedBranch.IsEmpty())
2584 upstream = L"remotes/" + remote + L"/" + trackedBranch;
2585 g_Git.GetHash(oldUpstreamHash, upstream);
2587 break;
2592 CString cmd, arg;
2593 arg = _T(" --progress");
2595 if (bDepth)
2596 arg.AppendFormat(_T(" --depth %d"), nDepth);
2598 if (prune == TRUE)
2599 arg += _T(" --prune");
2600 else if (prune == FALSE)
2601 arg += _T(" --no-prune");
2603 if (fetchTags == 1)
2604 arg += _T(" --tags");
2605 else if (fetchTags == 0)
2606 arg += _T(" --no-tags");
2608 if (fetchAllRemotes)
2609 cmd.Format(_T("git.exe fetch --all -v%s"), (LPCTSTR)arg);
2610 else
2611 cmd.Format(_T("git.exe fetch -v%s \"%s\" %s"), (LPCTSTR)arg, (LPCTSTR)url, (LPCTSTR)remoteBranch);
2613 CProgressDlg progress;
2614 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2616 if (status)
2618 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ DoFetch(url, fetchAllRemotes, loadPuttyAgent, prune, bDepth, nDepth, fetchTags, remoteBranch, runRebase, rebasePreserveMerges); });
2619 return;
2622 postCmdList.emplace_back(IDI_LOG, IDS_MENULOG, []
2624 CString cmd = _T("/command:log");
2625 cmd += _T(" /path:\"") + g_Git.m_CurrentDir + _T("\"");
2626 CAppUtils::RunTortoiseGitProc(cmd);
2629 postCmdList.emplace_back(IDI_REVERT, IDS_PROC_RESET, []
2631 CString pullRemote, pullBranch;
2632 g_Git.GetRemoteTrackedBranchForHEAD(pullRemote, pullBranch);
2633 CString defaultUpstream;
2634 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
2635 defaultUpstream.Format(_T("remotes/%s/%s"), (LPCTSTR)pullRemote, (LPCTSTR)pullBranch);
2636 CAppUtils::GitReset(&defaultUpstream, 2);
2639 postCmdList.emplace_back(IDI_PULL, IDS_MENUFETCH, []{ CAppUtils::Fetch(); });
2641 if (!runRebase && !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir))
2642 postCmdList.emplace_back(IDI_REBASE, IDS_MENUREBASE, [&]{ runRebase = false; CAppUtils::RebaseAfterFetch(); });
2645 progress.m_GitCmd = cmd;
2647 if (g_Git.UsingLibGit2(CGit::GIT_CMD_FETCH))
2649 CGitProgressDlg gitdlg;
2650 FetchProgressCommand fetchProgressCommand;
2651 if (!fetchAllRemotes)
2652 fetchProgressCommand.SetUrl(url);
2653 gitdlg.SetCommand(&fetchProgressCommand);
2654 fetchProgressCommand.m_PostCmdCallback = progress.m_PostCmdCallback;
2655 fetchProgressCommand.SetAutoTag(fetchTags == 1 ? GIT_REMOTE_DOWNLOAD_TAGS_ALL : fetchTags == 2 ? GIT_REMOTE_DOWNLOAD_TAGS_AUTO : GIT_REMOTE_DOWNLOAD_TAGS_NONE);
2656 if (!fetchAllRemotes)
2657 fetchProgressCommand.SetRefSpec(remoteBranch);
2658 return gitdlg.DoModal() == IDOK;
2661 progress.m_PostExecCallback = [&](DWORD& exitCode, CString&)
2663 if (exitCode || !runRebase)
2664 return;
2666 CGitHash remoteBranchHash;
2667 g_Git.GetHash(remoteBranchHash, upstream);
2668 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)
2669 return;
2671 if (runRebase == 1 && g_Git.IsFastForward(L"HEAD", upstream))
2673 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);
2674 if (ret == 3)
2675 return;
2676 if (ret == 1)
2678 CProgressDlg mergeProgress;
2679 mergeProgress.m_GitCmd = L"git.exe merge --ff-only " + upstream;
2680 mergeProgress.m_AutoClose = AUTOCLOSE_IF_NO_ERRORS;
2681 mergeProgress.m_PostCmdCallback = [](DWORD status, PostCmdList& postCmdList)
2683 if (status && g_Git.HasWorkingTreeConflicts())
2685 // there are conflict files
2686 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
2688 CString sCmd;
2689 sCmd.Format(L"/command:commit /path:\"%s\"", g_Git.m_CurrentDir);
2690 CAppUtils::RunTortoiseGitProc(sCmd);
2694 mergeProgress.DoModal();
2695 return;
2699 CAppUtils::RebaseAfterFetch(upstream, runRebase, rebasePreserveMerges);
2702 return progress.DoModal() == IDOK;
2705 bool CAppUtils::Fetch(const CString& remoteName, bool allRemotes)
2707 CPullFetchDlg dlg;
2708 dlg.m_PreSelectRemote = remoteName;
2709 dlg.m_IsPull=FALSE;
2710 dlg.m_bAllRemotes = allRemotes;
2712 if(dlg.DoModal()==IDOK)
2713 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);
2715 return false;
2718 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)
2720 CString error;
2721 DWORD exitcode = 0xFFFFFFFF;
2722 if (CHooks::Instance().PrePush(g_Git.m_CurrentDir, exitcode, error))
2724 if (exitcode)
2726 CString temp;
2727 temp.Format(IDS_ERR_HOOKFAILED, (LPCTSTR)error);
2728 CMessageBox::Show(nullptr, temp, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
2729 return false;
2733 int iRecurseSubmodules = 0;
2734 if (GetMsysgitVersion() >= 0x02070000)
2736 CString sRecurseSubmodules = g_Git.GetConfigValue(_T("push.recurseSubmodules"));
2737 if (sRecurseSubmodules == _T("check"))
2738 iRecurseSubmodules = 1;
2739 else if (sRecurseSubmodules == _T("on-demand"))
2740 iRecurseSubmodules = 2;
2743 CString arg;
2744 if (pack)
2745 arg += _T("--thin ");
2746 if (tags && !allBranches)
2747 arg += _T("--tags ");
2748 if (force)
2749 arg += _T("--force ");
2750 if (forceWithLease)
2751 arg += _T("--force-with-lease ");
2752 if (setUpstream)
2753 arg += _T("--set-upstream ");
2754 if (recurseSubmodules == 0 && recurseSubmodules != iRecurseSubmodules)
2755 arg += _T("--recurse-submodules=no ");
2756 if (recurseSubmodules == 1 && recurseSubmodules != iRecurseSubmodules)
2757 arg += _T("--recurse-submodules=check ");
2758 if (recurseSubmodules == 2 && recurseSubmodules != iRecurseSubmodules)
2759 arg += _T("--recurse-submodules=on-demand ");
2761 arg += _T("--progress ");
2763 CProgressDlg progress;
2765 STRING_VECTOR remotesList;
2766 if (allRemotes)
2767 g_Git.GetRemoteList(remotesList);
2768 else
2769 remotesList.push_back(remote);
2771 for (unsigned int i = 0; i < remotesList.size(); ++i)
2773 if (autoloadKey)
2774 CAppUtils::LaunchPAgent(nullptr, &remotesList[i]);
2776 CString cmd;
2777 if (allBranches)
2779 cmd.Format(_T("git.exe push --all %s\"%s\""),
2780 (LPCTSTR)arg,
2781 (LPCTSTR)remotesList[i]);
2783 if (tags)
2785 progress.m_GitCmdList.push_back(cmd);
2786 cmd.Format(_T("git.exe push --tags %s\"%s\""), (LPCTSTR)arg, (LPCTSTR)remotesList[i]);
2789 else
2791 cmd.Format(_T("git.exe push %s\"%s\" %s"),
2792 (LPCTSTR)arg,
2793 (LPCTSTR)remotesList[i],
2794 (LPCTSTR)localBranch);
2795 if (!remoteBranch.IsEmpty())
2797 cmd += L":";
2798 cmd += remoteBranch;
2801 progress.m_GitCmdList.push_back(cmd);
2803 if (!allBranches && !!CRegDWORD(_T("Software\\TortoiseGit\\ShowBranchRevisionNumber"), FALSE))
2805 cmd.Format(_T("git.exe rev-list --count --first-parent %s"), (LPCTSTR)localBranch);
2806 progress.m_GitCmdList.push_back(cmd);
2810 CString superprojectRoot;
2811 GitAdminDir::HasAdminDir(g_Git.m_CurrentDir, false, &superprojectRoot);
2812 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2814 // need to execute hooks as those might be needed by post action commands
2815 DWORD exitcode = 0xFFFFFFFF;
2816 CString error;
2817 if (CHooks::Instance().PostPush(g_Git.m_CurrentDir, exitcode, error))
2819 if (exitcode)
2821 CString temp;
2822 temp.Format(IDS_ERR_HOOKFAILED, (LPCTSTR)error);
2823 MessageBox(nullptr, temp, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
2827 if (status)
2829 bool rejected = progress.GetLogText().Find(_T("! [rejected]")) > 0;
2830 if (rejected)
2832 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, []{ Pull(true); });
2833 postCmdList.emplace_back(IDI_PULL, IDS_MENUFETCH, [&]{ Fetch(allRemotes ? _T("") : remote, allRemotes); });
2835 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, [&]{ Push(localBranch); });
2836 return;
2839 postCmdList.emplace_back(IDS_PROC_REQUESTPULL, [&]{ RequestPull(remoteBranch); });
2840 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, [&]{ Push(localBranch); });
2841 postCmdList.emplace_back(IDI_SWITCH, IDS_MENUSWITCH, [&]{ Switch(); });
2842 if (!superprojectRoot.IsEmpty())
2844 postCmdList.emplace_back(IDI_COMMIT, IDS_PROC_COMMIT_SUPERPROJECT, [&]
2846 CString sCmd;
2847 sCmd.Format(_T("/command:commit /path:\"%s\""), (LPCTSTR)superprojectRoot);
2848 RunTortoiseGitProc(sCmd);
2853 INT_PTR ret = progress.DoModal();
2854 return ret == IDOK;
2857 bool CAppUtils::Push(const CString& selectLocalBranch)
2859 CPushDlg dlg;
2860 dlg.m_BranchSourceName = selectLocalBranch;
2862 if (dlg.DoModal() == IDOK)
2863 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);
2865 return FALSE;
2868 bool CAppUtils::RequestPull(const CString& endrevision, const CString& repositoryUrl, bool bIsMainWnd)
2870 CRequestPullDlg dlg;
2871 dlg.m_RepositoryURL = repositoryUrl;
2872 dlg.m_EndRevision = endrevision;
2873 if (dlg.DoModal()==IDOK)
2875 CString cmd;
2876 cmd.Format(_T("git.exe request-pull %s \"%s\" %s"), (LPCTSTR)dlg.m_StartRevision, (LPCTSTR)dlg.m_RepositoryURL, (LPCTSTR)dlg.m_EndRevision);
2878 CSysProgressDlg sysProgressDlg;
2879 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
2880 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_CREATINGPULLREUQEST)));
2881 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
2882 sysProgressDlg.SetShowProgressBar(false);
2883 sysProgressDlg.ShowModeless((HWND)nullptr, true);
2885 CString tempFileName = GetTempFile();
2886 CString err;
2887 DeleteFile(tempFileName);
2888 CreateDirectory(tempFileName, nullptr);
2889 tempFileName += _T("\\pullrequest.txt");
2890 if (g_Git.RunLogFile(cmd, tempFileName, &err))
2892 CString msg;
2893 msg.LoadString(IDS_ERR_PULLREUQESTFAILED);
2894 MessageBox(nullptr, msg + _T("\n") + err, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
2895 return false;
2898 if (sysProgressDlg.HasUserCancelled())
2900 CMessageBox::Show(nullptr, IDS_USERCANCELLED, IDS_APPNAME, MB_OK);
2901 ::DeleteFile(tempFileName);
2902 return false;
2905 sysProgressDlg.Stop();
2907 if (dlg.m_bSendMail)
2909 CSendMailDlg sendmaildlg;
2910 sendmaildlg.m_PathList = CTGitPathList(CTGitPath(tempFileName));
2911 sendmaildlg.m_bCustomSubject = true;
2913 if (sendmaildlg.DoModal() == IDOK)
2915 if (sendmaildlg.m_PathList.IsEmpty())
2916 return FALSE;
2918 CGitProgressDlg progDlg;
2919 if (bIsMainWnd)
2920 theApp.m_pMainWnd = &progDlg;
2921 SendMailProgressCommand sendMailProgressCommand;
2922 progDlg.SetCommand(&sendMailProgressCommand);
2924 sendMailProgressCommand.SetPathList(sendmaildlg.m_PathList);
2925 progDlg.SetItemCount(sendmaildlg.m_PathList.GetCount());
2927 CSendMailCombineable sendMailCombineable(sendmaildlg.m_To, sendmaildlg.m_CC, sendmaildlg.m_Subject, !!sendmaildlg.m_bAttachment, !!sendmaildlg.m_bCombine);
2928 sendMailProgressCommand.SetSendMailOption(&sendMailCombineable);
2930 progDlg.DoModal();
2932 return true;
2934 return false;
2937 CAppUtils::LaunchAlternativeEditor(tempFileName);
2939 return true;
2942 void CAppUtils::RemoveTrailSlash(CString &path)
2944 if(path.IsEmpty())
2945 return ;
2947 // For URL, do not trim the slash just after the host name component.
2948 int index = path.Find(_T("://"));
2949 if (index >= 0)
2951 index += 4;
2952 index = path.Find(_T('/'), index);
2953 if (index == path.GetLength() - 1)
2954 return;
2957 while(path[path.GetLength()-1] == _T('\\') || path[path.GetLength()-1] == _T('/' ) )
2959 path=path.Left(path.GetLength()-1);
2960 if(path.IsEmpty())
2961 return;
2965 bool CAppUtils::CheckUserData()
2967 while(g_Git.GetUserName().IsEmpty() || g_Git.GetUserEmail().IsEmpty())
2969 if (CMessageBox::Show(nullptr, IDS_PROC_NOUSERDATA, IDS_APPNAME, MB_YESNO | MB_ICONERROR) == IDYES)
2971 CTGitPath path(g_Git.m_CurrentDir);
2972 CSettings dlg(IDS_PROC_SETTINGS_TITLE,&path);
2973 dlg.SetTreeViewMode(TRUE, TRUE, TRUE);
2974 dlg.SetTreeWidth(220);
2975 dlg.m_DefaultPage = _T("gitconfig");
2977 dlg.DoModal();
2978 dlg.HandleRestart();
2981 else
2982 return false;
2985 return true;
2988 BOOL CAppUtils::Commit(const CString& bugid, BOOL bWholeProject, CString &sLogMsg,
2989 CTGitPathList &pathList,
2990 CTGitPathList &selectedList,
2991 bool bSelectFilesForCommit)
2993 bool bFailed = true;
2995 if (!CheckUserData())
2996 return false;
2998 while (bFailed)
3000 bFailed = false;
3001 CCommitDlg dlg;
3002 dlg.m_sBugID = bugid;
3004 dlg.m_bWholeProject = bWholeProject;
3006 dlg.m_sLogMessage = sLogMsg;
3007 dlg.m_pathList = pathList;
3008 dlg.m_checkedPathList = selectedList;
3009 dlg.m_bSelectFilesForCommit = bSelectFilesForCommit;
3010 if (dlg.DoModal() == IDOK)
3012 if (dlg.m_pathList.IsEmpty())
3013 return false;
3014 // if the user hasn't changed the list of selected items
3015 // we don't use that list. Because if we would use the list
3016 // of pre-checked items, the dialog would show different
3017 // checked items on the next startup: it would only try
3018 // to check the parent folder (which might not even show)
3019 // instead, we simply use an empty list and let the
3020 // default checking do its job.
3021 if (!dlg.m_pathList.IsEqual(pathList))
3022 selectedList = dlg.m_pathList;
3023 pathList = dlg.m_updatedPathList;
3024 sLogMsg = dlg.m_sLogMessage;
3025 bSelectFilesForCommit = true;
3027 switch (dlg.m_PostCmd)
3029 case GIT_POSTCOMMIT_CMD_DCOMMIT:
3030 CAppUtils::SVNDCommit();
3031 break;
3032 case GIT_POSTCOMMIT_CMD_PUSH:
3033 CAppUtils::Push();
3034 break;
3035 case GIT_POSTCOMMIT_CMD_CREATETAG:
3036 CAppUtils::CreateBranchTag(TRUE);
3037 break;
3038 case GIT_POSTCOMMIT_CMD_PULL:
3039 CAppUtils::Pull(true);
3040 break;
3041 default:
3042 break;
3045 // CGitProgressDlg progDlg;
3046 // progDlg.SetChangeList(dlg.m_sChangeList, !!dlg.m_bKeepChangeList);
3047 // if (parser.HasVal(_T("closeonend")))
3048 // progDlg.SetAutoClose(parser.GetLongVal(_T("closeonend")));
3049 // progDlg.SetCommand(CGitProgressDlg::GitProgress_Commit);
3050 // progDlg.SetOptions(dlg.m_bKeepLocks ? ProgOptKeeplocks : ProgOptNone);
3051 // progDlg.SetPathList(dlg.m_pathList);
3052 // progDlg.SetCommitMessage(dlg.m_sLogMessage);
3053 // progDlg.SetDepth(dlg.m_bRecursive ? Git_depth_infinity : svn_depth_empty);
3054 // progDlg.SetSelectedList(dlg.m_selectedPathList);
3055 // progDlg.SetItemCount(dlg.m_itemsCount);
3056 // progDlg.SetBugTraqProvider(dlg.m_BugTraqProvider);
3057 // progDlg.DoModal();
3058 // CRegDWORD err = CRegDWORD(_T("Software\\TortoiseGit\\ErrorOccurred"), FALSE);
3059 // err = (DWORD)progDlg.DidErrorsOccur();
3060 // bFailed = progDlg.DidErrorsOccur();
3061 // bRet = progDlg.DidErrorsOccur();
3062 // CRegDWORD bFailRepeat = CRegDWORD(_T("Software\\TortoiseGit\\CommitReopen"), FALSE);
3063 // if (DWORD(bFailRepeat)==0)
3064 // bFailed = false; // do not repeat if the user chose not to in the settings.
3067 return true;
3070 BOOL CAppUtils::SVNDCommit()
3072 CSVNDCommitDlg dcommitdlg;
3073 CString gitSetting = g_Git.GetConfigValue(_T("svn.rmdir"));
3074 if (gitSetting.IsEmpty()) {
3075 if (dcommitdlg.DoModal() != IDOK)
3076 return false;
3077 else
3079 if (dcommitdlg.m_remember)
3081 if (dcommitdlg.m_rmdir)
3082 gitSetting = _T("true");
3083 else
3084 gitSetting = _T("false");
3085 if(g_Git.SetConfigValue(_T("svn.rmdir"),gitSetting))
3087 CString msg;
3088 msg.Format(IDS_PROC_SAVECONFIGFAILED, _T("svn.rmdir"), gitSetting);
3089 CMessageBox::Show(nullptr, msg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
3095 BOOL IsStash = false;
3096 if(!g_Git.CheckCleanWorkTree())
3098 if (CMessageBox::Show(nullptr, IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
3100 CSysProgressDlg sysProgressDlg;
3101 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3102 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3103 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3104 sysProgressDlg.SetShowProgressBar(false);
3105 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3106 sysProgressDlg.ShowModeless((HWND)nullptr, true);
3108 CString cmd,out;
3109 cmd=_T("git.exe stash");
3110 if (g_Git.Run(cmd, &out, CP_UTF8))
3112 sysProgressDlg.Stop();
3113 MessageBox(nullptr, out,_T("TortoiseGit"), MB_OK | MB_ICONERROR);
3114 return false;
3116 sysProgressDlg.Stop();
3118 IsStash =true;
3120 else
3121 return false;
3124 CProgressDlg progress;
3125 if (dcommitdlg.m_rmdir)
3126 progress.m_GitCmd=_T("git.exe svn dcommit --rmdir");
3127 else
3128 progress.m_GitCmd=_T("git.exe svn dcommit");
3129 if(progress.DoModal()==IDOK && progress.m_GitStatus == 0)
3131 if( IsStash)
3133 if (CMessageBox::Show(nullptr, IDS_DCOMMIT_STASH_POP, IDS_APPNAME, MB_YESNO | MB_ICONINFORMATION) == IDYES)
3135 CSysProgressDlg sysProgressDlg;
3136 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3137 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3138 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3139 sysProgressDlg.SetShowProgressBar(false);
3140 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3141 sysProgressDlg.ShowModeless((HWND)nullptr, true);
3143 CString cmd,out;
3144 cmd=_T("git.exe stash pop");
3145 if (g_Git.Run(cmd, &out, CP_UTF8))
3147 sysProgressDlg.Stop();
3148 MessageBox(nullptr, out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
3149 return false;
3151 sysProgressDlg.Stop();
3153 else
3154 return false;
3156 return TRUE;
3158 return FALSE;
3161 BOOL CAppUtils::Merge(const CString* commit, bool showStashPop)
3163 if (!CheckUserData())
3164 return FALSE;
3166 if (IsTGitRebaseActive())
3167 return FALSE;
3169 CMergeDlg dlg;
3170 if(commit)
3171 dlg.m_initialRefName = *commit;
3173 if(dlg.DoModal()==IDOK)
3175 CString cmd;
3176 CString args;
3178 if(dlg.m_bNoFF)
3179 args += _T(" --no-ff");
3180 else if (dlg.m_bFFonly)
3181 args += _T(" --ff-only");
3183 if(dlg.m_bSquash)
3184 args += _T(" --squash");
3186 if(dlg.m_bNoCommit)
3187 args += _T(" --no-commit");
3189 if (dlg.m_bLog)
3190 args.AppendFormat(L" --log=%d", dlg.m_nLog);
3192 if (!dlg.m_MergeStrategy.IsEmpty())
3194 args += _T(" --strategy=") + dlg.m_MergeStrategy;
3195 if (!dlg.m_StrategyOption.IsEmpty())
3197 args += _T(" --strategy-option=") + dlg.m_StrategyOption;
3198 if (!dlg.m_StrategyParam.IsEmpty())
3199 args += _T("=") + dlg.m_StrategyParam;
3203 if(!dlg.m_strLogMesage.IsEmpty())
3205 CString logmsg = dlg.m_strLogMesage;
3206 logmsg.Replace(_T("\\\""), _T("\\\\\""));
3207 logmsg.Replace(_T("\""), _T("\\\""));
3208 args += _T(" -m \"") + logmsg + _T("\"");
3210 cmd.Format(_T("git.exe merge%s %s"), (LPCTSTR)args, (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName));
3212 CProgressDlg Prodlg;
3213 Prodlg.m_GitCmd = cmd;
3215 Prodlg.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3217 if (status)
3219 int hasConflicts = g_Git.HasWorkingTreeConflicts();
3220 if (hasConflicts < 0)
3221 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(L"Checking for conflicts failed.", CGit::GIT_CMD_CHECKCONFLICTS), _T("TortoiseGit"), MB_ICONEXCLAMATION);
3222 else if (hasConflicts)
3224 // there are conflict files
3226 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
3228 CString sCmd;
3229 sCmd.Format(_T("/command:commit /path:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
3230 CAppUtils::RunTortoiseGitProc(sCmd);
3234 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSTASHSAVE, [&]{ CAppUtils::StashSave(_T(""), false, false, true, g_Git.FixBranchName(dlg.m_VersionName)); });
3235 return;
3238 if (showStashPop)
3239 postCmdList.emplace_back(IDI_RELOCATE, IDS_MENUSTASHPOP, []{ StashPop(); });
3241 if (dlg.m_bNoCommit)
3243 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUCOMMIT, []
3245 CString sCmd;
3246 sCmd.Format(_T("/command:commit /path:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
3247 CAppUtils::RunTortoiseGitProc(sCmd);
3249 return;
3252 if (dlg.m_bIsBranch && dlg.m_VersionName.Find(L"remotes/") == -1) // do not ask to remove remote branches
3254 postCmdList.emplace_back(IDI_DELETE, IDS_PROC_REMOVEBRANCH, [&]
3256 CString msg;
3257 msg.Format(IDS_PROC_DELETEBRANCHTAG, dlg.m_VersionName);
3258 if (CMessageBox::Show(nullptr, msg, _T("TortoiseGit"), 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
3260 CString cmd, out;
3261 cmd.Format(_T("git.exe branch -D -- %s"), (LPCTSTR)dlg.m_VersionName);
3262 if (g_Git.Run(cmd, &out, CP_UTF8))
3263 MessageBox(nullptr, out, _T("TortoiseGit"), MB_OK);
3267 if (dlg.m_bIsBranch)
3268 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, []{ Push(); });
3270 BOOL hasGitSVN = CTGitPath(g_Git.m_CurrentDir).GetAdminDirMask() & ITEMIS_GITSVN;
3271 if (hasGitSVN)
3272 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSVNDCOMMIT, []{ SVNDCommit(); });
3275 Prodlg.DoModal();
3276 return !Prodlg.m_GitStatus;
3278 return false;
3281 BOOL CAppUtils::MergeAbort()
3283 CMergeAbortDlg dlg;
3284 if (dlg.DoModal() == IDOK)
3285 return Reset(_T("HEAD"), dlg.m_ResetType + 1);
3287 return FALSE;
3290 void CAppUtils::EditNote(GitRevLoglist* rev)
3292 if (!CheckUserData())
3293 return;
3295 CInputDlg dlg;
3296 dlg.m_sHintText = CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES));
3297 dlg.m_sInputText = rev->m_Notes;
3298 dlg.m_sTitle = CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES));
3299 //dlg.m_pProjectProperties = &m_ProjectProperties;
3300 dlg.m_bUseLogWidth = true;
3301 if(dlg.DoModal() == IDOK)
3303 CString cmd,output;
3304 cmd=_T("notes add -f -F \"");
3306 CString tempfile=::GetTempFile();
3307 if (CAppUtils::SaveCommitUnicodeFile(tempfile, dlg.m_sInputText))
3309 CMessageBox::Show(nullptr, IDS_PROC_FAILEDSAVINGNOTES, IDS_APPNAME, MB_OK | MB_ICONERROR);
3310 return;
3312 cmd += tempfile;
3313 cmd += _T("\" ");
3314 cmd += rev->m_CommitHash.ToString();
3318 if (git_run_cmd("notes", CUnicodeUtils::GetMulti(cmd, CP_UTF8).GetBuffer()))
3319 CMessageBox::Show(nullptr, IDS_PROC_FAILEDSAVINGNOTES, IDS_APPNAME, MB_OK | MB_ICONERROR);
3320 else
3321 rev->m_Notes = dlg.m_sInputText;
3322 }catch(...)
3324 CMessageBox::Show(nullptr, IDS_PROC_FAILEDSAVINGNOTES, IDS_APPNAME, MB_OK | MB_ICONERROR);
3326 ::DeleteFile(tempfile);
3331 int CAppUtils::GetMsysgitVersion()
3333 if (g_Git.ms_LastMsysGitVersion)
3334 return g_Git.ms_LastMsysGitVersion;
3336 CString cmd;
3337 CString versiondebug;
3338 CString version;
3340 CRegDWORD regTime = CRegDWORD(_T("Software\\TortoiseGit\\git_file_time"));
3341 CRegDWORD regVersion = CRegDWORD(_T("Software\\TortoiseGit\\git_cached_version"));
3343 CString gitpath = CGit::ms_LastMsysGitDir+_T("\\git.exe");
3345 __int64 time=0;
3346 if (!CGit::GetFileModifyTime(gitpath, &time))
3348 if((DWORD)time == regTime)
3350 g_Git.ms_LastMsysGitVersion = regVersion;
3351 return regVersion;
3355 CString err;
3356 int ver = g_Git.GetGitVersion(&versiondebug, &err);
3357 if (ver < 0)
3359 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);
3360 return -1;
3364 if (!ver)
3366 CMessageBox::Show(nullptr, _T("Could not parse git.exe version number: \"") + versiondebug + _T("\""), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
3367 return -1;
3371 regTime = time&0xFFFFFFFF;
3372 regVersion = ver;
3373 g_Git.ms_LastMsysGitVersion = ver;
3375 return ver;
3378 void CAppUtils::MarkWindowAsUnpinnable(HWND hWnd)
3380 typedef HRESULT (WINAPI *SHGPSFW) (HWND hwnd,REFIID riid,void** ppv);
3382 CAutoLibrary hShell = AtlLoadSystemLibraryUsingFullPath(_T("Shell32.dll"));
3384 if (hShell.IsValid()) {
3385 SHGPSFW pfnSHGPSFW = (SHGPSFW)::GetProcAddress(hShell, "SHGetPropertyStoreForWindow");
3386 if (pfnSHGPSFW) {
3387 IPropertyStore *pps;
3388 HRESULT hr = pfnSHGPSFW(hWnd, IID_PPV_ARGS(&pps));
3389 if (SUCCEEDED(hr)) {
3390 PROPVARIANT var;
3391 var.vt = VT_BOOL;
3392 var.boolVal = VARIANT_TRUE;
3393 pps->SetValue(PKEY_AppUserModel_PreventPinning, var);
3394 pps->Release();
3400 void CAppUtils::SetWindowTitle(HWND hWnd, const CString& urlorpath, const CString& dialogname)
3402 ASSERT(dialogname.GetLength() < 70);
3403 ASSERT(urlorpath.GetLength() < MAX_PATH);
3404 WCHAR pathbuf[MAX_PATH] = {0};
3406 PathCompactPathEx(pathbuf, urlorpath, 70 - dialogname.GetLength(), 0);
3408 wcscat_s(pathbuf, L" - ");
3409 wcscat_s(pathbuf, dialogname);
3410 wcscat_s(pathbuf, L" - ");
3411 wcscat_s(pathbuf, CString(MAKEINTRESOURCE(IDS_APPNAME)));
3412 SetWindowText(hWnd, pathbuf);
3415 bool CAppUtils::BisectStart(const CString& lastGood, const CString& firstBad, bool bIsMainWnd)
3417 if (!g_Git.CheckCleanWorkTree())
3419 if (CMessageBox::Show(nullptr, IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
3421 CSysProgressDlg sysProgressDlg;
3422 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3423 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3424 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3425 sysProgressDlg.SetShowProgressBar(false);
3426 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3427 sysProgressDlg.ShowModeless((HWND)nullptr, true);
3429 CString cmd, out;
3430 cmd = _T("git.exe stash");
3431 if (g_Git.Run(cmd, &out, CP_UTF8))
3433 sysProgressDlg.Stop();
3434 MessageBox(nullptr, out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
3435 return false;
3437 sysProgressDlg.Stop();
3439 else
3440 return false;
3443 CBisectStartDlg bisectStartDlg;
3445 if (!lastGood.IsEmpty())
3446 bisectStartDlg.m_sLastGood = lastGood;
3447 if (!firstBad.IsEmpty())
3448 bisectStartDlg.m_sFirstBad = firstBad;
3450 if (bisectStartDlg.DoModal() == IDOK)
3452 CProgressDlg progress;
3453 if (bIsMainWnd)
3454 theApp.m_pMainWnd = &progress;
3455 progress.m_GitCmdList.push_back(_T("git.exe bisect start"));
3456 progress.m_GitCmdList.push_back(_T("git.exe bisect good ") + bisectStartDlg.m_LastGoodRevision);
3457 progress.m_GitCmdList.push_back(_T("git.exe bisect bad ") + bisectStartDlg.m_FirstBadRevision);
3459 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3461 if (status)
3462 return;
3464 CTGitPath path(g_Git.m_CurrentDir);
3465 if (path.HasSubmodules())
3467 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
3469 CString sCmd;
3470 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
3471 CAppUtils::RunTortoiseGitProc(sCmd);
3476 INT_PTR ret = progress.DoModal();
3477 return ret == IDOK;
3480 return false;
3483 bool CAppUtils::BisectOperation(const CString& op, const CString& ref, bool bIsMainWnd)
3485 CString cmd = _T("git.exe bisect ") + op;
3487 if (!ref.IsEmpty())
3489 cmd += _T(" ");
3490 cmd += ref;
3493 CProgressDlg progress;
3494 if (bIsMainWnd)
3495 theApp.m_pMainWnd = &progress;
3496 progress.m_GitCmd = cmd;
3498 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3500 if (status)
3501 return;
3503 CTGitPath path = g_Git.m_CurrentDir;
3504 if (path.HasSubmodules())
3506 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
3508 CString sCmd;
3509 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
3510 CAppUtils::RunTortoiseGitProc(sCmd);
3514 if (op != _T("reset"))
3515 postCmdList.emplace_back(IDS_MENUBISECTRESET, []{ CAppUtils::RunTortoiseGitProc(_T("/command:bisect /reset")); });
3518 INT_PTR ret = progress.DoModal();
3519 return ret == IDOK;
3522 int CAppUtils::Git2GetUserPassword(git_cred **out, const char *url, const char *username_from_url, unsigned int /*allowed_types*/, void * /*payload*/)
3524 CUserPassword dlg;
3525 dlg.m_URL = CUnicodeUtils::GetUnicode(url, CP_UTF8);
3526 if (username_from_url)
3527 dlg.m_UserName = CUnicodeUtils::GetUnicode(username_from_url, CP_UTF8);
3529 CStringA username, password;
3530 if (dlg.DoModal() == IDOK)
3532 username = CUnicodeUtils::GetMulti(dlg.m_UserName, CP_UTF8);
3533 password = CUnicodeUtils::GetMulti(dlg.m_Password, CP_UTF8);
3534 return git_cred_userpass_plaintext_new(out, username, password);
3536 giterr_set_str(GITERR_NONE, "User cancelled.");
3537 return GIT_EUSER;
3540 int CAppUtils::Git2CertificateCheck(git_cert* base_cert, int /*valid*/, const char* host, void* /*payload*/)
3542 if (base_cert->cert_type == GIT_CERT_X509)
3544 git_cert_x509* cert = (git_cert_x509*)base_cert;
3546 if (last_accepted_cert.cmp(cert))
3547 return 0;
3549 PCCERT_CONTEXT pServerCert = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, (BYTE*)cert->data, (DWORD)cert->len);
3550 SCOPE_EXIT { CertFreeCertificateContext(pServerCert); };
3552 DWORD verificationError = VerifyServerCertificate(pServerCert, CUnicodeUtils::GetUnicode(host).GetBuffer(), 0);
3553 if (!verificationError)
3555 last_accepted_cert.set(cert);
3556 return 0;
3559 CString servernameInCert;
3560 CertGetNameString(pServerCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nullptr, CStrBuf(servernameInCert, 128), 128);
3562 CString issuer;
3563 CertGetNameString(pServerCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, CERT_NAME_ISSUER_FLAG, nullptr, CStrBuf(issuer, 128), 128);
3565 CCheckCertificateDlg dlg;
3566 dlg.cert = cert;
3567 dlg.m_sCertificateCN = servernameInCert;
3568 dlg.m_sCertificateIssuer = issuer;
3569 dlg.m_sHostname = CUnicodeUtils::GetUnicode(host);
3570 dlg.m_sError = CFormatMessageWrapper(verificationError);
3571 if (dlg.DoModal() == IDOK)
3573 last_accepted_cert.set(cert);
3574 return 0;
3577 return GIT_ECERTIFICATE;
3580 int CAppUtils::ExploreTo(HWND hwnd, CString path)
3582 if (PathFileExists(path))
3584 HRESULT ret = -1;
3585 ITEMIDLIST __unaligned * pidl = ILCreateFromPath(path);
3586 if (pidl)
3588 ret = SHOpenFolderAndSelectItems(pidl, 0, 0, 0);
3589 ILFree(pidl);
3591 return SUCCEEDED(ret) ? 0 : -1;
3593 // if filepath does not exist any more, navigate to closest matching folder
3596 int pos = path.ReverseFind(_T('\\'));
3597 if (pos <= 3)
3598 break;
3599 path = path.Left(pos);
3600 } while (!PathFileExists(path));
3601 return (INT_PTR)ShellExecute(hwnd, _T("explore"), path, nullptr, nullptr, SW_SHOW) > 32 ? 0 : -1;
3604 int CAppUtils::ResolveConflict(CTGitPath& path, resolve_with resolveWith)
3606 bool b_local = false, b_remote = false;
3607 BYTE_VECTOR vector;
3609 CString cmd;
3610 cmd.Format(_T("git.exe ls-files -u -t -z -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3611 if (g_Git.Run(cmd, &vector))
3613 CMessageBox::Show(nullptr, _T("git ls-files failed!"), _T("TortoiseGit"), MB_OK);
3614 return -1;
3617 CTGitPathList list;
3618 if (list.ParserFromLsFile(vector))
3620 CMessageBox::Show(nullptr, _T("Parse ls-files failed!"), _T("TortoiseGit"), MB_OK);
3621 return -1;
3624 if (list.IsEmpty())
3625 return 0;
3626 for (int i = 0; i < list.GetCount(); ++i)
3628 if (list[i].m_Stage == 2)
3629 b_local = true;
3630 if (list[i].m_Stage == 3)
3631 b_remote = true;
3635 CBlockCacheForPath block(g_Git.m_CurrentDir);
3636 if (path.IsDirectory()) // is submodule conflict
3638 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.");
3639 if (b_local && b_remote)
3641 if (!path.HasAdminDir()) // check if submodule is initialized
3643 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).");
3644 MessageBox(nullptr, err, _T("TortoiseGit"), MB_ICONERROR);
3645 return -1;
3647 CGit subgit;
3648 subgit.m_CurrentDir = g_Git.CombinePath(path);
3649 CGitHash submoduleHead;
3650 if (subgit.GetHash(submoduleHead, _T("HEAD")))
3652 MessageBox(nullptr, err, _T("TortoiseGit"), MB_ICONERROR);
3653 return -1;
3655 CString baseHash, localHash, remoteHash;
3656 ParseHashesFromLsFile(vector, baseHash, localHash, remoteHash);
3657 if (resolveWith == RESOLVE_WITH_THEIRS && submoduleHead.ToString() != remoteHash)
3659 CString origPath = g_Git.m_CurrentDir;
3660 g_Git.m_CurrentDir = g_Git.CombinePath(path);
3661 if (!GitReset(&remoteHash))
3663 g_Git.m_CurrentDir = origPath;
3664 return -1;
3666 g_Git.m_CurrentDir = origPath;
3668 else if (resolveWith == RESOLVE_WITH_MINE && submoduleHead.ToString() != localHash)
3670 CString origPath = g_Git.m_CurrentDir;
3671 g_Git.m_CurrentDir = g_Git.CombinePath(path);
3672 if (!GitReset(&localHash))
3674 g_Git.m_CurrentDir = origPath;
3675 return -1;
3677 g_Git.m_CurrentDir = origPath;
3680 else
3682 MessageBox(nullptr, err, _T("TortoiseGit"), MB_ICONERROR);
3683 return -1;
3687 if (resolveWith == RESOLVE_WITH_THEIRS)
3689 CString gitcmd, output;
3690 if (b_local && b_remote)
3691 gitcmd.Format(_T("git.exe checkout-index -f --stage=3 -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3692 else if (b_remote)
3693 gitcmd.Format(_T("git.exe add -f -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3694 else if (b_local)
3695 gitcmd.Format(_T("git.exe rm -f -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3696 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3698 CMessageBox::Show(nullptr, output, _T("TortoiseGit"), MB_ICONERROR);
3699 return -1;
3702 else if (resolveWith == RESOLVE_WITH_MINE)
3704 CString gitcmd, output;
3705 if (b_local && b_remote)
3706 gitcmd.Format(_T("git.exe checkout-index -f --stage=2 -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3707 else if (b_local)
3708 gitcmd.Format(_T("git.exe add -f -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3709 else if (b_remote)
3710 gitcmd.Format(_T("git.exe rm -f -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3711 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3713 CMessageBox::Show(nullptr, output, _T("TortoiseGit"), MB_ICONERROR);
3714 return -1;
3718 if (b_local && b_remote && path.m_Action & CTGitPath::LOGACTIONS_UNMERGED)
3720 CString gitcmd, output;
3721 gitcmd.Format(_T("git.exe add -f -- \"%s\""), (LPCTSTR)path.GetGitPathString());
3722 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3723 CMessageBox::Show(nullptr, output, _T("TortoiseGit"), MB_ICONERROR);
3724 else
3726 path.m_Action |= CTGitPath::LOGACTIONS_MODIFIED;
3727 path.m_Action &= ~CTGitPath::LOGACTIONS_UNMERGED;
3731 RemoveTempMergeFile(path);
3732 return 0;
3735 bool CAppUtils::ShellOpen(const CString& file, HWND hwnd /*= nullptr */)
3737 if ((INT_PTR)ShellExecute(hwnd, nullptr, file, nullptr, nullptr, SW_SHOW) > HINSTANCE_ERROR)
3738 return true;
3740 return ShowOpenWithDialog(file, hwnd);
3743 bool CAppUtils::ShowOpenWithDialog(const CString& file, HWND hwnd /*= nullptr */)
3745 OPENASINFO oi = { 0 };
3746 oi.pcszFile = file;
3747 oi.oaifInFlags = OAIF_EXEC;
3748 return SUCCEEDED(SHOpenWithDialog(hwnd, &oi));
3751 bool CAppUtils::IsTGitRebaseActive()
3753 CString adminDir;
3754 if (!GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir))
3755 return false;
3757 if (!PathIsDirectory(adminDir + L"tgitrebase.active"))
3758 return false;
3760 if (CMessageBox::Show(nullptr, IDS_REBASELOCKFILEFOUND, IDS_APPNAME, 2, IDI_EXCLAMATION, IDS_REMOVESTALEBUTTON, IDS_ABORTBUTTON) == 2)
3761 return true;
3763 RemoveDirectory(adminDir + L"tgitrebase.active");
3765 return false;