Start new TortoiseGitProc instance for showing log and filediff
[TortoiseGit.git] / src / TortoiseProc / AppUtils.cpp
blobfe2d25e28ed25c436c78ad4ff984b4435015b816
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2016 - TortoiseGit
4 // Copyright (C) 2003-2011, 2013-2014 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "TortoiseProc.h"
22 #include "PathUtils.h"
23 #include "AppUtils.h"
24 #include "MessageBox.h"
25 #include "registry.h"
26 #include "TGitPath.h"
27 #include "Git.h"
28 #include "UnicodeUtils.h"
29 #include "ExportDlg.h"
30 #include "ProgressDlg.h"
31 #include "GitAdminDir.h"
32 #include "ProgressDlg.h"
33 #include "BrowseFolder.h"
34 #include "DirFileEnum.h"
35 #include "GitStatus.h"
36 #include "CreateBranchTagDlg.h"
37 #include "GitSwitchDlg.h"
38 #include "ResetDlg.h"
39 #include "DeleteConflictDlg.h"
40 #include "ChangedDlg.h"
41 #include "SendMailDlg.h"
42 #include "GitProgressDlg.h"
43 #include "PushDlg.h"
44 #include "CommitDlg.h"
45 #include "MergeDlg.h"
46 #include "MergeAbortDlg.h"
47 #include "Hooks.h"
48 #include "..\Settings\Settings.h"
49 #include "InputDlg.h"
50 #include "SVNDCommitDlg.h"
51 #include "requestpulldlg.h"
52 #include "PullFetchDlg.h"
53 #include "RebaseDlg.h"
54 #include "PropKey.h"
55 #include "StashSave.h"
56 #include "IgnoreDlg.h"
57 #include "FormatMessageWrapper.h"
58 #include "SmartHandle.h"
59 #include "BisectStartDlg.h"
60 #include "SysProgressDlg.h"
61 #include "UserPassword.h"
62 #include "SendmailPatch.h"
63 #include "Globals.h"
64 #include "ProgressCommands/ResetProgressCommand.h"
65 #include "ProgressCommands/FetchProgressCommand.h"
66 #include "ProgressCommands/SendMailProgressCommand.h"
67 #include "CertificateValidationHelper.h"
68 #include "CheckCertificateDlg.h"
69 #include "SubmoduleResolveConflictDlg.h"
70 #include "GitDiff.h"
71 #include "../TGitCache/CacheInterface.h"
73 static struct last_accepted_cert {
74 BYTE* data;
75 size_t len;
77 last_accepted_cert()
78 : data(nullptr)
79 , len(0)
82 ~last_accepted_cert()
84 free(data);
86 boolean cmp(git_cert_x509* cert)
88 return len > 0 && len == cert->len && memcmp(data, cert->data, len) == 0;
90 void set(git_cert_x509* cert)
92 free(data);
93 len = cert->len;
94 if (len == 0)
96 data = nullptr;
97 return;
99 data = new BYTE[len];
100 memcpy(data, cert->data, len);
102 } last_accepted_cert;
104 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);
106 bool CAppUtils::StashSave(const CString& msg, bool showPull, bool pullShowPush, bool showMerge, const CString& mergeRev)
108 CStashSaveDlg dlg;
109 dlg.m_sMessage = msg;
110 if (dlg.DoModal() == IDOK)
112 CString cmd = L"git.exe stash save";
114 if (dlg.m_bIncludeUntracked)
115 cmd += L" --include-untracked";
116 else if (dlg.m_bAll)
117 cmd += L" --all";
119 if (!dlg.m_sMessage.IsEmpty())
121 CString message = dlg.m_sMessage;
122 message.Replace(L"\"", L"\"\"");
123 cmd += L" -- \"" + message + L'"';
126 CProgressDlg progress;
127 progress.m_GitCmd = cmd;
128 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
130 if (status)
131 return;
133 if (showPull)
134 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&]{ CAppUtils::Pull(pullShowPush, true); });
135 if (showMerge)
136 postCmdList.emplace_back(IDI_MERGE, IDS_MENUMERGE, [&]{ CAppUtils::Merge(&mergeRev, true); });
138 return (progress.DoModal() == IDOK);
140 return false;
143 bool CAppUtils::StashApply(CString ref, bool showChanges /* true */)
145 CString cmd = L"git.exe stash apply ";
146 if (CStringUtils::StartsWith(ref, L"refs/"))
147 ref = ref.Mid(5);
148 if (CStringUtils::StartsWith(ref, L"stash{"))
149 ref = L"stash@" + ref.Mid(5);
150 cmd += ref;
152 CSysProgressDlg sysProgressDlg;
153 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
154 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
155 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
156 sysProgressDlg.SetShowProgressBar(false);
157 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
158 sysProgressDlg.ShowModeless((HWND)nullptr, true);
160 CString out;
161 int ret = g_Git.Run(cmd, &out, CP_UTF8);
163 sysProgressDlg.Stop();
165 bool hasConflicts = (out.Find(L"CONFLICT") >= 0);
166 if (ret && !(ret == 1 && hasConflicts))
167 CMessageBox::Show(nullptr, CString(MAKEINTRESOURCE(IDS_PROC_STASHAPPLYFAILED)) + L'\n' + out, L"TortoiseGit", MB_OK | MB_ICONERROR);
168 else
170 CString message;
171 message.LoadString(IDS_PROC_STASHAPPLYSUCCESS);
172 if (hasConflicts)
173 message.LoadString(IDS_PROC_STASHAPPLYFAILEDCONFLICTS);
174 if (showChanges)
176 if (CMessageBox::Show(nullptr, message + L'\n' + CString(MAKEINTRESOURCE(IDS_SEECHANGES)), L"TortoiseGit", MB_YESNO | MB_ICONINFORMATION) == IDYES)
178 CChangedDlg dlg;
179 dlg.m_pathList.AddPath(CTGitPath());
180 dlg.DoModal();
182 return true;
184 else
186 MessageBox(nullptr, message ,L"TortoiseGit", MB_OK | MB_ICONINFORMATION);
187 return true;
190 return false;
193 bool CAppUtils::StashPop(int showChanges /* = 1 */)
195 CString cmd = L"git.exe stash pop";
197 CSysProgressDlg sysProgressDlg;
198 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
199 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
200 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
201 sysProgressDlg.SetShowProgressBar(false);
202 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
203 sysProgressDlg.ShowModeless((HWND)nullptr, true);
205 CString out;
206 int ret = g_Git.Run(cmd, &out, CP_UTF8);
208 sysProgressDlg.Stop();
210 bool hasConflicts = (out.Find(L"CONFLICT") >= 0);
211 if (ret && !(ret == 1 && hasConflicts))
212 CMessageBox::Show(nullptr, CString(MAKEINTRESOURCE(IDS_PROC_STASHPOPFAILED)) + L'\n' + out, L"TortoiseGit", MB_OK | MB_ICONERROR);
213 else
215 CString message;
216 message.LoadString(IDS_PROC_STASHPOPSUCCESS);
217 if (hasConflicts)
218 message.LoadString(IDS_PROC_STASHPOPFAILEDCONFLICTS);
219 if (showChanges == 1 || (showChanges == 0 && hasConflicts))
221 if (CMessageBox::ShowCheck(nullptr, message + L'\n' + CString(MAKEINTRESOURCE(IDS_SEECHANGES)), L"TortoiseGit", MB_YESNO | (hasConflicts ? MB_ICONEXCLAMATION : MB_ICONINFORMATION), hasConflicts ? L"StashPopShowConflictChanges" : L"StashPopShowChanges") == IDYES)
223 CChangedDlg dlg;
224 dlg.m_pathList.AddPath(CTGitPath());
225 dlg.DoModal();
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 if ((bReadOnly)&&(bInternal))
394 com += L" /readonly";
396 if(!LaunchApplication(com, IDS_ERR_EXTMERGESTART, false))
398 return FALSE;
401 return TRUE;
404 BOOL CAppUtils::StartExtPatch(const CTGitPath& patchfile, const CTGitPath& dir, const CString& sOriginalDescription, const CString& sPatchedDescription, BOOL bReversed, BOOL bWait)
406 CString viewer;
407 // use TortoiseGitMerge
408 viewer = CPathUtils::GetAppDirectory();
409 viewer += L"TortoiseGitMerge.exe";
411 viewer = L'"' + viewer + L'"';
412 viewer = viewer + L" /diff:\"" + patchfile.GetWinPathString() + L'"';
413 viewer = viewer + L" /patchpath:\"" + dir.GetWinPathString() + L'"';
414 if (bReversed)
415 viewer += L" /reversedpatch";
416 if (!sOriginalDescription.IsEmpty())
417 viewer = viewer + L" /patchoriginal:\"" + sOriginalDescription + L'"';
418 if (!sPatchedDescription.IsEmpty())
419 viewer = viewer + L" /patchpatched:\"" + sPatchedDescription + L'"';
420 if (!g_sGroupingUUID.IsEmpty())
422 viewer += L" /groupuuid:\"";
423 viewer += g_sGroupingUUID;
424 viewer += L'"';
426 if(!LaunchApplication(viewer, IDS_ERR_DIFFVIEWSTART, !!bWait))
427 return FALSE;
428 return TRUE;
431 CString CAppUtils::PickDiffTool(const CTGitPath& file1, const CTGitPath& file2)
433 CString difftool = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + file2.GetFilename().MakeLower());
434 if (!difftool.IsEmpty())
435 return difftool;
436 difftool = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + file1.GetFilename().MakeLower());
437 if (!difftool.IsEmpty())
438 return difftool;
440 // Is there an extension specific diff tool?
441 CString ext = file2.GetFileExtension().MakeLower();
442 if (!ext.IsEmpty())
444 difftool = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + ext);
445 if (!difftool.IsEmpty())
446 return difftool;
447 // Maybe we should use TortoiseIDiff?
448 if ((ext == L".jpg") || (ext == L".jpeg") ||
449 (ext == L".bmp") || (ext == L".gif") ||
450 (ext == L".png") || (ext == L".ico") ||
451 (ext == L".tif") || (ext == L".tiff") ||
452 (ext == L".dib") || (ext == L".emf") ||
453 (ext == L".cur"))
455 return
456 L'"' + CPathUtils::GetAppDirectory() + L"TortoiseGitIDiff.exe" + L'"' +
457 L" /left:%base /right:%mine /lefttitle:%bname /righttitle:%yname" +
458 L" /groupuuid:\"" + g_sGroupingUUID + L'"';
462 // Finally, pick a generic external diff tool
463 difftool = CRegString(L"Software\\TortoiseGit\\Diff");
464 return difftool;
467 bool CAppUtils::StartExtDiff(
468 const CString& file1, const CString& file2,
469 const CString& sName1, const CString& sName2,
470 const CString& originalFile1, const CString& originalFile2,
471 const git_revnum_t& hash1, const git_revnum_t& hash2,
472 const DiffFlags& flags, int jumpToLine)
474 CString viewer;
476 CRegDWORD blamediff(L"Software\\TortoiseGit\\DiffBlamesWithTortoiseMerge", FALSE);
477 if (!flags.bBlame || !(DWORD)blamediff)
479 viewer = PickDiffTool(file1, file2);
480 // If registry entry for a diff program is commented out, use TortoiseGitMerge.
481 bool bCommentedOut = CStringUtils::StartsWith(viewer, L"#");
482 if (flags.bAlternativeTool)
484 // Invert external vs. internal diff tool selection.
485 if (bCommentedOut)
486 viewer.Delete(0); // uncomment
487 else
488 viewer.Empty();
490 else if (bCommentedOut)
491 viewer.Empty();
494 bool bInternal = viewer.IsEmpty();
495 if (bInternal)
497 viewer =
498 L'"' + CPathUtils::GetAppDirectory() + L"TortoiseGitMerge.exe" + L'"' +
499 L" /base:%base /mine:%mine /basename:%bname /minename:%yname" +
500 L" /basereflectedname:%bpath /minereflectedname:%ypath";
501 if (!g_sGroupingUUID.IsEmpty())
503 viewer += L" /groupuuid:\"";
504 viewer += g_sGroupingUUID;
505 viewer += L'"';
507 if (flags.bBlame)
508 viewer += L" /blame";
510 // check if the params are set. If not, just add the files to the command line
511 if ((viewer.Find(L"%base") < 0) && (viewer.Find(L"%mine") < 0))
513 viewer += L" \"" + file1 + L'"';
514 viewer += L" \"" + file2 + L'"';
516 if (viewer.Find(L"%base") >= 0)
517 viewer.Replace(L"%base", L'"' + file1 + L'"');
518 if (viewer.Find(L"%mine") >= 0)
519 viewer.Replace(L"%mine", L'"' + file2 + L'"');
521 if (sName1.IsEmpty())
522 viewer.Replace(L"%bname", L'"' + file1 + L'"');
523 else
524 viewer.Replace(L"%bname", L'"' + sName1 + L'"');
526 if (sName2.IsEmpty())
527 viewer.Replace(L"%yname", L'"' + file2 + L'"');
528 else
529 viewer.Replace(L"%yname", L'"' + sName2 + L'"');
531 viewer.Replace(L"%bpath", L'"' + originalFile1 + L'"');
532 viewer.Replace(L"%ypath", L'"' + originalFile2 + L'"');
534 viewer.Replace(L"%brev", L'"' + hash1 + L'"');
535 viewer.Replace(L"%yrev", L'"' + hash2 + L'"');
537 if (flags.bReadOnly && bInternal)
538 viewer += L" /readonly";
540 if (jumpToLine > 0)
541 viewer.AppendFormat(L" /line:%d", jumpToLine);
543 return LaunchApplication(viewer, IDS_ERR_EXTDIFFSTART, flags.bWait);
546 BOOL CAppUtils::StartUnifiedDiffViewer(const CString& patchfile, const CString& title, BOOL bWait, bool bAlternativeTool)
548 CString viewer;
549 CRegString v = CRegString(L"Software\\TortoiseGit\\DiffViewer");
550 viewer = v;
552 // If registry entry for a diff program is commented out, use TortoiseGitMerge.
553 bool bCommentedOut = CStringUtils::StartsWith(viewer, L"#");
554 if (bAlternativeTool)
556 // Invert external vs. internal diff tool selection.
557 if (bCommentedOut)
558 viewer.Delete(0); // uncomment
559 else
560 viewer.Empty();
562 else if (bCommentedOut)
563 viewer.Empty();
565 if (viewer.IsEmpty())
567 // use TortoiseGitUDiff
568 viewer = CPathUtils::GetAppDirectory();
569 viewer += L"TortoiseGitUDiff.exe";
570 // enquote the path to TortoiseGitUDiff
571 viewer = L'"' + viewer + L'"';
572 // add the params
573 viewer = viewer + L" /patchfile:%1 /title:\"%title\"";
574 if (!g_sGroupingUUID.IsEmpty())
576 viewer += L" /groupuuid:\"";
577 viewer += g_sGroupingUUID;
578 viewer += L'"';
581 if (viewer.Find(L"%1") >= 0)
583 if (viewer.Find(L"\"%1\"") >= 0)
584 viewer.Replace(L"%1", patchfile);
585 else
586 viewer.Replace(L"%1", L'"' + patchfile + L'"');
588 else
589 viewer += L" \"" + patchfile + L'"';
590 if (viewer.Find(L"%title") >= 0)
591 viewer.Replace(L"%title", title);
593 if(!LaunchApplication(viewer, IDS_ERR_DIFFVIEWSTART, !!bWait))
594 return FALSE;
595 return TRUE;
598 BOOL CAppUtils::StartTextViewer(CString file)
600 CString viewer;
601 CRegString txt = CRegString(L".txt\\", L"", FALSE, HKEY_CLASSES_ROOT);
602 viewer = txt;
603 viewer = viewer + L"\\Shell\\Open\\Command\\";
604 CRegString txtexe = CRegString(viewer, L"", FALSE, HKEY_CLASSES_ROOT);
605 viewer = txtexe;
607 DWORD len = ExpandEnvironmentStrings(viewer, nullptr, 0);
608 auto buf = std::make_unique<TCHAR[]>(len + 1);
609 ExpandEnvironmentStrings(viewer, buf.get(), len);
610 viewer = buf.get();
611 len = ExpandEnvironmentStrings(file, nullptr, 0);
612 auto buf2 = std::make_unique<TCHAR[]>(len + 1);
613 ExpandEnvironmentStrings(file, buf2.get(), len);
614 file = buf2.get();
615 file = L'"' + file + L'"';
616 if (viewer.IsEmpty())
617 return CAppUtils::ShowOpenWithDialog(file) ? TRUE : FALSE;
618 if (viewer.Find(L"\"%1\"") >= 0)
619 viewer.Replace(L"\"%1\"", file);
620 else if (viewer.Find(L"%1") >= 0)
621 viewer.Replace(L"%1", file);
622 else
623 viewer += L' ';
624 viewer += file;
626 if(!LaunchApplication(viewer, IDS_ERR_TEXTVIEWSTART, false))
627 return FALSE;
628 return TRUE;
631 BOOL CAppUtils::CheckForEmptyDiff(const CTGitPath& sDiffPath)
633 DWORD length = 0;
634 CAutoFile hFile = ::CreateFile(sDiffPath.GetWinPath(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
635 if (!hFile)
636 return TRUE;
637 length = ::GetFileSize(hFile, nullptr);
638 if (length < 4)
639 return TRUE;
640 return FALSE;
643 CString CAppUtils::GetLogFontName()
645 return (CString)CRegString(L"Software\\TortoiseGit\\LogFontName", L"Consolas");
648 DWORD CAppUtils::GetLogFontSize()
650 return (DWORD)CRegDWORD(L"Software\\TortoiseGit\\LogFontSize", 9);
653 void CAppUtils::CreateFontForLogs(CFont& fontToCreate)
655 LOGFONT logFont;
656 HDC hScreenDC = ::GetDC(nullptr);
657 logFont.lfHeight = -MulDiv(GetLogFontSize(), GetDeviceCaps(hScreenDC, LOGPIXELSY), 72);
658 ::ReleaseDC(nullptr, hScreenDC);
659 logFont.lfWidth = 0;
660 logFont.lfEscapement = 0;
661 logFont.lfOrientation = 0;
662 logFont.lfWeight = FW_NORMAL;
663 logFont.lfItalic = 0;
664 logFont.lfUnderline = 0;
665 logFont.lfStrikeOut = 0;
666 logFont.lfCharSet = DEFAULT_CHARSET;
667 logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
668 logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
669 logFont.lfQuality = DRAFT_QUALITY;
670 logFont.lfPitchAndFamily = FF_DONTCARE | FIXED_PITCH;
671 wcscpy_s(logFont.lfFaceName, 32, (LPCTSTR)GetLogFontName());
672 VERIFY(fontToCreate.CreateFontIndirect(&logFont));
675 bool CAppUtils::LaunchPAgent(const CString* keyfile, const CString* pRemote)
677 CString key,remote;
678 CString cmd,out;
679 if (!pRemote)
680 remote = L"origin";
681 else
682 remote=*pRemote;
684 if (!keyfile)
686 cmd.Format(L"remote.%s.puttykeyfile", (LPCTSTR)remote);
687 key = g_Git.GetConfigValue(cmd);
689 else
690 key=*keyfile;
692 if(key.IsEmpty())
693 return false;
695 CString proc=CPathUtils::GetAppDirectory();
696 proc += L"pageant.exe \"";
697 proc += key;
698 proc += L'"';
700 CString tempfile = GetTempFile();
701 ::DeleteFile(tempfile);
703 proc += L" -c \"";
704 proc += CPathUtils::GetAppDirectory();
705 proc += L"tgittouch.exe\"";
706 proc += L" \"";
707 proc += tempfile;
708 proc += L'"';
710 CString appDir = CPathUtils::GetAppDirectory();
711 bool b = LaunchApplication(proc, IDS_ERR_PAGEANT, true, &appDir);
712 if(!b)
713 return b;
715 int i=0;
716 while(!::PathFileExists(tempfile))
718 Sleep(100);
719 ++i;
720 if(i>10*60*5)
721 break; //timeout 5 minutes
724 if( i== 10*60*5)
725 CMessageBox::Show(nullptr, IDS_ERR_PAEGENTTIMEOUT, IDS_APPNAME, MB_OK | MB_ICONERROR);
726 ::DeleteFile(tempfile);
727 return true;
730 bool CAppUtils::LaunchAlternativeEditor(const CString& filename, bool uac)
732 CString editTool = CRegString(L"Software\\TortoiseGit\\AlternativeEditor");
733 if (editTool.IsEmpty() || CStringUtils::StartsWith(editTool, L"#"))
734 editTool = CPathUtils::GetAppDirectory() + L"notepad2.exe";
736 CString sCmd;
737 sCmd.Format(L"\"%s\" \"%s\"", (LPCTSTR)editTool, (LPCTSTR)filename);
739 LaunchApplication(sCmd, 0, false, nullptr, uac);
740 return true;
743 bool CAppUtils::LaunchRemoteSetting()
745 CTGitPath path(g_Git.m_CurrentDir);
746 CSettings dlg(IDS_PROC_SETTINGS_TITLE, &path);
747 dlg.SetTreeViewMode(TRUE, TRUE, TRUE);
748 dlg.SetTreeWidth(220);
749 dlg.m_DefaultPage = L"gitremote";
751 dlg.DoModal();
752 dlg.HandleRestart();
753 return true;
757 * Launch the external blame viewer
759 bool CAppUtils::LaunchTortoiseBlame(const CString& sBlameFile, const CString& Rev, const CString& sParams)
761 CString viewer = L'"' + CPathUtils::GetAppDirectory();
762 viewer += L"TortoiseGitBlame.exe";
763 viewer += L"\" \"" + sBlameFile + L'"';
764 //viewer += L" \"" + sLogFile + L'"';
765 //viewer += L" \"" + sOriginalFile + L'"';
766 if(!Rev.IsEmpty() && Rev != GIT_REV_ZERO)
767 viewer += L" /rev:" + Rev;
768 if (!g_sGroupingUUID.IsEmpty())
770 viewer += L" /groupuuid:\"";
771 viewer += g_sGroupingUUID;
772 viewer += L'"';
774 viewer += L' ' + sParams;
776 return LaunchApplication(viewer, IDS_ERR_TGITBLAME, false);
779 bool CAppUtils::FormatTextInRichEditControl(CWnd * pWnd)
781 CString sText;
782 if (!pWnd)
783 return false;
784 bool bStyled = false;
785 pWnd->GetWindowText(sText);
786 // the rich edit control doesn't count the CR char!
787 // to be exact: CRLF is treated as one char.
788 sText.Remove(L'\r');
790 // style each line separately
791 int offset = 0;
792 int nNewlinePos;
795 nNewlinePos = sText.Find('\n', offset);
796 CString sLine = nNewlinePos >= 0 ? sText.Mid(offset, nNewlinePos - offset) : sText.Mid(offset);
798 int start = 0;
799 int end = 0;
800 while (FindStyleChars(sLine, '*', start, end))
802 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
803 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
804 SetCharFormat(pWnd, CFM_BOLD, CFE_BOLD);
805 bStyled = true;
806 start = end;
808 start = 0;
809 end = 0;
810 while (FindStyleChars(sLine, '^', start, end))
812 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
813 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
814 SetCharFormat(pWnd, CFM_ITALIC, CFE_ITALIC);
815 bStyled = true;
816 start = end;
818 start = 0;
819 end = 0;
820 while (FindStyleChars(sLine, '_', start, end))
822 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
823 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
824 SetCharFormat(pWnd, CFM_UNDERLINE, CFE_UNDERLINE);
825 bStyled = true;
826 start = end;
828 offset = nNewlinePos+1;
829 } while(nNewlinePos>=0);
830 return bStyled;
833 bool CAppUtils::FindStyleChars(const CString& sText, TCHAR stylechar, int& start, int& end)
835 int i=start;
836 int last = sText.GetLength() - 1;
837 bool bFoundMarker = false;
838 TCHAR c = i == 0 ? L'\0' : sText[i - 1];
839 TCHAR nextChar = i >= last ? L'\0' : sText[i + 1];
841 // find a starting marker
842 while (i < last)
844 TCHAR prevChar = c;
845 c = nextChar;
846 nextChar = sText[i + 1];
848 // IsCharAlphaNumeric can be somewhat expensive.
849 // Long lines of "*****" or "----" will be pre-empted efficiently
850 // by the (c != nextChar) condition.
852 if ((c == stylechar) && (c != nextChar))
854 if (IsCharAlphaNumeric(nextChar) && !IsCharAlphaNumeric(prevChar))
856 start = ++i;
857 bFoundMarker = true;
858 break;
861 ++i;
863 if (!bFoundMarker)
864 return false;
866 // find ending marker
867 // c == sText[i - 1]
869 bFoundMarker = false;
870 while (i <= last)
872 TCHAR prevChar = c;
873 c = sText[i];
874 if (c == stylechar)
876 if ((i == last) || (!IsCharAlphaNumeric(sText[i + 1]) && IsCharAlphaNumeric(prevChar)))
878 end = i;
879 ++i;
880 bFoundMarker = true;
881 break;
884 ++i;
886 return bFoundMarker;
889 // from CSciEdit
890 namespace {
891 bool IsValidURLChar(wchar_t ch)
893 return iswalnum(ch) ||
894 ch == L'_' || ch == L'/' || ch == L';' || ch == L'?' || ch == L'&' || ch == L'=' ||
895 ch == L'%' || ch == L':' || ch == L'.' || ch == L'#' || ch == L'-' || ch == L'+' ||
896 ch == L'|' || ch == L'>' || ch == L'<' || ch == L'!' || ch == L'@';
899 bool IsUrlOrEmail(const CString& sText)
901 if (!PathIsURLW(sText))
903 auto atpos = sText.Find(L'@');
904 if (atpos <= 0)
905 return false;
906 if (sText.Find(L'.', atpos) <= atpos + 1) // a dot must follow after the @, but not directly after it
907 return false;
908 if (sText.Find(L':', atpos) < 0) // do not detect git@example.com:something as an email address
909 return true;
910 return false;
912 for (const CString& prefix : { L"http://", L"https://", L"git://", L"ftp://", L"file://", L"mailto:" })
914 if (CStringUtils::StartsWith(sText, prefix) && sText.GetLength() != prefix.GetLength())
915 return true;
917 return false;
921 BOOL CAppUtils::StyleURLs(const CString& msg, CWnd* pWnd)
923 std::vector<CHARRANGE> positions = FindURLMatches(msg);
924 CAppUtils::SetCharFormat(pWnd, CFM_LINK, CFE_LINK, positions);
926 return positions.empty() ? FALSE : TRUE;
930 * implements URL searching with the same logic as CSciEdit::StyleURLs
932 std::vector<CHARRANGE> CAppUtils::FindURLMatches(const CString& msg)
934 std::vector<CHARRANGE> result;
936 int len = msg.GetLength();
937 int starturl = -1;
939 for (int i = 0; i <= msg.GetLength(); ++i)
941 if ((i < len) && IsValidURLChar(msg[i]))
943 if (starturl < 0)
944 starturl = i;
946 else
948 if (starturl >= 0)
950 bool strip = true;
951 if (msg[starturl] == '<' && i < len) // try to detect and do not strip URLs put within <>
953 while (starturl <= i && msg[starturl] == '<') // strip leading '<'
954 ++starturl;
955 strip = false;
956 i = starturl;
957 while (i < len && msg[i] != '\r' && msg[i] != '\n' && msg[i] != '>') // find first '>' or new line after resetting i to start position
958 ++i;
961 int skipTrailing = 0;
962 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] == '!'))
963 ++skipTrailing;
965 if (!IsUrlOrEmail(msg.Mid(starturl, i - starturl - skipTrailing)))
967 starturl = -1;
968 continue;
971 CHARRANGE range = { starturl, i - skipTrailing };
972 result.push_back(range);
974 starturl = -1;
978 return result;
981 bool CAppUtils::StartShowUnifiedDiff(HWND hWnd, const CTGitPath& url1, const git_revnum_t& rev1,
982 const CTGitPath& /*url2*/, const git_revnum_t& rev2,
983 //const GitRev& peg /* = GitRev */, const GitRev& headpeg /* = GitRev */,
984 bool bAlternateDiff /* = false */, bool /*bIgnoreAncestry*/ /* = false */,
985 bool /* blame = false */,
986 bool bMerge,
987 bool bCombine,
988 bool bNoPrefix)
990 int diffContext = g_Git.GetConfigValueInt32(L"diff.context", -1);
991 CString tempfile=GetTempFile();
992 if (g_Git.GetUnifiedDiff(url1, rev1, rev2, tempfile, bMerge, bCombine, diffContext, bNoPrefix))
994 MessageBox(hWnd, g_Git.GetGitLastErr(L"Could not get unified diff.", CGit::GIT_CMD_DIFF), L"TortoiseGit", MB_OK);
995 return false;
997 CAppUtils::StartUnifiedDiffViewer(tempfile, rev1.IsEmpty() ? rev2 : rev1 + L':' + rev2, FALSE, bAlternateDiff);
999 #if 0
1000 CString sCmd;
1001 sCmd.Format(L"%s /command:showcompare /unified",
1002 (LPCTSTR)(CPathUtils::GetAppDirectory()+L"TortoiseGitProc.exe"));
1003 sCmd += L" /url1:\"" + url1.GetGitPathString() + L'"';
1004 if (rev1.IsValid())
1005 sCmd += L" /revision1:" + rev1.ToString();
1006 sCmd += L" /url2:\"" + url2.GetGitPathString() + L'"';
1007 if (rev2.IsValid())
1008 sCmd += L" /revision2:" + rev2.ToString();
1009 if (peg.IsValid())
1010 sCmd += L" /pegrevision:" + peg.ToString();
1011 if (headpeg.IsValid())
1012 sCmd += L" /headpegrevision:" + headpeg.ToString();
1014 if (bAlternateDiff)
1015 sCmd += L" /alternatediff";
1017 if (bIgnoreAncestry)
1018 sCmd += L" /ignoreancestry";
1020 if (hWnd)
1022 sCmd += L" /hwnd:";
1023 TCHAR buf[30];
1024 swprintf_s(buf, 30, L"%p", (void*)hWnd);
1025 sCmd += buf;
1028 return CAppUtils::LaunchApplication(sCmd, 0, false);
1029 #endif
1030 return TRUE;
1033 bool CAppUtils::SetupDiffScripts(bool force, const CString& type)
1035 CString scriptsdir = CPathUtils::GetAppParentDirectory();
1036 scriptsdir += L"Diff-Scripts";
1037 CSimpleFileFind files(scriptsdir);
1038 while (files.FindNextFileNoDirectories())
1040 CString file = files.GetFilePath();
1041 CString filename = files.GetFileName();
1042 CString ext = file.Mid(file.ReverseFind('-') + 1);
1043 ext = L"." + ext.Left(ext.ReverseFind(L'.'));
1044 std::set<CString> extensions;
1045 extensions.insert(ext);
1046 CString kind;
1047 if (CStringUtils::EndsWithI(file, L"vbs"))
1048 kind = L" //E:vbscript";
1049 if (CStringUtils::EndsWithI(file, L"js"))
1050 kind = L" //E:javascript";
1051 // open the file, read the first line and find possible extensions
1052 // this script can handle
1055 CStdioFile f(file, CFile::modeRead | CFile::shareDenyNone);
1056 CString extline;
1057 if (f.ReadString(extline))
1059 if ((extline.GetLength() > 15 ) &&
1060 (CStringUtils::StartsWith(extline, L"// extensions: ") ||
1061 CStringUtils::StartsWith(extline, L"' extensions: ")))
1063 if (extline[0] == '/')
1064 extline = extline.Mid(15);
1065 else
1066 extline = extline.Mid(14);
1067 CString sToken;
1068 int curPos = 0;
1069 sToken = extline.Tokenize(L";", curPos);
1070 while (!sToken.IsEmpty())
1072 if (!sToken.IsEmpty())
1074 if (sToken[0] != '.')
1075 sToken = L"." + sToken;
1076 extensions.insert(sToken);
1078 sToken = extline.Tokenize(L";", curPos);
1082 f.Close();
1084 catch (CFileException* e)
1086 e->Delete();
1089 for (const auto& extension : extensions)
1091 if (type.IsEmpty() || (type.Compare(L"Diff") == 0))
1093 if (CStringUtils::StartsWithI(filename, L"diff-"))
1095 CRegString diffreg = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + extension);
1096 CString diffregstring = diffreg;
1097 if (force || (diffregstring.IsEmpty()) || (diffregstring.Find(filename) >= 0))
1098 diffreg = L"wscript.exe \"" + file + L"\" %base %mine" + kind;
1101 if (type.IsEmpty() || (type.Compare(L"Merge") == 0))
1103 if (CStringUtils::StartsWithI(filename, L"merge-"))
1105 CRegString diffreg = CRegString(L"Software\\TortoiseGit\\MergeTools\\" + extension);
1106 CString diffregstring = diffreg;
1107 if (force || (diffregstring.IsEmpty()) || (diffregstring.Find(filename) >= 0))
1108 diffreg = L"wscript.exe \"" + file + L"\" %merged %theirs %mine %base" + kind;
1114 return true;
1117 bool CAppUtils::Export(const CString* BashHash, const CTGitPath* orgPath)
1119 // ask from where the export has to be done
1120 CExportDlg dlg;
1121 if(BashHash)
1122 dlg.m_initialRefName=*BashHash;
1123 if (orgPath)
1125 if (PathIsRelative(orgPath->GetWinPath()))
1126 dlg.m_orgPath = g_Git.CombinePath(orgPath);
1127 else
1128 dlg.m_orgPath = *orgPath;
1131 if (dlg.DoModal() == IDOK)
1133 CString cmd;
1134 cmd.Format(L"git.exe archive --output=\"%s\" --format=zip --verbose %s --",
1135 (LPCTSTR)dlg.m_strFile, (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName));
1137 CProgressDlg pro;
1138 pro.m_GitCmd=cmd;
1139 pro.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1141 if (status)
1142 return;
1143 postCmdList.emplace_back(IDI_EXPLORER, IDS_STATUSLIST_CONTEXT_EXPLORE, [&]{ CAppUtils::ExploreTo(hWndExplorer, dlg.m_strFile); });
1146 CGit git;
1147 if (!dlg.m_bWholeProject && !dlg.m_orgPath.IsEmpty() && PathIsDirectory(dlg.m_orgPath.GetWinPathString()))
1149 git.m_CurrentDir = dlg.m_orgPath.GetWinPathString();
1150 pro.m_Git = &git;
1152 return (pro.DoModal() == IDOK);
1154 return false;
1157 bool CAppUtils::UpdateBranchDescription(const CString& branch, CString description)
1159 if (branch.IsEmpty())
1160 return false;
1162 CString key;
1163 key.Format(L"branch.%s.description", (LPCTSTR)branch);
1164 description.Remove(L'\r');
1165 description.Trim();
1166 if (description.IsEmpty())
1167 g_Git.UnsetConfigValue(key);
1168 else
1169 g_Git.SetConfigValue(key, description);
1171 return true;
1174 bool CAppUtils::CreateBranchTag(bool isTag /*true*/, const CString* commitHash /*nullptr*/, bool switchNewBranch /*false*/, LPCTSTR name /*nullptr*/)
1176 CCreateBranchTagDlg dlg;
1177 dlg.m_bIsTag = isTag;
1178 dlg.m_bSwitch = switchNewBranch;
1180 if (commitHash)
1181 dlg.m_initialRefName = *commitHash;
1183 if (name)
1184 dlg.m_BranchTagName = name;
1186 if(dlg.DoModal()==IDOK)
1188 CString cmd;
1189 CString force;
1190 CString track;
1191 if(dlg.m_bTrack == TRUE)
1192 track = L"--track";
1193 else if(dlg.m_bTrack == FALSE)
1194 track = L"--no-track";
1196 if(dlg.m_bForce)
1197 force = L"-f";
1199 if (isTag)
1201 CString sign;
1202 if(dlg.m_bSign)
1203 sign = L"-s";
1205 cmd.Format(L"git.exe tag %s %s %s %s",
1206 (LPCTSTR)force,
1207 (LPCTSTR)sign,
1208 (LPCTSTR)dlg.m_BranchTagName,
1209 (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName)
1212 if(!dlg.m_Message.Trim().IsEmpty())
1214 CString tempfile = ::GetTempFile();
1215 if (CAppUtils::SaveCommitUnicodeFile(tempfile, dlg.m_Message))
1217 MessageBox(nullptr, L"Could not save tag message", L"TortoiseGit", MB_OK | MB_ICONERROR);
1218 return FALSE;
1220 cmd += L" -F " + tempfile;
1223 else
1225 cmd.Format(L"git.exe branch %s %s %s %s",
1226 (LPCTSTR)track,
1227 (LPCTSTR)force,
1228 (LPCTSTR)dlg.m_BranchTagName,
1229 (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName)
1232 CString out;
1233 if(g_Git.Run(cmd,&out,CP_UTF8))
1235 MessageBox(nullptr, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
1236 return FALSE;
1238 if (!isTag && dlg.m_bSwitch)
1240 // it is a new branch and the user has requested to switch to it
1241 PerformSwitch(dlg.m_BranchTagName);
1243 if (!isTag && !dlg.m_Message.IsEmpty())
1244 UpdateBranchDescription(dlg.m_BranchTagName, dlg.m_Message);
1246 return TRUE;
1248 return FALSE;
1251 bool CAppUtils::Switch(const CString& initialRefName)
1253 CGitSwitchDlg dlg;
1254 if(!initialRefName.IsEmpty())
1255 dlg.m_initialRefName = initialRefName;
1257 if (dlg.DoModal() == IDOK)
1259 CString branch;
1260 if (dlg.m_bBranch)
1261 branch = dlg.m_NewBranch;
1263 // if refs/heads/ is not stripped, checkout will detach HEAD
1264 // checkout prefers branches on name clashes (with tags)
1265 if (CStringUtils::StartsWith(dlg.m_VersionName, L"refs/heads/") && dlg.m_bBranchOverride != TRUE)
1266 dlg.m_VersionName = dlg.m_VersionName.Mid(11);
1268 return PerformSwitch(dlg.m_VersionName, dlg.m_bForce == TRUE , branch, dlg.m_bBranchOverride == TRUE, dlg.m_bTrack, dlg.m_bMerge == TRUE);
1270 return FALSE;
1273 bool CAppUtils::PerformSwitch(const CString& ref, bool bForce /* false */, const CString& sNewBranch /* CString() */, bool bBranchOverride /* false */, BOOL bTrack /* 2 */, bool bMerge /* false */)
1275 CString cmd;
1276 CString track;
1277 CString force;
1278 CString branch;
1279 CString merge;
1281 if(!sNewBranch.IsEmpty()){
1282 if (bBranchOverride)
1283 branch.Format(L"-B %s ", (LPCTSTR)sNewBranch);
1284 else
1285 branch.Format(L"-b %s ", (LPCTSTR)sNewBranch);
1286 if (bTrack == TRUE)
1287 track = L"--track ";
1288 else if (bTrack == FALSE)
1289 track = L"--no-track ";
1291 if (bForce)
1292 force = L"-f ";
1293 if (bMerge)
1294 merge = L"--merge ";
1296 cmd.Format(L"git.exe checkout %s%s%s%s%s --",
1297 (LPCTSTR)force,
1298 (LPCTSTR)track,
1299 (LPCTSTR)merge,
1300 (LPCTSTR)branch,
1301 (LPCTSTR)g_Git.FixBranchName(ref));
1303 CProgressDlg progress;
1304 progress.m_GitCmd = cmd;
1306 CString currentBranch;
1307 bool hasBranch = CGit::GetCurrentBranchFromFile(g_Git.m_CurrentDir, currentBranch) == 0;
1308 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1310 if (!status)
1312 CTGitPath gitPath = g_Git.m_CurrentDir;
1313 if (gitPath.HasSubmodules())
1315 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1317 CString sCmd;
1318 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
1319 RunTortoiseGitProc(sCmd);
1322 if (hasBranch)
1323 postCmdList.emplace_back(IDI_MERGE, IDS_MENUMERGE, [&]{ Merge(&currentBranch); });
1326 CString newBranch;
1327 if (!CGit::GetCurrentBranchFromFile(g_Git.m_CurrentDir, newBranch))
1328 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&]{ Pull(); });
1330 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUCOMMIT, []{
1331 CTGitPathList pathlist;
1332 CTGitPathList selectedlist;
1333 pathlist.AddPath(CTGitPath());
1334 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(L"Software\\TortoiseGit\\SelectFilesForCommit", TRUE));
1335 CString str;
1336 Commit(CString(), false, str, pathlist, selectedlist, bSelectFilesForCommit);
1339 else
1341 if (bMerge && g_Git.HasWorkingTreeConflicts() > 0)
1343 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
1345 CString sCmd;
1346 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
1347 CAppUtils::RunTortoiseGitProc(sCmd);
1350 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ PerformSwitch(ref, bForce, sNewBranch, bBranchOverride, bTrack, bMerge); });
1351 if (!bMerge)
1352 postCmdList.emplace_back(IDI_SWITCH, IDS_SWITCH_WITH_MERGE, [&]{ PerformSwitch(ref, bForce, sNewBranch, bBranchOverride, bTrack, true); });
1355 progress.m_PostExecCallback = [&](DWORD& exitCode, CString& extraMsg)
1357 if (bMerge && !exitCode && g_Git.HasWorkingTreeConflicts() > 0)
1359 exitCode = 1; // Treat it as failure
1360 extraMsg = L"Has merge conflict";
1364 INT_PTR ret = progress.DoModal();
1366 return ret == IDOK;
1369 class CIgnoreFile : public CStdioFile
1371 public:
1372 STRING_VECTOR m_Items;
1373 CString m_eol;
1375 virtual BOOL ReadString(CString& rString)
1377 if (GetPosition() == 0)
1379 unsigned char utf8bom[] = { 0xEF, 0xBB, 0xBF };
1380 char buf[3] = { 0, 0, 0 };
1381 Read(buf, 3);
1382 if (memcpy(buf, utf8bom, sizeof(utf8bom)))
1383 SeekToBegin();
1386 CStringA strA;
1387 char lastChar = '\0';
1388 for (char c = '\0'; Read(&c, 1) == 1; lastChar = c)
1390 if (c == '\r')
1391 continue;
1392 if (c == '\n')
1394 m_eol = lastChar == L'\r' ? L"\r\n" : L"\n";
1395 break;
1397 strA.AppendChar(c);
1399 if (strA.IsEmpty())
1400 return FALSE;
1402 rString = CUnicodeUtils::GetUnicode(strA);
1403 return TRUE;
1406 void ResetState()
1408 m_Items.clear();
1409 m_eol.Empty();
1413 bool CAppUtils::OpenIgnoreFile(CIgnoreFile &file, const CString& filename)
1415 file.ResetState();
1416 if (!file.Open(filename, CFile::modeCreate | CFile::modeReadWrite | CFile::modeNoTruncate | CFile::typeBinary))
1418 MessageBox(nullptr, filename + L" Open Failure", L"TortoiseGit", MB_OK | MB_ICONERROR);
1419 return false;
1422 if (file.GetLength() > 0)
1424 CString fileText;
1425 while (file.ReadString(fileText))
1426 file.m_Items.push_back(fileText);
1427 file.Seek(file.GetLength() - 1, 0);
1428 char lastchar[1] = { 0 };
1429 file.Read(lastchar, 1);
1430 file.SeekToEnd();
1431 if (lastchar[0] != '\n')
1433 CStringA eol = CStringA(file.m_eol.IsEmpty() ? L"\n" : file.m_eol);
1434 file.Write(eol, eol.GetLength());
1437 else
1438 file.SeekToEnd();
1440 return true;
1443 bool CAppUtils::IgnoreFile(const CTGitPathList& path,bool IsMask)
1445 CIgnoreDlg ignoreDlg;
1446 if (ignoreDlg.DoModal() == IDOK)
1448 CString ignorefile;
1449 ignorefile = g_Git.m_CurrentDir + L'\\';
1451 switch (ignoreDlg.m_IgnoreFile)
1453 case 0:
1454 ignorefile += L".gitignore";
1455 break;
1456 case 2:
1457 GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, ignorefile);
1458 ignorefile += L"info";
1459 if (!PathFileExists(ignorefile))
1460 CreateDirectory(ignorefile, nullptr);
1461 ignorefile += L"\\exclude";
1462 break;
1465 CIgnoreFile file;
1468 if (ignoreDlg.m_IgnoreFile != 1 && !OpenIgnoreFile(file, ignorefile))
1469 return false;
1471 for (int i = 0; i < path.GetCount(); ++i)
1473 if (ignoreDlg.m_IgnoreFile == 1)
1475 ignorefile = g_Git.CombinePath(path[i].GetContainingDirectory()) + L"\\.gitignore";
1476 if (!OpenIgnoreFile(file, ignorefile))
1477 return false;
1480 CString ignorePattern;
1481 if (ignoreDlg.m_IgnoreType == 0)
1483 if (ignoreDlg.m_IgnoreFile != 1 && !path[i].GetContainingDirectory().GetGitPathString().IsEmpty())
1484 ignorePattern += L'/' + path[i].GetContainingDirectory().GetGitPathString();
1486 ignorePattern += L'/';
1488 if (IsMask)
1489 ignorePattern += L'*' + path[i].GetFileExtension();
1490 else
1491 ignorePattern += path[i].GetFileOrDirectoryName();
1493 // escape [ and ] so that files get ignored correctly
1494 ignorePattern.Replace(L"[", L"\\[");
1495 ignorePattern.Replace(L"]", L"\\]");
1497 bool found = false;
1498 for (size_t j = 0; j < file.m_Items.size(); ++j)
1500 if (file.m_Items[j] == ignorePattern)
1502 found = true;
1503 break;
1506 if (!found)
1508 file.m_Items.push_back(ignorePattern);
1509 ignorePattern += file.m_eol.IsEmpty() ? L"\n" : file.m_eol;
1510 CStringA ignorePatternA = CUnicodeUtils::GetUTF8(ignorePattern);
1511 file.Write(ignorePatternA, ignorePatternA.GetLength());
1514 if (ignoreDlg.m_IgnoreFile == 1)
1515 file.Close();
1518 if (ignoreDlg.m_IgnoreFile != 1)
1519 file.Close();
1521 catch(...)
1523 file.Abort();
1524 return false;
1527 return true;
1529 return false;
1532 static bool Reset(const CString& resetTo, int resetType)
1534 CString cmd;
1535 CString type;
1536 switch (resetType)
1538 case 0:
1539 type = L"--soft";
1540 break;
1541 case 1:
1542 type = L"--mixed";
1543 break;
1544 case 2:
1545 type = L"--hard";
1546 break;
1547 default:
1548 resetType = 1;
1549 type = L"--mixed";
1550 break;
1552 cmd.Format(L"git.exe reset %s %s --", (LPCTSTR)type, (LPCTSTR)resetTo);
1554 CProgressDlg progress;
1555 progress.m_GitCmd = cmd;
1557 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1559 if (status)
1561 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ Reset(resetTo, resetType); });
1562 return;
1565 CTGitPath gitPath = g_Git.m_CurrentDir;
1566 if (gitPath.HasSubmodules() && resetType == 2)
1568 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1570 CString sCmd;
1571 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
1572 CAppUtils::RunTortoiseGitProc(sCmd);
1577 INT_PTR ret;
1578 if (g_Git.UsingLibGit2(CGit::GIT_CMD_RESET))
1580 CGitProgressDlg gitdlg;
1581 ResetProgressCommand resetProgressCommand;
1582 gitdlg.SetCommand(&resetProgressCommand);
1583 resetProgressCommand.m_PostCmdCallback = progress.m_PostCmdCallback;
1584 resetProgressCommand.SetRevision(resetTo);
1585 resetProgressCommand.SetResetType(resetType);
1586 ret = gitdlg.DoModal();
1588 else
1589 ret = progress.DoModal();
1591 return ret == IDOK;
1594 bool CAppUtils::GitReset(const CString* CommitHash, int type)
1596 CResetDlg dlg;
1597 dlg.m_ResetType=type;
1598 dlg.m_ResetToVersion=*CommitHash;
1599 dlg.m_initialRefName = *CommitHash;
1600 if (dlg.DoModal() == IDOK)
1601 return Reset(dlg.m_ResetToVersion, dlg.m_ResetType);
1603 return false;
1606 void CAppUtils::DescribeConflictFile(bool mode, bool base,CString &descript)
1608 if(mode == FALSE)
1610 descript.LoadString(IDS_SVNACTION_DELETE);
1611 return;
1613 if(base)
1615 descript.LoadString(IDS_SVNACTION_MODIFIED);
1616 return;
1618 descript.LoadString(IDS_PROC_CREATED);
1621 void CAppUtils::RemoveTempMergeFile(const CTGitPath& path)
1623 ::DeleteFile(CAppUtils::GetMergeTempFile(L"LOCAL", path));
1624 ::DeleteFile(CAppUtils::GetMergeTempFile(L"REMOTE", path));
1625 ::DeleteFile(CAppUtils::GetMergeTempFile(L"BASE", path));
1627 CString CAppUtils::GetMergeTempFile(const CString& type, const CTGitPath &merge)
1629 CString file;
1630 file = g_Git.CombinePath(merge.GetWinPathString() + L'.' + type + merge.GetFileExtension());
1632 return file;
1635 bool ParseHashesFromLsFile(const BYTE_VECTOR& out, CString& hash1, CString& hash2, CString& hash3)
1637 size_t pos = 0;
1638 CString one;
1639 CString part;
1641 while (pos < out.size())
1643 one.Empty();
1645 CGit::StringAppend(&one, &out[pos], CP_UTF8);
1646 int tabstart = 0;
1647 one.Tokenize(L"\t", tabstart);
1649 tabstart = 0;
1650 part = one.Tokenize(L" ", tabstart); //Tag
1651 part = one.Tokenize(L" ", tabstart); //Mode
1652 part = one.Tokenize(L" ", tabstart); //Hash
1653 CString hash = part;
1654 part = one.Tokenize(L"\t", tabstart); //Stage
1655 int stage = _wtol(part);
1656 if (stage == 1)
1657 hash1 = hash;
1658 else if (stage == 2)
1659 hash2 = hash;
1660 else if (stage == 3)
1662 hash3 = hash;
1663 return true;
1666 pos = out.findNextString(pos);
1669 return false;
1672 bool CAppUtils::ConflictEdit(CTGitPath& path, bool bAlternativeTool /*= false*/, bool revertTheirMy /*= false*/, HWND resolveMsgHwnd /*= nullptr*/)
1674 bool bRet = false;
1676 CTGitPath merge=path;
1677 CTGitPath directory = merge.GetDirectory();
1679 // we have the conflicted file (%merged)
1680 // now look for the other required files
1681 //GitStatus stat;
1682 //stat.GetStatus(merge);
1683 //if (stat.status == nullptr)
1684 // return false;
1686 BYTE_VECTOR vector;
1688 CString cmd;
1689 cmd.Format(L"git.exe ls-files -u -t -z -- \"%s\"", (LPCTSTR)merge.GetGitPathString());
1691 if (g_Git.Run(cmd, &vector))
1692 return FALSE;
1694 if (merge.IsDirectory())
1696 CString baseHash, realBaseHash(GIT_REV_ZERO), localHash(GIT_REV_ZERO), remoteHash(GIT_REV_ZERO);
1697 if (merge.HasAdminDir()) {
1698 CGit subgit;
1699 subgit.m_CurrentDir = g_Git.CombinePath(merge);
1700 CGitHash hash;
1701 subgit.GetHash(hash, L"HEAD");
1702 baseHash = hash;
1704 if (ParseHashesFromLsFile(vector, realBaseHash, localHash, remoteHash)) // in base no submodule, but in remote submodule
1705 baseHash = realBaseHash;
1707 CGitDiff::ChangeType changeTypeMine = CGitDiff::Unknown;
1708 CGitDiff::ChangeType changeTypeTheirs = CGitDiff::Unknown;
1710 bool baseOK = false, mineOK = false, theirsOK = false;
1711 CString baseSubject, mineSubject, theirsSubject;
1712 if (merge.HasAdminDir())
1714 CGit subgit;
1715 subgit.m_CurrentDir = g_Git.CombinePath(merge);
1716 CGitDiff::GetSubmoduleChangeType(subgit, baseHash, localHash, baseOK, mineOK, changeTypeMine, baseSubject, mineSubject);
1717 CGitDiff::GetSubmoduleChangeType(subgit, baseHash, remoteHash, baseOK, theirsOK, changeTypeTheirs, baseSubject, theirsSubject);
1719 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)
1721 changeTypeMine = CGitDiff::Identical;
1722 changeTypeTheirs = CGitDiff::NewSubmodule;
1723 baseSubject = L"no submodule";
1724 mineSubject = baseSubject;
1725 theirsSubject = L"not initialized";
1727 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
1729 baseHash = localHash;
1730 baseSubject = L"not initialized";
1731 mineSubject = baseSubject;
1732 theirsSubject = L"not initialized";
1733 changeTypeMine = CGitDiff::Identical;
1734 changeTypeTheirs = CGitDiff::DeleteSubmodule;
1736 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
1738 baseSubject = L"not initialized";
1739 mineSubject = baseSubject;
1740 theirsSubject = baseSubject;
1741 if (baseHash == localHash)
1742 changeTypeMine = CGitDiff::Identical;
1744 else
1745 return FALSE;
1747 CSubmoduleResolveConflictDlg resolveSubmoduleConflictDialog;
1748 resolveSubmoduleConflictDialog.SetDiff(merge.GetGitPathString(), revertTheirMy, baseHash, baseSubject, baseOK, localHash, mineSubject, mineOK, changeTypeMine, remoteHash, theirsSubject, theirsOK, changeTypeTheirs);
1749 resolveSubmoduleConflictDialog.DoModal();
1750 if (resolveSubmoduleConflictDialog.m_bResolved && resolveMsgHwnd)
1752 static UINT WM_REVERTMSG = RegisterWindowMessage(L"GITSLNM_NEEDSREFRESH");
1753 ::PostMessage(resolveMsgHwnd, WM_REVERTMSG, NULL, NULL);
1756 return TRUE;
1759 CTGitPathList list;
1760 if (list.ParserFromLsFile(vector))
1762 MessageBox(nullptr, L"Parse ls-files failed!", L"TortoiseGit", MB_OK | MB_ICONERROR);
1763 return FALSE;
1766 if (list.IsEmpty())
1767 return FALSE;
1769 CTGitPath theirs;
1770 CTGitPath mine;
1771 CTGitPath base;
1773 if (revertTheirMy)
1775 mine.SetFromGit(GetMergeTempFile(L"REMOTE", merge));
1776 theirs.SetFromGit(GetMergeTempFile(L"LOCAL", merge));
1778 else
1780 mine.SetFromGit(GetMergeTempFile(L"LOCAL", merge));
1781 theirs.SetFromGit(GetMergeTempFile(L"REMOTE", merge));
1783 base.SetFromGit(GetMergeTempFile(L"BASE",merge));
1785 CString format;
1787 //format=L"git.exe cat-file blob \":%d:%s\"";
1788 format = L"git.exe checkout-index --temp --stage=%d -- \"%s\"";
1789 CFile tempfile;
1790 //create a empty file, incase stage is not three
1791 tempfile.Open(mine.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1792 tempfile.Close();
1793 tempfile.Open(theirs.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1794 tempfile.Close();
1795 tempfile.Open(base.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1796 tempfile.Close();
1798 bool b_base=false, b_local=false, b_remote=false;
1800 for (int i = 0; i< list.GetCount(); ++i)
1802 CString outfile;
1803 cmd.Empty();
1804 outfile.Empty();
1806 if( list[i].m_Stage == 1)
1808 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1809 b_base = true;
1810 outfile = base.GetWinPathString();
1813 if( list[i].m_Stage == 2 )
1815 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1816 b_local = true;
1817 outfile = mine.GetWinPathString();
1820 if( list[i].m_Stage == 3 )
1822 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1823 b_remote = true;
1824 outfile = theirs.GetWinPathString();
1826 CString output, err;
1827 if(!outfile.IsEmpty())
1828 if (!g_Git.Run(cmd, &output, &err, CP_UTF8))
1830 CString file;
1831 int start =0 ;
1832 file = output.Tokenize(L"\t", start);
1833 ::MoveFileEx(file,outfile,MOVEFILE_REPLACE_EXISTING|MOVEFILE_COPY_ALLOWED);
1835 else
1836 CMessageBox::Show(nullptr, output + L'\n' + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
1839 if(b_local && b_remote )
1841 merge.SetFromWin(g_Git.CombinePath(merge));
1842 if( revertTheirMy )
1843 bRet = !!CAppUtils::StartExtMerge(bAlternativeTool, base, mine, theirs, merge, L"BASE", L"REMOTE", L"LOCAL", CString(), false, resolveMsgHwnd, true);
1844 else
1845 bRet = !!CAppUtils::StartExtMerge(bAlternativeTool, base, theirs, mine, merge, L"BASE", L"REMOTE", L"LOCAL", CString(), false, resolveMsgHwnd, true);
1847 else
1849 ::DeleteFile(mine.GetWinPathString());
1850 ::DeleteFile(theirs.GetWinPathString());
1851 ::DeleteFile(base.GetWinPathString());
1853 CDeleteConflictDlg dlg;
1854 if (!revertTheirMy)
1856 DescribeConflictFile(b_local, b_base, dlg.m_LocalStatus);
1857 DescribeConflictFile(b_remote, b_base, dlg.m_RemoteStatus);
1858 CGitHash localHash, remoteHash;
1859 if (!g_Git.GetHash(localHash, L"HEAD"))
1860 dlg.m_LocalHash = localHash.ToString();
1861 if (!g_Git.GetHash(remoteHash, L"MERGE_HEAD"))
1862 dlg.m_RemoteHash = remoteHash.ToString();
1863 else if (!g_Git.GetHash(remoteHash, L"rebase-apply/original-commit"))
1864 dlg.m_RemoteHash = remoteHash.ToString();
1865 else if (!g_Git.GetHash(remoteHash, L"CHERRY_PICK_HEAD"))
1866 dlg.m_RemoteHash = remoteHash.ToString();
1867 else if (!g_Git.GetHash(remoteHash, L"REVERT_HEAD"))
1868 dlg.m_RemoteHash = remoteHash.ToString();
1870 else
1872 DescribeConflictFile(b_local, b_base, dlg.m_RemoteStatus);
1873 DescribeConflictFile(b_remote, b_base, dlg.m_LocalStatus);
1874 CGitHash localHash, remoteHash;
1875 if (!g_Git.GetHash(remoteHash, L"HEAD"))
1876 dlg.m_RemoteHash = remoteHash.ToString();
1877 if (!g_Git.GetHash(localHash, L"MERGE_HEAD"))
1878 dlg.m_LocalHash = localHash.ToString();
1879 else if (!g_Git.GetHash(localHash, L"rebase-apply/original-commit"))
1880 dlg.m_LocalHash = localHash.ToString();
1881 else if (!g_Git.GetHash(localHash, L"CHERRY_PICK_HEAD"))
1882 dlg.m_LocalHash = localHash.ToString();
1883 else if (!g_Git.GetHash(localHash, L"REVERT_HEAD"))
1884 dlg.m_LocalHash = localHash.ToString();
1886 dlg.m_bShowModifiedButton = b_base;
1887 dlg.m_File=merge.GetGitPathString();
1888 if(dlg.DoModal() == IDOK)
1890 CString out;
1891 if(dlg.m_bIsDelete)
1892 cmd.Format(L"git.exe rm -- \"%s\"", (LPCTSTR)merge.GetGitPathString());
1893 else
1894 cmd.Format(L"git.exe add -- \"%s\"", (LPCTSTR)merge.GetGitPathString());
1896 if (g_Git.Run(cmd, &out, CP_UTF8))
1898 MessageBox(nullptr, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
1899 return FALSE;
1901 if (!dlg.m_bIsDelete)
1903 path.m_Action |= CTGitPath::LOGACTIONS_ADDED;
1904 path.m_Action &= ~CTGitPath::LOGACTIONS_UNMERGED;
1906 return TRUE;
1908 else
1909 return FALSE;
1912 #if 0
1913 CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool),
1914 base, theirs, mine, merge);
1915 #endif
1916 #if 0
1917 if (stat.status->text_status == svn_wc_status_conflicted)
1919 // we have a text conflict, use our merge tool to resolve the conflict
1921 CTSVNPath theirs(directory);
1922 CTSVNPath mine(directory);
1923 CTSVNPath base(directory);
1924 bool bConflictData = false;
1926 if ((stat.status->entry)&&(stat.status->entry->conflict_new))
1928 theirs.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->conflict_new));
1929 bConflictData = true;
1931 if ((stat.status->entry)&&(stat.status->entry->conflict_old))
1933 base.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->conflict_old));
1934 bConflictData = true;
1936 if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
1938 mine.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->conflict_wrk));
1939 bConflictData = true;
1941 else
1942 mine = merge;
1943 if (bConflictData)
1944 bRet = !!CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool),
1945 base, theirs, mine, merge);
1948 if (stat.status->prop_status == svn_wc_status_conflicted)
1950 // we have a property conflict
1951 CTSVNPath prej(directory);
1952 if ((stat.status->entry)&&(stat.status->entry->prejfile))
1954 prej.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->prejfile));
1955 // there's a problem: the prej file contains a _description_ of the conflict, and
1956 // that description string might be translated. That means we have no way of parsing
1957 // the file to find out the conflicting values.
1958 // The only thing we can do: show a dialog with the conflict description, then
1959 // let the user either accept the existing property or open the property edit dialog
1960 // to manually change the properties and values. And a button to mark the conflict as
1961 // resolved.
1962 CEditPropConflictDlg dlg;
1963 dlg.SetPrejFile(prej);
1964 dlg.SetConflictedItem(merge);
1965 bRet = (dlg.DoModal() != IDCANCEL);
1969 if (stat.status->tree_conflict)
1971 // we have a tree conflict
1972 SVNInfo info;
1973 const SVNInfoData * pInfoData = info.GetFirstFileInfo(merge, SVNRev(), SVNRev());
1974 if (pInfoData)
1976 if (pInfoData->treeconflict_kind == svn_wc_conflict_kind_text)
1978 CTSVNPath theirs(directory);
1979 CTSVNPath mine(directory);
1980 CTSVNPath base(directory);
1981 bool bConflictData = false;
1983 if (pInfoData->treeconflict_theirfile)
1985 theirs.AppendPathString(pInfoData->treeconflict_theirfile);
1986 bConflictData = true;
1988 if (pInfoData->treeconflict_basefile)
1990 base.AppendPathString(pInfoData->treeconflict_basefile);
1991 bConflictData = true;
1993 if (pInfoData->treeconflict_myfile)
1995 mine.AppendPathString(pInfoData->treeconflict_myfile);
1996 bConflictData = true;
1998 else
1999 mine = merge;
2000 if (bConflictData)
2001 bRet = !!CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool),
2002 base, theirs, mine, merge);
2004 else if (pInfoData->treeconflict_kind == svn_wc_conflict_kind_tree)
2006 CString sConflictAction;
2007 CString sConflictReason;
2008 CString sResolveTheirs;
2009 CString sResolveMine;
2010 CTSVNPath treeConflictPath = CTSVNPath(pInfoData->treeconflict_path);
2011 CString sItemName = treeConflictPath.GetUIFileOrDirectoryName();
2013 if (pInfoData->treeconflict_nodekind == svn_node_file)
2015 switch (pInfoData->treeconflict_operation)
2017 case svn_wc_operation_update:
2018 switch (pInfoData->treeconflict_action)
2020 case svn_wc_conflict_action_edit:
2021 sConflictAction.Format(IDS_TREECONFLICT_FILEUPDATEEDIT, (LPCTSTR)sItemName);
2022 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2023 break;
2024 case svn_wc_conflict_action_add:
2025 sConflictAction.Format(IDS_TREECONFLICT_FILEUPDATEADD, (LPCTSTR)sItemName);
2026 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2027 break;
2028 case svn_wc_conflict_action_delete:
2029 sConflictAction.Format(IDS_TREECONFLICT_FILEUPDATEDELETE, (LPCTSTR)sItemName);
2030 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2031 break;
2033 break;
2034 case svn_wc_operation_switch:
2035 switch (pInfoData->treeconflict_action)
2037 case svn_wc_conflict_action_edit:
2038 sConflictAction.Format(IDS_TREECONFLICT_FILESWITCHEDIT, (LPCTSTR)sItemName);
2039 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2040 break;
2041 case svn_wc_conflict_action_add:
2042 sConflictAction.Format(IDS_TREECONFLICT_FILESWITCHADD, (LPCTSTR)sItemName);
2043 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2044 break;
2045 case svn_wc_conflict_action_delete:
2046 sConflictAction.Format(IDS_TREECONFLICT_FILESWITCHDELETE, (LPCTSTR)sItemName);
2047 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2048 break;
2050 break;
2051 case svn_wc_operation_merge:
2052 switch (pInfoData->treeconflict_action)
2054 case svn_wc_conflict_action_edit:
2055 sConflictAction.Format(IDS_TREECONFLICT_FILEMERGEEDIT, (LPCTSTR)sItemName);
2056 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2057 break;
2058 case svn_wc_conflict_action_add:
2059 sResolveTheirs.Format(IDS_TREECONFLICT_FILEMERGEADD, (LPCTSTR)sItemName);
2060 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2061 break;
2062 case svn_wc_conflict_action_delete:
2063 sConflictAction.Format(IDS_TREECONFLICT_FILEMERGEDELETE, (LPCTSTR)sItemName);
2064 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2065 break;
2067 break;
2070 else if (pInfoData->treeconflict_nodekind == svn_node_dir)
2072 switch (pInfoData->treeconflict_operation)
2074 case svn_wc_operation_update:
2075 switch (pInfoData->treeconflict_action)
2077 case svn_wc_conflict_action_edit:
2078 sConflictAction.Format(IDS_TREECONFLICT_DIRUPDATEEDIT, (LPCTSTR)sItemName);
2079 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2080 break;
2081 case svn_wc_conflict_action_add:
2082 sConflictAction.Format(IDS_TREECONFLICT_DIRUPDATEADD, (LPCTSTR)sItemName);
2083 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2084 break;
2085 case svn_wc_conflict_action_delete:
2086 sConflictAction.Format(IDS_TREECONFLICT_DIRUPDATEDELETE, (LPCTSTR)sItemName);
2087 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR);
2088 break;
2090 break;
2091 case svn_wc_operation_switch:
2092 switch (pInfoData->treeconflict_action)
2094 case svn_wc_conflict_action_edit:
2095 sConflictAction.Format(IDS_TREECONFLICT_DIRSWITCHEDIT, (LPCTSTR)sItemName);
2096 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2097 break;
2098 case svn_wc_conflict_action_add:
2099 sConflictAction.Format(IDS_TREECONFLICT_DIRSWITCHADD, (LPCTSTR)sItemName);
2100 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2101 break;
2102 case svn_wc_conflict_action_delete:
2103 sConflictAction.Format(IDS_TREECONFLICT_DIRSWITCHDELETE, (LPCTSTR)sItemName);
2104 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR);
2105 break;
2107 break;
2108 case svn_wc_operation_merge:
2109 switch (pInfoData->treeconflict_action)
2111 case svn_wc_conflict_action_edit:
2112 sConflictAction.Format(IDS_TREECONFLICT_DIRMERGEEDIT, (LPCTSTR)sItemName);
2113 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2114 break;
2115 case svn_wc_conflict_action_add:
2116 sConflictAction.Format(IDS_TREECONFLICT_DIRMERGEADD, (LPCTSTR)sItemName);
2117 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2118 break;
2119 case svn_wc_conflict_action_delete:
2120 sConflictAction.Format(IDS_TREECONFLICT_DIRMERGEDELETE, (LPCTSTR)sItemName);
2121 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR);
2122 break;
2124 break;
2128 UINT uReasonID = 0;
2129 switch (pInfoData->treeconflict_reason)
2131 case svn_wc_conflict_reason_edited:
2132 uReasonID = IDS_TREECONFLICT_REASON_EDITED;
2133 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2134 break;
2135 case svn_wc_conflict_reason_obstructed:
2136 uReasonID = IDS_TREECONFLICT_REASON_OBSTRUCTED;
2137 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2138 break;
2139 case svn_wc_conflict_reason_deleted:
2140 uReasonID = IDS_TREECONFLICT_REASON_DELETED;
2141 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_REMOVEDIR : IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2142 break;
2143 case svn_wc_conflict_reason_added:
2144 uReasonID = IDS_TREECONFLICT_REASON_ADDED;
2145 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2146 break;
2147 case svn_wc_conflict_reason_missing:
2148 uReasonID = IDS_TREECONFLICT_REASON_MISSING;
2149 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_REMOVEDIR : IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2150 break;
2151 case svn_wc_conflict_reason_unversioned:
2152 uReasonID = IDS_TREECONFLICT_REASON_UNVERSIONED;
2153 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2154 break;
2156 sConflictReason.Format(uReasonID, (LPCTSTR)sConflictAction);
2158 CTreeConflictEditorDlg dlg;
2159 dlg.SetConflictInfoText(sConflictReason);
2160 dlg.SetResolveTexts(sResolveTheirs, sResolveMine);
2161 dlg.SetPath(treeConflictPath);
2162 INT_PTR dlgRet = dlg.DoModal();
2163 bRet = (dlgRet != IDCANCEL);
2167 #endif
2168 return bRet;
2171 bool CAppUtils::IsSSHPutty()
2173 CString sshclient = g_Git.m_Environment.GetEnv(L"GIT_SSH");
2174 sshclient=sshclient.MakeLower();
2175 return sshclient.Find(L"plink", 0) >= 0;
2178 CString CAppUtils::GetClipboardLink(const CString &skipGitPrefix, int paramsCount)
2180 if (!OpenClipboard(nullptr))
2181 return CString();
2183 CString sClipboardText;
2184 HGLOBAL hglb = GetClipboardData(CF_TEXT);
2185 if (hglb)
2187 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
2188 sClipboardText = CString(lpstr);
2189 GlobalUnlock(hglb);
2191 hglb = GetClipboardData(CF_UNICODETEXT);
2192 if (hglb)
2194 LPCTSTR lpstr = (LPCTSTR)GlobalLock(hglb);
2195 sClipboardText = lpstr;
2196 GlobalUnlock(hglb);
2198 CloseClipboard();
2200 if(!sClipboardText.IsEmpty())
2202 if (sClipboardText[0] == L'"' && sClipboardText[sClipboardText.GetLength() - 1] == L'"')
2203 sClipboardText=sClipboardText.Mid(1,sClipboardText.GetLength()-2);
2205 for (const CString& prefix : { L"http://", L"https://", L"git://", L"ssh://", L"git@" })
2207 if (CStringUtils::StartsWith(sClipboardText, prefix) && sClipboardText.GetLength() != prefix.GetLength())
2208 return sClipboardText;
2211 if(sClipboardText.GetLength()>=2)
2212 if (sClipboardText[1] == L':')
2213 if( (sClipboardText[0] >= 'A' && sClipboardText[0] <= 'Z')
2214 || (sClipboardText[0] >= 'a' && sClipboardText[0] <= 'z') )
2215 return sClipboardText;
2217 // trim prefixes like "git clone "
2218 if (!skipGitPrefix.IsEmpty() && CStringUtils::StartsWith(sClipboardText, skipGitPrefix))
2220 sClipboardText = sClipboardText.Mid(skipGitPrefix.GetLength()).Trim();
2221 int spacePos = -1;
2222 while (paramsCount >= 0)
2224 --paramsCount;
2225 spacePos = sClipboardText.Find(L' ', spacePos + 1);
2226 if (spacePos == -1)
2227 break;
2229 if (spacePos > 0 && paramsCount < 0)
2230 sClipboardText.Truncate(spacePos);
2231 return sClipboardText;
2235 return CString();
2238 CString CAppUtils::ChooseRepository(const CString* path)
2240 CBrowseFolder browseFolder;
2241 CRegString regLastResopitory = CRegString(L"Software\\TortoiseGit\\TortoiseProc\\LastRepo", L"");
2243 browseFolder.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
2244 CString strCloneDirectory;
2245 if(path)
2246 strCloneDirectory=*path;
2247 else
2248 strCloneDirectory = regLastResopitory;
2250 CString title;
2251 title.LoadString(IDS_CHOOSE_REPOSITORY);
2253 browseFolder.SetInfo(title);
2255 if (browseFolder.Show(nullptr, strCloneDirectory) == CBrowseFolder::OK)
2257 regLastResopitory = strCloneDirectory;
2258 return strCloneDirectory;
2260 else
2261 return CString();
2264 bool CAppUtils::SendPatchMail(CTGitPathList& list, bool bIsMainWnd)
2266 CSendMailDlg dlg;
2268 dlg.m_PathList = list;
2270 if(dlg.DoModal()==IDOK)
2272 if (dlg.m_PathList.IsEmpty())
2273 return FALSE;
2275 CGitProgressDlg progDlg;
2276 if (bIsMainWnd)
2277 theApp.m_pMainWnd = &progDlg;
2278 SendMailProgressCommand sendMailProgressCommand;
2279 progDlg.SetCommand(&sendMailProgressCommand);
2281 sendMailProgressCommand.SetPathList(dlg.m_PathList);
2282 progDlg.SetItemCount(dlg.m_PathList.GetCount());
2284 CSendMailPatch sendMailPatch(dlg.m_To, dlg.m_CC, dlg.m_Subject, !!dlg.m_bAttachment, !!dlg.m_bCombine);
2285 sendMailProgressCommand.SetSendMailOption(&sendMailPatch);
2287 progDlg.DoModal();
2289 return true;
2291 return false;
2294 bool CAppUtils::SendPatchMail(const CString& cmd, const CString& formatpatchoutput, bool bIsMainWnd)
2296 CTGitPathList list;
2297 CString log=formatpatchoutput;
2298 int start=log.Find(cmd);
2299 if(start >=0)
2300 log.Tokenize(L"\n", start);
2301 else
2302 start = 0;
2304 while(start>=0)
2306 CString one = log.Tokenize(L"\n", start);
2307 one=one.Trim();
2308 if (one.IsEmpty() || CStringUtils::StartsWith(one, CString(MAKEINTRESOURCE(IDS_SUCCESS))))
2309 continue;
2310 one.Replace(L'/', L'\\');
2311 CTGitPath path;
2312 path.SetFromWin(one);
2313 list.AddPath(path);
2315 if (!list.IsEmpty())
2316 return SendPatchMail(list, bIsMainWnd);
2317 else
2319 CMessageBox::Show(nullptr, IDS_ERR_NOPATCHES, IDS_APPNAME, MB_ICONINFORMATION);
2320 return true;
2325 int CAppUtils::GetLogOutputEncode(CGit *pGit)
2327 CString output;
2328 output = pGit->GetConfigValue(L"i18n.logOutputEncoding");
2329 if(output.IsEmpty())
2330 return CUnicodeUtils::GetCPCode(pGit->GetConfigValue(L"i18n.commitencoding"));
2331 else
2332 return CUnicodeUtils::GetCPCode(output);
2334 int CAppUtils::SaveCommitUnicodeFile(const CString& filename, CString &message)
2338 CFile file(filename, CFile::modeReadWrite | CFile::modeCreate);
2339 int cp = CUnicodeUtils::GetCPCode(g_Git.GetConfigValue(L"i18n.commitencoding"));
2341 bool stripComments = (CRegDWORD(L"Software\\TortoiseGit\\StripCommentedLines", FALSE) == TRUE);
2342 TCHAR commentChar = L'#';
2343 if (stripComments)
2345 CString commentCharValue = g_Git.GetConfigValue(L"core.commentchar");
2346 if (!commentCharValue.IsEmpty())
2347 commentChar = commentCharValue[0];
2350 bool sanitize = (CRegDWORD(L"Software\\TortoiseGit\\SanitizeCommitMsg", TRUE) == TRUE);
2351 if (sanitize)
2352 message.Trim(L" \r\n");
2354 int len = message.GetLength();
2355 int start = 0;
2356 int emptyLineCnt = 0;
2357 while (start >= 0 && start < len)
2359 int oldStart = start;
2360 start = message.Find(L'\n', oldStart);
2361 CString line = message.Mid(oldStart);
2362 if (start != -1)
2364 line.Truncate(start - oldStart);
2365 ++start; // move forward so we don't find the same char again
2367 if (stripComments && (!line.IsEmpty() && line.GetAt(0) == commentChar) || (start < 0 && line.IsEmpty()))
2368 continue;
2369 line.TrimRight(L" \r");
2370 if (sanitize)
2372 if (line.IsEmpty())
2374 ++emptyLineCnt;
2375 continue;
2377 if (emptyLineCnt) // squash multiple newlines
2378 file.Write("\n", 1);
2379 emptyLineCnt = 0;
2381 CStringA lineA = CUnicodeUtils::GetMulti(line + L'\n', cp);
2382 file.Write((LPCSTR)lineA, lineA.GetLength());
2384 file.Close();
2385 return 0;
2387 catch (CFileException *e)
2389 e->Delete();
2390 return -1;
2394 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)
2396 if (bAutoLoad)
2397 CAppUtils::LaunchPAgent(nullptr, &url);
2399 CGitHash hashOld;
2400 if (g_Git.GetHash(hashOld, L"HEAD"))
2402 MessageBox(nullptr, g_Git.GetGitLastErr(L"Could not get HEAD hash."), L"TortoiseGit", MB_ICONERROR);
2403 return false;
2406 CString args;
2407 if (CRegDWORD(L"Software\\TortoiseGit\\PullRebaseBehaviorLike1816", FALSE) == FALSE)
2408 args += L" --no-rebase";
2410 if (bFetchTags == BST_UNCHECKED)
2411 args += L" --no-tags";
2412 else if (bFetchTags == BST_CHECKED)
2413 args += L" --tags";
2415 if (bNoFF)
2416 args += L" --no-ff";
2418 if (bFFonly)
2419 args += L" --ff-only";
2421 if (bSquash)
2422 args += L" --squash";
2424 if (bNoCommit)
2425 args += L" --no-commit";
2427 if (nDepth)
2428 args.AppendFormat(L" --depth %d", *nDepth);
2430 if (bPrune == BST_CHECKED)
2431 args += L" --prune";
2432 else if (bPrune == BST_UNCHECKED)
2433 args += L" --no-prune";
2435 if (bUnrelated)
2436 args += L" --allow-unrelated-histories";
2438 CString cmd;
2439 cmd.Format(L"git.exe pull --progress -v%s \"%s\" %s", (LPCTSTR)args, (LPCTSTR)url, (LPCTSTR)remoteBranchName);
2440 CProgressDlg progress;
2441 progress.m_GitCmd = cmd;
2443 CGitHash hashNew; // declare outside lambda, because it is captured by reference
2444 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2446 if (status)
2448 if (CAppUtils::GetMsysgitVersion() >= 0x02090000)
2450 STRING_VECTOR remotes;
2451 g_Git.GetRemoteList(remotes);
2452 if (std::find(remotes.begin(), remotes.end(), url) != remotes.end())
2454 CString currentBranch;
2455 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, currentBranch))
2456 currentBranch.Empty();
2457 CString remoteRef = L"remotes/" + url + L"/" + remoteBranchName;
2458 if (!currentBranch.IsEmpty() && remoteBranchName.IsEmpty())
2460 CString pullRemote, pullBranch;
2461 g_Git.GetRemoteTrackedBranch(currentBranch, pullRemote, pullBranch);
2462 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
2463 remoteRef = L"remotes/" + pullRemote + L"/" + pullBranch;
2465 CGitHash common;
2466 g_Git.IsFastForward(L"HEAD", remoteRef, &common);
2467 if (common.IsEmpty())
2468 postCmdList.emplace_back(IDI_MERGE, IDS_MERGE_UNRELATED, [=] { DoPull(url, bAutoLoad, bFetchTags, bNoFF, bFFonly, bSquash, bNoCommit, nDepth, bPrune, remoteBranchName, showPush, showStashPop, true); });
2472 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&]{ CAppUtils::Pull(); });
2473 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSTASHSAVE, [&]{ CAppUtils::StashSave(L"", true); });
2474 return;
2477 if (showStashPop)
2478 postCmdList.emplace_back(IDI_RELOCATE, IDS_MENUSTASHPOP, []{ CAppUtils::StashPop(); });
2480 if (g_Git.GetHash(hashNew, L"HEAD"))
2481 MessageBox(nullptr, g_Git.GetGitLastErr(L"Could not get HEAD hash after pulling."), L"TortoiseGit", MB_ICONERROR);
2482 else
2484 postCmdList.emplace_back(IDI_DIFF, IDS_PROC_PULL_DIFFS, [&]
2486 CString sCmd;
2487 sCmd.Format(L"/command:showcompare /path:\"%s\" /revision1:%s /revision2:%s", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)hashOld.ToString(), (LPCTSTR)hashNew.ToString());
2488 CAppUtils::RunTortoiseGitProc(sCmd);
2490 postCmdList.emplace_back(IDI_LOG, IDS_PROC_PULL_LOG, [&]
2492 CString sCmd;
2493 sCmd.Format(L"/command:log /path:\"%s\" /range:%s", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)(hashOld.ToString() + L".." + hashNew.ToString()));
2494 CAppUtils::RunTortoiseGitProc(sCmd);
2498 if (showPush)
2499 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, []{ CAppUtils::Push(); });
2501 CTGitPath gitPath = g_Git.m_CurrentDir;
2502 if (gitPath.HasSubmodules())
2504 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
2506 CString sCmd;
2507 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
2508 CAppUtils::RunTortoiseGitProc(sCmd);
2513 INT_PTR ret = progress.DoModal();
2515 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)
2517 CChangedDlg changeddlg;
2518 changeddlg.m_pathList.AddPath(CTGitPath());
2519 changeddlg.DoModal();
2521 return true;
2524 return ret == IDOK;
2527 bool CAppUtils::Pull(bool showPush, bool showStashPop)
2529 if (IsTGitRebaseActive())
2530 return false;
2532 CPullFetchDlg dlg;
2533 dlg.m_IsPull = TRUE;
2534 if (dlg.DoModal() == IDOK)
2536 // "git.exe pull --rebase" is not supported, never and ever. So, adapting it to Fetch & Rebase.
2537 if (dlg.m_bRebase)
2538 return DoFetch(dlg.m_RemoteURL,
2539 FALSE, // Fetch all remotes
2540 dlg.m_bAutoLoad == BST_CHECKED,
2541 dlg.m_bPrune,
2542 dlg.m_bDepth == BST_CHECKED,
2543 dlg.m_nDepth,
2544 dlg.m_bFetchTags,
2545 dlg.m_RemoteBranchName,
2546 dlg.m_bRebaseActivatedInConfigForPull ? 2 : 1, // Rebase after fetching
2547 dlg.m_bRebasePreserveMerges == TRUE); // Preserve merges on rebase
2549 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);
2552 return false;
2555 bool CAppUtils::RebaseAfterFetch(const CString& upstream, int rebase, bool preserveMerges)
2557 while (true)
2559 CRebaseDlg dlg;
2560 if (!upstream.IsEmpty())
2561 dlg.m_Upstream = upstream;
2562 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENULOG)));
2563 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUPUSH)));
2564 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUDESSENDMAIL)));
2565 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUREBASE)));
2566 dlg.m_bRebaseAutoStart = (rebase == 2);
2567 dlg.m_bPreserveMerges = preserveMerges;
2568 INT_PTR response = dlg.DoModal();
2569 if (response == IDOK)
2570 return true;
2571 else if (response == IDC_REBASE_POST_BUTTON)
2573 CString cmd = L"/command:log";
2574 cmd += L" /path:\"" + g_Git.m_CurrentDir + L'"';
2575 CAppUtils::RunTortoiseGitProc(cmd);
2576 return true;
2578 else if (response == IDC_REBASE_POST_BUTTON + 1)
2579 return Push();
2580 else if (response == IDC_REBASE_POST_BUTTON + 2)
2582 CString cmd, out, err;
2583 cmd.Format(L"git.exe format-patch -o \"%s\" %s..%s",
2584 (LPCTSTR)g_Git.m_CurrentDir,
2585 (LPCTSTR)g_Git.FixBranchName(dlg.m_Upstream),
2586 (LPCTSTR)g_Git.FixBranchName(dlg.m_Branch));
2587 if (g_Git.Run(cmd, &out, &err, CP_UTF8))
2589 CMessageBox::Show(nullptr, out + L'\n' + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
2590 return false;
2592 CAppUtils::SendPatchMail(cmd, out);
2593 return true;
2595 else if (response == IDC_REBASE_POST_BUTTON + 3)
2596 continue;
2597 else if (response == IDCANCEL)
2598 return false;
2599 return false;
2603 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)
2605 if (loadPuttyAgent)
2607 if (fetchAllRemotes)
2609 STRING_VECTOR list;
2610 g_Git.GetRemoteList(list);
2612 for (const auto& remote : list)
2613 CAppUtils::LaunchPAgent(nullptr, &remote);
2615 else
2616 CAppUtils::LaunchPAgent(nullptr, &url);
2619 CString upstream = L"FETCH_HEAD";
2620 CGitHash oldUpstreamHash;
2621 if (runRebase)
2623 STRING_VECTOR list;
2624 g_Git.GetRemoteList(list);
2625 for (auto it = list.cbegin(); it != list.cend(); ++it)
2627 if (url == *it)
2629 upstream = L"remotes/" + *it + L'/' + remoteBranch;
2630 if (remoteBranch.IsEmpty()) // pulldlg might clear remote branch if its the default tracked branch
2632 CString currentBranch;
2633 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, currentBranch))
2634 currentBranch.Empty();
2635 if (!currentBranch.IsEmpty() && remoteBranch.IsEmpty())
2637 CString pullRemote, pullBranch;
2638 g_Git.GetRemoteTrackedBranch(currentBranch, pullRemote, pullBranch);
2639 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty() && pullRemote == url) // pullRemote == url is just another safety-check and should not be needed
2640 upstream += pullBranch;
2643 g_Git.GetHash(oldUpstreamHash, upstream);
2644 break;
2649 CString cmd, arg;
2650 arg = L" --progress";
2652 if (bDepth)
2653 arg.AppendFormat(L" --depth %d", nDepth);
2655 if (prune == TRUE)
2656 arg += L" --prune";
2657 else if (prune == FALSE)
2658 arg += L" --no-prune";
2660 if (fetchTags == 1)
2661 arg += L" --tags";
2662 else if (fetchTags == 0)
2663 arg += L" --no-tags";
2665 if (fetchAllRemotes)
2666 cmd.Format(L"git.exe fetch --all -v%s", (LPCTSTR)arg);
2667 else
2668 cmd.Format(L"git.exe fetch -v%s \"%s\" %s", (LPCTSTR)arg, (LPCTSTR)url, (LPCTSTR)remoteBranch);
2670 CProgressDlg progress;
2671 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2673 if (status)
2675 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ DoFetch(url, fetchAllRemotes, loadPuttyAgent, prune, bDepth, nDepth, fetchTags, remoteBranch, runRebase, rebasePreserveMerges); });
2676 return;
2679 postCmdList.emplace_back(IDI_LOG, IDS_MENULOG, []
2681 CString cmd = L"/command:log";
2682 cmd += L" /path:\"" + g_Git.m_CurrentDir + L'"';
2683 CAppUtils::RunTortoiseGitProc(cmd);
2686 postCmdList.emplace_back(IDI_REVERT, IDS_PROC_RESET, []
2688 CString pullRemote, pullBranch;
2689 g_Git.GetRemoteTrackedBranchForHEAD(pullRemote, pullBranch);
2690 CString defaultUpstream;
2691 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
2692 defaultUpstream.Format(L"remotes/%s/%s", (LPCTSTR)pullRemote, (LPCTSTR)pullBranch);
2693 CAppUtils::GitReset(&defaultUpstream, 2);
2696 postCmdList.emplace_back(IDI_PULL, IDS_MENUFETCH, []{ CAppUtils::Fetch(); });
2698 if (!runRebase && !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir))
2699 postCmdList.emplace_back(IDI_REBASE, IDS_MENUREBASE, [&]{ runRebase = false; CAppUtils::RebaseAfterFetch(); });
2702 progress.m_GitCmd = cmd;
2704 if (g_Git.UsingLibGit2(CGit::GIT_CMD_FETCH))
2706 CGitProgressDlg gitdlg;
2707 FetchProgressCommand fetchProgressCommand;
2708 if (!fetchAllRemotes)
2709 fetchProgressCommand.SetUrl(url);
2710 gitdlg.SetCommand(&fetchProgressCommand);
2711 fetchProgressCommand.m_PostCmdCallback = progress.m_PostCmdCallback;
2712 fetchProgressCommand.SetAutoTag(fetchTags == 1 ? GIT_REMOTE_DOWNLOAD_TAGS_ALL : fetchTags == 2 ? GIT_REMOTE_DOWNLOAD_TAGS_AUTO : GIT_REMOTE_DOWNLOAD_TAGS_NONE);
2713 if (!fetchAllRemotes)
2714 fetchProgressCommand.SetRefSpec(remoteBranch);
2715 return gitdlg.DoModal() == IDOK;
2718 progress.m_PostExecCallback = [&](DWORD& exitCode, CString&)
2720 if (exitCode || !runRebase)
2721 return;
2723 CGitHash remoteBranchHash;
2724 g_Git.GetHash(remoteBranchHash, upstream);
2725 if (runRebase == 1 && remoteBranchHash == oldUpstreamHash && !oldUpstreamHash.IsEmpty() && CMessageBox::ShowCheck(nullptr, IDS_REBASE_BRANCH_UNCHANGED, IDS_APPNAME, MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2, L"OpenRebaseRemoteBranchUnchanged", IDS_MSGBOX_DONOTSHOWAGAIN) == IDNO)
2726 return;
2728 if (runRebase == 1 && g_Git.IsFastForward(L"HEAD", upstream))
2730 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);
2731 if (ret == 3)
2732 return;
2733 if (ret == 1)
2735 CProgressDlg mergeProgress;
2736 mergeProgress.m_GitCmd = L"git.exe merge --ff-only " + upstream;
2737 mergeProgress.m_AutoClose = AUTOCLOSE_IF_NO_ERRORS;
2738 mergeProgress.m_PostCmdCallback = [](DWORD status, PostCmdList& postCmdList)
2740 if (status && g_Git.HasWorkingTreeConflicts())
2742 // there are conflict files
2743 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
2745 CString sCmd;
2746 sCmd.Format(L"/command:commit /path:\"%s\"", g_Git.m_CurrentDir);
2747 CAppUtils::RunTortoiseGitProc(sCmd);
2751 mergeProgress.DoModal();
2752 return;
2756 CAppUtils::RebaseAfterFetch(upstream, runRebase, rebasePreserveMerges);
2759 return progress.DoModal() == IDOK;
2762 bool CAppUtils::Fetch(const CString& remoteName, bool allRemotes)
2764 CPullFetchDlg dlg;
2765 dlg.m_PreSelectRemote = remoteName;
2766 dlg.m_IsPull=FALSE;
2767 dlg.m_bAllRemotes = allRemotes;
2769 if(dlg.DoModal()==IDOK)
2770 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);
2772 return false;
2775 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)
2777 CString error;
2778 DWORD exitcode = 0xFFFFFFFF;
2779 if (CHooks::Instance().PrePush(g_Git.m_CurrentDir, exitcode, error))
2781 if (exitcode)
2783 CString temp;
2784 temp.Format(IDS_ERR_HOOKFAILED, (LPCTSTR)error);
2785 MessageBox(nullptr, temp, L"TortoiseGit", MB_OK | MB_ICONERROR);
2786 return false;
2790 int iRecurseSubmodules = 0;
2791 if (GetMsysgitVersion() >= 0x02070000)
2793 CString sRecurseSubmodules = g_Git.GetConfigValue(L"push.recurseSubmodules");
2794 if (sRecurseSubmodules == L"check")
2795 iRecurseSubmodules = 1;
2796 else if (sRecurseSubmodules == L"on-demand")
2797 iRecurseSubmodules = 2;
2800 CString arg;
2801 if (pack)
2802 arg += L"--thin ";
2803 if (tags && !allBranches)
2804 arg += L"--tags ";
2805 if (force)
2806 arg += L"--force ";
2807 if (forceWithLease)
2808 arg += L"--force-with-lease ";
2809 if (setUpstream)
2810 arg += L"--set-upstream ";
2811 if (recurseSubmodules == 0 && recurseSubmodules != iRecurseSubmodules)
2812 arg += L"--recurse-submodules=no ";
2813 if (recurseSubmodules == 1 && recurseSubmodules != iRecurseSubmodules)
2814 arg += L"--recurse-submodules=check ";
2815 if (recurseSubmodules == 2 && recurseSubmodules != iRecurseSubmodules)
2816 arg += L"--recurse-submodules=on-demand ";
2818 arg += L"--progress ";
2820 CProgressDlg progress;
2822 STRING_VECTOR remotesList;
2823 if (allRemotes)
2824 g_Git.GetRemoteList(remotesList);
2825 else
2826 remotesList.push_back(remote);
2828 for (unsigned int i = 0; i < remotesList.size(); ++i)
2830 if (autoloadKey)
2831 CAppUtils::LaunchPAgent(nullptr, &remotesList[i]);
2833 CString cmd;
2834 if (allBranches)
2836 cmd.Format(L"git.exe push --all %s\"%s\"",
2837 (LPCTSTR)arg,
2838 (LPCTSTR)remotesList[i]);
2840 if (tags)
2842 progress.m_GitCmdList.push_back(cmd);
2843 cmd.Format(L"git.exe push --tags %s\"%s\"", (LPCTSTR)arg, (LPCTSTR)remotesList[i]);
2846 else
2848 cmd.Format(L"git.exe push %s\"%s\" %s",
2849 (LPCTSTR)arg,
2850 (LPCTSTR)remotesList[i],
2851 (LPCTSTR)localBranch);
2852 if (!remoteBranch.IsEmpty())
2854 cmd += L":";
2855 cmd += remoteBranch;
2858 progress.m_GitCmdList.push_back(cmd);
2860 if (!allBranches && !!CRegDWORD(L"Software\\TortoiseGit\\ShowBranchRevisionNumber", FALSE))
2862 cmd.Format(L"git.exe rev-list --count --first-parent %s", (LPCTSTR)localBranch);
2863 progress.m_GitCmdList.push_back(cmd);
2867 CString superprojectRoot;
2868 GitAdminDir::HasAdminDir(g_Git.m_CurrentDir, false, &superprojectRoot);
2869 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2871 // need to execute hooks as those might be needed by post action commands
2872 DWORD exitcode = 0xFFFFFFFF;
2873 CString error;
2874 if (CHooks::Instance().PostPush(g_Git.m_CurrentDir, exitcode, error))
2876 if (exitcode)
2878 CString temp;
2879 temp.Format(IDS_ERR_HOOKFAILED, (LPCTSTR)error);
2880 MessageBox(nullptr, temp, L"TortoiseGit", MB_OK | MB_ICONERROR);
2884 if (status)
2886 bool rejected = progress.GetLogText().Find(L"! [rejected]") > 0;
2887 if (rejected)
2889 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, []{ Pull(true); });
2890 postCmdList.emplace_back(IDI_PULL, IDS_MENUFETCH, [&]{ Fetch(allRemotes ? L"" : remote, allRemotes); });
2892 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, [&]{ Push(localBranch); });
2893 return;
2896 postCmdList.emplace_back(IDS_PROC_REQUESTPULL, [&]{ RequestPull(remoteBranch); });
2897 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, [&]{ Push(localBranch); });
2898 postCmdList.emplace_back(IDI_SWITCH, IDS_MENUSWITCH, [&]{ Switch(); });
2899 if (!superprojectRoot.IsEmpty())
2901 postCmdList.emplace_back(IDI_COMMIT, IDS_PROC_COMMIT_SUPERPROJECT, [&]
2903 CString sCmd;
2904 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)superprojectRoot);
2905 RunTortoiseGitProc(sCmd);
2910 INT_PTR ret = progress.DoModal();
2911 return ret == IDOK;
2914 bool CAppUtils::Push(const CString& selectLocalBranch)
2916 CPushDlg dlg;
2917 dlg.m_BranchSourceName = selectLocalBranch;
2919 if (dlg.DoModal() == IDOK)
2920 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);
2922 return FALSE;
2925 bool CAppUtils::RequestPull(const CString& endrevision, const CString& repositoryUrl, bool bIsMainWnd)
2927 CRequestPullDlg dlg;
2928 dlg.m_RepositoryURL = repositoryUrl;
2929 dlg.m_EndRevision = endrevision;
2930 if (dlg.DoModal()==IDOK)
2932 CString cmd;
2933 cmd.Format(L"git.exe request-pull %s \"%s\" %s", (LPCTSTR)dlg.m_StartRevision, (LPCTSTR)dlg.m_RepositoryURL, (LPCTSTR)dlg.m_EndRevision);
2935 CSysProgressDlg sysProgressDlg;
2936 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
2937 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_CREATINGPULLREUQEST)));
2938 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
2939 sysProgressDlg.SetShowProgressBar(false);
2940 sysProgressDlg.ShowModeless((HWND)nullptr, true);
2942 CString tempFileName = GetTempFile();
2943 CString err;
2944 DeleteFile(tempFileName);
2945 CreateDirectory(tempFileName, nullptr);
2946 tempFileName += L"\\pullrequest.txt";
2947 if (g_Git.RunLogFile(cmd, tempFileName, &err))
2949 CString msg;
2950 msg.LoadString(IDS_ERR_PULLREUQESTFAILED);
2951 MessageBox(nullptr, msg + L'\n' + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
2952 return false;
2955 if (sysProgressDlg.HasUserCancelled())
2957 CMessageBox::Show(nullptr, IDS_USERCANCELLED, IDS_APPNAME, MB_OK);
2958 ::DeleteFile(tempFileName);
2959 return false;
2962 sysProgressDlg.Stop();
2964 if (dlg.m_bSendMail)
2966 CSendMailDlg sendmaildlg;
2967 sendmaildlg.m_PathList = CTGitPathList(CTGitPath(tempFileName));
2968 sendmaildlg.m_bCustomSubject = true;
2970 if (sendmaildlg.DoModal() == IDOK)
2972 if (sendmaildlg.m_PathList.IsEmpty())
2973 return FALSE;
2975 CGitProgressDlg progDlg;
2976 if (bIsMainWnd)
2977 theApp.m_pMainWnd = &progDlg;
2978 SendMailProgressCommand sendMailProgressCommand;
2979 progDlg.SetCommand(&sendMailProgressCommand);
2981 sendMailProgressCommand.SetPathList(sendmaildlg.m_PathList);
2982 progDlg.SetItemCount(sendmaildlg.m_PathList.GetCount());
2984 CSendMailCombineable sendMailCombineable(sendmaildlg.m_To, sendmaildlg.m_CC, sendmaildlg.m_Subject, !!sendmaildlg.m_bAttachment, !!sendmaildlg.m_bCombine);
2985 sendMailProgressCommand.SetSendMailOption(&sendMailCombineable);
2987 progDlg.DoModal();
2989 return true;
2991 return false;
2994 CAppUtils::LaunchAlternativeEditor(tempFileName);
2996 return true;
2999 void CAppUtils::RemoveTrailSlash(CString &path)
3001 if(path.IsEmpty())
3002 return ;
3004 // For URL, do not trim the slash just after the host name component.
3005 int index = path.Find(L"://");
3006 if (index >= 0)
3008 index += 4;
3009 index = path.Find(L'/', index);
3010 if (index == path.GetLength() - 1)
3011 return;
3014 while (path[path.GetLength() - 1] == L'\\' || path[path.GetLength() - 1] == L'/')
3016 path.Truncate(path.GetLength() - 1);
3017 if(path.IsEmpty())
3018 return;
3022 bool CAppUtils::CheckUserData()
3024 while(g_Git.GetUserName().IsEmpty() || g_Git.GetUserEmail().IsEmpty())
3026 if (CMessageBox::Show(nullptr, IDS_PROC_NOUSERDATA, IDS_APPNAME, MB_YESNO | MB_ICONERROR) == IDYES)
3028 CTGitPath path(g_Git.m_CurrentDir);
3029 CSettings dlg(IDS_PROC_SETTINGS_TITLE,&path);
3030 dlg.SetTreeViewMode(TRUE, TRUE, TRUE);
3031 dlg.SetTreeWidth(220);
3032 dlg.m_DefaultPage = L"gitconfig";
3034 dlg.DoModal();
3035 dlg.HandleRestart();
3038 else
3039 return false;
3042 return true;
3045 BOOL CAppUtils::Commit(const CString& bugid, BOOL bWholeProject, CString &sLogMsg,
3046 CTGitPathList &pathList,
3047 CTGitPathList &selectedList,
3048 bool bSelectFilesForCommit)
3050 bool bFailed = true;
3052 if (!CheckUserData())
3053 return false;
3055 while (bFailed)
3057 bFailed = false;
3058 CCommitDlg dlg;
3059 dlg.m_sBugID = bugid;
3061 dlg.m_bWholeProject = bWholeProject;
3063 dlg.m_sLogMessage = sLogMsg;
3064 dlg.m_pathList = pathList;
3065 dlg.m_checkedPathList = selectedList;
3066 dlg.m_bSelectFilesForCommit = bSelectFilesForCommit;
3067 if (dlg.DoModal() == IDOK)
3069 if (dlg.m_pathList.IsEmpty())
3070 return false;
3071 // if the user hasn't changed the list of selected items
3072 // we don't use that list. Because if we would use the list
3073 // of pre-checked items, the dialog would show different
3074 // checked items on the next startup: it would only try
3075 // to check the parent folder (which might not even show)
3076 // instead, we simply use an empty list and let the
3077 // default checking do its job.
3078 if (!dlg.m_pathList.IsEqual(pathList))
3079 selectedList = dlg.m_pathList;
3080 pathList = dlg.m_updatedPathList;
3081 sLogMsg = dlg.m_sLogMessage;
3082 bSelectFilesForCommit = true;
3084 switch (dlg.m_PostCmd)
3086 case GIT_POSTCOMMIT_CMD_DCOMMIT:
3087 CAppUtils::SVNDCommit();
3088 break;
3089 case GIT_POSTCOMMIT_CMD_PUSH:
3090 CAppUtils::Push();
3091 break;
3092 case GIT_POSTCOMMIT_CMD_CREATETAG:
3093 CAppUtils::CreateBranchTag(TRUE);
3094 break;
3095 case GIT_POSTCOMMIT_CMD_PULL:
3096 CAppUtils::Pull(true);
3097 break;
3098 default:
3099 break;
3102 // CGitProgressDlg progDlg;
3103 // progDlg.SetChangeList(dlg.m_sChangeList, !!dlg.m_bKeepChangeList);
3104 // if (parser.HasVal(L"closeonend"))
3105 // progDlg.SetAutoClose(parser.GetLongVal(L"closeonend"));
3106 // progDlg.SetCommand(CGitProgressDlg::GitProgress_Commit);
3107 // progDlg.SetOptions(dlg.m_bKeepLocks ? ProgOptKeeplocks : ProgOptNone);
3108 // progDlg.SetPathList(dlg.m_pathList);
3109 // progDlg.SetCommitMessage(dlg.m_sLogMessage);
3110 // progDlg.SetDepth(dlg.m_bRecursive ? Git_depth_infinity : svn_depth_empty);
3111 // progDlg.SetSelectedList(dlg.m_selectedPathList);
3112 // progDlg.SetItemCount(dlg.m_itemsCount);
3113 // progDlg.SetBugTraqProvider(dlg.m_BugTraqProvider);
3114 // progDlg.DoModal();
3115 // CRegDWORD err = CRegDWORD(L"Software\\TortoiseGit\\ErrorOccurred", FALSE);
3116 // err = (DWORD)progDlg.DidErrorsOccur();
3117 // bFailed = progDlg.DidErrorsOccur();
3118 // bRet = progDlg.DidErrorsOccur();
3119 // CRegDWORD bFailRepeat = CRegDWORD(L"Software\\TortoiseGit\\CommitReopen", FALSE);
3120 // if (DWORD(bFailRepeat)==0)
3121 // bFailed = false; // do not repeat if the user chose not to in the settings.
3124 return true;
3127 BOOL CAppUtils::SVNDCommit()
3129 CSVNDCommitDlg dcommitdlg;
3130 CString gitSetting = g_Git.GetConfigValue(L"svn.rmdir");
3131 if (gitSetting.IsEmpty()) {
3132 if (dcommitdlg.DoModal() != IDOK)
3133 return false;
3134 else
3136 if (dcommitdlg.m_remember)
3138 if (dcommitdlg.m_rmdir)
3139 gitSetting = L"true";
3140 else
3141 gitSetting = L"false";
3142 if (g_Git.SetConfigValue(L"svn.rmdir", gitSetting))
3144 CString msg;
3145 msg.Format(IDS_PROC_SAVECONFIGFAILED, L"svn.rmdir", gitSetting);
3146 MessageBox(nullptr, msg, L"TortoiseGit", MB_OK | MB_ICONERROR);
3152 BOOL IsStash = false;
3153 if(!g_Git.CheckCleanWorkTree())
3155 if (CMessageBox::Show(nullptr, IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
3157 CSysProgressDlg sysProgressDlg;
3158 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3159 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3160 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3161 sysProgressDlg.SetShowProgressBar(false);
3162 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3163 sysProgressDlg.ShowModeless((HWND)nullptr, true);
3165 CString out;
3166 if (g_Git.Run(L"git.exe stash", &out, CP_UTF8))
3168 sysProgressDlg.Stop();
3169 MessageBox(nullptr, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3170 return false;
3172 sysProgressDlg.Stop();
3174 IsStash =true;
3176 else
3177 return false;
3180 CProgressDlg progress;
3181 if (dcommitdlg.m_rmdir)
3182 progress.m_GitCmd = L"git.exe svn dcommit --rmdir";
3183 else
3184 progress.m_GitCmd = L"git.exe svn dcommit";
3185 if(progress.DoModal()==IDOK && progress.m_GitStatus == 0)
3187 if( IsStash)
3189 if (CMessageBox::Show(nullptr, IDS_DCOMMIT_STASH_POP, IDS_APPNAME, MB_YESNO | MB_ICONINFORMATION) == IDYES)
3191 CSysProgressDlg sysProgressDlg;
3192 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3193 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3194 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3195 sysProgressDlg.SetShowProgressBar(false);
3196 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3197 sysProgressDlg.ShowModeless((HWND)nullptr, true);
3199 CString out;
3200 if (g_Git.Run(L"git.exe stash pop", &out, CP_UTF8))
3202 sysProgressDlg.Stop();
3203 MessageBox(nullptr, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3204 return false;
3206 sysProgressDlg.Stop();
3208 else
3209 return false;
3211 return TRUE;
3213 return FALSE;
3216 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)
3218 CString args;
3219 if (noFF)
3220 args += L" --no-ff";
3221 else if (ffOnly)
3222 args += L" --ff-only";
3224 if (squash)
3225 args += L" --squash";
3227 if (noCommit)
3228 args += L" --no-commit";
3230 if (unrelated)
3231 args += L" --allow-unrelated-histories";
3233 if (log)
3234 args.AppendFormat(L" --log=%d", *log);
3236 if (!mergeStrategy.IsEmpty())
3238 args += L" --strategy=" + mergeStrategy;
3239 if (!strategyOption.IsEmpty())
3241 args += L" --strategy-option=" + strategyOption;
3242 if (!strategyParam.IsEmpty())
3243 args += L'=' + strategyParam;
3247 if (!logMessage.IsEmpty())
3249 CString logmsg = logMessage;
3250 logmsg.Replace(L"\\\"", L"\\\\\"");
3251 logmsg.Replace(L"\"", L"\\\"");
3252 args += L" -m \"" + logmsg + L"\"";
3255 CString mergeVersion = g_Git.FixBranchName(version);
3256 CString cmd;
3257 cmd.Format(L"git.exe merge%s %s", (LPCTSTR)args, (LPCTSTR)mergeVersion);
3259 CProgressDlg Prodlg;
3260 Prodlg.m_GitCmd = cmd;
3262 Prodlg.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3264 if (status)
3266 int hasConflicts = g_Git.HasWorkingTreeConflicts();
3267 if (hasConflicts < 0)
3268 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(L"Checking for conflicts failed.", CGit::GIT_CMD_CHECKCONFLICTS), L"TortoiseGit", MB_ICONEXCLAMATION);
3269 else if (hasConflicts)
3271 // there are conflict files
3273 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
3275 CString sCmd;
3276 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3277 CAppUtils::RunTortoiseGitProc(sCmd);
3281 if (CAppUtils::GetMsysgitVersion() >= 0x02090000)
3283 CGitHash common;
3284 g_Git.IsFastForward(L"HEAD", mergeVersion, &common);
3285 if (common.IsEmpty())
3286 postCmdList.emplace_back(IDI_MERGE, IDS_MERGE_UNRELATED, [=] { DoMerge(noFF, ffOnly, squash, noCommit, log, true, mergeStrategy, strategyOption, strategyParam, logMessage, version, isBranch, showStashPop); });
3289 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSTASHSAVE, [=]{ CAppUtils::StashSave(L"", false, false, true, mergeVersion); });
3291 return;
3294 if (showStashPop)
3295 postCmdList.emplace_back(IDI_RELOCATE, IDS_MENUSTASHPOP, []{ CAppUtils::StashPop(); });
3297 if (noCommit || squash)
3299 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUCOMMIT, []
3301 CString sCmd;
3302 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3303 CAppUtils::RunTortoiseGitProc(sCmd);
3305 return;
3308 if (isBranch && !CStringUtils::StartsWith(version, L"remotes/")) // do not ask to remove remote branches
3310 postCmdList.emplace_back(IDI_DELETE, IDS_PROC_REMOVEBRANCH, [&]
3312 CString msg;
3313 msg.Format(IDS_PROC_DELETEBRANCHTAG, version);
3314 if (CMessageBox::Show(nullptr, msg, L"TortoiseGit", 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
3316 CString cmd, out;
3317 cmd.Format(L"git.exe branch -D -- %s", (LPCTSTR)version);
3318 if (g_Git.Run(cmd, &out, CP_UTF8))
3319 MessageBox(nullptr, out, L"TortoiseGit", MB_OK);
3323 if (isBranch)
3324 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, []{ CAppUtils::Push(); });
3326 BOOL hasGitSVN = CTGitPath(g_Git.m_CurrentDir).GetAdminDirMask() & ITEMIS_GITSVN;
3327 if (hasGitSVN)
3328 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSVNDCOMMIT, []{ CAppUtils::SVNDCommit(); });
3331 Prodlg.DoModal();
3332 return !Prodlg.m_GitStatus;
3335 BOOL CAppUtils::Merge(const CString* commit, bool showStashPop)
3337 if (!CheckUserData())
3338 return FALSE;
3340 if (IsTGitRebaseActive())
3341 return FALSE;
3343 CMergeDlg dlg;
3344 if (commit)
3345 dlg.m_initialRefName = *commit;
3347 if (dlg.DoModal() == IDOK)
3348 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);
3350 return FALSE;
3353 BOOL CAppUtils::MergeAbort()
3355 CMergeAbortDlg dlg;
3356 if (dlg.DoModal() == IDOK)
3357 return Reset(L"HEAD", dlg.m_ResetType + 1);
3359 return FALSE;
3362 void CAppUtils::EditNote(GitRevLoglist* rev, ProjectProperties* projectProperties)
3364 if (!CheckUserData())
3365 return;
3367 CInputDlg dlg;
3368 dlg.m_sHintText = CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES));
3369 dlg.m_sInputText = rev->m_Notes;
3370 dlg.m_sTitle = CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES));
3371 dlg.m_pProjectProperties = projectProperties;
3372 dlg.m_bUseLogWidth = true;
3373 if(dlg.DoModal() == IDOK)
3375 CString cmd,output;
3376 cmd = L"notes add -f -F \"";
3378 CString tempfile=::GetTempFile();
3379 if (!CStringUtils::WriteStringToTextFile(tempfile, dlg.m_sInputText))
3381 CMessageBox::Show(nullptr, IDS_PROC_FAILEDSAVINGNOTES, IDS_APPNAME, MB_OK | MB_ICONERROR);
3382 return;
3384 cmd += tempfile;
3385 cmd += L"\" ";
3386 cmd += rev->m_CommitHash.ToString();
3390 if (git_run_cmd("notes", CUnicodeUtils::GetMulti(cmd, CP_UTF8).GetBuffer()))
3391 CMessageBox::Show(nullptr, IDS_PROC_FAILEDSAVINGNOTES, IDS_APPNAME, MB_OK | MB_ICONERROR);
3392 }catch(...)
3394 CMessageBox::Show(nullptr, IDS_PROC_FAILEDSAVINGNOTES, IDS_APPNAME, MB_OK | MB_ICONERROR);
3396 ::DeleteFile(tempfile);
3398 if (g_Git.GetGitNotes(rev->m_CommitHash, rev->m_Notes))
3399 MessageBox(nullptr, g_Git.GetLibGit2LastErr(L"Could not load notes for commit " + rev->m_CommitHash.ToString() + L'.'), L"TortoiseGit", MB_OK | MB_ICONERROR);
3403 int CAppUtils::GetMsysgitVersion()
3405 if (g_Git.ms_LastMsysGitVersion)
3406 return g_Git.ms_LastMsysGitVersion;
3408 CString cmd;
3409 CString versiondebug;
3410 CString version;
3412 CRegDWORD regTime = CRegDWORD(L"Software\\TortoiseGit\\git_file_time");
3413 CRegDWORD regVersion = CRegDWORD(L"Software\\TortoiseGit\\git_cached_version");
3415 CString gitpath = CGit::ms_LastMsysGitDir + L"\\git.exe";
3417 __int64 time=0;
3418 if (!CGit::GetFileModifyTime(gitpath, &time))
3420 if((DWORD)time == regTime)
3422 g_Git.ms_LastMsysGitVersion = regVersion;
3423 return regVersion;
3427 CString err;
3428 int ver = g_Git.GetGitVersion(&versiondebug, &err);
3429 if (ver < 0)
3431 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);
3432 return -1;
3436 if (!ver)
3438 MessageBox(nullptr, L"Could not parse git.exe version number: \"" + versiondebug + L'"', L"TortoiseGit", MB_OK | MB_ICONERROR);
3439 return -1;
3443 regTime = time&0xFFFFFFFF;
3444 regVersion = ver;
3445 g_Git.ms_LastMsysGitVersion = ver;
3447 return ver;
3450 void CAppUtils::MarkWindowAsUnpinnable(HWND hWnd)
3452 typedef HRESULT (WINAPI *SHGPSFW) (HWND hwnd,REFIID riid,void** ppv);
3454 CAutoLibrary hShell = AtlLoadSystemLibraryUsingFullPath(L"Shell32.dll");
3456 if (hShell.IsValid()) {
3457 SHGPSFW pfnSHGPSFW = (SHGPSFW)::GetProcAddress(hShell, "SHGetPropertyStoreForWindow");
3458 if (pfnSHGPSFW) {
3459 IPropertyStore *pps;
3460 HRESULT hr = pfnSHGPSFW(hWnd, IID_PPV_ARGS(&pps));
3461 if (SUCCEEDED(hr)) {
3462 PROPVARIANT var;
3463 var.vt = VT_BOOL;
3464 var.boolVal = VARIANT_TRUE;
3465 pps->SetValue(PKEY_AppUserModel_PreventPinning, var);
3466 pps->Release();
3472 void CAppUtils::SetWindowTitle(HWND hWnd, const CString& urlorpath, const CString& dialogname)
3474 ASSERT(dialogname.GetLength() < 70);
3475 ASSERT(urlorpath.GetLength() < MAX_PATH);
3476 WCHAR pathbuf[MAX_PATH] = {0};
3478 PathCompactPathEx(pathbuf, urlorpath, 70 - dialogname.GetLength(), 0);
3480 wcscat_s(pathbuf, L" - ");
3481 wcscat_s(pathbuf, dialogname);
3482 wcscat_s(pathbuf, L" - ");
3483 wcscat_s(pathbuf, CString(MAKEINTRESOURCE(IDS_APPNAME)));
3484 SetWindowText(hWnd, pathbuf);
3487 bool CAppUtils::BisectStart(const CString& lastGood, const CString& firstBad, bool bIsMainWnd)
3489 if (!g_Git.CheckCleanWorkTree())
3491 if (CMessageBox::Show(nullptr, IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
3493 CSysProgressDlg sysProgressDlg;
3494 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3495 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3496 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3497 sysProgressDlg.SetShowProgressBar(false);
3498 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3499 sysProgressDlg.ShowModeless((HWND)nullptr, true);
3501 CString out;
3502 if (g_Git.Run(L"git.exe stash", &out, CP_UTF8))
3504 sysProgressDlg.Stop();
3505 MessageBox(nullptr, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3506 return false;
3508 sysProgressDlg.Stop();
3510 else
3511 return false;
3514 CBisectStartDlg bisectStartDlg;
3516 if (!lastGood.IsEmpty())
3517 bisectStartDlg.m_sLastGood = lastGood;
3518 if (!firstBad.IsEmpty())
3519 bisectStartDlg.m_sFirstBad = firstBad;
3521 if (bisectStartDlg.DoModal() == IDOK)
3523 CProgressDlg progress;
3524 if (bIsMainWnd)
3525 theApp.m_pMainWnd = &progress;
3526 progress.m_GitCmdList.push_back(L"git.exe bisect start");
3527 progress.m_GitCmdList.push_back(L"git.exe bisect good " + bisectStartDlg.m_LastGoodRevision);
3528 progress.m_GitCmdList.push_back(L"git.exe bisect bad " + bisectStartDlg.m_FirstBadRevision);
3530 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3532 if (status)
3533 return;
3535 CTGitPath path(g_Git.m_CurrentDir);
3536 if (path.HasSubmodules())
3538 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
3540 CString sCmd;
3541 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3542 CAppUtils::RunTortoiseGitProc(sCmd);
3547 INT_PTR ret = progress.DoModal();
3548 return ret == IDOK;
3551 return false;
3554 bool CAppUtils::BisectOperation(const CString& op, const CString& ref, bool bIsMainWnd)
3556 CString cmd = L"git.exe bisect " + op;
3558 if (!ref.IsEmpty())
3560 cmd += L' ';
3561 cmd += ref;
3564 CProgressDlg progress;
3565 if (bIsMainWnd)
3566 theApp.m_pMainWnd = &progress;
3567 progress.m_GitCmd = cmd;
3569 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3571 if (status)
3572 return;
3574 CTGitPath path = g_Git.m_CurrentDir;
3575 if (path.HasSubmodules())
3577 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
3579 CString sCmd;
3580 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3581 CAppUtils::RunTortoiseGitProc(sCmd);
3585 if (op != L"reset")
3586 postCmdList.emplace_back(IDS_MENUBISECTRESET, []{ CAppUtils::RunTortoiseGitProc(L"/command:bisect /reset"); });
3589 INT_PTR ret = progress.DoModal();
3590 return ret == IDOK;
3593 int CAppUtils::Git2GetUserPassword(git_cred **out, const char *url, const char *username_from_url, unsigned int /*allowed_types*/, void * /*payload*/)
3595 CUserPassword dlg;
3596 dlg.m_URL = CUnicodeUtils::GetUnicode(url, CP_UTF8);
3597 if (username_from_url)
3598 dlg.m_UserName = CUnicodeUtils::GetUnicode(username_from_url, CP_UTF8);
3600 CStringA username, password;
3601 if (dlg.DoModal() == IDOK)
3603 username = CUnicodeUtils::GetMulti(dlg.m_UserName, CP_UTF8);
3604 password = CUnicodeUtils::GetMulti(dlg.m_Password, CP_UTF8);
3605 return git_cred_userpass_plaintext_new(out, username, password);
3607 giterr_set_str(GITERR_NONE, "User cancelled.");
3608 return GIT_EUSER;
3611 int CAppUtils::Git2CertificateCheck(git_cert* base_cert, int /*valid*/, const char* host, void* /*payload*/)
3613 if (base_cert->cert_type == GIT_CERT_X509)
3615 git_cert_x509* cert = (git_cert_x509*)base_cert;
3617 if (last_accepted_cert.cmp(cert))
3618 return 0;
3620 PCCERT_CONTEXT pServerCert = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, (BYTE*)cert->data, (DWORD)cert->len);
3621 SCOPE_EXIT { CertFreeCertificateContext(pServerCert); };
3623 DWORD verificationError = VerifyServerCertificate(pServerCert, CUnicodeUtils::GetUnicode(host).GetBuffer(), 0);
3624 if (!verificationError)
3626 last_accepted_cert.set(cert);
3627 return 0;
3630 CString servernameInCert;
3631 CertGetNameString(pServerCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nullptr, CStrBuf(servernameInCert, 128), 128);
3633 CString issuer;
3634 CertGetNameString(pServerCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, CERT_NAME_ISSUER_FLAG, nullptr, CStrBuf(issuer, 128), 128);
3636 CCheckCertificateDlg dlg;
3637 dlg.cert = cert;
3638 dlg.m_sCertificateCN = servernameInCert;
3639 dlg.m_sCertificateIssuer = issuer;
3640 dlg.m_sHostname = CUnicodeUtils::GetUnicode(host);
3641 dlg.m_sError = CFormatMessageWrapper(verificationError);
3642 if (dlg.DoModal() == IDOK)
3644 last_accepted_cert.set(cert);
3645 return 0;
3648 return GIT_ECERTIFICATE;
3651 int CAppUtils::ExploreTo(HWND hwnd, CString path)
3653 if (PathFileExists(path))
3655 HRESULT ret = -1;
3656 ITEMIDLIST __unaligned * pidl = ILCreateFromPath(path);
3657 if (pidl)
3659 ret = SHOpenFolderAndSelectItems(pidl, 0, 0, 0);
3660 ILFree(pidl);
3662 return SUCCEEDED(ret) ? 0 : -1;
3664 // if filepath does not exist any more, navigate to closest matching folder
3667 int pos = path.ReverseFind(L'\\');
3668 if (pos <= 3)
3669 break;
3670 path.Truncate(pos);
3671 } while (!PathFileExists(path));
3672 return (INT_PTR)ShellExecute(hwnd, L"explore", path, nullptr, nullptr, SW_SHOW) > 32 ? 0 : -1;
3675 int CAppUtils::ResolveConflict(CTGitPath& path, resolve_with resolveWith)
3677 bool b_local = false, b_remote = false;
3678 BYTE_VECTOR vector;
3680 CString cmd;
3681 cmd.Format(L"git.exe ls-files -u -t -z -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3682 if (g_Git.Run(cmd, &vector))
3684 MessageBox(nullptr, L"git ls-files failed!", L"TortoiseGit", MB_OK);
3685 return -1;
3688 CTGitPathList list;
3689 if (list.ParserFromLsFile(vector))
3691 MessageBox(nullptr, L"Parse ls-files failed!", L"TortoiseGit", MB_OK);
3692 return -1;
3695 if (list.IsEmpty())
3696 return 0;
3697 for (int i = 0; i < list.GetCount(); ++i)
3699 if (list[i].m_Stage == 2)
3700 b_local = true;
3701 if (list[i].m_Stage == 3)
3702 b_remote = true;
3706 CBlockCacheForPath block(g_Git.m_CurrentDir);
3707 if (path.IsDirectory()) // is submodule conflict
3709 CString err = L"We're sorry, but you hit a very rare conflict condition with a submodule which cannot be resolved by TortoiseGit. You have to use the command line git for this.";
3710 if (b_local && b_remote)
3712 if (!path.HasAdminDir()) // check if submodule is initialized
3714 err += L"\n\nYou have to checkout the submodule manually into \"" + path.GetGitPathString() + L"\" and then reset HEAD to the right commit (see resolve submodule conflict dialog for this).";
3715 MessageBox(nullptr, err, L"TortoiseGit", MB_ICONERROR);
3716 return -1;
3718 CGit subgit;
3719 subgit.m_CurrentDir = g_Git.CombinePath(path);
3720 CGitHash submoduleHead;
3721 if (subgit.GetHash(submoduleHead, L"HEAD"))
3723 MessageBox(nullptr, err, L"TortoiseGit", MB_ICONERROR);
3724 return -1;
3726 CString baseHash, localHash, remoteHash;
3727 ParseHashesFromLsFile(vector, baseHash, localHash, remoteHash);
3728 if (resolveWith == RESOLVE_WITH_THEIRS && submoduleHead.ToString() != remoteHash)
3730 CString origPath = g_Git.m_CurrentDir;
3731 g_Git.m_CurrentDir = g_Git.CombinePath(path);
3732 if (!GitReset(&remoteHash))
3734 g_Git.m_CurrentDir = origPath;
3735 return -1;
3737 g_Git.m_CurrentDir = origPath;
3739 else if (resolveWith == RESOLVE_WITH_MINE && submoduleHead.ToString() != localHash)
3741 CString origPath = g_Git.m_CurrentDir;
3742 g_Git.m_CurrentDir = g_Git.CombinePath(path);
3743 if (!GitReset(&localHash))
3745 g_Git.m_CurrentDir = origPath;
3746 return -1;
3748 g_Git.m_CurrentDir = origPath;
3751 else
3753 MessageBox(nullptr, err, L"TortoiseGit", MB_ICONERROR);
3754 return -1;
3758 if (resolveWith == RESOLVE_WITH_THEIRS)
3760 CString gitcmd, output;
3761 if (b_local && b_remote)
3762 gitcmd.Format(L"git.exe checkout-index -f --stage=3 -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3763 else if (b_remote)
3764 gitcmd.Format(L"git.exe add -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3765 else if (b_local)
3766 gitcmd.Format(L"git.exe rm -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3767 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3769 MessageBox(nullptr, output, L"TortoiseGit", MB_ICONERROR);
3770 return -1;
3773 else if (resolveWith == RESOLVE_WITH_MINE)
3775 CString gitcmd, output;
3776 if (b_local && b_remote)
3777 gitcmd.Format(L"git.exe checkout-index -f --stage=2 -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3778 else if (b_local)
3779 gitcmd.Format(L"git.exe add -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3780 else if (b_remote)
3781 gitcmd.Format(L"git.exe rm -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3782 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3784 MessageBox(nullptr, output, L"TortoiseGit", MB_ICONERROR);
3785 return -1;
3789 if (b_local && b_remote && path.m_Action & CTGitPath::LOGACTIONS_UNMERGED)
3791 CString gitcmd, output;
3792 gitcmd.Format(L"git.exe add -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3793 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3794 MessageBox(nullptr, output, L"TortoiseGit", MB_ICONERROR);
3795 else
3797 path.m_Action |= CTGitPath::LOGACTIONS_MODIFIED;
3798 path.m_Action &= ~CTGitPath::LOGACTIONS_UNMERGED;
3802 RemoveTempMergeFile(path);
3803 return 0;
3806 bool CAppUtils::ShellOpen(const CString& file, HWND hwnd /*= nullptr */)
3808 if ((INT_PTR)ShellExecute(hwnd, nullptr, file, nullptr, nullptr, SW_SHOW) > HINSTANCE_ERROR)
3809 return true;
3811 return ShowOpenWithDialog(file, hwnd);
3814 bool CAppUtils::ShowOpenWithDialog(const CString& file, HWND hwnd /*= nullptr */)
3816 OPENASINFO oi = { 0 };
3817 oi.pcszFile = file;
3818 oi.oaifInFlags = OAIF_EXEC;
3819 return SUCCEEDED(SHOpenWithDialog(hwnd, &oi));
3822 bool CAppUtils::IsTGitRebaseActive()
3824 CString adminDir;
3825 if (!GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir))
3826 return false;
3828 if (!PathIsDirectory(adminDir + L"tgitrebase.active"))
3829 return false;
3831 if (CMessageBox::Show(nullptr, IDS_REBASELOCKFILEFOUND, IDS_APPNAME, 2, IDI_EXCLAMATION, IDS_REMOVESTALEBUTTON, IDS_ABORTBUTTON) == 2)
3832 return true;
3834 RemoveDirectory(adminDir + L"tgitrebase.active");
3836 return false;