Extend static functions in CAppUtils with a window handle parameter
[TortoiseGit.git] / src / Git / Git.cpp
blob3f92f4c90183f8a1854f7214679e5475e170c1ed
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 bool CGit::ms_bCygwinGit = (CRegDWORD(L"Software\\TortoiseGit\\CygwinHack", FALSE) == TRUE);
38 bool CGit::ms_bMsys2Git = (CRegDWORD(L"Software\\TortoiseGit\\Msys2Hack", FALSE) == TRUE);
39 int CGit::m_LogEncode=CP_UTF8;
40 typedef CComCritSecLock<CComCriticalSection> CAutoLocker;
42 static LPCTSTR nextpath(const wchar_t* path, wchar_t* buf, size_t buflen)
44 if (!path || !buf || buflen == 0)
45 return nullptr;
47 const wchar_t* base = path;
48 wchar_t term = (*path == L'"') ? *path++ : L';';
50 for (buflen--; *path && *path != term && buflen; buflen--)
51 *buf++ = *path++;
53 *buf = L'\0'; /* reserved a byte via initial subtract */
55 while (*path == term || *path == L';')
56 ++path;
58 return (path != base) ? path : nullptr;
61 static CString FindFileOnPath(const CString& filename, LPCTSTR env, bool wantDirectory = false)
63 TCHAR buf[MAX_PATH] = { 0 };
65 // search in all paths defined in PATH
66 while ((env = nextpath(env, buf, _countof(buf) - 1)) != nullptr && *buf)
68 TCHAR* pfin = buf + wcslen(buf) - 1;
70 // ensure trailing slash
71 if (*pfin != L'/' && *pfin != L'\\')
72 wcscpy_s(++pfin, 2, L"\\"); // we have enough space left, MAX_PATH-1 is used in nextpath above
74 const size_t len = wcslen(buf);
76 if ((len + filename.GetLength()) < _countof(buf))
77 wcscpy_s(pfin + 1, _countof(buf) - len, filename);
78 else
79 break;
81 if (PathFileExists(buf))
83 if (wantDirectory)
84 pfin[1] = L'\0';
85 return buf;
89 return L"";
92 static BOOL FindGitPath()
94 size_t size;
95 _wgetenv_s(&size, nullptr, 0, L"PATH");
96 if (!size)
97 return FALSE;
99 TCHAR* env = (TCHAR*)alloca(size * sizeof(TCHAR));
100 if (!env)
101 return FALSE;
102 _wgetenv_s(&size, env, size, L"PATH");
104 CString gitExeDirectory = FindFileOnPath(L"git.exe", env, true);
105 if (!gitExeDirectory.IsEmpty())
107 CGit::ms_LastMsysGitDir = gitExeDirectory;
108 CGit::ms_LastMsysGitDir.TrimRight(L'\\');
109 if (CGit::ms_LastMsysGitDir.GetLength() > 12 && (CStringUtils::EndsWith(CGit::ms_LastMsysGitDir, L"\\mingw32\\bin") || CStringUtils::EndsWith(CGit::ms_LastMsysGitDir, L"\\mingw64\\bin")))
111 // prefer cmd directory as early Git for Windows 2.x releases only had this
112 CString installRoot = CGit::ms_LastMsysGitDir.Mid(0, CGit::ms_LastMsysGitDir.GetLength() - 12) + L"\\cmd\\git.exe";
113 if (PathFileExists(installRoot))
114 CGit::ms_LastMsysGitDir = CGit::ms_LastMsysGitDir.Mid(0, CGit::ms_LastMsysGitDir.GetLength() - 12) + L"\\cmd";
116 if (CGit::ms_LastMsysGitDir.GetLength() > 4 && CStringUtils::EndsWith(CGit::ms_LastMsysGitDir, L"\\cmd"))
118 // often the msysgit\cmd folder is on the %PATH%, but
119 // that git.exe does not work, so try to guess the bin folder
120 CString binDir = CGit::ms_LastMsysGitDir.Mid(0, CGit::ms_LastMsysGitDir.GetLength() - 4) + L"\\bin\\git.exe";
121 if (PathFileExists(binDir))
122 CGit::ms_LastMsysGitDir = CGit::ms_LastMsysGitDir.Mid(0, CGit::ms_LastMsysGitDir.GetLength() - 4) + L"\\bin";
124 return TRUE;
127 return FALSE;
130 static CString FindExecutableOnPath(const CString& executable, LPCTSTR env)
132 CString filename = executable;
134 if (!CStringUtils::EndsWith(executable, L".exe"))
135 filename += L".exe";
137 if (PathFileExists(filename))
138 return filename;
140 filename = FindFileOnPath(filename, env);
141 if (!filename.IsEmpty())
142 return filename;
144 return executable;
147 static bool g_bSortLogical;
148 static bool g_bSortLocalBranchesFirst;
149 static bool g_bSortTagsReversed;
150 static git_cred_acquire_cb g_Git2CredCallback;
151 static git_transport_certificate_check_cb g_Git2CheckCertificateCallback;
153 static void GetSortOptions()
155 #ifdef GTEST_INCLUDE_GTEST_GTEST_H_
156 g_bSortLogical = true;
157 g_bSortLocalBranchesFirst = true;
158 g_bSortTagsReversed = false;
159 #else
160 g_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
161 if (g_bSortLogical)
162 g_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
163 g_bSortLocalBranchesFirst = !CRegDWORD(L"Software\\TortoiseGit\\NoSortLocalBranchesFirst", 0, false, HKEY_CURRENT_USER);
164 if (g_bSortLocalBranchesFirst)
165 g_bSortLocalBranchesFirst = !CRegDWORD(L"Software\\TortoiseGit\\NoSortLocalBranchesFirst", 0, false, HKEY_LOCAL_MACHINE);
166 g_bSortTagsReversed = !!CRegDWORD(L"Software\\TortoiseGit\\SortTagsReversed", 0, false, HKEY_LOCAL_MACHINE);
167 if (!g_bSortTagsReversed)
168 g_bSortTagsReversed = !!CRegDWORD(L"Software\\TortoiseGit\\SortTagsReversed", 0, false, HKEY_CURRENT_USER);
169 #endif
172 static int LogicalComparePredicate(const CString &left, const CString &right)
174 if (g_bSortLogical)
175 return StrCmpLogicalW(left, right) < 0;
176 return StrCmpI(left, right) < 0;
179 static int LogicalCompareReversedPredicate(const CString &left, const CString &right)
181 return LogicalComparePredicate(right, left);
184 static int LogicalCompareBranchesPredicate(const CString &left, const CString &right)
186 if (g_bSortLocalBranchesFirst)
188 bool leftIsRemote = CStringUtils::StartsWith(left, L"remotes/");
189 bool rightIsRemote = CStringUtils::StartsWith(right, L"remotes/");
191 if (leftIsRemote && !rightIsRemote)
192 return false;
193 else if (!leftIsRemote && rightIsRemote)
194 return true;
196 return LogicalComparePredicate(left, right);
199 #define CALL_OUTPUT_READ_CHUNK_SIZE 1024
201 CString CGit::ms_LastMsysGitDir;
202 CString CGit::ms_MsysGitRootDir;
203 int CGit::ms_LastMsysGitVersion = 0;
204 CGit g_Git;
207 CGit::CGit(void)
209 git_libgit2_init();
210 GetCurrentDirectory(MAX_PATH, CStrBuf(m_CurrentDir, MAX_PATH));
211 m_IsGitDllInited = false;
212 m_GitDiff=0;
213 m_GitSimpleListDiff=0;
214 m_IsUseGitDLL = !!CRegDWORD(L"Software\\TortoiseGit\\UsingGitDLL",1);
215 m_IsUseLibGit2 = !!CRegDWORD(L"Software\\TortoiseGit\\UseLibgit2", TRUE);
216 m_IsUseLibGit2_mask = CRegDWORD(L"Software\\TortoiseGit\\UseLibgit2_mask", DEFAULT_USE_LIBGIT2_MASK);
218 SecureZeroMemory(&m_CurrentGitPi, sizeof(PROCESS_INFORMATION));
220 GetSortOptions();
221 this->m_bInitialized =false;
222 CheckMsysGitDir();
223 m_critGitDllSec.Init();
224 m_critSecThreadMap.Init();
227 CGit::~CGit(void)
229 if(this->m_GitDiff)
231 git_close_diff(m_GitDiff);
232 m_GitDiff=0;
234 if(this->m_GitSimpleListDiff)
236 git_close_diff(m_GitSimpleListDiff);
237 m_GitSimpleListDiff=0;
239 git_libgit2_shutdown();
240 m_critSecThreadMap.Term();
241 m_critGitDllSec.Term();
244 bool CGit::IsBranchNameValid(const CString& branchname)
246 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
247 return false;
248 if (branchname.FindOneOf(L"\"|<>") >= 0) // not valid on Windows
249 return false;
250 if (branchname == L"HEAD") // Branch name HEAD is discouraged since Git v2.16.0, see https://github.com/git/git/commit/a625b092cc59940521789fe8a3ff69c8d6b14eb2
251 return false;
252 CStringA branchA = CUnicodeUtils::GetUTF8(L"refs/heads/" + branchname);
253 return !!git_reference_is_valid_name(branchA);
256 int CGit::RunAsync(CString cmd, PROCESS_INFORMATION* piOut, HANDLE* hReadOut, HANDLE* hErrReadOut, const CString* StdioFile)
258 CAutoGeneralHandle hRead, hWrite, hReadErr, hWriteErr, hWriteIn, hReadIn;
259 CAutoFile hStdioFile;
261 SECURITY_ATTRIBUTES sa = { 0 };
262 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
263 sa.bInheritHandle=TRUE;
264 if (!CreatePipe(hReadIn.GetPointer(), hWriteIn.GetPointer(), &sa, 0))
266 CString err = CFormatMessageWrapper();
267 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": could not open stdin pipe: %s\n", (LPCTSTR)err.Trim());
268 return TGIT_GIT_ERROR_OPEN_PIP;
270 if (!CreatePipe(hRead.GetPointer(), hWrite.GetPointer(), &sa, 0))
272 CString err = CFormatMessageWrapper();
273 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": could not open stdout pipe: %s\n", (LPCTSTR)err.Trim());
274 return TGIT_GIT_ERROR_OPEN_PIP;
276 if (hErrReadOut && !CreatePipe(hReadErr.GetPointer(), hWriteErr.GetPointer(), &sa, 0))
278 CString err = CFormatMessageWrapper();
279 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": could not open stderr pipe: %s\n", (LPCTSTR)err.Trim());
280 return TGIT_GIT_ERROR_OPEN_PIP;
283 if(StdioFile)
284 hStdioFile = CreateFile(*StdioFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
286 STARTUPINFO si = { 0 };
287 PROCESS_INFORMATION pi = { 0 };
288 si.cb=sizeof(STARTUPINFO);
289 si.hStdInput = hReadIn;
290 if (hErrReadOut)
291 si.hStdError = hWriteErr;
292 else
293 si.hStdError = hWrite;
294 if(StdioFile)
295 si.hStdOutput=hStdioFile;
296 else
297 si.hStdOutput=hWrite;
299 si.wShowWindow=SW_HIDE;
300 si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
302 LPTSTR pEnv = m_Environment;
303 DWORD dwFlags = CREATE_UNICODE_ENVIRONMENT;
304 dwFlags |= CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS;
306 memset(&this->m_CurrentGitPi,0,sizeof(PROCESS_INFORMATION));
308 bool startsWithGit = CStringUtils::StartsWith(cmd, L"git");
309 if (ms_bMsys2Git && startsWithGit && !CStringUtils::StartsWith(cmd, L"git.exe config "))
311 cmd.Replace(L"\\", L"\\\\\\\\");
312 cmd.Replace(L"\"", L"\\\"");
313 cmd = L'"' + CGit::ms_LastMsysGitDir + L"\\bash.exe\" -c \"/usr/bin/" + cmd + L'"';
315 else if (ms_bCygwinGit && startsWithGit && !CStringUtils::StartsWith(cmd, L"git.exe config "))
317 cmd.Replace(L'\\', L'/');
318 cmd.Replace(L"\"", L"\\\"");
319 cmd = L'"' + CGit::ms_LastMsysGitDir + L"\\bash.exe\" -c \"/bin/" + cmd + L'"';
321 else if (startsWithGit || CStringUtils::StartsWith(cmd, L"bash"))
323 int firstSpace = cmd.Find(L' ');
324 if (firstSpace > 0)
325 cmd = L'"' + CGit::ms_LastMsysGitDir + L'\\' + cmd.Left(firstSpace) + L'"' + cmd.Mid(firstSpace);
326 else
327 cmd = L'"' + CGit::ms_LastMsysGitDir + L'\\' + cmd + L'"';
330 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": executing %s\n", (LPCTSTR)cmd);
331 if(!CreateProcess(nullptr, cmd.GetBuffer(), nullptr, nullptr, TRUE, dwFlags, pEnv, m_CurrentDir.GetBuffer(), &si, &pi))
333 CString err = CFormatMessageWrapper();
334 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": error while executing command: %s\n", (LPCTSTR)err.Trim());
335 return TGIT_GIT_ERROR_CREATE_PROCESS;
338 // Close the pipe handle so the child process stops reading.
339 hWriteIn.CloseHandle();
341 m_CurrentGitPi = pi;
343 if(piOut)
344 *piOut=pi;
345 if(hReadOut)
346 *hReadOut = hRead.Detach();
347 if(hErrReadOut)
348 *hErrReadOut = hReadErr.Detach();
349 return 0;
351 //Must use sperate function to convert ANSI str to union code string
352 //Because A2W use stack as internal convert buffer.
353 void CGit::StringAppend(CString *str, const BYTE *p, int code,int length)
355 if (!str)
356 return ;
358 int len ;
359 if(length<0)
360 len = (int)strlen((const char*)p);
361 else
362 len=length;
363 if (len == 0)
364 return;
365 int currentContentLen = str->GetLength();
366 WCHAR * buf = str->GetBuffer(len * 4 + 1 + currentContentLen) + currentContentLen;
367 int appendedLen = MultiByteToWideChar(code, 0, (LPCSTR)p, len, buf, len * 4);
368 str->ReleaseBuffer(currentContentLen + appendedLen); // no - 1 because MultiByteToWideChar is called with a fixed length (thus no nul char included)
371 // This method was originally used to check for orphaned branches
372 BOOL CGit::CanParseRev(CString ref)
374 if (ref.IsEmpty())
375 ref = L"HEAD";
377 CString cmdout;
378 if (Run(L"git.exe rev-parse --revs-only " + ref, &cmdout, CP_UTF8))
379 return FALSE;
380 if(cmdout.IsEmpty())
381 return FALSE;
383 return TRUE;
386 // Checks if we have an orphaned HEAD
387 BOOL CGit::IsInitRepos()
389 CGitHash hash;
390 if (GetHash(hash, L"HEAD") != 0)
391 return FALSE;
392 return hash.IsEmpty() ? TRUE : FALSE;
395 DWORD WINAPI CGit::AsyncReadStdErrThread(LPVOID lpParam)
397 PASYNCREADSTDERRTHREADARGS pDataArray;
398 pDataArray = (PASYNCREADSTDERRTHREADARGS)lpParam;
400 DWORD readnumber;
401 BYTE data[CALL_OUTPUT_READ_CHUNK_SIZE];
402 while (ReadFile(pDataArray->fileHandle, data, CALL_OUTPUT_READ_CHUNK_SIZE, &readnumber, nullptr))
404 if (pDataArray->pcall->OnOutputErrData(data,readnumber))
405 break;
408 return 0;
411 #ifdef _MFC_VER
412 void CGit::KillRelatedThreads(CWinThread* thread)
414 CAutoLocker lock(m_critSecThreadMap);
415 auto it = m_AsyncReadStdErrThreadMap.find(thread->m_nThreadID);
416 if (it != m_AsyncReadStdErrThreadMap.cend())
418 TerminateThread(it->second, (DWORD)-1);
419 m_AsyncReadStdErrThreadMap.erase(it);
421 TerminateThread(thread->m_hThread, (DWORD)-1);
423 #endif
425 int CGit::Run(CGitCall* pcall)
427 PROCESS_INFORMATION pi;
428 CAutoGeneralHandle hRead, hReadErr;
429 if (RunAsync(pcall->GetCmd(), &pi, hRead.GetPointer(), hReadErr.GetPointer()))
430 return TGIT_GIT_ERROR_CREATE_PROCESS;
432 CAutoGeneralHandle piThread(std::move(pi.hThread));
433 CAutoGeneralHandle piProcess(std::move(pi.hProcess));
435 ASYNCREADSTDERRTHREADARGS threadArguments;
436 threadArguments.fileHandle = hReadErr;
437 threadArguments.pcall = pcall;
438 CAutoGeneralHandle thread = CreateThread(nullptr, 0, AsyncReadStdErrThread, &threadArguments, 0, nullptr);
440 if (thread)
442 CAutoLocker lock(m_critSecThreadMap);
443 m_AsyncReadStdErrThreadMap[GetCurrentThreadId()] = thread;
446 DWORD readnumber;
447 BYTE data[CALL_OUTPUT_READ_CHUNK_SIZE];
448 bool bAborted=false;
449 while (ReadFile(hRead, data, CALL_OUTPUT_READ_CHUNK_SIZE, &readnumber, nullptr))
451 // TODO: when OnOutputData() returns 'true', abort git-command. Send CTRL-C signal?
452 if(!bAborted)//For now, flush output when command aborted.
453 if(pcall->OnOutputData(data,readnumber))
454 bAborted=true;
456 if(!bAborted)
457 pcall->OnEnd();
459 if (thread)
461 WaitForSingleObject(thread, INFINITE);
463 CAutoLocker lock(m_critSecThreadMap);
464 m_AsyncReadStdErrThreadMap.erase(GetCurrentThreadId());
467 WaitForSingleObject(pi.hProcess, INFINITE);
468 DWORD exitcode =0;
470 if(!GetExitCodeProcess(pi.hProcess,&exitcode))
472 CString err = CFormatMessageWrapper();
473 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": could not get exit code: %s\n", (LPCTSTR)err.Trim());
474 return TGIT_GIT_ERROR_GET_EXIT_CODE;
476 else
477 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": process exited: %d\n", exitcode);
479 return exitcode;
481 class CGitCall_ByteVector : public CGitCall
483 public:
484 CGitCall_ByteVector(CString cmd,BYTE_VECTOR* pvector, BYTE_VECTOR* pvectorErr = nullptr) : CGitCall(cmd),m_pvector(pvector), m_pvectorErr(pvectorErr) {}
485 virtual bool OnOutputData(const BYTE* data, size_t size)
487 if (!m_pvector || size == 0)
488 return false;
489 size_t oldsize=m_pvector->size();
490 m_pvector->resize(m_pvector->size()+size);
491 memcpy(&*(m_pvector->begin()+oldsize),data,size);
492 return false;
494 virtual bool OnOutputErrData(const BYTE* data, size_t size)
496 if (!m_pvectorErr || size == 0)
497 return false;
498 size_t oldsize = m_pvectorErr->size();
499 m_pvectorErr->resize(m_pvectorErr->size() + size);
500 memcpy(&*(m_pvectorErr->begin() + oldsize), data, size);
501 return false;
503 BYTE_VECTOR* m_pvector;
504 BYTE_VECTOR* m_pvectorErr;
506 int CGit::Run(CString cmd,BYTE_VECTOR *vector, BYTE_VECTOR *vectorErr)
508 CGitCall_ByteVector call(cmd, vector, vectorErr);
509 return Run(&call);
511 int CGit::Run(CString cmd, CString* output, int code)
513 CString err;
514 int ret;
515 ret = Run(cmd, output, &err, code);
517 if (output && !err.IsEmpty())
519 if (!output->IsEmpty())
520 *output += L'\n';
521 *output += err;
524 return ret;
526 int CGit::Run(CString cmd, CString* output, CString* outputErr, int code)
528 BYTE_VECTOR vector, vectorErr;
529 int ret;
530 if (outputErr)
531 ret = Run(cmd, &vector, &vectorErr);
532 else
533 ret = Run(cmd, &vector);
535 vector.push_back(0);
536 StringAppend(output, vector.data(), code);
538 if (outputErr)
540 vectorErr.push_back(0);
541 StringAppend(outputErr, vectorErr.data(), code);
544 return ret;
547 class CGitCallCb : public CGitCall
549 public:
550 CGitCallCb(CString cmd, const GitReceiverFunc& recv, BYTE_VECTOR* pvectorErr = nullptr)
551 : CGitCall(cmd)
552 , m_recv(recv)
553 , m_pvectorErr(pvectorErr)
556 virtual bool OnOutputData(const BYTE* data, size_t size) override
558 // Add data
559 if (size == 0)
560 return false;
561 int oldEndPos = m_buffer.GetLength();
562 memcpy(m_buffer.GetBuffer(oldEndPos + (int)size) + oldEndPos, data, size);
563 m_buffer.ReleaseBuffer(oldEndPos + (int)size);
565 // Break into lines and feed to m_recv
566 int eolPos;
567 while ((eolPos = m_buffer.Find('\n')) >= 0)
569 m_recv(m_buffer.Left(eolPos));
570 m_buffer = m_buffer.Mid(eolPos + 1);
572 return false;
575 virtual bool OnOutputErrData(const BYTE* data, size_t size) override
577 if (!m_pvectorErr || size == 0)
578 return false;
579 size_t oldsize = m_pvectorErr->size();
580 m_pvectorErr->resize(m_pvectorErr->size() + size);
581 memcpy(&*(m_pvectorErr->begin() + oldsize), data, size);
582 return false;
585 virtual void OnEnd() override
587 if (!m_buffer.IsEmpty())
588 m_recv(m_buffer);
589 m_buffer.Empty(); // Just for sure
592 private:
593 GitReceiverFunc m_recv;
594 CStringA m_buffer;
595 BYTE_VECTOR* m_pvectorErr;
598 int CGit::Run(CString cmd, const GitReceiverFunc& recv, CString* outputErr)
600 if (outputErr)
602 BYTE_VECTOR vectorErr;
603 CGitCallCb call(cmd, recv, &vectorErr);
604 int ret = Run(&call);
605 vectorErr.push_back(0);
606 StringAppend(outputErr, vectorErr.data());
607 return ret;
610 CGitCallCb call(cmd, recv);
611 return Run(&call);
614 CString CGit::GetUserName(void)
616 CEnvironment env;
617 env.CopyProcessEnvironment();
618 CString envname = env.GetEnv(L"GIT_AUTHOR_NAME");
619 if (!envname.IsEmpty())
620 return envname;
621 return GetConfigValue(L"user.name");
623 CString CGit::GetUserEmail(void)
625 CEnvironment env;
626 env.CopyProcessEnvironment();
627 CString envmail = env.GetEnv(L"GIT_AUTHOR_EMAIL");
628 if (!envmail.IsEmpty())
629 return envmail;
631 return GetConfigValue(L"user.email");
634 CString CGit::GetConfigValue(const CString& name, const CString& def, bool wantBool)
636 CString configValue;
637 if(this->m_IsUseGitDLL)
639 CAutoLocker lock(g_Git.m_critGitDllSec);
643 CheckAndInitDll();
644 }catch(...)
647 CStringA key, value;
648 key = CUnicodeUtils::GetUTF8(name);
652 if (git_get_config(key, CStrBufA(value, 4096), 4096))
653 return def;
655 catch (const char *msg)
657 ::MessageBox(nullptr, L"Could not get config.\nlibgit reports:\n" + CString(msg), L"TortoiseGit", MB_OK | MB_ICONERROR);
658 return def;
661 StringAppend(&configValue, (BYTE*)(LPCSTR)value);
662 return configValue;
664 else
666 CString cmd;
667 cmd.Format(L"git.exe config%s %s", wantBool ? L" --bool" : L"", (LPCTSTR)name);
668 if (Run(cmd, &configValue, nullptr, CP_UTF8))
669 return def;
670 if (configValue.IsEmpty())
671 return configValue;
672 return configValue.Left(configValue.GetLength() - 1); // strip last newline character
676 bool CGit::GetConfigValueBool(const CString& name, const bool def)
678 CString configValue = GetConfigValue(name, def ? L"true" : L"false", true);
679 configValue.MakeLower();
680 configValue.Trim();
681 if (configValue == L"true" || configValue == L"on" || configValue == L"yes" || StrToInt(configValue) != 0)
682 return true;
683 else
684 return false;
687 int CGit::GetConfigValueInt32(const CString& name, const int def)
689 CString configValue = GetConfigValue(name);
690 int value = def;
691 if (!git_config_parse_int32(&value, CUnicodeUtils::GetUTF8(configValue)))
692 return value;
693 return def;
696 int CGit::SetConfigValue(const CString& key, const CString& value, CONFIG_TYPE type)
698 if(this->m_IsUseGitDLL)
700 CAutoLocker lock(g_Git.m_critGitDllSec);
704 CheckAndInitDll();
705 }catch(...)
708 CStringA keya, valuea;
709 keya = CUnicodeUtils::GetMulti(key, CP_UTF8);
710 valuea = CUnicodeUtils::GetUTF8(value);
714 return [=]() { return get_set_config(keya, valuea, type); }();
716 catch (const char *msg)
718 ::MessageBox(nullptr, L"Could not set config.\nlibgit reports:\n" + CString(msg), L"TortoiseGit", MB_OK | MB_ICONERROR);
719 return -1;
722 else
724 CString cmd;
725 CString option;
726 switch(type)
728 case CONFIG_GLOBAL:
729 option = L"--global";
730 break;
731 case CONFIG_SYSTEM:
732 option = L"--system";
733 break;
734 default:
735 break;
737 CString mangledValue = value;
738 mangledValue.Replace(L"\\\"", L"\\\\\"");
739 mangledValue.Replace(L"\"", L"\\\"");
740 cmd.Format(L"git.exe config %s %s \"%s\"", (LPCTSTR)option, (LPCTSTR)key, (LPCTSTR)mangledValue);
741 CString out;
742 if (Run(cmd, &out, nullptr, CP_UTF8))
743 return -1;
745 return 0;
748 int CGit::UnsetConfigValue(const CString& key, CONFIG_TYPE type)
750 if(this->m_IsUseGitDLL)
752 CAutoLocker lock(g_Git.m_critGitDllSec);
756 CheckAndInitDll();
757 }catch(...)
760 CStringA keya;
761 keya = CUnicodeUtils::GetMulti(key, CP_UTF8);
765 return [=]() { return get_set_config(keya, nullptr, type); }();
767 catch (const char *msg)
769 ::MessageBox(nullptr, L"Could not unset config.\nlibgit reports:\n" + CString(msg), L"TortoiseGit", MB_OK | MB_ICONERROR);
770 return -1;
773 else
775 CString cmd;
776 CString option;
777 switch(type)
779 case CONFIG_GLOBAL:
780 option = L"--global";
781 break;
782 case CONFIG_SYSTEM:
783 option = L"--system";
784 break;
785 default:
786 break;
788 cmd.Format(L"git.exe config %s --unset %s", (LPCTSTR)option, (LPCTSTR)key);
789 CString out;
790 if (Run(cmd, &out, nullptr, CP_UTF8))
791 return -1;
793 return 0;
796 CString CGit::GetCurrentBranch(bool fallback)
798 CString output;
799 //Run(L"git.exe branch", &branch);
801 int result = GetCurrentBranchFromFile(m_CurrentDir, output, fallback);
802 if (result != 0 && ((result == 1 && !fallback) || result != 1))
803 return L"(no branch)";
804 else
805 return output;
808 void CGit::GetRemoteTrackedBranch(const CString& localBranch, CString& pullRemote, CString& pullBranch)
810 if (localBranch.IsEmpty())
811 return;
813 CString configName;
814 configName.Format(L"branch.%s.remote", (LPCTSTR)localBranch);
815 pullRemote = GetConfigValue(configName);
817 //Select pull-branch from current branch
818 configName.Format(L"branch.%s.merge", (LPCTSTR)localBranch);
819 pullBranch = StripRefName(GetConfigValue(configName));
822 void CGit::GetRemoteTrackedBranchForHEAD(CString& remote, CString& branch)
824 CString refName;
825 if (GetCurrentBranchFromFile(m_CurrentDir, refName))
826 return;
827 GetRemoteTrackedBranch(StripRefName(refName), remote, branch);
830 void CGit::GetRemotePushBranch(const CString& localBranch, CString& pushRemote, CString& pushBranch)
832 if (localBranch.IsEmpty())
833 return;
835 CString configName;
837 configName.Format(L"branch.%s.pushremote", (LPCTSTR)localBranch);
838 pushRemote = g_Git.GetConfigValue(configName);
839 if (pushRemote.IsEmpty())
841 pushRemote = g_Git.GetConfigValue(L"remote.pushdefault");
842 if (pushRemote.IsEmpty())
844 configName.Format(L"branch.%s.remote", (LPCTSTR)localBranch);
845 pushRemote = g_Git.GetConfigValue(configName);
849 configName.Format(L"branch.%s.pushbranch", (LPCTSTR)localBranch);
850 pushBranch = g_Git.GetConfigValue(configName); // do not strip branch name (for gerrit), see issue #1609)
851 if (pushBranch.IsEmpty())
853 configName.Format(L"branch.%s.merge", (LPCTSTR)localBranch);
854 pushBranch = CGit::StripRefName(g_Git.GetConfigValue(configName));
858 CString CGit::GetFullRefName(const CString& shortRefName)
860 CString refName;
861 CString cmd;
862 cmd.Format(L"git.exe rev-parse --symbolic-full-name %s", (LPCTSTR)shortRefName);
863 if (Run(cmd, &refName, nullptr, CP_UTF8) != 0)
864 return CString();//Error
865 return refName.TrimRight();
868 CString CGit::StripRefName(CString refName)
870 if (CStringUtils::StartsWith(refName, L"refs/heads/"))
871 refName = refName.Mid(11);
872 else if (CStringUtils::StartsWith(refName, L"refs/"))
873 refName = refName.Mid(5);
874 return refName.TrimRight();
877 int CGit::GetCurrentBranchFromFile(const CString &sProjectRoot, CString &sBranchOut, bool fallback)
879 // read current branch name like git-gui does, by parsing the .git/HEAD file directly
881 if ( sProjectRoot.IsEmpty() )
882 return -1;
884 CString sDotGitPath;
885 if (!GitAdminDir::GetWorktreeAdminDirPath(sProjectRoot, sDotGitPath))
886 return -1;
888 CString sHeadFile = sDotGitPath + L"HEAD";
890 CAutoFILE pFile = _wfsopen(sHeadFile.GetString(), L"r", SH_DENYWR);
891 if (!pFile)
892 return -1;
894 char s[MAX_PATH] = {0};
895 fgets(s, sizeof(s), pFile);
897 const char *pfx = "ref: refs/heads/";
898 const size_t len = strlen(pfx);
900 if ( !strncmp(s, pfx, len) )
902 //# We're on a branch. It might not exist. But
903 //# HEAD looks good enough to be a branch.
904 CStringA utf8Branch(s + len);
905 sBranchOut = CUnicodeUtils::GetUnicode(utf8Branch);
906 sBranchOut.TrimRight(L" \r\n\t");
908 if ( sBranchOut.IsEmpty() )
909 return -1;
911 else if (fallback)
913 CStringA utf8Hash(s);
914 CString unicodeHash = CUnicodeUtils::GetUnicode(utf8Hash);
915 unicodeHash.TrimRight(L" \r\n\t");
916 if (CGitHash::IsValidSHA1(unicodeHash))
917 sBranchOut = unicodeHash;
918 else
919 //# Assume this is a detached head.
920 sBranchOut = L"HEAD";
921 return 1;
923 else
925 //# Assume this is a detached head.
926 sBranchOut = "HEAD";
928 return 1;
931 return 0;
934 int CGit::BuildOutputFormat(CString &format,bool IsFull)
936 CString log;
937 log.Format(L"#<%c>%%x00", LOG_REV_ITEM_BEGIN);
938 format += log;
939 if(IsFull)
941 log.Format(L"#<%c>%%an%%x00", LOG_REV_AUTHOR_NAME);
942 format += log;
943 log.Format(L"#<%c>%%ae%%x00", LOG_REV_AUTHOR_EMAIL);
944 format += log;
945 log.Format(L"#<%c>%%ai%%x00", LOG_REV_AUTHOR_DATE);
946 format += log;
947 log.Format(L"#<%c>%%cn%%x00", LOG_REV_COMMIT_NAME);
948 format += log;
949 log.Format(L"#<%c>%%ce%%x00", LOG_REV_COMMIT_EMAIL);
950 format += log;
951 log.Format(L"#<%c>%%ci%%x00", LOG_REV_COMMIT_DATE);
952 format += log;
953 log.Format(L"#<%c>%%b%%x00", LOG_REV_COMMIT_BODY);
954 format += log;
957 log.Format(L"#<%c>%%m%%H%%x00", LOG_REV_COMMIT_HASH);
958 format += log;
959 log.Format(L"#<%c>%%P%%x00", LOG_REV_COMMIT_PARENT);
960 format += log;
961 log.Format(L"#<%c>%%s%%x00", LOG_REV_COMMIT_SUBJECT);
962 format += log;
964 if(IsFull)
966 log.Format(L"#<%c>%%x00", LOG_REV_COMMIT_FILE);
967 format += log;
969 return 0;
972 CString CGit::GetLogCmd(CString range, const CTGitPath* path, int mask, CFilterData* Filter, int logOrderBy)
974 CString param;
976 if(mask& LOG_INFO_STAT )
977 param += L" --numstat";
978 if(mask& LOG_INFO_FILESTATE)
979 param += L" --raw";
981 if(mask& LOG_INFO_FULLHISTORY)
982 param += L" --full-history";
984 if(mask& LOG_INFO_BOUNDARY)
985 param += L" --left-right --boundary";
987 if(mask& CGit::LOG_INFO_ALL_BRANCH)
989 param += L" --all";
990 range.Empty();
993 if (mask& CGit::LOG_INFO_BASIC_REFS)
995 param += L" --branches";
996 param += L" --tags";
997 param += L" --remotes";
998 param += L" --glob=stas[h]"; // require at least one glob operator
999 param += L" --glob=bisect";
1000 range.Empty();
1003 if(mask & CGit::LOG_INFO_LOCAL_BRANCHES)
1005 param += L" --branches";
1006 range.Empty();
1009 if(mask& CGit::LOG_INFO_DETECT_COPYRENAME)
1010 param += L" -C";
1012 if(mask& CGit::LOG_INFO_DETECT_RENAME )
1013 param += L" -M";
1015 if(mask& CGit::LOG_INFO_FIRST_PARENT )
1016 param += L" --first-parent";
1018 if(mask& CGit::LOG_INFO_NO_MERGE )
1019 param += L" --no-merges";
1021 if(mask& CGit::LOG_INFO_FOLLOW)
1022 param += L" --follow";
1024 if(mask& CGit::LOG_INFO_SHOW_MERGEDFILE)
1025 param += L" -c";
1027 if(mask& CGit::LOG_INFO_FULL_DIFF)
1028 param += L" --full-diff";
1030 if(mask& CGit::LOG_INFO_SIMPILFY_BY_DECORATION)
1031 param += L" --simplify-by-decoration";
1033 if (Filter)
1035 if (Filter->m_NumberOfLogsScale >= CFilterData::SHOW_LAST_N_YEARS)
1037 CTime now = CTime::GetCurrentTime();
1038 CTime time = CTime(now.GetYear(), now.GetMonth(), now.GetDay(), 0, 0, 0);
1039 __time64_t substract = 86400;
1040 CString scale;
1041 switch (Filter->m_NumberOfLogsScale)
1043 case CFilterData::SHOW_LAST_N_YEARS:
1044 substract *= 365;
1045 break;
1046 case CFilterData::SHOW_LAST_N_MONTHS:
1047 substract *= 30;
1048 break;
1049 case CFilterData::SHOW_LAST_N_WEEKS:
1050 substract *= 7;
1051 break;
1053 Filter->m_From = (DWORD)time.GetTime() - (Filter->m_NumberOfLogs * substract);
1055 if (Filter->m_NumberOfLogsScale == CFilterData::SHOW_LAST_N_COMMITS)
1056 param.AppendFormat(L" -n%ld", Filter->m_NumberOfLogs);
1057 else if (Filter->m_NumberOfLogsScale >= CFilterData::SHOW_LAST_SEL_DATE && Filter->m_From > 0)
1058 param.AppendFormat(L" --max-age=%I64u", Filter->m_From);
1061 if( Filter && (Filter->m_To != -1))
1062 param.AppendFormat(L" --min-age=%I64u", Filter->m_To);
1064 bool isgrep = false;
1065 if( Filter && (!Filter->m_Author.IsEmpty()))
1067 param.AppendFormat(L" --author=\"%s\"", (LPCTSTR)Filter->m_Author);
1068 isgrep = true;
1071 if( Filter && (!Filter->m_Committer.IsEmpty()))
1073 param.AppendFormat(L" --committer=\"%s\"", (LPCTSTR)Filter->m_Author);
1074 isgrep = true;
1077 if( Filter && (!Filter->m_MessageFilter.IsEmpty()))
1079 param.AppendFormat(L" --grep=\"%s\"", (LPCTSTR)Filter->m_MessageFilter);
1080 isgrep = true;
1083 if(Filter && isgrep)
1085 if(!Filter->m_IsRegex)
1086 param += L" --fixed-strings";
1088 param += L" --regexp-ignore-case --extended-regexp";
1091 if (logOrderBy == LOG_ORDER_TOPOORDER || (mask & CGit::LOG_ORDER_TOPOORDER))
1092 param += L" --topo-order";
1093 else if (logOrderBy == LOG_ORDER_DATEORDER)
1094 param += L" --date-order";
1096 CString cmd;
1097 CString file;
1098 if (path)
1099 file.Format(L" \"%s\"", (LPCTSTR)path->GetGitPathString());
1100 // gitdll.dll:setup_revisions() only looks at args[1] and greater. To account for this, pass a dummy parameter in the 0th place
1101 cmd.Format(L"--ignore-this-parameter -z%s %s --parents --%s", (LPCTSTR)param, (LPCTSTR)range, (LPCTSTR)file);
1103 return cmd;
1105 #define BUFSIZE 512
1106 void GetTempPath(CString &path)
1108 TCHAR lpPathBuffer[BUFSIZE] = { 0 };
1109 DWORD dwRetVal;
1110 DWORD dwBufSize=BUFSIZE;
1111 dwRetVal = GetTortoiseGitTempPath(dwBufSize, // length of the buffer
1112 lpPathBuffer); // buffer for path
1113 if (dwRetVal > dwBufSize || (dwRetVal == 0))
1114 path.Empty();
1115 path.Format(L"%s", lpPathBuffer);
1117 CString GetTempFile()
1119 TCHAR lpPathBuffer[BUFSIZE] = { 0 };
1120 DWORD dwRetVal;
1121 DWORD dwBufSize=BUFSIZE;
1122 TCHAR szTempName[BUFSIZE] = { 0 };
1123 UINT uRetVal;
1125 dwRetVal = GetTortoiseGitTempPath(dwBufSize, // length of the buffer
1126 lpPathBuffer); // buffer for path
1127 if (dwRetVal > dwBufSize || (dwRetVal == 0))
1128 return L"";
1130 // Create a temporary file.
1131 uRetVal = GetTempFileName(lpPathBuffer, // directory for tmp files
1132 TEXT("Patch"), // temp file name prefix
1133 0, // create unique name
1134 szTempName); // buffer for name
1136 if (uRetVal == 0)
1137 return L"";
1139 return CString(szTempName);
1142 DWORD GetTortoiseGitTempPath(DWORD nBufferLength, LPTSTR lpBuffer)
1144 DWORD result = ::GetTempPath(nBufferLength, lpBuffer);
1145 if (result == 0) return 0;
1146 if (!lpBuffer || (result + 13 > nBufferLength))
1148 if (lpBuffer)
1149 lpBuffer[0] = '\0';
1150 return result + 13;
1153 wcscat_s(lpBuffer, nBufferLength, L"TortoiseGit\\");
1154 CreateDirectory(lpBuffer, nullptr);
1156 return result + 13;
1159 int CGit::RunLogFile(CString cmd, const CString &filename, CString *stdErr)
1161 PROCESS_INFORMATION pi;
1162 CAutoGeneralHandle hReadErr;
1163 if (RunAsync(cmd, &pi, nullptr, hReadErr.GetPointer(), &filename))
1164 return TGIT_GIT_ERROR_CREATE_PROCESS;
1166 CAutoGeneralHandle piThread(std::move(pi.hThread));
1167 CAutoGeneralHandle piProcess(std::move(pi.hProcess));
1169 BYTE_VECTOR stderrVector;
1170 CGitCall_ByteVector pcall(L"", nullptr, &stderrVector);
1171 ASYNCREADSTDERRTHREADARGS threadArguments;
1172 threadArguments.fileHandle = hReadErr;
1173 threadArguments.pcall = &pcall;
1174 CAutoGeneralHandle thread = CreateThread(nullptr, 0, AsyncReadStdErrThread, &threadArguments, 0, nullptr);
1176 if (thread)
1178 CAutoLocker lock(m_critSecThreadMap);
1179 m_AsyncReadStdErrThreadMap[GetCurrentThreadId()] = thread;
1182 WaitForSingleObject(pi.hProcess,INFINITE);
1184 if (thread)
1186 WaitForSingleObject(thread, INFINITE);
1188 CAutoLocker lock(m_critSecThreadMap);
1189 m_AsyncReadStdErrThreadMap.erase(GetCurrentThreadId());
1192 stderrVector.push_back(0);
1193 StringAppend(stdErr, stderrVector.data(), CP_UTF8);
1195 DWORD exitcode = 0;
1196 if (!GetExitCodeProcess(pi.hProcess, &exitcode))
1198 CString err = CFormatMessageWrapper();
1199 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": could not get exit code: %s\n", (LPCTSTR)err.Trim());
1200 return TGIT_GIT_ERROR_GET_EXIT_CODE;
1202 else
1203 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": process exited: %d\n", exitcode);
1205 return exitcode;
1208 CAutoRepository CGit::GetGitRepository() const
1210 return CAutoRepository(GetGitPathStringA(m_CurrentDir));
1213 int CGit::GetHash(git_repository * repo, CGitHash &hash, const CString& friendname, bool skipFastCheck /* = false */)
1215 ATLASSERT(repo);
1217 // no need to parse a ref if it's already a 40-byte hash
1218 if (!skipFastCheck && CGitHash::IsValidSHA1(friendname))
1220 hash = CGitHash(friendname);
1221 return 0;
1224 int isHeadOrphan = git_repository_head_unborn(repo);
1225 if (isHeadOrphan != 0)
1227 hash.Empty();
1228 if (isHeadOrphan == 1)
1229 return 0;
1230 else
1231 return -1;
1234 CAutoObject gitObject;
1235 if (git_revparse_single(gitObject.GetPointer(), repo, CUnicodeUtils::GetUTF8(friendname)))
1236 return -1;
1238 const git_oid * oid = git_object_id(gitObject);
1239 if (!oid)
1240 return -1;
1242 hash = CGitHash(oid->id);
1244 return 0;
1247 int CGit::GetHash(CGitHash &hash, const CString& friendname)
1249 // no need to parse a ref if it's already a 40-byte hash
1250 if (CGitHash::IsValidSHA1(friendname))
1252 hash = CGitHash(friendname);
1253 return 0;
1256 if (m_IsUseLibGit2)
1258 CAutoRepository repo(GetGitRepository());
1259 if (!repo)
1260 return -1;
1262 return GetHash(repo, hash, friendname, true);
1264 else
1266 CString branch = FixBranchName(friendname);
1267 if (friendname == L"FETCH_HEAD" && branch.IsEmpty())
1268 branch = friendname;
1269 CString cmd;
1270 cmd.Format(L"git.exe rev-parse %s", (LPCTSTR)branch);
1271 gitLastErr.Empty();
1272 int ret = Run(cmd, &gitLastErr, nullptr, CP_UTF8);
1273 hash = CGitHash(gitLastErr.Trim());
1274 if (ret == 0)
1275 gitLastErr.Empty();
1276 else if (friendname == L"HEAD") // special check for unborn branch
1278 CString currentbranch;
1279 if (GetCurrentBranchFromFile(m_CurrentDir, currentbranch))
1280 return -1;
1281 gitLastErr.Empty();
1282 return 0;
1284 return ret;
1288 int CGit::GetInitAddList(CTGitPathList &outputlist)
1290 BYTE_VECTOR cmdout;
1292 outputlist.Clear();
1293 if (Run(L"git.exe ls-files -s -t -z", &cmdout))
1294 return -1;
1296 if (outputlist.ParserFromLsFile(cmdout))
1297 return -1;
1298 for(int i = 0; i < outputlist.GetCount(); ++i)
1299 const_cast<CTGitPath&>(outputlist[i]).m_Action = CTGitPath::LOGACTIONS_ADDED;
1301 return 0;
1303 int CGit::GetCommitDiffList(const CString &rev1, const CString &rev2, CTGitPathList &outputlist, bool ignoreSpaceAtEol, bool ignoreSpaceChange, bool ignoreAllSpace , bool ignoreBlankLines)
1305 CString cmd;
1306 CString ignore;
1307 if (ignoreSpaceAtEol)
1308 ignore += L" --ignore-space-at-eol";
1309 if (ignoreSpaceChange)
1310 ignore += L" --ignore-space-change";
1311 if (ignoreAllSpace)
1312 ignore += L" --ignore-all-space";
1313 if (ignoreBlankLines)
1314 ignore += L" --ignore-blank-lines";
1316 if(rev1 == GIT_REV_ZERO || rev2 == GIT_REV_ZERO)
1318 if(rev1 == GIT_REV_ZERO)
1319 cmd.Format(L"git.exe diff -r --raw -C -M --numstat -z %s %s --", (LPCTSTR)ignore, (LPCTSTR)rev2);
1320 else
1321 cmd.Format(L"git.exe diff -r -R --raw -C -M --numstat -z %s %s --", (LPCTSTR)ignore, (LPCTSTR)rev1);
1323 else
1324 cmd.Format(L"git.exe diff-tree -r --raw -C -M --numstat -z %s %s %s --", (LPCTSTR)ignore, (LPCTSTR)rev2, (LPCTSTR)rev1);
1326 BYTE_VECTOR out;
1327 if (Run(cmd, &out))
1328 return -1;
1330 return outputlist.ParserFromLog(out);
1333 int addto_list_each_ref_fn(const char *refname, const unsigned char * /*sha1*/, int /*flags*/, void *cb_data)
1335 STRING_VECTOR *list = (STRING_VECTOR*)cb_data;
1336 list->push_back(CUnicodeUtils::GetUnicode(refname));
1337 return 0;
1340 int CGit::GetTagList(STRING_VECTOR &list)
1342 size_t prevCount = list.size();
1343 if (this->m_IsUseLibGit2)
1345 CAutoRepository repo(GetGitRepository());
1346 if (!repo)
1347 return -1;
1349 CAutoStrArray tag_names;
1351 if (git_tag_list(tag_names, repo))
1352 return -1;
1354 for (size_t i = 0; i < tag_names->count; ++i)
1356 CStringA tagName(tag_names->strings[i]);
1357 list.push_back(CUnicodeUtils::GetUnicode(tagName));
1360 std::sort(list.begin() + prevCount, list.end(), g_bSortTagsReversed ? LogicalCompareReversedPredicate : LogicalComparePredicate);
1362 return 0;
1364 else
1366 int ret = Run(L"git.exe tag -l", [&](const CStringA& lineA)
1368 if (lineA.IsEmpty())
1369 return;
1370 list.push_back(CUnicodeUtils::GetUnicode(lineA));
1372 if (!ret)
1373 std::sort(list.begin() + prevCount, list.end(), g_bSortTagsReversed ? LogicalCompareReversedPredicate : LogicalComparePredicate);
1374 else if (ret == 1 && IsInitRepos())
1375 return 0;
1376 return ret;
1380 CString CGit::GetGitLastErr(const CString& msg)
1382 if (this->m_IsUseLibGit2)
1383 return GetLibGit2LastErr(msg);
1384 else if (gitLastErr.IsEmpty())
1385 return msg + L"\nUnknown git.exe error.";
1386 else
1388 CString lastError = gitLastErr;
1389 gitLastErr.Empty();
1390 return msg + L'\n' + lastError;
1394 CString CGit::GetGitLastErr(const CString& msg, LIBGIT2_CMD cmd)
1396 if (UsingLibGit2(cmd))
1397 return GetLibGit2LastErr(msg);
1398 else if (gitLastErr.IsEmpty())
1399 return msg + L"\nUnknown git.exe error.";
1400 else
1402 CString lastError = gitLastErr;
1403 gitLastErr.Empty();
1404 return msg + L'\n' + lastError;
1408 CString CGit::GetLibGit2LastErr()
1410 const git_error *libgit2err = giterr_last();
1411 if (libgit2err)
1413 CString lastError = CUnicodeUtils::GetUnicode(CStringA(libgit2err->message));
1414 giterr_clear();
1415 return L"libgit2 returned: " + lastError;
1417 else
1418 return L"An error occoured in libgit2, but no message is available.";
1421 CString CGit::GetLibGit2LastErr(const CString& msg)
1423 if (!msg.IsEmpty())
1424 return msg + L'\n' + GetLibGit2LastErr();
1425 return GetLibGit2LastErr();
1428 CString CGit::FixBranchName_Mod(CString& branchName)
1430 if (branchName == L"FETCH_HEAD")
1431 branchName = DerefFetchHead();
1432 return branchName;
1435 CString CGit::FixBranchName(const CString& branchName)
1437 CString tempBranchName = branchName;
1438 FixBranchName_Mod(tempBranchName);
1439 return tempBranchName;
1442 bool CGit::IsBranchTagNameUnique(const CString& name)
1444 if (m_IsUseLibGit2)
1446 CAutoRepository repo(GetGitRepository());
1447 if (!repo)
1448 return true; // TODO: optimize error reporting
1450 CAutoReference tagRef;
1451 if (git_reference_lookup(tagRef.GetPointer(), repo, CUnicodeUtils::GetUTF8(L"refs/tags/" + name)))
1452 return true;
1454 CAutoReference branchRef;
1455 if (git_reference_lookup(branchRef.GetPointer(), repo, CUnicodeUtils::GetUTF8(L"refs/heads/" + name)))
1456 return true;
1458 return false;
1460 // else
1461 CString cmd;
1462 cmd.Format(L"git.exe show-ref --tags --heads refs/heads/%s refs/tags/%s", (LPCTSTR)name, (LPCTSTR)name);
1464 int refCnt = 0;
1465 Run(cmd, [&](const CStringA& lineA)
1467 if (lineA.IsEmpty())
1468 return;
1469 ++refCnt;
1472 return (refCnt <= 1);
1475 bool CGit::IsLocalBranch(const CString& shortName)
1477 STRING_VECTOR list;
1478 GetBranchList(list, nullptr, CGit::BRANCH_LOCAL);
1479 return std::find(list.cbegin(), list.cend(), shortName) != list.cend();
1482 bool CGit::BranchTagExists(const CString& name, bool isBranch /*= true*/)
1484 if (m_IsUseLibGit2)
1486 CAutoRepository repo(GetGitRepository());
1487 if (!repo)
1488 return false; // TODO: optimize error reporting
1490 CString prefix;
1491 if (isBranch)
1492 prefix = L"refs/heads/";
1493 else
1494 prefix = L"refs/tags/";
1496 CAutoReference ref;
1497 if (git_reference_lookup(ref.GetPointer(), repo, CUnicodeUtils::GetUTF8(prefix + name)))
1498 return false;
1500 return true;
1502 // else
1503 CString cmd, output;
1505 cmd = L"git.exe show-ref ";
1506 if (isBranch)
1507 cmd += L"--heads ";
1508 else
1509 cmd += L"--tags ";
1511 cmd += L"refs/heads/" + name;
1512 cmd += L" refs/tags/" + name;
1514 int ret = Run(cmd, &output, nullptr, CP_UTF8);
1515 if (!ret)
1517 if (!output.IsEmpty())
1518 return true;
1521 return false;
1524 CString CGit::DerefFetchHead()
1526 CString dotGitPath;
1527 GitAdminDir::GetWorktreeAdminDirPath(m_CurrentDir, dotGitPath);
1528 std::ifstream fetchHeadFile((dotGitPath + L"FETCH_HEAD").GetString(), std::ios::in | std::ios::binary);
1529 int forMergeLineCount = 0;
1530 std::string line;
1531 std::string hashToReturn;
1532 while(getline(fetchHeadFile, line))
1534 //Tokenize this line
1535 std::string::size_type prevPos = 0;
1536 std::string::size_type pos = line.find('\t');
1537 if(pos == std::string::npos) continue; //invalid line
1539 std::string hash = line.substr(0, pos);
1540 ++pos; prevPos = pos; pos = line.find('\t', pos); if(pos == std::string::npos) continue;
1542 bool forMerge = pos == prevPos;
1543 ++pos; prevPos = pos; pos = line.size(); if(pos == std::string::npos) continue;
1545 //std::string remoteBranch = line.substr(prevPos, pos - prevPos);
1547 //Process this line
1548 if(forMerge)
1550 hashToReturn = hash;
1551 ++forMergeLineCount;
1552 if(forMergeLineCount > 1)
1553 return L""; //More then 1 branch to merge found. Octopus merge needed. Cannot pick single ref from FETCH_HEAD
1557 return CUnicodeUtils::GetUnicode(hashToReturn.c_str());
1560 int CGit::GetBranchList(STRING_VECTOR &list, int *current, BRANCH_TYPE type, bool skipCurrent /*false*/)
1562 size_t prevCount = list.size();
1563 int ret = 0;
1564 CString cur;
1565 bool headIsDetached = false;
1566 if (m_IsUseLibGit2)
1568 CAutoRepository repo(GetGitRepository());
1569 if (!repo)
1570 return -1;
1572 if (git_repository_head_detached(repo) == 1)
1573 headIsDetached = true;
1575 if ((type & (BRANCH_LOCAL | BRANCH_REMOTE)) != 0)
1577 git_branch_t flags = GIT_BRANCH_LOCAL;
1578 if ((type & BRANCH_LOCAL) && (type & BRANCH_REMOTE))
1579 flags = GIT_BRANCH_ALL;
1580 else if (type & BRANCH_REMOTE)
1581 flags = GIT_BRANCH_REMOTE;
1583 CAutoBranchIterator it;
1584 if (git_branch_iterator_new(it.GetPointer(), repo, flags))
1585 return -1;
1587 CAutoReference ref;
1588 git_branch_t branchType;
1589 while (git_branch_next(ref.GetPointer(), &branchType, it) == 0)
1591 const char * name = nullptr;
1592 if (git_branch_name(&name, ref))
1593 continue;
1595 CString branchname = CUnicodeUtils::GetUnicode(name);
1596 if (branchType & GIT_BRANCH_REMOTE)
1597 list.push_back(L"remotes/" + branchname);
1598 else
1600 if (git_branch_is_head(ref))
1602 if (skipCurrent)
1603 continue;
1604 cur = branchname;
1606 list.push_back(branchname);
1611 else
1613 CString cmd = L"git.exe branch --no-color";
1615 if ((type & BRANCH_ALL) == BRANCH_ALL)
1616 cmd += L" -a";
1617 else if (type & BRANCH_REMOTE)
1618 cmd += L" -r";
1620 ret = Run(cmd, [&](CStringA lineA)
1622 lineA.Trim(" \r\n\t");
1623 if (lineA.IsEmpty())
1624 return;
1625 if (lineA.Find(" -> ") >= 0)
1626 return; // skip something like: refs/origin/HEAD -> refs/origin/master
1628 CString branch = CUnicodeUtils::GetUnicode(lineA);
1629 if (lineA[0] == '*')
1631 if (skipCurrent)
1632 return;
1633 branch = branch.Mid(2);
1634 cur = branch;
1636 // check whether HEAD is detached
1637 CString currentHead;
1638 if (branch[0] == L'(' && GetCurrentBranchFromFile(m_CurrentDir, currentHead) == 1)
1640 headIsDetached = true;
1641 return;
1644 if ((type & BRANCH_REMOTE) != 0 && (type & BRANCH_LOCAL) == 0)
1645 branch = L"remotes/" + branch;
1646 list.push_back(branch);
1648 if (ret == 1 && IsInitRepos())
1649 return 0;
1652 if(type & BRANCH_FETCH_HEAD && !DerefFetchHead().IsEmpty())
1653 list.push_back(L"FETCH_HEAD");
1655 std::sort(list.begin() + prevCount, list.end(), LogicalCompareBranchesPredicate);
1657 if (current && !headIsDetached && !skipCurrent)
1659 for (unsigned int i = 0; i < list.size(); ++i)
1661 if (list[i] == cur)
1663 *current = i;
1664 break;
1669 return ret;
1672 int CGit::GetRefsCommitIsOn(STRING_VECTOR& list, const CGitHash& hash, bool includeTags, bool includeBranches, BRANCH_TYPE type)
1674 if (!includeTags && !includeBranches || hash.IsEmpty())
1675 return 0;
1677 size_t prevCount = list.size();
1678 if (m_IsUseLibGit2)
1680 CAutoRepository repo(GetGitRepository());
1681 if (!repo)
1682 return -1;
1684 CAutoReferenceIterator it;
1685 if (git_reference_iterator_new(it.GetPointer(), repo))
1686 return -1;
1688 auto checkDescendent = [&list, &hash, &repo](const git_oid* oid, const git_reference* ref) {
1689 if (!oid)
1690 return;
1691 if (git_oid_equal(oid, (const git_oid*)hash.m_hash) || git_graph_descendant_of(repo, oid, (const git_oid*)hash.m_hash) == 1)
1693 const char* name = git_reference_name(ref);
1694 if (!name)
1695 return;
1697 list.push_back(CUnicodeUtils::GetUnicode(name));
1701 CAutoReference ref;
1702 while (git_reference_next(ref.GetPointer(), it) == 0)
1704 if (git_reference_is_tag(ref))
1706 if (!includeTags)
1707 continue;
1709 CAutoTag tag;
1710 if (git_tag_lookup(tag.GetPointer(), repo, git_reference_target(ref)) == 0)
1712 CAutoObject obj;
1713 if (git_tag_peel(obj.GetPointer(), tag) < 0)
1714 continue;
1715 checkDescendent(git_object_id(obj), ref);
1716 continue;
1719 else if (git_reference_is_remote(ref))
1721 if (!includeBranches || !(type & BRANCH_REMOTE))
1722 continue;
1724 else if (git_reference_is_branch(ref))
1726 if (!includeBranches || !(type & GIT_BRANCH_LOCAL))
1727 continue;
1729 else
1730 continue;
1732 if (git_reference_type(ref) == GIT_REF_SYMBOLIC)
1734 CAutoReference peeledRef;
1735 if (git_reference_resolve(peeledRef.GetPointer(), ref) < 0)
1736 continue;
1738 checkDescendent(git_reference_target(peeledRef), ref);
1739 continue;
1742 checkDescendent(git_reference_target(ref), ref);
1745 else
1747 if (includeBranches)
1749 CString cmd = L"git.exe branch --no-color";
1750 if ((type & BRANCH_ALL) == BRANCH_ALL)
1751 cmd += L" -a";
1752 else if (type & BRANCH_REMOTE)
1753 cmd += L" -r";
1754 cmd += L" --contains " + hash.ToString();
1756 if (Run(cmd, [&](CStringA lineA)
1758 lineA.Trim(" \r\n\t");
1759 if (lineA.IsEmpty())
1760 return;
1761 if (lineA.Find(" -> ") >= 0) // normalize symbolic refs: "refs/origin/HEAD -> refs/origin/master" to "refs/origin/HEAD"
1762 lineA.Truncate(lineA.Find(" -> "));
1764 CString branch = CUnicodeUtils::GetUnicode(lineA);
1765 if (lineA[0] == '*')
1767 branch = branch.Mid(2);
1768 CString currentHead;
1769 if (branch[0] == L'(' && GetCurrentBranchFromFile(m_CurrentDir, currentHead) == 1)
1770 return;
1773 if ((type & BRANCH_REMOTE) != 0 && (type & BRANCH_LOCAL) == 0)
1774 branch = L"refs/remotes/" + branch;
1775 else if (CStringUtils::StartsWith(branch, L"remotes/"))
1776 branch = L"refs/" + branch;
1777 else
1778 branch = L"refs/heads/" + branch;
1779 list.push_back(branch);
1781 return -1;
1784 if (includeTags)
1786 CString cmd = L"git.exe tag --contains " + hash.ToString();
1787 if (Run(cmd, [&list](CStringA lineA)
1789 if (lineA.IsEmpty())
1790 return;
1791 list.push_back(L"refs/tags/" + CUnicodeUtils::GetUnicode(lineA));
1793 return -1;
1797 std::sort(list.begin() + prevCount, list.end(), LogicalCompareBranchesPredicate);
1798 list.erase(std::unique(list.begin() + prevCount, list.end()), list.end());
1800 return 0;
1803 int CGit::GetRemoteList(STRING_VECTOR &list)
1805 size_t prevCount = list.size();
1806 if (this->m_IsUseLibGit2)
1808 CAutoRepository repo(GetGitRepository());
1809 if (!repo)
1810 return -1;
1812 CAutoStrArray remotes;
1813 if (git_remote_list(remotes, repo))
1814 return -1;
1816 for (size_t i = 0; i < remotes->count; ++i)
1818 CStringA remote(remotes->strings[i]);
1819 list.push_back(CUnicodeUtils::GetUnicode(remote));
1822 std::sort(list.begin() + prevCount, list.end(), LogicalComparePredicate);
1824 return 0;
1827 return Run(L"git.exe remote", [&](const CStringA& lineA)
1829 if (lineA.IsEmpty())
1830 return;
1831 list.push_back(CUnicodeUtils::GetUnicode(lineA));
1835 int CGit::GetRemoteTags(const CString& remote, REF_VECTOR& list)
1837 size_t prevCount = list.size();
1838 if (UsingLibGit2(GIT_CMD_FETCH))
1840 CAutoRepository repo(GetGitRepository());
1841 if (!repo)
1842 return -1;
1844 CStringA remoteA = CUnicodeUtils::GetUTF8(remote);
1845 CAutoRemote gitremote;
1846 // first try with a named remote (e.g. "origin")
1847 if (git_remote_lookup(gitremote.GetPointer(), repo, remoteA) < 0)
1849 // retry with repository located at a specific url
1850 if (git_remote_create_anonymous(gitremote.GetPointer(), repo, remoteA) < 0)
1851 return -1;
1854 git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
1855 callbacks.credentials = g_Git2CredCallback;
1856 callbacks.certificate_check = g_Git2CheckCertificateCallback;
1857 git_proxy_options proxy = GIT_PROXY_OPTIONS_INIT;
1858 proxy.type = GIT_PROXY_AUTO;
1859 if (git_remote_connect(gitremote, GIT_DIRECTION_FETCH, &callbacks, &proxy, nullptr) < 0)
1860 return -1;
1862 const git_remote_head** heads = nullptr;
1863 size_t size = 0;
1864 if (git_remote_ls(&heads, &size, gitremote) < 0)
1865 return -1;
1867 for (size_t i = 0; i < size; i++)
1869 CString ref = CUnicodeUtils::GetUnicode(heads[i]->name);
1870 CString shortname;
1871 if (!GetShortName(ref, shortname, L"refs/tags/"))
1872 continue;
1873 CGitHash hash;
1874 git_oid_cpy((git_oid*)hash.m_hash, &heads[i]->oid);
1875 list.emplace_back(TGitRef{ shortname, hash });
1877 std::sort(list.begin() + prevCount, list.end(), g_bSortTagsReversed ? LogicalCompareReversedPredicate : LogicalComparePredicate);
1878 return 0;
1881 CString cmd;
1882 cmd.Format(L"git.exe ls-remote -t \"%s\"", (LPCTSTR)remote);
1883 gitLastErr = cmd + L'\n';
1884 if (Run(cmd, [&](CStringA lineA)
1886 CGitHash hash;
1887 hash.ConvertFromStrA(lineA.Mid(0, GIT_HASH_SIZE * 2));
1888 lineA = lineA.Mid(51); // sha1, tab + refs/tags/
1889 if (!lineA.IsEmpty())
1890 list.emplace_back(TGitRef{ CUnicodeUtils::GetUnicode(lineA), hash });
1891 }, &gitLastErr))
1892 return -1;
1893 std::sort(list.begin() + prevCount, list.end(), g_bSortTagsReversed ? LogicalCompareReversedPredicate : LogicalComparePredicate);
1894 return 0;
1897 int CGit::DeleteRemoteRefs(const CString& sRemote, const STRING_VECTOR& list)
1899 if (UsingLibGit2(GIT_CMD_PUSH))
1901 CAutoRepository repo(GetGitRepository());
1902 if (!repo)
1903 return -1;
1905 CStringA remoteA = CUnicodeUtils::GetUTF8(sRemote);
1906 CAutoRemote remote;
1907 if (git_remote_lookup(remote.GetPointer(), repo, remoteA) < 0)
1908 return -1;
1910 git_push_options pushOpts = GIT_PUSH_OPTIONS_INIT;
1911 git_remote_callbacks& callbacks = pushOpts.callbacks;
1912 callbacks.credentials = g_Git2CredCallback;
1913 callbacks.certificate_check = g_Git2CheckCertificateCallback;
1914 std::vector<CStringA> refspecs;
1915 for (const auto& ref : list)
1916 refspecs.push_back(CUnicodeUtils::GetUTF8(L":" + ref));
1918 std::vector<char*> vc;
1919 vc.reserve(refspecs.size());
1920 std::transform(refspecs.begin(), refspecs.end(), std::back_inserter(vc), [](CStringA& s) -> char* { return s.GetBuffer(); });
1921 git_strarray specs = { vc.data(), vc.size() };
1923 if (git_remote_push(remote, &specs, &pushOpts) < 0)
1924 return -1;
1926 else
1928 CMassiveGitTaskBase mgtPush(L"push " + sRemote, FALSE);
1929 for (const auto& ref : list)
1930 mgtPush.AddFile(L':' + ref);
1932 BOOL cancel = FALSE;
1933 mgtPush.Execute(cancel);
1936 return 0;
1939 int libgit2_addto_list_each_ref_fn(git_reference *ref, void *payload)
1941 STRING_VECTOR *list = (STRING_VECTOR*)payload;
1942 list->push_back(CUnicodeUtils::GetUnicode(git_reference_name(ref)));
1943 return 0;
1946 int CGit::GetRefList(STRING_VECTOR &list)
1948 size_t prevCount = list.size();
1949 if (this->m_IsUseLibGit2)
1951 CAutoRepository repo(GetGitRepository());
1952 if (!repo)
1953 return -1;
1955 if (git_reference_foreach(repo, libgit2_addto_list_each_ref_fn, &list))
1956 return -1;
1958 std::sort(list.begin() + prevCount, list.end(), LogicalComparePredicate);
1960 return 0;
1963 int ret = Run(L"git.exe show-ref -d", [&](const CStringA& lineA)
1965 int start = lineA.Find(L' ');
1966 ASSERT(start == 2 * GIT_HASH_SIZE);
1967 if (start <= 0)
1968 return;
1970 CString name = CUnicodeUtils::GetUnicode(lineA.Mid(start + 1));
1971 if (list.empty() || name != *list.crbegin() + L"^{}")
1972 list.push_back(name);
1974 if (!ret)
1975 std::sort(list.begin() + prevCount, list.end(), LogicalComparePredicate);
1976 else if (ret == 1 && IsInitRepos())
1977 return 0;
1978 return ret;
1981 typedef struct map_each_ref_payload {
1982 git_repository * repo;
1983 MAP_HASH_NAME * map;
1984 } map_each_ref_payload;
1986 int libgit2_addto_map_each_ref_fn(git_reference *ref, void *payload)
1988 map_each_ref_payload *payloadContent = (map_each_ref_payload*)payload;
1990 CString str = CUnicodeUtils::GetUnicode(git_reference_name(ref));
1992 CAutoObject gitObject;
1993 if (git_revparse_single(gitObject.GetPointer(), payloadContent->repo, git_reference_name(ref)))
1994 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
1996 if (git_object_type(gitObject) == GIT_OBJ_TAG)
1998 str += L"^{}"; // deref tag
1999 CAutoObject derefedTag;
2000 if (git_object_peel(derefedTag.GetPointer(), gitObject, GIT_OBJ_ANY))
2001 return 1;
2002 gitObject.Swap(derefedTag);
2005 const git_oid * oid = git_object_id(gitObject);
2006 if (!oid)
2007 return 1;
2009 CGitHash hash(oid->id);
2010 (*payloadContent->map)[hash].push_back(str);
2012 return 0;
2014 int CGit::GetMapHashToFriendName(git_repository* repo, MAP_HASH_NAME &map)
2016 ATLASSERT(repo);
2018 map_each_ref_payload payloadContent = { repo, &map };
2020 if (git_reference_foreach(repo, libgit2_addto_map_each_ref_fn, &payloadContent))
2021 return -1;
2023 for (auto it = map.begin(); it != map.end(); ++it)
2025 std::sort(it->second.begin(), it->second.end());
2028 return 0;
2031 int CGit::GetMapHashToFriendName(MAP_HASH_NAME &map)
2033 if (this->m_IsUseLibGit2)
2035 CAutoRepository repo(GetGitRepository());
2036 if (!repo)
2037 return -1;
2039 return GetMapHashToFriendName(repo, map);
2042 int ret = Run(L"git.exe show-ref -d", [&](const CStringA& lineA)
2044 int start = lineA.Find(L' ');
2045 ASSERT(start == 2 * GIT_HASH_SIZE);
2046 if (start <= 0)
2047 return;
2049 CGitHash hash;
2050 hash.ConvertFromStrA(lineA);
2051 map[hash].push_back(CUnicodeUtils::GetUnicode(lineA.Mid(start + 1)));
2054 if (ret == 1 && IsInitRepos())
2055 return 0;
2056 return ret;
2059 int CGit::GuessRefForHash(CString& ref, const CGitHash& hash)
2061 MAP_HASH_NAME map;
2062 if (GetMapHashToFriendName(map))
2063 return -1;
2065 auto it = map.find(hash);
2066 if (it == map.cend())
2068 ref = hash.ToString();
2069 return 0;
2072 const auto& reflist = it->second;
2073 for (const auto& reftype : { L"refs/heads/", L"refs/remotes/", L"refs/tags/" })
2075 auto found = std::find_if(reflist.cbegin(), reflist.cend(), [&reftype](const auto& ref) { return CStringUtils::StartsWith(ref, reftype); });
2076 if (found == reflist.cend())
2077 continue;
2079 GetShortName(*found, ref, reftype);
2080 return 0;
2083 ref = hash.ToString();
2084 return 0;
2087 int CGit::GetBranchDescriptions(MAP_STRING_STRING& map)
2089 CAutoConfig config(true);
2090 if (git_config_add_file_ondisk(config, CGit::GetGitPathStringA(GetGitLocalConfig()), GIT_CONFIG_LEVEL_LOCAL, nullptr, FALSE) < 0)
2091 return -1;
2092 return git_config_foreach_match(config, "^branch\\..*\\.description$", [](const git_config_entry* entry, void* data)
2094 MAP_STRING_STRING* descriptions = (MAP_STRING_STRING*)data;
2095 CString key = CUnicodeUtils::GetUnicode(entry->name);
2096 key = key.Mid(7, key.GetLength() - 7 - 12); // 7: branch., 12: .description
2097 descriptions->insert(std::make_pair(key, CUnicodeUtils::GetUnicode(entry->value)));
2098 return 0;
2099 }, &map);
2102 static void SetLibGit2SearchPath(int level, const CString &value)
2104 CStringA valueA = CUnicodeUtils::GetMulti(value, CP_UTF8);
2105 git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, level, (LPCSTR)valueA);
2108 static void SetLibGit2TemplatePath(const CString &value)
2110 CStringA valueA = CUnicodeUtils::GetMulti(value, CP_UTF8);
2111 git_libgit2_opts(GIT_OPT_SET_TEMPLATE_PATH, (LPCSTR)valueA);
2114 int CGit::FindAndSetGitExePath(BOOL bFallback)
2116 CRegString msysdir = CRegString(REG_MSYSGIT_PATH, L"", FALSE);
2117 CString str = msysdir;
2118 if (!str.IsEmpty() && PathFileExists(str + L"\\git.exe"))
2120 CGit::ms_LastMsysGitDir = str;
2121 return TRUE;
2124 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": git.exe not exists: %s\n", (LPCTSTR)CGit::ms_LastMsysGitDir);
2125 if (!bFallback)
2126 return FALSE;
2128 // first, search PATH if git/bin directory is already present
2129 if (FindGitPath())
2131 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": FindGitPath() => %s\n", (LPCTSTR)CGit::ms_LastMsysGitDir);
2132 msysdir = CGit::ms_LastMsysGitDir;
2133 msysdir.write();
2134 return TRUE;
2137 if (FindGitForWindows(str))
2139 msysdir = str;
2140 CGit::ms_LastMsysGitDir = str;
2141 msysdir.write();
2142 return TRUE;
2145 return FALSE;
2148 BOOL CGit::CheckMsysGitDir(BOOL bFallback)
2150 if (m_bInitialized)
2151 return TRUE;
2153 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": CheckMsysGitDir(%d)\n", bFallback);
2154 this->m_Environment.clear();
2155 m_Environment.CopyProcessEnvironment();
2157 // 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,
2158 // because MSys2 changed the default to "ASCII". SO, make sure we have a proper default set
2159 if (m_Environment.GetEnv(L"LC_ALL").IsEmpty())
2160 m_Environment.SetEnv(L"LC_ALL", L"C");
2162 // set HOME if not set already
2163 size_t homesize;
2164 _wgetenv_s(&homesize, nullptr, 0, L"HOME");
2165 if (!homesize)
2166 m_Environment.SetEnv(L"HOME", GetHomeDirectory());
2168 //setup ssh client
2169 CString sshclient = CRegString(L"Software\\TortoiseGit\\SSH");
2170 if (sshclient.IsEmpty())
2171 sshclient = CRegString(L"Software\\TortoiseGit\\SSH", L"", FALSE, HKEY_LOCAL_MACHINE);
2173 if(!sshclient.IsEmpty())
2175 m_Environment.SetEnv(L"GIT_SSH", sshclient);
2176 if (CStringUtils::EndsWithI(sshclient, L"tortoisegitplink") || CStringUtils::EndsWithI(sshclient, L"tortoisegitplink.exe"))
2177 m_Environment.SetEnv(L"GIT_SSH_VARIANT", L"ssh");
2178 m_Environment.SetEnv(L"SVN_SSH", sshclient);
2180 else
2182 TCHAR sPlink[MAX_PATH] = {0};
2183 GetModuleFileName(nullptr, sPlink, _countof(sPlink));
2184 LPTSTR ptr = wcsrchr(sPlink, L'\\');
2185 if (ptr) {
2186 wcscpy_s(ptr + 1, _countof(sPlink) - (ptr - sPlink + 1), L"TortoiseGitPlink.exe");
2187 m_Environment.SetEnv(L"GIT_SSH", sPlink);
2188 m_Environment.SetEnv(L"GIT_SSH_VARIANT", L"ssh");
2189 m_Environment.SetEnv(L"SVN_SSH", sPlink);
2194 TCHAR sAskPass[MAX_PATH] = {0};
2195 GetModuleFileName(nullptr, sAskPass, _countof(sAskPass));
2196 LPTSTR ptr = wcsrchr(sAskPass, L'\\');
2197 if (ptr)
2199 wcscpy_s(ptr + 1, _countof(sAskPass) - (ptr - sAskPass + 1), L"SshAskPass.exe");
2200 m_Environment.SetEnv(L"DISPLAY",L":9999");
2201 m_Environment.SetEnv(L"SSH_ASKPASS",sAskPass);
2202 m_Environment.SetEnv(L"GIT_ASKPASS",sAskPass);
2203 m_Environment.SetEnv(L"GIT_ASK_YESNO", sAskPass);
2207 if (!FindAndSetGitExePath(bFallback))
2208 return FALSE;
2210 CString msysGitDir;
2211 PathCanonicalize(CStrBuf(msysGitDir, MAX_PATH), CGit::ms_LastMsysGitDir + L"\\..\\");
2212 static const CString prefixes[] = { L"mingw64\\etc", L"mingw32\\etc", L"etc" };
2213 static const int prefixes_len[] = { 8, 8, 0 };
2214 for (int i = 0; i < _countof(prefixes); ++i)
2216 #ifndef _WIN64
2217 if (i == 0)
2218 continue;
2219 #endif
2220 if (PathIsDirectory(msysGitDir + prefixes[i])) {
2221 msysGitDir += prefixes[i].Left(prefixes_len[i]);
2222 break;
2225 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
2226 PathCanonicalize(CStrBuf(msysGitDir, MAX_PATH), CGit::ms_LastMsysGitDir + L"\\..\\..");
2227 CGit::ms_MsysGitRootDir = msysGitDir;
2229 if ((CString)CRegString(REG_SYSTEM_GITCONFIGPATH, L"", FALSE) != g_Git.GetGitSystemConfig())
2230 CRegString(REG_SYSTEM_GITCONFIGPATH, L"", FALSE) = g_Git.GetGitSystemConfig();
2232 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": ms_LastMsysGitDir = %s\n", (LPCTSTR)CGit::ms_LastMsysGitDir);
2233 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": ms_MsysGitRootDir = %s\n", (LPCTSTR)CGit::ms_MsysGitRootDir);
2234 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": System config = %s\n", (LPCTSTR)g_Git.GetGitSystemConfig());
2235 if (!ms_bCygwinGit && !ms_bMsys2Git)
2237 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": ProgramData config = %s\n", (LPCTSTR)g_Git.GetGitProgramDataConfig());
2238 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_PROGRAMDATA, CTGitPath(g_Git.GetGitProgramDataConfig()).GetContainingDirectory().GetWinPathString());
2240 else
2241 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_PROGRAMDATA, CTGitPath(g_Git.GetGitSystemConfig()).GetContainingDirectory().GetWinPathString());
2243 // Configure libgit2 search paths
2244 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_SYSTEM, CTGitPath(g_Git.GetGitSystemConfig()).GetContainingDirectory().GetWinPathString());
2245 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_GLOBAL, g_Git.GetHomeDirectory());
2246 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_XDG, g_Git.GetGitGlobalXDGConfigPath());
2247 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 };
2248 git_transport_register("ssh", git_transport_smart, &ssh_wintunnel_subtransport_definition);
2249 git_transport_register("ssh+git", git_transport_smart, &ssh_wintunnel_subtransport_definition);
2250 git_transport_register("git+ssh", git_transport_smart, &ssh_wintunnel_subtransport_definition);
2251 git_libgit2_opts(GIT_OPT_SET_USER_AGENT, "TortoiseGit libgit2");
2252 if (!(ms_bCygwinGit || ms_bMsys2Git))
2253 SetLibGit2TemplatePath(CGit::ms_MsysGitRootDir + L"share\\git-core\\templates");
2254 else
2255 SetLibGit2TemplatePath(CGit::ms_MsysGitRootDir + L"usr\\share\\git-core\\templates");
2257 m_Environment.AddToPath(CGit::ms_LastMsysGitDir);
2258 m_Environment.AddToPath((CString)CRegString(REG_MSYSGIT_EXTRA_PATH, L"", FALSE));
2260 #if !defined(TGITCACHE) && !defined(TORTOISESHELL)
2261 // register filter only once
2262 if (!git_filter_lookup("filter"))
2264 CString sh;
2265 for (const CString& binDirPrefix : { L"\\..\\usr\\bin", L"\\..\\bin", L"" })
2267 CString possibleShExe = CGit::ms_LastMsysGitDir + binDirPrefix + L"\\sh.exe";
2268 if (PathFileExists(possibleShExe))
2270 CString temp;
2271 PathCanonicalize(CStrBuf(temp, MAX_PATH), possibleShExe);
2272 sh.Format(L"\"%s\"", (LPCTSTR)temp);
2273 // we need to put the usr\bin folder on the path for Git for Windows based on msys2
2274 m_Environment.AddToPath(temp.Left(temp.GetLength() - (int)wcslen(L"\\sh.exe")));
2275 break;
2279 // Add %GIT_EXEC_PATH% to %PATH% when launching libgit2 filter executable
2280 // It is possible that the filter points to a git subcommand, that is located at libexec\git-core
2281 CString gitExecPath = CGit::ms_MsysGitRootDir;
2282 gitExecPath.Append(L"libexec\\git-core");
2283 m_Environment.AddToPath(gitExecPath);
2285 if (git_filter_register("filter", git_filter_filter_new(sh, m_Environment), GIT_FILTER_DRIVER_PRIORITY))
2286 return FALSE;
2288 #endif
2290 m_bInitialized = TRUE;
2291 return true;
2294 CString CGit::GetHomeDirectory() const
2296 const wchar_t * homeDir = wget_windows_home_directory();
2297 return CString(homeDir, (int)wcslen(homeDir));
2300 CString CGit::GetGitLocalConfig() const
2302 CString path;
2303 GitAdminDir::GetAdminDirPath(m_CurrentDir, path);
2304 path += L"config";
2305 return path;
2308 CStringA CGit::GetGitPathStringA(const CString &path)
2310 return CUnicodeUtils::GetUTF8(CTGitPath(path).GetGitPathString());
2313 CString CGit::GetGitGlobalConfig() const
2315 return g_Git.GetHomeDirectory() + L"\\.gitconfig";
2318 CString CGit::GetGitGlobalXDGConfigPath() const
2320 return g_Git.GetHomeDirectory() + L"\\.config\\git";
2323 CString CGit::GetGitGlobalXDGConfig() const
2325 return g_Git.GetGitGlobalXDGConfigPath() + L"\\config";
2328 CString CGit::GetGitProgramDataConfig() const
2330 const wchar_t* programdataConfig = wget_program_data_config();
2331 return CString(programdataConfig);
2334 CString CGit::GetGitSystemConfig() const
2336 const wchar_t * systemConfig = wget_msysgit_etc();
2337 return CString(systemConfig, (int)wcslen(systemConfig));
2340 BOOL CGit::CheckCleanWorkTree(bool stagedOk /* false */)
2342 if (UsingLibGit2(GIT_CMD_CHECK_CLEAN_WT))
2344 CAutoRepository repo(GetGitRepository());
2345 if (!repo)
2346 return FALSE;
2348 if (git_repository_head_unborn(repo))
2349 return FALSE;
2351 git_status_options statusopt = GIT_STATUS_OPTIONS_INIT;
2352 statusopt.show = stagedOk ? GIT_STATUS_SHOW_WORKDIR_ONLY : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
2353 statusopt.flags = GIT_STATUS_OPT_UPDATE_INDEX | GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
2355 CAutoStatusList status;
2356 if (git_status_list_new(status.GetPointer(), repo, &statusopt))
2357 return FALSE;
2359 return (0 == git_status_list_entrycount(status));
2362 if (Run(L"git.exe rev-parse --verify HEAD", nullptr, nullptr, CP_UTF8))
2363 return FALSE;
2365 if (Run(L"git.exe update-index --ignore-submodules --refresh", nullptr, nullptr, CP_UTF8))
2366 return FALSE;
2368 if (Run(L"git.exe diff-files --quiet --ignore-submodules", nullptr, nullptr, CP_UTF8))
2369 return FALSE;
2371 if (!stagedOk && Run(L"git.exe diff-index --cached --quiet HEAD --ignore-submodules --", nullptr, nullptr, CP_UTF8))
2372 return FALSE;
2374 return TRUE;
2376 int CGit::Revert(const CString& commit, const CTGitPathList &list, CString& err)
2378 for (int i = 0; i < list.GetCount(); ++i)
2380 if (Revert(commit, (CTGitPath&)list[i], err))
2381 return -1;
2383 return 0;
2385 int CGit::Revert(const CString& commit, const CTGitPath &path, CString& err)
2387 if(path.m_Action & CTGitPath::LOGACTIONS_REPLACED && !path.GetGitOldPathString().IsEmpty())
2389 if (CTGitPath(path.GetGitOldPathString()).IsDirectory())
2391 err.Format(L"Cannot revert renaming of \"%s\". A directory with the old name \"%s\" exists.", (LPCTSTR)path.GetGitPathString(), (LPCTSTR)path.GetGitOldPathString());
2392 return -1;
2394 if (path.Exists())
2396 CString force;
2397 // if the filenames only differ in case, we have to pass "-f"
2398 if (path.GetGitPathString().CompareNoCase(path.GetGitOldPathString()) == 0)
2399 force = L"-f ";
2400 CString cmd;
2401 cmd.Format(L"git.exe mv %s-- \"%s\" \"%s\"", (LPCTSTR)force, (LPCTSTR)path.GetGitPathString(), (LPCTSTR)path.GetGitOldPathString());
2402 if (Run(cmd, &err, CP_UTF8))
2403 return -1;
2405 else
2407 CString cmd;
2408 cmd.Format(L"git.exe rm -f --cached -- \"%s\"", (LPCTSTR)path.GetGitPathString());
2409 if (Run(cmd, &err, CP_UTF8))
2410 return -1;
2413 CString cmd;
2414 cmd.Format(L"git.exe checkout %s -f -- \"%s\"", (LPCTSTR)commit, (LPCWSTR)path.GetGitOldPathString());
2415 if (Run(cmd, &err, CP_UTF8))
2416 return -1;
2418 else if(path.m_Action & CTGitPath::LOGACTIONS_ADDED)
2419 { //To init git repository, there are not HEAD, so we can use git reset command
2420 CString cmd;
2421 cmd.Format(L"git.exe rm -f --cached -- \"%s\"", (LPCTSTR)path.GetGitPathString());
2423 if (Run(cmd, &err, CP_UTF8))
2424 return -1;
2426 else
2428 CString cmd;
2429 cmd.Format(L"git.exe checkout %s -f -- \"%s\"", (LPCTSTR)commit, (LPCTSTR)path.GetGitPathString());
2430 if (Run(cmd, &err, CP_UTF8))
2431 return -1;
2434 if (path.m_Action & CTGitPath::LOGACTIONS_DELETED)
2436 CString cmd;
2437 cmd.Format(L"git.exe add -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
2438 if (Run(cmd, &err, CP_UTF8))
2439 return -1;
2442 return 0;
2445 int CGit::HasWorkingTreeConflicts(git_repository* repo)
2447 ATLASSERT(repo);
2449 CAutoIndex index;
2450 if (git_repository_index(index.GetPointer(), repo))
2451 return -1;
2453 return git_index_has_conflicts(index);
2456 int CGit::HasWorkingTreeConflicts()
2458 if (UsingLibGit2(GIT_CMD_CHECKCONFLICTS))
2460 CAutoRepository repo(GetGitRepository());
2461 if (!repo)
2462 return -1;
2464 return HasWorkingTreeConflicts(repo);
2467 CString output;
2468 gitLastErr.Empty();
2469 if (Run(L"git.exe ls-files -u -t -z", &output, &gitLastErr, CP_UTF8))
2470 return -1;
2472 return output.IsEmpty() ? 0 : 1;
2475 bool CGit::IsFastForward(const CString &from, const CString &to, CGitHash * commonAncestor)
2477 if (UsingLibGit2(GIT_CMD_MERGE_BASE))
2479 CAutoRepository repo(GetGitRepository());
2480 if (!repo)
2481 return false;
2483 CGitHash fromHash, toHash, baseHash;
2484 if (GetHash(repo, toHash, FixBranchName(to)))
2485 return false;
2487 if (GetHash(repo, fromHash, FixBranchName(from)))
2488 return false;
2490 git_oid baseOid;
2491 if (git_merge_base(&baseOid, repo, (const git_oid*)toHash.m_hash, (const git_oid*)fromHash.m_hash))
2492 return false;
2494 baseHash = baseOid.id;
2496 if (commonAncestor)
2497 *commonAncestor = baseHash;
2499 return fromHash == baseHash;
2501 // else
2502 CString base;
2503 CGitHash basehash,hash;
2504 CString cmd;
2505 cmd.Format(L"git.exe merge-base %s %s", (LPCTSTR)FixBranchName(to), (LPCTSTR)FixBranchName(from));
2507 gitLastErr.Empty();
2508 if (Run(cmd, &base, &gitLastErr, CP_UTF8))
2509 return false;
2510 basehash = base.Trim();
2512 GetHash(hash, from);
2514 if (commonAncestor)
2515 *commonAncestor = basehash;
2517 return hash == basehash;
2520 unsigned int CGit::Hash2int(const CGitHash &hash)
2522 int ret=0;
2523 for (int i = 0; i < 4; ++i)
2525 ret = ret << 8;
2526 ret |= hash.m_hash[i];
2528 return ret;
2531 int CGit::RefreshGitIndex()
2533 if(g_Git.m_IsUseGitDLL)
2535 CAutoLocker lock(g_Git.m_critGitDllSec);
2538 int result = [] { return git_run_cmd("update-index","update-index -q --refresh"); }();
2539 git_exit_cleanup();
2540 return result;
2542 }catch(...)
2544 git_exit_cleanup();
2545 return -1;
2549 else
2550 return Run(L"git.exe update-index --refresh", nullptr, nullptr, CP_UTF8);
2553 int CGit::GetOneFile(const CString &Refname, const CTGitPath &path, const CString &outputfile)
2555 if (UsingLibGit2(GIT_CMD_GETONEFILE))
2557 CAutoRepository repo(GetGitRepository());
2558 if (!repo)
2559 return -1;
2561 CGitHash hash;
2562 if (GetHash(repo, hash, Refname))
2563 return -1;
2565 CAutoCommit commit;
2566 if (git_commit_lookup(commit.GetPointer(), repo, (const git_oid *)hash.m_hash))
2567 return -1;
2569 CAutoTree tree;
2570 if (git_commit_tree(tree.GetPointer(), commit))
2571 return -1;
2573 CAutoTreeEntry entry;
2574 int ret = git_tree_entry_bypath(entry.GetPointer(), tree, CUnicodeUtils::GetUTF8(path.GetGitPathString()));
2575 if (ret)
2576 return ret;
2578 if (git_tree_entry_filemode(entry) == GIT_FILEMODE_COMMIT)
2580 giterr_set_str(GITERR_NONE, "The requested object is a submodule and not a file.");
2581 return -1;
2584 CAutoBlob blob;
2585 if (git_tree_entry_to_object((git_object**)blob.GetPointer(), repo, entry))
2586 return -1;
2588 CAutoFILE file = _wfsopen(outputfile, L"wb", SH_DENYWR);
2589 if (file == nullptr)
2591 giterr_set_str(GITERR_NONE, "Could not create file.");
2592 return -1;
2594 CAutoBuf buf;
2595 if (git_blob_filtered_content(buf, blob, CUnicodeUtils::GetUTF8(path.GetGitPathString()), 0))
2596 return -1;
2597 if (fwrite(buf->ptr, sizeof(char), buf->size, file) != buf->size)
2599 giterr_set_str(GITERR_OS, "Could not write to file.");
2600 return -1;
2603 return 0;
2605 else if (g_Git.m_IsUseGitDLL)
2607 CAutoLocker lock(g_Git.m_critGitDllSec);
2610 g_Git.CheckAndInitDll();
2611 CStringA ref, patha, outa;
2612 ref = CUnicodeUtils::GetMulti(Refname, CP_UTF8);
2613 patha = CUnicodeUtils::GetMulti(path.GetGitPathString(), CP_UTF8);
2614 outa = CUnicodeUtils::GetMulti(outputfile, CP_UTF8);
2615 ::DeleteFile(outputfile);
2616 int ret = git_checkout_file(ref, patha, outa.GetBuffer());
2617 outa.ReleaseBuffer();
2618 return ret;
2621 catch (const char * msg)
2623 gitLastErr = L"gitdll.dll reports: " + CString(msg);
2624 return -1;
2626 catch (...)
2628 gitLastErr = L"An unknown gitdll.dll error occurred.";
2629 return -1;
2632 else
2634 CString cmd;
2635 cmd.Format(L"git.exe cat-file -p %s:\"%s\"", (LPCTSTR)Refname, (LPCTSTR)path.GetGitPathString());
2636 gitLastErr.Empty();
2637 return RunLogFile(cmd, outputfile, &gitLastErr);
2641 void CEnvironment::clear()
2643 __super::clear();
2644 baseptr = nullptr;
2647 bool CEnvironment::empty()
2649 return size() < 3; // three is minimum for an empty environment with an empty key and empty value: "=\0\0"
2652 CEnvironment::operator LPTSTR()
2654 if (empty())
2655 return nullptr;
2656 return data();
2659 CEnvironment::operator LPWSTR*()
2661 return &baseptr;
2664 void CEnvironment::CopyProcessEnvironment()
2666 if (!empty())
2667 pop_back();
2668 TCHAR *porig = GetEnvironmentStrings();
2669 TCHAR *p = porig;
2670 while(*p !=0 || *(p+1) !=0)
2671 this->push_back(*p++);
2673 push_back(L'\0');
2674 push_back(L'\0');
2675 baseptr = data();
2677 FreeEnvironmentStrings(porig);
2680 CString CEnvironment::GetEnv(const TCHAR *name)
2682 CString str;
2683 for (size_t i = 0; i < size(); ++i)
2685 str = &(*this)[i];
2686 int start =0;
2687 CString sname = str.Tokenize(L"=", start);
2688 if(sname.CompareNoCase(name) == 0)
2689 return &(*this)[i+start];
2690 i+=str.GetLength();
2692 return L"";
2695 void CEnvironment::SetEnv(const TCHAR *name, const TCHAR* value)
2697 unsigned int i;
2698 for (i = 0; i < size(); ++i)
2700 CString str = &(*this)[i];
2701 int start =0;
2702 CString sname = str.Tokenize(L"=", start);
2703 if(sname.CompareNoCase(name) == 0)
2704 break;
2705 i+=str.GetLength();
2708 if(i == size())
2710 if (!value) // as we haven't found the variable we want to remove, just return
2711 return;
2712 if (i == 0) // make inserting into an empty environment work
2714 this->push_back(L'\0');
2715 ++i;
2717 i -= 1; // roll back terminate \0\0
2718 this->push_back(L'\0');
2721 CEnvironment::iterator it;
2722 it=this->begin();
2723 it += i;
2725 while(*it && i<size())
2727 this->erase(it);
2728 it=this->begin();
2729 it += i;
2732 if (value == nullptr) // remove the variable
2734 this->erase(it);
2735 if (empty())
2736 baseptr = nullptr;
2737 else
2738 baseptr = data();
2739 return;
2742 while(*name)
2744 this->insert(it,*name++);
2745 ++i;
2746 it= begin()+i;
2749 this->insert(it, L'=');
2750 ++i;
2751 it= begin()+i;
2753 while(*value)
2755 this->insert(it,*value++);
2756 ++i;
2757 it= begin()+i;
2759 baseptr = data();
2762 void CEnvironment::AddToPath(CString value)
2764 value.TrimRight(L'\\');
2765 if (value.IsEmpty())
2766 return;
2768 CString path = GetEnv(L"PATH").TrimRight(L';') + L';';
2770 // do not double add paths to %PATH%
2771 if (path.Find(value + L';') >= 0 || path.Find(value + L"\\;") >= 0)
2772 return;
2774 path += value;
2776 SetEnv(L"PATH", path);
2779 int CGit::GetGitEncode(TCHAR* configkey)
2781 CString str=GetConfigValue(configkey);
2783 if(str.IsEmpty())
2784 return CP_UTF8;
2786 return CUnicodeUtils::GetCPCode(str);
2789 int CGit::GetShortHASHLength() const
2791 return 7;
2794 CString CGit::GetShortName(const CString& ref, REF_TYPE *out_type)
2796 CString str=ref;
2797 CString shortname;
2798 REF_TYPE type = CGit::UNKNOWN;
2800 if (CGit::GetShortName(str, shortname, L"refs/heads/"))
2801 type = CGit::LOCAL_BRANCH;
2802 else if (CGit::GetShortName(str, shortname, L"refs/remotes/"))
2803 type = CGit::REMOTE_BRANCH;
2804 else if (CStringUtils::EndsWith(str, L"^{}") && CGit::GetShortName(str, shortname, L"refs/tags/"))
2805 type = CGit::ANNOTATED_TAG;
2806 else if (CGit::GetShortName(str, shortname, L"refs/tags/"))
2807 type = CGit::TAG;
2808 else if (CGit::GetShortName(str, shortname, L"refs/stash"))
2810 type = CGit::STASH;
2811 shortname = L"stash";
2813 else if (CGit::GetShortName(str, shortname, L"refs/bisect/"))
2815 CString bisectGood;
2816 CString bisectBad;
2817 g_Git.GetBisectTerms(&bisectGood, &bisectBad);
2818 TCHAR c;
2819 if (CStringUtils::StartsWith(shortname, bisectGood) && ((c = shortname.GetAt(bisectGood.GetLength())) == '-' || c == '\0'))
2821 type = CGit::BISECT_GOOD;
2822 shortname = bisectGood;
2825 if (CStringUtils::StartsWith(shortname, bisectBad) && ((c = shortname.GetAt(bisectBad.GetLength())) == '-' || c == '\0'))
2827 type = CGit::BISECT_BAD;
2828 shortname = bisectBad;
2831 if (CStringUtils::StartsWith(shortname, L"skip") && ((c = shortname.GetAt(4)) == '-' || c == '\0'))
2833 type = CGit::BISECT_SKIP;
2834 shortname = L"skip";
2837 else if (CGit::GetShortName(str, shortname, L"refs/notes/"))
2838 type = CGit::NOTES;
2839 else if (CGit::GetShortName(str, shortname, L"refs/"))
2840 type = CGit::UNKNOWN;
2841 else
2843 type = CGit::UNKNOWN;
2844 shortname = ref;
2847 if(out_type)
2848 *out_type = type;
2850 return shortname;
2853 bool CGit::UsingLibGit2(LIBGIT2_CMD cmd) const
2855 return m_IsUseLibGit2 && ((1 << cmd) & m_IsUseLibGit2_mask) ? true : false;
2858 void CGit::SetGit2CredentialCallback(void* callback)
2860 g_Git2CredCallback = (git_cred_acquire_cb)callback;
2863 void CGit::SetGit2CertificateCheckCertificate(void* callback)
2865 g_Git2CheckCertificateCallback = (git_transport_certificate_check_cb)callback;
2868 CString CGit::GetUnifiedDiffCmd(const CTGitPath& path, const CString& rev1, const CString& rev2, bool bMerge, bool bCombine, int diffContext, bool bNoPrefix)
2870 CString cmd;
2871 if (rev2 == GitRev::GetWorkingCopy())
2872 cmd.Format(L"git.exe diff --stat%s -p %s --", bNoPrefix ? L" --no-prefix" : L"", (LPCTSTR)rev1);
2873 else if (rev1 == GitRev::GetWorkingCopy())
2874 cmd.Format(L"git.exe diff -R --stat%s -p %s --", bNoPrefix ? L" --no-prefix" : L"", (LPCTSTR)rev2);
2875 else
2877 CString merge;
2878 if (bMerge)
2879 merge += L" -m";
2881 if (bCombine)
2882 merge += L" -c";
2884 CString unified;
2885 if (diffContext >= 0)
2886 unified.Format(L" --unified=%d", diffContext);
2887 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);
2890 if (!path.IsEmpty())
2892 cmd += L" \"";
2893 cmd += path.GetGitPathString();
2894 cmd += L'"';
2897 return cmd;
2900 static void UnifiedDiffStatToFile(const git_buf* text, void* payload)
2902 ATLASSERT(payload && text);
2903 fwrite(text->ptr, 1, text->size, (FILE *)payload);
2904 fwrite("\n", 1, 1, (FILE *)payload);
2907 static int UnifiedDiffToFile(const git_diff_delta * /* delta */, const git_diff_hunk * /* hunk */, const git_diff_line * line, void *payload)
2909 ATLASSERT(payload && line);
2910 if (line->origin == GIT_DIFF_LINE_CONTEXT || line->origin == GIT_DIFF_LINE_ADDITION || line->origin == GIT_DIFF_LINE_DELETION)
2911 fwrite(&line->origin, 1, 1, (FILE *)payload);
2912 fwrite(line->content, 1, line->content_len, (FILE *)payload);
2913 return 0;
2916 static int resolve_to_tree(git_repository *repo, const char *identifier, git_tree **tree)
2918 ATLASSERT(repo && identifier && tree);
2920 /* try to resolve identifier */
2921 CAutoObject obj;
2922 if (git_revparse_single(obj.GetPointer(), repo, identifier))
2923 return -1;
2925 if (obj == nullptr)
2926 return GIT_ENOTFOUND;
2928 int err = 0;
2929 switch (git_object_type(obj))
2931 case GIT_OBJ_TREE:
2932 *tree = (git_tree *)obj.Detach();
2933 break;
2934 case GIT_OBJ_COMMIT:
2935 err = git_commit_tree(tree, (git_commit *)(git_object*)obj);
2936 break;
2937 default:
2938 err = GIT_ENOTFOUND;
2941 return err;
2944 /* use libgit2 get unified diff */
2945 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)
2947 CStringA tree1 = CUnicodeUtils::GetMulti(revNew, CP_UTF8);
2948 CStringA tree2 = CUnicodeUtils::GetMulti(revOld, CP_UTF8);
2950 CAutoRepository repo(g_Git.GetGitRepository());
2951 if (!repo)
2952 return -1;
2954 int isHeadOrphan = git_repository_head_unborn(repo);
2955 if (isHeadOrphan == 1)
2956 return 0;
2957 else if (isHeadOrphan != 0)
2958 return -1;
2960 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
2961 CStringA pathA = CUnicodeUtils::GetMulti(path.GetGitPathString(), CP_UTF8);
2962 char *buf = pathA.GetBuffer();
2963 if (!pathA.IsEmpty())
2965 opts.pathspec.strings = &buf;
2966 opts.pathspec.count = 1;
2968 if (bNoPrefix)
2970 opts.new_prefix = "";
2971 opts.old_prefix = "";
2973 CAutoDiff diff;
2975 if (revNew == GitRev::GetWorkingCopy() || revOld == GitRev::GetWorkingCopy())
2977 CAutoTree t1;
2978 CAutoDiff diff2;
2980 if (revNew != GitRev::GetWorkingCopy() && resolve_to_tree(repo, tree1, t1.GetPointer()))
2981 return -1;
2983 if (revOld != GitRev::GetWorkingCopy() && resolve_to_tree(repo, tree2, t1.GetPointer()))
2984 return -1;
2986 if (git_diff_tree_to_index(diff.GetPointer(), repo, t1, nullptr, &opts))
2987 return -1;
2989 if (git_diff_index_to_workdir(diff2.GetPointer(), repo, nullptr, &opts))
2990 return -1;
2992 if (git_diff_merge(diff, diff2))
2993 return -1;
2995 else
2997 if (tree1.IsEmpty() && tree2.IsEmpty())
2998 return -1;
3000 if (tree1.IsEmpty())
3002 tree1 = tree2;
3003 tree2.Empty();
3006 CAutoTree t1;
3007 CAutoTree t2;
3008 if (!tree1.IsEmpty() && resolve_to_tree(repo, tree1, t1.GetPointer()))
3009 return -1;
3011 if (tree2.IsEmpty())
3013 /* don't check return value, there are not parent commit at first commit*/
3014 resolve_to_tree(repo, tree1 + "~1", t2.GetPointer());
3016 else if (resolve_to_tree(repo, tree2, t2.GetPointer()))
3017 return -1;
3018 if (git_diff_tree_to_tree(diff.GetPointer(), repo, t2, t1, &opts))
3019 return -1;
3022 CAutoDiffStats stats;
3023 if (git_diff_get_stats(stats.GetPointer(), diff))
3024 return -1;
3025 CAutoBuf statBuf;
3026 if (git_diff_stats_to_buf(statBuf, stats, GIT_DIFF_STATS_FULL, 0))
3027 return -1;
3028 statCallback(statBuf, data);
3030 for (size_t i = 0; i < git_diff_num_deltas(diff); ++i)
3032 CAutoPatch patch;
3033 if (git_patch_from_diff(patch.GetPointer(), diff, i))
3034 return -1;
3036 if (git_patch_print(patch, callback, data))
3037 return -1;
3040 pathA.ReleaseBuffer();
3042 return 0;
3045 int CGit::GetUnifiedDiff(const CTGitPath& path, const CString& rev1, const CString& rev2, CString patchfile, bool bMerge, bool bCombine, int diffContext, bool bNoPrefix)
3047 if (UsingLibGit2(GIT_CMD_DIFF))
3049 CAutoFILE file = _wfsopen(patchfile, L"wb", SH_DENYRW);
3050 if (!file)
3051 return -1;
3052 return GetUnifiedDiffLibGit2(path, rev1, rev2, UnifiedDiffStatToFile, UnifiedDiffToFile, file, bMerge, bNoPrefix);
3054 else
3056 CString cmd;
3057 cmd = GetUnifiedDiffCmd(path, rev1, rev2, bMerge, bCombine, diffContext, bNoPrefix);
3058 gitLastErr.Empty();
3059 return RunLogFile(cmd, patchfile, &gitLastErr);
3063 static void UnifiedDiffStatToStringA(const git_buf* text, void* payload)
3065 ATLASSERT(payload && text);
3066 CStringA *str = (CStringA*) payload;
3067 str->Append(text->ptr, (int)text->size);
3068 str->AppendChar('\n');
3071 static int UnifiedDiffToStringA(const git_diff_delta * /*delta*/, const git_diff_hunk * /*hunk*/, const git_diff_line *line, void *payload)
3073 ATLASSERT(payload && line);
3074 CStringA *str = (CStringA*) payload;
3075 if (line->origin == GIT_DIFF_LINE_CONTEXT || line->origin == GIT_DIFF_LINE_ADDITION || line->origin == GIT_DIFF_LINE_DELETION)
3076 str->Append(&line->origin, 1);
3077 str->Append(line->content, (int)line->content_len);
3078 return 0;
3081 int CGit::GetUnifiedDiff(const CTGitPath& path, const CString& rev1, const CString& rev2, CStringA* buffer, bool bMerge, bool bCombine, int diffContext)
3083 if (UsingLibGit2(GIT_CMD_DIFF))
3084 return GetUnifiedDiffLibGit2(path, rev1, rev2, UnifiedDiffStatToStringA, UnifiedDiffToStringA, buffer, bMerge, false);
3085 else
3087 CString cmd;
3088 cmd = GetUnifiedDiffCmd(path, rev1, rev2, bMerge, bCombine, diffContext);
3089 BYTE_VECTOR vector;
3090 int ret = Run(cmd, &vector);
3091 if (!vector.empty())
3093 vector.push_back(0); // vector is not NUL terminated
3094 buffer->Append((char*)vector.data());
3096 return ret;
3100 int CGit::GitRevert(int parent, const CGitHash &hash)
3102 if (UsingLibGit2(GIT_CMD_REVERT))
3104 CAutoRepository repo(GetGitRepository());
3105 if (!repo)
3106 return -1;
3108 CAutoCommit commit;
3109 if (git_commit_lookup(commit.GetPointer(), repo, (const git_oid *)hash.m_hash))
3110 return -1;
3112 git_revert_options revert_opts = GIT_REVERT_OPTIONS_INIT;
3113 revert_opts.mainline = parent;
3114 int result = git_revert(repo, commit, &revert_opts);
3116 return !result ? 0 : -1;
3118 else
3120 CString cmd, merge;
3121 if (parent)
3122 merge.Format(L"-m %d ", parent);
3123 cmd.Format(L"git.exe revert --no-edit --no-commit %s%s", (LPCTSTR)merge, (LPCTSTR)hash.ToString());
3124 gitLastErr = cmd + L'\n';
3125 if (Run(cmd, &gitLastErr, CP_UTF8))
3126 return -1;
3127 else
3129 gitLastErr.Empty();
3130 return 0;
3135 int CGit::DeleteRef(const CString& reference)
3137 if (UsingLibGit2(GIT_CMD_DELETETAGBRANCH))
3139 CAutoRepository repo(GetGitRepository());
3140 if (!repo)
3141 return -1;
3143 CStringA refA;
3144 if (CStringUtils::EndsWith(reference, L"^{}"))
3145 refA = CUnicodeUtils::GetUTF8(reference.Left(reference.GetLength() - 3));
3146 else
3147 refA = CUnicodeUtils::GetUTF8(reference);
3149 CAutoReference ref;
3150 if (git_reference_lookup(ref.GetPointer(), repo, refA))
3151 return -1;
3153 int result = -1;
3154 if (git_reference_is_tag(ref))
3155 result = git_tag_delete(repo, git_reference_shorthand(ref));
3156 else if (git_reference_is_branch(ref))
3157 result = git_branch_delete(ref);
3158 else if (git_reference_is_remote(ref))
3159 result = git_branch_delete(ref);
3160 else
3161 result = git_reference_delete(ref);
3163 return result;
3165 else
3167 CString cmd, shortname;
3168 if (GetShortName(reference, shortname, L"refs/heads/"))
3169 cmd.Format(L"git.exe branch -D -- %s", (LPCTSTR)shortname);
3170 else if (GetShortName(reference, shortname, L"refs/tags/"))
3171 cmd.Format(L"git.exe tag -d -- %s", (LPCTSTR)shortname);
3172 else if (GetShortName(reference, shortname, L"refs/remotes/"))
3173 cmd.Format(L"git.exe branch -r -D -- %s", (LPCTSTR)shortname);
3174 else
3176 gitLastErr = L"unsupported reference type: " + reference;
3177 return -1;
3180 gitLastErr.Empty();
3181 if (Run(cmd, &gitLastErr, CP_UTF8))
3182 return -1;
3184 gitLastErr.Empty();
3185 return 0;
3189 bool CGit::LoadTextFile(const CString &filename, CString &msg)
3191 if (!PathFileExists(filename))
3192 return false;
3194 CAutoFILE pFile = _wfsopen(filename, L"rb", SH_DENYWR);
3195 if (!pFile)
3197 ::MessageBox(nullptr, L"Could not open " + filename, L"TortoiseGit", MB_ICONERROR);
3198 return true; // load no further files
3201 CStringA str;
3204 char s[8196] = { 0 };
3205 int read = (int)fread(s, sizeof(char), sizeof(s), pFile);
3206 if (read == 0)
3207 break;
3208 str += CStringA(s, read);
3209 } while (true);
3210 msg += CUnicodeUtils::GetUnicode(str);
3211 msg.Replace(L"\r\n", L"\n");
3212 msg.TrimRight(L'\n');
3213 msg += L'\n';
3215 return true; // load no further files
3218 int CGit::GetWorkingTreeChanges(CTGitPathList& result, bool amend, const CTGitPathList* filterlist)
3220 if (IsInitRepos())
3221 return GetInitAddList(result);
3223 BYTE_VECTOR out;
3225 int count = 1;
3226 if (filterlist)
3227 count = filterlist->GetCount();
3228 ATLASSERT(count > 0);
3230 CString head = L"HEAD";
3231 if (amend)
3232 head = L"HEAD~1";
3234 for (int i = 0; i < count; ++i)
3236 ATLASSERT(!filterlist || !(*filterlist)[i].GetGitPathString().IsEmpty()); // pathspec must not be empty, be compatible with Git >= 2.16.0
3237 BYTE_VECTOR cmdout;
3238 CString cmd;
3239 if (ms_bCygwinGit || ms_bMsys2Git)
3241 // Prevent showing all files as modified when using cygwin's git
3242 if (!filterlist)
3243 cmd = L"git.exe status --";
3244 else
3245 cmd.Format(L"git.exe status -- \"%s\"", (LPCTSTR)(*filterlist)[i].GetGitPathString());
3246 Run(cmd, &cmdout);
3247 cmdout.clear();
3250 // also list staged files which will be in the commit
3251 Run(L"git.exe diff-index --cached --raw " + head + L" --numstat -C -M -z --", &cmdout);
3253 if (!filterlist)
3254 cmd = (L"git.exe diff-index --raw " + head + L" --numstat -C -M -z --");
3255 else
3256 cmd.Format(L"git.exe diff-index --raw " + head + L" --numstat -C -M -z -- \"%s\"", (LPCTSTR)(*filterlist)[i].GetGitPathString());
3258 BYTE_VECTOR cmdErr;
3259 if (Run(cmd, &cmdout, &cmdErr))
3261 size_t last = cmdErr.RevertFind(0);
3262 CString str;
3263 if (last != BYTE_VECTOR::npos)
3264 CGit::StringAppend(&str, &cmdErr[last + 1], CP_UTF8, (int)(cmdErr.size() - last) - 1);
3265 else if (!cmdErr.empty())
3266 CGit::StringAppend(&str, cmdErr.data(), CP_UTF8, (int)cmdErr.size() - 1);
3267 else
3268 str.Format(L"\"%s\" exited with an error code, but did not output any error message", (LPCTSTR)cmd);
3269 MessageBox(nullptr, str, L"TortoiseGit", MB_OK | MB_ICONERROR);
3272 out.append(cmdout, 0);
3274 result.ParserFromLog(out);
3276 std::map<CString, int> duplicateMap;
3277 for (int i = 0; i < result.GetCount(); ++i)
3278 duplicateMap.insert(std::pair<CString, int>(result[i].GetGitPathString(), i));
3280 // handle delete conflict case, when remote : modified, local : deleted.
3281 for (int i = 0; i < count; ++i)
3283 BYTE_VECTOR cmdout;
3284 CString cmd;
3286 if (!filterlist)
3287 cmd = L"git.exe ls-files -u -t -z";
3288 else
3289 cmd.Format(L"git.exe ls-files -u -t -z -- \"%s\"", (LPCTSTR)(*filterlist)[i].GetGitPathString());
3291 Run(cmd, &cmdout);
3293 CTGitPathList conflictlist;
3294 conflictlist.ParserFromLog(cmdout);
3295 for (int j = 0; j < conflictlist.GetCount(); ++j)
3297 auto existing = duplicateMap.find(conflictlist[j].GetGitPathString());
3298 if (existing != duplicateMap.end())
3300 CTGitPath& p = const_cast<CTGitPath&>(result[existing->second]);
3301 p.m_Action |= CTGitPath::LOGACTIONS_UNMERGED;
3303 else
3305 result.AddPath(conflictlist[j]);
3306 duplicateMap.insert(std::pair<CString, int>(result[i].GetGitPathString(), result.GetCount() - 1));
3311 // handle source files of file renames/moves (issue #860)
3312 // if a file gets renamed and the new file "git add"ed, diff-index doesn't list the source file anymore
3313 for (int i = 0; i < count; ++i)
3315 BYTE_VECTOR cmdout;
3316 CString cmd;
3318 if (!filterlist)
3319 cmd = L"git.exe ls-files -d -z";
3320 else
3321 cmd.Format(L"git.exe ls-files -d -z -- \"%s\"", (LPCTSTR)(*filterlist)[i].GetGitPathString());
3323 Run(cmd, &cmdout);
3325 CTGitPathList deletelist;
3326 deletelist.ParserFromLog(cmdout, true);
3327 for (int j = 0; j < deletelist.GetCount(); ++j)
3329 auto existing = duplicateMap.find(deletelist[j].GetGitPathString());
3330 if (existing == duplicateMap.end())
3332 result.AddPath(deletelist[j]);
3333 duplicateMap.insert(std::pair<CString, int>(result[i].GetGitPathString(), result.GetCount() - 1));
3335 else
3337 CTGitPath& p = const_cast<CTGitPath&>(result[existing->second]);
3338 p.m_Action |= CTGitPath::LOGACTIONS_MISSING;
3343 return 0;
3346 int CGit::IsRebaseRunning()
3348 CString adminDir;
3349 if (!GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir))
3350 return -1;
3352 if (PathIsDirectory(adminDir + L"rebase-apply") || PathIsDirectory(adminDir + L"tgitrebase.active"))
3353 return 1;
3354 return 0;
3357 void CGit::GetBisectTerms(CString* good, CString* bad)
3359 static CString lastGood;
3360 static CString lastBad;
3361 static ULONGLONG lastRead = 0;
3363 SCOPE_EXIT
3365 if (bad)
3366 *bad = lastBad;
3367 if (good)
3368 *good = lastGood;
3371 #ifndef GTEST_INCLUDE_GTEST_GTEST_H_
3372 // add some caching here, because this method might be called multiple times in a short time from LogDlg and RevisionGraph
3373 // as we only read a small file the performance effects should be negligible
3374 if (lastRead + 5000 > GetTickCount64())
3375 return;
3376 #endif
3378 lastGood = L"good";
3379 lastBad = L"bad";
3381 CString adminDir;
3382 if (!GitAdminDir::GetAdminDirPath(m_CurrentDir, adminDir))
3383 return;
3385 CString termsFile = adminDir + L"BISECT_TERMS";
3386 CAutoFILE fp;
3387 _wfopen_s(fp.GetPointer(), termsFile, L"rb");
3388 if (!fp)
3389 return;
3390 char badA[MAX_PATH] = { 0 };
3391 fgets(badA, sizeof(badA), fp);
3392 size_t len = strlen(badA);
3393 if (len > 0 && badA[len - 1] == '\n')
3394 badA[len - 1] = '\0';
3395 char goodA[MAX_PATH] = { 0 };
3396 fgets(goodA, sizeof(goodA), fp);
3397 len = strlen(goodA);
3398 if (len > 0 && goodA[len - 1] == '\n')
3399 goodA[len - 1] = '\0';
3400 lastGood = CUnicodeUtils::GetUnicode(goodA);
3401 lastBad = CUnicodeUtils::GetUnicode(badA);
3402 lastRead = GetTickCount64();
3405 int CGit::GetGitVersion(CString* versiondebug, CString* errStr)
3407 CString version, err;
3408 if (Run(L"git.exe --version", &version, &err, CP_UTF8))
3410 if (errStr)
3411 *errStr = err;
3412 return -1;
3415 int ver = 0;
3416 if (versiondebug)
3417 *versiondebug = version;
3421 int start = 0;
3422 CString str = version.Tokenize(L".", start);
3423 int space = str.ReverseFind(L' ');
3424 str = str.Mid(space + 1, start);
3425 ver = _wtol(str);
3426 ver <<= 24;
3428 version = version.Mid(start);
3429 start = 0;
3431 str = version.Tokenize(L".", start);
3432 ver |= (_wtol(str) & 0xFF) << 16;
3434 str = version.Tokenize(L".", start);
3435 ver |= (_wtol(str) & 0xFF) << 8;
3437 str = version.Tokenize(L".", start);
3438 ver |= (_wtol(str) & 0xFF);
3440 catch (...)
3442 if (!ver)
3443 return -1;
3446 return ver;
3449 int CGit::GetGitNotes(const CGitHash& hash, CString& notes)
3451 CAutoRepository repo(GetGitRepository());
3452 if (!repo)
3453 return -1;
3455 CAutoNote note;
3456 int ret = git_note_read(note.GetPointer(), repo, nullptr, reinterpret_cast<const git_oid*>(hash.m_hash));
3457 if (ret == GIT_ENOTFOUND)
3459 notes.Empty();
3460 return 0;
3462 else if (ret)
3463 return -1;
3464 notes = CUnicodeUtils::GetUnicode(git_note_message(note));
3465 return 0;
3468 int CGit::SetGitNotes(const CGitHash& hash, const CString& notes)
3470 CAutoRepository repo(GetGitRepository());
3471 if (!repo)
3472 return -1;
3474 CAutoSignature signature;
3475 if (git_signature_default(signature.GetPointer(), repo) < 0)
3476 return -1;
3478 git_oid oid;
3479 if (git_note_create(&oid, repo, nullptr, signature, signature, reinterpret_cast<const git_oid*>(hash.m_hash), CUnicodeUtils::GetUTF8(notes), 1) < 0)
3480 return -1;
3482 return 0;