Don't use (deprecated) alloca any more
[TortoiseGit.git] / src / Git / Git.cpp
blobc15c0075f27a46a1d97c115e9bd8f5ac0db15be3
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2018 - TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "Git.h"
22 #include "GitRev.h"
23 #include "registry.h"
24 #include "GitForWindows.h"
25 #include "UnicodeUtils.h"
26 #include "gitdll.h"
27 #include <fstream>
28 #include <iterator>
29 #include "FormatMessageWrapper.h"
30 #include "SmartHandle.h"
31 #include "MassiveGitTaskBase.h"
32 #include "git2/sys/filter.h"
33 #include "git2/sys/transport.h"
34 #include "../libgit2/filter-filter.h"
35 #include "../libgit2/ssh-wintunnel.h"
37 static int CalculateDiffSimilarityIndexThreshold(DWORD index)
39 if (index < 0 || index > 100)
40 return 50;
41 return index;
44 bool CGit::ms_bCygwinGit = (CRegDWORD(L"Software\\TortoiseGit\\CygwinHack", FALSE) == TRUE);
45 bool CGit::ms_bMsys2Git = (CRegDWORD(L"Software\\TortoiseGit\\Msys2Hack", FALSE) == TRUE);
46 int CGit::ms_iSimilarityIndexThreshold = CalculateDiffSimilarityIndexThreshold(CRegDWORD(L"Software\\TortoiseGit\\DiffSimilarityIndexThreshold", 50));
47 int CGit::m_LogEncode=CP_UTF8;
48 typedef CComCritSecLock<CComCriticalSection> CAutoLocker;
50 static LPCTSTR nextpath(const wchar_t* path, wchar_t* buf, size_t buflen)
52 if (!path || !buf || buflen == 0)
53 return nullptr;
55 const wchar_t* base = path;
56 wchar_t term = (*path == L'"') ? *path++ : L';';
58 for (buflen--; *path && *path != term && buflen; buflen--)
59 *buf++ = *path++;
61 *buf = L'\0'; /* reserved a byte via initial subtract */
63 while (*path == term || *path == L';')
64 ++path;
66 return (path != base) ? path : nullptr;
69 static CString FindFileOnPath(const CString& filename, LPCTSTR env, bool wantDirectory = false)
71 TCHAR buf[MAX_PATH] = { 0 };
73 // search in all paths defined in PATH
74 while ((env = nextpath(env, buf, _countof(buf) - 1)) != nullptr && *buf)
76 TCHAR* pfin = buf + wcslen(buf) - 1;
78 // ensure trailing slash
79 if (*pfin != L'/' && *pfin != L'\\')
80 wcscpy_s(++pfin, 2, L"\\"); // we have enough space left, MAX_PATH-1 is used in nextpath above
82 const size_t len = wcslen(buf);
84 if ((len + filename.GetLength()) < _countof(buf))
85 wcscpy_s(pfin + 1, _countof(buf) - len, filename);
86 else
87 break;
89 if (PathFileExists(buf))
91 if (wantDirectory)
92 pfin[1] = L'\0';
93 return buf;
97 return L"";
100 static BOOL FindGitPath()
102 size_t size;
103 _wgetenv_s(&size, nullptr, 0, L"PATH");
104 if (!size)
105 return FALSE;
107 auto env = std::make_unique<TCHAR[]>(size);
108 if (!env)
109 return FALSE;
110 _wgetenv_s(&size, env.get(), size, L"PATH");
112 CString gitExeDirectory = FindFileOnPath(L"git.exe", env.get(), true);
113 if (!gitExeDirectory.IsEmpty())
115 CGit::ms_LastMsysGitDir = gitExeDirectory;
116 CGit::ms_LastMsysGitDir.TrimRight(L'\\');
117 if (CGit::ms_LastMsysGitDir.GetLength() > 12 && (CStringUtils::EndsWith(CGit::ms_LastMsysGitDir, L"\\mingw32\\bin") || CStringUtils::EndsWith(CGit::ms_LastMsysGitDir, L"\\mingw64\\bin")))
119 // prefer cmd directory as early Git for Windows 2.x releases only had this
120 CString installRoot = CGit::ms_LastMsysGitDir.Mid(0, CGit::ms_LastMsysGitDir.GetLength() - 12) + L"\\cmd\\git.exe";
121 if (PathFileExists(installRoot))
122 CGit::ms_LastMsysGitDir = CGit::ms_LastMsysGitDir.Mid(0, CGit::ms_LastMsysGitDir.GetLength() - 12) + L"\\cmd";
124 if (CGit::ms_LastMsysGitDir.GetLength() > 4 && CStringUtils::EndsWith(CGit::ms_LastMsysGitDir, L"\\cmd"))
126 // often the msysgit\cmd folder is on the %PATH%, but
127 // that git.exe does not work, so try to guess the bin folder
128 CString binDir = CGit::ms_LastMsysGitDir.Mid(0, CGit::ms_LastMsysGitDir.GetLength() - 4) + L"\\bin\\git.exe";
129 if (PathFileExists(binDir))
130 CGit::ms_LastMsysGitDir = CGit::ms_LastMsysGitDir.Mid(0, CGit::ms_LastMsysGitDir.GetLength() - 4) + L"\\bin";
132 return TRUE;
135 return FALSE;
138 static CString FindExecutableOnPath(const CString& executable, LPCTSTR env)
140 CString filename = executable;
142 if (!CStringUtils::EndsWith(executable, L".exe"))
143 filename += L".exe";
145 if (PathFileExists(filename))
146 return filename;
148 filename = FindFileOnPath(filename, env);
149 if (!filename.IsEmpty())
150 return filename;
152 return executable;
155 static bool g_bSortLogical;
156 static bool g_bSortLocalBranchesFirst;
157 static bool g_bSortTagsReversed;
158 static git_cred_acquire_cb g_Git2CredCallback;
159 static git_transport_certificate_check_cb g_Git2CheckCertificateCallback;
161 static void GetSortOptions()
163 #ifdef GTEST_INCLUDE_GTEST_GTEST_H_
164 g_bSortLogical = true;
165 g_bSortLocalBranchesFirst = true;
166 g_bSortTagsReversed = false;
167 #else
168 g_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
169 if (g_bSortLogical)
170 g_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
171 g_bSortLocalBranchesFirst = !CRegDWORD(L"Software\\TortoiseGit\\NoSortLocalBranchesFirst", 0, false, HKEY_CURRENT_USER);
172 if (g_bSortLocalBranchesFirst)
173 g_bSortLocalBranchesFirst = !CRegDWORD(L"Software\\TortoiseGit\\NoSortLocalBranchesFirst", 0, false, HKEY_LOCAL_MACHINE);
174 g_bSortTagsReversed = !!CRegDWORD(L"Software\\TortoiseGit\\SortTagsReversed", 0, false, HKEY_LOCAL_MACHINE);
175 if (!g_bSortTagsReversed)
176 g_bSortTagsReversed = !!CRegDWORD(L"Software\\TortoiseGit\\SortTagsReversed", 0, false, HKEY_CURRENT_USER);
177 #endif
180 static int LogicalComparePredicate(const CString &left, const CString &right)
182 if (g_bSortLogical)
183 return StrCmpLogicalW(left, right) < 0;
184 return StrCmpI(left, right) < 0;
187 static int LogicalCompareReversedPredicate(const CString &left, const CString &right)
189 return LogicalComparePredicate(right, left);
192 static int LogicalCompareBranchesPredicate(const CString &left, const CString &right)
194 if (g_bSortLocalBranchesFirst)
196 bool leftIsRemote = CStringUtils::StartsWith(left, L"remotes/");
197 bool rightIsRemote = CStringUtils::StartsWith(right, L"remotes/");
199 if (leftIsRemote && !rightIsRemote)
200 return false;
201 else if (!leftIsRemote && rightIsRemote)
202 return true;
204 return LogicalComparePredicate(left, right);
207 #define CALL_OUTPUT_READ_CHUNK_SIZE 1024
209 CString CGit::ms_LastMsysGitDir;
210 CString CGit::ms_MsysGitRootDir;
211 int CGit::ms_LastMsysGitVersion = 0;
212 CGit g_Git;
215 CGit::CGit(void)
217 git_libgit2_init();
218 GetCurrentDirectory(MAX_PATH, CStrBuf(m_CurrentDir, MAX_PATH));
219 m_IsGitDllInited = false;
220 m_GitDiff=0;
221 m_GitSimpleListDiff=0;
222 m_IsUseGitDLL = !!CRegDWORD(L"Software\\TortoiseGit\\UsingGitDLL",1);
223 m_IsUseLibGit2 = !!CRegDWORD(L"Software\\TortoiseGit\\UseLibgit2", TRUE);
224 m_IsUseLibGit2_mask = CRegDWORD(L"Software\\TortoiseGit\\UseLibgit2_mask", DEFAULT_USE_LIBGIT2_MASK);
226 SecureZeroMemory(&m_CurrentGitPi, sizeof(PROCESS_INFORMATION));
228 GetSortOptions();
229 this->m_bInitialized =false;
230 CheckMsysGitDir();
231 m_critGitDllSec.Init();
232 m_critSecThreadMap.Init();
235 CGit::~CGit(void)
237 if(this->m_GitDiff)
239 git_close_diff(m_GitDiff);
240 m_GitDiff=0;
242 if(this->m_GitSimpleListDiff)
244 git_close_diff(m_GitSimpleListDiff);
245 m_GitSimpleListDiff=0;
247 git_libgit2_shutdown();
248 m_critSecThreadMap.Term();
249 m_critGitDllSec.Term();
252 bool CGit::IsBranchNameValid(const CString& branchname)
254 if (CStringUtils::StartsWith(branchname, L"-")) // branch names starting with a dash are discouraged when used with git.exe, see https://github.com/git/git/commit/6348624010888bd2353e5cebdc2b5329490b0f6d
255 return false;
256 if (branchname.FindOneOf(L"\"|<>") >= 0) // not valid on Windows
257 return false;
258 if (branchname == L"HEAD") // Branch name HEAD is discouraged since Git v2.16.0, see https://github.com/git/git/commit/a625b092cc59940521789fe8a3ff69c8d6b14eb2
259 return false;
260 CStringA branchA = CUnicodeUtils::GetUTF8(L"refs/heads/" + branchname);
261 return !!git_reference_is_valid_name(branchA);
264 int CGit::RunAsync(CString cmd, PROCESS_INFORMATION* piOut, HANDLE* hReadOut, HANDLE* hErrReadOut, const CString* StdioFile)
266 CAutoGeneralHandle hRead, hWrite, hReadErr, hWriteErr, hWriteIn, hReadIn;
267 CAutoFile hStdioFile;
269 SECURITY_ATTRIBUTES sa = { 0 };
270 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
271 sa.bInheritHandle=TRUE;
272 if (!CreatePipe(hReadIn.GetPointer(), hWriteIn.GetPointer(), &sa, 0))
274 CString err = CFormatMessageWrapper();
275 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": could not open stdin pipe: %s\n", (LPCTSTR)err.Trim());
276 return TGIT_GIT_ERROR_OPEN_PIP;
278 if (!CreatePipe(hRead.GetPointer(), hWrite.GetPointer(), &sa, 0))
280 CString err = CFormatMessageWrapper();
281 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": could not open stdout pipe: %s\n", (LPCTSTR)err.Trim());
282 return TGIT_GIT_ERROR_OPEN_PIP;
284 if (hErrReadOut && !CreatePipe(hReadErr.GetPointer(), hWriteErr.GetPointer(), &sa, 0))
286 CString err = CFormatMessageWrapper();
287 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": could not open stderr pipe: %s\n", (LPCTSTR)err.Trim());
288 return TGIT_GIT_ERROR_OPEN_PIP;
291 if(StdioFile)
292 hStdioFile = CreateFile(*StdioFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
294 STARTUPINFO si = { 0 };
295 PROCESS_INFORMATION pi = { 0 };
296 si.cb=sizeof(STARTUPINFO);
297 si.hStdInput = hReadIn;
298 if (hErrReadOut)
299 si.hStdError = hWriteErr;
300 else
301 si.hStdError = hWrite;
302 if(StdioFile)
303 si.hStdOutput=hStdioFile;
304 else
305 si.hStdOutput=hWrite;
307 si.wShowWindow=SW_HIDE;
308 si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
310 LPTSTR pEnv = m_Environment;
311 DWORD dwFlags = CREATE_UNICODE_ENVIRONMENT;
312 dwFlags |= CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS;
314 memset(&this->m_CurrentGitPi,0,sizeof(PROCESS_INFORMATION));
316 bool startsWithGit = CStringUtils::StartsWith(cmd, L"git");
317 if (ms_bMsys2Git && startsWithGit && !CStringUtils::StartsWith(cmd, L"git.exe config "))
319 cmd.Replace(L"\\", L"\\\\\\\\");
320 cmd.Replace(L"\"", L"\\\"");
321 cmd = L'"' + CGit::ms_LastMsysGitDir + L"\\bash.exe\" -c \"/usr/bin/" + cmd + L'"';
323 else if (ms_bCygwinGit && startsWithGit && !CStringUtils::StartsWith(cmd, L"git.exe config "))
325 cmd.Replace(L'\\', L'/');
326 cmd.Replace(L"\"", L"\\\"");
327 cmd = L'"' + CGit::ms_LastMsysGitDir + L"\\bash.exe\" -c \"/bin/" + cmd + L'"';
329 else if (startsWithGit || CStringUtils::StartsWith(cmd, L"bash"))
331 int firstSpace = cmd.Find(L' ');
332 if (firstSpace > 0)
333 cmd = L'"' + CGit::ms_LastMsysGitDir + L'\\' + cmd.Left(firstSpace) + L'"' + cmd.Mid(firstSpace);
334 else
335 cmd = L'"' + CGit::ms_LastMsysGitDir + L'\\' + cmd + L'"';
338 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": executing %s\n", (LPCTSTR)cmd);
339 if(!CreateProcess(nullptr, cmd.GetBuffer(), nullptr, nullptr, TRUE, dwFlags, pEnv, m_CurrentDir.GetBuffer(), &si, &pi))
341 CString err = CFormatMessageWrapper();
342 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": error while executing command: %s\n", (LPCTSTR)err.Trim());
343 return TGIT_GIT_ERROR_CREATE_PROCESS;
346 // Close the pipe handle so the child process stops reading.
347 hWriteIn.CloseHandle();
349 m_CurrentGitPi = pi;
351 if(piOut)
352 *piOut=pi;
353 if(hReadOut)
354 *hReadOut = hRead.Detach();
355 if(hErrReadOut)
356 *hErrReadOut = hReadErr.Detach();
357 return 0;
359 //Must use sperate function to convert ANSI str to union code string
360 //Because A2W use stack as internal convert buffer.
361 void CGit::StringAppend(CString *str, const BYTE *p, int code,int length)
363 if (!str)
364 return ;
366 int len ;
367 if(length<0)
368 len = (int)strlen((const char*)p);
369 else
370 len=length;
371 if (len == 0)
372 return;
373 int currentContentLen = str->GetLength();
374 WCHAR * buf = str->GetBuffer(len * 4 + 1 + currentContentLen) + currentContentLen;
375 int appendedLen = MultiByteToWideChar(code, 0, (LPCSTR)p, len, buf, len * 4);
376 str->ReleaseBuffer(currentContentLen + appendedLen); // no - 1 because MultiByteToWideChar is called with a fixed length (thus no nul char included)
379 // This method was originally used to check for orphaned branches
380 BOOL CGit::CanParseRev(CString ref)
382 if (ref.IsEmpty())
383 ref = L"HEAD";
385 CString cmdout;
386 if (Run(L"git.exe rev-parse --revs-only " + ref, &cmdout, CP_UTF8))
387 return FALSE;
388 if(cmdout.IsEmpty())
389 return FALSE;
391 return TRUE;
394 // Checks if we have an orphaned HEAD
395 BOOL CGit::IsInitRepos()
397 CGitHash hash;
398 if (GetHash(hash, L"HEAD") != 0)
399 return FALSE;
400 return hash.IsEmpty() ? TRUE : FALSE;
403 DWORD WINAPI CGit::AsyncReadStdErrThread(LPVOID lpParam)
405 PASYNCREADSTDERRTHREADARGS pDataArray;
406 pDataArray = (PASYNCREADSTDERRTHREADARGS)lpParam;
408 DWORD readnumber;
409 BYTE data[CALL_OUTPUT_READ_CHUNK_SIZE];
410 while (ReadFile(pDataArray->fileHandle, data, CALL_OUTPUT_READ_CHUNK_SIZE, &readnumber, nullptr))
412 if (pDataArray->pcall->OnOutputErrData(data,readnumber))
413 break;
416 return 0;
419 #ifdef _MFC_VER
420 void CGit::KillRelatedThreads(CWinThread* thread)
422 CAutoLocker lock(m_critSecThreadMap);
423 auto it = m_AsyncReadStdErrThreadMap.find(thread->m_nThreadID);
424 if (it != m_AsyncReadStdErrThreadMap.cend())
426 TerminateThread(it->second, (DWORD)-1);
427 m_AsyncReadStdErrThreadMap.erase(it);
429 TerminateThread(thread->m_hThread, (DWORD)-1);
431 #endif
433 int CGit::Run(CGitCall* pcall)
435 PROCESS_INFORMATION pi;
436 CAutoGeneralHandle hRead, hReadErr;
437 if (RunAsync(pcall->GetCmd(), &pi, hRead.GetPointer(), hReadErr.GetPointer()))
438 return TGIT_GIT_ERROR_CREATE_PROCESS;
440 CAutoGeneralHandle piThread(std::move(pi.hThread));
441 CAutoGeneralHandle piProcess(std::move(pi.hProcess));
443 ASYNCREADSTDERRTHREADARGS threadArguments;
444 threadArguments.fileHandle = hReadErr;
445 threadArguments.pcall = pcall;
446 CAutoGeneralHandle thread = CreateThread(nullptr, 0, AsyncReadStdErrThread, &threadArguments, 0, nullptr);
448 if (thread)
450 CAutoLocker lock(m_critSecThreadMap);
451 m_AsyncReadStdErrThreadMap[GetCurrentThreadId()] = thread;
454 DWORD readnumber;
455 BYTE data[CALL_OUTPUT_READ_CHUNK_SIZE];
456 bool bAborted=false;
457 while (ReadFile(hRead, data, CALL_OUTPUT_READ_CHUNK_SIZE, &readnumber, nullptr))
459 // TODO: when OnOutputData() returns 'true', abort git-command. Send CTRL-C signal?
460 if(!bAborted)//For now, flush output when command aborted.
461 if(pcall->OnOutputData(data,readnumber))
462 bAborted=true;
464 if(!bAborted)
465 pcall->OnEnd();
467 if (thread)
469 WaitForSingleObject(thread, INFINITE);
471 CAutoLocker lock(m_critSecThreadMap);
472 m_AsyncReadStdErrThreadMap.erase(GetCurrentThreadId());
475 WaitForSingleObject(pi.hProcess, INFINITE);
476 DWORD exitcode =0;
478 if(!GetExitCodeProcess(pi.hProcess,&exitcode))
480 CString err = CFormatMessageWrapper();
481 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": could not get exit code: %s\n", (LPCTSTR)err.Trim());
482 return TGIT_GIT_ERROR_GET_EXIT_CODE;
484 else
485 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": process exited: %d\n", exitcode);
487 return exitcode;
489 class CGitCall_ByteVector : public CGitCall
491 public:
492 CGitCall_ByteVector(CString cmd,BYTE_VECTOR* pvector, BYTE_VECTOR* pvectorErr = nullptr) : CGitCall(cmd),m_pvector(pvector), m_pvectorErr(pvectorErr) {}
493 virtual bool OnOutputData(const BYTE* data, size_t size)
495 if (!m_pvector || size == 0)
496 return false;
497 size_t oldsize=m_pvector->size();
498 m_pvector->resize(m_pvector->size()+size);
499 memcpy(&*(m_pvector->begin()+oldsize),data,size);
500 return false;
502 virtual bool OnOutputErrData(const BYTE* data, size_t size)
504 if (!m_pvectorErr || size == 0)
505 return false;
506 size_t oldsize = m_pvectorErr->size();
507 m_pvectorErr->resize(m_pvectorErr->size() + size);
508 memcpy(&*(m_pvectorErr->begin() + oldsize), data, size);
509 return false;
511 BYTE_VECTOR* m_pvector;
512 BYTE_VECTOR* m_pvectorErr;
514 int CGit::Run(CString cmd,BYTE_VECTOR *vector, BYTE_VECTOR *vectorErr)
516 CGitCall_ByteVector call(cmd, vector, vectorErr);
517 return Run(&call);
519 int CGit::Run(CString cmd, CString* output, int code)
521 CString err;
522 int ret;
523 ret = Run(cmd, output, &err, code);
525 if (output && !err.IsEmpty())
527 if (!output->IsEmpty())
528 *output += L'\n';
529 *output += err;
532 return ret;
534 int CGit::Run(CString cmd, CString* output, CString* outputErr, int code)
536 BYTE_VECTOR vector, vectorErr;
537 int ret;
538 if (outputErr)
539 ret = Run(cmd, &vector, &vectorErr);
540 else
541 ret = Run(cmd, &vector);
543 vector.push_back(0);
544 StringAppend(output, vector.data(), code);
546 if (outputErr)
548 vectorErr.push_back(0);
549 StringAppend(outputErr, vectorErr.data(), code);
552 return ret;
555 class CGitCallCb : public CGitCall
557 public:
558 CGitCallCb(CString cmd, const GitReceiverFunc& recv, BYTE_VECTOR* pvectorErr = nullptr)
559 : CGitCall(cmd)
560 , m_recv(recv)
561 , m_pvectorErr(pvectorErr)
564 virtual bool OnOutputData(const BYTE* data, size_t size) override
566 // Add data
567 if (size == 0)
568 return false;
569 int oldEndPos = m_buffer.GetLength();
570 memcpy(m_buffer.GetBuffer(oldEndPos + (int)size) + oldEndPos, data, size);
571 m_buffer.ReleaseBuffer(oldEndPos + (int)size);
573 // Break into lines and feed to m_recv
574 int eolPos;
575 while ((eolPos = m_buffer.Find('\n')) >= 0)
577 m_recv(m_buffer.Left(eolPos));
578 m_buffer = m_buffer.Mid(eolPos + 1);
580 return false;
583 virtual bool OnOutputErrData(const BYTE* data, size_t size) override
585 if (!m_pvectorErr || size == 0)
586 return false;
587 size_t oldsize = m_pvectorErr->size();
588 m_pvectorErr->resize(m_pvectorErr->size() + size);
589 memcpy(&*(m_pvectorErr->begin() + oldsize), data, size);
590 return false;
593 virtual void OnEnd() override
595 if (!m_buffer.IsEmpty())
596 m_recv(m_buffer);
597 m_buffer.Empty(); // Just for sure
600 private:
601 GitReceiverFunc m_recv;
602 CStringA m_buffer;
603 BYTE_VECTOR* m_pvectorErr;
606 int CGit::Run(CString cmd, const GitReceiverFunc& recv, CString* outputErr)
608 if (outputErr)
610 BYTE_VECTOR vectorErr;
611 CGitCallCb call(cmd, recv, &vectorErr);
612 int ret = Run(&call);
613 vectorErr.push_back(0);
614 StringAppend(outputErr, vectorErr.data());
615 return ret;
618 CGitCallCb call(cmd, recv);
619 return Run(&call);
622 CString CGit::GetUserName(void)
624 CEnvironment env;
625 env.CopyProcessEnvironment();
626 CString envname = env.GetEnv(L"GIT_AUTHOR_NAME");
627 if (!envname.IsEmpty())
628 return envname;
629 return GetConfigValue(L"user.name");
631 CString CGit::GetUserEmail(void)
633 CEnvironment env;
634 env.CopyProcessEnvironment();
635 CString envmail = env.GetEnv(L"GIT_AUTHOR_EMAIL");
636 if (!envmail.IsEmpty())
637 return envmail;
639 return GetConfigValue(L"user.email");
642 CString CGit::GetConfigValue(const CString& name, const CString& def, bool wantBool)
644 CString configValue;
645 if(this->m_IsUseGitDLL)
647 CAutoLocker lock(g_Git.m_critGitDllSec);
651 CheckAndInitDll();
652 }catch(...)
655 CStringA key, value;
656 key = CUnicodeUtils::GetUTF8(name);
660 if (git_get_config(key, CStrBufA(value, 4096), 4096))
661 return def;
663 catch (const char *msg)
665 ::MessageBox(nullptr, L"Could not get config.\nlibgit reports:\n" + CString(msg), L"TortoiseGit", MB_OK | MB_ICONERROR);
666 return def;
669 StringAppend(&configValue, (BYTE*)(LPCSTR)value);
670 return configValue;
672 else
674 CString cmd;
675 cmd.Format(L"git.exe config%s %s", wantBool ? L" --bool" : L"", (LPCTSTR)name);
676 if (Run(cmd, &configValue, nullptr, CP_UTF8))
677 return def;
678 if (configValue.IsEmpty())
679 return configValue;
680 return configValue.Left(configValue.GetLength() - 1); // strip last newline character
684 bool CGit::GetConfigValueBool(const CString& name, const bool def)
686 CString configValue = GetConfigValue(name, def ? L"true" : L"false", true);
687 configValue.MakeLower();
688 configValue.Trim();
689 if (configValue == L"true" || configValue == L"on" || configValue == L"yes" || StrToInt(configValue) != 0)
690 return true;
691 else
692 return false;
695 int CGit::GetConfigValueInt32(const CString& name, const int def)
697 CString configValue = GetConfigValue(name);
698 int value = def;
699 if (!git_config_parse_int32(&value, CUnicodeUtils::GetUTF8(configValue)))
700 return value;
701 return def;
704 int CGit::SetConfigValue(const CString& key, const CString& value, CONFIG_TYPE type)
706 if(this->m_IsUseGitDLL)
708 CAutoLocker lock(g_Git.m_critGitDllSec);
712 CheckAndInitDll();
713 }catch(...)
716 CStringA keya, valuea;
717 keya = CUnicodeUtils::GetMulti(key, CP_UTF8);
718 valuea = CUnicodeUtils::GetUTF8(value);
722 return [=]() { return get_set_config(keya, valuea, type); }();
724 catch (const char *msg)
726 ::MessageBox(nullptr, L"Could not set config.\nlibgit reports:\n" + CString(msg), L"TortoiseGit", MB_OK | MB_ICONERROR);
727 return -1;
730 else
732 CString cmd;
733 CString option;
734 switch(type)
736 case CONFIG_GLOBAL:
737 option = L"--global";
738 break;
739 case CONFIG_SYSTEM:
740 option = L"--system";
741 break;
742 default:
743 break;
745 CString mangledValue = value;
746 mangledValue.Replace(L"\\\"", L"\\\\\"");
747 mangledValue.Replace(L"\"", L"\\\"");
748 cmd.Format(L"git.exe config %s %s \"%s\"", (LPCTSTR)option, (LPCTSTR)key, (LPCTSTR)mangledValue);
749 CString out;
750 if (Run(cmd, &out, nullptr, CP_UTF8))
751 return -1;
753 return 0;
756 int CGit::UnsetConfigValue(const CString& key, CONFIG_TYPE type)
758 if(this->m_IsUseGitDLL)
760 CAutoLocker lock(g_Git.m_critGitDllSec);
764 CheckAndInitDll();
765 }catch(...)
768 CStringA keya;
769 keya = CUnicodeUtils::GetMulti(key, CP_UTF8);
773 return [=]() { return get_set_config(keya, nullptr, type); }();
775 catch (const char *msg)
777 ::MessageBox(nullptr, L"Could not unset config.\nlibgit reports:\n" + CString(msg), L"TortoiseGit", MB_OK | MB_ICONERROR);
778 return -1;
781 else
783 CString cmd;
784 CString option;
785 switch(type)
787 case CONFIG_GLOBAL:
788 option = L"--global";
789 break;
790 case CONFIG_SYSTEM:
791 option = L"--system";
792 break;
793 default:
794 break;
796 cmd.Format(L"git.exe config %s --unset %s", (LPCTSTR)option, (LPCTSTR)key);
797 CString out;
798 if (Run(cmd, &out, nullptr, CP_UTF8))
799 return -1;
801 return 0;
804 CString CGit::GetCurrentBranch(bool fallback)
806 CString output;
807 //Run(L"git.exe branch", &branch);
809 int result = GetCurrentBranchFromFile(m_CurrentDir, output, fallback);
810 if (result != 0 && ((result == 1 && !fallback) || result != 1))
811 return L"(no branch)";
812 else
813 return output;
816 void CGit::GetRemoteTrackedBranch(const CString& localBranch, CString& pullRemote, CString& pullBranch)
818 if (localBranch.IsEmpty())
819 return;
821 CString configName;
822 configName.Format(L"branch.%s.remote", (LPCTSTR)localBranch);
823 pullRemote = GetConfigValue(configName);
825 //Select pull-branch from current branch
826 configName.Format(L"branch.%s.merge", (LPCTSTR)localBranch);
827 pullBranch = StripRefName(GetConfigValue(configName));
830 void CGit::GetRemoteTrackedBranchForHEAD(CString& remote, CString& branch)
832 CString refName;
833 if (GetCurrentBranchFromFile(m_CurrentDir, refName))
834 return;
835 GetRemoteTrackedBranch(StripRefName(refName), remote, branch);
838 void CGit::GetRemotePushBranch(const CString& localBranch, CString& pushRemote, CString& pushBranch)
840 if (localBranch.IsEmpty())
841 return;
843 CString configName;
845 configName.Format(L"branch.%s.pushremote", (LPCTSTR)localBranch);
846 pushRemote = g_Git.GetConfigValue(configName);
847 if (pushRemote.IsEmpty())
849 pushRemote = g_Git.GetConfigValue(L"remote.pushdefault");
850 if (pushRemote.IsEmpty())
852 configName.Format(L"branch.%s.remote", (LPCTSTR)localBranch);
853 pushRemote = g_Git.GetConfigValue(configName);
857 configName.Format(L"branch.%s.pushbranch", (LPCTSTR)localBranch);
858 pushBranch = g_Git.GetConfigValue(configName); // do not strip branch name (for gerrit), see issue #1609)
859 if (pushBranch.IsEmpty())
861 configName.Format(L"branch.%s.merge", (LPCTSTR)localBranch);
862 pushBranch = CGit::StripRefName(g_Git.GetConfigValue(configName));
866 CString CGit::GetFullRefName(const CString& shortRefName)
868 CString refName;
869 CString cmd;
870 cmd.Format(L"git.exe rev-parse --symbolic-full-name %s", (LPCTSTR)shortRefName);
871 if (Run(cmd, &refName, nullptr, CP_UTF8) != 0)
872 return CString();//Error
873 return refName.TrimRight();
876 CString CGit::StripRefName(CString refName)
878 if (CStringUtils::StartsWith(refName, L"refs/heads/"))
879 refName = refName.Mid((int)wcslen(L"refs/heads/"));
880 else if (CStringUtils::StartsWith(refName, L"refs/"))
881 refName = refName.Mid((int)wcslen(L"refs/"));
882 return refName.TrimRight();
885 int CGit::GetCurrentBranchFromFile(const CString &sProjectRoot, CString &sBranchOut, bool fallback)
887 // read current branch name like git-gui does, by parsing the .git/HEAD file directly
889 if ( sProjectRoot.IsEmpty() )
890 return -1;
892 CString sDotGitPath;
893 if (!GitAdminDir::GetWorktreeAdminDirPath(sProjectRoot, sDotGitPath))
894 return -1;
896 CString sHeadFile = sDotGitPath + L"HEAD";
898 CAutoFILE pFile = _wfsopen(sHeadFile.GetString(), L"r", SH_DENYWR);
899 if (!pFile)
900 return -1;
902 char s[MAX_PATH] = {0};
903 fgets(s, sizeof(s), pFile);
905 const char *pfx = "ref: refs/heads/";
906 const size_t len = strlen(pfx);
908 if ( !strncmp(s, pfx, len) )
910 //# We're on a branch. It might not exist. But
911 //# HEAD looks good enough to be a branch.
912 CStringA utf8Branch(s + len);
913 sBranchOut = CUnicodeUtils::GetUnicode(utf8Branch);
914 sBranchOut.TrimRight(L" \r\n\t");
916 if ( sBranchOut.IsEmpty() )
917 return -1;
919 else if (fallback)
921 CStringA utf8Hash(s);
922 CString unicodeHash = CUnicodeUtils::GetUnicode(utf8Hash);
923 unicodeHash.TrimRight(L" \r\n\t");
924 if (CGitHash::IsValidSHA1(unicodeHash))
925 sBranchOut = unicodeHash;
926 else
927 //# Assume this is a detached head.
928 sBranchOut = L"HEAD";
929 return 1;
931 else
933 //# Assume this is a detached head.
934 sBranchOut = "HEAD";
936 return 1;
939 return 0;
942 int CGit::BuildOutputFormat(CString &format,bool IsFull)
944 CString log;
945 log.Format(L"#<%c>%%x00", LOG_REV_ITEM_BEGIN);
946 format += log;
947 if(IsFull)
949 log.Format(L"#<%c>%%an%%x00", LOG_REV_AUTHOR_NAME);
950 format += log;
951 log.Format(L"#<%c>%%ae%%x00", LOG_REV_AUTHOR_EMAIL);
952 format += log;
953 log.Format(L"#<%c>%%ai%%x00", LOG_REV_AUTHOR_DATE);
954 format += log;
955 log.Format(L"#<%c>%%cn%%x00", LOG_REV_COMMIT_NAME);
956 format += log;
957 log.Format(L"#<%c>%%ce%%x00", LOG_REV_COMMIT_EMAIL);
958 format += log;
959 log.Format(L"#<%c>%%ci%%x00", LOG_REV_COMMIT_DATE);
960 format += log;
961 log.Format(L"#<%c>%%b%%x00", LOG_REV_COMMIT_BODY);
962 format += log;
965 log.Format(L"#<%c>%%m%%H%%x00", LOG_REV_COMMIT_HASH);
966 format += log;
967 log.Format(L"#<%c>%%P%%x00", LOG_REV_COMMIT_PARENT);
968 format += log;
969 log.Format(L"#<%c>%%s%%x00", LOG_REV_COMMIT_SUBJECT);
970 format += log;
972 if(IsFull)
974 log.Format(L"#<%c>%%x00", LOG_REV_COMMIT_FILE);
975 format += log;
977 return 0;
980 CString CGit::GetLogCmd(CString range, const CTGitPath* path, int mask, CFilterData* Filter, int logOrderBy)
982 CString param;
984 if(mask& LOG_INFO_STAT )
985 param += L" --numstat";
986 if(mask& LOG_INFO_FILESTATE)
987 param += L" --raw";
989 if(mask& LOG_INFO_BOUNDARY)
990 param += L" --left-right --boundary";
992 if(mask& CGit::LOG_INFO_ALL_BRANCH)
994 param += L" --all";
995 range.Empty();
998 if (mask& CGit::LOG_INFO_BASIC_REFS)
1000 param += L" --branches";
1001 param += L" --tags";
1002 param += L" --remotes";
1003 param += L" --glob=stas[h]"; // require at least one glob operator
1004 param += L" --glob=bisect";
1005 range.Empty();
1008 if(mask & CGit::LOG_INFO_LOCAL_BRANCHES)
1010 param += L" --branches";
1011 range.Empty();
1014 if(mask& CGit::LOG_INFO_DETECT_COPYRENAME)
1015 param.AppendFormat(L" -C%d%%", ms_iSimilarityIndexThreshold);
1017 if(mask& CGit::LOG_INFO_DETECT_RENAME )
1018 param.AppendFormat(L" -M%d%%", ms_iSimilarityIndexThreshold);
1020 if(mask& CGit::LOG_INFO_FIRST_PARENT )
1021 param += L" --first-parent";
1023 if(mask& CGit::LOG_INFO_NO_MERGE )
1024 param += L" --no-merges";
1026 if(mask& CGit::LOG_INFO_FOLLOW)
1027 param += L" --follow";
1029 if(mask& CGit::LOG_INFO_SHOW_MERGEDFILE)
1030 param += L" -c";
1032 if(mask& CGit::LOG_INFO_FULL_DIFF)
1033 param += L" --full-diff";
1035 if(mask& CGit::LOG_INFO_SIMPILFY_BY_DECORATION)
1036 param += L" --simplify-by-decoration";
1038 if (Filter)
1040 if (Filter->m_NumberOfLogsScale >= CFilterData::SHOW_LAST_N_YEARS)
1042 CTime now = CTime::GetCurrentTime();
1043 CTime time = CTime(now.GetYear(), now.GetMonth(), now.GetDay(), 0, 0, 0);
1044 __time64_t substract = 86400;
1045 CString scale;
1046 switch (Filter->m_NumberOfLogsScale)
1048 case CFilterData::SHOW_LAST_N_YEARS:
1049 substract *= 365;
1050 break;
1051 case CFilterData::SHOW_LAST_N_MONTHS:
1052 substract *= 30;
1053 break;
1054 case CFilterData::SHOW_LAST_N_WEEKS:
1055 substract *= 7;
1056 break;
1058 Filter->m_From = (DWORD)time.GetTime() - (Filter->m_NumberOfLogs * substract);
1060 if (Filter->m_NumberOfLogsScale == CFilterData::SHOW_LAST_N_COMMITS)
1061 param.AppendFormat(L" -n%ld", Filter->m_NumberOfLogs);
1062 else if (Filter->m_NumberOfLogsScale >= CFilterData::SHOW_LAST_SEL_DATE && Filter->m_From > 0)
1063 param.AppendFormat(L" --max-age=%I64u", Filter->m_From);
1066 if( Filter && (Filter->m_To != -1))
1067 param.AppendFormat(L" --min-age=%I64u", Filter->m_To);
1069 if (logOrderBy == LOG_ORDER_TOPOORDER || (mask & CGit::LOG_ORDER_TOPOORDER))
1070 param += L" --topo-order";
1071 else if (logOrderBy == LOG_ORDER_DATEORDER)
1072 param += L" --date-order";
1074 CString cmd;
1075 CString file;
1076 if (path)
1077 file.Format(L" \"%s\"", (LPCTSTR)path->GetGitPathString());
1078 // gitdll.dll:setup_revisions() only looks at args[1] and greater. To account for this, pass a dummy parameter in the 0th place
1079 cmd.Format(L"-z%s %s --parents --%s", (LPCTSTR)param, (LPCTSTR)range, (LPCTSTR)file);
1081 return cmd;
1083 #define BUFSIZE 512
1084 void GetTempPath(CString &path)
1086 TCHAR lpPathBuffer[BUFSIZE] = { 0 };
1087 DWORD dwRetVal;
1088 DWORD dwBufSize=BUFSIZE;
1089 dwRetVal = GetTortoiseGitTempPath(dwBufSize, // length of the buffer
1090 lpPathBuffer); // buffer for path
1091 if (dwRetVal > dwBufSize || (dwRetVal == 0))
1092 path.Empty();
1093 path.Format(L"%s", lpPathBuffer);
1095 CString GetTempFile()
1097 TCHAR lpPathBuffer[BUFSIZE] = { 0 };
1098 DWORD dwRetVal;
1099 DWORD dwBufSize=BUFSIZE;
1100 TCHAR szTempName[BUFSIZE] = { 0 };
1101 UINT uRetVal;
1103 dwRetVal = GetTortoiseGitTempPath(dwBufSize, // length of the buffer
1104 lpPathBuffer); // buffer for path
1105 if (dwRetVal > dwBufSize || (dwRetVal == 0))
1106 return L"";
1108 // Create a temporary file.
1109 uRetVal = GetTempFileName(lpPathBuffer, // directory for tmp files
1110 TEXT("Patch"), // temp file name prefix
1111 0, // create unique name
1112 szTempName); // buffer for name
1114 if (uRetVal == 0)
1115 return L"";
1117 return CString(szTempName);
1120 DWORD GetTortoiseGitTempPath(DWORD nBufferLength, LPTSTR lpBuffer)
1122 DWORD result = ::GetTempPath(nBufferLength, lpBuffer);
1123 if (result == 0) return 0;
1124 if (!lpBuffer || (result + 13 > nBufferLength))
1126 if (lpBuffer)
1127 lpBuffer[0] = '\0';
1128 return result + 13;
1131 wcscat_s(lpBuffer, nBufferLength, L"TortoiseGit\\");
1132 CreateDirectory(lpBuffer, nullptr);
1134 return result + 13;
1137 int CGit::RunLogFile(CString cmd, const CString &filename, CString *stdErr)
1139 PROCESS_INFORMATION pi;
1140 CAutoGeneralHandle hReadErr;
1141 if (RunAsync(cmd, &pi, nullptr, hReadErr.GetPointer(), &filename))
1142 return TGIT_GIT_ERROR_CREATE_PROCESS;
1144 CAutoGeneralHandle piThread(std::move(pi.hThread));
1145 CAutoGeneralHandle piProcess(std::move(pi.hProcess));
1147 BYTE_VECTOR stderrVector;
1148 CGitCall_ByteVector pcall(L"", nullptr, &stderrVector);
1149 ASYNCREADSTDERRTHREADARGS threadArguments;
1150 threadArguments.fileHandle = hReadErr;
1151 threadArguments.pcall = &pcall;
1152 CAutoGeneralHandle thread = CreateThread(nullptr, 0, AsyncReadStdErrThread, &threadArguments, 0, nullptr);
1154 if (thread)
1156 CAutoLocker lock(m_critSecThreadMap);
1157 m_AsyncReadStdErrThreadMap[GetCurrentThreadId()] = thread;
1160 WaitForSingleObject(pi.hProcess,INFINITE);
1162 if (thread)
1164 WaitForSingleObject(thread, INFINITE);
1166 CAutoLocker lock(m_critSecThreadMap);
1167 m_AsyncReadStdErrThreadMap.erase(GetCurrentThreadId());
1170 stderrVector.push_back(0);
1171 StringAppend(stdErr, stderrVector.data(), CP_UTF8);
1173 DWORD exitcode = 0;
1174 if (!GetExitCodeProcess(pi.hProcess, &exitcode))
1176 CString err = CFormatMessageWrapper();
1177 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": could not get exit code: %s\n", (LPCTSTR)err.Trim());
1178 return TGIT_GIT_ERROR_GET_EXIT_CODE;
1180 else
1181 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": process exited: %d\n", exitcode);
1183 return exitcode;
1186 CAutoRepository CGit::GetGitRepository() const
1188 return CAutoRepository(GetGitPathStringA(m_CurrentDir));
1191 int CGit::GetHash(git_repository * repo, CGitHash &hash, const CString& friendname, bool skipFastCheck /* = false */)
1193 ATLASSERT(repo);
1195 // no need to parse a ref if it's already a 40-byte hash
1196 if (!skipFastCheck && CGitHash::IsValidSHA1(friendname))
1198 hash = CGitHash(friendname);
1199 return 0;
1202 int isHeadOrphan = git_repository_head_unborn(repo);
1203 if (isHeadOrphan != 0)
1205 hash.Empty();
1206 if (isHeadOrphan == 1)
1207 return 0;
1208 else
1209 return -1;
1212 CAutoObject gitObject;
1213 if (git_revparse_single(gitObject.GetPointer(), repo, CUnicodeUtils::GetUTF8(friendname)))
1214 return -1;
1216 hash = git_object_id(gitObject);
1218 return 0;
1221 int CGit::GetHash(CGitHash &hash, const CString& friendname)
1223 // no need to parse a ref if it's already a 40-byte hash
1224 if (CGitHash::IsValidSHA1(friendname))
1226 hash = CGitHash(friendname);
1227 return 0;
1230 if (m_IsUseLibGit2)
1232 CAutoRepository repo(GetGitRepository());
1233 if (!repo)
1234 return -1;
1236 return GetHash(repo, hash, friendname, true);
1238 else
1240 CString branch = FixBranchName(friendname);
1241 if (friendname == L"FETCH_HEAD" && branch.IsEmpty())
1242 branch = friendname;
1243 CString cmd;
1244 cmd.Format(L"git.exe rev-parse %s", (LPCTSTR)branch);
1245 gitLastErr.Empty();
1246 int ret = Run(cmd, &gitLastErr, nullptr, CP_UTF8);
1247 hash = CGitHash(gitLastErr.Trim());
1248 if (ret == 0)
1249 gitLastErr.Empty();
1250 else if (friendname == L"HEAD") // special check for unborn branch
1252 CString currentbranch;
1253 if (GetCurrentBranchFromFile(m_CurrentDir, currentbranch))
1254 return -1;
1255 gitLastErr.Empty();
1256 return 0;
1258 return ret;
1262 int CGit::GetInitAddList(CTGitPathList &outputlist)
1264 BYTE_VECTOR cmdout;
1266 outputlist.Clear();
1267 if (Run(L"git.exe ls-files -s -t -z", &cmdout))
1268 return -1;
1270 if (outputlist.ParserFromLsFile(cmdout))
1271 return -1;
1272 for(int i = 0; i < outputlist.GetCount(); ++i)
1273 const_cast<CTGitPath&>(outputlist[i]).m_Action = CTGitPath::LOGACTIONS_ADDED;
1275 return 0;
1277 int CGit::GetCommitDiffList(const CString &rev1, const CString &rev2, CTGitPathList &outputlist, bool ignoreSpaceAtEol, bool ignoreSpaceChange, bool ignoreAllSpace , bool ignoreBlankLines)
1279 CString cmd;
1280 CString ignore;
1281 if (ignoreSpaceAtEol)
1282 ignore += L" --ignore-space-at-eol";
1283 if (ignoreSpaceChange)
1284 ignore += L" --ignore-space-change";
1285 if (ignoreAllSpace)
1286 ignore += L" --ignore-all-space";
1287 if (ignoreBlankLines)
1288 ignore += L" --ignore-blank-lines";
1290 if(rev1 == GIT_REV_ZERO || rev2 == GIT_REV_ZERO)
1292 if(rev1 == GIT_REV_ZERO)
1293 cmd.Format(L"git.exe diff -r --raw -C%d%% -M%d%% --numstat -z %s %s --", ms_iSimilarityIndexThreshold, ms_iSimilarityIndexThreshold, (LPCTSTR)ignore, (LPCTSTR)rev2);
1294 else
1295 cmd.Format(L"git.exe diff -r -R --raw -C%d%% -M%d%% --numstat -z %s %s --", ms_iSimilarityIndexThreshold, ms_iSimilarityIndexThreshold, (LPCTSTR)ignore, (LPCTSTR)rev1);
1297 else
1298 cmd.Format(L"git.exe diff-tree -r --raw -C%d%% -M%d%% --numstat -z %s %s %s --", ms_iSimilarityIndexThreshold, ms_iSimilarityIndexThreshold, (LPCTSTR)ignore, (LPCTSTR)rev2, (LPCTSTR)rev1);
1300 BYTE_VECTOR out;
1301 if (Run(cmd, &out))
1302 return -1;
1304 return outputlist.ParserFromLog(out);
1307 int addto_list_each_ref_fn(const char *refname, const unsigned char * /*sha1*/, int /*flags*/, void *cb_data)
1309 STRING_VECTOR *list = (STRING_VECTOR*)cb_data;
1310 list->push_back(CUnicodeUtils::GetUnicode(refname));
1311 return 0;
1314 int CGit::GetTagList(STRING_VECTOR &list)
1316 size_t prevCount = list.size();
1317 if (this->m_IsUseLibGit2)
1319 CAutoRepository repo(GetGitRepository());
1320 if (!repo)
1321 return -1;
1323 CAutoStrArray tag_names;
1325 if (git_tag_list(tag_names, repo))
1326 return -1;
1328 for (size_t i = 0; i < tag_names->count; ++i)
1330 CStringA tagName(tag_names->strings[i]);
1331 list.push_back(CUnicodeUtils::GetUnicode(tagName));
1334 std::sort(list.begin() + prevCount, list.end(), g_bSortTagsReversed ? LogicalCompareReversedPredicate : LogicalComparePredicate);
1336 return 0;
1338 else
1340 int ret = Run(L"git.exe tag -l", [&](const CStringA& lineA)
1342 if (lineA.IsEmpty())
1343 return;
1344 list.push_back(CUnicodeUtils::GetUnicode(lineA));
1346 if (!ret)
1347 std::sort(list.begin() + prevCount, list.end(), g_bSortTagsReversed ? LogicalCompareReversedPredicate : LogicalComparePredicate);
1348 else if (ret == 1 && IsInitRepos())
1349 return 0;
1350 return ret;
1354 CString CGit::GetGitLastErr(const CString& msg)
1356 if (this->m_IsUseLibGit2)
1357 return GetLibGit2LastErr(msg);
1358 else if (gitLastErr.IsEmpty())
1359 return msg + L"\nUnknown git.exe error.";
1360 else
1362 CString lastError = gitLastErr;
1363 gitLastErr.Empty();
1364 return msg + L'\n' + lastError;
1368 CString CGit::GetGitLastErr(const CString& msg, LIBGIT2_CMD cmd)
1370 if (UsingLibGit2(cmd))
1371 return GetLibGit2LastErr(msg);
1372 else if (gitLastErr.IsEmpty())
1373 return msg + L"\nUnknown git.exe error.";
1374 else
1376 CString lastError = gitLastErr;
1377 gitLastErr.Empty();
1378 return msg + L'\n' + lastError;
1382 CString CGit::GetLibGit2LastErr()
1384 const git_error *libgit2err = giterr_last();
1385 if (libgit2err)
1387 CString lastError = CUnicodeUtils::GetUnicode(CStringA(libgit2err->message));
1388 giterr_clear();
1389 return L"libgit2 returned: " + lastError;
1391 else
1392 return L"An error occoured in libgit2, but no message is available.";
1395 CString CGit::GetLibGit2LastErr(const CString& msg)
1397 if (!msg.IsEmpty())
1398 return msg + L'\n' + GetLibGit2LastErr();
1399 return GetLibGit2LastErr();
1402 CString CGit::FixBranchName_Mod(CString& branchName)
1404 if (branchName == L"FETCH_HEAD")
1405 branchName = DerefFetchHead();
1406 return branchName;
1409 CString CGit::FixBranchName(const CString& branchName)
1411 CString tempBranchName = branchName;
1412 FixBranchName_Mod(tempBranchName);
1413 return tempBranchName;
1416 bool CGit::IsBranchTagNameUnique(const CString& name)
1418 if (m_IsUseLibGit2)
1420 CAutoRepository repo(GetGitRepository());
1421 if (!repo)
1422 return true; // TODO: optimize error reporting
1424 CAutoReference tagRef;
1425 if (git_reference_lookup(tagRef.GetPointer(), repo, CUnicodeUtils::GetUTF8(L"refs/tags/" + name)))
1426 return true;
1428 CAutoReference branchRef;
1429 if (git_reference_lookup(branchRef.GetPointer(), repo, CUnicodeUtils::GetUTF8(L"refs/heads/" + name)))
1430 return true;
1432 return false;
1434 // else
1435 CString cmd;
1436 cmd.Format(L"git.exe show-ref --tags --heads refs/heads/%s refs/tags/%s", (LPCTSTR)name, (LPCTSTR)name);
1438 int refCnt = 0;
1439 Run(cmd, [&](const CStringA& lineA)
1441 if (lineA.IsEmpty())
1442 return;
1443 ++refCnt;
1446 return (refCnt <= 1);
1449 bool CGit::IsLocalBranch(const CString& shortName)
1451 STRING_VECTOR list;
1452 GetBranchList(list, nullptr, CGit::BRANCH_LOCAL);
1453 return std::find(list.cbegin(), list.cend(), shortName) != list.cend();
1456 bool CGit::BranchTagExists(const CString& name, bool isBranch /*= true*/)
1458 if (m_IsUseLibGit2)
1460 CAutoRepository repo(GetGitRepository());
1461 if (!repo)
1462 return false; // TODO: optimize error reporting
1464 CString prefix;
1465 if (isBranch)
1466 prefix = L"refs/heads/";
1467 else
1468 prefix = L"refs/tags/";
1470 CAutoReference ref;
1471 if (git_reference_lookup(ref.GetPointer(), repo, CUnicodeUtils::GetUTF8(prefix + name)))
1472 return false;
1474 return true;
1476 // else
1477 CString cmd, output;
1479 cmd = L"git.exe show-ref ";
1480 if (isBranch)
1481 cmd += L"--heads ";
1482 else
1483 cmd += L"--tags ";
1485 cmd += L"refs/heads/" + name;
1486 cmd += L" refs/tags/" + name;
1488 int ret = Run(cmd, &output, nullptr, CP_UTF8);
1489 if (!ret)
1491 if (!output.IsEmpty())
1492 return true;
1495 return false;
1498 CString CGit::DerefFetchHead()
1500 CString dotGitPath;
1501 GitAdminDir::GetWorktreeAdminDirPath(m_CurrentDir, dotGitPath);
1502 std::ifstream fetchHeadFile((dotGitPath + L"FETCH_HEAD").GetString(), std::ios::in | std::ios::binary);
1503 int forMergeLineCount = 0;
1504 std::string line;
1505 std::string hashToReturn;
1506 while(getline(fetchHeadFile, line))
1508 //Tokenize this line
1509 std::string::size_type prevPos = 0;
1510 std::string::size_type pos = line.find('\t');
1511 if(pos == std::string::npos) continue; //invalid line
1513 std::string hash = line.substr(0, pos);
1514 ++pos; prevPos = pos; pos = line.find('\t', pos); if(pos == std::string::npos) continue;
1516 bool forMerge = pos == prevPos;
1517 ++pos; prevPos = pos; pos = line.size(); if(pos == std::string::npos) continue;
1519 //std::string remoteBranch = line.substr(prevPos, pos - prevPos);
1521 //Process this line
1522 if(forMerge)
1524 hashToReturn = hash;
1525 ++forMergeLineCount;
1526 if(forMergeLineCount > 1)
1527 return L""; //More then 1 branch to merge found. Octopus merge needed. Cannot pick single ref from FETCH_HEAD
1531 return CUnicodeUtils::GetUnicode(hashToReturn.c_str());
1534 int CGit::GetBranchList(STRING_VECTOR &list, int *current, BRANCH_TYPE type, bool skipCurrent /*false*/)
1536 size_t prevCount = list.size();
1537 int ret = 0;
1538 CString cur;
1539 bool headIsDetached = false;
1540 if (m_IsUseLibGit2)
1542 CAutoRepository repo(GetGitRepository());
1543 if (!repo)
1544 return -1;
1546 if (git_repository_head_detached(repo) == 1)
1547 headIsDetached = true;
1549 if ((type & (BRANCH_LOCAL | BRANCH_REMOTE)) != 0)
1551 git_branch_t flags = GIT_BRANCH_LOCAL;
1552 if ((type & BRANCH_LOCAL) && (type & BRANCH_REMOTE))
1553 flags = GIT_BRANCH_ALL;
1554 else if (type & BRANCH_REMOTE)
1555 flags = GIT_BRANCH_REMOTE;
1557 CAutoBranchIterator it;
1558 if (git_branch_iterator_new(it.GetPointer(), repo, flags))
1559 return -1;
1561 CAutoReference ref;
1562 git_branch_t branchType;
1563 while (git_branch_next(ref.GetPointer(), &branchType, it) == 0)
1565 const char * name = nullptr;
1566 if (git_branch_name(&name, ref))
1567 continue;
1569 CString branchname = CUnicodeUtils::GetUnicode(name);
1570 if (branchType & GIT_BRANCH_REMOTE)
1571 list.push_back(L"remotes/" + branchname);
1572 else
1574 if (git_branch_is_head(ref))
1576 if (skipCurrent)
1577 continue;
1578 cur = branchname;
1580 list.push_back(branchname);
1585 else
1587 CString cmd = L"git.exe branch --no-color";
1589 if ((type & BRANCH_ALL) == BRANCH_ALL)
1590 cmd += L" -a";
1591 else if (type & BRANCH_REMOTE)
1592 cmd += L" -r";
1594 ret = Run(cmd, [&](CStringA lineA)
1596 lineA.Trim(" \r\n\t");
1597 if (lineA.IsEmpty())
1598 return;
1599 if (lineA.Find(" -> ") >= 0)
1600 return; // skip something like: refs/origin/HEAD -> refs/origin/master
1602 CString branch = CUnicodeUtils::GetUnicode(lineA);
1603 if (lineA[0] == '*')
1605 if (skipCurrent)
1606 return;
1607 branch = branch.Mid((int)wcslen(L"* "));
1608 cur = branch;
1610 // check whether HEAD is detached
1611 CString currentHead;
1612 if (branch[0] == L'(' && GetCurrentBranchFromFile(m_CurrentDir, currentHead) == 1)
1614 headIsDetached = true;
1615 return;
1618 if ((type & BRANCH_REMOTE) != 0 && (type & BRANCH_LOCAL) == 0)
1619 branch = L"remotes/" + branch;
1620 list.push_back(branch);
1622 if (ret == 1 && IsInitRepos())
1623 return 0;
1626 if(type & BRANCH_FETCH_HEAD && !DerefFetchHead().IsEmpty())
1627 list.push_back(L"FETCH_HEAD");
1629 std::sort(list.begin() + prevCount, list.end(), LogicalCompareBranchesPredicate);
1631 if (current && !headIsDetached && !skipCurrent)
1633 for (unsigned int i = 0; i < list.size(); ++i)
1635 if (list[i] == cur)
1637 *current = i;
1638 break;
1643 return ret;
1646 int CGit::GetRefsCommitIsOn(STRING_VECTOR& list, const CGitHash& hash, bool includeTags, bool includeBranches, BRANCH_TYPE type)
1648 if (!includeTags && !includeBranches || hash.IsEmpty())
1649 return 0;
1651 size_t prevCount = list.size();
1652 if (m_IsUseLibGit2)
1654 CAutoRepository repo(GetGitRepository());
1655 if (!repo)
1656 return -1;
1658 CAutoReferenceIterator it;
1659 if (git_reference_iterator_new(it.GetPointer(), repo))
1660 return -1;
1662 auto checkDescendent = [&list, &hash, &repo](const git_oid* oid, const git_reference* ref) {
1663 if (!oid)
1664 return;
1665 if (git_oid_equal(oid, hash) || git_graph_descendant_of(repo, oid, hash) == 1)
1667 const char* name = git_reference_name(ref);
1668 if (!name)
1669 return;
1671 list.push_back(CUnicodeUtils::GetUnicode(name));
1675 CAutoReference ref;
1676 while (git_reference_next(ref.GetPointer(), it) == 0)
1678 if (git_reference_is_tag(ref))
1680 if (!includeTags)
1681 continue;
1683 CAutoTag tag;
1684 if (git_tag_lookup(tag.GetPointer(), repo, git_reference_target(ref)) == 0)
1686 CAutoObject obj;
1687 if (git_tag_peel(obj.GetPointer(), tag) < 0)
1688 continue;
1689 checkDescendent(git_object_id(obj), ref);
1690 continue;
1693 else if (git_reference_is_remote(ref))
1695 if (!includeBranches || !(type & BRANCH_REMOTE))
1696 continue;
1698 else if (git_reference_is_branch(ref))
1700 if (!includeBranches || !(type & GIT_BRANCH_LOCAL))
1701 continue;
1703 else
1704 continue;
1706 if (git_reference_type(ref) == GIT_REF_SYMBOLIC)
1708 CAutoReference peeledRef;
1709 if (git_reference_resolve(peeledRef.GetPointer(), ref) < 0)
1710 continue;
1712 checkDescendent(git_reference_target(peeledRef), ref);
1713 continue;
1716 checkDescendent(git_reference_target(ref), ref);
1719 else
1721 if (includeBranches)
1723 CString cmd = L"git.exe branch --no-color";
1724 if ((type & BRANCH_ALL) == BRANCH_ALL)
1725 cmd += L" -a";
1726 else if (type & BRANCH_REMOTE)
1727 cmd += L" -r";
1728 cmd += L" --contains " + hash.ToString();
1730 if (Run(cmd, [&](CStringA lineA)
1732 lineA.Trim(" \r\n\t");
1733 if (lineA.IsEmpty())
1734 return;
1735 if (lineA.Find(" -> ") >= 0) // normalize symbolic refs: "refs/origin/HEAD -> refs/origin/master" to "refs/origin/HEAD"
1736 lineA.Truncate(lineA.Find(" -> "));
1738 CString branch = CUnicodeUtils::GetUnicode(lineA);
1739 if (lineA[0] == '*')
1741 branch = branch.Mid((int)wcslen(L"* "));
1742 CString currentHead;
1743 if (branch[0] == L'(' && GetCurrentBranchFromFile(m_CurrentDir, currentHead) == 1)
1744 return;
1747 if ((type & BRANCH_REMOTE) != 0 && (type & BRANCH_LOCAL) == 0)
1748 branch = L"refs/remotes/" + branch;
1749 else if (CStringUtils::StartsWith(branch, L"remotes/"))
1750 branch = L"refs/" + branch;
1751 else
1752 branch = L"refs/heads/" + branch;
1753 list.push_back(branch);
1755 return -1;
1758 if (includeTags)
1760 CString cmd = L"git.exe tag --contains " + hash.ToString();
1761 if (Run(cmd, [&list](CStringA lineA)
1763 if (lineA.IsEmpty())
1764 return;
1765 list.push_back(L"refs/tags/" + CUnicodeUtils::GetUnicode(lineA));
1767 return -1;
1771 std::sort(list.begin() + prevCount, list.end(), LogicalCompareBranchesPredicate);
1772 list.erase(std::unique(list.begin() + prevCount, list.end()), list.end());
1774 return 0;
1777 int CGit::GetRemoteList(STRING_VECTOR &list)
1779 size_t prevCount = list.size();
1780 if (this->m_IsUseLibGit2)
1782 CAutoRepository repo(GetGitRepository());
1783 if (!repo)
1784 return -1;
1786 CAutoStrArray remotes;
1787 if (git_remote_list(remotes, repo))
1788 return -1;
1790 for (size_t i = 0; i < remotes->count; ++i)
1792 CStringA remote(remotes->strings[i]);
1793 list.push_back(CUnicodeUtils::GetUnicode(remote));
1796 std::sort(list.begin() + prevCount, list.end(), LogicalComparePredicate);
1798 return 0;
1801 return Run(L"git.exe remote", [&](const CStringA& lineA)
1803 if (lineA.IsEmpty())
1804 return;
1805 list.push_back(CUnicodeUtils::GetUnicode(lineA));
1809 int CGit::GetRemoteTags(const CString& remote, REF_VECTOR& list)
1811 size_t prevCount = list.size();
1812 if (UsingLibGit2(GIT_CMD_FETCH))
1814 CAutoRepository repo(GetGitRepository());
1815 if (!repo)
1816 return -1;
1818 CStringA remoteA = CUnicodeUtils::GetUTF8(remote);
1819 CAutoRemote gitremote;
1820 // first try with a named remote (e.g. "origin")
1821 if (git_remote_lookup(gitremote.GetPointer(), repo, remoteA) < 0)
1823 // retry with repository located at a specific url
1824 if (git_remote_create_anonymous(gitremote.GetPointer(), repo, remoteA) < 0)
1825 return -1;
1828 git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
1829 callbacks.credentials = g_Git2CredCallback;
1830 callbacks.certificate_check = g_Git2CheckCertificateCallback;
1831 git_proxy_options proxy = GIT_PROXY_OPTIONS_INIT;
1832 proxy.type = GIT_PROXY_AUTO;
1833 if (git_remote_connect(gitremote, GIT_DIRECTION_FETCH, &callbacks, &proxy, nullptr) < 0)
1834 return -1;
1836 const git_remote_head** heads = nullptr;
1837 size_t size = 0;
1838 if (git_remote_ls(&heads, &size, gitremote) < 0)
1839 return -1;
1841 for (size_t i = 0; i < size; i++)
1843 CString ref = CUnicodeUtils::GetUnicode(heads[i]->name);
1844 CString shortname;
1845 if (!GetShortName(ref, shortname, L"refs/tags/"))
1846 continue;
1847 list.emplace_back(TGitRef{ shortname, &heads[i]->oid });
1849 std::sort(list.begin() + prevCount, list.end(), g_bSortTagsReversed ? LogicalCompareReversedPredicate : LogicalComparePredicate);
1850 return 0;
1853 CString cmd;
1854 cmd.Format(L"git.exe ls-remote -t \"%s\"", (LPCTSTR)remote);
1855 gitLastErr = cmd + L'\n';
1856 if (Run(cmd, [&](CStringA lineA)
1858 CGitHash hash;
1859 hash.ConvertFromStrA(lineA.Mid(0, GIT_HASH_SIZE * 2));
1860 lineA = lineA.Mid(GIT_HASH_SIZE * 2 + (int)wcslen(L"\trefs/tags/")); // sha1, tab + refs/tags/
1861 if (!lineA.IsEmpty())
1862 list.emplace_back(TGitRef{ CUnicodeUtils::GetUnicode(lineA), hash });
1863 }, &gitLastErr))
1864 return -1;
1865 std::sort(list.begin() + prevCount, list.end(), g_bSortTagsReversed ? LogicalCompareReversedPredicate : LogicalComparePredicate);
1866 return 0;
1869 int CGit::DeleteRemoteRefs(const CString& sRemote, const STRING_VECTOR& list)
1871 if (UsingLibGit2(GIT_CMD_PUSH))
1873 CAutoRepository repo(GetGitRepository());
1874 if (!repo)
1875 return -1;
1877 CStringA remoteA = CUnicodeUtils::GetUTF8(sRemote);
1878 CAutoRemote remote;
1879 if (git_remote_lookup(remote.GetPointer(), repo, remoteA) < 0)
1880 return -1;
1882 git_push_options pushOpts = GIT_PUSH_OPTIONS_INIT;
1883 git_remote_callbacks& callbacks = pushOpts.callbacks;
1884 callbacks.credentials = g_Git2CredCallback;
1885 callbacks.certificate_check = g_Git2CheckCertificateCallback;
1886 std::vector<CStringA> refspecs;
1887 for (const auto& ref : list)
1888 refspecs.push_back(CUnicodeUtils::GetUTF8(L":" + ref));
1890 std::vector<char*> vc;
1891 vc.reserve(refspecs.size());
1892 std::transform(refspecs.begin(), refspecs.end(), std::back_inserter(vc), [](CStringA& s) -> char* { return s.GetBuffer(); });
1893 git_strarray specs = { vc.data(), vc.size() };
1895 if (git_remote_push(remote, &specs, &pushOpts) < 0)
1896 return -1;
1898 else
1900 CMassiveGitTaskBase mgtPush(L"push " + sRemote, FALSE);
1901 for (const auto& ref : list)
1902 mgtPush.AddFile(L':' + ref);
1904 BOOL cancel = FALSE;
1905 mgtPush.Execute(cancel);
1908 return 0;
1911 int libgit2_addto_list_each_ref_fn(git_reference *ref, void *payload)
1913 STRING_VECTOR *list = (STRING_VECTOR*)payload;
1914 list->push_back(CUnicodeUtils::GetUnicode(git_reference_name(ref)));
1915 return 0;
1918 int CGit::GetRefList(STRING_VECTOR &list)
1920 size_t prevCount = list.size();
1921 if (this->m_IsUseLibGit2)
1923 CAutoRepository repo(GetGitRepository());
1924 if (!repo)
1925 return -1;
1927 if (git_reference_foreach(repo, libgit2_addto_list_each_ref_fn, &list))
1928 return -1;
1930 std::sort(list.begin() + prevCount, list.end(), LogicalComparePredicate);
1932 return 0;
1935 int ret = Run(L"git.exe show-ref -d", [&](const CStringA& lineA)
1937 int start = lineA.Find(L' ');
1938 ASSERT(start == 2 * GIT_HASH_SIZE);
1939 if (start <= 0)
1940 return;
1942 CString name = CUnicodeUtils::GetUnicode(lineA.Mid(start + 1));
1943 if (list.empty() || name != *list.crbegin() + L"^{}")
1944 list.push_back(name);
1946 if (!ret)
1947 std::sort(list.begin() + prevCount, list.end(), LogicalComparePredicate);
1948 else if (ret == 1 && IsInitRepos())
1949 return 0;
1950 return ret;
1953 typedef struct map_each_ref_payload {
1954 git_repository * repo;
1955 MAP_HASH_NAME * map;
1956 } map_each_ref_payload;
1958 int libgit2_addto_map_each_ref_fn(git_reference *ref, void *payload)
1960 map_each_ref_payload *payloadContent = (map_each_ref_payload*)payload;
1962 CString str = CUnicodeUtils::GetUnicode(git_reference_name(ref));
1964 CAutoObject gitObject;
1965 if (git_revparse_single(gitObject.GetPointer(), payloadContent->repo, git_reference_name(ref)))
1966 return (git_reference_is_remote(ref) && git_reference_type(ref) == GIT_REF_SYMBOLIC) ? 0 : 1; // don't bail out for symbolic remote references ("git.exe show-ref -d" also doesn't complain), cf. issue #2926
1968 if (git_object_type(gitObject) == GIT_OBJ_TAG)
1970 str += L"^{}"; // deref tag
1971 CAutoObject derefedTag;
1972 if (git_object_peel(derefedTag.GetPointer(), gitObject, GIT_OBJ_ANY))
1973 return 1;
1974 gitObject.Swap(derefedTag);
1977 (*payloadContent->map)[git_object_id(gitObject)].push_back(str);
1979 return 0;
1981 int CGit::GetMapHashToFriendName(git_repository* repo, MAP_HASH_NAME &map)
1983 ATLASSERT(repo);
1985 map_each_ref_payload payloadContent = { repo, &map };
1987 if (git_reference_foreach(repo, libgit2_addto_map_each_ref_fn, &payloadContent))
1988 return -1;
1990 for (auto it = map.begin(); it != map.end(); ++it)
1992 std::sort(it->second.begin(), it->second.end());
1995 return 0;
1998 int CGit::GetMapHashToFriendName(MAP_HASH_NAME &map)
2000 if (this->m_IsUseLibGit2)
2002 CAutoRepository repo(GetGitRepository());
2003 if (!repo)
2004 return -1;
2006 return GetMapHashToFriendName(repo, map);
2009 int ret = Run(L"git.exe show-ref -d", [&](const CStringA& lineA)
2011 int start = lineA.Find(L' ');
2012 ASSERT(start == 2 * GIT_HASH_SIZE);
2013 if (start <= 0)
2014 return;
2016 CGitHash hash;
2017 hash.ConvertFromStrA(lineA);
2018 map[hash].push_back(CUnicodeUtils::GetUnicode(lineA.Mid(start + 1)));
2021 if (ret == 1 && IsInitRepos())
2022 return 0;
2023 return ret;
2026 int CGit::GuessRefForHash(CString& ref, const CGitHash& hash)
2028 MAP_HASH_NAME map;
2029 if (GetMapHashToFriendName(map))
2030 return -1;
2032 auto it = map.find(hash);
2033 if (it == map.cend())
2035 ref = hash.ToString().Left(GetShortHASHLength());
2036 return 0;
2039 const auto& reflist = it->second;
2040 for (const auto& reftype : { L"refs/heads/", L"refs/remotes/", L"refs/tags/" })
2042 auto found = std::find_if(reflist.cbegin(), reflist.cend(), [&reftype](const auto& ref) { return CStringUtils::StartsWith(ref, reftype); });
2043 if (found == reflist.cend())
2044 continue;
2046 GetShortName(*found, ref, reftype);
2047 return 0;
2050 ref = hash.ToString().Left(GetShortHASHLength());
2051 return 0;
2054 int CGit::GetBranchDescriptions(MAP_STRING_STRING& map)
2056 CAutoConfig config(true);
2057 if (git_config_add_file_ondisk(config, CGit::GetGitPathStringA(GetGitLocalConfig()), GIT_CONFIG_LEVEL_LOCAL, nullptr, FALSE) < 0)
2058 return -1;
2059 return git_config_foreach_match(config, "^branch\\..*\\.description$", [](const git_config_entry* entry, void* data)
2061 MAP_STRING_STRING* descriptions = (MAP_STRING_STRING*)data;
2062 CString key = CUnicodeUtils::GetUnicode(entry->name);
2063 // extract branch name from config key
2064 key = key.Mid((int)wcslen(L"branch."), key.GetLength() - (int)wcslen(L"branch.") - (int)wcslen(L".description"));
2065 descriptions->insert(std::make_pair(key, CUnicodeUtils::GetUnicode(entry->value)));
2066 return 0;
2067 }, &map);
2070 static void SetLibGit2SearchPath(int level, const CString &value)
2072 CStringA valueA = CUnicodeUtils::GetMulti(value, CP_UTF8);
2073 git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, level, (LPCSTR)valueA);
2076 static void SetLibGit2TemplatePath(const CString &value)
2078 CStringA valueA = CUnicodeUtils::GetMulti(value, CP_UTF8);
2079 git_libgit2_opts(GIT_OPT_SET_TEMPLATE_PATH, (LPCSTR)valueA);
2082 int CGit::FindAndSetGitExePath(BOOL bFallback)
2084 CRegString msysdir = CRegString(REG_MSYSGIT_PATH, L"", FALSE);
2085 CString str = msysdir;
2086 if (!str.IsEmpty() && PathFileExists(str + L"\\git.exe"))
2088 CGit::ms_LastMsysGitDir = str;
2089 return TRUE;
2092 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": git.exe not exists: %s\n", (LPCTSTR)CGit::ms_LastMsysGitDir);
2093 if (!bFallback)
2094 return FALSE;
2096 // first, search PATH if git/bin directory is already present
2097 if (FindGitPath())
2099 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": FindGitPath() => %s\n", (LPCTSTR)CGit::ms_LastMsysGitDir);
2100 msysdir = CGit::ms_LastMsysGitDir;
2101 msysdir.write();
2102 return TRUE;
2105 if (FindGitForWindows(str))
2107 msysdir = str;
2108 CGit::ms_LastMsysGitDir = str;
2109 msysdir.write();
2110 return TRUE;
2113 return FALSE;
2116 BOOL CGit::CheckMsysGitDir(BOOL bFallback)
2118 if (m_bInitialized)
2119 return TRUE;
2121 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": CheckMsysGitDir(%d)\n", bFallback);
2122 this->m_Environment.clear();
2123 m_Environment.CopyProcessEnvironment();
2125 // Git for Windows 2.10.1 and 2.10.2 require LC_ALL to be set, see https://tortoisegit.org/issue/2859 and https://github.com/git-for-windows/git/issues/945,
2126 // because MSys2 changed the default to "ASCII". SO, make sure we have a proper default set
2127 if (m_Environment.GetEnv(L"LC_ALL").IsEmpty())
2128 m_Environment.SetEnv(L"LC_ALL", L"C");
2130 // set HOME if not set already
2131 size_t homesize;
2132 _wgetenv_s(&homesize, nullptr, 0, L"HOME");
2133 if (!homesize)
2134 m_Environment.SetEnv(L"HOME", GetHomeDirectory());
2136 //setup ssh client
2137 CString sshclient = CRegString(L"Software\\TortoiseGit\\SSH");
2138 if (sshclient.IsEmpty())
2139 sshclient = CRegString(L"Software\\TortoiseGit\\SSH", L"", FALSE, HKEY_LOCAL_MACHINE);
2141 if(!sshclient.IsEmpty())
2143 m_Environment.SetEnv(L"GIT_SSH", sshclient);
2144 if (CStringUtils::EndsWithI(sshclient, L"tortoisegitplink") || CStringUtils::EndsWithI(sshclient, L"tortoisegitplink.exe"))
2145 m_Environment.SetEnv(L"GIT_SSH_VARIANT", L"ssh");
2146 m_Environment.SetEnv(L"SVN_SSH", sshclient);
2148 else
2150 TCHAR sPlink[MAX_PATH] = {0};
2151 GetModuleFileName(nullptr, sPlink, _countof(sPlink));
2152 LPTSTR ptr = wcsrchr(sPlink, L'\\');
2153 if (ptr) {
2154 wcscpy_s(ptr + 1, _countof(sPlink) - (ptr - sPlink + 1), L"TortoiseGitPlink.exe");
2155 m_Environment.SetEnv(L"GIT_SSH", sPlink);
2156 m_Environment.SetEnv(L"GIT_SSH_VARIANT", L"ssh");
2157 m_Environment.SetEnv(L"SVN_SSH", sPlink);
2162 TCHAR sAskPass[MAX_PATH] = {0};
2163 GetModuleFileName(nullptr, sAskPass, _countof(sAskPass));
2164 LPTSTR ptr = wcsrchr(sAskPass, L'\\');
2165 if (ptr)
2167 wcscpy_s(ptr + 1, _countof(sAskPass) - (ptr - sAskPass + 1), L"SshAskPass.exe");
2168 m_Environment.SetEnv(L"DISPLAY",L":9999");
2169 m_Environment.SetEnv(L"SSH_ASKPASS",sAskPass);
2170 m_Environment.SetEnv(L"GIT_ASKPASS",sAskPass);
2171 m_Environment.SetEnv(L"GIT_ASK_YESNO", sAskPass);
2175 if (!FindAndSetGitExePath(bFallback))
2176 return FALSE;
2178 CString msysGitDir;
2179 PathCanonicalize(CStrBuf(msysGitDir, MAX_PATH), CGit::ms_LastMsysGitDir + L"\\..\\");
2180 static const CString prefixes[] = { L"mingw64\\etc", L"mingw32\\etc", L"etc" };
2181 static const int prefixes_len[] = { 8, 8, 0 };
2182 for (int i = 0; i < _countof(prefixes); ++i)
2184 #ifndef _WIN64
2185 if (i == 0)
2186 continue;
2187 #endif
2188 if (PathIsDirectory(msysGitDir + prefixes[i])) {
2189 msysGitDir += prefixes[i].Left(prefixes_len[i]);
2190 break;
2193 if (ms_bMsys2Git) // in Msys2 git.exe is in usr\bin; this also need to be after the check for etc folder, as Msys2 also has mingw64\etc, but uses etc
2194 PathCanonicalize(CStrBuf(msysGitDir, MAX_PATH), CGit::ms_LastMsysGitDir + L"\\..\\..");
2195 CGit::ms_MsysGitRootDir = msysGitDir;
2197 if ((CString)CRegString(REG_SYSTEM_GITCONFIGPATH, L"", FALSE) != g_Git.GetGitSystemConfig())
2198 CRegString(REG_SYSTEM_GITCONFIGPATH, L"", FALSE) = g_Git.GetGitSystemConfig();
2200 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": ms_LastMsysGitDir = %s\n", (LPCTSTR)CGit::ms_LastMsysGitDir);
2201 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": ms_MsysGitRootDir = %s\n", (LPCTSTR)CGit::ms_MsysGitRootDir);
2202 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": System config = %s\n", (LPCTSTR)g_Git.GetGitSystemConfig());
2203 if (!ms_bCygwinGit && !ms_bMsys2Git)
2205 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": ProgramData config = %s\n", (LPCTSTR)g_Git.GetGitProgramDataConfig());
2206 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_PROGRAMDATA, CTGitPath(g_Git.GetGitProgramDataConfig()).GetContainingDirectory().GetWinPathString());
2208 else
2209 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_PROGRAMDATA, CTGitPath(g_Git.GetGitSystemConfig()).GetContainingDirectory().GetWinPathString());
2211 // Configure libgit2 search paths
2212 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_SYSTEM, CTGitPath(g_Git.GetGitSystemConfig()).GetContainingDirectory().GetWinPathString());
2213 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_GLOBAL, g_Git.GetHomeDirectory());
2214 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_XDG, g_Git.GetGitGlobalXDGConfigPath());
2215 static git_smart_subtransport_definition ssh_wintunnel_subtransport_definition = { [](git_smart_subtransport **out, git_transport* owner, void*) -> int { return git_smart_subtransport_ssh_wintunnel(out, owner, FindExecutableOnPath(g_Git.m_Environment.GetEnv(L"GIT_SSH"), g_Git.m_Environment.GetEnv(L"PATH")), g_Git.m_Environment); }, 0 };
2216 git_transport_register("ssh", git_transport_smart, &ssh_wintunnel_subtransport_definition);
2217 git_transport_register("ssh+git", git_transport_smart, &ssh_wintunnel_subtransport_definition);
2218 git_transport_register("git+ssh", git_transport_smart, &ssh_wintunnel_subtransport_definition);
2219 git_libgit2_opts(GIT_OPT_SET_USER_AGENT, "TortoiseGit libgit2");
2220 if (!(ms_bCygwinGit || ms_bMsys2Git))
2221 SetLibGit2TemplatePath(CGit::ms_MsysGitRootDir + L"share\\git-core\\templates");
2222 else
2223 SetLibGit2TemplatePath(CGit::ms_MsysGitRootDir + L"usr\\share\\git-core\\templates");
2225 m_Environment.AddToPath(CGit::ms_LastMsysGitDir);
2226 m_Environment.AddToPath((CString)CRegString(REG_MSYSGIT_EXTRA_PATH, L"", FALSE));
2228 #if !defined(TGITCACHE) && !defined(TORTOISESHELL)
2229 // register filter only once
2230 if (!git_filter_lookup("filter"))
2232 CString sh;
2233 for (const CString& binDirPrefix : { L"\\..\\usr\\bin", L"\\..\\bin", L"" })
2235 CString possibleShExe = CGit::ms_LastMsysGitDir + binDirPrefix + L"\\sh.exe";
2236 if (PathFileExists(possibleShExe))
2238 CString temp;
2239 PathCanonicalize(CStrBuf(temp, MAX_PATH), possibleShExe);
2240 sh.Format(L"\"%s\"", (LPCTSTR)temp);
2241 // we need to put the usr\bin folder on the path for Git for Windows based on msys2
2242 m_Environment.AddToPath(temp.Left(temp.GetLength() - (int)wcslen(L"\\sh.exe")));
2243 break;
2247 // Add %GIT_EXEC_PATH% to %PATH% when launching libgit2 filter executable
2248 // It is possible that the filter points to a git subcommand, that is located at libexec\git-core
2249 CString gitExecPath = CGit::ms_MsysGitRootDir;
2250 gitExecPath.Append(L"libexec\\git-core");
2251 m_Environment.AddToPath(gitExecPath);
2253 if (git_filter_register("filter", git_filter_filter_new(sh, m_Environment), GIT_FILTER_DRIVER_PRIORITY))
2254 return FALSE;
2256 #endif
2258 m_bInitialized = TRUE;
2259 return true;
2262 CString CGit::GetHomeDirectory() const
2264 const wchar_t * homeDir = wget_windows_home_directory();
2265 return CString(homeDir, (int)wcslen(homeDir));
2268 CString CGit::GetGitLocalConfig() const
2270 CString path;
2271 GitAdminDir::GetAdminDirPath(m_CurrentDir, path);
2272 path += L"config";
2273 return path;
2276 CStringA CGit::GetGitPathStringA(const CString &path)
2278 return CUnicodeUtils::GetUTF8(CTGitPath(path).GetGitPathString());
2281 CString CGit::GetGitGlobalConfig() const
2283 return g_Git.GetHomeDirectory() + L"\\.gitconfig";
2286 CString CGit::GetGitGlobalXDGConfigPath() const
2288 return g_Git.GetHomeDirectory() + L"\\.config\\git";
2291 CString CGit::GetGitGlobalXDGConfig() const
2293 return g_Git.GetGitGlobalXDGConfigPath() + L"\\config";
2296 CString CGit::GetGitProgramDataConfig() const
2298 const wchar_t* programdataConfig = wget_program_data_config();
2299 return CString(programdataConfig);
2302 CString CGit::GetGitSystemConfig() const
2304 const wchar_t * systemConfig = wget_msysgit_etc();
2305 return CString(systemConfig, (int)wcslen(systemConfig));
2308 BOOL CGit::CheckCleanWorkTree(bool stagedOk /* false */)
2310 if (UsingLibGit2(GIT_CMD_CHECK_CLEAN_WT))
2312 CAutoRepository repo(GetGitRepository());
2313 if (!repo)
2314 return FALSE;
2316 if (git_repository_head_unborn(repo))
2317 return FALSE;
2319 git_status_options statusopt = GIT_STATUS_OPTIONS_INIT;
2320 statusopt.show = stagedOk ? GIT_STATUS_SHOW_WORKDIR_ONLY : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
2321 statusopt.flags = GIT_STATUS_OPT_UPDATE_INDEX | GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
2323 CAutoStatusList status;
2324 if (git_status_list_new(status.GetPointer(), repo, &statusopt))
2325 return FALSE;
2327 return (0 == git_status_list_entrycount(status));
2330 if (Run(L"git.exe rev-parse --verify HEAD", nullptr, nullptr, CP_UTF8))
2331 return FALSE;
2333 if (Run(L"git.exe update-index --ignore-submodules --refresh", nullptr, nullptr, CP_UTF8))
2334 return FALSE;
2336 if (Run(L"git.exe diff-files --quiet --ignore-submodules", nullptr, nullptr, CP_UTF8))
2337 return FALSE;
2339 if (!stagedOk && Run(L"git.exe diff-index --cached --quiet HEAD --ignore-submodules --", nullptr, nullptr, CP_UTF8))
2340 return FALSE;
2342 return TRUE;
2344 int CGit::Revert(const CString& commit, const CTGitPathList &list, CString& err)
2346 for (int i = 0; i < list.GetCount(); ++i)
2348 if (Revert(commit, (CTGitPath&)list[i], err))
2349 return -1;
2351 return 0;
2353 int CGit::Revert(const CString& commit, const CTGitPath &path, CString& err)
2355 if(path.m_Action & CTGitPath::LOGACTIONS_REPLACED && !path.GetGitOldPathString().IsEmpty())
2357 if (CTGitPath(path.GetGitOldPathString()).IsDirectory())
2359 err.Format(L"Cannot revert renaming of \"%s\". A directory with the old name \"%s\" exists.", (LPCTSTR)path.GetGitPathString(), (LPCTSTR)path.GetGitOldPathString());
2360 return -1;
2362 if (path.Exists())
2364 CString force;
2365 // if the filenames only differ in case, we have to pass "-f"
2366 if (path.GetGitPathString().CompareNoCase(path.GetGitOldPathString()) == 0)
2367 force = L"-f ";
2368 CString cmd;
2369 cmd.Format(L"git.exe mv %s-- \"%s\" \"%s\"", (LPCTSTR)force, (LPCTSTR)path.GetGitPathString(), (LPCTSTR)path.GetGitOldPathString());
2370 if (Run(cmd, &err, CP_UTF8))
2371 return -1;
2373 else
2375 CString cmd;
2376 cmd.Format(L"git.exe rm -f --cached -- \"%s\"", (LPCTSTR)path.GetGitPathString());
2377 if (Run(cmd, &err, CP_UTF8))
2378 return -1;
2381 CString cmd;
2382 cmd.Format(L"git.exe checkout %s -f -- \"%s\"", (LPCTSTR)commit, (LPCWSTR)path.GetGitOldPathString());
2383 if (Run(cmd, &err, CP_UTF8))
2384 return -1;
2386 else if(path.m_Action & CTGitPath::LOGACTIONS_ADDED)
2387 { //To init git repository, there are not HEAD, so we can use git reset command
2388 CString cmd;
2389 cmd.Format(L"git.exe rm -f --cached -- \"%s\"", (LPCTSTR)path.GetGitPathString());
2391 if (Run(cmd, &err, CP_UTF8))
2392 return -1;
2394 else
2396 CString cmd;
2397 cmd.Format(L"git.exe checkout %s -f -- \"%s\"", (LPCTSTR)commit, (LPCTSTR)path.GetGitPathString());
2398 if (Run(cmd, &err, CP_UTF8))
2399 return -1;
2402 if (path.m_Action & CTGitPath::LOGACTIONS_DELETED)
2404 CString cmd;
2405 cmd.Format(L"git.exe add -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
2406 if (Run(cmd, &err, CP_UTF8))
2407 return -1;
2410 return 0;
2413 int CGit::HasWorkingTreeConflicts(git_repository* repo)
2415 ATLASSERT(repo);
2417 CAutoIndex index;
2418 if (git_repository_index(index.GetPointer(), repo))
2419 return -1;
2421 return git_index_has_conflicts(index);
2424 int CGit::HasWorkingTreeConflicts()
2426 if (UsingLibGit2(GIT_CMD_CHECKCONFLICTS))
2428 CAutoRepository repo(GetGitRepository());
2429 if (!repo)
2430 return -1;
2432 return HasWorkingTreeConflicts(repo);
2435 CString output;
2436 gitLastErr.Empty();
2437 if (Run(L"git.exe ls-files -u -t -z", &output, &gitLastErr, CP_UTF8))
2438 return -1;
2440 return output.IsEmpty() ? 0 : 1;
2443 bool CGit::IsFastForward(const CString &from, const CString &to, CGitHash * commonAncestor)
2445 if (UsingLibGit2(GIT_CMD_MERGE_BASE))
2447 CAutoRepository repo(GetGitRepository());
2448 if (!repo)
2449 return false;
2451 CGitHash fromHash, toHash, baseHash;
2452 if (GetHash(repo, toHash, FixBranchName(to)))
2453 return false;
2455 if (GetHash(repo, fromHash, FixBranchName(from)))
2456 return false;
2458 git_oid baseOid;
2459 if (git_merge_base(&baseOid, repo, toHash, fromHash))
2460 return false;
2462 baseHash = baseOid;
2464 if (commonAncestor)
2465 *commonAncestor = baseHash;
2467 return fromHash == baseHash;
2469 // else
2470 CString base;
2471 CGitHash basehash,hash;
2472 CString cmd;
2473 cmd.Format(L"git.exe merge-base %s %s", (LPCTSTR)FixBranchName(to), (LPCTSTR)FixBranchName(from));
2475 gitLastErr.Empty();
2476 if (Run(cmd, &base, &gitLastErr, CP_UTF8))
2477 return false;
2478 basehash = base.Trim();
2480 GetHash(hash, from);
2482 if (commonAncestor)
2483 *commonAncestor = basehash;
2485 return hash == basehash;
2488 unsigned int CGit::Hash2int(const CGitHash &hash)
2490 int ret=0;
2491 for (int i = 0; i < 4; ++i)
2493 ret = ret << 8;
2494 ret |= static_cast<const unsigned char*>(hash)[i];
2496 return ret;
2499 int CGit::RefreshGitIndex()
2501 CString adminDir;
2502 GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir);
2503 // HACK: don't use internal update-index if we have a git-lfs enabled repository as the libgit version fails when executing the filter, issue #3220
2504 if (g_Git.m_IsUseGitDLL && !PathFileExists(adminDir + L"lfs"))
2506 CAutoLocker lock(g_Git.m_critGitDllSec);
2509 g_Git.CheckAndInitDll();
2511 int result = git_update_index();
2512 git_exit_cleanup();
2513 return result;
2515 }catch(...)
2517 git_exit_cleanup();
2518 return -1;
2522 else
2523 return Run(L"git.exe update-index --refresh", nullptr, nullptr, CP_UTF8);
2526 int CGit::GetOneFile(const CString &Refname, const CTGitPath &path, const CString &outputfile)
2528 if (UsingLibGit2(GIT_CMD_GETONEFILE))
2530 CAutoRepository repo(GetGitRepository());
2531 if (!repo)
2532 return -1;
2534 CGitHash hash;
2535 if (GetHash(repo, hash, Refname))
2536 return -1;
2538 CAutoCommit commit;
2539 if (git_commit_lookup(commit.GetPointer(), repo, hash))
2540 return -1;
2542 CAutoTree tree;
2543 if (git_commit_tree(tree.GetPointer(), commit))
2544 return -1;
2546 CAutoTreeEntry entry;
2547 int ret = git_tree_entry_bypath(entry.GetPointer(), tree, CUnicodeUtils::GetUTF8(path.GetGitPathString()));
2548 if (ret)
2549 return ret;
2551 if (git_tree_entry_filemode(entry) == GIT_FILEMODE_COMMIT)
2553 giterr_set_str(GITERR_NONE, "The requested object is a submodule and not a file.");
2554 return -1;
2557 CAutoBlob blob;
2558 if (git_tree_entry_to_object((git_object**)blob.GetPointer(), repo, entry))
2559 return -1;
2561 CAutoFILE file = _wfsopen(outputfile, L"wb", SH_DENYWR);
2562 if (file == nullptr)
2564 giterr_set_str(GITERR_NONE, "Could not create file.");
2565 return -1;
2567 CAutoBuf buf;
2568 if (git_blob_filtered_content(buf, blob, CUnicodeUtils::GetUTF8(path.GetGitPathString()), 0))
2569 return -1;
2570 if (fwrite(buf->ptr, sizeof(char), buf->size, file) != buf->size)
2572 giterr_set_str(GITERR_OS, "Could not write to file.");
2573 return -1;
2576 return 0;
2578 else if (g_Git.m_IsUseGitDLL)
2580 CAutoLocker lock(g_Git.m_critGitDllSec);
2583 g_Git.CheckAndInitDll();
2584 CStringA ref, patha, outa;
2585 ref = CUnicodeUtils::GetMulti(Refname, CP_UTF8);
2586 patha = CUnicodeUtils::GetMulti(path.GetGitPathString(), CP_UTF8);
2587 outa = CUnicodeUtils::GetMulti(outputfile, CP_UTF8);
2588 ::DeleteFile(outputfile);
2589 int ret = git_checkout_file(ref, patha, outa.GetBuffer());
2590 outa.ReleaseBuffer();
2591 return ret;
2594 catch (const char * msg)
2596 gitLastErr = L"gitdll.dll reports: " + CString(msg);
2597 return -1;
2599 catch (...)
2601 gitLastErr = L"An unknown gitdll.dll error occurred.";
2602 return -1;
2605 else
2607 CString cmd;
2608 cmd.Format(L"git.exe cat-file -p %s:\"%s\"", (LPCTSTR)Refname, (LPCTSTR)path.GetGitPathString());
2609 gitLastErr.Empty();
2610 return RunLogFile(cmd, outputfile, &gitLastErr);
2614 void CEnvironment::clear()
2616 __super::clear();
2617 baseptr = nullptr;
2620 bool CEnvironment::empty()
2622 return size() < 3; // three is minimum for an empty environment with an empty key and empty value: "=\0\0"
2625 CEnvironment::operator LPTSTR()
2627 if (empty())
2628 return nullptr;
2629 return data();
2632 CEnvironment::operator LPWSTR*()
2634 return &baseptr;
2637 void CEnvironment::CopyProcessEnvironment()
2639 if (!empty())
2640 pop_back();
2641 TCHAR *porig = GetEnvironmentStrings();
2642 TCHAR *p = porig;
2643 while(*p !=0 || *(p+1) !=0)
2644 this->push_back(*p++);
2646 push_back(L'\0');
2647 push_back(L'\0');
2648 baseptr = data();
2650 FreeEnvironmentStrings(porig);
2653 CString CEnvironment::GetEnv(const TCHAR *name)
2655 CString str;
2656 for (size_t i = 0; i < size(); ++i)
2658 str = &(*this)[i];
2659 int start =0;
2660 CString sname = str.Tokenize(L"=", start);
2661 if(sname.CompareNoCase(name) == 0)
2662 return &(*this)[i+start];
2663 i+=str.GetLength();
2665 return L"";
2668 void CEnvironment::SetEnv(const TCHAR *name, const TCHAR* value)
2670 unsigned int i;
2671 for (i = 0; i < size(); ++i)
2673 CString str = &(*this)[i];
2674 int start =0;
2675 CString sname = str.Tokenize(L"=", start);
2676 if(sname.CompareNoCase(name) == 0)
2677 break;
2678 i+=str.GetLength();
2681 if(i == size())
2683 if (!value) // as we haven't found the variable we want to remove, just return
2684 return;
2685 if (i == 0) // make inserting into an empty environment work
2687 this->push_back(L'\0');
2688 ++i;
2690 i -= 1; // roll back terminate \0\0
2691 this->push_back(L'\0');
2694 CEnvironment::iterator it;
2695 it=this->begin();
2696 it += i;
2698 while(*it && i<size())
2700 this->erase(it);
2701 it=this->begin();
2702 it += i;
2705 if (value == nullptr) // remove the variable
2707 this->erase(it);
2708 if (empty())
2709 baseptr = nullptr;
2710 else
2711 baseptr = data();
2712 return;
2715 while(*name)
2717 this->insert(it,*name++);
2718 ++i;
2719 it= begin()+i;
2722 this->insert(it, L'=');
2723 ++i;
2724 it= begin()+i;
2726 while(*value)
2728 this->insert(it,*value++);
2729 ++i;
2730 it= begin()+i;
2732 baseptr = data();
2735 void CEnvironment::AddToPath(CString value)
2737 value.TrimRight(L'\\');
2738 if (value.IsEmpty())
2739 return;
2741 CString path = GetEnv(L"PATH").TrimRight(L';') + L';';
2743 // do not double add paths to %PATH%
2744 if (path.Find(value + L';') >= 0 || path.Find(value + L"\\;") >= 0)
2745 return;
2747 path += value;
2749 SetEnv(L"PATH", path);
2752 int CGit::GetGitEncode(TCHAR* configkey)
2754 CString str=GetConfigValue(configkey);
2756 if(str.IsEmpty())
2757 return CP_UTF8;
2759 return CUnicodeUtils::GetCPCode(str);
2762 int CGit::GetShortHASHLength() const
2764 return 8;
2767 CString CGit::GetShortName(const CString& ref, REF_TYPE *out_type)
2769 CString str=ref;
2770 CString shortname;
2771 REF_TYPE type = CGit::UNKNOWN;
2773 if (CGit::GetShortName(str, shortname, L"refs/heads/"))
2774 type = CGit::LOCAL_BRANCH;
2775 else if (CGit::GetShortName(str, shortname, L"refs/remotes/"))
2776 type = CGit::REMOTE_BRANCH;
2777 else if (CStringUtils::EndsWith(str, L"^{}") && CGit::GetShortName(str, shortname, L"refs/tags/"))
2778 type = CGit::ANNOTATED_TAG;
2779 else if (CGit::GetShortName(str, shortname, L"refs/tags/"))
2780 type = CGit::TAG;
2781 else if (CGit::GetShortName(str, shortname, L"refs/stash"))
2783 type = CGit::STASH;
2784 shortname = L"stash";
2786 else if (CGit::GetShortName(str, shortname, L"refs/bisect/"))
2788 CString bisectGood;
2789 CString bisectBad;
2790 g_Git.GetBisectTerms(&bisectGood, &bisectBad);
2791 TCHAR c;
2792 if (CStringUtils::StartsWith(shortname, bisectGood) && ((c = shortname.GetAt(bisectGood.GetLength())) == '-' || c == '\0'))
2794 type = CGit::BISECT_GOOD;
2795 shortname = bisectGood;
2798 if (CStringUtils::StartsWith(shortname, bisectBad) && ((c = shortname.GetAt(bisectBad.GetLength())) == '-' || c == '\0'))
2800 type = CGit::BISECT_BAD;
2801 shortname = bisectBad;
2804 if (CStringUtils::StartsWith(shortname, L"skip") && ((c = shortname.GetAt(4)) == '-' || c == '\0'))
2806 type = CGit::BISECT_SKIP;
2807 shortname = L"skip";
2810 else if (CGit::GetShortName(str, shortname, L"refs/notes/"))
2811 type = CGit::NOTES;
2812 else if (CGit::GetShortName(str, shortname, L"refs/"))
2813 type = CGit::UNKNOWN;
2814 else
2816 type = CGit::UNKNOWN;
2817 shortname = ref;
2820 if(out_type)
2821 *out_type = type;
2823 return shortname;
2826 bool CGit::UsingLibGit2(LIBGIT2_CMD cmd) const
2828 return m_IsUseLibGit2 && ((1 << cmd) & m_IsUseLibGit2_mask) ? true : false;
2831 void CGit::SetGit2CredentialCallback(void* callback)
2833 g_Git2CredCallback = (git_cred_acquire_cb)callback;
2836 void CGit::SetGit2CertificateCheckCertificate(void* callback)
2838 g_Git2CheckCertificateCallback = (git_transport_certificate_check_cb)callback;
2841 CString CGit::GetUnifiedDiffCmd(const CTGitPath& path, const CString& rev1, const CString& rev2, bool bMerge, bool bCombine, int diffContext, bool bNoPrefix)
2843 CString cmd;
2844 if (rev2 == GitRev::GetWorkingCopy())
2845 cmd.Format(L"git.exe diff --stat%s -p %s --", bNoPrefix ? L" --no-prefix" : L"", (LPCTSTR)rev1);
2846 else if (rev1 == GitRev::GetWorkingCopy())
2847 cmd.Format(L"git.exe diff -R --stat%s -p %s --", bNoPrefix ? L" --no-prefix" : L"", (LPCTSTR)rev2);
2848 else
2850 CString merge;
2851 if (bMerge)
2852 merge += L" -m";
2854 if (bCombine)
2855 merge += L" -c";
2857 CString unified;
2858 if (diffContext >= 0)
2859 unified.Format(L" --unified=%d", diffContext);
2860 cmd.Format(L"git.exe diff-tree -r -p%s%s --stat%s %s %s --", (LPCTSTR)merge, (LPCTSTR)unified, bNoPrefix ? L" --no-prefix" : L"", (LPCTSTR)rev1, (LPCTSTR)rev2);
2863 if (!path.IsEmpty())
2865 cmd += L" \"";
2866 cmd += path.GetGitPathString();
2867 cmd += L'"';
2870 return cmd;
2873 static void UnifiedDiffStatToFile(const git_buf* text, void* payload)
2875 ATLASSERT(payload && text);
2876 fwrite(text->ptr, 1, text->size, (FILE *)payload);
2877 fwrite("\n", 1, 1, (FILE *)payload);
2880 static int UnifiedDiffToFile(const git_diff_delta * /* delta */, const git_diff_hunk * /* hunk */, const git_diff_line * line, void *payload)
2882 ATLASSERT(payload && line);
2883 if (line->origin == GIT_DIFF_LINE_CONTEXT || line->origin == GIT_DIFF_LINE_ADDITION || line->origin == GIT_DIFF_LINE_DELETION)
2884 fwrite(&line->origin, 1, 1, (FILE *)payload);
2885 fwrite(line->content, 1, line->content_len, (FILE *)payload);
2886 return 0;
2889 static int resolve_to_tree(git_repository *repo, const char *identifier, git_tree **tree)
2891 ATLASSERT(repo && identifier && tree);
2893 /* try to resolve identifier */
2894 CAutoObject obj;
2895 if (git_revparse_single(obj.GetPointer(), repo, identifier))
2896 return -1;
2898 if (obj == nullptr)
2899 return GIT_ENOTFOUND;
2901 int err = 0;
2902 switch (git_object_type(obj))
2904 case GIT_OBJ_TREE:
2905 *tree = (git_tree *)obj.Detach();
2906 break;
2907 case GIT_OBJ_COMMIT:
2908 err = git_commit_tree(tree, (git_commit *)(git_object*)obj);
2909 break;
2910 default:
2911 err = GIT_ENOTFOUND;
2914 return err;
2917 /* use libgit2 get unified diff */
2918 static int GetUnifiedDiffLibGit2(const CTGitPath& path, const CString& revOld, const CString& revNew, std::function<void(const git_buf*, void*)> statCallback, git_diff_line_cb callback, void* data, bool /* bMerge */, bool bNoPrefix)
2920 CStringA tree1 = CUnicodeUtils::GetMulti(revNew, CP_UTF8);
2921 CStringA tree2 = CUnicodeUtils::GetMulti(revOld, CP_UTF8);
2923 CAutoRepository repo(g_Git.GetGitRepository());
2924 if (!repo)
2925 return -1;
2927 int isHeadOrphan = git_repository_head_unborn(repo);
2928 if (isHeadOrphan == 1)
2929 return 0;
2930 else if (isHeadOrphan != 0)
2931 return -1;
2933 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
2934 CStringA pathA = CUnicodeUtils::GetMulti(path.GetGitPathString(), CP_UTF8);
2935 char *buf = pathA.GetBuffer();
2936 if (!pathA.IsEmpty())
2938 opts.pathspec.strings = &buf;
2939 opts.pathspec.count = 1;
2941 if (bNoPrefix)
2943 opts.new_prefix = "";
2944 opts.old_prefix = "";
2946 CAutoDiff diff;
2948 if (revNew == GitRev::GetWorkingCopy() || revOld == GitRev::GetWorkingCopy())
2950 CAutoTree t1;
2951 CAutoDiff diff2;
2953 if (revNew != GitRev::GetWorkingCopy() && resolve_to_tree(repo, tree1, t1.GetPointer()))
2954 return -1;
2956 if (revOld != GitRev::GetWorkingCopy() && resolve_to_tree(repo, tree2, t1.GetPointer()))
2957 return -1;
2959 if (git_diff_tree_to_index(diff.GetPointer(), repo, t1, nullptr, &opts))
2960 return -1;
2962 if (git_diff_index_to_workdir(diff2.GetPointer(), repo, nullptr, &opts))
2963 return -1;
2965 if (git_diff_merge(diff, diff2))
2966 return -1;
2968 else
2970 if (tree1.IsEmpty() && tree2.IsEmpty())
2971 return -1;
2973 if (tree1.IsEmpty())
2975 tree1 = tree2;
2976 tree2.Empty();
2979 CAutoTree t1;
2980 CAutoTree t2;
2981 if (!tree1.IsEmpty() && resolve_to_tree(repo, tree1, t1.GetPointer()))
2982 return -1;
2984 if (tree2.IsEmpty())
2986 /* don't check return value, there are not parent commit at first commit*/
2987 resolve_to_tree(repo, tree1 + "~1", t2.GetPointer());
2989 else if (resolve_to_tree(repo, tree2, t2.GetPointer()))
2990 return -1;
2991 if (git_diff_tree_to_tree(diff.GetPointer(), repo, t2, t1, &opts))
2992 return -1;
2995 CAutoDiffStats stats;
2996 if (git_diff_get_stats(stats.GetPointer(), diff))
2997 return -1;
2998 CAutoBuf statBuf;
2999 if (git_diff_stats_to_buf(statBuf, stats, GIT_DIFF_STATS_FULL, 0))
3000 return -1;
3001 statCallback(statBuf, data);
3003 for (size_t i = 0; i < git_diff_num_deltas(diff); ++i)
3005 CAutoPatch patch;
3006 if (git_patch_from_diff(patch.GetPointer(), diff, i))
3007 return -1;
3009 if (git_patch_print(patch, callback, data))
3010 return -1;
3013 pathA.ReleaseBuffer();
3015 return 0;
3018 int CGit::GetUnifiedDiff(const CTGitPath& path, const CString& rev1, const CString& rev2, CString patchfile, bool bMerge, bool bCombine, int diffContext, bool bNoPrefix)
3020 if (UsingLibGit2(GIT_CMD_DIFF))
3022 CAutoFILE file = _wfsopen(patchfile, L"wb", SH_DENYRW);
3023 if (!file)
3024 return -1;
3025 return GetUnifiedDiffLibGit2(path, rev1, rev2, UnifiedDiffStatToFile, UnifiedDiffToFile, file, bMerge, bNoPrefix);
3027 else
3029 CString cmd;
3030 cmd = GetUnifiedDiffCmd(path, rev1, rev2, bMerge, bCombine, diffContext, bNoPrefix);
3031 gitLastErr.Empty();
3032 return RunLogFile(cmd, patchfile, &gitLastErr);
3036 static void UnifiedDiffStatToStringA(const git_buf* text, void* payload)
3038 ATLASSERT(payload && text);
3039 CStringA *str = (CStringA*) payload;
3040 str->Append(text->ptr, (int)text->size);
3041 str->AppendChar('\n');
3044 static int UnifiedDiffToStringA(const git_diff_delta * /*delta*/, const git_diff_hunk * /*hunk*/, const git_diff_line *line, void *payload)
3046 ATLASSERT(payload && line);
3047 CStringA *str = (CStringA*) payload;
3048 if (line->origin == GIT_DIFF_LINE_CONTEXT || line->origin == GIT_DIFF_LINE_ADDITION || line->origin == GIT_DIFF_LINE_DELETION)
3049 str->Append(&line->origin, 1);
3050 str->Append(line->content, (int)line->content_len);
3051 return 0;
3054 int CGit::GetUnifiedDiff(const CTGitPath& path, const CString& rev1, const CString& rev2, CStringA* buffer, bool bMerge, bool bCombine, int diffContext)
3056 if (UsingLibGit2(GIT_CMD_DIFF))
3057 return GetUnifiedDiffLibGit2(path, rev1, rev2, UnifiedDiffStatToStringA, UnifiedDiffToStringA, buffer, bMerge, false);
3058 else
3060 CString cmd;
3061 cmd = GetUnifiedDiffCmd(path, rev1, rev2, bMerge, bCombine, diffContext);
3062 BYTE_VECTOR vector;
3063 int ret = Run(cmd, &vector);
3064 if (!vector.empty())
3066 vector.push_back(0); // vector is not NUL terminated
3067 buffer->Append((char*)vector.data());
3069 return ret;
3073 int CGit::GitRevert(int parent, const CGitHash &hash)
3075 if (UsingLibGit2(GIT_CMD_REVERT))
3077 CAutoRepository repo(GetGitRepository());
3078 if (!repo)
3079 return -1;
3081 CAutoCommit commit;
3082 if (git_commit_lookup(commit.GetPointer(), repo, hash))
3083 return -1;
3085 git_revert_options revert_opts = GIT_REVERT_OPTIONS_INIT;
3086 revert_opts.mainline = parent;
3087 int result = git_revert(repo, commit, &revert_opts);
3089 return !result ? 0 : -1;
3091 else
3093 CString cmd, merge;
3094 if (parent)
3095 merge.Format(L"-m %d ", parent);
3096 cmd.Format(L"git.exe revert --no-edit --no-commit %s%s", (LPCTSTR)merge, (LPCTSTR)hash.ToString());
3097 gitLastErr = cmd + L'\n';
3098 if (Run(cmd, &gitLastErr, CP_UTF8))
3099 return -1;
3100 else
3102 gitLastErr.Empty();
3103 return 0;
3108 int CGit::DeleteRef(const CString& reference)
3110 if (UsingLibGit2(GIT_CMD_DELETETAGBRANCH))
3112 CAutoRepository repo(GetGitRepository());
3113 if (!repo)
3114 return -1;
3116 CStringA refA;
3117 if (CStringUtils::EndsWith(reference, L"^{}"))
3118 refA = CUnicodeUtils::GetUTF8(reference.Left(reference.GetLength() - 3));
3119 else
3120 refA = CUnicodeUtils::GetUTF8(reference);
3122 CAutoReference ref;
3123 if (git_reference_lookup(ref.GetPointer(), repo, refA))
3124 return -1;
3126 int result = -1;
3127 if (git_reference_is_tag(ref))
3128 result = git_tag_delete(repo, git_reference_shorthand(ref));
3129 else if (git_reference_is_branch(ref))
3130 result = git_branch_delete(ref);
3131 else if (git_reference_is_remote(ref))
3132 result = git_branch_delete(ref);
3133 else
3134 result = git_reference_delete(ref);
3136 return result;
3138 else
3140 CString cmd, shortname;
3141 if (GetShortName(reference, shortname, L"refs/heads/"))
3142 cmd.Format(L"git.exe branch -D -- %s", (LPCTSTR)shortname);
3143 else if (GetShortName(reference, shortname, L"refs/tags/"))
3144 cmd.Format(L"git.exe tag -d -- %s", (LPCTSTR)shortname);
3145 else if (GetShortName(reference, shortname, L"refs/remotes/"))
3146 cmd.Format(L"git.exe branch -r -D -- %s", (LPCTSTR)shortname);
3147 else
3149 gitLastErr = L"unsupported reference type: " + reference;
3150 return -1;
3153 gitLastErr.Empty();
3154 if (Run(cmd, &gitLastErr, CP_UTF8))
3155 return -1;
3157 gitLastErr.Empty();
3158 return 0;
3162 bool CGit::LoadTextFile(const CString &filename, CString &msg)
3164 if (!PathFileExists(filename))
3165 return false;
3167 CAutoFILE pFile = _wfsopen(filename, L"rb", SH_DENYWR);
3168 if (!pFile)
3170 ::MessageBox(nullptr, L"Could not open " + filename, L"TortoiseGit", MB_ICONERROR);
3171 return true; // load no further files
3174 CStringA str;
3177 char s[8196] = { 0 };
3178 int read = (int)fread(s, sizeof(char), sizeof(s), pFile);
3179 if (read == 0)
3180 break;
3181 str += CStringA(s, read);
3182 } while (true);
3183 msg += CUnicodeUtils::GetUnicode(str);
3184 msg.Replace(L"\r\n", L"\n");
3185 msg.TrimRight(L'\n');
3186 msg += L'\n';
3188 return true; // load no further files
3191 int CGit::GetWorkingTreeChanges(CTGitPathList& result, bool amend, const CTGitPathList* filterlist, bool includedStaged /* = false */)
3193 if (IsInitRepos())
3194 return GetInitAddList(result);
3196 BYTE_VECTOR out;
3198 int count = 1;
3199 if (filterlist)
3200 count = filterlist->GetCount();
3201 ATLASSERT(count > 0);
3203 CString head = L"HEAD";
3204 if (amend)
3205 head = L"HEAD~1";
3207 CString gitStatusParams;
3208 if (ms_LastMsysGitVersion >= ConvertVersionToInt(2, 17, 0))
3209 gitStatusParams = L" --no-ahead-behind";
3211 for (int i = 0; i < count; ++i)
3213 ATLASSERT(!filterlist || !(*filterlist)[i].GetGitPathString().IsEmpty()); // pathspec must not be empty, be compatible with Git >= 2.16.0
3214 BYTE_VECTOR cmdout;
3215 CString cmd;
3216 if (ms_bCygwinGit || ms_bMsys2Git)
3218 // Prevent showing all files as modified when using cygwin's git
3219 if (!filterlist)
3220 cmd.Format(L"git.exe status%s --", (LPCTSTR)gitStatusParams);
3221 else
3222 cmd.Format(L"git.exe status%s -- \"%s\"", (LPCTSTR)gitStatusParams, (LPCTSTR)(*filterlist)[i].GetGitPathString());
3223 Run(cmd, &cmdout);
3224 cmdout.clear();
3227 // also list staged files which will be in the commit
3228 if (includedStaged || !filterlist)
3229 Run(L"git.exe diff-index --cached --raw " + head + L" --numstat -C -M -z --", &cmdout);
3230 else
3232 cmd.Format(L"git.exe diff-index --cached --raw %s --numstat -C -M -z -- \"%s\"", (LPCTSTR)head, (LPCTSTR)(*filterlist)[i].GetGitPathString());
3233 Run(cmd, &cmdout);
3236 if (!filterlist)
3237 cmd.Format(L"git.exe diff-index --raw %s --numstat -C%d%% -M%d%% -z --", (LPCTSTR)head, ms_iSimilarityIndexThreshold, ms_iSimilarityIndexThreshold);
3238 else
3239 cmd.Format(L"git.exe diff-index --raw %s --numstat -C%d%% -M%d%% -z -- \"%s\"", (LPCTSTR)head, ms_iSimilarityIndexThreshold, ms_iSimilarityIndexThreshold, (LPCTSTR)(*filterlist)[i].GetGitPathString());
3241 BYTE_VECTOR cmdErr;
3242 if (Run(cmd, &cmdout, &cmdErr))
3244 size_t last = cmdErr.RevertFind(0);
3245 CString str;
3246 if (last != BYTE_VECTOR::npos)
3247 CGit::StringAppend(&str, &cmdErr[last + 1], CP_UTF8, (int)(cmdErr.size() - last) - 1);
3248 else if (!cmdErr.empty())
3249 CGit::StringAppend(&str, cmdErr.data(), CP_UTF8, (int)cmdErr.size() - 1);
3250 else
3251 str.Format(L"\"%s\" exited with an error code, but did not output any error message", (LPCTSTR)cmd);
3252 MessageBox(nullptr, str, L"TortoiseGit", MB_OK | MB_ICONERROR);
3255 out.append(cmdout, 0);
3257 result.ParserFromLog(out);
3259 std::map<CString, int> duplicateMap;
3260 for (int i = 0; i < result.GetCount(); ++i)
3261 duplicateMap.insert(std::pair<CString, int>(result[i].GetGitPathString(), i));
3263 // handle delete conflict case, when remote : modified, local : deleted.
3264 for (int i = 0; i < count; ++i)
3266 BYTE_VECTOR cmdout;
3267 CString cmd;
3269 if (!filterlist)
3270 cmd = L"git.exe ls-files -u -t -z";
3271 else
3272 cmd.Format(L"git.exe ls-files -u -t -z -- \"%s\"", (LPCTSTR)(*filterlist)[i].GetGitPathString());
3274 Run(cmd, &cmdout);
3276 CTGitPathList conflictlist;
3277 conflictlist.ParserFromLog(cmdout);
3278 for (int j = 0; j < conflictlist.GetCount(); ++j)
3280 auto existing = duplicateMap.find(conflictlist[j].GetGitPathString());
3281 if (existing != duplicateMap.end())
3283 CTGitPath& p = const_cast<CTGitPath&>(result[existing->second]);
3284 p.m_Action |= CTGitPath::LOGACTIONS_UNMERGED;
3286 else
3288 result.AddPath(conflictlist[j]);
3289 duplicateMap.insert(std::pair<CString, int>(result[i].GetGitPathString(), result.GetCount() - 1));
3294 // handle source files of file renames/moves (issue #860)
3295 // if a file gets renamed and the new file "git add"ed, diff-index doesn't list the source file anymore
3296 for (int i = 0; i < count; ++i)
3298 BYTE_VECTOR cmdout;
3299 CString cmd;
3301 if (!filterlist)
3302 cmd = L"git.exe ls-files -d -z";
3303 else
3304 cmd.Format(L"git.exe ls-files -d -z -- \"%s\"", (LPCTSTR)(*filterlist)[i].GetGitPathString());
3306 Run(cmd, &cmdout);
3308 CTGitPathList deletelist;
3309 deletelist.ParserFromLog(cmdout, true);
3310 for (int j = 0; j < deletelist.GetCount(); ++j)
3312 auto existing = duplicateMap.find(deletelist[j].GetGitPathString());
3313 if (existing == duplicateMap.end())
3315 result.AddPath(deletelist[j]);
3316 duplicateMap.insert(std::pair<CString, int>(result[i].GetGitPathString(), result.GetCount() - 1));
3318 else
3320 CTGitPath& p = const_cast<CTGitPath&>(result[existing->second]);
3321 p.m_Action |= CTGitPath::LOGACTIONS_MISSING;
3326 return 0;
3329 int CGit::IsRebaseRunning()
3331 CString adminDir;
3332 if (!GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir))
3333 return -1;
3335 if (PathIsDirectory(adminDir + L"rebase-apply") || PathIsDirectory(adminDir + L"tgitrebase.active"))
3336 return 1;
3337 return 0;
3340 void CGit::GetBisectTerms(CString* good, CString* bad)
3342 static CString lastGood;
3343 static CString lastBad;
3344 static ULONGLONG lastRead = 0;
3346 SCOPE_EXIT
3348 if (bad)
3349 *bad = lastBad;
3350 if (good)
3351 *good = lastGood;
3354 #ifndef GTEST_INCLUDE_GTEST_GTEST_H_
3355 // add some caching here, because this method might be called multiple times in a short time from LogDlg and RevisionGraph
3356 // as we only read a small file the performance effects should be negligible
3357 if (lastRead + 5000 > GetTickCount64())
3358 return;
3359 #endif
3361 lastGood = L"good";
3362 lastBad = L"bad";
3364 CString adminDir;
3365 if (!GitAdminDir::GetAdminDirPath(m_CurrentDir, adminDir))
3366 return;
3368 CString termsFile = adminDir + L"BISECT_TERMS";
3369 CAutoFILE fp;
3370 _wfopen_s(fp.GetPointer(), termsFile, L"rb");
3371 if (!fp)
3372 return;
3373 char badA[MAX_PATH] = { 0 };
3374 fgets(badA, sizeof(badA), fp);
3375 size_t len = strlen(badA);
3376 if (len > 0 && badA[len - 1] == '\n')
3377 badA[len - 1] = '\0';
3378 char goodA[MAX_PATH] = { 0 };
3379 fgets(goodA, sizeof(goodA), fp);
3380 len = strlen(goodA);
3381 if (len > 0 && goodA[len - 1] == '\n')
3382 goodA[len - 1] = '\0';
3383 lastGood = CUnicodeUtils::GetUnicode(goodA);
3384 lastBad = CUnicodeUtils::GetUnicode(badA);
3385 lastRead = GetTickCount64();
3388 int CGit::GetGitVersion(CString* versiondebug, CString* errStr)
3390 CString version, err;
3391 if (Run(L"git.exe --version", &version, &err, CP_UTF8))
3393 if (errStr)
3394 *errStr = err;
3395 return -1;
3398 int ver = 0;
3399 if (versiondebug)
3400 *versiondebug = version;
3404 int start = 0;
3405 CString str = version.Tokenize(L".", start);
3406 int space = str.ReverseFind(L' ');
3407 str = str.Mid(space + 1, start);
3408 ver = _wtol(str);
3409 ver <<= 24;
3411 version = version.Mid(start);
3412 start = 0;
3414 str = version.Tokenize(L".", start);
3415 ver |= (_wtol(str) & 0xFF) << 16;
3417 str = version.Tokenize(L".", start);
3418 ver |= (_wtol(str) & 0xFF) << 8;
3420 str = version.Tokenize(L".", start);
3421 ver |= (_wtol(str) & 0xFF);
3423 catch (...)
3425 if (!ver)
3426 return -1;
3429 return ver;
3432 int CGit::GetGitNotes(const CGitHash& hash, CString& notes)
3434 CAutoRepository repo(GetGitRepository());
3435 if (!repo)
3436 return -1;
3438 CAutoNote note;
3439 int ret = git_note_read(note.GetPointer(), repo, nullptr, hash);
3440 if (ret == GIT_ENOTFOUND)
3442 notes.Empty();
3443 return 0;
3445 else if (ret)
3446 return -1;
3447 notes = CUnicodeUtils::GetUnicode(git_note_message(note));
3448 return 0;
3451 int CGit::SetGitNotes(const CGitHash& hash, const CString& notes)
3453 CAutoRepository repo(GetGitRepository());
3454 if (!repo)
3455 return -1;
3457 CAutoSignature signature;
3458 if (git_signature_default(signature.GetPointer(), repo) < 0)
3459 return -1;
3461 git_oid oid;
3462 if (git_note_create(&oid, repo, nullptr, signature, signature, hash, CUnicodeUtils::GetUTF8(notes), 1) < 0)
3463 return -1;
3465 return 0;