PullDlg: Pull default tracked branch with rebase might fail
[TortoiseGit.git] / src / TortoiseProc / AppUtils.cpp
blob3e810c40dd989f8b441343997f0afc3b0fd31b59
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 "FileDiffDlg.h"
54 #include "RebaseDlg.h"
55 #include "PropKey.h"
56 #include "StashSave.h"
57 #include "IgnoreDlg.h"
58 #include "FormatMessageWrapper.h"
59 #include "SmartHandle.h"
60 #include "BisectStartDlg.h"
61 #include "SysProgressDlg.h"
62 #include "UserPassword.h"
63 #include "SendmailPatch.h"
64 #include "Globals.h"
65 #include "ProgressCommands/ResetProgressCommand.h"
66 #include "ProgressCommands/FetchProgressCommand.h"
67 #include "ProgressCommands/SendMailProgressCommand.h"
68 #include "CertificateValidationHelper.h"
69 #include "CheckCertificateDlg.h"
70 #include "SubmoduleResolveConflictDlg.h"
71 #include "GitDiff.h"
72 #include "../TGitCache/CacheInterface.h"
74 static struct last_accepted_cert {
75 BYTE* data;
76 size_t len;
78 last_accepted_cert()
79 : data(nullptr)
80 , len(0)
83 ~last_accepted_cert()
85 free(data);
87 boolean cmp(git_cert_x509* cert)
89 return len > 0 && len == cert->len && memcmp(data, cert->data, len) == 0;
91 void set(git_cert_x509* cert)
93 free(data);
94 len = cert->len;
95 if (len == 0)
97 data = nullptr;
98 return;
100 data = new BYTE[len];
101 memcpy(data, cert->data, len);
103 } last_accepted_cert;
105 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);
107 bool CAppUtils::StashSave(const CString& msg, bool showPull, bool pullShowPush, bool showMerge, const CString& mergeRev)
109 CStashSaveDlg dlg;
110 dlg.m_sMessage = msg;
111 if (dlg.DoModal() == IDOK)
113 CString cmd = L"git.exe stash save";
115 if (dlg.m_bIncludeUntracked)
116 cmd += L" --include-untracked";
117 else if (dlg.m_bAll)
118 cmd += L" --all";
120 if (!dlg.m_sMessage.IsEmpty())
122 CString message = dlg.m_sMessage;
123 message.Replace(L"\"", L"\"\"");
124 cmd += L" -- \"" + message + L'"';
127 CProgressDlg progress;
128 progress.m_GitCmd = cmd;
129 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
131 if (status)
132 return;
134 if (showPull)
135 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&]{ CAppUtils::Pull(pullShowPush, true); });
136 if (showMerge)
137 postCmdList.emplace_back(IDI_MERGE, IDS_MENUMERGE, [&]{ CAppUtils::Merge(&mergeRev, true); });
139 return (progress.DoModal() == IDOK);
141 return false;
144 bool CAppUtils::StashApply(CString ref, bool showChanges /* true */)
146 CString cmd = L"git.exe stash apply ";
147 if (CStringUtils::StartsWith(ref, L"refs/"))
148 ref = ref.Mid(5);
149 if (CStringUtils::StartsWith(ref, L"stash{"))
150 ref = L"stash@" + ref.Mid(5);
151 cmd += ref;
153 CSysProgressDlg sysProgressDlg;
154 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
155 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
156 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
157 sysProgressDlg.SetShowProgressBar(false);
158 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
159 sysProgressDlg.ShowModeless((HWND)nullptr, true);
161 CString out;
162 int ret = g_Git.Run(cmd, &out, CP_UTF8);
164 sysProgressDlg.Stop();
166 bool hasConflicts = (out.Find(L"CONFLICT") >= 0);
167 if (ret && !(ret == 1 && hasConflicts))
168 CMessageBox::Show(nullptr, CString(MAKEINTRESOURCE(IDS_PROC_STASHAPPLYFAILED)) + L'\n' + out, L"TortoiseGit", MB_OK | MB_ICONERROR);
169 else
171 CString message;
172 message.LoadString(IDS_PROC_STASHAPPLYSUCCESS);
173 if (hasConflicts)
174 message.LoadString(IDS_PROC_STASHAPPLYFAILEDCONFLICTS);
175 if (showChanges)
177 if (CMessageBox::Show(nullptr, message + L'\n' + CString(MAKEINTRESOURCE(IDS_SEECHANGES)), L"TortoiseGit", MB_YESNO | MB_ICONINFORMATION) == IDYES)
179 CChangedDlg dlg;
180 dlg.m_pathList.AddPath(CTGitPath());
181 dlg.DoModal();
183 return true;
185 else
187 MessageBox(nullptr, message ,L"TortoiseGit", MB_OK | MB_ICONINFORMATION);
188 return true;
191 return false;
194 bool CAppUtils::StashPop(int showChanges /* = 1 */)
196 CString cmd = L"git.exe stash pop";
198 CSysProgressDlg sysProgressDlg;
199 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
200 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
201 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
202 sysProgressDlg.SetShowProgressBar(false);
203 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
204 sysProgressDlg.ShowModeless((HWND)nullptr, true);
206 CString out;
207 int ret = g_Git.Run(cmd, &out, CP_UTF8);
209 sysProgressDlg.Stop();
211 bool hasConflicts = (out.Find(L"CONFLICT") >= 0);
212 if (ret && !(ret == 1 && hasConflicts))
213 CMessageBox::Show(nullptr, CString(MAKEINTRESOURCE(IDS_PROC_STASHPOPFAILED)) + L'\n' + out, L"TortoiseGit", MB_OK | MB_ICONERROR);
214 else
216 CString message;
217 message.LoadString(IDS_PROC_STASHPOPSUCCESS);
218 if (hasConflicts)
219 message.LoadString(IDS_PROC_STASHPOPFAILEDCONFLICTS);
220 if (showChanges == 1 || (showChanges == 0 && hasConflicts))
222 if (CMessageBox::ShowCheck(nullptr, message + L'\n' + CString(MAKEINTRESOURCE(IDS_SEECHANGES)), L"TortoiseGit", MB_YESNO | (hasConflicts ? MB_ICONEXCLAMATION : MB_ICONINFORMATION), hasConflicts ? L"StashPopShowConflictChanges" : L"StashPopShowChanges") == IDYES)
224 CChangedDlg dlg;
225 dlg.m_pathList.AddPath(CTGitPath());
226 dlg.DoModal();
228 return true;
230 else if (showChanges > 1)
232 MessageBox(nullptr, message, L"TortoiseGit", MB_OK | MB_ICONINFORMATION);
233 return true;
235 else if (showChanges == 0)
236 return true;
238 return false;
241 BOOL CAppUtils::StartExtMerge(bool bAlternative,
242 const CTGitPath& basefile, const CTGitPath& theirfile, const CTGitPath& yourfile, const CTGitPath& mergedfile,
243 const CString& basename, const CString& theirname, const CString& yourname, const CString& mergedname, bool bReadOnly,
244 HWND resolveMsgHwnd, bool bDeleteBaseTheirsMineOnClose)
246 CRegString regCom = CRegString(L"Software\\TortoiseGit\\Merge");
247 CString ext = mergedfile.GetFileExtension();
248 CString com = regCom;
249 bool bInternal = false;
251 if (!ext.IsEmpty())
253 // is there an extension specific merge tool?
254 CRegString mergetool(L"Software\\TortoiseGit\\MergeTools\\" + ext.MakeLower());
255 if (!CString(mergetool).IsEmpty())
256 com = mergetool;
258 // is there a filename specific merge tool?
259 CRegString mergetool(L"Software\\TortoiseGit\\MergeTools\\." + mergedfile.GetFilename().MakeLower());
260 if (!CString(mergetool).IsEmpty())
261 com = mergetool;
263 if (bAlternative && !com.IsEmpty())
265 if (CStringUtils::StartsWith(com, L"#"))
266 com.Delete(0);
267 else
268 com.Empty();
271 if (com.IsEmpty() || CStringUtils::StartsWith(com, L"#"))
273 // Maybe we should use TortoiseIDiff?
274 if ((ext == L".jpg") || (ext == L".jpeg") ||
275 (ext == L".bmp") || (ext == L".gif") ||
276 (ext == L".png") || (ext == L".ico") ||
277 (ext == L".tif") || (ext == L".tiff") ||
278 (ext == L".dib") || (ext == L".emf") ||
279 (ext == L".cur"))
281 com = CPathUtils::GetAppDirectory() + L"TortoiseGitIDiff.exe";
282 com = L'"' + com + L'"';
283 com = com + L" /base:%base /theirs:%theirs /mine:%mine /result:%merged";
284 com = com + L" /basetitle:%bname /theirstitle:%tname /minetitle:%yname";
285 if (resolveMsgHwnd)
286 com.AppendFormat(L" /resolvemsghwnd:%I64d", (__int64)resolveMsgHwnd);
288 else
290 // use TortoiseGitMerge
291 bInternal = true;
292 com = CPathUtils::GetAppDirectory() + L"TortoiseGitMerge.exe";
293 com = L'"' + com + L'"';
294 com = com + L" /base:%base /theirs:%theirs /mine:%mine /merged:%merged";
295 com = com + L" /basename:%bname /theirsname:%tname /minename:%yname /mergedname:%mname";
296 com += L" /saverequired";
297 if (resolveMsgHwnd)
298 com.AppendFormat(L" /resolvemsghwnd:%I64d", (__int64)resolveMsgHwnd);
299 if (bDeleteBaseTheirsMineOnClose)
300 com += L" /deletebasetheirsmineonclose";
302 if (!g_sGroupingUUID.IsEmpty())
304 com += L" /groupuuid:\"";
305 com += g_sGroupingUUID;
306 com += L'"';
309 // check if the params are set. If not, just add the files to the command line
310 if ((com.Find(L"%merged") < 0) && (com.Find(L"%base") < 0) && (com.Find(L"%theirs") < 0) && (com.Find(L"%mine") < 0))
312 com += L" \"" + basefile.GetWinPathString() + L'"';
313 com += L" \"" + theirfile.GetWinPathString() + L'"';
314 com += L" \"" + yourfile.GetWinPathString() + L'"';
315 com += L" \"" + mergedfile.GetWinPathString() + L'"';
317 if (basefile.IsEmpty())
319 com.Replace(L"/base:%base", L"");
320 com.Replace(L"%base", L"");
322 else
323 com.Replace(L"%base", L'"' + basefile.GetWinPathString() + L'"');
324 if (theirfile.IsEmpty())
326 com.Replace(L"/theirs:%theirs", L"");
327 com.Replace(L"%theirs", L"");
329 else
330 com.Replace(L"%theirs", L'"' + theirfile.GetWinPathString() + L'"');
331 if (yourfile.IsEmpty())
333 com.Replace(L"/mine:%mine", L"");
334 com.Replace(L"%mine", L"");
336 else
337 com.Replace(L"%mine", L'"' + yourfile.GetWinPathString() + L'"');
338 if (mergedfile.IsEmpty())
340 com.Replace(L"/merged:%merged", L"");
341 com.Replace(L"%merged", L"");
343 else
344 com.Replace(L"%merged", L'"' + mergedfile.GetWinPathString() + L'"');
345 if (basename.IsEmpty())
347 if (basefile.IsEmpty())
349 com.Replace(L"/basename:%bname", L"");
350 com.Replace(L"%bname", L"");
352 else
353 com.Replace(L"%bname", L'"' + basefile.GetUIFileOrDirectoryName() + L'"');
355 else
356 com.Replace(L"%bname", L'"' + basename + L'"');
357 if (theirname.IsEmpty())
359 if (theirfile.IsEmpty())
361 com.Replace(L"/theirsname:%tname", L"");
362 com.Replace(L"%tname", L"");
364 else
365 com.Replace(L"%tname", L'"' + theirfile.GetUIFileOrDirectoryName() + L'"');
367 else
368 com.Replace(L"%tname", L'"' + theirname + L'"');
369 if (yourname.IsEmpty())
371 if (yourfile.IsEmpty())
373 com.Replace(L"/minename:%yname", L"");
374 com.Replace(L"%yname", L"");
376 else
377 com.Replace(L"%yname", L'"' + yourfile.GetUIFileOrDirectoryName() + L'"');
379 else
380 com.Replace(L"%yname", L'"' + yourname + L'"');
381 if (mergedname.IsEmpty())
383 if (mergedfile.IsEmpty())
385 com.Replace(L"/mergedname:%mname", L"");
386 com.Replace(L"%mname", L"");
388 else
389 com.Replace(L"%mname", L'"' + mergedfile.GetUIFileOrDirectoryName() + L'"');
391 else
392 com.Replace(L"%mname", L'"' + mergedname + L'"');
394 if ((bReadOnly)&&(bInternal))
395 com += L" /readonly";
397 if(!LaunchApplication(com, IDS_ERR_EXTMERGESTART, false))
399 return FALSE;
402 return TRUE;
405 BOOL CAppUtils::StartExtPatch(const CTGitPath& patchfile, const CTGitPath& dir, const CString& sOriginalDescription, const CString& sPatchedDescription, BOOL bReversed, BOOL bWait)
407 CString viewer;
408 // use TortoiseGitMerge
409 viewer = CPathUtils::GetAppDirectory();
410 viewer += L"TortoiseGitMerge.exe";
412 viewer = L'"' + viewer + L'"';
413 viewer = viewer + L" /diff:\"" + patchfile.GetWinPathString() + L'"';
414 viewer = viewer + L" /patchpath:\"" + dir.GetWinPathString() + L'"';
415 if (bReversed)
416 viewer += L" /reversedpatch";
417 if (!sOriginalDescription.IsEmpty())
418 viewer = viewer + L" /patchoriginal:\"" + sOriginalDescription + L'"';
419 if (!sPatchedDescription.IsEmpty())
420 viewer = viewer + L" /patchpatched:\"" + sPatchedDescription + L'"';
421 if (!g_sGroupingUUID.IsEmpty())
423 viewer += L" /groupuuid:\"";
424 viewer += g_sGroupingUUID;
425 viewer += L'"';
427 if(!LaunchApplication(viewer, IDS_ERR_DIFFVIEWSTART, !!bWait))
428 return FALSE;
429 return TRUE;
432 CString CAppUtils::PickDiffTool(const CTGitPath& file1, const CTGitPath& file2)
434 CString difftool = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + file2.GetFilename().MakeLower());
435 if (!difftool.IsEmpty())
436 return difftool;
437 difftool = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + file1.GetFilename().MakeLower());
438 if (!difftool.IsEmpty())
439 return difftool;
441 // Is there an extension specific diff tool?
442 CString ext = file2.GetFileExtension().MakeLower();
443 if (!ext.IsEmpty())
445 difftool = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + ext);
446 if (!difftool.IsEmpty())
447 return difftool;
448 // Maybe we should use TortoiseIDiff?
449 if ((ext == L".jpg") || (ext == L".jpeg") ||
450 (ext == L".bmp") || (ext == L".gif") ||
451 (ext == L".png") || (ext == L".ico") ||
452 (ext == L".tif") || (ext == L".tiff") ||
453 (ext == L".dib") || (ext == L".emf") ||
454 (ext == L".cur"))
456 return
457 L'"' + CPathUtils::GetAppDirectory() + L"TortoiseGitIDiff.exe" + L'"' +
458 L" /left:%base /right:%mine /lefttitle:%bname /righttitle:%yname" +
459 L" /groupuuid:\"" + g_sGroupingUUID + L'"';
463 // Finally, pick a generic external diff tool
464 difftool = CRegString(L"Software\\TortoiseGit\\Diff");
465 return difftool;
468 bool CAppUtils::StartExtDiff(
469 const CString& file1, const CString& file2,
470 const CString& sName1, const CString& sName2,
471 const CString& originalFile1, const CString& originalFile2,
472 const git_revnum_t& hash1, const git_revnum_t& hash2,
473 const DiffFlags& flags, int jumpToLine)
475 CString viewer;
477 CRegDWORD blamediff(L"Software\\TortoiseGit\\DiffBlamesWithTortoiseMerge", FALSE);
478 if (!flags.bBlame || !(DWORD)blamediff)
480 viewer = PickDiffTool(file1, file2);
481 // If registry entry for a diff program is commented out, use TortoiseGitMerge.
482 bool bCommentedOut = CStringUtils::StartsWith(viewer, L"#");
483 if (flags.bAlternativeTool)
485 // Invert external vs. internal diff tool selection.
486 if (bCommentedOut)
487 viewer.Delete(0); // uncomment
488 else
489 viewer.Empty();
491 else if (bCommentedOut)
492 viewer.Empty();
495 bool bInternal = viewer.IsEmpty();
496 if (bInternal)
498 viewer =
499 L'"' + CPathUtils::GetAppDirectory() + L"TortoiseGitMerge.exe" + L'"' +
500 L" /base:%base /mine:%mine /basename:%bname /minename:%yname" +
501 L" /basereflectedname:%bpath /minereflectedname:%ypath";
502 if (!g_sGroupingUUID.IsEmpty())
504 viewer += L" /groupuuid:\"";
505 viewer += g_sGroupingUUID;
506 viewer += L'"';
508 if (flags.bBlame)
509 viewer += L" /blame";
511 // check if the params are set. If not, just add the files to the command line
512 if ((viewer.Find(L"%base") < 0) && (viewer.Find(L"%mine") < 0))
514 viewer += L" \"" + file1 + L'"';
515 viewer += L" \"" + file2 + L'"';
517 if (viewer.Find(L"%base") >= 0)
518 viewer.Replace(L"%base", L'"' + file1 + L'"');
519 if (viewer.Find(L"%mine") >= 0)
520 viewer.Replace(L"%mine", L'"' + file2 + L'"');
522 if (sName1.IsEmpty())
523 viewer.Replace(L"%bname", L'"' + file1 + L'"');
524 else
525 viewer.Replace(L"%bname", L'"' + sName1 + L'"');
527 if (sName2.IsEmpty())
528 viewer.Replace(L"%yname", L'"' + file2 + L'"');
529 else
530 viewer.Replace(L"%yname", L'"' + sName2 + L'"');
532 viewer.Replace(L"%bpath", L'"' + originalFile1 + L'"');
533 viewer.Replace(L"%ypath", L'"' + originalFile2 + L'"');
535 viewer.Replace(L"%brev", L'"' + hash1 + L'"');
536 viewer.Replace(L"%yrev", L'"' + hash2 + L'"');
538 if (flags.bReadOnly && bInternal)
539 viewer += L" /readonly";
541 if (jumpToLine > 0)
542 viewer.AppendFormat(L" /line:%d", jumpToLine);
544 return LaunchApplication(viewer, IDS_ERR_EXTDIFFSTART, flags.bWait);
547 BOOL CAppUtils::StartUnifiedDiffViewer(const CString& patchfile, const CString& title, BOOL bWait, bool bAlternativeTool)
549 CString viewer;
550 CRegString v = CRegString(L"Software\\TortoiseGit\\DiffViewer");
551 viewer = v;
553 // If registry entry for a diff program is commented out, use TortoiseGitMerge.
554 bool bCommentedOut = CStringUtils::StartsWith(viewer, L"#");
555 if (bAlternativeTool)
557 // Invert external vs. internal diff tool selection.
558 if (bCommentedOut)
559 viewer.Delete(0); // uncomment
560 else
561 viewer.Empty();
563 else if (bCommentedOut)
564 viewer.Empty();
566 if (viewer.IsEmpty())
568 // use TortoiseGitUDiff
569 viewer = CPathUtils::GetAppDirectory();
570 viewer += L"TortoiseGitUDiff.exe";
571 // enquote the path to TortoiseGitUDiff
572 viewer = L'"' + viewer + L'"';
573 // add the params
574 viewer = viewer + L" /patchfile:%1 /title:\"%title\"";
575 if (!g_sGroupingUUID.IsEmpty())
577 viewer += L" /groupuuid:\"";
578 viewer += g_sGroupingUUID;
579 viewer += L'"';
582 if (viewer.Find(L"%1") >= 0)
584 if (viewer.Find(L"\"%1\"") >= 0)
585 viewer.Replace(L"%1", patchfile);
586 else
587 viewer.Replace(L"%1", L'"' + patchfile + L'"');
589 else
590 viewer += L" \"" + patchfile + L'"';
591 if (viewer.Find(L"%title") >= 0)
592 viewer.Replace(L"%title", title);
594 if(!LaunchApplication(viewer, IDS_ERR_DIFFVIEWSTART, !!bWait))
595 return FALSE;
596 return TRUE;
599 BOOL CAppUtils::StartTextViewer(CString file)
601 CString viewer;
602 CRegString txt = CRegString(L".txt\\", L"", FALSE, HKEY_CLASSES_ROOT);
603 viewer = txt;
604 viewer = viewer + L"\\Shell\\Open\\Command\\";
605 CRegString txtexe = CRegString(viewer, L"", FALSE, HKEY_CLASSES_ROOT);
606 viewer = txtexe;
608 DWORD len = ExpandEnvironmentStrings(viewer, nullptr, 0);
609 auto buf = std::make_unique<TCHAR[]>(len + 1);
610 ExpandEnvironmentStrings(viewer, buf.get(), len);
611 viewer = buf.get();
612 len = ExpandEnvironmentStrings(file, nullptr, 0);
613 auto buf2 = std::make_unique<TCHAR[]>(len + 1);
614 ExpandEnvironmentStrings(file, buf2.get(), len);
615 file = buf2.get();
616 file = L'"' + file + L'"';
617 if (viewer.IsEmpty())
618 return CAppUtils::ShowOpenWithDialog(file) ? TRUE : FALSE;
619 if (viewer.Find(L"\"%1\"") >= 0)
620 viewer.Replace(L"\"%1\"", file);
621 else if (viewer.Find(L"%1") >= 0)
622 viewer.Replace(L"%1", file);
623 else
624 viewer += L' ';
625 viewer += file;
627 if(!LaunchApplication(viewer, IDS_ERR_TEXTVIEWSTART, false))
628 return FALSE;
629 return TRUE;
632 BOOL CAppUtils::CheckForEmptyDiff(const CTGitPath& sDiffPath)
634 DWORD length = 0;
635 CAutoFile hFile = ::CreateFile(sDiffPath.GetWinPath(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
636 if (!hFile)
637 return TRUE;
638 length = ::GetFileSize(hFile, nullptr);
639 if (length < 4)
640 return TRUE;
641 return FALSE;
644 CString CAppUtils::GetLogFontName()
646 return (CString)CRegString(L"Software\\TortoiseGit\\LogFontName", L"Consolas");
649 DWORD CAppUtils::GetLogFontSize()
651 return (DWORD)CRegDWORD(L"Software\\TortoiseGit\\LogFontSize", 9);
654 void CAppUtils::CreateFontForLogs(CFont& fontToCreate)
656 LOGFONT logFont;
657 HDC hScreenDC = ::GetDC(nullptr);
658 logFont.lfHeight = -MulDiv(GetLogFontSize(), GetDeviceCaps(hScreenDC, LOGPIXELSY), 72);
659 ::ReleaseDC(nullptr, hScreenDC);
660 logFont.lfWidth = 0;
661 logFont.lfEscapement = 0;
662 logFont.lfOrientation = 0;
663 logFont.lfWeight = FW_NORMAL;
664 logFont.lfItalic = 0;
665 logFont.lfUnderline = 0;
666 logFont.lfStrikeOut = 0;
667 logFont.lfCharSet = DEFAULT_CHARSET;
668 logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
669 logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
670 logFont.lfQuality = DRAFT_QUALITY;
671 logFont.lfPitchAndFamily = FF_DONTCARE | FIXED_PITCH;
672 wcscpy_s(logFont.lfFaceName, 32, (LPCTSTR)GetLogFontName());
673 VERIFY(fontToCreate.CreateFontIndirect(&logFont));
676 bool CAppUtils::LaunchPAgent(const CString* keyfile, const CString* pRemote)
678 CString key,remote;
679 CString cmd,out;
680 if (!pRemote)
681 remote = L"origin";
682 else
683 remote=*pRemote;
685 if (!keyfile)
687 cmd.Format(L"remote.%s.puttykeyfile", (LPCTSTR)remote);
688 key = g_Git.GetConfigValue(cmd);
690 else
691 key=*keyfile;
693 if(key.IsEmpty())
694 return false;
696 CString proc=CPathUtils::GetAppDirectory();
697 proc += L"pageant.exe \"";
698 proc += key;
699 proc += L'"';
701 CString tempfile = GetTempFile();
702 ::DeleteFile(tempfile);
704 proc += L" -c \"";
705 proc += CPathUtils::GetAppDirectory();
706 proc += L"tgittouch.exe\"";
707 proc += L" \"";
708 proc += tempfile;
709 proc += L'"';
711 CString appDir = CPathUtils::GetAppDirectory();
712 bool b = LaunchApplication(proc, IDS_ERR_PAGEANT, true, &appDir);
713 if(!b)
714 return b;
716 int i=0;
717 while(!::PathFileExists(tempfile))
719 Sleep(100);
720 ++i;
721 if(i>10*60*5)
722 break; //timeout 5 minutes
725 if( i== 10*60*5)
726 CMessageBox::Show(nullptr, IDS_ERR_PAEGENTTIMEOUT, IDS_APPNAME, MB_OK | MB_ICONERROR);
727 ::DeleteFile(tempfile);
728 return true;
731 bool CAppUtils::LaunchAlternativeEditor(const CString& filename, bool uac)
733 CString editTool = CRegString(L"Software\\TortoiseGit\\AlternativeEditor");
734 if (editTool.IsEmpty() || CStringUtils::StartsWith(editTool, L"#"))
735 editTool = CPathUtils::GetAppDirectory() + L"notepad2.exe";
737 CString sCmd;
738 sCmd.Format(L"\"%s\" \"%s\"", (LPCTSTR)editTool, (LPCTSTR)filename);
740 LaunchApplication(sCmd, 0, false, nullptr, uac);
741 return true;
744 bool CAppUtils::LaunchRemoteSetting()
746 CTGitPath path(g_Git.m_CurrentDir);
747 CSettings dlg(IDS_PROC_SETTINGS_TITLE, &path);
748 dlg.SetTreeViewMode(TRUE, TRUE, TRUE);
749 dlg.SetTreeWidth(220);
750 dlg.m_DefaultPage = L"gitremote";
752 dlg.DoModal();
753 dlg.HandleRestart();
754 return true;
758 * Launch the external blame viewer
760 bool CAppUtils::LaunchTortoiseBlame(const CString& sBlameFile, const CString& Rev, const CString& sParams)
762 CString viewer = L'"' + CPathUtils::GetAppDirectory();
763 viewer += L"TortoiseGitBlame.exe";
764 viewer += L"\" \"" + sBlameFile + L'"';
765 //viewer += L" \"" + sLogFile + L'"';
766 //viewer += L" \"" + sOriginalFile + L'"';
767 if(!Rev.IsEmpty() && Rev != GIT_REV_ZERO)
768 viewer += L" /rev:" + Rev;
769 if (!g_sGroupingUUID.IsEmpty())
771 viewer += L" /groupuuid:\"";
772 viewer += g_sGroupingUUID;
773 viewer += L'"';
775 viewer += L' ' + sParams;
777 return LaunchApplication(viewer, IDS_ERR_TGITBLAME, false);
780 bool CAppUtils::FormatTextInRichEditControl(CWnd * pWnd)
782 CString sText;
783 if (!pWnd)
784 return false;
785 bool bStyled = false;
786 pWnd->GetWindowText(sText);
787 // the rich edit control doesn't count the CR char!
788 // to be exact: CRLF is treated as one char.
789 sText.Remove(L'\r');
791 // style each line separately
792 int offset = 0;
793 int nNewlinePos;
796 nNewlinePos = sText.Find('\n', offset);
797 CString sLine = nNewlinePos >= 0 ? sText.Mid(offset, nNewlinePos - offset) : sText.Mid(offset);
799 int start = 0;
800 int end = 0;
801 while (FindStyleChars(sLine, '*', start, end))
803 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
804 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
805 SetCharFormat(pWnd, CFM_BOLD, CFE_BOLD);
806 bStyled = true;
807 start = end;
809 start = 0;
810 end = 0;
811 while (FindStyleChars(sLine, '^', start, end))
813 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
814 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
815 SetCharFormat(pWnd, CFM_ITALIC, CFE_ITALIC);
816 bStyled = true;
817 start = end;
819 start = 0;
820 end = 0;
821 while (FindStyleChars(sLine, '_', start, end))
823 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
824 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
825 SetCharFormat(pWnd, CFM_UNDERLINE, CFE_UNDERLINE);
826 bStyled = true;
827 start = end;
829 offset = nNewlinePos+1;
830 } while(nNewlinePos>=0);
831 return bStyled;
834 bool CAppUtils::FindStyleChars(const CString& sText, TCHAR stylechar, int& start, int& end)
836 int i=start;
837 int last = sText.GetLength() - 1;
838 bool bFoundMarker = false;
839 TCHAR c = i == 0 ? L'\0' : sText[i - 1];
840 TCHAR nextChar = i >= last ? L'\0' : sText[i + 1];
842 // find a starting marker
843 while (i < last)
845 TCHAR prevChar = c;
846 c = nextChar;
847 nextChar = sText[i + 1];
849 // IsCharAlphaNumeric can be somewhat expensive.
850 // Long lines of "*****" or "----" will be pre-empted efficiently
851 // by the (c != nextChar) condition.
853 if ((c == stylechar) && (c != nextChar))
855 if (IsCharAlphaNumeric(nextChar) && !IsCharAlphaNumeric(prevChar))
857 start = ++i;
858 bFoundMarker = true;
859 break;
862 ++i;
864 if (!bFoundMarker)
865 return false;
867 // find ending marker
868 // c == sText[i - 1]
870 bFoundMarker = false;
871 while (i <= last)
873 TCHAR prevChar = c;
874 c = sText[i];
875 if (c == stylechar)
877 if ((i == last) || (!IsCharAlphaNumeric(sText[i + 1]) && IsCharAlphaNumeric(prevChar)))
879 end = i;
880 ++i;
881 bFoundMarker = true;
882 break;
885 ++i;
887 return bFoundMarker;
890 // from CSciEdit
891 namespace {
892 bool IsValidURLChar(wchar_t ch)
894 return iswalnum(ch) ||
895 ch == L'_' || ch == L'/' || ch == L';' || ch == L'?' || ch == L'&' || ch == L'=' ||
896 ch == L'%' || ch == L':' || ch == L'.' || ch == L'#' || ch == L'-' || ch == L'+' ||
897 ch == L'|' || ch == L'>' || ch == L'<' || ch == L'!' || ch == L'@';
900 bool IsUrlOrEmail(const CString& sText)
902 if (!PathIsURLW(sText))
904 auto atpos = sText.Find(L'@');
905 if (atpos <= 0)
906 return false;
907 if (sText.Find(L'.', atpos) <= atpos + 1) // a dot must follow after the @, but not directly after it
908 return false;
909 if (sText.Find(L':', atpos) < 0) // do not detect git@example.com:something as an email address
910 return true;
911 return false;
913 for (const CString& prefix : { L"http://", L"https://", L"git://", L"ftp://", L"file://", L"mailto:" })
915 if (CStringUtils::StartsWith(sText, prefix) && sText.GetLength() != prefix.GetLength())
916 return true;
918 return false;
922 BOOL CAppUtils::StyleURLs(const CString& msg, CWnd* pWnd)
924 std::vector<CHARRANGE> positions = FindURLMatches(msg);
925 CAppUtils::SetCharFormat(pWnd, CFM_LINK, CFE_LINK, positions);
927 return positions.empty() ? FALSE : TRUE;
931 * implements URL searching with the same logic as CSciEdit::StyleURLs
933 std::vector<CHARRANGE> CAppUtils::FindURLMatches(const CString& msg)
935 std::vector<CHARRANGE> result;
937 int len = msg.GetLength();
938 int starturl = -1;
940 for (int i = 0; i <= msg.GetLength(); ++i)
942 if ((i < len) && IsValidURLChar(msg[i]))
944 if (starturl < 0)
945 starturl = i;
947 else
949 if (starturl >= 0)
951 bool strip = true;
952 if (msg[starturl] == '<' && i < len) // try to detect and do not strip URLs put within <>
954 while (starturl <= i && msg[starturl] == '<') // strip leading '<'
955 ++starturl;
956 strip = false;
957 i = starturl;
958 while (i < len && msg[i] != '\r' && msg[i] != '\n' && msg[i] != '>') // find first '>' or new line after resetting i to start position
959 ++i;
962 int skipTrailing = 0;
963 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] == '!'))
964 ++skipTrailing;
966 if (!IsUrlOrEmail(msg.Mid(starturl, i - starturl - skipTrailing)))
968 starturl = -1;
969 continue;
972 CHARRANGE range = { starturl, i - skipTrailing };
973 result.push_back(range);
975 starturl = -1;
979 return result;
982 bool CAppUtils::StartShowUnifiedDiff(HWND hWnd, const CTGitPath& url1, const git_revnum_t& rev1,
983 const CTGitPath& /*url2*/, const git_revnum_t& rev2,
984 //const GitRev& peg /* = GitRev */, const GitRev& headpeg /* = GitRev */,
985 bool bAlternateDiff /* = false */, bool /*bIgnoreAncestry*/ /* = false */,
986 bool /* blame = false */,
987 bool bMerge,
988 bool bCombine,
989 bool bNoPrefix)
991 int diffContext = g_Git.GetConfigValueInt32(L"diff.context", -1);
992 CString tempfile=GetTempFile();
993 if (g_Git.GetUnifiedDiff(url1, rev1, rev2, tempfile, bMerge, bCombine, diffContext, bNoPrefix))
995 MessageBox(hWnd, g_Git.GetGitLastErr(L"Could not get unified diff.", CGit::GIT_CMD_DIFF), L"TortoiseGit", MB_OK);
996 return false;
998 CAppUtils::StartUnifiedDiffViewer(tempfile, rev1.IsEmpty() ? rev2 : rev1 + L':' + rev2, FALSE, bAlternateDiff);
1000 #if 0
1001 CString sCmd;
1002 sCmd.Format(L"%s /command:showcompare /unified",
1003 (LPCTSTR)(CPathUtils::GetAppDirectory()+L"TortoiseGitProc.exe"));
1004 sCmd += L" /url1:\"" + url1.GetGitPathString() + L'"';
1005 if (rev1.IsValid())
1006 sCmd += L" /revision1:" + rev1.ToString();
1007 sCmd += L" /url2:\"" + url2.GetGitPathString() + L'"';
1008 if (rev2.IsValid())
1009 sCmd += L" /revision2:" + rev2.ToString();
1010 if (peg.IsValid())
1011 sCmd += L" /pegrevision:" + peg.ToString();
1012 if (headpeg.IsValid())
1013 sCmd += L" /headpegrevision:" + headpeg.ToString();
1015 if (bAlternateDiff)
1016 sCmd += L" /alternatediff";
1018 if (bIgnoreAncestry)
1019 sCmd += L" /ignoreancestry";
1021 if (hWnd)
1023 sCmd += L" /hwnd:";
1024 TCHAR buf[30];
1025 swprintf_s(buf, 30, L"%p", (void*)hWnd);
1026 sCmd += buf;
1029 return CAppUtils::LaunchApplication(sCmd, 0, false);
1030 #endif
1031 return TRUE;
1034 bool CAppUtils::SetupDiffScripts(bool force, const CString& type)
1036 CString scriptsdir = CPathUtils::GetAppParentDirectory();
1037 scriptsdir += L"Diff-Scripts";
1038 CSimpleFileFind files(scriptsdir);
1039 while (files.FindNextFileNoDirectories())
1041 CString file = files.GetFilePath();
1042 CString filename = files.GetFileName();
1043 CString ext = file.Mid(file.ReverseFind('-') + 1);
1044 ext = L"." + ext.Left(ext.ReverseFind(L'.'));
1045 std::set<CString> extensions;
1046 extensions.insert(ext);
1047 CString kind;
1048 if (CStringUtils::EndsWithI(file, L"vbs"))
1049 kind = L" //E:vbscript";
1050 if (CStringUtils::EndsWithI(file, L"js"))
1051 kind = L" //E:javascript";
1052 // open the file, read the first line and find possible extensions
1053 // this script can handle
1056 CStdioFile f(file, CFile::modeRead | CFile::shareDenyNone);
1057 CString extline;
1058 if (f.ReadString(extline))
1060 if ((extline.GetLength() > 15 ) &&
1061 (CStringUtils::StartsWith(extline, L"// extensions: ") ||
1062 CStringUtils::StartsWith(extline, L"' extensions: ")))
1064 if (extline[0] == '/')
1065 extline = extline.Mid(15);
1066 else
1067 extline = extline.Mid(14);
1068 CString sToken;
1069 int curPos = 0;
1070 sToken = extline.Tokenize(L";", curPos);
1071 while (!sToken.IsEmpty())
1073 if (!sToken.IsEmpty())
1075 if (sToken[0] != '.')
1076 sToken = L"." + sToken;
1077 extensions.insert(sToken);
1079 sToken = extline.Tokenize(L";", curPos);
1083 f.Close();
1085 catch (CFileException* e)
1087 e->Delete();
1090 for (const auto& extension : extensions)
1092 if (type.IsEmpty() || (type.Compare(L"Diff") == 0))
1094 if (CStringUtils::StartsWithI(filename, L"diff-"))
1096 CRegString diffreg = CRegString(L"Software\\TortoiseGit\\DiffTools\\" + extension);
1097 CString diffregstring = diffreg;
1098 if (force || (diffregstring.IsEmpty()) || (diffregstring.Find(filename) >= 0))
1099 diffreg = L"wscript.exe \"" + file + L"\" %base %mine" + kind;
1102 if (type.IsEmpty() || (type.Compare(L"Merge") == 0))
1104 if (CStringUtils::StartsWithI(filename, L"merge-"))
1106 CRegString diffreg = CRegString(L"Software\\TortoiseGit\\MergeTools\\" + extension);
1107 CString diffregstring = diffreg;
1108 if (force || (diffregstring.IsEmpty()) || (diffregstring.Find(filename) >= 0))
1109 diffreg = L"wscript.exe \"" + file + L"\" %merged %theirs %mine %base" + kind;
1115 return true;
1118 bool CAppUtils::Export(const CString* BashHash, const CTGitPath* orgPath)
1120 // ask from where the export has to be done
1121 CExportDlg dlg;
1122 if(BashHash)
1123 dlg.m_initialRefName=*BashHash;
1124 if (orgPath)
1126 if (PathIsRelative(orgPath->GetWinPath()))
1127 dlg.m_orgPath = g_Git.CombinePath(orgPath);
1128 else
1129 dlg.m_orgPath = *orgPath;
1132 if (dlg.DoModal() == IDOK)
1134 CString cmd;
1135 cmd.Format(L"git.exe archive --output=\"%s\" --format=zip --verbose %s --",
1136 (LPCTSTR)dlg.m_strFile, (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName));
1138 CProgressDlg pro;
1139 pro.m_GitCmd=cmd;
1140 pro.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1142 if (status)
1143 return;
1144 postCmdList.emplace_back(IDI_EXPLORER, IDS_STATUSLIST_CONTEXT_EXPLORE, [&]{ CAppUtils::ExploreTo(hWndExplorer, dlg.m_strFile); });
1147 CGit git;
1148 if (!dlg.m_bWholeProject && !dlg.m_orgPath.IsEmpty() && PathIsDirectory(dlg.m_orgPath.GetWinPathString()))
1150 git.m_CurrentDir = dlg.m_orgPath.GetWinPathString();
1151 pro.m_Git = &git;
1153 return (pro.DoModal() == IDOK);
1155 return false;
1158 bool CAppUtils::UpdateBranchDescription(const CString& branch, CString description)
1160 if (branch.IsEmpty())
1161 return false;
1163 CString key;
1164 key.Format(L"branch.%s.description", (LPCTSTR)branch);
1165 description.Remove(L'\r');
1166 description.Trim();
1167 if (description.IsEmpty())
1168 g_Git.UnsetConfigValue(key);
1169 else
1170 g_Git.SetConfigValue(key, description);
1172 return true;
1175 bool CAppUtils::CreateBranchTag(bool isTag /*true*/, const CString* commitHash /*nullptr*/, bool switchNewBranch /*false*/, LPCTSTR name /*nullptr*/)
1177 CCreateBranchTagDlg dlg;
1178 dlg.m_bIsTag = isTag;
1179 dlg.m_bSwitch = switchNewBranch;
1181 if (commitHash)
1182 dlg.m_initialRefName = *commitHash;
1184 if (name)
1185 dlg.m_BranchTagName = name;
1187 if(dlg.DoModal()==IDOK)
1189 CString cmd;
1190 CString force;
1191 CString track;
1192 if(dlg.m_bTrack == TRUE)
1193 track = L"--track";
1194 else if(dlg.m_bTrack == FALSE)
1195 track = L"--no-track";
1197 if(dlg.m_bForce)
1198 force = L"-f";
1200 if (isTag)
1202 CString sign;
1203 if(dlg.m_bSign)
1204 sign = L"-s";
1206 cmd.Format(L"git.exe tag %s %s %s %s",
1207 (LPCTSTR)force,
1208 (LPCTSTR)sign,
1209 (LPCTSTR)dlg.m_BranchTagName,
1210 (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName)
1213 if(!dlg.m_Message.Trim().IsEmpty())
1215 CString tempfile = ::GetTempFile();
1216 if (CAppUtils::SaveCommitUnicodeFile(tempfile, dlg.m_Message))
1218 MessageBox(nullptr, L"Could not save tag message", L"TortoiseGit", MB_OK | MB_ICONERROR);
1219 return FALSE;
1221 cmd += L" -F " + tempfile;
1224 else
1226 cmd.Format(L"git.exe branch %s %s %s %s",
1227 (LPCTSTR)track,
1228 (LPCTSTR)force,
1229 (LPCTSTR)dlg.m_BranchTagName,
1230 (LPCTSTR)g_Git.FixBranchName(dlg.m_VersionName)
1233 CString out;
1234 if(g_Git.Run(cmd,&out,CP_UTF8))
1236 MessageBox(nullptr, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
1237 return FALSE;
1239 if (!isTag && dlg.m_bSwitch)
1241 // it is a new branch and the user has requested to switch to it
1242 PerformSwitch(dlg.m_BranchTagName);
1244 if (!isTag && !dlg.m_Message.IsEmpty())
1245 UpdateBranchDescription(dlg.m_BranchTagName, dlg.m_Message);
1247 return TRUE;
1249 return FALSE;
1252 bool CAppUtils::Switch(const CString& initialRefName)
1254 CGitSwitchDlg dlg;
1255 if(!initialRefName.IsEmpty())
1256 dlg.m_initialRefName = initialRefName;
1258 if (dlg.DoModal() == IDOK)
1260 CString branch;
1261 if (dlg.m_bBranch)
1262 branch = dlg.m_NewBranch;
1264 // if refs/heads/ is not stripped, checkout will detach HEAD
1265 // checkout prefers branches on name clashes (with tags)
1266 if (CStringUtils::StartsWith(dlg.m_VersionName, L"refs/heads/") && dlg.m_bBranchOverride != TRUE)
1267 dlg.m_VersionName = dlg.m_VersionName.Mid(11);
1269 return PerformSwitch(dlg.m_VersionName, dlg.m_bForce == TRUE , branch, dlg.m_bBranchOverride == TRUE, dlg.m_bTrack, dlg.m_bMerge == TRUE);
1271 return FALSE;
1274 bool CAppUtils::PerformSwitch(const CString& ref, bool bForce /* false */, const CString& sNewBranch /* CString() */, bool bBranchOverride /* false */, BOOL bTrack /* 2 */, bool bMerge /* false */)
1276 CString cmd;
1277 CString track;
1278 CString force;
1279 CString branch;
1280 CString merge;
1282 if(!sNewBranch.IsEmpty()){
1283 if (bBranchOverride)
1284 branch.Format(L"-B %s ", (LPCTSTR)sNewBranch);
1285 else
1286 branch.Format(L"-b %s ", (LPCTSTR)sNewBranch);
1287 if (bTrack == TRUE)
1288 track = L"--track ";
1289 else if (bTrack == FALSE)
1290 track = L"--no-track ";
1292 if (bForce)
1293 force = L"-f ";
1294 if (bMerge)
1295 merge = L"--merge ";
1297 cmd.Format(L"git.exe checkout %s%s%s%s%s --",
1298 (LPCTSTR)force,
1299 (LPCTSTR)track,
1300 (LPCTSTR)merge,
1301 (LPCTSTR)branch,
1302 (LPCTSTR)g_Git.FixBranchName(ref));
1304 CProgressDlg progress;
1305 progress.m_GitCmd = cmd;
1307 CString currentBranch;
1308 bool hasBranch = CGit::GetCurrentBranchFromFile(g_Git.m_CurrentDir, currentBranch) == 0;
1309 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1311 if (!status)
1313 CTGitPath gitPath = g_Git.m_CurrentDir;
1314 if (gitPath.HasSubmodules())
1316 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1318 CString sCmd;
1319 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
1320 RunTortoiseGitProc(sCmd);
1323 if (hasBranch)
1324 postCmdList.emplace_back(IDI_MERGE, IDS_MENUMERGE, [&]{ Merge(&currentBranch); });
1327 CString newBranch;
1328 if (!CGit::GetCurrentBranchFromFile(g_Git.m_CurrentDir, newBranch))
1329 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&]{ Pull(); });
1331 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUCOMMIT, []{
1332 CTGitPathList pathlist;
1333 CTGitPathList selectedlist;
1334 pathlist.AddPath(CTGitPath());
1335 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(L"Software\\TortoiseGit\\SelectFilesForCommit", TRUE));
1336 CString str;
1337 Commit(CString(), false, str, pathlist, selectedlist, bSelectFilesForCommit);
1340 else
1342 if (bMerge && g_Git.HasWorkingTreeConflicts() > 0)
1344 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
1346 CString sCmd;
1347 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
1348 CAppUtils::RunTortoiseGitProc(sCmd);
1351 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ PerformSwitch(ref, bForce, sNewBranch, bBranchOverride, bTrack, bMerge); });
1352 if (!bMerge)
1353 postCmdList.emplace_back(IDI_SWITCH, IDS_SWITCH_WITH_MERGE, [&]{ PerformSwitch(ref, bForce, sNewBranch, bBranchOverride, bTrack, true); });
1356 progress.m_PostExecCallback = [&](DWORD& exitCode, CString& extraMsg)
1358 if (bMerge && !exitCode && g_Git.HasWorkingTreeConflicts() > 0)
1360 exitCode = 1; // Treat it as failure
1361 extraMsg = L"Has merge conflict";
1365 INT_PTR ret = progress.DoModal();
1367 return ret == IDOK;
1370 class CIgnoreFile : public CStdioFile
1372 public:
1373 STRING_VECTOR m_Items;
1374 CString m_eol;
1376 virtual BOOL ReadString(CString& rString)
1378 if (GetPosition() == 0)
1380 unsigned char utf8bom[] = { 0xEF, 0xBB, 0xBF };
1381 char buf[3] = { 0, 0, 0 };
1382 Read(buf, 3);
1383 if (memcpy(buf, utf8bom, sizeof(utf8bom)))
1384 SeekToBegin();
1387 CStringA strA;
1388 char lastChar = '\0';
1389 for (char c = '\0'; Read(&c, 1) == 1; lastChar = c)
1391 if (c == '\r')
1392 continue;
1393 if (c == '\n')
1395 m_eol = lastChar == L'\r' ? L"\r\n" : L"\n";
1396 break;
1398 strA.AppendChar(c);
1400 if (strA.IsEmpty())
1401 return FALSE;
1403 rString = CUnicodeUtils::GetUnicode(strA);
1404 return TRUE;
1407 void ResetState()
1409 m_Items.clear();
1410 m_eol.Empty();
1414 bool CAppUtils::OpenIgnoreFile(CIgnoreFile &file, const CString& filename)
1416 file.ResetState();
1417 if (!file.Open(filename, CFile::modeCreate | CFile::modeReadWrite | CFile::modeNoTruncate | CFile::typeBinary))
1419 MessageBox(nullptr, filename + L" Open Failure", L"TortoiseGit", MB_OK | MB_ICONERROR);
1420 return false;
1423 if (file.GetLength() > 0)
1425 CString fileText;
1426 while (file.ReadString(fileText))
1427 file.m_Items.push_back(fileText);
1428 file.Seek(file.GetLength() - 1, 0);
1429 char lastchar[1] = { 0 };
1430 file.Read(lastchar, 1);
1431 file.SeekToEnd();
1432 if (lastchar[0] != '\n')
1434 CStringA eol = CStringA(file.m_eol.IsEmpty() ? L"\n" : file.m_eol);
1435 file.Write(eol, eol.GetLength());
1438 else
1439 file.SeekToEnd();
1441 return true;
1444 bool CAppUtils::IgnoreFile(const CTGitPathList& path,bool IsMask)
1446 CIgnoreDlg ignoreDlg;
1447 if (ignoreDlg.DoModal() == IDOK)
1449 CString ignorefile;
1450 ignorefile = g_Git.m_CurrentDir + L'\\';
1452 switch (ignoreDlg.m_IgnoreFile)
1454 case 0:
1455 ignorefile += L".gitignore";
1456 break;
1457 case 2:
1458 GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, ignorefile);
1459 ignorefile += L"info";
1460 if (!PathFileExists(ignorefile))
1461 CreateDirectory(ignorefile, nullptr);
1462 ignorefile += L"\\exclude";
1463 break;
1466 CIgnoreFile file;
1469 if (ignoreDlg.m_IgnoreFile != 1 && !OpenIgnoreFile(file, ignorefile))
1470 return false;
1472 for (int i = 0; i < path.GetCount(); ++i)
1474 if (ignoreDlg.m_IgnoreFile == 1)
1476 ignorefile = g_Git.CombinePath(path[i].GetContainingDirectory()) + L"\\.gitignore";
1477 if (!OpenIgnoreFile(file, ignorefile))
1478 return false;
1481 CString ignorePattern;
1482 if (ignoreDlg.m_IgnoreType == 0)
1484 if (ignoreDlg.m_IgnoreFile != 1 && !path[i].GetContainingDirectory().GetGitPathString().IsEmpty())
1485 ignorePattern += L'/' + path[i].GetContainingDirectory().GetGitPathString();
1487 ignorePattern += L'/';
1489 if (IsMask)
1490 ignorePattern += L'*' + path[i].GetFileExtension();
1491 else
1492 ignorePattern += path[i].GetFileOrDirectoryName();
1494 // escape [ and ] so that files get ignored correctly
1495 ignorePattern.Replace(L"[", L"\\[");
1496 ignorePattern.Replace(L"]", L"\\]");
1498 bool found = false;
1499 for (size_t j = 0; j < file.m_Items.size(); ++j)
1501 if (file.m_Items[j] == ignorePattern)
1503 found = true;
1504 break;
1507 if (!found)
1509 file.m_Items.push_back(ignorePattern);
1510 ignorePattern += file.m_eol.IsEmpty() ? L"\n" : file.m_eol;
1511 CStringA ignorePatternA = CUnicodeUtils::GetUTF8(ignorePattern);
1512 file.Write(ignorePatternA, ignorePatternA.GetLength());
1515 if (ignoreDlg.m_IgnoreFile == 1)
1516 file.Close();
1519 if (ignoreDlg.m_IgnoreFile != 1)
1520 file.Close();
1522 catch(...)
1524 file.Abort();
1525 return false;
1528 return true;
1530 return false;
1533 static bool Reset(const CString& resetTo, int resetType)
1535 CString cmd;
1536 CString type;
1537 switch (resetType)
1539 case 0:
1540 type = L"--soft";
1541 break;
1542 case 1:
1543 type = L"--mixed";
1544 break;
1545 case 2:
1546 type = L"--hard";
1547 break;
1548 default:
1549 resetType = 1;
1550 type = L"--mixed";
1551 break;
1553 cmd.Format(L"git.exe reset %s %s --", (LPCTSTR)type, (LPCTSTR)resetTo);
1555 CProgressDlg progress;
1556 progress.m_GitCmd = cmd;
1558 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1560 if (status)
1562 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ Reset(resetTo, resetType); });
1563 return;
1566 CTGitPath gitPath = g_Git.m_CurrentDir;
1567 if (gitPath.HasSubmodules() && resetType == 2)
1569 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1571 CString sCmd;
1572 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
1573 CAppUtils::RunTortoiseGitProc(sCmd);
1578 INT_PTR ret;
1579 if (g_Git.UsingLibGit2(CGit::GIT_CMD_RESET))
1581 CGitProgressDlg gitdlg;
1582 ResetProgressCommand resetProgressCommand;
1583 gitdlg.SetCommand(&resetProgressCommand);
1584 resetProgressCommand.m_PostCmdCallback = progress.m_PostCmdCallback;
1585 resetProgressCommand.SetRevision(resetTo);
1586 resetProgressCommand.SetResetType(resetType);
1587 ret = gitdlg.DoModal();
1589 else
1590 ret = progress.DoModal();
1592 return ret == IDOK;
1595 bool CAppUtils::GitReset(const CString* CommitHash, int type)
1597 CResetDlg dlg;
1598 dlg.m_ResetType=type;
1599 dlg.m_ResetToVersion=*CommitHash;
1600 dlg.m_initialRefName = *CommitHash;
1601 if (dlg.DoModal() == IDOK)
1602 return Reset(dlg.m_ResetToVersion, dlg.m_ResetType);
1604 return false;
1607 void CAppUtils::DescribeConflictFile(bool mode, bool base,CString &descript)
1609 if(mode == FALSE)
1611 descript.LoadString(IDS_SVNACTION_DELETE);
1612 return;
1614 if(base)
1616 descript.LoadString(IDS_SVNACTION_MODIFIED);
1617 return;
1619 descript.LoadString(IDS_PROC_CREATED);
1622 void CAppUtils::RemoveTempMergeFile(const CTGitPath& path)
1624 ::DeleteFile(CAppUtils::GetMergeTempFile(L"LOCAL", path));
1625 ::DeleteFile(CAppUtils::GetMergeTempFile(L"REMOTE", path));
1626 ::DeleteFile(CAppUtils::GetMergeTempFile(L"BASE", path));
1628 CString CAppUtils::GetMergeTempFile(const CString& type, const CTGitPath &merge)
1630 CString file;
1631 file = g_Git.CombinePath(merge.GetWinPathString() + L'.' + type + merge.GetFileExtension());
1633 return file;
1636 bool ParseHashesFromLsFile(const BYTE_VECTOR& out, CString& hash1, CString& hash2, CString& hash3)
1638 size_t pos = 0;
1639 CString one;
1640 CString part;
1642 while (pos < out.size())
1644 one.Empty();
1646 CGit::StringAppend(&one, &out[pos], CP_UTF8);
1647 int tabstart = 0;
1648 one.Tokenize(L"\t", tabstart);
1650 tabstart = 0;
1651 part = one.Tokenize(L" ", tabstart); //Tag
1652 part = one.Tokenize(L" ", tabstart); //Mode
1653 part = one.Tokenize(L" ", tabstart); //Hash
1654 CString hash = part;
1655 part = one.Tokenize(L"\t", tabstart); //Stage
1656 int stage = _wtol(part);
1657 if (stage == 1)
1658 hash1 = hash;
1659 else if (stage == 2)
1660 hash2 = hash;
1661 else if (stage == 3)
1663 hash3 = hash;
1664 return true;
1667 pos = out.findNextString(pos);
1670 return false;
1673 bool CAppUtils::ConflictEdit(CTGitPath& path, bool bAlternativeTool /*= false*/, bool revertTheirMy /*= false*/, HWND resolveMsgHwnd /*= nullptr*/)
1675 bool bRet = false;
1677 CTGitPath merge=path;
1678 CTGitPath directory = merge.GetDirectory();
1680 // we have the conflicted file (%merged)
1681 // now look for the other required files
1682 //GitStatus stat;
1683 //stat.GetStatus(merge);
1684 //if (stat.status == nullptr)
1685 // return false;
1687 BYTE_VECTOR vector;
1689 CString cmd;
1690 cmd.Format(L"git.exe ls-files -u -t -z -- \"%s\"", (LPCTSTR)merge.GetGitPathString());
1692 if (g_Git.Run(cmd, &vector))
1693 return FALSE;
1695 if (merge.IsDirectory())
1697 CString baseHash, realBaseHash(GIT_REV_ZERO), localHash(GIT_REV_ZERO), remoteHash(GIT_REV_ZERO);
1698 if (merge.HasAdminDir()) {
1699 CGit subgit;
1700 subgit.m_CurrentDir = g_Git.CombinePath(merge);
1701 CGitHash hash;
1702 subgit.GetHash(hash, L"HEAD");
1703 baseHash = hash;
1705 if (ParseHashesFromLsFile(vector, realBaseHash, localHash, remoteHash)) // in base no submodule, but in remote submodule
1706 baseHash = realBaseHash;
1708 CGitDiff::ChangeType changeTypeMine = CGitDiff::Unknown;
1709 CGitDiff::ChangeType changeTypeTheirs = CGitDiff::Unknown;
1711 bool baseOK = false, mineOK = false, theirsOK = false;
1712 CString baseSubject, mineSubject, theirsSubject;
1713 if (merge.HasAdminDir())
1715 CGit subgit;
1716 subgit.m_CurrentDir = g_Git.CombinePath(merge);
1717 CGitDiff::GetSubmoduleChangeType(subgit, baseHash, localHash, baseOK, mineOK, changeTypeMine, baseSubject, mineSubject);
1718 CGitDiff::GetSubmoduleChangeType(subgit, baseHash, remoteHash, baseOK, theirsOK, changeTypeTheirs, baseSubject, theirsSubject);
1720 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)
1722 changeTypeMine = CGitDiff::Identical;
1723 changeTypeTheirs = CGitDiff::NewSubmodule;
1724 baseSubject = L"no submodule";
1725 mineSubject = baseSubject;
1726 theirsSubject = L"not initialized";
1728 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
1730 baseHash = localHash;
1731 baseSubject = L"not initialized";
1732 mineSubject = baseSubject;
1733 theirsSubject = L"not initialized";
1734 changeTypeMine = CGitDiff::Identical;
1735 changeTypeTheirs = CGitDiff::DeleteSubmodule;
1737 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
1739 baseSubject = L"not initialized";
1740 mineSubject = baseSubject;
1741 theirsSubject = baseSubject;
1742 if (baseHash == localHash)
1743 changeTypeMine = CGitDiff::Identical;
1745 else
1746 return FALSE;
1748 CSubmoduleResolveConflictDlg resolveSubmoduleConflictDialog;
1749 resolveSubmoduleConflictDialog.SetDiff(merge.GetGitPathString(), revertTheirMy, baseHash, baseSubject, baseOK, localHash, mineSubject, mineOK, changeTypeMine, remoteHash, theirsSubject, theirsOK, changeTypeTheirs);
1750 resolveSubmoduleConflictDialog.DoModal();
1751 if (resolveSubmoduleConflictDialog.m_bResolved && resolveMsgHwnd)
1753 static UINT WM_REVERTMSG = RegisterWindowMessage(L"GITSLNM_NEEDSREFRESH");
1754 ::PostMessage(resolveMsgHwnd, WM_REVERTMSG, NULL, NULL);
1757 return TRUE;
1760 CTGitPathList list;
1761 if (list.ParserFromLsFile(vector))
1763 MessageBox(nullptr, L"Parse ls-files failed!", L"TortoiseGit", MB_OK | MB_ICONERROR);
1764 return FALSE;
1767 if (list.IsEmpty())
1768 return FALSE;
1770 CTGitPath theirs;
1771 CTGitPath mine;
1772 CTGitPath base;
1774 if (revertTheirMy)
1776 mine.SetFromGit(GetMergeTempFile(L"REMOTE", merge));
1777 theirs.SetFromGit(GetMergeTempFile(L"LOCAL", merge));
1779 else
1781 mine.SetFromGit(GetMergeTempFile(L"LOCAL", merge));
1782 theirs.SetFromGit(GetMergeTempFile(L"REMOTE", merge));
1784 base.SetFromGit(GetMergeTempFile(L"BASE",merge));
1786 CString format;
1788 //format=L"git.exe cat-file blob \":%d:%s\"";
1789 format = L"git.exe checkout-index --temp --stage=%d -- \"%s\"";
1790 CFile tempfile;
1791 //create a empty file, incase stage is not three
1792 tempfile.Open(mine.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1793 tempfile.Close();
1794 tempfile.Open(theirs.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1795 tempfile.Close();
1796 tempfile.Open(base.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1797 tempfile.Close();
1799 bool b_base=false, b_local=false, b_remote=false;
1801 for (int i = 0; i< list.GetCount(); ++i)
1803 CString outfile;
1804 cmd.Empty();
1805 outfile.Empty();
1807 if( list[i].m_Stage == 1)
1809 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1810 b_base = true;
1811 outfile = base.GetWinPathString();
1814 if( list[i].m_Stage == 2 )
1816 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1817 b_local = true;
1818 outfile = mine.GetWinPathString();
1821 if( list[i].m_Stage == 3 )
1823 cmd.Format(format, list[i].m_Stage, (LPCTSTR)list[i].GetGitPathString());
1824 b_remote = true;
1825 outfile = theirs.GetWinPathString();
1827 CString output, err;
1828 if(!outfile.IsEmpty())
1829 if (!g_Git.Run(cmd, &output, &err, CP_UTF8))
1831 CString file;
1832 int start =0 ;
1833 file = output.Tokenize(L"\t", start);
1834 ::MoveFileEx(file,outfile,MOVEFILE_REPLACE_EXISTING|MOVEFILE_COPY_ALLOWED);
1836 else
1837 CMessageBox::Show(nullptr, output + L'\n' + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
1840 if(b_local && b_remote )
1842 merge.SetFromWin(g_Git.CombinePath(merge));
1843 if( revertTheirMy )
1844 bRet = !!CAppUtils::StartExtMerge(bAlternativeTool, base, mine, theirs, merge, L"BASE", L"REMOTE", L"LOCAL", CString(), false, resolveMsgHwnd, true);
1845 else
1846 bRet = !!CAppUtils::StartExtMerge(bAlternativeTool, base, theirs, mine, merge, L"BASE", L"REMOTE", L"LOCAL", CString(), false, resolveMsgHwnd, true);
1848 else
1850 ::DeleteFile(mine.GetWinPathString());
1851 ::DeleteFile(theirs.GetWinPathString());
1852 ::DeleteFile(base.GetWinPathString());
1854 CDeleteConflictDlg dlg;
1855 if (!revertTheirMy)
1857 DescribeConflictFile(b_local, b_base, dlg.m_LocalStatus);
1858 DescribeConflictFile(b_remote, b_base, dlg.m_RemoteStatus);
1859 CGitHash localHash, remoteHash;
1860 if (!g_Git.GetHash(localHash, L"HEAD"))
1861 dlg.m_LocalHash = localHash.ToString();
1862 if (!g_Git.GetHash(remoteHash, L"MERGE_HEAD"))
1863 dlg.m_RemoteHash = remoteHash.ToString();
1864 else if (!g_Git.GetHash(remoteHash, L"rebase-apply/original-commit"))
1865 dlg.m_RemoteHash = remoteHash.ToString();
1866 else if (!g_Git.GetHash(remoteHash, L"CHERRY_PICK_HEAD"))
1867 dlg.m_RemoteHash = remoteHash.ToString();
1868 else if (!g_Git.GetHash(remoteHash, L"REVERT_HEAD"))
1869 dlg.m_RemoteHash = remoteHash.ToString();
1871 else
1873 DescribeConflictFile(b_local, b_base, dlg.m_RemoteStatus);
1874 DescribeConflictFile(b_remote, b_base, dlg.m_LocalStatus);
1875 CGitHash localHash, remoteHash;
1876 if (!g_Git.GetHash(remoteHash, L"HEAD"))
1877 dlg.m_RemoteHash = remoteHash.ToString();
1878 if (!g_Git.GetHash(localHash, L"MERGE_HEAD"))
1879 dlg.m_LocalHash = localHash.ToString();
1880 else if (!g_Git.GetHash(localHash, L"rebase-apply/original-commit"))
1881 dlg.m_LocalHash = localHash.ToString();
1882 else if (!g_Git.GetHash(localHash, L"CHERRY_PICK_HEAD"))
1883 dlg.m_LocalHash = localHash.ToString();
1884 else if (!g_Git.GetHash(localHash, L"REVERT_HEAD"))
1885 dlg.m_LocalHash = localHash.ToString();
1887 dlg.m_bShowModifiedButton = b_base;
1888 dlg.m_File=merge.GetGitPathString();
1889 if(dlg.DoModal() == IDOK)
1891 CString out;
1892 if(dlg.m_bIsDelete)
1893 cmd.Format(L"git.exe rm -- \"%s\"", (LPCTSTR)merge.GetGitPathString());
1894 else
1895 cmd.Format(L"git.exe add -- \"%s\"", (LPCTSTR)merge.GetGitPathString());
1897 if (g_Git.Run(cmd, &out, CP_UTF8))
1899 MessageBox(nullptr, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
1900 return FALSE;
1902 if (!dlg.m_bIsDelete)
1904 path.m_Action |= CTGitPath::LOGACTIONS_ADDED;
1905 path.m_Action &= ~CTGitPath::LOGACTIONS_UNMERGED;
1907 return TRUE;
1909 else
1910 return FALSE;
1913 #if 0
1914 CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool),
1915 base, theirs, mine, merge);
1916 #endif
1917 #if 0
1918 if (stat.status->text_status == svn_wc_status_conflicted)
1920 // we have a text conflict, use our merge tool to resolve the conflict
1922 CTSVNPath theirs(directory);
1923 CTSVNPath mine(directory);
1924 CTSVNPath base(directory);
1925 bool bConflictData = false;
1927 if ((stat.status->entry)&&(stat.status->entry->conflict_new))
1929 theirs.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->conflict_new));
1930 bConflictData = true;
1932 if ((stat.status->entry)&&(stat.status->entry->conflict_old))
1934 base.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->conflict_old));
1935 bConflictData = true;
1937 if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
1939 mine.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->conflict_wrk));
1940 bConflictData = true;
1942 else
1943 mine = merge;
1944 if (bConflictData)
1945 bRet = !!CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool),
1946 base, theirs, mine, merge);
1949 if (stat.status->prop_status == svn_wc_status_conflicted)
1951 // we have a property conflict
1952 CTSVNPath prej(directory);
1953 if ((stat.status->entry)&&(stat.status->entry->prejfile))
1955 prej.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->prejfile));
1956 // there's a problem: the prej file contains a _description_ of the conflict, and
1957 // that description string might be translated. That means we have no way of parsing
1958 // the file to find out the conflicting values.
1959 // The only thing we can do: show a dialog with the conflict description, then
1960 // let the user either accept the existing property or open the property edit dialog
1961 // to manually change the properties and values. And a button to mark the conflict as
1962 // resolved.
1963 CEditPropConflictDlg dlg;
1964 dlg.SetPrejFile(prej);
1965 dlg.SetConflictedItem(merge);
1966 bRet = (dlg.DoModal() != IDCANCEL);
1970 if (stat.status->tree_conflict)
1972 // we have a tree conflict
1973 SVNInfo info;
1974 const SVNInfoData * pInfoData = info.GetFirstFileInfo(merge, SVNRev(), SVNRev());
1975 if (pInfoData)
1977 if (pInfoData->treeconflict_kind == svn_wc_conflict_kind_text)
1979 CTSVNPath theirs(directory);
1980 CTSVNPath mine(directory);
1981 CTSVNPath base(directory);
1982 bool bConflictData = false;
1984 if (pInfoData->treeconflict_theirfile)
1986 theirs.AppendPathString(pInfoData->treeconflict_theirfile);
1987 bConflictData = true;
1989 if (pInfoData->treeconflict_basefile)
1991 base.AppendPathString(pInfoData->treeconflict_basefile);
1992 bConflictData = true;
1994 if (pInfoData->treeconflict_myfile)
1996 mine.AppendPathString(pInfoData->treeconflict_myfile);
1997 bConflictData = true;
1999 else
2000 mine = merge;
2001 if (bConflictData)
2002 bRet = !!CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool),
2003 base, theirs, mine, merge);
2005 else if (pInfoData->treeconflict_kind == svn_wc_conflict_kind_tree)
2007 CString sConflictAction;
2008 CString sConflictReason;
2009 CString sResolveTheirs;
2010 CString sResolveMine;
2011 CTSVNPath treeConflictPath = CTSVNPath(pInfoData->treeconflict_path);
2012 CString sItemName = treeConflictPath.GetUIFileOrDirectoryName();
2014 if (pInfoData->treeconflict_nodekind == svn_node_file)
2016 switch (pInfoData->treeconflict_operation)
2018 case svn_wc_operation_update:
2019 switch (pInfoData->treeconflict_action)
2021 case svn_wc_conflict_action_edit:
2022 sConflictAction.Format(IDS_TREECONFLICT_FILEUPDATEEDIT, (LPCTSTR)sItemName);
2023 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2024 break;
2025 case svn_wc_conflict_action_add:
2026 sConflictAction.Format(IDS_TREECONFLICT_FILEUPDATEADD, (LPCTSTR)sItemName);
2027 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2028 break;
2029 case svn_wc_conflict_action_delete:
2030 sConflictAction.Format(IDS_TREECONFLICT_FILEUPDATEDELETE, (LPCTSTR)sItemName);
2031 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2032 break;
2034 break;
2035 case svn_wc_operation_switch:
2036 switch (pInfoData->treeconflict_action)
2038 case svn_wc_conflict_action_edit:
2039 sConflictAction.Format(IDS_TREECONFLICT_FILESWITCHEDIT, (LPCTSTR)sItemName);
2040 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2041 break;
2042 case svn_wc_conflict_action_add:
2043 sConflictAction.Format(IDS_TREECONFLICT_FILESWITCHADD, (LPCTSTR)sItemName);
2044 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2045 break;
2046 case svn_wc_conflict_action_delete:
2047 sConflictAction.Format(IDS_TREECONFLICT_FILESWITCHDELETE, (LPCTSTR)sItemName);
2048 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2049 break;
2051 break;
2052 case svn_wc_operation_merge:
2053 switch (pInfoData->treeconflict_action)
2055 case svn_wc_conflict_action_edit:
2056 sConflictAction.Format(IDS_TREECONFLICT_FILEMERGEEDIT, (LPCTSTR)sItemName);
2057 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2058 break;
2059 case svn_wc_conflict_action_add:
2060 sResolveTheirs.Format(IDS_TREECONFLICT_FILEMERGEADD, (LPCTSTR)sItemName);
2061 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2062 break;
2063 case svn_wc_conflict_action_delete:
2064 sConflictAction.Format(IDS_TREECONFLICT_FILEMERGEDELETE, (LPCTSTR)sItemName);
2065 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2066 break;
2068 break;
2071 else if (pInfoData->treeconflict_nodekind == svn_node_dir)
2073 switch (pInfoData->treeconflict_operation)
2075 case svn_wc_operation_update:
2076 switch (pInfoData->treeconflict_action)
2078 case svn_wc_conflict_action_edit:
2079 sConflictAction.Format(IDS_TREECONFLICT_DIRUPDATEEDIT, (LPCTSTR)sItemName);
2080 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2081 break;
2082 case svn_wc_conflict_action_add:
2083 sConflictAction.Format(IDS_TREECONFLICT_DIRUPDATEADD, (LPCTSTR)sItemName);
2084 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2085 break;
2086 case svn_wc_conflict_action_delete:
2087 sConflictAction.Format(IDS_TREECONFLICT_DIRUPDATEDELETE, (LPCTSTR)sItemName);
2088 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR);
2089 break;
2091 break;
2092 case svn_wc_operation_switch:
2093 switch (pInfoData->treeconflict_action)
2095 case svn_wc_conflict_action_edit:
2096 sConflictAction.Format(IDS_TREECONFLICT_DIRSWITCHEDIT, (LPCTSTR)sItemName);
2097 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2098 break;
2099 case svn_wc_conflict_action_add:
2100 sConflictAction.Format(IDS_TREECONFLICT_DIRSWITCHADD, (LPCTSTR)sItemName);
2101 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2102 break;
2103 case svn_wc_conflict_action_delete:
2104 sConflictAction.Format(IDS_TREECONFLICT_DIRSWITCHDELETE, (LPCTSTR)sItemName);
2105 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR);
2106 break;
2108 break;
2109 case svn_wc_operation_merge:
2110 switch (pInfoData->treeconflict_action)
2112 case svn_wc_conflict_action_edit:
2113 sConflictAction.Format(IDS_TREECONFLICT_DIRMERGEEDIT, (LPCTSTR)sItemName);
2114 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2115 break;
2116 case svn_wc_conflict_action_add:
2117 sConflictAction.Format(IDS_TREECONFLICT_DIRMERGEADD, (LPCTSTR)sItemName);
2118 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2119 break;
2120 case svn_wc_conflict_action_delete:
2121 sConflictAction.Format(IDS_TREECONFLICT_DIRMERGEDELETE, (LPCTSTR)sItemName);
2122 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR);
2123 break;
2125 break;
2129 UINT uReasonID = 0;
2130 switch (pInfoData->treeconflict_reason)
2132 case svn_wc_conflict_reason_edited:
2133 uReasonID = IDS_TREECONFLICT_REASON_EDITED;
2134 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2135 break;
2136 case svn_wc_conflict_reason_obstructed:
2137 uReasonID = IDS_TREECONFLICT_REASON_OBSTRUCTED;
2138 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2139 break;
2140 case svn_wc_conflict_reason_deleted:
2141 uReasonID = IDS_TREECONFLICT_REASON_DELETED;
2142 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_REMOVEDIR : IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2143 break;
2144 case svn_wc_conflict_reason_added:
2145 uReasonID = IDS_TREECONFLICT_REASON_ADDED;
2146 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2147 break;
2148 case svn_wc_conflict_reason_missing:
2149 uReasonID = IDS_TREECONFLICT_REASON_MISSING;
2150 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_REMOVEDIR : IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2151 break;
2152 case svn_wc_conflict_reason_unversioned:
2153 uReasonID = IDS_TREECONFLICT_REASON_UNVERSIONED;
2154 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2155 break;
2157 sConflictReason.Format(uReasonID, (LPCTSTR)sConflictAction);
2159 CTreeConflictEditorDlg dlg;
2160 dlg.SetConflictInfoText(sConflictReason);
2161 dlg.SetResolveTexts(sResolveTheirs, sResolveMine);
2162 dlg.SetPath(treeConflictPath);
2163 INT_PTR dlgRet = dlg.DoModal();
2164 bRet = (dlgRet != IDCANCEL);
2168 #endif
2169 return bRet;
2172 bool CAppUtils::IsSSHPutty()
2174 CString sshclient = g_Git.m_Environment.GetEnv(L"GIT_SSH");
2175 sshclient=sshclient.MakeLower();
2176 return sshclient.Find(L"plink", 0) >= 0;
2179 CString CAppUtils::GetClipboardLink(const CString &skipGitPrefix, int paramsCount)
2181 if (!OpenClipboard(nullptr))
2182 return CString();
2184 CString sClipboardText;
2185 HGLOBAL hglb = GetClipboardData(CF_TEXT);
2186 if (hglb)
2188 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
2189 sClipboardText = CString(lpstr);
2190 GlobalUnlock(hglb);
2192 hglb = GetClipboardData(CF_UNICODETEXT);
2193 if (hglb)
2195 LPCTSTR lpstr = (LPCTSTR)GlobalLock(hglb);
2196 sClipboardText = lpstr;
2197 GlobalUnlock(hglb);
2199 CloseClipboard();
2201 if(!sClipboardText.IsEmpty())
2203 if (sClipboardText[0] == L'"' && sClipboardText[sClipboardText.GetLength() - 1] == L'"')
2204 sClipboardText=sClipboardText.Mid(1,sClipboardText.GetLength()-2);
2206 for (const CString& prefix : { L"http://", L"https://", L"git://", L"ssh://", L"git@" })
2208 if (CStringUtils::StartsWith(sClipboardText, prefix) && sClipboardText.GetLength() != prefix.GetLength())
2209 return sClipboardText;
2212 if(sClipboardText.GetLength()>=2)
2213 if (sClipboardText[1] == L':')
2214 if( (sClipboardText[0] >= 'A' && sClipboardText[0] <= 'Z')
2215 || (sClipboardText[0] >= 'a' && sClipboardText[0] <= 'z') )
2216 return sClipboardText;
2218 // trim prefixes like "git clone "
2219 if (!skipGitPrefix.IsEmpty() && CStringUtils::StartsWith(sClipboardText, skipGitPrefix))
2221 sClipboardText = sClipboardText.Mid(skipGitPrefix.GetLength()).Trim();
2222 int spacePos = -1;
2223 while (paramsCount >= 0)
2225 --paramsCount;
2226 spacePos = sClipboardText.Find(L' ', spacePos + 1);
2227 if (spacePos == -1)
2228 break;
2230 if (spacePos > 0 && paramsCount < 0)
2231 sClipboardText.Truncate(spacePos);
2232 return sClipboardText;
2236 return CString();
2239 CString CAppUtils::ChooseRepository(const CString* path)
2241 CBrowseFolder browseFolder;
2242 CRegString regLastResopitory = CRegString(L"Software\\TortoiseGit\\TortoiseProc\\LastRepo", L"");
2244 browseFolder.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
2245 CString strCloneDirectory;
2246 if(path)
2247 strCloneDirectory=*path;
2248 else
2249 strCloneDirectory = regLastResopitory;
2251 CString title;
2252 title.LoadString(IDS_CHOOSE_REPOSITORY);
2254 browseFolder.SetInfo(title);
2256 if (browseFolder.Show(nullptr, strCloneDirectory) == CBrowseFolder::OK)
2258 regLastResopitory = strCloneDirectory;
2259 return strCloneDirectory;
2261 else
2262 return CString();
2265 bool CAppUtils::SendPatchMail(CTGitPathList& list, bool bIsMainWnd)
2267 CSendMailDlg dlg;
2269 dlg.m_PathList = list;
2271 if(dlg.DoModal()==IDOK)
2273 if (dlg.m_PathList.IsEmpty())
2274 return FALSE;
2276 CGitProgressDlg progDlg;
2277 if (bIsMainWnd)
2278 theApp.m_pMainWnd = &progDlg;
2279 SendMailProgressCommand sendMailProgressCommand;
2280 progDlg.SetCommand(&sendMailProgressCommand);
2282 sendMailProgressCommand.SetPathList(dlg.m_PathList);
2283 progDlg.SetItemCount(dlg.m_PathList.GetCount());
2285 CSendMailPatch sendMailPatch(dlg.m_To, dlg.m_CC, dlg.m_Subject, !!dlg.m_bAttachment, !!dlg.m_bCombine);
2286 sendMailProgressCommand.SetSendMailOption(&sendMailPatch);
2288 progDlg.DoModal();
2290 return true;
2292 return false;
2295 bool CAppUtils::SendPatchMail(const CString& cmd, const CString& formatpatchoutput, bool bIsMainWnd)
2297 CTGitPathList list;
2298 CString log=formatpatchoutput;
2299 int start=log.Find(cmd);
2300 if(start >=0)
2301 log.Tokenize(L"\n", start);
2302 else
2303 start = 0;
2305 while(start>=0)
2307 CString one = log.Tokenize(L"\n", start);
2308 one=one.Trim();
2309 if (one.IsEmpty() || CStringUtils::StartsWith(one, CString(MAKEINTRESOURCE(IDS_SUCCESS))))
2310 continue;
2311 one.Replace(L'/', L'\\');
2312 CTGitPath path;
2313 path.SetFromWin(one);
2314 list.AddPath(path);
2316 if (!list.IsEmpty())
2317 return SendPatchMail(list, bIsMainWnd);
2318 else
2320 CMessageBox::Show(nullptr, IDS_ERR_NOPATCHES, IDS_APPNAME, MB_ICONINFORMATION);
2321 return true;
2326 int CAppUtils::GetLogOutputEncode(CGit *pGit)
2328 CString output;
2329 output = pGit->GetConfigValue(L"i18n.logOutputEncoding");
2330 if(output.IsEmpty())
2331 return CUnicodeUtils::GetCPCode(pGit->GetConfigValue(L"i18n.commitencoding"));
2332 else
2333 return CUnicodeUtils::GetCPCode(output);
2335 int CAppUtils::SaveCommitUnicodeFile(const CString& filename, CString &message)
2339 CFile file(filename, CFile::modeReadWrite | CFile::modeCreate);
2340 int cp = CUnicodeUtils::GetCPCode(g_Git.GetConfigValue(L"i18n.commitencoding"));
2342 bool stripComments = (CRegDWORD(L"Software\\TortoiseGit\\StripCommentedLines", FALSE) == TRUE);
2343 TCHAR commentChar = L'#';
2344 if (stripComments)
2346 CString commentCharValue = g_Git.GetConfigValue(L"core.commentchar");
2347 if (!commentCharValue.IsEmpty())
2348 commentChar = commentCharValue[0];
2351 bool sanitize = (CRegDWORD(L"Software\\TortoiseGit\\SanitizeCommitMsg", TRUE) == TRUE);
2352 if (sanitize)
2353 message.Trim(L" \r\n");
2355 int len = message.GetLength();
2356 int start = 0;
2357 int emptyLineCnt = 0;
2358 while (start >= 0 && start < len)
2360 int oldStart = start;
2361 start = message.Find(L'\n', oldStart);
2362 CString line = message.Mid(oldStart);
2363 if (start != -1)
2365 line.Truncate(start - oldStart);
2366 ++start; // move forward so we don't find the same char again
2368 if (stripComments && (!line.IsEmpty() && line.GetAt(0) == commentChar) || (start < 0 && line.IsEmpty()))
2369 continue;
2370 line.TrimRight(L" \r");
2371 if (sanitize)
2373 if (line.IsEmpty())
2375 ++emptyLineCnt;
2376 continue;
2378 if (emptyLineCnt) // squash multiple newlines
2379 file.Write("\n", 1);
2380 emptyLineCnt = 0;
2382 CStringA lineA = CUnicodeUtils::GetMulti(line + L'\n', cp);
2383 file.Write((LPCSTR)lineA, lineA.GetLength());
2385 file.Close();
2386 return 0;
2388 catch (CFileException *e)
2390 e->Delete();
2391 return -1;
2395 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)
2397 if (bAutoLoad)
2398 CAppUtils::LaunchPAgent(nullptr, &url);
2400 CGitHash hashOld;
2401 if (g_Git.GetHash(hashOld, L"HEAD"))
2403 MessageBox(nullptr, g_Git.GetGitLastErr(L"Could not get HEAD hash."), L"TortoiseGit", MB_ICONERROR);
2404 return false;
2407 CString args;
2408 if (CRegDWORD(L"Software\\TortoiseGit\\PullRebaseBehaviorLike1816", FALSE) == FALSE)
2409 args += L" --no-rebase";
2411 if (bFetchTags == BST_UNCHECKED)
2412 args += L" --no-tags";
2413 else if (bFetchTags == BST_CHECKED)
2414 args += L" --tags";
2416 if (bNoFF)
2417 args += L" --no-ff";
2419 if (bFFonly)
2420 args += L" --ff-only";
2422 if (bSquash)
2423 args += L" --squash";
2425 if (bNoCommit)
2426 args += L" --no-commit";
2428 if (nDepth)
2429 args.AppendFormat(L" --depth %d", *nDepth);
2431 if (bPrune == BST_CHECKED)
2432 args += L" --prune";
2433 else if (bPrune == BST_UNCHECKED)
2434 args += L" --no-prune";
2436 if (bUnrelated)
2437 args += L" --allow-unrelated-histories";
2439 CString cmd;
2440 cmd.Format(L"git.exe pull --progress -v%s \"%s\" %s", (LPCTSTR)args, (LPCTSTR)url, (LPCTSTR)remoteBranchName);
2441 CProgressDlg progress;
2442 progress.m_GitCmd = cmd;
2444 CGitHash hashNew; // declare outside lambda, because it is captured by reference
2445 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2447 if (status)
2449 if (CAppUtils::GetMsysgitVersion() >= 0x02090000)
2451 STRING_VECTOR remotes;
2452 g_Git.GetRemoteList(remotes);
2453 if (std::find(remotes.begin(), remotes.end(), url) != remotes.end())
2455 CString currentBranch;
2456 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, currentBranch))
2457 currentBranch.Empty();
2458 CString remoteRef = L"remotes/" + url + L"/" + remoteBranchName;
2459 if (!currentBranch.IsEmpty() && remoteBranchName.IsEmpty())
2461 CString pullRemote, pullBranch;
2462 g_Git.GetRemoteTrackedBranch(currentBranch, pullRemote, pullBranch);
2463 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
2464 remoteRef = L"remotes/" + pullRemote + L"/" + pullBranch;
2466 CGitHash common;
2467 g_Git.IsFastForward(L"HEAD", remoteRef, &common);
2468 if (common.IsEmpty())
2469 postCmdList.emplace_back(IDI_MERGE, IDS_MERGE_UNRELATED, [=] { DoPull(url, bAutoLoad, bFetchTags, bNoFF, bFFonly, bSquash, bNoCommit, nDepth, bPrune, remoteBranchName, showPush, showStashPop, true); });
2473 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, [&]{ CAppUtils::Pull(); });
2474 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSTASHSAVE, [&]{ CAppUtils::StashSave(L"", true); });
2475 return;
2478 if (showStashPop)
2479 postCmdList.emplace_back(IDI_RELOCATE, IDS_MENUSTASHPOP, []{ CAppUtils::StashPop(); });
2481 if (g_Git.GetHash(hashNew, L"HEAD"))
2482 MessageBox(nullptr, g_Git.GetGitLastErr(L"Could not get HEAD hash after pulling."), L"TortoiseGit", MB_ICONERROR);
2483 else
2485 postCmdList.emplace_back(IDI_DIFF, IDS_PROC_PULL_DIFFS, [&]
2487 CFileDiffDlg dlg;
2488 dlg.SetDiff(nullptr, hashOld.ToString(), hashNew.ToString());
2489 dlg.DoModal();
2491 postCmdList.emplace_back(IDI_LOG, IDS_PROC_PULL_LOG, [&]
2493 CLogDlg dlg;
2494 dlg.SetParams(CTGitPath(L""), CTGitPath(L""), L"", hashOld.ToString() + L".." + hashNew.ToString(), 0);
2495 dlg.DoModal();
2499 if (showPush)
2500 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, []{ CAppUtils::Push(); });
2502 CTGitPath gitPath = g_Git.m_CurrentDir;
2503 if (gitPath.HasSubmodules())
2505 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
2507 CString sCmd;
2508 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
2509 CAppUtils::RunTortoiseGitProc(sCmd);
2514 INT_PTR ret = progress.DoModal();
2516 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)
2518 CChangedDlg changeddlg;
2519 changeddlg.m_pathList.AddPath(CTGitPath());
2520 changeddlg.DoModal();
2522 return true;
2525 return ret == IDOK;
2528 bool CAppUtils::Pull(bool showPush, bool showStashPop)
2530 if (IsTGitRebaseActive())
2531 return false;
2533 CPullFetchDlg dlg;
2534 dlg.m_IsPull = TRUE;
2535 if (dlg.DoModal() == IDOK)
2537 // "git.exe pull --rebase" is not supported, never and ever. So, adapting it to Fetch & Rebase.
2538 if (dlg.m_bRebase)
2539 return DoFetch(dlg.m_RemoteURL,
2540 FALSE, // Fetch all remotes
2541 dlg.m_bAutoLoad == BST_CHECKED,
2542 dlg.m_bPrune,
2543 dlg.m_bDepth == BST_CHECKED,
2544 dlg.m_nDepth,
2545 dlg.m_bFetchTags,
2546 dlg.m_RemoteBranchName,
2547 dlg.m_bRebaseActivatedInConfigForPull ? 2 : 1, // Rebase after fetching
2548 dlg.m_bRebasePreserveMerges == TRUE); // Preserve merges on rebase
2550 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);
2553 return false;
2556 bool CAppUtils::RebaseAfterFetch(const CString& upstream, int rebase, bool preserveMerges)
2558 while (true)
2560 CRebaseDlg dlg;
2561 if (!upstream.IsEmpty())
2562 dlg.m_Upstream = upstream;
2563 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENULOG)));
2564 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUPUSH)));
2565 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUDESSENDMAIL)));
2566 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUREBASE)));
2567 dlg.m_bRebaseAutoStart = (rebase == 2);
2568 dlg.m_bPreserveMerges = preserveMerges;
2569 INT_PTR response = dlg.DoModal();
2570 if (response == IDOK)
2571 return true;
2572 else if (response == IDC_REBASE_POST_BUTTON)
2574 CString cmd = L"/command:log";
2575 cmd += L" /path:\"" + g_Git.m_CurrentDir + L'"';
2576 CAppUtils::RunTortoiseGitProc(cmd);
2577 return true;
2579 else if (response == IDC_REBASE_POST_BUTTON + 1)
2580 return Push();
2581 else if (response == IDC_REBASE_POST_BUTTON + 2)
2583 CString cmd, out, err;
2584 cmd.Format(L"git.exe format-patch -o \"%s\" %s..%s",
2585 (LPCTSTR)g_Git.m_CurrentDir,
2586 (LPCTSTR)g_Git.FixBranchName(dlg.m_Upstream),
2587 (LPCTSTR)g_Git.FixBranchName(dlg.m_Branch));
2588 if (g_Git.Run(cmd, &out, &err, CP_UTF8))
2590 CMessageBox::Show(nullptr, out + L'\n' + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
2591 return false;
2593 CAppUtils::SendPatchMail(cmd, out);
2594 return true;
2596 else if (response == IDC_REBASE_POST_BUTTON + 3)
2597 continue;
2598 else if (response == IDCANCEL)
2599 return false;
2600 return false;
2604 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)
2606 if (loadPuttyAgent)
2608 if (fetchAllRemotes)
2610 STRING_VECTOR list;
2611 g_Git.GetRemoteList(list);
2613 for (const auto& remote : list)
2614 CAppUtils::LaunchPAgent(nullptr, &remote);
2616 else
2617 CAppUtils::LaunchPAgent(nullptr, &url);
2620 CString upstream = L"FETCH_HEAD";
2621 CGitHash oldUpstreamHash;
2622 if (runRebase)
2624 STRING_VECTOR list;
2625 g_Git.GetRemoteList(list);
2626 for (auto it = list.cbegin(); it != list.cend(); ++it)
2628 if (url == *it)
2630 upstream = L"remotes/" + *it + L'/' + remoteBranch;
2631 if (remoteBranch.IsEmpty()) // pulldlg might clear remote branch if its the default tracked branch
2633 CString currentBranch;
2634 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, currentBranch))
2635 currentBranch.Empty();
2636 if (!currentBranch.IsEmpty() && remoteBranch.IsEmpty())
2638 CString pullRemote, pullBranch;
2639 g_Git.GetRemoteTrackedBranch(currentBranch, pullRemote, pullBranch);
2640 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty() && pullRemote == url) // pullRemote == url is just another safety-check and should not be needed
2641 upstream += pullBranch;
2644 g_Git.GetHash(oldUpstreamHash, upstream);
2645 break;
2650 CString cmd, arg;
2651 arg = L" --progress";
2653 if (bDepth)
2654 arg.AppendFormat(L" --depth %d", nDepth);
2656 if (prune == TRUE)
2657 arg += L" --prune";
2658 else if (prune == FALSE)
2659 arg += L" --no-prune";
2661 if (fetchTags == 1)
2662 arg += L" --tags";
2663 else if (fetchTags == 0)
2664 arg += L" --no-tags";
2666 if (fetchAllRemotes)
2667 cmd.Format(L"git.exe fetch --all -v%s", (LPCTSTR)arg);
2668 else
2669 cmd.Format(L"git.exe fetch -v%s \"%s\" %s", (LPCTSTR)arg, (LPCTSTR)url, (LPCTSTR)remoteBranch);
2671 CProgressDlg progress;
2672 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2674 if (status)
2676 postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ DoFetch(url, fetchAllRemotes, loadPuttyAgent, prune, bDepth, nDepth, fetchTags, remoteBranch, runRebase, rebasePreserveMerges); });
2677 return;
2680 postCmdList.emplace_back(IDI_LOG, IDS_MENULOG, []
2682 CString cmd = L"/command:log";
2683 cmd += L" /path:\"" + g_Git.m_CurrentDir + L'"';
2684 CAppUtils::RunTortoiseGitProc(cmd);
2687 postCmdList.emplace_back(IDI_REVERT, IDS_PROC_RESET, []
2689 CString pullRemote, pullBranch;
2690 g_Git.GetRemoteTrackedBranchForHEAD(pullRemote, pullBranch);
2691 CString defaultUpstream;
2692 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
2693 defaultUpstream.Format(L"remotes/%s/%s", (LPCTSTR)pullRemote, (LPCTSTR)pullBranch);
2694 CAppUtils::GitReset(&defaultUpstream, 2);
2697 postCmdList.emplace_back(IDI_PULL, IDS_MENUFETCH, []{ CAppUtils::Fetch(); });
2699 if (!runRebase && !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir))
2700 postCmdList.emplace_back(IDI_REBASE, IDS_MENUREBASE, [&]{ runRebase = false; CAppUtils::RebaseAfterFetch(); });
2703 progress.m_GitCmd = cmd;
2705 if (g_Git.UsingLibGit2(CGit::GIT_CMD_FETCH))
2707 CGitProgressDlg gitdlg;
2708 FetchProgressCommand fetchProgressCommand;
2709 if (!fetchAllRemotes)
2710 fetchProgressCommand.SetUrl(url);
2711 gitdlg.SetCommand(&fetchProgressCommand);
2712 fetchProgressCommand.m_PostCmdCallback = progress.m_PostCmdCallback;
2713 fetchProgressCommand.SetAutoTag(fetchTags == 1 ? GIT_REMOTE_DOWNLOAD_TAGS_ALL : fetchTags == 2 ? GIT_REMOTE_DOWNLOAD_TAGS_AUTO : GIT_REMOTE_DOWNLOAD_TAGS_NONE);
2714 if (!fetchAllRemotes)
2715 fetchProgressCommand.SetRefSpec(remoteBranch);
2716 return gitdlg.DoModal() == IDOK;
2719 progress.m_PostExecCallback = [&](DWORD& exitCode, CString&)
2721 if (exitCode || !runRebase)
2722 return;
2724 CGitHash remoteBranchHash;
2725 g_Git.GetHash(remoteBranchHash, upstream);
2726 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)
2727 return;
2729 if (runRebase == 1 && g_Git.IsFastForward(L"HEAD", upstream))
2731 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);
2732 if (ret == 3)
2733 return;
2734 if (ret == 1)
2736 CProgressDlg mergeProgress;
2737 mergeProgress.m_GitCmd = L"git.exe merge --ff-only " + upstream;
2738 mergeProgress.m_AutoClose = AUTOCLOSE_IF_NO_ERRORS;
2739 mergeProgress.m_PostCmdCallback = [](DWORD status, PostCmdList& postCmdList)
2741 if (status && g_Git.HasWorkingTreeConflicts())
2743 // there are conflict files
2744 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
2746 CString sCmd;
2747 sCmd.Format(L"/command:commit /path:\"%s\"", g_Git.m_CurrentDir);
2748 CAppUtils::RunTortoiseGitProc(sCmd);
2752 mergeProgress.DoModal();
2753 return;
2757 CAppUtils::RebaseAfterFetch(upstream, runRebase, rebasePreserveMerges);
2760 return progress.DoModal() == IDOK;
2763 bool CAppUtils::Fetch(const CString& remoteName, bool allRemotes)
2765 CPullFetchDlg dlg;
2766 dlg.m_PreSelectRemote = remoteName;
2767 dlg.m_IsPull=FALSE;
2768 dlg.m_bAllRemotes = allRemotes;
2770 if(dlg.DoModal()==IDOK)
2771 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);
2773 return false;
2776 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)
2778 CString error;
2779 DWORD exitcode = 0xFFFFFFFF;
2780 if (CHooks::Instance().PrePush(g_Git.m_CurrentDir, exitcode, error))
2782 if (exitcode)
2784 CString temp;
2785 temp.Format(IDS_ERR_HOOKFAILED, (LPCTSTR)error);
2786 MessageBox(nullptr, temp, L"TortoiseGit", MB_OK | MB_ICONERROR);
2787 return false;
2791 int iRecurseSubmodules = 0;
2792 if (GetMsysgitVersion() >= 0x02070000)
2794 CString sRecurseSubmodules = g_Git.GetConfigValue(L"push.recurseSubmodules");
2795 if (sRecurseSubmodules == L"check")
2796 iRecurseSubmodules = 1;
2797 else if (sRecurseSubmodules == L"on-demand")
2798 iRecurseSubmodules = 2;
2801 CString arg;
2802 if (pack)
2803 arg += L"--thin ";
2804 if (tags && !allBranches)
2805 arg += L"--tags ";
2806 if (force)
2807 arg += L"--force ";
2808 if (forceWithLease)
2809 arg += L"--force-with-lease ";
2810 if (setUpstream)
2811 arg += L"--set-upstream ";
2812 if (recurseSubmodules == 0 && recurseSubmodules != iRecurseSubmodules)
2813 arg += L"--recurse-submodules=no ";
2814 if (recurseSubmodules == 1 && recurseSubmodules != iRecurseSubmodules)
2815 arg += L"--recurse-submodules=check ";
2816 if (recurseSubmodules == 2 && recurseSubmodules != iRecurseSubmodules)
2817 arg += L"--recurse-submodules=on-demand ";
2819 arg += L"--progress ";
2821 CProgressDlg progress;
2823 STRING_VECTOR remotesList;
2824 if (allRemotes)
2825 g_Git.GetRemoteList(remotesList);
2826 else
2827 remotesList.push_back(remote);
2829 for (unsigned int i = 0; i < remotesList.size(); ++i)
2831 if (autoloadKey)
2832 CAppUtils::LaunchPAgent(nullptr, &remotesList[i]);
2834 CString cmd;
2835 if (allBranches)
2837 cmd.Format(L"git.exe push --all %s\"%s\"",
2838 (LPCTSTR)arg,
2839 (LPCTSTR)remotesList[i]);
2841 if (tags)
2843 progress.m_GitCmdList.push_back(cmd);
2844 cmd.Format(L"git.exe push --tags %s\"%s\"", (LPCTSTR)arg, (LPCTSTR)remotesList[i]);
2847 else
2849 cmd.Format(L"git.exe push %s\"%s\" %s",
2850 (LPCTSTR)arg,
2851 (LPCTSTR)remotesList[i],
2852 (LPCTSTR)localBranch);
2853 if (!remoteBranch.IsEmpty())
2855 cmd += L":";
2856 cmd += remoteBranch;
2859 progress.m_GitCmdList.push_back(cmd);
2861 if (!allBranches && !!CRegDWORD(L"Software\\TortoiseGit\\ShowBranchRevisionNumber", FALSE))
2863 cmd.Format(L"git.exe rev-list --count --first-parent %s", (LPCTSTR)localBranch);
2864 progress.m_GitCmdList.push_back(cmd);
2868 CString superprojectRoot;
2869 GitAdminDir::HasAdminDir(g_Git.m_CurrentDir, false, &superprojectRoot);
2870 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2872 // need to execute hooks as those might be needed by post action commands
2873 DWORD exitcode = 0xFFFFFFFF;
2874 CString error;
2875 if (CHooks::Instance().PostPush(g_Git.m_CurrentDir, exitcode, error))
2877 if (exitcode)
2879 CString temp;
2880 temp.Format(IDS_ERR_HOOKFAILED, (LPCTSTR)error);
2881 MessageBox(nullptr, temp, L"TortoiseGit", MB_OK | MB_ICONERROR);
2885 if (status)
2887 bool rejected = progress.GetLogText().Find(L"! [rejected]") > 0;
2888 if (rejected)
2890 postCmdList.emplace_back(IDI_PULL, IDS_MENUPULL, []{ Pull(true); });
2891 postCmdList.emplace_back(IDI_PULL, IDS_MENUFETCH, [&]{ Fetch(allRemotes ? L"" : remote, allRemotes); });
2893 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, [&]{ Push(localBranch); });
2894 return;
2897 postCmdList.emplace_back(IDS_PROC_REQUESTPULL, [&]{ RequestPull(remoteBranch); });
2898 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, [&]{ Push(localBranch); });
2899 postCmdList.emplace_back(IDI_SWITCH, IDS_MENUSWITCH, [&]{ Switch(); });
2900 if (!superprojectRoot.IsEmpty())
2902 postCmdList.emplace_back(IDI_COMMIT, IDS_PROC_COMMIT_SUPERPROJECT, [&]
2904 CString sCmd;
2905 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)superprojectRoot);
2906 RunTortoiseGitProc(sCmd);
2911 INT_PTR ret = progress.DoModal();
2912 return ret == IDOK;
2915 bool CAppUtils::Push(const CString& selectLocalBranch)
2917 CPushDlg dlg;
2918 dlg.m_BranchSourceName = selectLocalBranch;
2920 if (dlg.DoModal() == IDOK)
2921 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);
2923 return FALSE;
2926 bool CAppUtils::RequestPull(const CString& endrevision, const CString& repositoryUrl, bool bIsMainWnd)
2928 CRequestPullDlg dlg;
2929 dlg.m_RepositoryURL = repositoryUrl;
2930 dlg.m_EndRevision = endrevision;
2931 if (dlg.DoModal()==IDOK)
2933 CString cmd;
2934 cmd.Format(L"git.exe request-pull %s \"%s\" %s", (LPCTSTR)dlg.m_StartRevision, (LPCTSTR)dlg.m_RepositoryURL, (LPCTSTR)dlg.m_EndRevision);
2936 CSysProgressDlg sysProgressDlg;
2937 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
2938 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_CREATINGPULLREUQEST)));
2939 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
2940 sysProgressDlg.SetShowProgressBar(false);
2941 sysProgressDlg.ShowModeless((HWND)nullptr, true);
2943 CString tempFileName = GetTempFile();
2944 CString err;
2945 DeleteFile(tempFileName);
2946 CreateDirectory(tempFileName, nullptr);
2947 tempFileName += L"\\pullrequest.txt";
2948 if (g_Git.RunLogFile(cmd, tempFileName, &err))
2950 CString msg;
2951 msg.LoadString(IDS_ERR_PULLREUQESTFAILED);
2952 MessageBox(nullptr, msg + L'\n' + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
2953 return false;
2956 if (sysProgressDlg.HasUserCancelled())
2958 CMessageBox::Show(nullptr, IDS_USERCANCELLED, IDS_APPNAME, MB_OK);
2959 ::DeleteFile(tempFileName);
2960 return false;
2963 sysProgressDlg.Stop();
2965 if (dlg.m_bSendMail)
2967 CSendMailDlg sendmaildlg;
2968 sendmaildlg.m_PathList = CTGitPathList(CTGitPath(tempFileName));
2969 sendmaildlg.m_bCustomSubject = true;
2971 if (sendmaildlg.DoModal() == IDOK)
2973 if (sendmaildlg.m_PathList.IsEmpty())
2974 return FALSE;
2976 CGitProgressDlg progDlg;
2977 if (bIsMainWnd)
2978 theApp.m_pMainWnd = &progDlg;
2979 SendMailProgressCommand sendMailProgressCommand;
2980 progDlg.SetCommand(&sendMailProgressCommand);
2982 sendMailProgressCommand.SetPathList(sendmaildlg.m_PathList);
2983 progDlg.SetItemCount(sendmaildlg.m_PathList.GetCount());
2985 CSendMailCombineable sendMailCombineable(sendmaildlg.m_To, sendmaildlg.m_CC, sendmaildlg.m_Subject, !!sendmaildlg.m_bAttachment, !!sendmaildlg.m_bCombine);
2986 sendMailProgressCommand.SetSendMailOption(&sendMailCombineable);
2988 progDlg.DoModal();
2990 return true;
2992 return false;
2995 CAppUtils::LaunchAlternativeEditor(tempFileName);
2997 return true;
3000 void CAppUtils::RemoveTrailSlash(CString &path)
3002 if(path.IsEmpty())
3003 return ;
3005 // For URL, do not trim the slash just after the host name component.
3006 int index = path.Find(L"://");
3007 if (index >= 0)
3009 index += 4;
3010 index = path.Find(L'/', index);
3011 if (index == path.GetLength() - 1)
3012 return;
3015 while (path[path.GetLength() - 1] == L'\\' || path[path.GetLength() - 1] == L'/')
3017 path.Truncate(path.GetLength() - 1);
3018 if(path.IsEmpty())
3019 return;
3023 bool CAppUtils::CheckUserData()
3025 while(g_Git.GetUserName().IsEmpty() || g_Git.GetUserEmail().IsEmpty())
3027 if (CMessageBox::Show(nullptr, IDS_PROC_NOUSERDATA, IDS_APPNAME, MB_YESNO | MB_ICONERROR) == IDYES)
3029 CTGitPath path(g_Git.m_CurrentDir);
3030 CSettings dlg(IDS_PROC_SETTINGS_TITLE,&path);
3031 dlg.SetTreeViewMode(TRUE, TRUE, TRUE);
3032 dlg.SetTreeWidth(220);
3033 dlg.m_DefaultPage = L"gitconfig";
3035 dlg.DoModal();
3036 dlg.HandleRestart();
3039 else
3040 return false;
3043 return true;
3046 BOOL CAppUtils::Commit(const CString& bugid, BOOL bWholeProject, CString &sLogMsg,
3047 CTGitPathList &pathList,
3048 CTGitPathList &selectedList,
3049 bool bSelectFilesForCommit)
3051 bool bFailed = true;
3053 if (!CheckUserData())
3054 return false;
3056 while (bFailed)
3058 bFailed = false;
3059 CCommitDlg dlg;
3060 dlg.m_sBugID = bugid;
3062 dlg.m_bWholeProject = bWholeProject;
3064 dlg.m_sLogMessage = sLogMsg;
3065 dlg.m_pathList = pathList;
3066 dlg.m_checkedPathList = selectedList;
3067 dlg.m_bSelectFilesForCommit = bSelectFilesForCommit;
3068 if (dlg.DoModal() == IDOK)
3070 if (dlg.m_pathList.IsEmpty())
3071 return false;
3072 // if the user hasn't changed the list of selected items
3073 // we don't use that list. Because if we would use the list
3074 // of pre-checked items, the dialog would show different
3075 // checked items on the next startup: it would only try
3076 // to check the parent folder (which might not even show)
3077 // instead, we simply use an empty list and let the
3078 // default checking do its job.
3079 if (!dlg.m_pathList.IsEqual(pathList))
3080 selectedList = dlg.m_pathList;
3081 pathList = dlg.m_updatedPathList;
3082 sLogMsg = dlg.m_sLogMessage;
3083 bSelectFilesForCommit = true;
3085 switch (dlg.m_PostCmd)
3087 case GIT_POSTCOMMIT_CMD_DCOMMIT:
3088 CAppUtils::SVNDCommit();
3089 break;
3090 case GIT_POSTCOMMIT_CMD_PUSH:
3091 CAppUtils::Push();
3092 break;
3093 case GIT_POSTCOMMIT_CMD_CREATETAG:
3094 CAppUtils::CreateBranchTag(TRUE);
3095 break;
3096 case GIT_POSTCOMMIT_CMD_PULL:
3097 CAppUtils::Pull(true);
3098 break;
3099 default:
3100 break;
3103 // CGitProgressDlg progDlg;
3104 // progDlg.SetChangeList(dlg.m_sChangeList, !!dlg.m_bKeepChangeList);
3105 // if (parser.HasVal(L"closeonend"))
3106 // progDlg.SetAutoClose(parser.GetLongVal(L"closeonend"));
3107 // progDlg.SetCommand(CGitProgressDlg::GitProgress_Commit);
3108 // progDlg.SetOptions(dlg.m_bKeepLocks ? ProgOptKeeplocks : ProgOptNone);
3109 // progDlg.SetPathList(dlg.m_pathList);
3110 // progDlg.SetCommitMessage(dlg.m_sLogMessage);
3111 // progDlg.SetDepth(dlg.m_bRecursive ? Git_depth_infinity : svn_depth_empty);
3112 // progDlg.SetSelectedList(dlg.m_selectedPathList);
3113 // progDlg.SetItemCount(dlg.m_itemsCount);
3114 // progDlg.SetBugTraqProvider(dlg.m_BugTraqProvider);
3115 // progDlg.DoModal();
3116 // CRegDWORD err = CRegDWORD(L"Software\\TortoiseGit\\ErrorOccurred", FALSE);
3117 // err = (DWORD)progDlg.DidErrorsOccur();
3118 // bFailed = progDlg.DidErrorsOccur();
3119 // bRet = progDlg.DidErrorsOccur();
3120 // CRegDWORD bFailRepeat = CRegDWORD(L"Software\\TortoiseGit\\CommitReopen", FALSE);
3121 // if (DWORD(bFailRepeat)==0)
3122 // bFailed = false; // do not repeat if the user chose not to in the settings.
3125 return true;
3128 BOOL CAppUtils::SVNDCommit()
3130 CSVNDCommitDlg dcommitdlg;
3131 CString gitSetting = g_Git.GetConfigValue(L"svn.rmdir");
3132 if (gitSetting.IsEmpty()) {
3133 if (dcommitdlg.DoModal() != IDOK)
3134 return false;
3135 else
3137 if (dcommitdlg.m_remember)
3139 if (dcommitdlg.m_rmdir)
3140 gitSetting = L"true";
3141 else
3142 gitSetting = L"false";
3143 if (g_Git.SetConfigValue(L"svn.rmdir", gitSetting))
3145 CString msg;
3146 msg.Format(IDS_PROC_SAVECONFIGFAILED, L"svn.rmdir", gitSetting);
3147 MessageBox(nullptr, msg, L"TortoiseGit", MB_OK | MB_ICONERROR);
3153 BOOL IsStash = false;
3154 if(!g_Git.CheckCleanWorkTree())
3156 if (CMessageBox::Show(nullptr, IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
3158 CSysProgressDlg sysProgressDlg;
3159 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3160 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3161 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3162 sysProgressDlg.SetShowProgressBar(false);
3163 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3164 sysProgressDlg.ShowModeless((HWND)nullptr, true);
3166 CString out;
3167 if (g_Git.Run(L"git.exe stash", &out, CP_UTF8))
3169 sysProgressDlg.Stop();
3170 MessageBox(nullptr, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3171 return false;
3173 sysProgressDlg.Stop();
3175 IsStash =true;
3177 else
3178 return false;
3181 CProgressDlg progress;
3182 if (dcommitdlg.m_rmdir)
3183 progress.m_GitCmd = L"git.exe svn dcommit --rmdir";
3184 else
3185 progress.m_GitCmd = L"git.exe svn dcommit";
3186 if(progress.DoModal()==IDOK && progress.m_GitStatus == 0)
3188 if( IsStash)
3190 if (CMessageBox::Show(nullptr, IDS_DCOMMIT_STASH_POP, IDS_APPNAME, MB_YESNO | MB_ICONINFORMATION) == IDYES)
3192 CSysProgressDlg sysProgressDlg;
3193 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3194 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3195 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3196 sysProgressDlg.SetShowProgressBar(false);
3197 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3198 sysProgressDlg.ShowModeless((HWND)nullptr, true);
3200 CString out;
3201 if (g_Git.Run(L"git.exe stash pop", &out, CP_UTF8))
3203 sysProgressDlg.Stop();
3204 MessageBox(nullptr, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3205 return false;
3207 sysProgressDlg.Stop();
3209 else
3210 return false;
3212 return TRUE;
3214 return FALSE;
3217 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)
3219 CString args;
3220 if (noFF)
3221 args += L" --no-ff";
3222 else if (ffOnly)
3223 args += L" --ff-only";
3225 if (squash)
3226 args += L" --squash";
3228 if (noCommit)
3229 args += L" --no-commit";
3231 if (unrelated)
3232 args += L" --allow-unrelated-histories";
3234 if (log)
3235 args.AppendFormat(L" --log=%d", *log);
3237 if (!mergeStrategy.IsEmpty())
3239 args += L" --strategy=" + mergeStrategy;
3240 if (!strategyOption.IsEmpty())
3242 args += L" --strategy-option=" + strategyOption;
3243 if (!strategyParam.IsEmpty())
3244 args += L'=' + strategyParam;
3248 if (!logMessage.IsEmpty())
3250 CString logmsg = logMessage;
3251 logmsg.Replace(L"\\\"", L"\\\\\"");
3252 logmsg.Replace(L"\"", L"\\\"");
3253 args += L" -m \"" + logmsg + L"\"";
3256 CString mergeVersion = g_Git.FixBranchName(version);
3257 CString cmd;
3258 cmd.Format(L"git.exe merge%s %s", (LPCTSTR)args, (LPCTSTR)mergeVersion);
3260 CProgressDlg Prodlg;
3261 Prodlg.m_GitCmd = cmd;
3263 Prodlg.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3265 if (status)
3267 int hasConflicts = g_Git.HasWorkingTreeConflicts();
3268 if (hasConflicts < 0)
3269 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(L"Checking for conflicts failed.", CGit::GIT_CMD_CHECKCONFLICTS), L"TortoiseGit", MB_ICONEXCLAMATION);
3270 else if (hasConflicts)
3272 // there are conflict files
3274 postCmdList.emplace_back(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
3276 CString sCmd;
3277 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3278 CAppUtils::RunTortoiseGitProc(sCmd);
3282 if (CAppUtils::GetMsysgitVersion() >= 0x02090000)
3284 CGitHash common;
3285 g_Git.IsFastForward(L"HEAD", mergeVersion, &common);
3286 if (common.IsEmpty())
3287 postCmdList.emplace_back(IDI_MERGE, IDS_MERGE_UNRELATED, [=] { DoMerge(noFF, ffOnly, squash, noCommit, log, true, mergeStrategy, strategyOption, strategyParam, logMessage, version, isBranch, showStashPop); });
3290 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSTASHSAVE, [=]{ CAppUtils::StashSave(L"", false, false, true, mergeVersion); });
3292 return;
3295 if (showStashPop)
3296 postCmdList.emplace_back(IDI_RELOCATE, IDS_MENUSTASHPOP, []{ CAppUtils::StashPop(); });
3298 if (noCommit || squash)
3300 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUCOMMIT, []
3302 CString sCmd;
3303 sCmd.Format(L"/command:commit /path:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3304 CAppUtils::RunTortoiseGitProc(sCmd);
3306 return;
3309 if (isBranch && !CStringUtils::StartsWith(version, L"remotes/")) // do not ask to remove remote branches
3311 postCmdList.emplace_back(IDI_DELETE, IDS_PROC_REMOVEBRANCH, [&]
3313 CString msg;
3314 msg.Format(IDS_PROC_DELETEBRANCHTAG, version);
3315 if (CMessageBox::Show(nullptr, msg, L"TortoiseGit", 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
3317 CString cmd, out;
3318 cmd.Format(L"git.exe branch -D -- %s", (LPCTSTR)version);
3319 if (g_Git.Run(cmd, &out, CP_UTF8))
3320 MessageBox(nullptr, out, L"TortoiseGit", MB_OK);
3324 if (isBranch)
3325 postCmdList.emplace_back(IDI_PUSH, IDS_MENUPUSH, []{ CAppUtils::Push(); });
3327 BOOL hasGitSVN = CTGitPath(g_Git.m_CurrentDir).GetAdminDirMask() & ITEMIS_GITSVN;
3328 if (hasGitSVN)
3329 postCmdList.emplace_back(IDI_COMMIT, IDS_MENUSVNDCOMMIT, []{ CAppUtils::SVNDCommit(); });
3332 Prodlg.DoModal();
3333 return !Prodlg.m_GitStatus;
3336 BOOL CAppUtils::Merge(const CString* commit, bool showStashPop)
3338 if (!CheckUserData())
3339 return FALSE;
3341 if (IsTGitRebaseActive())
3342 return FALSE;
3344 CMergeDlg dlg;
3345 if (commit)
3346 dlg.m_initialRefName = *commit;
3348 if (dlg.DoModal() == IDOK)
3349 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);
3351 return FALSE;
3354 BOOL CAppUtils::MergeAbort()
3356 CMergeAbortDlg dlg;
3357 if (dlg.DoModal() == IDOK)
3358 return Reset(L"HEAD", dlg.m_ResetType + 1);
3360 return FALSE;
3363 void CAppUtils::EditNote(GitRevLoglist* rev, ProjectProperties* projectProperties)
3365 if (!CheckUserData())
3366 return;
3368 CInputDlg dlg;
3369 dlg.m_sHintText = CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES));
3370 dlg.m_sInputText = rev->m_Notes;
3371 dlg.m_sTitle = CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES));
3372 dlg.m_pProjectProperties = projectProperties;
3373 dlg.m_bUseLogWidth = true;
3374 if(dlg.DoModal() == IDOK)
3376 CString cmd,output;
3377 cmd = L"notes add -f -F \"";
3379 CString tempfile=::GetTempFile();
3380 if (!CStringUtils::WriteStringToTextFile(tempfile, dlg.m_sInputText))
3382 CMessageBox::Show(nullptr, IDS_PROC_FAILEDSAVINGNOTES, IDS_APPNAME, MB_OK | MB_ICONERROR);
3383 return;
3385 cmd += tempfile;
3386 cmd += L"\" ";
3387 cmd += rev->m_CommitHash.ToString();
3391 if (git_run_cmd("notes", CUnicodeUtils::GetMulti(cmd, CP_UTF8).GetBuffer()))
3392 CMessageBox::Show(nullptr, IDS_PROC_FAILEDSAVINGNOTES, IDS_APPNAME, MB_OK | MB_ICONERROR);
3393 }catch(...)
3395 CMessageBox::Show(nullptr, IDS_PROC_FAILEDSAVINGNOTES, IDS_APPNAME, MB_OK | MB_ICONERROR);
3397 ::DeleteFile(tempfile);
3399 if (g_Git.GetGitNotes(rev->m_CommitHash, rev->m_Notes))
3400 MessageBox(nullptr, g_Git.GetLibGit2LastErr(L"Could not load notes for commit " + rev->m_CommitHash.ToString() + L'.'), L"TortoiseGit", MB_OK | MB_ICONERROR);
3404 int CAppUtils::GetMsysgitVersion()
3406 if (g_Git.ms_LastMsysGitVersion)
3407 return g_Git.ms_LastMsysGitVersion;
3409 CString cmd;
3410 CString versiondebug;
3411 CString version;
3413 CRegDWORD regTime = CRegDWORD(L"Software\\TortoiseGit\\git_file_time");
3414 CRegDWORD regVersion = CRegDWORD(L"Software\\TortoiseGit\\git_cached_version");
3416 CString gitpath = CGit::ms_LastMsysGitDir + L"\\git.exe";
3418 __int64 time=0;
3419 if (!CGit::GetFileModifyTime(gitpath, &time))
3421 if((DWORD)time == regTime)
3423 g_Git.ms_LastMsysGitVersion = regVersion;
3424 return regVersion;
3428 CString err;
3429 int ver = g_Git.GetGitVersion(&versiondebug, &err);
3430 if (ver < 0)
3432 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);
3433 return -1;
3437 if (!ver)
3439 MessageBox(nullptr, L"Could not parse git.exe version number: \"" + versiondebug + L'"', L"TortoiseGit", MB_OK | MB_ICONERROR);
3440 return -1;
3444 regTime = time&0xFFFFFFFF;
3445 regVersion = ver;
3446 g_Git.ms_LastMsysGitVersion = ver;
3448 return ver;
3451 void CAppUtils::MarkWindowAsUnpinnable(HWND hWnd)
3453 typedef HRESULT (WINAPI *SHGPSFW) (HWND hwnd,REFIID riid,void** ppv);
3455 CAutoLibrary hShell = AtlLoadSystemLibraryUsingFullPath(L"Shell32.dll");
3457 if (hShell.IsValid()) {
3458 SHGPSFW pfnSHGPSFW = (SHGPSFW)::GetProcAddress(hShell, "SHGetPropertyStoreForWindow");
3459 if (pfnSHGPSFW) {
3460 IPropertyStore *pps;
3461 HRESULT hr = pfnSHGPSFW(hWnd, IID_PPV_ARGS(&pps));
3462 if (SUCCEEDED(hr)) {
3463 PROPVARIANT var;
3464 var.vt = VT_BOOL;
3465 var.boolVal = VARIANT_TRUE;
3466 pps->SetValue(PKEY_AppUserModel_PreventPinning, var);
3467 pps->Release();
3473 void CAppUtils::SetWindowTitle(HWND hWnd, const CString& urlorpath, const CString& dialogname)
3475 ASSERT(dialogname.GetLength() < 70);
3476 ASSERT(urlorpath.GetLength() < MAX_PATH);
3477 WCHAR pathbuf[MAX_PATH] = {0};
3479 PathCompactPathEx(pathbuf, urlorpath, 70 - dialogname.GetLength(), 0);
3481 wcscat_s(pathbuf, L" - ");
3482 wcscat_s(pathbuf, dialogname);
3483 wcscat_s(pathbuf, L" - ");
3484 wcscat_s(pathbuf, CString(MAKEINTRESOURCE(IDS_APPNAME)));
3485 SetWindowText(hWnd, pathbuf);
3488 bool CAppUtils::BisectStart(const CString& lastGood, const CString& firstBad, bool bIsMainWnd)
3490 if (!g_Git.CheckCleanWorkTree())
3492 if (CMessageBox::Show(nullptr, IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
3494 CSysProgressDlg sysProgressDlg;
3495 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3496 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3497 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3498 sysProgressDlg.SetShowProgressBar(false);
3499 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3500 sysProgressDlg.ShowModeless((HWND)nullptr, true);
3502 CString out;
3503 if (g_Git.Run(L"git.exe stash", &out, CP_UTF8))
3505 sysProgressDlg.Stop();
3506 MessageBox(nullptr, out, L"TortoiseGit", MB_OK | MB_ICONERROR);
3507 return false;
3509 sysProgressDlg.Stop();
3511 else
3512 return false;
3515 CBisectStartDlg bisectStartDlg;
3517 if (!lastGood.IsEmpty())
3518 bisectStartDlg.m_sLastGood = lastGood;
3519 if (!firstBad.IsEmpty())
3520 bisectStartDlg.m_sFirstBad = firstBad;
3522 if (bisectStartDlg.DoModal() == IDOK)
3524 CProgressDlg progress;
3525 if (bIsMainWnd)
3526 theApp.m_pMainWnd = &progress;
3527 progress.m_GitCmdList.push_back(L"git.exe bisect start");
3528 progress.m_GitCmdList.push_back(L"git.exe bisect good " + bisectStartDlg.m_LastGoodRevision);
3529 progress.m_GitCmdList.push_back(L"git.exe bisect bad " + bisectStartDlg.m_FirstBadRevision);
3531 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3533 if (status)
3534 return;
3536 CTGitPath path(g_Git.m_CurrentDir);
3537 if (path.HasSubmodules())
3539 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
3541 CString sCmd;
3542 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3543 CAppUtils::RunTortoiseGitProc(sCmd);
3548 INT_PTR ret = progress.DoModal();
3549 return ret == IDOK;
3552 return false;
3555 bool CAppUtils::BisectOperation(const CString& op, const CString& ref, bool bIsMainWnd)
3557 CString cmd = L"git.exe bisect " + op;
3559 if (!ref.IsEmpty())
3561 cmd += L' ';
3562 cmd += ref;
3565 CProgressDlg progress;
3566 if (bIsMainWnd)
3567 theApp.m_pMainWnd = &progress;
3568 progress.m_GitCmd = cmd;
3570 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3572 if (status)
3573 return;
3575 CTGitPath path = g_Git.m_CurrentDir;
3576 if (path.HasSubmodules())
3578 postCmdList.emplace_back(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
3580 CString sCmd;
3581 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
3582 CAppUtils::RunTortoiseGitProc(sCmd);
3586 if (op != L"reset")
3587 postCmdList.emplace_back(IDS_MENUBISECTRESET, []{ CAppUtils::RunTortoiseGitProc(L"/command:bisect /reset"); });
3590 INT_PTR ret = progress.DoModal();
3591 return ret == IDOK;
3594 int CAppUtils::Git2GetUserPassword(git_cred **out, const char *url, const char *username_from_url, unsigned int /*allowed_types*/, void * /*payload*/)
3596 CUserPassword dlg;
3597 dlg.m_URL = CUnicodeUtils::GetUnicode(url, CP_UTF8);
3598 if (username_from_url)
3599 dlg.m_UserName = CUnicodeUtils::GetUnicode(username_from_url, CP_UTF8);
3601 CStringA username, password;
3602 if (dlg.DoModal() == IDOK)
3604 username = CUnicodeUtils::GetMulti(dlg.m_UserName, CP_UTF8);
3605 password = CUnicodeUtils::GetMulti(dlg.m_Password, CP_UTF8);
3606 return git_cred_userpass_plaintext_new(out, username, password);
3608 giterr_set_str(GITERR_NONE, "User cancelled.");
3609 return GIT_EUSER;
3612 int CAppUtils::Git2CertificateCheck(git_cert* base_cert, int /*valid*/, const char* host, void* /*payload*/)
3614 if (base_cert->cert_type == GIT_CERT_X509)
3616 git_cert_x509* cert = (git_cert_x509*)base_cert;
3618 if (last_accepted_cert.cmp(cert))
3619 return 0;
3621 PCCERT_CONTEXT pServerCert = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, (BYTE*)cert->data, (DWORD)cert->len);
3622 SCOPE_EXIT { CertFreeCertificateContext(pServerCert); };
3624 DWORD verificationError = VerifyServerCertificate(pServerCert, CUnicodeUtils::GetUnicode(host).GetBuffer(), 0);
3625 if (!verificationError)
3627 last_accepted_cert.set(cert);
3628 return 0;
3631 CString servernameInCert;
3632 CertGetNameString(pServerCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nullptr, CStrBuf(servernameInCert, 128), 128);
3634 CString issuer;
3635 CertGetNameString(pServerCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, CERT_NAME_ISSUER_FLAG, nullptr, CStrBuf(issuer, 128), 128);
3637 CCheckCertificateDlg dlg;
3638 dlg.cert = cert;
3639 dlg.m_sCertificateCN = servernameInCert;
3640 dlg.m_sCertificateIssuer = issuer;
3641 dlg.m_sHostname = CUnicodeUtils::GetUnicode(host);
3642 dlg.m_sError = CFormatMessageWrapper(verificationError);
3643 if (dlg.DoModal() == IDOK)
3645 last_accepted_cert.set(cert);
3646 return 0;
3649 return GIT_ECERTIFICATE;
3652 int CAppUtils::ExploreTo(HWND hwnd, CString path)
3654 if (PathFileExists(path))
3656 HRESULT ret = -1;
3657 ITEMIDLIST __unaligned * pidl = ILCreateFromPath(path);
3658 if (pidl)
3660 ret = SHOpenFolderAndSelectItems(pidl, 0, 0, 0);
3661 ILFree(pidl);
3663 return SUCCEEDED(ret) ? 0 : -1;
3665 // if filepath does not exist any more, navigate to closest matching folder
3668 int pos = path.ReverseFind(L'\\');
3669 if (pos <= 3)
3670 break;
3671 path.Truncate(pos);
3672 } while (!PathFileExists(path));
3673 return (INT_PTR)ShellExecute(hwnd, L"explore", path, nullptr, nullptr, SW_SHOW) > 32 ? 0 : -1;
3676 int CAppUtils::ResolveConflict(CTGitPath& path, resolve_with resolveWith)
3678 bool b_local = false, b_remote = false;
3679 BYTE_VECTOR vector;
3681 CString cmd;
3682 cmd.Format(L"git.exe ls-files -u -t -z -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3683 if (g_Git.Run(cmd, &vector))
3685 MessageBox(nullptr, L"git ls-files failed!", L"TortoiseGit", MB_OK);
3686 return -1;
3689 CTGitPathList list;
3690 if (list.ParserFromLsFile(vector))
3692 MessageBox(nullptr, L"Parse ls-files failed!", L"TortoiseGit", MB_OK);
3693 return -1;
3696 if (list.IsEmpty())
3697 return 0;
3698 for (int i = 0; i < list.GetCount(); ++i)
3700 if (list[i].m_Stage == 2)
3701 b_local = true;
3702 if (list[i].m_Stage == 3)
3703 b_remote = true;
3707 CBlockCacheForPath block(g_Git.m_CurrentDir);
3708 if (path.IsDirectory()) // is submodule conflict
3710 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.";
3711 if (b_local && b_remote)
3713 if (!path.HasAdminDir()) // check if submodule is initialized
3715 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).";
3716 MessageBox(nullptr, err, L"TortoiseGit", MB_ICONERROR);
3717 return -1;
3719 CGit subgit;
3720 subgit.m_CurrentDir = g_Git.CombinePath(path);
3721 CGitHash submoduleHead;
3722 if (subgit.GetHash(submoduleHead, L"HEAD"))
3724 MessageBox(nullptr, err, L"TortoiseGit", MB_ICONERROR);
3725 return -1;
3727 CString baseHash, localHash, remoteHash;
3728 ParseHashesFromLsFile(vector, baseHash, localHash, remoteHash);
3729 if (resolveWith == RESOLVE_WITH_THEIRS && submoduleHead.ToString() != remoteHash)
3731 CString origPath = g_Git.m_CurrentDir;
3732 g_Git.m_CurrentDir = g_Git.CombinePath(path);
3733 if (!GitReset(&remoteHash))
3735 g_Git.m_CurrentDir = origPath;
3736 return -1;
3738 g_Git.m_CurrentDir = origPath;
3740 else if (resolveWith == RESOLVE_WITH_MINE && submoduleHead.ToString() != localHash)
3742 CString origPath = g_Git.m_CurrentDir;
3743 g_Git.m_CurrentDir = g_Git.CombinePath(path);
3744 if (!GitReset(&localHash))
3746 g_Git.m_CurrentDir = origPath;
3747 return -1;
3749 g_Git.m_CurrentDir = origPath;
3752 else
3754 MessageBox(nullptr, err, L"TortoiseGit", MB_ICONERROR);
3755 return -1;
3759 if (resolveWith == RESOLVE_WITH_THEIRS)
3761 CString gitcmd, output;
3762 if (b_local && b_remote)
3763 gitcmd.Format(L"git.exe checkout-index -f --stage=3 -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3764 else if (b_remote)
3765 gitcmd.Format(L"git.exe add -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3766 else if (b_local)
3767 gitcmd.Format(L"git.exe rm -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3768 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3770 MessageBox(nullptr, output, L"TortoiseGit", MB_ICONERROR);
3771 return -1;
3774 else if (resolveWith == RESOLVE_WITH_MINE)
3776 CString gitcmd, output;
3777 if (b_local && b_remote)
3778 gitcmd.Format(L"git.exe checkout-index -f --stage=2 -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3779 else if (b_local)
3780 gitcmd.Format(L"git.exe add -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3781 else if (b_remote)
3782 gitcmd.Format(L"git.exe rm -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3783 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3785 MessageBox(nullptr, output, L"TortoiseGit", MB_ICONERROR);
3786 return -1;
3790 if (b_local && b_remote && path.m_Action & CTGitPath::LOGACTIONS_UNMERGED)
3792 CString gitcmd, output;
3793 gitcmd.Format(L"git.exe add -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
3794 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3795 MessageBox(nullptr, output, L"TortoiseGit", MB_ICONERROR);
3796 else
3798 path.m_Action |= CTGitPath::LOGACTIONS_MODIFIED;
3799 path.m_Action &= ~CTGitPath::LOGACTIONS_UNMERGED;
3803 RemoveTempMergeFile(path);
3804 return 0;
3807 bool CAppUtils::ShellOpen(const CString& file, HWND hwnd /*= nullptr */)
3809 if ((INT_PTR)ShellExecute(hwnd, nullptr, file, nullptr, nullptr, SW_SHOW) > HINSTANCE_ERROR)
3810 return true;
3812 return ShowOpenWithDialog(file, hwnd);
3815 bool CAppUtils::ShowOpenWithDialog(const CString& file, HWND hwnd /*= nullptr */)
3817 OPENASINFO oi = { 0 };
3818 oi.pcszFile = file;
3819 oi.oaifInFlags = OAIF_EXEC;
3820 return SUCCEEDED(SHOpenWithDialog(hwnd, &oi));
3823 bool CAppUtils::IsTGitRebaseActive()
3825 CString adminDir;
3826 if (!GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir))
3827 return false;
3829 if (!PathIsDirectory(adminDir + L"tgitrebase.active"))
3830 return false;
3832 if (CMessageBox::Show(nullptr, IDS_REBASELOCKFILEFOUND, IDS_APPNAME, 2, IDI_EXCLAMATION, IDS_REMOVESTALEBUTTON, IDS_ABORTBUTTON) == 2)
3833 return true;
3835 RemoveDirectory(adminDir + L"tgitrebase.active");
3837 return false;