Use CUnicodeUtils::GetUnicode instead of CGit::StringAppend if possible
[TortoiseGit.git] / src / TortoiseProc / AppUtils.cpp
blobfea669d314863c635e3bf1bb746ae5e4966055bd
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2015 - 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 "StringUtils.h"
25 #include "MessageBox.h"
26 #include "registry.h"
27 #include "TGitPath.h"
28 #include "Git.h"
29 #include "UnicodeUtils.h"
30 #include "ExportDlg.h"
31 #include "ProgressDlg.h"
32 #include "GitAdminDir.h"
33 #include "ProgressDlg.h"
34 #include "BrowseFolder.h"
35 #include "DirFileEnum.h"
36 #include "MessageBox.h"
37 #include "GitStatus.h"
38 #include "CreateBranchTagDlg.h"
39 #include "GitSwitchDlg.h"
40 #include "ResetDlg.h"
41 #include "DeleteConflictDlg.h"
42 #include "ChangedDlg.h"
43 #include "SendMailDlg.h"
44 #include "GitProgressDlg.h"
45 #include "PushDlg.h"
46 #include "CommitDlg.h"
47 #include "MergeDlg.h"
48 #include "MergeAbortDlg.h"
49 #include "Hooks.h"
50 #include "..\Settings\Settings.h"
51 #include "InputDlg.h"
52 #include "SVNDCommitDlg.h"
53 #include "requestpulldlg.h"
54 #include "PullFetchDlg.h"
55 #include "FileDiffDlg.h"
56 #include "RebaseDlg.h"
57 #include "PropKey.h"
58 #include "StashSave.h"
59 #include "IgnoreDlg.h"
60 #include "FormatMessageWrapper.h"
61 #include "SmartHandle.h"
62 #include "BisectStartDlg.h"
63 #include "SysProgressDlg.h"
64 #include "UserPassword.h"
65 #include "Patch.h"
66 #include "Globals.h"
67 #include "ProgressCommands/ResetProgressCommand.h"
68 #include "ProgressCommands/FetchProgressCommand.h"
69 #include "ProgressCommands/SendMailProgressCommand.h"
70 #include "CertificateValidationHelper.h"
71 #include "CheckCertificateDlg.h"
72 #include "SubmoduleResolveConflictDlg.h"
73 #include "GitDiff.h"
74 #include "../TGitCache/CacheInterface.h"
76 static struct last_accepted_cert {
77 BYTE* data;
78 size_t len;
80 last_accepted_cert()
81 : data(nullptr)
82 , len(0)
85 ~last_accepted_cert()
87 free(data);
89 boolean cmp(git_cert_x509* cert)
91 return len > 0 && len == cert->len && memcmp(data, cert->data, len) == 0;
93 void set(git_cert_x509* cert)
95 free(data);
96 len = cert->len;
97 if (len == 0)
99 data = nullptr;
100 return;
102 data = new BYTE[len];
103 memcpy(data, cert->data, len);
105 } last_accepted_cert;
107 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, boolean runRebase);
109 CAppUtils::CAppUtils(void)
113 CAppUtils::~CAppUtils(void)
117 bool CAppUtils::StashSave(const CString& msg, bool showPull, bool pullShowPush, bool showMerge, const CString& mergeRev)
119 CStashSaveDlg dlg;
120 dlg.m_sMessage = msg;
121 if (dlg.DoModal() == IDOK)
123 CString cmd;
124 cmd = _T("git.exe stash save");
126 if (CAppUtils::GetMsysgitVersion() >= 0x01070700)
128 if (dlg.m_bIncludeUntracked)
129 cmd += _T(" --include-untracked");
130 else if (dlg.m_bAll)
131 cmd += _T(" --all");
134 if (!dlg.m_sMessage.IsEmpty())
136 CString message = dlg.m_sMessage;
137 message.Replace(_T("\""), _T("\"\""));
138 cmd += _T(" -- \"") + message + _T("\"");
141 CProgressDlg progress;
142 progress.m_GitCmd = cmd;
143 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
145 if (status)
146 return;
148 if (showPull)
149 postCmdList.push_back(PostCmd(IDI_PULL, IDS_MENUPULL, [&]{ CAppUtils::Pull(pullShowPush, true); }));
150 if (showMerge)
151 postCmdList.push_back(PostCmd(IDI_MERGE, IDS_MENUMERGE, [&]{ CAppUtils::Merge(&mergeRev, true); }));
153 return (progress.DoModal() == IDOK);
155 return false;
158 bool CAppUtils::StashApply(CString ref, bool showChanges /* true */)
160 CString cmd,out;
161 cmd = _T("git.exe stash apply ");
162 if (ref.Find(_T("refs/")) == 0)
163 ref = ref.Mid(5);
164 if (ref.Find(_T("stash{")) == 0)
165 ref = _T("stash@") + ref.Mid(5);
166 cmd += ref;
168 CSysProgressDlg sysProgressDlg;
169 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
170 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
171 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
172 sysProgressDlg.SetShowProgressBar(false);
173 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
174 sysProgressDlg.ShowModeless((HWND)NULL, true);
176 int ret = g_Git.Run(cmd, &out, CP_UTF8);
178 sysProgressDlg.Stop();
180 bool hasConflicts = (out.Find(_T("CONFLICT")) >= 0);
181 if (ret && !(ret == 1 && hasConflicts))
183 CMessageBox::Show(NULL, CString(MAKEINTRESOURCE(IDS_PROC_STASHAPPLYFAILED)) + _T("\n") + out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
185 else
187 CString message;
188 message.LoadString(IDS_PROC_STASHAPPLYSUCCESS);
189 if (hasConflicts)
190 message.LoadString(IDS_PROC_STASHAPPLYFAILEDCONFLICTS);
191 if (showChanges)
193 if(CMessageBox::Show(NULL,message + _T("\n") + CString(MAKEINTRESOURCE(IDS_SEECHANGES))
194 ,_T("TortoiseGit"),MB_YESNO|MB_ICONINFORMATION) == IDYES)
196 CChangedDlg dlg;
197 dlg.m_pathList.AddPath(CTGitPath());
198 dlg.DoModal();
200 return true;
202 else
204 CMessageBox::Show(NULL, message ,_T("TortoiseGit"), MB_OK | MB_ICONINFORMATION);
205 return true;
208 return false;
211 bool CAppUtils::StashPop(bool showChanges /* true */)
213 CString cmd,out;
214 cmd=_T("git.exe stash pop ");
216 CSysProgressDlg sysProgressDlg;
217 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
218 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
219 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
220 sysProgressDlg.SetShowProgressBar(false);
221 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
222 sysProgressDlg.ShowModeless((HWND)NULL, true);
224 int ret = g_Git.Run(cmd, &out, CP_UTF8);
226 sysProgressDlg.Stop();
228 bool hasConflicts = (out.Find(_T("CONFLICT")) >= 0);
229 if (ret && !(ret == 1 && hasConflicts))
231 CMessageBox::Show(NULL,CString(MAKEINTRESOURCE(IDS_PROC_STASHPOPFAILED)) + _T("\n") + out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
233 else
235 CString message;
236 message.LoadString(IDS_PROC_STASHPOPSUCCESS);
237 if (hasConflicts)
238 message.LoadString(IDS_PROC_STASHPOPFAILEDCONFLICTS);
239 if (showChanges)
241 if(CMessageBox::Show(NULL,CString(message + _T("\n") + CString(MAKEINTRESOURCE(IDS_SEECHANGES)))
242 ,_T("TortoiseGit"),MB_YESNO|MB_ICONINFORMATION) == IDYES)
244 CChangedDlg dlg;
245 dlg.m_pathList.AddPath(CTGitPath());
246 dlg.DoModal();
248 return true;
250 else
252 CMessageBox::Show(NULL, message ,_T("TortoiseGit"), MB_OK | MB_ICONINFORMATION);
253 return true;
256 return false;
259 BOOL CAppUtils::StartExtMerge(
260 const CTGitPath& basefile, const CTGitPath& theirfile, const CTGitPath& yourfile, const CTGitPath& mergedfile,
261 const CString& basename, const CString& theirname, const CString& yourname, const CString& mergedname, bool bReadOnly,
262 HWND resolveMsgHwnd, bool bDeleteBaseTheirsMineOnClose)
265 CRegString regCom = CRegString(_T("Software\\TortoiseGit\\Merge"));
266 CString ext = mergedfile.GetFileExtension();
267 CString com = regCom;
268 bool bInternal = false;
270 if (!ext.IsEmpty())
272 // is there an extension specific merge tool?
273 CRegString mergetool(_T("Software\\TortoiseGit\\MergeTools\\") + ext.MakeLower());
274 if (!CString(mergetool).IsEmpty())
276 com = mergetool;
279 // is there a filename specific merge tool?
280 CRegString mergetool(_T("Software\\TortoiseGit\\MergeTools\\.") + mergedfile.GetFilename().MakeLower());
281 if (!CString(mergetool).IsEmpty())
283 com = mergetool;
286 if (com.IsEmpty()||(com.Left(1).Compare(_T("#"))==0))
288 // Maybe we should use TortoiseIDiff?
289 if ((ext == _T(".jpg")) || (ext == _T(".jpeg")) ||
290 (ext == _T(".bmp")) || (ext == _T(".gif")) ||
291 (ext == _T(".png")) || (ext == _T(".ico")) ||
292 (ext == _T(".tif")) || (ext == _T(".tiff")) ||
293 (ext == _T(".dib")) || (ext == _T(".emf")) ||
294 (ext == _T(".cur")))
296 com = CPathUtils::GetAppDirectory() + _T("TortoiseGitIDiff.exe");
297 com = _T("\"") + com + _T("\"");
298 com = com + _T(" /base:%base /theirs:%theirs /mine:%mine /result:%merged");
299 com = com + _T(" /basetitle:%bname /theirstitle:%tname /minetitle:%yname");
301 else
303 // use TortoiseGitMerge
304 bInternal = true;
305 com = CPathUtils::GetAppDirectory() + _T("TortoiseGitMerge.exe");
306 com = _T("\"") + com + _T("\"");
307 com = com + _T(" /base:%base /theirs:%theirs /mine:%mine /merged:%merged");
308 com = com + _T(" /basename:%bname /theirsname:%tname /minename:%yname /mergedname:%mname");
309 com += _T(" /saverequired");
310 if (resolveMsgHwnd)
312 CString s;
313 s.Format(L" /resolvemsghwnd:%I64d", (__int64)resolveMsgHwnd);
314 com += s;
316 if (bDeleteBaseTheirsMineOnClose)
317 com += _T(" /deletebasetheirsmineonclose");
319 if (!g_sGroupingUUID.IsEmpty())
321 com += L" /groupuuid:\"";
322 com += g_sGroupingUUID;
323 com += L"\"";
326 // check if the params are set. If not, just add the files to the command line
327 if ((com.Find(_T("%merged"))<0)&&(com.Find(_T("%base"))<0)&&(com.Find(_T("%theirs"))<0)&&(com.Find(_T("%mine"))<0))
329 com += _T(" \"")+basefile.GetWinPathString()+_T("\"");
330 com += _T(" \"")+theirfile.GetWinPathString()+_T("\"");
331 com += _T(" \"")+yourfile.GetWinPathString()+_T("\"");
332 com += _T(" \"")+mergedfile.GetWinPathString()+_T("\"");
334 if (basefile.IsEmpty())
336 com.Replace(_T("/base:%base"), _T(""));
337 com.Replace(_T("%base"), _T(""));
339 else
340 com.Replace(_T("%base"), _T("\"") + basefile.GetWinPathString() + _T("\""));
341 if (theirfile.IsEmpty())
343 com.Replace(_T("/theirs:%theirs"), _T(""));
344 com.Replace(_T("%theirs"), _T(""));
346 else
347 com.Replace(_T("%theirs"), _T("\"") + theirfile.GetWinPathString() + _T("\""));
348 if (yourfile.IsEmpty())
350 com.Replace(_T("/mine:%mine"), _T(""));
351 com.Replace(_T("%mine"), _T(""));
353 else
354 com.Replace(_T("%mine"), _T("\"") + yourfile.GetWinPathString() + _T("\""));
355 if (mergedfile.IsEmpty())
357 com.Replace(_T("/merged:%merged"), _T(""));
358 com.Replace(_T("%merged"), _T(""));
360 else
361 com.Replace(_T("%merged"), _T("\"") + mergedfile.GetWinPathString() + _T("\""));
362 if (basename.IsEmpty())
364 if (basefile.IsEmpty())
366 com.Replace(_T("/basename:%bname"), _T(""));
367 com.Replace(_T("%bname"), _T(""));
369 else
371 com.Replace(_T("%bname"), _T("\"") + basefile.GetUIFileOrDirectoryName() + _T("\""));
374 else
375 com.Replace(_T("%bname"), _T("\"") + basename + _T("\""));
376 if (theirname.IsEmpty())
378 if (theirfile.IsEmpty())
380 com.Replace(_T("/theirsname:%tname"), _T(""));
381 com.Replace(_T("%tname"), _T(""));
383 else
385 com.Replace(_T("%tname"), _T("\"") + theirfile.GetUIFileOrDirectoryName() + _T("\""));
388 else
389 com.Replace(_T("%tname"), _T("\"") + theirname + _T("\""));
390 if (yourname.IsEmpty())
392 if (yourfile.IsEmpty())
394 com.Replace(_T("/minename:%yname"), _T(""));
395 com.Replace(_T("%yname"), _T(""));
397 else
399 com.Replace(_T("%yname"), _T("\"") + yourfile.GetUIFileOrDirectoryName() + _T("\""));
402 else
403 com.Replace(_T("%yname"), _T("\"") + yourname + _T("\""));
404 if (mergedname.IsEmpty())
406 if (mergedfile.IsEmpty())
408 com.Replace(_T("/mergedname:%mname"), _T(""));
409 com.Replace(_T("%mname"), _T(""));
411 else
413 com.Replace(_T("%mname"), _T("\"") + mergedfile.GetUIFileOrDirectoryName() + _T("\""));
416 else
417 com.Replace(_T("%mname"), _T("\"") + mergedname + _T("\""));
419 if ((bReadOnly)&&(bInternal))
420 com += _T(" /readonly");
422 if(!LaunchApplication(com, IDS_ERR_EXTMERGESTART, false))
424 return FALSE;
427 return TRUE;
430 BOOL CAppUtils::StartExtPatch(const CTGitPath& patchfile, const CTGitPath& dir, const CString& sOriginalDescription, const CString& sPatchedDescription, BOOL bReversed, BOOL bWait)
432 CString viewer;
433 // use TortoiseGitMerge
434 viewer = CPathUtils::GetAppDirectory();
435 viewer += _T("TortoiseGitMerge.exe");
437 viewer = _T("\"") + viewer + _T("\"");
438 viewer = viewer + _T(" /diff:\"") + patchfile.GetWinPathString() + _T("\"");
439 viewer = viewer + _T(" /patchpath:\"") + dir.GetWinPathString() + _T("\"");
440 if (bReversed)
441 viewer += _T(" /reversedpatch");
442 if (!sOriginalDescription.IsEmpty())
443 viewer = viewer + _T(" /patchoriginal:\"") + sOriginalDescription + _T("\"");
444 if (!sPatchedDescription.IsEmpty())
445 viewer = viewer + _T(" /patchpatched:\"") + sPatchedDescription + _T("\"");
446 if (!g_sGroupingUUID.IsEmpty())
448 viewer += L" /groupuuid:\"";
449 viewer += g_sGroupingUUID;
450 viewer += L"\"";
452 if(!LaunchApplication(viewer, IDS_ERR_DIFFVIEWSTART, !!bWait))
454 return FALSE;
456 return TRUE;
459 CString CAppUtils::PickDiffTool(const CTGitPath& file1, const CTGitPath& file2)
461 CString difftool = CRegString(_T("Software\\TortoiseGit\\DiffTools\\.") + file2.GetFilename().MakeLower());
462 if (!difftool.IsEmpty())
463 return difftool;
464 difftool = CRegString(_T("Software\\TortoiseGit\\DiffTools\\.") + file1.GetFilename().MakeLower());
465 if (!difftool.IsEmpty())
466 return difftool;
468 // Is there an extension specific diff tool?
469 CString ext = file2.GetFileExtension().MakeLower();
470 if (!ext.IsEmpty())
472 difftool = CRegString(_T("Software\\TortoiseGit\\DiffTools\\") + ext);
473 if (!difftool.IsEmpty())
474 return difftool;
475 // Maybe we should use TortoiseIDiff?
476 if ((ext == _T(".jpg")) || (ext == _T(".jpeg")) ||
477 (ext == _T(".bmp")) || (ext == _T(".gif")) ||
478 (ext == _T(".png")) || (ext == _T(".ico")) ||
479 (ext == _T(".tif")) || (ext == _T(".tiff")) ||
480 (ext == _T(".dib")) || (ext == _T(".emf")) ||
481 (ext == _T(".cur")))
483 return
484 _T("\"") + CPathUtils::GetAppDirectory() + _T("TortoiseGitIDiff.exe") + _T("\"") +
485 _T(" /left:%base /right:%mine /lefttitle:%bname /righttitle:%yname") +
486 L" /groupuuid:\"" + g_sGroupingUUID + L"\"";
490 // Finally, pick a generic external diff tool
491 difftool = CRegString(_T("Software\\TortoiseGit\\Diff"));
492 return difftool;
495 bool CAppUtils::StartExtDiff(
496 const CString& file1, const CString& file2,
497 const CString& sName1, const CString& sName2,
498 const CString& originalFile1, const CString& originalFile2,
499 const git_revnum_t& hash1, const git_revnum_t& hash2,
500 const DiffFlags& flags, int jumpToLine)
502 CString viewer;
504 CRegDWORD blamediff(_T("Software\\TortoiseGit\\DiffBlamesWithTortoiseMerge"), FALSE);
505 if (!flags.bBlame || !(DWORD)blamediff)
507 viewer = PickDiffTool(file1, file2);
508 // If registry entry for a diff program is commented out, use TortoiseGitMerge.
509 bool bCommentedOut = viewer.Left(1) == _T("#");
510 if (flags.bAlternativeTool)
512 // Invert external vs. internal diff tool selection.
513 if (bCommentedOut)
514 viewer.Delete(0); // uncomment
515 else
516 viewer = "";
518 else if (bCommentedOut)
519 viewer = "";
522 bool bInternal = viewer.IsEmpty();
523 if (bInternal)
525 viewer =
526 _T("\"") + CPathUtils::GetAppDirectory() + _T("TortoiseGitMerge.exe") + _T("\"") +
527 _T(" /base:%base /mine:%mine /basename:%bname /minename:%yname") +
528 _T(" /basereflectedname:%bpath /minereflectedname:%ypath");
529 if (!g_sGroupingUUID.IsEmpty())
531 viewer += L" /groupuuid:\"";
532 viewer += g_sGroupingUUID;
533 viewer += L"\"";
535 if (flags.bBlame)
536 viewer += _T(" /blame");
538 // check if the params are set. If not, just add the files to the command line
539 if ((viewer.Find(_T("%base"))<0)&&(viewer.Find(_T("%mine"))<0))
541 viewer += _T(" \"")+file1+_T("\"");
542 viewer += _T(" \"")+file2+_T("\"");
544 if (viewer.Find(_T("%base")) >= 0)
546 viewer.Replace(_T("%base"), _T("\"")+file1+_T("\""));
548 if (viewer.Find(_T("%mine")) >= 0)
550 viewer.Replace(_T("%mine"), _T("\"")+file2+_T("\""));
553 if (sName1.IsEmpty())
554 viewer.Replace(_T("%bname"), _T("\"") + file1 + _T("\""));
555 else
556 viewer.Replace(_T("%bname"), _T("\"") + sName1 + _T("\""));
558 if (sName2.IsEmpty())
559 viewer.Replace(_T("%yname"), _T("\"") + file2 + _T("\""));
560 else
561 viewer.Replace(_T("%yname"), _T("\"") + sName2 + _T("\""));
563 viewer.Replace(_T("%bpath"), _T("\"") + originalFile1 + _T("\""));
564 viewer.Replace(_T("%ypath"), _T("\"") + originalFile2 + _T("\""));
566 viewer.Replace(_T("%brev"), _T("\"") + hash1 + _T("\""));
567 viewer.Replace(_T("%yrev"), _T("\"") + hash2 + _T("\""));
569 if (flags.bReadOnly && bInternal)
570 viewer += _T(" /readonly");
572 if (jumpToLine > 0)
574 CString temp;
575 temp.Format(_T(" /line:%d"), jumpToLine);
576 viewer += temp;
579 return LaunchApplication(viewer, IDS_ERR_EXTDIFFSTART, flags.bWait);
582 BOOL CAppUtils::StartUnifiedDiffViewer(const CString& patchfile, const CString& title, BOOL bWait)
584 CString viewer;
585 CRegString v = CRegString(_T("Software\\TortoiseGit\\DiffViewer"));
586 viewer = v;
587 if (viewer.IsEmpty() || (viewer.Left(1).Compare(_T("#"))==0))
589 // use TortoiseGitUDiff
590 viewer = CPathUtils::GetAppDirectory();
591 viewer += _T("TortoiseGitUDiff.exe");
592 // enquote the path to TortoiseGitUDiff
593 viewer = _T("\"") + viewer + _T("\"");
594 // add the params
595 viewer = viewer + _T(" /patchfile:%1 /title:\"%title\"");
596 if (!g_sGroupingUUID.IsEmpty())
598 viewer += L" /groupuuid:\"";
599 viewer += g_sGroupingUUID;
600 viewer += L"\"";
603 if (viewer.Find(_T("%1"))>=0)
605 if (viewer.Find(_T("\"%1\"")) >= 0)
606 viewer.Replace(_T("%1"), patchfile);
607 else
608 viewer.Replace(_T("%1"), _T("\"") + patchfile + _T("\""));
610 else
611 viewer += _T(" \"") + patchfile + _T("\"");
612 if (viewer.Find(_T("%title")) >= 0)
614 viewer.Replace(_T("%title"), title);
617 if(!LaunchApplication(viewer, IDS_ERR_DIFFVIEWSTART, !!bWait))
619 return FALSE;
621 return TRUE;
624 BOOL CAppUtils::StartTextViewer(CString file)
626 CString viewer;
627 CRegString txt = CRegString(_T(".txt\\"), _T(""), FALSE, HKEY_CLASSES_ROOT);
628 viewer = txt;
629 viewer = viewer + _T("\\Shell\\Open\\Command\\");
630 CRegString txtexe = CRegString(viewer, _T(""), FALSE, HKEY_CLASSES_ROOT);
631 viewer = txtexe;
633 DWORD len = ExpandEnvironmentStrings(viewer, NULL, 0);
634 std::unique_ptr<TCHAR[]> buf(new TCHAR[len + 1]);
635 ExpandEnvironmentStrings(viewer, buf.get(), len);
636 viewer = buf.get();
637 len = ExpandEnvironmentStrings(file, NULL, 0);
638 std::unique_ptr<TCHAR[]> buf2(new TCHAR[len + 1]);
639 ExpandEnvironmentStrings(file, buf2.get(), len);
640 file = buf2.get();
641 file = _T("\"")+file+_T("\"");
642 if (viewer.IsEmpty())
644 return CAppUtils::ShowOpenWithDialog(file) ? TRUE : FALSE;
646 if (viewer.Find(_T("\"%1\"")) >= 0)
648 viewer.Replace(_T("\"%1\""), file);
650 else if (viewer.Find(_T("%1")) >= 0)
652 viewer.Replace(_T("%1"), file);
654 else
656 viewer += _T(" ");
657 viewer += file;
660 if(!LaunchApplication(viewer, IDS_ERR_TEXTVIEWSTART, false))
662 return FALSE;
664 return TRUE;
667 BOOL CAppUtils::CheckForEmptyDiff(const CTGitPath& sDiffPath)
669 DWORD length = 0;
670 CAutoFile hFile = ::CreateFile(sDiffPath.GetWinPath(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL);
671 if (!hFile)
672 return TRUE;
673 length = ::GetFileSize(hFile, NULL);
674 if (length < 4)
675 return TRUE;
676 return FALSE;
680 void CAppUtils::CreateFontForLogs(CFont& fontToCreate)
682 LOGFONT logFont;
683 HDC hScreenDC = ::GetDC(NULL);
684 logFont.lfHeight = -MulDiv((DWORD)CRegDWORD(_T("Software\\TortoiseGit\\LogFontSize"), 8), GetDeviceCaps(hScreenDC, LOGPIXELSY), 72);
685 ::ReleaseDC(NULL, hScreenDC);
686 logFont.lfWidth = 0;
687 logFont.lfEscapement = 0;
688 logFont.lfOrientation = 0;
689 logFont.lfWeight = FW_NORMAL;
690 logFont.lfItalic = 0;
691 logFont.lfUnderline = 0;
692 logFont.lfStrikeOut = 0;
693 logFont.lfCharSet = DEFAULT_CHARSET;
694 logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
695 logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
696 logFont.lfQuality = DRAFT_QUALITY;
697 logFont.lfPitchAndFamily = FF_DONTCARE | FIXED_PITCH;
698 _tcscpy_s(logFont.lfFaceName, 32, (LPCTSTR)(CString)CRegString(_T("Software\\TortoiseGit\\LogFontName"), _T("Courier New")));
699 VERIFY(fontToCreate.CreateFontIndirect(&logFont));
702 bool CAppUtils::LaunchPAgent(const CString* keyfile, const CString* pRemote)
704 CString key,remote;
705 CString cmd,out;
706 if( pRemote == NULL)
708 remote=_T("origin");
710 else
712 remote=*pRemote;
714 if(keyfile == NULL)
716 cmd.Format(_T("remote.%s.puttykeyfile"),remote);
717 key = g_Git.GetConfigValue(cmd);
719 else
720 key=*keyfile;
722 if(key.IsEmpty())
723 return false;
725 CString proc=CPathUtils::GetAppDirectory();
726 proc += _T("pageant.exe \"");
727 proc += key;
728 proc += _T("\"");
730 CString tempfile = GetTempFile();
731 ::DeleteFile(tempfile);
733 proc += _T(" -c \"");
734 proc += CPathUtils::GetAppDirectory();
735 proc += _T("tgittouch.exe\"");
736 proc += _T(" \"");
737 proc += tempfile;
738 proc += _T("\"");
740 CString appDir = CPathUtils::GetAppDirectory();
741 bool b = LaunchApplication(proc, IDS_ERR_PAGEANT, true, &appDir);
742 if(!b)
743 return b;
745 int i=0;
746 while(!::PathFileExists(tempfile))
748 Sleep(100);
749 ++i;
750 if(i>10*60*5)
751 break; //timeout 5 minutes
754 if( i== 10*60*5)
756 CMessageBox::Show(NULL, IDS_ERR_PAEGENTTIMEOUT, IDS_APPNAME, MB_OK | MB_ICONERROR);
758 ::DeleteFile(tempfile);
759 return true;
761 bool CAppUtils::LaunchAlternativeEditor(const CString& filename, bool uac)
763 CString editTool = CRegString(_T("Software\\TortoiseGit\\AlternativeEditor"));
764 if (editTool.IsEmpty() || (editTool.Left(1).Compare(_T("#"))==0)) {
765 editTool = CPathUtils::GetAppDirectory() + _T("notepad2.exe");
768 CString sCmd;
769 sCmd.Format(_T("\"%s\" \"%s\""), editTool, filename);
771 LaunchApplication(sCmd, NULL, false, NULL, uac);
772 return true;
774 bool CAppUtils::LaunchRemoteSetting()
776 CTGitPath path(g_Git.m_CurrentDir);
777 CSettings dlg(IDS_PROC_SETTINGS_TITLE, &path);
778 dlg.SetTreeViewMode(TRUE, TRUE, TRUE);
779 dlg.SetTreeWidth(220);
780 dlg.m_DefaultPage = _T("gitremote");
782 dlg.DoModal();
783 dlg.HandleRestart();
784 return true;
787 * Launch the external blame viewer
789 bool CAppUtils::LaunchTortoiseBlame(const CString& sBlameFile, const CString& Rev, const CString& sParams)
791 CString viewer = _T("\"") + CPathUtils::GetAppDirectory();
792 viewer += _T("TortoiseGitBlame.exe");
793 viewer += _T("\" \"") + sBlameFile + _T("\"");
794 //viewer += _T(" \"") + sLogFile + _T("\"");
795 //viewer += _T(" \"") + sOriginalFile + _T("\"");
796 if(!Rev.IsEmpty() && Rev != GIT_REV_ZERO)
797 viewer += CString(_T(" /rev:"))+Rev;
798 if (!g_sGroupingUUID.IsEmpty())
800 viewer += L" /groupuuid:\"";
801 viewer += g_sGroupingUUID;
802 viewer += L"\"";
804 viewer += _T(" ")+sParams;
806 return LaunchApplication(viewer, IDS_ERR_TGITBLAME, false);
809 bool CAppUtils::FormatTextInRichEditControl(CWnd * pWnd)
811 CString sText;
812 if (pWnd == NULL)
813 return false;
814 bool bStyled = false;
815 pWnd->GetWindowText(sText);
816 // the rich edit control doesn't count the CR char!
817 // to be exact: CRLF is treated as one char.
818 sText.Remove(_T('\r'));
820 // style each line separately
821 int offset = 0;
822 int nNewlinePos;
825 nNewlinePos = sText.Find('\n', offset);
826 CString sLine = nNewlinePos >= 0 ? sText.Mid(offset, nNewlinePos - offset) : sText.Mid(offset);
828 int start = 0;
829 int end = 0;
830 while (FindStyleChars(sLine, '*', start, end))
832 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
833 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
834 SetCharFormat(pWnd, CFM_BOLD, CFE_BOLD);
835 bStyled = true;
836 start = end;
838 start = 0;
839 end = 0;
840 while (FindStyleChars(sLine, '^', start, end))
842 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
843 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
844 SetCharFormat(pWnd, CFM_ITALIC, CFE_ITALIC);
845 bStyled = true;
846 start = end;
848 start = 0;
849 end = 0;
850 while (FindStyleChars(sLine, '_', start, end))
852 CHARRANGE range = {(LONG)start+offset, (LONG)end+offset};
853 pWnd->SendMessage(EM_EXSETSEL, NULL, (LPARAM)&range);
854 SetCharFormat(pWnd, CFM_UNDERLINE, CFE_UNDERLINE);
855 bStyled = true;
856 start = end;
858 offset = nNewlinePos+1;
859 } while(nNewlinePos>=0);
860 return bStyled;
863 bool CAppUtils::FindStyleChars(const CString& sText, TCHAR stylechar, int& start, int& end)
865 int i=start;
866 int last = sText.GetLength() - 1;
867 bool bFoundMarker = false;
868 TCHAR c = i == 0 ? _T('\0') : sText[i - 1];
869 TCHAR nextChar = i >= last ? _T('\0') : sText[i + 1];
871 // find a starting marker
872 while (i < last)
874 TCHAR prevChar = c;
875 c = nextChar;
876 nextChar = sText[i + 1];
878 // IsCharAlphaNumeric can be somewhat expensive.
879 // Long lines of "*****" or "----" will be pre-empted efficiently
880 // by the (c != nextChar) condition.
882 if ((c == stylechar) && (c != nextChar))
884 if (IsCharAlphaNumeric(nextChar) && !IsCharAlphaNumeric(prevChar))
886 start = ++i;
887 bFoundMarker = true;
888 break;
891 ++i;
893 if (!bFoundMarker)
894 return false;
896 // find ending marker
897 // c == sText[i - 1]
899 bFoundMarker = false;
900 while (i <= last)
902 TCHAR prevChar = c;
903 c = sText[i];
904 if (c == stylechar)
906 if ((i == last) || (!IsCharAlphaNumeric(sText[i + 1]) && IsCharAlphaNumeric(prevChar)))
908 end = i;
909 ++i;
910 bFoundMarker = true;
911 break;
914 ++i;
916 return bFoundMarker;
919 // from CSciEdit
920 namespace {
921 bool IsValidURLChar(wchar_t ch)
923 return iswalnum(ch) ||
924 ch == L'_' || ch == L'/' || ch == L';' || ch == L'?' || ch == L'&' || ch == L'=' ||
925 ch == L'%' || ch == L':' || ch == L'.' || ch == L'#' || ch == L'-' || ch == L'+' ||
926 ch == L'|' || ch == L'>' || ch == L'<';
929 bool IsUrl(const CString& sText)
931 if (!PathIsURLW(sText))
932 return false;
933 CString prefixes[] = { L"http://", L"https://", L"git://", L"ftp://", L"file://", L"mailto:" };
934 for (const CString& prefix : prefixes)
936 if (sText.Find(prefix) == 0 && sText.GetLength() != prefix.GetLength())
937 return true;
939 return false;
943 BOOL CAppUtils::StyleURLs(const CString& msg, CWnd* pWnd)
945 std::vector<CHARRANGE> positions = FindURLMatches(msg);
946 CAppUtils::SetCharFormat(pWnd, CFM_LINK, CFE_LINK, positions);
948 return positions.empty() ? FALSE : TRUE;
952 * implements URL searching with the same logic as CSciEdit::StyleURLs
954 std::vector<CHARRANGE> CAppUtils::FindURLMatches(const CString& msg)
956 std::vector<CHARRANGE> result;
958 int len = msg.GetLength();
959 int starturl = -1;
961 for (int i = 0; i <= msg.GetLength(); ++i)
963 if ((i < len) && IsValidURLChar(msg[i]))
965 if (starturl < 0)
966 starturl = i;
968 else
970 if (starturl >= 0)
972 bool strip = true;
973 if (msg[starturl] == '<' && i < len) // try to detect and do not strip URLs put within <>
975 while (starturl <= i && msg[starturl] == '<') // strip leading '<'
976 ++starturl;
977 strip = false;
978 i = starturl;
979 while (i < len && msg[i] != '\r' && msg[i] != '\n' && msg[i] != '>') // find first '>' or new line after resetting i to start position
980 ++i;
982 if (!IsUrl(msg.Mid(starturl, i - starturl)))
984 starturl = -1;
985 continue;
988 int skipTrailing = 0;
989 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] == '<'))
990 ++skipTrailing;
992 CHARRANGE range = { starturl, i - skipTrailing };
993 result.push_back(range);
995 starturl = -1;
999 return result;
1002 bool CAppUtils::StartShowUnifiedDiff(HWND hWnd, const CTGitPath& url1, const git_revnum_t& rev1,
1003 const CTGitPath& /*url2*/, const git_revnum_t& rev2,
1004 //const GitRev& peg /* = GitRev */, const GitRev& headpeg /* = GitRev */,
1005 bool /*bAlternateDiff*/ /* = false */, bool /*bIgnoreAncestry*/ /* = false */,
1006 bool /* blame = false */,
1007 bool bMerge,
1008 bool bCombine)
1010 int diffContext = 0;
1011 if (GetMsysgitVersion() > 0x01080100)
1012 diffContext = g_Git.GetConfigValueInt32(_T("diff.context"), -1);
1013 CString tempfile=GetTempFile();
1014 if (g_Git.GetUnifiedDiff(url1, rev1, rev2, tempfile, bMerge, bCombine, diffContext))
1016 CMessageBox::Show(hWnd, g_Git.GetGitLastErr(_T("Could not get unified diff."), CGit::GIT_CMD_DIFF), _T("TortoiseGit"), MB_OK);
1017 return false;
1019 CAppUtils::StartUnifiedDiffViewer(tempfile, rev1 + _T(":") + rev2);
1021 #if 0
1022 CString sCmd;
1023 sCmd.Format(_T("%s /command:showcompare /unified"),
1024 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseGitProc.exe")));
1025 sCmd += _T(" /url1:\"") + url1.GetGitPathString() + _T("\"");
1026 if (rev1.IsValid())
1027 sCmd += _T(" /revision1:") + rev1.ToString();
1028 sCmd += _T(" /url2:\"") + url2.GetGitPathString() + _T("\"");
1029 if (rev2.IsValid())
1030 sCmd += _T(" /revision2:") + rev2.ToString();
1031 if (peg.IsValid())
1032 sCmd += _T(" /pegrevision:") + peg.ToString();
1033 if (headpeg.IsValid())
1034 sCmd += _T(" /headpegrevision:") + headpeg.ToString();
1036 if (bAlternateDiff)
1037 sCmd += _T(" /alternatediff");
1039 if (bIgnoreAncestry)
1040 sCmd += _T(" /ignoreancestry");
1042 if (hWnd)
1044 sCmd += _T(" /hwnd:");
1045 TCHAR buf[30];
1046 _stprintf_s(buf, 30, _T("%p"), (void*)hWnd);
1047 sCmd += buf;
1050 return CAppUtils::LaunchApplication(sCmd, NULL, false);
1051 #endif
1052 return TRUE;
1055 bool CAppUtils::SetupDiffScripts(bool force, const CString& type)
1057 CString scriptsdir = CPathUtils::GetAppParentDirectory();
1058 scriptsdir += _T("Diff-Scripts");
1059 CSimpleFileFind files(scriptsdir);
1060 while (files.FindNextFileNoDirectories())
1062 CString file = files.GetFilePath();
1063 CString filename = files.GetFileName();
1064 CString ext = file.Mid(file.ReverseFind('-') + 1);
1065 ext = _T(".") + ext.Left(ext.ReverseFind('.'));
1066 std::set<CString> extensions;
1067 extensions.insert(ext);
1068 CString kind;
1069 if (file.Right(3).CompareNoCase(_T("vbs"))==0)
1071 kind = _T(" //E:vbscript");
1073 if (file.Right(2).CompareNoCase(_T("js"))==0)
1075 kind = _T(" //E:javascript");
1077 // open the file, read the first line and find possible extensions
1078 // this script can handle
1081 CStdioFile f(file, CFile::modeRead | CFile::shareDenyNone);
1082 CString extline;
1083 if (f.ReadString(extline))
1085 if ((extline.GetLength() > 15 ) &&
1086 ((extline.Left(15).Compare(_T("// extensions: ")) == 0) ||
1087 (extline.Left(14).Compare(_T("' extensions: ")) == 0)))
1089 if (extline[0] == '/')
1090 extline = extline.Mid(15);
1091 else
1092 extline = extline.Mid(14);
1093 CString sToken;
1094 int curPos = 0;
1095 sToken = extline.Tokenize(_T(";"), curPos);
1096 while (!sToken.IsEmpty())
1098 if (!sToken.IsEmpty())
1100 if (sToken[0] != '.')
1101 sToken = _T(".") + sToken;
1102 extensions.insert(sToken);
1104 sToken = extline.Tokenize(_T(";"), curPos);
1108 f.Close();
1110 catch (CFileException* e)
1112 e->Delete();
1115 for (std::set<CString>::const_iterator it = extensions.begin(); it != extensions.end(); ++it)
1117 if (type.IsEmpty() || (type.Compare(_T("Diff")) == 0))
1119 if (filename.Left(5).CompareNoCase(_T("diff-")) == 0)
1121 CRegString diffreg = CRegString(_T("Software\\TortoiseGit\\DiffTools\\") + *it);
1122 CString diffregstring = diffreg;
1123 if (force || (diffregstring.IsEmpty()) || (diffregstring.Find(filename) >= 0))
1124 diffreg = _T("wscript.exe \"") + file + _T("\" %base %mine") + kind;
1127 if (type.IsEmpty() || (type.Compare(_T("Merge"))==0))
1129 if (filename.Left(6).CompareNoCase(_T("merge-"))==0)
1131 CRegString diffreg = CRegString(_T("Software\\TortoiseGit\\MergeTools\\") + *it);
1132 CString diffregstring = diffreg;
1133 if (force || (diffregstring.IsEmpty()) || (diffregstring.Find(filename) >= 0))
1134 diffreg = _T("wscript.exe \"") + file + _T("\" %merged %theirs %mine %base") + kind;
1140 return true;
1143 bool CAppUtils::Export(const CString* BashHash, const CTGitPath* orgPath)
1145 // ask from where the export has to be done
1146 CExportDlg dlg;
1147 if(BashHash)
1148 dlg.m_initialRefName=*BashHash;
1149 if (orgPath)
1151 if (PathIsRelative(orgPath->GetWinPath()))
1152 dlg.m_orgPath = g_Git.CombinePath(orgPath);
1153 else
1154 dlg.m_orgPath = *orgPath;
1157 if (dlg.DoModal() == IDOK)
1159 CString cmd;
1160 cmd.Format(_T("git.exe archive --output=\"%s\" --format=zip --verbose %s --"),
1161 dlg.m_strFile, g_Git.FixBranchName(dlg.m_VersionName));
1163 CProgressDlg pro;
1164 pro.m_GitCmd=cmd;
1165 pro.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1167 if (status)
1168 return;
1169 postCmdList.push_back(PostCmd(IDI_EXPLORER, IDS_STATUSLIST_CONTEXT_EXPLORE, [&]{ CAppUtils::ExploreTo(hWndExplorer, dlg.m_strFile); }));
1172 CGit git;
1173 if (!dlg.m_bWholeProject && !dlg.m_orgPath.IsEmpty() && PathIsDirectory(dlg.m_orgPath.GetWinPathString()))
1175 git.m_CurrentDir = dlg.m_orgPath.GetWinPathString();
1176 pro.m_Git = &git;
1178 return (pro.DoModal() == IDOK);
1180 return false;
1183 bool CAppUtils::CreateBranchTag(bool IsTag, const CString* CommitHash, bool switch_new_brach)
1185 CCreateBranchTagDlg dlg;
1186 dlg.m_bIsTag=IsTag;
1187 dlg.m_bSwitch=switch_new_brach;
1189 if(CommitHash)
1190 dlg.m_initialRefName = *CommitHash;
1192 if(dlg.DoModal()==IDOK)
1194 CString cmd;
1195 CString force;
1196 CString track;
1197 if(dlg.m_bTrack == TRUE)
1198 track=_T(" --track ");
1199 else if(dlg.m_bTrack == FALSE)
1200 track=_T(" --no-track");
1202 if(dlg.m_bForce)
1203 force=_T(" -f ");
1205 if(IsTag)
1207 CString sign;
1208 if(dlg.m_bSign)
1209 sign=_T("-s");
1211 cmd.Format(_T("git.exe tag %s %s %s %s"),
1212 force,
1213 sign,
1214 dlg.m_BranchTagName,
1215 g_Git.FixBranchName(dlg.m_VersionName)
1218 if(!dlg.m_Message.Trim().IsEmpty())
1220 CString tempfile = ::GetTempFile();
1221 if (CAppUtils::SaveCommitUnicodeFile(tempfile, dlg.m_Message))
1223 CMessageBox::Show(nullptr, _T("Could not save tag message"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1224 return FALSE;
1226 cmd += _T(" -F ")+tempfile;
1229 else
1231 cmd.Format(_T("git.exe branch %s %s %s %s"),
1232 track,
1233 force,
1234 dlg.m_BranchTagName,
1235 g_Git.FixBranchName(dlg.m_VersionName)
1238 CString out;
1239 if(g_Git.Run(cmd,&out,CP_UTF8))
1241 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
1242 return FALSE;
1244 if( !IsTag && dlg.m_bSwitch )
1246 // it is a new branch and the user has requested to switch to it
1247 PerformSwitch(dlg.m_BranchTagName);
1250 return TRUE;
1252 return FALSE;
1255 bool CAppUtils::Switch(const CString& initialRefName)
1257 CGitSwitchDlg dlg;
1258 if(!initialRefName.IsEmpty())
1259 dlg.m_initialRefName = initialRefName;
1261 if (dlg.DoModal() == IDOK)
1263 CString branch;
1264 if (dlg.m_bBranch)
1265 branch = dlg.m_NewBranch;
1267 // if refs/heads/ is not stripped, checkout will detach HEAD
1268 // checkout prefers branches on name clashes (with tags)
1269 if (dlg.m_VersionName.Left(11) ==_T("refs/heads/") && dlg.m_bBranchOverride != TRUE)
1270 dlg.m_VersionName = dlg.m_VersionName.Mid(11);
1272 return PerformSwitch(dlg.m_VersionName, dlg.m_bForce == TRUE , branch, dlg.m_bBranchOverride == TRUE, dlg.m_bTrack, dlg.m_bMerge == TRUE);
1274 return FALSE;
1277 bool CAppUtils::PerformSwitch(const CString& ref, bool bForce /* false */, const CString& sNewBranch /* CString() */, bool bBranchOverride /* false */, BOOL bTrack /* 2 */, bool bMerge /* false */)
1279 CString cmd;
1280 CString track;
1281 CString force;
1282 CString branch;
1283 CString merge;
1285 if(!sNewBranch.IsEmpty()){
1286 if (bBranchOverride)
1288 branch.Format(_T("-B %s"), sNewBranch);
1290 else
1292 branch.Format(_T("-b %s"), sNewBranch);
1294 if (bTrack == TRUE)
1295 track = _T("--track");
1296 else if (bTrack == FALSE)
1297 track = _T("--no-track");
1299 if (bForce)
1300 force = _T("-f");
1301 if (bMerge)
1302 merge = _T("--merge");
1304 cmd.Format(_T("git.exe checkout %s %s %s %s %s --"),
1305 force,
1306 track,
1307 merge,
1308 branch,
1309 g_Git.FixBranchName(ref));
1311 CProgressDlg progress;
1312 progress.m_GitCmd = cmd;
1314 CString currentBranch;
1315 bool hasBranch = CGit::GetCurrentBranchFromFile(g_Git.m_CurrentDir, currentBranch) == 0;
1316 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1318 if (!status)
1320 CTGitPath gitPath = g_Git.m_CurrentDir;
1321 if (gitPath.HasSubmodules())
1323 postCmdList.push_back(PostCmd(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1325 CString sCmd;
1326 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), g_Git.m_CurrentDir);
1327 RunTortoiseGitProc(sCmd);
1328 }));
1330 if (hasBranch)
1331 postCmdList.push_back(PostCmd(IDI_MERGE, IDS_MENUMERGE, [&]{ Merge(&currentBranch); }));
1334 CString newBranch;
1335 if (!CGit::GetCurrentBranchFromFile(g_Git.m_CurrentDir, newBranch))
1336 postCmdList.push_back(PostCmd(IDI_PULL, IDS_MENUPULL, [&]{ Pull(); }));
1338 postCmdList.push_back(PostCmd(IDI_COMMIT, IDS_MENUCOMMIT, []{
1339 CTGitPathList pathlist;
1340 CTGitPathList selectedlist;
1341 bool bSelectFilesForCommit = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE));
1342 CString str;
1343 Commit(CString(), false, str, pathlist, selectedlist, bSelectFilesForCommit);
1344 }));
1346 else
1348 postCmdList.push_back(PostCmd(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ PerformSwitch(ref, bForce, sNewBranch, bBranchOverride, bTrack, bMerge); }));
1349 if (!bMerge)
1350 postCmdList.push_back(PostCmd(IDI_SWITCH, IDS_SWITCH_WITH_MERGE, [&]{ PerformSwitch(ref, bForce, sNewBranch, bBranchOverride, bTrack, true); }));
1354 INT_PTR ret = progress.DoModal();
1356 return ret == IDOK;
1359 class CIgnoreFile : public CStdioFile
1361 public:
1362 STRING_VECTOR m_Items;
1363 CString m_eol;
1365 virtual BOOL ReadString(CString& rString)
1367 if (GetPosition() == 0)
1369 unsigned char utf8bom[] = { 0xEF, 0xBB, 0xBF };
1370 char buf[3] = { 0, 0, 0 };
1371 Read(buf, 3);
1372 if (memcpy(buf, utf8bom, sizeof(utf8bom)))
1374 SeekToBegin();
1378 CStringA strA;
1379 char lastChar = '\0';
1380 for (char c = '\0'; Read(&c, 1) == 1; lastChar = c)
1382 if (c == '\r')
1383 continue;
1384 if (c == '\n')
1386 m_eol = lastChar == '\r' ? _T("\r\n") : _T("\n");
1387 break;
1389 strA.AppendChar(c);
1391 if (strA.IsEmpty())
1392 return FALSE;
1394 rString = CUnicodeUtils::GetUnicode(strA);
1395 return TRUE;
1398 void ResetState()
1400 m_Items.clear();
1401 m_eol = _T("");
1405 bool CAppUtils::OpenIgnoreFile(CIgnoreFile &file, const CString& filename)
1407 file.ResetState();
1408 if (!file.Open(filename, CFile::modeCreate | CFile::modeReadWrite | CFile::modeNoTruncate | CFile::typeBinary))
1410 CMessageBox::Show(NULL, filename + _T(" Open Failure"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1411 return false;
1414 if (file.GetLength() > 0)
1416 CString fileText;
1417 while (file.ReadString(fileText))
1418 file.m_Items.push_back(fileText);
1419 file.Seek(file.GetLength() - 1, 0);
1420 char lastchar[1] = { 0 };
1421 file.Read(lastchar, 1);
1422 file.SeekToEnd();
1423 if (lastchar[0] != '\n')
1425 CStringA eol = CStringA(file.m_eol.IsEmpty() ? _T("\n") : file.m_eol);
1426 file.Write(eol, eol.GetLength());
1429 else
1430 file.SeekToEnd();
1432 return true;
1435 bool CAppUtils::IgnoreFile(const CTGitPathList& path,bool IsMask)
1437 CIgnoreDlg ignoreDlg;
1438 if (ignoreDlg.DoModal() == IDOK)
1440 CString ignorefile;
1441 ignorefile = g_Git.m_CurrentDir + _T("\\");
1443 switch (ignoreDlg.m_IgnoreFile)
1445 case 0:
1446 ignorefile += _T(".gitignore");
1447 break;
1448 case 2:
1449 GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, ignorefile);
1450 ignorefile += _T("info/exclude");
1451 break;
1454 CIgnoreFile file;
1457 if (ignoreDlg.m_IgnoreFile != 1 && !OpenIgnoreFile(file, ignorefile))
1458 return false;
1460 for (int i = 0; i < path.GetCount(); ++i)
1462 if (ignoreDlg.m_IgnoreFile == 1)
1464 ignorefile = g_Git.CombinePath(path[i].GetContainingDirectory()) + _T("\\.gitignore");
1465 if (!OpenIgnoreFile(file, ignorefile))
1466 return false;
1469 CString ignorePattern;
1470 if (ignoreDlg.m_IgnoreType == 0)
1472 if (ignoreDlg.m_IgnoreFile != 1 && !path[i].GetContainingDirectory().GetGitPathString().IsEmpty())
1473 ignorePattern += _T("/") + path[i].GetContainingDirectory().GetGitPathString();
1475 ignorePattern += _T("/");
1477 if (IsMask)
1479 ignorePattern += _T("*") + path[i].GetFileExtension();
1481 else
1483 ignorePattern += path[i].GetFileOrDirectoryName();
1486 // escape [ and ] so that files get ignored correctly
1487 ignorePattern.Replace(_T("["), _T("\\["));
1488 ignorePattern.Replace(_T("]"), _T("\\]"));
1490 bool found = false;
1491 for (size_t j = 0; j < file.m_Items.size(); ++j)
1493 if (file.m_Items[j] == ignorePattern)
1495 found = true;
1496 break;
1499 if (!found)
1501 file.m_Items.push_back(ignorePattern);
1502 ignorePattern += file.m_eol.IsEmpty() ? _T("\n") : file.m_eol;
1503 CStringA ignorePatternA = CUnicodeUtils::GetUTF8(ignorePattern);
1504 file.Write(ignorePatternA, ignorePatternA.GetLength());
1507 if (ignoreDlg.m_IgnoreFile == 1)
1508 file.Close();
1511 if (ignoreDlg.m_IgnoreFile != 1)
1512 file.Close();
1514 catch(...)
1516 file.Abort();
1517 return false;
1520 return true;
1522 return false;
1525 static bool Reset(const CString& resetTo, int resetType)
1527 CString cmd;
1528 CString type;
1529 switch (resetType)
1531 case 0:
1532 type = _T("--soft");
1533 break;
1534 case 1:
1535 type = _T("--mixed");
1536 break;
1537 case 2:
1538 type = _T("--hard");
1539 break;
1540 default:
1541 resetType = 1;
1542 type = _T("--mixed");
1543 break;
1545 cmd.Format(_T("git.exe reset %s %s --"), type, resetTo);
1547 CProgressDlg progress;
1548 progress.m_GitCmd = cmd;
1550 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
1552 if (status)
1554 postCmdList.push_back(PostCmd(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ Reset(resetTo, resetType); }));
1555 return;
1558 CTGitPath gitPath = g_Git.m_CurrentDir;
1559 if (gitPath.HasSubmodules() && resetType == 2)
1561 postCmdList.push_back(PostCmd(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, [&]
1563 CString sCmd;
1564 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), g_Git.m_CurrentDir);
1565 CAppUtils::RunTortoiseGitProc(sCmd);
1566 }));
1570 INT_PTR ret;
1571 if (g_Git.UsingLibGit2(CGit::GIT_CMD_RESET))
1573 CGitProgressDlg gitdlg;
1574 ResetProgressCommand resetProgressCommand;
1575 gitdlg.SetCommand(&resetProgressCommand);
1576 resetProgressCommand.m_PostCmdCallback = progress.m_PostCmdCallback;
1577 resetProgressCommand.SetRevision(resetTo);
1578 resetProgressCommand.SetResetType(resetType);
1579 ret = gitdlg.DoModal();
1581 else
1582 ret = progress.DoModal();
1584 return ret == IDOK;
1587 bool CAppUtils::GitReset(const CString* CommitHash, int type)
1589 CResetDlg dlg;
1590 dlg.m_ResetType=type;
1591 dlg.m_ResetToVersion=*CommitHash;
1592 dlg.m_initialRefName = *CommitHash;
1593 if (dlg.DoModal() == IDOK)
1594 return Reset(dlg.m_ResetToVersion, dlg.m_ResetType);
1596 return false;
1599 void CAppUtils::DescribeConflictFile(bool mode, bool base,CString &descript)
1601 if(mode == FALSE)
1603 descript = CString(MAKEINTRESOURCE(IDS_SVNACTION_DELETE));
1604 return;
1606 if(base)
1608 descript = CString(MAKEINTRESOURCE(IDS_SVNACTION_MODIFIED));
1609 return;
1611 descript = CString(MAKEINTRESOURCE(IDS_PROC_CREATED));
1612 return;
1615 void CAppUtils::RemoveTempMergeFile(const CTGitPath& path)
1617 ::DeleteFile(CAppUtils::GetMergeTempFile(_T("LOCAL"), path));
1618 ::DeleteFile(CAppUtils::GetMergeTempFile(_T("REMOTE"), path));
1619 ::DeleteFile(CAppUtils::GetMergeTempFile(_T("BASE"), path));
1621 CString CAppUtils::GetMergeTempFile(const CString& type, const CTGitPath &merge)
1623 CString file;
1624 file = g_Git.CombinePath(merge.GetWinPathString() + _T(".") + type + merge.GetFileExtension());
1626 return file;
1629 bool ParseHashesFromLsFile(const BYTE_VECTOR& out, CString& hash1, CString& hash2, CString& hash3)
1631 int pos = 0;
1632 CString one;
1633 CString part;
1635 while (pos >= 0 && pos < (int)out.size())
1637 one.Empty();
1639 CGit::StringAppend(&one, &out[pos], CP_UTF8);
1640 int tabstart = 0;
1641 one.Tokenize(_T("\t"), tabstart);
1643 tabstart = 0;
1644 part = one.Tokenize(_T(" "), tabstart); //Tag
1645 part = one.Tokenize(_T(" "), tabstart); //Mode
1646 part = one.Tokenize(_T(" "), tabstart); //Hash
1647 CString hash = part;
1648 part = one.Tokenize(_T("\t"), tabstart); //Stage
1649 int stage = _ttol(part);
1650 if (stage == 1)
1651 hash1 = hash;
1652 else if (stage == 2)
1653 hash2 = hash;
1654 else if (stage == 3)
1656 hash3 = hash;
1657 return true;
1660 pos = out.findNextString(pos);
1663 return false;
1666 bool CAppUtils::ConflictEdit(const CTGitPath& path, bool /*bAlternativeTool = false*/, bool revertTheirMy /*= false*/, HWND resolveMsgHwnd /*= nullptr*/)
1668 bool bRet = false;
1670 CTGitPath merge=path;
1671 CTGitPath directory = merge.GetDirectory();
1673 // we have the conflicted file (%merged)
1674 // now look for the other required files
1675 //GitStatus stat;
1676 //stat.GetStatus(merge);
1677 //if (stat.status == NULL)
1678 // return false;
1680 BYTE_VECTOR vector;
1682 CString cmd;
1683 cmd.Format(_T("git.exe ls-files -u -t -z -- \"%s\""),merge.GetGitPathString());
1685 if (g_Git.Run(cmd, &vector))
1687 return FALSE;
1690 if (merge.IsDirectory())
1692 CString baseHash, realBaseHash(GIT_REV_ZERO), localHash(GIT_REV_ZERO), remoteHash(GIT_REV_ZERO);
1693 if (merge.HasAdminDir()) {
1694 CGit subgit;
1695 subgit.m_CurrentDir = g_Git.CombinePath(merge);
1696 CGitHash hash;
1697 subgit.GetHash(hash, _T("HEAD"));
1698 baseHash = hash;
1700 if (ParseHashesFromLsFile(vector, realBaseHash, localHash, remoteHash)) // in base no submodule, but in remote submodule
1701 baseHash = realBaseHash;
1703 CGitDiff::ChangeType changeTypeMine = CGitDiff::Unknown;
1704 CGitDiff::ChangeType changeTypeTheirs = CGitDiff::Unknown;
1706 bool baseOK = false, mineOK = false, theirsOK = false;
1707 CString baseSubject, mineSubject, theirsSubject;
1708 if (merge.HasAdminDir())
1710 CGit subgit;
1711 subgit.m_CurrentDir = g_Git.CombinePath(merge);
1712 CGitDiff::GetSubmoduleChangeType(subgit, baseHash, localHash, baseOK, mineOK, changeTypeMine, baseSubject, mineSubject);
1713 CGitDiff::GetSubmoduleChangeType(subgit, baseHash, remoteHash, baseOK, theirsOK, changeTypeTheirs, baseSubject, theirsSubject);
1715 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)
1717 changeTypeMine = CGitDiff::Identical;
1718 changeTypeTheirs = CGitDiff::NewSubmodule;
1719 baseSubject = _T("no submodule");
1720 mineSubject = baseSubject;
1721 theirsSubject = _T("not initialized");
1723 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
1725 baseHash = localHash;
1726 baseSubject = _T("not initialized");
1727 mineSubject = baseSubject;
1728 theirsSubject = _T("not initialized");
1729 changeTypeMine = CGitDiff::Identical;
1730 changeTypeTheirs = CGitDiff::DeleteSubmodule;
1732 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
1734 baseSubject = _T("not initialized");
1735 mineSubject = baseSubject;
1736 theirsSubject = baseSubject;
1737 if (baseHash == localHash)
1738 changeTypeMine = CGitDiff::Identical;
1740 else
1741 return FALSE;
1743 CSubmoduleResolveConflictDlg resolveSubmoduleConflictDialog;
1744 resolveSubmoduleConflictDialog.SetDiff(merge.GetGitPathString(), revertTheirMy, baseHash, baseSubject, baseOK, localHash, mineSubject, mineOK, changeTypeMine, remoteHash, theirsSubject, theirsOK, changeTypeTheirs);
1745 resolveSubmoduleConflictDialog.DoModal();
1746 if (resolveSubmoduleConflictDialog.m_bResolved && resolveMsgHwnd)
1748 static UINT WM_REVERTMSG = RegisterWindowMessage(_T("GITSLNM_NEEDSREFRESH"));
1749 ::PostMessage(resolveMsgHwnd, WM_REVERTMSG, NULL, NULL);
1752 return TRUE;
1755 CTGitPathList list;
1756 if (list.ParserFromLsFile(vector))
1758 CMessageBox::Show(NULL, _T("Parse ls-files failed!"), _T("TortoiseGit"), MB_OK);
1759 return FALSE;
1762 if (list.IsEmpty())
1763 return FALSE;
1765 CTGitPath theirs;
1766 CTGitPath mine;
1767 CTGitPath base;
1769 if (revertTheirMy)
1771 mine.SetFromGit(GetMergeTempFile(_T("REMOTE"), merge));
1772 theirs.SetFromGit(GetMergeTempFile(_T("LOCAL"), merge));
1774 else
1776 mine.SetFromGit(GetMergeTempFile(_T("LOCAL"), merge));
1777 theirs.SetFromGit(GetMergeTempFile(_T("REMOTE"), merge));
1779 base.SetFromGit(GetMergeTempFile(_T("BASE"),merge));
1781 CString format;
1783 //format=_T("git.exe cat-file blob \":%d:%s\"");
1784 format = _T("git.exe checkout-index --temp --stage=%d -- \"%s\"");
1785 CFile tempfile;
1786 //create a empty file, incase stage is not three
1787 tempfile.Open(mine.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1788 tempfile.Close();
1789 tempfile.Open(theirs.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1790 tempfile.Close();
1791 tempfile.Open(base.GetWinPathString(),CFile::modeCreate|CFile::modeReadWrite);
1792 tempfile.Close();
1794 bool b_base=false, b_local=false, b_remote=false;
1796 for (int i = 0; i< list.GetCount(); ++i)
1798 CString cmd;
1799 CString outfile;
1800 cmd.Empty();
1801 outfile.Empty();
1803 if( list[i].m_Stage == 1)
1805 cmd.Format(format, list[i].m_Stage, list[i].GetGitPathString());
1806 b_base = true;
1807 outfile = base.GetWinPathString();
1810 if( list[i].m_Stage == 2 )
1812 cmd.Format(format, list[i].m_Stage, list[i].GetGitPathString());
1813 b_local = true;
1814 outfile = mine.GetWinPathString();
1817 if( list[i].m_Stage == 3 )
1819 cmd.Format(format, list[i].m_Stage, list[i].GetGitPathString());
1820 b_remote = true;
1821 outfile = theirs.GetWinPathString();
1823 CString output, err;
1824 if(!outfile.IsEmpty())
1825 if (!g_Git.Run(cmd, &output, &err, CP_UTF8))
1827 CString file;
1828 int start =0 ;
1829 file = output.Tokenize(_T("\t"), start);
1830 ::MoveFileEx(file,outfile,MOVEFILE_REPLACE_EXISTING|MOVEFILE_COPY_ALLOWED);
1832 else
1834 CMessageBox::Show(NULL, output + L"\n" + err, _T("TortoiseGit"), MB_OK|MB_ICONERROR);
1838 if(b_local && b_remote )
1840 merge.SetFromWin(g_Git.CombinePath(merge));
1841 if( revertTheirMy )
1842 bRet = !!CAppUtils::StartExtMerge(base, mine, theirs, merge, _T("BASE"), _T("REMOTE"), _T("LOCAL"), CString(), false, resolveMsgHwnd, true);
1843 else
1844 bRet = !!CAppUtils::StartExtMerge(base, theirs, mine, merge, _T("BASE"), _T("REMOTE"), _T("LOCAL"), CString(), false, resolveMsgHwnd, true);
1847 else
1849 ::DeleteFile(mine.GetWinPathString());
1850 ::DeleteFile(theirs.GetWinPathString());
1851 ::DeleteFile(base.GetWinPathString());
1853 CDeleteConflictDlg dlg;
1854 DescribeConflictFile(b_local, b_base,dlg.m_LocalStatus);
1855 DescribeConflictFile(b_remote,b_base,dlg.m_RemoteStatus);
1856 CGitHash localHash, remoteHash;
1857 if (!g_Git.GetHash(localHash, _T("HEAD")))
1858 dlg.m_LocalHash = localHash.ToString();
1859 if (!g_Git.GetHash(remoteHash, _T("MERGE_HEAD")))
1860 dlg.m_RemoteHash = remoteHash.ToString();
1861 else if (!g_Git.GetHash(remoteHash, _T("rebase-apply/original-commit")))
1862 dlg.m_RemoteHash = remoteHash.ToString();
1863 else if (!g_Git.GetHash(remoteHash, _T("CHERRY_PICK_HEAD")))
1864 dlg.m_RemoteHash = remoteHash.ToString();
1865 else if (!g_Git.GetHash(remoteHash, _T("REVERT_HEAD")))
1866 dlg.m_RemoteHash = remoteHash.ToString();
1867 dlg.m_bShowModifiedButton=b_base;
1868 dlg.m_File=merge.GetGitPathString();
1869 if(dlg.DoModal() == IDOK)
1871 CString cmd,out;
1872 if(dlg.m_bIsDelete)
1874 cmd.Format(_T("git.exe rm -- \"%s\""),merge.GetGitPathString());
1876 else
1877 cmd.Format(_T("git.exe add -- \"%s\""),merge.GetGitPathString());
1879 if (g_Git.Run(cmd, &out, CP_UTF8))
1881 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
1882 return FALSE;
1884 return TRUE;
1886 else
1887 return FALSE;
1890 #if 0
1891 CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool),
1892 base, theirs, mine, merge);
1893 #endif
1894 #if 0
1895 if (stat.status->text_status == svn_wc_status_conflicted)
1897 // we have a text conflict, use our merge tool to resolve the conflict
1899 CTSVNPath theirs(directory);
1900 CTSVNPath mine(directory);
1901 CTSVNPath base(directory);
1902 bool bConflictData = false;
1904 if ((stat.status->entry)&&(stat.status->entry->conflict_new))
1906 theirs.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->conflict_new));
1907 bConflictData = true;
1909 if ((stat.status->entry)&&(stat.status->entry->conflict_old))
1911 base.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->conflict_old));
1912 bConflictData = true;
1914 if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
1916 mine.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->conflict_wrk));
1917 bConflictData = true;
1919 else
1921 mine = merge;
1923 if (bConflictData)
1924 bRet = !!CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool),
1925 base, theirs, mine, merge);
1928 if (stat.status->prop_status == svn_wc_status_conflicted)
1930 // we have a property conflict
1931 CTSVNPath prej(directory);
1932 if ((stat.status->entry)&&(stat.status->entry->prejfile))
1934 prej.AppendPathString(CUnicodeUtils::GetUnicode(stat.status->entry->prejfile));
1935 // there's a problem: the prej file contains a _description_ of the conflict, and
1936 // that description string might be translated. That means we have no way of parsing
1937 // the file to find out the conflicting values.
1938 // The only thing we can do: show a dialog with the conflict description, then
1939 // let the user either accept the existing property or open the property edit dialog
1940 // to manually change the properties and values. And a button to mark the conflict as
1941 // resolved.
1942 CEditPropConflictDlg dlg;
1943 dlg.SetPrejFile(prej);
1944 dlg.SetConflictedItem(merge);
1945 bRet = (dlg.DoModal() != IDCANCEL);
1949 if (stat.status->tree_conflict)
1951 // we have a tree conflict
1952 SVNInfo info;
1953 const SVNInfoData * pInfoData = info.GetFirstFileInfo(merge, SVNRev(), SVNRev());
1954 if (pInfoData)
1956 if (pInfoData->treeconflict_kind == svn_wc_conflict_kind_text)
1958 CTSVNPath theirs(directory);
1959 CTSVNPath mine(directory);
1960 CTSVNPath base(directory);
1961 bool bConflictData = false;
1963 if (pInfoData->treeconflict_theirfile)
1965 theirs.AppendPathString(pInfoData->treeconflict_theirfile);
1966 bConflictData = true;
1968 if (pInfoData->treeconflict_basefile)
1970 base.AppendPathString(pInfoData->treeconflict_basefile);
1971 bConflictData = true;
1973 if (pInfoData->treeconflict_myfile)
1975 mine.AppendPathString(pInfoData->treeconflict_myfile);
1976 bConflictData = true;
1978 else
1980 mine = merge;
1982 if (bConflictData)
1983 bRet = !!CAppUtils::StartExtMerge(CAppUtils::MergeFlags().AlternativeTool(bAlternativeTool),
1984 base, theirs, mine, merge);
1986 else if (pInfoData->treeconflict_kind == svn_wc_conflict_kind_tree)
1988 CString sConflictAction;
1989 CString sConflictReason;
1990 CString sResolveTheirs;
1991 CString sResolveMine;
1992 CTSVNPath treeConflictPath = CTSVNPath(pInfoData->treeconflict_path);
1993 CString sItemName = treeConflictPath.GetUIFileOrDirectoryName();
1995 if (pInfoData->treeconflict_nodekind == svn_node_file)
1997 switch (pInfoData->treeconflict_operation)
1999 case svn_wc_operation_update:
2000 switch (pInfoData->treeconflict_action)
2002 case svn_wc_conflict_action_edit:
2003 sConflictAction.Format(IDS_TREECONFLICT_FILEUPDATEEDIT, (LPCTSTR)sItemName);
2004 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2005 break;
2006 case svn_wc_conflict_action_add:
2007 sConflictAction.Format(IDS_TREECONFLICT_FILEUPDATEADD, (LPCTSTR)sItemName);
2008 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2009 break;
2010 case svn_wc_conflict_action_delete:
2011 sConflictAction.Format(IDS_TREECONFLICT_FILEUPDATEDELETE, (LPCTSTR)sItemName);
2012 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2013 break;
2015 break;
2016 case svn_wc_operation_switch:
2017 switch (pInfoData->treeconflict_action)
2019 case svn_wc_conflict_action_edit:
2020 sConflictAction.Format(IDS_TREECONFLICT_FILESWITCHEDIT, (LPCTSTR)sItemName);
2021 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2022 break;
2023 case svn_wc_conflict_action_add:
2024 sConflictAction.Format(IDS_TREECONFLICT_FILESWITCHADD, (LPCTSTR)sItemName);
2025 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2026 break;
2027 case svn_wc_conflict_action_delete:
2028 sConflictAction.Format(IDS_TREECONFLICT_FILESWITCHDELETE, (LPCTSTR)sItemName);
2029 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2030 break;
2032 break;
2033 case svn_wc_operation_merge:
2034 switch (pInfoData->treeconflict_action)
2036 case svn_wc_conflict_action_edit:
2037 sConflictAction.Format(IDS_TREECONFLICT_FILEMERGEEDIT, (LPCTSTR)sItemName);
2038 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2039 break;
2040 case svn_wc_conflict_action_add:
2041 sResolveTheirs.Format(IDS_TREECONFLICT_FILEMERGEADD, (LPCTSTR)sItemName);
2042 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYFILE);
2043 break;
2044 case svn_wc_conflict_action_delete:
2045 sConflictAction.Format(IDS_TREECONFLICT_FILEMERGEDELETE, (LPCTSTR)sItemName);
2046 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2047 break;
2049 break;
2052 else if (pInfoData->treeconflict_nodekind == svn_node_dir)
2054 switch (pInfoData->treeconflict_operation)
2056 case svn_wc_operation_update:
2057 switch (pInfoData->treeconflict_action)
2059 case svn_wc_conflict_action_edit:
2060 sConflictAction.Format(IDS_TREECONFLICT_DIRUPDATEEDIT, (LPCTSTR)sItemName);
2061 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2062 break;
2063 case svn_wc_conflict_action_add:
2064 sConflictAction.Format(IDS_TREECONFLICT_DIRUPDATEADD, (LPCTSTR)sItemName);
2065 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2066 break;
2067 case svn_wc_conflict_action_delete:
2068 sConflictAction.Format(IDS_TREECONFLICT_DIRUPDATEDELETE, (LPCTSTR)sItemName);
2069 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR);
2070 break;
2072 break;
2073 case svn_wc_operation_switch:
2074 switch (pInfoData->treeconflict_action)
2076 case svn_wc_conflict_action_edit:
2077 sConflictAction.Format(IDS_TREECONFLICT_DIRSWITCHEDIT, (LPCTSTR)sItemName);
2078 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2079 break;
2080 case svn_wc_conflict_action_add:
2081 sConflictAction.Format(IDS_TREECONFLICT_DIRSWITCHADD, (LPCTSTR)sItemName);
2082 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2083 break;
2084 case svn_wc_conflict_action_delete:
2085 sConflictAction.Format(IDS_TREECONFLICT_DIRSWITCHDELETE, (LPCTSTR)sItemName);
2086 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR);
2087 break;
2089 break;
2090 case svn_wc_operation_merge:
2091 switch (pInfoData->treeconflict_action)
2093 case svn_wc_conflict_action_edit:
2094 sConflictAction.Format(IDS_TREECONFLICT_DIRMERGEEDIT, (LPCTSTR)sItemName);
2095 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2096 break;
2097 case svn_wc_conflict_action_add:
2098 sConflictAction.Format(IDS_TREECONFLICT_DIRMERGEADD, (LPCTSTR)sItemName);
2099 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_KEEPREPOSITORYDIR);
2100 break;
2101 case svn_wc_conflict_action_delete:
2102 sConflictAction.Format(IDS_TREECONFLICT_DIRMERGEDELETE, (LPCTSTR)sItemName);
2103 sResolveTheirs.LoadString(IDS_TREECONFLICT_RESOLVE_REMOVEDIR);
2104 break;
2106 break;
2110 UINT uReasonID = 0;
2111 switch (pInfoData->treeconflict_reason)
2113 case svn_wc_conflict_reason_edited:
2114 uReasonID = IDS_TREECONFLICT_REASON_EDITED;
2115 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2116 break;
2117 case svn_wc_conflict_reason_obstructed:
2118 uReasonID = IDS_TREECONFLICT_REASON_OBSTRUCTED;
2119 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2120 break;
2121 case svn_wc_conflict_reason_deleted:
2122 uReasonID = IDS_TREECONFLICT_REASON_DELETED;
2123 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_REMOVEDIR : IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2124 break;
2125 case svn_wc_conflict_reason_added:
2126 uReasonID = IDS_TREECONFLICT_REASON_ADDED;
2127 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2128 break;
2129 case svn_wc_conflict_reason_missing:
2130 uReasonID = IDS_TREECONFLICT_REASON_MISSING;
2131 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_REMOVEDIR : IDS_TREECONFLICT_RESOLVE_REMOVEFILE);
2132 break;
2133 case svn_wc_conflict_reason_unversioned:
2134 uReasonID = IDS_TREECONFLICT_REASON_UNVERSIONED;
2135 sResolveMine.LoadString(pInfoData->treeconflict_nodekind == svn_node_dir ? IDS_TREECONFLICT_RESOLVE_KEEPLOCALDIR : IDS_TREECONFLICT_RESOLVE_KEEPLOCALFILE);
2136 break;
2138 sConflictReason.Format(uReasonID, (LPCTSTR)sConflictAction);
2140 CTreeConflictEditorDlg dlg;
2141 dlg.SetConflictInfoText(sConflictReason);
2142 dlg.SetResolveTexts(sResolveTheirs, sResolveMine);
2143 dlg.SetPath(treeConflictPath);
2144 INT_PTR dlgRet = dlg.DoModal();
2145 bRet = (dlgRet != IDCANCEL);
2149 #endif
2150 return bRet;
2153 bool CAppUtils::IsSSHPutty()
2155 CString sshclient=g_Git.m_Environment.GetEnv(_T("GIT_SSH"));
2156 sshclient=sshclient.MakeLower();
2157 if(sshclient.Find(_T("plink.exe"),0)>=0)
2159 return true;
2161 return false;
2164 CString CAppUtils::GetClipboardLink(const CString &skipGitPrefix, int paramsCount)
2166 if (!OpenClipboard(NULL))
2167 return CString();
2169 CString sClipboardText;
2170 HGLOBAL hglb = GetClipboardData(CF_TEXT);
2171 if (hglb)
2173 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
2174 sClipboardText = CString(lpstr);
2175 GlobalUnlock(hglb);
2177 hglb = GetClipboardData(CF_UNICODETEXT);
2178 if (hglb)
2180 LPCTSTR lpstr = (LPCTSTR)GlobalLock(hglb);
2181 sClipboardText = lpstr;
2182 GlobalUnlock(hglb);
2184 CloseClipboard();
2186 if(!sClipboardText.IsEmpty())
2188 if(sClipboardText[0] == _T('\"') && sClipboardText[sClipboardText.GetLength()-1] == _T('\"'))
2189 sClipboardText=sClipboardText.Mid(1,sClipboardText.GetLength()-2);
2191 if(sClipboardText.Find( _T("http://")) == 0)
2192 return sClipboardText;
2194 if(sClipboardText.Find( _T("https://")) == 0)
2195 return sClipboardText;
2197 if(sClipboardText.Find( _T("git://")) == 0)
2198 return sClipboardText;
2200 if(sClipboardText.Find( _T("ssh://")) == 0)
2201 return sClipboardText;
2203 if (sClipboardText.Find(_T("git@")) == 0)
2204 return sClipboardText;
2206 if(sClipboardText.GetLength()>=2)
2207 if( sClipboardText[1] == _T(':') )
2208 if( (sClipboardText[0] >= 'A' && sClipboardText[0] <= 'Z')
2209 || (sClipboardText[0] >= 'a' && sClipboardText[0] <= 'z') )
2210 return sClipboardText;
2212 // trim prefixes like "git clone "
2213 if (!skipGitPrefix.IsEmpty() && sClipboardText.Find(skipGitPrefix) == 0)
2215 sClipboardText = sClipboardText.Mid(skipGitPrefix.GetLength()).Trim();
2216 int spacePos = -1;
2217 while (paramsCount >= 0)
2219 --paramsCount;
2220 spacePos = sClipboardText.Find(_T(' '), spacePos + 1);
2221 if (spacePos == -1)
2222 break;
2224 if (spacePos > 0 && paramsCount < 0)
2225 sClipboardText = sClipboardText.Left(spacePos);
2226 return sClipboardText;
2230 return CString(_T(""));
2233 CString CAppUtils::ChooseRepository(const CString* path)
2235 CBrowseFolder browseFolder;
2236 CRegString regLastResopitory = CRegString(_T("Software\\TortoiseGit\\TortoiseProc\\LastRepo"),_T(""));
2238 browseFolder.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
2239 CString strCloneDirectory;
2240 if(path)
2241 strCloneDirectory=*path;
2242 else
2244 strCloneDirectory = regLastResopitory;
2247 CString title;
2248 title.LoadString(IDS_CHOOSE_REPOSITORY);
2250 browseFolder.SetInfo(title);
2252 if (browseFolder.Show(NULL, strCloneDirectory) == CBrowseFolder::OK)
2254 regLastResopitory = strCloneDirectory;
2255 return strCloneDirectory;
2257 else
2259 return CString();
2263 bool CAppUtils::SendPatchMail(CTGitPathList& list, bool bIsMainWnd)
2265 CSendMailDlg dlg;
2267 dlg.m_PathList = list;
2269 if(dlg.DoModal()==IDOK)
2271 if (dlg.m_PathList.IsEmpty())
2272 return FALSE;
2274 CGitProgressDlg progDlg;
2275 if (bIsMainWnd)
2276 theApp.m_pMainWnd = &progDlg;
2277 SendMailProgressCommand sendMailProgressCommand;
2278 progDlg.SetCommand(&sendMailProgressCommand);
2280 sendMailProgressCommand.SetPathList(dlg.m_PathList);
2281 progDlg.SetItemCount(dlg.m_PathList.GetCount());
2283 CSendMailPatch sendMailPatch(dlg.m_To, dlg.m_CC, dlg.m_Subject, !!dlg.m_bAttachment, !!dlg.m_bCombine);
2284 sendMailProgressCommand.SetSendMailOption(&sendMailPatch);
2286 progDlg.DoModal();
2288 return true;
2290 return false;
2293 bool CAppUtils::SendPatchMail(const CString& cmd, const CString& formatpatchoutput, bool bIsMainWnd)
2295 CTGitPathList list;
2296 CString log=formatpatchoutput;
2297 int start=log.Find(cmd);
2298 if(start >=0)
2299 CString one=log.Tokenize(_T("\n"),start);
2300 else
2301 start = 0;
2303 while(start>=0)
2305 CString one=log.Tokenize(_T("\n"),start);
2306 one=one.Trim();
2307 if(one.IsEmpty() || one.Find(_T("Success")) == 0)
2308 continue;
2309 one.Replace(_T('/'),_T('\\'));
2310 CTGitPath path;
2311 path.SetFromWin(one);
2312 list.AddPath(path);
2314 if (!list.IsEmpty())
2316 return SendPatchMail(list, bIsMainWnd);
2318 else
2320 CMessageBox::Show(NULL, IDS_ERR_NOPATCHES, IDS_APPNAME, MB_ICONINFORMATION);
2321 return true;
2326 int CAppUtils::GetLogOutputEncode(CGit *pGit)
2328 CString output;
2329 output = pGit->GetConfigValue(_T("i18n.logOutputEncoding"));
2330 if(output.IsEmpty())
2331 return CUnicodeUtils::GetCPCode(pGit->GetConfigValue(_T("i18n.commitencoding")));
2332 else
2334 return CUnicodeUtils::GetCPCode(output);
2337 int CAppUtils::SaveCommitUnicodeFile(const CString& filename, CString &message)
2341 CFile file(filename, CFile::modeReadWrite | CFile::modeCreate);
2342 int cp = CUnicodeUtils::GetCPCode(g_Git.GetConfigValue(_T("i18n.commitencoding")));
2344 bool stripComments = (CRegDWORD(_T("Software\\TortoiseGit\\StripCommentedLines"), FALSE) == TRUE);
2346 if (CRegDWORD(_T("Software\\TortoiseGit\\SanitizeCommitMsg"), TRUE) == TRUE)
2347 message.TrimRight(L" \r\n");
2349 int len = message.GetLength();
2350 int start = 0;
2351 while (start >= 0 && start < len)
2353 int oldStart = start;
2354 start = message.Find(L"\n", oldStart);
2355 CString line = message.Mid(oldStart);
2356 if (start != -1)
2358 line = line.Left(start - oldStart);
2359 ++start; // move forward so we don't find the same char again
2361 if (stripComments && (line.GetLength() >= 1 && line.GetAt(0) == '#') || (start < 0 && line.IsEmpty()))
2362 continue;
2363 line.TrimRight(L" \r");
2364 CStringA lineA = CUnicodeUtils::GetMulti(line + L"\n", cp);
2365 file.Write(lineA.GetBuffer(), lineA.GetLength());
2367 file.Close();
2368 return 0;
2370 catch (CFileException *e)
2372 e->Delete();
2373 return -1;
2377 bool CAppUtils::Pull(bool showPush, bool showStashPop)
2379 CPullFetchDlg dlg;
2380 dlg.m_IsPull = TRUE;
2381 if (dlg.DoModal() == IDOK)
2383 // "git.exe pull --rebase" is not supported, never and ever. So, adapting it to Fetch & Rebase.
2384 if (dlg.m_bRebase)
2385 return DoFetch(dlg.m_RemoteURL,
2386 FALSE, // Fetch all remotes
2387 dlg.m_bAutoLoad == BST_CHECKED,
2388 dlg.m_bPrune,
2389 dlg.m_bDepth == BST_CHECKED,
2390 dlg.m_nDepth,
2391 dlg.m_bFetchTags,
2392 dlg.m_RemoteBranchName,
2393 TRUE); // Rebase after fetching
2395 CString url = dlg.m_RemoteURL;
2397 if (dlg.m_bAutoLoad)
2399 CAppUtils::LaunchPAgent(NULL, &dlg.m_RemoteURL);
2402 CString cmd;
2403 CGitHash hashOld;
2404 if (g_Git.GetHash(hashOld, _T("HEAD")))
2406 MessageBox(NULL, g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
2407 return false;
2410 CString cmdRebase;
2411 CString noff;
2412 CString ffonly;
2413 CString squash;
2414 CString nocommit;
2415 CString depth;
2416 CString notags;
2417 CString prune;
2419 if (!dlg.m_bFetchTags)
2420 notags = _T("--no-tags ");
2422 if (dlg.m_bFetchTags == TRUE)
2423 notags = _T("--tags ");
2425 if (dlg.m_bNoFF)
2426 noff=_T("--no-ff ");
2428 if (dlg.m_bFFonly)
2429 ffonly = _T("--ff-only ");
2431 if (dlg.m_bSquash)
2432 squash = _T("--squash ");
2434 if (dlg.m_bNoCommit)
2435 nocommit = _T("--no-commit ");
2437 if (dlg.m_bDepth)
2438 depth.Format(_T("--depth %d "), dlg.m_nDepth);
2440 int ver = CAppUtils::GetMsysgitVersion();
2442 if (dlg.m_bPrune == TRUE)
2443 prune = _T("--prune ");
2444 else if (dlg.m_bPrune == FALSE && ver >= 0x01080500)
2445 prune = _T("--no-prune ");
2447 if(ver >= 0x01070203) //above 1.7.0.2
2448 cmdRebase += _T("--progress ");
2450 cmd.Format(_T("git.exe pull -v --no-rebase %s%s%s%s%s%s%s%s\"%s\" %s"), cmdRebase, noff, ffonly, squash, nocommit, depth, notags, prune, url, dlg.m_RemoteBranchName);
2451 CProgressDlg progress;
2452 progress.m_GitCmd = cmd;
2454 CGitHash hashNew; // declare outside lambda, because it is captured by reference
2455 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2457 if (status)
2459 postCmdList.push_back(PostCmd(IDI_PULL, IDS_MENUPULL, [&]{ Pull(); }));
2460 postCmdList.push_back(PostCmd(IDI_COMMIT, IDS_MENUSTASHSAVE, [&]{ StashSave(_T(""), true); }));
2461 return;
2464 if (showStashPop)
2465 postCmdList.push_back(PostCmd(IDI_RELOCATE, IDS_MENUSTASHPOP, []{ StashPop(); }));
2467 if (g_Git.GetHash(hashNew, _T("HEAD")))
2468 MessageBox(nullptr, g_Git.GetGitLastErr(_T("Could not get HEAD hash after pulling.")), _T("TortoiseGit"), MB_ICONERROR);
2469 else
2471 postCmdList.push_back(PostCmd(IDI_DIFF, IDS_PROC_PULL_DIFFS, [&]
2473 CFileDiffDlg dlg;
2474 dlg.SetDiff(NULL, hashNew.ToString(), hashOld.ToString());
2475 dlg.DoModal();
2476 }));
2477 postCmdList.push_back(PostCmd(IDI_LOG, IDS_PROC_PULL_LOG, [&]
2479 CLogDlg dlg;
2480 dlg.SetParams(CTGitPath(_T("")), CTGitPath(_T("")), _T(""), hashOld.ToString() + _T("..") + hashNew.ToString(), 0);
2481 dlg.DoModal();
2482 }));
2485 if (showPush)
2486 postCmdList.push_back(PostCmd(IDI_PUSH, IDS_MENUPUSH, []{ Push(_T("")); }));
2488 CTGitPath gitPath = g_Git.m_CurrentDir;
2489 if (gitPath.HasSubmodules())
2491 postCmdList.push_back(PostCmd(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
2493 CString sCmd;
2494 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), g_Git.m_CurrentDir);
2495 CAppUtils::RunTortoiseGitProc(sCmd);
2496 }));
2500 INT_PTR ret = progress.DoModal();
2502 if (ret == IDOK && progress.m_GitStatus == 1 && progress.m_LogText.Find(_T("CONFLICT")) >= 0 && CMessageBox::Show(NULL, IDS_SEECHANGES, IDS_APPNAME, MB_YESNO | MB_ICONINFORMATION) == IDYES)
2504 CChangedDlg dlg;
2505 dlg.m_pathList.AddPath(CTGitPath());
2506 dlg.DoModal();
2508 return true;
2511 return ret == IDOK;
2514 return false;
2517 bool CAppUtils::RebaseAfterFetch(const CString& upstream)
2519 while (true)
2521 CRebaseDlg dlg;
2522 if (!upstream.IsEmpty())
2523 dlg.m_Upstream = upstream;
2524 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENULOG)));
2525 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUDESSENDMAIL)));
2526 dlg.m_PostButtonTexts.Add(CString(MAKEINTRESOURCE(IDS_MENUREBASE)));
2527 INT_PTR response = dlg.DoModal();
2528 if (response == IDOK)
2530 return true;
2532 else if (response == IDC_REBASE_POST_BUTTON)
2534 CString cmd = _T("/command:log");
2535 cmd += _T(" /path:\"") + g_Git.m_CurrentDir + _T("\"");
2536 CAppUtils::RunTortoiseGitProc(cmd);
2537 return true;
2539 else if (response == IDC_REBASE_POST_BUTTON + 1)
2541 CString cmd, out, err;
2542 cmd.Format(_T("git.exe format-patch -o \"%s\" %s..%s"),
2543 g_Git.m_CurrentDir,
2544 g_Git.FixBranchName(dlg.m_Upstream),
2545 g_Git.FixBranchName(dlg.m_Branch));
2546 if (g_Git.Run(cmd, &out, &err, CP_UTF8))
2548 CMessageBox::Show(NULL, out + L"\n" + err, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
2549 return false;
2551 CAppUtils::SendPatchMail(cmd, out);
2552 return true;
2554 else if (response == IDC_REBASE_POST_BUTTON + 2)
2555 continue;
2556 else if (response == IDCANCEL)
2557 return false;
2558 return false;
2562 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, boolean runRebase)
2564 if (loadPuttyAgent)
2566 if (fetchAllRemotes)
2568 STRING_VECTOR list;
2569 g_Git.GetRemoteList(list);
2571 STRING_VECTOR::const_iterator it = list.begin();
2572 while (it != list.end())
2574 CString remote(*it);
2575 CAppUtils::LaunchPAgent(NULL, &remote);
2576 ++it;
2579 else
2580 CAppUtils::LaunchPAgent(NULL, &url);
2583 CString cmd, arg;
2584 int ver = CAppUtils::GetMsysgitVersion();
2585 if (ver >= 0x01070203) //above 1.7.0.2
2586 arg += _T(" --progress");
2588 if (bDepth)
2589 arg.AppendFormat(_T(" --depth %d"), nDepth);
2591 if (prune == TRUE)
2592 arg += _T(" --prune");
2593 else if (prune == FALSE && ver >= 0x01080500)
2594 arg += _T(" --no-prune");
2596 if (fetchTags == 1)
2597 arg += _T(" --tags");
2598 else if (fetchTags == 0)
2599 arg += _T(" --no-tags");
2601 if (fetchAllRemotes)
2602 cmd.Format(_T("git.exe fetch --all -v%s"), arg);
2603 else
2604 cmd.Format(_T("git.exe fetch -v%s \"%s\" %s"), arg, url, remoteBranch);
2606 CProgressDlg progress;
2607 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2609 if (status)
2611 postCmdList.push_back(PostCmd(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ DoFetch(url, fetchAllRemotes, loadPuttyAgent, prune, bDepth, nDepth, fetchTags, remoteBranch, runRebase); }));
2612 return;
2615 postCmdList.push_back(PostCmd(IDI_LOG, IDS_MENULOG, []
2617 CString cmd = _T("/command:log");
2618 cmd += _T(" /path:\"") + g_Git.m_CurrentDir + _T("\"");
2619 CAppUtils::RunTortoiseGitProc(cmd);
2620 }));
2622 postCmdList.push_back(PostCmd(IDI_REVERT, IDS_PROC_RESET, []
2624 CString pullRemote, pullBranch;
2625 g_Git.GetRemoteTrackedBranchForHEAD(pullRemote, pullBranch);
2626 CString defaultUpstream;
2627 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
2628 defaultUpstream.Format(_T("remotes/%s/%s"), pullRemote, pullBranch);
2629 CAppUtils::GitReset(&defaultUpstream, 2);
2630 }));
2632 postCmdList.push_back(PostCmd(IDI_PULL, IDS_MENUFETCH, []{ CAppUtils::Fetch(); }));
2634 if (!runRebase && !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir))
2635 postCmdList.push_back(PostCmd(IDI_REBASE, IDS_MENUREBASE, [&]{ runRebase = false; CAppUtils::RebaseAfterFetch(); }));
2638 progress.m_GitCmd = cmd;
2639 INT_PTR userResponse;
2641 if (g_Git.UsingLibGit2(CGit::GIT_CMD_FETCH))
2643 CGitProgressDlg gitdlg;
2644 FetchProgressCommand fetchProgressCommand;
2645 if (!fetchAllRemotes)
2646 fetchProgressCommand.SetUrl(url);
2647 gitdlg.SetCommand(&fetchProgressCommand);
2648 fetchProgressCommand.m_PostCmdCallback = progress.m_PostCmdCallback;
2649 fetchProgressCommand.SetAutoTag(fetchTags == 1 ? GIT_REMOTE_DOWNLOAD_TAGS_ALL : fetchTags == 2 ? GIT_REMOTE_DOWNLOAD_TAGS_AUTO : GIT_REMOTE_DOWNLOAD_TAGS_NONE);
2650 if (!fetchAllRemotes)
2651 fetchProgressCommand.SetRefSpec(remoteBranch);
2652 userResponse = gitdlg.DoModal();
2653 return userResponse == IDOK;
2656 userResponse = progress.DoModal();
2657 if (!progress.m_GitStatus)
2659 if (runRebase)
2660 return CAppUtils::RebaseAfterFetch();
2663 return userResponse == IDOK;
2666 bool CAppUtils::Fetch(const CString& remoteName, bool allRemotes)
2668 CPullFetchDlg dlg;
2669 dlg.m_PreSelectRemote = remoteName;
2670 dlg.m_IsPull=FALSE;
2671 dlg.m_bAllRemotes = allRemotes;
2673 if(dlg.DoModal()==IDOK)
2674 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);
2676 return false;
2679 bool CAppUtils::Push(const CString& selectLocalBranch)
2681 CPushDlg dlg;
2682 dlg.m_BranchSourceName = selectLocalBranch;
2684 if (dlg.DoModal() == IDOK)
2686 CString error;
2687 DWORD exitcode = 0xFFFFFFFF;
2688 if (CHooks::Instance().PrePush(g_Git.m_CurrentDir, exitcode, error))
2690 if (exitcode)
2692 CString temp;
2693 temp.Format(IDS_ERR_HOOKFAILED, (LPCTSTR)error);
2694 CMessageBox::Show(nullptr, temp, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
2695 return false;
2699 CString arg;
2701 if(dlg.m_bPack)
2702 arg += _T("--thin ");
2703 if(dlg.m_bTags && !dlg.m_bPushAllBranches)
2704 arg += _T("--tags ");
2705 if(dlg.m_bForce)
2706 arg += _T("--force ");
2707 if (dlg.m_bForceWithLease)
2708 arg += _T("--force-with-lease ");
2709 if (dlg.m_bSetUpstream)
2710 arg += _T("--set-upstream ");
2711 if (dlg.m_RecurseSubmodules == 1)
2712 arg += _T("--recurse-submodules=check ");
2713 if (dlg.m_RecurseSubmodules == 2)
2714 arg += _T("--recurse-submodules=on-demand ");
2716 int ver = CAppUtils::GetMsysgitVersion();
2718 if(ver >= 0x01070203) //above 1.7.0.2
2719 arg += _T("--progress ");
2721 CProgressDlg progress;
2723 STRING_VECTOR remotesList;
2724 if (dlg.m_bPushAllRemotes)
2725 g_Git.GetRemoteList(remotesList);
2726 else
2727 remotesList.push_back(dlg.m_URL);
2729 for (unsigned int i = 0; i < remotesList.size(); ++i)
2731 if (dlg.m_bAutoLoad)
2732 CAppUtils::LaunchPAgent(NULL, &remotesList[i]);
2734 CString cmd;
2735 if (dlg.m_bPushAllBranches)
2737 cmd.Format(_T("git.exe push --all %s\"%s\""),
2738 arg,
2739 remotesList[i]);
2741 if (dlg.m_bTags)
2743 progress.m_GitCmdList.push_back(cmd);
2744 cmd.Format(_T("git.exe push --tags %s\"%s\""), arg, remotesList[i]);
2747 else
2749 cmd.Format(_T("git.exe push %s\"%s\" %s"),
2750 arg,
2751 remotesList[i],
2752 dlg.m_BranchSourceName);
2753 if (!dlg.m_BranchRemoteName.IsEmpty())
2755 cmd += _T(":") + dlg.m_BranchRemoteName;
2758 progress.m_GitCmdList.push_back(cmd);
2761 CString superprojectRoot;
2762 GitAdminDir::HasAdminDir(g_Git.m_CurrentDir, false, &superprojectRoot);
2763 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
2765 // need to execute hooks as those might be needed by post action commands
2766 DWORD exitcode = 0xFFFFFFFF;
2767 CString error;
2768 if (CHooks::Instance().PostPush(g_Git.m_CurrentDir, exitcode, error))
2770 if (exitcode)
2772 CString temp;
2773 temp.Format(IDS_ERR_HOOKFAILED, (LPCTSTR)error);
2774 MessageBox(nullptr, temp, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
2778 if (status)
2780 bool rejected = progress.GetLogText().Find(_T("! [rejected]")) > 0;
2781 if (rejected)
2783 postCmdList.push_back(PostCmd(IDI_PULL, IDS_MENUPULL, []{ Pull(true); }));
2784 postCmdList.push_back(PostCmd(IDI_PULL, IDS_MENUFETCH, [&]{ Fetch(dlg.m_bPushAllRemotes ? _T("") : dlg.m_URL, !!dlg.m_bPushAllRemotes); }));
2786 postCmdList.push_back(PostCmd(IDI_PUSH, IDS_MENUPUSH, [&]{ Push(selectLocalBranch); }));
2787 return;
2790 postCmdList.push_back(PostCmd(IDS_PROC_REQUESTPULL, [&]{ RequestPull(dlg.m_BranchRemoteName); }));
2791 postCmdList.push_back(PostCmd(IDI_PUSH, IDS_MENUPUSH, [&]{ Push(selectLocalBranch); }));
2792 postCmdList.push_back(PostCmd(IDI_SWITCH, IDS_MENUSWITCH, [&]{ Switch(); }));
2793 if (!superprojectRoot.IsEmpty())
2795 postCmdList.push_back(PostCmd(IDI_COMMIT, IDS_PROC_COMMIT_SUPERPROJECT, [&]
2797 CString sCmd;
2798 sCmd.Format(_T("/command:commit /path:\"%s\""), superprojectRoot);
2799 RunTortoiseGitProc(sCmd);
2800 }));
2804 INT_PTR ret = progress.DoModal();
2805 return ret == IDOK;
2807 return FALSE;
2810 bool CAppUtils::RequestPull(const CString& endrevision, const CString& repositoryUrl, bool bIsMainWnd)
2812 CRequestPullDlg dlg;
2813 dlg.m_RepositoryURL = repositoryUrl;
2814 dlg.m_EndRevision = endrevision;
2815 if (dlg.DoModal()==IDOK)
2817 CString cmd;
2818 cmd.Format(_T("git.exe request-pull %s \"%s\" %s"), dlg.m_StartRevision, dlg.m_RepositoryURL, dlg.m_EndRevision);
2820 CSysProgressDlg sysProgressDlg;
2821 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
2822 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_CREATINGPULLREUQEST)));
2823 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
2824 sysProgressDlg.SetShowProgressBar(false);
2825 sysProgressDlg.ShowModeless((HWND)NULL, true);
2827 CString tempFileName = GetTempFile();
2828 CString err;
2829 DeleteFile(tempFileName);
2830 CreateDirectory(tempFileName, NULL);
2831 tempFileName += _T("\\pullrequest.txt");
2832 if (g_Git.RunLogFile(cmd, tempFileName, &err))
2834 CString msg;
2835 msg.LoadString(IDS_ERR_PULLREUQESTFAILED);
2836 CMessageBox::Show(NULL, msg + _T("\n") + err, _T("TortoiseGit"), MB_OK);
2837 return false;
2840 if (sysProgressDlg.HasUserCancelled())
2842 CMessageBox::Show(NULL, IDS_SVN_USERCANCELLED, IDS_APPNAME, MB_OK);
2843 ::DeleteFile(tempFileName);
2844 return false;
2847 sysProgressDlg.Stop();
2849 if (dlg.m_bSendMail)
2851 CSendMailDlg dlg;
2852 dlg.m_PathList = CTGitPathList(CTGitPath(tempFileName));
2853 dlg.m_bCustomSubject = true;
2855 if (dlg.DoModal() == IDOK)
2857 if (dlg.m_PathList.IsEmpty())
2858 return FALSE;
2860 CGitProgressDlg progDlg;
2861 if (bIsMainWnd)
2862 theApp.m_pMainWnd = &progDlg;
2863 SendMailProgressCommand sendMailProgressCommand;
2864 progDlg.SetCommand(&sendMailProgressCommand);
2866 sendMailProgressCommand.SetPathList(dlg.m_PathList);
2867 progDlg.SetItemCount(dlg.m_PathList.GetCount());
2869 CSendMailCombineable sendMailCombineable(dlg.m_To, dlg.m_CC, dlg.m_Subject, !!dlg.m_bAttachment, !!dlg.m_bCombine);
2870 sendMailProgressCommand.SetSendMailOption(&sendMailCombineable);
2872 progDlg.DoModal();
2874 return true;
2876 return false;
2879 CAppUtils::LaunchAlternativeEditor(tempFileName);
2881 return true;
2884 bool CAppUtils::CreateMultipleDirectory(const CString& szPath)
2886 CString strDir(szPath);
2887 if (strDir.GetAt(strDir.GetLength()-1)!=_T('\\'))
2889 strDir.AppendChar(_T('\\'));
2891 std::vector<CString> vPath;
2892 CString strTemp;
2893 bool bSuccess = false;
2895 for (int i=0;i<strDir.GetLength();++i)
2897 if (strDir.GetAt(i) != _T('\\'))
2899 strTemp.AppendChar(strDir.GetAt(i));
2901 else
2903 vPath.push_back(strTemp);
2904 strTemp.AppendChar(_T('\\'));
2908 for (auto vIter = vPath.begin(); vIter != vPath.end(); ++vIter)
2910 bSuccess = CreateDirectory(*vIter, NULL) ? true : false;
2913 return bSuccess;
2916 void CAppUtils::RemoveTrailSlash(CString &path)
2918 if(path.IsEmpty())
2919 return ;
2921 // For URL, do not trim the slash just after the host name component.
2922 int index = path.Find(_T("://"));
2923 if (index >= 0)
2925 index += 4;
2926 index = path.Find(_T('/'), index);
2927 if (index == path.GetLength() - 1)
2928 return;
2931 while(path[path.GetLength()-1] == _T('\\') || path[path.GetLength()-1] == _T('/' ) )
2933 path=path.Left(path.GetLength()-1);
2934 if(path.IsEmpty())
2935 return;
2939 bool CAppUtils::CheckUserData()
2941 while(g_Git.GetUserName().IsEmpty() || g_Git.GetUserEmail().IsEmpty())
2943 if(CMessageBox::Show(NULL, IDS_PROC_NOUSERDATA, IDS_APPNAME, MB_YESNO| MB_ICONERROR) == IDYES)
2945 CTGitPath path(g_Git.m_CurrentDir);
2946 CSettings dlg(IDS_PROC_SETTINGS_TITLE,&path);
2947 dlg.SetTreeViewMode(TRUE, TRUE, TRUE);
2948 dlg.SetTreeWidth(220);
2949 dlg.m_DefaultPage = _T("gitconfig");
2951 dlg.DoModal();
2952 dlg.HandleRestart();
2955 else
2956 return false;
2959 return true;
2962 BOOL CAppUtils::Commit(const CString& bugid, BOOL bWholeProject, CString &sLogMsg,
2963 CTGitPathList &pathList,
2964 CTGitPathList &selectedList,
2965 bool bSelectFilesForCommit)
2967 bool bFailed = true;
2969 if (!CheckUserData())
2970 return false;
2972 while (bFailed)
2974 bFailed = false;
2975 CCommitDlg dlg;
2976 dlg.m_sBugID = bugid;
2978 dlg.m_bWholeProject = bWholeProject;
2980 dlg.m_sLogMessage = sLogMsg;
2981 dlg.m_pathList = pathList;
2982 dlg.m_checkedPathList = selectedList;
2983 dlg.m_bSelectFilesForCommit = bSelectFilesForCommit;
2984 if (dlg.DoModal() == IDOK)
2986 if (dlg.m_pathList.IsEmpty())
2987 return false;
2988 // if the user hasn't changed the list of selected items
2989 // we don't use that list. Because if we would use the list
2990 // of pre-checked items, the dialog would show different
2991 // checked items on the next startup: it would only try
2992 // to check the parent folder (which might not even show)
2993 // instead, we simply use an empty list and let the
2994 // default checking do its job.
2995 if (!dlg.m_pathList.IsEqual(pathList))
2996 selectedList = dlg.m_pathList;
2997 pathList = dlg.m_updatedPathList;
2998 sLogMsg = dlg.m_sLogMessage;
2999 bSelectFilesForCommit = true;
3001 switch (dlg.m_PostCmd)
3003 case GIT_POSTCOMMIT_CMD_DCOMMIT:
3004 CAppUtils::SVNDCommit();
3005 break;
3006 case GIT_POSTCOMMIT_CMD_PUSH:
3007 CAppUtils::Push();
3008 break;
3009 case GIT_POSTCOMMIT_CMD_CREATETAG:
3010 CAppUtils::CreateBranchTag(TRUE);
3011 break;
3012 case GIT_POSTCOMMIT_CMD_PULL:
3013 CAppUtils::Pull(true);
3014 break;
3015 default:
3016 break;
3019 // CGitProgressDlg progDlg;
3020 // progDlg.SetChangeList(dlg.m_sChangeList, !!dlg.m_bKeepChangeList);
3021 // if (parser.HasVal(_T("closeonend")))
3022 // progDlg.SetAutoClose(parser.GetLongVal(_T("closeonend")));
3023 // progDlg.SetCommand(CGitProgressDlg::GitProgress_Commit);
3024 // progDlg.SetOptions(dlg.m_bKeepLocks ? ProgOptKeeplocks : ProgOptNone);
3025 // progDlg.SetPathList(dlg.m_pathList);
3026 // progDlg.SetCommitMessage(dlg.m_sLogMessage);
3027 // progDlg.SetDepth(dlg.m_bRecursive ? Git_depth_infinity : svn_depth_empty);
3028 // progDlg.SetSelectedList(dlg.m_selectedPathList);
3029 // progDlg.SetItemCount(dlg.m_itemsCount);
3030 // progDlg.SetBugTraqProvider(dlg.m_BugTraqProvider);
3031 // progDlg.DoModal();
3032 // CRegDWORD err = CRegDWORD(_T("Software\\TortoiseGit\\ErrorOccurred"), FALSE);
3033 // err = (DWORD)progDlg.DidErrorsOccur();
3034 // bFailed = progDlg.DidErrorsOccur();
3035 // bRet = progDlg.DidErrorsOccur();
3036 // CRegDWORD bFailRepeat = CRegDWORD(_T("Software\\TortoiseGit\\CommitReopen"), FALSE);
3037 // if (DWORD(bFailRepeat)==0)
3038 // bFailed = false; // do not repeat if the user chose not to in the settings.
3041 return true;
3045 BOOL CAppUtils::SVNDCommit()
3047 CSVNDCommitDlg dcommitdlg;
3048 CString gitSetting = g_Git.GetConfigValue(_T("svn.rmdir"));
3049 if (gitSetting == _T("")) {
3050 if (dcommitdlg.DoModal() != IDOK)
3052 return false;
3054 else
3056 if (dcommitdlg.m_remember)
3058 if (dcommitdlg.m_rmdir)
3060 gitSetting = _T("true");
3062 else
3064 gitSetting = _T("false");
3066 if(g_Git.SetConfigValue(_T("svn.rmdir"),gitSetting))
3068 CString msg;
3069 msg.Format(IDS_PROC_SAVECONFIGFAILED, _T("svn.rmdir"), gitSetting);
3070 CMessageBox::Show(NULL, msg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
3076 BOOL IsStash = false;
3077 if(!g_Git.CheckCleanWorkTree())
3079 if (CMessageBox::Show(NULL, IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
3081 CSysProgressDlg sysProgressDlg;
3082 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3083 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3084 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3085 sysProgressDlg.SetShowProgressBar(false);
3086 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3087 sysProgressDlg.ShowModeless((HWND)NULL, true);
3089 CString cmd,out;
3090 cmd=_T("git.exe stash");
3091 if (g_Git.Run(cmd, &out, CP_UTF8))
3093 sysProgressDlg.Stop();
3094 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
3095 return false;
3097 sysProgressDlg.Stop();
3099 IsStash =true;
3101 else
3103 return false;
3107 CProgressDlg progress;
3108 if (dcommitdlg.m_rmdir)
3110 progress.m_GitCmd=_T("git.exe svn dcommit --rmdir");
3112 else
3114 progress.m_GitCmd=_T("git.exe svn dcommit");
3116 if(progress.DoModal()==IDOK && progress.m_GitStatus == 0)
3118 ::DeleteFile(g_Git.m_CurrentDir + _T("\\sys$command"));
3119 if( IsStash)
3121 if(CMessageBox::Show(NULL,IDS_DCOMMIT_STASH_POP,IDS_APPNAME,MB_YESNO|MB_ICONINFORMATION)==IDYES)
3123 CSysProgressDlg sysProgressDlg;
3124 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3125 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3126 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3127 sysProgressDlg.SetShowProgressBar(false);
3128 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3129 sysProgressDlg.ShowModeless((HWND)NULL, true);
3131 CString cmd,out;
3132 cmd=_T("git.exe stash pop");
3133 if (g_Git.Run(cmd, &out, CP_UTF8))
3135 sysProgressDlg.Stop();
3136 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
3137 return false;
3139 sysProgressDlg.Stop();
3141 else
3143 return false;
3146 return TRUE;
3148 return FALSE;
3151 BOOL CAppUtils::Merge(const CString* commit, bool showStashPop)
3153 if (!CheckUserData())
3154 return FALSE;
3156 CMergeDlg dlg;
3157 if(commit)
3158 dlg.m_initialRefName = *commit;
3160 if(dlg.DoModal()==IDOK)
3162 CString cmd;
3163 CString args;
3165 if(dlg.m_bNoFF)
3166 args += _T(" --no-ff");
3168 if(dlg.m_bSquash)
3169 args += _T(" --squash");
3171 if(dlg.m_bNoCommit)
3172 args += _T(" --no-commit");
3174 if (dlg.m_bLog)
3176 CString fmt;
3177 fmt.Format(_T(" --log=%d"), dlg.m_nLog);
3178 args += fmt;
3181 if (!dlg.m_MergeStrategy.IsEmpty())
3183 args += _T(" --strategy=") + dlg.m_MergeStrategy;
3184 if (!dlg.m_StrategyOption.IsEmpty())
3186 args += _T(" --strategy-option=") + dlg.m_StrategyOption;
3187 if (!dlg.m_StrategyParam.IsEmpty())
3188 args += _T("=") + dlg.m_StrategyParam;
3192 if(!dlg.m_strLogMesage.IsEmpty())
3194 CString logmsg = dlg.m_strLogMesage;
3195 logmsg.Replace(_T("\""), _T("\\\""));
3196 args += _T(" -m \"") + logmsg + _T("\"");
3198 cmd.Format(_T("git.exe merge %s %s"), args, g_Git.FixBranchName(dlg.m_VersionName));
3200 CProgressDlg Prodlg;
3201 Prodlg.m_GitCmd = cmd;
3203 Prodlg.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3205 if (status)
3207 CTGitPathList list;
3208 if (!g_Git.ListConflictFile(list) && !list.IsEmpty())
3210 // there are conflict files
3212 postCmdList.push_back(PostCmd(IDI_RESOLVE, IDS_PROGRS_CMD_RESOLVE, []
3214 CString sCmd;
3215 sCmd.Format(_T("/command:commit /path:\"%s\""), g_Git.m_CurrentDir);
3216 CAppUtils::RunTortoiseGitProc(sCmd);
3217 }));
3220 postCmdList.push_back(PostCmd(IDI_COMMIT, IDS_MENUSTASHSAVE, [&]{ CAppUtils::StashSave(_T(""), false, false, true, g_Git.FixBranchName(dlg.m_VersionName)); }));
3221 return;
3224 if (showStashPop)
3225 postCmdList.push_back(PostCmd(IDI_RELOCATE, IDS_MENUSTASHPOP, []{ StashPop(); }));
3227 if (dlg.m_bNoCommit)
3229 postCmdList.push_back(PostCmd(IDI_COMMIT, IDS_MENUCOMMIT, []
3231 CString sCmd;
3232 sCmd.Format(_T("/command:commit /path:\"%s\""), g_Git.m_CurrentDir);
3233 CAppUtils::RunTortoiseGitProc(sCmd);
3234 }));
3235 return;
3238 if (dlg.m_bIsBranch && dlg.m_VersionName.Find(L"remotes/") == -1) // do not ask to remove remote branches
3240 postCmdList.push_back(PostCmd(IDI_DELETE, IDS_PROC_REMOVEBRANCH, [&]
3242 CString msg;
3243 msg.Format(IDS_PROC_DELETEBRANCHTAG, dlg.m_VersionName);
3244 if (CMessageBox::Show(nullptr, msg, _T("TortoiseGit"), 2, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 1)
3246 CString cmd, out;
3247 cmd.Format(_T("git.exe branch -D -- %s"), dlg.m_VersionName);
3248 if (g_Git.Run(cmd, &out, CP_UTF8))
3249 MessageBox(nullptr, out, _T("TortoiseGit"), MB_OK);
3251 }));
3253 if (dlg.m_bIsBranch)
3254 postCmdList.push_back(PostCmd(IDI_PUSH, IDS_MENUPUSH, []{ Push(); }));
3256 BOOL hasGitSVN = CTGitPath(g_Git.m_CurrentDir).GetAdminDirMask() & ITEMIS_GITSVN;
3257 if (hasGitSVN)
3258 postCmdList.push_back(PostCmd(IDI_COMMIT, IDS_MENUSVNDCOMMIT, []{ SVNDCommit(); }));
3261 Prodlg.DoModal();
3262 return !Prodlg.m_GitStatus;
3264 return false;
3267 BOOL CAppUtils::MergeAbort()
3269 CMergeAbortDlg dlg;
3270 if (dlg.DoModal() == IDOK)
3271 return Reset(_T("HEAD"), dlg.m_ResetType + 1);
3273 return FALSE;
3276 void CAppUtils::EditNote(GitRev *rev)
3278 if (!CheckUserData())
3279 return;
3281 CInputDlg dlg;
3282 dlg.m_sHintText = CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES));
3283 dlg.m_sInputText = rev->m_Notes;
3284 dlg.m_sTitle = CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_EDITNOTES));
3285 //dlg.m_pProjectProperties = &m_ProjectProperties;
3286 dlg.m_bUseLogWidth = true;
3287 if(dlg.DoModal() == IDOK)
3289 CString cmd,output;
3290 cmd=_T("notes add -f -F \"");
3292 CString tempfile=::GetTempFile();
3293 if (CAppUtils::SaveCommitUnicodeFile(tempfile, dlg.m_sInputText))
3295 CMessageBox::Show(nullptr, IDS_PROC_FAILEDSAVINGNOTES, IDS_APPNAME, MB_OK | MB_ICONERROR);
3296 return;
3298 cmd += tempfile;
3299 cmd += _T("\" ");
3300 cmd += rev->m_CommitHash.ToString();
3304 if (git_run_cmd("notes", CUnicodeUtils::GetMulti(cmd, CP_UTF8).GetBuffer()))
3306 CMessageBox::Show(NULL, IDS_PROC_FAILEDSAVINGNOTES, IDS_APPNAME, MB_OK | MB_ICONERROR);
3309 else
3311 rev->m_Notes = dlg.m_sInputText;
3313 }catch(...)
3315 CMessageBox::Show(NULL, IDS_PROC_FAILEDSAVINGNOTES, IDS_APPNAME, MB_OK | MB_ICONERROR);
3317 ::DeleteFile(tempfile);
3322 int CAppUtils::GetMsysgitVersion()
3324 if (g_Git.ms_LastMsysGitVersion)
3325 return g_Git.ms_LastMsysGitVersion;
3327 CString cmd;
3328 CString versiondebug;
3329 CString version;
3331 CRegDWORD regTime = CRegDWORD(_T("Software\\TortoiseGit\\git_file_time"));
3332 CRegDWORD regVersion = CRegDWORD(_T("Software\\TortoiseGit\\git_cached_version"));
3334 CString gitpath = CGit::ms_LastMsysGitDir+_T("\\git.exe");
3336 __int64 time=0;
3337 if (!g_Git.GetFileModifyTime(gitpath, &time))
3339 if((DWORD)time == regTime)
3341 g_Git.ms_LastMsysGitVersion = regVersion;
3342 return regVersion;
3346 CString err;
3347 cmd = _T("git.exe --version");
3348 if (g_Git.Run(cmd, &version, &err, CP_UTF8))
3350 CMessageBox::Show(NULL, _T("git.exe not correctly set up (") + err + _T(")\nCheck TortoiseGit settings and consult help file for \"Git.exe Path\"."), _T("TortoiseGit"), MB_OK|MB_ICONERROR);
3351 return -1;
3354 int start=0;
3355 int ver = 0;
3357 versiondebug = version;
3361 CString str=version.Tokenize(_T("."), start);
3362 int space = str.ReverseFind(_T(' '));
3363 str = str.Mid(space+1,start);
3364 ver = _ttol(str);
3365 ver <<=24;
3367 version = version.Mid(start);
3368 start = 0;
3370 str = version.Tokenize(_T("."), start);
3372 ver |= (_ttol(str) & 0xFF) << 16;
3374 str = version.Tokenize(_T("."), start);
3375 ver |= (_ttol(str) & 0xFF) << 8;
3377 str = version.Tokenize(_T("."), start);
3378 ver |= (_ttol(str) & 0xFF);
3380 catch(...)
3382 if (!ver)
3384 CMessageBox::Show(NULL, _T("Could not parse git.exe version number: \"") + versiondebug + _T("\""), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
3385 return -1;
3389 regTime = time&0xFFFFFFFF;
3390 regVersion = ver;
3391 g_Git.ms_LastMsysGitVersion = ver;
3393 return ver;
3396 void CAppUtils::MarkWindowAsUnpinnable(HWND hWnd)
3398 typedef HRESULT (WINAPI *SHGPSFW) (HWND hwnd,REFIID riid,void** ppv);
3400 CAutoLibrary hShell = AtlLoadSystemLibraryUsingFullPath(_T("Shell32.dll"));
3402 if (hShell.IsValid()) {
3403 SHGPSFW pfnSHGPSFW = (SHGPSFW)::GetProcAddress(hShell, "SHGetPropertyStoreForWindow");
3404 if (pfnSHGPSFW) {
3405 IPropertyStore *pps;
3406 HRESULT hr = pfnSHGPSFW(hWnd, IID_PPV_ARGS(&pps));
3407 if (SUCCEEDED(hr)) {
3408 PROPVARIANT var;
3409 var.vt = VT_BOOL;
3410 var.boolVal = VARIANT_TRUE;
3411 pps->SetValue(PKEY_AppUserModel_PreventPinning, var);
3412 pps->Release();
3418 void CAppUtils::SetWindowTitle(HWND hWnd, const CString& urlorpath, const CString& dialogname)
3420 ASSERT(dialogname.GetLength() < 70);
3421 ASSERT(urlorpath.GetLength() < MAX_PATH);
3422 WCHAR pathbuf[MAX_PATH] = {0};
3424 PathCompactPathEx(pathbuf, urlorpath, 70 - dialogname.GetLength(), 0);
3426 wcscat_s(pathbuf, L" - ");
3427 wcscat_s(pathbuf, dialogname);
3428 wcscat_s(pathbuf, L" - ");
3429 wcscat_s(pathbuf, CString(MAKEINTRESOURCE(IDS_APPNAME)));
3430 SetWindowText(hWnd, pathbuf);
3433 bool CAppUtils::BisectStart(const CString& lastGood, const CString& firstBad, bool bIsMainWnd)
3435 if (!g_Git.CheckCleanWorkTree())
3437 if (CMessageBox::Show(NULL, IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
3439 CSysProgressDlg sysProgressDlg;
3440 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
3441 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_STASHRUNNING)));
3442 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
3443 sysProgressDlg.SetShowProgressBar(false);
3444 sysProgressDlg.SetCancelMsg(IDS_PROGRS_INFOFAILED);
3445 sysProgressDlg.ShowModeless((HWND)NULL, true);
3447 CString cmd, out;
3448 cmd = _T("git.exe stash");
3449 if (g_Git.Run(cmd, &out, CP_UTF8))
3451 sysProgressDlg.Stop();
3452 CMessageBox::Show(NULL, out, _T("TortoiseGit"), MB_OK);
3453 return false;
3455 sysProgressDlg.Stop();
3457 else
3458 return false;
3461 CBisectStartDlg bisectStartDlg;
3463 if (!lastGood.IsEmpty())
3464 bisectStartDlg.m_sLastGood = lastGood;
3465 if (!firstBad.IsEmpty())
3466 bisectStartDlg.m_sFirstBad = firstBad;
3468 if (bisectStartDlg.DoModal() == IDOK)
3470 CProgressDlg progress;
3471 if (bIsMainWnd)
3472 theApp.m_pMainWnd = &progress;
3473 progress.m_GitCmdList.push_back(_T("git.exe bisect start"));
3474 progress.m_GitCmdList.push_back(_T("git.exe bisect good ") + bisectStartDlg.m_LastGoodRevision);
3475 progress.m_GitCmdList.push_back(_T("git.exe bisect bad ") + bisectStartDlg.m_FirstBadRevision);
3477 progress.m_PostCmdCallback = [&](DWORD status, PostCmdList& postCmdList)
3479 if (status)
3480 return;
3482 CTGitPath path(g_Git.m_CurrentDir);
3483 if (path.HasSubmodules())
3485 postCmdList.push_back(PostCmd(IDI_UPDATE, IDS_PROC_SUBMODULESUPDATE, []
3487 CString sCmd;
3488 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), g_Git.m_CurrentDir);
3489 CAppUtils::RunTortoiseGitProc(sCmd);
3490 }));
3496 INT_PTR ret = progress.DoModal();
3497 return ret == IDOK;
3500 return false;
3503 int CAppUtils::Git2GetUserPassword(git_cred **out, const char *url, const char *username_from_url, unsigned int /*allowed_types*/, void * /*payload*/)
3505 CUserPassword dlg;
3506 dlg.m_URL = CUnicodeUtils::GetUnicode(url, CP_UTF8);
3507 if (username_from_url)
3508 dlg.m_UserName = CUnicodeUtils::GetUnicode(username_from_url, CP_UTF8);
3510 CStringA username, password;
3511 if (dlg.DoModal() == IDOK)
3513 username = CUnicodeUtils::GetMulti(dlg.m_UserName, CP_UTF8);
3514 password = CUnicodeUtils::GetMulti(dlg.m_Password, CP_UTF8);
3515 return git_cred_userpass_plaintext_new(out, username, password);
3517 giterr_set_str(GITERR_NONE, "User cancelled.");
3518 return GIT_EUSER;
3521 int CAppUtils::Git2CertificateCheck(git_cert* base_cert, int /*valid*/, const char* host, void* /*payload*/)
3523 if (base_cert->cert_type == GIT_CERT_X509)
3525 git_cert_x509* cert = (git_cert_x509*)base_cert;
3527 if (last_accepted_cert.cmp(cert))
3528 return 0;
3530 PCCERT_CONTEXT pServerCert = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, (BYTE*)cert->data, (DWORD)cert->len);
3532 DWORD verificationError = VerifyServerCertificate(pServerCert, CUnicodeUtils::GetUnicode(host).GetBuffer(), 0);
3533 if (!verificationError)
3535 last_accepted_cert.set(cert);
3536 CertFreeCertificateContext(pServerCert);
3537 return 0;
3540 CString servernameInCert;
3541 CertGetNameString(pServerCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nullptr, servernameInCert.GetBuffer(128), 128);
3542 servernameInCert.ReleaseBuffer();
3544 CString issuer;
3545 CertGetNameString(pServerCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, CERT_NAME_ISSUER_FLAG, nullptr, issuer.GetBuffer(128), 128);
3546 issuer.ReleaseBuffer();
3548 CertFreeCertificateContext(pServerCert);
3550 CCheckCertificateDlg dlg;
3551 dlg.cert = cert;
3552 dlg.m_sCertificateCN = servernameInCert;
3553 dlg.m_sCertificateIssuer = issuer;
3554 dlg.m_sHostname = CUnicodeUtils::GetUnicode(host);
3555 dlg.m_sError = CFormatMessageWrapper(verificationError);
3556 if (dlg.DoModal() == IDOK)
3558 last_accepted_cert.set(cert);
3559 return 0;
3562 return GIT_ECERTIFICATE;
3565 void CAppUtils::ExploreTo(HWND hwnd, CString path)
3567 if (PathFileExists(path))
3569 ITEMIDLIST __unaligned * pidl = ILCreateFromPath(path);
3570 if (pidl)
3572 SHOpenFolderAndSelectItems(pidl, 0, 0, 0);
3573 ILFree(pidl);
3575 return;
3577 // if filepath does not exist any more, navigate to closest matching folder
3580 int pos = path.ReverseFind(_T('\\'));
3581 if (pos <= 3)
3582 break;
3583 path = path.Left(pos);
3584 } while (!PathFileExists(path));
3585 ShellExecute(hwnd, _T("explore"), path, nullptr, nullptr, SW_SHOW);
3588 int CAppUtils::ResolveConflict(CTGitPath& path, resolve_with resolveWith)
3590 bool b_local = false, b_remote = false;
3591 BYTE_VECTOR vector;
3593 CString cmd;
3594 cmd.Format(_T("git.exe ls-files -u -t -z -- \"%s\""), path.GetGitPathString());
3595 if (g_Git.Run(cmd, &vector))
3597 CMessageBox::Show(nullptr, _T("git ls-files failed!"), _T("TortoiseGit"), MB_OK);
3598 return -1;
3601 CTGitPathList list;
3602 if (list.ParserFromLsFile(vector))
3604 CMessageBox::Show(nullptr, _T("Parse ls-files failed!"), _T("TortoiseGit"), MB_OK);
3605 return -1;
3608 if (list.IsEmpty())
3609 return 0;
3610 for (int i = 0; i < list.GetCount(); ++i)
3612 if (list[i].m_Stage == 2)
3613 b_local = true;
3614 if (list[i].m_Stage == 3)
3615 b_remote = true;
3619 CBlockCacheForPath block(g_Git.m_CurrentDir);
3620 if (path.IsDirectory()) // is submodule conflict
3622 CString err = _T("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.");
3623 if (b_local && b_remote)
3625 if (!path.HasAdminDir()) // check if submodule is initialized
3627 err += _T("\n\nYou have to checkout the submodule manually into \"") + path.GetGitPathString() + _T("\" and then reset HEAD to the right commit (see resolve submodule conflict dialog for this).");
3628 MessageBox(nullptr, err, _T("TortoiseGit"), MB_ICONERROR);
3629 return -1;
3631 CGit subgit;
3632 subgit.m_CurrentDir = g_Git.CombinePath(path);
3633 CGitHash submoduleHead;
3634 if (subgit.GetHash(submoduleHead, _T("HEAD")))
3636 MessageBox(nullptr, err, _T("TortoiseGit"), MB_ICONERROR);
3637 return -1;
3639 CString baseHash, localHash, remoteHash;
3640 ParseHashesFromLsFile(vector, baseHash, localHash, remoteHash);
3641 if (resolveWith == RESOLVE_WITH_THEIRS && submoduleHead.ToString() != remoteHash)
3643 CString origPath = g_Git.m_CurrentDir;
3644 g_Git.m_CurrentDir = g_Git.CombinePath(path);
3645 if (!GitReset(&remoteHash))
3647 g_Git.m_CurrentDir = origPath;
3648 return -1;
3650 g_Git.m_CurrentDir = origPath;
3652 else if (resolveWith == RESOLVE_WITH_MINE && submoduleHead.ToString() != localHash)
3654 CString origPath = g_Git.m_CurrentDir;
3655 g_Git.m_CurrentDir = g_Git.CombinePath(path);
3656 if (!GitReset(&localHash))
3658 g_Git.m_CurrentDir = origPath;
3659 return -1;
3661 g_Git.m_CurrentDir = origPath;
3664 else
3666 MessageBox(nullptr, err, _T("TortoiseGit"), MB_ICONERROR);
3667 return -1;
3671 if (resolveWith == RESOLVE_WITH_THEIRS)
3673 CString gitcmd, output;
3674 if (b_local && b_remote)
3675 gitcmd.Format(_T("git.exe checkout-index -f --stage=3 -- \"%s\""), path.GetGitPathString());
3676 else if (b_remote)
3677 gitcmd.Format(_T("git.exe add -f -- \"%s\""), path.GetGitPathString());
3678 else if (b_local)
3679 gitcmd.Format(_T("git.exe rm -f -- \"%s\""), path.GetGitPathString());
3680 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3682 CMessageBox::Show(nullptr, output, _T("TortoiseGit"), MB_ICONERROR);
3683 return -1;
3686 else if (resolveWith == RESOLVE_WITH_MINE)
3688 CString gitcmd, output;
3689 if (b_local && b_remote)
3690 gitcmd.Format(_T("git.exe checkout-index -f --stage=2 -- \"%s\""), path.GetGitPathString());
3691 else if (b_local)
3692 gitcmd.Format(_T("git.exe add -f -- \"%s\""), path.GetGitPathString());
3693 else if (b_remote)
3694 gitcmd.Format(_T("git.exe rm -f -- \"%s\""), path.GetGitPathString());
3695 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3697 CMessageBox::Show(nullptr, output, _T("TortoiseGit"), MB_ICONERROR);
3698 return -1;
3702 if (b_local && b_remote && path.m_Action & CTGitPath::LOGACTIONS_UNMERGED)
3704 CString gitcmd, output;
3705 gitcmd.Format(_T("git.exe add -f -- \"%s\""), path.GetGitPathString());
3706 if (g_Git.Run(gitcmd, &output, CP_UTF8))
3707 CMessageBox::Show(nullptr, output, _T("TortoiseGit"), MB_ICONERROR);
3708 else
3710 path.m_Action |= CTGitPath::LOGACTIONS_MODIFIED;
3711 path.m_Action &= ~CTGitPath::LOGACTIONS_UNMERGED;
3715 RemoveTempMergeFile(path);
3716 return 0;
3719 bool CAppUtils::ShellOpen(const CString& file, HWND hwnd /*= nullptr */)
3721 if ((int)ShellExecute(hwnd, NULL, file, NULL, NULL, SW_SHOW) > HINSTANCE_ERROR)
3722 return true;
3724 return ShowOpenWithDialog(file, hwnd);
3727 bool CAppUtils::ShowOpenWithDialog(const CString& file, HWND hwnd /*= nullptr */)
3729 CAutoLibrary hShell = AtlLoadSystemLibraryUsingFullPath(_T("shell32.dll"));
3730 if (hShell)
3732 typedef HRESULT STDAPICALLTYPE SHOpenWithDialoFN(_In_opt_ HWND hwndParent, _In_ const OPENASINFO *poainfo);
3733 SHOpenWithDialoFN *pfnSHOpenWithDialog = (SHOpenWithDialoFN*)GetProcAddress(hShell, "SHOpenWithDialog");
3734 if (pfnSHOpenWithDialog)
3736 OPENASINFO oi = { 0 };
3737 oi.pcszFile = file;
3738 oi.oaifInFlags = OAIF_EXEC;
3739 return SUCCEEDED(pfnSHOpenWithDialog(hwnd, &oi));
3742 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
3743 cmd += file;
3744 return CAppUtils::LaunchApplication(cmd, NULL, false);