SetGitNote -> SetGitNotes
[TortoiseGit.git] / src / TortoiseProc / AppUtils.cpp
blobcdaa8d9ec1e05b480abf6ba7350b036abc6b42d4
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); });
138 postCmdList.emplace_back(IDI_RELOCATE, IDS_MENUSTASHPOP, [] { CAppUtils::StashPop(); });
140 return (progress.DoModal() == IDOK);
142 return false;
145 bool CAppUtils::StashApply(CString ref, bool showChanges /* true */)
147 CString cmd = L"git.exe stash apply ";
148 if (CStringUtils::StartsWith(ref, L"refs/"))
149 ref = ref.Mid(5);
150 if (CStringUtils::StartsWith(ref, L"stash{"))
151 ref = L"stash@" + ref.Mid(5);
152 cmd += ref;
154 CSysProgressDlg sysProgressDlg;
155 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
156 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
157 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
158 sysProgressDlg.SetShowProgressBar(false);
159 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
160 sysProgressDlg.ShowModeless((HWND)nullptr, true);
162 CString out;
163 int ret = g_Git.Run(cmd, &out, CP_UTF8);
165 sysProgressDlg.Stop();
167 bool hasConflicts = (out.Find(L"CONFLICT") >= 0);
168 if (ret && !(ret == 1 && hasConflicts))
169 CMessageBox::Show(nullptr, CString(MAKEINTRESOURCE(IDS_PROC_STASHAPPLYFAILED)) + L'\n' + out, L"TortoiseGit", MB_OK | MB_ICONERROR);
170 else
172 CString message;
173 message.LoadString(IDS_PROC_STASHAPPLYSUCCESS);
174 if (hasConflicts)
175 message.LoadString(IDS_PROC_STASHAPPLYFAILEDCONFLICTS);
176 if (showChanges)
178 if (CMessageBox::Show(nullptr, message + L'\n' + CString(MAKEINTRESOURCE(IDS_SEECHANGES)), L"TortoiseGit", MB_YESNO | MB_ICONINFORMATION) == IDYES)
180 cmd.Format(L"/command:repostatus /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
181 CAppUtils::RunTortoiseGitProc(cmd);
183 return true;
185 else
187 MessageBox(nullptr, message ,L"TortoiseGit", MB_OK | MB_ICONINFORMATION);
188 return true;
191 return false;
194 bool CAppUtils::StashPop(int showChanges /* = 1 */)
196 CString cmd = L"git.exe stash pop";
198 CSysProgressDlg sysProgressDlg;
199 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
200 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
201 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
202 sysProgressDlg.SetShowProgressBar(false);
203 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
204 sysProgressDlg.ShowModeless((HWND)nullptr, true);
206 CString out;
207 int ret = g_Git.Run(cmd, &out, CP_UTF8);
209 sysProgressDlg.Stop();
211 bool hasConflicts = (out.Find(L"CONFLICT") >= 0);
212 if (ret && !(ret == 1 && hasConflicts))
213 CMessageBox::Show(nullptr, CString(MAKEINTRESOURCE(IDS_PROC_STASHPOPFAILED)) + L'\n' + out, L"TortoiseGit", MB_OK | MB_ICONERROR);
214 else
216 CString message;
217 message.LoadString(IDS_PROC_STASHPOPSUCCESS);
218 if (hasConflicts)
219 message.LoadString(IDS_PROC_STASHPOPFAILEDCONFLICTS);
220 if (showChanges == 1 || (showChanges == 0 && hasConflicts))
222 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)
224 cmd.Format(L"/command:repostatus /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
225 CAppUtils::RunTortoiseGitProc(cmd);
227 return true;
229 else if (showChanges > 1)
231 MessageBox(nullptr, message, L"TortoiseGit", MB_OK | MB_ICONINFORMATION);
232 return true;
234 else if (showChanges == 0)
235 return true;
237 return false;
240 BOOL CAppUtils::StartExtMerge(bool bAlternative,
241 const CTGitPath& basefile, const CTGitPath& theirfile, const CTGitPath& yourfile, const CTGitPath& mergedfile,
242 const CString& basename, const CString& theirname, const CString& yourname, const CString& mergedname, bool bReadOnly,
243 HWND resolveMsgHwnd, bool bDeleteBaseTheirsMineOnClose)
245 CRegString regCom = CRegString(L"Software\\TortoiseGit\\Merge");
246 CString ext = mergedfile.GetFileExtension();
247 CString com = regCom;
248 bool bInternal = false;
250 if (!ext.IsEmpty())
252 // is there an extension specific merge tool?
253 CRegString mergetool(L"Software\\TortoiseGit\\MergeTools\\" + ext.MakeLower());
254 if (!CString(mergetool).IsEmpty())
255 com = mergetool;
257 // is there a filename specific merge tool?
258 CRegString mergetool(L"Software\\TortoiseGit\\MergeTools\\." + mergedfile.GetFilename().MakeLower());
259 if (!CString(mergetool).IsEmpty())
260 com = mergetool;
262 if (bAlternative && !com.IsEmpty())
264 if (CStringUtils::StartsWith(com, L"#"))
265 com.Delete(0);
266 else
267 com.Empty();
270 if (com.IsEmpty() || CStringUtils::StartsWith(com, L"#"))
272 // Maybe we should use TortoiseIDiff?
273 if ((ext == L".jpg") || (ext == L".jpeg") ||
274 (ext == L".bmp") || (ext == L".gif") ||
275 (ext == L".png") || (ext == L".ico") ||
276 (ext == L".tif") || (ext == L".tiff") ||
277 (ext == L".dib") || (ext == L".emf") ||
278 (ext == L".cur"))
280 com = CPathUtils::GetAppDirectory() + L"TortoiseGitIDiff.exe";
281 com = L'"' + com + L'"';
282 com = com + L" /base:%base /theirs:%theirs /mine:%mine /result:%merged";
283 com = com + L" /basetitle:%bname /theirstitle:%tname /minetitle:%yname";
284 if (resolveMsgHwnd)
285 com.AppendFormat(L" /resolvemsghwnd:%I64d", (__int64)resolveMsgHwnd);
287 else
289 // use TortoiseGitMerge
290 bInternal = true;
291 com = CPathUtils::GetAppDirectory() + L"TortoiseGitMerge.exe";
292 com = L'"' + com + L'"';
293 com = com + L" /base:%base /theirs:%theirs /mine:%mine /merged:%merged";
294 com = com + L" /basename:%bname /theirsname:%tname /minename:%yname /mergedname:%mname";
295 com += L" /saverequired";
296 if (resolveMsgHwnd)
297 com.AppendFormat(L" /resolvemsghwnd:%I64d", (__int64)resolveMsgHwnd);
298 if (bDeleteBaseTheirsMineOnClose)
299 com += L" /deletebasetheirsmineonclose";
301 if (!g_sGroupingUUID.IsEmpty())
303 com += L" /groupuuid:\"";
304 com += g_sGroupingUUID;
305 com += L'"';
308 // check if the params are set. If not, just add the files to the command line
309 if ((com.Find(L"%merged") < 0) && (com.Find(L"%base") < 0) && (com.Find(L"%theirs") < 0) && (com.Find(L"%mine") < 0))
311 com += L" \"" + basefile.GetWinPathString() + L'"';
312 com += L" \"" + theirfile.GetWinPathString() + L'"';
313 com += L" \"" + yourfile.GetWinPathString() + L'"';
314 com += L" \"" + mergedfile.GetWinPathString() + L'"';
316 if (basefile.IsEmpty())
318 com.Replace(L"/base:%base", L"");
319 com.Replace(L"%base", L"");
321 else
322 com.Replace(L"%base", L'"' + basefile.GetWinPathString() + L'"');
323 if (theirfile.IsEmpty())
325 com.Replace(L"/theirs:%theirs", L"");
326 com.Replace(L"%theirs", L"");
328 else
329 com.Replace(L"%theirs", L'"' + theirfile.GetWinPathString() + L'"');
330 if (yourfile.IsEmpty())
332 com.Replace(L"/mine:%mine", L"");
333 com.Replace(L"%mine", L"");
335 else
336 com.Replace(L"%mine", L'"' + yourfile.GetWinPathString() + L'"');
337 if (mergedfile.IsEmpty())
339 com.Replace(L"/merged:%merged", L"");
340 com.Replace(L"%merged", L"");
342 else
343 com.Replace(L"%merged", L'"' + mergedfile.GetWinPathString() + L'"');
344 if (basename.IsEmpty())
346 if (basefile.IsEmpty())
348 com.Replace(L"/basename:%bname", L"");
349 com.Replace(L"%bname", L"");
351 else
352 com.Replace(L"%bname", L'"' + basefile.GetUIFileOrDirectoryName() + L'"');
354 else
355 com.Replace(L"%bname", L'"' + basename + L'"');
356 if (theirname.IsEmpty())
358 if (theirfile.IsEmpty())
360 com.Replace(L"/theirsname:%tname", L"");
361 com.Replace(L"%tname", L"");
363 else
364 com.Replace(L"%tname", L'"' + theirfile.GetUIFileOrDirectoryName() + L'"');
366 else
367 com.Replace(L"%tname", L'"' + theirname + L'"');
368 if (yourname.IsEmpty())
370 if (yourfile.IsEmpty())
372 com.Replace(L"/minename:%yname", L"");
373 com.Replace(L"%yname", L"");
375 else
376 com.Replace(L"%yname", L'"' + yourfile.GetUIFileOrDirectoryName() + L'"');
378 else
379 com.Replace(L"%yname", L'"' + yourname + L'"');
380 if (mergedname.IsEmpty())
382 if (mergedfile.IsEmpty())
384 com.Replace(L"/mergedname:%mname", L"");
385 com.Replace(L"%mname", L"");
387 else
388 com.Replace(L"%mname", L'"' + mergedfile.GetUIFileOrDirectoryName() + L'"');
390 else
391 com.Replace(L"%mname", L'"' + mergedname + L'"');
393 com.Replace(L"%wtroot", L'"' + g_Git.m_CurrentDir + L'"');
395 if ((bReadOnly)&&(bInternal))
396 com += L" /readonly";
398 if(!LaunchApplication(com, IDS_ERR_EXTMERGESTART, false))
400 return FALSE;
403 return TRUE;
406 BOOL CAppUtils::StartExtPatch(const CTGitPath& patchfile, const CTGitPath& dir, const CString& sOriginalDescription, const CString& sPatchedDescription, BOOL bReversed, BOOL bWait)
408 CString viewer;
409 // use TortoiseGitMerge
410 viewer = CPathUtils::GetAppDirectory();
411 viewer += L"TortoiseGitMerge.exe";
413 viewer = L'"' + viewer + L'"';
414 viewer = viewer + L" /diff:\"" + patchfile.GetWinPathString() + L'"';
415 viewer = viewer + L" /patchpath:\"" + dir.GetWinPathString() + L'"';
416 if (bReversed)
417 viewer += L" /reversedpatch";
418 if (!sOriginalDescription.IsEmpty())
419 viewer = viewer + L" /patchoriginal:\"" + sOriginalDescription + L'"';
420 if (!sPatchedDescription.IsEmpty())
421 viewer = viewer + L" /patchpatched:\"" + sPatchedDescription + L'"';
422 if (!g_sGroupingUUID.IsEmpty())
424 viewer += L" /groupuuid:\"";
425 viewer += g_sGroupingUUID;
426 viewer += L'"';
428 if(!LaunchApplication(viewer, IDS_ERR_DIFFVIEWSTART, !!bWait))
429 return FALSE;
430 return TRUE;
433 CString CAppUtils::PickDiffTool(const CTGitPath& file1, const CTGitPath& file2)
435 CString difftool = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + file2.GetFilename().MakeLower());
436 if (!difftool.IsEmpty())
437 return difftool;
438 difftool = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + file1.GetFilename().MakeLower());
439 if (!difftool.IsEmpty())
440 return difftool;
442 // Is there an extension specific diff tool?
443 CString ext = file2.GetFileExtension().MakeLower();
444 if (!ext.IsEmpty())
446 difftool = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + ext);
447 if (!difftool.IsEmpty())
448 return difftool;
449 // Maybe we should use TortoiseIDiff?
450 if ((ext == L".jpg") || (ext == L".jpeg") ||
451 (ext == L".bmp") || (ext == L".gif") ||
452 (ext == L".png") || (ext == L".ico") ||
453 (ext == L".tif") || (ext == L".tiff") ||
454 (ext == L".dib") || (ext == L".emf") ||
455 (ext == L".cur"))
457 return
458 L'"' + CPathUtils::GetAppDirectory() + L"TortoiseGitIDiff.exe" + L'"' +
459 L" /left:%base /right:%mine /lefttitle:%bname /righttitle:%yname" +
460 L" /groupuuid:\"" + g_sGroupingUUID + L'"';
464 // Finally, pick a generic external diff tool
465 difftool = CRegString(L"Software\\TortoiseGit\\Diff");
466 return difftool;
469 bool CAppUtils::StartExtDiff(
470 const CString& file1, const CString& file2,
471 const CString& sName1, const CString& sName2,
472 const CString& originalFile1, const CString& originalFile2,
473 const CString& hash1, const CString& hash2,
474 const DiffFlags& flags, int jumpToLine)
476 CString viewer;
478 CRegDWORD blamediff(L"Software\\TortoiseGit\\DiffBlamesWithTortoiseMerge", FALSE);
479 if (!flags.bBlame || !(DWORD)blamediff)
481 viewer = PickDiffTool(file1, file2);
482 // If registry entry for a diff program is commented out, use TortoiseGitMerge.
483 bool bCommentedOut = CStringUtils::StartsWith(viewer, L"#");
484 if (flags.bAlternativeTool)
486 // Invert external vs. internal diff tool selection.
487 if (bCommentedOut)
488 viewer.Delete(0); // uncomment
489 else
490 viewer.Empty();
492 else if (bCommentedOut)
493 viewer.Empty();
496 bool bInternal = viewer.IsEmpty();
497 if (bInternal)
499 viewer =
500 L'"' + CPathUtils::GetAppDirectory() + L"TortoiseGitMerge.exe" + L'"' +
501 L" /base:%base /mine:%mine /basename:%bname /minename:%yname" +
502 L" /basereflectedname:%bpath /minereflectedname:%ypath";
503 if (!g_sGroupingUUID.IsEmpty())
505 viewer += L" /groupuuid:\"";
506 viewer += g_sGroupingUUID;
507 viewer += L'"';
509 if (flags.bBlame)
510 viewer += L" /blame";
512 // check if the params are set. If not, just add the files to the command line
513 if ((viewer.Find(L"%base") < 0) && (viewer.Find(L"%mine") < 0))
515 viewer += L" \"" + file1 + L'"';
516 viewer += L" \"" + file2 + L'"';
518 if (viewer.Find(L"%base") >= 0)
519 viewer.Replace(L"%base", L'"' + file1 + L'"');
520 if (viewer.Find(L"%mine") >= 0)
521 viewer.Replace(L"%mine", L'"' + file2 + L'"');
523 if (sName1.IsEmpty())
524 viewer.Replace(L"%bname", L'"' + file1 + L'"');
525 else
526 viewer.Replace(L"%bname", L'"' + sName1 + L'"');
528 if (sName2.IsEmpty())
529 viewer.Replace(L"%yname", L'"' + file2 + L'"');
530 else
531 viewer.Replace(L"%yname", L'"' + sName2 + L'"');
533 viewer.Replace(L"%bpath", L'"' + originalFile1 + L'"');
534 viewer.Replace(L"%ypath", L'"' + originalFile2 + L'"');
536 viewer.Replace(L"%brev", L'"' + hash1 + L'"');
537 viewer.Replace(L"%yrev", L'"' + hash2 + L'"');
539 viewer.Replace(L"%wtroot", L'"' + g_Git.m_CurrentDir + L'"');
541 if (flags.bReadOnly && bInternal)
542 viewer += L" /readonly";
544 if (jumpToLine > 0)
545 viewer.AppendFormat(L" /line:%d", jumpToLine);
547 return LaunchApplication(viewer, IDS_ERR_EXTDIFFSTART, flags.bWait);
550 BOOL CAppUtils::StartUnifiedDiffViewer(const CString& patchfile, const CString& title, BOOL bWait, bool bAlternativeTool)
552 CString viewer;
553 CRegString v = CRegString(L"Software\\TortoiseGit\\DiffViewer");
554 viewer = v;
556 // If registry entry for a diff program is commented out, use TortoiseGitMerge.
557 bool bCommentedOut = CStringUtils::StartsWith(viewer, L"#");
558 if (bAlternativeTool)
560 // Invert external vs. internal diff tool selection.
561 if (bCommentedOut)
562 viewer.Delete(0); // uncomment
563 else
564 viewer.Empty();
566 else if (bCommentedOut)
567 viewer.Empty();
569 if (viewer.IsEmpty())
571 // use TortoiseGitUDiff
572 viewer = CPathUtils::GetAppDirectory();
573 viewer += L"TortoiseGitUDiff.exe";
574 // enquote the path to TortoiseGitUDiff
575 viewer = L'"' + viewer + L'"';
576 // add the params
577 viewer = viewer + L" /patchfile:%1 /title:\"%title\"";
578 if (!g_sGroupingUUID.IsEmpty())
580 viewer += L" /groupuuid:\"";
581 viewer += g_sGroupingUUID;
582 viewer += L'"';
585 if (viewer.Find(L"%1") >= 0)
587 if (viewer.Find(L"\"%1\"") >= 0)
588 viewer.Replace(L"%1", patchfile);
589 else
590 viewer.Replace(L"%1", L'"' + patchfile + L'"');
592 else
593 viewer += L" \"" + patchfile + L'"';
594 if (viewer.Find(L"%title") >= 0)
595 viewer.Replace(L"%title", title);
597 if(!LaunchApplication(viewer, IDS_ERR_DIFFVIEWSTART, !!bWait))
598 return FALSE;
599 return TRUE;
602 BOOL CAppUtils::StartTextViewer(CString file)
604 CString viewer;
605 CRegString txt = CRegString(L".txt\\", L"", FALSE, HKEY_CLASSES_ROOT);
606 viewer = txt;
607 viewer = viewer + L"\\Shell\\Open\\Command\\";
608 CRegString txtexe = CRegString(viewer, L"", FALSE, HKEY_CLASSES_ROOT);
609 viewer = txtexe;
611 DWORD len = ExpandEnvironmentStrings(viewer, nullptr, 0);
612 auto buf = std::make_unique<TCHAR[]>(len + 1);
613 ExpandEnvironmentStrings(viewer, buf.get(), len);
614 viewer = buf.get();
615 len = ExpandEnvironmentStrings(file, nullptr, 0);
616 auto buf2 = std::make_unique<TCHAR[]>(len + 1);
617 ExpandEnvironmentStrings(file, buf2.get(), len);
618 file = buf2.get();
619 file = L'"' + file + L'"';
620 if (viewer.IsEmpty())
621 return CAppUtils::ShowOpenWithDialog(file) ? TRUE : FALSE;
622 if (viewer.Find(L"\"%1\"") >= 0)
623 viewer.Replace(L"\"%1\"", file);
624 else if (viewer.Find(L"%1") >= 0)
625 viewer.Replace(L"%1", file);
626 else
627 viewer += L' ';
628 viewer += file;
630 if(!LaunchApplication(viewer, IDS_ERR_TEXTVIEWSTART, false))
631 return FALSE;
632 return TRUE;
635 BOOL CAppUtils::CheckForEmptyDiff(const CTGitPath& sDiffPath)
637 DWORD length = 0;
638 CAutoFile hFile = ::CreateFile(sDiffPath.GetWinPath(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
639 if (!hFile)
640 return TRUE;
641 length = ::GetFileSize(hFile, nullptr);
642 if (length < 4)
643 return TRUE;
644 return FALSE;
647 CString CAppUtils::GetLogFontName()
649 return (CString)CRegString(L"Software\\TortoiseGit\\LogFontName", L"Consolas");
652 DWORD CAppUtils::GetLogFontSize()
654 return (DWORD)CRegDWORD(L"Software\\TortoiseGit\\LogFontSize", 9);
657 void CAppUtils::CreateFontForLogs(CFont& fontToCreate)
659 LOGFONT logFont;
660 HDC hScreenDC = ::GetDC(nullptr);
661 logFont.lfHeight = -MulDiv(GetLogFontSize(), GetDeviceCaps(hScreenDC, LOGPIXELSY), 72);
662 ::ReleaseDC(nullptr, hScreenDC);
663 logFont.lfWidth = 0;
664 logFont.lfEscapement = 0;
665 logFont.lfOrientation = 0;
666 logFont.lfWeight = FW_NORMAL;
667 logFont.lfItalic = 0;
668 logFont.lfUnderline = 0;
669 logFont.lfStrikeOut = 0;
670 logFont.lfCharSet = DEFAULT_CHARSET;
671 logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
672 logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
673 logFont.lfQuality = DRAFT_QUALITY;
674 logFont.lfPitchAndFamily = FF_DONTCARE | FIXED_PITCH;
675 wcscpy_s(logFont.lfFaceName, 32, (LPCTSTR)GetLogFontName());
676 VERIFY(fontToCreate.CreateFontIndirect(&logFont));
679 bool CAppUtils::LaunchPAgent(const CString* keyfile, const CString* pRemote)
681 CString key,remote;
682 CString cmd,out;
683 if (!pRemote)
684 remote = L"origin";
685 else
686 remote=*pRemote;
688 if (!keyfile)
690 cmd.Format(L"remote.%s.puttykeyfile", (LPCTSTR)remote);
691 key = g_Git.GetConfigValue(cmd);
693 else
694 key=*keyfile;
696 if(key.IsEmpty())
697 return false;
699 CString proc=CPathUtils::GetAppDirectory();
700 proc += L"pageant.exe \"";
701 proc += key;
702 proc += L'"';
704 CString tempfile = GetTempFile();
705 ::DeleteFile(tempfile);
707 proc += L" -c \"";
708 proc += CPathUtils::GetAppDirectory();
709 proc += L"tgittouch.exe\"";
710 proc += L" \"";
711 proc += tempfile;
712 proc += L'"';
714 CString appDir = CPathUtils::GetAppDirectory();
715 bool b = LaunchApplication(proc, IDS_ERR_PAGEANT, true, &appDir);
716 if(!b)
717 return b;
719 int i=0;
720 while(!::PathFileExists(tempfile))
722 Sleep(100);
723 ++i;
724 if(i>10*60*5)
725 break; //timeout 5 minutes
728 if( i== 10*60*5)
729 CMessageBox::Show(nullptr, IDS_ERR_PAEGENTTIMEOUT, IDS_APPNAME, MB_OK | MB_ICONERROR);
730 ::DeleteFile(tempfile);
731 return true;
734 bool CAppUtils::LaunchAlternativeEditor(const CString& filename, bool uac)
736 CString editTool = CRegString(L"Software\\TortoiseGit\\AlternativeEditor");
737 if (editTool.IsEmpty() || CStringUtils::StartsWith(editTool, L"#"))
738 editTool = CPathUtils::GetAppDirectory() + L"notepad2.exe";
740 CString sCmd;
741 sCmd.Format(L"\"%s\" \"%s\"", (LPCTSTR)editTool, (LPCTSTR)filename);
743 LaunchApplication(sCmd, 0, false, nullptr, uac);
744 return true;
747 bool CAppUtils::LaunchRemoteSetting()
749 CTGitPath path(g_Git.m_CurrentDir);
750 CSettings dlg(IDS_PROC_SETTINGS_TITLE, &path);
751 dlg.SetTreeViewMode(TRUE, TRUE, TRUE);
752 dlg.SetTreeWidth(220);
753 dlg.m_DefaultPage = L"gitremote";
755 dlg.DoModal();
756 dlg.HandleRestart();
757 return true;
761 * Launch the external blame viewer
763 bool CAppUtils::LaunchTortoiseBlame(const CString& sBlameFile, const CString& Rev, const CString& sParams)
765 CString viewer = L'"' + CPathUtils::GetAppDirectory();
766 viewer += L"TortoiseGitBlame.exe";
767 viewer += L"\" \"" + sBlameFile + L'"';
768 //viewer += L" \"" + sLogFile + L'"';
769 //viewer += L" \"" + sOriginalFile + L'"';
770 if(!Rev.IsEmpty() && Rev != GIT_REV_ZERO)
771 viewer += L" /rev:" + Rev;
772 if (!g_sGroupingUUID.IsEmpty())
774 viewer += L" /groupuuid:\"";
775 viewer += g_sGroupingUUID;
776 viewer += L'"';
778 viewer += L' ' + sParams;
780 return LaunchApplication(viewer, IDS_ERR_TGITBLAME, false);
783 bool CAppUtils::FormatTextInRichEditControl(CWnd * pWnd)
785 CString sText;
786 if (!pWnd)
787 return false;
788 bool bStyled = false;
789 pWnd->GetWindowText(sText);
790 // the rich edit control doesn't count the CR char!
791 // to be exact: CRLF is treated as one char.
792 sText.Remove(L'\r');
794 // style each line separately
795 int offset = 0;
796 int nNewlinePos;
799 nNewlinePos = sText.Find('\n', offset);
800 CString sLine = nNewlinePos >= 0 ? sText.Mid(offset, nNewlinePos - offset) : sText.Mid(offset);
802 int start = 0;
803 int end = 0;
804 while (FindStyleChars(sLine, '*', start, end))
806 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
807 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
808 SetCharFormat(pWnd, CFM_BOLD, CFE_BOLD);
809 bStyled = true;
810 start = end;
812 start = 0;
813 end = 0;
814 while (FindStyleChars(sLine, '^', start, end))
816 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
817 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
818 SetCharFormat(pWnd, CFM_ITALIC, CFE_ITALIC);
819 bStyled = true;
820 start = end;
822 start = 0;
823 end = 0;
824 while (FindStyleChars(sLine, '_', start, end))
826 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
827 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
828 SetCharFormat(pWnd, CFM_UNDERLINE, CFE_UNDERLINE);
829 bStyled = true;
830 start = end;
832 offset = nNewlinePos+1;
833 } while(nNewlinePos>=0);
834 return bStyled;
837 bool CAppUtils::FindStyleChars(const CString& sText, TCHAR stylechar, int& start, int& end)
839 int i=start;
840 int last = sText.GetLength() - 1;
841 bool bFoundMarker = false;
842 TCHAR c = i == 0 ? L'\0' : sText[i - 1];
843 TCHAR nextChar = i >= last ? L'\0' : sText[i + 1];
845 // find a starting marker
846 while (i < last)
848 TCHAR prevChar = c;
849 c = nextChar;
850 nextChar = sText[i + 1];
852 // IsCharAlphaNumeric can be somewhat expensive.
853 // Long lines of "*****" or "----" will be pre-empted efficiently
854 // by the (c != nextChar) condition.
856 if ((c == stylechar) && (c != nextChar))
858 if (IsCharAlphaNumeric(nextChar) && !IsCharAlphaNumeric(prevChar))
860 start = ++i;
861 bFoundMarker = true;
862 break;
865 ++i;
867 if (!bFoundMarker)
868 return false;
870 // find ending marker
871 // c == sText[i - 1]
873 bFoundMarker = false;
874 while (i <= last)
876 TCHAR prevChar = c;
877 c = sText[i];
878 if (c == stylechar)
880 if ((i == last) || (!IsCharAlphaNumeric(sText[i + 1]) && IsCharAlphaNumeric(prevChar)))
882 end = i;
883 ++i;
884 bFoundMarker = true;
885 break;
888 ++i;
890 return bFoundMarker;
893 // from CSciEdit
894 namespace {
895 bool IsValidURLChar(wchar_t ch)
897 return iswalnum(ch) ||
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'+' ||
900 ch == L'|' || ch == L'>' || ch == L'<' || ch == L'!' || ch == L'@' || ch == L'~';
903 bool IsUrlOrEmail(const CString& sText)
905 if (!PathIsURLW(sText))
907 auto atpos = sText.Find(L'@');
908 if (atpos <= 0)
909 return false;
910 if (sText.Find(L'.', atpos) <= atpos + 1) // a dot must follow after the @, but not directly after it
911 return false;
912 if (sText.Find(L':', atpos) < 0) // do not detect git@example.com:something as an email address
913 return true;
914 return false;
916 for (const CString& prefix : { L"http://", L"https://", L"git://", L"ftp://", L"file://", L"mailto:" })
918 if (CStringUtils::StartsWith(sText, prefix) && sText.GetLength() != prefix.GetLength())
919 return true;
921 return false;
925 BOOL CAppUtils::StyleURLs(const CString& msg, CWnd* pWnd)
927 std::vector<CHARRANGE> positions = FindURLMatches(msg);
928 CAppUtils::SetCharFormat(pWnd, CFM_LINK, CFE_LINK, positions);
930 return positions.empty() ? FALSE : TRUE;
934 * implements URL searching with the same logic as CSciEdit::StyleURLs
936 std::vector<CHARRANGE> CAppUtils::FindURLMatches(const CString& msg)
938 std::vector<CHARRANGE> result;
940 int len = msg.GetLength();
941 int starturl = -1;
943 for (int i = 0; i <= msg.GetLength(); ++i)
945 if ((i < len) && IsValidURLChar(msg[i]))
947 if (starturl < 0)
948 starturl = i;
950 else
952 if (starturl >= 0)
954 bool strip = true;
955 if (msg[starturl] == '<' && i < len) // try to detect and do not strip URLs put within <>
957 while (starturl <= i && msg[starturl] == '<') // strip leading '<'
958 ++starturl;
959 strip = false;
960 i = starturl;
961 while (i < len && msg[i] != '\r' && msg[i] != '\n' && msg[i] != '>') // find first '>' or new line after resetting i to start position
962 ++i;
965 int skipTrailing = 0;
966 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] == '!'))
967 ++skipTrailing;
969 if (!IsUrlOrEmail(msg.Mid(starturl, i - starturl - skipTrailing)))
971 starturl = -1;
972 continue;
975 CHARRANGE range = { starturl, i - skipTrailing };
976 result.push_back(range);
978 starturl = -1;
982 return result;
985 bool CAppUtils::StartShowUnifiedDiff(HWND hWnd, const CTGitPath& url1, const CString& rev1,
986 const CTGitPath& /*url2*/, const CString& rev2,
987 //const GitRev& peg /* = GitRev */, const GitRev& headpeg /* = GitRev */,
988 bool bAlternateDiff /* = false */, bool /*bIgnoreAncestry*/ /* = false */,
989 bool /* blame = false */,
990 bool bMerge,
991 bool bCombine,
992 bool bNoPrefix)
994 int diffContext = g_Git.GetConfigValueInt32(L"diff.context", -1);
995 CString tempfile=GetTempFile();
996 if (g_Git.GetUnifiedDiff(url1, rev1, rev2, tempfile, bMerge, bCombine, diffContext, bNoPrefix))
998 MessageBox(hWnd, g_Git.GetGitLastErr(L"Could not get unified diff.", CGit::GIT_CMD_DIFF), L"TortoiseGit", MB_OK);
999 return false;
1001 CAppUtils::StartUnifiedDiffViewer(tempfile, rev1.IsEmpty() ? rev2 : rev1 + L':' + rev2, FALSE, bAlternateDiff);
1003 #if 0
1004 CString sCmd;
1005 sCmd.Format(L"%s /command:showcompare /unified",
1006 (LPCTSTR)(CPathUtils::GetAppDirectory()+L"TortoiseGitProc.exe"));
1007 sCmd += L" /url1:\"" + url1.GetGitPathString() + L'"';
1008 if (rev1.IsValid())
1009 sCmd += L" /revision1:" + rev1.ToString();
1010 sCmd += L" /url2:\"" + url2.GetGitPathString() + L'"';
1011 if (rev2.IsValid())
1012 sCmd += L" /revision2:" + rev2.ToString();
1013 if (peg.IsValid())
1014 sCmd += L" /pegrevision:" + peg.ToString();
1015 if (headpeg.IsValid())
1016 sCmd += L" /headpegrevision:" + headpeg.ToString();
1018 if (bAlternateDiff)
1019 sCmd += L" /alternatediff";
1021 if (bIgnoreAncestry)
1022 sCmd += L" /ignoreancestry";
1024 if (hWnd)
1026 sCmd += L" /hwnd:";
1027 TCHAR buf[30];
1028 swprintf_s(buf, 30, L"%p", (void*)hWnd);
1029 sCmd += buf;
1032 return CAppUtils::LaunchApplication(sCmd, 0, false);
1033 #endif
1034 return TRUE;
1037 bool CAppUtils::SetupDiffScripts(bool force, const CString& type)
1039 CString scriptsdir = CPathUtils::GetAppParentDirectory();
1040 scriptsdir += L"Diff-Scripts";
1041 CSimpleFileFind files(scriptsdir);
1042 while (files.FindNextFileNoDirectories())
1044 CString file = files.GetFilePath();
1045 CString filename = files.GetFileName();
1046 CString ext = file.Mid(file.ReverseFind('-') + 1);
1047 ext = L"." + ext.Left(ext.ReverseFind(L'.'));
1048 std::set<CString> extensions;
1049 extensions.insert(ext);
1050 CString kind;
1051 if (CStringUtils::EndsWithI(file, L"vbs"))
1052 kind = L" //E:vbscript";
1053 if (CStringUtils::EndsWithI(file, L"js"))
1054 kind = L" //E:javascript";
1055 // open the file, read the first line and find possible extensions
1056 // this script can handle
1059 CStdioFile f(file, CFile::modeRead | CFile::shareDenyNone);
1060 CString extline;
1061 if (f.ReadString(extline))
1063 if ((extline.GetLength() > 15 ) &&
1064 (CStringUtils::StartsWith(extline, L"// extensions: ") ||
1065 CStringUtils::StartsWith(extline, L"' extensions: ")))
1067 if (extline[0] == '/')
1068 extline = extline.Mid(15);
1069 else
1070 extline = extline.Mid(14);
1071 CString sToken;
1072 int curPos = 0;
1073 sToken = extline.Tokenize(L";", curPos);
1074 while (!sToken.IsEmpty())
1076 if (!sToken.IsEmpty())
1078 if (sToken[0] != '.')
1079 sToken = L"." + sToken;
1080 extensions.insert(sToken);
1082 sToken = extline.Tokenize(L";", curPos);
1086 f.Close();
1088 catch (CFileException* e)
1090 e->Delete();
1093 for (const auto& extension : extensions)
1095 if (type.IsEmpty() || (type.Compare(L"Diff") == 0))
1097 if (CStringUtils::StartsWithI(filename, L"diff-"))
1099 CRegString diffreg = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + extension);
1100 CString diffregstring = diffreg;
1101 if (force || (diffregstring.IsEmpty()) || (diffregstring.Find(filename) >= 0))
1102 diffreg = L"wscript.exe \"" + file + L"\" %base %mine" + kind;
1105 if (type.IsEmpty() || (type.Compare(L"Merge") == 0))
1107 if (CStringUtils::StartsWithI(filename, L"merge-"))
1109 CRegString diffreg = CRegString(L"Software\\TortoiseGit\\MergeTools\\" + extension);
1110 CString diffregstring = diffreg;
1111 if (force || (diffregstring.IsEmpty()) || (diffregstring.Find(filename) >= 0))
1112 diffreg = L"wscript.exe \"" + file + L"\" %merged %theirs %mine %base" + kind;
1118 return true;
1121 bool CAppUtils::Export(const CString* BashHash, const CTGitPath* orgPath)
1123 // ask from where the export has to be done
1124 CExportDlg dlg;
1125 if(BashHash)
1126 dlg.m_initialRefName=*BashHash;
1127 if (orgPath)
1129 if (PathIsRelative(orgPath->GetWinPath()))
1130 dlg.m_orgPath = g_Git.CombinePath(orgPath);
1131 else
1132 dlg.m_orgPath = *orgPath;
1135 if (dlg.DoModal() == IDOK)
1137 CString cmd;
1138 cmd.Format(L"git.exe archive --output=\"%s\" --format=zip --verbose %s --",
1139 (LPCTSTR)dlg.m_strFile, (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName));
1141 CProgressDlg pro;
1142 pro.m_GitCmd=cmd;
1143 pro.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1145 if (status)
1146 return;
1147 postCmdList.emplace_back(IDI_EXPLORER, IDS_STATUSLIST_CONTEXT_EXPLORE, [&]{ CAppUtils::ExploreTo(hWndExplorer, dlg.m_strFile); });
1150 CGit git;
1151 if (!dlg.m_bWholeProject && !dlg.m_orgPath.IsEmpty() && PathIsDirectory(dlg.m_orgPath.GetWinPathString()))
1153 git.m_CurrentDir = dlg.m_orgPath.GetWinPathString();
1154 pro.m_Git = &git;
1156 return (pro.DoModal() == IDOK);
1158 return false;
1161 bool CAppUtils::UpdateBranchDescription(const CString& branch, CString description)
1163 if (branch.IsEmpty())
1164 return false;
1166 CString key;
1167 key.Format(L"branch.%s.description", (LPCTSTR)branch);
1168 description.Remove(L'\r');
1169 description.Trim();
1170 if (description.IsEmpty())
1171 g_Git.UnsetConfigValue(key);
1172 else
1173 g_Git.SetConfigValue(key, description);
1175 return true;
1178 bool CAppUtils::CreateBranchTag(bool isTag /*true*/, const CString* commitHash /*nullptr*/, bool switchNewBranch /*false*/, LPCTSTR name /*nullptr*/)
1180 CCreateBranchTagDlg dlg;
1181 dlg.m_bIsTag = isTag;
1182 dlg.m_bSwitch = switchNewBranch;
1184 if (commitHash)
1185 dlg.m_initialRefName = *commitHash;
1187 if (name)
1188 dlg.m_BranchTagName = name;
1190 if(dlg.DoModal()==IDOK)
1192 CString cmd;
1193 CString force;
1194 CString track;
1195 if(dlg.m_bTrack == TRUE)
1196 track = L"--track";
1197 else if(dlg.m_bTrack == FALSE)
1198 track = L"--no-track";
1200 if(dlg.m_bForce)
1201 force = L"-f";
1203 if (isTag)
1205 CString sign;
1206 if(dlg.m_bSign)
1207 sign = L"-s";
1209 cmd.Format(L"git.exe tag %s %s %s %s",
1210 (LPCTSTR)force,
1211 (LPCTSTR)sign,
1212 (LPCTSTR)dlg.m_BranchTagName,
1213 (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName)
1216 if(!dlg.m_Message.Trim().IsEmpty())
1218 CString tempfile = ::GetTempFile();
1219 if (CAppUtils::SaveCommitUnicodeFile(tempfile, dlg.m_Message))
1221 MessageBox(nullptr, L"Could not save tag message", L"TortoiseGit", MB_OK | MB_ICONERROR);
1222 return FALSE;
1224 cmd += L" -F " + tempfile;
1227 else
1229 cmd.Format(L"git.exe branch %s %s %s %s",
1230 (LPCTSTR)track,
1231 (LPCTSTR)force,
1232 (LPCTSTR)dlg.m_BranchTagName,
1233 (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName)
1236 CString out;
1237 if(g_Git.Run(cmd,&out,CP_UTF8))
1239 MessageBox(nullptr, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
1240 return FALSE;
1242 if (!isTag && dlg.m_bSwitch)
1244 // it is a new branch and the user has requested to switch to it
1245 PerformSwitch(dlg.m_BranchTagName);
1247 if (!isTag && !dlg.m_Message.IsEmpty())
1248 UpdateBranchDescription(dlg.m_BranchTagName, dlg.m_Message);
1250 return TRUE;
1252 return FALSE;
1255 bool CAppUtils::Switch(const CString& initialRefName)
1257 CGitSwitchDlg dlg;
1258 if(!initialRefName.IsEmpty())
1259 dlg.m_initialRefName = initialRefName;
1261 if (dlg.DoModal() == IDOK)
1263 CString branch;
1264 if (dlg.m_bBranch)
1265 branch = dlg.m_NewBranch;
1267 // if refs/heads/ is not stripped, checkout will detach HEAD
1268 // checkout prefers branches on name clashes (with tags)
1269 if (CStringUtils::StartsWith(dlg.m_VersionName, L"refs/heads/") && dlg.m_bBranchOverride != TRUE)
1270 dlg.m_VersionName = dlg.m_VersionName.Mid(11);
1272 return PerformSwitch(dlg.m_VersionName, dlg.m_bForce == TRUE , branch, dlg.m_bBranchOverride == TRUE, dlg.m_bTrack, dlg.m_bMerge == TRUE);
1274 return FALSE;
1277 bool CAppUtils::PerformSwitch(const CString& ref, bool bForce /* false */, const CString& sNewBranch /* CString() */, bool bBranchOverride /* false */, BOOL bTrack /* 2 */, bool bMerge /* false */)
1279 CString cmd;
1280 CString track;
1281 CString force;
1282 CString branch;
1283 CString merge;
1285 if(!sNewBranch.IsEmpty()){
1286 if (bBranchOverride)
1287 branch.Format(L"-B %s ", (LPCTSTR)sNewBranch);
1288 else
1289 branch.Format(L"-b %s ", (LPCTSTR)sNewBranch);
1290 if (bTrack == TRUE)
1291 track = L"--track ";
1292 else if (bTrack == FALSE)
1293 track = L"--no-track ";
1295 if (bForce)
1296 force = L"-f ";
1297 if (bMerge)
1298 merge = L"--merge ";
1300 cmd.Format(L"git.exe checkout %s%s%s%s%s --",
1301 (LPCTSTR)force,
1302 (LPCTSTR)track,
1303 (LPCTSTR)merge,
1304 (LPCTSTR)branch,
1305 (LPCTSTR)g_Git.FixBranchName(ref));
1307 CProgressDlg progress;
1308 progress.m_GitCmd = cmd;
1310 CString currentBranch;
1311 bool hasBranch = CGit::GetCurrentBranchFromFile(g_Git.m_CurrentDir, currentBranch) == 0;
1312 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1314 if (!status)
1316 CTGitPath gitPath = g_Git.m_CurrentDir;
1317 if (gitPath.HasSubmodules())
1319 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1321 CString sCmd;
1322 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
1323 RunTortoiseGitProc(sCmd);
1326 if (hasBranch)
1327 postCmdList.emplace_back(IDI_MERGE, IDS_MENUMERGE, [&]{ Merge(&currentBranch); });
1330 CString newBranch;
1331 if (!CGit::GetCurrentBranchFromFile(g_Git.m_CurrentDir, newBranch))
1332 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&]{ Pull(); });
1334 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUCOMMIT, []{
1335 CTGitPathList pathlist;
1336 CTGitPathList selectedlist;
1337 pathlist.AddPath(CTGitPath());
1338 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(L"Software\\TortoiseGit\\SelectFilesForCommit", TRUE));
1339 CString str;
1340 Commit(CString(), false, str, pathlist, selectedlist, bSelectFilesForCommit);
1343 else
1345 if (bMerge && g_Git.HasWorkingTreeConflicts() > 0)
1347 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
1349 CString sCmd;
1350 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
1351 CAppUtils::RunTortoiseGitProc(sCmd);
1354 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ PerformSwitch(ref, bForce, sNewBranch, bBranchOverride, bTrack, bMerge); });
1355 if (!bMerge)
1356 postCmdList.emplace_back(IDI_SWITCH, IDS_SWITCH_WITH_MERGE, [&]{ PerformSwitch(ref, bForce, sNewBranch, bBranchOverride, bTrack, true); });
1359 progress.m_PostExecCallback = [&](DWORD& exitCode, CString& extraMsg)
1361 if (bMerge && !exitCode && g_Git.HasWorkingTreeConflicts() > 0)
1363 exitCode = 1; // Treat it as failure
1364 extraMsg = L"Has merge conflict";
1368 INT_PTR ret = progress.DoModal();
1370 return ret == IDOK;
1373 class CIgnoreFile : public CStdioFile
1375 public:
1376 STRING_VECTOR m_Items;
1377 CString m_eol;
1379 virtual BOOL ReadString(CString& rString)
1381 if (GetPosition() == 0)
1383 unsigned char utf8bom[] = { 0xEF, 0xBB, 0xBF };
1384 char buf[3] = { 0, 0, 0 };
1385 Read(buf, 3);
1386 if (memcpy(buf, utf8bom, sizeof(utf8bom)))
1387 SeekToBegin();
1390 CStringA strA;
1391 char lastChar = '\0';
1392 for (char c = '\0'; Read(&c, 1) == 1; lastChar = c)
1394 if (c == '\r')
1395 continue;
1396 if (c == '\n')
1398 m_eol = lastChar == L'\r' ? L"\r\n" : L"\n";
1399 break;
1401 strA.AppendChar(c);
1403 if (strA.IsEmpty())
1404 return FALSE;
1406 rString = CUnicodeUtils::GetUnicode(strA);
1407 return TRUE;
1410 void ResetState()
1412 m_Items.clear();
1413 m_eol.Empty();
1417 bool CAppUtils::OpenIgnoreFile(CIgnoreFile &file, const CString& filename)
1419 file.ResetState();
1420 if (!file.Open(filename, CFile::modeCreate | CFile::modeReadWrite | CFile::modeNoTruncate | CFile::typeBinary))
1422 MessageBox(nullptr, filename + L" Open Failure", L"TortoiseGit", MB_OK | MB_ICONERROR);
1423 return false;
1426 if (file.GetLength() > 0)
1428 CString fileText;
1429 while (file.ReadString(fileText))
1430 file.m_Items.push_back(fileText);
1431 file.Seek(file.GetLength() - 1, 0);
1432 char lastchar[1] = { 0 };
1433 file.Read(lastchar, 1);
1434 file.SeekToEnd();
1435 if (lastchar[0] != '\n')
1437 CStringA eol = CStringA(file.m_eol.IsEmpty() ? L"\n" : file.m_eol);
1438 file.Write(eol, eol.GetLength());
1441 else
1442 file.SeekToEnd();
1444 return true;
1447 bool CAppUtils::IgnoreFile(const CTGitPathList& path,bool IsMask)
1449 CIgnoreDlg ignoreDlg;
1450 if (ignoreDlg.DoModal() == IDOK)
1452 CString ignorefile;
1453 ignorefile = g_Git.m_CurrentDir + L'\\';
1455 switch (ignoreDlg.m_IgnoreFile)
1457 case 0:
1458 ignorefile += L".gitignore";
1459 break;
1460 case 2:
1461 GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, ignorefile);
1462 ignorefile += L"info";
1463 if (!PathFileExists(ignorefile))
1464 CreateDirectory(ignorefile, nullptr);
1465 ignorefile += L"\\exclude";
1466 break;
1469 CIgnoreFile file;
1472 if (ignoreDlg.m_IgnoreFile != 1 && !OpenIgnoreFile(file, ignorefile))
1473 return false;
1475 for (int i = 0; i < path.GetCount(); ++i)
1477 if (ignoreDlg.m_IgnoreFile == 1)
1479 ignorefile = g_Git.CombinePath(path[i].GetContainingDirectory()) + L"\\.gitignore";
1480 if (!OpenIgnoreFile(file, ignorefile))
1481 return false;
1484 CString ignorePattern;
1485 if (ignoreDlg.m_IgnoreType == 0)
1487 if (ignoreDlg.m_IgnoreFile != 1 && !path[i].GetContainingDirectory().GetGitPathString().IsEmpty())
1488 ignorePattern += L'/' + path[i].GetContainingDirectory().GetGitPathString();
1490 ignorePattern += L'/';
1492 if (IsMask)
1493 ignorePattern += L'*' + path[i].GetFileExtension();
1494 else
1495 ignorePattern += path[i].GetFileOrDirectoryName();
1497 // escape [ and ] so that files get ignored correctly
1498 ignorePattern.Replace(L"[", L"\\[");
1499 ignorePattern.Replace(L"]", L"\\]");
1501 bool found = false;
1502 for (size_t j = 0; j < file.m_Items.size(); ++j)
1504 if (file.m_Items[j] == ignorePattern)
1506 found = true;
1507 break;
1510 if (!found)
1512 file.m_Items.push_back(ignorePattern);
1513 ignorePattern += file.m_eol.IsEmpty() ? L"\n" : file.m_eol;
1514 CStringA ignorePatternA = CUnicodeUtils::GetUTF8(ignorePattern);
1515 file.Write(ignorePatternA, ignorePatternA.GetLength());
1518 if (ignoreDlg.m_IgnoreFile == 1)
1519 file.Close();
1522 if (ignoreDlg.m_IgnoreFile != 1)
1523 file.Close();
1525 catch(...)
1527 file.Abort();
1528 return false;
1531 return true;
1533 return false;
1536 static bool Reset(const CString& resetTo, int resetType)
1538 CString cmd;
1539 CString type;
1540 switch (resetType)
1542 case 0:
1543 type = L"--soft";
1544 break;
1545 case 1:
1546 type = L"--mixed";
1547 break;
1548 case 2:
1549 type = L"--hard";
1550 break;
1551 case 3:
1553 CProgressDlg progress;
1554 progress.m_GitCmd = L"git.exe reset --merge";
1555 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1557 if (status)
1559 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [] { CAppUtils::MergeAbort(); });
1560 return;
1563 CTGitPath gitPath = g_Git.m_CurrentDir;
1564 if (gitPath.HasSubmodules() && resetType == 2)
1566 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1568 CString sCmd;
1569 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
1570 CAppUtils::RunTortoiseGitProc(sCmd);
1574 return progress.DoModal() == IDOK;
1576 default:
1577 ATLASSERT(false);
1578 resetType = 1;
1579 type = L"--mixed";
1580 break;
1582 cmd.Format(L"git.exe reset %s %s --", (LPCTSTR)type, (LPCTSTR)resetTo);
1584 CProgressDlg progress;
1585 progress.m_GitCmd = cmd;
1587 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1589 if (status)
1591 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ Reset(resetTo, resetType); });
1592 return;
1595 CTGitPath gitPath = g_Git.m_CurrentDir;
1596 if (gitPath.HasSubmodules() && resetType == 2)
1598 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1600 CString sCmd;
1601 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
1602 CAppUtils::RunTortoiseGitProc(sCmd);
1607 INT_PTR ret;
1608 if (g_Git.UsingLibGit2(CGit::GIT_CMD_RESET))
1610 CGitProgressDlg gitdlg;
1611 ResetProgressCommand resetProgressCommand;
1612 gitdlg.SetCommand(&resetProgressCommand);
1613 resetProgressCommand.m_PostCmdCallback = progress.m_PostCmdCallback;
1614 resetProgressCommand.SetRevision(resetTo);
1615 resetProgressCommand.SetResetType(resetType);
1616 ret = gitdlg.DoModal();
1618 else
1619 ret = progress.DoModal();
1621 return ret == IDOK;
1624 bool CAppUtils::GitReset(const CString* CommitHash, int type)
1626 CResetDlg dlg;
1627 dlg.m_ResetType=type;
1628 dlg.m_ResetToVersion=*CommitHash;
1629 dlg.m_initialRefName = *CommitHash;
1630 if (dlg.DoModal() == IDOK)
1631 return Reset(dlg.m_ResetToVersion, dlg.m_ResetType);
1633 return false;
1636 void CAppUtils::DescribeConflictFile(bool mode, bool base,CString &descript)
1638 if(mode == FALSE)
1640 descript.LoadString(IDS_SVNACTION_DELETE);
1641 return;
1643 if(base)
1645 descript.LoadString(IDS_SVNACTION_MODIFIED);
1646 return;
1648 descript.LoadString(IDS_PROC_CREATED);
1651 void CAppUtils::RemoveTempMergeFile(const CTGitPath& path)
1653 ::DeleteFile(CAppUtils::GetMergeTempFile(L"LOCAL", path));
1654 ::DeleteFile(CAppUtils::GetMergeTempFile(L"REMOTE", path));
1655 ::DeleteFile(CAppUtils::GetMergeTempFile(L"BASE", path));
1657 CString CAppUtils::GetMergeTempFile(const CString& type, const CTGitPath &merge)
1659 return g_Git.CombinePath(merge.GetWinPathString() + L'.' + type + merge.GetFileExtension());;
1662 static bool ParseHashesFromLsFile(const BYTE_VECTOR& out, CString& hash1, bool& isFile1, CString& hash2, bool& isFile2, CString& hash3, bool& isFile3)
1664 size_t pos = 0;
1665 CString one;
1666 CString part;
1668 while (pos < out.size())
1670 one.Empty();
1672 CGit::StringAppend(&one, &out[pos], CP_UTF8);
1673 int tabstart = 0;
1674 one.Tokenize(L"\t", tabstart);
1676 tabstart = 0;
1677 part = one.Tokenize(L" ", tabstart); //Tag
1678 CString mode = one.Tokenize(L" ", tabstart); //Mode
1679 part = one.Tokenize(L" ", tabstart); //Hash
1680 CString hash = part;
1681 part = one.Tokenize(L"\t", tabstart); //Stage
1682 int stage = _wtol(part);
1683 if (stage == 1)
1685 hash1 = hash;
1686 isFile1 = _wtol(mode) != 160000;
1688 else if (stage == 2)
1690 hash2 = hash;
1691 isFile2 = _wtol(mode) != 160000;
1693 else if (stage == 3)
1695 hash3 = hash;
1696 isFile3 = _wtol(mode) != 160000;
1697 return true;
1700 pos = out.findNextString(pos);
1703 return false;
1706 void CAppUtils::GetConflictTitles(CString* baseText, CString& mineText, CString& theirsText, bool rebaseActive)
1708 if (baseText)
1709 baseText->LoadString(IDS_PROC_DIFF_BASE);
1710 if (rebaseActive)
1712 CString adminDir;
1713 GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir);
1714 mineText = L"Branch being rebased onto";
1715 if (!CStringUtils::ReadStringFromTextFile(adminDir + L"tgitrebase.active\\onto", mineText))
1717 CGitHash hash;
1718 if (!g_Git.GetHash(hash, L"rebase-apply/onto"))
1719 g_Git.GuessRefForHash(mineText, hash);
1721 theirsText = L"Branch being rebased";
1722 if (!CStringUtils::ReadStringFromTextFile(adminDir + L"tgitrebase.active\\head-name", theirsText))
1724 if (CStringUtils::ReadStringFromTextFile(adminDir + L"rebase-apply/head-name", theirsText))
1725 theirsText = CGit::StripRefName(theirsText);
1727 return;
1730 static const struct {
1731 const wchar_t* headref;
1732 bool guessRef;
1733 UINT theirstext;
1734 } infotexts[] = { { L"MERGE_HEAD", true, IDS_CONFLICT_INFOTEXT }, { L"CHERRY_PICK_HEAD", false, IDS_CONFLICT_INFOTEXT }, { L"REVERT_HEAD", false, IDS_CONFLICT_REVERT } };
1735 mineText = L"HEAD";
1736 theirsText.LoadString(IDS_CONFLICT_REFTOBEMERGED);
1737 for (const auto& infotext : infotexts)
1739 CGitHash hash;
1740 if (!g_Git.GetHash(hash, infotext.headref))
1742 CString guessedRef;
1743 if (!infotext.guessRef)
1744 guessedRef = hash.ToString();
1745 else
1746 g_Git.GuessRefForHash(guessedRef, hash);
1747 theirsText.FormatMessage(infotext.theirstext, infotext.headref, (LPCTSTR)guessedRef);
1748 break;
1753 bool CAppUtils::ConflictEdit(CTGitPath& path, bool bAlternativeTool /*= false*/, bool isRebase /*= false*/, HWND resolveMsgHwnd /*= nullptr*/)
1755 CTGitPath merge=path;
1756 CTGitPath directory = merge.GetDirectory();
1758 BYTE_VECTOR vector;
1760 CString cmd;
1761 cmd.Format(L"git.exe ls-files -u -t -z -- \"%s\"", (LPCTSTR)merge.GetGitPathString());
1763 if (g_Git.Run(cmd, &vector))
1764 return FALSE;
1766 CString baseTitle, mineTitle, theirsTitle;
1767 GetConflictTitles(&baseTitle, mineTitle, theirsTitle, isRebase);
1769 CString baseHash, realBaseHash(GIT_REV_ZERO), localHash(GIT_REV_ZERO), remoteHash(GIT_REV_ZERO);
1770 bool baseIsFile = true, localIsFile = true, remoteIsFile = true;
1771 if (ParseHashesFromLsFile(vector, realBaseHash, baseIsFile, localHash, localIsFile, remoteHash, remoteIsFile))
1772 baseHash = realBaseHash;
1774 if (!baseIsFile || !localIsFile || !remoteIsFile)
1776 if (merge.HasAdminDir())
1778 CGit subgit;
1779 subgit.m_CurrentDir = g_Git.CombinePath(merge);
1780 CGitHash hash;
1781 subgit.GetHash(hash, L"HEAD");
1782 baseHash = hash;
1785 CGitDiff::ChangeType changeTypeMine = CGitDiff::Unknown;
1786 CGitDiff::ChangeType changeTypeTheirs = CGitDiff::Unknown;
1788 bool baseOK = false, mineOK = false, theirsOK = false;
1789 CString baseSubject, mineSubject, theirsSubject;
1790 if (merge.HasAdminDir())
1792 CGit subgit;
1793 subgit.m_CurrentDir = g_Git.CombinePath(merge);
1794 CGitDiff::GetSubmoduleChangeType(subgit, baseHash, localHash, baseOK, mineOK, changeTypeMine, baseSubject, mineSubject);
1795 CGitDiff::GetSubmoduleChangeType(subgit, baseHash, remoteHash, baseOK, theirsOK, changeTypeTheirs, baseSubject, theirsSubject);
1797 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)
1799 changeTypeMine = CGitDiff::Identical;
1800 changeTypeTheirs = CGitDiff::NewSubmodule;
1801 baseSubject.LoadString(IDS_CONFLICT_NOSUBMODULE);
1802 mineSubject = baseSubject;
1803 theirsSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1805 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
1807 baseHash = localHash;
1808 baseSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1809 mineSubject = baseSubject;
1810 theirsSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1811 changeTypeMine = CGitDiff::Identical;
1812 changeTypeTheirs = CGitDiff::DeleteSubmodule;
1814 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
1816 baseSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1817 mineSubject = baseSubject;
1818 theirsSubject = baseSubject;
1819 if (baseHash == localHash)
1820 changeTypeMine = CGitDiff::Identical;
1822 else if (baseHash == GIT_REV_ZERO && localHash != GIT_REV_ZERO && remoteHash != GIT_REV_ZERO)
1824 baseOK = true;
1825 mineSubject = baseSubject;
1826 if (remoteIsFile)
1828 theirsSubject.LoadString(IDS_CONFLICT_NOTASUBMODULE);
1829 changeTypeMine = CGitDiff::NewSubmodule;
1831 else
1832 theirsSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1833 if (localIsFile)
1835 mineSubject.LoadString(IDS_CONFLICT_NOTASUBMODULE);
1836 changeTypeTheirs = CGitDiff::NewSubmodule;
1838 else
1839 mineSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1841 else if (baseHash != GIT_REV_ZERO && (localHash == GIT_REV_ZERO || remoteHash == GIT_REV_ZERO))
1843 baseSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1844 if (localHash == GIT_REV_ZERO)
1846 mineSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1847 changeTypeMine = CGitDiff::DeleteSubmodule;
1849 else
1851 mineSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1852 if (localHash == baseHash)
1853 changeTypeMine = CGitDiff::Identical;
1855 if (remoteHash == GIT_REV_ZERO)
1857 theirsSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1858 changeTypeTheirs = CGitDiff::DeleteSubmodule;
1860 else
1862 theirsSubject.LoadString(IDS_CONFLICT_SUBMODULENOTINITIALIZED);
1863 if (remoteHash == baseHash)
1864 changeTypeTheirs = CGitDiff::Identical;
1867 else
1868 return FALSE;
1870 CSubmoduleResolveConflictDlg resolveSubmoduleConflictDialog;
1871 resolveSubmoduleConflictDialog.SetDiff(merge.GetGitPathString(), isRebase, baseTitle, mineTitle, theirsTitle, baseHash, baseSubject, baseOK, localHash, mineSubject, mineOK, changeTypeMine, remoteHash, theirsSubject, theirsOK, changeTypeTheirs);
1872 resolveSubmoduleConflictDialog.DoModal();
1873 if (resolveSubmoduleConflictDialog.m_bResolved && resolveMsgHwnd)
1875 static UINT WM_REVERTMSG = RegisterWindowMessage(L"GITSLNM_NEEDSREFRESH");
1876 ::PostMessage(resolveMsgHwnd, WM_REVERTMSG, NULL, NULL);
1879 return TRUE;
1882 CTGitPathList list;
1883 if (list.ParserFromLsFile(vector))
1885 MessageBox(nullptr, L"Parse ls-files failed!", L"TortoiseGit", MB_OK | MB_ICONERROR);
1886 return FALSE;
1889 if (list.IsEmpty())
1890 return FALSE;
1892 CTGitPath theirs;
1893 CTGitPath mine;
1894 CTGitPath base;
1896 if (isRebase)
1898 mine.SetFromGit(GetMergeTempFile(L"REMOTE", merge));
1899 theirs.SetFromGit(GetMergeTempFile(L"LOCAL", merge));
1901 else
1903 mine.SetFromGit(GetMergeTempFile(L"LOCAL", merge));
1904 theirs.SetFromGit(GetMergeTempFile(L"REMOTE", merge));
1906 base.SetFromGit(GetMergeTempFile(L"BASE",merge));
1908 CString format = L"git.exe checkout-index --temp --stage=%d -- \"%s\"";
1909 CFile tempfile;
1910 //create a empty file, incase stage is not three
1911 tempfile.Open(mine.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1912 tempfile.Close();
1913 tempfile.Open(theirs.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1914 tempfile.Close();
1915 tempfile.Open(base.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1916 tempfile.Close();
1918 bool b_base=false, b_local=false, b_remote=false;
1920 for (int i = 0; i< list.GetCount(); ++i)
1922 CString outfile;
1923 cmd.Empty();
1924 outfile.Empty();
1926 if( list[i].m_Stage == 1)
1928 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1929 b_base = true;
1930 outfile = base.GetWinPathString();
1933 if( list[i].m_Stage == 2 )
1935 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1936 b_local = true;
1937 outfile = mine.GetWinPathString();
1940 if( list[i].m_Stage == 3 )
1942 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1943 b_remote = true;
1944 outfile = theirs.GetWinPathString();
1946 CString output, err;
1947 if(!outfile.IsEmpty())
1948 if (!g_Git.Run(cmd, &output, &err, CP_UTF8))
1950 CString file;
1951 int start =0 ;
1952 file = output.Tokenize(L"\t", start);
1953 ::MoveFileEx(file,outfile,MOVEFILE_REPLACE_EXISTING|MOVEFILE_COPY_ALLOWED);
1955 else
1956 CMessageBox::Show(nullptr, output + L'\n' + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
1959 if(b_local && b_remote )
1961 merge.SetFromWin(g_Git.CombinePath(merge));
1962 if (isRebase)
1963 return !!CAppUtils::StartExtMerge(bAlternativeTool, base, mine, theirs, merge, baseTitle, mineTitle, theirsTitle, CString(), false, resolveMsgHwnd, true);
1965 return !!CAppUtils::StartExtMerge(bAlternativeTool, base, theirs, mine, merge, baseTitle, theirsTitle, mineTitle, CString(), false, resolveMsgHwnd, true);
1967 else
1969 ::DeleteFile(mine.GetWinPathString());
1970 ::DeleteFile(theirs.GetWinPathString());
1971 if (!b_base)
1972 ::DeleteFile(base.GetWinPathString());
1974 SCOPE_EXIT{
1975 if (b_base)
1976 ::DeleteFile(base.GetWinPathString());
1979 CDeleteConflictDlg dlg;
1980 if (!isRebase)
1982 DescribeConflictFile(b_local, b_base, dlg.m_LocalStatus);
1983 DescribeConflictFile(b_remote, b_base, dlg.m_RemoteStatus);
1984 dlg.m_LocalHash = mineTitle;
1985 dlg.m_RemoteHash = theirsTitle;
1986 dlg.m_bDiffMine = b_local;
1988 else
1990 DescribeConflictFile(b_local, b_base, dlg.m_RemoteStatus);
1991 DescribeConflictFile(b_remote, b_base, dlg.m_LocalStatus);
1992 dlg.m_LocalHash = theirsTitle;
1993 dlg.m_RemoteHash = mineTitle;
1994 dlg.m_bDiffMine = !b_local;
1996 dlg.m_bShowModifiedButton = b_base;
1997 dlg.m_File = merge;
1998 dlg.m_FileBaseVersion = base;
1999 if(dlg.DoModal() == IDOK)
2001 CString out;
2002 if(dlg.m_bIsDelete)
2003 cmd.Format(L"git.exe rm -- \"%s\"", (LPCTSTR)merge.GetGitPathString());
2004 else
2005 cmd.Format(L"git.exe add -- \"%s\"", (LPCTSTR)merge.GetGitPathString());
2007 if (g_Git.Run(cmd, &out, CP_UTF8))
2009 MessageBox(nullptr, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
2010 return FALSE;
2012 if (!dlg.m_bIsDelete)
2014 path.m_Action |= CTGitPath::LOGACTIONS_ADDED;
2015 path.m_Action &= ~CTGitPath::LOGACTIONS_UNMERGED;
2017 return TRUE;
2019 return FALSE;
2023 bool CAppUtils::IsSSHPutty()
2025 CString sshclient = g_Git.m_Environment.GetEnv(L"GIT_SSH");
2026 sshclient=sshclient.MakeLower();
2027 return sshclient.Find(L"plink", 0) >= 0;
2030 CString CAppUtils::GetClipboardLink(const CString &skipGitPrefix, int paramsCount)
2032 if (!OpenClipboard(nullptr))
2033 return CString();
2035 CString sClipboardText;
2036 HGLOBAL hglb = GetClipboardData(CF_TEXT);
2037 if (hglb)
2039 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
2040 sClipboardText = CString(lpstr);
2041 GlobalUnlock(hglb);
2043 hglb = GetClipboardData(CF_UNICODETEXT);
2044 if (hglb)
2046 LPCTSTR lpstr = (LPCTSTR)GlobalLock(hglb);
2047 sClipboardText = lpstr;
2048 GlobalUnlock(hglb);
2050 CloseClipboard();
2052 if(!sClipboardText.IsEmpty())
2054 if (sClipboardText[0] == L'"' && sClipboardText[sClipboardText.GetLength() - 1] == L'"')
2055 sClipboardText=sClipboardText.Mid(1,sClipboardText.GetLength()-2);
2057 for (const CString& prefix : { L"http://", L"https://", L"git://", L"ssh://", L"git@" })
2059 if (CStringUtils::StartsWith(sClipboardText, prefix) && sClipboardText.GetLength() != prefix.GetLength())
2060 return sClipboardText;
2063 if(sClipboardText.GetLength()>=2)
2064 if (sClipboardText[1] == L':')
2065 if( (sClipboardText[0] >= 'A' && sClipboardText[0] <= 'Z')
2066 || (sClipboardText[0] >= 'a' && sClipboardText[0] <= 'z') )
2067 return sClipboardText;
2069 // trim prefixes like "git clone "
2070 if (!skipGitPrefix.IsEmpty() && CStringUtils::StartsWith(sClipboardText, skipGitPrefix))
2072 sClipboardText = sClipboardText.Mid(skipGitPrefix.GetLength()).Trim();
2073 int spacePos = -1;
2074 while (paramsCount >= 0)
2076 --paramsCount;
2077 spacePos = sClipboardText.Find(L' ', spacePos + 1);
2078 if (spacePos == -1)
2079 break;
2081 if (spacePos > 0 && paramsCount < 0)
2082 sClipboardText.Truncate(spacePos);
2083 return sClipboardText;
2087 return CString();
2090 CString CAppUtils::ChooseRepository(const CString* path)
2092 CBrowseFolder browseFolder;
2093 CRegString regLastResopitory = CRegString(L"Software\\TortoiseGit\\TortoiseProc\\LastRepo", L"");
2095 browseFolder.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
2096 CString strCloneDirectory;
2097 if(path)
2098 strCloneDirectory=*path;
2099 else
2100 strCloneDirectory = regLastResopitory;
2102 CString title;
2103 title.LoadString(IDS_CHOOSE_REPOSITORY);
2105 browseFolder.SetInfo(title);
2107 if (browseFolder.Show(nullptr, strCloneDirectory) == CBrowseFolder::OK)
2109 regLastResopitory = strCloneDirectory;
2110 return strCloneDirectory;
2112 else
2113 return CString();
2116 bool CAppUtils::SendPatchMail(CTGitPathList& list, bool bIsMainWnd)
2118 CSendMailDlg dlg;
2120 dlg.m_PathList = list;
2122 if(dlg.DoModal()==IDOK)
2124 if (dlg.m_PathList.IsEmpty())
2125 return FALSE;
2127 CGitProgressDlg progDlg;
2128 if (bIsMainWnd)
2129 theApp.m_pMainWnd = &progDlg;
2130 SendMailProgressCommand sendMailProgressCommand;
2131 progDlg.SetCommand(&sendMailProgressCommand);
2133 sendMailProgressCommand.SetPathList(dlg.m_PathList);
2134 progDlg.SetItemCount(dlg.m_PathList.GetCount());
2136 CSendMailPatch sendMailPatch(dlg.m_To, dlg.m_CC, dlg.m_Subject, !!dlg.m_bAttachment, !!dlg.m_bCombine);
2137 sendMailProgressCommand.SetSendMailOption(&sendMailPatch);
2139 progDlg.DoModal();
2141 return true;
2143 return false;
2146 bool CAppUtils::SendPatchMail(const CString& cmd, const CString& formatpatchoutput, bool bIsMainWnd)
2148 CTGitPathList list;
2149 CString log=formatpatchoutput;
2150 int start=log.Find(cmd);
2151 if(start >=0)
2152 log.Tokenize(L"\n", start);
2153 else
2154 start = 0;
2156 while(start>=0)
2158 CString one = log.Tokenize(L"\n", start);
2159 one=one.Trim();
2160 if (one.IsEmpty() || CStringUtils::StartsWith(one, CString(MAKEINTRESOURCE(IDS_SUCCESS))))
2161 continue;
2162 one.Replace(L'/', L'\\');
2163 CTGitPath path;
2164 path.SetFromWin(one);
2165 list.AddPath(path);
2167 if (!list.IsEmpty())
2168 return SendPatchMail(list, bIsMainWnd);
2169 else
2171 CMessageBox::Show(nullptr, IDS_ERR_NOPATCHES, IDS_APPNAME, MB_ICONINFORMATION);
2172 return true;
2177 int CAppUtils::GetLogOutputEncode(CGit *pGit)
2179 CString output;
2180 output = pGit->GetConfigValue(L"i18n.logOutputEncoding");
2181 if(output.IsEmpty())
2182 return CUnicodeUtils::GetCPCode(pGit->GetConfigValue(L"i18n.commitencoding"));
2183 else
2184 return CUnicodeUtils::GetCPCode(output);
2186 int CAppUtils::SaveCommitUnicodeFile(const CString& filename, CString &message)
2190 CFile file(filename, CFile::modeReadWrite | CFile::modeCreate);
2191 int cp = CUnicodeUtils::GetCPCode(g_Git.GetConfigValue(L"i18n.commitencoding"));
2193 bool stripComments = (CRegDWORD(L"Software\\TortoiseGit\\StripCommentedLines", FALSE) == TRUE);
2194 TCHAR commentChar = L'#';
2195 if (stripComments)
2197 CString commentCharValue = g_Git.GetConfigValue(L"core.commentchar");
2198 if (!commentCharValue.IsEmpty())
2199 commentChar = commentCharValue[0];
2202 bool sanitize = (CRegDWORD(L"Software\\TortoiseGit\\SanitizeCommitMsg", TRUE) == TRUE);
2203 if (sanitize)
2204 message.Trim(L" \r\n");
2206 int len = message.GetLength();
2207 int start = 0;
2208 int emptyLineCnt = 0;
2209 while (start >= 0 && start < len)
2211 int oldStart = start;
2212 start = message.Find(L'\n', oldStart);
2213 CString line = message.Mid(oldStart);
2214 if (start != -1)
2216 line.Truncate(start - oldStart);
2217 ++start; // move forward so we don't find the same char again
2219 if (stripComments && (!line.IsEmpty() && line.GetAt(0) == commentChar) || (start < 0 && line.IsEmpty()))
2220 continue;
2221 line.TrimRight(L" \r");
2222 if (sanitize)
2224 if (line.IsEmpty())
2226 ++emptyLineCnt;
2227 continue;
2229 if (emptyLineCnt) // squash multiple newlines
2230 file.Write("\n", 1);
2231 emptyLineCnt = 0;
2233 CStringA lineA = CUnicodeUtils::GetMulti(line + L'\n', cp);
2234 file.Write((LPCSTR)lineA, lineA.GetLength());
2236 file.Close();
2237 return 0;
2239 catch (CFileException *e)
2241 e->Delete();
2242 return -1;
2246 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)
2248 if (bAutoLoad)
2249 CAppUtils::LaunchPAgent(nullptr, &url);
2251 CGitHash hashOld;
2252 if (g_Git.GetHash(hashOld, L"HEAD"))
2254 MessageBox(nullptr, g_Git.GetGitLastErr(L"Could not get HEAD hash."), L"TortoiseGit", MB_ICONERROR);
2255 return false;
2258 CString args;
2259 if (CRegDWORD(L"Software\\TortoiseGit\\PullRebaseBehaviorLike1816", FALSE) == FALSE)
2260 args += L" --no-rebase";
2262 if (bFetchTags == BST_UNCHECKED)
2263 args += L" --no-tags";
2264 else if (bFetchTags == BST_CHECKED)
2265 args += L" --tags";
2267 if (bNoFF)
2268 args += L" --no-ff";
2270 if (bFFonly)
2271 args += L" --ff-only";
2273 if (bSquash)
2274 args += L" --squash";
2276 if (bNoCommit)
2277 args += L" --no-commit";
2279 if (nDepth)
2280 args.AppendFormat(L" --depth %d", *nDepth);
2282 if (bPrune == BST_CHECKED)
2283 args += L" --prune";
2284 else if (bPrune == BST_UNCHECKED)
2285 args += L" --no-prune";
2287 if (bUnrelated)
2288 args += L" --allow-unrelated-histories";
2290 CString cmd;
2291 cmd.Format(L"git.exe pull --progress -v%s \"%s\" %s", (LPCTSTR)args, (LPCTSTR)url, (LPCTSTR)remoteBranchName);
2292 CProgressDlg progress;
2293 progress.m_GitCmd = cmd;
2295 CGitHash hashNew; // declare outside lambda, because it is captured by reference
2296 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2298 if (status)
2300 if (CAppUtils::GetMsysgitVersion() >= 0x02090000)
2302 STRING_VECTOR remotes;
2303 g_Git.GetRemoteList(remotes);
2304 if (std::find(remotes.begin(), remotes.end(), url) != remotes.end())
2306 CString currentBranch;
2307 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, currentBranch))
2308 currentBranch.Empty();
2309 CString remoteRef = L"remotes/" + url + L"/" + remoteBranchName;
2310 if (!currentBranch.IsEmpty() && remoteBranchName.IsEmpty())
2312 CString pullRemote, pullBranch;
2313 g_Git.GetRemoteTrackedBranch(currentBranch, pullRemote, pullBranch);
2314 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
2315 remoteRef = L"remotes/" + pullRemote + L"/" + pullBranch;
2317 CGitHash common;
2318 g_Git.IsFastForward(L"HEAD", remoteRef, &common);
2319 if (common.IsEmpty())
2320 postCmdList.emplace_back(IDI_MERGE, IDS_MERGE_UNRELATED, [=] { DoPull(url, bAutoLoad, bFetchTags, bNoFF, bFFonly, bSquash, bNoCommit, nDepth, bPrune, remoteBranchName, showPush, showStashPop, true); });
2324 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&]{ CAppUtils::Pull(); });
2325 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSTASHSAVE, [&]{ CAppUtils::StashSave(L"", true); });
2326 return;
2329 if (showStashPop)
2330 postCmdList.emplace_back(IDI_RELOCATE, IDS_MENUSTASHPOP, []{ CAppUtils::StashPop(); });
2332 if (g_Git.GetHash(hashNew, L"HEAD"))
2333 MessageBox(nullptr, g_Git.GetGitLastErr(L"Could not get HEAD hash after pulling."), L"TortoiseGit", MB_ICONERROR);
2334 else
2336 postCmdList.emplace_back(IDI_DIFF, IDS_PROC_PULL_DIFFS, [&]
2338 CString sCmd;
2339 sCmd.Format(L"/command:showcompare /path:\"%s\" /revision1:%s /revision2:%s", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)hashOld.ToString(), (LPCTSTR)hashNew.ToString());
2340 CAppUtils::RunTortoiseGitProc(sCmd);
2342 postCmdList.emplace_back(IDI_LOG, IDS_PROC_PULL_LOG, [&]
2344 CString sCmd;
2345 sCmd.Format(L"/command:log /path:\"%s\" /range:%s", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)(hashOld.ToString() + L".." + hashNew.ToString()));
2346 CAppUtils::RunTortoiseGitProc(sCmd);
2350 if (showPush)
2351 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, []{ CAppUtils::Push(); });
2353 CTGitPath gitPath = g_Git.m_CurrentDir;
2354 if (gitPath.HasSubmodules())
2356 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
2358 CString sCmd;
2359 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
2360 CAppUtils::RunTortoiseGitProc(sCmd);
2365 INT_PTR ret = progress.DoModal();
2367 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)
2369 cmd.Format(L"/command:repostatus /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
2370 CAppUtils::RunTortoiseGitProc(cmd);
2372 return true;
2375 return ret == IDOK;
2378 bool CAppUtils::Pull(bool showPush, bool showStashPop)
2380 if (IsTGitRebaseActive())
2381 return false;
2383 CPullFetchDlg dlg;
2384 dlg.m_IsPull = TRUE;
2385 if (dlg.DoModal() == IDOK)
2387 // "git.exe pull --rebase" is not supported, never and ever. So, adapting it to Fetch & Rebase.
2388 if (dlg.m_bRebase)
2389 return DoFetch(dlg.m_RemoteURL,
2390 FALSE, // Fetch all remotes
2391 dlg.m_bAutoLoad == BST_CHECKED,
2392 dlg.m_bPrune,
2393 dlg.m_bDepth == BST_CHECKED,
2394 dlg.m_nDepth,
2395 dlg.m_bFetchTags,
2396 dlg.m_RemoteBranchName,
2397 dlg.m_bRebaseActivatedInConfigForPull ? 2 : 1, // Rebase after fetching
2398 dlg.m_bRebasePreserveMerges == TRUE); // Preserve merges on rebase
2400 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);
2403 return false;
2406 bool CAppUtils::RebaseAfterFetch(const CString& upstream, int rebase, bool preserveMerges)
2408 while (true)
2410 CRebaseDlg dlg;
2411 if (!upstream.IsEmpty())
2412 dlg.m_Upstream = upstream;
2413 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENULOG)));
2414 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUPUSH)));
2415 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUDESSENDMAIL)));
2416 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUREBASE)));
2417 dlg.m_bRebaseAutoStart = (rebase == 2);
2418 dlg.m_bPreserveMerges = preserveMerges;
2419 INT_PTR response = dlg.DoModal();
2420 if (response == IDOK)
2421 return true;
2422 else if (response == IDC_REBASE_POST_BUTTON)
2424 CString cmd = L"/command:log";
2425 cmd += L" /path:\"" + g_Git.m_CurrentDir + L'"';
2426 CAppUtils::RunTortoiseGitProc(cmd);
2427 return true;
2429 else if (response == IDC_REBASE_POST_BUTTON + 1)
2430 return Push();
2431 else if (response == IDC_REBASE_POST_BUTTON + 2)
2433 CString cmd, out, err;
2434 cmd.Format(L"git.exe format-patch -o \"%s\" %s..%s",
2435 (LPCTSTR)g_Git.m_CurrentDir,
2436 (LPCTSTR)g_Git.FixBranchName(dlg.m_Upstream),
2437 (LPCTSTR)g_Git.FixBranchName(dlg.m_Branch));
2438 if (g_Git.Run(cmd, &out, &err, CP_UTF8))
2440 CMessageBox::Show(nullptr, out + L'\n' + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
2441 return false;
2443 CAppUtils::SendPatchMail(cmd, out);
2444 return true;
2446 else if (response == IDC_REBASE_POST_BUTTON + 3)
2447 continue;
2448 else if (response == IDCANCEL)
2449 return false;
2450 return false;
2454 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)
2456 if (loadPuttyAgent)
2458 if (fetchAllRemotes)
2460 STRING_VECTOR list;
2461 g_Git.GetRemoteList(list);
2463 for (const auto& remote : list)
2464 CAppUtils::LaunchPAgent(nullptr, &remote);
2466 else
2467 CAppUtils::LaunchPAgent(nullptr, &url);
2470 CString upstream = L"FETCH_HEAD";
2471 CGitHash oldUpstreamHash;
2472 if (runRebase)
2474 STRING_VECTOR list;
2475 g_Git.GetRemoteList(list);
2476 for (auto it = list.cbegin(); it != list.cend(); ++it)
2478 if (url == *it)
2480 upstream.Empty();
2481 if (remoteBranch.IsEmpty()) // pulldlg might clear remote branch if its the default tracked branch
2483 CString currentBranch;
2484 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, currentBranch))
2485 currentBranch.Empty();
2486 if (!currentBranch.IsEmpty() && remoteBranch.IsEmpty())
2488 CString pullRemote, pullBranch;
2489 g_Git.GetRemoteTrackedBranch(currentBranch, pullRemote, pullBranch);
2490 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty() && pullRemote == url) // pullRemote == url is just another safety-check and should not be needed
2491 upstream = L"remotes/" + *it + L'/' + pullBranch;
2494 else
2495 upstream = L"remotes/" + *it + L'/' + remoteBranch;
2497 g_Git.GetHash(oldUpstreamHash, upstream);
2498 break;
2503 CString cmd, arg;
2504 arg = L" --progress";
2506 if (bDepth)
2507 arg.AppendFormat(L" --depth %d", nDepth);
2509 if (prune == TRUE)
2510 arg += L" --prune";
2511 else if (prune == FALSE)
2512 arg += L" --no-prune";
2514 if (fetchTags == 1)
2515 arg += L" --tags";
2516 else if (fetchTags == 0)
2517 arg += L" --no-tags";
2519 if (fetchAllRemotes)
2520 cmd.Format(L"git.exe fetch --all -v%s", (LPCTSTR)arg);
2521 else
2522 cmd.Format(L"git.exe fetch -v%s \"%s\" %s", (LPCTSTR)arg, (LPCTSTR)url, (LPCTSTR)remoteBranch);
2524 CProgressDlg progress;
2525 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2527 if (status)
2529 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ DoFetch(url, fetchAllRemotes, loadPuttyAgent, prune, bDepth, nDepth, fetchTags, remoteBranch, runRebase, rebasePreserveMerges); });
2530 if (fetchAllRemotes)
2531 postCmdList.emplace_back(IDI_LOG, IDS_MENULOG, []
2533 CString cmd = L"/command:log";
2534 cmd += L" /path:\"" + g_Git.m_CurrentDir + L'"';
2535 CAppUtils::RunTortoiseGitProc(cmd);
2537 return;
2540 postCmdList.emplace_back(IDI_LOG, IDS_MENULOG, []
2542 CString cmd = L"/command:log";
2543 cmd += L" /path:\"" + g_Git.m_CurrentDir + L'"';
2544 CAppUtils::RunTortoiseGitProc(cmd);
2547 postCmdList.emplace_back(IDI_REVERT, IDS_PROC_RESET, []
2549 CString pullRemote, pullBranch;
2550 g_Git.GetRemoteTrackedBranchForHEAD(pullRemote, pullBranch);
2551 CString defaultUpstream;
2552 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
2553 defaultUpstream.Format(L"remotes/%s/%s", (LPCTSTR)pullRemote, (LPCTSTR)pullBranch);
2554 CAppUtils::GitReset(&defaultUpstream, 2);
2557 postCmdList.emplace_back(IDI_PULL, IDS_MENUFETCH, []{ CAppUtils::Fetch(); });
2559 if (!runRebase && !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir))
2560 postCmdList.emplace_back(IDI_REBASE, IDS_MENUREBASE, [&]{ runRebase = false; CAppUtils::RebaseAfterFetch(); });
2563 progress.m_GitCmd = cmd;
2565 if (g_Git.UsingLibGit2(CGit::GIT_CMD_FETCH))
2567 CGitProgressDlg gitdlg;
2568 FetchProgressCommand fetchProgressCommand;
2569 if (!fetchAllRemotes)
2570 fetchProgressCommand.SetUrl(url);
2571 gitdlg.SetCommand(&fetchProgressCommand);
2572 fetchProgressCommand.m_PostCmdCallback = progress.m_PostCmdCallback;
2573 fetchProgressCommand.SetAutoTag(fetchTags == 1 ? GIT_REMOTE_DOWNLOAD_TAGS_ALL : fetchTags == 2 ? GIT_REMOTE_DOWNLOAD_TAGS_AUTO : GIT_REMOTE_DOWNLOAD_TAGS_NONE);
2574 fetchProgressCommand.SetPrune(prune == BST_CHECKED ? GIT_FETCH_PRUNE : prune == BST_INDETERMINATE ? GIT_FETCH_PRUNE_UNSPECIFIED : GIT_FETCH_NO_PRUNE);
2575 if (!fetchAllRemotes)
2576 fetchProgressCommand.SetRefSpec(remoteBranch);
2577 return gitdlg.DoModal() == IDOK;
2580 progress.m_PostExecCallback = [&](DWORD& exitCode, CString&)
2582 if (exitCode || !runRebase)
2583 return;
2585 CGitHash remoteBranchHash;
2586 g_Git.GetHash(remoteBranchHash, upstream);
2588 if (runRebase == 1)
2590 CGitHash headHash, commonAcestor;
2591 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)
2592 return;
2594 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)
2595 return;
2598 if (runRebase == 1 && g_Git.IsFastForward(L"HEAD", upstream))
2600 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);
2601 if (ret == 3)
2602 return;
2603 if (ret == 1)
2605 CProgressDlg mergeProgress;
2606 mergeProgress.m_GitCmd = L"git.exe merge --ff-only " + upstream;
2607 mergeProgress.m_AutoClose = AUTOCLOSE_IF_NO_ERRORS;
2608 mergeProgress.m_PostCmdCallback = [](DWORD status, PostCmdList& postCmdList)
2610 if (status && g_Git.HasWorkingTreeConflicts())
2612 // there are conflict files
2613 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
2615 CString sCmd;
2616 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
2617 CAppUtils::RunTortoiseGitProc(sCmd);
2621 mergeProgress.DoModal();
2622 return;
2626 CAppUtils::RebaseAfterFetch(upstream, runRebase, rebasePreserveMerges);
2629 return progress.DoModal() == IDOK;
2632 bool CAppUtils::Fetch(const CString& remoteName, bool allRemotes)
2634 CPullFetchDlg dlg;
2635 dlg.m_PreSelectRemote = remoteName;
2636 dlg.m_IsPull=FALSE;
2637 dlg.m_bAllRemotes = allRemotes;
2639 if(dlg.DoModal()==IDOK)
2640 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);
2642 return false;
2645 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)
2647 CString error;
2648 DWORD exitcode = 0xFFFFFFFF;
2649 if (CHooks::Instance().PrePush(g_Git.m_CurrentDir, exitcode, error))
2651 if (exitcode)
2653 CString temp;
2654 temp.Format(IDS_ERR_HOOKFAILED, (LPCTSTR)error);
2655 MessageBox(nullptr, temp, L"TortoiseGit", MB_OK | MB_ICONERROR);
2656 return false;
2660 int iRecurseSubmodules = 0;
2661 if (GetMsysgitVersion() >= 0x02070000)
2663 CString sRecurseSubmodules = g_Git.GetConfigValue(L"push.recurseSubmodules");
2664 if (sRecurseSubmodules == L"check")
2665 iRecurseSubmodules = 1;
2666 else if (sRecurseSubmodules == L"on-demand")
2667 iRecurseSubmodules = 2;
2670 CString arg;
2671 if (pack)
2672 arg += L"--thin ";
2673 if (tags && !allBranches)
2674 arg += L"--tags ";
2675 if (force)
2676 arg += L"--force ";
2677 if (forceWithLease)
2678 arg += L"--force-with-lease ";
2679 if (setUpstream)
2680 arg += L"--set-upstream ";
2681 if (recurseSubmodules == 0 && recurseSubmodules != iRecurseSubmodules)
2682 arg += L"--recurse-submodules=no ";
2683 if (recurseSubmodules == 1 && recurseSubmodules != iRecurseSubmodules)
2684 arg += L"--recurse-submodules=check ";
2685 if (recurseSubmodules == 2 && recurseSubmodules != iRecurseSubmodules)
2686 arg += L"--recurse-submodules=on-demand ";
2688 arg += L"--progress ";
2690 CProgressDlg progress;
2692 STRING_VECTOR remotesList;
2693 if (allRemotes)
2694 g_Git.GetRemoteList(remotesList);
2695 else
2696 remotesList.push_back(remote);
2698 for (unsigned int i = 0; i < remotesList.size(); ++i)
2700 if (autoloadKey)
2701 CAppUtils::LaunchPAgent(nullptr, &remotesList[i]);
2703 CString cmd;
2704 if (allBranches)
2706 cmd.Format(L"git.exe push --all %s\"%s\"",
2707 (LPCTSTR)arg,
2708 (LPCTSTR)remotesList[i]);
2710 if (tags)
2712 progress.m_GitCmdList.push_back(cmd);
2713 cmd.Format(L"git.exe push --tags %s\"%s\"", (LPCTSTR)arg, (LPCTSTR)remotesList[i]);
2716 else
2718 cmd.Format(L"git.exe push %s\"%s\" %s",
2719 (LPCTSTR)arg,
2720 (LPCTSTR)remotesList[i],
2721 (LPCTSTR)localBranch);
2722 if (!remoteBranch.IsEmpty())
2724 cmd += L":";
2725 cmd += remoteBranch;
2728 progress.m_GitCmdList.push_back(cmd);
2730 if (!allBranches && !!CRegDWORD(L"Software\\TortoiseGit\\ShowBranchRevisionNumber", FALSE))
2732 cmd.Format(L"git.exe rev-list --count --first-parent %s", (LPCTSTR)localBranch);
2733 progress.m_GitCmdList.push_back(cmd);
2737 CString superprojectRoot;
2738 GitAdminDir::HasAdminDir(g_Git.m_CurrentDir, false, &superprojectRoot);
2739 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2741 // need to execute hooks as those might be needed by post action commands
2742 DWORD exitcode = 0xFFFFFFFF;
2743 CString error;
2744 if (CHooks::Instance().PostPush(g_Git.m_CurrentDir, exitcode, error))
2746 if (exitcode)
2748 CString temp;
2749 temp.Format(IDS_ERR_HOOKFAILED, (LPCTSTR)error);
2750 MessageBox(nullptr, temp, L"TortoiseGit", MB_OK | MB_ICONERROR);
2754 if (status)
2756 bool rejected = progress.GetLogText().Find(L"! [rejected]") > 0;
2757 if (rejected)
2759 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, []{ Pull(true); });
2760 postCmdList.emplace_back(IDI_PULL, IDS_MENUFETCH, [&]{ Fetch(allRemotes ? L"" : remote, allRemotes); });
2762 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, [&]{ Push(localBranch); });
2763 return;
2766 postCmdList.emplace_back(IDS_PROC_REQUESTPULL, [&]{ RequestPull(remoteBranch); });
2767 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, [&]{ Push(localBranch); });
2768 postCmdList.emplace_back(IDI_SWITCH, IDS_MENUSWITCH, [&]{ Switch(); });
2769 if (!superprojectRoot.IsEmpty())
2771 postCmdList.emplace_back(IDI_COMMIT, IDS_PROC_COMMIT_SUPERPROJECT, [&]
2773 CString sCmd;
2774 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)superprojectRoot);
2775 RunTortoiseGitProc(sCmd);
2780 INT_PTR ret = progress.DoModal();
2781 return ret == IDOK;
2784 bool CAppUtils::Push(const CString& selectLocalBranch)
2786 CPushDlg dlg;
2787 dlg.m_BranchSourceName = selectLocalBranch;
2789 if (dlg.DoModal() == IDOK)
2790 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);
2792 return FALSE;
2795 bool CAppUtils::RequestPull(const CString& endrevision, const CString& repositoryUrl, bool bIsMainWnd)
2797 CRequestPullDlg dlg;
2798 dlg.m_RepositoryURL = repositoryUrl;
2799 dlg.m_EndRevision = endrevision;
2800 if (dlg.DoModal()==IDOK)
2802 CString cmd;
2803 cmd.Format(L"git.exe request-pull %s \"%s\" %s", (LPCTSTR)dlg.m_StartRevision, (LPCTSTR)dlg.m_RepositoryURL, (LPCTSTR)dlg.m_EndRevision);
2805 CSysProgressDlg sysProgressDlg;
2806 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
2807 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_CREATINGPULLREUQEST)));
2808 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
2809 sysProgressDlg.SetShowProgressBar(false);
2810 sysProgressDlg.ShowModeless((HWND)nullptr, true);
2812 CString tempFileName = GetTempFile();
2813 CString err;
2814 DeleteFile(tempFileName);
2815 CreateDirectory(tempFileName, nullptr);
2816 tempFileName += L"\\pullrequest.txt";
2817 if (g_Git.RunLogFile(cmd, tempFileName, &err))
2819 CString msg;
2820 msg.LoadString(IDS_ERR_PULLREUQESTFAILED);
2821 MessageBox(nullptr, msg + L'\n' + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
2822 return false;
2825 if (sysProgressDlg.HasUserCancelled())
2827 CMessageBox::Show(nullptr, IDS_USERCANCELLED, IDS_APPNAME, MB_OK);
2828 ::DeleteFile(tempFileName);
2829 return false;
2832 sysProgressDlg.Stop();
2834 if (dlg.m_bSendMail)
2836 CSendMailDlg sendmaildlg;
2837 sendmaildlg.m_PathList = CTGitPathList(CTGitPath(tempFileName));
2838 sendmaildlg.m_bCustomSubject = true;
2840 if (sendmaildlg.DoModal() == IDOK)
2842 if (sendmaildlg.m_PathList.IsEmpty())
2843 return FALSE;
2845 CGitProgressDlg progDlg;
2846 if (bIsMainWnd)
2847 theApp.m_pMainWnd = &progDlg;
2848 SendMailProgressCommand sendMailProgressCommand;
2849 progDlg.SetCommand(&sendMailProgressCommand);
2851 sendMailProgressCommand.SetPathList(sendmaildlg.m_PathList);
2852 progDlg.SetItemCount(sendmaildlg.m_PathList.GetCount());
2854 CSendMailCombineable sendMailCombineable(sendmaildlg.m_To, sendmaildlg.m_CC, sendmaildlg.m_Subject, !!sendmaildlg.m_bAttachment, !!sendmaildlg.m_bCombine);
2855 sendMailProgressCommand.SetSendMailOption(&sendMailCombineable);
2857 progDlg.DoModal();
2859 return true;
2861 return false;
2864 CAppUtils::LaunchAlternativeEditor(tempFileName);
2866 return true;
2869 void CAppUtils::RemoveTrailSlash(CString &path)
2871 if(path.IsEmpty())
2872 return ;
2874 // For URL, do not trim the slash just after the host name component.
2875 int index = path.Find(L"://");
2876 if (index >= 0)
2878 index += 4;
2879 index = path.Find(L'/', index);
2880 if (index == path.GetLength() - 1)
2881 return;
2884 while (path[path.GetLength() - 1] == L'\\' || path[path.GetLength() - 1] == L'/')
2886 path.Truncate(path.GetLength() - 1);
2887 if(path.IsEmpty())
2888 return;
2892 bool CAppUtils::CheckUserData()
2894 while(g_Git.GetUserName().IsEmpty() || g_Git.GetUserEmail().IsEmpty())
2896 if (CMessageBox::Show(nullptr, IDS_PROC_NOUSERDATA, IDS_APPNAME, MB_YESNO | MB_ICONERROR) == IDYES)
2898 CTGitPath path(g_Git.m_CurrentDir);
2899 CSettings dlg(IDS_PROC_SETTINGS_TITLE,&path);
2900 dlg.SetTreeViewMode(TRUE, TRUE, TRUE);
2901 dlg.SetTreeWidth(220);
2902 dlg.m_DefaultPage = L"gitconfig";
2904 dlg.DoModal();
2905 dlg.HandleRestart();
2908 else
2909 return false;
2912 return true;
2915 BOOL CAppUtils::Commit(const CString& bugid, BOOL bWholeProject, CString &sLogMsg,
2916 CTGitPathList &pathList,
2917 CTGitPathList &selectedList,
2918 bool bSelectFilesForCommit)
2920 bool bFailed = true;
2922 if (!CheckUserData())
2923 return false;
2925 while (bFailed)
2927 bFailed = false;
2928 CCommitDlg dlg;
2929 dlg.m_sBugID = bugid;
2931 dlg.m_bWholeProject = bWholeProject;
2933 dlg.m_sLogMessage = sLogMsg;
2934 dlg.m_pathList = pathList;
2935 dlg.m_checkedPathList = selectedList;
2936 dlg.m_bSelectFilesForCommit = bSelectFilesForCommit;
2937 if (dlg.DoModal() == IDOK)
2939 if (dlg.m_pathList.IsEmpty())
2940 return false;
2941 // if the user hasn't changed the list of selected items
2942 // we don't use that list. Because if we would use the list
2943 // of pre-checked items, the dialog would show different
2944 // checked items on the next startup: it would only try
2945 // to check the parent folder (which might not even show)
2946 // instead, we simply use an empty list and let the
2947 // default checking do its job.
2948 if (!dlg.m_pathList.IsEqual(pathList))
2949 selectedList = dlg.m_pathList;
2950 pathList = dlg.m_updatedPathList;
2951 sLogMsg = dlg.m_sLogMessage;
2952 bSelectFilesForCommit = true;
2954 switch (dlg.m_PostCmd)
2956 case GIT_POSTCOMMIT_CMD_DCOMMIT:
2957 CAppUtils::SVNDCommit();
2958 break;
2959 case GIT_POSTCOMMIT_CMD_PUSH:
2960 CAppUtils::Push();
2961 break;
2962 case GIT_POSTCOMMIT_CMD_CREATETAG:
2963 CAppUtils::CreateBranchTag(TRUE);
2964 break;
2965 case GIT_POSTCOMMIT_CMD_PULL:
2966 CAppUtils::Pull(true);
2967 break;
2968 default:
2969 break;
2972 // CGitProgressDlg progDlg;
2973 // progDlg.SetChangeList(dlg.m_sChangeList, !!dlg.m_bKeepChangeList);
2974 // if (parser.HasVal(L"closeonend"))
2975 // progDlg.SetAutoClose(parser.GetLongVal(L"closeonend"));
2976 // progDlg.SetCommand(CGitProgressDlg::GitProgress_Commit);
2977 // progDlg.SetPathList(dlg.m_pathList);
2978 // progDlg.SetCommitMessage(dlg.m_sLogMessage);
2979 // progDlg.SetDepth(dlg.m_bRecursive ? Git_depth_infinity : svn_depth_empty);
2980 // progDlg.SetSelectedList(dlg.m_selectedPathList);
2981 // progDlg.SetItemCount(dlg.m_itemsCount);
2982 // progDlg.SetBugTraqProvider(dlg.m_BugTraqProvider);
2983 // progDlg.DoModal();
2984 // CRegDWORD err = CRegDWORD(L"Software\\TortoiseGit\\ErrorOccurred", FALSE);
2985 // err = (DWORD)progDlg.DidErrorsOccur();
2986 // bFailed = progDlg.DidErrorsOccur();
2987 // bRet = progDlg.DidErrorsOccur();
2988 // CRegDWORD bFailRepeat = CRegDWORD(L"Software\\TortoiseGit\\CommitReopen", FALSE);
2989 // if (DWORD(bFailRepeat)==0)
2990 // bFailed = false; // do not repeat if the user chose not to in the settings.
2993 return true;
2996 BOOL CAppUtils::SVNDCommit()
2998 CSVNDCommitDlg dcommitdlg;
2999 CString gitSetting = g_Git.GetConfigValue(L"svn.rmdir");
3000 if (gitSetting.IsEmpty()) {
3001 if (dcommitdlg.DoModal() != IDOK)
3002 return false;
3003 else
3005 if (dcommitdlg.m_remember)
3007 if (dcommitdlg.m_rmdir)
3008 gitSetting = L"true";
3009 else
3010 gitSetting = L"false";
3011 if (g_Git.SetConfigValue(L"svn.rmdir", gitSetting))
3013 CString msg;
3014 msg.Format(IDS_PROC_SAVECONFIGFAILED, L"svn.rmdir", (LPCTSTR)gitSetting);
3015 MessageBox(nullptr, msg, L"TortoiseGit", MB_OK | MB_ICONERROR);
3021 BOOL IsStash = false;
3022 if(!g_Git.CheckCleanWorkTree())
3024 if (CMessageBox::Show(nullptr, IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
3026 CSysProgressDlg sysProgressDlg;
3027 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3028 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3029 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3030 sysProgressDlg.SetShowProgressBar(false);
3031 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3032 sysProgressDlg.ShowModeless((HWND)nullptr, true);
3034 CString out;
3035 if (g_Git.Run(L"git.exe stash", &out, CP_UTF8))
3037 sysProgressDlg.Stop();
3038 MessageBox(nullptr, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3039 return false;
3041 sysProgressDlg.Stop();
3043 IsStash =true;
3045 else
3046 return false;
3049 CProgressDlg progress;
3050 if (dcommitdlg.m_rmdir)
3051 progress.m_GitCmd = L"git.exe svn dcommit --rmdir";
3052 else
3053 progress.m_GitCmd = L"git.exe svn dcommit";
3054 if(progress.DoModal()==IDOK && progress.m_GitStatus == 0)
3056 if( IsStash)
3058 if (CMessageBox::Show(nullptr, IDS_DCOMMIT_STASH_POP, IDS_APPNAME, MB_YESNO | MB_ICONINFORMATION) == IDYES)
3060 CSysProgressDlg sysProgressDlg;
3061 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3062 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3063 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3064 sysProgressDlg.SetShowProgressBar(false);
3065 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3066 sysProgressDlg.ShowModeless((HWND)nullptr, true);
3068 CString out;
3069 if (g_Git.Run(L"git.exe stash pop", &out, CP_UTF8))
3071 sysProgressDlg.Stop();
3072 MessageBox(nullptr, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3073 return false;
3075 sysProgressDlg.Stop();
3077 else
3078 return false;
3080 return TRUE;
3082 return FALSE;
3085 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)
3087 CString args;
3088 if (noFF)
3089 args += L" --no-ff";
3090 else if (ffOnly)
3091 args += L" --ff-only";
3093 if (squash)
3094 args += L" --squash";
3096 if (noCommit)
3097 args += L" --no-commit";
3099 if (unrelated)
3100 args += L" --allow-unrelated-histories";
3102 if (log)
3103 args.AppendFormat(L" --log=%d", *log);
3105 if (!mergeStrategy.IsEmpty())
3107 args += L" --strategy=" + mergeStrategy;
3108 if (!strategyOption.IsEmpty())
3110 args += L" --strategy-option=" + strategyOption;
3111 if (!strategyParam.IsEmpty())
3112 args += L'=' + strategyParam;
3116 if (!logMessage.IsEmpty())
3118 CString logmsg = logMessage;
3119 logmsg.Replace(L"\\\"", L"\\\\\"");
3120 logmsg.Replace(L"\"", L"\\\"");
3121 args += L" -m \"" + logmsg + L"\"";
3124 CString mergeVersion = g_Git.FixBranchName(version);
3125 CString cmd;
3126 cmd.Format(L"git.exe merge%s %s", (LPCTSTR)args, (LPCTSTR)mergeVersion);
3128 CProgressDlg Prodlg;
3129 Prodlg.m_GitCmd = cmd;
3131 Prodlg.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3133 if (status)
3135 int hasConflicts = g_Git.HasWorkingTreeConflicts();
3136 if (hasConflicts < 0)
3137 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(L"Checking for conflicts failed.", CGit::GIT_CMD_CHECKCONFLICTS), L"TortoiseGit", MB_ICONEXCLAMATION);
3138 else if (hasConflicts)
3140 // there are conflict files
3142 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
3144 CString sCmd;
3145 sCmd.Format(L"/command:resolve /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3146 CAppUtils::RunTortoiseGitProc(sCmd);
3149 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUCOMMIT, []
3151 CString sCmd;
3152 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3153 CAppUtils::RunTortoiseGitProc(sCmd);
3157 if (CAppUtils::GetMsysgitVersion() >= 0x02090000)
3159 CGitHash common;
3160 g_Git.IsFastForward(L"HEAD", mergeVersion, &common);
3161 if (common.IsEmpty())
3162 postCmdList.emplace_back(IDI_MERGE, IDS_MERGE_UNRELATED, [=] { DoMerge(noFF, ffOnly, squash, noCommit, log, true, mergeStrategy, strategyOption, strategyParam, logMessage, version, isBranch, showStashPop); });
3165 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSTASHSAVE, [=]{ CAppUtils::StashSave(L"", false, false, true, mergeVersion); });
3167 return;
3170 if (showStashPop)
3171 postCmdList.emplace_back(IDI_RELOCATE, IDS_MENUSTASHPOP, []{ CAppUtils::StashPop(); });
3173 if (noCommit || squash)
3175 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUCOMMIT, []
3177 CString sCmd;
3178 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3179 CAppUtils::RunTortoiseGitProc(sCmd);
3181 return;
3184 if (isBranch && !CStringUtils::StartsWith(version, L"remotes/")) // do not ask to remove remote branches
3186 postCmdList.emplace_back(IDI_DELETE, IDS_PROC_REMOVEBRANCH, [&]
3188 CString msg;
3189 msg.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)version);
3190 if (CMessageBox::Show(nullptr, msg, L"TortoiseGit", 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
3192 CString cmd, out;
3193 cmd.Format(L"git.exe branch -D -- %s", (LPCTSTR)version);
3194 if (g_Git.Run(cmd, &out, CP_UTF8))
3195 MessageBox(nullptr, out, L"TortoiseGit", MB_OK);
3199 if (isBranch)
3200 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, []{ CAppUtils::Push(); });
3202 BOOL hasGitSVN = CTGitPath(g_Git.m_CurrentDir).GetAdminDirMask() & ITEMIS_GITSVN;
3203 if (hasGitSVN)
3204 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSVNDCOMMIT, []{ CAppUtils::SVNDCommit(); });
3207 Prodlg.DoModal();
3208 return !Prodlg.m_GitStatus;
3211 BOOL CAppUtils::Merge(const CString* commit, bool showStashPop)
3213 if (!CheckUserData())
3214 return FALSE;
3216 if (IsTGitRebaseActive())
3217 return FALSE;
3219 CMergeDlg dlg;
3220 if (commit)
3221 dlg.m_initialRefName = *commit;
3223 if (dlg.DoModal() == IDOK)
3224 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);
3226 return FALSE;
3229 BOOL CAppUtils::MergeAbort()
3231 CMergeAbortDlg dlg;
3232 if (dlg.DoModal() == IDOK)
3233 return Reset(L"HEAD", (dlg.m_ResetType == 0) ? 3 : dlg.m_ResetType);
3235 return FALSE;
3238 void CAppUtils::EditNote(GitRevLoglist* rev, ProjectProperties* projectProperties)
3240 if (!CheckUserData())
3241 return;
3243 CInputDlg dlg;
3244 dlg.m_sHintText = CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES));
3245 dlg.m_sInputText = rev->m_Notes;
3246 dlg.m_sTitle = CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES));
3247 dlg.m_pProjectProperties = projectProperties;
3248 dlg.m_bUseLogWidth = true;
3249 if(dlg.DoModal() == IDOK)
3251 if (g_Git.SetGitNotes(rev->m_CommitHash, dlg.m_sInputText))
3253 CString err;
3254 err.LoadString(IDS_PROC_FAILEDSAVINGNOTES);
3255 MessageBox(nullptr, g_Git.GetLibGit2LastErr(err), L"TortoiseGit", MB_OK | MB_ICONERROR);
3256 return;
3259 if (g_Git.GetGitNotes(rev->m_CommitHash, rev->m_Notes))
3260 MessageBox(nullptr, g_Git.GetLibGit2LastErr(L"Could not load notes for commit " + rev->m_CommitHash.ToString() + L'.'), L"TortoiseGit", MB_OK | MB_ICONERROR);
3264 int CAppUtils::GetMsysgitVersion()
3266 if (g_Git.ms_LastMsysGitVersion)
3267 return g_Git.ms_LastMsysGitVersion;
3269 CString cmd;
3270 CString versiondebug;
3271 CString version;
3273 CRegDWORD regTime = CRegDWORD(L"Software\\TortoiseGit\\git_file_time");
3274 CRegDWORD regVersion = CRegDWORD(L"Software\\TortoiseGit\\git_cached_version");
3276 CString gitpath = CGit::ms_LastMsysGitDir + L"\\git.exe";
3278 __int64 time=0;
3279 if (!CGit::GetFileModifyTime(gitpath, &time))
3281 if ((DWORD)CGit::filetime_to_time_t(time) == regTime)
3283 g_Git.ms_LastMsysGitVersion = regVersion;
3284 return regVersion;
3288 CString err;
3289 int ver = g_Git.GetGitVersion(&versiondebug, &err);
3290 if (ver < 0)
3292 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);
3293 return -1;
3297 if (!ver)
3299 MessageBox(nullptr, L"Could not parse git.exe version number: \"" + versiondebug + L'"', L"TortoiseGit", MB_OK | MB_ICONERROR);
3300 return -1;
3304 regTime = time&0xFFFFFFFF;
3305 regVersion = ver;
3306 g_Git.ms_LastMsysGitVersion = ver;
3308 return ver;
3311 void CAppUtils::MarkWindowAsUnpinnable(HWND hWnd)
3313 typedef HRESULT (WINAPI *SHGPSFW) (HWND hwnd,REFIID riid,void** ppv);
3315 CAutoLibrary hShell = AtlLoadSystemLibraryUsingFullPath(L"Shell32.dll");
3317 if (hShell.IsValid()) {
3318 SHGPSFW pfnSHGPSFW = (SHGPSFW)::GetProcAddress(hShell, "SHGetPropertyStoreForWindow");
3319 if (pfnSHGPSFW) {
3320 IPropertyStore *pps;
3321 HRESULT hr = pfnSHGPSFW(hWnd, IID_PPV_ARGS(&pps));
3322 if (SUCCEEDED(hr)) {
3323 PROPVARIANT var;
3324 var.vt = VT_BOOL;
3325 var.boolVal = VARIANT_TRUE;
3326 pps->SetValue(PKEY_AppUserModel_PreventPinning, var);
3327 pps->Release();
3333 void CAppUtils::SetWindowTitle(HWND hWnd, const CString& urlorpath, const CString& dialogname)
3335 ASSERT(dialogname.GetLength() < 70);
3336 ASSERT(urlorpath.GetLength() < MAX_PATH);
3337 WCHAR pathbuf[MAX_PATH] = {0};
3339 PathCompactPathEx(pathbuf, urlorpath, 70 - dialogname.GetLength(), 0);
3341 wcscat_s(pathbuf, L" - ");
3342 wcscat_s(pathbuf, dialogname);
3343 wcscat_s(pathbuf, L" - ");
3344 wcscat_s(pathbuf, CString(MAKEINTRESOURCE(IDS_APPNAME)));
3345 SetWindowText(hWnd, pathbuf);
3348 bool CAppUtils::BisectStart(const CString& lastGood, const CString& firstBad, bool bIsMainWnd)
3350 if (!g_Git.CheckCleanWorkTree())
3352 if (CMessageBox::Show(nullptr, IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
3354 CSysProgressDlg sysProgressDlg;
3355 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3356 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3357 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3358 sysProgressDlg.SetShowProgressBar(false);
3359 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3360 sysProgressDlg.ShowModeless((HWND)nullptr, true);
3362 CString out;
3363 if (g_Git.Run(L"git.exe stash", &out, CP_UTF8))
3365 sysProgressDlg.Stop();
3366 MessageBox(nullptr, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3367 return false;
3369 sysProgressDlg.Stop();
3371 else
3372 return false;
3375 CBisectStartDlg bisectStartDlg;
3377 if (!lastGood.IsEmpty())
3378 bisectStartDlg.m_sLastGood = lastGood;
3379 if (!firstBad.IsEmpty())
3380 bisectStartDlg.m_sFirstBad = firstBad;
3382 if (bisectStartDlg.DoModal() == IDOK)
3384 CProgressDlg progress;
3385 if (bIsMainWnd)
3386 theApp.m_pMainWnd = &progress;
3387 progress.m_GitCmdList.push_back(L"git.exe bisect start");
3388 progress.m_GitCmdList.push_back(L"git.exe bisect good " + bisectStartDlg.m_LastGoodRevision);
3389 progress.m_GitCmdList.push_back(L"git.exe bisect bad " + bisectStartDlg.m_FirstBadRevision);
3391 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3393 if (status)
3394 return;
3396 CTGitPath path(g_Git.m_CurrentDir);
3397 if (path.HasSubmodules())
3399 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
3401 CString sCmd;
3402 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3403 CAppUtils::RunTortoiseGitProc(sCmd);
3408 INT_PTR ret = progress.DoModal();
3409 return ret == IDOK;
3412 return false;
3415 bool CAppUtils::BisectOperation(const CString& op, const CString& ref, bool bIsMainWnd)
3417 CString cmd = L"git.exe bisect " + op;
3419 if (!ref.IsEmpty())
3421 cmd += L' ';
3422 cmd += ref;
3425 CProgressDlg progress;
3426 if (bIsMainWnd)
3427 theApp.m_pMainWnd = &progress;
3428 progress.m_GitCmd = cmd;
3430 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3432 if (status)
3433 return;
3435 CTGitPath path = g_Git.m_CurrentDir;
3436 if (path.HasSubmodules())
3438 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
3440 CString sCmd;
3441 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3442 CAppUtils::RunTortoiseGitProc(sCmd);
3446 if (op != L"reset")
3447 postCmdList.emplace_back(IDS_MENUBISECTRESET, []{ CAppUtils::RunTortoiseGitProc(L"/command:bisect /reset"); });
3450 INT_PTR ret = progress.DoModal();
3451 return ret == IDOK;
3454 int CAppUtils::Git2GetUserPassword(git_cred **out, const char *url, const char *username_from_url, unsigned int /*allowed_types*/, void * /*payload*/)
3456 CUserPassword dlg;
3457 dlg.m_URL = CUnicodeUtils::GetUnicode(url, CP_UTF8);
3458 if (username_from_url)
3459 dlg.m_UserName = CUnicodeUtils::GetUnicode(username_from_url, CP_UTF8);
3461 CStringA username, password;
3462 if (dlg.DoModal() == IDOK)
3464 username = CUnicodeUtils::GetMulti(dlg.m_UserName, CP_UTF8);
3465 password = CUnicodeUtils::GetMulti(dlg.m_Password, CP_UTF8);
3466 return git_cred_userpass_plaintext_new(out, username, password);
3468 giterr_set_str(GITERR_NONE, "User cancelled.");
3469 return GIT_EUSER;
3472 int CAppUtils::Git2CertificateCheck(git_cert* base_cert, int /*valid*/, const char* host, void* /*payload*/)
3474 if (base_cert->cert_type == GIT_CERT_X509)
3476 git_cert_x509* cert = (git_cert_x509*)base_cert;
3478 if (last_accepted_cert.cmp(cert))
3479 return 0;
3481 PCCERT_CONTEXT pServerCert = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, (BYTE*)cert->data, (DWORD)cert->len);
3482 SCOPE_EXIT { CertFreeCertificateContext(pServerCert); };
3484 DWORD verificationError = VerifyServerCertificate(pServerCert, CUnicodeUtils::GetUnicode(host).GetBuffer(), 0);
3485 if (!verificationError)
3487 last_accepted_cert.set(cert);
3488 return 0;
3491 CString servernameInCert;
3492 CertGetNameString(pServerCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nullptr, CStrBuf(servernameInCert, 128), 128);
3494 CString issuer;
3495 CertGetNameString(pServerCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, CERT_NAME_ISSUER_FLAG, nullptr, CStrBuf(issuer, 128), 128);
3497 CCheckCertificateDlg dlg;
3498 dlg.cert = cert;
3499 dlg.m_sCertificateCN = servernameInCert;
3500 dlg.m_sCertificateIssuer = issuer;
3501 dlg.m_sHostname = CUnicodeUtils::GetUnicode(host);
3502 dlg.m_sError = CFormatMessageWrapper(verificationError);
3503 if (dlg.DoModal() == IDOK)
3505 last_accepted_cert.set(cert);
3506 return 0;
3509 return GIT_ECERTIFICATE;
3512 int CAppUtils::ExploreTo(HWND hwnd, CString path)
3514 if (PathFileExists(path))
3516 HRESULT ret = E_FAIL;
3517 ITEMIDLIST __unaligned * pidl = ILCreateFromPath(path);
3518 if (pidl)
3520 ret = SHOpenFolderAndSelectItems(pidl, 0, 0, 0);
3521 ILFree(pidl);
3523 return SUCCEEDED(ret) ? 0 : -1;
3525 // if filepath does not exist any more, navigate to closest matching folder
3528 int pos = path.ReverseFind(L'\\');
3529 if (pos <= 3)
3530 break;
3531 path.Truncate(pos);
3532 } while (!PathFileExists(path));
3533 return (INT_PTR)ShellExecute(hwnd, L"explore", path, nullptr, nullptr, SW_SHOW) > 32 ? 0 : -1;
3536 int CAppUtils::ResolveConflict(CTGitPath& path, resolve_with resolveWith)
3538 bool b_local = false, b_remote = false;
3539 BYTE_VECTOR vector;
3541 CString cmd;
3542 cmd.Format(L"git.exe ls-files -u -t -z -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3543 if (g_Git.Run(cmd, &vector))
3545 MessageBox(nullptr, L"git ls-files failed!", L"TortoiseGit", MB_OK);
3546 return -1;
3549 CTGitPathList list;
3550 if (list.ParserFromLsFile(vector))
3552 MessageBox(nullptr, L"Parse ls-files failed!", L"TortoiseGit", MB_OK);
3553 return -1;
3556 if (list.IsEmpty())
3557 return 0;
3558 for (int i = 0; i < list.GetCount(); ++i)
3560 if (list[i].m_Stage == 2)
3561 b_local = true;
3562 if (list[i].m_Stage == 3)
3563 b_remote = true;
3567 bool baseIsFile = true, localIsFile = true, remoteIsFile = true;
3568 CString baseHash, localHash, remoteHash;
3569 ParseHashesFromLsFile(vector, baseHash, baseIsFile, localHash, localIsFile, remoteHash, remoteIsFile);
3571 CBlockCacheForPath block(g_Git.m_CurrentDir);
3572 if ((resolveWith == RESOLVE_WITH_THEIRS && !b_remote) || (resolveWith == RESOLVE_WITH_MINE && !b_local))
3574 CString gitcmd, output; //retest with registered submodule!
3575 gitcmd.Format(L"git.exe rm -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3576 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3578 // a .git folder in a submodule which is not in .gitmodules cannot be deleted using "git rm"
3579 if (PathIsDirectory(path.GetGitPathString()) && !PathIsDirectoryEmpty(path.GetGitPathString()))
3581 CString message(output);
3582 output += L"\n\n";
3583 output.AppendFormat(IDS_PROC_DELETEBRANCHTAG, path.GetWinPath());
3584 CString deleteButton;
3585 deleteButton.LoadString(IDS_DELETEBUTTON);
3586 CString abortButton;
3587 abortButton.LoadString(IDS_ABORTBUTTON);
3588 if (CMessageBox::Show(nullptr, output, L"TortoiseGit", 2, IDI_QUESTION, deleteButton, abortButton) == 2)
3589 return -1;
3590 path.Delete(true, true);
3591 output.Empty();
3592 if (!g_Git.Run(gitcmd, &output, CP_UTF8))
3594 RemoveTempMergeFile(path);
3595 return 0;
3598 MessageBox(nullptr, output, L"TortoiseGit", MB_ICONERROR);
3599 return -1;
3601 RemoveTempMergeFile(path);
3602 return 0;
3605 if (resolveWith == RESOLVE_WITH_THEIRS || resolveWith == RESOLVE_WITH_MINE)
3607 auto resolve = [&b_local, &b_remote](const CTGitPath& path, int stage, bool willBeFile, const CString& hash) -> int
3609 if (!willBeFile)
3611 if (!path.HasAdminDir()) // check if submodule is initialized
3613 CString gitcmd, output;
3614 if (!path.IsDirectory())
3616 gitcmd.Format(L"git.exe checkout-index -f --stage=%d -- \"%s\"", stage, (LPCTSTR)path.GetGitPathString());
3617 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3619 MessageBox(nullptr, output, L"TortoiseGit", MB_ICONERROR);
3620 return -1;
3623 gitcmd.Format(L"git.exe update-index --replace --cacheinfo 0160000,%s,\"%s\"", (LPCTSTR)hash, (LPCTSTR)path.GetGitPathString());
3624 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3626 MessageBox(nullptr, output, L"TortoiseGit", MB_ICONERROR);
3627 return -1;
3629 return 0;
3632 CGit subgit;
3633 subgit.m_CurrentDir = g_Git.CombinePath(path);
3634 CGitHash submoduleHead;
3635 if (subgit.GetHash(submoduleHead, L"HEAD"))
3637 MessageBox(nullptr, subgit.GetGitLastErr(L"Could not get HEAD hash of submodule, this should not happen!"), L"TortoiseGit", MB_ICONERROR);
3638 return -1;
3640 if (submoduleHead.ToString() != hash)
3642 CString origPath = g_Git.m_CurrentDir;
3643 g_Git.m_CurrentDir = g_Git.CombinePath(path);
3644 if (!GitReset(&hash))
3646 g_Git.m_CurrentDir = origPath;
3647 return -1;
3649 g_Git.m_CurrentDir = origPath;
3652 else
3654 CString gitcmd, output;
3655 if (b_local && b_remote)
3656 gitcmd.Format(L"git.exe checkout-index -f --stage=%d -- \"%s\"", stage, (LPCTSTR)path.GetGitPathString());
3657 else
3658 gitcmd.Format(L"git.exe add -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3659 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3661 MessageBox(nullptr, output, L"TortoiseGit", MB_ICONERROR);
3662 return -1;
3665 return 0;
3667 int ret = -1;
3668 if (resolveWith == RESOLVE_WITH_THEIRS)
3669 ret = resolve(path, 3, remoteIsFile, remoteHash);
3670 else
3671 ret = resolve(path, 2, localIsFile, localHash);
3672 if (ret)
3673 return ret;
3676 if (PathFileExists(g_Git.CombinePath(path)) && (path.m_Action & CTGitPath::LOGACTIONS_UNMERGED))
3678 CString gitcmd, output;
3679 gitcmd.Format(L"git.exe add -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3680 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3682 MessageBox(nullptr, output, L"TortoiseGit", MB_ICONERROR);
3683 return -1;
3686 path.m_Action |= CTGitPath::LOGACTIONS_MODIFIED;
3687 path.m_Action &= ~CTGitPath::LOGACTIONS_UNMERGED;
3690 RemoveTempMergeFile(path);
3691 return 0;
3694 bool CAppUtils::ShellOpen(const CString& file, HWND hwnd /*= nullptr */)
3696 if ((INT_PTR)ShellExecute(hwnd, nullptr, file, nullptr, nullptr, SW_SHOW) > HINSTANCE_ERROR)
3697 return true;
3699 return ShowOpenWithDialog(file, hwnd);
3702 bool CAppUtils::ShowOpenWithDialog(const CString& file, HWND hwnd /*= nullptr */)
3704 OPENASINFO oi = { 0 };
3705 oi.pcszFile = file;
3706 oi.oaifInFlags = OAIF_EXEC;
3707 return SUCCEEDED(SHOpenWithDialog(hwnd, &oi));
3710 bool CAppUtils::IsTGitRebaseActive()
3712 CString adminDir;
3713 if (!GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir))
3714 return false;
3716 if (!PathIsDirectory(adminDir + L"tgitrebase.active"))
3717 return false;
3719 if (CMessageBox::Show(nullptr, IDS_REBASELOCKFILEFOUND, IDS_APPNAME, 2, IDI_EXCLAMATION, IDS_REMOVESTALEBUTTON, IDS_ABORTBUTTON) == 2)
3720 return true;
3722 RemoveDirectory(adminDir + L"tgitrebase.active");
3724 return false;
3727 bool CAppUtils::DeleteRef(CWnd* parent, const CString& ref)
3729 CString shortname;
3730 if (CGit::GetShortName(ref, shortname, L"refs/remotes/"))
3732 CString msg;
3733 msg.Format(IDS_PROC_DELETEREMOTEBRANCH, (LPCTSTR)ref);
3734 int result = CMessageBox::Show(parent->GetSafeOwner()->GetSafeHwnd(), msg, L"TortoiseGit", 3, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_PROC_DELETEREMOTEBRANCH_LOCALREMOTE)), CString(MAKEINTRESOURCE(IDS_PROC_DELETEREMOTEBRANCH_LOCAL)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON)));
3735 if (result == 1)
3737 CString remoteName = shortname.Left(shortname.Find(L'/'));
3738 shortname = shortname.Mid(shortname.Find(L'/') + 1);
3739 if (CAppUtils::IsSSHPutty())
3740 CAppUtils::LaunchPAgent(nullptr, &remoteName);
3742 CSysProgressDlg sysProgressDlg;
3743 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3744 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
3745 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3746 sysProgressDlg.SetShowProgressBar(false);
3747 sysProgressDlg.ShowModal(parent, true);
3748 STRING_VECTOR list;
3749 list.push_back(L"refs/heads/" + shortname);
3750 if (g_Git.DeleteRemoteRefs(remoteName, list))
3751 CMessageBox::Show(parent->GetSafeOwner()->GetSafeHwnd(), g_Git.GetGitLastErr(L"Could not delete remote ref.", CGit::GIT_CMD_PUSH), L"TortoiseGit", MB_OK | MB_ICONERROR);
3752 sysProgressDlg.Stop();
3753 return true;
3755 else if (result == 2)
3757 if (g_Git.DeleteRef(ref))
3759 CMessageBox::Show(parent->GetSafeOwner()->GetSafeHwnd(), g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), L"TortoiseGit", MB_OK | MB_ICONERROR);
3760 return false;
3762 return true;
3764 return false;
3766 else if (CGit::GetShortName(ref, shortname, L"refs/stash"))
3768 CString err;
3769 std::vector<GitRevLoglist> stashList;
3770 size_t count = !GitRevLoglist::GetRefLog(ref, stashList, err) ? stashList.size() : 0;
3771 CString msg;
3772 msg.Format(IDS_PROC_DELETEALLSTASH, count);
3773 int choose = CMessageBox::Show(parent->GetSafeOwner()->GetSafeHwnd(), msg, L"TortoiseGit", 3, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_DROPONESTASH)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON)));
3774 if (choose == 1)
3776 CString out;
3777 if (g_Git.Run(L"git.exe stash clear", &out, CP_UTF8))
3778 CMessageBox::Show(parent->GetSafeOwner()->GetSafeHwnd(), out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3779 return true;
3781 else if (choose == 2)
3783 CString out;
3784 if (g_Git.Run(L"git.exe stash drop refs/stash@{0}", &out, CP_UTF8))
3785 CMessageBox::Show(parent->GetSafeOwner()->GetSafeHwnd(), out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3786 return true;
3788 return false;
3791 CString msg;
3792 msg.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)ref);
3793 // Check if branch is fully merged in HEAD
3794 if (CGit::GetShortName(ref, shortname, L"refs/heads/") && !g_Git.IsFastForward(ref, L"HEAD"))
3796 msg += L"\n\n";
3797 msg += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED));
3799 if (CMessageBox::Show(parent->GetSafeOwner()->GetSafeHwnd(), msg, L"TortoiseGit", 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
3801 if (g_Git.DeleteRef(ref))
3803 CMessageBox::Show(parent->GetSafeOwner()->GetSafeHwnd(), g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), L"TortoiseGit", MB_OK | MB_ICONERROR);
3804 return false;
3806 return true;
3808 return false;