Re-integrate the GPG signed commit into the unit tests
[TortoiseGit.git] / src / TortoiseProc / AppUtils.cpp
blobfb0ee4c4a54358c942da33545a71ecda52aea640
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2017 - TortoiseGit
4 // Copyright (C) 2003-2011, 2013-2014 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "TortoiseProc.h"
22 #include "PathUtils.h"
23 #include "AppUtils.h"
24 #include "MessageBox.h"
25 #include "registry.h"
26 #include "TGitPath.h"
27 #include "Git.h"
28 #include "UnicodeUtils.h"
29 #include "ExportDlg.h"
30 #include "ProgressDlg.h"
31 #include "GitAdminDir.h"
32 #include "ProgressDlg.h"
33 #include "BrowseFolder.h"
34 #include "DirFileEnum.h"
35 #include "CreateBranchTagDlg.h"
36 #include "GitSwitchDlg.h"
37 #include "ResetDlg.h"
38 #include "DeleteConflictDlg.h"
39 #include "SendMailDlg.h"
40 #include "GitProgressDlg.h"
41 #include "PushDlg.h"
42 #include "CommitDlg.h"
43 #include "MergeDlg.h"
44 #include "MergeAbortDlg.h"
45 #include "Hooks.h"
46 #include "..\Settings\Settings.h"
47 #include "InputDlg.h"
48 #include "SVNDCommitDlg.h"
49 #include "requestpulldlg.h"
50 #include "PullFetchDlg.h"
51 #include "RebaseDlg.h"
52 #include "PropKey.h"
53 #include "StashSave.h"
54 #include "IgnoreDlg.h"
55 #include "FormatMessageWrapper.h"
56 #include "SmartHandle.h"
57 #include "BisectStartDlg.h"
58 #include "SysProgressDlg.h"
59 #include "UserPassword.h"
60 #include "SendmailPatch.h"
61 #include "Globals.h"
62 #include "ProgressCommands/ResetProgressCommand.h"
63 #include "ProgressCommands/FetchProgressCommand.h"
64 #include "ProgressCommands/SendMailProgressCommand.h"
65 #include "CertificateValidationHelper.h"
66 #include "CheckCertificateDlg.h"
67 #include "SubmoduleResolveConflictDlg.h"
68 #include "GitDiff.h"
69 #include "../TGitCache/CacheInterface.h"
71 static struct last_accepted_cert {
72 BYTE* data;
73 size_t len;
75 last_accepted_cert()
76 : data(nullptr)
77 , len(0)
80 ~last_accepted_cert()
82 free(data);
84 boolean cmp(git_cert_x509* cert)
86 return len > 0 && len == cert->len && memcmp(data, cert->data, len) == 0;
88 void set(git_cert_x509* cert)
90 free(data);
91 len = cert->len;
92 if (len == 0)
94 data = nullptr;
95 return;
97 data = new BYTE[len];
98 memcpy(data, cert->data, len);
100 } last_accepted_cert;
102 static bool DoFetch(const CString& url, const bool fetchAllRemotes, const bool loadPuttyAgent, const int prune, const bool bDepth, const int nDepth, const int fetchTags, const CString& remoteBranch, int runRebase, const bool rebasePreserveMerges);
104 bool CAppUtils::StashSave(const CString& msg, bool showPull, bool pullShowPush, bool showMerge, const CString& mergeRev)
106 if (!CheckUserData())
107 return false;
109 CStashSaveDlg dlg;
110 dlg.m_sMessage = msg;
111 if (dlg.DoModal() == IDOK)
113 CString cmd = L"git.exe stash save";
115 if (dlg.m_bIncludeUntracked)
116 cmd += L" --include-untracked";
117 else if (dlg.m_bAll)
118 cmd += L" --all";
120 if (!dlg.m_sMessage.IsEmpty())
122 CString message = dlg.m_sMessage;
123 message.Replace(L"\"", L"\"\"");
124 cmd += L" -- \"" + message + L'"';
127 CProgressDlg progress;
128 progress.m_GitCmd = cmd;
129 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
131 if (status)
132 return;
134 if (showPull)
135 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&]{ CAppUtils::Pull(pullShowPush, true); });
136 if (showMerge)
137 postCmdList.emplace_back(IDI_MERGE, IDS_MENUMERGE, [&]{ CAppUtils::Merge(&mergeRev, true); });
139 return (progress.DoModal() == IDOK);
141 return false;
144 bool CAppUtils::StashApply(CString ref, bool showChanges /* true */)
146 CString cmd = L"git.exe stash apply ";
147 if (CStringUtils::StartsWith(ref, L"refs/"))
148 ref = ref.Mid(5);
149 if (CStringUtils::StartsWith(ref, L"stash{"))
150 ref = L"stash@" + ref.Mid(5);
151 cmd += ref;
153 CSysProgressDlg sysProgressDlg;
154 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
155 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
156 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
157 sysProgressDlg.SetShowProgressBar(false);
158 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
159 sysProgressDlg.ShowModeless((HWND)nullptr, true);
161 CString out;
162 int ret = g_Git.Run(cmd, &out, CP_UTF8);
164 sysProgressDlg.Stop();
166 bool hasConflicts = (out.Find(L"CONFLICT") >= 0);
167 if (ret && !(ret == 1 && hasConflicts))
168 CMessageBox::Show(nullptr, CString(MAKEINTRESOURCE(IDS_PROC_STASHAPPLYFAILED)) + L'\n' + out, L"TortoiseGit", MB_OK | MB_ICONERROR);
169 else
171 CString message;
172 message.LoadString(IDS_PROC_STASHAPPLYSUCCESS);
173 if (hasConflicts)
174 message.LoadString(IDS_PROC_STASHAPPLYFAILEDCONFLICTS);
175 if (showChanges)
177 if (CMessageBox::Show(nullptr, message + L'\n' + CString(MAKEINTRESOURCE(IDS_SEECHANGES)), L"TortoiseGit", MB_YESNO | MB_ICONINFORMATION) == IDYES)
179 cmd.Format(L"/command:repostatus /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
180 CAppUtils::RunTortoiseGitProc(cmd);
182 return true;
184 else
186 MessageBox(nullptr, message ,L"TortoiseGit", MB_OK | MB_ICONINFORMATION);
187 return true;
190 return false;
193 bool CAppUtils::StashPop(int showChanges /* = 1 */)
195 CString cmd = L"git.exe stash pop";
197 CSysProgressDlg sysProgressDlg;
198 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
199 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
200 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
201 sysProgressDlg.SetShowProgressBar(false);
202 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
203 sysProgressDlg.ShowModeless((HWND)nullptr, true);
205 CString out;
206 int ret = g_Git.Run(cmd, &out, CP_UTF8);
208 sysProgressDlg.Stop();
210 bool hasConflicts = (out.Find(L"CONFLICT") >= 0);
211 if (ret && !(ret == 1 && hasConflicts))
212 CMessageBox::Show(nullptr, CString(MAKEINTRESOURCE(IDS_PROC_STASHPOPFAILED)) + L'\n' + out, L"TortoiseGit", MB_OK | MB_ICONERROR);
213 else
215 CString message;
216 message.LoadString(IDS_PROC_STASHPOPSUCCESS);
217 if (hasConflicts)
218 message.LoadString(IDS_PROC_STASHPOPFAILEDCONFLICTS);
219 if (showChanges == 1 || (showChanges == 0 && hasConflicts))
221 if (CMessageBox::ShowCheck(nullptr, message + L'\n' + CString(MAKEINTRESOURCE(IDS_SEECHANGES)), L"TortoiseGit", MB_YESNO | (hasConflicts ? MB_ICONEXCLAMATION : MB_ICONINFORMATION), hasConflicts ? L"StashPopShowConflictChanges" : L"StashPopShowChanges") == IDYES)
223 cmd.Format(L"/command:repostatus /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
224 CAppUtils::RunTortoiseGitProc(cmd);
226 return true;
228 else if (showChanges > 1)
230 MessageBox(nullptr, message, L"TortoiseGit", MB_OK | MB_ICONINFORMATION);
231 return true;
233 else if (showChanges == 0)
234 return true;
236 return false;
239 BOOL CAppUtils::StartExtMerge(bool bAlternative,
240 const CTGitPath& basefile, const CTGitPath& theirfile, const CTGitPath& yourfile, const CTGitPath& mergedfile,
241 const CString& basename, const CString& theirname, const CString& yourname, const CString& mergedname, bool bReadOnly,
242 HWND resolveMsgHwnd, bool bDeleteBaseTheirsMineOnClose)
244 CRegString regCom = CRegString(L"Software\\TortoiseGit\\Merge");
245 CString ext = mergedfile.GetFileExtension();
246 CString com = regCom;
247 bool bInternal = false;
249 if (!ext.IsEmpty())
251 // is there an extension specific merge tool?
252 CRegString mergetool(L"Software\\TortoiseGit\\MergeTools\\" + ext.MakeLower());
253 if (!CString(mergetool).IsEmpty())
254 com = mergetool;
256 // is there a filename specific merge tool?
257 CRegString mergetool(L"Software\\TortoiseGit\\MergeTools\\." + mergedfile.GetFilename().MakeLower());
258 if (!CString(mergetool).IsEmpty())
259 com = mergetool;
261 if (bAlternative && !com.IsEmpty())
263 if (CStringUtils::StartsWith(com, L"#"))
264 com.Delete(0);
265 else
266 com.Empty();
269 if (com.IsEmpty() || CStringUtils::StartsWith(com, L"#"))
271 // Maybe we should use TortoiseIDiff?
272 if ((ext == L".jpg") || (ext == L".jpeg") ||
273 (ext == L".bmp") || (ext == L".gif") ||
274 (ext == L".png") || (ext == L".ico") ||
275 (ext == L".tif") || (ext == L".tiff") ||
276 (ext == L".dib") || (ext == L".emf") ||
277 (ext == L".cur"))
279 com = CPathUtils::GetAppDirectory() + L"TortoiseGitIDiff.exe";
280 com = L'"' + com + L'"';
281 com = com + L" /base:%base /theirs:%theirs /mine:%mine /result:%merged";
282 com = com + L" /basetitle:%bname /theirstitle:%tname /minetitle:%yname";
283 if (resolveMsgHwnd)
284 com.AppendFormat(L" /resolvemsghwnd:%I64d", (__int64)resolveMsgHwnd);
286 else
288 // use TortoiseGitMerge
289 bInternal = true;
290 com = CPathUtils::GetAppDirectory() + L"TortoiseGitMerge.exe";
291 com = L'"' + com + L'"';
292 com = com + L" /base:%base /theirs:%theirs /mine:%mine /merged:%merged";
293 com = com + L" /basename:%bname /theirsname:%tname /minename:%yname /mergedname:%mname";
294 com += L" /saverequired";
295 if (resolveMsgHwnd)
296 com.AppendFormat(L" /resolvemsghwnd:%I64d", (__int64)resolveMsgHwnd);
297 if (bDeleteBaseTheirsMineOnClose)
298 com += L" /deletebasetheirsmineonclose";
300 if (!g_sGroupingUUID.IsEmpty())
302 com += L" /groupuuid:\"";
303 com += g_sGroupingUUID;
304 com += L'"';
307 // check if the params are set. If not, just add the files to the command line
308 if ((com.Find(L"%merged") < 0) && (com.Find(L"%base") < 0) && (com.Find(L"%theirs") < 0) && (com.Find(L"%mine") < 0))
310 com += L" \"" + basefile.GetWinPathString() + L'"';
311 com += L" \"" + theirfile.GetWinPathString() + L'"';
312 com += L" \"" + yourfile.GetWinPathString() + L'"';
313 com += L" \"" + mergedfile.GetWinPathString() + L'"';
315 if (basefile.IsEmpty())
317 com.Replace(L"/base:%base", L"");
318 com.Replace(L"%base", L"");
320 else
321 com.Replace(L"%base", L'"' + basefile.GetWinPathString() + L'"');
322 if (theirfile.IsEmpty())
324 com.Replace(L"/theirs:%theirs", L"");
325 com.Replace(L"%theirs", L"");
327 else
328 com.Replace(L"%theirs", L'"' + theirfile.GetWinPathString() + L'"');
329 if (yourfile.IsEmpty())
331 com.Replace(L"/mine:%mine", L"");
332 com.Replace(L"%mine", L"");
334 else
335 com.Replace(L"%mine", L'"' + yourfile.GetWinPathString() + L'"');
336 if (mergedfile.IsEmpty())
338 com.Replace(L"/merged:%merged", L"");
339 com.Replace(L"%merged", L"");
341 else
342 com.Replace(L"%merged", L'"' + mergedfile.GetWinPathString() + L'"');
343 if (basename.IsEmpty())
345 if (basefile.IsEmpty())
347 com.Replace(L"/basename:%bname", L"");
348 com.Replace(L"%bname", L"");
350 else
351 com.Replace(L"%bname", L'"' + basefile.GetUIFileOrDirectoryName() + L'"');
353 else
354 com.Replace(L"%bname", L'"' + basename + L'"');
355 if (theirname.IsEmpty())
357 if (theirfile.IsEmpty())
359 com.Replace(L"/theirsname:%tname", L"");
360 com.Replace(L"%tname", L"");
362 else
363 com.Replace(L"%tname", L'"' + theirfile.GetUIFileOrDirectoryName() + L'"');
365 else
366 com.Replace(L"%tname", L'"' + theirname + L'"');
367 if (yourname.IsEmpty())
369 if (yourfile.IsEmpty())
371 com.Replace(L"/minename:%yname", L"");
372 com.Replace(L"%yname", L"");
374 else
375 com.Replace(L"%yname", L'"' + yourfile.GetUIFileOrDirectoryName() + L'"');
377 else
378 com.Replace(L"%yname", L'"' + yourname + L'"');
379 if (mergedname.IsEmpty())
381 if (mergedfile.IsEmpty())
383 com.Replace(L"/mergedname:%mname", L"");
384 com.Replace(L"%mname", L"");
386 else
387 com.Replace(L"%mname", L'"' + mergedfile.GetUIFileOrDirectoryName() + L'"');
389 else
390 com.Replace(L"%mname", L'"' + mergedname + L'"');
392 com.Replace(L"%wtroot", L'"' + g_Git.m_CurrentDir + L'"');
394 if ((bReadOnly)&&(bInternal))
395 com += L" /readonly";
397 if(!LaunchApplication(com, IDS_ERR_EXTMERGESTART, false))
399 return FALSE;
402 return TRUE;
405 BOOL CAppUtils::StartExtPatch(const CTGitPath& patchfile, const CTGitPath& dir, const CString& sOriginalDescription, const CString& sPatchedDescription, BOOL bReversed, BOOL bWait)
407 CString viewer;
408 // use TortoiseGitMerge
409 viewer = CPathUtils::GetAppDirectory();
410 viewer += L"TortoiseGitMerge.exe";
412 viewer = L'"' + viewer + L'"';
413 viewer = viewer + L" /diff:\"" + patchfile.GetWinPathString() + L'"';
414 viewer = viewer + L" /patchpath:\"" + dir.GetWinPathString() + L'"';
415 if (bReversed)
416 viewer += L" /reversedpatch";
417 if (!sOriginalDescription.IsEmpty())
418 viewer = viewer + L" /patchoriginal:\"" + sOriginalDescription + L'"';
419 if (!sPatchedDescription.IsEmpty())
420 viewer = viewer + L" /patchpatched:\"" + sPatchedDescription + L'"';
421 if (!g_sGroupingUUID.IsEmpty())
423 viewer += L" /groupuuid:\"";
424 viewer += g_sGroupingUUID;
425 viewer += L'"';
427 if(!LaunchApplication(viewer, IDS_ERR_DIFFVIEWSTART, !!bWait))
428 return FALSE;
429 return TRUE;
432 CString CAppUtils::PickDiffTool(const CTGitPath& file1, const CTGitPath& file2)
434 CString difftool = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + file2.GetFilename().MakeLower());
435 if (!difftool.IsEmpty())
436 return difftool;
437 difftool = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + file1.GetFilename().MakeLower());
438 if (!difftool.IsEmpty())
439 return difftool;
441 // Is there an extension specific diff tool?
442 CString ext = file2.GetFileExtension().MakeLower();
443 if (!ext.IsEmpty())
445 difftool = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + ext);
446 if (!difftool.IsEmpty())
447 return difftool;
448 // Maybe we should use TortoiseIDiff?
449 if ((ext == L".jpg") || (ext == L".jpeg") ||
450 (ext == L".bmp") || (ext == L".gif") ||
451 (ext == L".png") || (ext == L".ico") ||
452 (ext == L".tif") || (ext == L".tiff") ||
453 (ext == L".dib") || (ext == L".emf") ||
454 (ext == L".cur"))
456 return
457 L'"' + CPathUtils::GetAppDirectory() + L"TortoiseGitIDiff.exe" + L'"' +
458 L" /left:%base /right:%mine /lefttitle:%bname /righttitle:%yname" +
459 L" /groupuuid:\"" + g_sGroupingUUID + L'"';
463 // Finally, pick a generic external diff tool
464 difftool = CRegString(L"Software\\TortoiseGit\\Diff");
465 return difftool;
468 bool CAppUtils::StartExtDiff(
469 const CString& file1, const CString& file2,
470 const CString& sName1, const CString& sName2,
471 const CString& originalFile1, const CString& originalFile2,
472 const CString& hash1, const CString& hash2,
473 const DiffFlags& flags, int jumpToLine)
475 CString viewer;
477 CRegDWORD blamediff(L"Software\\TortoiseGit\\DiffBlamesWithTortoiseMerge", FALSE);
478 if (!flags.bBlame || !(DWORD)blamediff)
480 viewer = PickDiffTool(file1, file2);
481 // If registry entry for a diff program is commented out, use TortoiseGitMerge.
482 bool bCommentedOut = CStringUtils::StartsWith(viewer, L"#");
483 if (flags.bAlternativeTool)
485 // Invert external vs. internal diff tool selection.
486 if (bCommentedOut)
487 viewer.Delete(0); // uncomment
488 else
489 viewer.Empty();
491 else if (bCommentedOut)
492 viewer.Empty();
495 bool bInternal = viewer.IsEmpty();
496 if (bInternal)
498 viewer =
499 L'"' + CPathUtils::GetAppDirectory() + L"TortoiseGitMerge.exe" + L'"' +
500 L" /base:%base /mine:%mine /basename:%bname /minename:%yname" +
501 L" /basereflectedname:%bpath /minereflectedname:%ypath";
502 if (!g_sGroupingUUID.IsEmpty())
504 viewer += L" /groupuuid:\"";
505 viewer += g_sGroupingUUID;
506 viewer += L'"';
508 if (flags.bBlame)
509 viewer += L" /blame";
511 // check if the params are set. If not, just add the files to the command line
512 if ((viewer.Find(L"%base") < 0) && (viewer.Find(L"%mine") < 0))
514 viewer += L" \"" + file1 + L'"';
515 viewer += L" \"" + file2 + L'"';
517 if (viewer.Find(L"%base") >= 0)
518 viewer.Replace(L"%base", L'"' + file1 + L'"');
519 if (viewer.Find(L"%mine") >= 0)
520 viewer.Replace(L"%mine", L'"' + file2 + L'"');
522 if (sName1.IsEmpty())
523 viewer.Replace(L"%bname", L'"' + file1 + L'"');
524 else
525 viewer.Replace(L"%bname", L'"' + sName1 + L'"');
527 if (sName2.IsEmpty())
528 viewer.Replace(L"%yname", L'"' + file2 + L'"');
529 else
530 viewer.Replace(L"%yname", L'"' + sName2 + L'"');
532 viewer.Replace(L"%bpath", L'"' + originalFile1 + L'"');
533 viewer.Replace(L"%ypath", L'"' + originalFile2 + L'"');
535 viewer.Replace(L"%brev", L'"' + hash1 + L'"');
536 viewer.Replace(L"%yrev", L'"' + hash2 + L'"');
538 viewer.Replace(L"%wtroot", L'"' + g_Git.m_CurrentDir + L'"');
540 if (flags.bReadOnly && bInternal)
541 viewer += L" /readonly";
543 if (jumpToLine > 0)
544 viewer.AppendFormat(L" /line:%d", jumpToLine);
546 return LaunchApplication(viewer, IDS_ERR_EXTDIFFSTART, flags.bWait);
549 BOOL CAppUtils::StartUnifiedDiffViewer(const CString& patchfile, const CString& title, BOOL bWait, bool bAlternativeTool)
551 CString viewer;
552 CRegString v = CRegString(L"Software\\TortoiseGit\\DiffViewer");
553 viewer = v;
555 // If registry entry for a diff program is commented out, use TortoiseGitMerge.
556 bool bCommentedOut = CStringUtils::StartsWith(viewer, L"#");
557 if (bAlternativeTool)
559 // Invert external vs. internal diff tool selection.
560 if (bCommentedOut)
561 viewer.Delete(0); // uncomment
562 else
563 viewer.Empty();
565 else if (bCommentedOut)
566 viewer.Empty();
568 if (viewer.IsEmpty())
570 // use TortoiseGitUDiff
571 viewer = CPathUtils::GetAppDirectory();
572 viewer += L"TortoiseGitUDiff.exe";
573 // enquote the path to TortoiseGitUDiff
574 viewer = L'"' + viewer + L'"';
575 // add the params
576 viewer = viewer + L" /patchfile:%1 /title:\"%title\"";
577 if (!g_sGroupingUUID.IsEmpty())
579 viewer += L" /groupuuid:\"";
580 viewer += g_sGroupingUUID;
581 viewer += L'"';
584 if (viewer.Find(L"%1") >= 0)
586 if (viewer.Find(L"\"%1\"") >= 0)
587 viewer.Replace(L"%1", patchfile);
588 else
589 viewer.Replace(L"%1", L'"' + patchfile + L'"');
591 else
592 viewer += L" \"" + patchfile + L'"';
593 if (viewer.Find(L"%title") >= 0)
594 viewer.Replace(L"%title", title);
596 if(!LaunchApplication(viewer, IDS_ERR_DIFFVIEWSTART, !!bWait))
597 return FALSE;
598 return TRUE;
601 BOOL CAppUtils::StartTextViewer(CString file)
603 CString viewer;
604 CRegString txt = CRegString(L".txt\\", L"", FALSE, HKEY_CLASSES_ROOT);
605 viewer = txt;
606 viewer = viewer + L"\\Shell\\Open\\Command\\";
607 CRegString txtexe = CRegString(viewer, L"", FALSE, HKEY_CLASSES_ROOT);
608 viewer = txtexe;
610 DWORD len = ExpandEnvironmentStrings(viewer, nullptr, 0);
611 auto buf = std::make_unique<TCHAR[]>(len + 1);
612 ExpandEnvironmentStrings(viewer, buf.get(), len);
613 viewer = buf.get();
614 len = ExpandEnvironmentStrings(file, nullptr, 0);
615 auto buf2 = std::make_unique<TCHAR[]>(len + 1);
616 ExpandEnvironmentStrings(file, buf2.get(), len);
617 file = buf2.get();
618 file = L'"' + file + L'"';
619 if (viewer.IsEmpty())
620 return CAppUtils::ShowOpenWithDialog(file) ? TRUE : FALSE;
621 if (viewer.Find(L"\"%1\"") >= 0)
622 viewer.Replace(L"\"%1\"", file);
623 else if (viewer.Find(L"%1") >= 0)
624 viewer.Replace(L"%1", file);
625 else
626 viewer += L' ';
627 viewer += file;
629 if(!LaunchApplication(viewer, IDS_ERR_TEXTVIEWSTART, false))
630 return FALSE;
631 return TRUE;
634 BOOL CAppUtils::CheckForEmptyDiff(const CTGitPath& sDiffPath)
636 DWORD length = 0;
637 CAutoFile hFile = ::CreateFile(sDiffPath.GetWinPath(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
638 if (!hFile)
639 return TRUE;
640 length = ::GetFileSize(hFile, nullptr);
641 if (length < 4)
642 return TRUE;
643 return FALSE;
646 CString CAppUtils::GetLogFontName()
648 return (CString)CRegString(L"Software\\TortoiseGit\\LogFontName", L"Consolas");
651 DWORD CAppUtils::GetLogFontSize()
653 return (DWORD)CRegDWORD(L"Software\\TortoiseGit\\LogFontSize", 9);
656 void CAppUtils::CreateFontForLogs(CFont& fontToCreate)
658 LOGFONT logFont;
659 HDC hScreenDC = ::GetDC(nullptr);
660 logFont.lfHeight = -MulDiv(GetLogFontSize(), GetDeviceCaps(hScreenDC, LOGPIXELSY), 72);
661 ::ReleaseDC(nullptr, hScreenDC);
662 logFont.lfWidth = 0;
663 logFont.lfEscapement = 0;
664 logFont.lfOrientation = 0;
665 logFont.lfWeight = FW_NORMAL;
666 logFont.lfItalic = 0;
667 logFont.lfUnderline = 0;
668 logFont.lfStrikeOut = 0;
669 logFont.lfCharSet = DEFAULT_CHARSET;
670 logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
671 logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
672 logFont.lfQuality = DRAFT_QUALITY;
673 logFont.lfPitchAndFamily = FF_DONTCARE | FIXED_PITCH;
674 wcscpy_s(logFont.lfFaceName, 32, (LPCTSTR)GetLogFontName());
675 VERIFY(fontToCreate.CreateFontIndirect(&logFont));
678 bool CAppUtils::LaunchPAgent(const CString* keyfile, const CString* pRemote)
680 CString key,remote;
681 CString cmd,out;
682 if (!pRemote)
683 remote = L"origin";
684 else
685 remote=*pRemote;
687 if (!keyfile)
689 cmd.Format(L"remote.%s.puttykeyfile", (LPCTSTR)remote);
690 key = g_Git.GetConfigValue(cmd);
692 else
693 key=*keyfile;
695 if(key.IsEmpty())
696 return false;
698 CString proc=CPathUtils::GetAppDirectory();
699 proc += L"pageant.exe \"";
700 proc += key;
701 proc += L'"';
703 CString tempfile = GetTempFile();
704 ::DeleteFile(tempfile);
706 proc += L" -c \"";
707 proc += CPathUtils::GetAppDirectory();
708 proc += L"tgittouch.exe\"";
709 proc += L" \"";
710 proc += tempfile;
711 proc += L'"';
713 CString appDir = CPathUtils::GetAppDirectory();
714 bool b = LaunchApplication(proc, IDS_ERR_PAGEANT, true, &appDir);
715 if(!b)
716 return b;
718 int i=0;
719 while(!::PathFileExists(tempfile))
721 Sleep(100);
722 ++i;
723 if(i>10*60*5)
724 break; //timeout 5 minutes
727 if( i== 10*60*5)
728 CMessageBox::Show(nullptr, IDS_ERR_PAEGENTTIMEOUT, IDS_APPNAME, MB_OK | MB_ICONERROR);
729 ::DeleteFile(tempfile);
730 return true;
733 bool CAppUtils::LaunchAlternativeEditor(const CString& filename, bool uac)
735 CString editTool = CRegString(L"Software\\TortoiseGit\\AlternativeEditor");
736 if (editTool.IsEmpty() || CStringUtils::StartsWith(editTool, L"#"))
737 editTool = CPathUtils::GetAppDirectory() + L"notepad2.exe";
739 CString sCmd;
740 sCmd.Format(L"\"%s\" \"%s\"", (LPCTSTR)editTool, (LPCTSTR)filename);
742 LaunchApplication(sCmd, 0, false, nullptr, uac);
743 return true;
746 bool CAppUtils::LaunchRemoteSetting()
748 CTGitPath path(g_Git.m_CurrentDir);
749 CSettings dlg(IDS_PROC_SETTINGS_TITLE, &path);
750 dlg.SetTreeViewMode(TRUE, TRUE, TRUE);
751 dlg.SetTreeWidth(220);
752 dlg.m_DefaultPage = L"gitremote";
754 dlg.DoModal();
755 dlg.HandleRestart();
756 return true;
760 * Launch the external blame viewer
762 bool CAppUtils::LaunchTortoiseBlame(const CString& sBlameFile, const CString& Rev, const CString& sParams)
764 CString viewer = L'"' + CPathUtils::GetAppDirectory();
765 viewer += L"TortoiseGitBlame.exe";
766 viewer += L"\" \"" + sBlameFile + L'"';
767 //viewer += L" \"" + sLogFile + L'"';
768 //viewer += L" \"" + sOriginalFile + L'"';
769 if(!Rev.IsEmpty() && Rev != GIT_REV_ZERO)
770 viewer += L" /rev:" + Rev;
771 if (!g_sGroupingUUID.IsEmpty())
773 viewer += L" /groupuuid:\"";
774 viewer += g_sGroupingUUID;
775 viewer += L'"';
777 viewer += L' ' + sParams;
779 return LaunchApplication(viewer, IDS_ERR_TGITBLAME, false);
782 bool CAppUtils::FormatTextInRichEditControl(CWnd * pWnd)
784 CString sText;
785 if (!pWnd)
786 return false;
787 bool bStyled = false;
788 pWnd->GetWindowText(sText);
789 // the rich edit control doesn't count the CR char!
790 // to be exact: CRLF is treated as one char.
791 sText.Remove(L'\r');
793 // style each line separately
794 int offset = 0;
795 int nNewlinePos;
798 nNewlinePos = sText.Find('\n', offset);
799 CString sLine = nNewlinePos >= 0 ? sText.Mid(offset, nNewlinePos - offset) : sText.Mid(offset);
801 int start = 0;
802 int end = 0;
803 while (FindStyleChars(sLine, '*', start, end))
805 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
806 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
807 SetCharFormat(pWnd, CFM_BOLD, CFE_BOLD);
808 bStyled = true;
809 start = end;
811 start = 0;
812 end = 0;
813 while (FindStyleChars(sLine, '^', start, end))
815 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
816 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
817 SetCharFormat(pWnd, CFM_ITALIC, CFE_ITALIC);
818 bStyled = true;
819 start = end;
821 start = 0;
822 end = 0;
823 while (FindStyleChars(sLine, '_', start, end))
825 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
826 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
827 SetCharFormat(pWnd, CFM_UNDERLINE, CFE_UNDERLINE);
828 bStyled = true;
829 start = end;
831 offset = nNewlinePos+1;
832 } while(nNewlinePos>=0);
833 return bStyled;
836 bool CAppUtils::FindStyleChars(const CString& sText, TCHAR stylechar, int& start, int& end)
838 int i=start;
839 int last = sText.GetLength() - 1;
840 bool bFoundMarker = false;
841 TCHAR c = i == 0 ? L'\0' : sText[i - 1];
842 TCHAR nextChar = i >= last ? L'\0' : sText[i + 1];
844 // find a starting marker
845 while (i < last)
847 TCHAR prevChar = c;
848 c = nextChar;
849 nextChar = sText[i + 1];
851 // IsCharAlphaNumeric can be somewhat expensive.
852 // Long lines of "*****" or "----" will be pre-empted efficiently
853 // by the (c != nextChar) condition.
855 if ((c == stylechar) && (c != nextChar))
857 if (IsCharAlphaNumeric(nextChar) && !IsCharAlphaNumeric(prevChar))
859 start = ++i;
860 bFoundMarker = true;
861 break;
864 ++i;
866 if (!bFoundMarker)
867 return false;
869 // find ending marker
870 // c == sText[i - 1]
872 bFoundMarker = false;
873 while (i <= last)
875 TCHAR prevChar = c;
876 c = sText[i];
877 if (c == stylechar)
879 if ((i == last) || (!IsCharAlphaNumeric(sText[i + 1]) && IsCharAlphaNumeric(prevChar)))
881 end = i;
882 ++i;
883 bFoundMarker = true;
884 break;
887 ++i;
889 return bFoundMarker;
892 // from CSciEdit
893 namespace {
894 bool IsValidURLChar(wchar_t ch)
896 return iswalnum(ch) ||
897 ch == L'_' || ch == L'/' || ch == L';' || ch == L'?' || ch == L'&' || ch == L'=' ||
898 ch == L'%' || ch == L':' || ch == L'.' || ch == L'#' || ch == L'-' || ch == L'+' ||
899 ch == L'|' || ch == L'>' || ch == L'<' || ch == L'!' || ch == L'@' || ch == L'~';
902 bool IsUrlOrEmail(const CString& sText)
904 if (!PathIsURLW(sText))
906 auto atpos = sText.Find(L'@');
907 if (atpos <= 0)
908 return false;
909 if (sText.Find(L'.', atpos) <= atpos + 1) // a dot must follow after the @, but not directly after it
910 return false;
911 if (sText.Find(L':', atpos) < 0) // do not detect git@example.com:something as an email address
912 return true;
913 return false;
915 for (const CString& prefix : { L"http://", L"https://", L"git://", L"ftp://", L"file://", L"mailto:" })
917 if (CStringUtils::StartsWith(sText, prefix) && sText.GetLength() != prefix.GetLength())
918 return true;
920 return false;
924 BOOL CAppUtils::StyleURLs(const CString& msg, CWnd* pWnd)
926 std::vector<CHARRANGE> positions = FindURLMatches(msg);
927 CAppUtils::SetCharFormat(pWnd, CFM_LINK, CFE_LINK, positions);
929 return positions.empty() ? FALSE : TRUE;
933 * implements URL searching with the same logic as CSciEdit::StyleURLs
935 std::vector<CHARRANGE> CAppUtils::FindURLMatches(const CString& msg)
937 std::vector<CHARRANGE> result;
939 int len = msg.GetLength();
940 int starturl = -1;
942 for (int i = 0; i <= msg.GetLength(); ++i)
944 if ((i < len) && IsValidURLChar(msg[i]))
946 if (starturl < 0)
947 starturl = i;
949 else
951 if (starturl >= 0)
953 bool strip = true;
954 if (msg[starturl] == '<' && i < len) // try to detect and do not strip URLs put within <>
956 while (starturl <= i && msg[starturl] == '<') // strip leading '<'
957 ++starturl;
958 strip = false;
959 i = starturl;
960 while (i < len && msg[i] != '\r' && msg[i] != '\n' && msg[i] != '>') // find first '>' or new line after resetting i to start position
961 ++i;
964 int skipTrailing = 0;
965 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] == '!'))
966 ++skipTrailing;
968 if (!IsUrlOrEmail(msg.Mid(starturl, i - starturl - skipTrailing)))
970 starturl = -1;
971 continue;
974 CHARRANGE range = { starturl, i - skipTrailing };
975 result.push_back(range);
977 starturl = -1;
981 return result;
984 bool CAppUtils::StartShowUnifiedDiff(HWND hWnd, const CTGitPath& url1, const CString& rev1,
985 const CTGitPath& /*url2*/, const CString& rev2,
986 //const GitRev& peg /* = GitRev */, const GitRev& headpeg /* = GitRev */,
987 bool bAlternateDiff /* = false */, bool /*bIgnoreAncestry*/ /* = false */,
988 bool /* blame = false */,
989 bool bMerge,
990 bool bCombine,
991 bool bNoPrefix)
993 int diffContext = g_Git.GetConfigValueInt32(L"diff.context", -1);
994 CString tempfile=GetTempFile();
995 if (g_Git.GetUnifiedDiff(url1, rev1, rev2, tempfile, bMerge, bCombine, diffContext, bNoPrefix))
997 MessageBox(hWnd, g_Git.GetGitLastErr(L"Could not get unified diff.", CGit::GIT_CMD_DIFF), L"TortoiseGit", MB_OK);
998 return false;
1000 CAppUtils::StartUnifiedDiffViewer(tempfile, rev1.IsEmpty() ? rev2 : rev1 + L':' + rev2, FALSE, bAlternateDiff);
1002 #if 0
1003 CString sCmd;
1004 sCmd.Format(L"%s /command:showcompare /unified",
1005 (LPCTSTR)(CPathUtils::GetAppDirectory()+L"TortoiseGitProc.exe"));
1006 sCmd += L" /url1:\"" + url1.GetGitPathString() + L'"';
1007 if (rev1.IsValid())
1008 sCmd += L" /revision1:" + rev1.ToString();
1009 sCmd += L" /url2:\"" + url2.GetGitPathString() + L'"';
1010 if (rev2.IsValid())
1011 sCmd += L" /revision2:" + rev2.ToString();
1012 if (peg.IsValid())
1013 sCmd += L" /pegrevision:" + peg.ToString();
1014 if (headpeg.IsValid())
1015 sCmd += L" /headpegrevision:" + headpeg.ToString();
1017 if (bAlternateDiff)
1018 sCmd += L" /alternatediff";
1020 if (bIgnoreAncestry)
1021 sCmd += L" /ignoreancestry";
1023 if (hWnd)
1025 sCmd += L" /hwnd:";
1026 TCHAR buf[30];
1027 swprintf_s(buf, 30, L"%p", (void*)hWnd);
1028 sCmd += buf;
1031 return CAppUtils::LaunchApplication(sCmd, 0, false);
1032 #endif
1033 return TRUE;
1036 bool CAppUtils::SetupDiffScripts(bool force, const CString& type)
1038 CString scriptsdir = CPathUtils::GetAppParentDirectory();
1039 scriptsdir += L"Diff-Scripts";
1040 CSimpleFileFind files(scriptsdir);
1041 while (files.FindNextFileNoDirectories())
1043 CString file = files.GetFilePath();
1044 CString filename = files.GetFileName();
1045 CString ext = file.Mid(file.ReverseFind('-') + 1);
1046 ext = L"." + ext.Left(ext.ReverseFind(L'.'));
1047 std::set<CString> extensions;
1048 extensions.insert(ext);
1049 CString kind;
1050 if (CStringUtils::EndsWithI(file, L"vbs"))
1051 kind = L" //E:vbscript";
1052 if (CStringUtils::EndsWithI(file, L"js"))
1053 kind = L" //E:javascript";
1054 // open the file, read the first line and find possible extensions
1055 // this script can handle
1058 CStdioFile f(file, CFile::modeRead | CFile::shareDenyNone);
1059 CString extline;
1060 if (f.ReadString(extline))
1062 if ((extline.GetLength() > 15 ) &&
1063 (CStringUtils::StartsWith(extline, L"// extensions: ") ||
1064 CStringUtils::StartsWith(extline, L"' extensions: ")))
1066 if (extline[0] == '/')
1067 extline = extline.Mid(15);
1068 else
1069 extline = extline.Mid(14);
1070 CString sToken;
1071 int curPos = 0;
1072 sToken = extline.Tokenize(L";", curPos);
1073 while (!sToken.IsEmpty())
1075 if (!sToken.IsEmpty())
1077 if (sToken[0] != '.')
1078 sToken = L"." + sToken;
1079 extensions.insert(sToken);
1081 sToken = extline.Tokenize(L";", curPos);
1085 f.Close();
1087 catch (CFileException* e)
1089 e->Delete();
1092 for (const auto& extension : extensions)
1094 if (type.IsEmpty() || (type.Compare(L"Diff") == 0))
1096 if (CStringUtils::StartsWithI(filename, L"diff-"))
1098 CRegString diffreg = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + extension);
1099 CString diffregstring = diffreg;
1100 if (force || (diffregstring.IsEmpty()) || (diffregstring.Find(filename) >= 0))
1101 diffreg = L"wscript.exe \"" + file + L"\" %base %mine" + kind;
1104 if (type.IsEmpty() || (type.Compare(L"Merge") == 0))
1106 if (CStringUtils::StartsWithI(filename, L"merge-"))
1108 CRegString diffreg = CRegString(L"Software\\TortoiseGit\\MergeTools\\" + extension);
1109 CString diffregstring = diffreg;
1110 if (force || (diffregstring.IsEmpty()) || (diffregstring.Find(filename) >= 0))
1111 diffreg = L"wscript.exe \"" + file + L"\" %merged %theirs %mine %base" + kind;
1117 return true;
1120 bool CAppUtils::Export(const CString* BashHash, const CTGitPath* orgPath)
1122 // ask from where the export has to be done
1123 CExportDlg dlg;
1124 if(BashHash)
1125 dlg.m_initialRefName=*BashHash;
1126 if (orgPath)
1128 if (PathIsRelative(orgPath->GetWinPath()))
1129 dlg.m_orgPath = g_Git.CombinePath(orgPath);
1130 else
1131 dlg.m_orgPath = *orgPath;
1134 if (dlg.DoModal() == IDOK)
1136 CString cmd;
1137 cmd.Format(L"git.exe archive --output=\"%s\" --format=zip --verbose %s --",
1138 (LPCTSTR)dlg.m_strFile, (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName));
1140 CProgressDlg pro;
1141 pro.m_GitCmd=cmd;
1142 pro.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1144 if (status)
1145 return;
1146 postCmdList.emplace_back(IDI_EXPLORER, IDS_STATUSLIST_CONTEXT_EXPLORE, [&]{ CAppUtils::ExploreTo(hWndExplorer, dlg.m_strFile); });
1149 CGit git;
1150 if (!dlg.m_bWholeProject && !dlg.m_orgPath.IsEmpty() && PathIsDirectory(dlg.m_orgPath.GetWinPathString()))
1152 git.m_CurrentDir = dlg.m_orgPath.GetWinPathString();
1153 pro.m_Git = &git;
1155 return (pro.DoModal() == IDOK);
1157 return false;
1160 bool CAppUtils::UpdateBranchDescription(const CString& branch, CString description)
1162 if (branch.IsEmpty())
1163 return false;
1165 CString key;
1166 key.Format(L"branch.%s.description", (LPCTSTR)branch);
1167 description.Remove(L'\r');
1168 description.Trim();
1169 if (description.IsEmpty())
1170 g_Git.UnsetConfigValue(key);
1171 else
1172 g_Git.SetConfigValue(key, description);
1174 return true;
1177 bool CAppUtils::CreateBranchTag(bool isTag /*true*/, const CString* commitHash /*nullptr*/, bool switchNewBranch /*false*/, LPCTSTR name /*nullptr*/)
1179 CCreateBranchTagDlg dlg;
1180 dlg.m_bIsTag = isTag;
1181 dlg.m_bSwitch = switchNewBranch;
1183 if (commitHash)
1184 dlg.m_initialRefName = *commitHash;
1186 if (name)
1187 dlg.m_BranchTagName = name;
1189 if(dlg.DoModal()==IDOK)
1191 CString cmd;
1192 CString force;
1193 CString track;
1194 if(dlg.m_bTrack == TRUE)
1195 track = L"--track";
1196 else if(dlg.m_bTrack == FALSE)
1197 track = L"--no-track";
1199 if(dlg.m_bForce)
1200 force = L"-f";
1202 if (isTag)
1204 CString sign;
1205 if(dlg.m_bSign)
1206 sign = L"-s";
1208 cmd.Format(L"git.exe tag %s %s %s %s",
1209 (LPCTSTR)force,
1210 (LPCTSTR)sign,
1211 (LPCTSTR)dlg.m_BranchTagName,
1212 (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName)
1215 if(!dlg.m_Message.Trim().IsEmpty())
1217 CString tempfile = ::GetTempFile();
1218 if (CAppUtils::SaveCommitUnicodeFile(tempfile, dlg.m_Message))
1220 MessageBox(nullptr, L"Could not save tag message", L"TortoiseGit", MB_OK | MB_ICONERROR);
1221 return FALSE;
1223 cmd += L" -F " + tempfile;
1226 else
1228 cmd.Format(L"git.exe branch %s %s %s %s",
1229 (LPCTSTR)track,
1230 (LPCTSTR)force,
1231 (LPCTSTR)dlg.m_BranchTagName,
1232 (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName)
1235 CString out;
1236 if(g_Git.Run(cmd,&out,CP_UTF8))
1238 MessageBox(nullptr, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
1239 return FALSE;
1241 if (!isTag && dlg.m_bSwitch)
1243 // it is a new branch and the user has requested to switch to it
1244 PerformSwitch(dlg.m_BranchTagName);
1246 if (!isTag && !dlg.m_Message.IsEmpty())
1247 UpdateBranchDescription(dlg.m_BranchTagName, dlg.m_Message);
1249 return TRUE;
1251 return FALSE;
1254 bool CAppUtils::Switch(const CString& initialRefName)
1256 CGitSwitchDlg dlg;
1257 if(!initialRefName.IsEmpty())
1258 dlg.m_initialRefName = initialRefName;
1260 if (dlg.DoModal() == IDOK)
1262 CString branch;
1263 if (dlg.m_bBranch)
1264 branch = dlg.m_NewBranch;
1266 // if refs/heads/ is not stripped, checkout will detach HEAD
1267 // checkout prefers branches on name clashes (with tags)
1268 if (CStringUtils::StartsWith(dlg.m_VersionName, L"refs/heads/") && dlg.m_bBranchOverride != TRUE)
1269 dlg.m_VersionName = dlg.m_VersionName.Mid(11);
1271 return PerformSwitch(dlg.m_VersionName, dlg.m_bForce == TRUE , branch, dlg.m_bBranchOverride == TRUE, dlg.m_bTrack, dlg.m_bMerge == TRUE);
1273 return FALSE;
1276 bool CAppUtils::PerformSwitch(const CString& ref, bool bForce /* false */, const CString& sNewBranch /* CString() */, bool bBranchOverride /* false */, BOOL bTrack /* 2 */, bool bMerge /* false */)
1278 CString cmd;
1279 CString track;
1280 CString force;
1281 CString branch;
1282 CString merge;
1284 if(!sNewBranch.IsEmpty()){
1285 if (bBranchOverride)
1286 branch.Format(L"-B %s ", (LPCTSTR)sNewBranch);
1287 else
1288 branch.Format(L"-b %s ", (LPCTSTR)sNewBranch);
1289 if (bTrack == TRUE)
1290 track = L"--track ";
1291 else if (bTrack == FALSE)
1292 track = L"--no-track ";
1294 if (bForce)
1295 force = L"-f ";
1296 if (bMerge)
1297 merge = L"--merge ";
1299 cmd.Format(L"git.exe checkout %s%s%s%s%s --",
1300 (LPCTSTR)force,
1301 (LPCTSTR)track,
1302 (LPCTSTR)merge,
1303 (LPCTSTR)branch,
1304 (LPCTSTR)g_Git.FixBranchName(ref));
1306 CProgressDlg progress;
1307 progress.m_GitCmd = cmd;
1309 CString currentBranch;
1310 bool hasBranch = CGit::GetCurrentBranchFromFile(g_Git.m_CurrentDir, currentBranch) == 0;
1311 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1313 if (!status)
1315 CTGitPath gitPath = g_Git.m_CurrentDir;
1316 if (gitPath.HasSubmodules())
1318 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1320 CString sCmd;
1321 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
1322 RunTortoiseGitProc(sCmd);
1325 if (hasBranch)
1326 postCmdList.emplace_back(IDI_MERGE, IDS_MENUMERGE, [&]{ Merge(&currentBranch); });
1329 CString newBranch;
1330 if (!CGit::GetCurrentBranchFromFile(g_Git.m_CurrentDir, newBranch))
1331 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&]{ Pull(); });
1333 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUCOMMIT, []{
1334 CTGitPathList pathlist;
1335 CTGitPathList selectedlist;
1336 pathlist.AddPath(CTGitPath());
1337 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(L"Software\\TortoiseGit\\SelectFilesForCommit", TRUE));
1338 CString str;
1339 Commit(CString(), false, str, pathlist, selectedlist, bSelectFilesForCommit);
1342 else
1344 if (bMerge && g_Git.HasWorkingTreeConflicts() > 0)
1346 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
1348 CString sCmd;
1349 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
1350 CAppUtils::RunTortoiseGitProc(sCmd);
1353 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ PerformSwitch(ref, bForce, sNewBranch, bBranchOverride, bTrack, bMerge); });
1354 if (!bMerge)
1355 postCmdList.emplace_back(IDI_SWITCH, IDS_SWITCH_WITH_MERGE, [&]{ PerformSwitch(ref, bForce, sNewBranch, bBranchOverride, bTrack, true); });
1358 progress.m_PostExecCallback = [&](DWORD& exitCode, CString& extraMsg)
1360 if (bMerge && !exitCode && g_Git.HasWorkingTreeConflicts() > 0)
1362 exitCode = 1; // Treat it as failure
1363 extraMsg = L"Has merge conflict";
1367 INT_PTR ret = progress.DoModal();
1369 return ret == IDOK;
1372 class CIgnoreFile : public CStdioFile
1374 public:
1375 STRING_VECTOR m_Items;
1376 CString m_eol;
1378 virtual BOOL ReadString(CString& rString)
1380 if (GetPosition() == 0)
1382 unsigned char utf8bom[] = { 0xEF, 0xBB, 0xBF };
1383 char buf[3] = { 0, 0, 0 };
1384 Read(buf, 3);
1385 if (memcpy(buf, utf8bom, sizeof(utf8bom)))
1386 SeekToBegin();
1389 CStringA strA;
1390 char lastChar = '\0';
1391 for (char c = '\0'; Read(&c, 1) == 1; lastChar = c)
1393 if (c == '\r')
1394 continue;
1395 if (c == '\n')
1397 m_eol = lastChar == L'\r' ? L"\r\n" : L"\n";
1398 break;
1400 strA.AppendChar(c);
1402 if (strA.IsEmpty())
1403 return FALSE;
1405 rString = CUnicodeUtils::GetUnicode(strA);
1406 return TRUE;
1409 void ResetState()
1411 m_Items.clear();
1412 m_eol.Empty();
1416 bool CAppUtils::OpenIgnoreFile(CIgnoreFile &file, const CString& filename)
1418 file.ResetState();
1419 if (!file.Open(filename, CFile::modeCreate | CFile::modeReadWrite | CFile::modeNoTruncate | CFile::typeBinary))
1421 MessageBox(nullptr, filename + L" Open Failure", L"TortoiseGit", MB_OK | MB_ICONERROR);
1422 return false;
1425 if (file.GetLength() > 0)
1427 CString fileText;
1428 while (file.ReadString(fileText))
1429 file.m_Items.push_back(fileText);
1430 file.Seek(file.GetLength() - 1, 0);
1431 char lastchar[1] = { 0 };
1432 file.Read(lastchar, 1);
1433 file.SeekToEnd();
1434 if (lastchar[0] != '\n')
1436 CStringA eol = CStringA(file.m_eol.IsEmpty() ? L"\n" : file.m_eol);
1437 file.Write(eol, eol.GetLength());
1440 else
1441 file.SeekToEnd();
1443 return true;
1446 bool CAppUtils::IgnoreFile(const CTGitPathList& path,bool IsMask)
1448 CIgnoreDlg ignoreDlg;
1449 if (ignoreDlg.DoModal() == IDOK)
1451 CString ignorefile;
1452 ignorefile = g_Git.m_CurrentDir + L'\\';
1454 switch (ignoreDlg.m_IgnoreFile)
1456 case 0:
1457 ignorefile += L".gitignore";
1458 break;
1459 case 2:
1460 GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, ignorefile);
1461 ignorefile += L"info";
1462 if (!PathFileExists(ignorefile))
1463 CreateDirectory(ignorefile, nullptr);
1464 ignorefile += L"\\exclude";
1465 break;
1468 CIgnoreFile file;
1471 if (ignoreDlg.m_IgnoreFile != 1 && !OpenIgnoreFile(file, ignorefile))
1472 return false;
1474 for (int i = 0; i < path.GetCount(); ++i)
1476 if (ignoreDlg.m_IgnoreFile == 1)
1478 ignorefile = g_Git.CombinePath(path[i].GetContainingDirectory()) + L"\\.gitignore";
1479 if (!OpenIgnoreFile(file, ignorefile))
1480 return false;
1483 CString ignorePattern;
1484 if (ignoreDlg.m_IgnoreType == 0)
1486 if (ignoreDlg.m_IgnoreFile != 1 && !path[i].GetContainingDirectory().GetGitPathString().IsEmpty())
1487 ignorePattern += L'/' + path[i].GetContainingDirectory().GetGitPathString();
1489 ignorePattern += L'/';
1491 if (IsMask)
1492 ignorePattern += L'*' + path[i].GetFileExtension();
1493 else
1494 ignorePattern += path[i].GetFileOrDirectoryName();
1496 // escape [ and ] so that files get ignored correctly
1497 ignorePattern.Replace(L"[", L"\\[");
1498 ignorePattern.Replace(L"]", L"\\]");
1500 bool found = false;
1501 for (size_t j = 0; j < file.m_Items.size(); ++j)
1503 if (file.m_Items[j] == ignorePattern)
1505 found = true;
1506 break;
1509 if (!found)
1511 file.m_Items.push_back(ignorePattern);
1512 ignorePattern += file.m_eol.IsEmpty() ? L"\n" : file.m_eol;
1513 CStringA ignorePatternA = CUnicodeUtils::GetUTF8(ignorePattern);
1514 file.Write(ignorePatternA, ignorePatternA.GetLength());
1517 if (ignoreDlg.m_IgnoreFile == 1)
1518 file.Close();
1521 if (ignoreDlg.m_IgnoreFile != 1)
1522 file.Close();
1524 catch(...)
1526 file.Abort();
1527 return false;
1530 return true;
1532 return false;
1535 static bool Reset(const CString& resetTo, int resetType)
1537 CString cmd;
1538 CString type;
1539 switch (resetType)
1541 case 0:
1542 type = L"--soft";
1543 break;
1544 case 1:
1545 type = L"--mixed";
1546 break;
1547 case 2:
1548 type = L"--hard";
1549 break;
1550 case 3:
1552 CProgressDlg progress;
1553 progress.m_GitCmd = L"git.exe reset --merge";
1554 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1556 if (status)
1558 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [] { CAppUtils::MergeAbort(); });
1559 return;
1562 CTGitPath gitPath = g_Git.m_CurrentDir;
1563 if (gitPath.HasSubmodules() && resetType == 2)
1565 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1567 CString sCmd;
1568 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
1569 CAppUtils::RunTortoiseGitProc(sCmd);
1573 return progress.DoModal() == IDOK;
1575 default:
1576 ATLASSERT(false);
1577 resetType = 1;
1578 type = L"--mixed";
1579 break;
1581 cmd.Format(L"git.exe reset %s %s --", (LPCTSTR)type, (LPCTSTR)resetTo);
1583 CProgressDlg progress;
1584 progress.m_GitCmd = cmd;
1586 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1588 if (status)
1590 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ Reset(resetTo, resetType); });
1591 return;
1594 CTGitPath gitPath = g_Git.m_CurrentDir;
1595 if (gitPath.HasSubmodules() && resetType == 2)
1597 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1599 CString sCmd;
1600 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
1601 CAppUtils::RunTortoiseGitProc(sCmd);
1606 INT_PTR ret;
1607 if (g_Git.UsingLibGit2(CGit::GIT_CMD_RESET))
1609 CGitProgressDlg gitdlg;
1610 ResetProgressCommand resetProgressCommand;
1611 gitdlg.SetCommand(&resetProgressCommand);
1612 resetProgressCommand.m_PostCmdCallback = progress.m_PostCmdCallback;
1613 resetProgressCommand.SetRevision(resetTo);
1614 resetProgressCommand.SetResetType(resetType);
1615 ret = gitdlg.DoModal();
1617 else
1618 ret = progress.DoModal();
1620 return ret == IDOK;
1623 bool CAppUtils::GitReset(const CString* CommitHash, int type)
1625 CResetDlg dlg;
1626 dlg.m_ResetType=type;
1627 dlg.m_ResetToVersion=*CommitHash;
1628 dlg.m_initialRefName = *CommitHash;
1629 if (dlg.DoModal() == IDOK)
1630 return Reset(dlg.m_ResetToVersion, dlg.m_ResetType);
1632 return false;
1635 void CAppUtils::DescribeConflictFile(bool mode, bool base,CString &descript)
1637 if(mode == FALSE)
1639 descript.LoadString(IDS_SVNACTION_DELETE);
1640 return;
1642 if(base)
1644 descript.LoadString(IDS_SVNACTION_MODIFIED);
1645 return;
1647 descript.LoadString(IDS_PROC_CREATED);
1650 void CAppUtils::RemoveTempMergeFile(const CTGitPath& path)
1652 ::DeleteFile(CAppUtils::GetMergeTempFile(L"LOCAL", path));
1653 ::DeleteFile(CAppUtils::GetMergeTempFile(L"REMOTE", path));
1654 ::DeleteFile(CAppUtils::GetMergeTempFile(L"BASE", path));
1656 CString CAppUtils::GetMergeTempFile(const CString& type, const CTGitPath &merge)
1658 return g_Git.CombinePath(merge.GetWinPathString() + L'.' + type + merge.GetFileExtension());;
1661 static bool ParseHashesFromLsFile(const BYTE_VECTOR& out, CString& hash1, bool& isFile1, CString& hash2, bool& isFile2, CString& hash3, bool& isFile3)
1663 size_t pos = 0;
1664 CString one;
1665 CString part;
1667 while (pos < out.size())
1669 one.Empty();
1671 CGit::StringAppend(&one, &out[pos], CP_UTF8);
1672 int tabstart = 0;
1673 one.Tokenize(L"\t", tabstart);
1675 tabstart = 0;
1676 part = one.Tokenize(L" ", tabstart); //Tag
1677 CString mode = one.Tokenize(L" ", tabstart); //Mode
1678 part = one.Tokenize(L" ", tabstart); //Hash
1679 CString hash = part;
1680 part = one.Tokenize(L"\t", tabstart); //Stage
1681 int stage = _wtol(part);
1682 if (stage == 1)
1684 hash1 = hash;
1685 isFile1 = _wtol(mode) != 160000;
1687 else if (stage == 2)
1689 hash2 = hash;
1690 isFile2 = _wtol(mode) != 160000;
1692 else if (stage == 3)
1694 hash3 = hash;
1695 isFile3 = _wtol(mode) != 160000;
1696 return true;
1699 pos = out.findNextString(pos);
1702 return false;
1705 void CAppUtils::GetConflictTitles(CString* baseText, CString& mineText, CString& theirsText, bool rebaseActive)
1707 if (baseText)
1708 baseText->LoadString(IDS_PROC_DIFF_BASE);
1709 if (rebaseActive)
1711 CString adminDir;
1712 GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir);
1713 mineText = L"Branch being rebased onto";
1714 if (!CStringUtils::ReadStringFromTextFile(adminDir + L"tgitrebase.active\\onto", mineText))
1716 CGitHash hash;
1717 if (!g_Git.GetHash(hash, L"rebase-apply/onto"))
1718 g_Git.GuessRefForHash(mineText, hash);
1720 theirsText = L"Branch being rebased";
1721 if (!CStringUtils::ReadStringFromTextFile(adminDir + L"tgitrebase.active\\head-name", theirsText))
1723 if (CStringUtils::ReadStringFromTextFile(adminDir + L"rebase-apply/head-name", theirsText))
1724 theirsText = CGit::StripRefName(theirsText);
1726 return;
1729 static const struct {
1730 const wchar_t* headref;
1731 bool guessRef;
1732 UINT theirstext;
1733 } infotexts[] = { { L"MERGE_HEAD", true, IDS_CONFLICT_INFOTEXT }, { L"CHERRY_PICK_HEAD", false, IDS_CONFLICT_INFOTEXT }, { L"REVERT_HEAD", false, IDS_CONFLICT_REVERT } };
1734 mineText = L"HEAD";
1735 theirsText.LoadString(IDS_CONFLICT_REFTOBEMERGED);
1736 for (const auto& infotext : infotexts)
1738 CGitHash hash;
1739 if (!g_Git.GetHash(hash, infotext.headref))
1741 CString guessedRef;
1742 if (!infotext.guessRef)
1743 guessedRef = hash.ToString();
1744 else
1745 g_Git.GuessRefForHash(guessedRef, hash);
1746 theirsText.FormatMessage(infotext.theirstext, infotext.headref, (LPCTSTR)guessedRef);
1747 break;
1752 bool CAppUtils::ConflictEdit(CTGitPath& path, bool bAlternativeTool /*= false*/, bool isRebase /*= false*/, HWND resolveMsgHwnd /*= nullptr*/)
1754 CTGitPath merge=path;
1755 CTGitPath directory = merge.GetDirectory();
1757 BYTE_VECTOR vector;
1759 CString cmd;
1760 cmd.Format(L"git.exe ls-files -u -t -z -- \"%s\"", (LPCTSTR)merge.GetGitPathString());
1762 if (g_Git.Run(cmd, &vector))
1763 return FALSE;
1765 CString baseTitle, mineTitle, theirsTitle;
1766 GetConflictTitles(&baseTitle, mineTitle, theirsTitle, isRebase);
1768 CString baseHash, realBaseHash(GIT_REV_ZERO), localHash(GIT_REV_ZERO), remoteHash(GIT_REV_ZERO);
1769 bool baseIsFile = true, localIsFile = true, remoteIsFile = true;
1770 if (ParseHashesFromLsFile(vector, realBaseHash, baseIsFile, localHash, localIsFile, remoteHash, remoteIsFile))
1771 baseHash = realBaseHash;
1773 if (!baseIsFile || !localIsFile || !remoteIsFile)
1775 if (merge.HasAdminDir())
1777 CGit subgit;
1778 subgit.m_CurrentDir = g_Git.CombinePath(merge);
1779 CGitHash hash;
1780 subgit.GetHash(hash, L"HEAD");
1781 baseHash = hash;
1784 CGitDiff::ChangeType changeTypeMine = CGitDiff::Unknown;
1785 CGitDiff::ChangeType changeTypeTheirs = CGitDiff::Unknown;
1787 bool baseOK = false, mineOK = false, theirsOK = false;
1788 CString baseSubject, mineSubject, theirsSubject;
1789 if (merge.HasAdminDir())
1791 CGit subgit;
1792 subgit.m_CurrentDir = g_Git.CombinePath(merge);
1793 CGitDiff::GetSubmoduleChangeType(subgit, baseHash, localHash, baseOK, mineOK, changeTypeMine, baseSubject, mineSubject);
1794 CGitDiff::GetSubmoduleChangeType(subgit, baseHash, remoteHash, baseOK, theirsOK, changeTypeTheirs, baseSubject, theirsSubject);
1796 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)
1798 changeTypeMine = CGitDiff::Identical;
1799 changeTypeTheirs = CGitDiff::NewSubmodule;
1800 baseSubject.LoadString(IDS_CONFLICT_NOSUBMODULE);
1801 mineSubject = baseSubject;
1802 theirsSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1804 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
1806 baseHash = localHash;
1807 baseSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1808 mineSubject = baseSubject;
1809 theirsSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1810 changeTypeMine = CGitDiff::Identical;
1811 changeTypeTheirs = CGitDiff::DeleteSubmodule;
1813 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
1815 baseSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1816 mineSubject = baseSubject;
1817 theirsSubject = baseSubject;
1818 if (baseHash == localHash)
1819 changeTypeMine = CGitDiff::Identical;
1821 else if (baseHash == GIT_REV_ZERO && localHash != GIT_REV_ZERO && remoteHash != GIT_REV_ZERO)
1823 baseOK = true;
1824 mineSubject = baseSubject;
1825 if (remoteIsFile)
1827 theirsSubject.LoadString(IDS_CONFLICT_NOTASUBMODULE);
1828 changeTypeMine = CGitDiff::NewSubmodule;
1830 else
1831 theirsSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1832 if (localIsFile)
1834 mineSubject.LoadString(IDS_CONFLICT_NOTASUBMODULE);
1835 changeTypeTheirs = CGitDiff::NewSubmodule;
1837 else
1838 mineSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1840 else if (baseHash != GIT_REV_ZERO && (localHash == GIT_REV_ZERO || remoteHash == GIT_REV_ZERO))
1842 baseSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1843 if (localHash == GIT_REV_ZERO)
1845 mineSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1846 changeTypeMine = CGitDiff::DeleteSubmodule;
1848 else
1850 mineSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1851 if (localHash == baseHash)
1852 changeTypeMine = CGitDiff::Identical;
1854 if (remoteHash == GIT_REV_ZERO)
1856 theirsSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1857 changeTypeTheirs = CGitDiff::DeleteSubmodule;
1859 else
1861 theirsSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1862 if (remoteHash == baseHash)
1863 changeTypeTheirs = CGitDiff::Identical;
1866 else
1867 return FALSE;
1869 CSubmoduleResolveConflictDlg resolveSubmoduleConflictDialog;
1870 resolveSubmoduleConflictDialog.SetDiff(merge.GetGitPathString(), isRebase, baseTitle, mineTitle, theirsTitle, baseHash, baseSubject, baseOK, localHash, mineSubject, mineOK, changeTypeMine, remoteHash, theirsSubject, theirsOK, changeTypeTheirs);
1871 resolveSubmoduleConflictDialog.DoModal();
1872 if (resolveSubmoduleConflictDialog.m_bResolved && resolveMsgHwnd)
1874 static UINT WM_REVERTMSG = RegisterWindowMessage(L"GITSLNM_NEEDSREFRESH");
1875 ::PostMessage(resolveMsgHwnd, WM_REVERTMSG, NULL, NULL);
1878 return TRUE;
1881 CTGitPathList list;
1882 if (list.ParserFromLsFile(vector))
1884 MessageBox(nullptr, L"Parse ls-files failed!", L"TortoiseGit", MB_OK | MB_ICONERROR);
1885 return FALSE;
1888 if (list.IsEmpty())
1889 return FALSE;
1891 CTGitPath theirs;
1892 CTGitPath mine;
1893 CTGitPath base;
1895 if (isRebase)
1897 mine.SetFromGit(GetMergeTempFile(L"REMOTE", merge));
1898 theirs.SetFromGit(GetMergeTempFile(L"LOCAL", merge));
1900 else
1902 mine.SetFromGit(GetMergeTempFile(L"LOCAL", merge));
1903 theirs.SetFromGit(GetMergeTempFile(L"REMOTE", merge));
1905 base.SetFromGit(GetMergeTempFile(L"BASE",merge));
1907 CString format = L"git.exe checkout-index --temp --stage=%d -- \"%s\"";
1908 CFile tempfile;
1909 //create a empty file, incase stage is not three
1910 tempfile.Open(mine.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1911 tempfile.Close();
1912 tempfile.Open(theirs.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1913 tempfile.Close();
1914 tempfile.Open(base.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1915 tempfile.Close();
1917 bool b_base=false, b_local=false, b_remote=false;
1919 for (int i = 0; i< list.GetCount(); ++i)
1921 CString outfile;
1922 cmd.Empty();
1923 outfile.Empty();
1925 if( list[i].m_Stage == 1)
1927 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1928 b_base = true;
1929 outfile = base.GetWinPathString();
1932 if( list[i].m_Stage == 2 )
1934 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1935 b_local = true;
1936 outfile = mine.GetWinPathString();
1939 if( list[i].m_Stage == 3 )
1941 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1942 b_remote = true;
1943 outfile = theirs.GetWinPathString();
1945 CString output, err;
1946 if(!outfile.IsEmpty())
1947 if (!g_Git.Run(cmd, &output, &err, CP_UTF8))
1949 CString file;
1950 int start =0 ;
1951 file = output.Tokenize(L"\t", start);
1952 ::MoveFileEx(file,outfile,MOVEFILE_REPLACE_EXISTING|MOVEFILE_COPY_ALLOWED);
1954 else
1955 CMessageBox::Show(nullptr, output + L'\n' + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
1958 if(b_local && b_remote )
1960 merge.SetFromWin(g_Git.CombinePath(merge));
1961 if (isRebase)
1962 return !!CAppUtils::StartExtMerge(bAlternativeTool, base, mine, theirs, merge, baseTitle, mineTitle, theirsTitle, CString(), false, resolveMsgHwnd, true);
1964 return !!CAppUtils::StartExtMerge(bAlternativeTool, base, theirs, mine, merge, baseTitle, theirsTitle, mineTitle, CString(), false, resolveMsgHwnd, true);
1966 else
1968 ::DeleteFile(mine.GetWinPathString());
1969 ::DeleteFile(theirs.GetWinPathString());
1970 if (!b_base)
1971 ::DeleteFile(base.GetWinPathString());
1973 SCOPE_EXIT{
1974 if (b_base)
1975 ::DeleteFile(base.GetWinPathString());
1978 CDeleteConflictDlg dlg;
1979 if (!isRebase)
1981 DescribeConflictFile(b_local, b_base, dlg.m_LocalStatus);
1982 DescribeConflictFile(b_remote, b_base, dlg.m_RemoteStatus);
1983 dlg.m_LocalHash = mineTitle;
1984 dlg.m_RemoteHash = theirsTitle;
1985 dlg.m_bDiffMine = b_local;
1987 else
1989 DescribeConflictFile(b_local, b_base, dlg.m_RemoteStatus);
1990 DescribeConflictFile(b_remote, b_base, dlg.m_LocalStatus);
1991 dlg.m_LocalHash = theirsTitle;
1992 dlg.m_RemoteHash = mineTitle;
1993 dlg.m_bDiffMine = !b_local;
1995 dlg.m_bShowModifiedButton = b_base;
1996 dlg.m_File = merge;
1997 dlg.m_FileBaseVersion = base;
1998 if(dlg.DoModal() == IDOK)
2000 CString out;
2001 if(dlg.m_bIsDelete)
2002 cmd.Format(L"git.exe rm -- \"%s\"", (LPCTSTR)merge.GetGitPathString());
2003 else
2004 cmd.Format(L"git.exe add -- \"%s\"", (LPCTSTR)merge.GetGitPathString());
2006 if (g_Git.Run(cmd, &out, CP_UTF8))
2008 MessageBox(nullptr, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
2009 return FALSE;
2011 if (!dlg.m_bIsDelete)
2013 path.m_Action |= CTGitPath::LOGACTIONS_ADDED;
2014 path.m_Action &= ~CTGitPath::LOGACTIONS_UNMERGED;
2016 return TRUE;
2018 return FALSE;
2022 bool CAppUtils::IsSSHPutty()
2024 CString sshclient = g_Git.m_Environment.GetEnv(L"GIT_SSH");
2025 sshclient=sshclient.MakeLower();
2026 return sshclient.Find(L"plink", 0) >= 0;
2029 CString CAppUtils::GetClipboardLink(const CString &skipGitPrefix, int paramsCount)
2031 if (!OpenClipboard(nullptr))
2032 return CString();
2034 CString sClipboardText;
2035 HGLOBAL hglb = GetClipboardData(CF_TEXT);
2036 if (hglb)
2038 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
2039 sClipboardText = CString(lpstr);
2040 GlobalUnlock(hglb);
2042 hglb = GetClipboardData(CF_UNICODETEXT);
2043 if (hglb)
2045 LPCTSTR lpstr = (LPCTSTR)GlobalLock(hglb);
2046 sClipboardText = lpstr;
2047 GlobalUnlock(hglb);
2049 CloseClipboard();
2051 if(!sClipboardText.IsEmpty())
2053 if (sClipboardText[0] == L'"' && sClipboardText[sClipboardText.GetLength() - 1] == L'"')
2054 sClipboardText=sClipboardText.Mid(1,sClipboardText.GetLength()-2);
2056 for (const CString& prefix : { L"http://", L"https://", L"git://", L"ssh://", L"git@" })
2058 if (CStringUtils::StartsWith(sClipboardText, prefix) && sClipboardText.GetLength() != prefix.GetLength())
2059 return sClipboardText;
2062 if(sClipboardText.GetLength()>=2)
2063 if (sClipboardText[1] == L':')
2064 if( (sClipboardText[0] >= 'A' && sClipboardText[0] <= 'Z')
2065 || (sClipboardText[0] >= 'a' && sClipboardText[0] <= 'z') )
2066 return sClipboardText;
2068 // trim prefixes like "git clone "
2069 if (!skipGitPrefix.IsEmpty() && CStringUtils::StartsWith(sClipboardText, skipGitPrefix))
2071 sClipboardText = sClipboardText.Mid(skipGitPrefix.GetLength()).Trim();
2072 int spacePos = -1;
2073 while (paramsCount >= 0)
2075 --paramsCount;
2076 spacePos = sClipboardText.Find(L' ', spacePos + 1);
2077 if (spacePos == -1)
2078 break;
2080 if (spacePos > 0 && paramsCount < 0)
2081 sClipboardText.Truncate(spacePos);
2082 return sClipboardText;
2086 return CString();
2089 CString CAppUtils::ChooseRepository(const CString* path)
2091 CBrowseFolder browseFolder;
2092 CRegString regLastResopitory = CRegString(L"Software\\TortoiseGit\\TortoiseProc\\LastRepo", L"");
2094 browseFolder.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
2095 CString strCloneDirectory;
2096 if(path)
2097 strCloneDirectory=*path;
2098 else
2099 strCloneDirectory = regLastResopitory;
2101 CString title;
2102 title.LoadString(IDS_CHOOSE_REPOSITORY);
2104 browseFolder.SetInfo(title);
2106 if (browseFolder.Show(nullptr, strCloneDirectory) == CBrowseFolder::OK)
2108 regLastResopitory = strCloneDirectory;
2109 return strCloneDirectory;
2111 else
2112 return CString();
2115 bool CAppUtils::SendPatchMail(CTGitPathList& list, bool bIsMainWnd)
2117 CSendMailDlg dlg;
2119 dlg.m_PathList = list;
2121 if(dlg.DoModal()==IDOK)
2123 if (dlg.m_PathList.IsEmpty())
2124 return FALSE;
2126 CGitProgressDlg progDlg;
2127 if (bIsMainWnd)
2128 theApp.m_pMainWnd = &progDlg;
2129 SendMailProgressCommand sendMailProgressCommand;
2130 progDlg.SetCommand(&sendMailProgressCommand);
2132 sendMailProgressCommand.SetPathList(dlg.m_PathList);
2133 progDlg.SetItemCount(dlg.m_PathList.GetCount());
2135 CSendMailPatch sendMailPatch(dlg.m_To, dlg.m_CC, dlg.m_Subject, !!dlg.m_bAttachment, !!dlg.m_bCombine);
2136 sendMailProgressCommand.SetSendMailOption(&sendMailPatch);
2138 progDlg.DoModal();
2140 return true;
2142 return false;
2145 bool CAppUtils::SendPatchMail(const CString& cmd, const CString& formatpatchoutput, bool bIsMainWnd)
2147 CTGitPathList list;
2148 CString log=formatpatchoutput;
2149 int start=log.Find(cmd);
2150 if(start >=0)
2151 log.Tokenize(L"\n", start);
2152 else
2153 start = 0;
2155 while(start>=0)
2157 CString one = log.Tokenize(L"\n", start);
2158 one=one.Trim();
2159 if (one.IsEmpty() || CStringUtils::StartsWith(one, CString(MAKEINTRESOURCE(IDS_SUCCESS))))
2160 continue;
2161 one.Replace(L'/', L'\\');
2162 CTGitPath path;
2163 path.SetFromWin(one);
2164 list.AddPath(path);
2166 if (!list.IsEmpty())
2167 return SendPatchMail(list, bIsMainWnd);
2168 else
2170 CMessageBox::Show(nullptr, IDS_ERR_NOPATCHES, IDS_APPNAME, MB_ICONINFORMATION);
2171 return true;
2176 int CAppUtils::GetLogOutputEncode(CGit *pGit)
2178 CString output;
2179 output = pGit->GetConfigValue(L"i18n.logOutputEncoding");
2180 if(output.IsEmpty())
2181 return CUnicodeUtils::GetCPCode(pGit->GetConfigValue(L"i18n.commitencoding"));
2182 else
2183 return CUnicodeUtils::GetCPCode(output);
2185 int CAppUtils::SaveCommitUnicodeFile(const CString& filename, CString &message)
2189 CFile file(filename, CFile::modeReadWrite | CFile::modeCreate);
2190 int cp = CUnicodeUtils::GetCPCode(g_Git.GetConfigValue(L"i18n.commitencoding"));
2192 bool stripComments = (CRegDWORD(L"Software\\TortoiseGit\\StripCommentedLines", FALSE) == TRUE);
2193 TCHAR commentChar = L'#';
2194 if (stripComments)
2196 CString commentCharValue = g_Git.GetConfigValue(L"core.commentchar");
2197 if (!commentCharValue.IsEmpty())
2198 commentChar = commentCharValue[0];
2201 bool sanitize = (CRegDWORD(L"Software\\TortoiseGit\\SanitizeCommitMsg", TRUE) == TRUE);
2202 if (sanitize)
2203 message.Trim(L" \r\n");
2205 int len = message.GetLength();
2206 int start = 0;
2207 int emptyLineCnt = 0;
2208 while (start >= 0 && start < len)
2210 int oldStart = start;
2211 start = message.Find(L'\n', oldStart);
2212 CString line = message.Mid(oldStart);
2213 if (start != -1)
2215 line.Truncate(start - oldStart);
2216 ++start; // move forward so we don't find the same char again
2218 if (stripComments && (!line.IsEmpty() && line.GetAt(0) == commentChar) || (start < 0 && line.IsEmpty()))
2219 continue;
2220 line.TrimRight(L" \r");
2221 if (sanitize)
2223 if (line.IsEmpty())
2225 ++emptyLineCnt;
2226 continue;
2228 if (emptyLineCnt) // squash multiple newlines
2229 file.Write("\n", 1);
2230 emptyLineCnt = 0;
2232 CStringA lineA = CUnicodeUtils::GetMulti(line + L'\n', cp);
2233 file.Write((LPCSTR)lineA, lineA.GetLength());
2235 file.Close();
2236 return 0;
2238 catch (CFileException *e)
2240 e->Delete();
2241 return -1;
2245 bool DoPull(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)
2247 if (bAutoLoad)
2248 CAppUtils::LaunchPAgent(nullptr, &url);
2250 CGitHash hashOld;
2251 if (g_Git.GetHash(hashOld, L"HEAD"))
2253 MessageBox(nullptr, g_Git.GetGitLastErr(L"Could not get HEAD hash."), L"TortoiseGit", MB_ICONERROR);
2254 return false;
2257 CString args;
2258 if (CRegDWORD(L"Software\\TortoiseGit\\PullRebaseBehaviorLike1816", FALSE) == FALSE)
2259 args += L" --no-rebase";
2261 if (bFetchTags == BST_UNCHECKED)
2262 args += L" --no-tags";
2263 else if (bFetchTags == BST_CHECKED)
2264 args += L" --tags";
2266 if (bNoFF)
2267 args += L" --no-ff";
2269 if (bFFonly)
2270 args += L" --ff-only";
2272 if (bSquash)
2273 args += L" --squash";
2275 if (bNoCommit)
2276 args += L" --no-commit";
2278 if (nDepth)
2279 args.AppendFormat(L" --depth %d", *nDepth);
2281 if (bPrune == BST_CHECKED)
2282 args += L" --prune";
2283 else if (bPrune == BST_UNCHECKED)
2284 args += L" --no-prune";
2286 if (bUnrelated)
2287 args += L" --allow-unrelated-histories";
2289 CString cmd;
2290 cmd.Format(L"git.exe pull --progress -v%s \"%s\" %s", (LPCTSTR)args, (LPCTSTR)url, (LPCTSTR)remoteBranchName);
2291 CProgressDlg progress;
2292 progress.m_GitCmd = cmd;
2294 CGitHash hashNew; // declare outside lambda, because it is captured by reference
2295 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2297 if (status)
2299 if (CAppUtils::GetMsysgitVersion() >= 0x02090000)
2301 STRING_VECTOR remotes;
2302 g_Git.GetRemoteList(remotes);
2303 if (std::find(remotes.begin(), remotes.end(), url) != remotes.end())
2305 CString currentBranch;
2306 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, currentBranch))
2307 currentBranch.Empty();
2308 CString remoteRef = L"remotes/" + url + L"/" + remoteBranchName;
2309 if (!currentBranch.IsEmpty() && remoteBranchName.IsEmpty())
2311 CString pullRemote, pullBranch;
2312 g_Git.GetRemoteTrackedBranch(currentBranch, pullRemote, pullBranch);
2313 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
2314 remoteRef = L"remotes/" + pullRemote + L"/" + pullBranch;
2316 CGitHash common;
2317 g_Git.IsFastForward(L"HEAD", remoteRef, &common);
2318 if (common.IsEmpty())
2319 postCmdList.emplace_back(IDI_MERGE, IDS_MERGE_UNRELATED, [=] { DoPull(url, bAutoLoad, bFetchTags, bNoFF, bFFonly, bSquash, bNoCommit, nDepth, bPrune, remoteBranchName, showPush, showStashPop, true); });
2323 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&]{ CAppUtils::Pull(); });
2324 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSTASHSAVE, [&]{ CAppUtils::StashSave(L"", true); });
2325 return;
2328 if (showStashPop)
2329 postCmdList.emplace_back(IDI_RELOCATE, IDS_MENUSTASHPOP, []{ CAppUtils::StashPop(); });
2331 if (g_Git.GetHash(hashNew, L"HEAD"))
2332 MessageBox(nullptr, g_Git.GetGitLastErr(L"Could not get HEAD hash after pulling."), L"TortoiseGit", MB_ICONERROR);
2333 else
2335 postCmdList.emplace_back(IDI_DIFF, IDS_PROC_PULL_DIFFS, [&]
2337 CString sCmd;
2338 sCmd.Format(L"/command:showcompare /path:\"%s\" /revision1:%s /revision2:%s", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)hashOld.ToString(), (LPCTSTR)hashNew.ToString());
2339 CAppUtils::RunTortoiseGitProc(sCmd);
2341 postCmdList.emplace_back(IDI_LOG, IDS_PROC_PULL_LOG, [&]
2343 CString sCmd;
2344 sCmd.Format(L"/command:log /path:\"%s\" /range:%s", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)(hashOld.ToString() + L".." + hashNew.ToString()));
2345 CAppUtils::RunTortoiseGitProc(sCmd);
2349 if (showPush)
2350 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, []{ CAppUtils::Push(); });
2352 CTGitPath gitPath = g_Git.m_CurrentDir;
2353 if (gitPath.HasSubmodules())
2355 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
2357 CString sCmd;
2358 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
2359 CAppUtils::RunTortoiseGitProc(sCmd);
2364 INT_PTR ret = progress.DoModal();
2366 if (ret == IDOK && progress.m_GitStatus == 1 && progress.m_LogText.Find(L"CONFLICT") >= 0 && CMessageBox::Show(nullptr, IDS_SEECHANGES, IDS_APPNAME, MB_YESNO | MB_ICONINFORMATION) == IDYES)
2368 cmd.Format(L"/command:repostatus /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
2369 CAppUtils::RunTortoiseGitProc(cmd);
2371 return true;
2374 return ret == IDOK;
2377 bool CAppUtils::Pull(bool showPush, bool showStashPop)
2379 if (IsTGitRebaseActive())
2380 return false;
2382 CPullFetchDlg dlg;
2383 dlg.m_IsPull = TRUE;
2384 if (dlg.DoModal() == IDOK)
2386 // "git.exe pull --rebase" is not supported, never and ever. So, adapting it to Fetch & Rebase.
2387 if (dlg.m_bRebase)
2388 return DoFetch(dlg.m_RemoteURL,
2389 FALSE, // Fetch all remotes
2390 dlg.m_bAutoLoad == BST_CHECKED,
2391 dlg.m_bPrune,
2392 dlg.m_bDepth == BST_CHECKED,
2393 dlg.m_nDepth,
2394 dlg.m_bFetchTags,
2395 dlg.m_RemoteBranchName,
2396 dlg.m_bRebaseActivatedInConfigForPull ? 2 : 1, // Rebase after fetching
2397 dlg.m_bRebasePreserveMerges == TRUE); // Preserve merges on rebase
2399 return DoPull(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);
2402 return false;
2405 bool CAppUtils::RebaseAfterFetch(const CString& upstream, int rebase, bool preserveMerges)
2407 while (true)
2409 CRebaseDlg dlg;
2410 if (!upstream.IsEmpty())
2411 dlg.m_Upstream = upstream;
2412 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENULOG)));
2413 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUPUSH)));
2414 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUDESSENDMAIL)));
2415 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUREBASE)));
2416 dlg.m_bRebaseAutoStart = (rebase == 2);
2417 dlg.m_bPreserveMerges = preserveMerges;
2418 INT_PTR response = dlg.DoModal();
2419 if (response == IDOK)
2420 return true;
2421 else if (response == IDC_REBASE_POST_BUTTON)
2423 CString cmd = L"/command:log";
2424 cmd += L" /path:\"" + g_Git.m_CurrentDir + L'"';
2425 CAppUtils::RunTortoiseGitProc(cmd);
2426 return true;
2428 else if (response == IDC_REBASE_POST_BUTTON + 1)
2429 return Push();
2430 else if (response == IDC_REBASE_POST_BUTTON + 2)
2432 CString cmd, out, err;
2433 cmd.Format(L"git.exe format-patch -o \"%s\" %s..%s",
2434 (LPCTSTR)g_Git.m_CurrentDir,
2435 (LPCTSTR)g_Git.FixBranchName(dlg.m_Upstream),
2436 (LPCTSTR)g_Git.FixBranchName(dlg.m_Branch));
2437 if (g_Git.Run(cmd, &out, &err, CP_UTF8))
2439 CMessageBox::Show(nullptr, out + L'\n' + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
2440 return false;
2442 CAppUtils::SendPatchMail(cmd, out);
2443 return true;
2445 else if (response == IDC_REBASE_POST_BUTTON + 3)
2446 continue;
2447 else if (response == IDCANCEL)
2448 return false;
2449 return false;
2453 static bool DoFetch(const CString& url, const bool fetchAllRemotes, const bool loadPuttyAgent, const int prune, const bool bDepth, const int nDepth, const int fetchTags, const CString& remoteBranch, int runRebase, const bool rebasePreserveMerges)
2455 if (loadPuttyAgent)
2457 if (fetchAllRemotes)
2459 STRING_VECTOR list;
2460 g_Git.GetRemoteList(list);
2462 for (const auto& remote : list)
2463 CAppUtils::LaunchPAgent(nullptr, &remote);
2465 else
2466 CAppUtils::LaunchPAgent(nullptr, &url);
2469 CString upstream = L"FETCH_HEAD";
2470 CGitHash oldUpstreamHash;
2471 if (runRebase)
2473 STRING_VECTOR list;
2474 g_Git.GetRemoteList(list);
2475 for (auto it = list.cbegin(); it != list.cend(); ++it)
2477 if (url == *it)
2479 upstream.Empty();
2480 if (remoteBranch.IsEmpty()) // pulldlg might clear remote branch if its the default tracked branch
2482 CString currentBranch;
2483 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, currentBranch))
2484 currentBranch.Empty();
2485 if (!currentBranch.IsEmpty() && remoteBranch.IsEmpty())
2487 CString pullRemote, pullBranch;
2488 g_Git.GetRemoteTrackedBranch(currentBranch, pullRemote, pullBranch);
2489 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty() && pullRemote == url) // pullRemote == url is just another safety-check and should not be needed
2490 upstream = L"remotes/" + *it + L'/' + pullBranch;
2493 else
2494 upstream = L"remotes/" + *it + L'/' + remoteBranch;
2496 g_Git.GetHash(oldUpstreamHash, upstream);
2497 break;
2502 CString cmd, arg;
2503 arg = L" --progress";
2505 if (bDepth)
2506 arg.AppendFormat(L" --depth %d", nDepth);
2508 if (prune == TRUE)
2509 arg += L" --prune";
2510 else if (prune == FALSE)
2511 arg += L" --no-prune";
2513 if (fetchTags == 1)
2514 arg += L" --tags";
2515 else if (fetchTags == 0)
2516 arg += L" --no-tags";
2518 if (fetchAllRemotes)
2519 cmd.Format(L"git.exe fetch --all -v%s", (LPCTSTR)arg);
2520 else
2521 cmd.Format(L"git.exe fetch -v%s \"%s\" %s", (LPCTSTR)arg, (LPCTSTR)url, (LPCTSTR)remoteBranch);
2523 CProgressDlg progress;
2524 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2526 if (status)
2528 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ DoFetch(url, fetchAllRemotes, loadPuttyAgent, prune, bDepth, nDepth, fetchTags, remoteBranch, runRebase, rebasePreserveMerges); });
2529 if (fetchAllRemotes)
2530 postCmdList.emplace_back(IDI_LOG, IDS_MENULOG, []
2532 CString cmd = L"/command:log";
2533 cmd += L" /path:\"" + g_Git.m_CurrentDir + L'"';
2534 CAppUtils::RunTortoiseGitProc(cmd);
2536 return;
2539 postCmdList.emplace_back(IDI_LOG, IDS_MENULOG, []
2541 CString cmd = L"/command:log";
2542 cmd += L" /path:\"" + g_Git.m_CurrentDir + L'"';
2543 CAppUtils::RunTortoiseGitProc(cmd);
2546 postCmdList.emplace_back(IDI_REVERT, IDS_PROC_RESET, []
2548 CString pullRemote, pullBranch;
2549 g_Git.GetRemoteTrackedBranchForHEAD(pullRemote, pullBranch);
2550 CString defaultUpstream;
2551 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
2552 defaultUpstream.Format(L"remotes/%s/%s", (LPCTSTR)pullRemote, (LPCTSTR)pullBranch);
2553 CAppUtils::GitReset(&defaultUpstream, 2);
2556 postCmdList.emplace_back(IDI_PULL, IDS_MENUFETCH, []{ CAppUtils::Fetch(); });
2558 if (!runRebase && !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir))
2559 postCmdList.emplace_back(IDI_REBASE, IDS_MENUREBASE, [&]{ runRebase = false; CAppUtils::RebaseAfterFetch(); });
2562 progress.m_GitCmd = cmd;
2564 if (g_Git.UsingLibGit2(CGit::GIT_CMD_FETCH))
2566 CGitProgressDlg gitdlg;
2567 FetchProgressCommand fetchProgressCommand;
2568 if (!fetchAllRemotes)
2569 fetchProgressCommand.SetUrl(url);
2570 gitdlg.SetCommand(&fetchProgressCommand);
2571 fetchProgressCommand.m_PostCmdCallback = progress.m_PostCmdCallback;
2572 fetchProgressCommand.SetAutoTag(fetchTags == 1 ? GIT_REMOTE_DOWNLOAD_TAGS_ALL : fetchTags == 2 ? GIT_REMOTE_DOWNLOAD_TAGS_AUTO : GIT_REMOTE_DOWNLOAD_TAGS_NONE);
2573 fetchProgressCommand.SetPrune(prune == BST_CHECKED ? GIT_FETCH_PRUNE : prune == BST_INDETERMINATE ? GIT_FETCH_PRUNE_UNSPECIFIED : GIT_FETCH_NO_PRUNE);
2574 if (!fetchAllRemotes)
2575 fetchProgressCommand.SetRefSpec(remoteBranch);
2576 return gitdlg.DoModal() == IDOK;
2579 progress.m_PostExecCallback = [&](DWORD& exitCode, CString&)
2581 if (exitCode || !runRebase)
2582 return;
2584 CGitHash remoteBranchHash;
2585 g_Git.GetHash(remoteBranchHash, upstream);
2587 if (runRebase == 1)
2589 CGitHash headHash, commonAcestor;
2590 if (!g_Git.GetHash(headHash, L"HEAD") && (remoteBranchHash == headHash || (g_Git.IsFastForward(upstream, L"HEAD", &commonAcestor) && commonAcestor == remoteBranchHash)) && CMessageBox::ShowCheck(nullptr, IDS_REBASE_CURRENTBRANCHUPTODATE, IDS_APPNAME, MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2, L"OpenRebaseRemoteBranchEqualsHEAD", IDS_MSGBOX_DONOTSHOWAGAIN) == IDNO)
2591 return;
2593 if (remoteBranchHash == oldUpstreamHash && !oldUpstreamHash.IsEmpty() && CMessageBox::ShowCheck(nullptr, IDS_REBASE_BRANCH_UNCHANGED, IDS_APPNAME, MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2, L"OpenRebaseRemoteBranchUnchanged", IDS_MSGBOX_DONOTSHOWAGAIN) == IDNO)
2594 return;
2597 if (runRebase == 1 && g_Git.IsFastForward(L"HEAD", upstream))
2599 UINT ret = CMessageBox::ShowCheck(nullptr, IDS_REBASE_BRANCH_FF, IDS_APPNAME, 2, IDI_QUESTION, IDS_MERGEBUTTON, IDS_REBASEBUTTON, IDS_ABORTBUTTON, L"OpenRebaseRemoteBranchFastForwards", IDS_MSGBOX_DONOTSHOWAGAIN);
2600 if (ret == 3)
2601 return;
2602 if (ret == 1)
2604 CProgressDlg mergeProgress;
2605 mergeProgress.m_GitCmd = L"git.exe merge --ff-only " + upstream;
2606 mergeProgress.m_AutoClose = AUTOCLOSE_IF_NO_ERRORS;
2607 mergeProgress.m_PostCmdCallback = [](DWORD status, PostCmdList& postCmdList)
2609 if (status && g_Git.HasWorkingTreeConflicts())
2611 // there are conflict files
2612 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
2614 CString sCmd;
2615 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
2616 CAppUtils::RunTortoiseGitProc(sCmd);
2620 mergeProgress.DoModal();
2621 return;
2625 CAppUtils::RebaseAfterFetch(upstream, runRebase, rebasePreserveMerges);
2628 return progress.DoModal() == IDOK;
2631 bool CAppUtils::Fetch(const CString& remoteName, bool allRemotes)
2633 CPullFetchDlg dlg;
2634 dlg.m_PreSelectRemote = remoteName;
2635 dlg.m_IsPull=FALSE;
2636 dlg.m_bAllRemotes = allRemotes;
2638 if(dlg.DoModal()==IDOK)
2639 return DoFetch(dlg.m_RemoteURL, dlg.m_bAllRemotes == BST_CHECKED, dlg.m_bAutoLoad == BST_CHECKED, dlg.m_bPrune, dlg.m_bDepth == BST_CHECKED, dlg.m_nDepth, dlg.m_bFetchTags, dlg.m_RemoteBranchName, dlg.m_bRebase == BST_CHECKED ? 1 : 0, FALSE);
2641 return false;
2644 bool CAppUtils::DoPush(bool autoloadKey, bool pack, bool tags, bool allRemotes, bool allBranches, bool force, bool forceWithLease, const CString& localBranch, const CString& remote, const CString& remoteBranch, bool setUpstream, int recurseSubmodules)
2646 CString error;
2647 DWORD exitcode = 0xFFFFFFFF;
2648 if (CHooks::Instance().PrePush(g_Git.m_CurrentDir, exitcode, error))
2650 if (exitcode)
2652 CString temp;
2653 temp.Format(IDS_ERR_HOOKFAILED, (LPCTSTR)error);
2654 MessageBox(nullptr, temp, L"TortoiseGit", MB_OK | MB_ICONERROR);
2655 return false;
2659 int iRecurseSubmodules = 0;
2660 if (GetMsysgitVersion() >= 0x02070000)
2662 CString sRecurseSubmodules = g_Git.GetConfigValue(L"push.recurseSubmodules");
2663 if (sRecurseSubmodules == L"check")
2664 iRecurseSubmodules = 1;
2665 else if (sRecurseSubmodules == L"on-demand")
2666 iRecurseSubmodules = 2;
2669 CString arg;
2670 if (pack)
2671 arg += L"--thin ";
2672 if (tags && !allBranches)
2673 arg += L"--tags ";
2674 if (force)
2675 arg += L"--force ";
2676 if (forceWithLease)
2677 arg += L"--force-with-lease ";
2678 if (setUpstream)
2679 arg += L"--set-upstream ";
2680 if (recurseSubmodules == 0 && recurseSubmodules != iRecurseSubmodules)
2681 arg += L"--recurse-submodules=no ";
2682 if (recurseSubmodules == 1 && recurseSubmodules != iRecurseSubmodules)
2683 arg += L"--recurse-submodules=check ";
2684 if (recurseSubmodules == 2 && recurseSubmodules != iRecurseSubmodules)
2685 arg += L"--recurse-submodules=on-demand ";
2687 arg += L"--progress ";
2689 CProgressDlg progress;
2691 STRING_VECTOR remotesList;
2692 if (allRemotes)
2693 g_Git.GetRemoteList(remotesList);
2694 else
2695 remotesList.push_back(remote);
2697 for (unsigned int i = 0; i < remotesList.size(); ++i)
2699 if (autoloadKey)
2700 CAppUtils::LaunchPAgent(nullptr, &remotesList[i]);
2702 CString cmd;
2703 if (allBranches)
2705 cmd.Format(L"git.exe push --all %s\"%s\"",
2706 (LPCTSTR)arg,
2707 (LPCTSTR)remotesList[i]);
2709 if (tags)
2711 progress.m_GitCmdList.push_back(cmd);
2712 cmd.Format(L"git.exe push --tags %s\"%s\"", (LPCTSTR)arg, (LPCTSTR)remotesList[i]);
2715 else
2717 cmd.Format(L"git.exe push %s\"%s\" %s",
2718 (LPCTSTR)arg,
2719 (LPCTSTR)remotesList[i],
2720 (LPCTSTR)localBranch);
2721 if (!remoteBranch.IsEmpty())
2723 cmd += L":";
2724 cmd += remoteBranch;
2727 progress.m_GitCmdList.push_back(cmd);
2729 if (!allBranches && !!CRegDWORD(L"Software\\TortoiseGit\\ShowBranchRevisionNumber", FALSE))
2731 cmd.Format(L"git.exe rev-list --count --first-parent %s", (LPCTSTR)localBranch);
2732 progress.m_GitCmdList.push_back(cmd);
2736 CString superprojectRoot;
2737 GitAdminDir::HasAdminDir(g_Git.m_CurrentDir, false, &superprojectRoot);
2738 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2740 // need to execute hooks as those might be needed by post action commands
2741 DWORD exitcode = 0xFFFFFFFF;
2742 CString error;
2743 if (CHooks::Instance().PostPush(g_Git.m_CurrentDir, exitcode, error))
2745 if (exitcode)
2747 CString temp;
2748 temp.Format(IDS_ERR_HOOKFAILED, (LPCTSTR)error);
2749 MessageBox(nullptr, temp, L"TortoiseGit", MB_OK | MB_ICONERROR);
2753 if (status)
2755 bool rejected = progress.GetLogText().Find(L"! [rejected]") > 0;
2756 if (rejected)
2758 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, []{ Pull(true); });
2759 postCmdList.emplace_back(IDI_PULL, IDS_MENUFETCH, [&]{ Fetch(allRemotes ? L"" : remote, allRemotes); });
2761 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, [&]{ Push(localBranch); });
2762 return;
2765 postCmdList.emplace_back(IDS_PROC_REQUESTPULL, [&]{ RequestPull(remoteBranch); });
2766 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, [&]{ Push(localBranch); });
2767 postCmdList.emplace_back(IDI_SWITCH, IDS_MENUSWITCH, [&]{ Switch(); });
2768 if (!superprojectRoot.IsEmpty())
2770 postCmdList.emplace_back(IDI_COMMIT, IDS_PROC_COMMIT_SUPERPROJECT, [&]
2772 CString sCmd;
2773 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)superprojectRoot);
2774 RunTortoiseGitProc(sCmd);
2779 INT_PTR ret = progress.DoModal();
2780 return ret == IDOK;
2783 bool CAppUtils::Push(const CString& selectLocalBranch)
2785 CPushDlg dlg;
2786 dlg.m_BranchSourceName = selectLocalBranch;
2788 if (dlg.DoModal() == IDOK)
2789 return DoPush(!!dlg.m_bAutoLoad, !!dlg.m_bPack, !!dlg.m_bTags, !!dlg.m_bPushAllRemotes, !!dlg.m_bPushAllBranches, !!dlg.m_bForce, !!dlg.m_bForceWithLease, dlg.m_BranchSourceName, dlg.m_URL, dlg.m_BranchRemoteName, !!dlg.m_bSetUpstream, dlg.m_RecurseSubmodules);
2791 return FALSE;
2794 bool CAppUtils::RequestPull(const CString& endrevision, const CString& repositoryUrl, bool bIsMainWnd)
2796 CRequestPullDlg dlg;
2797 dlg.m_RepositoryURL = repositoryUrl;
2798 dlg.m_EndRevision = endrevision;
2799 if (dlg.DoModal()==IDOK)
2801 CString cmd;
2802 cmd.Format(L"git.exe request-pull %s \"%s\" %s", (LPCTSTR)dlg.m_StartRevision, (LPCTSTR)dlg.m_RepositoryURL, (LPCTSTR)dlg.m_EndRevision);
2804 CSysProgressDlg sysProgressDlg;
2805 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
2806 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_CREATINGPULLREUQEST)));
2807 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
2808 sysProgressDlg.SetShowProgressBar(false);
2809 sysProgressDlg.ShowModeless((HWND)nullptr, true);
2811 CString tempFileName = GetTempFile();
2812 CString err;
2813 DeleteFile(tempFileName);
2814 CreateDirectory(tempFileName, nullptr);
2815 tempFileName += L"\\pullrequest.txt";
2816 if (g_Git.RunLogFile(cmd, tempFileName, &err))
2818 CString msg;
2819 msg.LoadString(IDS_ERR_PULLREUQESTFAILED);
2820 MessageBox(nullptr, msg + L'\n' + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
2821 return false;
2824 if (sysProgressDlg.HasUserCancelled())
2826 CMessageBox::Show(nullptr, IDS_USERCANCELLED, IDS_APPNAME, MB_OK);
2827 ::DeleteFile(tempFileName);
2828 return false;
2831 sysProgressDlg.Stop();
2833 if (dlg.m_bSendMail)
2835 CSendMailDlg sendmaildlg;
2836 sendmaildlg.m_PathList = CTGitPathList(CTGitPath(tempFileName));
2837 sendmaildlg.m_bCustomSubject = true;
2839 if (sendmaildlg.DoModal() == IDOK)
2841 if (sendmaildlg.m_PathList.IsEmpty())
2842 return FALSE;
2844 CGitProgressDlg progDlg;
2845 if (bIsMainWnd)
2846 theApp.m_pMainWnd = &progDlg;
2847 SendMailProgressCommand sendMailProgressCommand;
2848 progDlg.SetCommand(&sendMailProgressCommand);
2850 sendMailProgressCommand.SetPathList(sendmaildlg.m_PathList);
2851 progDlg.SetItemCount(sendmaildlg.m_PathList.GetCount());
2853 CSendMailCombineable sendMailCombineable(sendmaildlg.m_To, sendmaildlg.m_CC, sendmaildlg.m_Subject, !!sendmaildlg.m_bAttachment, !!sendmaildlg.m_bCombine);
2854 sendMailProgressCommand.SetSendMailOption(&sendMailCombineable);
2856 progDlg.DoModal();
2858 return true;
2860 return false;
2863 CAppUtils::LaunchAlternativeEditor(tempFileName);
2865 return true;
2868 void CAppUtils::RemoveTrailSlash(CString &path)
2870 if(path.IsEmpty())
2871 return ;
2873 // For URL, do not trim the slash just after the host name component.
2874 int index = path.Find(L"://");
2875 if (index >= 0)
2877 index += 4;
2878 index = path.Find(L'/', index);
2879 if (index == path.GetLength() - 1)
2880 return;
2883 while (path[path.GetLength() - 1] == L'\\' || path[path.GetLength() - 1] == L'/')
2885 path.Truncate(path.GetLength() - 1);
2886 if(path.IsEmpty())
2887 return;
2891 bool CAppUtils::CheckUserData()
2893 while(g_Git.GetUserName().IsEmpty() || g_Git.GetUserEmail().IsEmpty())
2895 if (CMessageBox::Show(nullptr, IDS_PROC_NOUSERDATA, IDS_APPNAME, MB_YESNO | MB_ICONERROR) == IDYES)
2897 CTGitPath path(g_Git.m_CurrentDir);
2898 CSettings dlg(IDS_PROC_SETTINGS_TITLE,&path);
2899 dlg.SetTreeViewMode(TRUE, TRUE, TRUE);
2900 dlg.SetTreeWidth(220);
2901 dlg.m_DefaultPage = L"gitconfig";
2903 dlg.DoModal();
2904 dlg.HandleRestart();
2907 else
2908 return false;
2911 return true;
2914 BOOL CAppUtils::Commit(const CString& bugid, BOOL bWholeProject, CString &sLogMsg,
2915 CTGitPathList &pathList,
2916 CTGitPathList &selectedList,
2917 bool bSelectFilesForCommit)
2919 bool bFailed = true;
2921 if (!CheckUserData())
2922 return false;
2924 while (bFailed)
2926 bFailed = false;
2927 CCommitDlg dlg;
2928 dlg.m_sBugID = bugid;
2930 dlg.m_bWholeProject = bWholeProject;
2932 dlg.m_sLogMessage = sLogMsg;
2933 dlg.m_pathList = pathList;
2934 dlg.m_checkedPathList = selectedList;
2935 dlg.m_bSelectFilesForCommit = bSelectFilesForCommit;
2936 if (dlg.DoModal() == IDOK)
2938 if (dlg.m_pathList.IsEmpty())
2939 return false;
2940 // if the user hasn't changed the list of selected items
2941 // we don't use that list. Because if we would use the list
2942 // of pre-checked items, the dialog would show different
2943 // checked items on the next startup: it would only try
2944 // to check the parent folder (which might not even show)
2945 // instead, we simply use an empty list and let the
2946 // default checking do its job.
2947 if (!dlg.m_pathList.IsEqual(pathList))
2948 selectedList = dlg.m_pathList;
2949 pathList = dlg.m_updatedPathList;
2950 sLogMsg = dlg.m_sLogMessage;
2951 bSelectFilesForCommit = true;
2953 switch (dlg.m_PostCmd)
2955 case GIT_POSTCOMMIT_CMD_DCOMMIT:
2956 CAppUtils::SVNDCommit();
2957 break;
2958 case GIT_POSTCOMMIT_CMD_PUSH:
2959 CAppUtils::Push();
2960 break;
2961 case GIT_POSTCOMMIT_CMD_CREATETAG:
2962 CAppUtils::CreateBranchTag(TRUE);
2963 break;
2964 case GIT_POSTCOMMIT_CMD_PULL:
2965 CAppUtils::Pull(true);
2966 break;
2967 default:
2968 break;
2971 // CGitProgressDlg progDlg;
2972 // progDlg.SetChangeList(dlg.m_sChangeList, !!dlg.m_bKeepChangeList);
2973 // if (parser.HasVal(L"closeonend"))
2974 // progDlg.SetAutoClose(parser.GetLongVal(L"closeonend"));
2975 // progDlg.SetCommand(CGitProgressDlg::GitProgress_Commit);
2976 // progDlg.SetPathList(dlg.m_pathList);
2977 // progDlg.SetCommitMessage(dlg.m_sLogMessage);
2978 // progDlg.SetDepth(dlg.m_bRecursive ? Git_depth_infinity : svn_depth_empty);
2979 // progDlg.SetSelectedList(dlg.m_selectedPathList);
2980 // progDlg.SetItemCount(dlg.m_itemsCount);
2981 // progDlg.SetBugTraqProvider(dlg.m_BugTraqProvider);
2982 // progDlg.DoModal();
2983 // CRegDWORD err = CRegDWORD(L"Software\\TortoiseGit\\ErrorOccurred", FALSE);
2984 // err = (DWORD)progDlg.DidErrorsOccur();
2985 // bFailed = progDlg.DidErrorsOccur();
2986 // bRet = progDlg.DidErrorsOccur();
2987 // CRegDWORD bFailRepeat = CRegDWORD(L"Software\\TortoiseGit\\CommitReopen", FALSE);
2988 // if (DWORD(bFailRepeat)==0)
2989 // bFailed = false; // do not repeat if the user chose not to in the settings.
2992 return true;
2995 BOOL CAppUtils::SVNDCommit()
2997 CSVNDCommitDlg dcommitdlg;
2998 CString gitSetting = g_Git.GetConfigValue(L"svn.rmdir");
2999 if (gitSetting.IsEmpty()) {
3000 if (dcommitdlg.DoModal() != IDOK)
3001 return false;
3002 else
3004 if (dcommitdlg.m_remember)
3006 if (dcommitdlg.m_rmdir)
3007 gitSetting = L"true";
3008 else
3009 gitSetting = L"false";
3010 if (g_Git.SetConfigValue(L"svn.rmdir", gitSetting))
3012 CString msg;
3013 msg.Format(IDS_PROC_SAVECONFIGFAILED, L"svn.rmdir", (LPCTSTR)gitSetting);
3014 MessageBox(nullptr, msg, L"TortoiseGit", MB_OK | MB_ICONERROR);
3020 BOOL IsStash = false;
3021 if(!g_Git.CheckCleanWorkTree())
3023 if (CMessageBox::Show(nullptr, IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
3025 CSysProgressDlg sysProgressDlg;
3026 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3027 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3028 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3029 sysProgressDlg.SetShowProgressBar(false);
3030 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3031 sysProgressDlg.ShowModeless((HWND)nullptr, true);
3033 CString out;
3034 if (g_Git.Run(L"git.exe stash", &out, CP_UTF8))
3036 sysProgressDlg.Stop();
3037 MessageBox(nullptr, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3038 return false;
3040 sysProgressDlg.Stop();
3042 IsStash =true;
3044 else
3045 return false;
3048 CProgressDlg progress;
3049 if (dcommitdlg.m_rmdir)
3050 progress.m_GitCmd = L"git.exe svn dcommit --rmdir";
3051 else
3052 progress.m_GitCmd = L"git.exe svn dcommit";
3053 if(progress.DoModal()==IDOK && progress.m_GitStatus == 0)
3055 if( IsStash)
3057 if (CMessageBox::Show(nullptr, IDS_DCOMMIT_STASH_POP, IDS_APPNAME, MB_YESNO | MB_ICONINFORMATION) == IDYES)
3059 CSysProgressDlg sysProgressDlg;
3060 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3061 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3062 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3063 sysProgressDlg.SetShowProgressBar(false);
3064 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3065 sysProgressDlg.ShowModeless((HWND)nullptr, true);
3067 CString out;
3068 if (g_Git.Run(L"git.exe stash pop", &out, CP_UTF8))
3070 sysProgressDlg.Stop();
3071 MessageBox(nullptr, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3072 return false;
3074 sysProgressDlg.Stop();
3076 else
3077 return false;
3079 return TRUE;
3081 return FALSE;
3084 static bool DoMerge(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)
3086 CString args;
3087 if (noFF)
3088 args += L" --no-ff";
3089 else if (ffOnly)
3090 args += L" --ff-only";
3092 if (squash)
3093 args += L" --squash";
3095 if (noCommit)
3096 args += L" --no-commit";
3098 if (unrelated)
3099 args += L" --allow-unrelated-histories";
3101 if (log)
3102 args.AppendFormat(L" --log=%d", *log);
3104 if (!mergeStrategy.IsEmpty())
3106 args += L" --strategy=" + mergeStrategy;
3107 if (!strategyOption.IsEmpty())
3109 args += L" --strategy-option=" + strategyOption;
3110 if (!strategyParam.IsEmpty())
3111 args += L'=' + strategyParam;
3115 if (!logMessage.IsEmpty())
3117 CString logmsg = logMessage;
3118 logmsg.Replace(L"\\\"", L"\\\\\"");
3119 logmsg.Replace(L"\"", L"\\\"");
3120 args += L" -m \"" + logmsg + L"\"";
3123 CString mergeVersion = g_Git.FixBranchName(version);
3124 CString cmd;
3125 cmd.Format(L"git.exe merge%s %s", (LPCTSTR)args, (LPCTSTR)mergeVersion);
3127 CProgressDlg Prodlg;
3128 Prodlg.m_GitCmd = cmd;
3130 Prodlg.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3132 if (status)
3134 int hasConflicts = g_Git.HasWorkingTreeConflicts();
3135 if (hasConflicts < 0)
3136 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(L"Checking for conflicts failed.", CGit::GIT_CMD_CHECKCONFLICTS), L"TortoiseGit", MB_ICONEXCLAMATION);
3137 else if (hasConflicts)
3139 // there are conflict files
3141 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
3143 CString sCmd;
3144 sCmd.Format(L"/command:resolve /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3145 CAppUtils::RunTortoiseGitProc(sCmd);
3148 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUCOMMIT, []
3150 CString sCmd;
3151 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3152 CAppUtils::RunTortoiseGitProc(sCmd);
3156 if (CAppUtils::GetMsysgitVersion() >= 0x02090000)
3158 CGitHash common;
3159 g_Git.IsFastForward(L"HEAD", mergeVersion, &common);
3160 if (common.IsEmpty())
3161 postCmdList.emplace_back(IDI_MERGE, IDS_MERGE_UNRELATED, [=] { DoMerge(noFF, ffOnly, squash, noCommit, log, true, mergeStrategy, strategyOption, strategyParam, logMessage, version, isBranch, showStashPop); });
3164 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSTASHSAVE, [=]{ CAppUtils::StashSave(L"", false, false, true, mergeVersion); });
3166 return;
3169 if (showStashPop)
3170 postCmdList.emplace_back(IDI_RELOCATE, IDS_MENUSTASHPOP, []{ CAppUtils::StashPop(); });
3172 if (noCommit || squash)
3174 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUCOMMIT, []
3176 CString sCmd;
3177 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3178 CAppUtils::RunTortoiseGitProc(sCmd);
3180 return;
3183 if (isBranch && !CStringUtils::StartsWith(version, L"remotes/")) // do not ask to remove remote branches
3185 postCmdList.emplace_back(IDI_DELETE, IDS_PROC_REMOVEBRANCH, [&]
3187 CString msg;
3188 msg.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)version);
3189 if (CMessageBox::Show(nullptr, msg, L"TortoiseGit", 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
3191 CString cmd, out;
3192 cmd.Format(L"git.exe branch -D -- %s", (LPCTSTR)version);
3193 if (g_Git.Run(cmd, &out, CP_UTF8))
3194 MessageBox(nullptr, out, L"TortoiseGit", MB_OK);
3198 if (isBranch)
3199 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, []{ CAppUtils::Push(); });
3201 BOOL hasGitSVN = CTGitPath(g_Git.m_CurrentDir).GetAdminDirMask() & ITEMIS_GITSVN;
3202 if (hasGitSVN)
3203 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSVNDCOMMIT, []{ CAppUtils::SVNDCommit(); });
3206 Prodlg.DoModal();
3207 return !Prodlg.m_GitStatus;
3210 BOOL CAppUtils::Merge(const CString* commit, bool showStashPop)
3212 if (!CheckUserData())
3213 return FALSE;
3215 if (IsTGitRebaseActive())
3216 return FALSE;
3218 CMergeDlg dlg;
3219 if (commit)
3220 dlg.m_initialRefName = *commit;
3222 if (dlg.DoModal() == IDOK)
3223 return DoMerge(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);
3225 return FALSE;
3228 BOOL CAppUtils::MergeAbort()
3230 CMergeAbortDlg dlg;
3231 if (dlg.DoModal() == IDOK)
3232 return Reset(L"HEAD", (dlg.m_ResetType == 0) ? 3 : dlg.m_ResetType);
3234 return FALSE;
3237 void CAppUtils::EditNote(GitRevLoglist* rev, ProjectProperties* projectProperties)
3239 if (!CheckUserData())
3240 return;
3242 CInputDlg dlg;
3243 dlg.m_sHintText = CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES));
3244 dlg.m_sInputText = rev->m_Notes;
3245 dlg.m_sTitle = CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES));
3246 dlg.m_pProjectProperties = projectProperties;
3247 dlg.m_bUseLogWidth = true;
3248 if(dlg.DoModal() == IDOK)
3250 CString cmd,output;
3251 cmd = L"notes add -f -F \"";
3253 CString tempfile=::GetTempFile();
3254 if (!CStringUtils::WriteStringToTextFile(tempfile, dlg.m_sInputText))
3256 CMessageBox::Show(nullptr, IDS_PROC_FAILEDSAVINGNOTES, IDS_APPNAME, MB_OK | MB_ICONERROR);
3257 return;
3259 cmd += tempfile;
3260 cmd += L"\" ";
3261 cmd += rev->m_CommitHash.ToString();
3265 if (git_run_cmd("notes", CUnicodeUtils::GetMulti(cmd, CP_UTF8).GetBuffer()))
3266 CMessageBox::Show(nullptr, IDS_PROC_FAILEDSAVINGNOTES, IDS_APPNAME, MB_OK | MB_ICONERROR);
3267 }catch(...)
3269 CMessageBox::Show(nullptr, IDS_PROC_FAILEDSAVINGNOTES, IDS_APPNAME, MB_OK | MB_ICONERROR);
3271 ::DeleteFile(tempfile);
3273 if (g_Git.GetGitNotes(rev->m_CommitHash, rev->m_Notes))
3274 MessageBox(nullptr, g_Git.GetLibGit2LastErr(L"Could not load notes for commit " + rev->m_CommitHash.ToString() + L'.'), L"TortoiseGit", MB_OK | MB_ICONERROR);
3278 int CAppUtils::GetMsysgitVersion()
3280 if (g_Git.ms_LastMsysGitVersion)
3281 return g_Git.ms_LastMsysGitVersion;
3283 CString cmd;
3284 CString versiondebug;
3285 CString version;
3287 CRegDWORD regTime = CRegDWORD(L"Software\\TortoiseGit\\git_file_time");
3288 CRegDWORD regVersion = CRegDWORD(L"Software\\TortoiseGit\\git_cached_version");
3290 CString gitpath = CGit::ms_LastMsysGitDir + L"\\git.exe";
3292 __int64 time=0;
3293 if (!CGit::GetFileModifyTime(gitpath, &time))
3295 if ((DWORD)CGit::filetime_to_time_t(time) == regTime)
3297 g_Git.ms_LastMsysGitVersion = regVersion;
3298 return regVersion;
3302 CString err;
3303 int ver = g_Git.GetGitVersion(&versiondebug, &err);
3304 if (ver < 0)
3306 MessageBox(nullptr, 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);
3307 return -1;
3311 if (!ver)
3313 MessageBox(nullptr, L"Could not parse git.exe version number: \"" + versiondebug + L'"', L"TortoiseGit", MB_OK | MB_ICONERROR);
3314 return -1;
3318 regTime = time&0xFFFFFFFF;
3319 regVersion = ver;
3320 g_Git.ms_LastMsysGitVersion = ver;
3322 return ver;
3325 void CAppUtils::MarkWindowAsUnpinnable(HWND hWnd)
3327 typedef HRESULT (WINAPI *SHGPSFW) (HWND hwnd,REFIID riid,void** ppv);
3329 CAutoLibrary hShell = AtlLoadSystemLibraryUsingFullPath(L"Shell32.dll");
3331 if (hShell.IsValid()) {
3332 SHGPSFW pfnSHGPSFW = (SHGPSFW)::GetProcAddress(hShell, "SHGetPropertyStoreForWindow");
3333 if (pfnSHGPSFW) {
3334 IPropertyStore *pps;
3335 HRESULT hr = pfnSHGPSFW(hWnd, IID_PPV_ARGS(&pps));
3336 if (SUCCEEDED(hr)) {
3337 PROPVARIANT var;
3338 var.vt = VT_BOOL;
3339 var.boolVal = VARIANT_TRUE;
3340 pps->SetValue(PKEY_AppUserModel_PreventPinning, var);
3341 pps->Release();
3347 void CAppUtils::SetWindowTitle(HWND hWnd, const CString& urlorpath, const CString& dialogname)
3349 ASSERT(dialogname.GetLength() < 70);
3350 ASSERT(urlorpath.GetLength() < MAX_PATH);
3351 WCHAR pathbuf[MAX_PATH] = {0};
3353 PathCompactPathEx(pathbuf, urlorpath, 70 - dialogname.GetLength(), 0);
3355 wcscat_s(pathbuf, L" - ");
3356 wcscat_s(pathbuf, dialogname);
3357 wcscat_s(pathbuf, L" - ");
3358 wcscat_s(pathbuf, CString(MAKEINTRESOURCE(IDS_APPNAME)));
3359 SetWindowText(hWnd, pathbuf);
3362 bool CAppUtils::BisectStart(const CString& lastGood, const CString& firstBad, bool bIsMainWnd)
3364 if (!g_Git.CheckCleanWorkTree())
3366 if (CMessageBox::Show(nullptr, IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
3368 CSysProgressDlg sysProgressDlg;
3369 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3370 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3371 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3372 sysProgressDlg.SetShowProgressBar(false);
3373 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3374 sysProgressDlg.ShowModeless((HWND)nullptr, true);
3376 CString out;
3377 if (g_Git.Run(L"git.exe stash", &out, CP_UTF8))
3379 sysProgressDlg.Stop();
3380 MessageBox(nullptr, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3381 return false;
3383 sysProgressDlg.Stop();
3385 else
3386 return false;
3389 CBisectStartDlg bisectStartDlg;
3391 if (!lastGood.IsEmpty())
3392 bisectStartDlg.m_sLastGood = lastGood;
3393 if (!firstBad.IsEmpty())
3394 bisectStartDlg.m_sFirstBad = firstBad;
3396 if (bisectStartDlg.DoModal() == IDOK)
3398 CProgressDlg progress;
3399 if (bIsMainWnd)
3400 theApp.m_pMainWnd = &progress;
3401 progress.m_GitCmdList.push_back(L"git.exe bisect start");
3402 progress.m_GitCmdList.push_back(L"git.exe bisect good " + bisectStartDlg.m_LastGoodRevision);
3403 progress.m_GitCmdList.push_back(L"git.exe bisect bad " + bisectStartDlg.m_FirstBadRevision);
3405 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3407 if (status)
3408 return;
3410 CTGitPath path(g_Git.m_CurrentDir);
3411 if (path.HasSubmodules())
3413 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
3415 CString sCmd;
3416 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3417 CAppUtils::RunTortoiseGitProc(sCmd);
3422 INT_PTR ret = progress.DoModal();
3423 return ret == IDOK;
3426 return false;
3429 bool CAppUtils::BisectOperation(const CString& op, const CString& ref, bool bIsMainWnd)
3431 CString cmd = L"git.exe bisect " + op;
3433 if (!ref.IsEmpty())
3435 cmd += L' ';
3436 cmd += ref;
3439 CProgressDlg progress;
3440 if (bIsMainWnd)
3441 theApp.m_pMainWnd = &progress;
3442 progress.m_GitCmd = cmd;
3444 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3446 if (status)
3447 return;
3449 CTGitPath path = g_Git.m_CurrentDir;
3450 if (path.HasSubmodules())
3452 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
3454 CString sCmd;
3455 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3456 CAppUtils::RunTortoiseGitProc(sCmd);
3460 if (op != L"reset")
3461 postCmdList.emplace_back(IDS_MENUBISECTRESET, []{ CAppUtils::RunTortoiseGitProc(L"/command:bisect /reset"); });
3464 INT_PTR ret = progress.DoModal();
3465 return ret == IDOK;
3468 int CAppUtils::Git2GetUserPassword(git_cred **out, const char *url, const char *username_from_url, unsigned int /*allowed_types*/, void * /*payload*/)
3470 CUserPassword dlg;
3471 dlg.m_URL = CUnicodeUtils::GetUnicode(url, CP_UTF8);
3472 if (username_from_url)
3473 dlg.m_UserName = CUnicodeUtils::GetUnicode(username_from_url, CP_UTF8);
3475 CStringA username, password;
3476 if (dlg.DoModal() == IDOK)
3478 username = CUnicodeUtils::GetMulti(dlg.m_UserName, CP_UTF8);
3479 password = CUnicodeUtils::GetMulti(dlg.m_Password, CP_UTF8);
3480 return git_cred_userpass_plaintext_new(out, username, password);
3482 giterr_set_str(GITERR_NONE, "User cancelled.");
3483 return GIT_EUSER;
3486 int CAppUtils::Git2CertificateCheck(git_cert* base_cert, int /*valid*/, const char* host, void* /*payload*/)
3488 if (base_cert->cert_type == GIT_CERT_X509)
3490 git_cert_x509* cert = (git_cert_x509*)base_cert;
3492 if (last_accepted_cert.cmp(cert))
3493 return 0;
3495 PCCERT_CONTEXT pServerCert = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, (BYTE*)cert->data, (DWORD)cert->len);
3496 SCOPE_EXIT { CertFreeCertificateContext(pServerCert); };
3498 DWORD verificationError = VerifyServerCertificate(pServerCert, CUnicodeUtils::GetUnicode(host).GetBuffer(), 0);
3499 if (!verificationError)
3501 last_accepted_cert.set(cert);
3502 return 0;
3505 CString servernameInCert;
3506 CertGetNameString(pServerCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nullptr, CStrBuf(servernameInCert, 128), 128);
3508 CString issuer;
3509 CertGetNameString(pServerCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, CERT_NAME_ISSUER_FLAG, nullptr, CStrBuf(issuer, 128), 128);
3511 CCheckCertificateDlg dlg;
3512 dlg.cert = cert;
3513 dlg.m_sCertificateCN = servernameInCert;
3514 dlg.m_sCertificateIssuer = issuer;
3515 dlg.m_sHostname = CUnicodeUtils::GetUnicode(host);
3516 dlg.m_sError = CFormatMessageWrapper(verificationError);
3517 if (dlg.DoModal() == IDOK)
3519 last_accepted_cert.set(cert);
3520 return 0;
3523 return GIT_ECERTIFICATE;
3526 int CAppUtils::ExploreTo(HWND hwnd, CString path)
3528 if (PathFileExists(path))
3530 HRESULT ret = E_FAIL;
3531 ITEMIDLIST __unaligned * pidl = ILCreateFromPath(path);
3532 if (pidl)
3534 ret = SHOpenFolderAndSelectItems(pidl, 0, 0, 0);
3535 ILFree(pidl);
3537 return SUCCEEDED(ret) ? 0 : -1;
3539 // if filepath does not exist any more, navigate to closest matching folder
3542 int pos = path.ReverseFind(L'\\');
3543 if (pos <= 3)
3544 break;
3545 path.Truncate(pos);
3546 } while (!PathFileExists(path));
3547 return (INT_PTR)ShellExecute(hwnd, L"explore", path, nullptr, nullptr, SW_SHOW) > 32 ? 0 : -1;
3550 int CAppUtils::ResolveConflict(CTGitPath& path, resolve_with resolveWith)
3552 bool b_local = false, b_remote = false;
3553 BYTE_VECTOR vector;
3555 CString cmd;
3556 cmd.Format(L"git.exe ls-files -u -t -z -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3557 if (g_Git.Run(cmd, &vector))
3559 MessageBox(nullptr, L"git ls-files failed!", L"TortoiseGit", MB_OK);
3560 return -1;
3563 CTGitPathList list;
3564 if (list.ParserFromLsFile(vector))
3566 MessageBox(nullptr, L"Parse ls-files failed!", L"TortoiseGit", MB_OK);
3567 return -1;
3570 if (list.IsEmpty())
3571 return 0;
3572 for (int i = 0; i < list.GetCount(); ++i)
3574 if (list[i].m_Stage == 2)
3575 b_local = true;
3576 if (list[i].m_Stage == 3)
3577 b_remote = true;
3581 bool baseIsFile = true, localIsFile = true, remoteIsFile = true;
3582 CString baseHash, localHash, remoteHash;
3583 ParseHashesFromLsFile(vector, baseHash, baseIsFile, localHash, localIsFile, remoteHash, remoteIsFile);
3585 CBlockCacheForPath block(g_Git.m_CurrentDir);
3586 if ((resolveWith == RESOLVE_WITH_THEIRS && !b_remote) || (resolveWith == RESOLVE_WITH_MINE && !b_local))
3588 CString gitcmd, output; //retest with registered submodule!
3589 gitcmd.Format(L"git.exe rm -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3590 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3592 // a .git folder in a submodule which is not in .gitmodules cannot be deleted using "git rm"
3593 if (PathIsDirectory(path.GetGitPathString()) && !PathIsDirectoryEmpty(path.GetGitPathString()))
3595 CString message(output);
3596 output += L"\n\n";
3597 output.AppendFormat(IDS_PROC_DELETEBRANCHTAG, path.GetWinPath());
3598 CString deleteButton;
3599 deleteButton.LoadString(IDS_DELETEBUTTON);
3600 CString abortButton;
3601 abortButton.LoadString(IDS_ABORTBUTTON);
3602 if (CMessageBox::Show(nullptr, output, L"TortoiseGit", 2, IDI_QUESTION, deleteButton, abortButton) == 2)
3603 return -1;
3604 path.Delete(true, true);
3605 output.Empty();
3606 if (!g_Git.Run(gitcmd, &output, CP_UTF8))
3608 RemoveTempMergeFile(path);
3609 return 0;
3612 MessageBox(nullptr, output, L"TortoiseGit", MB_ICONERROR);
3613 return -1;
3615 RemoveTempMergeFile(path);
3616 return 0;
3619 if (resolveWith == RESOLVE_WITH_THEIRS || resolveWith == RESOLVE_WITH_MINE)
3621 auto resolve = [&b_local, &b_remote](const CTGitPath& path, int stage, bool willBeFile, const CString& hash) -> int
3623 if (!willBeFile)
3625 if (!path.HasAdminDir()) // check if submodule is initialized
3627 CString gitcmd, output;
3628 if (!path.IsDirectory())
3630 gitcmd.Format(L"git.exe checkout-index -f --stage=%d -- \"%s\"", stage, (LPCTSTR)path.GetGitPathString());
3631 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3633 MessageBox(nullptr, output, L"TortoiseGit", MB_ICONERROR);
3634 return -1;
3637 gitcmd.Format(L"git.exe update-index --replace --cacheinfo 0160000,%s,\"%s\"", (LPCTSTR)hash, (LPCTSTR)path.GetGitPathString());
3638 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3640 MessageBox(nullptr, output, L"TortoiseGit", MB_ICONERROR);
3641 return -1;
3643 return 0;
3646 CGit subgit;
3647 subgit.m_CurrentDir = g_Git.CombinePath(path);
3648 CGitHash submoduleHead;
3649 if (subgit.GetHash(submoduleHead, L"HEAD"))
3651 MessageBox(nullptr, subgit.GetGitLastErr(L"Could not get HEAD hash of submodule, this should not happen!"), L"TortoiseGit", MB_ICONERROR);
3652 return -1;
3654 if (submoduleHead.ToString() != hash)
3656 CString origPath = g_Git.m_CurrentDir;
3657 g_Git.m_CurrentDir = g_Git.CombinePath(path);
3658 if (!GitReset(&hash))
3660 g_Git.m_CurrentDir = origPath;
3661 return -1;
3663 g_Git.m_CurrentDir = origPath;
3666 else
3668 CString gitcmd, output;
3669 if (b_local && b_remote)
3670 gitcmd.Format(L"git.exe checkout-index -f --stage=%d -- \"%s\"", stage, (LPCTSTR)path.GetGitPathString());
3671 else
3672 gitcmd.Format(L"git.exe add -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3673 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3675 MessageBox(nullptr, output, L"TortoiseGit", MB_ICONERROR);
3676 return -1;
3679 return 0;
3681 int ret = -1;
3682 if (resolveWith == RESOLVE_WITH_THEIRS)
3683 ret = resolve(path, 3, remoteIsFile, remoteHash);
3684 else
3685 ret = resolve(path, 2, localIsFile, localHash);
3686 if (ret)
3687 return ret;
3690 if (PathFileExists(g_Git.CombinePath(path)) && (path.m_Action & CTGitPath::LOGACTIONS_UNMERGED))
3692 CString gitcmd, output;
3693 gitcmd.Format(L"git.exe add -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3694 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3696 MessageBox(nullptr, output, L"TortoiseGit", MB_ICONERROR);
3697 return -1;
3700 path.m_Action |= CTGitPath::LOGACTIONS_MODIFIED;
3701 path.m_Action &= ~CTGitPath::LOGACTIONS_UNMERGED;
3704 RemoveTempMergeFile(path);
3705 return 0;
3708 bool CAppUtils::ShellOpen(const CString& file, HWND hwnd /*= nullptr */)
3710 if ((INT_PTR)ShellExecute(hwnd, nullptr, file, nullptr, nullptr, SW_SHOW) > HINSTANCE_ERROR)
3711 return true;
3713 return ShowOpenWithDialog(file, hwnd);
3716 bool CAppUtils::ShowOpenWithDialog(const CString& file, HWND hwnd /*= nullptr */)
3718 OPENASINFO oi = { 0 };
3719 oi.pcszFile = file;
3720 oi.oaifInFlags = OAIF_EXEC;
3721 return SUCCEEDED(SHOpenWithDialog(hwnd, &oi));
3724 bool CAppUtils::IsTGitRebaseActive()
3726 CString adminDir;
3727 if (!GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir))
3728 return false;
3730 if (!PathIsDirectory(adminDir + L"tgitrebase.active"))
3731 return false;
3733 if (CMessageBox::Show(nullptr, IDS_REBASELOCKFILEFOUND, IDS_APPNAME, 2, IDI_EXCLAMATION, IDS_REMOVESTALEBUTTON, IDS_ABORTBUTTON) == 2)
3734 return true;
3736 RemoveDirectory(adminDir + L"tgitrebase.active");
3738 return false;