SetGitNote -> SetGitNotes
[TortoiseGit.git] / src / Git / Git.cpp
blob574ba27dc919a1866be3dd2f3d6053f0ae20aabc
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2017 - 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 CStringA branchA = CUnicodeUtils::GetUTF8(L"refs/heads/" + branchname);
251 return !!git_reference_is_valid_name(branchA);
254 static bool IsPowerShell(CString cmd)
256 cmd.MakeLower();
257 int powerShellPos = cmd.Find(L"powershell");
258 if (powerShellPos < 0)
259 return false;
261 // found the word powershell, check that it is the command and not any parameter
262 int end = cmd.GetLength();
263 if (end > 0 && cmd[0] == L'"')
265 int secondDoubleQuote = cmd.Find(L'"', 1);
266 if (secondDoubleQuote > 0)
267 end = secondDoubleQuote;
269 else
271 int firstSpace = cmd.Find(L' ');
272 if (firstSpace > 0)
273 end = firstSpace;
276 return (end - (int)wcslen(L"powershell") - (int)wcslen(L".exe") == powerShellPos || end - (int)wcslen(L"powershell") == powerShellPos);
279 int CGit::RunAsync(CString cmd, PROCESS_INFORMATION* piOut, HANDLE* hReadOut, HANDLE* hErrReadOut, const CString* StdioFile)
281 CAutoGeneralHandle hRead, hWrite, hReadErr, hWriteErr;
282 CAutoFile hStdioFile;
284 SECURITY_ATTRIBUTES sa = { 0 };
285 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
286 sa.bInheritHandle=TRUE;
287 if (!CreatePipe(hRead.GetPointer(), hWrite.GetPointer(), &sa, 0))
289 CString err = CFormatMessageWrapper();
290 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": could not open stdout pipe: %s\n", (LPCTSTR)err.Trim());
291 return TGIT_GIT_ERROR_OPEN_PIP;
293 if (hErrReadOut && !CreatePipe(hReadErr.GetPointer(), hWriteErr.GetPointer(), &sa, 0))
295 CString err = CFormatMessageWrapper();
296 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": could not open stderr pipe: %s\n", (LPCTSTR)err.Trim());
297 return TGIT_GIT_ERROR_OPEN_PIP;
300 if(StdioFile)
301 hStdioFile = CreateFile(*StdioFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
303 STARTUPINFO si = { 0 };
304 PROCESS_INFORMATION pi = { 0 };
305 si.cb=sizeof(STARTUPINFO);
307 if (hErrReadOut)
308 si.hStdError = hWriteErr;
309 else
310 si.hStdError = hWrite;
311 if(StdioFile)
312 si.hStdOutput=hStdioFile;
313 else
314 si.hStdOutput=hWrite;
316 si.wShowWindow=SW_HIDE;
317 si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
319 LPTSTR pEnv = m_Environment;
320 DWORD dwFlags = CREATE_UNICODE_ENVIRONMENT;
322 dwFlags |= CREATE_NEW_PROCESS_GROUP;
324 // CREATE_NEW_CONSOLE makes git (but not ssh.exe, see issue #2257) recognize that it has no console in order to launch askpass to ask for the password,
325 // DETACHED_PROCESS which was originally used here has the same effect (but works with git.exe AND ssh.exe), however, it prevents PowerShell from working (cf. issue #2143)
326 // => we keep using DETACHED_PROCESS as the default, but if cmd contains pwershell we use CREATE_NEW_CONSOLE
327 if (IsPowerShell(cmd))
328 dwFlags |= CREATE_NEW_CONSOLE;
329 else
330 dwFlags |= DETACHED_PROCESS;
332 memset(&this->m_CurrentGitPi,0,sizeof(PROCESS_INFORMATION));
334 bool startsWithGit = CStringUtils::StartsWith(cmd, L"git");
335 if (ms_bMsys2Git && startsWithGit && !CStringUtils::StartsWith(cmd, L"git.exe config "))
337 cmd.Replace(L"\\", L"\\\\\\\\");
338 cmd.Replace(L"\"", L"\\\"");
339 cmd = L'"' + CGit::ms_LastMsysGitDir + L"\\bash.exe\" -c \"/usr/bin/" + cmd + L'"';
341 else if (ms_bCygwinGit && startsWithGit && !CStringUtils::StartsWith(cmd, L"git.exe config "))
343 cmd.Replace(L'\\', L'/');
344 cmd.Replace(L"\"", L"\\\"");
345 cmd = L'"' + CGit::ms_LastMsysGitDir + L"\\bash.exe\" -c \"/bin/" + cmd + L'"';
347 else if (startsWithGit || CStringUtils::StartsWith(cmd, L"bash"))
349 int firstSpace = cmd.Find(L' ');
350 if (firstSpace > 0)
351 cmd = L'"' + CGit::ms_LastMsysGitDir + L'\\' + cmd.Left(firstSpace) + L'"' + cmd.Mid(firstSpace);
352 else
353 cmd = L'"' + CGit::ms_LastMsysGitDir + L'\\' + cmd + L'"';
356 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": executing %s\n", (LPCTSTR)cmd);
357 if(!CreateProcess(nullptr, cmd.GetBuffer(), nullptr, nullptr, TRUE, dwFlags, pEnv, m_CurrentDir.GetBuffer(), &si, &pi))
359 CString err = CFormatMessageWrapper();
360 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": error while executing command: %s\n", (LPCTSTR)err.Trim());
361 return TGIT_GIT_ERROR_CREATE_PROCESS;
364 m_CurrentGitPi = pi;
366 if(piOut)
367 *piOut=pi;
368 if(hReadOut)
369 *hReadOut = hRead.Detach();
370 if(hErrReadOut)
371 *hErrReadOut = hReadErr.Detach();
372 return 0;
374 //Must use sperate function to convert ANSI str to union code string
375 //Becuase A2W use stack as internal convert buffer.
376 void CGit::StringAppend(CString *str, const BYTE *p, int code,int length)
378 if (!str)
379 return ;
381 int len ;
382 if(length<0)
383 len = (int)strlen((const char*)p);
384 else
385 len=length;
386 if (len == 0)
387 return;
388 int currentContentLen = str->GetLength();
389 WCHAR * buf = str->GetBuffer(len * 4 + 1 + currentContentLen) + currentContentLen;
390 int appendedLen = MultiByteToWideChar(code, 0, (LPCSTR)p, len, buf, len * 4);
391 str->ReleaseBuffer(currentContentLen + appendedLen); // no - 1 because MultiByteToWideChar is called with a fixed length (thus no nul char included)
394 // This method was originally used to check for orphaned branches
395 BOOL CGit::CanParseRev(CString ref)
397 if (ref.IsEmpty())
398 ref = L"HEAD";
400 CString cmdout;
401 if (Run(L"git.exe rev-parse --revs-only " + ref, &cmdout, CP_UTF8))
402 return FALSE;
403 if(cmdout.IsEmpty())
404 return FALSE;
406 return TRUE;
409 // Checks if we have an orphaned HEAD
410 BOOL CGit::IsInitRepos()
412 CGitHash hash;
413 if (GetHash(hash, L"HEAD") != 0)
414 return FALSE;
415 return hash.IsEmpty() ? TRUE : FALSE;
418 DWORD WINAPI CGit::AsyncReadStdErrThread(LPVOID lpParam)
420 PASYNCREADSTDERRTHREADARGS pDataArray;
421 pDataArray = (PASYNCREADSTDERRTHREADARGS)lpParam;
423 DWORD readnumber;
424 BYTE data[CALL_OUTPUT_READ_CHUNK_SIZE];
425 while (ReadFile(pDataArray->fileHandle, data, CALL_OUTPUT_READ_CHUNK_SIZE, &readnumber, nullptr))
427 if (pDataArray->pcall->OnOutputErrData(data,readnumber))
428 break;
431 return 0;
434 #ifdef _MFC_VER
435 void CGit::KillRelatedThreads(CWinThread* thread)
437 CAutoLocker lock(m_critSecThreadMap);
438 auto it = m_AsyncReadStdErrThreadMap.find(thread->m_nThreadID);
439 if (it != m_AsyncReadStdErrThreadMap.cend())
441 TerminateThread(it->second, (DWORD)-1);
442 m_AsyncReadStdErrThreadMap.erase(it);
444 TerminateThread(thread->m_hThread, (DWORD)-1);
446 #endif
448 int CGit::Run(CGitCall* pcall)
450 PROCESS_INFORMATION pi;
451 CAutoGeneralHandle hRead, hReadErr;
452 if (RunAsync(pcall->GetCmd(), &pi, hRead.GetPointer(), hReadErr.GetPointer()))
453 return TGIT_GIT_ERROR_CREATE_PROCESS;
455 CAutoGeneralHandle piThread(std::move(pi.hThread));
456 CAutoGeneralHandle piProcess(std::move(pi.hProcess));
458 ASYNCREADSTDERRTHREADARGS threadArguments;
459 threadArguments.fileHandle = hReadErr;
460 threadArguments.pcall = pcall;
461 CAutoGeneralHandle thread = CreateThread(nullptr, 0, AsyncReadStdErrThread, &threadArguments, 0, nullptr);
463 if (thread)
465 CAutoLocker lock(m_critSecThreadMap);
466 m_AsyncReadStdErrThreadMap[GetCurrentThreadId()] = thread;
469 DWORD readnumber;
470 BYTE data[CALL_OUTPUT_READ_CHUNK_SIZE];
471 bool bAborted=false;
472 while (ReadFile(hRead, data, CALL_OUTPUT_READ_CHUNK_SIZE, &readnumber, nullptr))
474 // TODO: when OnOutputData() returns 'true', abort git-command. Send CTRL-C signal?
475 if(!bAborted)//For now, flush output when command aborted.
476 if(pcall->OnOutputData(data,readnumber))
477 bAborted=true;
479 if(!bAborted)
480 pcall->OnEnd();
482 if (thread)
484 WaitForSingleObject(thread, INFINITE);
486 CAutoLocker lock(m_critSecThreadMap);
487 m_AsyncReadStdErrThreadMap.erase(GetCurrentThreadId());
490 WaitForSingleObject(pi.hProcess, INFINITE);
491 DWORD exitcode =0;
493 if(!GetExitCodeProcess(pi.hProcess,&exitcode))
495 CString err = CFormatMessageWrapper();
496 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": could not get exit code: %s\n", (LPCTSTR)err.Trim());
497 return TGIT_GIT_ERROR_GET_EXIT_CODE;
499 else
500 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": process exited: %d\n", exitcode);
502 return exitcode;
504 class CGitCall_ByteVector : public CGitCall
506 public:
507 CGitCall_ByteVector(CString cmd,BYTE_VECTOR* pvector, BYTE_VECTOR* pvectorErr = nullptr) : CGitCall(cmd),m_pvector(pvector), m_pvectorErr(pvectorErr) {}
508 virtual bool OnOutputData(const BYTE* data, size_t size)
510 if (!m_pvector || size == 0)
511 return false;
512 size_t oldsize=m_pvector->size();
513 m_pvector->resize(m_pvector->size()+size);
514 memcpy(&*(m_pvector->begin()+oldsize),data,size);
515 return false;
517 virtual bool OnOutputErrData(const BYTE* data, size_t size)
519 if (!m_pvectorErr || size == 0)
520 return false;
521 size_t oldsize = m_pvectorErr->size();
522 m_pvectorErr->resize(m_pvectorErr->size() + size);
523 memcpy(&*(m_pvectorErr->begin() + oldsize), data, size);
524 return false;
526 BYTE_VECTOR* m_pvector;
527 BYTE_VECTOR* m_pvectorErr;
529 int CGit::Run(CString cmd,BYTE_VECTOR *vector, BYTE_VECTOR *vectorErr)
531 CGitCall_ByteVector call(cmd, vector, vectorErr);
532 return Run(&call);
534 int CGit::Run(CString cmd, CString* output, int code)
536 CString err;
537 int ret;
538 ret = Run(cmd, output, &err, code);
540 if (output && !err.IsEmpty())
542 if (!output->IsEmpty())
543 *output += L'\n';
544 *output += err;
547 return ret;
549 int CGit::Run(CString cmd, CString* output, CString* outputErr, int code)
551 BYTE_VECTOR vector, vectorErr;
552 int ret;
553 if (outputErr)
554 ret = Run(cmd, &vector, &vectorErr);
555 else
556 ret = Run(cmd, &vector);
558 vector.push_back(0);
559 StringAppend(output, vector.data(), code);
561 if (outputErr)
563 vectorErr.push_back(0);
564 StringAppend(outputErr, vectorErr.data(), code);
567 return ret;
570 class CGitCallCb : public CGitCall
572 public:
573 CGitCallCb(CString cmd, const GitReceiverFunc& recv, BYTE_VECTOR* pvectorErr = nullptr)
574 : CGitCall(cmd)
575 , m_recv(recv)
576 , m_pvectorErr(pvectorErr)
579 virtual bool OnOutputData(const BYTE* data, size_t size) override
581 // Add data
582 if (size == 0)
583 return false;
584 int oldEndPos = m_buffer.GetLength();
585 memcpy(m_buffer.GetBuffer(oldEndPos + (int)size) + oldEndPos, data, size);
586 m_buffer.ReleaseBuffer(oldEndPos + (int)size);
588 // Break into lines and feed to m_recv
589 int eolPos;
590 while ((eolPos = m_buffer.Find('\n')) >= 0)
592 m_recv(m_buffer.Left(eolPos));
593 m_buffer = m_buffer.Mid(eolPos + 1);
595 return false;
598 virtual bool OnOutputErrData(const BYTE* data, size_t size) override
600 if (!m_pvectorErr || size == 0)
601 return false;
602 size_t oldsize = m_pvectorErr->size();
603 m_pvectorErr->resize(m_pvectorErr->size() + size);
604 memcpy(&*(m_pvectorErr->begin() + oldsize), data, size);
605 return false;
608 virtual void OnEnd() override
610 if (!m_buffer.IsEmpty())
611 m_recv(m_buffer);
612 m_buffer.Empty(); // Just for sure
615 private:
616 GitReceiverFunc m_recv;
617 CStringA m_buffer;
618 BYTE_VECTOR* m_pvectorErr;
621 int CGit::Run(CString cmd, const GitReceiverFunc& recv, CString* outputErr)
623 if (outputErr)
625 BYTE_VECTOR vectorErr;
626 CGitCallCb call(cmd, recv, &vectorErr);
627 int ret = Run(&call);
628 vectorErr.push_back(0);
629 StringAppend(outputErr, vectorErr.data());
630 return ret;
633 CGitCallCb call(cmd, recv);
634 return Run(&call);
637 CString CGit::GetUserName(void)
639 CEnvironment env;
640 env.CopyProcessEnvironment();
641 CString envname = env.GetEnv(L"GIT_AUTHOR_NAME");
642 if (!envname.IsEmpty())
643 return envname;
644 return GetConfigValue(L"user.name");
646 CString CGit::GetUserEmail(void)
648 CEnvironment env;
649 env.CopyProcessEnvironment();
650 CString envmail = env.GetEnv(L"GIT_AUTHOR_EMAIL");
651 if (!envmail.IsEmpty())
652 return envmail;
654 return GetConfigValue(L"user.email");
657 CString CGit::GetConfigValue(const CString& name, const CString& def, bool wantBool)
659 CString configValue;
660 if(this->m_IsUseGitDLL)
662 CAutoLocker lock(g_Git.m_critGitDllSec);
666 CheckAndInitDll();
667 }catch(...)
670 CStringA key, value;
671 key = CUnicodeUtils::GetUTF8(name);
675 if (git_get_config(key, CStrBufA(value, 4096), 4096))
676 return def;
678 catch (const char *msg)
680 ::MessageBox(nullptr, L"Could not get config.\nlibgit reports:\n" + CString(msg), L"TortoiseGit", MB_OK | MB_ICONERROR);
681 return def;
684 StringAppend(&configValue, (BYTE*)(LPCSTR)value);
685 return configValue;
687 else
689 CString cmd;
690 cmd.Format(L"git.exe config%s %s", wantBool ? L" --bool" : L"", (LPCTSTR)name);
691 if (Run(cmd, &configValue, nullptr, CP_UTF8))
692 return def;
693 if (configValue.IsEmpty())
694 return configValue;
695 return configValue.Left(configValue.GetLength() - 1); // strip last newline character
699 bool CGit::GetConfigValueBool(const CString& name, const bool def)
701 CString configValue = GetConfigValue(name, def ? L"true" : L"false", true);
702 configValue.MakeLower();
703 configValue.Trim();
704 if (configValue == L"true" || configValue == L"on" || configValue == L"yes" || StrToInt(configValue) != 0)
705 return true;
706 else
707 return false;
710 int CGit::GetConfigValueInt32(const CString& name, const int def)
712 CString configValue = GetConfigValue(name);
713 int value = def;
714 if (!git_config_parse_int32(&value, CUnicodeUtils::GetUTF8(configValue)))
715 return value;
716 return def;
719 int CGit::SetConfigValue(const CString& key, const CString& value, CONFIG_TYPE type)
721 if(this->m_IsUseGitDLL)
723 CAutoLocker lock(g_Git.m_critGitDllSec);
727 CheckAndInitDll();
728 }catch(...)
731 CStringA keya, valuea;
732 keya = CUnicodeUtils::GetMulti(key, CP_UTF8);
733 valuea = CUnicodeUtils::GetUTF8(value);
737 return [=]() { return get_set_config(keya, valuea, type); }();
739 catch (const char *msg)
741 ::MessageBox(nullptr, L"Could not set config.\nlibgit reports:\n" + CString(msg), L"TortoiseGit", MB_OK | MB_ICONERROR);
742 return -1;
745 else
747 CString cmd;
748 CString option;
749 switch(type)
751 case CONFIG_GLOBAL:
752 option = L"--global";
753 break;
754 case CONFIG_SYSTEM:
755 option = L"--system";
756 break;
757 default:
758 break;
760 CString mangledValue = value;
761 mangledValue.Replace(L"\\\"", L"\\\\\"");
762 mangledValue.Replace(L"\"", L"\\\"");
763 cmd.Format(L"git.exe config %s %s \"%s\"", (LPCTSTR)option, (LPCTSTR)key, (LPCTSTR)mangledValue);
764 CString out;
765 if (Run(cmd, &out, nullptr, CP_UTF8))
766 return -1;
768 return 0;
771 int CGit::UnsetConfigValue(const CString& key, CONFIG_TYPE type)
773 if(this->m_IsUseGitDLL)
775 CAutoLocker lock(g_Git.m_critGitDllSec);
779 CheckAndInitDll();
780 }catch(...)
783 CStringA keya;
784 keya = CUnicodeUtils::GetMulti(key, CP_UTF8);
788 return [=]() { return get_set_config(keya, nullptr, type); }();
790 catch (const char *msg)
792 ::MessageBox(nullptr, L"Could not unset config.\nlibgit reports:\n" + CString(msg), L"TortoiseGit", MB_OK | MB_ICONERROR);
793 return -1;
796 else
798 CString cmd;
799 CString option;
800 switch(type)
802 case CONFIG_GLOBAL:
803 option = L"--global";
804 break;
805 case CONFIG_SYSTEM:
806 option = L"--system";
807 break;
808 default:
809 break;
811 cmd.Format(L"git.exe config %s --unset %s", (LPCTSTR)option, (LPCTSTR)key);
812 CString out;
813 if (Run(cmd, &out, nullptr, CP_UTF8))
814 return -1;
816 return 0;
819 CString CGit::GetCurrentBranch(bool fallback)
821 CString output;
822 //Run(L"git.exe branch", &branch);
824 int result = GetCurrentBranchFromFile(m_CurrentDir, output, fallback);
825 if (result != 0 && ((result == 1 && !fallback) || result != 1))
826 return L"(no branch)";
827 else
828 return output;
831 void CGit::GetRemoteTrackedBranch(const CString& localBranch, CString& pullRemote, CString& pullBranch)
833 if (localBranch.IsEmpty())
834 return;
836 CString configName;
837 configName.Format(L"branch.%s.remote", (LPCTSTR)localBranch);
838 pullRemote = GetConfigValue(configName);
840 //Select pull-branch from current branch
841 configName.Format(L"branch.%s.merge", (LPCTSTR)localBranch);
842 pullBranch = StripRefName(GetConfigValue(configName));
845 void CGit::GetRemoteTrackedBranchForHEAD(CString& remote, CString& branch)
847 CString refName;
848 if (GetCurrentBranchFromFile(m_CurrentDir, refName))
849 return;
850 GetRemoteTrackedBranch(StripRefName(refName), remote, branch);
853 void CGit::GetRemotePushBranch(const CString& localBranch, CString& pushRemote, CString& pushBranch)
855 if (localBranch.IsEmpty())
856 return;
858 CString configName;
860 configName.Format(L"branch.%s.pushremote", (LPCTSTR)localBranch);
861 pushRemote = g_Git.GetConfigValue(configName);
862 if (pushRemote.IsEmpty())
864 pushRemote = g_Git.GetConfigValue(L"remote.pushdefault");
865 if (pushRemote.IsEmpty())
867 configName.Format(L"branch.%s.remote", (LPCTSTR)localBranch);
868 pushRemote = g_Git.GetConfigValue(configName);
872 configName.Format(L"branch.%s.pushbranch", (LPCTSTR)localBranch);
873 pushBranch = g_Git.GetConfigValue(configName); // do not strip branch name (for gerrit), see issue #1609)
874 if (pushBranch.IsEmpty())
876 configName.Format(L"branch.%s.merge", (LPCTSTR)localBranch);
877 pushBranch = CGit::StripRefName(g_Git.GetConfigValue(configName));
881 CString CGit::GetFullRefName(const CString& shortRefName)
883 CString refName;
884 CString cmd;
885 cmd.Format(L"git.exe rev-parse --symbolic-full-name %s", (LPCTSTR)shortRefName);
886 if (Run(cmd, &refName, nullptr, CP_UTF8) != 0)
887 return CString();//Error
888 return refName.TrimRight();
891 CString CGit::StripRefName(CString refName)
893 if (CStringUtils::StartsWith(refName, L"refs/heads/"))
894 refName = refName.Mid(11);
895 else if (CStringUtils::StartsWith(refName, L"refs/"))
896 refName = refName.Mid(5);
897 return refName.TrimRight();
900 int CGit::GetCurrentBranchFromFile(const CString &sProjectRoot, CString &sBranchOut, bool fallback)
902 // read current branch name like git-gui does, by parsing the .git/HEAD file directly
904 if ( sProjectRoot.IsEmpty() )
905 return -1;
907 CString sDotGitPath;
908 if (!GitAdminDir::GetWorktreeAdminDirPath(sProjectRoot, sDotGitPath))
909 return -1;
911 CString sHeadFile = sDotGitPath + L"HEAD";
913 CAutoFILE pFile = _wfsopen(sHeadFile.GetString(), L"r", SH_DENYWR);
914 if (!pFile)
915 return -1;
917 char s[MAX_PATH] = {0};
918 fgets(s, sizeof(s), pFile);
920 const char *pfx = "ref: refs/heads/";
921 const size_t len = strlen(pfx);
923 if ( !strncmp(s, pfx, len) )
925 //# We're on a branch. It might not exist. But
926 //# HEAD looks good enough to be a branch.
927 CStringA utf8Branch(s + len);
928 sBranchOut = CUnicodeUtils::GetUnicode(utf8Branch);
929 sBranchOut.TrimRight(L" \r\n\t");
931 if ( sBranchOut.IsEmpty() )
932 return -1;
934 else if (fallback)
936 CStringA utf8Hash(s);
937 CString unicodeHash = CUnicodeUtils::GetUnicode(utf8Hash);
938 unicodeHash.TrimRight(L" \r\n\t");
939 if (CGitHash::IsValidSHA1(unicodeHash))
940 sBranchOut = unicodeHash;
941 else
942 //# Assume this is a detached head.
943 sBranchOut = L"HEAD";
944 return 1;
946 else
948 //# Assume this is a detached head.
949 sBranchOut = "HEAD";
951 return 1;
954 return 0;
957 int CGit::BuildOutputFormat(CString &format,bool IsFull)
959 CString log;
960 log.Format(L"#<%c>%%x00", LOG_REV_ITEM_BEGIN);
961 format += log;
962 if(IsFull)
964 log.Format(L"#<%c>%%an%%x00", LOG_REV_AUTHOR_NAME);
965 format += log;
966 log.Format(L"#<%c>%%ae%%x00", LOG_REV_AUTHOR_EMAIL);
967 format += log;
968 log.Format(L"#<%c>%%ai%%x00", LOG_REV_AUTHOR_DATE);
969 format += log;
970 log.Format(L"#<%c>%%cn%%x00", LOG_REV_COMMIT_NAME);
971 format += log;
972 log.Format(L"#<%c>%%ce%%x00", LOG_REV_COMMIT_EMAIL);
973 format += log;
974 log.Format(L"#<%c>%%ci%%x00", LOG_REV_COMMIT_DATE);
975 format += log;
976 log.Format(L"#<%c>%%b%%x00", LOG_REV_COMMIT_BODY);
977 format += log;
980 log.Format(L"#<%c>%%m%%H%%x00", LOG_REV_COMMIT_HASH);
981 format += log;
982 log.Format(L"#<%c>%%P%%x00", LOG_REV_COMMIT_PARENT);
983 format += log;
984 log.Format(L"#<%c>%%s%%x00", LOG_REV_COMMIT_SUBJECT);
985 format += log;
987 if(IsFull)
989 log.Format(L"#<%c>%%x00", LOG_REV_COMMIT_FILE);
990 format += log;
992 return 0;
995 CString CGit::GetLogCmd(CString range, const CTGitPath* path, int mask, CFilterData* Filter, int logOrderBy)
997 CString param;
999 if(mask& LOG_INFO_STAT )
1000 param += L" --numstat";
1001 if(mask& LOG_INFO_FILESTATE)
1002 param += L" --raw";
1004 if(mask& LOG_INFO_FULLHISTORY)
1005 param += L" --full-history";
1007 if(mask& LOG_INFO_BOUNDARY)
1008 param += L" --left-right --boundary";
1010 if(mask& CGit::LOG_INFO_ALL_BRANCH)
1012 param += L" --all";
1013 range.Empty();
1016 if (mask& CGit::LOG_INFO_BASIC_REFS)
1018 param += L" --branches";
1019 param += L" --tags";
1020 param += L" --remotes";
1021 param += L" --glob=stas[h]"; // require at least one glob operator
1022 param += L" --glob=bisect";
1023 range.Empty();
1026 if(mask & CGit::LOG_INFO_LOCAL_BRANCHES)
1028 param += L" --branches";
1029 range.Empty();
1032 if(mask& CGit::LOG_INFO_DETECT_COPYRENAME)
1033 param += L" -C";
1035 if(mask& CGit::LOG_INFO_DETECT_RENAME )
1036 param += L" -M";
1038 if(mask& CGit::LOG_INFO_FIRST_PARENT )
1039 param += L" --first-parent";
1041 if(mask& CGit::LOG_INFO_NO_MERGE )
1042 param += L" --no-merges";
1044 if(mask& CGit::LOG_INFO_FOLLOW)
1045 param += L" --follow";
1047 if(mask& CGit::LOG_INFO_SHOW_MERGEDFILE)
1048 param += L" -c";
1050 if(mask& CGit::LOG_INFO_FULL_DIFF)
1051 param += L" --full-diff";
1053 if(mask& CGit::LOG_INFO_SIMPILFY_BY_DECORATION)
1054 param += L" --simplify-by-decoration";
1056 if (Filter)
1058 if (Filter->m_NumberOfLogsScale >= CFilterData::SHOW_LAST_N_YEARS)
1060 CTime now = CTime::GetCurrentTime();
1061 CTime time = CTime(now.GetYear(), now.GetMonth(), now.GetDay(), 0, 0, 0);
1062 __time64_t substract = 86400;
1063 CString scale;
1064 switch (Filter->m_NumberOfLogsScale)
1066 case CFilterData::SHOW_LAST_N_YEARS:
1067 substract *= 365;
1068 break;
1069 case CFilterData::SHOW_LAST_N_MONTHS:
1070 substract *= 30;
1071 break;
1072 case CFilterData::SHOW_LAST_N_WEEKS:
1073 substract *= 7;
1074 break;
1076 Filter->m_From = (DWORD)time.GetTime() - (Filter->m_NumberOfLogs * substract);
1078 if (Filter->m_NumberOfLogsScale == CFilterData::SHOW_LAST_N_COMMITS)
1079 param.AppendFormat(L" -n%ld", Filter->m_NumberOfLogs);
1080 else if (Filter->m_NumberOfLogsScale >= CFilterData::SHOW_LAST_SEL_DATE && Filter->m_From > 0)
1081 param.AppendFormat(L" --max-age=%I64u", Filter->m_From);
1084 if( Filter && (Filter->m_To != -1))
1085 param.AppendFormat(L" --min-age=%I64u", Filter->m_To);
1087 bool isgrep = false;
1088 if( Filter && (!Filter->m_Author.IsEmpty()))
1090 param.AppendFormat(L" --author=\"%s\"", (LPCTSTR)Filter->m_Author);
1091 isgrep = true;
1094 if( Filter && (!Filter->m_Committer.IsEmpty()))
1096 param.AppendFormat(L" --committer=\"%s\"", (LPCTSTR)Filter->m_Author);
1097 isgrep = true;
1100 if( Filter && (!Filter->m_MessageFilter.IsEmpty()))
1102 param.AppendFormat(L" --grep=\"%s\"", (LPCTSTR)Filter->m_MessageFilter);
1103 isgrep = true;
1106 if(Filter && isgrep)
1108 if(!Filter->m_IsRegex)
1109 param += L" --fixed-strings";
1111 param += L" --regexp-ignore-case --extended-regexp";
1114 if (logOrderBy == LOG_ORDER_TOPOORDER || (mask & CGit::LOG_ORDER_TOPOORDER))
1115 param += L" --topo-order";
1116 else if (logOrderBy == LOG_ORDER_DATEORDER)
1117 param += L" --date-order";
1119 CString cmd;
1120 CString file;
1121 if (path)
1122 file.Format(L" \"%s\"", (LPCTSTR)path->GetGitPathString());
1123 // gitdll.dll:setup_revisions() only looks at args[1] and greater. To account for this, pass a dummy parameter in the 0th place
1124 cmd.Format(L"--ignore-this-parameter -z%s %s --parents --%s", (LPCTSTR)param, (LPCTSTR)range, (LPCTSTR)file);
1126 return cmd;
1128 #define BUFSIZE 512
1129 void GetTempPath(CString &path)
1131 TCHAR lpPathBuffer[BUFSIZE] = { 0 };
1132 DWORD dwRetVal;
1133 DWORD dwBufSize=BUFSIZE;
1134 dwRetVal = GetTortoiseGitTempPath(dwBufSize, // length of the buffer
1135 lpPathBuffer); // buffer for path
1136 if (dwRetVal > dwBufSize || (dwRetVal == 0))
1137 path.Empty();
1138 path.Format(L"%s", lpPathBuffer);
1140 CString GetTempFile()
1142 TCHAR lpPathBuffer[BUFSIZE] = { 0 };
1143 DWORD dwRetVal;
1144 DWORD dwBufSize=BUFSIZE;
1145 TCHAR szTempName[BUFSIZE] = { 0 };
1146 UINT uRetVal;
1148 dwRetVal = GetTortoiseGitTempPath(dwBufSize, // length of the buffer
1149 lpPathBuffer); // buffer for path
1150 if (dwRetVal > dwBufSize || (dwRetVal == 0))
1151 return L"";
1153 // Create a temporary file.
1154 uRetVal = GetTempFileName(lpPathBuffer, // directory for tmp files
1155 TEXT("Patch"), // temp file name prefix
1156 0, // create unique name
1157 szTempName); // buffer for name
1159 if (uRetVal == 0)
1160 return L"";
1162 return CString(szTempName);
1165 DWORD GetTortoiseGitTempPath(DWORD nBufferLength, LPTSTR lpBuffer)
1167 DWORD result = ::GetTempPath(nBufferLength, lpBuffer);
1168 if (result == 0) return 0;
1169 if (!lpBuffer || (result + 13 > nBufferLength))
1171 if (lpBuffer)
1172 lpBuffer[0] = '\0';
1173 return result + 13;
1176 wcscat_s(lpBuffer, nBufferLength, L"TortoiseGit\\");
1177 CreateDirectory(lpBuffer, nullptr);
1179 return result + 13;
1182 int CGit::RunLogFile(CString cmd, const CString &filename, CString *stdErr)
1184 PROCESS_INFORMATION pi;
1185 CAutoGeneralHandle hReadErr;
1186 if (RunAsync(cmd, &pi, nullptr, hReadErr.GetPointer(), &filename))
1187 return TGIT_GIT_ERROR_CREATE_PROCESS;
1189 CAutoGeneralHandle piThread(std::move(pi.hThread));
1190 CAutoGeneralHandle piProcess(std::move(pi.hProcess));
1192 BYTE_VECTOR stderrVector;
1193 CGitCall_ByteVector pcall(L"", nullptr, &stderrVector);
1194 ASYNCREADSTDERRTHREADARGS threadArguments;
1195 threadArguments.fileHandle = hReadErr;
1196 threadArguments.pcall = &pcall;
1197 CAutoGeneralHandle thread = CreateThread(nullptr, 0, AsyncReadStdErrThread, &threadArguments, 0, nullptr);
1199 if (thread)
1201 CAutoLocker lock(m_critSecThreadMap);
1202 m_AsyncReadStdErrThreadMap[GetCurrentThreadId()] = thread;
1205 WaitForSingleObject(pi.hProcess,INFINITE);
1207 if (thread)
1209 WaitForSingleObject(thread, INFINITE);
1211 CAutoLocker lock(m_critSecThreadMap);
1212 m_AsyncReadStdErrThreadMap.erase(GetCurrentThreadId());
1215 stderrVector.push_back(0);
1216 StringAppend(stdErr, stderrVector.data(), CP_UTF8);
1218 DWORD exitcode = 0;
1219 if (!GetExitCodeProcess(pi.hProcess, &exitcode))
1221 CString err = CFormatMessageWrapper();
1222 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": could not get exit code: %s\n", (LPCTSTR)err.Trim());
1223 return TGIT_GIT_ERROR_GET_EXIT_CODE;
1225 else
1226 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": process exited: %d\n", exitcode);
1228 return exitcode;
1231 CAutoRepository CGit::GetGitRepository() const
1233 return CAutoRepository(GetGitPathStringA(m_CurrentDir));
1236 int CGit::GetHash(git_repository * repo, CGitHash &hash, const CString& friendname, bool skipFastCheck /* = false */)
1238 ATLASSERT(repo);
1240 // no need to parse a ref if it's already a 40-byte hash
1241 if (!skipFastCheck && CGitHash::IsValidSHA1(friendname))
1243 hash = CGitHash(friendname);
1244 return 0;
1247 int isHeadOrphan = git_repository_head_unborn(repo);
1248 if (isHeadOrphan != 0)
1250 hash.Empty();
1251 if (isHeadOrphan == 1)
1252 return 0;
1253 else
1254 return -1;
1257 CAutoObject gitObject;
1258 if (git_revparse_single(gitObject.GetPointer(), repo, CUnicodeUtils::GetUTF8(friendname)))
1259 return -1;
1261 const git_oid * oid = git_object_id(gitObject);
1262 if (!oid)
1263 return -1;
1265 hash = CGitHash(oid->id);
1267 return 0;
1270 int CGit::GetHash(CGitHash &hash, const CString& friendname)
1272 // no need to parse a ref if it's already a 40-byte hash
1273 if (CGitHash::IsValidSHA1(friendname))
1275 hash = CGitHash(friendname);
1276 return 0;
1279 if (m_IsUseLibGit2)
1281 CAutoRepository repo(GetGitRepository());
1282 if (!repo)
1283 return -1;
1285 return GetHash(repo, hash, friendname, true);
1287 else
1289 CString branch = FixBranchName(friendname);
1290 if (friendname == L"FETCH_HEAD" && branch.IsEmpty())
1291 branch = friendname;
1292 CString cmd;
1293 cmd.Format(L"git.exe rev-parse %s", (LPCTSTR)branch);
1294 gitLastErr.Empty();
1295 int ret = Run(cmd, &gitLastErr, nullptr, CP_UTF8);
1296 hash = CGitHash(gitLastErr.Trim());
1297 if (ret == 0)
1298 gitLastErr.Empty();
1299 else if (friendname == L"HEAD") // special check for unborn branch
1301 CString currentbranch;
1302 if (GetCurrentBranchFromFile(m_CurrentDir, currentbranch))
1303 return -1;
1304 gitLastErr.Empty();
1305 return 0;
1307 return ret;
1311 int CGit::GetInitAddList(CTGitPathList &outputlist)
1313 BYTE_VECTOR cmdout;
1315 outputlist.Clear();
1316 if (Run(L"git.exe ls-files -s -t -z", &cmdout))
1317 return -1;
1319 if (outputlist.ParserFromLsFile(cmdout))
1320 return -1;
1321 for(int i = 0; i < outputlist.GetCount(); ++i)
1322 const_cast<CTGitPath&>(outputlist[i]).m_Action = CTGitPath::LOGACTIONS_ADDED;
1324 return 0;
1326 int CGit::GetCommitDiffList(const CString &rev1, const CString &rev2, CTGitPathList &outputlist, bool ignoreSpaceAtEol, bool ignoreSpaceChange, bool ignoreAllSpace , bool ignoreBlankLines)
1328 CString cmd;
1329 CString ignore;
1330 if (ignoreSpaceAtEol)
1331 ignore += L" --ignore-space-at-eol";
1332 if (ignoreSpaceChange)
1333 ignore += L" --ignore-space-change";
1334 if (ignoreAllSpace)
1335 ignore += L" --ignore-all-space";
1336 if (ignoreBlankLines)
1337 ignore += L" --ignore-blank-lines";
1339 if(rev1 == GIT_REV_ZERO || rev2 == GIT_REV_ZERO)
1341 if(rev1 == GIT_REV_ZERO)
1342 cmd.Format(L"git.exe diff -r --raw -C -M --numstat -z %s %s --", (LPCTSTR)ignore, (LPCTSTR)rev2);
1343 else
1344 cmd.Format(L"git.exe diff -r -R --raw -C -M --numstat -z %s %s --", (LPCTSTR)ignore, (LPCTSTR)rev1);
1346 else
1347 cmd.Format(L"git.exe diff-tree -r --raw -C -M --numstat -z %s %s %s --", (LPCTSTR)ignore, (LPCTSTR)rev2, (LPCTSTR)rev1);
1349 BYTE_VECTOR out;
1350 if (Run(cmd, &out))
1351 return -1;
1353 return outputlist.ParserFromLog(out);
1356 int addto_list_each_ref_fn(const char *refname, const unsigned char * /*sha1*/, int /*flags*/, void *cb_data)
1358 STRING_VECTOR *list = (STRING_VECTOR*)cb_data;
1359 list->push_back(CUnicodeUtils::GetUnicode(refname));
1360 return 0;
1363 int CGit::GetTagList(STRING_VECTOR &list)
1365 size_t prevCount = list.size();
1366 if (this->m_IsUseLibGit2)
1368 CAutoRepository repo(GetGitRepository());
1369 if (!repo)
1370 return -1;
1372 CAutoStrArray tag_names;
1374 if (git_tag_list(tag_names, repo))
1375 return -1;
1377 for (size_t i = 0; i < tag_names->count; ++i)
1379 CStringA tagName(tag_names->strings[i]);
1380 list.push_back(CUnicodeUtils::GetUnicode(tagName));
1383 std::sort(list.begin() + prevCount, list.end(), g_bSortTagsReversed ? LogicalCompareReversedPredicate : LogicalComparePredicate);
1385 return 0;
1387 else
1389 int ret = Run(L"git.exe tag -l", [&](const CStringA& lineA)
1391 if (lineA.IsEmpty())
1392 return;
1393 list.push_back(CUnicodeUtils::GetUnicode(lineA));
1395 if (!ret)
1396 std::sort(list.begin() + prevCount, list.end(), g_bSortTagsReversed ? LogicalCompareReversedPredicate : LogicalComparePredicate);
1397 else if (ret == 1 && IsInitRepos())
1398 return 0;
1399 return ret;
1403 CString CGit::GetGitLastErr(const CString& msg)
1405 if (this->m_IsUseLibGit2)
1406 return GetLibGit2LastErr(msg);
1407 else if (gitLastErr.IsEmpty())
1408 return msg + L"\nUnknown git.exe error.";
1409 else
1411 CString lastError = gitLastErr;
1412 gitLastErr.Empty();
1413 return msg + L'\n' + lastError;
1417 CString CGit::GetGitLastErr(const CString& msg, LIBGIT2_CMD cmd)
1419 if (UsingLibGit2(cmd))
1420 return GetLibGit2LastErr(msg);
1421 else if (gitLastErr.IsEmpty())
1422 return msg + L"\nUnknown git.exe error.";
1423 else
1425 CString lastError = gitLastErr;
1426 gitLastErr.Empty();
1427 return msg + L'\n' + lastError;
1431 CString CGit::GetLibGit2LastErr()
1433 const git_error *libgit2err = giterr_last();
1434 if (libgit2err)
1436 CString lastError = CUnicodeUtils::GetUnicode(CStringA(libgit2err->message));
1437 giterr_clear();
1438 return L"libgit2 returned: " + lastError;
1440 else
1441 return L"An error occoured in libgit2, but no message is available.";
1444 CString CGit::GetLibGit2LastErr(const CString& msg)
1446 if (!msg.IsEmpty())
1447 return msg + L'\n' + GetLibGit2LastErr();
1448 return GetLibGit2LastErr();
1451 CString CGit::FixBranchName_Mod(CString& branchName)
1453 if (branchName == L"FETCH_HEAD")
1454 branchName = DerefFetchHead();
1455 return branchName;
1458 CString CGit::FixBranchName(const CString& branchName)
1460 CString tempBranchName = branchName;
1461 FixBranchName_Mod(tempBranchName);
1462 return tempBranchName;
1465 bool CGit::IsBranchTagNameUnique(const CString& name)
1467 if (m_IsUseLibGit2)
1469 CAutoRepository repo(GetGitRepository());
1470 if (!repo)
1471 return true; // TODO: optimize error reporting
1473 CAutoReference tagRef;
1474 if (git_reference_lookup(tagRef.GetPointer(), repo, CUnicodeUtils::GetUTF8(L"refs/tags/" + name)))
1475 return true;
1477 CAutoReference branchRef;
1478 if (git_reference_lookup(branchRef.GetPointer(), repo, CUnicodeUtils::GetUTF8(L"refs/heads/" + name)))
1479 return true;
1481 return false;
1483 // else
1484 CString cmd;
1485 cmd.Format(L"git.exe show-ref --tags --heads refs/heads/%s refs/tags/%s", (LPCTSTR)name, (LPCTSTR)name);
1487 int refCnt = 0;
1488 Run(cmd, [&](const CStringA& lineA)
1490 if (lineA.IsEmpty())
1491 return;
1492 ++refCnt;
1495 return (refCnt <= 1);
1498 bool CGit::IsLocalBranch(const CString& shortName)
1500 STRING_VECTOR list;
1501 GetBranchList(list, nullptr, CGit::BRANCH_LOCAL);
1502 return std::find(list.cbegin(), list.cend(), shortName) != list.cend();
1505 bool CGit::BranchTagExists(const CString& name, bool isBranch /*= true*/)
1507 if (m_IsUseLibGit2)
1509 CAutoRepository repo(GetGitRepository());
1510 if (!repo)
1511 return false; // TODO: optimize error reporting
1513 CString prefix;
1514 if (isBranch)
1515 prefix = L"refs/heads/";
1516 else
1517 prefix = L"refs/tags/";
1519 CAutoReference ref;
1520 if (git_reference_lookup(ref.GetPointer(), repo, CUnicodeUtils::GetUTF8(prefix + name)))
1521 return false;
1523 return true;
1525 // else
1526 CString cmd, output;
1528 cmd = L"git.exe show-ref ";
1529 if (isBranch)
1530 cmd += L"--heads ";
1531 else
1532 cmd += L"--tags ";
1534 cmd += L"refs/heads/" + name;
1535 cmd += L" refs/tags/" + name;
1537 int ret = Run(cmd, &output, nullptr, CP_UTF8);
1538 if (!ret)
1540 if (!output.IsEmpty())
1541 return true;
1544 return false;
1547 CString CGit::DerefFetchHead()
1549 CString dotGitPath;
1550 GitAdminDir::GetWorktreeAdminDirPath(m_CurrentDir, dotGitPath);
1551 std::ifstream fetchHeadFile((dotGitPath + L"FETCH_HEAD").GetString(), std::ios::in | std::ios::binary);
1552 int forMergeLineCount = 0;
1553 std::string line;
1554 std::string hashToReturn;
1555 while(getline(fetchHeadFile, line))
1557 //Tokenize this line
1558 std::string::size_type prevPos = 0;
1559 std::string::size_type pos = line.find('\t');
1560 if(pos == std::string::npos) continue; //invalid line
1562 std::string hash = line.substr(0, pos);
1563 ++pos; prevPos = pos; pos = line.find('\t', pos); if(pos == std::string::npos) continue;
1565 bool forMerge = pos == prevPos;
1566 ++pos; prevPos = pos; pos = line.size(); if(pos == std::string::npos) continue;
1568 //std::string remoteBranch = line.substr(prevPos, pos - prevPos);
1570 //Process this line
1571 if(forMerge)
1573 hashToReturn = hash;
1574 ++forMergeLineCount;
1575 if(forMergeLineCount > 1)
1576 return L""; //More then 1 branch to merge found. Octopus merge needed. Cannot pick single ref from FETCH_HEAD
1580 return CUnicodeUtils::GetUnicode(hashToReturn.c_str());
1583 int CGit::GetBranchList(STRING_VECTOR &list, int *current, BRANCH_TYPE type, bool skipCurrent /*false*/)
1585 size_t prevCount = list.size();
1586 int ret = 0;
1587 CString cur;
1588 bool headIsDetached = false;
1589 if (m_IsUseLibGit2)
1591 CAutoRepository repo(GetGitRepository());
1592 if (!repo)
1593 return -1;
1595 if (git_repository_head_detached(repo) == 1)
1596 headIsDetached = true;
1598 if ((type & (BRANCH_LOCAL | BRANCH_REMOTE)) != 0)
1600 git_branch_t flags = GIT_BRANCH_LOCAL;
1601 if ((type & BRANCH_LOCAL) && (type & BRANCH_REMOTE))
1602 flags = GIT_BRANCH_ALL;
1603 else if (type & BRANCH_REMOTE)
1604 flags = GIT_BRANCH_REMOTE;
1606 CAutoBranchIterator it;
1607 if (git_branch_iterator_new(it.GetPointer(), repo, flags))
1608 return -1;
1610 CAutoReference ref;
1611 git_branch_t branchType;
1612 while (git_branch_next(ref.GetPointer(), &branchType, it) == 0)
1614 const char * name = nullptr;
1615 if (git_branch_name(&name, ref))
1616 continue;
1618 CString branchname = CUnicodeUtils::GetUnicode(name);
1619 if (branchType & GIT_BRANCH_REMOTE)
1620 list.push_back(L"remotes/" + branchname);
1621 else
1623 if (git_branch_is_head(ref))
1625 if (skipCurrent)
1626 continue;
1627 cur = branchname;
1629 list.push_back(branchname);
1634 else
1636 CString cmd = L"git.exe branch --no-color";
1638 if ((type & BRANCH_ALL) == BRANCH_ALL)
1639 cmd += L" -a";
1640 else if (type & BRANCH_REMOTE)
1641 cmd += L" -r";
1643 ret = Run(cmd, [&](CStringA lineA)
1645 lineA.Trim(" \r\n\t");
1646 if (lineA.IsEmpty())
1647 return;
1648 if (lineA.Find(" -> ") >= 0)
1649 return; // skip something like: refs/origin/HEAD -> refs/origin/master
1651 CString branch = CUnicodeUtils::GetUnicode(lineA);
1652 if (lineA[0] == '*')
1654 if (skipCurrent)
1655 return;
1656 branch = branch.Mid(2);
1657 cur = branch;
1659 // check whether HEAD is detached
1660 CString currentHead;
1661 if (branch[0] == L'(' && GetCurrentBranchFromFile(m_CurrentDir, currentHead) == 1)
1663 headIsDetached = true;
1664 return;
1667 if ((type & BRANCH_REMOTE) != 0 && (type & BRANCH_LOCAL) == 0)
1668 branch = L"remotes/" + branch;
1669 list.push_back(branch);
1671 if (ret == 1 && IsInitRepos())
1672 return 0;
1675 if(type & BRANCH_FETCH_HEAD && !DerefFetchHead().IsEmpty())
1676 list.push_back(L"FETCH_HEAD");
1678 std::sort(list.begin() + prevCount, list.end(), LogicalCompareBranchesPredicate);
1680 if (current && !headIsDetached && !skipCurrent)
1682 for (unsigned int i = 0; i < list.size(); ++i)
1684 if (list[i] == cur)
1686 *current = i;
1687 break;
1692 return ret;
1695 int CGit::GetRefsCommitIsOn(STRING_VECTOR& list, const CGitHash& hash, bool includeTags, bool includeBranches, BRANCH_TYPE type)
1697 if (!includeTags && !includeBranches || hash.IsEmpty())
1698 return 0;
1700 size_t prevCount = list.size();
1701 if (m_IsUseLibGit2)
1703 CAutoRepository repo(GetGitRepository());
1704 if (!repo)
1705 return -1;
1707 CAutoReferenceIterator it;
1708 if (git_reference_iterator_new(it.GetPointer(), repo))
1709 return -1;
1711 auto checkDescendent = [&list, &hash, &repo](const git_oid* oid, const git_reference* ref) {
1712 if (!oid)
1713 return;
1714 if (git_oid_equal(oid, (const git_oid*)hash.m_hash) || git_graph_descendant_of(repo, oid, (const git_oid*)hash.m_hash) == 1)
1716 const char* name = git_reference_name(ref);
1717 if (!name)
1718 return;
1720 list.push_back(CUnicodeUtils::GetUnicode(name));
1724 CAutoReference ref;
1725 while (git_reference_next(ref.GetPointer(), it) == 0)
1727 if (git_reference_is_tag(ref))
1729 if (!includeTags)
1730 continue;
1732 CAutoTag tag;
1733 if (git_tag_lookup(tag.GetPointer(), repo, git_reference_target(ref)) == 0)
1735 CAutoObject obj;
1736 if (git_tag_peel(obj.GetPointer(), tag) < 0)
1737 continue;
1738 checkDescendent(git_object_id(obj), ref);
1739 continue;
1742 else if (git_reference_is_remote(ref))
1744 if (!includeBranches || !(type & BRANCH_REMOTE))
1745 continue;
1747 else if (git_reference_is_branch(ref))
1749 if (!includeBranches || !(type & GIT_BRANCH_LOCAL))
1750 continue;
1752 else
1753 continue;
1755 if (git_reference_type(ref) == GIT_REF_SYMBOLIC)
1757 CAutoReference peeledRef;
1758 if (git_reference_resolve(peeledRef.GetPointer(), ref) < 0)
1759 continue;
1761 checkDescendent(git_reference_target(peeledRef), ref);
1762 continue;
1765 checkDescendent(git_reference_target(ref), ref);
1768 else
1770 if (includeBranches)
1772 CString cmd = L"git.exe branch --no-color";
1773 if ((type & BRANCH_ALL) == BRANCH_ALL)
1774 cmd += L" -a";
1775 else if (type & BRANCH_REMOTE)
1776 cmd += L" -r";
1777 cmd += L" --contains " + hash.ToString();
1779 if (Run(cmd, [&](CStringA lineA)
1781 lineA.Trim(" \r\n\t");
1782 if (lineA.IsEmpty())
1783 return;
1784 if (lineA.Find(" -> ") >= 0) // normalize symbolic refs: "refs/origin/HEAD -> refs/origin/master" to "refs/origin/HEAD"
1785 lineA.Truncate(lineA.Find(" -> "));
1787 CString branch = CUnicodeUtils::GetUnicode(lineA);
1788 if (lineA[0] == '*')
1790 branch = branch.Mid(2);
1791 CString currentHead;
1792 if (branch[0] == L'(' && GetCurrentBranchFromFile(m_CurrentDir, currentHead) == 1)
1793 return;
1796 if ((type & BRANCH_REMOTE) != 0 && (type & BRANCH_LOCAL) == 0)
1797 branch = L"refs/remotes/" + branch;
1798 else if (CStringUtils::StartsWith(branch, L"remotes/"))
1799 branch = L"refs/" + branch;
1800 else
1801 branch = L"refs/heads/" + branch;
1802 list.push_back(branch);
1804 return -1;
1807 if (includeTags)
1809 CString cmd = L"git.exe tag --contains " + hash.ToString();
1810 if (Run(cmd, [&list](CStringA lineA)
1812 if (lineA.IsEmpty())
1813 return;
1814 list.push_back(L"refs/tags/" + CUnicodeUtils::GetUnicode(lineA));
1816 return -1;
1820 std::sort(list.begin() + prevCount, list.end(), LogicalCompareBranchesPredicate);
1821 list.erase(std::unique(list.begin() + prevCount, list.end()), list.end());
1823 return 0;
1826 int CGit::GetRemoteList(STRING_VECTOR &list)
1828 size_t prevCount = list.size();
1829 if (this->m_IsUseLibGit2)
1831 CAutoRepository repo(GetGitRepository());
1832 if (!repo)
1833 return -1;
1835 CAutoStrArray remotes;
1836 if (git_remote_list(remotes, repo))
1837 return -1;
1839 for (size_t i = 0; i < remotes->count; ++i)
1841 CStringA remote(remotes->strings[i]);
1842 list.push_back(CUnicodeUtils::GetUnicode(remote));
1845 std::sort(list.begin() + prevCount, list.end(), LogicalComparePredicate);
1847 return 0;
1850 return Run(L"git.exe remote", [&](const CStringA& lineA)
1852 if (lineA.IsEmpty())
1853 return;
1854 list.push_back(CUnicodeUtils::GetUnicode(lineA));
1858 int CGit::GetRemoteTags(const CString& remote, REF_VECTOR& list)
1860 size_t prevCount = list.size();
1861 if (UsingLibGit2(GIT_CMD_FETCH))
1863 CAutoRepository repo(GetGitRepository());
1864 if (!repo)
1865 return -1;
1867 CStringA remoteA = CUnicodeUtils::GetUTF8(remote);
1868 CAutoRemote gitremote;
1869 // first try with a named remote (e.g. "origin")
1870 if (git_remote_lookup(gitremote.GetPointer(), repo, remoteA) < 0)
1872 // retry with repository located at a specific url
1873 if (git_remote_create_anonymous(gitremote.GetPointer(), repo, remoteA) < 0)
1874 return -1;
1877 git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
1878 callbacks.credentials = g_Git2CredCallback;
1879 callbacks.certificate_check = g_Git2CheckCertificateCallback;
1880 git_proxy_options proxy = GIT_PROXY_OPTIONS_INIT;
1881 proxy.type = GIT_PROXY_AUTO;
1882 if (git_remote_connect(gitremote, GIT_DIRECTION_FETCH, &callbacks, &proxy, nullptr) < 0)
1883 return -1;
1885 const git_remote_head** heads = nullptr;
1886 size_t size = 0;
1887 if (git_remote_ls(&heads, &size, gitremote) < 0)
1888 return -1;
1890 for (size_t i = 0; i < size; i++)
1892 CString ref = CUnicodeUtils::GetUnicode(heads[i]->name);
1893 CString shortname;
1894 if (!GetShortName(ref, shortname, L"refs/tags/"))
1895 continue;
1896 CGitHash hash;
1897 git_oid_cpy((git_oid*)hash.m_hash, &heads[i]->oid);
1898 list.emplace_back(TGitRef{ shortname, hash });
1900 std::sort(list.begin() + prevCount, list.end(), g_bSortTagsReversed ? LogicalCompareReversedPredicate : LogicalComparePredicate);
1901 return 0;
1904 CString cmd;
1905 cmd.Format(L"git.exe ls-remote -t \"%s\"", (LPCTSTR)remote);
1906 gitLastErr = cmd + L'\n';
1907 if (Run(cmd, [&](CStringA lineA)
1909 CGitHash hash;
1910 hash.ConvertFromStrA(lineA.Mid(0, GIT_HASH_SIZE * 2));
1911 lineA = lineA.Mid(51); // sha1, tab + refs/tags/
1912 if (!lineA.IsEmpty())
1913 list.emplace_back(TGitRef{ CUnicodeUtils::GetUnicode(lineA), hash });
1914 }, &gitLastErr))
1915 return -1;
1916 std::sort(list.begin() + prevCount, list.end(), g_bSortTagsReversed ? LogicalCompareReversedPredicate : LogicalComparePredicate);
1917 return 0;
1920 int CGit::DeleteRemoteRefs(const CString& sRemote, const STRING_VECTOR& list)
1922 if (UsingLibGit2(GIT_CMD_PUSH))
1924 CAutoRepository repo(GetGitRepository());
1925 if (!repo)
1926 return -1;
1928 CStringA remoteA = CUnicodeUtils::GetUTF8(sRemote);
1929 CAutoRemote remote;
1930 if (git_remote_lookup(remote.GetPointer(), repo, remoteA) < 0)
1931 return -1;
1933 git_push_options pushOpts = GIT_PUSH_OPTIONS_INIT;
1934 git_remote_callbacks& callbacks = pushOpts.callbacks;
1935 callbacks.credentials = g_Git2CredCallback;
1936 callbacks.certificate_check = g_Git2CheckCertificateCallback;
1937 std::vector<CStringA> refspecs;
1938 for (const auto& ref : list)
1939 refspecs.push_back(CUnicodeUtils::GetUTF8(L":" + ref));
1941 std::vector<char*> vc;
1942 vc.reserve(refspecs.size());
1943 std::transform(refspecs.begin(), refspecs.end(), std::back_inserter(vc), [](CStringA& s) -> char* { return s.GetBuffer(); });
1944 git_strarray specs = { vc.data(), vc.size() };
1946 if (git_remote_push(remote, &specs, &pushOpts) < 0)
1947 return -1;
1949 else
1951 CMassiveGitTaskBase mgtPush(L"push " + sRemote, FALSE);
1952 for (const auto& ref : list)
1953 mgtPush.AddFile(L':' + ref);
1955 BOOL cancel = FALSE;
1956 mgtPush.Execute(cancel);
1959 return 0;
1962 int libgit2_addto_list_each_ref_fn(git_reference *ref, void *payload)
1964 STRING_VECTOR *list = (STRING_VECTOR*)payload;
1965 list->push_back(CUnicodeUtils::GetUnicode(git_reference_name(ref)));
1966 return 0;
1969 int CGit::GetRefList(STRING_VECTOR &list)
1971 size_t prevCount = list.size();
1972 if (this->m_IsUseLibGit2)
1974 CAutoRepository repo(GetGitRepository());
1975 if (!repo)
1976 return -1;
1978 if (git_reference_foreach(repo, libgit2_addto_list_each_ref_fn, &list))
1979 return -1;
1981 std::sort(list.begin() + prevCount, list.end(), LogicalComparePredicate);
1983 return 0;
1986 int ret = Run(L"git.exe show-ref -d", [&](const CStringA& lineA)
1988 int start = lineA.Find(L' ');
1989 ASSERT(start == 2 * GIT_HASH_SIZE);
1990 if (start <= 0)
1991 return;
1993 CString name = CUnicodeUtils::GetUnicode(lineA.Mid(start + 1));
1994 if (list.empty() || name != *list.crbegin() + L"^{}")
1995 list.push_back(name);
1997 if (!ret)
1998 std::sort(list.begin() + prevCount, list.end(), LogicalComparePredicate);
1999 else if (ret == 1 && IsInitRepos())
2000 return 0;
2001 return ret;
2004 typedef struct map_each_ref_payload {
2005 git_repository * repo;
2006 MAP_HASH_NAME * map;
2007 } map_each_ref_payload;
2009 int libgit2_addto_map_each_ref_fn(git_reference *ref, void *payload)
2011 map_each_ref_payload *payloadContent = (map_each_ref_payload*)payload;
2013 CString str = CUnicodeUtils::GetUnicode(git_reference_name(ref));
2015 CAutoObject gitObject;
2016 if (git_revparse_single(gitObject.GetPointer(), payloadContent->repo, git_reference_name(ref)))
2017 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
2019 if (git_object_type(gitObject) == GIT_OBJ_TAG)
2021 str += L"^{}"; // deref tag
2022 CAutoObject derefedTag;
2023 if (git_object_peel(derefedTag.GetPointer(), gitObject, GIT_OBJ_ANY))
2024 return 1;
2025 gitObject.Swap(derefedTag);
2028 const git_oid * oid = git_object_id(gitObject);
2029 if (!oid)
2030 return 1;
2032 CGitHash hash(oid->id);
2033 (*payloadContent->map)[hash].push_back(str);
2035 return 0;
2037 int CGit::GetMapHashToFriendName(git_repository* repo, MAP_HASH_NAME &map)
2039 ATLASSERT(repo);
2041 map_each_ref_payload payloadContent = { repo, &map };
2043 if (git_reference_foreach(repo, libgit2_addto_map_each_ref_fn, &payloadContent))
2044 return -1;
2046 for (auto it = map.begin(); it != map.end(); ++it)
2048 std::sort(it->second.begin(), it->second.end());
2051 return 0;
2054 int CGit::GetMapHashToFriendName(MAP_HASH_NAME &map)
2056 if (this->m_IsUseLibGit2)
2058 CAutoRepository repo(GetGitRepository());
2059 if (!repo)
2060 return -1;
2062 return GetMapHashToFriendName(repo, map);
2065 int ret = Run(L"git.exe show-ref -d", [&](const CStringA& lineA)
2067 int start = lineA.Find(L' ');
2068 ASSERT(start == 2 * GIT_HASH_SIZE);
2069 if (start <= 0)
2070 return;
2072 CGitHash hash;
2073 hash.ConvertFromStrA(lineA);
2074 map[hash].push_back(CUnicodeUtils::GetUnicode(lineA.Mid(start + 1)));
2077 if (ret == 1 && IsInitRepos())
2078 return 0;
2079 return ret;
2082 int CGit::GuessRefForHash(CString& ref, const CGitHash& hash)
2084 MAP_HASH_NAME map;
2085 if (GetMapHashToFriendName(map))
2086 return -1;
2088 auto it = map.find(hash);
2089 if (it == map.cend())
2091 ref = hash.ToString();
2092 return 0;
2095 const auto& reflist = it->second;
2096 for (const auto& reftype : { L"refs/heads/", L"refs/remotes/", L"refs/tags/" })
2098 auto found = std::find_if(reflist.cbegin(), reflist.cend(), [&reftype](const auto& ref) { return CStringUtils::StartsWith(ref, reftype); });
2099 if (found == reflist.cend())
2100 continue;
2102 GetShortName(*found, ref, reftype);
2103 return 0;
2106 ref = hash.ToString();
2107 return 0;
2110 int CGit::GetBranchDescriptions(MAP_STRING_STRING& map)
2112 CAutoConfig config(true);
2113 if (git_config_add_file_ondisk(config, CGit::GetGitPathStringA(GetGitLocalConfig()), GIT_CONFIG_LEVEL_LOCAL, nullptr, FALSE) < 0)
2114 return -1;
2115 return git_config_foreach_match(config, "^branch\\..*\\.description$", [](const git_config_entry* entry, void* data)
2117 MAP_STRING_STRING* descriptions = (MAP_STRING_STRING*)data;
2118 CString key = CUnicodeUtils::GetUnicode(entry->name);
2119 key = key.Mid(7, key.GetLength() - 7 - 12); // 7: branch., 12: .description
2120 descriptions->insert(std::make_pair(key, CUnicodeUtils::GetUnicode(entry->value)));
2121 return 0;
2122 }, &map);
2125 static void SetLibGit2SearchPath(int level, const CString &value)
2127 CStringA valueA = CUnicodeUtils::GetMulti(value, CP_UTF8);
2128 git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, level, (LPCSTR)valueA);
2131 static void SetLibGit2TemplatePath(const CString &value)
2133 CStringA valueA = CUnicodeUtils::GetMulti(value, CP_UTF8);
2134 git_libgit2_opts(GIT_OPT_SET_TEMPLATE_PATH, (LPCSTR)valueA);
2137 int CGit::FindAndSetGitExePath(BOOL bFallback)
2139 CRegString msysdir = CRegString(REG_MSYSGIT_PATH, L"", FALSE);
2140 CString str = msysdir;
2141 if (!str.IsEmpty() && PathFileExists(str + L"\\git.exe"))
2143 CGit::ms_LastMsysGitDir = str;
2144 return TRUE;
2147 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": git.exe not exists: %s\n", (LPCTSTR)CGit::ms_LastMsysGitDir);
2148 if (!bFallback)
2149 return FALSE;
2151 // first, search PATH if git/bin directory is already present
2152 if (FindGitPath())
2154 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": FindGitPath() => %s\n", (LPCTSTR)CGit::ms_LastMsysGitDir);
2155 msysdir = CGit::ms_LastMsysGitDir;
2156 msysdir.write();
2157 return TRUE;
2160 if (FindGitForWindows(str))
2162 msysdir = str;
2163 CGit::ms_LastMsysGitDir = str;
2164 msysdir.write();
2165 return TRUE;
2168 return FALSE;
2171 BOOL CGit::CheckMsysGitDir(BOOL bFallback)
2173 if (m_bInitialized)
2174 return TRUE;
2176 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": CheckMsysGitDir(%d)\n", bFallback);
2177 this->m_Environment.clear();
2178 m_Environment.CopyProcessEnvironment();
2180 // 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,
2181 // because MSys2 changed the default to "ASCII". SO, make sure we have a proper default set
2182 if (m_Environment.GetEnv(L"LC_ALL").IsEmpty())
2183 m_Environment.SetEnv(L"LC_ALL", L"C");
2185 // set HOME if not set already
2186 size_t homesize;
2187 _wgetenv_s(&homesize, nullptr, 0, L"HOME");
2188 if (!homesize)
2189 m_Environment.SetEnv(L"HOME", GetHomeDirectory());
2191 //setup ssh client
2192 CString sshclient = CRegString(L"Software\\TortoiseGit\\SSH");
2193 if (sshclient.IsEmpty())
2194 sshclient = CRegString(L"Software\\TortoiseGit\\SSH", L"", FALSE, HKEY_LOCAL_MACHINE);
2196 if(!sshclient.IsEmpty())
2198 m_Environment.SetEnv(L"GIT_SSH", sshclient);
2199 m_Environment.SetEnv(L"SVN_SSH", sshclient);
2201 else
2203 TCHAR sPlink[MAX_PATH] = {0};
2204 GetModuleFileName(nullptr, sPlink, _countof(sPlink));
2205 LPTSTR ptr = wcsrchr(sPlink, L'\\');
2206 if (ptr) {
2207 wcscpy_s(ptr + 1, _countof(sPlink) - (ptr - sPlink + 1), L"TortoiseGitPlink.exe");
2208 m_Environment.SetEnv(L"GIT_SSH", sPlink);
2209 m_Environment.SetEnv(L"SVN_SSH", sPlink);
2214 TCHAR sAskPass[MAX_PATH] = {0};
2215 GetModuleFileName(nullptr, sAskPass, _countof(sAskPass));
2216 LPTSTR ptr = wcsrchr(sAskPass, L'\\');
2217 if (ptr)
2219 wcscpy_s(ptr + 1, _countof(sAskPass) - (ptr - sAskPass + 1), L"SshAskPass.exe");
2220 m_Environment.SetEnv(L"DISPLAY",L":9999");
2221 m_Environment.SetEnv(L"SSH_ASKPASS",sAskPass);
2222 m_Environment.SetEnv(L"GIT_ASKPASS",sAskPass);
2223 m_Environment.SetEnv(L"GIT_ASK_YESNO", sAskPass);
2227 if (!FindAndSetGitExePath(bFallback))
2228 return FALSE;
2230 CString msysGitDir;
2231 PathCanonicalize(CStrBuf(msysGitDir, MAX_PATH), CGit::ms_LastMsysGitDir + L"\\..\\");
2232 static const CString prefixes[] = { L"mingw64\\etc", L"mingw32\\etc", L"etc" };
2233 static const int prefixes_len[] = { 8, 8, 0 };
2234 for (int i = 0; i < _countof(prefixes); ++i)
2236 #ifndef _WIN64
2237 if (i == 0)
2238 continue;
2239 #endif
2240 if (PathIsDirectory(msysGitDir + prefixes[i])) {
2241 msysGitDir += prefixes[i].Left(prefixes_len[i]);
2242 break;
2245 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
2246 PathCanonicalize(CStrBuf(msysGitDir, MAX_PATH), CGit::ms_LastMsysGitDir + L"\\..\\..");
2247 CGit::ms_MsysGitRootDir = msysGitDir;
2249 if ((CString)CRegString(REG_SYSTEM_GITCONFIGPATH, L"", FALSE) != g_Git.GetGitSystemConfig())
2250 CRegString(REG_SYSTEM_GITCONFIGPATH, L"", FALSE) = g_Git.GetGitSystemConfig();
2252 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": ms_LastMsysGitDir = %s\n", (LPCTSTR)CGit::ms_LastMsysGitDir);
2253 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": ms_MsysGitRootDir = %s\n", (LPCTSTR)CGit::ms_MsysGitRootDir);
2254 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": System config = %s\n", (LPCTSTR)g_Git.GetGitSystemConfig());
2255 if (!ms_bCygwinGit && !ms_bMsys2Git)
2257 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": ProgramData config = %s\n", (LPCTSTR)g_Git.GetGitProgramDataConfig());
2258 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_PROGRAMDATA, CTGitPath(g_Git.GetGitProgramDataConfig()).GetContainingDirectory().GetWinPathString());
2260 else
2261 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_PROGRAMDATA, CTGitPath(g_Git.GetGitSystemConfig()).GetContainingDirectory().GetWinPathString());
2263 // Configure libgit2 search paths
2264 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_SYSTEM, CTGitPath(g_Git.GetGitSystemConfig()).GetContainingDirectory().GetWinPathString());
2265 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_GLOBAL, g_Git.GetHomeDirectory());
2266 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_XDG, g_Git.GetGitGlobalXDGConfigPath());
2267 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 };
2268 git_transport_register("ssh", git_transport_smart, &ssh_wintunnel_subtransport_definition);
2269 git_transport_register("ssh+git", git_transport_smart, &ssh_wintunnel_subtransport_definition);
2270 git_transport_register("git+ssh", git_transport_smart, &ssh_wintunnel_subtransport_definition);
2271 git_libgit2_opts(GIT_OPT_SET_USER_AGENT, "TortoiseGit libgit2");
2272 if (!(ms_bCygwinGit || ms_bMsys2Git))
2273 SetLibGit2TemplatePath(CGit::ms_MsysGitRootDir + L"share\\git-core\\templates");
2274 else
2275 SetLibGit2TemplatePath(CGit::ms_MsysGitRootDir + L"usr\\share\\git-core\\templates");
2277 m_Environment.AddToPath(CGit::ms_LastMsysGitDir);
2278 m_Environment.AddToPath((CString)CRegString(REG_MSYSGIT_EXTRA_PATH, L"", FALSE));
2280 #if !defined(TGITCACHE) && !defined(TORTOISESHELL)
2281 // register filter only once
2282 if (!git_filter_lookup("filter"))
2284 CString sh;
2285 for (const CString& binDirPrefix : { L"\\..\\usr\\bin", L"\\..\\bin", L"" })
2287 CString possibleShExe = CGit::ms_LastMsysGitDir + binDirPrefix + L"\\sh.exe";
2288 if (PathFileExists(possibleShExe))
2290 CString temp;
2291 PathCanonicalize(CStrBuf(temp, MAX_PATH), possibleShExe);
2292 sh.Format(L"\"%s\"", (LPCTSTR)temp);
2293 // we need to put the usr\bin folder on the path for Git for Windows based on msys2
2294 m_Environment.AddToPath(temp.Left(temp.GetLength() - (int)wcslen(L"\\sh.exe")));
2295 break;
2299 // Add %GIT_EXEC_PATH% to %PATH% when launching libgit2 filter executable
2300 // It is possible that the filter points to a git subcommand, that is located at libexec\git-core
2301 CString gitExecPath = CGit::ms_MsysGitRootDir;
2302 gitExecPath.Append(L"libexec\\git-core");
2303 m_Environment.AddToPath(gitExecPath);
2305 if (git_filter_register("filter", git_filter_filter_new(sh, m_Environment), GIT_FILTER_DRIVER_PRIORITY))
2306 return FALSE;
2308 #endif
2310 m_bInitialized = TRUE;
2311 return true;
2314 CString CGit::GetHomeDirectory() const
2316 const wchar_t * homeDir = wget_windows_home_directory();
2317 return CString(homeDir, (int)wcslen(homeDir));
2320 CString CGit::GetGitLocalConfig() const
2322 CString path;
2323 GitAdminDir::GetAdminDirPath(m_CurrentDir, path);
2324 path += L"config";
2325 return path;
2328 CStringA CGit::GetGitPathStringA(const CString &path)
2330 return CUnicodeUtils::GetUTF8(CTGitPath(path).GetGitPathString());
2333 CString CGit::GetGitGlobalConfig() const
2335 return g_Git.GetHomeDirectory() + L"\\.gitconfig";
2338 CString CGit::GetGitGlobalXDGConfigPath() const
2340 return g_Git.GetHomeDirectory() + L"\\.config\\git";
2343 CString CGit::GetGitGlobalXDGConfig() const
2345 return g_Git.GetGitGlobalXDGConfigPath() + L"\\config";
2348 CString CGit::GetGitProgramDataConfig() const
2350 const wchar_t* programdataConfig = wget_program_data_config();
2351 return CString(programdataConfig);
2354 CString CGit::GetGitSystemConfig() const
2356 const wchar_t * systemConfig = wget_msysgit_etc();
2357 return CString(systemConfig, (int)wcslen(systemConfig));
2360 BOOL CGit::CheckCleanWorkTree(bool stagedOk /* false */)
2362 if (UsingLibGit2(GIT_CMD_CHECK_CLEAN_WT))
2364 CAutoRepository repo(GetGitRepository());
2365 if (!repo)
2366 return FALSE;
2368 if (git_repository_head_unborn(repo))
2369 return FALSE;
2371 git_status_options statusopt = GIT_STATUS_OPTIONS_INIT;
2372 statusopt.show = stagedOk ? GIT_STATUS_SHOW_WORKDIR_ONLY : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
2373 statusopt.flags = GIT_STATUS_OPT_UPDATE_INDEX | GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
2375 CAutoStatusList status;
2376 if (git_status_list_new(status.GetPointer(), repo, &statusopt))
2377 return FALSE;
2379 return (0 == git_status_list_entrycount(status));
2382 if (Run(L"git.exe rev-parse --verify HEAD", nullptr, nullptr, CP_UTF8))
2383 return FALSE;
2385 if (Run(L"git.exe update-index --ignore-submodules --refresh", nullptr, nullptr, CP_UTF8))
2386 return FALSE;
2388 if (Run(L"git.exe diff-files --quiet --ignore-submodules", nullptr, nullptr, CP_UTF8))
2389 return FALSE;
2391 if (!stagedOk && Run(L"git.exe diff-index --cached --quiet HEAD --ignore-submodules --", nullptr, nullptr, CP_UTF8))
2392 return FALSE;
2394 return TRUE;
2396 int CGit::Revert(const CString& commit, const CTGitPathList &list, CString& err)
2398 for (int i = 0; i < list.GetCount(); ++i)
2400 if (Revert(commit, (CTGitPath&)list[i], err))
2401 return -1;
2403 return 0;
2405 int CGit::Revert(const CString& commit, const CTGitPath &path, CString& err)
2407 if(path.m_Action & CTGitPath::LOGACTIONS_REPLACED && !path.GetGitOldPathString().IsEmpty())
2409 if (CTGitPath(path.GetGitOldPathString()).IsDirectory())
2411 err.Format(L"Cannot revert renaming of \"%s\". A directory with the old name \"%s\" exists.", (LPCTSTR)path.GetGitPathString(), (LPCTSTR)path.GetGitOldPathString());
2412 return -1;
2414 if (path.Exists())
2416 CString force;
2417 // if the filenames only differ in case, we have to pass "-f"
2418 if (path.GetGitPathString().CompareNoCase(path.GetGitOldPathString()) == 0)
2419 force = L"-f ";
2420 CString cmd;
2421 cmd.Format(L"git.exe mv %s-- \"%s\" \"%s\"", (LPCTSTR)force, (LPCTSTR)path.GetGitPathString(), (LPCTSTR)path.GetGitOldPathString());
2422 if (Run(cmd, &err, CP_UTF8))
2423 return -1;
2425 else
2427 CString cmd;
2428 cmd.Format(L"git.exe rm -f --cached -- \"%s\"", (LPCTSTR)path.GetGitPathString());
2429 if (Run(cmd, &err, CP_UTF8))
2430 return -1;
2433 CString cmd;
2434 cmd.Format(L"git.exe checkout %s -f -- \"%s\"", (LPCTSTR)commit, (LPCWSTR)path.GetGitOldPathString());
2435 if (Run(cmd, &err, CP_UTF8))
2436 return -1;
2438 else if(path.m_Action & CTGitPath::LOGACTIONS_ADDED)
2439 { //To init git repository, there are not HEAD, so we can use git reset command
2440 CString cmd;
2441 cmd.Format(L"git.exe rm -f --cached -- \"%s\"", (LPCTSTR)path.GetGitPathString());
2443 if (Run(cmd, &err, CP_UTF8))
2444 return -1;
2446 else
2448 CString cmd;
2449 cmd.Format(L"git.exe checkout %s -f -- \"%s\"", (LPCTSTR)commit, (LPCTSTR)path.GetGitPathString());
2450 if (Run(cmd, &err, CP_UTF8))
2451 return -1;
2454 if (path.m_Action & CTGitPath::LOGACTIONS_DELETED)
2456 CString cmd;
2457 cmd.Format(L"git.exe add -f -- \"%s\"", (LPCTSTR)path.GetGitPathString());
2458 if (Run(cmd, &err, CP_UTF8))
2459 return -1;
2462 return 0;
2465 int CGit::HasWorkingTreeConflicts(git_repository* repo)
2467 ATLASSERT(repo);
2469 CAutoIndex index;
2470 if (git_repository_index(index.GetPointer(), repo))
2471 return -1;
2473 return git_index_has_conflicts(index);
2476 int CGit::HasWorkingTreeConflicts()
2478 if (UsingLibGit2(GIT_CMD_CHECKCONFLICTS))
2480 CAutoRepository repo(GetGitRepository());
2481 if (!repo)
2482 return -1;
2484 return HasWorkingTreeConflicts(repo);
2487 CString output;
2488 gitLastErr.Empty();
2489 if (Run(L"git.exe ls-files -u -t -z", &output, &gitLastErr, CP_UTF8))
2490 return -1;
2492 return output.IsEmpty() ? 0 : 1;
2495 bool CGit::IsFastForward(const CString &from, const CString &to, CGitHash * commonAncestor)
2497 if (UsingLibGit2(GIT_CMD_MERGE_BASE))
2499 CAutoRepository repo(GetGitRepository());
2500 if (!repo)
2501 return false;
2503 CGitHash fromHash, toHash, baseHash;
2504 if (GetHash(repo, toHash, FixBranchName(to)))
2505 return false;
2507 if (GetHash(repo, fromHash, FixBranchName(from)))
2508 return false;
2510 git_oid baseOid;
2511 if (git_merge_base(&baseOid, repo, (const git_oid*)toHash.m_hash, (const git_oid*)fromHash.m_hash))
2512 return false;
2514 baseHash = baseOid.id;
2516 if (commonAncestor)
2517 *commonAncestor = baseHash;
2519 return fromHash == baseHash;
2521 // else
2522 CString base;
2523 CGitHash basehash,hash;
2524 CString cmd;
2525 cmd.Format(L"git.exe merge-base %s %s", (LPCTSTR)FixBranchName(to), (LPCTSTR)FixBranchName(from));
2527 gitLastErr.Empty();
2528 if (Run(cmd, &base, &gitLastErr, CP_UTF8))
2529 return false;
2530 basehash = base.Trim();
2532 GetHash(hash, from);
2534 if (commonAncestor)
2535 *commonAncestor = basehash;
2537 return hash == basehash;
2540 unsigned int CGit::Hash2int(const CGitHash &hash)
2542 int ret=0;
2543 for (int i = 0; i < 4; ++i)
2545 ret = ret << 8;
2546 ret |= hash.m_hash[i];
2548 return ret;
2551 int CGit::RefreshGitIndex()
2553 if(g_Git.m_IsUseGitDLL)
2555 CAutoLocker lock(g_Git.m_critGitDllSec);
2558 int result = [] { return git_run_cmd("update-index","update-index -q --refresh"); }();
2559 git_exit_cleanup();
2560 return result;
2562 }catch(...)
2564 git_exit_cleanup();
2565 return -1;
2569 else
2570 return Run(L"git.exe update-index --refresh", nullptr, nullptr, CP_UTF8);
2573 int CGit::GetOneFile(const CString &Refname, const CTGitPath &path, const CString &outputfile)
2575 if (UsingLibGit2(GIT_CMD_GETONEFILE))
2577 CAutoRepository repo(GetGitRepository());
2578 if (!repo)
2579 return -1;
2581 CGitHash hash;
2582 if (GetHash(repo, hash, Refname))
2583 return -1;
2585 CAutoCommit commit;
2586 if (git_commit_lookup(commit.GetPointer(), repo, (const git_oid *)hash.m_hash))
2587 return -1;
2589 CAutoTree tree;
2590 if (git_commit_tree(tree.GetPointer(), commit))
2591 return -1;
2593 CAutoTreeEntry entry;
2594 int ret = git_tree_entry_bypath(entry.GetPointer(), tree, CUnicodeUtils::GetUTF8(path.GetGitPathString()));
2595 if (ret)
2596 return ret;
2598 if (git_tree_entry_filemode(entry) == GIT_FILEMODE_COMMIT)
2600 giterr_set_str(GITERR_NONE, "The requested object is a submodule and not a file.");
2601 return -1;
2604 CAutoBlob blob;
2605 if (git_tree_entry_to_object((git_object**)blob.GetPointer(), repo, entry))
2606 return -1;
2608 CAutoFILE file = _wfsopen(outputfile, L"wb", SH_DENYWR);
2609 if (file == nullptr)
2611 giterr_set_str(GITERR_NONE, "Could not create file.");
2612 return -1;
2614 CAutoBuf buf;
2615 if (git_blob_filtered_content(buf, blob, CUnicodeUtils::GetUTF8(path.GetGitPathString()), 0))
2616 return -1;
2617 if (fwrite(buf->ptr, sizeof(char), buf->size, file) != buf->size)
2619 giterr_set_str(GITERR_OS, "Could not write to file.");
2620 return -1;
2623 return 0;
2625 else if (g_Git.m_IsUseGitDLL)
2627 CAutoLocker lock(g_Git.m_critGitDllSec);
2630 g_Git.CheckAndInitDll();
2631 CStringA ref, patha, outa;
2632 ref = CUnicodeUtils::GetMulti(Refname, CP_UTF8);
2633 patha = CUnicodeUtils::GetMulti(path.GetGitPathString(), CP_UTF8);
2634 outa = CUnicodeUtils::GetMulti(outputfile, CP_UTF8);
2635 ::DeleteFile(outputfile);
2636 int ret = git_checkout_file(ref, patha, outa.GetBuffer());
2637 outa.ReleaseBuffer();
2638 return ret;
2641 catch (const char * msg)
2643 gitLastErr = L"gitdll.dll reports: " + CString(msg);
2644 return -1;
2646 catch (...)
2648 gitLastErr = L"An unknown gitdll.dll error occurred.";
2649 return -1;
2652 else
2654 CString cmd;
2655 cmd.Format(L"git.exe cat-file -p %s:\"%s\"", (LPCTSTR)Refname, (LPCTSTR)path.GetGitPathString());
2656 gitLastErr.Empty();
2657 return RunLogFile(cmd, outputfile, &gitLastErr);
2661 void CEnvironment::clear()
2663 __super::clear();
2664 baseptr = nullptr;
2667 bool CEnvironment::empty()
2669 return size() < 3; // three is minimum for an empty environment with an empty key and empty value: "=\0\0"
2672 CEnvironment::operator LPTSTR()
2674 if (empty())
2675 return nullptr;
2676 return data();
2679 CEnvironment::operator LPWSTR*()
2681 return &baseptr;
2684 void CEnvironment::CopyProcessEnvironment()
2686 if (!empty())
2687 pop_back();
2688 TCHAR *porig = GetEnvironmentStrings();
2689 TCHAR *p = porig;
2690 while(*p !=0 || *(p+1) !=0)
2691 this->push_back(*p++);
2693 push_back(L'\0');
2694 push_back(L'\0');
2695 baseptr = data();
2697 FreeEnvironmentStrings(porig);
2700 CString CEnvironment::GetEnv(const TCHAR *name)
2702 CString str;
2703 for (size_t i = 0; i < size(); ++i)
2705 str = &(*this)[i];
2706 int start =0;
2707 CString sname = str.Tokenize(L"=", start);
2708 if(sname.CompareNoCase(name) == 0)
2709 return &(*this)[i+start];
2710 i+=str.GetLength();
2712 return L"";
2715 void CEnvironment::SetEnv(const TCHAR *name, const TCHAR* value)
2717 unsigned int i;
2718 for (i = 0; i < size(); ++i)
2720 CString str = &(*this)[i];
2721 int start =0;
2722 CString sname = str.Tokenize(L"=", start);
2723 if(sname.CompareNoCase(name) == 0)
2724 break;
2725 i+=str.GetLength();
2728 if(i == size())
2730 if (!value) // as we haven't found the variable we want to remove, just return
2731 return;
2732 if (i == 0) // make inserting into an empty environment work
2734 this->push_back(L'\0');
2735 ++i;
2737 i -= 1; // roll back terminate \0\0
2738 this->push_back(L'\0');
2741 CEnvironment::iterator it;
2742 it=this->begin();
2743 it += i;
2745 while(*it && i<size())
2747 this->erase(it);
2748 it=this->begin();
2749 it += i;
2752 if (value == nullptr) // remove the variable
2754 this->erase(it);
2755 if (empty())
2756 baseptr = nullptr;
2757 else
2758 baseptr = data();
2759 return;
2762 while(*name)
2764 this->insert(it,*name++);
2765 ++i;
2766 it= begin()+i;
2769 this->insert(it, L'=');
2770 ++i;
2771 it= begin()+i;
2773 while(*value)
2775 this->insert(it,*value++);
2776 ++i;
2777 it= begin()+i;
2779 baseptr = data();
2782 void CEnvironment::AddToPath(CString value)
2784 value.TrimRight(L'\\');
2785 if (value.IsEmpty())
2786 return;
2788 CString path = GetEnv(L"PATH").TrimRight(L';') + L';';
2790 // do not double add paths to %PATH%
2791 if (path.Find(value + L';') >= 0 || path.Find(value + L"\\;") >= 0)
2792 return;
2794 path += value;
2796 SetEnv(L"PATH", path);
2799 int CGit::GetGitEncode(TCHAR* configkey)
2801 CString str=GetConfigValue(configkey);
2803 if(str.IsEmpty())
2804 return CP_UTF8;
2806 return CUnicodeUtils::GetCPCode(str);
2809 int CGit::GetShortHASHLength() const
2811 return 7;
2814 CString CGit::GetShortName(const CString& ref, REF_TYPE *out_type)
2816 CString str=ref;
2817 CString shortname;
2818 REF_TYPE type = CGit::UNKNOWN;
2820 if (CGit::GetShortName(str, shortname, L"refs/heads/"))
2821 type = CGit::LOCAL_BRANCH;
2822 else if (CGit::GetShortName(str, shortname, L"refs/remotes/"))
2823 type = CGit::REMOTE_BRANCH;
2824 else if (CStringUtils::EndsWith(str, L"^{}") && CGit::GetShortName(str, shortname, L"refs/tags/"))
2825 type = CGit::ANNOTATED_TAG;
2826 else if (CGit::GetShortName(str, shortname, L"refs/tags/"))
2827 type = CGit::TAG;
2828 else if (CGit::GetShortName(str, shortname, L"refs/stash"))
2830 type = CGit::STASH;
2831 shortname = L"stash";
2833 else if (CGit::GetShortName(str, shortname, L"refs/bisect/"))
2835 CString bisectGood;
2836 CString bisectBad;
2837 g_Git.GetBisectTerms(&bisectGood, &bisectBad);
2838 TCHAR c;
2839 if (CStringUtils::StartsWith(shortname, bisectGood) && ((c = shortname.GetAt(bisectGood.GetLength())) == '-' || c == '\0'))
2841 type = CGit::BISECT_GOOD;
2842 shortname = bisectGood;
2845 if (CStringUtils::StartsWith(shortname, bisectBad) && ((c = shortname.GetAt(bisectBad.GetLength())) == '-' || c == '\0'))
2847 type = CGit::BISECT_BAD;
2848 shortname = bisectBad;
2851 if (CStringUtils::StartsWith(shortname, L"skip") && ((c = shortname.GetAt(4)) == '-' || c == '\0'))
2853 type = CGit::BISECT_SKIP;
2854 shortname = L"skip";
2857 else if (CGit::GetShortName(str, shortname, L"refs/notes/"))
2858 type = CGit::NOTES;
2859 else if (CGit::GetShortName(str, shortname, L"refs/"))
2860 type = CGit::UNKNOWN;
2861 else
2863 type = CGit::UNKNOWN;
2864 shortname = ref;
2867 if(out_type)
2868 *out_type = type;
2870 return shortname;
2873 bool CGit::UsingLibGit2(LIBGIT2_CMD cmd) const
2875 return m_IsUseLibGit2 && ((1 << cmd) & m_IsUseLibGit2_mask) ? true : false;
2878 void CGit::SetGit2CredentialCallback(void* callback)
2880 g_Git2CredCallback = (git_cred_acquire_cb)callback;
2883 void CGit::SetGit2CertificateCheckCertificate(void* callback)
2885 g_Git2CheckCertificateCallback = (git_transport_certificate_check_cb)callback;
2888 CString CGit::GetUnifiedDiffCmd(const CTGitPath& path, const CString& rev1, const CString& rev2, bool bMerge, bool bCombine, int diffContext, bool bNoPrefix)
2890 CString cmd;
2891 if (rev2 == GitRev::GetWorkingCopy())
2892 cmd.Format(L"git.exe diff --stat%s -p %s --", bNoPrefix ? L" --no-prefix" : L"", (LPCTSTR)rev1);
2893 else if (rev1 == GitRev::GetWorkingCopy())
2894 cmd.Format(L"git.exe diff -R --stat%s -p %s --", bNoPrefix ? L" --no-prefix" : L"", (LPCTSTR)rev2);
2895 else
2897 CString merge;
2898 if (bMerge)
2899 merge += L" -m";
2901 if (bCombine)
2902 merge += L" -c";
2904 CString unified;
2905 if (diffContext >= 0)
2906 unified.Format(L" --unified=%d", diffContext);
2907 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);
2910 if (!path.IsEmpty())
2912 cmd += L" \"";
2913 cmd += path.GetGitPathString();
2914 cmd += L'"';
2917 return cmd;
2920 static void UnifiedDiffStatToFile(const git_buf* text, void* payload)
2922 ATLASSERT(payload && text);
2923 fwrite(text->ptr, 1, text->size, (FILE *)payload);
2924 fwrite("\n", 1, 1, (FILE *)payload);
2927 static int UnifiedDiffToFile(const git_diff_delta * /* delta */, const git_diff_hunk * /* hunk */, const git_diff_line * line, void *payload)
2929 ATLASSERT(payload && line);
2930 if (line->origin == GIT_DIFF_LINE_CONTEXT || line->origin == GIT_DIFF_LINE_ADDITION || line->origin == GIT_DIFF_LINE_DELETION)
2931 fwrite(&line->origin, 1, 1, (FILE *)payload);
2932 fwrite(line->content, 1, line->content_len, (FILE *)payload);
2933 return 0;
2936 static int resolve_to_tree(git_repository *repo, const char *identifier, git_tree **tree)
2938 ATLASSERT(repo && identifier && tree);
2940 /* try to resolve identifier */
2941 CAutoObject obj;
2942 if (git_revparse_single(obj.GetPointer(), repo, identifier))
2943 return -1;
2945 if (obj == nullptr)
2946 return GIT_ENOTFOUND;
2948 int err = 0;
2949 switch (git_object_type(obj))
2951 case GIT_OBJ_TREE:
2952 *tree = (git_tree *)obj.Detach();
2953 break;
2954 case GIT_OBJ_COMMIT:
2955 err = git_commit_tree(tree, (git_commit *)(git_object*)obj);
2956 break;
2957 default:
2958 err = GIT_ENOTFOUND;
2961 return err;
2964 /* use libgit2 get unified diff */
2965 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)
2967 CStringA tree1 = CUnicodeUtils::GetMulti(revNew, CP_UTF8);
2968 CStringA tree2 = CUnicodeUtils::GetMulti(revOld, CP_UTF8);
2970 CAutoRepository repo(g_Git.GetGitRepository());
2971 if (!repo)
2972 return -1;
2974 int isHeadOrphan = git_repository_head_unborn(repo);
2975 if (isHeadOrphan == 1)
2976 return 0;
2977 else if (isHeadOrphan != 0)
2978 return -1;
2980 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
2981 CStringA pathA = CUnicodeUtils::GetMulti(path.GetGitPathString(), CP_UTF8);
2982 char *buf = pathA.GetBuffer();
2983 if (!pathA.IsEmpty())
2985 opts.pathspec.strings = &buf;
2986 opts.pathspec.count = 1;
2988 if (bNoPrefix)
2990 opts.new_prefix = "";
2991 opts.old_prefix = "";
2993 CAutoDiff diff;
2995 if (revNew == GitRev::GetWorkingCopy() || revOld == GitRev::GetWorkingCopy())
2997 CAutoTree t1;
2998 CAutoDiff diff2;
3000 if (revNew != GitRev::GetWorkingCopy() && resolve_to_tree(repo, tree1, t1.GetPointer()))
3001 return -1;
3003 if (revOld != GitRev::GetWorkingCopy() && resolve_to_tree(repo, tree2, t1.GetPointer()))
3004 return -1;
3006 if (git_diff_tree_to_index(diff.GetPointer(), repo, t1, nullptr, &opts))
3007 return -1;
3009 if (git_diff_index_to_workdir(diff2.GetPointer(), repo, nullptr, &opts))
3010 return -1;
3012 if (git_diff_merge(diff, diff2))
3013 return -1;
3015 else
3017 if (tree1.IsEmpty() && tree2.IsEmpty())
3018 return -1;
3020 if (tree1.IsEmpty())
3022 tree1 = tree2;
3023 tree2.Empty();
3026 CAutoTree t1;
3027 CAutoTree t2;
3028 if (!tree1.IsEmpty() && resolve_to_tree(repo, tree1, t1.GetPointer()))
3029 return -1;
3031 if (tree2.IsEmpty())
3033 /* don't check return value, there are not parent commit at first commit*/
3034 resolve_to_tree(repo, tree1 + "~1", t2.GetPointer());
3036 else if (resolve_to_tree(repo, tree2, t2.GetPointer()))
3037 return -1;
3038 if (git_diff_tree_to_tree(diff.GetPointer(), repo, t2, t1, &opts))
3039 return -1;
3042 CAutoDiffStats stats;
3043 if (git_diff_get_stats(stats.GetPointer(), diff))
3044 return -1;
3045 CAutoBuf statBuf;
3046 if (git_diff_stats_to_buf(statBuf, stats, GIT_DIFF_STATS_FULL, 0))
3047 return -1;
3048 statCallback(statBuf, data);
3050 for (size_t i = 0; i < git_diff_num_deltas(diff); ++i)
3052 CAutoPatch patch;
3053 if (git_patch_from_diff(patch.GetPointer(), diff, i))
3054 return -1;
3056 if (git_patch_print(patch, callback, data))
3057 return -1;
3060 pathA.ReleaseBuffer();
3062 return 0;
3065 int CGit::GetUnifiedDiff(const CTGitPath& path, const CString& rev1, const CString& rev2, CString patchfile, bool bMerge, bool bCombine, int diffContext, bool bNoPrefix)
3067 if (UsingLibGit2(GIT_CMD_DIFF))
3069 CAutoFILE file = _wfsopen(patchfile, L"wb", SH_DENYRW);
3070 if (!file)
3071 return -1;
3072 return GetUnifiedDiffLibGit2(path, rev1, rev2, UnifiedDiffStatToFile, UnifiedDiffToFile, file, bMerge, bNoPrefix);
3074 else
3076 CString cmd;
3077 cmd = GetUnifiedDiffCmd(path, rev1, rev2, bMerge, bCombine, diffContext, bNoPrefix);
3078 gitLastErr.Empty();
3079 return RunLogFile(cmd, patchfile, &gitLastErr);
3083 static void UnifiedDiffStatToStringA(const git_buf* text, void* payload)
3085 ATLASSERT(payload && text);
3086 CStringA *str = (CStringA*) payload;
3087 str->Append(text->ptr, (int)text->size);
3088 str->AppendChar('\n');
3091 static int UnifiedDiffToStringA(const git_diff_delta * /*delta*/, const git_diff_hunk * /*hunk*/, const git_diff_line *line, void *payload)
3093 ATLASSERT(payload && line);
3094 CStringA *str = (CStringA*) payload;
3095 if (line->origin == GIT_DIFF_LINE_CONTEXT || line->origin == GIT_DIFF_LINE_ADDITION || line->origin == GIT_DIFF_LINE_DELETION)
3096 str->Append(&line->origin, 1);
3097 str->Append(line->content, (int)line->content_len);
3098 return 0;
3101 int CGit::GetUnifiedDiff(const CTGitPath& path, const CString& rev1, const CString& rev2, CStringA* buffer, bool bMerge, bool bCombine, int diffContext)
3103 if (UsingLibGit2(GIT_CMD_DIFF))
3104 return GetUnifiedDiffLibGit2(path, rev1, rev2, UnifiedDiffStatToStringA, UnifiedDiffToStringA, buffer, bMerge, false);
3105 else
3107 CString cmd;
3108 cmd = GetUnifiedDiffCmd(path, rev1, rev2, bMerge, bCombine, diffContext);
3109 BYTE_VECTOR vector;
3110 int ret = Run(cmd, &vector);
3111 if (!vector.empty())
3113 vector.push_back(0); // vector is not NUL terminated
3114 buffer->Append((char*)vector.data());
3116 return ret;
3120 int CGit::GitRevert(int parent, const CGitHash &hash)
3122 if (UsingLibGit2(GIT_CMD_REVERT))
3124 CAutoRepository repo(GetGitRepository());
3125 if (!repo)
3126 return -1;
3128 CAutoCommit commit;
3129 if (git_commit_lookup(commit.GetPointer(), repo, (const git_oid *)hash.m_hash))
3130 return -1;
3132 git_revert_options revert_opts = GIT_REVERT_OPTIONS_INIT;
3133 revert_opts.mainline = parent;
3134 int result = git_revert(repo, commit, &revert_opts);
3136 return !result ? 0 : -1;
3138 else
3140 CString cmd, merge;
3141 if (parent)
3142 merge.Format(L"-m %d ", parent);
3143 cmd.Format(L"git.exe revert --no-edit --no-commit %s%s", (LPCTSTR)merge, (LPCTSTR)hash.ToString());
3144 gitLastErr = cmd + L'\n';
3145 if (Run(cmd, &gitLastErr, CP_UTF8))
3146 return -1;
3147 else
3149 gitLastErr.Empty();
3150 return 0;
3155 int CGit::DeleteRef(const CString& reference)
3157 if (UsingLibGit2(GIT_CMD_DELETETAGBRANCH))
3159 CAutoRepository repo(GetGitRepository());
3160 if (!repo)
3161 return -1;
3163 CStringA refA;
3164 if (CStringUtils::EndsWith(reference, L"^{}"))
3165 refA = CUnicodeUtils::GetUTF8(reference.Left(reference.GetLength() - 3));
3166 else
3167 refA = CUnicodeUtils::GetUTF8(reference);
3169 CAutoReference ref;
3170 if (git_reference_lookup(ref.GetPointer(), repo, refA))
3171 return -1;
3173 int result = -1;
3174 if (git_reference_is_tag(ref))
3175 result = git_tag_delete(repo, git_reference_shorthand(ref));
3176 else if (git_reference_is_branch(ref))
3177 result = git_branch_delete(ref);
3178 else if (git_reference_is_remote(ref))
3179 result = git_branch_delete(ref);
3180 else
3181 result = git_reference_delete(ref);
3183 return result;
3185 else
3187 CString cmd, shortname;
3188 if (GetShortName(reference, shortname, L"refs/heads/"))
3189 cmd.Format(L"git.exe branch -D -- %s", (LPCTSTR)shortname);
3190 else if (GetShortName(reference, shortname, L"refs/tags/"))
3191 cmd.Format(L"git.exe tag -d -- %s", (LPCTSTR)shortname);
3192 else if (GetShortName(reference, shortname, L"refs/remotes/"))
3193 cmd.Format(L"git.exe branch -r -D -- %s", (LPCTSTR)shortname);
3194 else
3196 gitLastErr = L"unsupported reference type: " + reference;
3197 return -1;
3200 gitLastErr.Empty();
3201 if (Run(cmd, &gitLastErr, CP_UTF8))
3202 return -1;
3204 gitLastErr.Empty();
3205 return 0;
3209 bool CGit::LoadTextFile(const CString &filename, CString &msg)
3211 if (!PathFileExists(filename))
3212 return false;
3214 CAutoFILE pFile = _wfsopen(filename, L"rb", SH_DENYWR);
3215 if (!pFile)
3217 ::MessageBox(nullptr, L"Could not open " + filename, L"TortoiseGit", MB_ICONERROR);
3218 return true; // load no further files
3221 CStringA str;
3224 char s[8196] = { 0 };
3225 int read = (int)fread(s, sizeof(char), sizeof(s), pFile);
3226 if (read == 0)
3227 break;
3228 str += CStringA(s, read);
3229 } while (true);
3230 msg += CUnicodeUtils::GetUnicode(str);
3231 msg.Replace(L"\r\n", L"\n");
3232 msg.TrimRight(L'\n');
3233 msg += L'\n';
3235 return true; // load no further files
3238 int CGit::GetWorkingTreeChanges(CTGitPathList& result, bool amend, const CTGitPathList* filterlist)
3240 if (IsInitRepos())
3241 return GetInitAddList(result);
3243 BYTE_VECTOR out;
3245 int count = 1;
3246 if (filterlist)
3247 count = filterlist->GetCount();
3249 CString head = L"HEAD";
3250 if (amend)
3251 head = L"HEAD~1";
3253 for (int i = 0; i < count; ++i)
3255 BYTE_VECTOR cmdout;
3256 CString cmd;
3257 if (ms_bCygwinGit || ms_bMsys2Git)
3259 // Prevent showing all files as modified when using cygwin's git
3260 if (!filterlist)
3261 cmd = L"git.exe status --";
3262 else
3263 cmd.Format(L"git.exe status -- \"%s\"", (LPCTSTR)(*filterlist)[i].GetGitPathString());
3264 Run(cmd, &cmdout);
3265 cmdout.clear();
3268 // also list staged files which will be in the commit
3269 Run(L"git.exe diff-index --cached --raw " + head + L" --numstat -C -M -z --", &cmdout);
3271 if (!filterlist)
3272 cmd = (L"git.exe diff-index --raw " + head + L" --numstat -C -M -z --");
3273 else
3274 cmd.Format(L"git.exe diff-index --raw " + head + L" --numstat -C -M -z -- \"%s\"", (LPCTSTR)(*filterlist)[i].GetGitPathString());
3276 BYTE_VECTOR cmdErr;
3277 if (Run(cmd, &cmdout, &cmdErr))
3279 size_t last = cmdErr.RevertFind(0);
3280 CString str;
3281 if (last != BYTE_VECTOR::npos)
3282 CGit::StringAppend(&str, &cmdErr[last + 1], CP_UTF8, (int)(cmdErr.size() - last) - 1);
3283 else if (!cmdErr.empty())
3284 CGit::StringAppend(&str, cmdErr.data(), CP_UTF8, (int)cmdErr.size() - 1);
3285 else
3286 str.Format(L"\"%s\" exited with an error code, but did not output any error message", (LPCTSTR)cmd);
3287 MessageBox(nullptr, str, L"TortoiseGit", MB_OK | MB_ICONERROR);
3290 out.append(cmdout, 0);
3292 result.ParserFromLog(out);
3294 std::map<CString, int> duplicateMap;
3295 for (int i = 0; i < result.GetCount(); ++i)
3296 duplicateMap.insert(std::pair<CString, int>(result[i].GetGitPathString(), i));
3298 // handle delete conflict case, when remote : modified, local : deleted.
3299 for (int i = 0; i < count; ++i)
3301 BYTE_VECTOR cmdout;
3302 CString cmd;
3304 if (!filterlist)
3305 cmd = L"git.exe ls-files -u -t -z";
3306 else
3307 cmd.Format(L"git.exe ls-files -u -t -z -- \"%s\"", (LPCTSTR)(*filterlist)[i].GetGitPathString());
3309 Run(cmd, &cmdout);
3311 CTGitPathList conflictlist;
3312 conflictlist.ParserFromLog(cmdout);
3313 for (int j = 0; j < conflictlist.GetCount(); ++j)
3315 auto existing = duplicateMap.find(conflictlist[j].GetGitPathString());
3316 if (existing != duplicateMap.end())
3318 CTGitPath& p = const_cast<CTGitPath&>(result[existing->second]);
3319 p.m_Action |= CTGitPath::LOGACTIONS_UNMERGED;
3321 else
3323 result.AddPath(conflictlist[j]);
3324 duplicateMap.insert(std::pair<CString, int>(result[i].GetGitPathString(), result.GetCount() - 1));
3329 // handle source files of file renames/moves (issue #860)
3330 // if a file gets renamed and the new file "git add"ed, diff-index doesn't list the source file anymore
3331 for (int i = 0; i < count; ++i)
3333 BYTE_VECTOR cmdout;
3334 CString cmd;
3336 if (!filterlist)
3337 cmd = L"git.exe ls-files -d -z";
3338 else
3339 cmd.Format(L"git.exe ls-files -d -z -- \"%s\"", (LPCTSTR)(*filterlist)[i].GetGitPathString());
3341 Run(cmd, &cmdout);
3343 CTGitPathList deletelist;
3344 deletelist.ParserFromLog(cmdout, true);
3345 for (int j = 0; j < deletelist.GetCount(); ++j)
3347 auto existing = duplicateMap.find(deletelist[j].GetGitPathString());
3348 if (existing == duplicateMap.end())
3350 result.AddPath(deletelist[j]);
3351 duplicateMap.insert(std::pair<CString, int>(result[i].GetGitPathString(), result.GetCount() - 1));
3353 else
3355 CTGitPath& p = const_cast<CTGitPath&>(result[existing->second]);
3356 p.m_Action |= CTGitPath::LOGACTIONS_MISSING;
3361 return 0;
3364 int CGit::IsRebaseRunning()
3366 CString adminDir;
3367 if (!GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir))
3368 return -1;
3370 if (PathIsDirectory(adminDir + L"rebase-apply") || PathIsDirectory(adminDir + L"tgitrebase.active"))
3371 return 1;
3372 return 0;
3375 void CGit::GetBisectTerms(CString* good, CString* bad)
3377 static CString lastGood;
3378 static CString lastBad;
3379 static ULONGLONG lastRead = 0;
3381 SCOPE_EXIT
3383 if (bad)
3384 *bad = lastBad;
3385 if (good)
3386 *good = lastGood;
3389 #ifndef GTEST_INCLUDE_GTEST_GTEST_H_
3390 // add some caching here, because this method might be called multiple times in a short time from LogDlg and RevisionGraph
3391 // as we only read a small file the performance effects should be negligible
3392 if (lastRead + 5000 > GetTickCount64())
3393 return;
3394 #endif
3396 lastGood = L"good";
3397 lastBad = L"bad";
3399 CString adminDir;
3400 if (!GitAdminDir::GetAdminDirPath(m_CurrentDir, adminDir))
3401 return;
3403 CString termsFile = adminDir + L"BISECT_TERMS";
3404 CAutoFILE fp;
3405 _wfopen_s(fp.GetPointer(), termsFile, L"rb");
3406 if (!fp)
3407 return;
3408 char badA[MAX_PATH] = { 0 };
3409 fgets(badA, sizeof(badA), fp);
3410 size_t len = strlen(badA);
3411 if (len > 0 && badA[len - 1] == '\n')
3412 badA[len - 1] = '\0';
3413 char goodA[MAX_PATH] = { 0 };
3414 fgets(goodA, sizeof(goodA), fp);
3415 len = strlen(goodA);
3416 if (len > 0 && goodA[len - 1] == '\n')
3417 goodA[len - 1] = '\0';
3418 lastGood = CUnicodeUtils::GetUnicode(goodA);
3419 lastBad = CUnicodeUtils::GetUnicode(badA);
3420 lastRead = GetTickCount64();
3423 int CGit::GetGitVersion(CString* versiondebug, CString* errStr)
3425 CString version, err;
3426 if (Run(L"git.exe --version", &version, &err, CP_UTF8))
3428 if (errStr)
3429 *errStr = err;
3430 return -1;
3433 int ver = 0;
3434 if (versiondebug)
3435 *versiondebug = version;
3439 int start = 0;
3440 CString str = version.Tokenize(L".", start);
3441 int space = str.ReverseFind(L' ');
3442 str = str.Mid(space + 1, start);
3443 ver = _wtol(str);
3444 ver <<= 24;
3446 version = version.Mid(start);
3447 start = 0;
3449 str = version.Tokenize(L".", start);
3450 ver |= (_wtol(str) & 0xFF) << 16;
3452 str = version.Tokenize(L".", start);
3453 ver |= (_wtol(str) & 0xFF) << 8;
3455 str = version.Tokenize(L".", start);
3456 ver |= (_wtol(str) & 0xFF);
3458 catch (...)
3460 if (!ver)
3461 return -1;
3464 return ver;
3467 int CGit::GetGitNotes(const CGitHash& hash, CString& notes)
3469 CAutoRepository repo(GetGitRepository());
3470 if (!repo)
3471 return -1;
3473 CAutoNote note;
3474 int ret = git_note_read(note.GetPointer(), repo, nullptr, reinterpret_cast<const git_oid*>(hash.m_hash));
3475 if (ret == GIT_ENOTFOUND)
3477 notes.Empty();
3478 return 0;
3480 else if (ret)
3481 return -1;
3482 notes = CUnicodeUtils::GetUnicode(git_note_message(note));
3483 return 0;
3486 int CGit::SetGitNotes(const CGitHash& hash, const CString& notes)
3488 CAutoRepository repo(GetGitRepository());
3489 if (!repo)
3490 return -1;
3492 CAutoSignature signature;
3493 if (git_signature_default(signature.GetPointer(), repo) < 0)
3494 return -1;
3496 git_oid oid;
3497 if (git_note_create(&oid, repo, nullptr, signature, signature, reinterpret_cast<const git_oid*>(hash.m_hash), CUnicodeUtils::GetUTF8(notes), 1) < 0)
3498 return -1;
3500 return 0;