Don't import ogdf namespace
[TortoiseGit.git] / src / TortoiseProc / AppUtils.cpp
blobb8d99d001c0e6c4a8a8f3d86edbfb155e714f0d8
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"
70 #include "DPIAware.h"
72 static struct last_accepted_cert {
73 BYTE* data;
74 size_t len;
76 last_accepted_cert()
77 : data(nullptr)
78 , len(0)
81 ~last_accepted_cert()
83 free(data);
85 boolean cmp(git_cert_x509* cert)
87 return len > 0 && len == cert->len && memcmp(data, cert->data, len) == 0;
89 void set(git_cert_x509* cert)
91 free(data);
92 len = cert->len;
93 if (len == 0)
95 data = nullptr;
96 return;
98 data = new BYTE[len];
99 memcpy(data, cert->data, len);
101 } last_accepted_cert;
103 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);
105 bool CAppUtils::StashSave(HWND hWnd, const CString& msg, bool showPull, bool pullShowPush, bool showMerge, const CString& mergeRev)
107 if (!CheckUserData(hWnd))
108 return false;
110 CStashSaveDlg dlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
111 dlg.m_sMessage = msg;
112 if (dlg.DoModal() == IDOK)
114 CString cmd = L"git.exe stash push";
115 if (!CAppUtils::IsGitVersionNewerOrEqual(hWnd, 2, 14))
116 cmd = L"git.exe stash save";
118 if (dlg.m_bIncludeUntracked)
119 cmd += L" --include-untracked";
120 else if (dlg.m_bAll)
121 cmd += L" --all";
123 if (!dlg.m_sMessage.IsEmpty())
125 CString message = dlg.m_sMessage;
126 message.Replace(L"\"", L"\"\"");
127 if (CAppUtils::IsGitVersionNewerOrEqual(hWnd, 2, 14))
128 cmd += L" -m \"" + message + L'"';
129 else
130 cmd += L" -- \"" + message + L'"';
133 CProgressDlg progress(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
134 progress.m_GitCmd = cmd;
135 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
137 if (status)
138 return;
140 if (showPull)
141 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&]{ CAppUtils::Pull(hWnd, pullShowPush, true); });
142 if (showMerge)
143 postCmdList.emplace_back(IDI_MERGE, IDS_MENUMERGE, [&]{ CAppUtils::Merge(hWnd, &mergeRev, true); });
144 postCmdList.emplace_back(IDI_UNSHELVE, IDS_MENUSTASHPOP, [&hWnd] { CAppUtils::StashPop(hWnd); });
146 return (progress.DoModal() == IDOK);
148 return false;
151 bool CAppUtils::StashApply(HWND hWnd, CString ref, bool showChanges /* true */)
153 CString cmd = L"git.exe stash apply ";
154 if (CStringUtils::StartsWith(ref, L"refs/"))
155 ref = ref.Mid((int)wcslen(L"refs/"));
156 if (CStringUtils::StartsWith(ref, L"stash{"))
157 ref = L"stash@" + ref.Mid((int)wcslen(L"stash"));
158 cmd += ref;
160 CSysProgressDlg sysProgressDlg;
161 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
162 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
163 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
164 sysProgressDlg.SetShowProgressBar(false);
165 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
166 sysProgressDlg.ShowModeless(hWnd, true);
168 CString out;
169 int ret = g_Git.Run(cmd, &out, CP_UTF8);
171 sysProgressDlg.Stop();
173 bool hasConflicts = (out.Find(L"CONFLICT") >= 0);
174 if (ret && !(ret == 1 && hasConflicts))
175 CMessageBox::Show(hWnd, CString(MAKEINTRESOURCE(IDS_PROC_STASHAPPLYFAILED)) + L'\n' + out, L"TortoiseGit", MB_OK | MB_ICONERROR);
176 else
178 CString message;
179 message.LoadString(IDS_PROC_STASHAPPLYSUCCESS);
180 if (hasConflicts)
181 message.LoadString(IDS_PROC_STASHAPPLYFAILEDCONFLICTS);
182 if (showChanges)
184 if (CMessageBox::Show(hWnd, message + L'\n' + CString(MAKEINTRESOURCE(IDS_SEECHANGES)), L"TortoiseGit", MB_YESNO | MB_ICONINFORMATION) == IDYES)
186 cmd.Format(L"/command:repostatus /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
187 CAppUtils::RunTortoiseGitProc(cmd);
189 return true;
191 else
193 MessageBox(hWnd, message ,L"TortoiseGit", MB_OK | MB_ICONINFORMATION);
194 return true;
197 return false;
200 bool CAppUtils::StashPop(HWND hWnd, int showChanges /* = 1 */)
202 CString cmd = L"git.exe stash pop";
204 CSysProgressDlg sysProgressDlg;
205 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
206 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
207 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
208 sysProgressDlg.SetShowProgressBar(false);
209 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
210 sysProgressDlg.ShowModeless(hWnd, true);
212 CString out;
213 int ret = g_Git.Run(cmd, &out, CP_UTF8);
215 sysProgressDlg.Stop();
217 bool hasConflicts = (out.Find(L"CONFLICT") >= 0);
218 if (ret && !(ret == 1 && hasConflicts))
219 CMessageBox::Show(hWnd, CString(MAKEINTRESOURCE(IDS_PROC_STASHPOPFAILED)) + L'\n' + out, L"TortoiseGit", MB_OK | MB_ICONERROR);
220 else
222 CString message;
223 message.LoadString(IDS_PROC_STASHPOPSUCCESS);
224 if (hasConflicts)
225 message.LoadString(IDS_PROC_STASHPOPFAILEDCONFLICTS);
226 if (showChanges == 1 || (showChanges == 0 && hasConflicts))
228 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)
230 cmd.Format(L"/command:repostatus /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
231 CAppUtils::RunTortoiseGitProc(cmd);
233 return true;
235 else if (showChanges > 1)
237 MessageBox(hWnd, message, L"TortoiseGit", MB_OK | MB_ICONINFORMATION);
238 return true;
240 else if (showChanges == 0)
241 return true;
243 return false;
246 BOOL CAppUtils::StartExtMerge(bool bAlternative,
247 const CTGitPath& basefile, const CTGitPath& theirfile, const CTGitPath& yourfile, const CTGitPath& mergedfile,
248 const CString& basename, const CString& theirname, const CString& yourname, const CString& mergedname, bool bReadOnly,
249 HWND resolveMsgHwnd, bool bDeleteBaseTheirsMineOnClose)
251 CRegString regCom = CRegString(L"Software\\TortoiseGit\\Merge");
252 CString ext = mergedfile.GetFileExtension();
253 CString com = regCom;
254 bool bInternal = false;
256 if (!ext.IsEmpty())
258 // is there an extension specific merge tool?
259 CRegString mergetool(L"Software\\TortoiseGit\\MergeTools\\" + ext.MakeLower());
260 if (!CString(mergetool).IsEmpty())
261 com = mergetool;
263 // is there a filename specific merge tool?
264 CRegString mergetool(L"Software\\TortoiseGit\\MergeTools\\." + mergedfile.GetFilename().MakeLower());
265 if (!CString(mergetool).IsEmpty())
266 com = mergetool;
268 if (bAlternative && !com.IsEmpty())
270 if (CStringUtils::StartsWith(com, L"#"))
271 com.Delete(0);
272 else
273 com.Empty();
276 if (com.IsEmpty() || CStringUtils::StartsWith(com, L"#"))
278 // Maybe we should use TortoiseIDiff?
279 if ((ext == L".jpg") || (ext == L".jpeg") ||
280 (ext == L".bmp") || (ext == L".gif") ||
281 (ext == L".png") || (ext == L".ico") ||
282 (ext == L".tif") || (ext == L".tiff") ||
283 (ext == L".dib") || (ext == L".emf") ||
284 (ext == L".cur"))
286 com = CPathUtils::GetAppDirectory() + L"TortoiseGitIDiff.exe";
287 com = L'"' + com + L'"';
288 com = com + L" /base:%base /theirs:%theirs /mine:%mine /result:%merged";
289 com = com + L" /basetitle:%bname /theirstitle:%tname /minetitle:%yname";
290 if (resolveMsgHwnd)
291 com.AppendFormat(L" /resolvemsghwnd:%I64d", (__int64)resolveMsgHwnd);
293 else
295 // use TortoiseGitMerge
296 bInternal = true;
297 com = CPathUtils::GetAppDirectory() + L"TortoiseGitMerge.exe";
298 com = L'"' + com + L'"';
299 com = com + L" /base:%base /theirs:%theirs /mine:%mine /merged:%merged";
300 com = com + L" /basename:%bname /theirsname:%tname /minename:%yname /mergedname:%mname";
301 com += L" /saverequired";
302 if (resolveMsgHwnd)
303 com.AppendFormat(L" /resolvemsghwnd:%I64d", (__int64)resolveMsgHwnd);
304 if (bDeleteBaseTheirsMineOnClose)
305 com += L" /deletebasetheirsmineonclose";
307 if (!g_sGroupingUUID.IsEmpty())
309 com += L" /groupuuid:\"";
310 com += g_sGroupingUUID;
311 com += L'"';
314 // check if the params are set. If not, just add the files to the command line
315 if ((com.Find(L"%merged") < 0) && (com.Find(L"%base") < 0) && (com.Find(L"%theirs") < 0) && (com.Find(L"%mine") < 0))
317 com += L" \"" + basefile.GetWinPathString() + L'"';
318 com += L" \"" + theirfile.GetWinPathString() + L'"';
319 com += L" \"" + yourfile.GetWinPathString() + L'"';
320 com += L" \"" + mergedfile.GetWinPathString() + L'"';
322 if (basefile.IsEmpty())
324 com.Replace(L"/base:%base", L"");
325 com.Replace(L"%base", L"");
327 else
328 com.Replace(L"%base", L'"' + basefile.GetWinPathString() + L'"');
329 if (theirfile.IsEmpty())
331 com.Replace(L"/theirs:%theirs", L"");
332 com.Replace(L"%theirs", L"");
334 else
335 com.Replace(L"%theirs", L'"' + theirfile.GetWinPathString() + L'"');
336 if (yourfile.IsEmpty())
338 com.Replace(L"/mine:%mine", L"");
339 com.Replace(L"%mine", L"");
341 else
342 com.Replace(L"%mine", L'"' + yourfile.GetWinPathString() + L'"');
343 if (mergedfile.IsEmpty())
345 com.Replace(L"/merged:%merged", L"");
346 com.Replace(L"%merged", L"");
348 else
349 com.Replace(L"%merged", L'"' + mergedfile.GetWinPathString() + L'"');
350 if (basename.IsEmpty())
352 if (basefile.IsEmpty())
354 com.Replace(L"/basename:%bname", L"");
355 com.Replace(L"%bname", L"");
357 else
358 com.Replace(L"%bname", L'"' + basefile.GetUIFileOrDirectoryName() + L'"');
360 else
361 com.Replace(L"%bname", L'"' + basename + L'"');
362 if (theirname.IsEmpty())
364 if (theirfile.IsEmpty())
366 com.Replace(L"/theirsname:%tname", L"");
367 com.Replace(L"%tname", L"");
369 else
370 com.Replace(L"%tname", L'"' + theirfile.GetUIFileOrDirectoryName() + L'"');
372 else
373 com.Replace(L"%tname", L'"' + theirname + L'"');
374 if (yourname.IsEmpty())
376 if (yourfile.IsEmpty())
378 com.Replace(L"/minename:%yname", L"");
379 com.Replace(L"%yname", L"");
381 else
382 com.Replace(L"%yname", L'"' + yourfile.GetUIFileOrDirectoryName() + L'"');
384 else
385 com.Replace(L"%yname", L'"' + yourname + L'"');
386 if (mergedname.IsEmpty())
388 if (mergedfile.IsEmpty())
390 com.Replace(L"/mergedname:%mname", L"");
391 com.Replace(L"%mname", L"");
393 else
394 com.Replace(L"%mname", L'"' + mergedfile.GetUIFileOrDirectoryName() + L'"');
396 else
397 com.Replace(L"%mname", L'"' + mergedname + L'"');
399 com.Replace(L"%wtroot", L'"' + g_Git.m_CurrentDir + L'"');
401 if ((bReadOnly)&&(bInternal))
402 com += L" /readonly";
404 if(!LaunchApplication(com, IDS_ERR_EXTMERGESTART, false))
406 return FALSE;
409 return TRUE;
412 BOOL CAppUtils::StartExtPatch(const CTGitPath& patchfile, const CTGitPath& dir, const CString& sOriginalDescription, const CString& sPatchedDescription, BOOL bReversed, BOOL bWait)
414 CString viewer;
415 // use TortoiseGitMerge
416 viewer = CPathUtils::GetAppDirectory();
417 viewer += L"TortoiseGitMerge.exe";
419 viewer = L'"' + viewer + L'"';
420 viewer = viewer + L" /diff:\"" + patchfile.GetWinPathString() + L'"';
421 viewer = viewer + L" /patchpath:\"" + dir.GetWinPathString() + L'"';
422 if (bReversed)
423 viewer += L" /reversedpatch";
424 if (!sOriginalDescription.IsEmpty())
425 viewer = viewer + L" /patchoriginal:\"" + sOriginalDescription + L'"';
426 if (!sPatchedDescription.IsEmpty())
427 viewer = viewer + L" /patchpatched:\"" + sPatchedDescription + L'"';
428 if (!g_sGroupingUUID.IsEmpty())
430 viewer += L" /groupuuid:\"";
431 viewer += g_sGroupingUUID;
432 viewer += L'"';
434 if(!LaunchApplication(viewer, IDS_ERR_DIFFVIEWSTART, !!bWait))
435 return FALSE;
436 return TRUE;
439 CString CAppUtils::PickDiffTool(const CTGitPath& file1, const CTGitPath& file2)
441 CString difftool = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + file2.GetFilename().MakeLower());
442 if (!difftool.IsEmpty())
443 return difftool;
444 difftool = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + file1.GetFilename().MakeLower());
445 if (!difftool.IsEmpty())
446 return difftool;
448 // Is there an extension specific diff tool?
449 CString ext = file2.GetFileExtension().MakeLower();
450 if (!ext.IsEmpty())
452 difftool = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + ext);
453 if (!difftool.IsEmpty())
454 return difftool;
455 // Maybe we should use TortoiseIDiff?
456 if ((ext == L".jpg") || (ext == L".jpeg") ||
457 (ext == L".bmp") || (ext == L".gif") ||
458 (ext == L".png") || (ext == L".ico") ||
459 (ext == L".tif") || (ext == L".tiff") ||
460 (ext == L".dib") || (ext == L".emf") ||
461 (ext == L".cur"))
463 return
464 L'"' + CPathUtils::GetAppDirectory() + L"TortoiseGitIDiff.exe" + L'"' +
465 L" /left:%base /right:%mine /lefttitle:%bname /righttitle:%yname" +
466 L" /groupuuid:\"" + g_sGroupingUUID + L'"';
470 // Finally, pick a generic external diff tool
471 difftool = CRegString(L"Software\\TortoiseGit\\Diff");
472 return difftool;
475 bool CAppUtils::StartExtDiff(
476 const CString& file1, const CString& file2,
477 const CString& sName1, const CString& sName2,
478 const CString& originalFile1, const CString& originalFile2,
479 const CString& hash1, const CString& hash2,
480 const DiffFlags& flags, int jumpToLine)
482 CString viewer;
484 CRegDWORD blamediff(L"Software\\TortoiseGit\\DiffBlamesWithTortoiseMerge", FALSE);
485 if (!flags.bBlame || !(DWORD)blamediff)
487 viewer = PickDiffTool(file1, file2);
488 // If registry entry for a diff program is commented out, use TortoiseGitMerge.
489 bool bCommentedOut = CStringUtils::StartsWith(viewer, L"#");
490 if (flags.bAlternativeTool)
492 // Invert external vs. internal diff tool selection.
493 if (bCommentedOut)
494 viewer.Delete(0); // uncomment
495 else
496 viewer.Empty();
498 else if (bCommentedOut)
499 viewer.Empty();
502 bool bInternal = viewer.IsEmpty();
503 if (bInternal)
505 viewer =
506 L'"' + CPathUtils::GetAppDirectory() + L"TortoiseGitMerge.exe" + L'"' +
507 L" /base:%base /mine:%mine /basename:%bname /minename:%yname" +
508 L" /basereflectedname:%bpath /minereflectedname:%ypath";
509 if (!g_sGroupingUUID.IsEmpty())
511 viewer += L" /groupuuid:\"";
512 viewer += g_sGroupingUUID;
513 viewer += L'"';
515 if (flags.bBlame)
516 viewer += L" /blame";
518 // check if the params are set. If not, just add the files to the command line
519 if ((viewer.Find(L"%base") < 0) && (viewer.Find(L"%mine") < 0))
521 viewer += L" \"" + file1 + L'"';
522 viewer += L" \"" + file2 + L'"';
524 if (viewer.Find(L"%base") >= 0)
525 viewer.Replace(L"%base", L'"' + file1 + L'"');
526 if (viewer.Find(L"%mine") >= 0)
527 viewer.Replace(L"%mine", L'"' + file2 + L'"');
529 if (sName1.IsEmpty())
530 viewer.Replace(L"%bname", L'"' + file1 + L'"');
531 else
532 viewer.Replace(L"%bname", L'"' + sName1 + L'"');
534 if (sName2.IsEmpty())
535 viewer.Replace(L"%yname", L'"' + file2 + L'"');
536 else
537 viewer.Replace(L"%yname", L'"' + sName2 + L'"');
539 viewer.Replace(L"%bpath", L'"' + originalFile1 + L'"');
540 viewer.Replace(L"%ypath", L'"' + originalFile2 + L'"');
542 viewer.Replace(L"%brev", L'"' + hash1 + L'"');
543 viewer.Replace(L"%yrev", L'"' + hash2 + L'"');
545 viewer.Replace(L"%wtroot", L'"' + g_Git.m_CurrentDir + L'"');
547 if (flags.bReadOnly && bInternal)
548 viewer += L" /readonly";
550 if (jumpToLine > 0)
551 viewer.AppendFormat(L" /line:%d", jumpToLine);
553 return LaunchApplication(viewer, IDS_ERR_EXTDIFFSTART, flags.bWait);
556 BOOL CAppUtils::StartUnifiedDiffViewer(const CString& patchfile, const CString& title, BOOL bWait, bool bAlternativeTool)
558 CString viewer;
559 CRegString v = CRegString(L"Software\\TortoiseGit\\DiffViewer");
560 viewer = v;
562 // If registry entry for a diff program is commented out, use TortoiseGitMerge.
563 bool bCommentedOut = CStringUtils::StartsWith(viewer, L"#");
564 if (bAlternativeTool)
566 // Invert external vs. internal diff tool selection.
567 if (bCommentedOut)
568 viewer.Delete(0); // uncomment
569 else
570 viewer.Empty();
572 else if (bCommentedOut)
573 viewer.Empty();
575 if (viewer.IsEmpty())
577 // use TortoiseGitUDiff
578 viewer = CPathUtils::GetAppDirectory();
579 viewer += L"TortoiseGitUDiff.exe";
580 // enquote the path to TortoiseGitUDiff
581 viewer = L'"' + viewer + L'"';
582 // add the params
583 viewer = viewer + L" /patchfile:%1 /title:\"%title\"";
584 if (!g_sGroupingUUID.IsEmpty())
586 viewer += L" /groupuuid:\"";
587 viewer += g_sGroupingUUID;
588 viewer += L'"';
591 if (viewer.Find(L"%1") >= 0)
593 if (viewer.Find(L"\"%1\"") >= 0)
594 viewer.Replace(L"%1", patchfile);
595 else
596 viewer.Replace(L"%1", L'"' + patchfile + L'"');
598 else
599 viewer += L" \"" + patchfile + L'"';
600 if (viewer.Find(L"%title") >= 0)
601 viewer.Replace(L"%title", title);
603 if(!LaunchApplication(viewer, IDS_ERR_DIFFVIEWSTART, !!bWait))
604 return FALSE;
605 return TRUE;
608 BOOL CAppUtils::StartTextViewer(CString file)
610 CString viewer;
611 CRegString txt = CRegString(L".txt\\", L"", FALSE, HKEY_CLASSES_ROOT);
612 viewer = txt;
613 viewer = viewer + L"\\Shell\\Open\\Command\\";
614 CRegString txtexe = CRegString(viewer, L"", FALSE, HKEY_CLASSES_ROOT);
615 viewer = txtexe;
617 DWORD len = ExpandEnvironmentStrings(viewer, nullptr, 0);
618 auto buf = std::make_unique<TCHAR[]>(len + 1);
619 ExpandEnvironmentStrings(viewer, buf.get(), len);
620 viewer = buf.get();
621 len = ExpandEnvironmentStrings(file, nullptr, 0);
622 auto buf2 = std::make_unique<TCHAR[]>(len + 1);
623 ExpandEnvironmentStrings(file, buf2.get(), len);
624 file = buf2.get();
625 file = L'"' + file + L'"';
626 if (viewer.IsEmpty())
627 return CAppUtils::ShowOpenWithDialog(file) ? TRUE : FALSE;
628 if (viewer.Find(L"\"%1\"") >= 0)
629 viewer.Replace(L"\"%1\"", file);
630 else if (viewer.Find(L"%1") >= 0)
631 viewer.Replace(L"%1", file);
632 else
633 viewer += L' ';
634 viewer += file;
636 if(!LaunchApplication(viewer, IDS_ERR_TEXTVIEWSTART, false))
637 return FALSE;
638 return TRUE;
641 BOOL CAppUtils::CheckForEmptyDiff(const CTGitPath& sDiffPath)
643 DWORD length = 0;
644 CAutoFile hFile = ::CreateFile(sDiffPath.GetWinPath(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
645 if (!hFile)
646 return TRUE;
647 length = ::GetFileSize(hFile, nullptr);
648 if (length < 4)
649 return TRUE;
650 return FALSE;
653 CString CAppUtils::GetLogFontName()
655 return (CString)CRegString(L"Software\\TortoiseGit\\LogFontName", L"Consolas");
658 DWORD CAppUtils::GetLogFontSize()
660 return (DWORD)CRegDWORD(L"Software\\TortoiseGit\\LogFontSize", 9);
663 void CAppUtils::CreateFontForLogs(CFont& fontToCreate)
665 LOGFONT logFont;
666 HDC hScreenDC = ::GetDC(nullptr);
667 logFont.lfHeight = -CDPIAware::Instance().PointsToPixelsY(GetLogFontSize());
668 ::ReleaseDC(nullptr, hScreenDC);
669 logFont.lfWidth = 0;
670 logFont.lfEscapement = 0;
671 logFont.lfOrientation = 0;
672 logFont.lfWeight = FW_NORMAL;
673 logFont.lfItalic = 0;
674 logFont.lfUnderline = 0;
675 logFont.lfStrikeOut = 0;
676 logFont.lfCharSet = DEFAULT_CHARSET;
677 logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
678 logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
679 logFont.lfQuality = DRAFT_QUALITY;
680 logFont.lfPitchAndFamily = FF_DONTCARE | FIXED_PITCH;
681 wcscpy_s(logFont.lfFaceName, 32, (LPCTSTR)GetLogFontName());
682 VERIFY(fontToCreate.CreateFontIndirect(&logFont));
685 bool CAppUtils::LaunchPAgent(HWND hWnd, const CString* keyfile, const CString* pRemote)
687 CString key,remote;
688 CString cmd,out;
689 if (!pRemote)
690 remote = L"origin";
691 else
692 remote=*pRemote;
694 if (!keyfile)
696 cmd.Format(L"remote.%s.puttykeyfile", (LPCTSTR)remote);
697 key = g_Git.GetConfigValue(cmd);
699 else
700 key=*keyfile;
702 if(key.IsEmpty())
703 return false;
705 CString proc=CPathUtils::GetAppDirectory();
706 proc += L"pageant.exe \"";
707 proc += key;
708 proc += L'"';
710 CString tempfile = GetTempFile();
711 ::DeleteFile(tempfile);
713 proc += L" -c \"";
714 proc += CPathUtils::GetAppDirectory();
715 proc += L"tgittouch.exe\"";
716 proc += L" \"";
717 proc += tempfile;
718 proc += L'"';
720 CString appDir = CPathUtils::GetAppDirectory();
721 bool b = LaunchApplication(proc, IDS_ERR_PAGEANT, true, &appDir);
722 if(!b)
723 return b;
725 int i=0;
726 while(!::PathFileExists(tempfile))
728 Sleep(100);
729 ++i;
730 if(i>10*60*5)
731 break; //timeout 5 minutes
734 if( i== 10*60*5)
735 CMessageBox::Show(hWnd, IDS_ERR_PAEGENTTIMEOUT, IDS_APPNAME, MB_OK | MB_ICONERROR);
736 ::DeleteFile(tempfile);
737 return true;
740 bool CAppUtils::LaunchAlternativeEditor(const CString& filename, bool uac)
742 CString editTool = CRegString(L"Software\\TortoiseGit\\AlternativeEditor");
743 if (editTool.IsEmpty() || CStringUtils::StartsWith(editTool, L"#"))
744 editTool = CPathUtils::GetAppDirectory() + L"notepad2.exe";
746 CString sCmd;
747 sCmd.Format(L"\"%s\" \"%s\"", (LPCTSTR)editTool, (LPCTSTR)filename);
749 LaunchApplication(sCmd, 0, false, nullptr, uac);
750 return true;
753 bool CAppUtils::LaunchRemoteSetting()
755 CTGitPath path(g_Git.m_CurrentDir);
756 CSettings dlg(IDS_PROC_SETTINGS_TITLE, &path);
757 dlg.SetTreeViewMode(TRUE, TRUE, TRUE);
758 dlg.SetTreeWidth(CDPIAware::Instance().ScaleX(220));
759 dlg.m_DefaultPage = L"gitremote";
761 dlg.DoModal();
762 dlg.HandleRestart();
763 return true;
767 * Launch the external blame viewer
769 bool CAppUtils::LaunchTortoiseBlame(const CString& sBlameFile, const CString& Rev, const CString& sParams)
771 CString viewer = L'"' + CPathUtils::GetAppDirectory();
772 viewer += L"TortoiseGitBlame.exe";
773 viewer += L"\" \"" + sBlameFile + L'"';
774 //viewer += L" \"" + sLogFile + L'"';
775 //viewer += L" \"" + sOriginalFile + L'"';
776 if(!Rev.IsEmpty() && Rev != GIT_REV_ZERO)
777 viewer += L" /rev:" + Rev;
778 if (!g_sGroupingUUID.IsEmpty())
780 viewer += L" /groupuuid:\"";
781 viewer += g_sGroupingUUID;
782 viewer += L'"';
784 viewer += L' ' + sParams;
786 return LaunchApplication(viewer, IDS_ERR_TGITBLAME, false);
789 bool CAppUtils::FormatTextInRichEditControl(CWnd * pWnd)
791 CString sText;
792 if (!pWnd)
793 return false;
794 bool bStyled = false;
795 pWnd->GetWindowText(sText);
796 // the rich edit control doesn't count the CR char!
797 // to be exact: CRLF is treated as one char.
798 sText.Remove(L'\r');
800 // style each line separately
801 int offset = 0;
802 int nNewlinePos;
805 nNewlinePos = sText.Find('\n', offset);
806 CString sLine = nNewlinePos >= 0 ? sText.Mid(offset, nNewlinePos - offset) : sText.Mid(offset);
808 int start = 0;
809 int end = 0;
810 while (FindStyleChars(sLine, '*', start, end))
812 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
813 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
814 SetCharFormat(pWnd, CFM_BOLD, CFE_BOLD);
815 bStyled = true;
816 start = end;
818 start = 0;
819 end = 0;
820 while (FindStyleChars(sLine, '^', start, end))
822 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
823 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
824 SetCharFormat(pWnd, CFM_ITALIC, CFE_ITALIC);
825 bStyled = true;
826 start = end;
828 start = 0;
829 end = 0;
830 while (FindStyleChars(sLine, '_', start, end))
832 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
833 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
834 SetCharFormat(pWnd, CFM_UNDERLINE, CFE_UNDERLINE);
835 bStyled = true;
836 start = end;
838 offset = nNewlinePos+1;
839 } while(nNewlinePos>=0);
840 return bStyled;
843 bool CAppUtils::FindStyleChars(const CString& sText, TCHAR stylechar, int& start, int& end)
845 int i=start;
846 int last = sText.GetLength() - 1;
847 bool bFoundMarker = false;
848 TCHAR c = i == 0 ? L'\0' : sText[i - 1];
849 TCHAR nextChar = i >= last ? L'\0' : sText[i + 1];
851 // find a starting marker
852 while (i < last)
854 TCHAR prevChar = c;
855 c = nextChar;
856 nextChar = sText[i + 1];
858 // IsCharAlphaNumeric can be somewhat expensive.
859 // Long lines of "*****" or "----" will be pre-emptied efficiently
860 // by the (c != nextChar) condition.
862 if ((c == stylechar) && (c != nextChar))
864 if (IsCharAlphaNumeric(nextChar) && !IsCharAlphaNumeric(prevChar))
866 start = ++i;
867 bFoundMarker = true;
868 break;
871 ++i;
873 if (!bFoundMarker)
874 return false;
876 // find ending marker
877 // c == sText[i - 1]
879 bFoundMarker = false;
880 while (i <= last)
882 TCHAR prevChar = c;
883 c = sText[i];
884 if (c == stylechar)
886 if ((i == last) || (!IsCharAlphaNumeric(sText[i + 1]) && IsCharAlphaNumeric(prevChar)))
888 end = i;
889 ++i;
890 bFoundMarker = true;
891 break;
894 ++i;
896 return bFoundMarker;
899 // from CSciEdit
900 namespace {
901 bool IsValidURLChar(wchar_t ch)
903 return iswalnum(ch) ||
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'+' ||
906 ch == L'|' || ch == L'>' || ch == L'<' || ch == L'!' || ch == L'@' || ch == L'~';
909 bool IsUrlOrEmail(const CString& sText)
911 if (!PathIsURLW(sText))
913 auto atpos = sText.Find(L'@');
914 if (atpos <= 0)
915 return false;
916 if (sText.Find(L'.', atpos) <= atpos + 1) // a dot must follow after the @, but not directly after it
917 return false;
918 if (sText.Find(L':', atpos) < 0) // do not detect git@example.com:something as an email address
919 return true;
920 return false;
922 for (const CString& prefix : { L"http://", L"https://", L"git://", L"ftp://", L"file://", L"mailto:" })
924 if (CStringUtils::StartsWith(sText, prefix) && sText.GetLength() != prefix.GetLength())
925 return true;
927 return false;
931 BOOL CAppUtils::StyleURLs(const CString& msg, CWnd* pWnd)
933 std::vector<CHARRANGE> positions = FindURLMatches(msg);
934 CAppUtils::SetCharFormat(pWnd, CFM_LINK, CFE_LINK, positions);
936 return positions.empty() ? FALSE : TRUE;
940 * implements URL searching with the same logic as CSciEdit::StyleURLs
942 std::vector<CHARRANGE> CAppUtils::FindURLMatches(const CString& msg)
944 std::vector<CHARRANGE> result;
946 int len = msg.GetLength();
947 int starturl = -1;
949 for (int i = 0; i <= msg.GetLength(); ++i)
951 if ((i < len) && IsValidURLChar(msg[i]))
953 if (starturl < 0)
954 starturl = i;
956 else
958 if (starturl >= 0)
960 bool strip = true;
961 if (msg[starturl] == '<' && i < len) // try to detect and do not strip URLs put within <>
963 while (starturl <= i && msg[starturl] == '<') // strip leading '<'
964 ++starturl;
965 strip = false;
966 i = starturl;
967 while (i < len && msg[i] != '\r' && msg[i] != '\n' && msg[i] != '>') // find first '>' or new line after resetting i to start position
968 ++i;
971 int skipTrailing = 0;
972 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] == '!'))
973 ++skipTrailing;
975 if (!IsUrlOrEmail(msg.Mid(starturl, i - starturl - skipTrailing)))
977 starturl = -1;
978 continue;
981 CHARRANGE range = { starturl, i - skipTrailing };
982 result.push_back(range);
984 starturl = -1;
988 return result;
991 bool CAppUtils::StartShowUnifiedDiff(HWND hWnd, const CTGitPath& url1, const CString& rev1,
992 const CTGitPath& /*url2*/, const CString& rev2,
993 //const GitRev& peg /* = GitRev */, const GitRev& headpeg /* = GitRev */,
994 bool bAlternateDiff /* = false */, bool /*bIgnoreAncestry*/ /* = false */,
995 bool /* blame = false */,
996 bool bMerge,
997 bool bCombine,
998 bool bNoPrefix)
1000 int diffContext = g_Git.GetConfigValueInt32(L"diff.context", -1);
1001 CString tempfile=GetTempFile();
1002 if (g_Git.GetUnifiedDiff(url1, rev1, rev2, tempfile, bMerge, bCombine, diffContext, bNoPrefix))
1004 MessageBox(hWnd, g_Git.GetGitLastErr(L"Could not get unified diff.", CGit::GIT_CMD_DIFF), L"TortoiseGit", MB_OK);
1005 return false;
1007 CAppUtils::StartUnifiedDiffViewer(tempfile, rev1.IsEmpty() ? rev2 : rev1 + L':' + rev2, FALSE, bAlternateDiff);
1009 #if 0
1010 CString sCmd;
1011 sCmd.Format(L"%s /command:showcompare /unified",
1012 (LPCTSTR)(CPathUtils::GetAppDirectory()+L"TortoiseGitProc.exe"));
1013 sCmd += L" /url1:\"" + url1.GetGitPathString() + L'"';
1014 if (rev1.IsValid())
1015 sCmd += L" /revision1:" + rev1.ToString();
1016 sCmd += L" /url2:\"" + url2.GetGitPathString() + L'"';
1017 if (rev2.IsValid())
1018 sCmd += L" /revision2:" + rev2.ToString();
1019 if (peg.IsValid())
1020 sCmd += L" /pegrevision:" + peg.ToString();
1021 if (headpeg.IsValid())
1022 sCmd += L" /headpegrevision:" + headpeg.ToString();
1024 if (bAlternateDiff)
1025 sCmd += L" /alternatediff";
1027 if (bIgnoreAncestry)
1028 sCmd += L" /ignoreancestry";
1030 if (hWnd)
1032 sCmd += L" /hwnd:";
1033 TCHAR buf[30];
1034 swprintf_s(buf, 30, L"%p", (void*)hWnd);
1035 sCmd += buf;
1038 return CAppUtils::LaunchApplication(sCmd, 0, false);
1039 #endif
1040 return TRUE;
1043 bool CAppUtils::SetupDiffScripts(bool force, const CString& type)
1045 CString scriptsdir = CPathUtils::GetAppParentDirectory();
1046 scriptsdir += L"Diff-Scripts";
1047 CSimpleFileFind files(scriptsdir);
1048 while (files.FindNextFileNoDirectories())
1050 CString file = files.GetFilePath();
1051 CString filename = files.GetFileName();
1052 CString ext = file.Mid(file.ReverseFind('-') + 1);
1053 ext = L"." + ext.Left(ext.ReverseFind(L'.'));
1054 std::set<CString> extensions;
1055 extensions.insert(ext);
1056 CString kind;
1057 if (CStringUtils::EndsWithI(file, L"vbs"))
1058 kind = L" //E:vbscript";
1059 if (CStringUtils::EndsWithI(file, L"js"))
1060 kind = L" //E:javascript";
1061 // open the file, read the first line and find possible extensions
1062 // this script can handle
1065 CStdioFile f(file, CFile::modeRead | CFile::shareDenyNone);
1066 CString extline;
1067 if (f.ReadString(extline))
1069 if ((extline.GetLength() > 15 ) &&
1070 (CStringUtils::StartsWith(extline, L"// extensions: ") ||
1071 CStringUtils::StartsWith(extline, L"' extensions: ")))
1073 if (extline[0] == '/')
1074 extline = extline.Mid((int)wcslen(L"// extensions: "));
1075 else
1076 extline = extline.Mid((int)wcslen(L"' extensions: "));
1077 CString sToken;
1078 int curPos = 0;
1079 sToken = extline.Tokenize(L";", curPos);
1080 while (!sToken.IsEmpty())
1082 if (!sToken.IsEmpty())
1084 if (sToken[0] != '.')
1085 sToken = L"." + sToken;
1086 extensions.insert(sToken);
1088 sToken = extline.Tokenize(L";", curPos);
1092 f.Close();
1094 catch (CFileException* e)
1096 e->Delete();
1099 for (const auto& extension : extensions)
1101 if (type.IsEmpty() || (type.Compare(L"Diff") == 0))
1103 if (CStringUtils::StartsWithI(filename, L"diff-"))
1105 CRegString diffreg = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + extension);
1106 CString diffregstring = diffreg;
1107 if (force || (diffregstring.IsEmpty()) || (diffregstring.Find(filename) >= 0))
1108 diffreg = L"wscript.exe \"" + file + L"\" %base %mine" + kind;
1111 if (type.IsEmpty() || (type.Compare(L"Merge") == 0))
1113 if (CStringUtils::StartsWithI(filename, L"merge-"))
1115 CRegString diffreg = CRegString(L"Software\\TortoiseGit\\MergeTools\\" + extension);
1116 CString diffregstring = diffreg;
1117 if (force || (diffregstring.IsEmpty()) || (diffregstring.Find(filename) >= 0))
1118 diffreg = L"wscript.exe \"" + file + L"\" %merged %theirs %mine %base" + kind;
1124 return true;
1127 bool CAppUtils::Export(HWND hWnd, const CString* BashHash, const CTGitPath* orgPath)
1129 // ask from where the export has to be done
1130 CExportDlg dlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
1131 if(BashHash)
1132 dlg.m_initialRefName=*BashHash;
1133 if (orgPath)
1135 if (PathIsRelative(orgPath->GetWinPath()))
1136 dlg.m_orgPath = g_Git.CombinePath(orgPath);
1137 else
1138 dlg.m_orgPath = *orgPath;
1141 if (dlg.DoModal() == IDOK)
1143 CString cmd;
1144 cmd.Format(L"git.exe archive --output=\"%s\" --format=zip --verbose %s --",
1145 (LPCTSTR)dlg.m_strFile, (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName));
1147 CProgressDlg pro(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
1148 if (GetExplorerHWND() == hWnd)
1149 theApp.m_pMainWnd = &pro;
1150 pro.m_GitCmd=cmd;
1151 pro.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1153 if (status)
1154 return;
1155 postCmdList.emplace_back(IDI_EXPLORER, IDS_STATUSLIST_CONTEXT_EXPLORE, [&]{ CAppUtils::ExploreTo(hWnd, dlg.m_strFile); });
1158 CGit git;
1159 if (!dlg.m_bWholeProject && !dlg.m_orgPath.IsEmpty() && PathIsDirectory(dlg.m_orgPath.GetWinPathString()))
1161 git.m_CurrentDir = dlg.m_orgPath.GetWinPathString();
1162 pro.m_Git = &git;
1164 return (pro.DoModal() == IDOK);
1166 return false;
1169 bool CAppUtils::UpdateBranchDescription(const CString& branch, CString description)
1171 if (branch.IsEmpty())
1172 return false;
1174 CString key;
1175 key.Format(L"branch.%s.description", (LPCTSTR)branch);
1176 description.Remove(L'\r');
1177 description.Trim();
1178 if (description.IsEmpty())
1179 g_Git.UnsetConfigValue(key);
1180 else
1181 g_Git.SetConfigValue(key, description);
1183 return true;
1186 bool CAppUtils::CreateBranchTag(HWND hWnd, bool isTag /*true*/, const CString* commitHash /*nullptr*/, bool switchNewBranch /*false*/, LPCTSTR name /*nullptr*/)
1188 CCreateBranchTagDlg dlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
1189 dlg.m_bIsTag = isTag;
1190 dlg.m_bSwitch = switchNewBranch;
1192 if (commitHash)
1193 dlg.m_initialRefName = *commitHash;
1195 if (name)
1196 dlg.m_BranchTagName = name;
1198 if(dlg.DoModal()==IDOK)
1200 CString cmd;
1201 CString force;
1202 CString track;
1203 if(dlg.m_bTrack == TRUE)
1204 track = L"--track";
1205 else if(dlg.m_bTrack == FALSE)
1206 track = L"--no-track";
1208 if(dlg.m_bForce)
1209 force = L"-f";
1211 if (isTag)
1213 CString sign;
1214 if(dlg.m_bSign)
1215 sign = L"-s";
1217 cmd.Format(L"git.exe tag %s %s %s %s",
1218 (LPCTSTR)force,
1219 (LPCTSTR)sign,
1220 (LPCTSTR)dlg.m_BranchTagName,
1221 (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName)
1224 if(!dlg.m_Message.Trim().IsEmpty())
1226 CString tempfile = ::GetTempFile();
1227 if (CAppUtils::SaveCommitUnicodeFile(tempfile, dlg.m_Message))
1229 MessageBox(hWnd, L"Could not save tag message", L"TortoiseGit", MB_OK | MB_ICONERROR);
1230 return FALSE;
1232 cmd += L" -F " + tempfile;
1235 else
1237 cmd.Format(L"git.exe branch %s %s %s %s",
1238 (LPCTSTR)track,
1239 (LPCTSTR)force,
1240 (LPCTSTR)dlg.m_BranchTagName,
1241 (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName)
1244 CString out;
1245 if(g_Git.Run(cmd,&out,CP_UTF8))
1247 MessageBox(hWnd, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
1248 return FALSE;
1250 if (!isTag && dlg.m_bSwitch)
1252 // it is a new branch and the user has requested to switch to it
1253 PerformSwitch(hWnd, dlg.m_BranchTagName);
1255 if (!isTag && !dlg.m_Message.IsEmpty())
1256 UpdateBranchDescription(dlg.m_BranchTagName, dlg.m_Message);
1258 return TRUE;
1260 return FALSE;
1263 bool CAppUtils::Switch(HWND hWnd, const CString& initialRefName)
1265 CGitSwitchDlg dlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
1266 if(!initialRefName.IsEmpty())
1267 dlg.m_initialRefName = initialRefName;
1269 if (dlg.DoModal() == IDOK)
1271 CString branch;
1272 if (dlg.m_bBranch)
1273 branch = dlg.m_NewBranch;
1275 // if refs/heads/ is not stripped, checkout will detach HEAD
1276 // checkout prefers branches on name clashes (with tags)
1277 if (CStringUtils::StartsWith(dlg.m_VersionName, L"refs/heads/") && dlg.m_bBranchOverride != TRUE)
1278 dlg.m_VersionName = dlg.m_VersionName.Mid((int)wcslen(L"refs/heads/"));
1280 return PerformSwitch(hWnd, dlg.m_VersionName, dlg.m_bForce == TRUE, branch, dlg.m_bBranchOverride == TRUE, dlg.m_bTrack, dlg.m_bMerge == TRUE);
1282 return FALSE;
1285 bool CAppUtils::PerformSwitch(HWND hWnd, const CString& ref, bool bForce /* false */, const CString& sNewBranch /* CString() */, bool bBranchOverride /* false */, BOOL bTrack /* 2 */, bool bMerge /* false */)
1287 CString cmd;
1288 CString track;
1289 CString force;
1290 CString branch;
1291 CString merge;
1293 if(!sNewBranch.IsEmpty()){
1294 if (bBranchOverride)
1295 branch.Format(L"-B %s ", (LPCTSTR)sNewBranch);
1296 else
1297 branch.Format(L"-b %s ", (LPCTSTR)sNewBranch);
1298 if (bTrack == TRUE)
1299 track = L"--track ";
1300 else if (bTrack == FALSE)
1301 track = L"--no-track ";
1303 if (bForce)
1304 force = L"-f ";
1305 if (bMerge)
1306 merge = L"--merge ";
1308 cmd.Format(L"git.exe checkout %s%s%s%s%s --",
1309 (LPCTSTR)force,
1310 (LPCTSTR)track,
1311 (LPCTSTR)merge,
1312 (LPCTSTR)branch,
1313 (LPCTSTR)g_Git.FixBranchName(ref));
1315 CProgressDlg progress(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
1316 progress.m_GitCmd = cmd;
1318 CString currentBranch;
1319 bool hasBranch = CGit::GetCurrentBranchFromFile(g_Git.m_CurrentDir, currentBranch) == 0;
1320 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1322 if (!status)
1324 CTGitPath gitPath = g_Git.m_CurrentDir;
1325 if (gitPath.HasSubmodules())
1327 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1329 CString sCmd;
1330 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
1331 RunTortoiseGitProc(sCmd);
1334 if (hasBranch)
1335 postCmdList.emplace_back(IDI_MERGE, IDS_MENUMERGE, [&]{ Merge(hWnd, &currentBranch); });
1338 CString newBranch;
1339 if (!CGit::GetCurrentBranchFromFile(g_Git.m_CurrentDir, newBranch))
1340 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&hWnd]{ Pull(hWnd); });
1342 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUCOMMIT, [&]{
1343 CTGitPathList pathlist;
1344 pathlist.AddPath(CTGitPath());
1345 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(L"Software\\TortoiseGit\\SelectFilesForCommit", TRUE));
1346 CString str;
1347 Commit(hWnd, CString(), false, str, pathlist, bSelectFilesForCommit);
1350 else
1352 if (bMerge && g_Git.HasWorkingTreeConflicts() > 0)
1354 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
1356 CString sCmd;
1357 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
1358 CAppUtils::RunTortoiseGitProc(sCmd);
1361 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ PerformSwitch(hWnd, ref, bForce, sNewBranch, bBranchOverride, bTrack, bMerge); });
1362 if (!bMerge)
1363 postCmdList.emplace_back(IDI_SWITCH, IDS_SWITCH_WITH_MERGE, [&]{ PerformSwitch(hWnd, ref, bForce, sNewBranch, bBranchOverride, bTrack, true); });
1366 progress.m_PostExecCallback = [&](DWORD& exitCode, CString& extraMsg)
1368 if (bMerge && !exitCode && g_Git.HasWorkingTreeConflicts() > 0)
1370 exitCode = 1; // Treat it as failure
1371 extraMsg = L"Has merge conflict";
1375 INT_PTR ret = progress.DoModal();
1377 return ret == IDOK;
1380 class CIgnoreFile : public CStdioFile
1382 public:
1383 STRING_VECTOR m_Items;
1384 CString m_eol;
1386 virtual BOOL ReadString(CString& rString)
1388 if (GetPosition() == 0)
1390 unsigned char utf8bom[] = { 0xEF, 0xBB, 0xBF };
1391 char buf[3] = { 0, 0, 0 };
1392 Read(buf, 3);
1393 if (memcpy(buf, utf8bom, sizeof(utf8bom)))
1394 SeekToBegin();
1397 CStringA strA;
1398 char lastChar = '\0';
1399 for (char c = '\0'; Read(&c, 1) == 1; lastChar = c)
1401 if (c == '\r')
1402 continue;
1403 if (c == '\n')
1405 m_eol = lastChar == L'\r' ? L"\r\n" : L"\n";
1406 break;
1408 strA.AppendChar(c);
1410 if (strA.IsEmpty())
1411 return FALSE;
1413 rString = CUnicodeUtils::GetUnicode(strA);
1414 return TRUE;
1417 void ResetState()
1419 m_Items.clear();
1420 m_eol.Empty();
1424 bool CAppUtils::OpenIgnoreFile(HWND hWnd, CIgnoreFile &file, const CString& filename)
1426 file.ResetState();
1427 if (!file.Open(filename, CFile::modeCreate | CFile::modeReadWrite | CFile::modeNoTruncate | CFile::typeBinary))
1429 MessageBox(hWnd, filename + L" Open Failure", L"TortoiseGit", MB_OK | MB_ICONERROR);
1430 return false;
1433 if (file.GetLength() > 0)
1435 CString fileText;
1436 while (file.ReadString(fileText))
1437 file.m_Items.push_back(fileText);
1438 file.Seek(file.GetLength() - 1, 0);
1439 char lastchar[1] = { 0 };
1440 file.Read(lastchar, 1);
1441 file.SeekToEnd();
1442 if (lastchar[0] != '\n')
1444 CStringA eol = CStringA(file.m_eol.IsEmpty() ? L"\n" : file.m_eol);
1445 file.Write(eol, eol.GetLength());
1448 else
1449 file.SeekToEnd();
1451 return true;
1454 bool CAppUtils::IgnoreFile(HWND hWnd, const CTGitPathList& path,bool IsMask)
1456 CIgnoreDlg ignoreDlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
1457 if (ignoreDlg.DoModal() == IDOK)
1459 CString ignorefile;
1460 ignorefile = g_Git.m_CurrentDir + L'\\';
1462 switch (ignoreDlg.m_IgnoreFile)
1464 case 0:
1465 ignorefile += L".gitignore";
1466 break;
1467 case 2:
1468 GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, ignorefile);
1469 ignorefile += L"info";
1470 if (!PathFileExists(ignorefile))
1471 CreateDirectory(ignorefile, nullptr);
1472 ignorefile += L"\\exclude";
1473 break;
1476 CIgnoreFile file;
1479 if (ignoreDlg.m_IgnoreFile != 1 && !OpenIgnoreFile(hWnd, file, ignorefile))
1480 return false;
1482 for (int i = 0; i < path.GetCount(); ++i)
1484 if (ignoreDlg.m_IgnoreFile == 1)
1486 ignorefile = g_Git.CombinePath(path[i].GetContainingDirectory()) + L"\\.gitignore";
1487 if (!OpenIgnoreFile(hWnd, file, ignorefile))
1488 return false;
1491 CString ignorePattern;
1492 if (ignoreDlg.m_IgnoreType == 0)
1494 if (ignoreDlg.m_IgnoreFile != 1 && !path[i].GetContainingDirectory().GetGitPathString().IsEmpty())
1495 ignorePattern += L'/' + path[i].GetContainingDirectory().GetGitPathString();
1497 ignorePattern += L'/';
1499 if (IsMask)
1500 ignorePattern += L'*' + path[i].GetFileExtension();
1501 else
1502 ignorePattern += path[i].GetFileOrDirectoryName();
1504 // escape [ and ] so that files get ignored correctly
1505 ignorePattern.Replace(L"[", L"\\[");
1506 ignorePattern.Replace(L"]", L"\\]");
1508 bool found = false;
1509 for (size_t j = 0; j < file.m_Items.size(); ++j)
1511 if (file.m_Items[j] == ignorePattern)
1513 found = true;
1514 break;
1517 if (!found)
1519 file.m_Items.push_back(ignorePattern);
1520 ignorePattern += file.m_eol.IsEmpty() ? L"\n" : file.m_eol;
1521 CStringA ignorePatternA = CUnicodeUtils::GetUTF8(ignorePattern);
1522 file.Write(ignorePatternA, ignorePatternA.GetLength());
1525 if (ignoreDlg.m_IgnoreFile == 1)
1526 file.Close();
1529 if (ignoreDlg.m_IgnoreFile != 1)
1530 file.Close();
1532 catch(...)
1534 file.Abort();
1535 return false;
1538 return true;
1540 return false;
1543 static bool Reset(HWND hWnd, const CString& resetTo, int resetType)
1545 CString cmd;
1546 CString type;
1547 switch (resetType)
1549 case 0:
1550 type = L"--soft";
1551 break;
1552 case 1:
1553 type = L"--mixed";
1554 break;
1555 case 2:
1556 type = L"--hard";
1557 break;
1558 case 3:
1560 CProgressDlg progress(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
1561 progress.m_GitCmd = L"git.exe reset --merge";
1562 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1564 if (status)
1566 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&hWnd] { CAppUtils::MergeAbort(hWnd); });
1567 return;
1570 CTGitPath gitPath = g_Git.m_CurrentDir;
1571 if (gitPath.HasSubmodules() && resetType == 2)
1573 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1575 CString sCmd;
1576 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
1577 CAppUtils::RunTortoiseGitProc(sCmd);
1581 return progress.DoModal() == IDOK;
1583 default:
1584 ATLASSERT(false);
1585 resetType = 1;
1586 type = L"--mixed";
1587 break;
1589 cmd.Format(L"git.exe reset %s %s --", (LPCTSTR)type, (LPCTSTR)resetTo);
1591 CProgressDlg progress(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
1592 progress.m_GitCmd = cmd;
1594 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1596 if (status)
1598 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ Reset(hWnd, resetTo, resetType); });
1599 return;
1602 CTGitPath gitPath = g_Git.m_CurrentDir;
1603 if (gitPath.HasSubmodules() && resetType == 2)
1605 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1607 CString sCmd;
1608 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
1609 CAppUtils::RunTortoiseGitProc(sCmd);
1613 if (gitPath.IsBisectActive())
1615 postCmdList.emplace_back(IDI_THUMB_UP, IDS_MENUBISECTGOOD, [] { CAppUtils::RunTortoiseGitProc(L"/command:bisect /good"); });
1616 postCmdList.emplace_back(IDI_THUMB_DOWN, IDS_MENUBISECTBAD, [] { CAppUtils::RunTortoiseGitProc(L"/command:bisect /bad"); });
1617 postCmdList.emplace_back(IDI_BISECT, IDS_MENUBISECTSKIP, [] { CAppUtils::RunTortoiseGitProc(L"/command:bisect /skip"); });
1618 postCmdList.emplace_back(IDI_BISECT_RESET, IDS_MENUBISECTRESET, [] { CAppUtils::RunTortoiseGitProc(L"/command:bisect /reset"); });
1622 INT_PTR ret;
1623 if (g_Git.UsingLibGit2(CGit::GIT_CMD_RESET))
1625 CGitProgressDlg gitdlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
1626 ResetProgressCommand resetProgressCommand;
1627 gitdlg.SetCommand(&resetProgressCommand);
1628 resetProgressCommand.m_PostCmdCallback = progress.m_PostCmdCallback;
1629 resetProgressCommand.SetRevision(resetTo);
1630 resetProgressCommand.SetResetType(resetType);
1631 ret = gitdlg.DoModal();
1633 else
1634 ret = progress.DoModal();
1636 return ret == IDOK;
1639 bool CAppUtils::GitReset(HWND hWnd, const CString* CommitHash, int type)
1641 CResetDlg dlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
1642 dlg.m_ResetType=type;
1643 dlg.m_ResetToVersion=*CommitHash;
1644 dlg.m_initialRefName = *CommitHash;
1645 if (dlg.DoModal() == IDOK)
1646 return Reset(hWnd, dlg.m_ResetToVersion, dlg.m_ResetType);
1648 return false;
1651 void CAppUtils::DescribeConflictFile(bool mode, bool base,CString &descript)
1653 if(mode == FALSE)
1655 descript.LoadString(IDS_SVNACTION_DELETE);
1656 return;
1658 if(base)
1660 descript.LoadString(IDS_SVNACTION_MODIFIED);
1661 return;
1663 descript.LoadString(IDS_PROC_CREATED);
1666 void CAppUtils::RemoveTempMergeFile(const CTGitPath& path)
1668 ::DeleteFile(CAppUtils::GetMergeTempFile(L"LOCAL", path));
1669 ::DeleteFile(CAppUtils::GetMergeTempFile(L"REMOTE", path));
1670 ::DeleteFile(CAppUtils::GetMergeTempFile(L"BASE", path));
1672 CString CAppUtils::GetMergeTempFile(const CString& type, const CTGitPath &merge)
1674 return g_Git.CombinePath(merge.GetWinPathString() + L'.' + type + merge.GetFileExtension());;
1677 static bool ParseHashesFromLsFile(const BYTE_VECTOR& out, CString& hash1, bool& isFile1, CString& hash2, bool& isFile2, CString& hash3, bool& isFile3)
1679 size_t pos = 0;
1680 CString one;
1681 CString part;
1683 while (pos < out.size())
1685 one.Empty();
1687 CGit::StringAppend(&one, &out[pos], CP_UTF8);
1688 int tabstart = 0;
1689 one.Tokenize(L"\t", tabstart);
1691 tabstart = 0;
1692 part = one.Tokenize(L" ", tabstart); //Tag
1693 CString mode = one.Tokenize(L" ", tabstart); //Mode
1694 part = one.Tokenize(L" ", tabstart); //Hash
1695 CString hash = part;
1696 part = one.Tokenize(L"\t", tabstart); //Stage
1697 int stage = _wtol(part);
1698 if (stage == 1)
1700 hash1 = hash;
1701 isFile1 = _wtol(mode) != 160000;
1703 else if (stage == 2)
1705 hash2 = hash;
1706 isFile2 = _wtol(mode) != 160000;
1708 else if (stage == 3)
1710 hash3 = hash;
1711 isFile3 = _wtol(mode) != 160000;
1712 return true;
1715 pos = out.findNextString(pos);
1718 return false;
1721 void CAppUtils::GetConflictTitles(CString* baseText, CString& mineText, CString& theirsText, bool rebaseActive)
1723 if (baseText)
1724 baseText->LoadString(IDS_PROC_DIFF_BASE);
1725 if (rebaseActive)
1727 CString adminDir;
1728 GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir);
1729 mineText = L"Branch being rebased onto";
1730 if (!CStringUtils::ReadStringFromTextFile(adminDir + L"tgitrebase.active\\onto", mineText))
1732 CGitHash hash;
1733 if (!g_Git.GetHash(hash, L"rebase-apply/onto"))
1734 g_Git.GuessRefForHash(mineText, hash);
1736 theirsText = L"Branch being rebased";
1737 if (!CStringUtils::ReadStringFromTextFile(adminDir + L"tgitrebase.active\\head-name", theirsText))
1739 if (CStringUtils::ReadStringFromTextFile(adminDir + L"rebase-apply/head-name", theirsText))
1740 theirsText = CGit::StripRefName(theirsText);
1742 return;
1745 static const struct {
1746 const wchar_t* headref;
1747 bool guessRef;
1748 UINT theirstext;
1749 } infotexts[] = { { L"MERGE_HEAD", true, IDS_CONFLICT_INFOTEXT }, { L"CHERRY_PICK_HEAD", false, IDS_CONFLICT_INFOTEXT }, { L"REVERT_HEAD", false, IDS_CONFLICT_REVERT } };
1750 mineText = L"HEAD";
1751 theirsText.LoadString(IDS_CONFLICT_REFTOBEMERGED);
1752 for (const auto& infotext : infotexts)
1754 CGitHash hash;
1755 if (!g_Git.GetHash(hash, infotext.headref))
1757 CString guessedRef;
1758 if (!infotext.guessRef)
1759 guessedRef = hash.ToString();
1760 else
1761 g_Git.GuessRefForHash(guessedRef, hash);
1762 theirsText.FormatMessage(infotext.theirstext, infotext.headref, (LPCTSTR)guessedRef);
1763 break;
1768 bool CAppUtils::ConflictEdit(HWND hWnd, CTGitPath& path, bool bAlternativeTool /*= false*/, bool isRebase /*= false*/, HWND resolveMsgHwnd /*= nullptr*/)
1770 CTGitPath merge=path;
1771 CTGitPath directory = merge.GetDirectory();
1773 BYTE_VECTOR vector;
1775 CString cmd;
1776 cmd.Format(L"git.exe ls-files -u -t -z -- \"%s\"", (LPCTSTR)merge.GetGitPathString());
1778 if (g_Git.Run(cmd, &vector))
1779 return FALSE;
1781 CString baseTitle, mineTitle, theirsTitle;
1782 GetConflictTitles(&baseTitle, mineTitle, theirsTitle, isRebase);
1784 CString baseHash, realBaseHash(GIT_REV_ZERO), localHash(GIT_REV_ZERO), remoteHash(GIT_REV_ZERO);
1785 bool baseIsFile = true, localIsFile = true, remoteIsFile = true;
1786 if (ParseHashesFromLsFile(vector, realBaseHash, baseIsFile, localHash, localIsFile, remoteHash, remoteIsFile))
1787 baseHash = realBaseHash;
1789 if (!baseIsFile || !localIsFile || !remoteIsFile)
1791 if (merge.HasAdminDir())
1793 CGit subgit;
1794 subgit.m_CurrentDir = g_Git.CombinePath(merge);
1795 CGitHash hash;
1796 subgit.GetHash(hash, L"HEAD");
1797 baseHash = hash.ToString();
1800 CGitDiff::ChangeType changeTypeMine = CGitDiff::Unknown;
1801 CGitDiff::ChangeType changeTypeTheirs = CGitDiff::Unknown;
1803 bool baseOK = false, mineOK = false, theirsOK = false;
1804 CString baseSubject, mineSubject, theirsSubject;
1805 if (merge.HasAdminDir())
1807 CGit subgit;
1808 subgit.m_CurrentDir = g_Git.CombinePath(merge);
1809 CGitDiff::GetSubmoduleChangeType(subgit, baseHash, localHash, baseOK, mineOK, changeTypeMine, baseSubject, mineSubject);
1810 CGitDiff::GetSubmoduleChangeType(subgit, baseHash, remoteHash, baseOK, theirsOK, changeTypeTheirs, baseSubject, theirsSubject);
1812 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)
1814 changeTypeMine = CGitDiff::Identical;
1815 changeTypeTheirs = CGitDiff::NewSubmodule;
1816 baseSubject.LoadString(IDS_CONFLICT_NOSUBMODULE);
1817 mineSubject = baseSubject;
1818 theirsSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1820 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
1822 baseHash = localHash;
1823 baseSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1824 mineSubject = baseSubject;
1825 theirsSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1826 changeTypeMine = CGitDiff::Identical;
1827 changeTypeTheirs = CGitDiff::DeleteSubmodule;
1829 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
1831 baseSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1832 mineSubject = baseSubject;
1833 theirsSubject = baseSubject;
1834 if (baseHash == localHash)
1835 changeTypeMine = CGitDiff::Identical;
1837 else if (baseHash == GIT_REV_ZERO && localHash != GIT_REV_ZERO && remoteHash != GIT_REV_ZERO)
1839 baseOK = true;
1840 mineSubject = baseSubject;
1841 if (remoteIsFile)
1843 theirsSubject.LoadString(IDS_CONFLICT_NOTASUBMODULE);
1844 changeTypeMine = CGitDiff::NewSubmodule;
1846 else
1847 theirsSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1848 if (localIsFile)
1850 mineSubject.LoadString(IDS_CONFLICT_NOTASUBMODULE);
1851 changeTypeTheirs = CGitDiff::NewSubmodule;
1853 else
1854 mineSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1856 else if (baseHash != GIT_REV_ZERO && (localHash == GIT_REV_ZERO || remoteHash == GIT_REV_ZERO))
1858 baseSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1859 if (localHash == GIT_REV_ZERO)
1861 mineSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1862 changeTypeMine = CGitDiff::DeleteSubmodule;
1864 else
1866 mineSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1867 if (localHash == baseHash)
1868 changeTypeMine = CGitDiff::Identical;
1870 if (remoteHash == GIT_REV_ZERO)
1872 theirsSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1873 changeTypeTheirs = CGitDiff::DeleteSubmodule;
1875 else
1877 theirsSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1878 if (remoteHash == baseHash)
1879 changeTypeTheirs = CGitDiff::Identical;
1882 else
1883 return FALSE;
1885 CSubmoduleResolveConflictDlg resolveSubmoduleConflictDialog(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
1886 resolveSubmoduleConflictDialog.SetDiff(merge.GetGitPathString(), isRebase, baseTitle, mineTitle, theirsTitle, baseHash, baseSubject, baseOK, localHash, mineSubject, mineOK, changeTypeMine, remoteHash, theirsSubject, theirsOK, changeTypeTheirs);
1887 resolveSubmoduleConflictDialog.DoModal();
1888 if (resolveSubmoduleConflictDialog.m_bResolved && resolveMsgHwnd)
1890 static UINT WM_REVERTMSG = RegisterWindowMessage(L"GITSLNM_NEEDSREFRESH");
1891 ::PostMessage(resolveMsgHwnd, WM_REVERTMSG, NULL, NULL);
1894 return TRUE;
1897 CTGitPathList list;
1898 if (list.ParserFromLsFile(vector))
1900 MessageBox(hWnd, L"Parse ls-files failed!", L"TortoiseGit", MB_OK | MB_ICONERROR);
1901 return FALSE;
1904 if (list.IsEmpty())
1905 return FALSE;
1907 CTGitPath theirs;
1908 CTGitPath mine;
1909 CTGitPath base;
1911 if (isRebase)
1913 mine.SetFromGit(GetMergeTempFile(L"REMOTE", merge));
1914 theirs.SetFromGit(GetMergeTempFile(L"LOCAL", merge));
1916 else
1918 mine.SetFromGit(GetMergeTempFile(L"LOCAL", merge));
1919 theirs.SetFromGit(GetMergeTempFile(L"REMOTE", merge));
1921 base.SetFromGit(GetMergeTempFile(L"BASE",merge));
1923 CString format = L"git.exe checkout-index --temp --stage=%d -- \"%s\"";
1924 CFile tempfile;
1925 //create a empty file, incase stage is not three
1926 tempfile.Open(mine.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1927 tempfile.Close();
1928 tempfile.Open(theirs.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1929 tempfile.Close();
1930 tempfile.Open(base.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1931 tempfile.Close();
1933 bool b_base=false, b_local=false, b_remote=false;
1935 for (int i = 0; i< list.GetCount(); ++i)
1937 CString outfile;
1938 cmd.Empty();
1939 outfile.Empty();
1941 if( list[i].m_Stage == 1)
1943 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1944 b_base = true;
1945 outfile = base.GetWinPathString();
1948 if( list[i].m_Stage == 2 )
1950 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1951 b_local = true;
1952 outfile = mine.GetWinPathString();
1955 if( list[i].m_Stage == 3 )
1957 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1958 b_remote = true;
1959 outfile = theirs.GetWinPathString();
1961 CString output, err;
1962 if(!outfile.IsEmpty())
1963 if (!g_Git.Run(cmd, &output, &err, CP_UTF8))
1965 CString file;
1966 int start =0 ;
1967 file = output.Tokenize(L"\t", start);
1968 ::MoveFileEx(file,outfile,MOVEFILE_REPLACE_EXISTING|MOVEFILE_COPY_ALLOWED);
1970 else
1971 CMessageBox::Show(hWnd, output + L'\n' + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
1974 if(b_local && b_remote )
1976 merge.SetFromWin(g_Git.CombinePath(merge));
1977 if (isRebase)
1978 return !!CAppUtils::StartExtMerge(bAlternativeTool, base, mine, theirs, merge, baseTitle, mineTitle, theirsTitle, CString(), false, resolveMsgHwnd, true);
1980 return !!CAppUtils::StartExtMerge(bAlternativeTool, base, theirs, mine, merge, baseTitle, theirsTitle, mineTitle, CString(), false, resolveMsgHwnd, true);
1982 else
1984 ::DeleteFile(mine.GetWinPathString());
1985 ::DeleteFile(theirs.GetWinPathString());
1986 if (!b_base)
1987 ::DeleteFile(base.GetWinPathString());
1989 SCOPE_EXIT{
1990 if (b_base)
1991 ::DeleteFile(base.GetWinPathString());
1994 CDeleteConflictDlg dlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
1995 if (!isRebase)
1997 DescribeConflictFile(b_local, b_base, dlg.m_LocalStatus);
1998 DescribeConflictFile(b_remote, b_base, dlg.m_RemoteStatus);
1999 dlg.m_LocalHash = mineTitle;
2000 dlg.m_RemoteHash = theirsTitle;
2001 dlg.m_bDiffMine = b_local;
2003 else
2005 DescribeConflictFile(b_local, b_base, dlg.m_RemoteStatus);
2006 DescribeConflictFile(b_remote, b_base, dlg.m_LocalStatus);
2007 dlg.m_LocalHash = theirsTitle;
2008 dlg.m_RemoteHash = mineTitle;
2009 dlg.m_bDiffMine = !b_local;
2011 dlg.m_bShowModifiedButton = b_base;
2012 dlg.m_File = merge;
2013 dlg.m_FileBaseVersion = base;
2014 if(dlg.DoModal() == IDOK)
2016 CString out;
2017 if(dlg.m_bIsDelete)
2018 cmd.Format(L"git.exe rm -- \"%s\"", (LPCTSTR)merge.GetGitPathString());
2019 else
2020 cmd.Format(L"git.exe add -- \"%s\"", (LPCTSTR)merge.GetGitPathString());
2022 if (g_Git.Run(cmd, &out, CP_UTF8))
2024 MessageBox(hWnd, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
2025 return FALSE;
2027 if (!dlg.m_bIsDelete)
2029 path.m_Action |= CTGitPath::LOGACTIONS_ADDED;
2030 path.m_Action &= ~CTGitPath::LOGACTIONS_UNMERGED;
2032 return TRUE;
2034 return FALSE;
2038 bool CAppUtils::IsSSHPutty()
2040 CString sshclient = g_Git.m_Environment.GetEnv(L"GIT_SSH");
2041 sshclient=sshclient.MakeLower();
2042 return sshclient.Find(L"plink", 0) >= 0;
2045 CString CAppUtils::GetClipboardLink(const CString &skipGitPrefix, int paramsCount)
2047 if (!OpenClipboard(nullptr))
2048 return CString();
2050 CString sClipboardText;
2051 HGLOBAL hglb = GetClipboardData(CF_TEXT);
2052 if (hglb)
2054 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
2055 sClipboardText = CString(lpstr);
2056 GlobalUnlock(hglb);
2058 hglb = GetClipboardData(CF_UNICODETEXT);
2059 if (hglb)
2061 LPCTSTR lpstr = (LPCTSTR)GlobalLock(hglb);
2062 sClipboardText = lpstr;
2063 GlobalUnlock(hglb);
2065 CloseClipboard();
2067 if(!sClipboardText.IsEmpty())
2069 if (sClipboardText[0] == L'"' && sClipboardText[sClipboardText.GetLength() - 1] == L'"')
2070 sClipboardText=sClipboardText.Mid(1,sClipboardText.GetLength()-2);
2072 for (const CString& prefix : { L"http://", L"https://", L"git://", L"ssh://", L"git@" })
2074 if (CStringUtils::StartsWith(sClipboardText, prefix) && sClipboardText.GetLength() != prefix.GetLength())
2075 return sClipboardText;
2078 if(sClipboardText.GetLength()>=2)
2079 if (sClipboardText[1] == L':')
2080 if( (sClipboardText[0] >= 'A' && sClipboardText[0] <= 'Z')
2081 || (sClipboardText[0] >= 'a' && sClipboardText[0] <= 'z') )
2082 return sClipboardText;
2084 // trim prefixes like "git clone "
2085 if (!skipGitPrefix.IsEmpty() && CStringUtils::StartsWith(sClipboardText, skipGitPrefix))
2087 sClipboardText = sClipboardText.Mid(skipGitPrefix.GetLength()).Trim();
2088 int spacePos = -1;
2089 while (paramsCount >= 0)
2091 --paramsCount;
2092 spacePos = sClipboardText.Find(L' ', spacePos + 1);
2093 if (spacePos == -1)
2094 break;
2096 if (spacePos > 0 && paramsCount < 0)
2097 sClipboardText.Truncate(spacePos);
2098 return sClipboardText;
2102 return CString();
2105 CString CAppUtils::ChooseRepository(HWND hWnd, const CString* path)
2107 CBrowseFolder browseFolder;
2108 CRegString regLastResopitory = CRegString(L"Software\\TortoiseGit\\TortoiseProc\\LastRepo", L"");
2110 browseFolder.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
2111 CString strCloneDirectory;
2112 if(path)
2113 strCloneDirectory=*path;
2114 else
2115 strCloneDirectory = regLastResopitory;
2117 CString title;
2118 title.LoadString(IDS_CHOOSE_REPOSITORY);
2120 browseFolder.SetInfo(title);
2122 if (browseFolder.Show(hWnd, strCloneDirectory) == CBrowseFolder::OK)
2124 regLastResopitory = strCloneDirectory;
2125 return strCloneDirectory;
2127 else
2128 return CString();
2131 bool CAppUtils::SendPatchMail(HWND hWnd, CTGitPathList& list)
2133 CSendMailDlg dlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
2135 dlg.m_PathList = list;
2137 if(dlg.DoModal()==IDOK)
2139 if (dlg.m_PathList.IsEmpty())
2140 return FALSE;
2142 CGitProgressDlg progDlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
2143 if (GetExplorerHWND() == hWnd)
2144 theApp.m_pMainWnd = &progDlg;
2145 SendMailProgressCommand sendMailProgressCommand;
2146 progDlg.SetCommand(&sendMailProgressCommand);
2148 sendMailProgressCommand.SetPathList(dlg.m_PathList);
2149 progDlg.SetItemCount(dlg.m_PathList.GetCount());
2151 CSendMailPatch sendMailPatch(dlg.m_To, dlg.m_CC, dlg.m_Subject, !!dlg.m_bAttachment, !!dlg.m_bCombine);
2152 sendMailProgressCommand.SetSendMailOption(&sendMailPatch);
2154 progDlg.DoModal();
2156 return true;
2158 return false;
2161 bool CAppUtils::SendPatchMail(HWND hWnd, const CString& cmd, const CString& formatpatchoutput)
2163 CTGitPathList list;
2164 CString log=formatpatchoutput;
2165 int start=log.Find(cmd);
2166 if(start >=0)
2167 log.Tokenize(L"\n", start);
2168 else
2169 start = 0;
2171 while(start>=0)
2173 CString one = log.Tokenize(L"\n", start);
2174 one=one.Trim();
2175 if (one.IsEmpty() || CStringUtils::StartsWith(one, CString(MAKEINTRESOURCE(IDS_SUCCESS))))
2176 continue;
2177 one.Replace(L'/', L'\\');
2178 CTGitPath path;
2179 path.SetFromWin(one);
2180 list.AddPath(path);
2182 if (!list.IsEmpty())
2183 return SendPatchMail(hWnd, list);
2184 else
2186 CMessageBox::Show(hWnd, IDS_ERR_NOPATCHES, IDS_APPNAME, MB_ICONINFORMATION);
2187 return true;
2192 int CAppUtils::GetLogOutputEncode(CGit *pGit)
2194 CString output;
2195 output = pGit->GetConfigValue(L"i18n.logOutputEncoding");
2196 if(output.IsEmpty())
2197 return CUnicodeUtils::GetCPCode(pGit->GetConfigValue(L"i18n.commitencoding"));
2198 else
2199 return CUnicodeUtils::GetCPCode(output);
2202 bool CAppUtils::MessageContainsConflictHints(HWND hWnd, const CString& message)
2204 bool stripComments = (CRegDWORD(L"Software\\TortoiseGit\\StripCommentedLines", FALSE) == TRUE);
2205 if (stripComments)
2206 return false;
2207 CString cleanupMode = g_Git.GetConfigValue(L"core.cleanup", L"default");
2208 if (cleanupMode == L"verbatim" || cleanupMode == L"whitespace" || cleanupMode == L"scissors")
2209 return false;
2210 TCHAR commentChar = L'#';
2211 CString commentCharValue = g_Git.GetConfigValue(L"core.commentchar");
2212 if (!commentCharValue.IsEmpty())
2213 commentChar = commentCharValue[0];
2215 CString conflictsHint;
2216 conflictsHint.Format(L"\n%c Conflicts:\n%c\t", commentChar, commentChar);
2218 if (message.Find(conflictsHint) <= 0)
2219 return false;
2221 BOOL dontaskagainchecked = FALSE;
2222 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)
2224 if (dontaskagainchecked)
2225 CMessageBox::RemoveRegistryKey(L"CommitMessageContainsConflictHint");
2226 return true;
2228 return false;
2231 int CAppUtils::SaveCommitUnicodeFile(const CString& filename, CString &message)
2235 CFile file(filename, CFile::modeReadWrite | CFile::modeCreate);
2236 int cp = CUnicodeUtils::GetCPCode(g_Git.GetConfigValue(L"i18n.commitencoding"));
2238 bool stripComments = (CRegDWORD(L"Software\\TortoiseGit\\StripCommentedLines", FALSE) == TRUE);
2239 TCHAR commentChar = L'#';
2240 if (stripComments)
2242 CString commentCharValue = g_Git.GetConfigValue(L"core.commentchar");
2243 if (!commentCharValue.IsEmpty())
2244 commentChar = commentCharValue[0];
2247 bool sanitize = (CRegDWORD(L"Software\\TortoiseGit\\SanitizeCommitMsg", TRUE) == TRUE);
2248 if (sanitize)
2249 message.Trim(L" \r\n");
2251 int len = message.GetLength();
2252 int start = 0;
2253 int emptyLineCnt = 0;
2254 while (start >= 0 && start < len)
2256 int oldStart = start;
2257 start = message.Find(L'\n', oldStart);
2258 CString line = message.Mid(oldStart);
2259 if (start != -1)
2261 line.Truncate(start - oldStart);
2262 ++start; // move forward so we don't find the same char again
2264 if (stripComments && (!line.IsEmpty() && line.GetAt(0) == commentChar) || (start < 0 && line.IsEmpty()))
2265 continue;
2266 line.TrimRight(L" \r");
2267 if (sanitize)
2269 if (line.IsEmpty())
2271 ++emptyLineCnt;
2272 continue;
2274 if (emptyLineCnt) // squash multiple newlines
2275 file.Write("\n", 1);
2276 emptyLineCnt = 0;
2278 CStringA lineA = CUnicodeUtils::GetMulti(line + L'\n', cp);
2279 file.Write((LPCSTR)lineA, lineA.GetLength());
2281 file.Close();
2282 return 0;
2284 catch (CFileException *e)
2286 e->Delete();
2287 return -1;
2291 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)
2293 if (bAutoLoad)
2294 CAppUtils::LaunchPAgent(hWnd, nullptr, &url);
2296 CGitHash hashOld;
2297 if (g_Git.GetHash(hashOld, L"HEAD"))
2299 MessageBox(hWnd, g_Git.GetGitLastErr(L"Could not get HEAD hash."), L"TortoiseGit", MB_ICONERROR);
2300 return false;
2303 CString args;
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 --no-rebase%s \"%s\" %s", (LPCTSTR)args, (LPCTSTR)url, (LPCTSTR)remoteBranchName);
2334 CProgressDlg progress(GetExplorerHWND() == hWnd ? nullptr : 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 STRING_VECTOR remotes;
2365 g_Git.GetRemoteList(remotes);
2366 if (std::find(remotes.begin(), remotes.end(), url) != remotes.end())
2368 CString currentBranch;
2369 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, currentBranch))
2370 currentBranch.Empty();
2371 CString remoteRef = L"remotes/" + url + L"/" + remoteBranchName;
2372 if (!currentBranch.IsEmpty() && remoteBranchName.IsEmpty())
2374 CString pullRemote, pullBranch;
2375 g_Git.GetRemoteTrackedBranch(currentBranch, pullRemote, pullBranch);
2376 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
2377 remoteRef = L"remotes/" + pullRemote + L"/" + pullBranch;
2379 CGitHash common;
2380 g_Git.IsFastForward(L"HEAD", remoteRef, &common);
2381 if (common.IsEmpty())
2382 postCmdList.emplace_back(IDI_MERGE, IDS_MERGE_UNRELATED, [=, &hWnd] { DoPull(hWnd, url, bAutoLoad, bFetchTags, bNoFF, bFFonly, bSquash, bNoCommit, nDepth, bPrune, remoteBranchName, showPush, showStashPop, true); });
2385 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&hWnd]{ CAppUtils::Pull(hWnd); });
2386 postCmdList.emplace_back(IDI_SHELVE, IDS_MENUSTASHSAVE, [&hWnd]{ CAppUtils::StashSave(hWnd, L"", true); });
2387 return;
2390 if (showStashPop)
2391 postCmdList.emplace_back(IDI_UNSHELVE, IDS_MENUSTASHPOP, [&hWnd]{ CAppUtils::StashPop(hWnd); });
2393 if (g_Git.GetHash(hashNew, L"HEAD"))
2394 MessageBox(hWnd, g_Git.GetGitLastErr(L"Could not get HEAD hash after pulling."), L"TortoiseGit", MB_ICONERROR);
2395 else
2397 postCmdList.emplace_back(IDI_DIFF, IDS_PROC_PULL_DIFFS, [&]
2399 CString sCmd;
2400 sCmd.Format(L"/command:showcompare /path:\"%s\" /revision1:%s /revision2:%s", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)hashOld.ToString(), (LPCTSTR)hashNew.ToString());
2401 CAppUtils::RunTortoiseGitProc(sCmd);
2403 postCmdList.emplace_back(IDI_LOG, IDS_PROC_PULL_LOG, [&]
2405 CString sCmd;
2406 sCmd.Format(L"/command:log /path:\"%s\" /range:%s", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)(hashOld.ToString() + L".." + hashNew.ToString()));
2407 CAppUtils::RunTortoiseGitProc(sCmd);
2411 if (showPush)
2412 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, [&hWnd]{ CAppUtils::Push(hWnd); });
2414 CTGitPath gitPath = g_Git.m_CurrentDir;
2415 if (gitPath.HasSubmodules())
2417 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
2419 CString sCmd;
2420 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
2421 CAppUtils::RunTortoiseGitProc(sCmd);
2426 INT_PTR ret = progress.DoModal();
2428 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)
2430 cmd.Format(L"/command:repostatus /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
2431 CAppUtils::RunTortoiseGitProc(cmd);
2433 return true;
2436 return ret == IDOK;
2439 bool CAppUtils::Pull(HWND hWnd, bool showPush, bool showStashPop)
2441 if (IsTGitRebaseActive(hWnd))
2442 return false;
2444 CPullFetchDlg dlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
2445 dlg.m_IsPull = TRUE;
2446 if (dlg.DoModal() == IDOK)
2448 // "git.exe pull --rebase" is not supported, never and ever. So, adapting it to Fetch & Rebase.
2449 if (dlg.m_bRebase)
2450 return DoFetch(hWnd,
2451 dlg.m_RemoteURL,
2452 FALSE, // Fetch all remotes
2453 dlg.m_bAutoLoad == BST_CHECKED,
2454 dlg.m_bPrune,
2455 dlg.m_bDepth == BST_CHECKED,
2456 dlg.m_nDepth,
2457 dlg.m_bFetchTags,
2458 dlg.m_RemoteBranchName,
2459 dlg.m_bRebaseActivatedInConfigForPull ? 2 : 1, // Rebase after fetching
2460 dlg.m_bRebasePreserveMerges == TRUE); // Preserve merges on rebase
2462 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);
2465 return false;
2468 bool CAppUtils::RebaseAfterFetch(HWND hWnd, const CString& upstream, int rebase, bool preserveMerges)
2470 while (true)
2472 CRebaseDlg dlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
2473 if (!upstream.IsEmpty())
2474 dlg.m_Upstream = upstream;
2475 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENULOG)));
2476 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUPUSH)));
2477 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUDESSENDMAIL)));
2478 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUREBASE)));
2479 dlg.m_bRebaseAutoStart = (rebase == 2);
2480 dlg.m_bPreserveMerges = preserveMerges;
2481 INT_PTR response = dlg.DoModal();
2482 if (response == IDOK)
2483 return true;
2484 else if (response == IDC_REBASE_POST_BUTTON)
2486 CString cmd = L"/command:log";
2487 cmd += L" /path:\"" + g_Git.m_CurrentDir + L'"';
2488 CAppUtils::RunTortoiseGitProc(cmd);
2489 return true;
2491 else if (response == IDC_REBASE_POST_BUTTON + 1)
2492 return Push(hWnd);
2493 else if (response == IDC_REBASE_POST_BUTTON + 2)
2495 CString cmd, out, err;
2496 cmd.Format(L"git.exe format-patch -o \"%s\" %s..%s",
2497 (LPCTSTR)g_Git.m_CurrentDir,
2498 (LPCTSTR)g_Git.FixBranchName(dlg.m_Upstream),
2499 (LPCTSTR)g_Git.FixBranchName(dlg.m_Branch));
2500 if (g_Git.Run(cmd, &out, &err, CP_UTF8))
2502 CMessageBox::Show(hWnd, out + L'\n' + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
2503 return false;
2505 CAppUtils::SendPatchMail(hWnd, cmd, out);
2506 return true;
2508 else if (response == IDC_REBASE_POST_BUTTON + 3)
2509 continue;
2510 else if (response == IDCANCEL)
2511 return false;
2512 return false;
2516 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)
2518 if (loadPuttyAgent)
2520 if (fetchAllRemotes)
2522 STRING_VECTOR list;
2523 g_Git.GetRemoteList(list);
2525 for (const auto& remote : list)
2526 CAppUtils::LaunchPAgent(hWnd, nullptr, &remote);
2528 else
2529 CAppUtils::LaunchPAgent(hWnd, nullptr, &url);
2532 CString upstream = L"FETCH_HEAD";
2533 CGitHash oldUpstreamHash;
2534 if (runRebase)
2536 STRING_VECTOR list;
2537 g_Git.GetRemoteList(list);
2538 for (auto it = list.cbegin(); it != list.cend(); ++it)
2540 if (url == *it)
2542 upstream.Empty();
2543 if (remoteBranch.IsEmpty()) // pulldlg might clear remote branch if its the default tracked branch
2545 CString currentBranch;
2546 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, currentBranch))
2547 currentBranch.Empty();
2548 if (!currentBranch.IsEmpty() && remoteBranch.IsEmpty())
2550 CString pullRemote, pullBranch;
2551 g_Git.GetRemoteTrackedBranch(currentBranch, pullRemote, pullBranch);
2552 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty() && pullRemote == url) // pullRemote == url is just another safety-check and should not be needed
2553 upstream = L"remotes/" + *it + L'/' + pullBranch;
2556 else
2557 upstream = L"remotes/" + *it + L'/' + remoteBranch;
2559 g_Git.GetHash(oldUpstreamHash, upstream);
2560 break;
2565 CString cmd, arg;
2566 arg = L" --progress";
2568 if (bDepth)
2569 arg.AppendFormat(L" --depth %d", nDepth);
2571 if (prune == TRUE)
2572 arg += L" --prune";
2573 else if (prune == FALSE)
2574 arg += L" --no-prune";
2576 if (fetchTags == 1)
2577 arg += L" --tags";
2578 else if (fetchTags == 0)
2579 arg += L" --no-tags";
2581 if (fetchAllRemotes)
2582 cmd.Format(L"git.exe fetch --all -v%s", (LPCTSTR)arg);
2583 else
2584 cmd.Format(L"git.exe fetch -v%s \"%s\" %s", (LPCTSTR)arg, (LPCTSTR)url, (LPCTSTR)remoteBranch);
2586 CProgressDlg progress(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
2587 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2589 if (status)
2591 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ DoFetch(hWnd, url, fetchAllRemotes, loadPuttyAgent, prune, bDepth, nDepth, fetchTags, remoteBranch, runRebase, rebasePreserveMerges); });
2592 if (fetchAllRemotes)
2593 postCmdList.emplace_back(IDI_LOG, IDS_MENULOG, []
2595 CString cmd = L"/command:log";
2596 cmd += L" /path:\"" + g_Git.m_CurrentDir + L'"';
2597 CAppUtils::RunTortoiseGitProc(cmd);
2599 return;
2602 postCmdList.emplace_back(IDI_LOG, IDS_MENULOG, []
2604 CString cmd = L"/command:log";
2605 cmd += L" /path:\"" + g_Git.m_CurrentDir + L'"';
2606 CAppUtils::RunTortoiseGitProc(cmd);
2609 postCmdList.emplace_back(IDI_RESET, IDS_PROC_RESET, [&hWnd]
2611 CString pullRemote, pullBranch;
2612 g_Git.GetRemoteTrackedBranchForHEAD(pullRemote, pullBranch);
2613 CString defaultUpstream;
2614 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
2615 defaultUpstream.Format(L"remotes/%s/%s", (LPCTSTR)pullRemote, (LPCTSTR)pullBranch);
2616 CAppUtils::GitReset(hWnd, &defaultUpstream, 2);
2619 postCmdList.emplace_back(IDI_UPDATE, IDS_MENUFETCH, [&hWnd]{ CAppUtils::Fetch(hWnd); });
2621 if (!runRebase && !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir))
2622 postCmdList.emplace_back(IDI_REBASE, IDS_MENUREBASE, [&]{ runRebase = false; CAppUtils::RebaseAfterFetch(hWnd); });
2625 progress.m_GitCmd = cmd;
2627 if (g_Git.UsingLibGit2(CGit::GIT_CMD_FETCH))
2629 CGitProgressDlg gitdlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
2630 FetchProgressCommand fetchProgressCommand;
2631 if (!fetchAllRemotes)
2632 fetchProgressCommand.SetUrl(url);
2633 gitdlg.SetCommand(&fetchProgressCommand);
2634 fetchProgressCommand.m_PostCmdCallback = progress.m_PostCmdCallback;
2635 fetchProgressCommand.SetAutoTag(fetchTags == 1 ? GIT_REMOTE_DOWNLOAD_TAGS_ALL : fetchTags == 2 ? GIT_REMOTE_DOWNLOAD_TAGS_AUTO : GIT_REMOTE_DOWNLOAD_TAGS_NONE);
2636 fetchProgressCommand.SetPrune(prune == BST_CHECKED ? GIT_FETCH_PRUNE : prune == BST_INDETERMINATE ? GIT_FETCH_PRUNE_UNSPECIFIED : GIT_FETCH_NO_PRUNE);
2637 if (!fetchAllRemotes)
2638 fetchProgressCommand.SetRefSpec(remoteBranch);
2639 return gitdlg.DoModal() == IDOK;
2642 progress.m_PostExecCallback = [&](DWORD& exitCode, CString&)
2644 if (exitCode || !runRebase)
2645 return;
2647 CGitHash remoteBranchHash;
2648 g_Git.GetHash(remoteBranchHash, upstream);
2650 if (runRebase == 1)
2652 CGitHash headHash, commonAcestor;
2653 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)
2654 return;
2656 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)
2657 return;
2660 if (runRebase == 1 && g_Git.IsFastForward(L"HEAD", upstream))
2662 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);
2663 if (ret == 3)
2664 return;
2665 if (ret == 1)
2667 CProgressDlg mergeProgress(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
2668 mergeProgress.m_GitCmd = L"git.exe merge --ff-only " + upstream;
2669 mergeProgress.m_AutoClose = AUTOCLOSE_IF_NO_ERRORS;
2670 mergeProgress.m_PostCmdCallback = [](DWORD status, PostCmdList& postCmdList)
2672 if (status && g_Git.HasWorkingTreeConflicts())
2674 // there are conflict files
2675 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
2677 CString sCmd;
2678 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
2679 CAppUtils::RunTortoiseGitProc(sCmd);
2683 mergeProgress.DoModal();
2684 return;
2688 CAppUtils::RebaseAfterFetch(hWnd, upstream, runRebase, rebasePreserveMerges);
2691 return progress.DoModal() == IDOK;
2694 bool CAppUtils::Fetch(HWND hWnd, const CString& remoteName, bool allRemotes)
2696 CPullFetchDlg dlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
2697 dlg.m_PreSelectRemote = remoteName;
2698 dlg.m_IsPull=FALSE;
2699 dlg.m_bAllRemotes = allRemotes;
2701 if(dlg.DoModal()==IDOK)
2702 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);
2704 return false;
2707 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)
2709 CString error;
2710 DWORD exitcode = 0xFFFFFFFF;
2711 if (CHooks::Instance().PrePush(g_Git.m_CurrentDir, exitcode, error))
2713 if (exitcode)
2715 CString temp;
2716 temp.Format(IDS_ERR_HOOKFAILED, (LPCTSTR)error);
2717 MessageBox(hWnd, temp, L"TortoiseGit", MB_OK | MB_ICONERROR);
2718 return false;
2722 int iRecurseSubmodules = 0;
2723 CString sRecurseSubmodules = g_Git.GetConfigValue(L"push.recurseSubmodules");
2724 if (sRecurseSubmodules == L"check")
2725 iRecurseSubmodules = 1;
2726 else if (sRecurseSubmodules == L"on-demand")
2727 iRecurseSubmodules = 2;
2729 CString arg;
2730 if (pack)
2731 arg += L"--thin ";
2732 if (tags && !allBranches)
2733 arg += L"--tags ";
2734 if (force)
2735 arg += L"--force ";
2736 if (forceWithLease)
2737 arg += L"--force-with-lease ";
2738 if (setUpstream)
2739 arg += L"--set-upstream ";
2740 if (recurseSubmodules == 0 && recurseSubmodules != iRecurseSubmodules)
2741 arg += L"--recurse-submodules=no ";
2742 if (recurseSubmodules == 1 && recurseSubmodules != iRecurseSubmodules)
2743 arg += L"--recurse-submodules=check ";
2744 if (recurseSubmodules == 2 && recurseSubmodules != iRecurseSubmodules)
2745 arg += L"--recurse-submodules=on-demand ";
2747 arg += L"--progress ";
2749 CProgressDlg progress(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
2751 STRING_VECTOR remotesList;
2752 if (allRemotes)
2753 g_Git.GetRemoteList(remotesList);
2754 else
2755 remotesList.push_back(remote);
2757 for (unsigned int i = 0; i < remotesList.size(); ++i)
2759 if (autoloadKey)
2760 CAppUtils::LaunchPAgent(hWnd, nullptr, &remotesList[i]);
2762 CString cmd;
2763 if (allBranches)
2765 cmd.Format(L"git.exe push --all %s\"%s\"",
2766 (LPCTSTR)arg,
2767 (LPCTSTR)remotesList[i]);
2769 if (tags)
2771 progress.m_GitCmdList.push_back(cmd);
2772 cmd.Format(L"git.exe push --tags %s\"%s\"", (LPCTSTR)arg, (LPCTSTR)remotesList[i]);
2775 else
2777 cmd.Format(L"git.exe push %s\"%s\" %s",
2778 (LPCTSTR)arg,
2779 (LPCTSTR)remotesList[i],
2780 (LPCTSTR)localBranch);
2781 if (!remoteBranch.IsEmpty())
2783 cmd += L":";
2784 cmd += remoteBranch;
2787 progress.m_GitCmdList.push_back(cmd);
2789 if (!allBranches && !!CRegDWORD(L"Software\\TortoiseGit\\ShowBranchRevisionNumber", FALSE))
2791 cmd.Format(L"git.exe rev-list --count --first-parent %s", (LPCTSTR)localBranch);
2792 progress.m_GitCmdList.push_back(cmd);
2796 CString superprojectRoot;
2797 GitAdminDir::HasAdminDir(g_Git.m_CurrentDir, false, &superprojectRoot);
2798 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2800 // need to execute hooks as those might be needed by post action commands
2801 DWORD exitcode = 0xFFFFFFFF;
2802 CString error;
2803 if (CHooks::Instance().PostPush(g_Git.m_CurrentDir, exitcode, error))
2805 if (exitcode)
2807 CString temp;
2808 temp.Format(IDS_ERR_HOOKFAILED, (LPCTSTR)error);
2809 MessageBox(hWnd, temp, L"TortoiseGit", MB_OK | MB_ICONERROR);
2813 if (status)
2815 bool rejected = progress.GetLogText().Find(L"! [rejected]") > 0;
2816 if (rejected)
2818 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&hWnd]{ Pull(hWnd, true); });
2819 postCmdList.emplace_back(IDI_UPDATE, IDS_MENUFETCH, [&]{ Fetch(hWnd, allRemotes ? L"" : remote, allRemotes); });
2821 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, [&]{ Push(hWnd, localBranch); });
2822 return;
2825 postCmdList.emplace_back(IDS_PROC_REQUESTPULL, [&]{ RequestPull(hWnd, remoteBranch); });
2826 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, [&]{ Push(hWnd, localBranch); });
2827 postCmdList.emplace_back(IDI_SWITCH, IDS_MENUSWITCH, [&hWnd]{ Switch(hWnd); });
2828 if (!superprojectRoot.IsEmpty())
2830 postCmdList.emplace_back(IDI_COMMIT, IDS_PROC_COMMIT_SUPERPROJECT, [&]
2832 CString sCmd;
2833 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)superprojectRoot);
2834 RunTortoiseGitProc(sCmd);
2839 INT_PTR ret = progress.DoModal();
2840 return ret == IDOK;
2843 bool CAppUtils::Push(HWND hWnd, const CString& selectLocalBranch)
2845 CPushDlg dlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
2846 dlg.m_BranchSourceName = selectLocalBranch;
2848 if (dlg.DoModal() == IDOK)
2849 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);
2851 return FALSE;
2854 bool CAppUtils::RequestPull(HWND hWnd, const CString& endrevision, const CString& repositoryUrl)
2856 CRequestPullDlg dlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
2857 dlg.m_RepositoryURL = repositoryUrl;
2858 dlg.m_EndRevision = endrevision;
2859 if (dlg.DoModal()==IDOK)
2861 CString cmd;
2862 cmd.Format(L"git.exe request-pull %s \"%s\" %s", (LPCTSTR)dlg.m_StartRevision, (LPCTSTR)dlg.m_RepositoryURL, (LPCTSTR)dlg.m_EndRevision);
2864 CSysProgressDlg sysProgressDlg;
2865 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
2866 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_CREATINGPULLREUQEST)));
2867 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
2868 sysProgressDlg.SetShowProgressBar(false);
2869 sysProgressDlg.ShowModeless(hWnd, true);
2871 CString tempFileName = GetTempFile();
2872 CString err;
2873 DeleteFile(tempFileName);
2874 CreateDirectory(tempFileName, nullptr);
2875 tempFileName += L"\\pullrequest.txt";
2876 if (g_Git.RunLogFile(cmd, tempFileName, &err))
2878 CString msg;
2879 msg.LoadString(IDS_ERR_PULLREUQESTFAILED);
2880 MessageBox(hWnd, msg + L'\n' + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
2881 return false;
2884 if (sysProgressDlg.HasUserCancelled())
2886 CMessageBox::Show(hWnd, IDS_USERCANCELLED, IDS_APPNAME, MB_OK);
2887 ::DeleteFile(tempFileName);
2888 return false;
2891 sysProgressDlg.Stop();
2893 if (dlg.m_bSendMail)
2895 CSendMailDlg sendmaildlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
2896 sendmaildlg.m_PathList = CTGitPathList(CTGitPath(tempFileName));
2897 sendmaildlg.m_bCustomSubject = true;
2899 if (sendmaildlg.DoModal() == IDOK)
2901 if (sendmaildlg.m_PathList.IsEmpty())
2902 return FALSE;
2904 CGitProgressDlg progDlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
2905 SendMailProgressCommand sendMailProgressCommand;
2906 progDlg.SetCommand(&sendMailProgressCommand);
2908 sendMailProgressCommand.SetPathList(sendmaildlg.m_PathList);
2909 progDlg.SetItemCount(sendmaildlg.m_PathList.GetCount());
2911 CSendMailCombineable sendMailCombineable(sendmaildlg.m_To, sendmaildlg.m_CC, sendmaildlg.m_Subject, !!sendmaildlg.m_bAttachment, !!sendmaildlg.m_bCombine);
2912 sendMailProgressCommand.SetSendMailOption(&sendMailCombineable);
2914 progDlg.DoModal();
2916 return true;
2918 return false;
2921 CAppUtils::LaunchAlternativeEditor(tempFileName);
2923 return true;
2926 void CAppUtils::RemoveTrailSlash(CString &path)
2928 if(path.IsEmpty())
2929 return ;
2931 // For URL, do not trim the slash just after the host name component.
2932 int index = path.Find(L"://");
2933 if (index >= 0)
2935 index += 4;
2936 index = path.Find(L'/', index);
2937 if (index == path.GetLength() - 1)
2938 return;
2941 while (path[path.GetLength() - 1] == L'\\' || path[path.GetLength() - 1] == L'/')
2943 path.Truncate(path.GetLength() - 1);
2944 if(path.IsEmpty())
2945 return;
2949 bool CAppUtils::CheckUserData(HWND hWnd)
2951 while(g_Git.GetUserName().IsEmpty() || g_Git.GetUserEmail().IsEmpty())
2953 if (CMessageBox::Show(hWnd, IDS_PROC_NOUSERDATA, IDS_APPNAME, MB_YESNO | MB_ICONERROR) == IDYES)
2955 CTGitPath path(g_Git.m_CurrentDir);
2956 CSettings dlg(IDS_PROC_SETTINGS_TITLE, &path, CWnd::FromHandle(hWnd));
2957 dlg.SetTreeViewMode(TRUE, TRUE, TRUE);
2958 dlg.SetTreeWidth(220);
2959 dlg.m_DefaultPage = L"gitconfig";
2961 dlg.DoModal();
2962 dlg.HandleRestart();
2965 else
2966 return false;
2969 return true;
2972 BOOL CAppUtils::Commit(HWND hWnd, const CString& bugid, BOOL bWholeProject, CString &sLogMsg,
2973 CTGitPathList &pathList,
2974 bool bSelectFilesForCommit)
2976 bool bFailed = true;
2978 if (!CheckUserData(hWnd))
2979 return false;
2981 while (bFailed)
2983 bFailed = false;
2984 CCommitDlg dlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
2985 dlg.m_sBugID = bugid;
2987 dlg.m_bWholeProject = bWholeProject;
2989 dlg.m_sLogMessage = sLogMsg;
2990 dlg.m_pathList = pathList;
2991 dlg.m_bSelectFilesForCommit = bSelectFilesForCommit;
2992 if (dlg.DoModal() == IDOK)
2994 if (dlg.m_pathList.IsEmpty())
2995 return false;
2996 // if the user hasn't changed the list of selected items
2997 // we don't use that list. Because if we would use the list
2998 // of pre-checked items, the dialog would show different
2999 // checked items on the next startup: it would only try
3000 // to check the parent folder (which might not even show)
3001 // instead, we simply use an empty list and let the
3002 // default checking do its job.
3003 sLogMsg = dlg.m_sLogMessage;
3004 bSelectFilesForCommit = true;
3006 switch (dlg.m_PostCmd)
3008 case GIT_POSTCOMMIT_CMD_DCOMMIT:
3009 CAppUtils::SVNDCommit(hWnd);
3010 break;
3011 case GIT_POSTCOMMIT_CMD_PUSH:
3012 CAppUtils::Push(hWnd);
3013 break;
3014 case GIT_POSTCOMMIT_CMD_CREATETAG:
3015 CAppUtils::CreateBranchTag(hWnd, TRUE);
3016 break;
3017 case GIT_POSTCOMMIT_CMD_PULL:
3018 CAppUtils::Pull(hWnd, true);
3019 break;
3020 default:
3021 break;
3024 // CGitProgressDlg progDlg;
3025 // progDlg.SetChangeList(dlg.m_sChangeList, !!dlg.m_bKeepChangeList);
3026 // if (parser.HasVal(L"closeonend"))
3027 // progDlg.SetAutoClose(parser.GetLongVal(L"closeonend"));
3028 // progDlg.SetCommand(CGitProgressDlg::GitProgress_Commit);
3029 // progDlg.SetPathList(dlg.m_pathList);
3030 // progDlg.SetCommitMessage(dlg.m_sLogMessage);
3031 // progDlg.SetDepth(dlg.m_bRecursive ? Git_depth_infinity : svn_depth_empty);
3032 // progDlg.SetSelectedList(dlg.m_selectedPathList);
3033 // progDlg.SetItemCount(dlg.m_itemsCount);
3034 // progDlg.SetBugTraqProvider(dlg.m_BugTraqProvider);
3035 // progDlg.DoModal();
3036 // CRegDWORD err = CRegDWORD(L"Software\\TortoiseGit\\ErrorOccurred", FALSE);
3037 // err = (DWORD)progDlg.DidErrorsOccur();
3038 // bFailed = progDlg.DidErrorsOccur();
3039 // bRet = progDlg.DidErrorsOccur();
3040 // CRegDWORD bFailRepeat = CRegDWORD(L"Software\\TortoiseGit\\CommitReopen", FALSE);
3041 // if (DWORD(bFailRepeat)==0)
3042 // bFailed = false; // do not repeat if the user chose not to in the settings.
3045 return true;
3048 BOOL CAppUtils::SVNDCommit(HWND hWnd)
3050 CSVNDCommitDlg dcommitdlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
3051 CString gitSetting = g_Git.GetConfigValue(L"svn.rmdir");
3052 if (gitSetting.IsEmpty()) {
3053 if (dcommitdlg.DoModal() != IDOK)
3054 return false;
3055 else
3057 if (dcommitdlg.m_remember)
3059 if (dcommitdlg.m_rmdir)
3060 gitSetting = L"true";
3061 else
3062 gitSetting = L"false";
3063 if (g_Git.SetConfigValue(L"svn.rmdir", gitSetting))
3065 CString msg;
3066 msg.FormatMessage(IDS_PROC_SAVECONFIGFAILED, L"svn.rmdir", (LPCTSTR)gitSetting);
3067 MessageBox(hWnd, msg, L"TortoiseGit", MB_OK | MB_ICONERROR);
3073 BOOL IsStash = false;
3074 if(!g_Git.CheckCleanWorkTree())
3076 if (CMessageBox::Show(hWnd, IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
3078 CSysProgressDlg sysProgressDlg;
3079 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3080 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3081 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3082 sysProgressDlg.SetShowProgressBar(false);
3083 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3084 sysProgressDlg.ShowModeless(hWnd, true);
3086 CString out;
3087 if (g_Git.Run(L"git.exe stash", &out, CP_UTF8))
3089 sysProgressDlg.Stop();
3090 MessageBox(hWnd, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3091 return false;
3093 sysProgressDlg.Stop();
3095 IsStash =true;
3097 else
3098 return false;
3101 CProgressDlg progress(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
3102 if (dcommitdlg.m_rmdir)
3103 progress.m_GitCmd = L"git.exe svn dcommit --rmdir";
3104 else
3105 progress.m_GitCmd = L"git.exe svn dcommit";
3106 if(progress.DoModal()==IDOK && progress.m_GitStatus == 0)
3108 if( IsStash)
3110 if (CMessageBox::Show(hWnd, IDS_DCOMMIT_STASH_POP, IDS_APPNAME, MB_YESNO | MB_ICONINFORMATION) == IDYES)
3112 CSysProgressDlg sysProgressDlg;
3113 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3114 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3115 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3116 sysProgressDlg.SetShowProgressBar(false);
3117 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3118 sysProgressDlg.ShowModeless(hWnd, true);
3120 CString out;
3121 if (g_Git.Run(L"git.exe stash pop", &out, CP_UTF8))
3123 sysProgressDlg.Stop();
3124 MessageBox(hWnd, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3125 return false;
3127 sysProgressDlg.Stop();
3129 else
3130 return false;
3132 return TRUE;
3134 return FALSE;
3137 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)
3139 CString args;
3140 if (noFF)
3141 args += L" --no-ff";
3142 else if (ffOnly)
3143 args += L" --ff-only";
3145 if (squash)
3146 args += L" --squash";
3148 if (noCommit)
3149 args += L" --no-commit";
3151 if (unrelated)
3152 args += L" --allow-unrelated-histories";
3154 if (log)
3155 args.AppendFormat(L" --log=%d", *log);
3157 if (!mergeStrategy.IsEmpty())
3159 args += L" --strategy=" + mergeStrategy;
3160 if (!strategyOption.IsEmpty())
3162 args += L" --strategy-option=" + strategyOption;
3163 if (!strategyParam.IsEmpty())
3164 args += L'=' + strategyParam;
3168 if (!logMessage.IsEmpty())
3170 CString logmsg = logMessage;
3171 logmsg.Replace(L"\\\"", L"\\\\\"");
3172 logmsg.Replace(L"\"", L"\\\"");
3173 args += L" -m \"" + logmsg + L"\"";
3176 CString mergeVersion = g_Git.FixBranchName(version);
3177 CString cmd;
3178 cmd.Format(L"git.exe merge%s %s", (LPCTSTR)args, (LPCTSTR)mergeVersion);
3180 CProgressDlg Prodlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
3181 Prodlg.m_GitCmd = cmd;
3183 Prodlg.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3185 if (status)
3187 int hasConflicts = g_Git.HasWorkingTreeConflicts();
3188 if (hasConflicts < 0)
3189 CMessageBox::Show(hWnd, g_Git.GetGitLastErr(L"Checking for conflicts failed.", CGit::GIT_CMD_CHECKCONFLICTS), L"TortoiseGit", MB_ICONEXCLAMATION);
3190 else if (hasConflicts)
3192 // there are conflict files
3193 CMessageBox::ShowCheck(hWnd, IDS_NEED_TO_RESOLVE_CONFLICTS_HINT, IDS_APPNAME, MB_ICONINFORMATION, L"MergeConflictsNeedsCommit", IDS_MSGBOX_DONOTSHOWAGAIN);
3194 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
3196 CString sCmd;
3197 sCmd.Format(L"/command:resolve /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3198 CAppUtils::RunTortoiseGitProc(sCmd);
3201 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUCOMMIT, []
3203 CString sCmd;
3204 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3205 CAppUtils::RunTortoiseGitProc(sCmd);
3209 CGitHash common;
3210 g_Git.IsFastForward(L"HEAD", mergeVersion, &common);
3211 if (common.IsEmpty())
3212 postCmdList.emplace_back(IDI_MERGE, IDS_MERGE_UNRELATED, [=, &hWnd] { DoMerge(hWnd, noFF, ffOnly, squash, noCommit, log, true, mergeStrategy, strategyOption, strategyParam, logMessage, version, isBranch, showStashPop); });
3214 postCmdList.emplace_back(IDI_SHELVE, IDS_MENUSTASHSAVE, [mergeVersion, &hWnd]{ CAppUtils::StashSave(hWnd, L"", false, false, true, mergeVersion); });
3216 return;
3219 if (showStashPop)
3220 postCmdList.emplace_back(IDI_UNSHELVE, IDS_MENUSTASHPOP, [&hWnd]{ CAppUtils::StashPop(hWnd); });
3222 if (noCommit || squash)
3224 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUCOMMIT, []
3226 CString sCmd;
3227 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3228 CAppUtils::RunTortoiseGitProc(sCmd);
3230 return;
3233 if (isBranch && !CStringUtils::StartsWith(version, L"remotes/")) // do not ask to remove remote branches
3235 postCmdList.emplace_back(IDI_DELETE, IDS_PROC_REMOVEBRANCH, [&]
3237 CString msg;
3238 msg.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)version);
3239 if (CMessageBox::Show(hWnd, msg, L"TortoiseGit", 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
3241 CString cmd, out;
3242 cmd.Format(L"git.exe branch -D -- %s", (LPCTSTR)version);
3243 if (g_Git.Run(cmd, &out, CP_UTF8))
3244 MessageBox(hWnd, out, L"TortoiseGit", MB_OK);
3248 if (isBranch)
3249 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, [&hWnd]{ CAppUtils::Push(hWnd); });
3251 BOOL hasGitSVN = CTGitPath(g_Git.m_CurrentDir).GetAdminDirMask() & ITEMIS_GITSVN;
3252 if (hasGitSVN)
3253 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSVNDCOMMIT, [&hWnd]{ CAppUtils::SVNDCommit(hWnd); });
3256 Prodlg.DoModal();
3257 return !Prodlg.m_GitStatus;
3260 BOOL CAppUtils::Merge(HWND hWnd, const CString* commit, bool showStashPop)
3262 if (!CheckUserData(hWnd))
3263 return FALSE;
3265 if (IsTGitRebaseActive(hWnd))
3266 return FALSE;
3268 CMergeDlg dlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
3269 if (commit)
3270 dlg.m_initialRefName = *commit;
3272 if (dlg.DoModal() == IDOK)
3273 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);
3275 return FALSE;
3278 BOOL CAppUtils::MergeAbort(HWND hWnd)
3280 CMergeAbortDlg dlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
3281 if (dlg.DoModal() == IDOK)
3282 return Reset(hWnd, L"HEAD", (dlg.m_ResetType == 0) ? 3 : dlg.m_ResetType);
3284 return FALSE;
3287 void CAppUtils::EditNote(HWND hWnd, GitRevLoglist* rev, ProjectProperties* projectProperties)
3289 if (!CheckUserData(hWnd))
3290 return;
3292 CInputDlg dlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
3293 dlg.m_sHintText = CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES));
3294 dlg.m_sInputText = rev->m_Notes;
3295 dlg.m_sTitle = CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES));
3296 dlg.m_pProjectProperties = projectProperties;
3297 dlg.m_bUseLogWidth = true;
3298 if(dlg.DoModal() == IDOK)
3300 if (g_Git.SetGitNotes(rev->m_CommitHash, dlg.m_sInputText))
3302 CString err;
3303 err.LoadString(IDS_PROC_FAILEDSAVINGNOTES);
3304 MessageBox(hWnd, g_Git.GetLibGit2LastErr(err), L"TortoiseGit", MB_OK | MB_ICONERROR);
3305 return;
3308 if (g_Git.GetGitNotes(rev->m_CommitHash, rev->m_Notes))
3309 MessageBox(hWnd, g_Git.GetLibGit2LastErr(L"Could not load notes for commit " + rev->m_CommitHash.ToString() + L'.'), L"TortoiseGit", MB_OK | MB_ICONERROR);
3313 inline bool CAppUtils::IsGitVersionNewerOrEqual(HWND hWnd, unsigned __int8 major, unsigned __int8 minor, unsigned __int8 patchlevel, unsigned __int8 build)
3315 auto ver = GetMsysgitVersion(hWnd);
3316 return ver >= ConvertVersionToInt(major, minor, patchlevel, build);
3319 int CAppUtils::GetMsysgitVersion(HWND hWnd)
3321 if (g_Git.ms_LastMsysGitVersion)
3322 return g_Git.ms_LastMsysGitVersion;
3324 CString cmd;
3325 CString versiondebug;
3326 CString version;
3328 CRegDWORD regTime = CRegDWORD(L"Software\\TortoiseGit\\git_file_time");
3329 CRegDWORD regVersion = CRegDWORD(L"Software\\TortoiseGit\\git_cached_version");
3331 CString gitpath = CGit::ms_LastMsysGitDir + L"\\git.exe";
3333 __int64 time=0;
3334 if (!CGit::GetFileModifyTime(gitpath, &time))
3336 if ((DWORD)CGit::filetime_to_time_t(time) == regTime)
3338 g_Git.ms_LastMsysGitVersion = regVersion;
3339 return regVersion;
3343 CString err;
3344 int ver = g_Git.GetGitVersion(&versiondebug, &err);
3345 if (ver < 0)
3347 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);
3348 return -1;
3352 if (!ver)
3354 MessageBox(hWnd, L"Could not parse git.exe version number: \"" + versiondebug + L'"', L"TortoiseGit", MB_OK | MB_ICONERROR);
3355 return -1;
3359 regTime = (DWORD)CGit::filetime_to_time_t(time);
3360 regVersion = ver;
3361 g_Git.ms_LastMsysGitVersion = ver;
3363 return ver;
3366 void CAppUtils::MarkWindowAsUnpinnable(HWND hWnd)
3368 typedef HRESULT (WINAPI *SHGPSFW) (HWND hwnd,REFIID riid,void** ppv);
3370 CAutoLibrary hShell = AtlLoadSystemLibraryUsingFullPath(L"Shell32.dll");
3372 if (hShell.IsValid()) {
3373 SHGPSFW pfnSHGPSFW = (SHGPSFW)::GetProcAddress(hShell, "SHGetPropertyStoreForWindow");
3374 if (pfnSHGPSFW) {
3375 IPropertyStore *pps;
3376 HRESULT hr = pfnSHGPSFW(hWnd, IID_PPV_ARGS(&pps));
3377 if (SUCCEEDED(hr)) {
3378 PROPVARIANT var;
3379 var.vt = VT_BOOL;
3380 var.boolVal = VARIANT_TRUE;
3381 pps->SetValue(PKEY_AppUserModel_PreventPinning, var);
3382 pps->Release();
3388 void CAppUtils::SetWindowTitle(HWND hWnd, const CString& urlorpath, const CString& dialogname)
3390 ASSERT(dialogname.GetLength() < 70);
3391 ASSERT(urlorpath.GetLength() < MAX_PATH);
3392 WCHAR pathbuf[MAX_PATH] = {0};
3394 PathCompactPathEx(pathbuf, urlorpath, 70 - dialogname.GetLength(), 0);
3396 wcscat_s(pathbuf, L" - ");
3397 wcscat_s(pathbuf, dialogname);
3398 wcscat_s(pathbuf, L" - ");
3399 wcscat_s(pathbuf, CString(MAKEINTRESOURCE(IDS_APPNAME)));
3400 SetWindowText(hWnd, pathbuf);
3403 bool CAppUtils::BisectStart(HWND hWnd, const CString& lastGood, const CString& firstBad)
3405 if (!g_Git.CheckCleanWorkTree())
3407 if (CMessageBox::Show(hWnd, IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
3409 CSysProgressDlg sysProgressDlg;
3410 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3411 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3412 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3413 sysProgressDlg.SetShowProgressBar(false);
3414 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3415 sysProgressDlg.ShowModeless(hWnd, true);
3417 CString out;
3418 if (g_Git.Run(L"git.exe stash", &out, CP_UTF8))
3420 sysProgressDlg.Stop();
3421 MessageBox(hWnd, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3422 return false;
3424 sysProgressDlg.Stop();
3426 else
3427 return false;
3430 CBisectStartDlg bisectStartDlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
3432 if (!lastGood.IsEmpty())
3433 bisectStartDlg.m_sLastGood = lastGood;
3434 if (!firstBad.IsEmpty())
3435 bisectStartDlg.m_sFirstBad = firstBad;
3437 if (bisectStartDlg.DoModal() == IDOK)
3439 CProgressDlg progress(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
3440 if (GetExplorerHWND() == hWnd)
3441 theApp.m_pMainWnd = &progress;
3442 progress.m_GitCmdList.push_back(L"git.exe bisect start");
3443 progress.m_GitCmdList.push_back(L"git.exe bisect good " + bisectStartDlg.m_LastGoodRevision);
3444 progress.m_GitCmdList.push_back(L"git.exe bisect bad " + bisectStartDlg.m_FirstBadRevision);
3446 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3448 if (status)
3449 return;
3451 CTGitPath path(g_Git.m_CurrentDir);
3452 if (path.HasSubmodules())
3454 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
3456 CString sCmd;
3457 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3458 CAppUtils::RunTortoiseGitProc(sCmd);
3462 postCmdList.emplace_back(IDI_THUMB_UP, IDS_MENUBISECTGOOD, [] { CAppUtils::RunTortoiseGitProc(L"/command:bisect /good"); });
3463 postCmdList.emplace_back(IDI_THUMB_DOWN, IDS_MENUBISECTBAD, [] { CAppUtils::RunTortoiseGitProc(L"/command:bisect /bad"); });
3464 postCmdList.emplace_back(IDI_BISECT, IDS_MENUBISECTSKIP, [] { CAppUtils::RunTortoiseGitProc(L"/command:bisect /skip"); });
3465 postCmdList.emplace_back(IDI_BISECT_RESET, IDS_MENUBISECTRESET, [] { CAppUtils::RunTortoiseGitProc(L"/command:bisect /reset"); });
3468 INT_PTR ret = progress.DoModal();
3469 return ret == IDOK;
3472 return false;
3475 bool CAppUtils::BisectOperation(HWND hWnd, const CString& op, const CString& ref)
3477 CString cmd = L"git.exe bisect " + op;
3479 if (!ref.IsEmpty())
3481 cmd += L' ';
3482 cmd += ref;
3485 CProgressDlg progress(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
3486 if (GetExplorerHWND() == hWnd)
3487 theApp.m_pMainWnd = &progress;
3488 progress.m_GitCmd = cmd;
3490 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3492 if (status)
3493 return;
3495 CTGitPath path = g_Git.m_CurrentDir;
3496 if (path.HasSubmodules())
3498 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
3500 CString sCmd;
3501 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3502 CAppUtils::RunTortoiseGitProc(sCmd);
3506 postCmdList.emplace_back(IDI_THUMB_UP, IDS_MENUBISECTGOOD, [] { CAppUtils::RunTortoiseGitProc(L"/command:bisect /good"); });
3507 postCmdList.emplace_back(IDI_THUMB_DOWN, IDS_MENUBISECTBAD, [] { CAppUtils::RunTortoiseGitProc(L"/command:bisect /bad"); });
3508 postCmdList.emplace_back(IDI_BISECT, IDS_MENUBISECTSKIP, [] { CAppUtils::RunTortoiseGitProc(L"/command:bisect /skip"); });
3509 if (op != L"reset")
3510 postCmdList.emplace_back(IDI_BISECT_RESET, IDS_MENUBISECTRESET, []{ CAppUtils::RunTortoiseGitProc(L"/command:bisect /reset"); });
3513 INT_PTR ret = progress.DoModal();
3514 return ret == IDOK;
3517 int CAppUtils::Git2GetUserPassword(git_cred **out, const char *url, const char *username_from_url, unsigned int /*allowed_types*/, void * /*payload*/)
3519 CUserPassword dlg;
3520 dlg.m_URL = CUnicodeUtils::GetUnicode(url, CP_UTF8);
3521 if (username_from_url)
3522 dlg.m_UserName = CUnicodeUtils::GetUnicode(username_from_url, CP_UTF8);
3524 CStringA username, password;
3525 if (dlg.DoModal() == IDOK)
3527 username = CUnicodeUtils::GetMulti(dlg.m_UserName, CP_UTF8);
3528 password = CUnicodeUtils::GetMulti(dlg.m_Password, CP_UTF8);
3529 return git_cred_userpass_plaintext_new(out, username, password);
3531 giterr_set_str(GITERR_NONE, "User cancelled.");
3532 return GIT_EUSER;
3535 int CAppUtils::Git2CertificateCheck(git_cert* base_cert, int /*valid*/, const char* host, void* /*payload*/)
3537 if (base_cert->cert_type == GIT_CERT_X509)
3539 git_cert_x509* cert = (git_cert_x509*)base_cert;
3541 if (last_accepted_cert.cmp(cert))
3542 return 0;
3544 PCCERT_CONTEXT pServerCert = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, (BYTE*)cert->data, (DWORD)cert->len);
3545 SCOPE_EXIT { CertFreeCertificateContext(pServerCert); };
3547 DWORD verificationError = VerifyServerCertificate(pServerCert, CUnicodeUtils::GetUnicode(host).GetBuffer(), 0);
3548 if (!verificationError)
3550 last_accepted_cert.set(cert);
3551 return 0;
3554 CString servernameInCert;
3555 CertGetNameString(pServerCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nullptr, CStrBuf(servernameInCert, 128), 128);
3557 CString issuer;
3558 CertGetNameString(pServerCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, CERT_NAME_ISSUER_FLAG, nullptr, CStrBuf(issuer, 128), 128);
3560 CCheckCertificateDlg dlg;
3561 dlg.cert = cert;
3562 dlg.m_sCertificateCN = servernameInCert;
3563 dlg.m_sCertificateIssuer = issuer;
3564 dlg.m_sHostname = CUnicodeUtils::GetUnicode(host);
3565 dlg.m_sError = CFormatMessageWrapper(verificationError);
3566 if (dlg.DoModal() == IDOK)
3568 last_accepted_cert.set(cert);
3569 return 0;
3572 return GIT_ECERTIFICATE;
3575 int CAppUtils::ExploreTo(HWND hwnd, CString path)
3577 if (PathFileExists(path))
3579 HRESULT ret = E_FAIL;
3580 ITEMIDLIST __unaligned * pidl = ILCreateFromPath(path);
3581 if (pidl)
3583 ret = SHOpenFolderAndSelectItems(pidl, 0, 0, 0);
3584 ILFree(pidl);
3586 return SUCCEEDED(ret) ? 0 : -1;
3588 // if filepath does not exist any more, navigate to closest matching folder
3591 int pos = path.ReverseFind(L'\\');
3592 if (pos <= 3)
3593 break;
3594 path.Truncate(pos);
3595 } while (!PathFileExists(path));
3596 return (INT_PTR)ShellExecute(hwnd, L"explore", path, nullptr, nullptr, SW_SHOW) > 32 ? 0 : -1;
3599 int CAppUtils::ResolveConflict(HWND hWnd, CTGitPath& path, resolve_with resolveWith)
3601 bool b_local = false, b_remote = false;
3602 BYTE_VECTOR vector;
3604 CString cmd;
3605 cmd.Format(L"git.exe ls-files -u -t -z -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3606 if (g_Git.Run(cmd, &vector))
3608 MessageBox(hWnd, L"git ls-files failed!", L"TortoiseGit", MB_OK);
3609 return -1;
3612 CTGitPathList list;
3613 if (list.ParserFromLsFile(vector))
3615 MessageBox(hWnd, L"Parse ls-files failed!", L"TortoiseGit", MB_OK);
3616 return -1;
3619 if (list.IsEmpty())
3620 return 0;
3621 for (int i = 0; i < list.GetCount(); ++i)
3623 if (list[i].m_Stage == 2)
3624 b_local = true;
3625 if (list[i].m_Stage == 3)
3626 b_remote = true;
3630 bool baseIsFile = true, localIsFile = true, remoteIsFile = true;
3631 CString baseHash, localHash, remoteHash;
3632 ParseHashesFromLsFile(vector, baseHash, baseIsFile, localHash, localIsFile, remoteHash, remoteIsFile);
3634 CBlockCacheForPath block(g_Git.m_CurrentDir);
3635 if ((resolveWith == RESOLVE_WITH_THEIRS && !b_remote) || (resolveWith == RESOLVE_WITH_MINE && !b_local))
3637 CString gitcmd, output; //retest with registered submodule!
3638 gitcmd.Format(L"git.exe rm -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3639 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3641 // a .git folder in a submodule which is not in .gitmodules cannot be deleted using "git rm"
3642 if (PathIsDirectory(path.GetGitPathString()) && !PathIsDirectoryEmpty(path.GetGitPathString()))
3644 CString message(output);
3645 output += L"\n\n";
3646 output.AppendFormat(IDS_PROC_DELETEBRANCHTAG, path.GetWinPath());
3647 CString deleteButton;
3648 deleteButton.LoadString(IDS_DELETEBUTTON);
3649 CString abortButton;
3650 abortButton.LoadString(IDS_ABORTBUTTON);
3651 if (CMessageBox::Show(hWnd, output, L"TortoiseGit", 2, IDI_QUESTION, deleteButton, abortButton) == 2)
3652 return -1;
3653 path.Delete(true, true);
3654 output.Empty();
3655 if (!g_Git.Run(gitcmd, &output, CP_UTF8))
3657 RemoveTempMergeFile(path);
3658 return 0;
3661 MessageBox(hWnd, output, L"TortoiseGit", MB_ICONERROR);
3662 return -1;
3664 RemoveTempMergeFile(path);
3665 return 0;
3668 if (resolveWith == RESOLVE_WITH_THEIRS || resolveWith == RESOLVE_WITH_MINE)
3670 auto resolve = [&b_local, &b_remote, &hWnd](const CTGitPath& path, int stage, bool willBeFile, const CString& hash) -> int
3672 if (!willBeFile)
3674 if (!path.HasAdminDir()) // check if submodule is initialized
3676 CString gitcmd, output;
3677 if (!path.IsDirectory())
3679 gitcmd.Format(L"git.exe checkout-index -f --stage=%d -- \"%s\"", stage, (LPCTSTR)path.GetGitPathString());
3680 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3682 MessageBox(hWnd, output, L"TortoiseGit", MB_ICONERROR);
3683 return -1;
3686 gitcmd.Format(L"git.exe update-index --replace --cacheinfo 0160000,%s,\"%s\"", (LPCTSTR)hash, (LPCTSTR)path.GetGitPathString());
3687 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3689 MessageBox(hWnd, output, L"TortoiseGit", MB_ICONERROR);
3690 return -1;
3692 return 0;
3695 CGit subgit;
3696 subgit.m_CurrentDir = g_Git.CombinePath(path);
3697 CGitHash submoduleHead;
3698 if (subgit.GetHash(submoduleHead, L"HEAD"))
3700 MessageBox(hWnd, subgit.GetGitLastErr(L"Could not get HEAD hash of submodule, this should not happen!"), L"TortoiseGit", MB_ICONERROR);
3701 return -1;
3703 if (submoduleHead.ToString() != hash)
3705 CString origPath = g_Git.m_CurrentDir;
3706 g_Git.m_CurrentDir = g_Git.CombinePath(path);
3707 SetCurrentDirectory(g_Git.m_CurrentDir);
3708 if (!GitReset(hWnd, &hash))
3710 g_Git.m_CurrentDir = origPath;
3711 SetCurrentDirectory(g_Git.m_CurrentDir);
3712 return -1;
3714 g_Git.m_CurrentDir = origPath;
3715 SetCurrentDirectory(g_Git.m_CurrentDir);
3718 else
3720 CString gitcmd, output;
3721 if (b_local && b_remote)
3722 gitcmd.Format(L"git.exe checkout-index -f --stage=%d -- \"%s\"", stage, (LPCTSTR)path.GetGitPathString());
3723 else
3724 gitcmd.Format(L"git.exe add -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3725 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3727 MessageBox(hWnd, output, L"TortoiseGit", MB_ICONERROR);
3728 return -1;
3731 return 0;
3733 int ret = -1;
3734 if (resolveWith == RESOLVE_WITH_THEIRS)
3735 ret = resolve(path, 3, remoteIsFile, remoteHash);
3736 else
3737 ret = resolve(path, 2, localIsFile, localHash);
3738 if (ret)
3739 return ret;
3742 if (PathFileExists(g_Git.CombinePath(path)) && (path.m_Action & CTGitPath::LOGACTIONS_UNMERGED))
3744 CString gitcmd, output;
3745 gitcmd.Format(L"git.exe add -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3746 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3748 MessageBox(hWnd, output, L"TortoiseGit", MB_ICONERROR);
3749 return -1;
3752 path.m_Action |= CTGitPath::LOGACTIONS_MODIFIED;
3753 path.m_Action &= ~CTGitPath::LOGACTIONS_UNMERGED;
3756 RemoveTempMergeFile(path);
3757 return 0;
3760 bool CAppUtils::ShellOpen(const CString& file, HWND hwnd /*= nullptr */)
3762 if ((INT_PTR)ShellExecute(hwnd, nullptr, file, nullptr, nullptr, SW_SHOW) > HINSTANCE_ERROR)
3763 return true;
3765 return ShowOpenWithDialog(file, hwnd);
3768 bool CAppUtils::ShowOpenWithDialog(const CString& file, HWND hwnd /*= nullptr */)
3770 OPENASINFO oi = { 0 };
3771 oi.pcszFile = file;
3772 oi.oaifInFlags = OAIF_EXEC;
3773 return SUCCEEDED(SHOpenWithDialog(hwnd, &oi));
3776 bool CAppUtils::IsTGitRebaseActive(HWND hWnd)
3778 CString adminDir;
3779 if (!GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir))
3780 return false;
3782 if (!PathIsDirectory(adminDir + L"tgitrebase.active"))
3783 return false;
3785 if (CMessageBox::Show(hWnd, IDS_REBASELOCKFILEFOUND, IDS_APPNAME, 2, IDI_EXCLAMATION, IDS_REMOVESTALEBUTTON, IDS_ABORTBUTTON) == 2)
3786 return true;
3788 RemoveDirectory(adminDir + L"tgitrebase.active");
3790 return false;
3793 bool CAppUtils::DeleteRef(CWnd* parent, const CString& ref)
3795 CString shortname;
3796 if (CGit::GetShortName(ref, shortname, L"refs/remotes/"))
3798 CString msg;
3799 msg.Format(IDS_PROC_DELETEREMOTEBRANCH, (LPCTSTR)ref);
3800 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)));
3801 if (result == 1)
3803 CString remoteName = shortname.Left(shortname.Find(L'/'));
3804 shortname = shortname.Mid(shortname.Find(L'/') + 1);
3805 if (CAppUtils::IsSSHPutty())
3806 CAppUtils::LaunchPAgent(parent->GetSafeHwnd(), nullptr, &remoteName);
3808 CSysProgressDlg sysProgressDlg;
3809 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3810 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
3811 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3812 sysProgressDlg.SetShowProgressBar(false);
3813 sysProgressDlg.ShowModal(parent, true);
3814 STRING_VECTOR list;
3815 list.push_back(L"refs/heads/" + shortname);
3816 if (g_Git.DeleteRemoteRefs(remoteName, list))
3817 CMessageBox::Show(parent->GetSafeOwner()->GetSafeHwnd(), g_Git.GetGitLastErr(L"Could not delete remote ref.", CGit::GIT_CMD_PUSH), L"TortoiseGit", MB_OK | MB_ICONERROR);
3818 sysProgressDlg.Stop();
3819 return true;
3821 else if (result == 2)
3823 if (g_Git.DeleteRef(ref))
3825 CMessageBox::Show(parent->GetSafeOwner()->GetSafeHwnd(), g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), L"TortoiseGit", MB_OK | MB_ICONERROR);
3826 return false;
3828 return true;
3830 return false;
3832 else if (CGit::GetShortName(ref, shortname, L"refs/stash"))
3834 CString err;
3835 std::vector<GitRevLoglist> stashList;
3836 size_t count = !GitRevLoglist::GetRefLog(ref, stashList, err) ? stashList.size() : 0;
3837 CString msg;
3838 msg.Format(IDS_PROC_DELETEALLSTASH, count);
3839 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)));
3840 if (choose == 1)
3842 CString out;
3843 if (g_Git.Run(L"git.exe stash clear", &out, CP_UTF8))
3844 CMessageBox::Show(parent->GetSafeOwner()->GetSafeHwnd(), out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3845 return true;
3847 else if (choose == 2)
3849 CString out;
3850 if (g_Git.Run(L"git.exe stash drop refs/stash@{0}", &out, CP_UTF8))
3851 CMessageBox::Show(parent->GetSafeOwner()->GetSafeHwnd(), out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3852 return true;
3854 return false;
3857 CString msg;
3858 msg.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)ref);
3859 // Check if branch is fully merged in HEAD
3860 if (CGit::GetShortName(ref, shortname, L"refs/heads/") && !g_Git.IsFastForward(ref, L"HEAD"))
3862 msg += L"\n\n";
3863 msg += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED));
3865 if (CMessageBox::Show(parent->GetSafeOwner()->GetSafeHwnd(), msg, L"TortoiseGit", 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
3867 if (g_Git.DeleteRef(ref))
3869 CMessageBox::Show(parent->GetSafeOwner()->GetSafeHwnd(), g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), L"TortoiseGit", MB_OK | MB_ICONERROR);
3870 return false;
3872 return true;
3874 return false;