Pass right parameters to LaunchPAgent
[TortoiseGit.git] / src / TortoiseProc / AppUtils.cpp
blobc356ec0ad791eb48a0e3217b396ca15d6f27d319
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2018 - 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 "CreateBranchTagDlg.h"
36 #include "GitSwitchDlg.h"
37 #include "ResetDlg.h"
38 #include "DeleteConflictDlg.h"
39 #include "SendMailDlg.h"
40 #include "GitProgressDlg.h"
41 #include "PushDlg.h"
42 #include "CommitDlg.h"
43 #include "MergeDlg.h"
44 #include "MergeAbortDlg.h"
45 #include "Hooks.h"
46 #include "../Settings/Settings.h"
47 #include "InputDlg.h"
48 #include "SVNDCommitDlg.h"
49 #include "requestpulldlg.h"
50 #include "PullFetchDlg.h"
51 #include "RebaseDlg.h"
52 #include "PropKey.h"
53 #include "StashSave.h"
54 #include "IgnoreDlg.h"
55 #include "FormatMessageWrapper.h"
56 #include "SmartHandle.h"
57 #include "BisectStartDlg.h"
58 #include "SysProgressDlg.h"
59 #include "UserPassword.h"
60 #include "SendmailPatch.h"
61 #include "Globals.h"
62 #include "ProgressCommands/ResetProgressCommand.h"
63 #include "ProgressCommands/FetchProgressCommand.h"
64 #include "ProgressCommands/SendMailProgressCommand.h"
65 #include "CertificateValidationHelper.h"
66 #include "CheckCertificateDlg.h"
67 #include "SubmoduleResolveConflictDlg.h"
68 #include "GitDiff.h"
69 #include "../TGitCache/CacheInterface.h"
71 static struct last_accepted_cert {
72 BYTE* data;
73 size_t len;
75 last_accepted_cert()
76 : data(nullptr)
77 , len(0)
80 ~last_accepted_cert()
82 free(data);
84 boolean cmp(git_cert_x509* cert)
86 return len > 0 && len == cert->len && memcmp(data, cert->data, len) == 0;
88 void set(git_cert_x509* cert)
90 free(data);
91 len = cert->len;
92 if (len == 0)
94 data = nullptr;
95 return;
97 data = new BYTE[len];
98 memcpy(data, cert->data, len);
100 } last_accepted_cert;
102 static bool DoFetch(HWND hWnd, 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);
104 bool CAppUtils::StashSave(HWND hWnd, const CString& msg, bool showPull, bool pullShowPush, bool showMerge, const CString& mergeRev)
106 if (!CheckUserData(hWnd))
107 return false;
109 CStashSaveDlg dlg(CWnd::FromHandle(hWnd));
110 dlg.m_sMessage = msg;
111 if (dlg.DoModal() == IDOK)
113 CString cmd = L"git.exe stash push";
114 if (!CAppUtils::IsGitVersionNewerOrEqual(hWnd, 2, 14))
115 cmd = L"git.exe stash save";
117 if (dlg.m_bIncludeUntracked)
118 cmd += L" --include-untracked";
119 else if (dlg.m_bAll)
120 cmd += L" --all";
122 if (!dlg.m_sMessage.IsEmpty())
124 CString message = dlg.m_sMessage;
125 message.Replace(L"\"", L"\"\"");
126 if (CAppUtils::IsGitVersionNewerOrEqual(hWnd, 2, 14))
127 cmd += L" -m \"" + message + L'"';
128 else
129 cmd += L" -- \"" + message + L'"';
132 CProgressDlg progress(CWnd::FromHandle(hWnd));
133 progress.m_GitCmd = cmd;
134 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
136 if (status)
137 return;
139 if (showPull)
140 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&]{ CAppUtils::Pull(hWnd, pullShowPush, true); });
141 if (showMerge)
142 postCmdList.emplace_back(IDI_MERGE, IDS_MENUMERGE, [&]{ CAppUtils::Merge(hWnd, &mergeRev, true); });
143 postCmdList.emplace_back(IDI_RELOCATE, IDS_MENUSTASHPOP, [&hWnd] { CAppUtils::StashPop(hWnd); });
145 return (progress.DoModal() == IDOK);
147 return false;
150 bool CAppUtils::StashApply(HWND hWnd, CString ref, bool showChanges /* true */)
152 CString cmd = L"git.exe stash apply ";
153 if (CStringUtils::StartsWith(ref, L"refs/"))
154 ref = ref.Mid(5);
155 if (CStringUtils::StartsWith(ref, L"stash{"))
156 ref = L"stash@" + ref.Mid(5);
157 cmd += ref;
159 CSysProgressDlg sysProgressDlg;
160 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
161 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
162 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
163 sysProgressDlg.SetShowProgressBar(false);
164 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
165 sysProgressDlg.ShowModeless(hWnd, true);
167 CString out;
168 int ret = g_Git.Run(cmd, &out, CP_UTF8);
170 sysProgressDlg.Stop();
172 bool hasConflicts = (out.Find(L"CONFLICT") >= 0);
173 if (ret && !(ret == 1 && hasConflicts))
174 CMessageBox::Show(hWnd, CString(MAKEINTRESOURCE(IDS_PROC_STASHAPPLYFAILED)) + L'\n' + out, L"TortoiseGit", MB_OK | MB_ICONERROR);
175 else
177 CString message;
178 message.LoadString(IDS_PROC_STASHAPPLYSUCCESS);
179 if (hasConflicts)
180 message.LoadString(IDS_PROC_STASHAPPLYFAILEDCONFLICTS);
181 if (showChanges)
183 if (CMessageBox::Show(hWnd, message + L'\n' + CString(MAKEINTRESOURCE(IDS_SEECHANGES)), L"TortoiseGit", MB_YESNO | MB_ICONINFORMATION) == IDYES)
185 cmd.Format(L"/command:repostatus /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
186 CAppUtils::RunTortoiseGitProc(cmd);
188 return true;
190 else
192 MessageBox(hWnd, message ,L"TortoiseGit", MB_OK | MB_ICONINFORMATION);
193 return true;
196 return false;
199 bool CAppUtils::StashPop(HWND hWnd, int showChanges /* = 1 */)
201 CString cmd = L"git.exe stash pop";
203 CSysProgressDlg sysProgressDlg;
204 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
205 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
206 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
207 sysProgressDlg.SetShowProgressBar(false);
208 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
209 sysProgressDlg.ShowModeless(hWnd, true);
211 CString out;
212 int ret = g_Git.Run(cmd, &out, CP_UTF8);
214 sysProgressDlg.Stop();
216 bool hasConflicts = (out.Find(L"CONFLICT") >= 0);
217 if (ret && !(ret == 1 && hasConflicts))
218 CMessageBox::Show(hWnd, CString(MAKEINTRESOURCE(IDS_PROC_STASHPOPFAILED)) + L'\n' + out, L"TortoiseGit", MB_OK | MB_ICONERROR);
219 else
221 CString message;
222 message.LoadString(IDS_PROC_STASHPOPSUCCESS);
223 if (hasConflicts)
224 message.LoadString(IDS_PROC_STASHPOPFAILEDCONFLICTS);
225 if (showChanges == 1 || (showChanges == 0 && hasConflicts))
227 if (CMessageBox::ShowCheck(hWnd, message + L'\n' + CString(MAKEINTRESOURCE(IDS_SEECHANGES)), L"TortoiseGit", MB_YESNO | (hasConflicts ? MB_ICONEXCLAMATION : MB_ICONINFORMATION), hasConflicts ? L"StashPopShowConflictChanges" : L"StashPopShowChanges") == IDYES)
229 cmd.Format(L"/command:repostatus /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
230 CAppUtils::RunTortoiseGitProc(cmd);
232 return true;
234 else if (showChanges > 1)
236 MessageBox(hWnd, message, L"TortoiseGit", MB_OK | MB_ICONINFORMATION);
237 return true;
239 else if (showChanges == 0)
240 return true;
242 return false;
245 BOOL CAppUtils::StartExtMerge(bool bAlternative,
246 const CTGitPath& basefile, const CTGitPath& theirfile, const CTGitPath& yourfile, const CTGitPath& mergedfile,
247 const CString& basename, const CString& theirname, const CString& yourname, const CString& mergedname, bool bReadOnly,
248 HWND resolveMsgHwnd, bool bDeleteBaseTheirsMineOnClose)
250 CRegString regCom = CRegString(L"Software\\TortoiseGit\\Merge");
251 CString ext = mergedfile.GetFileExtension();
252 CString com = regCom;
253 bool bInternal = false;
255 if (!ext.IsEmpty())
257 // is there an extension specific merge tool?
258 CRegString mergetool(L"Software\\TortoiseGit\\MergeTools\\" + ext.MakeLower());
259 if (!CString(mergetool).IsEmpty())
260 com = mergetool;
262 // is there a filename specific merge tool?
263 CRegString mergetool(L"Software\\TortoiseGit\\MergeTools\\." + mergedfile.GetFilename().MakeLower());
264 if (!CString(mergetool).IsEmpty())
265 com = mergetool;
267 if (bAlternative && !com.IsEmpty())
269 if (CStringUtils::StartsWith(com, L"#"))
270 com.Delete(0);
271 else
272 com.Empty();
275 if (com.IsEmpty() || CStringUtils::StartsWith(com, L"#"))
277 // Maybe we should use TortoiseIDiff?
278 if ((ext == L".jpg") || (ext == L".jpeg") ||
279 (ext == L".bmp") || (ext == L".gif") ||
280 (ext == L".png") || (ext == L".ico") ||
281 (ext == L".tif") || (ext == L".tiff") ||
282 (ext == L".dib") || (ext == L".emf") ||
283 (ext == L".cur"))
285 com = CPathUtils::GetAppDirectory() + L"TortoiseGitIDiff.exe";
286 com = L'"' + com + L'"';
287 com = com + L" /base:%base /theirs:%theirs /mine:%mine /result:%merged";
288 com = com + L" /basetitle:%bname /theirstitle:%tname /minetitle:%yname";
289 if (resolveMsgHwnd)
290 com.AppendFormat(L" /resolvemsghwnd:%I64d", (__int64)resolveMsgHwnd);
292 else
294 // use TortoiseGitMerge
295 bInternal = true;
296 com = CPathUtils::GetAppDirectory() + L"TortoiseGitMerge.exe";
297 com = L'"' + com + L'"';
298 com = com + L" /base:%base /theirs:%theirs /mine:%mine /merged:%merged";
299 com = com + L" /basename:%bname /theirsname:%tname /minename:%yname /mergedname:%mname";
300 com += L" /saverequired";
301 if (resolveMsgHwnd)
302 com.AppendFormat(L" /resolvemsghwnd:%I64d", (__int64)resolveMsgHwnd);
303 if (bDeleteBaseTheirsMineOnClose)
304 com += L" /deletebasetheirsmineonclose";
306 if (!g_sGroupingUUID.IsEmpty())
308 com += L" /groupuuid:\"";
309 com += g_sGroupingUUID;
310 com += L'"';
313 // check if the params are set. If not, just add the files to the command line
314 if ((com.Find(L"%merged") < 0) && (com.Find(L"%base") < 0) && (com.Find(L"%theirs") < 0) && (com.Find(L"%mine") < 0))
316 com += L" \"" + basefile.GetWinPathString() + L'"';
317 com += L" \"" + theirfile.GetWinPathString() + L'"';
318 com += L" \"" + yourfile.GetWinPathString() + L'"';
319 com += L" \"" + mergedfile.GetWinPathString() + L'"';
321 if (basefile.IsEmpty())
323 com.Replace(L"/base:%base", L"");
324 com.Replace(L"%base", L"");
326 else
327 com.Replace(L"%base", L'"' + basefile.GetWinPathString() + L'"');
328 if (theirfile.IsEmpty())
330 com.Replace(L"/theirs:%theirs", L"");
331 com.Replace(L"%theirs", L"");
333 else
334 com.Replace(L"%theirs", L'"' + theirfile.GetWinPathString() + L'"');
335 if (yourfile.IsEmpty())
337 com.Replace(L"/mine:%mine", L"");
338 com.Replace(L"%mine", L"");
340 else
341 com.Replace(L"%mine", L'"' + yourfile.GetWinPathString() + L'"');
342 if (mergedfile.IsEmpty())
344 com.Replace(L"/merged:%merged", L"");
345 com.Replace(L"%merged", L"");
347 else
348 com.Replace(L"%merged", L'"' + mergedfile.GetWinPathString() + L'"');
349 if (basename.IsEmpty())
351 if (basefile.IsEmpty())
353 com.Replace(L"/basename:%bname", L"");
354 com.Replace(L"%bname", L"");
356 else
357 com.Replace(L"%bname", L'"' + basefile.GetUIFileOrDirectoryName() + L'"');
359 else
360 com.Replace(L"%bname", L'"' + basename + L'"');
361 if (theirname.IsEmpty())
363 if (theirfile.IsEmpty())
365 com.Replace(L"/theirsname:%tname", L"");
366 com.Replace(L"%tname", L"");
368 else
369 com.Replace(L"%tname", L'"' + theirfile.GetUIFileOrDirectoryName() + L'"');
371 else
372 com.Replace(L"%tname", L'"' + theirname + L'"');
373 if (yourname.IsEmpty())
375 if (yourfile.IsEmpty())
377 com.Replace(L"/minename:%yname", L"");
378 com.Replace(L"%yname", L"");
380 else
381 com.Replace(L"%yname", L'"' + yourfile.GetUIFileOrDirectoryName() + L'"');
383 else
384 com.Replace(L"%yname", L'"' + yourname + L'"');
385 if (mergedname.IsEmpty())
387 if (mergedfile.IsEmpty())
389 com.Replace(L"/mergedname:%mname", L"");
390 com.Replace(L"%mname", L"");
392 else
393 com.Replace(L"%mname", L'"' + mergedfile.GetUIFileOrDirectoryName() + L'"');
395 else
396 com.Replace(L"%mname", L'"' + mergedname + L'"');
398 com.Replace(L"%wtroot", L'"' + g_Git.m_CurrentDir + L'"');
400 if ((bReadOnly)&&(bInternal))
401 com += L" /readonly";
403 if(!LaunchApplication(com, IDS_ERR_EXTMERGESTART, false))
405 return FALSE;
408 return TRUE;
411 BOOL CAppUtils::StartExtPatch(const CTGitPath& patchfile, const CTGitPath& dir, const CString& sOriginalDescription, const CString& sPatchedDescription, BOOL bReversed, BOOL bWait)
413 CString viewer;
414 // use TortoiseGitMerge
415 viewer = CPathUtils::GetAppDirectory();
416 viewer += L"TortoiseGitMerge.exe";
418 viewer = L'"' + viewer + L'"';
419 viewer = viewer + L" /diff:\"" + patchfile.GetWinPathString() + L'"';
420 viewer = viewer + L" /patchpath:\"" + dir.GetWinPathString() + L'"';
421 if (bReversed)
422 viewer += L" /reversedpatch";
423 if (!sOriginalDescription.IsEmpty())
424 viewer = viewer + L" /patchoriginal:\"" + sOriginalDescription + L'"';
425 if (!sPatchedDescription.IsEmpty())
426 viewer = viewer + L" /patchpatched:\"" + sPatchedDescription + L'"';
427 if (!g_sGroupingUUID.IsEmpty())
429 viewer += L" /groupuuid:\"";
430 viewer += g_sGroupingUUID;
431 viewer += L'"';
433 if(!LaunchApplication(viewer, IDS_ERR_DIFFVIEWSTART, !!bWait))
434 return FALSE;
435 return TRUE;
438 CString CAppUtils::PickDiffTool(const CTGitPath& file1, const CTGitPath& file2)
440 CString difftool = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + file2.GetFilename().MakeLower());
441 if (!difftool.IsEmpty())
442 return difftool;
443 difftool = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + file1.GetFilename().MakeLower());
444 if (!difftool.IsEmpty())
445 return difftool;
447 // Is there an extension specific diff tool?
448 CString ext = file2.GetFileExtension().MakeLower();
449 if (!ext.IsEmpty())
451 difftool = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + ext);
452 if (!difftool.IsEmpty())
453 return difftool;
454 // Maybe we should use TortoiseIDiff?
455 if ((ext == L".jpg") || (ext == L".jpeg") ||
456 (ext == L".bmp") || (ext == L".gif") ||
457 (ext == L".png") || (ext == L".ico") ||
458 (ext == L".tif") || (ext == L".tiff") ||
459 (ext == L".dib") || (ext == L".emf") ||
460 (ext == L".cur"))
462 return
463 L'"' + CPathUtils::GetAppDirectory() + L"TortoiseGitIDiff.exe" + L'"' +
464 L" /left:%base /right:%mine /lefttitle:%bname /righttitle:%yname" +
465 L" /groupuuid:\"" + g_sGroupingUUID + L'"';
469 // Finally, pick a generic external diff tool
470 difftool = CRegString(L"Software\\TortoiseGit\\Diff");
471 return difftool;
474 bool CAppUtils::StartExtDiff(
475 const CString& file1, const CString& file2,
476 const CString& sName1, const CString& sName2,
477 const CString& originalFile1, const CString& originalFile2,
478 const CString& hash1, const CString& hash2,
479 const DiffFlags& flags, int jumpToLine)
481 CString viewer;
483 CRegDWORD blamediff(L"Software\\TortoiseGit\\DiffBlamesWithTortoiseMerge", FALSE);
484 if (!flags.bBlame || !(DWORD)blamediff)
486 viewer = PickDiffTool(file1, file2);
487 // If registry entry for a diff program is commented out, use TortoiseGitMerge.
488 bool bCommentedOut = CStringUtils::StartsWith(viewer, L"#");
489 if (flags.bAlternativeTool)
491 // Invert external vs. internal diff tool selection.
492 if (bCommentedOut)
493 viewer.Delete(0); // uncomment
494 else
495 viewer.Empty();
497 else if (bCommentedOut)
498 viewer.Empty();
501 bool bInternal = viewer.IsEmpty();
502 if (bInternal)
504 viewer =
505 L'"' + CPathUtils::GetAppDirectory() + L"TortoiseGitMerge.exe" + L'"' +
506 L" /base:%base /mine:%mine /basename:%bname /minename:%yname" +
507 L" /basereflectedname:%bpath /minereflectedname:%ypath";
508 if (!g_sGroupingUUID.IsEmpty())
510 viewer += L" /groupuuid:\"";
511 viewer += g_sGroupingUUID;
512 viewer += L'"';
514 if (flags.bBlame)
515 viewer += L" /blame";
517 // check if the params are set. If not, just add the files to the command line
518 if ((viewer.Find(L"%base") < 0) && (viewer.Find(L"%mine") < 0))
520 viewer += L" \"" + file1 + L'"';
521 viewer += L" \"" + file2 + L'"';
523 if (viewer.Find(L"%base") >= 0)
524 viewer.Replace(L"%base", L'"' + file1 + L'"');
525 if (viewer.Find(L"%mine") >= 0)
526 viewer.Replace(L"%mine", L'"' + file2 + L'"');
528 if (sName1.IsEmpty())
529 viewer.Replace(L"%bname", L'"' + file1 + L'"');
530 else
531 viewer.Replace(L"%bname", L'"' + sName1 + L'"');
533 if (sName2.IsEmpty())
534 viewer.Replace(L"%yname", L'"' + file2 + L'"');
535 else
536 viewer.Replace(L"%yname", L'"' + sName2 + L'"');
538 viewer.Replace(L"%bpath", L'"' + originalFile1 + L'"');
539 viewer.Replace(L"%ypath", L'"' + originalFile2 + L'"');
541 viewer.Replace(L"%brev", L'"' + hash1 + L'"');
542 viewer.Replace(L"%yrev", L'"' + hash2 + L'"');
544 viewer.Replace(L"%wtroot", L'"' + g_Git.m_CurrentDir + L'"');
546 if (flags.bReadOnly && bInternal)
547 viewer += L" /readonly";
549 if (jumpToLine > 0)
550 viewer.AppendFormat(L" /line:%d", jumpToLine);
552 return LaunchApplication(viewer, IDS_ERR_EXTDIFFSTART, flags.bWait);
555 BOOL CAppUtils::StartUnifiedDiffViewer(const CString& patchfile, const CString& title, BOOL bWait, bool bAlternativeTool)
557 CString viewer;
558 CRegString v = CRegString(L"Software\\TortoiseGit\\DiffViewer");
559 viewer = v;
561 // If registry entry for a diff program is commented out, use TortoiseGitMerge.
562 bool bCommentedOut = CStringUtils::StartsWith(viewer, L"#");
563 if (bAlternativeTool)
565 // Invert external vs. internal diff tool selection.
566 if (bCommentedOut)
567 viewer.Delete(0); // uncomment
568 else
569 viewer.Empty();
571 else if (bCommentedOut)
572 viewer.Empty();
574 if (viewer.IsEmpty())
576 // use TortoiseGitUDiff
577 viewer = CPathUtils::GetAppDirectory();
578 viewer += L"TortoiseGitUDiff.exe";
579 // enquote the path to TortoiseGitUDiff
580 viewer = L'"' + viewer + L'"';
581 // add the params
582 viewer = viewer + L" /patchfile:%1 /title:\"%title\"";
583 if (!g_sGroupingUUID.IsEmpty())
585 viewer += L" /groupuuid:\"";
586 viewer += g_sGroupingUUID;
587 viewer += L'"';
590 if (viewer.Find(L"%1") >= 0)
592 if (viewer.Find(L"\"%1\"") >= 0)
593 viewer.Replace(L"%1", patchfile);
594 else
595 viewer.Replace(L"%1", L'"' + patchfile + L'"');
597 else
598 viewer += L" \"" + patchfile + L'"';
599 if (viewer.Find(L"%title") >= 0)
600 viewer.Replace(L"%title", title);
602 if(!LaunchApplication(viewer, IDS_ERR_DIFFVIEWSTART, !!bWait))
603 return FALSE;
604 return TRUE;
607 BOOL CAppUtils::StartTextViewer(CString file)
609 CString viewer;
610 CRegString txt = CRegString(L".txt\\", L"", FALSE, HKEY_CLASSES_ROOT);
611 viewer = txt;
612 viewer = viewer + L"\\Shell\\Open\\Command\\";
613 CRegString txtexe = CRegString(viewer, L"", FALSE, HKEY_CLASSES_ROOT);
614 viewer = txtexe;
616 DWORD len = ExpandEnvironmentStrings(viewer, nullptr, 0);
617 auto buf = std::make_unique<TCHAR[]>(len + 1);
618 ExpandEnvironmentStrings(viewer, buf.get(), len);
619 viewer = buf.get();
620 len = ExpandEnvironmentStrings(file, nullptr, 0);
621 auto buf2 = std::make_unique<TCHAR[]>(len + 1);
622 ExpandEnvironmentStrings(file, buf2.get(), len);
623 file = buf2.get();
624 file = L'"' + file + L'"';
625 if (viewer.IsEmpty())
626 return CAppUtils::ShowOpenWithDialog(file) ? TRUE : FALSE;
627 if (viewer.Find(L"\"%1\"") >= 0)
628 viewer.Replace(L"\"%1\"", file);
629 else if (viewer.Find(L"%1") >= 0)
630 viewer.Replace(L"%1", file);
631 else
632 viewer += L' ';
633 viewer += file;
635 if(!LaunchApplication(viewer, IDS_ERR_TEXTVIEWSTART, false))
636 return FALSE;
637 return TRUE;
640 BOOL CAppUtils::CheckForEmptyDiff(const CTGitPath& sDiffPath)
642 DWORD length = 0;
643 CAutoFile hFile = ::CreateFile(sDiffPath.GetWinPath(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
644 if (!hFile)
645 return TRUE;
646 length = ::GetFileSize(hFile, nullptr);
647 if (length < 4)
648 return TRUE;
649 return FALSE;
652 CString CAppUtils::GetLogFontName()
654 return (CString)CRegString(L"Software\\TortoiseGit\\LogFontName", L"Consolas");
657 DWORD CAppUtils::GetLogFontSize()
659 return (DWORD)CRegDWORD(L"Software\\TortoiseGit\\LogFontSize", 9);
662 void CAppUtils::CreateFontForLogs(CFont& fontToCreate)
664 LOGFONT logFont;
665 HDC hScreenDC = ::GetDC(nullptr);
666 logFont.lfHeight = -MulDiv(GetLogFontSize(), GetDeviceCaps(hScreenDC, LOGPIXELSY), 72);
667 ::ReleaseDC(nullptr, hScreenDC);
668 logFont.lfWidth = 0;
669 logFont.lfEscapement = 0;
670 logFont.lfOrientation = 0;
671 logFont.lfWeight = FW_NORMAL;
672 logFont.lfItalic = 0;
673 logFont.lfUnderline = 0;
674 logFont.lfStrikeOut = 0;
675 logFont.lfCharSet = DEFAULT_CHARSET;
676 logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
677 logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
678 logFont.lfQuality = DRAFT_QUALITY;
679 logFont.lfPitchAndFamily = FF_DONTCARE | FIXED_PITCH;
680 wcscpy_s(logFont.lfFaceName, 32, (LPCTSTR)GetLogFontName());
681 VERIFY(fontToCreate.CreateFontIndirect(&logFont));
684 bool CAppUtils::LaunchPAgent(HWND hWnd, const CString* keyfile, const CString* pRemote)
686 CString key,remote;
687 CString cmd,out;
688 if (!pRemote)
689 remote = L"origin";
690 else
691 remote=*pRemote;
693 if (!keyfile)
695 cmd.Format(L"remote.%s.puttykeyfile", (LPCTSTR)remote);
696 key = g_Git.GetConfigValue(cmd);
698 else
699 key=*keyfile;
701 if(key.IsEmpty())
702 return false;
704 CString proc=CPathUtils::GetAppDirectory();
705 proc += L"pageant.exe \"";
706 proc += key;
707 proc += L'"';
709 CString tempfile = GetTempFile();
710 ::DeleteFile(tempfile);
712 proc += L" -c \"";
713 proc += CPathUtils::GetAppDirectory();
714 proc += L"tgittouch.exe\"";
715 proc += L" \"";
716 proc += tempfile;
717 proc += L'"';
719 CString appDir = CPathUtils::GetAppDirectory();
720 bool b = LaunchApplication(proc, IDS_ERR_PAGEANT, true, &appDir);
721 if(!b)
722 return b;
724 int i=0;
725 while(!::PathFileExists(tempfile))
727 Sleep(100);
728 ++i;
729 if(i>10*60*5)
730 break; //timeout 5 minutes
733 if( i== 10*60*5)
734 CMessageBox::Show(hWnd, IDS_ERR_PAEGENTTIMEOUT, IDS_APPNAME, MB_OK | MB_ICONERROR);
735 ::DeleteFile(tempfile);
736 return true;
739 bool CAppUtils::LaunchAlternativeEditor(const CString& filename, bool uac)
741 CString editTool = CRegString(L"Software\\TortoiseGit\\AlternativeEditor");
742 if (editTool.IsEmpty() || CStringUtils::StartsWith(editTool, L"#"))
743 editTool = CPathUtils::GetAppDirectory() + L"notepad2.exe";
745 CString sCmd;
746 sCmd.Format(L"\"%s\" \"%s\"", (LPCTSTR)editTool, (LPCTSTR)filename);
748 LaunchApplication(sCmd, 0, false, nullptr, uac);
749 return true;
752 bool CAppUtils::LaunchRemoteSetting()
754 CTGitPath path(g_Git.m_CurrentDir);
755 CSettings dlg(IDS_PROC_SETTINGS_TITLE, &path);
756 dlg.SetTreeViewMode(TRUE, TRUE, TRUE);
757 dlg.SetTreeWidth(220);
758 dlg.m_DefaultPage = L"gitremote";
760 dlg.DoModal();
761 dlg.HandleRestart();
762 return true;
766 * Launch the external blame viewer
768 bool CAppUtils::LaunchTortoiseBlame(const CString& sBlameFile, const CString& Rev, const CString& sParams)
770 CString viewer = L'"' + CPathUtils::GetAppDirectory();
771 viewer += L"TortoiseGitBlame.exe";
772 viewer += L"\" \"" + sBlameFile + L'"';
773 //viewer += L" \"" + sLogFile + L'"';
774 //viewer += L" \"" + sOriginalFile + L'"';
775 if(!Rev.IsEmpty() && Rev != GIT_REV_ZERO)
776 viewer += L" /rev:" + Rev;
777 if (!g_sGroupingUUID.IsEmpty())
779 viewer += L" /groupuuid:\"";
780 viewer += g_sGroupingUUID;
781 viewer += L'"';
783 viewer += L' ' + sParams;
785 return LaunchApplication(viewer, IDS_ERR_TGITBLAME, false);
788 bool CAppUtils::FormatTextInRichEditControl(CWnd * pWnd)
790 CString sText;
791 if (!pWnd)
792 return false;
793 bool bStyled = false;
794 pWnd->GetWindowText(sText);
795 // the rich edit control doesn't count the CR char!
796 // to be exact: CRLF is treated as one char.
797 sText.Remove(L'\r');
799 // style each line separately
800 int offset = 0;
801 int nNewlinePos;
804 nNewlinePos = sText.Find('\n', offset);
805 CString sLine = nNewlinePos >= 0 ? sText.Mid(offset, nNewlinePos - offset) : sText.Mid(offset);
807 int start = 0;
808 int 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_BOLD, CFE_BOLD);
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_ITALIC, CFE_ITALIC);
824 bStyled = true;
825 start = end;
827 start = 0;
828 end = 0;
829 while (FindStyleChars(sLine, '_', start, end))
831 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
832 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
833 SetCharFormat(pWnd, CFM_UNDERLINE, CFE_UNDERLINE);
834 bStyled = true;
835 start = end;
837 offset = nNewlinePos+1;
838 } while(nNewlinePos>=0);
839 return bStyled;
842 bool CAppUtils::FindStyleChars(const CString& sText, TCHAR stylechar, int& start, int& end)
844 int i=start;
845 int last = sText.GetLength() - 1;
846 bool bFoundMarker = false;
847 TCHAR c = i == 0 ? L'\0' : sText[i - 1];
848 TCHAR nextChar = i >= last ? L'\0' : sText[i + 1];
850 // find a starting marker
851 while (i < last)
853 TCHAR prevChar = c;
854 c = nextChar;
855 nextChar = sText[i + 1];
857 // IsCharAlphaNumeric can be somewhat expensive.
858 // Long lines of "*****" or "----" will be pre-emptied efficiently
859 // by the (c != nextChar) condition.
861 if ((c == stylechar) && (c != nextChar))
863 if (IsCharAlphaNumeric(nextChar) && !IsCharAlphaNumeric(prevChar))
865 start = ++i;
866 bFoundMarker = true;
867 break;
870 ++i;
872 if (!bFoundMarker)
873 return false;
875 // find ending marker
876 // c == sText[i - 1]
878 bFoundMarker = false;
879 while (i <= last)
881 TCHAR prevChar = c;
882 c = sText[i];
883 if (c == stylechar)
885 if ((i == last) || (!IsCharAlphaNumeric(sText[i + 1]) && IsCharAlphaNumeric(prevChar)))
887 end = i;
888 ++i;
889 bFoundMarker = true;
890 break;
893 ++i;
895 return bFoundMarker;
898 // from CSciEdit
899 namespace {
900 bool IsValidURLChar(wchar_t ch)
902 return iswalnum(ch) ||
903 ch == L'_' || ch == L'/' || ch == L';' || ch == L'?' || ch == L'&' || ch == L'=' ||
904 ch == L'%' || ch == L':' || ch == L'.' || ch == L'#' || ch == L'-' || ch == L'+' ||
905 ch == L'|' || ch == L'>' || ch == L'<' || ch == L'!' || ch == L'@' || ch == L'~';
908 bool IsUrlOrEmail(const CString& sText)
910 if (!PathIsURLW(sText))
912 auto atpos = sText.Find(L'@');
913 if (atpos <= 0)
914 return false;
915 if (sText.Find(L'.', atpos) <= atpos + 1) // a dot must follow after the @, but not directly after it
916 return false;
917 if (sText.Find(L':', atpos) < 0) // do not detect git@example.com:something as an email address
918 return true;
919 return false;
921 for (const CString& prefix : { L"http://", L"https://", L"git://", L"ftp://", L"file://", L"mailto:" })
923 if (CStringUtils::StartsWith(sText, prefix) && sText.GetLength() != prefix.GetLength())
924 return true;
926 return false;
930 BOOL CAppUtils::StyleURLs(const CString& msg, CWnd* pWnd)
932 std::vector<CHARRANGE> positions = FindURLMatches(msg);
933 CAppUtils::SetCharFormat(pWnd, CFM_LINK, CFE_LINK, positions);
935 return positions.empty() ? FALSE : TRUE;
939 * implements URL searching with the same logic as CSciEdit::StyleURLs
941 std::vector<CHARRANGE> CAppUtils::FindURLMatches(const CString& msg)
943 std::vector<CHARRANGE> result;
945 int len = msg.GetLength();
946 int starturl = -1;
948 for (int i = 0; i <= msg.GetLength(); ++i)
950 if ((i < len) && IsValidURLChar(msg[i]))
952 if (starturl < 0)
953 starturl = i;
955 else
957 if (starturl >= 0)
959 bool strip = true;
960 if (msg[starturl] == '<' && i < len) // try to detect and do not strip URLs put within <>
962 while (starturl <= i && msg[starturl] == '<') // strip leading '<'
963 ++starturl;
964 strip = false;
965 i = starturl;
966 while (i < len && msg[i] != '\r' && msg[i] != '\n' && msg[i] != '>') // find first '>' or new line after resetting i to start position
967 ++i;
970 int skipTrailing = 0;
971 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] == '<' || msg[i - skipTrailing - 1] == '!'))
972 ++skipTrailing;
974 if (!IsUrlOrEmail(msg.Mid(starturl, i - starturl - skipTrailing)))
976 starturl = -1;
977 continue;
980 CHARRANGE range = { starturl, i - skipTrailing };
981 result.push_back(range);
983 starturl = -1;
987 return result;
990 bool CAppUtils::StartShowUnifiedDiff(HWND hWnd, const CTGitPath& url1, const CString& rev1,
991 const CTGitPath& /*url2*/, const CString& rev2,
992 //const GitRev& peg /* = GitRev */, const GitRev& headpeg /* = GitRev */,
993 bool bAlternateDiff /* = false */, bool /*bIgnoreAncestry*/ /* = false */,
994 bool /* blame = false */,
995 bool bMerge,
996 bool bCombine,
997 bool bNoPrefix)
999 int diffContext = g_Git.GetConfigValueInt32(L"diff.context", -1);
1000 CString tempfile=GetTempFile();
1001 if (g_Git.GetUnifiedDiff(url1, rev1, rev2, tempfile, bMerge, bCombine, diffContext, bNoPrefix))
1003 MessageBox(hWnd, g_Git.GetGitLastErr(L"Could not get unified diff.", CGit::GIT_CMD_DIFF), L"TortoiseGit", MB_OK);
1004 return false;
1006 CAppUtils::StartUnifiedDiffViewer(tempfile, rev1.IsEmpty() ? rev2 : rev1 + L':' + rev2, FALSE, bAlternateDiff);
1008 #if 0
1009 CString sCmd;
1010 sCmd.Format(L"%s /command:showcompare /unified",
1011 (LPCTSTR)(CPathUtils::GetAppDirectory()+L"TortoiseGitProc.exe"));
1012 sCmd += L" /url1:\"" + url1.GetGitPathString() + L'"';
1013 if (rev1.IsValid())
1014 sCmd += L" /revision1:" + rev1.ToString();
1015 sCmd += L" /url2:\"" + url2.GetGitPathString() + L'"';
1016 if (rev2.IsValid())
1017 sCmd += L" /revision2:" + rev2.ToString();
1018 if (peg.IsValid())
1019 sCmd += L" /pegrevision:" + peg.ToString();
1020 if (headpeg.IsValid())
1021 sCmd += L" /headpegrevision:" + headpeg.ToString();
1023 if (bAlternateDiff)
1024 sCmd += L" /alternatediff";
1026 if (bIgnoreAncestry)
1027 sCmd += L" /ignoreancestry";
1029 if (hWnd)
1031 sCmd += L" /hwnd:";
1032 TCHAR buf[30];
1033 swprintf_s(buf, 30, L"%p", (void*)hWnd);
1034 sCmd += buf;
1037 return CAppUtils::LaunchApplication(sCmd, 0, false);
1038 #endif
1039 return TRUE;
1042 bool CAppUtils::SetupDiffScripts(bool force, const CString& type)
1044 CString scriptsdir = CPathUtils::GetAppParentDirectory();
1045 scriptsdir += L"Diff-Scripts";
1046 CSimpleFileFind files(scriptsdir);
1047 while (files.FindNextFileNoDirectories())
1049 CString file = files.GetFilePath();
1050 CString filename = files.GetFileName();
1051 CString ext = file.Mid(file.ReverseFind('-') + 1);
1052 ext = L"." + ext.Left(ext.ReverseFind(L'.'));
1053 std::set<CString> extensions;
1054 extensions.insert(ext);
1055 CString kind;
1056 if (CStringUtils::EndsWithI(file, L"vbs"))
1057 kind = L" //E:vbscript";
1058 if (CStringUtils::EndsWithI(file, L"js"))
1059 kind = L" //E:javascript";
1060 // open the file, read the first line and find possible extensions
1061 // this script can handle
1064 CStdioFile f(file, CFile::modeRead | CFile::shareDenyNone);
1065 CString extline;
1066 if (f.ReadString(extline))
1068 if ((extline.GetLength() > 15 ) &&
1069 (CStringUtils::StartsWith(extline, L"// extensions: ") ||
1070 CStringUtils::StartsWith(extline, L"' extensions: ")))
1072 if (extline[0] == '/')
1073 extline = extline.Mid(15);
1074 else
1075 extline = extline.Mid(14);
1076 CString sToken;
1077 int curPos = 0;
1078 sToken = extline.Tokenize(L";", curPos);
1079 while (!sToken.IsEmpty())
1081 if (!sToken.IsEmpty())
1083 if (sToken[0] != '.')
1084 sToken = L"." + sToken;
1085 extensions.insert(sToken);
1087 sToken = extline.Tokenize(L";", curPos);
1091 f.Close();
1093 catch (CFileException* e)
1095 e->Delete();
1098 for (const auto& extension : extensions)
1100 if (type.IsEmpty() || (type.Compare(L"Diff") == 0))
1102 if (CStringUtils::StartsWithI(filename, L"diff-"))
1104 CRegString diffreg = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + extension);
1105 CString diffregstring = diffreg;
1106 if (force || (diffregstring.IsEmpty()) || (diffregstring.Find(filename) >= 0))
1107 diffreg = L"wscript.exe \"" + file + L"\" %base %mine" + kind;
1110 if (type.IsEmpty() || (type.Compare(L"Merge") == 0))
1112 if (CStringUtils::StartsWithI(filename, L"merge-"))
1114 CRegString diffreg = CRegString(L"Software\\TortoiseGit\\MergeTools\\" + extension);
1115 CString diffregstring = diffreg;
1116 if (force || (diffregstring.IsEmpty()) || (diffregstring.Find(filename) >= 0))
1117 diffreg = L"wscript.exe \"" + file + L"\" %merged %theirs %mine %base" + kind;
1123 return true;
1126 bool CAppUtils::Export(HWND hWnd, const CString* BashHash, const CTGitPath* orgPath)
1128 // ask from where the export has to be done
1129 CExportDlg dlg(CWnd::FromHandle(hWnd));
1130 if(BashHash)
1131 dlg.m_initialRefName=*BashHash;
1132 if (orgPath)
1134 if (PathIsRelative(orgPath->GetWinPath()))
1135 dlg.m_orgPath = g_Git.CombinePath(orgPath);
1136 else
1137 dlg.m_orgPath = *orgPath;
1140 if (dlg.DoModal() == IDOK)
1142 CString cmd;
1143 cmd.Format(L"git.exe archive --output=\"%s\" --format=zip --verbose %s --",
1144 (LPCTSTR)dlg.m_strFile, (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName));
1146 CProgressDlg pro(CWnd::FromHandle(hWnd));
1147 pro.m_GitCmd=cmd;
1148 pro.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1150 if (status)
1151 return;
1152 postCmdList.emplace_back(IDI_EXPLORER, IDS_STATUSLIST_CONTEXT_EXPLORE, [&]{ CAppUtils::ExploreTo(hWnd, dlg.m_strFile); });
1155 CGit git;
1156 if (!dlg.m_bWholeProject && !dlg.m_orgPath.IsEmpty() && PathIsDirectory(dlg.m_orgPath.GetWinPathString()))
1158 git.m_CurrentDir = dlg.m_orgPath.GetWinPathString();
1159 pro.m_Git = &git;
1161 return (pro.DoModal() == IDOK);
1163 return false;
1166 bool CAppUtils::UpdateBranchDescription(const CString& branch, CString description)
1168 if (branch.IsEmpty())
1169 return false;
1171 CString key;
1172 key.Format(L"branch.%s.description", (LPCTSTR)branch);
1173 description.Remove(L'\r');
1174 description.Trim();
1175 if (description.IsEmpty())
1176 g_Git.UnsetConfigValue(key);
1177 else
1178 g_Git.SetConfigValue(key, description);
1180 return true;
1183 bool CAppUtils::CreateBranchTag(HWND hWnd, bool isTag /*true*/, const CString* commitHash /*nullptr*/, bool switchNewBranch /*false*/, LPCTSTR name /*nullptr*/)
1185 CCreateBranchTagDlg dlg(CWnd::FromHandle(hWnd));
1186 dlg.m_bIsTag = isTag;
1187 dlg.m_bSwitch = switchNewBranch;
1189 if (commitHash)
1190 dlg.m_initialRefName = *commitHash;
1192 if (name)
1193 dlg.m_BranchTagName = name;
1195 if(dlg.DoModal()==IDOK)
1197 CString cmd;
1198 CString force;
1199 CString track;
1200 if(dlg.m_bTrack == TRUE)
1201 track = L"--track";
1202 else if(dlg.m_bTrack == FALSE)
1203 track = L"--no-track";
1205 if(dlg.m_bForce)
1206 force = L"-f";
1208 if (isTag)
1210 CString sign;
1211 if(dlg.m_bSign)
1212 sign = L"-s";
1214 cmd.Format(L"git.exe tag %s %s %s %s",
1215 (LPCTSTR)force,
1216 (LPCTSTR)sign,
1217 (LPCTSTR)dlg.m_BranchTagName,
1218 (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName)
1221 if(!dlg.m_Message.Trim().IsEmpty())
1223 CString tempfile = ::GetTempFile();
1224 if (CAppUtils::SaveCommitUnicodeFile(tempfile, dlg.m_Message))
1226 MessageBox(hWnd, L"Could not save tag message", L"TortoiseGit", MB_OK | MB_ICONERROR);
1227 return FALSE;
1229 cmd += L" -F " + tempfile;
1232 else
1234 cmd.Format(L"git.exe branch %s %s %s %s",
1235 (LPCTSTR)track,
1236 (LPCTSTR)force,
1237 (LPCTSTR)dlg.m_BranchTagName,
1238 (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName)
1241 CString out;
1242 if(g_Git.Run(cmd,&out,CP_UTF8))
1244 MessageBox(hWnd, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
1245 return FALSE;
1247 if (!isTag && dlg.m_bSwitch)
1249 // it is a new branch and the user has requested to switch to it
1250 PerformSwitch(hWnd, dlg.m_BranchTagName);
1252 if (!isTag && !dlg.m_Message.IsEmpty())
1253 UpdateBranchDescription(dlg.m_BranchTagName, dlg.m_Message);
1255 return TRUE;
1257 return FALSE;
1260 bool CAppUtils::Switch(HWND hWnd, const CString& initialRefName)
1262 CGitSwitchDlg dlg(CWnd::FromHandle(hWnd));
1263 if(!initialRefName.IsEmpty())
1264 dlg.m_initialRefName = initialRefName;
1266 if (dlg.DoModal() == IDOK)
1268 CString branch;
1269 if (dlg.m_bBranch)
1270 branch = dlg.m_NewBranch;
1272 // if refs/heads/ is not stripped, checkout will detach HEAD
1273 // checkout prefers branches on name clashes (with tags)
1274 if (CStringUtils::StartsWith(dlg.m_VersionName, L"refs/heads/") && dlg.m_bBranchOverride != TRUE)
1275 dlg.m_VersionName = dlg.m_VersionName.Mid(11);
1277 return PerformSwitch(hWnd, dlg.m_VersionName, dlg.m_bForce == TRUE, branch, dlg.m_bBranchOverride == TRUE, dlg.m_bTrack, dlg.m_bMerge == TRUE);
1279 return FALSE;
1282 bool CAppUtils::PerformSwitch(HWND hWnd, const CString& ref, bool bForce /* false */, const CString& sNewBranch /* CString() */, bool bBranchOverride /* false */, BOOL bTrack /* 2 */, bool bMerge /* false */)
1284 CString cmd;
1285 CString track;
1286 CString force;
1287 CString branch;
1288 CString merge;
1290 if(!sNewBranch.IsEmpty()){
1291 if (bBranchOverride)
1292 branch.Format(L"-B %s ", (LPCTSTR)sNewBranch);
1293 else
1294 branch.Format(L"-b %s ", (LPCTSTR)sNewBranch);
1295 if (bTrack == TRUE)
1296 track = L"--track ";
1297 else if (bTrack == FALSE)
1298 track = L"--no-track ";
1300 if (bForce)
1301 force = L"-f ";
1302 if (bMerge)
1303 merge = L"--merge ";
1305 cmd.Format(L"git.exe checkout %s%s%s%s%s --",
1306 (LPCTSTR)force,
1307 (LPCTSTR)track,
1308 (LPCTSTR)merge,
1309 (LPCTSTR)branch,
1310 (LPCTSTR)g_Git.FixBranchName(ref));
1312 CProgressDlg progress(CWnd::FromHandle(hWnd));
1313 progress.m_GitCmd = cmd;
1315 CString currentBranch;
1316 bool hasBranch = CGit::GetCurrentBranchFromFile(g_Git.m_CurrentDir, currentBranch) == 0;
1317 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1319 if (!status)
1321 CTGitPath gitPath = g_Git.m_CurrentDir;
1322 if (gitPath.HasSubmodules())
1324 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1326 CString sCmd;
1327 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
1328 RunTortoiseGitProc(sCmd);
1331 if (hasBranch)
1332 postCmdList.emplace_back(IDI_MERGE, IDS_MENUMERGE, [&]{ Merge(hWnd, &currentBranch); });
1335 CString newBranch;
1336 if (!CGit::GetCurrentBranchFromFile(g_Git.m_CurrentDir, newBranch))
1337 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&hWnd]{ Pull(hWnd); });
1339 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUCOMMIT, [&]{
1340 CTGitPathList pathlist;
1341 pathlist.AddPath(CTGitPath());
1342 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(L"Software\\TortoiseGit\\SelectFilesForCommit", TRUE));
1343 CString str;
1344 Commit(hWnd, CString(), false, str, pathlist, bSelectFilesForCommit);
1347 else
1349 if (bMerge && g_Git.HasWorkingTreeConflicts() > 0)
1351 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
1353 CString sCmd;
1354 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
1355 CAppUtils::RunTortoiseGitProc(sCmd);
1358 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ PerformSwitch(hWnd, ref, bForce, sNewBranch, bBranchOverride, bTrack, bMerge); });
1359 if (!bMerge)
1360 postCmdList.emplace_back(IDI_SWITCH, IDS_SWITCH_WITH_MERGE, [&]{ PerformSwitch(hWnd, ref, bForce, sNewBranch, bBranchOverride, bTrack, true); });
1363 progress.m_PostExecCallback = [&](DWORD& exitCode, CString& extraMsg)
1365 if (bMerge && !exitCode && g_Git.HasWorkingTreeConflicts() > 0)
1367 exitCode = 1; // Treat it as failure
1368 extraMsg = L"Has merge conflict";
1372 INT_PTR ret = progress.DoModal();
1374 return ret == IDOK;
1377 class CIgnoreFile : public CStdioFile
1379 public:
1380 STRING_VECTOR m_Items;
1381 CString m_eol;
1383 virtual BOOL ReadString(CString& rString)
1385 if (GetPosition() == 0)
1387 unsigned char utf8bom[] = { 0xEF, 0xBB, 0xBF };
1388 char buf[3] = { 0, 0, 0 };
1389 Read(buf, 3);
1390 if (memcpy(buf, utf8bom, sizeof(utf8bom)))
1391 SeekToBegin();
1394 CStringA strA;
1395 char lastChar = '\0';
1396 for (char c = '\0'; Read(&c, 1) == 1; lastChar = c)
1398 if (c == '\r')
1399 continue;
1400 if (c == '\n')
1402 m_eol = lastChar == L'\r' ? L"\r\n" : L"\n";
1403 break;
1405 strA.AppendChar(c);
1407 if (strA.IsEmpty())
1408 return FALSE;
1410 rString = CUnicodeUtils::GetUnicode(strA);
1411 return TRUE;
1414 void ResetState()
1416 m_Items.clear();
1417 m_eol.Empty();
1421 bool CAppUtils::OpenIgnoreFile(HWND hWnd, CIgnoreFile &file, const CString& filename)
1423 file.ResetState();
1424 if (!file.Open(filename, CFile::modeCreate | CFile::modeReadWrite | CFile::modeNoTruncate | CFile::typeBinary))
1426 MessageBox(hWnd, filename + L" Open Failure", L"TortoiseGit", MB_OK | MB_ICONERROR);
1427 return false;
1430 if (file.GetLength() > 0)
1432 CString fileText;
1433 while (file.ReadString(fileText))
1434 file.m_Items.push_back(fileText);
1435 file.Seek(file.GetLength() - 1, 0);
1436 char lastchar[1] = { 0 };
1437 file.Read(lastchar, 1);
1438 file.SeekToEnd();
1439 if (lastchar[0] != '\n')
1441 CStringA eol = CStringA(file.m_eol.IsEmpty() ? L"\n" : file.m_eol);
1442 file.Write(eol, eol.GetLength());
1445 else
1446 file.SeekToEnd();
1448 return true;
1451 bool CAppUtils::IgnoreFile(HWND hWnd, const CTGitPathList& path,bool IsMask)
1453 CIgnoreDlg ignoreDlg(CWnd::FromHandle(hWnd));
1454 if (ignoreDlg.DoModal() == IDOK)
1456 CString ignorefile;
1457 ignorefile = g_Git.m_CurrentDir + L'\\';
1459 switch (ignoreDlg.m_IgnoreFile)
1461 case 0:
1462 ignorefile += L".gitignore";
1463 break;
1464 case 2:
1465 GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, ignorefile);
1466 ignorefile += L"info";
1467 if (!PathFileExists(ignorefile))
1468 CreateDirectory(ignorefile, nullptr);
1469 ignorefile += L"\\exclude";
1470 break;
1473 CIgnoreFile file;
1476 if (ignoreDlg.m_IgnoreFile != 1 && !OpenIgnoreFile(hWnd, file, ignorefile))
1477 return false;
1479 for (int i = 0; i < path.GetCount(); ++i)
1481 if (ignoreDlg.m_IgnoreFile == 1)
1483 ignorefile = g_Git.CombinePath(path[i].GetContainingDirectory()) + L"\\.gitignore";
1484 if (!OpenIgnoreFile(hWnd, file, ignorefile))
1485 return false;
1488 CString ignorePattern;
1489 if (ignoreDlg.m_IgnoreType == 0)
1491 if (ignoreDlg.m_IgnoreFile != 1 && !path[i].GetContainingDirectory().GetGitPathString().IsEmpty())
1492 ignorePattern += L'/' + path[i].GetContainingDirectory().GetGitPathString();
1494 ignorePattern += L'/';
1496 if (IsMask)
1497 ignorePattern += L'*' + path[i].GetFileExtension();
1498 else
1499 ignorePattern += path[i].GetFileOrDirectoryName();
1501 // escape [ and ] so that files get ignored correctly
1502 ignorePattern.Replace(L"[", L"\\[");
1503 ignorePattern.Replace(L"]", L"\\]");
1505 bool found = false;
1506 for (size_t j = 0; j < file.m_Items.size(); ++j)
1508 if (file.m_Items[j] == ignorePattern)
1510 found = true;
1511 break;
1514 if (!found)
1516 file.m_Items.push_back(ignorePattern);
1517 ignorePattern += file.m_eol.IsEmpty() ? L"\n" : file.m_eol;
1518 CStringA ignorePatternA = CUnicodeUtils::GetUTF8(ignorePattern);
1519 file.Write(ignorePatternA, ignorePatternA.GetLength());
1522 if (ignoreDlg.m_IgnoreFile == 1)
1523 file.Close();
1526 if (ignoreDlg.m_IgnoreFile != 1)
1527 file.Close();
1529 catch(...)
1531 file.Abort();
1532 return false;
1535 return true;
1537 return false;
1540 static bool Reset(HWND hWnd, const CString& resetTo, int resetType)
1542 CString cmd;
1543 CString type;
1544 switch (resetType)
1546 case 0:
1547 type = L"--soft";
1548 break;
1549 case 1:
1550 type = L"--mixed";
1551 break;
1552 case 2:
1553 type = L"--hard";
1554 break;
1555 case 3:
1557 CProgressDlg progress(CWnd::FromHandle(hWnd));
1558 progress.m_GitCmd = L"git.exe reset --merge";
1559 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1561 if (status)
1563 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&hWnd] { CAppUtils::MergeAbort(hWnd); });
1564 return;
1567 CTGitPath gitPath = g_Git.m_CurrentDir;
1568 if (gitPath.HasSubmodules() && resetType == 2)
1570 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1572 CString sCmd;
1573 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
1574 CAppUtils::RunTortoiseGitProc(sCmd);
1578 return progress.DoModal() == IDOK;
1580 default:
1581 ATLASSERT(false);
1582 resetType = 1;
1583 type = L"--mixed";
1584 break;
1586 cmd.Format(L"git.exe reset %s %s --", (LPCTSTR)type, (LPCTSTR)resetTo);
1588 CProgressDlg progress(CWnd::FromHandle(hWnd));
1589 progress.m_GitCmd = cmd;
1591 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1593 if (status)
1595 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ Reset(hWnd, resetTo, resetType); });
1596 return;
1599 CTGitPath gitPath = g_Git.m_CurrentDir;
1600 if (gitPath.HasSubmodules() && resetType == 2)
1602 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1604 CString sCmd;
1605 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
1606 CAppUtils::RunTortoiseGitProc(sCmd);
1610 if (gitPath.IsBisectActive())
1612 postCmdList.emplace_back(IDI_THUMB_UP, IDS_MENUBISECTGOOD, [] { CAppUtils::RunTortoiseGitProc(L"/command:bisect /good"); });
1613 postCmdList.emplace_back(IDI_THUMB_DOWN, IDS_MENUBISECTBAD, [] { CAppUtils::RunTortoiseGitProc(L"/command:bisect /bad"); });
1614 postCmdList.emplace_back(IDI_BISECT, IDS_MENUBISECTSKIP, [] { CAppUtils::RunTortoiseGitProc(L"/command:bisect /skip"); });
1615 postCmdList.emplace_back(IDI_BISECT_RESET, IDS_MENUBISECTRESET, [] { CAppUtils::RunTortoiseGitProc(L"/command:bisect /reset"); });
1619 INT_PTR ret;
1620 if (g_Git.UsingLibGit2(CGit::GIT_CMD_RESET))
1622 CGitProgressDlg gitdlg(CWnd::FromHandle(hWnd));
1623 ResetProgressCommand resetProgressCommand;
1624 gitdlg.SetCommand(&resetProgressCommand);
1625 resetProgressCommand.m_PostCmdCallback = progress.m_PostCmdCallback;
1626 resetProgressCommand.SetRevision(resetTo);
1627 resetProgressCommand.SetResetType(resetType);
1628 ret = gitdlg.DoModal();
1630 else
1631 ret = progress.DoModal();
1633 return ret == IDOK;
1636 bool CAppUtils::GitReset(HWND hWnd, const CString* CommitHash, int type)
1638 CResetDlg dlg(CWnd::FromHandle(hWnd));
1639 dlg.m_ResetType=type;
1640 dlg.m_ResetToVersion=*CommitHash;
1641 dlg.m_initialRefName = *CommitHash;
1642 if (dlg.DoModal() == IDOK)
1643 return Reset(hWnd, dlg.m_ResetToVersion, dlg.m_ResetType);
1645 return false;
1648 void CAppUtils::DescribeConflictFile(bool mode, bool base,CString &descript)
1650 if(mode == FALSE)
1652 descript.LoadString(IDS_SVNACTION_DELETE);
1653 return;
1655 if(base)
1657 descript.LoadString(IDS_SVNACTION_MODIFIED);
1658 return;
1660 descript.LoadString(IDS_PROC_CREATED);
1663 void CAppUtils::RemoveTempMergeFile(const CTGitPath& path)
1665 ::DeleteFile(CAppUtils::GetMergeTempFile(L"LOCAL", path));
1666 ::DeleteFile(CAppUtils::GetMergeTempFile(L"REMOTE", path));
1667 ::DeleteFile(CAppUtils::GetMergeTempFile(L"BASE", path));
1669 CString CAppUtils::GetMergeTempFile(const CString& type, const CTGitPath &merge)
1671 return g_Git.CombinePath(merge.GetWinPathString() + L'.' + type + merge.GetFileExtension());;
1674 static bool ParseHashesFromLsFile(const BYTE_VECTOR& out, CString& hash1, bool& isFile1, CString& hash2, bool& isFile2, CString& hash3, bool& isFile3)
1676 size_t pos = 0;
1677 CString one;
1678 CString part;
1680 while (pos < out.size())
1682 one.Empty();
1684 CGit::StringAppend(&one, &out[pos], CP_UTF8);
1685 int tabstart = 0;
1686 one.Tokenize(L"\t", tabstart);
1688 tabstart = 0;
1689 part = one.Tokenize(L" ", tabstart); //Tag
1690 CString mode = one.Tokenize(L" ", tabstart); //Mode
1691 part = one.Tokenize(L" ", tabstart); //Hash
1692 CString hash = part;
1693 part = one.Tokenize(L"\t", tabstart); //Stage
1694 int stage = _wtol(part);
1695 if (stage == 1)
1697 hash1 = hash;
1698 isFile1 = _wtol(mode) != 160000;
1700 else if (stage == 2)
1702 hash2 = hash;
1703 isFile2 = _wtol(mode) != 160000;
1705 else if (stage == 3)
1707 hash3 = hash;
1708 isFile3 = _wtol(mode) != 160000;
1709 return true;
1712 pos = out.findNextString(pos);
1715 return false;
1718 void CAppUtils::GetConflictTitles(CString* baseText, CString& mineText, CString& theirsText, bool rebaseActive)
1720 if (baseText)
1721 baseText->LoadString(IDS_PROC_DIFF_BASE);
1722 if (rebaseActive)
1724 CString adminDir;
1725 GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir);
1726 mineText = L"Branch being rebased onto";
1727 if (!CStringUtils::ReadStringFromTextFile(adminDir + L"tgitrebase.active\\onto", mineText))
1729 CGitHash hash;
1730 if (!g_Git.GetHash(hash, L"rebase-apply/onto"))
1731 g_Git.GuessRefForHash(mineText, hash);
1733 theirsText = L"Branch being rebased";
1734 if (!CStringUtils::ReadStringFromTextFile(adminDir + L"tgitrebase.active\\head-name", theirsText))
1736 if (CStringUtils::ReadStringFromTextFile(adminDir + L"rebase-apply/head-name", theirsText))
1737 theirsText = CGit::StripRefName(theirsText);
1739 return;
1742 static const struct {
1743 const wchar_t* headref;
1744 bool guessRef;
1745 UINT theirstext;
1746 } infotexts[] = { { L"MERGE_HEAD", true, IDS_CONFLICT_INFOTEXT }, { L"CHERRY_PICK_HEAD", false, IDS_CONFLICT_INFOTEXT }, { L"REVERT_HEAD", false, IDS_CONFLICT_REVERT } };
1747 mineText = L"HEAD";
1748 theirsText.LoadString(IDS_CONFLICT_REFTOBEMERGED);
1749 for (const auto& infotext : infotexts)
1751 CGitHash hash;
1752 if (!g_Git.GetHash(hash, infotext.headref))
1754 CString guessedRef;
1755 if (!infotext.guessRef)
1756 guessedRef = hash.ToString();
1757 else
1758 g_Git.GuessRefForHash(guessedRef, hash);
1759 theirsText.FormatMessage(infotext.theirstext, infotext.headref, (LPCTSTR)guessedRef);
1760 break;
1765 bool CAppUtils::ConflictEdit(HWND hWnd, CTGitPath& path, bool bAlternativeTool /*= false*/, bool isRebase /*= false*/, HWND resolveMsgHwnd /*= nullptr*/)
1767 CTGitPath merge=path;
1768 CTGitPath directory = merge.GetDirectory();
1770 BYTE_VECTOR vector;
1772 CString cmd;
1773 cmd.Format(L"git.exe ls-files -u -t -z -- \"%s\"", (LPCTSTR)merge.GetGitPathString());
1775 if (g_Git.Run(cmd, &vector))
1776 return FALSE;
1778 CString baseTitle, mineTitle, theirsTitle;
1779 GetConflictTitles(&baseTitle, mineTitle, theirsTitle, isRebase);
1781 CString baseHash, realBaseHash(GIT_REV_ZERO), localHash(GIT_REV_ZERO), remoteHash(GIT_REV_ZERO);
1782 bool baseIsFile = true, localIsFile = true, remoteIsFile = true;
1783 if (ParseHashesFromLsFile(vector, realBaseHash, baseIsFile, localHash, localIsFile, remoteHash, remoteIsFile))
1784 baseHash = realBaseHash;
1786 if (!baseIsFile || !localIsFile || !remoteIsFile)
1788 if (merge.HasAdminDir())
1790 CGit subgit;
1791 subgit.m_CurrentDir = g_Git.CombinePath(merge);
1792 CGitHash hash;
1793 subgit.GetHash(hash, L"HEAD");
1794 baseHash = hash;
1797 CGitDiff::ChangeType changeTypeMine = CGitDiff::Unknown;
1798 CGitDiff::ChangeType changeTypeTheirs = CGitDiff::Unknown;
1800 bool baseOK = false, mineOK = false, theirsOK = false;
1801 CString baseSubject, mineSubject, theirsSubject;
1802 if (merge.HasAdminDir())
1804 CGit subgit;
1805 subgit.m_CurrentDir = g_Git.CombinePath(merge);
1806 CGitDiff::GetSubmoduleChangeType(subgit, baseHash, localHash, baseOK, mineOK, changeTypeMine, baseSubject, mineSubject);
1807 CGitDiff::GetSubmoduleChangeType(subgit, baseHash, remoteHash, baseOK, theirsOK, changeTypeTheirs, baseSubject, theirsSubject);
1809 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)
1811 changeTypeMine = CGitDiff::Identical;
1812 changeTypeTheirs = CGitDiff::NewSubmodule;
1813 baseSubject.LoadString(IDS_CONFLICT_NOSUBMODULE);
1814 mineSubject = baseSubject;
1815 theirsSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1817 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
1819 baseHash = localHash;
1820 baseSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1821 mineSubject = baseSubject;
1822 theirsSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1823 changeTypeMine = CGitDiff::Identical;
1824 changeTypeTheirs = CGitDiff::DeleteSubmodule;
1826 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
1828 baseSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1829 mineSubject = baseSubject;
1830 theirsSubject = baseSubject;
1831 if (baseHash == localHash)
1832 changeTypeMine = CGitDiff::Identical;
1834 else if (baseHash == GIT_REV_ZERO && localHash != GIT_REV_ZERO && remoteHash != GIT_REV_ZERO)
1836 baseOK = true;
1837 mineSubject = baseSubject;
1838 if (remoteIsFile)
1840 theirsSubject.LoadString(IDS_CONFLICT_NOTASUBMODULE);
1841 changeTypeMine = CGitDiff::NewSubmodule;
1843 else
1844 theirsSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1845 if (localIsFile)
1847 mineSubject.LoadString(IDS_CONFLICT_NOTASUBMODULE);
1848 changeTypeTheirs = CGitDiff::NewSubmodule;
1850 else
1851 mineSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1853 else if (baseHash != GIT_REV_ZERO && (localHash == GIT_REV_ZERO || remoteHash == GIT_REV_ZERO))
1855 baseSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1856 if (localHash == GIT_REV_ZERO)
1858 mineSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1859 changeTypeMine = CGitDiff::DeleteSubmodule;
1861 else
1863 mineSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1864 if (localHash == baseHash)
1865 changeTypeMine = CGitDiff::Identical;
1867 if (remoteHash == GIT_REV_ZERO)
1869 theirsSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1870 changeTypeTheirs = CGitDiff::DeleteSubmodule;
1872 else
1874 theirsSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1875 if (remoteHash == baseHash)
1876 changeTypeTheirs = CGitDiff::Identical;
1879 else
1880 return FALSE;
1882 CSubmoduleResolveConflictDlg resolveSubmoduleConflictDialog(CWnd::FromHandle(hWnd));
1883 resolveSubmoduleConflictDialog.SetDiff(merge.GetGitPathString(), isRebase, baseTitle, mineTitle, theirsTitle, baseHash, baseSubject, baseOK, localHash, mineSubject, mineOK, changeTypeMine, remoteHash, theirsSubject, theirsOK, changeTypeTheirs);
1884 resolveSubmoduleConflictDialog.DoModal();
1885 if (resolveSubmoduleConflictDialog.m_bResolved && resolveMsgHwnd)
1887 static UINT WM_REVERTMSG = RegisterWindowMessage(L"GITSLNM_NEEDSREFRESH");
1888 ::PostMessage(resolveMsgHwnd, WM_REVERTMSG, NULL, NULL);
1891 return TRUE;
1894 CTGitPathList list;
1895 if (list.ParserFromLsFile(vector))
1897 MessageBox(hWnd, L"Parse ls-files failed!", L"TortoiseGit", MB_OK | MB_ICONERROR);
1898 return FALSE;
1901 if (list.IsEmpty())
1902 return FALSE;
1904 CTGitPath theirs;
1905 CTGitPath mine;
1906 CTGitPath base;
1908 if (isRebase)
1910 mine.SetFromGit(GetMergeTempFile(L"REMOTE", merge));
1911 theirs.SetFromGit(GetMergeTempFile(L"LOCAL", merge));
1913 else
1915 mine.SetFromGit(GetMergeTempFile(L"LOCAL", merge));
1916 theirs.SetFromGit(GetMergeTempFile(L"REMOTE", merge));
1918 base.SetFromGit(GetMergeTempFile(L"BASE",merge));
1920 CString format = L"git.exe checkout-index --temp --stage=%d -- \"%s\"";
1921 CFile tempfile;
1922 //create a empty file, incase stage is not three
1923 tempfile.Open(mine.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1924 tempfile.Close();
1925 tempfile.Open(theirs.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1926 tempfile.Close();
1927 tempfile.Open(base.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1928 tempfile.Close();
1930 bool b_base=false, b_local=false, b_remote=false;
1932 for (int i = 0; i< list.GetCount(); ++i)
1934 CString outfile;
1935 cmd.Empty();
1936 outfile.Empty();
1938 if( list[i].m_Stage == 1)
1940 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1941 b_base = true;
1942 outfile = base.GetWinPathString();
1945 if( list[i].m_Stage == 2 )
1947 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1948 b_local = true;
1949 outfile = mine.GetWinPathString();
1952 if( list[i].m_Stage == 3 )
1954 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1955 b_remote = true;
1956 outfile = theirs.GetWinPathString();
1958 CString output, err;
1959 if(!outfile.IsEmpty())
1960 if (!g_Git.Run(cmd, &output, &err, CP_UTF8))
1962 CString file;
1963 int start =0 ;
1964 file = output.Tokenize(L"\t", start);
1965 ::MoveFileEx(file,outfile,MOVEFILE_REPLACE_EXISTING|MOVEFILE_COPY_ALLOWED);
1967 else
1968 CMessageBox::Show(hWnd, output + L'\n' + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
1971 if(b_local && b_remote )
1973 merge.SetFromWin(g_Git.CombinePath(merge));
1974 if (isRebase)
1975 return !!CAppUtils::StartExtMerge(bAlternativeTool, base, mine, theirs, merge, baseTitle, mineTitle, theirsTitle, CString(), false, resolveMsgHwnd, true);
1977 return !!CAppUtils::StartExtMerge(bAlternativeTool, base, theirs, mine, merge, baseTitle, theirsTitle, mineTitle, CString(), false, resolveMsgHwnd, true);
1979 else
1981 ::DeleteFile(mine.GetWinPathString());
1982 ::DeleteFile(theirs.GetWinPathString());
1983 if (!b_base)
1984 ::DeleteFile(base.GetWinPathString());
1986 SCOPE_EXIT{
1987 if (b_base)
1988 ::DeleteFile(base.GetWinPathString());
1991 CDeleteConflictDlg dlg(CWnd::FromHandle(hWnd));
1992 if (!isRebase)
1994 DescribeConflictFile(b_local, b_base, dlg.m_LocalStatus);
1995 DescribeConflictFile(b_remote, b_base, dlg.m_RemoteStatus);
1996 dlg.m_LocalHash = mineTitle;
1997 dlg.m_RemoteHash = theirsTitle;
1998 dlg.m_bDiffMine = b_local;
2000 else
2002 DescribeConflictFile(b_local, b_base, dlg.m_RemoteStatus);
2003 DescribeConflictFile(b_remote, b_base, dlg.m_LocalStatus);
2004 dlg.m_LocalHash = theirsTitle;
2005 dlg.m_RemoteHash = mineTitle;
2006 dlg.m_bDiffMine = !b_local;
2008 dlg.m_bShowModifiedButton = b_base;
2009 dlg.m_File = merge;
2010 dlg.m_FileBaseVersion = base;
2011 if(dlg.DoModal() == IDOK)
2013 CString out;
2014 if(dlg.m_bIsDelete)
2015 cmd.Format(L"git.exe rm -- \"%s\"", (LPCTSTR)merge.GetGitPathString());
2016 else
2017 cmd.Format(L"git.exe add -- \"%s\"", (LPCTSTR)merge.GetGitPathString());
2019 if (g_Git.Run(cmd, &out, CP_UTF8))
2021 MessageBox(hWnd, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
2022 return FALSE;
2024 if (!dlg.m_bIsDelete)
2026 path.m_Action |= CTGitPath::LOGACTIONS_ADDED;
2027 path.m_Action &= ~CTGitPath::LOGACTIONS_UNMERGED;
2029 return TRUE;
2031 return FALSE;
2035 bool CAppUtils::IsSSHPutty()
2037 CString sshclient = g_Git.m_Environment.GetEnv(L"GIT_SSH");
2038 sshclient=sshclient.MakeLower();
2039 return sshclient.Find(L"plink", 0) >= 0;
2042 CString CAppUtils::GetClipboardLink(const CString &skipGitPrefix, int paramsCount)
2044 if (!OpenClipboard(nullptr))
2045 return CString();
2047 CString sClipboardText;
2048 HGLOBAL hglb = GetClipboardData(CF_TEXT);
2049 if (hglb)
2051 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
2052 sClipboardText = CString(lpstr);
2053 GlobalUnlock(hglb);
2055 hglb = GetClipboardData(CF_UNICODETEXT);
2056 if (hglb)
2058 LPCTSTR lpstr = (LPCTSTR)GlobalLock(hglb);
2059 sClipboardText = lpstr;
2060 GlobalUnlock(hglb);
2062 CloseClipboard();
2064 if(!sClipboardText.IsEmpty())
2066 if (sClipboardText[0] == L'"' && sClipboardText[sClipboardText.GetLength() - 1] == L'"')
2067 sClipboardText=sClipboardText.Mid(1,sClipboardText.GetLength()-2);
2069 for (const CString& prefix : { L"http://", L"https://", L"git://", L"ssh://", L"git@" })
2071 if (CStringUtils::StartsWith(sClipboardText, prefix) && sClipboardText.GetLength() != prefix.GetLength())
2072 return sClipboardText;
2075 if(sClipboardText.GetLength()>=2)
2076 if (sClipboardText[1] == L':')
2077 if( (sClipboardText[0] >= 'A' && sClipboardText[0] <= 'Z')
2078 || (sClipboardText[0] >= 'a' && sClipboardText[0] <= 'z') )
2079 return sClipboardText;
2081 // trim prefixes like "git clone "
2082 if (!skipGitPrefix.IsEmpty() && CStringUtils::StartsWith(sClipboardText, skipGitPrefix))
2084 sClipboardText = sClipboardText.Mid(skipGitPrefix.GetLength()).Trim();
2085 int spacePos = -1;
2086 while (paramsCount >= 0)
2088 --paramsCount;
2089 spacePos = sClipboardText.Find(L' ', spacePos + 1);
2090 if (spacePos == -1)
2091 break;
2093 if (spacePos > 0 && paramsCount < 0)
2094 sClipboardText.Truncate(spacePos);
2095 return sClipboardText;
2099 return CString();
2102 CString CAppUtils::ChooseRepository(HWND hWnd, const CString* path)
2104 CBrowseFolder browseFolder;
2105 CRegString regLastResopitory = CRegString(L"Software\\TortoiseGit\\TortoiseProc\\LastRepo", L"");
2107 browseFolder.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
2108 CString strCloneDirectory;
2109 if(path)
2110 strCloneDirectory=*path;
2111 else
2112 strCloneDirectory = regLastResopitory;
2114 CString title;
2115 title.LoadString(IDS_CHOOSE_REPOSITORY);
2117 browseFolder.SetInfo(title);
2119 if (browseFolder.Show(hWnd, strCloneDirectory) == CBrowseFolder::OK)
2121 regLastResopitory = strCloneDirectory;
2122 return strCloneDirectory;
2124 else
2125 return CString();
2128 bool CAppUtils::SendPatchMail(HWND hWnd, CTGitPathList& list, bool bIsMainWnd)
2130 CSendMailDlg dlg(CWnd::FromHandle(hWnd));
2132 dlg.m_PathList = list;
2134 if(dlg.DoModal()==IDOK)
2136 if (dlg.m_PathList.IsEmpty())
2137 return FALSE;
2139 CGitProgressDlg progDlg(CWnd::FromHandle(hWnd));
2140 if (bIsMainWnd)
2141 theApp.m_pMainWnd = &progDlg;
2142 SendMailProgressCommand sendMailProgressCommand;
2143 progDlg.SetCommand(&sendMailProgressCommand);
2145 sendMailProgressCommand.SetPathList(dlg.m_PathList);
2146 progDlg.SetItemCount(dlg.m_PathList.GetCount());
2148 CSendMailPatch sendMailPatch(dlg.m_To, dlg.m_CC, dlg.m_Subject, !!dlg.m_bAttachment, !!dlg.m_bCombine);
2149 sendMailProgressCommand.SetSendMailOption(&sendMailPatch);
2151 progDlg.DoModal();
2153 return true;
2155 return false;
2158 bool CAppUtils::SendPatchMail(HWND hWnd, const CString& cmd, const CString& formatpatchoutput, bool bIsMainWnd)
2160 CTGitPathList list;
2161 CString log=formatpatchoutput;
2162 int start=log.Find(cmd);
2163 if(start >=0)
2164 log.Tokenize(L"\n", start);
2165 else
2166 start = 0;
2168 while(start>=0)
2170 CString one = log.Tokenize(L"\n", start);
2171 one=one.Trim();
2172 if (one.IsEmpty() || CStringUtils::StartsWith(one, CString(MAKEINTRESOURCE(IDS_SUCCESS))))
2173 continue;
2174 one.Replace(L'/', L'\\');
2175 CTGitPath path;
2176 path.SetFromWin(one);
2177 list.AddPath(path);
2179 if (!list.IsEmpty())
2180 return SendPatchMail(hWnd, list, bIsMainWnd);
2181 else
2183 CMessageBox::Show(hWnd, IDS_ERR_NOPATCHES, IDS_APPNAME, MB_ICONINFORMATION);
2184 return true;
2189 int CAppUtils::GetLogOutputEncode(CGit *pGit)
2191 CString output;
2192 output = pGit->GetConfigValue(L"i18n.logOutputEncoding");
2193 if(output.IsEmpty())
2194 return CUnicodeUtils::GetCPCode(pGit->GetConfigValue(L"i18n.commitencoding"));
2195 else
2196 return CUnicodeUtils::GetCPCode(output);
2199 bool CAppUtils::MessageContainsConflictHints(HWND hWnd, const CString& message)
2201 bool stripComments = (CRegDWORD(L"Software\\TortoiseGit\\StripCommentedLines", FALSE) == TRUE);
2202 if (stripComments)
2203 return false;
2204 CString cleanupMode = g_Git.GetConfigValue(L"core.cleanup", L"default");
2205 if (cleanupMode == L"verbatim" || cleanupMode == L"whitespace" || cleanupMode == L"scissors")
2206 return false;
2207 TCHAR commentChar = L'#';
2208 CString commentCharValue = g_Git.GetConfigValue(L"core.commentchar");
2209 if (!commentCharValue.IsEmpty())
2210 commentChar = commentCharValue[0];
2212 CString conflictsHint;
2213 conflictsHint.Format(L"\n%c Conflicts:\n%c\t", commentChar, commentChar);
2215 if (message.Find(conflictsHint) <= 0)
2216 return false;
2218 BOOL dontaskagainchecked = FALSE;
2219 if (CMessageBox::ShowCheck(hWnd, IDS_CONFLICT_HINT_IN_COMMIT_MESSAGE, IDS_APPNAME, 2, IDI_QUESTION, IDS_IGNOREBUTTON, IDS_ABORTBUTTON, 0, L"CommitMessageContainsConflictHint", IDS_MSGBOX_DONOTSHOWAGAIN, &dontaskagainchecked) == 2)
2221 if (dontaskagainchecked)
2222 CMessageBox::RemoveRegistryKey(L"CommitMessageContainsConflictHint");
2223 return true;
2225 return false;
2228 int CAppUtils::SaveCommitUnicodeFile(const CString& filename, CString &message)
2232 CFile file(filename, CFile::modeReadWrite | CFile::modeCreate);
2233 int cp = CUnicodeUtils::GetCPCode(g_Git.GetConfigValue(L"i18n.commitencoding"));
2235 bool stripComments = (CRegDWORD(L"Software\\TortoiseGit\\StripCommentedLines", FALSE) == TRUE);
2236 TCHAR commentChar = L'#';
2237 if (stripComments)
2239 CString commentCharValue = g_Git.GetConfigValue(L"core.commentchar");
2240 if (!commentCharValue.IsEmpty())
2241 commentChar = commentCharValue[0];
2244 bool sanitize = (CRegDWORD(L"Software\\TortoiseGit\\SanitizeCommitMsg", TRUE) == TRUE);
2245 if (sanitize)
2246 message.Trim(L" \r\n");
2248 int len = message.GetLength();
2249 int start = 0;
2250 int emptyLineCnt = 0;
2251 while (start >= 0 && start < len)
2253 int oldStart = start;
2254 start = message.Find(L'\n', oldStart);
2255 CString line = message.Mid(oldStart);
2256 if (start != -1)
2258 line.Truncate(start - oldStart);
2259 ++start; // move forward so we don't find the same char again
2261 if (stripComments && (!line.IsEmpty() && line.GetAt(0) == commentChar) || (start < 0 && line.IsEmpty()))
2262 continue;
2263 line.TrimRight(L" \r");
2264 if (sanitize)
2266 if (line.IsEmpty())
2268 ++emptyLineCnt;
2269 continue;
2271 if (emptyLineCnt) // squash multiple newlines
2272 file.Write("\n", 1);
2273 emptyLineCnt = 0;
2275 CStringA lineA = CUnicodeUtils::GetMulti(line + L'\n', cp);
2276 file.Write((LPCSTR)lineA, lineA.GetLength());
2278 file.Close();
2279 return 0;
2281 catch (CFileException *e)
2283 e->Delete();
2284 return -1;
2288 bool DoPull(HWND hWnd, const CString& url, bool bAutoLoad, BOOL bFetchTags, bool bNoFF, bool bFFonly, bool bSquash, bool bNoCommit, int* nDepth, BOOL bPrune, const CString& remoteBranchName, bool showPush, bool showStashPop, bool bUnrelated)
2290 if (bAutoLoad)
2291 CAppUtils::LaunchPAgent(hWnd, nullptr, &url);
2293 CGitHash hashOld;
2294 if (g_Git.GetHash(hashOld, L"HEAD"))
2296 MessageBox(hWnd, g_Git.GetGitLastErr(L"Could not get HEAD hash."), L"TortoiseGit", MB_ICONERROR);
2297 return false;
2300 CString args;
2301 if (CRegDWORD(L"Software\\TortoiseGit\\PullRebaseBehaviorLike1816", FALSE) == FALSE)
2302 args += L" --no-rebase";
2304 if (bFetchTags == BST_UNCHECKED)
2305 args += L" --no-tags";
2306 else if (bFetchTags == BST_CHECKED)
2307 args += L" --tags";
2309 if (bNoFF)
2310 args += L" --no-ff";
2312 if (bFFonly)
2313 args += L" --ff-only";
2315 if (bSquash)
2316 args += L" --squash";
2318 if (bNoCommit)
2319 args += L" --no-commit";
2321 if (nDepth)
2322 args.AppendFormat(L" --depth %d", *nDepth);
2324 if (bPrune == BST_CHECKED)
2325 args += L" --prune";
2326 else if (bPrune == BST_UNCHECKED)
2327 args += L" --no-prune";
2329 if (bUnrelated)
2330 args += L" --allow-unrelated-histories";
2332 CString cmd;
2333 cmd.Format(L"git.exe pull --progress -v%s \"%s\" %s", (LPCTSTR)args, (LPCTSTR)url, (LPCTSTR)remoteBranchName);
2334 CProgressDlg progress(CWnd::FromHandle(hWnd));
2335 progress.m_GitCmd = cmd;
2337 CGitHash hashNew; // declare outside lambda, because it is captured by reference
2338 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2340 if (status)
2342 int hasConflicts = g_Git.HasWorkingTreeConflicts();
2343 if (hasConflicts < 0)
2344 CMessageBox::Show(hWnd, g_Git.GetGitLastErr(L"Checking for conflicts failed.", CGit::GIT_CMD_CHECKCONFLICTS), L"TortoiseGit", MB_ICONEXCLAMATION);
2345 else if (hasConflicts)
2347 CMessageBox::ShowCheck(hWnd, IDS_NEED_TO_RESOLVE_CONFLICTS_HINT, IDS_APPNAME, MB_ICONINFORMATION, L"MergeConflictsNeedsCommit", IDS_MSGBOX_DONOTSHOWAGAIN);
2348 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
2350 CString sCmd;
2351 sCmd.Format(L"/command:resolve /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
2352 CAppUtils::RunTortoiseGitProc(sCmd);
2355 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUCOMMIT, []
2357 CString sCmd;
2358 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
2359 CAppUtils::RunTortoiseGitProc(sCmd);
2361 return;
2364 if (CAppUtils::IsGitVersionNewerOrEqual(hWnd, 2, 9))
2366 STRING_VECTOR remotes;
2367 g_Git.GetRemoteList(remotes);
2368 if (std::find(remotes.begin(), remotes.end(), url) != remotes.end())
2370 CString currentBranch;
2371 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, currentBranch))
2372 currentBranch.Empty();
2373 CString remoteRef = L"remotes/" + url + L"/" + remoteBranchName;
2374 if (!currentBranch.IsEmpty() && remoteBranchName.IsEmpty())
2376 CString pullRemote, pullBranch;
2377 g_Git.GetRemoteTrackedBranch(currentBranch, pullRemote, pullBranch);
2378 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
2379 remoteRef = L"remotes/" + pullRemote + L"/" + pullBranch;
2381 CGitHash common;
2382 g_Git.IsFastForward(L"HEAD", remoteRef, &common);
2383 if (common.IsEmpty())
2384 postCmdList.emplace_back(IDI_MERGE, IDS_MERGE_UNRELATED, [=, &hWnd] { DoPull(hWnd, url, bAutoLoad, bFetchTags, bNoFF, bFFonly, bSquash, bNoCommit, nDepth, bPrune, remoteBranchName, showPush, showStashPop, true); });
2388 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&hWnd]{ CAppUtils::Pull(hWnd); });
2389 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSTASHSAVE, [&hWnd]{ CAppUtils::StashSave(hWnd, L"", true); });
2390 return;
2393 if (showStashPop)
2394 postCmdList.emplace_back(IDI_RELOCATE, IDS_MENUSTASHPOP, [&hWnd]{ CAppUtils::StashPop(hWnd); });
2396 if (g_Git.GetHash(hashNew, L"HEAD"))
2397 MessageBox(hWnd, g_Git.GetGitLastErr(L"Could not get HEAD hash after pulling."), L"TortoiseGit", MB_ICONERROR);
2398 else
2400 postCmdList.emplace_back(IDI_DIFF, IDS_PROC_PULL_DIFFS, [&]
2402 CString sCmd;
2403 sCmd.Format(L"/command:showcompare /path:\"%s\" /revision1:%s /revision2:%s", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)hashOld.ToString(), (LPCTSTR)hashNew.ToString());
2404 CAppUtils::RunTortoiseGitProc(sCmd);
2406 postCmdList.emplace_back(IDI_LOG, IDS_PROC_PULL_LOG, [&]
2408 CString sCmd;
2409 sCmd.Format(L"/command:log /path:\"%s\" /range:%s", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)(hashOld.ToString() + L".." + hashNew.ToString()));
2410 CAppUtils::RunTortoiseGitProc(sCmd);
2414 if (showPush)
2415 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, [&hWnd]{ CAppUtils::Push(hWnd); });
2417 CTGitPath gitPath = g_Git.m_CurrentDir;
2418 if (gitPath.HasSubmodules())
2420 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
2422 CString sCmd;
2423 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
2424 CAppUtils::RunTortoiseGitProc(sCmd);
2429 INT_PTR ret = progress.DoModal();
2431 if (ret == IDOK && progress.m_GitStatus == 1 && progress.m_LogText.Find(L"CONFLICT") >= 0 && CMessageBox::Show(hWnd, IDS_SEECHANGES, IDS_APPNAME, MB_YESNO | MB_ICONINFORMATION) == IDYES)
2433 cmd.Format(L"/command:repostatus /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
2434 CAppUtils::RunTortoiseGitProc(cmd);
2436 return true;
2439 return ret == IDOK;
2442 bool CAppUtils::Pull(HWND hWnd, bool showPush, bool showStashPop)
2444 if (IsTGitRebaseActive(hWnd))
2445 return false;
2447 CPullFetchDlg dlg(CWnd::FromHandle(hWnd));
2448 dlg.m_IsPull = TRUE;
2449 if (dlg.DoModal() == IDOK)
2451 // "git.exe pull --rebase" is not supported, never and ever. So, adapting it to Fetch & Rebase.
2452 if (dlg.m_bRebase)
2453 return DoFetch(hWnd,
2454 dlg.m_RemoteURL,
2455 FALSE, // Fetch all remotes
2456 dlg.m_bAutoLoad == BST_CHECKED,
2457 dlg.m_bPrune,
2458 dlg.m_bDepth == BST_CHECKED,
2459 dlg.m_nDepth,
2460 dlg.m_bFetchTags,
2461 dlg.m_RemoteBranchName,
2462 dlg.m_bRebaseActivatedInConfigForPull ? 2 : 1, // Rebase after fetching
2463 dlg.m_bRebasePreserveMerges == TRUE); // Preserve merges on rebase
2465 return DoPull(hWnd, dlg.m_RemoteURL, dlg.m_bAutoLoad == BST_CHECKED, dlg.m_bFetchTags, dlg.m_bNoFF == BST_CHECKED, dlg.m_bFFonly == BST_CHECKED, dlg.m_bSquash == BST_CHECKED, dlg.m_bNoCommit == BST_CHECKED, dlg.m_bDepth ? &dlg.m_nDepth : nullptr, dlg.m_bPrune, dlg.m_RemoteBranchName, showPush, showStashPop, false);
2468 return false;
2471 bool CAppUtils::RebaseAfterFetch(HWND hWnd, const CString& upstream, int rebase, bool preserveMerges)
2473 while (true)
2475 CRebaseDlg dlg(CWnd::FromHandle(hWnd));
2476 if (!upstream.IsEmpty())
2477 dlg.m_Upstream = upstream;
2478 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENULOG)));
2479 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUPUSH)));
2480 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUDESSENDMAIL)));
2481 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUREBASE)));
2482 dlg.m_bRebaseAutoStart = (rebase == 2);
2483 dlg.m_bPreserveMerges = preserveMerges;
2484 INT_PTR response = dlg.DoModal();
2485 if (response == IDOK)
2486 return true;
2487 else if (response == IDC_REBASE_POST_BUTTON)
2489 CString cmd = L"/command:log";
2490 cmd += L" /path:\"" + g_Git.m_CurrentDir + L'"';
2491 CAppUtils::RunTortoiseGitProc(cmd);
2492 return true;
2494 else if (response == IDC_REBASE_POST_BUTTON + 1)
2495 return Push(hWnd);
2496 else if (response == IDC_REBASE_POST_BUTTON + 2)
2498 CString cmd, out, err;
2499 cmd.Format(L"git.exe format-patch -o \"%s\" %s..%s",
2500 (LPCTSTR)g_Git.m_CurrentDir,
2501 (LPCTSTR)g_Git.FixBranchName(dlg.m_Upstream),
2502 (LPCTSTR)g_Git.FixBranchName(dlg.m_Branch));
2503 if (g_Git.Run(cmd, &out, &err, CP_UTF8))
2505 CMessageBox::Show(hWnd, out + L'\n' + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
2506 return false;
2508 CAppUtils::SendPatchMail(hWnd, cmd, out);
2509 return true;
2511 else if (response == IDC_REBASE_POST_BUTTON + 3)
2512 continue;
2513 else if (response == IDCANCEL)
2514 return false;
2515 return false;
2519 static bool DoFetch(HWND hWnd, 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)
2521 if (loadPuttyAgent)
2523 if (fetchAllRemotes)
2525 STRING_VECTOR list;
2526 g_Git.GetRemoteList(list);
2528 for (const auto& remote : list)
2529 CAppUtils::LaunchPAgent(hWnd, nullptr, &remote);
2531 else
2532 CAppUtils::LaunchPAgent(hWnd, nullptr, &url);
2535 CString upstream = L"FETCH_HEAD";
2536 CGitHash oldUpstreamHash;
2537 if (runRebase)
2539 STRING_VECTOR list;
2540 g_Git.GetRemoteList(list);
2541 for (auto it = list.cbegin(); it != list.cend(); ++it)
2543 if (url == *it)
2545 upstream.Empty();
2546 if (remoteBranch.IsEmpty()) // pulldlg might clear remote branch if its the default tracked branch
2548 CString currentBranch;
2549 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, currentBranch))
2550 currentBranch.Empty();
2551 if (!currentBranch.IsEmpty() && remoteBranch.IsEmpty())
2553 CString pullRemote, pullBranch;
2554 g_Git.GetRemoteTrackedBranch(currentBranch, pullRemote, pullBranch);
2555 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty() && pullRemote == url) // pullRemote == url is just another safety-check and should not be needed
2556 upstream = L"remotes/" + *it + L'/' + pullBranch;
2559 else
2560 upstream = L"remotes/" + *it + L'/' + remoteBranch;
2562 g_Git.GetHash(oldUpstreamHash, upstream);
2563 break;
2568 CString cmd, arg;
2569 arg = L" --progress";
2571 if (bDepth)
2572 arg.AppendFormat(L" --depth %d", nDepth);
2574 if (prune == TRUE)
2575 arg += L" --prune";
2576 else if (prune == FALSE)
2577 arg += L" --no-prune";
2579 if (fetchTags == 1)
2580 arg += L" --tags";
2581 else if (fetchTags == 0)
2582 arg += L" --no-tags";
2584 if (fetchAllRemotes)
2585 cmd.Format(L"git.exe fetch --all -v%s", (LPCTSTR)arg);
2586 else
2587 cmd.Format(L"git.exe fetch -v%s \"%s\" %s", (LPCTSTR)arg, (LPCTSTR)url, (LPCTSTR)remoteBranch);
2589 CProgressDlg progress(CWnd::FromHandle(hWnd));
2590 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2592 if (status)
2594 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ DoFetch(hWnd, url, fetchAllRemotes, loadPuttyAgent, prune, bDepth, nDepth, fetchTags, remoteBranch, runRebase, rebasePreserveMerges); });
2595 if (fetchAllRemotes)
2596 postCmdList.emplace_back(IDI_LOG, IDS_MENULOG, []
2598 CString cmd = L"/command:log";
2599 cmd += L" /path:\"" + g_Git.m_CurrentDir + L'"';
2600 CAppUtils::RunTortoiseGitProc(cmd);
2602 return;
2605 postCmdList.emplace_back(IDI_LOG, IDS_MENULOG, []
2607 CString cmd = L"/command:log";
2608 cmd += L" /path:\"" + g_Git.m_CurrentDir + L'"';
2609 CAppUtils::RunTortoiseGitProc(cmd);
2612 postCmdList.emplace_back(IDI_REVERT, IDS_PROC_RESET, [&hWnd]
2614 CString pullRemote, pullBranch;
2615 g_Git.GetRemoteTrackedBranchForHEAD(pullRemote, pullBranch);
2616 CString defaultUpstream;
2617 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
2618 defaultUpstream.Format(L"remotes/%s/%s", (LPCTSTR)pullRemote, (LPCTSTR)pullBranch);
2619 CAppUtils::GitReset(hWnd, &defaultUpstream, 2);
2622 postCmdList.emplace_back(IDI_PULL, IDS_MENUFETCH, [&hWnd]{ CAppUtils::Fetch(hWnd); });
2624 if (!runRebase && !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir))
2625 postCmdList.emplace_back(IDI_REBASE, IDS_MENUREBASE, [&]{ runRebase = false; CAppUtils::RebaseAfterFetch(hWnd); });
2628 progress.m_GitCmd = cmd;
2630 if (g_Git.UsingLibGit2(CGit::GIT_CMD_FETCH))
2632 CGitProgressDlg gitdlg(CWnd::FromHandle(hWnd));
2633 FetchProgressCommand fetchProgressCommand;
2634 if (!fetchAllRemotes)
2635 fetchProgressCommand.SetUrl(url);
2636 gitdlg.SetCommand(&fetchProgressCommand);
2637 fetchProgressCommand.m_PostCmdCallback = progress.m_PostCmdCallback;
2638 fetchProgressCommand.SetAutoTag(fetchTags == 1 ? GIT_REMOTE_DOWNLOAD_TAGS_ALL : fetchTags == 2 ? GIT_REMOTE_DOWNLOAD_TAGS_AUTO : GIT_REMOTE_DOWNLOAD_TAGS_NONE);
2639 fetchProgressCommand.SetPrune(prune == BST_CHECKED ? GIT_FETCH_PRUNE : prune == BST_INDETERMINATE ? GIT_FETCH_PRUNE_UNSPECIFIED : GIT_FETCH_NO_PRUNE);
2640 if (!fetchAllRemotes)
2641 fetchProgressCommand.SetRefSpec(remoteBranch);
2642 return gitdlg.DoModal() == IDOK;
2645 progress.m_PostExecCallback = [&](DWORD& exitCode, CString&)
2647 if (exitCode || !runRebase)
2648 return;
2650 CGitHash remoteBranchHash;
2651 g_Git.GetHash(remoteBranchHash, upstream);
2653 if (runRebase == 1)
2655 CGitHash headHash, commonAcestor;
2656 if (!g_Git.GetHash(headHash, L"HEAD") && (remoteBranchHash == headHash || (g_Git.IsFastForward(upstream, L"HEAD", &commonAcestor) && commonAcestor == remoteBranchHash)) && CMessageBox::ShowCheck(hWnd, IDS_REBASE_CURRENTBRANCHUPTODATE, IDS_APPNAME, MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2, L"OpenRebaseRemoteBranchEqualsHEAD", IDS_MSGBOX_DONOTSHOWAGAIN) == IDNO)
2657 return;
2659 if (remoteBranchHash == oldUpstreamHash && !oldUpstreamHash.IsEmpty() && CMessageBox::ShowCheck(hWnd, IDS_REBASE_BRANCH_UNCHANGED, IDS_APPNAME, MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2, L"OpenRebaseRemoteBranchUnchanged", IDS_MSGBOX_DONOTSHOWAGAIN) == IDNO)
2660 return;
2663 if (runRebase == 1 && g_Git.IsFastForward(L"HEAD", upstream))
2665 UINT ret = CMessageBox::ShowCheck(hWnd, IDS_REBASE_BRANCH_FF, IDS_APPNAME, 2, IDI_QUESTION, IDS_MERGEBUTTON, IDS_REBASEBUTTON, IDS_ABORTBUTTON, L"OpenRebaseRemoteBranchFastForwards", IDS_MSGBOX_DONOTSHOWAGAIN);
2666 if (ret == 3)
2667 return;
2668 if (ret == 1)
2670 CProgressDlg mergeProgress(CWnd::FromHandle(hWnd));
2671 mergeProgress.m_GitCmd = L"git.exe merge --ff-only " + upstream;
2672 mergeProgress.m_AutoClose = AUTOCLOSE_IF_NO_ERRORS;
2673 mergeProgress.m_PostCmdCallback = [](DWORD status, PostCmdList& postCmdList)
2675 if (status && g_Git.HasWorkingTreeConflicts())
2677 // there are conflict files
2678 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
2680 CString sCmd;
2681 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
2682 CAppUtils::RunTortoiseGitProc(sCmd);
2686 mergeProgress.DoModal();
2687 return;
2691 CAppUtils::RebaseAfterFetch(hWnd, upstream, runRebase, rebasePreserveMerges);
2694 return progress.DoModal() == IDOK;
2697 bool CAppUtils::Fetch(HWND hWnd, const CString& remoteName, bool allRemotes)
2699 CPullFetchDlg dlg(CWnd::FromHandle(hWnd));
2700 dlg.m_PreSelectRemote = remoteName;
2701 dlg.m_IsPull=FALSE;
2702 dlg.m_bAllRemotes = allRemotes;
2704 if(dlg.DoModal()==IDOK)
2705 return DoFetch(hWnd, 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);
2707 return false;
2710 bool CAppUtils::DoPush(HWND hWnd, 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)
2712 CString error;
2713 DWORD exitcode = 0xFFFFFFFF;
2714 if (CHooks::Instance().PrePush(g_Git.m_CurrentDir, exitcode, error))
2716 if (exitcode)
2718 CString temp;
2719 temp.Format(IDS_ERR_HOOKFAILED, (LPCTSTR)error);
2720 MessageBox(hWnd, temp, L"TortoiseGit", MB_OK | MB_ICONERROR);
2721 return false;
2725 int iRecurseSubmodules = 0;
2726 if (IsGitVersionNewerOrEqual(hWnd, 2, 7))
2728 CString sRecurseSubmodules = g_Git.GetConfigValue(L"push.recurseSubmodules");
2729 if (sRecurseSubmodules == L"check")
2730 iRecurseSubmodules = 1;
2731 else if (sRecurseSubmodules == L"on-demand")
2732 iRecurseSubmodules = 2;
2735 CString arg;
2736 if (pack)
2737 arg += L"--thin ";
2738 if (tags && !allBranches)
2739 arg += L"--tags ";
2740 if (force)
2741 arg += L"--force ";
2742 if (forceWithLease)
2743 arg += L"--force-with-lease ";
2744 if (setUpstream)
2745 arg += L"--set-upstream ";
2746 if (recurseSubmodules == 0 && recurseSubmodules != iRecurseSubmodules)
2747 arg += L"--recurse-submodules=no ";
2748 if (recurseSubmodules == 1 && recurseSubmodules != iRecurseSubmodules)
2749 arg += L"--recurse-submodules=check ";
2750 if (recurseSubmodules == 2 && recurseSubmodules != iRecurseSubmodules)
2751 arg += L"--recurse-submodules=on-demand ";
2753 arg += L"--progress ";
2755 CProgressDlg progress(CWnd::FromHandle(hWnd));
2757 STRING_VECTOR remotesList;
2758 if (allRemotes)
2759 g_Git.GetRemoteList(remotesList);
2760 else
2761 remotesList.push_back(remote);
2763 for (unsigned int i = 0; i < remotesList.size(); ++i)
2765 if (autoloadKey)
2766 CAppUtils::LaunchPAgent(hWnd, nullptr, &remotesList[i]);
2768 CString cmd;
2769 if (allBranches)
2771 cmd.Format(L"git.exe push --all %s\"%s\"",
2772 (LPCTSTR)arg,
2773 (LPCTSTR)remotesList[i]);
2775 if (tags)
2777 progress.m_GitCmdList.push_back(cmd);
2778 cmd.Format(L"git.exe push --tags %s\"%s\"", (LPCTSTR)arg, (LPCTSTR)remotesList[i]);
2781 else
2783 cmd.Format(L"git.exe push %s\"%s\" %s",
2784 (LPCTSTR)arg,
2785 (LPCTSTR)remotesList[i],
2786 (LPCTSTR)localBranch);
2787 if (!remoteBranch.IsEmpty())
2789 cmd += L":";
2790 cmd += remoteBranch;
2793 progress.m_GitCmdList.push_back(cmd);
2795 if (!allBranches && !!CRegDWORD(L"Software\\TortoiseGit\\ShowBranchRevisionNumber", FALSE))
2797 cmd.Format(L"git.exe rev-list --count --first-parent %s", (LPCTSTR)localBranch);
2798 progress.m_GitCmdList.push_back(cmd);
2802 CString superprojectRoot;
2803 GitAdminDir::HasAdminDir(g_Git.m_CurrentDir, false, &superprojectRoot);
2804 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2806 // need to execute hooks as those might be needed by post action commands
2807 DWORD exitcode = 0xFFFFFFFF;
2808 CString error;
2809 if (CHooks::Instance().PostPush(g_Git.m_CurrentDir, exitcode, error))
2811 if (exitcode)
2813 CString temp;
2814 temp.Format(IDS_ERR_HOOKFAILED, (LPCTSTR)error);
2815 MessageBox(hWnd, temp, L"TortoiseGit", MB_OK | MB_ICONERROR);
2819 if (status)
2821 bool rejected = progress.GetLogText().Find(L"! [rejected]") > 0;
2822 if (rejected)
2824 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&hWnd]{ Pull(hWnd, true); });
2825 postCmdList.emplace_back(IDI_PULL, IDS_MENUFETCH, [&]{ Fetch(hWnd, allRemotes ? L"" : remote, allRemotes); });
2827 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, [&]{ Push(hWnd, localBranch); });
2828 return;
2831 postCmdList.emplace_back(IDS_PROC_REQUESTPULL, [&]{ RequestPull(hWnd, remoteBranch); });
2832 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, [&]{ Push(hWnd, localBranch); });
2833 postCmdList.emplace_back(IDI_SWITCH, IDS_MENUSWITCH, [&hWnd]{ Switch(hWnd); });
2834 if (!superprojectRoot.IsEmpty())
2836 postCmdList.emplace_back(IDI_COMMIT, IDS_PROC_COMMIT_SUPERPROJECT, [&]
2838 CString sCmd;
2839 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)superprojectRoot);
2840 RunTortoiseGitProc(sCmd);
2845 INT_PTR ret = progress.DoModal();
2846 return ret == IDOK;
2849 bool CAppUtils::Push(HWND hWnd, const CString& selectLocalBranch)
2851 CPushDlg dlg(CWnd::FromHandle(hWnd));
2852 dlg.m_BranchSourceName = selectLocalBranch;
2854 if (dlg.DoModal() == IDOK)
2855 return DoPush(hWnd, !!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);
2857 return FALSE;
2860 bool CAppUtils::RequestPull(HWND hWnd, const CString& endrevision, const CString& repositoryUrl, bool bIsMainWnd)
2862 CRequestPullDlg dlg(CWnd::FromHandle(hWnd));
2863 dlg.m_RepositoryURL = repositoryUrl;
2864 dlg.m_EndRevision = endrevision;
2865 if (dlg.DoModal()==IDOK)
2867 CString cmd;
2868 cmd.Format(L"git.exe request-pull %s \"%s\" %s", (LPCTSTR)dlg.m_StartRevision, (LPCTSTR)dlg.m_RepositoryURL, (LPCTSTR)dlg.m_EndRevision);
2870 CSysProgressDlg sysProgressDlg;
2871 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
2872 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_CREATINGPULLREUQEST)));
2873 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
2874 sysProgressDlg.SetShowProgressBar(false);
2875 sysProgressDlg.ShowModeless(hWnd, true);
2877 CString tempFileName = GetTempFile();
2878 CString err;
2879 DeleteFile(tempFileName);
2880 CreateDirectory(tempFileName, nullptr);
2881 tempFileName += L"\\pullrequest.txt";
2882 if (g_Git.RunLogFile(cmd, tempFileName, &err))
2884 CString msg;
2885 msg.LoadString(IDS_ERR_PULLREUQESTFAILED);
2886 MessageBox(hWnd, msg + L'\n' + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
2887 return false;
2890 if (sysProgressDlg.HasUserCancelled())
2892 CMessageBox::Show(hWnd, IDS_USERCANCELLED, IDS_APPNAME, MB_OK);
2893 ::DeleteFile(tempFileName);
2894 return false;
2897 sysProgressDlg.Stop();
2899 if (dlg.m_bSendMail)
2901 CSendMailDlg sendmaildlg(CWnd::FromHandle(hWnd));
2902 sendmaildlg.m_PathList = CTGitPathList(CTGitPath(tempFileName));
2903 sendmaildlg.m_bCustomSubject = true;
2905 if (sendmaildlg.DoModal() == IDOK)
2907 if (sendmaildlg.m_PathList.IsEmpty())
2908 return FALSE;
2910 CGitProgressDlg progDlg(CWnd::FromHandle(hWnd));
2911 if (bIsMainWnd)
2912 theApp.m_pMainWnd = &progDlg;
2913 SendMailProgressCommand sendMailProgressCommand;
2914 progDlg.SetCommand(&sendMailProgressCommand);
2916 sendMailProgressCommand.SetPathList(sendmaildlg.m_PathList);
2917 progDlg.SetItemCount(sendmaildlg.m_PathList.GetCount());
2919 CSendMailCombineable sendMailCombineable(sendmaildlg.m_To, sendmaildlg.m_CC, sendmaildlg.m_Subject, !!sendmaildlg.m_bAttachment, !!sendmaildlg.m_bCombine);
2920 sendMailProgressCommand.SetSendMailOption(&sendMailCombineable);
2922 progDlg.DoModal();
2924 return true;
2926 return false;
2929 CAppUtils::LaunchAlternativeEditor(tempFileName);
2931 return true;
2934 void CAppUtils::RemoveTrailSlash(CString &path)
2936 if(path.IsEmpty())
2937 return ;
2939 // For URL, do not trim the slash just after the host name component.
2940 int index = path.Find(L"://");
2941 if (index >= 0)
2943 index += 4;
2944 index = path.Find(L'/', index);
2945 if (index == path.GetLength() - 1)
2946 return;
2949 while (path[path.GetLength() - 1] == L'\\' || path[path.GetLength() - 1] == L'/')
2951 path.Truncate(path.GetLength() - 1);
2952 if(path.IsEmpty())
2953 return;
2957 bool CAppUtils::CheckUserData(HWND hWnd)
2959 while(g_Git.GetUserName().IsEmpty() || g_Git.GetUserEmail().IsEmpty())
2961 if (CMessageBox::Show(hWnd, IDS_PROC_NOUSERDATA, IDS_APPNAME, MB_YESNO | MB_ICONERROR) == IDYES)
2963 CTGitPath path(g_Git.m_CurrentDir);
2964 CSettings dlg(IDS_PROC_SETTINGS_TITLE, &path, CWnd::FromHandle(hWnd));
2965 dlg.SetTreeViewMode(TRUE, TRUE, TRUE);
2966 dlg.SetTreeWidth(220);
2967 dlg.m_DefaultPage = L"gitconfig";
2969 dlg.DoModal();
2970 dlg.HandleRestart();
2973 else
2974 return false;
2977 return true;
2980 BOOL CAppUtils::Commit(HWND hWnd, const CString& bugid, BOOL bWholeProject, CString &sLogMsg,
2981 CTGitPathList &pathList,
2982 bool bSelectFilesForCommit)
2984 bool bFailed = true;
2986 if (!CheckUserData(hWnd))
2987 return false;
2989 while (bFailed)
2991 bFailed = false;
2992 CCommitDlg dlg(CWnd::FromHandle(hWnd));
2993 dlg.m_sBugID = bugid;
2995 dlg.m_bWholeProject = bWholeProject;
2997 dlg.m_sLogMessage = sLogMsg;
2998 dlg.m_pathList = pathList;
2999 dlg.m_bSelectFilesForCommit = bSelectFilesForCommit;
3000 if (dlg.DoModal() == IDOK)
3002 if (dlg.m_pathList.IsEmpty())
3003 return false;
3004 // if the user hasn't changed the list of selected items
3005 // we don't use that list. Because if we would use the list
3006 // of pre-checked items, the dialog would show different
3007 // checked items on the next startup: it would only try
3008 // to check the parent folder (which might not even show)
3009 // instead, we simply use an empty list and let the
3010 // default checking do its job.
3011 sLogMsg = dlg.m_sLogMessage;
3012 bSelectFilesForCommit = true;
3014 switch (dlg.m_PostCmd)
3016 case GIT_POSTCOMMIT_CMD_DCOMMIT:
3017 CAppUtils::SVNDCommit(hWnd);
3018 break;
3019 case GIT_POSTCOMMIT_CMD_PUSH:
3020 CAppUtils::Push(hWnd);
3021 break;
3022 case GIT_POSTCOMMIT_CMD_CREATETAG:
3023 CAppUtils::CreateBranchTag(hWnd, TRUE);
3024 break;
3025 case GIT_POSTCOMMIT_CMD_PULL:
3026 CAppUtils::Pull(hWnd, true);
3027 break;
3028 default:
3029 break;
3032 // CGitProgressDlg progDlg;
3033 // progDlg.SetChangeList(dlg.m_sChangeList, !!dlg.m_bKeepChangeList);
3034 // if (parser.HasVal(L"closeonend"))
3035 // progDlg.SetAutoClose(parser.GetLongVal(L"closeonend"));
3036 // progDlg.SetCommand(CGitProgressDlg::GitProgress_Commit);
3037 // progDlg.SetPathList(dlg.m_pathList);
3038 // progDlg.SetCommitMessage(dlg.m_sLogMessage);
3039 // progDlg.SetDepth(dlg.m_bRecursive ? Git_depth_infinity : svn_depth_empty);
3040 // progDlg.SetSelectedList(dlg.m_selectedPathList);
3041 // progDlg.SetItemCount(dlg.m_itemsCount);
3042 // progDlg.SetBugTraqProvider(dlg.m_BugTraqProvider);
3043 // progDlg.DoModal();
3044 // CRegDWORD err = CRegDWORD(L"Software\\TortoiseGit\\ErrorOccurred", FALSE);
3045 // err = (DWORD)progDlg.DidErrorsOccur();
3046 // bFailed = progDlg.DidErrorsOccur();
3047 // bRet = progDlg.DidErrorsOccur();
3048 // CRegDWORD bFailRepeat = CRegDWORD(L"Software\\TortoiseGit\\CommitReopen", FALSE);
3049 // if (DWORD(bFailRepeat)==0)
3050 // bFailed = false; // do not repeat if the user chose not to in the settings.
3053 return true;
3056 BOOL CAppUtils::SVNDCommit(HWND hWnd)
3058 CSVNDCommitDlg dcommitdlg(CWnd::FromHandle(hWnd));
3059 CString gitSetting = g_Git.GetConfigValue(L"svn.rmdir");
3060 if (gitSetting.IsEmpty()) {
3061 if (dcommitdlg.DoModal() != IDOK)
3062 return false;
3063 else
3065 if (dcommitdlg.m_remember)
3067 if (dcommitdlg.m_rmdir)
3068 gitSetting = L"true";
3069 else
3070 gitSetting = L"false";
3071 if (g_Git.SetConfigValue(L"svn.rmdir", gitSetting))
3073 CString msg;
3074 msg.Format(IDS_PROC_SAVECONFIGFAILED, L"svn.rmdir", (LPCTSTR)gitSetting);
3075 MessageBox(hWnd, msg, L"TortoiseGit", MB_OK | MB_ICONERROR);
3081 BOOL IsStash = false;
3082 if(!g_Git.CheckCleanWorkTree())
3084 if (CMessageBox::Show(hWnd, IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
3086 CSysProgressDlg sysProgressDlg;
3087 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3088 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3089 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3090 sysProgressDlg.SetShowProgressBar(false);
3091 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3092 sysProgressDlg.ShowModeless(hWnd, true);
3094 CString out;
3095 if (g_Git.Run(L"git.exe stash", &out, CP_UTF8))
3097 sysProgressDlg.Stop();
3098 MessageBox(hWnd, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3099 return false;
3101 sysProgressDlg.Stop();
3103 IsStash =true;
3105 else
3106 return false;
3109 CProgressDlg progress(CWnd::FromHandle(hWnd));
3110 if (dcommitdlg.m_rmdir)
3111 progress.m_GitCmd = L"git.exe svn dcommit --rmdir";
3112 else
3113 progress.m_GitCmd = L"git.exe svn dcommit";
3114 if(progress.DoModal()==IDOK && progress.m_GitStatus == 0)
3116 if( IsStash)
3118 if (CMessageBox::Show(hWnd, IDS_DCOMMIT_STASH_POP, IDS_APPNAME, MB_YESNO | MB_ICONINFORMATION) == IDYES)
3120 CSysProgressDlg sysProgressDlg;
3121 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3122 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3123 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3124 sysProgressDlg.SetShowProgressBar(false);
3125 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3126 sysProgressDlg.ShowModeless(hWnd, true);
3128 CString out;
3129 if (g_Git.Run(L"git.exe stash pop", &out, CP_UTF8))
3131 sysProgressDlg.Stop();
3132 MessageBox(hWnd, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3133 return false;
3135 sysProgressDlg.Stop();
3137 else
3138 return false;
3140 return TRUE;
3142 return FALSE;
3145 static bool DoMerge(HWND hWnd, bool noFF, bool ffOnly, bool squash, bool noCommit, const int* log, bool unrelated, const CString& mergeStrategy, const CString& strategyOption, const CString& strategyParam, const CString& logMessage, const CString& version, bool isBranch, bool showStashPop)
3147 CString args;
3148 if (noFF)
3149 args += L" --no-ff";
3150 else if (ffOnly)
3151 args += L" --ff-only";
3153 if (squash)
3154 args += L" --squash";
3156 if (noCommit)
3157 args += L" --no-commit";
3159 if (unrelated)
3160 args += L" --allow-unrelated-histories";
3162 if (log)
3163 args.AppendFormat(L" --log=%d", *log);
3165 if (!mergeStrategy.IsEmpty())
3167 args += L" --strategy=" + mergeStrategy;
3168 if (!strategyOption.IsEmpty())
3170 args += L" --strategy-option=" + strategyOption;
3171 if (!strategyParam.IsEmpty())
3172 args += L'=' + strategyParam;
3176 if (!logMessage.IsEmpty())
3178 CString logmsg = logMessage;
3179 logmsg.Replace(L"\\\"", L"\\\\\"");
3180 logmsg.Replace(L"\"", L"\\\"");
3181 args += L" -m \"" + logmsg + L"\"";
3184 CString mergeVersion = g_Git.FixBranchName(version);
3185 CString cmd;
3186 cmd.Format(L"git.exe merge%s %s", (LPCTSTR)args, (LPCTSTR)mergeVersion);
3188 CProgressDlg Prodlg(CWnd::FromHandle(hWnd));
3189 Prodlg.m_GitCmd = cmd;
3191 Prodlg.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3193 if (status)
3195 int hasConflicts = g_Git.HasWorkingTreeConflicts();
3196 if (hasConflicts < 0)
3197 CMessageBox::Show(hWnd, g_Git.GetGitLastErr(L"Checking for conflicts failed.", CGit::GIT_CMD_CHECKCONFLICTS), L"TortoiseGit", MB_ICONEXCLAMATION);
3198 else if (hasConflicts)
3200 // there are conflict files
3201 CMessageBox::ShowCheck(hWnd, IDS_NEED_TO_RESOLVE_CONFLICTS_HINT, IDS_APPNAME, MB_ICONINFORMATION, L"MergeConflictsNeedsCommit", IDS_MSGBOX_DONOTSHOWAGAIN);
3202 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
3204 CString sCmd;
3205 sCmd.Format(L"/command:resolve /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3206 CAppUtils::RunTortoiseGitProc(sCmd);
3209 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUCOMMIT, []
3211 CString sCmd;
3212 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3213 CAppUtils::RunTortoiseGitProc(sCmd);
3217 if (CAppUtils::IsGitVersionNewerOrEqual(hWnd, 2, 9))
3219 CGitHash common;
3220 g_Git.IsFastForward(L"HEAD", mergeVersion, &common);
3221 if (common.IsEmpty())
3222 postCmdList.emplace_back(IDI_MERGE, IDS_MERGE_UNRELATED, [=, &hWnd] { DoMerge(hWnd, noFF, ffOnly, squash, noCommit, log, true, mergeStrategy, strategyOption, strategyParam, logMessage, version, isBranch, showStashPop); });
3225 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSTASHSAVE, [mergeVersion, &hWnd]{ CAppUtils::StashSave(hWnd, L"", false, false, true, mergeVersion); });
3227 return;
3230 if (showStashPop)
3231 postCmdList.emplace_back(IDI_RELOCATE, IDS_MENUSTASHPOP, [&hWnd]{ CAppUtils::StashPop(hWnd); });
3233 if (noCommit || squash)
3235 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUCOMMIT, []
3237 CString sCmd;
3238 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3239 CAppUtils::RunTortoiseGitProc(sCmd);
3241 return;
3244 if (isBranch && !CStringUtils::StartsWith(version, L"remotes/")) // do not ask to remove remote branches
3246 postCmdList.emplace_back(IDI_DELETE, IDS_PROC_REMOVEBRANCH, [&]
3248 CString msg;
3249 msg.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)version);
3250 if (CMessageBox::Show(hWnd, msg, L"TortoiseGit", 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
3252 CString cmd, out;
3253 cmd.Format(L"git.exe branch -D -- %s", (LPCTSTR)version);
3254 if (g_Git.Run(cmd, &out, CP_UTF8))
3255 MessageBox(hWnd, out, L"TortoiseGit", MB_OK);
3259 if (isBranch)
3260 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, [&hWnd]{ CAppUtils::Push(hWnd); });
3262 BOOL hasGitSVN = CTGitPath(g_Git.m_CurrentDir).GetAdminDirMask() & ITEMIS_GITSVN;
3263 if (hasGitSVN)
3264 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSVNDCOMMIT, [&hWnd]{ CAppUtils::SVNDCommit(hWnd); });
3267 Prodlg.DoModal();
3268 return !Prodlg.m_GitStatus;
3271 BOOL CAppUtils::Merge(HWND hWnd, const CString* commit, bool showStashPop)
3273 if (!CheckUserData(hWnd))
3274 return FALSE;
3276 if (IsTGitRebaseActive(hWnd))
3277 return FALSE;
3279 CMergeDlg dlg(CWnd::FromHandle(hWnd));
3280 if (commit)
3281 dlg.m_initialRefName = *commit;
3283 if (dlg.DoModal() == IDOK)
3284 return DoMerge(hWnd, dlg.m_bNoFF == BST_CHECKED, dlg.m_bFFonly == BST_CHECKED, dlg.m_bSquash == BST_CHECKED, dlg.m_bNoCommit == BST_CHECKED, dlg.m_bLog ? &dlg.m_nLog : nullptr, false, dlg.m_MergeStrategy, dlg.m_StrategyOption, dlg.m_StrategyParam, dlg.m_strLogMesage, dlg.m_VersionName, dlg.m_bIsBranch, showStashPop);
3286 return FALSE;
3289 BOOL CAppUtils::MergeAbort(HWND hWnd)
3291 CMergeAbortDlg dlg(CWnd::FromHandle(hWnd));
3292 if (dlg.DoModal() == IDOK)
3293 return Reset(hWnd, L"HEAD", (dlg.m_ResetType == 0) ? 3 : dlg.m_ResetType);
3295 return FALSE;
3298 void CAppUtils::EditNote(HWND hWnd, GitRevLoglist* rev, ProjectProperties* projectProperties)
3300 if (!CheckUserData(hWnd))
3301 return;
3303 CInputDlg dlg(CWnd::FromHandle(hWnd));
3304 dlg.m_sHintText = CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES));
3305 dlg.m_sInputText = rev->m_Notes;
3306 dlg.m_sTitle = CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES));
3307 dlg.m_pProjectProperties = projectProperties;
3308 dlg.m_bUseLogWidth = true;
3309 if(dlg.DoModal() == IDOK)
3311 if (g_Git.SetGitNotes(rev->m_CommitHash, dlg.m_sInputText))
3313 CString err;
3314 err.LoadString(IDS_PROC_FAILEDSAVINGNOTES);
3315 MessageBox(hWnd, g_Git.GetLibGit2LastErr(err), L"TortoiseGit", MB_OK | MB_ICONERROR);
3316 return;
3319 if (g_Git.GetGitNotes(rev->m_CommitHash, rev->m_Notes))
3320 MessageBox(hWnd, g_Git.GetLibGit2LastErr(L"Could not load notes for commit " + rev->m_CommitHash.ToString() + L'.'), L"TortoiseGit", MB_OK | MB_ICONERROR);
3324 inline bool CAppUtils::IsGitVersionNewerOrEqual(HWND hWnd, unsigned __int8 major, unsigned __int8 minor, unsigned __int8 patchlevel, unsigned __int8 build)
3326 auto ver = GetMsysgitVersion(hWnd);
3327 return ver >= ConvertVersionToInt(major, minor, patchlevel, build);
3330 int CAppUtils::GetMsysgitVersion(HWND hWnd)
3332 if (g_Git.ms_LastMsysGitVersion)
3333 return g_Git.ms_LastMsysGitVersion;
3335 CString cmd;
3336 CString versiondebug;
3337 CString version;
3339 CRegDWORD regTime = CRegDWORD(L"Software\\TortoiseGit\\git_file_time");
3340 CRegDWORD regVersion = CRegDWORD(L"Software\\TortoiseGit\\git_cached_version");
3342 CString gitpath = CGit::ms_LastMsysGitDir + L"\\git.exe";
3344 __int64 time=0;
3345 if (!CGit::GetFileModifyTime(gitpath, &time))
3347 if ((DWORD)CGit::filetime_to_time_t(time) == regTime)
3349 g_Git.ms_LastMsysGitVersion = regVersion;
3350 return regVersion;
3354 CString err;
3355 int ver = g_Git.GetGitVersion(&versiondebug, &err);
3356 if (ver < 0)
3358 MessageBox(hWnd, L"git.exe not correctly set up (" + err + L")\nCheck TortoiseGit settings and consult help file for \"Git.exe Path\".", L"TortoiseGit", MB_OK | MB_ICONERROR);
3359 return -1;
3363 if (!ver)
3365 MessageBox(hWnd, L"Could not parse git.exe version number: \"" + versiondebug + L'"', L"TortoiseGit", MB_OK | MB_ICONERROR);
3366 return -1;
3370 regTime = (DWORD)CGit::filetime_to_time_t(time);
3371 regVersion = ver;
3372 g_Git.ms_LastMsysGitVersion = ver;
3374 return ver;
3377 void CAppUtils::MarkWindowAsUnpinnable(HWND hWnd)
3379 typedef HRESULT (WINAPI *SHGPSFW) (HWND hwnd,REFIID riid,void** ppv);
3381 CAutoLibrary hShell = AtlLoadSystemLibraryUsingFullPath(L"Shell32.dll");
3383 if (hShell.IsValid()) {
3384 SHGPSFW pfnSHGPSFW = (SHGPSFW)::GetProcAddress(hShell, "SHGetPropertyStoreForWindow");
3385 if (pfnSHGPSFW) {
3386 IPropertyStore *pps;
3387 HRESULT hr = pfnSHGPSFW(hWnd, IID_PPV_ARGS(&pps));
3388 if (SUCCEEDED(hr)) {
3389 PROPVARIANT var;
3390 var.vt = VT_BOOL;
3391 var.boolVal = VARIANT_TRUE;
3392 pps->SetValue(PKEY_AppUserModel_PreventPinning, var);
3393 pps->Release();
3399 void CAppUtils::SetWindowTitle(HWND hWnd, const CString& urlorpath, const CString& dialogname)
3401 ASSERT(dialogname.GetLength() < 70);
3402 ASSERT(urlorpath.GetLength() < MAX_PATH);
3403 WCHAR pathbuf[MAX_PATH] = {0};
3405 PathCompactPathEx(pathbuf, urlorpath, 70 - dialogname.GetLength(), 0);
3407 wcscat_s(pathbuf, L" - ");
3408 wcscat_s(pathbuf, dialogname);
3409 wcscat_s(pathbuf, L" - ");
3410 wcscat_s(pathbuf, CString(MAKEINTRESOURCE(IDS_APPNAME)));
3411 SetWindowText(hWnd, pathbuf);
3414 bool CAppUtils::BisectStart(HWND hWnd, const CString& lastGood, const CString& firstBad, bool bIsMainWnd)
3416 if (!g_Git.CheckCleanWorkTree())
3418 if (CMessageBox::Show(hWnd, IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
3420 CSysProgressDlg sysProgressDlg;
3421 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3422 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3423 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3424 sysProgressDlg.SetShowProgressBar(false);
3425 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3426 sysProgressDlg.ShowModeless(hWnd, true);
3428 CString out;
3429 if (g_Git.Run(L"git.exe stash", &out, CP_UTF8))
3431 sysProgressDlg.Stop();
3432 MessageBox(hWnd, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3433 return false;
3435 sysProgressDlg.Stop();
3437 else
3438 return false;
3441 CBisectStartDlg bisectStartDlg(CWnd::FromHandle(hWnd));
3443 if (!lastGood.IsEmpty())
3444 bisectStartDlg.m_sLastGood = lastGood;
3445 if (!firstBad.IsEmpty())
3446 bisectStartDlg.m_sFirstBad = firstBad;
3448 if (bisectStartDlg.DoModal() == IDOK)
3450 CProgressDlg progress(CWnd::FromHandle(hWnd));
3451 if (bIsMainWnd)
3452 theApp.m_pMainWnd = &progress;
3453 progress.m_GitCmdList.push_back(L"git.exe bisect start");
3454 progress.m_GitCmdList.push_back(L"git.exe bisect good " + bisectStartDlg.m_LastGoodRevision);
3455 progress.m_GitCmdList.push_back(L"git.exe bisect bad " + bisectStartDlg.m_FirstBadRevision);
3457 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3459 if (status)
3460 return;
3462 CTGitPath path(g_Git.m_CurrentDir);
3463 if (path.HasSubmodules())
3465 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
3467 CString sCmd;
3468 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3469 CAppUtils::RunTortoiseGitProc(sCmd);
3473 postCmdList.emplace_back(IDI_THUMB_UP, IDS_MENUBISECTGOOD, [] { CAppUtils::RunTortoiseGitProc(L"/command:bisect /good"); });
3474 postCmdList.emplace_back(IDI_THUMB_DOWN, IDS_MENUBISECTBAD, [] { CAppUtils::RunTortoiseGitProc(L"/command:bisect /bad"); });
3475 postCmdList.emplace_back(IDI_BISECT, IDS_MENUBISECTSKIP, [] { CAppUtils::RunTortoiseGitProc(L"/command:bisect /skip"); });
3476 postCmdList.emplace_back(IDI_BISECT_RESET, IDS_MENUBISECTRESET, [] { CAppUtils::RunTortoiseGitProc(L"/command:bisect /reset"); });
3479 INT_PTR ret = progress.DoModal();
3480 return ret == IDOK;
3483 return false;
3486 bool CAppUtils::BisectOperation(HWND hWnd, const CString& op, const CString& ref, bool bIsMainWnd)
3488 CString cmd = L"git.exe bisect " + op;
3490 if (!ref.IsEmpty())
3492 cmd += L' ';
3493 cmd += ref;
3496 CProgressDlg progress(CWnd::FromHandle(hWnd));
3497 if (bIsMainWnd)
3498 theApp.m_pMainWnd = &progress;
3499 progress.m_GitCmd = cmd;
3501 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3503 if (status)
3504 return;
3506 CTGitPath path = g_Git.m_CurrentDir;
3507 if (path.HasSubmodules())
3509 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
3511 CString sCmd;
3512 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3513 CAppUtils::RunTortoiseGitProc(sCmd);
3517 postCmdList.emplace_back(IDI_THUMB_UP, IDS_MENUBISECTGOOD, [] { CAppUtils::RunTortoiseGitProc(L"/command:bisect /good"); });
3518 postCmdList.emplace_back(IDI_THUMB_DOWN, IDS_MENUBISECTBAD, [] { CAppUtils::RunTortoiseGitProc(L"/command:bisect /bad"); });
3519 postCmdList.emplace_back(IDI_BISECT, IDS_MENUBISECTSKIP, [] { CAppUtils::RunTortoiseGitProc(L"/command:bisect /skip"); });
3520 if (op != L"reset")
3521 postCmdList.emplace_back(IDI_BISECT_RESET, IDS_MENUBISECTRESET, []{ CAppUtils::RunTortoiseGitProc(L"/command:bisect /reset"); });
3524 INT_PTR ret = progress.DoModal();
3525 return ret == IDOK;
3528 int CAppUtils::Git2GetUserPassword(git_cred **out, const char *url, const char *username_from_url, unsigned int /*allowed_types*/, void * /*payload*/)
3530 CUserPassword dlg;
3531 dlg.m_URL = CUnicodeUtils::GetUnicode(url, CP_UTF8);
3532 if (username_from_url)
3533 dlg.m_UserName = CUnicodeUtils::GetUnicode(username_from_url, CP_UTF8);
3535 CStringA username, password;
3536 if (dlg.DoModal() == IDOK)
3538 username = CUnicodeUtils::GetMulti(dlg.m_UserName, CP_UTF8);
3539 password = CUnicodeUtils::GetMulti(dlg.m_Password, CP_UTF8);
3540 return git_cred_userpass_plaintext_new(out, username, password);
3542 giterr_set_str(GITERR_NONE, "User cancelled.");
3543 return GIT_EUSER;
3546 int CAppUtils::Git2CertificateCheck(git_cert* base_cert, int /*valid*/, const char* host, void* /*payload*/)
3548 if (base_cert->cert_type == GIT_CERT_X509)
3550 git_cert_x509* cert = (git_cert_x509*)base_cert;
3552 if (last_accepted_cert.cmp(cert))
3553 return 0;
3555 PCCERT_CONTEXT pServerCert = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, (BYTE*)cert->data, (DWORD)cert->len);
3556 SCOPE_EXIT { CertFreeCertificateContext(pServerCert); };
3558 DWORD verificationError = VerifyServerCertificate(pServerCert, CUnicodeUtils::GetUnicode(host).GetBuffer(), 0);
3559 if (!verificationError)
3561 last_accepted_cert.set(cert);
3562 return 0;
3565 CString servernameInCert;
3566 CertGetNameString(pServerCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nullptr, CStrBuf(servernameInCert, 128), 128);
3568 CString issuer;
3569 CertGetNameString(pServerCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, CERT_NAME_ISSUER_FLAG, nullptr, CStrBuf(issuer, 128), 128);
3571 CCheckCertificateDlg dlg;
3572 dlg.cert = cert;
3573 dlg.m_sCertificateCN = servernameInCert;
3574 dlg.m_sCertificateIssuer = issuer;
3575 dlg.m_sHostname = CUnicodeUtils::GetUnicode(host);
3576 dlg.m_sError = CFormatMessageWrapper(verificationError);
3577 if (dlg.DoModal() == IDOK)
3579 last_accepted_cert.set(cert);
3580 return 0;
3583 return GIT_ECERTIFICATE;
3586 int CAppUtils::ExploreTo(HWND hwnd, CString path)
3588 if (PathFileExists(path))
3590 HRESULT ret = E_FAIL;
3591 ITEMIDLIST __unaligned * pidl = ILCreateFromPath(path);
3592 if (pidl)
3594 ret = SHOpenFolderAndSelectItems(pidl, 0, 0, 0);
3595 ILFree(pidl);
3597 return SUCCEEDED(ret) ? 0 : -1;
3599 // if filepath does not exist any more, navigate to closest matching folder
3602 int pos = path.ReverseFind(L'\\');
3603 if (pos <= 3)
3604 break;
3605 path.Truncate(pos);
3606 } while (!PathFileExists(path));
3607 return (INT_PTR)ShellExecute(hwnd, L"explore", path, nullptr, nullptr, SW_SHOW) > 32 ? 0 : -1;
3610 int CAppUtils::ResolveConflict(HWND hWnd, CTGitPath& path, resolve_with resolveWith)
3612 bool b_local = false, b_remote = false;
3613 BYTE_VECTOR vector;
3615 CString cmd;
3616 cmd.Format(L"git.exe ls-files -u -t -z -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3617 if (g_Git.Run(cmd, &vector))
3619 MessageBox(hWnd, L"git ls-files failed!", L"TortoiseGit", MB_OK);
3620 return -1;
3623 CTGitPathList list;
3624 if (list.ParserFromLsFile(vector))
3626 MessageBox(hWnd, L"Parse ls-files failed!", L"TortoiseGit", MB_OK);
3627 return -1;
3630 if (list.IsEmpty())
3631 return 0;
3632 for (int i = 0; i < list.GetCount(); ++i)
3634 if (list[i].m_Stage == 2)
3635 b_local = true;
3636 if (list[i].m_Stage == 3)
3637 b_remote = true;
3641 bool baseIsFile = true, localIsFile = true, remoteIsFile = true;
3642 CString baseHash, localHash, remoteHash;
3643 ParseHashesFromLsFile(vector, baseHash, baseIsFile, localHash, localIsFile, remoteHash, remoteIsFile);
3645 CBlockCacheForPath block(g_Git.m_CurrentDir);
3646 if ((resolveWith == RESOLVE_WITH_THEIRS && !b_remote) || (resolveWith == RESOLVE_WITH_MINE && !b_local))
3648 CString gitcmd, output; //retest with registered submodule!
3649 gitcmd.Format(L"git.exe rm -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3650 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3652 // a .git folder in a submodule which is not in .gitmodules cannot be deleted using "git rm"
3653 if (PathIsDirectory(path.GetGitPathString()) && !PathIsDirectoryEmpty(path.GetGitPathString()))
3655 CString message(output);
3656 output += L"\n\n";
3657 output.AppendFormat(IDS_PROC_DELETEBRANCHTAG, path.GetWinPath());
3658 CString deleteButton;
3659 deleteButton.LoadString(IDS_DELETEBUTTON);
3660 CString abortButton;
3661 abortButton.LoadString(IDS_ABORTBUTTON);
3662 if (CMessageBox::Show(hWnd, output, L"TortoiseGit", 2, IDI_QUESTION, deleteButton, abortButton) == 2)
3663 return -1;
3664 path.Delete(true, true);
3665 output.Empty();
3666 if (!g_Git.Run(gitcmd, &output, CP_UTF8))
3668 RemoveTempMergeFile(path);
3669 return 0;
3672 MessageBox(hWnd, output, L"TortoiseGit", MB_ICONERROR);
3673 return -1;
3675 RemoveTempMergeFile(path);
3676 return 0;
3679 if (resolveWith == RESOLVE_WITH_THEIRS || resolveWith == RESOLVE_WITH_MINE)
3681 auto resolve = [&b_local, &b_remote, &hWnd](const CTGitPath& path, int stage, bool willBeFile, const CString& hash) -> int
3683 if (!willBeFile)
3685 if (!path.HasAdminDir()) // check if submodule is initialized
3687 CString gitcmd, output;
3688 if (!path.IsDirectory())
3690 gitcmd.Format(L"git.exe checkout-index -f --stage=%d -- \"%s\"", stage, (LPCTSTR)path.GetGitPathString());
3691 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3693 MessageBox(hWnd, output, L"TortoiseGit", MB_ICONERROR);
3694 return -1;
3697 gitcmd.Format(L"git.exe update-index --replace --cacheinfo 0160000,%s,\"%s\"", (LPCTSTR)hash, (LPCTSTR)path.GetGitPathString());
3698 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3700 MessageBox(hWnd, output, L"TortoiseGit", MB_ICONERROR);
3701 return -1;
3703 return 0;
3706 CGit subgit;
3707 subgit.m_CurrentDir = g_Git.CombinePath(path);
3708 CGitHash submoduleHead;
3709 if (subgit.GetHash(submoduleHead, L"HEAD"))
3711 MessageBox(hWnd, subgit.GetGitLastErr(L"Could not get HEAD hash of submodule, this should not happen!"), L"TortoiseGit", MB_ICONERROR);
3712 return -1;
3714 if (submoduleHead.ToString() != hash)
3716 CString origPath = g_Git.m_CurrentDir;
3717 g_Git.m_CurrentDir = g_Git.CombinePath(path);
3718 if (!GitReset(hWnd, &hash))
3720 g_Git.m_CurrentDir = origPath;
3721 return -1;
3723 g_Git.m_CurrentDir = origPath;
3726 else
3728 CString gitcmd, output;
3729 if (b_local && b_remote)
3730 gitcmd.Format(L"git.exe checkout-index -f --stage=%d -- \"%s\"", stage, (LPCTSTR)path.GetGitPathString());
3731 else
3732 gitcmd.Format(L"git.exe add -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3733 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3735 MessageBox(hWnd, output, L"TortoiseGit", MB_ICONERROR);
3736 return -1;
3739 return 0;
3741 int ret = -1;
3742 if (resolveWith == RESOLVE_WITH_THEIRS)
3743 ret = resolve(path, 3, remoteIsFile, remoteHash);
3744 else
3745 ret = resolve(path, 2, localIsFile, localHash);
3746 if (ret)
3747 return ret;
3750 if (PathFileExists(g_Git.CombinePath(path)) && (path.m_Action & CTGitPath::LOGACTIONS_UNMERGED))
3752 CString gitcmd, output;
3753 gitcmd.Format(L"git.exe add -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3754 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3756 MessageBox(hWnd, output, L"TortoiseGit", MB_ICONERROR);
3757 return -1;
3760 path.m_Action |= CTGitPath::LOGACTIONS_MODIFIED;
3761 path.m_Action &= ~CTGitPath::LOGACTIONS_UNMERGED;
3764 RemoveTempMergeFile(path);
3765 return 0;
3768 bool CAppUtils::ShellOpen(const CString& file, HWND hwnd /*= nullptr */)
3770 if ((INT_PTR)ShellExecute(hwnd, nullptr, file, nullptr, nullptr, SW_SHOW) > HINSTANCE_ERROR)
3771 return true;
3773 return ShowOpenWithDialog(file, hwnd);
3776 bool CAppUtils::ShowOpenWithDialog(const CString& file, HWND hwnd /*= nullptr */)
3778 OPENASINFO oi = { 0 };
3779 oi.pcszFile = file;
3780 oi.oaifInFlags = OAIF_EXEC;
3781 return SUCCEEDED(SHOpenWithDialog(hwnd, &oi));
3784 bool CAppUtils::IsTGitRebaseActive(HWND hWnd)
3786 CString adminDir;
3787 if (!GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir))
3788 return false;
3790 if (!PathIsDirectory(adminDir + L"tgitrebase.active"))
3791 return false;
3793 if (CMessageBox::Show(hWnd, IDS_REBASELOCKFILEFOUND, IDS_APPNAME, 2, IDI_EXCLAMATION, IDS_REMOVESTALEBUTTON, IDS_ABORTBUTTON) == 2)
3794 return true;
3796 RemoveDirectory(adminDir + L"tgitrebase.active");
3798 return false;
3801 bool CAppUtils::DeleteRef(CWnd* parent, const CString& ref)
3803 CString shortname;
3804 if (CGit::GetShortName(ref, shortname, L"refs/remotes/"))
3806 CString msg;
3807 msg.Format(IDS_PROC_DELETEREMOTEBRANCH, (LPCTSTR)ref);
3808 int result = CMessageBox::Show(parent->GetSafeOwner()->GetSafeHwnd(), msg, L"TortoiseGit", 3, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_PROC_DELETEREMOTEBRANCH_LOCALREMOTE)), CString(MAKEINTRESOURCE(IDS_PROC_DELETEREMOTEBRANCH_LOCAL)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON)));
3809 if (result == 1)
3811 CString remoteName = shortname.Left(shortname.Find(L'/'));
3812 shortname = shortname.Mid(shortname.Find(L'/') + 1);
3813 if (CAppUtils::IsSSHPutty())
3814 CAppUtils::LaunchPAgent(parent->GetSafeHwnd(), nullptr, &remoteName);
3816 CSysProgressDlg sysProgressDlg;
3817 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3818 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
3819 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3820 sysProgressDlg.SetShowProgressBar(false);
3821 sysProgressDlg.ShowModal(parent, true);
3822 STRING_VECTOR list;
3823 list.push_back(L"refs/heads/" + shortname);
3824 if (g_Git.DeleteRemoteRefs(remoteName, list))
3825 CMessageBox::Show(parent->GetSafeOwner()->GetSafeHwnd(), g_Git.GetGitLastErr(L"Could not delete remote ref.", CGit::GIT_CMD_PUSH), L"TortoiseGit", MB_OK | MB_ICONERROR);
3826 sysProgressDlg.Stop();
3827 return true;
3829 else if (result == 2)
3831 if (g_Git.DeleteRef(ref))
3833 CMessageBox::Show(parent->GetSafeOwner()->GetSafeHwnd(), g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), L"TortoiseGit", MB_OK | MB_ICONERROR);
3834 return false;
3836 return true;
3838 return false;
3840 else if (CGit::GetShortName(ref, shortname, L"refs/stash"))
3842 CString err;
3843 std::vector<GitRevLoglist> stashList;
3844 size_t count = !GitRevLoglist::GetRefLog(ref, stashList, err) ? stashList.size() : 0;
3845 CString msg;
3846 msg.Format(IDS_PROC_DELETEALLSTASH, count);
3847 int choose = CMessageBox::Show(parent->GetSafeOwner()->GetSafeHwnd(), msg, L"TortoiseGit", 3, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_DROPONESTASH)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON)));
3848 if (choose == 1)
3850 CString out;
3851 if (g_Git.Run(L"git.exe stash clear", &out, CP_UTF8))
3852 CMessageBox::Show(parent->GetSafeOwner()->GetSafeHwnd(), out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3853 return true;
3855 else if (choose == 2)
3857 CString out;
3858 if (g_Git.Run(L"git.exe stash drop refs/stash@{0}", &out, CP_UTF8))
3859 CMessageBox::Show(parent->GetSafeOwner()->GetSafeHwnd(), out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3860 return true;
3862 return false;
3865 CString msg;
3866 msg.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)ref);
3867 // Check if branch is fully merged in HEAD
3868 if (CGit::GetShortName(ref, shortname, L"refs/heads/") && !g_Git.IsFastForward(ref, L"HEAD"))
3870 msg += L"\n\n";
3871 msg += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED));
3873 if (CMessageBox::Show(parent->GetSafeOwner()->GetSafeHwnd(), msg, L"TortoiseGit", 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
3875 if (g_Git.DeleteRef(ref))
3877 CMessageBox::Show(parent->GetSafeOwner()->GetSafeHwnd(), g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), L"TortoiseGit", MB_OK | MB_ICONERROR);
3878 return false;
3880 return true;
3882 return false;