Drop support for Git < 2.24 (released in November 2019)
[TortoiseGit.git] / src / Git / Git.cpp
blobd7dd8fcd22836d047c5d52797944422c26531e35
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2021 - TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "Git.h"
22 #include "GitRev.h"
23 #include "registry.h"
24 #include "GitForWindows.h"
25 #include "UnicodeUtils.h"
26 #include "gitdll.h"
27 #include <fstream>
28 #include <iterator>
29 #include "FormatMessageWrapper.h"
30 #include "SmartHandle.h"
31 #include "MassiveGitTaskBase.h"
32 #include "git2/sys/filter.h"
33 #include "git2/sys/transport.h"
34 #include "../libgit2/filter-filter.h"
35 #include "../libgit2/ssh-wintunnel.h"
37 static int CalculateDiffSimilarityIndexThreshold(DWORD index)
39 if (index < 0 || index > 100)
40 return 50;
41 return index;
44 bool CGit::ms_bCygwinGit = (CRegDWORD(L"Software\\TortoiseGit\\CygwinHack", FALSE) == TRUE);
45 bool CGit::ms_bMsys2Git = (CRegDWORD(L"Software\\TortoiseGit\\Msys2Hack", FALSE) == TRUE);
46 int CGit::ms_iSimilarityIndexThreshold = CalculateDiffSimilarityIndexThreshold(CRegDWORD(L"Software\\TortoiseGit\\DiffSimilarityIndexThreshold", 50));
47 int CGit::m_LogEncode=CP_UTF8;
49 static LPCWSTR nextpath(const wchar_t* path, wchar_t* buf, size_t buflen)
51 if (!path || !buf || buflen == 0)
52 return nullptr;
54 const wchar_t* base = path;
55 wchar_t term = (*path == L'"') ? *path++ : L';';
57 for (buflen--; *path && *path != term && buflen; buflen--)
58 *buf++ = *path++;
60 *buf = L'\0'; /* reserved a byte via initial subtract */
62 while (*path == term || *path == L';')
63 ++path;
65 return (path != base) ? path : nullptr;
68 static CString FindFileOnPath(const CString& filename, LPCWSTR env, bool wantDirectory = false)
70 wchar_t buf[MAX_PATH] = { 0 };
72 // search in all paths defined in PATH
73 while ((env = nextpath(env, buf, _countof(buf) - 1)) != nullptr && *buf)
75 wchar_t* pfin = buf + wcslen(buf) - 1;
77 // ensure trailing slash
78 if (*pfin != L'/' && *pfin != L'\\')
79 wcscpy_s(++pfin, 2, L"\\"); // we have enough space left, MAX_PATH-1 is used in nextpath above
81 const size_t len = wcslen(buf);
83 if ((len + filename.GetLength()) < _countof(buf))
84 wcscpy_s(pfin + 1, _countof(buf) - len, filename);
85 else
86 break;
88 if (PathFileExists(buf))
90 if (wantDirectory)
91 pfin[1] = L'\0';
92 return CString(buf);
96 return L"";
99 static BOOL FindGitPath()
101 size_t size;
102 _wgetenv_s(&size, nullptr, 0, L"PATH");
103 if (!size)
104 return FALSE;
106 auto env = std::make_unique<wchar_t[]>(size);
107 if (!env)
108 return FALSE;
109 _wgetenv_s(&size, env.get(), size, L"PATH");
111 CString gitExeDirectory = FindFileOnPath(L"git.exe", env.get(), true);
112 if (!gitExeDirectory.IsEmpty())
114 CGit::ms_LastMsysGitDir = gitExeDirectory;
115 CGit::ms_LastMsysGitDir.TrimRight(L'\\');
116 if (CStringUtils::EndsWith(CGit::ms_LastMsysGitDir, L"\\mingw32\\bin") || CStringUtils::EndsWith(CGit::ms_LastMsysGitDir, L"\\mingw64\\bin"))
118 // prefer cmd directory as early Git for Windows 2.x releases only had this
119 CString installRoot = CGit::ms_LastMsysGitDir.Left(CGit::ms_LastMsysGitDir.GetLength() - static_cast<int>(wcslen(L"\\mingw64\\bin"))) + L"\\cmd\\git.exe";
120 if (PathFileExists(installRoot))
121 CGit::ms_LastMsysGitDir = CGit::ms_LastMsysGitDir.Left(CGit::ms_LastMsysGitDir.GetLength() - static_cast<int>(wcslen(L"\\mingw64\\bin"))) + L"\\cmd";
123 if (CStringUtils::EndsWith(CGit::ms_LastMsysGitDir, L"\\cmd"))
125 // often the msysgit\cmd folder is on the %PATH%, but
126 // that git.exe does not work, so try to guess the bin folder
127 if (PathFileExists(CGit::ms_LastMsysGitDir.Left(CGit::ms_LastMsysGitDir.GetLength() - static_cast<int>(wcslen(L"\\cmd"))) + L"\\bin\\git.exe"))
128 CGit::ms_LastMsysGitDir = CGit::ms_LastMsysGitDir.Left(CGit::ms_LastMsysGitDir.GetLength() - static_cast<int>(wcslen(L"\\cmd"))) + L"\\bin";
130 return TRUE;
133 return FALSE;
136 static CString FindExecutableOnPath(const CString& executable, LPCWSTR env)
138 CString filename = executable;
140 if (!CStringUtils::EndsWith(executable, L".exe"))
141 filename += L".exe";
143 if (PathFileExists(filename))
144 return filename;
146 filename = FindFileOnPath(filename, env);
147 if (!filename.IsEmpty())
148 return filename;
150 return executable;
153 static bool g_bSortLogical;
154 static bool g_bSortLocalBranchesFirst;
155 static bool g_bSortTagsReversed;
156 static git_credential_acquire_cb g_Git2CredCallback;
157 static git_transport_certificate_check_cb g_Git2CheckCertificateCallback;
159 static void GetSortOptions()
161 #ifdef GOOGLETEST_INCLUDE_GTEST_GTEST_H_
162 g_bSortLogical = true;
163 g_bSortLocalBranchesFirst = true;
164 g_bSortTagsReversed = false;
165 #else
166 g_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
167 if (g_bSortLogical)
168 g_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
169 g_bSortLocalBranchesFirst = !CRegDWORD(L"Software\\TortoiseGit\\NoSortLocalBranchesFirst", 0, false, HKEY_CURRENT_USER);
170 if (g_bSortLocalBranchesFirst)
171 g_bSortLocalBranchesFirst = !CRegDWORD(L"Software\\TortoiseGit\\NoSortLocalBranchesFirst", 0, false, HKEY_LOCAL_MACHINE);
172 g_bSortTagsReversed = !!CRegDWORD(L"Software\\TortoiseGit\\SortTagsReversed", 0, false, HKEY_LOCAL_MACHINE);
173 if (!g_bSortTagsReversed)
174 g_bSortTagsReversed = !!CRegDWORD(L"Software\\TortoiseGit\\SortTagsReversed", 0, false, HKEY_CURRENT_USER);
175 #endif
178 static int LogicalComparePredicate(const CString &left, const CString &right)
180 if (g_bSortLogical)
181 return StrCmpLogicalW(left, right) < 0;
182 return StrCmpI(left, right) < 0;
185 static int LogicalCompareReversedPredicate(const CString &left, const CString &right)
187 return LogicalComparePredicate(right, left);
190 static int LogicalCompareBranchesPredicate(const CString &left, const CString &right)
192 if (g_bSortLocalBranchesFirst)
194 bool leftIsRemote = CStringUtils::StartsWith(left, L"remotes/");
195 bool rightIsRemote = CStringUtils::StartsWith(right, L"remotes/");
197 if (leftIsRemote && !rightIsRemote)
198 return false;
199 else if (!leftIsRemote && rightIsRemote)
200 return true;
202 return LogicalComparePredicate(left, right);
205 #define CALL_OUTPUT_READ_CHUNK_SIZE 1024
207 CString CGit::ms_LastMsysGitDir;
208 CString CGit::ms_MsysGitRootDir;
209 int CGit::ms_LastMsysGitVersion = 0;
210 CGit g_Git;
213 CGit::CGit()
215 git_libgit2_init();
216 GetCurrentDirectory(MAX_PATH, CStrBuf(m_CurrentDir, MAX_PATH));
217 m_IsGitDllInited = false;
218 m_GitDiff=0;
219 m_GitSimpleListDiff=0;
220 m_IsUseGitDLL = !!CRegDWORD(L"Software\\TortoiseGit\\UsingGitDLL",1);
221 m_IsUseLibGit2 = !!CRegDWORD(L"Software\\TortoiseGit\\UseLibgit2", TRUE);
222 m_IsUseLibGit2_mask = CRegDWORD(L"Software\\TortoiseGit\\UseLibgit2_mask", DEFAULT_USE_LIBGIT2_MASK);
224 SecureZeroMemory(&m_CurrentGitPi, sizeof(PROCESS_INFORMATION));
226 GetSortOptions();
227 this->m_bInitialized =false;
228 CheckMsysGitDir();
231 CGit::~CGit()
233 if(this->m_GitDiff)
235 git_close_diff(m_GitDiff);
236 m_GitDiff=0;
238 if(this->m_GitSimpleListDiff)
240 git_close_diff(m_GitSimpleListDiff);
241 m_GitSimpleListDiff=0;
243 git_libgit2_shutdown();
246 bool CGit::IsBranchNameValid(const CString& branchname)
248 if (branchname.FindOneOf(L"\"|<>") >= 0) // not valid on Windows
249 return false;
250 int valid = 0;
251 git_branch_name_is_valid(&valid, CUnicodeUtils::GetUTF8(branchname));
252 return valid == 1;
255 int CGit::RunAsync(CString cmd, PROCESS_INFORMATION* piOut, HANDLE* hReadOut, HANDLE* hErrReadOut, const CString* StdioFile)
257 CAutoGeneralHandle hRead, hWrite, hReadErr, hWriteErr, hWriteIn, hReadIn;
258 CAutoFile hStdioFile;
260 SECURITY_ATTRIBUTES sa = { 0 };
261 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
262 sa.bInheritHandle=TRUE;
263 if (!CreatePipe(hReadIn.GetPointer(), hWriteIn.GetPointer(), &sa, 0))
265 CString err = CFormatMessageWrapper();
266 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": could not open stdin pipe: %s\n", static_cast<LPCWSTR>(err.Trim()));
267 return TGIT_GIT_ERROR_OPEN_PIP;
269 if (!CreatePipe(hRead.GetPointer(), hWrite.GetPointer(), &sa, 0))
271 CString err = CFormatMessageWrapper();
272 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": could not open stdout pipe: %s\n", static_cast<LPCWSTR>(err.Trim()));
273 return TGIT_GIT_ERROR_OPEN_PIP;
275 if (hErrReadOut && !CreatePipe(hReadErr.GetPointer(), hWriteErr.GetPointer(), &sa, 0))
277 CString err = CFormatMessageWrapper();
278 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": could not open stderr pipe: %s\n", static_cast<LPCWSTR>(err.Trim()));
279 return TGIT_GIT_ERROR_OPEN_PIP;
282 if(StdioFile)
283 hStdioFile = CreateFile(*StdioFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
285 STARTUPINFO si = { 0 };
286 PROCESS_INFORMATION pi = { 0 };
287 si.cb=sizeof(STARTUPINFO);
288 si.hStdInput = hReadIn;
289 if (hErrReadOut)
290 si.hStdError = hWriteErr;
291 else
292 si.hStdError = hWrite;
293 if(StdioFile)
294 si.hStdOutput=hStdioFile;
295 else
296 si.hStdOutput=hWrite;
298 si.wShowWindow=SW_HIDE;
299 si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
301 LPWSTR pEnv = m_Environment;
302 DWORD dwFlags = CREATE_UNICODE_ENVIRONMENT;
303 dwFlags |= CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS;
305 memset(&this->m_CurrentGitPi,0,sizeof(PROCESS_INFORMATION));
307 bool startsWithGit = CStringUtils::StartsWith(cmd, L"git");
308 if (ms_bMsys2Git && startsWithGit && !CStringUtils::StartsWith(cmd, L"git.exe config "))
310 cmd.Replace(L"\\", L"\\\\\\\\");
311 cmd.Replace(L"\"", L"\\\"");
312 cmd = L'"' + CGit::ms_LastMsysGitDir + L"\\bash.exe\" -c \"/usr/bin/" + cmd + L'"';
314 else if (ms_bCygwinGit && startsWithGit && !CStringUtils::StartsWith(cmd, L"git.exe config "))
316 cmd.Replace(L'\\', L'/');
317 cmd.Replace(L"\"", L"\\\"");
318 cmd = L'"' + CGit::ms_LastMsysGitDir + L"\\bash.exe\" -c \"/bin/" + cmd + L'"';
320 else if (startsWithGit || CStringUtils::StartsWith(cmd, L"bash"))
322 int firstSpace = cmd.Find(L' ');
323 if (firstSpace > 0)
324 cmd = L'"' + CGit::ms_LastMsysGitDir + L'\\' + cmd.Left(firstSpace) + L'"' + cmd.Mid(firstSpace);
325 else
326 cmd = L'"' + CGit::ms_LastMsysGitDir + L'\\' + cmd + L'"';
329 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": executing %s\n", static_cast<LPCWSTR>(cmd));
330 if(!CreateProcess(nullptr, cmd.GetBuffer(), nullptr, nullptr, TRUE, dwFlags, pEnv, m_CurrentDir.GetBuffer(), &si, &pi))
332 CString err = CFormatMessageWrapper();
333 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": error while executing command: %s\n", static_cast<LPCWSTR>(err.Trim()));
334 return TGIT_GIT_ERROR_CREATE_PROCESS;
337 // Close the pipe handle so the child process stops reading.
338 hWriteIn.CloseHandle();
340 m_CurrentGitPi = pi;
342 if(piOut)
343 *piOut=pi;
344 if(hReadOut)
345 *hReadOut = hRead.Detach();
346 if(hErrReadOut)
347 *hErrReadOut = hReadErr.Detach();
348 return 0;
350 //Must use sperate function to convert ANSI str to union code string
351 //Because A2W use stack as internal convert buffer.
352 void CGit::StringAppend(CString* str, const char* p, int code, int length)
354 if (!str)
355 return ;
357 int len ;
358 if(length<0)
359 len = static_cast<int>(strlen(p));
360 else
361 len=length;
362 if (len == 0)
363 return;
364 int currentContentLen = str->GetLength();
365 auto* buf = str->GetBuffer(len * 2 + currentContentLen) + currentContentLen;
366 int appendedLen = MultiByteToWideChar(code, 0, p, len, buf, len * 2);
367 str->ReleaseBuffer(currentContentLen + appendedLen); // no - 1 because MultiByteToWideChar is called with a fixed length (thus no nul char included)
370 // This method was originally used to check for orphaned branches
371 BOOL CGit::CanParseRev(CString ref)
373 if (ref.IsEmpty())
374 ref = L"HEAD";
375 else if (ms_LastMsysGitVersion >= ConvertVersionToInt(2, 30, 0))
376 ref = L"--end-of-options " + ref;
378 CString cmdout;
379 if (Run(L"git.exe rev-parse --revs-only " + ref, &cmdout, CP_UTF8))
380 return FALSE;
381 if(cmdout.IsEmpty())
382 return FALSE;
384 return TRUE;
387 // Checks if we have an orphaned HEAD
388 BOOL CGit::IsInitRepos()
390 CGitHash hash;
391 if (GetHash(hash, L"HEAD") != 0)
392 return FALSE;
393 return hash.IsEmpty() ? TRUE : FALSE;
396 DWORD WINAPI CGit::AsyncReadStdErrThread(LPVOID lpParam)
398 auto pDataArray = static_cast<PASYNCREADSTDERRTHREADARGS>(lpParam);
400 DWORD readnumber;
401 BYTE data[CALL_OUTPUT_READ_CHUNK_SIZE];
402 while (ReadFile(pDataArray->fileHandle, data, CALL_OUTPUT_READ_CHUNK_SIZE, &readnumber, nullptr))
404 if (pDataArray->pcall->OnOutputErrData(data,readnumber))
405 break;
408 return 0;
411 #ifdef _MFC_VER
412 void CGit::KillRelatedThreads(CWinThread* thread)
414 CAutoLocker lock(m_critSecThreadMap);
415 auto it = m_AsyncReadStdErrThreadMap.find(thread->m_nThreadID);
416 if (it != m_AsyncReadStdErrThreadMap.cend())
418 TerminateThread(it->second, static_cast<DWORD>(-1));
419 m_AsyncReadStdErrThreadMap.erase(it);
421 TerminateThread(thread->m_hThread, static_cast<DWORD>(-1));
423 #endif
425 int CGit::Run(CGitCall* pcall)
427 PROCESS_INFORMATION pi;
428 CAutoGeneralHandle hRead, hReadErr;
429 if (RunAsync(pcall->GetCmd(), &pi, hRead.GetPointer(), hReadErr.GetPointer()))
430 return TGIT_GIT_ERROR_CREATE_PROCESS;
432 CAutoGeneralHandle piThread(std::move(pi.hThread));
433 CAutoGeneralHandle piProcess(std::move(pi.hProcess));
435 ASYNCREADSTDERRTHREADARGS threadArguments;
436 threadArguments.fileHandle = hReadErr;
437 threadArguments.pcall = pcall;
438 CAutoGeneralHandle thread = CreateThread(nullptr, 0, AsyncReadStdErrThread, &threadArguments, 0, nullptr);
440 if (thread)
442 CAutoLocker lock(m_critSecThreadMap);
443 m_AsyncReadStdErrThreadMap[GetCurrentThreadId()] = thread;
446 DWORD readnumber;
447 BYTE data[CALL_OUTPUT_READ_CHUNK_SIZE];
448 bool bAborted=false;
449 while (ReadFile(hRead, data, CALL_OUTPUT_READ_CHUNK_SIZE, &readnumber, nullptr))
451 // TODO: when OnOutputData() returns 'true', abort git-command. Send CTRL-C signal?
452 if(!bAborted)//For now, flush output when command aborted.
453 if(pcall->OnOutputData(data,readnumber))
454 bAborted=true;
456 if(!bAborted)
457 pcall->OnEnd();
459 if (thread)
461 WaitForSingleObject(thread, INFINITE);
463 CAutoLocker lock(m_critSecThreadMap);
464 m_AsyncReadStdErrThreadMap.erase(GetCurrentThreadId());
467 WaitForSingleObject(pi.hProcess, INFINITE);
468 DWORD exitcode =0;
470 if(!GetExitCodeProcess(pi.hProcess,&exitcode))
472 CString err = CFormatMessageWrapper();
473 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": could not get exit code: %s\n", static_cast<LPCWSTR>(err.Trim()));
474 return TGIT_GIT_ERROR_GET_EXIT_CODE;
476 else
477 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": process exited: %d\n", exitcode);
479 return exitcode;
481 class CGitCall_ByteVector : public CGitCall
483 public:
484 CGitCall_ByteVector(CString cmd,BYTE_VECTOR* pvector, BYTE_VECTOR* pvectorErr = nullptr) : CGitCall(cmd),m_pvector(pvector), m_pvectorErr(pvectorErr) {}
485 virtual bool OnOutputData(const BYTE* data, size_t size) override
487 if (!m_pvector || size == 0)
488 return false;
489 size_t oldsize=m_pvector->size();
490 m_pvector->resize(m_pvector->size()+size);
491 memcpy(&*(m_pvector->begin()+oldsize),data,size);
492 return false;
494 virtual bool OnOutputErrData(const BYTE* data, size_t size) override
496 if (!m_pvectorErr || size == 0)
497 return false;
498 size_t oldsize = m_pvectorErr->size();
499 m_pvectorErr->resize(m_pvectorErr->size() + size);
500 memcpy(&*(m_pvectorErr->begin() + oldsize), data, size);
501 return false;
503 BYTE_VECTOR* m_pvector;
504 BYTE_VECTOR* m_pvectorErr;
506 int CGit::Run(CString cmd,BYTE_VECTOR *vector, BYTE_VECTOR *vectorErr)
508 CGitCall_ByteVector call(cmd, vector, vectorErr);
509 return Run(&call);
511 int CGit::Run(CString cmd, CString* output, int code)
513 CString err;
514 int ret = Run(cmd, output, &err, code);
516 if (output && !err.IsEmpty())
518 if (!output->IsEmpty())
519 *output += L'\n';
520 *output += err;
523 return ret;
525 int CGit::Run(CString cmd, CString* output, CString* outputErr, int code)
527 BYTE_VECTOR vector, vectorErr;
528 int ret;
529 if (outputErr)
530 ret = Run(cmd, &vector, &vectorErr);
531 else
532 ret = Run(cmd, &vector);
534 vector.push_back(0);
535 StringAppend(output, vector.data(), code);
537 if (outputErr)
539 vectorErr.push_back(0);
540 StringAppend(outputErr, vectorErr.data(), code);
543 return ret;
546 class CGitCallCb : public CGitCall
548 public:
549 CGitCallCb(CString cmd, const GitReceiverFunc& recv, BYTE_VECTOR* pvectorErr = nullptr)
550 : CGitCall(cmd)
551 , m_recv(recv)
552 , m_pvectorErr(pvectorErr)
555 virtual bool OnOutputData(const BYTE* data, size_t size) override
557 // Add data
558 if (size == 0)
559 return false;
560 int oldEndPos = m_buffer.GetLength();
561 memcpy(m_buffer.GetBuffer(oldEndPos + static_cast<int>(size)) + oldEndPos, data, size);
562 m_buffer.ReleaseBuffer(oldEndPos + static_cast<int>(size));
564 // Break into lines and feed to m_recv
565 int eolPos;
566 CStringA line;
567 while ((eolPos = m_buffer.Find('\n')) >= 0)
569 memcpy(line.GetBuffer(eolPos), static_cast<const char*>(m_buffer), eolPos);
570 line.ReleaseBuffer(eolPos);
571 auto oldLen = m_buffer.GetLength();
572 memmove(m_buffer.GetBuffer(oldLen), static_cast<const char*>(m_buffer) + eolPos + 1, m_buffer.GetLength() - eolPos - 1);
573 m_buffer.ReleaseBuffer(oldLen - eolPos - 1);
574 m_recv(line);
576 return false;
579 virtual bool OnOutputErrData(const BYTE* data, size_t size) override
581 if (!m_pvectorErr || size == 0)
582 return false;
583 size_t oldsize = m_pvectorErr->size();
584 m_pvectorErr->resize(m_pvectorErr->size() + size);
585 memcpy(&*(m_pvectorErr->begin() + oldsize), data, size);
586 return false;
589 virtual void OnEnd() override
591 if (!m_buffer.IsEmpty())
592 m_recv(m_buffer);
593 m_buffer.Empty(); // Just for sure
596 private:
597 GitReceiverFunc m_recv;
598 CStringA m_buffer;
599 BYTE_VECTOR* m_pvectorErr;
602 int CGit::Run(CString cmd, const GitReceiverFunc& recv, CString* outputErr)
604 if (outputErr)
606 BYTE_VECTOR vectorErr;
607 CGitCallCb call(cmd, recv, &vectorErr);
608 int ret = Run(&call);
609 vectorErr.push_back(0);
610 StringAppend(outputErr, vectorErr.data());
611 return ret;
614 CGitCallCb call(cmd, recv);
615 return Run(&call);
618 CString CGit::GetUserName()
620 CEnvironment env;
621 env.CopyProcessEnvironment();
622 CString envname = env.GetEnv(L"GIT_AUTHOR_NAME");
623 if (!envname.IsEmpty())
624 return envname;
626 if (ms_LastMsysGitVersion >= ConvertVersionToInt(2, 22, 0))
627 if (auto authorname = GetConfigValue(L"author.name"); !authorname.IsEmpty())
628 return authorname;
630 return GetConfigValue(L"user.name");
632 CString CGit::GetUserEmail()
634 CEnvironment env;
635 env.CopyProcessEnvironment();
636 CString envmail = env.GetEnv(L"GIT_AUTHOR_EMAIL");
637 if (!envmail.IsEmpty())
638 return envmail;
640 if (ms_LastMsysGitVersion >= ConvertVersionToInt(2, 22, 0))
641 if (auto authormail = GetConfigValue(L"author.email"); !authormail.IsEmpty())
642 return authormail;
644 return GetConfigValue(L"user.email");
647 CString CGit::GetCommitterName()
649 CEnvironment env;
650 env.CopyProcessEnvironment();
651 CString envname = env.GetEnv(L"GIT_COMMITTER_NAME");
652 if (!envname.IsEmpty())
653 return envname;
655 if (ms_LastMsysGitVersion >= ConvertVersionToInt(2, 22, 0))
656 if (auto committername = GetConfigValue(L"committer.name"); !committername.IsEmpty())
657 return committername;
659 return GetConfigValue(L"user.name");
662 CString CGit::GetCommitterEmail()
664 CEnvironment env;
665 env.CopyProcessEnvironment();
666 CString envmail = env.GetEnv(L"GIT_AUTHOR_EMAIL");
667 if (!envmail.IsEmpty())
668 return envmail;
670 if (ms_LastMsysGitVersion >= ConvertVersionToInt(2, 22, 0))
671 if (auto committermail = GetConfigValue(L"committer.email"); !committermail.IsEmpty())
672 return committermail;
674 return GetConfigValue(L"user.email");
677 CString CGit::GetConfigValue(const CString& name, const CString& def, bool wantBool)
679 CString configValue;
680 if(this->m_IsUseGitDLL)
682 CAutoLocker lock(g_Git.m_critGitDllSec);
686 CheckAndInitDll();
687 }catch(...)
690 CStringA key, value;
691 key = CUnicodeUtils::GetUTF8(name);
695 if (git_get_config(key, CStrBufA(value, 4096), 4096))
696 return def;
698 catch (const char *msg)
700 ::MessageBox(nullptr, L"Could not get config.\nlibgit reports:\n" + CString(msg), L"TortoiseGit", MB_OK | MB_ICONERROR);
701 return def;
704 StringAppend(&configValue, value);
705 return configValue;
707 else
709 CString cmd;
710 cmd.Format(L"git.exe config%s %s", wantBool ? L" --bool" : L"", static_cast<LPCWSTR>(name));
711 if (Run(cmd, &configValue, nullptr, CP_UTF8))
712 return def;
713 if (configValue.IsEmpty())
714 return configValue;
715 return configValue.Left(configValue.GetLength() - 1); // strip last newline character
719 bool CGit::GetConfigValueBool(const CString& name, const bool def)
721 CString configValue = GetConfigValue(name, def ? L"true" : L"false", true);
722 configValue.MakeLower();
723 configValue.Trim();
724 if (configValue == L"true" || configValue == L"on" || configValue == L"yes" || StrToInt(configValue) != 0)
725 return true;
726 else
727 return false;
730 int CGit::GetConfigValueInt32(const CString& name, const int def)
732 CString configValue = GetConfigValue(name);
733 int value = def;
734 if (!git_config_parse_int32(&value, CUnicodeUtils::GetUTF8(configValue)))
735 return value;
736 return def;
739 int CGit::SetConfigValue(const CString& key, const CString& value, CONFIG_TYPE type)
741 if(this->m_IsUseGitDLL)
743 CAutoLocker lock(g_Git.m_critGitDllSec);
747 CheckAndInitDll();
748 }catch(...)
751 CStringA keya, valuea;
752 keya = CUnicodeUtils::GetUTF8(key);
753 valuea = CUnicodeUtils::GetUTF8(value);
757 return [=]() { return get_set_config(keya, valuea, type); }();
759 catch (const char *msg)
761 ::MessageBox(nullptr, L"Could not set config.\nlibgit reports:\n" + CString(msg), L"TortoiseGit", MB_OK | MB_ICONERROR);
762 return -1;
765 else
767 CString cmd;
768 CString option;
769 switch(type)
771 case CONFIG_GLOBAL:
772 option = L"--global";
773 break;
774 case CONFIG_SYSTEM:
775 option = L"--system";
776 break;
777 default:
778 break;
780 CString mangledValue = value;
781 mangledValue.Replace(L"\\\"", L"\\\\\"");
782 mangledValue.Replace(L"\"", L"\\\"");
783 cmd.Format(L"git.exe config %s %s \"%s\"", static_cast<LPCWSTR>(option), static_cast<LPCWSTR>(key), static_cast<LPCWSTR>(mangledValue));
784 CString out;
785 if (Run(cmd, &out, nullptr, CP_UTF8))
786 return -1;
788 return 0;
791 int CGit::UnsetConfigValue(const CString& key, CONFIG_TYPE type)
793 if(this->m_IsUseGitDLL)
795 CAutoLocker lock(g_Git.m_critGitDllSec);
799 CheckAndInitDll();
800 }catch(...)
803 CStringA keya;
804 keya = CUnicodeUtils::GetUTF8(key);
808 return [=]() { return get_set_config(keya, nullptr, type); }();
810 catch (const char *msg)
812 ::MessageBox(nullptr, L"Could not unset config.\nlibgit reports:\n" + CString(msg), L"TortoiseGit", MB_OK | MB_ICONERROR);
813 return -1;
816 else
818 CString cmd;
819 CString option;
820 switch(type)
822 case CONFIG_GLOBAL:
823 option = L"--global";
824 break;
825 case CONFIG_SYSTEM:
826 option = L"--system";
827 break;
828 default:
829 break;
831 cmd.Format(L"git.exe config %s --unset %s", static_cast<LPCWSTR>(option), static_cast<LPCWSTR>(key));
832 CString out;
833 if (Run(cmd, &out, nullptr, CP_UTF8))
834 return -1;
836 return 0;
839 int CGit::ApplyPatchToIndex(const CString& patchPath, CString* out)
841 CString cmd;
842 cmd.Format(L"git.exe apply --cached -- \"%s\"", static_cast<LPCWSTR>(patchPath));
843 return Run(cmd, out, CP_UTF8);
846 int CGit::ApplyPatchToIndexReverse(const CString& patchPath, CString* out)
848 CString cmd;
849 cmd.Format(L"git.exe apply --cached -R -- \"%s\"", static_cast<LPCWSTR>(patchPath));
850 return Run(cmd, out, CP_UTF8);
853 CString CGit::GetCurrentBranch(bool fallback)
855 CString output;
856 //Run(L"git.exe branch", &branch);
858 int result = GetCurrentBranchFromFile(m_CurrentDir, output, fallback);
859 if (result != 0 && ((result == 1 && !fallback) || result != 1))
860 return L"(no branch)";
861 else
862 return output;
865 void CGit::GetRemoteTrackedBranch(const CString& localBranch, CString& pullRemote, CString& pullBranch)
867 if (localBranch.IsEmpty())
868 return;
870 CString configName;
871 configName.Format(L"branch.%s.remote", static_cast<LPCWSTR>(localBranch));
872 pullRemote = GetConfigValue(configName);
874 //Select pull-branch from current branch
875 configName.Format(L"branch.%s.merge", static_cast<LPCWSTR>(localBranch));
876 pullBranch = StripRefName(GetConfigValue(configName));
879 void CGit::GetRemoteTrackedBranchForHEAD(CString& remote, CString& branch)
881 CString refName;
882 if (GetCurrentBranchFromFile(m_CurrentDir, refName))
883 return;
884 GetRemoteTrackedBranch(StripRefName(refName), remote, branch);
887 void CGit::GetRemotePushBranch(const CString& localBranch, CString& pushRemote, CString& pushBranch)
889 if (localBranch.IsEmpty())
890 return;
892 CString configName;
894 configName.Format(L"branch.%s.pushremote", static_cast<LPCWSTR>(localBranch));
895 pushRemote = g_Git.GetConfigValue(configName);
896 if (pushRemote.IsEmpty())
898 pushRemote = g_Git.GetConfigValue(L"remote.pushdefault");
899 if (pushRemote.IsEmpty())
901 configName.Format(L"branch.%s.remote", static_cast<LPCWSTR>(localBranch));
902 pushRemote = g_Git.GetConfigValue(configName);
906 configName.Format(L"branch.%s.pushbranch", static_cast<LPCWSTR>(localBranch));
907 pushBranch = g_Git.GetConfigValue(configName); // do not strip branch name (for gerrit), see issue #1609)
908 if (pushBranch.IsEmpty())
910 configName.Format(L"branch.%s.merge", static_cast<LPCWSTR>(localBranch));
911 pushBranch = CGit::StripRefName(g_Git.GetConfigValue(configName));
915 CString CGit::GetFullRefName(const CString& shortRefName)
917 CString refName;
918 CString cmd;
919 cmd.Format(L"git.exe rev-parse --symbolic-full-name %s", static_cast<LPCWSTR>(shortRefName));
920 if (Run(cmd, &refName, nullptr, CP_UTF8) != 0)
921 return CString();//Error
922 return refName.TrimRight();
925 CString CGit::StripRefName(CString refName)
927 if (CStringUtils::StartsWith(refName, L"refs/heads/"))
928 refName = refName.Mid(static_cast<int>(wcslen(L"refs/heads/")));
929 else if (CStringUtils::StartsWith(refName, L"refs/"))
930 refName = refName.Mid(static_cast<int>(wcslen(L"refs/")));
931 return refName.TrimRight();
934 int CGit::GetCurrentBranchFromFile(const CString &sProjectRoot, CString &sBranchOut, bool fallback)
936 // read current branch name like git-gui does, by parsing the .git/HEAD file directly
938 if ( sProjectRoot.IsEmpty() )
939 return -1;
941 CString sDotGitPath;
942 if (!GitAdminDir::GetWorktreeAdminDirPath(sProjectRoot, sDotGitPath))
943 return -1;
945 CString sHeadFile = sDotGitPath + L"HEAD";
947 CAutoFILE pFile = _wfsopen(sHeadFile.GetString(), L"r", SH_DENYWR);
948 if (!pFile)
949 return -1;
951 char s[MAX_PATH] = {0};
952 fgets(s, sizeof(s), pFile);
954 const char *pfx = "ref: refs/heads/";
955 const size_t len = strlen(pfx);
957 if ( !strncmp(s, pfx, len) )
959 //# We're on a branch. It might not exist. But
960 //# HEAD looks good enough to be a branch.
961 CStringA utf8Branch(s + len);
962 sBranchOut = CUnicodeUtils::GetUnicode(utf8Branch);
963 sBranchOut.TrimRight(L" \r\n\t");
965 if ( sBranchOut.IsEmpty() )
966 return -1;
968 else if (fallback)
970 CStringA utf8Hash(s);
971 CString unicodeHash = CUnicodeUtils::GetUnicode(utf8Hash);
972 unicodeHash.TrimRight(L" \r\n\t");
973 if (CGitHash::IsValidSHA1(unicodeHash))
974 sBranchOut = unicodeHash;
975 else
976 //# Assume this is a detached head.
977 sBranchOut = L"HEAD";
978 return 1;
980 else
982 //# Assume this is a detached head.
983 sBranchOut = "HEAD";
985 return 1;
988 return 0;
991 CString CGit::GetLogCmd(CString range, const CTGitPath* path, int mask, CFilterData* Filter, int logOrderBy)
993 CString param;
995 if(mask& LOG_INFO_STAT )
996 param += L" --numstat";
997 if(mask& LOG_INFO_FILESTATE)
998 param += L" --raw";
1000 if(mask& LOG_INFO_BOUNDARY)
1001 param += L" --left-right --boundary";
1003 if(mask& CGit::LOG_INFO_ALL_BRANCH)
1005 param += L" --all";
1006 if ((mask & LOG_INFO_ALWAYS_APPLY_RANGE) == 0)
1007 range.Empty();
1010 if (mask& CGit::LOG_INFO_BASIC_REFS)
1012 param += L" --branches";
1013 param += L" --tags";
1014 param += L" --remotes";
1015 param += L" --glob=stas[h]"; // require at least one glob operator
1016 param += L" --glob=bisect";
1017 if ((mask & LOG_INFO_ALWAYS_APPLY_RANGE) == 0)
1018 range.Empty();
1021 if(mask & CGit::LOG_INFO_LOCAL_BRANCHES)
1023 param += L" --branches";
1024 if ((mask & LOG_INFO_ALWAYS_APPLY_RANGE) == 0)
1025 range.Empty();
1028 if(mask& CGit::LOG_INFO_DETECT_COPYRENAME)
1029 param.AppendFormat(L" -C%d%%", ms_iSimilarityIndexThreshold);
1031 if(mask& CGit::LOG_INFO_DETECT_RENAME )
1032 param.AppendFormat(L" -M%d%%", ms_iSimilarityIndexThreshold);
1034 if(mask& CGit::LOG_INFO_FIRST_PARENT )
1035 param += L" --first-parent";
1037 if(mask& CGit::LOG_INFO_NO_MERGE )
1038 param += L" --no-merges";
1040 if(mask& CGit::LOG_INFO_FOLLOW)
1041 param += L" --follow";
1043 if(mask& CGit::LOG_INFO_SHOW_MERGEDFILE)
1044 param += L" -c";
1046 if(mask& CGit::LOG_INFO_FULL_DIFF)
1047 param += L" --full-diff";
1049 if(mask& CGit::LOG_INFO_SIMPILFY_BY_DECORATION)
1050 param += L" --simplify-by-decoration";
1052 if (mask & CGit::LOG_INFO_SPARSE)
1053 param += L" --sparse";
1055 if (Filter)
1057 if (Filter->m_NumberOfLogsScale >= CFilterData::SHOW_LAST_N_YEARS)
1059 CTime now = CTime::GetCurrentTime();
1060 CTime time = CTime(now.GetYear(), now.GetMonth(), now.GetDay(), 0, 0, 0);
1061 __time64_t substract = 86400;
1062 CString scale;
1063 switch (Filter->m_NumberOfLogsScale)
1065 case CFilterData::SHOW_LAST_N_YEARS:
1066 substract *= 365;
1067 break;
1068 case CFilterData::SHOW_LAST_N_MONTHS:
1069 substract *= 30;
1070 break;
1071 case CFilterData::SHOW_LAST_N_WEEKS:
1072 substract *= 7;
1073 break;
1075 Filter->m_From = static_cast<DWORD>(time.GetTime()) - (Filter->m_NumberOfLogs * substract);
1077 if (Filter->m_NumberOfLogsScale == CFilterData::SHOW_LAST_N_COMMITS)
1078 param.AppendFormat(L" -n%ld", Filter->m_NumberOfLogs);
1079 else if (Filter->m_NumberOfLogsScale >= CFilterData::SHOW_LAST_SEL_DATE && Filter->m_From > 0)
1080 param.AppendFormat(L" --max-age=%I64u", Filter->m_From);
1083 if( Filter && (Filter->m_To != -1))
1084 param.AppendFormat(L" --min-age=%I64u", Filter->m_To);
1086 if (logOrderBy == LOG_ORDER_TOPOORDER || (mask & CGit::LOG_ORDER_TOPOORDER))
1087 param += L" --topo-order";
1088 else if (logOrderBy == LOG_ORDER_DATEORDER)
1089 param += L" --date-order";
1091 CString cmd;
1092 CString file;
1093 if (path)
1094 file.Format(L" \"%s\"", static_cast<LPCWSTR>(path->GetGitPathString()));
1095 // gitdll.dll:setup_revisions() only looks at args[1] and greater. To account for this, pass a dummy parameter in the 0th place
1096 cmd.Format(L"-z%s %s --parents --%s", static_cast<LPCWSTR>(param), static_cast<LPCWSTR>(range), static_cast<LPCWSTR>(file));
1098 return cmd;
1100 #define BUFSIZE 512
1101 void GetTempPath(CString &path)
1103 wchar_t lpPathBuffer[BUFSIZE] = { 0 };
1104 DWORD dwBufSize=BUFSIZE;
1105 DWORD dwRetVal = GetTortoiseGitTempPath(dwBufSize, lpPathBuffer);
1106 if (dwRetVal > dwBufSize || (dwRetVal == 0))
1107 path.Empty();
1108 path.Format(L"%s", lpPathBuffer);
1110 CString GetTempFile()
1112 wchar_t lpPathBuffer[BUFSIZE] = { 0 };
1113 DWORD dwBufSize=BUFSIZE;
1114 wchar_t szTempName[BUFSIZE] = { 0 };
1116 auto dwRetVal = GetTortoiseGitTempPath(dwBufSize, lpPathBuffer);
1117 if (dwRetVal > dwBufSize || (dwRetVal == 0))
1118 return L"";
1120 // Create a temporary file.
1121 if (!GetTempFileName(lpPathBuffer, // directory for tmp files
1122 TEXT("Patch"), // temp file name prefix
1123 0, // create unique name
1124 szTempName)) // buffer for name
1125 return L"";
1127 return CString(szTempName);
1130 DWORD GetTortoiseGitTempPath(DWORD nBufferLength, LPWSTR lpBuffer)
1132 DWORD result = ::GetTempPath(nBufferLength, lpBuffer);
1133 if (result == 0) return 0;
1134 if (!lpBuffer || (result + 13 > nBufferLength))
1136 if (lpBuffer)
1137 lpBuffer[0] = '\0';
1138 return result + 13;
1141 wcscat_s(lpBuffer, nBufferLength, L"TortoiseGit\\");
1142 CreateDirectory(lpBuffer, nullptr);
1144 return result + 13;
1147 int CGit::RunLogFile(CString cmd, const CString &filename, CString *stdErr)
1149 PROCESS_INFORMATION pi;
1150 CAutoGeneralHandle hReadErr;
1151 if (RunAsync(cmd, &pi, nullptr, hReadErr.GetPointer(), &filename))
1152 return TGIT_GIT_ERROR_CREATE_PROCESS;
1154 CAutoGeneralHandle piThread(std::move(pi.hThread));
1155 CAutoGeneralHandle piProcess(std::move(pi.hProcess));
1157 BYTE_VECTOR stderrVector;
1158 CGitCall_ByteVector pcall(L"", nullptr, &stderrVector);
1159 ASYNCREADSTDERRTHREADARGS threadArguments;
1160 threadArguments.fileHandle = hReadErr;
1161 threadArguments.pcall = &pcall;
1162 CAutoGeneralHandle thread = CreateThread(nullptr, 0, AsyncReadStdErrThread, &threadArguments, 0, nullptr);
1164 if (thread)
1166 CAutoLocker lock(m_critSecThreadMap);
1167 m_AsyncReadStdErrThreadMap[GetCurrentThreadId()] = thread;
1170 WaitForSingleObject(pi.hProcess,INFINITE);
1172 if (thread)
1174 WaitForSingleObject(thread, INFINITE);
1176 CAutoLocker lock(m_critSecThreadMap);
1177 m_AsyncReadStdErrThreadMap.erase(GetCurrentThreadId());
1180 stderrVector.push_back(0);
1181 StringAppend(stdErr, stderrVector.data(), CP_UTF8);
1183 DWORD exitcode = 0;
1184 if (!GetExitCodeProcess(pi.hProcess, &exitcode))
1186 CString err = CFormatMessageWrapper();
1187 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": could not get exit code: %s\n", static_cast<LPCWSTR>(err.Trim()));
1188 return TGIT_GIT_ERROR_GET_EXIT_CODE;
1190 else
1191 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": process exited: %d\n", exitcode);
1193 return exitcode;
1196 CAutoRepository CGit::GetGitRepository() const
1198 return CAutoRepository(GetGitPathStringA(m_CurrentDir));
1201 int CGit::GetHash(git_repository * repo, CGitHash &hash, const CString& friendname, bool skipFastCheck /* = false */)
1203 ATLASSERT(repo);
1205 // no need to parse a ref if it's already a 40-byte hash
1206 if (!skipFastCheck && CGitHash::IsValidSHA1(friendname))
1208 hash = CGitHash::FromHexStr(friendname);
1209 return 0;
1212 int isHeadOrphan = git_repository_head_unborn(repo);
1213 if (isHeadOrphan != 0)
1215 hash.Empty();
1216 if (isHeadOrphan == 1)
1218 if (friendname == GitRev::GetHead()) // special check for unborn branch: if not requesting HEAD, do normal commit lookup
1219 return 0;
1221 else
1222 return -1;
1225 CAutoObject gitObject;
1226 if (git_revparse_single(gitObject.GetPointer(), repo, CUnicodeUtils::GetUTF8(friendname)))
1227 return -1;
1229 hash = git_object_id(gitObject);
1231 return 0;
1234 int CGit::GetHash(CGitHash &hash, const CString& friendname)
1236 // no need to parse a ref if it's already a 40-byte hash
1237 if (CGitHash::IsValidSHA1(friendname))
1239 hash = CGitHash::FromHexStr(friendname);
1240 return 0;
1243 if (m_IsUseLibGit2)
1245 CAutoRepository repo(GetGitRepository());
1246 if (!repo)
1247 return -1;
1249 return GetHash(repo, hash, friendname, true);
1251 else
1253 if (friendname.IsEmpty())
1255 gitLastErr.Empty();
1256 return -1;
1258 CString branch = FixBranchName(friendname);
1259 if (friendname == L"FETCH_HEAD" && branch.IsEmpty())
1260 branch = friendname;
1261 CString cmd;
1262 cmd.Format(L"git.exe rev-parse %s", static_cast<LPCWSTR>(branch));
1263 gitLastErr.Empty();
1264 int ret = Run(cmd, &gitLastErr, nullptr, CP_UTF8);
1265 hash = CGitHash::FromHexStrTry(gitLastErr.Trim());
1266 if (ret == 0)
1267 gitLastErr.Empty();
1268 else if (friendname == L"HEAD") // special check for unborn branch
1270 CString currentbranch;
1271 if (GetCurrentBranchFromFile(m_CurrentDir, currentbranch))
1272 return -1;
1273 gitLastErr.Empty();
1274 return 0;
1276 return ret;
1280 int CGit::GetInitAddList(CTGitPathList& outputlist, bool getStagingStatus)
1282 BYTE_VECTOR cmdout;
1284 outputlist.Clear();
1285 if (Run(L"git.exe ls-files -s -t -z", &cmdout))
1286 return -1;
1288 if (outputlist.ParserFromLsFile(cmdout))
1289 return -1;
1290 for(int i = 0; i < outputlist.GetCount(); ++i)
1291 const_cast<CTGitPath&>(outputlist[i]).m_Action = CTGitPath::LOGACTIONS_ADDED;
1293 if (getStagingStatus)
1295 BYTE_VECTOR cmdunstagedout;
1296 for (int i = 0; i < outputlist.GetCount(); ++i)
1297 const_cast<CTGitPath&>(outputlist[i]).m_stagingStatus = CTGitPath::StagingStatus::TotallyStaged;
1298 if (Run(L"git.exe diff-files --raw --numstat -C -M -z --", &cmdunstagedout))
1299 return -1;
1301 CTGitPathList unstaged;
1302 unstaged.ParserFromLog(cmdunstagedout);
1303 // File shows up both in the output of ls-files and diff-files: partially staged (typically modified after being added)
1304 for (int j = 0; j < unstaged.GetCount(); ++j)
1306 CString path = unstaged[j].GetGitPathString();
1307 if (outputlist.LookForGitPath(path))
1308 outputlist.UpdateStagingStatusFromPath(path, CTGitPath::StagingStatus::PartiallyStaged); // TODO: This is inefficient
1311 return 0;
1313 int CGit::GetCommitDiffList(const CString &rev1, const CString &rev2, CTGitPathList &outputlist, bool ignoreSpaceAtEol, bool ignoreSpaceChange, bool ignoreAllSpace , bool ignoreBlankLines)
1315 CString cmd;
1316 CString ignore;
1317 if (ignoreSpaceAtEol)
1318 ignore += L" --ignore-space-at-eol";
1319 if (ignoreSpaceChange)
1320 ignore += L" --ignore-space-change";
1321 if (ignoreAllSpace)
1322 ignore += L" --ignore-all-space";
1323 if (ignoreBlankLines)
1324 ignore += L" --ignore-blank-lines";
1326 if(rev1 == GIT_REV_ZERO || rev2 == GIT_REV_ZERO)
1328 if(rev1 == GIT_REV_ZERO)
1329 cmd.Format(L"git.exe diff -r --raw -C%d%% -M%d%% --numstat -z %s %s --", ms_iSimilarityIndexThreshold, ms_iSimilarityIndexThreshold, static_cast<LPCWSTR>(ignore), static_cast<LPCWSTR>(rev2));
1330 else
1331 cmd.Format(L"git.exe diff -r -R --raw -C%d%% -M%d%% --numstat -z %s %s --", ms_iSimilarityIndexThreshold, ms_iSimilarityIndexThreshold, static_cast<LPCWSTR>(ignore), static_cast<LPCWSTR>(rev1));
1333 else
1334 cmd.Format(L"git.exe diff-tree -r --raw -C%d%% -M%d%% --numstat -z %s %s %s --", ms_iSimilarityIndexThreshold, ms_iSimilarityIndexThreshold, static_cast<LPCWSTR>(ignore), static_cast<LPCWSTR>(rev2), static_cast<LPCWSTR>(rev1));
1336 BYTE_VECTOR out;
1337 if (Run(cmd, &out))
1338 return -1;
1340 return outputlist.ParserFromLog(out);
1343 int CGit::GetTagList(STRING_VECTOR &list)
1345 size_t prevCount = list.size();
1346 if (this->m_IsUseLibGit2)
1348 CAutoRepository repo(GetGitRepository());
1349 if (!repo)
1350 return -1;
1352 CAutoStrArray tag_names;
1354 if (git_tag_list(tag_names, repo))
1355 return -1;
1357 for (size_t i = 0; i < tag_names->count; ++i)
1359 list.push_back(CUnicodeUtils::GetUnicode(tag_names->strings[i]));
1362 std::sort(list.begin() + prevCount, list.end(), g_bSortTagsReversed ? LogicalCompareReversedPredicate : LogicalComparePredicate);
1364 return 0;
1366 else
1368 int ret = Run(L"git.exe tag -l", [&](const CStringA& lineA)
1370 if (lineA.IsEmpty())
1371 return;
1372 list.push_back(CUnicodeUtils::GetUnicode(lineA));
1374 if (!ret)
1375 std::sort(list.begin() + prevCount, list.end(), g_bSortTagsReversed ? LogicalCompareReversedPredicate : LogicalComparePredicate);
1376 else if (ret == 1 && IsInitRepos())
1377 return 0;
1378 return ret;
1382 CString CGit::GetGitLastErr(const CString& msg)
1384 if (this->m_IsUseLibGit2)
1385 return GetLibGit2LastErr(msg);
1386 else if (gitLastErr.IsEmpty())
1387 return msg + L"\nUnknown git.exe error.";
1388 else
1390 CString lastError = gitLastErr;
1391 gitLastErr.Empty();
1392 return msg + L'\n' + lastError;
1396 CString CGit::GetGitLastErr(const CString& msg, LIBGIT2_CMD cmd)
1398 if (UsingLibGit2(cmd))
1399 return GetLibGit2LastErr(msg);
1400 else if (gitLastErr.IsEmpty())
1401 return msg + L"\nUnknown git.exe error.";
1402 else
1404 CString lastError = gitLastErr;
1405 gitLastErr.Empty();
1406 return msg + L'\n' + lastError;
1410 CString CGit::GetLibGit2LastErr()
1412 const git_error *libgit2err = git_error_last();
1413 if (libgit2err)
1415 CString lastError = CUnicodeUtils::GetUnicode(libgit2err->message);
1416 git_error_clear();
1417 return L"libgit2 returned: " + lastError;
1419 else
1420 return L"An error occoured in libgit2, but no message is available.";
1423 CString CGit::GetLibGit2LastErr(const CString& msg)
1425 if (!msg.IsEmpty())
1426 return msg + L'\n' + GetLibGit2LastErr();
1427 return GetLibGit2LastErr();
1430 CString CGit::FixBranchName_Mod(CString& branchName)
1432 if (branchName == L"FETCH_HEAD")
1433 branchName = DerefFetchHead();
1434 return branchName;
1437 CString CGit::FixBranchName(const CString& branchName)
1439 CString tempBranchName = branchName;
1440 FixBranchName_Mod(tempBranchName);
1441 return tempBranchName;
1444 bool CGit::IsBranchTagNameUnique(const CString& name)
1446 if (m_IsUseLibGit2)
1448 CAutoRepository repo(GetGitRepository());
1449 if (!repo)
1450 return true; // TODO: optimize error reporting
1452 CAutoReference tagRef;
1453 if (git_reference_lookup(tagRef.GetPointer(), repo, CUnicodeUtils::GetUTF8(L"refs/tags/" + name)))
1454 return true;
1456 CAutoReference branchRef;
1457 if (git_reference_lookup(branchRef.GetPointer(), repo, CUnicodeUtils::GetUTF8(L"refs/heads/" + name)))
1458 return true;
1460 return false;
1462 // else
1463 CString cmd;
1464 cmd.Format(L"git.exe show-ref --tags --heads refs/heads/%s refs/tags/%s", static_cast<LPCWSTR>(name), static_cast<LPCWSTR>(name));
1466 int refCnt = 0;
1467 Run(cmd, [&](const CStringA& lineA)
1469 if (lineA.IsEmpty())
1470 return;
1471 ++refCnt;
1474 return (refCnt <= 1);
1477 bool CGit::IsLocalBranch(const CString& shortName)
1479 STRING_VECTOR list;
1480 GetBranchList(list, nullptr, CGit::BRANCH_LOCAL);
1481 return std::find(list.cbegin(), list.cend(), shortName) != list.cend();
1484 bool CGit::BranchTagExists(const CString& name, bool isBranch /*= true*/)
1486 if (m_IsUseLibGit2)
1488 CAutoRepository repo(GetGitRepository());
1489 if (!repo)
1490 return false; // TODO: optimize error reporting
1492 CString prefix;
1493 if (isBranch)
1494 prefix = L"refs/heads/";
1495 else
1496 prefix = L"refs/tags/";
1498 CAutoReference ref;
1499 if (git_reference_lookup(ref.GetPointer(), repo, CUnicodeUtils::GetUTF8(prefix + name)))
1500 return false;
1502 return true;
1504 // else
1505 CString cmd, output;
1507 cmd = L"git.exe show-ref ";
1508 if (isBranch)
1509 cmd += L"--heads ";
1510 else
1511 cmd += L"--tags ";
1513 cmd += L"refs/heads/" + name;
1514 cmd += L" refs/tags/" + name;
1516 int ret = Run(cmd, &output, nullptr, CP_UTF8);
1517 if (!ret)
1519 if (!output.IsEmpty())
1520 return true;
1523 return false;
1526 CString CGit::DerefFetchHead()
1528 CString dotGitPath;
1529 GitAdminDir::GetWorktreeAdminDirPath(m_CurrentDir, dotGitPath);
1530 std::ifstream fetchHeadFile((dotGitPath + L"FETCH_HEAD").GetString(), std::ios::in | std::ios::binary);
1531 int forMergeLineCount = 0;
1532 std::string line;
1533 std::string hashToReturn;
1534 while(getline(fetchHeadFile, line))
1536 //Tokenize this line
1537 std::string::size_type prevPos = 0;
1538 std::string::size_type pos = line.find('\t');
1539 if(pos == std::string::npos) continue; //invalid line
1541 std::string hash = line.substr(0, pos);
1542 ++pos; prevPos = pos; pos = line.find('\t', pos); if(pos == std::string::npos) continue;
1544 bool forMerge = pos == prevPos;
1545 ++pos; prevPos = pos; pos = line.size(); if(pos == std::string::npos) continue;
1547 //std::string remoteBranch = line.substr(prevPos, pos - prevPos);
1549 //Process this line
1550 if(forMerge)
1552 hashToReturn = hash;
1553 ++forMergeLineCount;
1554 if(forMergeLineCount > 1)
1555 return L""; //More then 1 branch to merge found. Octopus merge needed. Cannot pick single ref from FETCH_HEAD
1559 return CUnicodeUtils::GetUnicode(hashToReturn.c_str());
1562 int CGit::GetBranchList(STRING_VECTOR &list, int *current, BRANCH_TYPE type, bool skipCurrent /*false*/)
1564 size_t prevCount = list.size();
1565 int ret = 0;
1566 CString cur;
1567 bool headIsDetached = false;
1568 if (m_IsUseLibGit2)
1570 CAutoRepository repo(GetGitRepository());
1571 if (!repo)
1572 return -1;
1574 if (git_repository_head_detached(repo) == 1)
1575 headIsDetached = true;
1577 if ((type & (BRANCH_LOCAL | BRANCH_REMOTE)) != 0)
1579 git_branch_t flags = GIT_BRANCH_LOCAL;
1580 if ((type & BRANCH_LOCAL) && (type & BRANCH_REMOTE))
1581 flags = GIT_BRANCH_ALL;
1582 else if (type & BRANCH_REMOTE)
1583 flags = GIT_BRANCH_REMOTE;
1585 CAutoBranchIterator it;
1586 if (git_branch_iterator_new(it.GetPointer(), repo, flags))
1587 return -1;
1589 CAutoReference ref;
1590 git_branch_t branchType;
1591 while (git_branch_next(ref.GetPointer(), &branchType, it) == 0)
1593 const char * name = nullptr;
1594 if (git_branch_name(&name, ref))
1595 continue;
1597 CString branchname = CUnicodeUtils::GetUnicode(name);
1598 if (branchType & GIT_BRANCH_REMOTE)
1599 list.push_back(L"remotes/" + branchname);
1600 else
1602 if (git_branch_is_head(ref))
1604 if (skipCurrent)
1605 continue;
1606 cur = branchname;
1608 list.push_back(branchname);
1613 else
1615 CString cmd = L"git.exe branch --no-color";
1617 if ((type & BRANCH_ALL) == BRANCH_ALL)
1618 cmd += L" -a";
1619 else if (type & BRANCH_REMOTE)
1620 cmd += L" -r";
1622 ret = Run(cmd, [&](CStringA lineA)
1624 lineA.Trim(" \r\n\t");
1625 if (lineA.IsEmpty())
1626 return;
1627 if (lineA.Find(" -> ") >= 0)
1628 return; // skip something like: refs/origin/HEAD -> refs/origin/master
1630 CString branch = CUnicodeUtils::GetUnicode(lineA);
1631 if (lineA[0] == '*')
1633 if (skipCurrent)
1634 return;
1635 branch = branch.Mid(static_cast<int>(wcslen(L"* ")));
1636 cur = branch;
1638 // check whether HEAD is detached
1639 CString currentHead;
1640 if (branch[0] == L'(' && GetCurrentBranchFromFile(m_CurrentDir, currentHead) == 1)
1642 headIsDetached = true;
1643 return;
1646 else if (lineA[0] == '+') // since Git 2.23 branches that are checked out in other worktrees connected to the same repository prefixed with '+'
1647 branch = branch.Mid(static_cast<int>(wcslen(L"+ ")));
1648 if ((type & BRANCH_REMOTE) != 0 && (type & BRANCH_LOCAL) == 0)
1649 branch = L"remotes/" + branch;
1650 list.push_back(branch);
1652 if (ret == 1 && IsInitRepos())
1653 return 0;
1656 if(type & BRANCH_FETCH_HEAD && !DerefFetchHead().IsEmpty())
1657 list.push_back(L"FETCH_HEAD");
1659 std::sort(list.begin() + prevCount, list.end(), LogicalCompareBranchesPredicate);
1661 if (current && !headIsDetached && !skipCurrent)
1663 for (unsigned int i = 0; i < list.size(); ++i)
1665 if (list[i] == cur)
1667 *current = i;
1668 break;
1673 return ret;
1676 int CGit::GetRefsCommitIsOn(STRING_VECTOR& list, const CGitHash& hash, bool includeTags, bool includeBranches, BRANCH_TYPE type)
1678 if (!includeTags && !includeBranches || hash.IsEmpty())
1679 return 0;
1681 size_t prevCount = list.size();
1682 if (m_IsUseLibGit2)
1684 CAutoRepository repo(GetGitRepository());
1685 if (!repo)
1686 return -1;
1688 CAutoReferenceIterator it;
1689 if (git_reference_iterator_new(it.GetPointer(), repo))
1690 return -1;
1692 auto checkDescendent = [&list, &hash, &repo](const git_oid* oid, const git_reference* ref) {
1693 if (!oid)
1694 return;
1695 if (git_oid_equal(oid, hash) || git_graph_descendant_of(repo, oid, hash) == 1)
1697 const char* name = git_reference_name(ref);
1698 if (!name)
1699 return;
1701 list.push_back(CUnicodeUtils::GetUnicode(name));
1705 CAutoReference ref;
1706 while (git_reference_next(ref.GetPointer(), it) == 0)
1708 if (git_reference_is_tag(ref))
1710 if (!includeTags)
1711 continue;
1713 CAutoTag tag;
1714 if (git_tag_lookup(tag.GetPointer(), repo, git_reference_target(ref)) == 0)
1716 CAutoObject obj;
1717 if (git_tag_peel(obj.GetPointer(), tag) < 0)
1718 continue;
1719 checkDescendent(git_object_id(obj), ref);
1720 continue;
1723 else if (git_reference_is_remote(ref))
1725 if (!includeBranches || !(type & BRANCH_REMOTE))
1726 continue;
1728 else if (git_reference_is_branch(ref))
1730 if (!includeBranches || !(type & GIT_BRANCH_LOCAL))
1731 continue;
1733 else
1734 continue;
1736 if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC)
1738 CAutoReference peeledRef;
1739 if (git_reference_resolve(peeledRef.GetPointer(), ref) < 0)
1740 continue;
1742 checkDescendent(git_reference_target(peeledRef), ref);
1743 continue;
1746 checkDescendent(git_reference_target(ref), ref);
1749 else
1751 if (includeBranches)
1753 CString cmd = L"git.exe branch --no-color";
1754 if ((type & BRANCH_ALL) == BRANCH_ALL)
1755 cmd += L" -a";
1756 else if (type & BRANCH_REMOTE)
1757 cmd += L" -r";
1758 cmd += L" --contains " + hash.ToString();
1760 if (Run(cmd, [&](CStringA lineA)
1762 lineA.Trim(" \r\n\t");
1763 if (lineA.IsEmpty())
1764 return;
1765 if (lineA.Find(" -> ") >= 0) // normalize symbolic refs: "refs/origin/HEAD -> refs/origin/master" to "refs/origin/HEAD"
1766 lineA.Truncate(lineA.Find(" -> "));
1768 CString branch = CUnicodeUtils::GetUnicode(lineA);
1769 if (lineA[0] == '*')
1771 branch = branch.Mid(static_cast<int>(wcslen(L"* ")));
1772 CString currentHead;
1773 if (branch[0] == L'(' && GetCurrentBranchFromFile(m_CurrentDir, currentHead) == 1)
1774 return;
1777 if ((type & BRANCH_REMOTE) != 0 && (type & BRANCH_LOCAL) == 0)
1778 branch = L"refs/remotes/" + branch;
1779 else if (CStringUtils::StartsWith(branch, L"remotes/"))
1780 branch = L"refs/" + branch;
1781 else
1782 branch = L"refs/heads/" + branch;
1783 list.push_back(branch);
1785 return -1;
1788 if (includeTags)
1790 CString cmd = L"git.exe tag --contains " + hash.ToString();
1791 if (Run(cmd, [&list](CStringA lineA)
1793 if (lineA.IsEmpty())
1794 return;
1795 list.push_back(L"refs/tags/" + CUnicodeUtils::GetUnicode(lineA));
1797 return -1;
1801 std::sort(list.begin() + prevCount, list.end(), LogicalCompareBranchesPredicate);
1802 list.erase(std::unique(list.begin() + prevCount, list.end()), list.end());
1804 return 0;
1807 int CGit::GetRemoteList(STRING_VECTOR &list)
1809 size_t prevCount = list.size();
1810 if (this->m_IsUseLibGit2)
1812 CAutoRepository repo(GetGitRepository());
1813 if (!repo)
1814 return -1;
1816 CAutoStrArray remotes;
1817 if (git_remote_list(remotes, repo))
1818 return -1;
1820 for (size_t i = 0; i < remotes->count; ++i)
1822 list.push_back(CUnicodeUtils::GetUnicode(remotes->strings[i]));
1825 std::sort(list.begin() + prevCount, list.end(), LogicalComparePredicate);
1827 return 0;
1830 return Run(L"git.exe remote", [&](const CStringA& lineA)
1832 if (lineA.IsEmpty())
1833 return;
1834 list.push_back(CUnicodeUtils::GetUnicode(lineA));
1838 int CGit::GetRemoteRefs(const CString& remote, REF_VECTOR& list, bool includeTags, bool includeBranches)
1840 if (!includeTags && !includeBranches)
1841 return 0;
1843 size_t prevCount = list.size();
1844 if (UsingLibGit2(GIT_CMD_FETCH))
1846 CAutoRepository repo(GetGitRepository());
1847 if (!repo)
1848 return -1;
1850 CStringA remoteA = CUnicodeUtils::GetUTF8(remote);
1851 CAutoRemote gitremote;
1852 // first try with a named remote (e.g. "origin")
1853 if (git_remote_lookup(gitremote.GetPointer(), repo, remoteA) < 0)
1855 // retry with repository located at a specific url
1856 if (git_remote_create_anonymous(gitremote.GetPointer(), repo, remoteA) < 0)
1857 return -1;
1860 git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
1861 callbacks.credentials = g_Git2CredCallback;
1862 callbacks.certificate_check = g_Git2CheckCertificateCallback;
1863 git_proxy_options proxy = GIT_PROXY_OPTIONS_INIT;
1864 proxy.type = GIT_PROXY_AUTO;
1865 if (git_remote_connect(gitremote, GIT_DIRECTION_FETCH, &callbacks, &proxy, nullptr) < 0)
1866 return -1;
1868 const git_remote_head** heads = nullptr;
1869 size_t size = 0;
1870 if (git_remote_ls(&heads, &size, gitremote) < 0)
1871 return -1;
1873 for (size_t i = 0; i < size; i++)
1875 CString ref = CUnicodeUtils::GetUnicode(heads[i]->name);
1876 CString shortname;
1877 if (GetShortName(ref, shortname, L"refs/tags/"))
1879 if (!includeTags)
1880 continue;
1882 else
1884 if (!includeBranches)
1885 continue;
1886 if (!GetShortName(ref, shortname, L"refs/heads/"))
1887 shortname = ref;
1889 if (includeTags && includeBranches)
1890 list.emplace_back(TGitRef{ ref, &heads[i]->oid });
1891 else
1892 list.emplace_back(TGitRef{ shortname, &heads[i]->oid });
1894 std::sort(list.begin() + prevCount, list.end(), g_bSortTagsReversed && includeTags && !includeBranches ? LogicalCompareReversedPredicate : LogicalComparePredicate);
1895 return 0;
1898 CString cmd;
1899 cmd.Format(L"git.exe ls-remote%s \"%s\"", (includeTags && !includeBranches) ? L" -t" : L" --refs", static_cast<LPCWSTR>(remote));
1900 gitLastErr = cmd + L'\n';
1901 if (Run(
1902 cmd, [&](CStringA lineA) {
1903 CGitHash hash = CGitHash::FromHexStr(lineA.Left(GIT_HASH_SIZE * 2));
1904 lineA = lineA.Mid(GIT_HASH_SIZE * 2 + static_cast<int>(wcslen(L"\t"))); // sha1, tab
1905 if (lineA.IsEmpty())
1906 return;
1907 CString ref = CUnicodeUtils::GetUnicode(lineA);
1908 CString shortname;
1909 if (GetShortName(ref, shortname, L"refs/tags/"))
1911 if (!includeTags)
1912 return;
1914 else
1916 if (!includeBranches)
1917 return;
1918 if (!GetShortName(ref, shortname, L"refs/heads/"))
1919 shortname = ref;
1921 if (includeTags && includeBranches)
1922 list.emplace_back(TGitRef{ ref, hash });
1923 else if (includeTags && CStringUtils::EndsWith(ref, L"^{}"))
1924 list.emplace_back(TGitRef{ shortname + L"^{}", hash });
1925 else
1926 list.emplace_back(TGitRef{ shortname, hash });
1928 &gitLastErr))
1929 return -1;
1930 std::sort(list.begin() + prevCount, list.end(), g_bSortTagsReversed && includeTags && !includeBranches ? LogicalCompareReversedPredicate : LogicalComparePredicate);
1931 return 0;
1934 int CGit::DeleteRemoteRefs(const CString& sRemote, const STRING_VECTOR& list)
1936 if (UsingLibGit2(GIT_CMD_PUSH))
1938 CAutoRepository repo(GetGitRepository());
1939 if (!repo)
1940 return -1;
1942 CStringA remoteA = CUnicodeUtils::GetUTF8(sRemote);
1943 CAutoRemote remote;
1944 if (git_remote_lookup(remote.GetPointer(), repo, remoteA) < 0)
1945 return -1;
1947 git_push_options pushOpts = GIT_PUSH_OPTIONS_INIT;
1948 git_remote_callbacks& callbacks = pushOpts.callbacks;
1949 callbacks.credentials = g_Git2CredCallback;
1950 callbacks.certificate_check = g_Git2CheckCertificateCallback;
1951 std::vector<CStringA> refspecs;
1952 refspecs.reserve(list.size());
1953 std::transform(list.cbegin(), list.cend(), std::back_inserter(refspecs), [](auto& ref) { return CUnicodeUtils::GetUTF8(L":" + ref); });
1955 std::vector<char*> vc;
1956 vc.reserve(refspecs.size());
1957 std::transform(refspecs.begin(), refspecs.end(), std::back_inserter(vc), [](CStringA& s) -> char* { return s.GetBuffer(); });
1958 git_strarray specs = { vc.data(), vc.size() };
1960 if (git_remote_push(remote, &specs, &pushOpts) < 0)
1961 return -1;
1963 else
1965 CMassiveGitTaskBase mgtPush(L"push " + sRemote, FALSE);
1966 for (const auto& ref : list)
1967 mgtPush.AddFile(L':' + ref);
1969 BOOL cancel = FALSE;
1970 mgtPush.Execute(cancel);
1973 return 0;
1976 int libgit2_addto_list_each_ref_fn(git_reference *ref, void *payload)
1978 auto list = static_cast<STRING_VECTOR*>(payload);
1979 list->push_back(CUnicodeUtils::GetUnicode(git_reference_name(ref)));
1980 return 0;
1983 int CGit::GetRefList(STRING_VECTOR &list)
1985 size_t prevCount = list.size();
1986 if (this->m_IsUseLibGit2)
1988 CAutoRepository repo(GetGitRepository());
1989 if (!repo)
1990 return -1;
1992 if (git_reference_foreach(repo, libgit2_addto_list_each_ref_fn, &list))
1993 return -1;
1995 std::sort(list.begin() + prevCount, list.end(), LogicalComparePredicate);
1997 return 0;
2000 int ret = Run(L"git.exe show-ref -d", [&](const CStringA& lineA)
2002 int start = lineA.Find(L' ');
2003 ASSERT(start == 2 * GIT_HASH_SIZE);
2004 if (start <= 0)
2005 return;
2007 CString name = CUnicodeUtils::GetUnicode(lineA.Mid(start + 1));
2008 if (list.empty() || name != *list.crbegin() + L"^{}")
2009 list.push_back(name);
2011 if (!ret)
2012 std::sort(list.begin() + prevCount, list.end(), LogicalComparePredicate);
2013 else if (ret == 1 && IsInitRepos())
2014 return 0;
2015 return ret;
2018 typedef struct map_each_ref_payload {
2019 git_repository * repo;
2020 MAP_HASH_NAME * map;
2021 } map_each_ref_payload;
2023 int libgit2_addto_map_each_ref_fn(git_reference *ref, void *payload)
2025 auto payloadContent = static_cast<map_each_ref_payload*>(payload);
2027 CString str = CUnicodeUtils::GetUnicode(git_reference_name(ref));
2029 CAutoObject gitObject;
2030 if (git_revparse_single(gitObject.GetPointer(), payloadContent->repo, git_reference_name(ref)))
2031 return (git_reference_is_remote(ref) && git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) ? 0 : 1; // don't bail out for symbolic remote references ("git.exe show-ref -d" also doesn't complain), cf. issue #2926
2033 if (git_object_type(gitObject) == GIT_OBJECT_TAG)
2035 str += L"^{}"; // deref tag
2036 CAutoObject derefedTag;
2037 if (git_tag_target(derefedTag.GetPointer(), reinterpret_cast<git_tag*>(static_cast<git_object*>(gitObject))))
2038 return 1;
2039 gitObject.Swap(derefedTag);
2042 (*payloadContent->map)[git_object_id(gitObject)].push_back(str);
2044 return 0;
2046 int CGit::GetMapHashToFriendName(git_repository* repo, MAP_HASH_NAME &map)
2048 ATLASSERT(repo);
2050 map_each_ref_payload payloadContent = { repo, &map };
2052 if (git_reference_foreach(repo, libgit2_addto_map_each_ref_fn, &payloadContent))
2053 return -1;
2055 for (auto it = map.begin(); it != map.end(); ++it)
2057 std::sort(it->second.begin(), it->second.end());
2060 return 0;
2063 int CGit::GetMapHashToFriendName(MAP_HASH_NAME &map)
2065 if (this->m_IsUseLibGit2)
2067 CAutoRepository repo(GetGitRepository());
2068 if (!repo)
2069 return -1;
2071 return GetMapHashToFriendName(repo, map);
2074 int ret = Run(L"git.exe show-ref -d", [&](const CStringA& lineA)
2076 int start = lineA.Find(L' ');
2077 ASSERT(start == 2 * GIT_HASH_SIZE);
2078 if (start <= 0)
2079 return;
2081 CGitHash hash = CGitHash::FromHexStr(lineA);
2082 map[hash].push_back(CUnicodeUtils::GetUnicode(lineA.Mid(start + 1)));
2085 if (ret == 1 && IsInitRepos())
2086 return 0;
2087 return ret;
2090 int CGit::GuessRefForHash(CString& ref, const CGitHash& hash)
2092 MAP_HASH_NAME map;
2093 if (GetMapHashToFriendName(map))
2094 return -1;
2096 auto it = map.find(hash);
2097 if (it == map.cend())
2099 ref = hash.ToString(GetShortHASHLength());
2100 return 1;
2103 const auto& reflist = it->second;
2104 for (const auto& reftype : { L"refs/heads/", L"refs/remotes/", L"refs/tags/" })
2106 auto found = std::find_if(reflist.cbegin(), reflist.cend(), [&reftype](const auto& ref) { return CStringUtils::StartsWith(ref, reftype); });
2107 if (found == reflist.cend())
2108 continue;
2110 GetShortName(*found, ref, reftype);
2111 return 0;
2114 ref = hash.ToString(GetShortHASHLength());
2115 return 1;
2118 int CGit::GetBranchDescriptions(MAP_STRING_STRING& map)
2120 CAutoConfig config(true);
2121 if (git_config_add_file_ondisk(config, CGit::GetGitPathStringA(GetGitLocalConfig()), GIT_CONFIG_LEVEL_LOCAL, nullptr, FALSE) < 0)
2122 return -1;
2123 return git_config_foreach_match(config, "^branch\\..*\\.description$", [](const git_config_entry* entry, void* data)
2125 auto descriptions = static_cast<MAP_STRING_STRING*>(data);
2126 CString key = CUnicodeUtils::GetUnicode(entry->name);
2127 // extract branch name from config key
2128 key = key.Mid(static_cast<int>(wcslen(L"branch.")), key.GetLength() - static_cast<int>(wcslen(L"branch.")) - static_cast<int>(wcslen(L".description")));
2129 descriptions->insert(std::make_pair(key, CUnicodeUtils::GetUnicode(entry->value)));
2130 return 0;
2131 }, &map);
2134 static void SetLibGit2SearchPath(int level, const CString &value)
2136 CStringA valueA = CUnicodeUtils::GetUTF8(value);
2137 git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, level, static_cast<LPCSTR>(valueA));
2140 static void SetLibGit2TemplatePath(const CString &value)
2142 CStringA valueA = CUnicodeUtils::GetUTF8(value);
2143 git_libgit2_opts(GIT_OPT_SET_TEMPLATE_PATH, static_cast<LPCSTR>(valueA));
2146 int CGit::FindAndSetGitExePath(BOOL bFallback)
2148 CRegString msysdir = CRegString(REG_MSYSGIT_PATH, L"", FALSE);
2149 CString str = msysdir;
2150 if (!str.IsEmpty() && PathFileExists(str + L"\\git.exe"))
2152 CGit::ms_LastMsysGitDir = str;
2153 return TRUE;
2156 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": git.exe not exists: %s\n", static_cast<LPCWSTR>(CGit::ms_LastMsysGitDir));
2157 if (!bFallback)
2158 return FALSE;
2160 // first, search PATH if git/bin directory is already present
2161 if (FindGitPath())
2163 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": FindGitPath() => %s\n", static_cast<LPCWSTR>(CGit::ms_LastMsysGitDir));
2164 msysdir = CGit::ms_LastMsysGitDir;
2165 msysdir.write();
2166 return TRUE;
2169 if (FindGitForWindows(str))
2171 msysdir = str;
2172 CGit::ms_LastMsysGitDir = str;
2173 msysdir.write();
2174 return TRUE;
2177 return FALSE;
2180 BOOL CGit::CheckMsysGitDir(BOOL bFallback)
2182 if (m_bInitialized)
2183 return TRUE;
2185 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": CheckMsysGitDir(%d)\n", bFallback);
2186 this->m_Environment.clear();
2187 m_Environment.CopyProcessEnvironment();
2189 // 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,
2190 // because MSys2 changed the default to "ASCII". SO, make sure we have a proper default set
2191 if (m_Environment.GetEnv(L"LC_ALL").IsEmpty())
2192 m_Environment.SetEnv(L"LC_ALL", L"C");
2194 // set HOME if not set already
2195 size_t homesize;
2196 _wgetenv_s(&homesize, nullptr, 0, L"HOME");
2197 if (!homesize)
2198 m_Environment.SetEnv(L"HOME", GetHomeDirectory());
2200 //setup ssh client
2201 CString sshclient = CRegString(L"Software\\TortoiseGit\\SSH");
2202 if (sshclient.IsEmpty())
2203 sshclient = CRegString(L"Software\\TortoiseGit\\SSH", L"", FALSE, HKEY_LOCAL_MACHINE);
2205 if(!sshclient.IsEmpty())
2207 if (ms_bCygwinGit)
2208 sshclient.Replace(L'\\', L'/');
2209 m_Environment.SetEnv(L"GIT_SSH", sshclient);
2210 if (CStringUtils::EndsWithI(sshclient, L"tortoisegitplink") || CStringUtils::EndsWithI(sshclient, L"tortoisegitplink.exe"))
2211 m_Environment.SetEnv(L"GIT_SSH_VARIANT", L"ssh");
2212 m_Environment.SetEnv(L"SVN_SSH", sshclient);
2214 else
2216 wchar_t sPlink[MAX_PATH] = { 0 };
2217 GetModuleFileName(nullptr, sPlink, _countof(sPlink));
2218 LPWSTR ptr = wcsrchr(sPlink, L'\\');
2219 if (ptr) {
2220 wcscpy_s(ptr + 1, _countof(sPlink) - (ptr - sPlink + 1), L"TortoiseGitPlink.exe");
2221 if (ms_bCygwinGit)
2222 CPathUtils::ConvertToSlash(sPlink);
2223 m_Environment.SetEnv(L"GIT_SSH", sPlink);
2224 m_Environment.SetEnv(L"GIT_SSH_VARIANT", L"ssh");
2225 m_Environment.SetEnv(L"SVN_SSH", sPlink);
2230 wchar_t sAskPass[MAX_PATH] = { 0 };
2231 GetModuleFileName(nullptr, sAskPass, _countof(sAskPass));
2232 LPWSTR ptr = wcsrchr(sAskPass, L'\\');
2233 if (ptr)
2235 wcscpy_s(ptr + 1, _countof(sAskPass) - (ptr - sAskPass + 1), L"SshAskPass.exe");
2236 if (ms_bCygwinGit)
2237 CPathUtils::ConvertToSlash(sAskPass);
2238 m_Environment.SetEnv(L"DISPLAY",L":9999");
2239 m_Environment.SetEnv(L"SSH_ASKPASS",sAskPass);
2240 m_Environment.SetEnv(L"GIT_ASKPASS",sAskPass);
2241 m_Environment.SetEnv(L"GIT_ASK_YESNO", sAskPass);
2245 if (!FindAndSetGitExePath(bFallback))
2246 return FALSE;
2248 CString msysGitDir;
2249 PathCanonicalize(CStrBuf(msysGitDir, MAX_PATH), CGit::ms_LastMsysGitDir + L"\\..\\");
2250 static const CString prefixes[] = { L"mingw64\\etc", L"mingw32\\etc", L"etc" };
2251 static const int prefixes_len[] = { 8, 8, 0 };
2252 for (int i = 0; i < _countof(prefixes); ++i)
2254 #ifndef _WIN64
2255 if (i == 0)
2256 continue;
2257 #endif
2258 if (PathIsDirectory(msysGitDir + prefixes[i])) {
2259 msysGitDir += prefixes[i].Left(prefixes_len[i]);
2260 break;
2263 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
2264 PathCanonicalize(CStrBuf(msysGitDir, MAX_PATH), CGit::ms_LastMsysGitDir + L"\\..\\..");
2265 CGit::ms_MsysGitRootDir = msysGitDir;
2267 if (static_cast<CString>(CRegString(REG_SYSTEM_GITCONFIGPATH, L"", FALSE)) != g_Git.GetGitSystemConfig())
2268 CRegString(REG_SYSTEM_GITCONFIGPATH, L"", FALSE) = g_Git.GetGitSystemConfig();
2270 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": ms_LastMsysGitDir = %s\n", static_cast<LPCWSTR>(CGit::ms_LastMsysGitDir));
2271 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": ms_MsysGitRootDir = %s\n", static_cast<LPCWSTR>(CGit::ms_MsysGitRootDir));
2272 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": System config = %s\n", static_cast<LPCWSTR>(g_Git.GetGitSystemConfig()));
2273 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_PROGRAMDATA, L"");
2275 if (ms_bCygwinGit)
2276 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": CygwinHack: true\n");
2277 if (ms_bMsys2Git)
2278 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": Msys2Hack: true\n");
2280 // Configure libgit2 search paths
2281 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_SYSTEM, CTGitPath(g_Git.GetGitSystemConfig()).GetContainingDirectory().GetWinPathString());
2282 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_GLOBAL, g_Git.GetHomeDirectory());
2283 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_XDG, g_Git.GetGitGlobalXDGConfigPath());
2284 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 };
2285 git_transport_register("ssh", git_transport_smart, &ssh_wintunnel_subtransport_definition);
2286 git_transport_register("ssh+git", git_transport_smart, &ssh_wintunnel_subtransport_definition);
2287 git_transport_register("git+ssh", git_transport_smart, &ssh_wintunnel_subtransport_definition);
2288 git_libgit2_opts(GIT_OPT_SET_USER_AGENT, "TortoiseGit libgit2");
2289 if (!(ms_bCygwinGit || ms_bMsys2Git))
2290 SetLibGit2TemplatePath(CGit::ms_MsysGitRootDir + L"share\\git-core\\templates");
2291 else
2292 SetLibGit2TemplatePath(CGit::ms_MsysGitRootDir + L"usr\\share\\git-core\\templates");
2294 m_Environment.AddToPath(CGit::ms_LastMsysGitDir);
2295 m_Environment.AddToPath(static_cast<CString>(CRegString(REG_MSYSGIT_EXTRA_PATH, L"", FALSE)));
2297 #if !defined(TGITCACHE) && !defined(TORTOISESHELL)
2298 // register filter only once
2299 if (!git_filter_lookup("filter"))
2301 CString sh;
2302 for (const CString& binDirPrefix : { L"\\..\\usr\\bin", L"\\..\\bin", L"" })
2304 CString possibleShExe = CGit::ms_LastMsysGitDir + binDirPrefix + L"\\sh.exe";
2305 if (PathFileExists(possibleShExe))
2307 CString temp;
2308 PathCanonicalize(CStrBuf(temp, MAX_PATH), possibleShExe);
2309 sh.Format(L"\"%s\"", static_cast<LPCWSTR>(temp));
2310 // we need to put the usr\bin folder on the path for Git for Windows based on msys2
2311 m_Environment.AddToPath(temp.Left(temp.GetLength() - static_cast<int>(wcslen(L"\\sh.exe"))));
2312 break;
2316 // Add %GIT_EXEC_PATH% to %PATH% when launching libgit2 filter executable
2317 // It is possible that the filter points to a git subcommand, that is located at libexec\git-core
2318 CString gitExecPath = CGit::ms_MsysGitRootDir;
2319 gitExecPath.Append(L"libexec\\git-core");
2320 m_Environment.AddToPath(gitExecPath);
2322 if (git_filter_register("filter", git_filter_filter_new(sh, m_Environment), GIT_FILTER_DRIVER_PRIORITY))
2323 return FALSE;
2325 #endif
2327 m_bInitialized = TRUE;
2328 return true;
2331 CString CGit::GetHomeDirectory() const
2333 const wchar_t * homeDir = wget_windows_home_directory();
2334 return homeDir;
2337 CString CGit::GetGitLocalConfig() const
2339 CString path;
2340 GitAdminDir::GetAdminDirPath(m_CurrentDir, path);
2341 path += L"config";
2342 return path;
2345 CStringA CGit::GetGitPathStringA(const CString &path)
2347 return CUnicodeUtils::GetUTF8(CTGitPath(path).GetGitPathString());
2350 CString CGit::GetGitGlobalConfig() const
2352 return g_Git.GetHomeDirectory() + L"\\.gitconfig";
2355 CString CGit::GetGitGlobalXDGConfigPath() const
2357 return g_Git.GetHomeDirectory() + L"\\.config\\git";
2360 CString CGit::GetGitGlobalXDGConfig() const
2362 return g_Git.GetGitGlobalXDGConfigPath() + L"\\config";
2365 CString CGit::GetGitSystemConfig() const
2367 const wchar_t * systemConfig = wget_msysgit_etc();
2368 return systemConfig;
2371 BOOL CGit::CheckCleanWorkTree(bool stagedOk /* false */)
2373 if (UsingLibGit2(GIT_CMD_CHECK_CLEAN_WT))
2375 CAutoRepository repo(GetGitRepository());
2376 if (!repo)
2377 return FALSE;
2379 if (git_repository_head_unborn(repo))
2380 return FALSE;
2382 git_status_options statusopt = GIT_STATUS_OPTIONS_INIT;
2383 statusopt.show = stagedOk ? GIT_STATUS_SHOW_WORKDIR_ONLY : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
2384 statusopt.flags = GIT_STATUS_OPT_UPDATE_INDEX | GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
2386 CAutoStatusList status;
2387 if (git_status_list_new(status.GetPointer(), repo, &statusopt))
2388 return FALSE;
2390 return (0 == git_status_list_entrycount(status));
2393 if (Run(L"git.exe rev-parse --verify HEAD", nullptr, nullptr, CP_UTF8))
2394 return FALSE;
2396 if (Run(L"git.exe update-index --ignore-submodules --refresh", nullptr, nullptr, CP_UTF8))
2397 return FALSE;
2399 if (Run(L"git.exe diff-files --quiet --ignore-submodules", nullptr, nullptr, CP_UTF8))
2400 return FALSE;
2402 if (!stagedOk && Run(L"git.exe diff-index --cached --quiet HEAD --ignore-submodules --", nullptr, nullptr, CP_UTF8))
2403 return FALSE;
2405 return TRUE;
2408 BOOL CGit::IsResultingCommitBecomeEmpty(bool amend /* = false */)
2410 CString cmd;
2411 cmd.Format(L"git.exe diff-index --cached --quiet HEAD%s --", amend ? L"~1" : L"");
2412 return Run(cmd, nullptr, nullptr, CP_UTF8) == 0;
2415 int CGit::Revert(const CString& commit, const CTGitPathList &list, CString& err)
2417 for (int i = 0; i < list.GetCount(); ++i)
2419 if (Revert(commit, list[i], err))
2420 return -1;
2422 return 0;
2424 int CGit::Revert(const CString& commit, const CTGitPath &path, CString& err)
2426 if(path.m_Action & CTGitPath::LOGACTIONS_REPLACED && !path.GetGitOldPathString().IsEmpty())
2428 if (CTGitPath(path.GetGitOldPathString()).IsDirectory())
2430 err.Format(L"Cannot revert renaming of \"%s\". A directory with the old name \"%s\" exists.", static_cast<LPCWSTR>(path.GetGitPathString()), static_cast<LPCWSTR>(path.GetGitOldPathString()));
2431 return -1;
2433 if (path.Exists())
2435 CString force;
2436 // if the filenames only differ in case, we have to pass "-f"
2437 if (path.GetGitPathString().CompareNoCase(path.GetGitOldPathString()) == 0)
2438 force = L"-f ";
2439 CString cmd;
2440 cmd.Format(L"git.exe mv %s-- \"%s\" \"%s\"", static_cast<LPCWSTR>(force), static_cast<LPCWSTR>(path.GetGitPathString()), static_cast<LPCWSTR>(path.GetGitOldPathString()));
2441 if (Run(cmd, &err, CP_UTF8))
2442 return -1;
2444 else
2446 CString cmd;
2447 cmd.Format(L"git.exe rm -f --cached -- \"%s\"", static_cast<LPCWSTR>(path.GetGitPathString()));
2448 if (Run(cmd, &err, CP_UTF8))
2449 return -1;
2452 CString cmd;
2453 cmd.Format(L"git.exe checkout %s -f -- \"%s\"", static_cast<LPCWSTR>(commit), static_cast<LPCWSTR>(path.GetGitOldPathString()));
2454 if (Run(cmd, &err, CP_UTF8))
2455 return -1;
2457 else if(path.m_Action & CTGitPath::LOGACTIONS_ADDED)
2458 { //To init git repository, there are not HEAD, so we can use git reset command
2459 CString cmd;
2460 cmd.Format(L"git.exe rm -f --cached -- \"%s\"", static_cast<LPCWSTR>(path.GetGitPathString()));
2462 if (Run(cmd, &err, CP_UTF8))
2463 return -1;
2465 else
2467 CString cmd;
2468 cmd.Format(L"git.exe checkout %s -f -- \"%s\"", static_cast<LPCWSTR>(commit), static_cast<LPCWSTR>(path.GetGitPathString()));
2469 if (Run(cmd, &err, CP_UTF8))
2470 return -1;
2473 if (path.m_Action & CTGitPath::LOGACTIONS_DELETED)
2475 CString cmd;
2476 cmd.Format(L"git.exe add -f -- \"%s\"", static_cast<LPCWSTR>(path.GetGitPathString()));
2477 if (Run(cmd, &err, CP_UTF8))
2478 return -1;
2481 return 0;
2484 int CGit::HasWorkingTreeConflicts(git_repository* repo)
2486 ATLASSERT(repo);
2488 CAutoIndex index;
2489 if (git_repository_index(index.GetPointer(), repo))
2490 return -1;
2492 return git_index_has_conflicts(index);
2495 int CGit::HasWorkingTreeConflicts()
2497 if (UsingLibGit2(GIT_CMD_CHECKCONFLICTS))
2499 CAutoRepository repo(GetGitRepository());
2500 if (!repo)
2501 return -1;
2503 return HasWorkingTreeConflicts(repo);
2506 CString output;
2507 gitLastErr.Empty();
2508 if (Run(L"git.exe ls-files -u -t -z", &output, &gitLastErr, CP_UTF8))
2509 return -1;
2511 return output.IsEmpty() ? 0 : 1;
2514 bool CGit::IsFastForward(const CString &from, const CString &to, CGitHash * commonAncestor)
2516 if (UsingLibGit2(GIT_CMD_MERGE_BASE))
2518 CAutoRepository repo(GetGitRepository());
2519 if (!repo)
2520 return false;
2522 CGitHash fromHash, toHash, baseHash;
2523 if (GetHash(repo, toHash, FixBranchName(to)))
2524 return false;
2526 if (GetHash(repo, fromHash, FixBranchName(from)))
2527 return false;
2529 git_oid baseOid;
2530 if (git_merge_base(&baseOid, repo, toHash, fromHash))
2531 return false;
2533 baseHash = baseOid;
2535 if (commonAncestor)
2536 *commonAncestor = baseHash;
2538 return fromHash == baseHash;
2540 // else
2541 CString base;
2542 CGitHash basehash,hash;
2543 CString cmd;
2544 cmd.Format(L"git.exe merge-base %s %s", static_cast<LPCWSTR>(FixBranchName(to)), static_cast<LPCWSTR>(FixBranchName(from)));
2546 gitLastErr.Empty();
2547 if (Run(cmd, &base, &gitLastErr, CP_UTF8))
2548 return false;
2549 basehash = CGitHash::FromHexStrTry(base.Trim());
2551 GetHash(hash, from);
2553 if (commonAncestor)
2554 *commonAncestor = basehash;
2556 return hash == basehash;
2559 unsigned int CGit::Hash2int(const CGitHash &hash)
2561 int ret=0;
2562 for (int i = 0; i < 4; ++i)
2564 ret = ret << 8;
2565 ret |= hash.ToRaw()[i];
2567 return ret;
2570 int CGit::RefreshGitIndex()
2572 CString adminDir;
2573 GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir);
2574 // HACK: don't use internal update-index if we have a git-lfs enabled repository as the libgit version fails when executing the filter, issue #3220
2575 if (g_Git.m_IsUseGitDLL && !PathFileExists(adminDir + L"lfs"))
2577 CAutoLocker lock(g_Git.m_critGitDllSec);
2580 g_Git.CheckAndInitDll();
2582 int result = git_update_index();
2583 git_exit_cleanup();
2584 return result;
2586 }catch(...)
2588 git_exit_cleanup();
2589 return -1;
2593 else
2594 return Run(L"git.exe update-index --refresh", nullptr, nullptr, CP_UTF8);
2597 int CGit::GetOneFile(const CString &Refname, const CTGitPath &path, const CString &outputfile)
2599 if (UsingLibGit2(GIT_CMD_GETONEFILE))
2601 CAutoRepository repo(GetGitRepository());
2602 if (!repo)
2603 return -1;
2605 CGitHash hash;
2606 if (GetHash(repo, hash, Refname + L"^{}")) // add ^{} in order to dereference signed tags
2607 return -1;
2609 CAutoCommit commit;
2610 if (git_commit_lookup(commit.GetPointer(), repo, hash))
2611 return -1;
2613 CAutoTree tree;
2614 if (git_commit_tree(tree.GetPointer(), commit))
2615 return -1;
2617 CAutoTreeEntry entry;
2618 if (auto ret = git_tree_entry_bypath(entry.GetPointer(), tree, CUnicodeUtils::GetUTF8(path.GetGitPathString())); ret)
2619 return ret;
2621 if (git_tree_entry_filemode(entry) == GIT_FILEMODE_COMMIT)
2623 git_error_set_str(GIT_ERROR_NONE, "The requested object is a submodule and not a file.");
2624 return -1;
2627 CAutoBlob blob;
2628 if (git_tree_entry_to_object(reinterpret_cast<git_object**>(blob.GetPointer()), repo, entry))
2629 return -1;
2631 CAutoFILE file = _wfsopen(outputfile, L"wb", SH_DENYWR);
2632 if (file == nullptr)
2634 git_error_set_str(GIT_ERROR_NONE, "Could not create file.");
2635 return -1;
2637 CAutoBuf buf;
2638 git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT;
2639 opts.flags &= ~static_cast<uint32_t>(GIT_BLOB_FILTER_CHECK_FOR_BINARY);
2640 if (git_blob_filter(buf, blob, CUnicodeUtils::GetUTF8(path.GetGitPathString()), &opts))
2641 return -1;
2642 if (fwrite(buf->ptr, sizeof(char), buf->size, file) != buf->size)
2644 git_error_set_str(GIT_ERROR_OS, "Could not write to file.");
2645 return -1;
2648 return 0;
2650 else if (g_Git.m_IsUseGitDLL)
2652 CAutoLocker lock(g_Git.m_critGitDllSec);
2655 g_Git.CheckAndInitDll();
2656 CStringA ref, patha, outa;
2657 ref = CUnicodeUtils::GetUTF8(Refname);
2658 patha = CUnicodeUtils::GetUTF8(path.GetGitPathString());
2659 outa = CUnicodeUtils::GetUTF8(outputfile);
2660 ::DeleteFile(outputfile);
2661 int ret = git_checkout_file(ref, patha, outa.GetBuffer());
2662 outa.ReleaseBuffer();
2663 return ret;
2666 catch (const char * msg)
2668 gitLastErr = L"gitdll.dll reports: " + CString(msg);
2669 return -1;
2671 catch (...)
2673 gitLastErr = L"An unknown gitdll.dll error occurred.";
2674 return -1;
2677 else
2679 CString cmd;
2680 cmd.Format(L"git.exe cat-file -p %s:\"%s\"", static_cast<LPCWSTR>(Refname), static_cast<LPCWSTR>(path.GetGitPathString()));
2681 gitLastErr.Empty();
2682 return RunLogFile(cmd, outputfile, &gitLastErr);
2686 void CEnvironment::clear()
2688 __super::clear();
2689 baseptr = nullptr;
2692 bool CEnvironment::empty()
2694 return size() < 3; // three is minimum for an empty environment with an empty key and empty value: "=\0\0"
2697 CEnvironment::operator LPWSTR()
2699 if (empty())
2700 return nullptr;
2701 return data();
2704 CEnvironment::operator LPWSTR*()
2706 return &baseptr;
2709 void CEnvironment::CopyProcessEnvironment()
2711 if (!empty())
2712 pop_back();
2713 wchar_t* porig = GetEnvironmentStrings();
2714 wchar_t* p = porig;
2715 while(*p !=0 || *(p+1) !=0)
2716 this->push_back(*p++);
2718 push_back(L'\0');
2719 push_back(L'\0');
2720 baseptr = data();
2722 FreeEnvironmentStrings(porig);
2725 CString CEnvironment::GetEnv(const wchar_t* name)
2727 CString str;
2728 for (size_t i = 0; i < size(); ++i)
2730 str = &(*this)[i];
2731 int start =0;
2732 CString sname = str.Tokenize(L"=", start);
2733 if(sname.CompareNoCase(name) == 0)
2734 return &(*this)[i+start];
2735 i+=str.GetLength();
2737 return L"";
2740 void CEnvironment::SetEnv(const wchar_t* name, const wchar_t* value)
2742 unsigned int i;
2743 for (i = 0; i < size(); ++i)
2745 CString str = &(*this)[i];
2746 int start =0;
2747 CString sname = str.Tokenize(L"=", start);
2748 if(sname.CompareNoCase(name) == 0)
2749 break;
2750 i+=str.GetLength();
2753 if(i == size())
2755 if (!value) // as we haven't found the variable we want to remove, just return
2756 return;
2757 if (i == 0) // make inserting into an empty environment work
2759 this->push_back(L'\0');
2760 ++i;
2762 i -= 1; // roll back terminate \0\0
2763 this->push_back(L'\0');
2766 CEnvironment::iterator it;
2767 it=this->begin();
2768 it += i;
2770 while(*it && i<size())
2772 this->erase(it);
2773 it=this->begin();
2774 it += i;
2777 if (value == nullptr) // remove the variable
2779 this->erase(it);
2780 if (empty())
2781 baseptr = nullptr;
2782 else
2783 baseptr = data();
2784 return;
2787 while(*name)
2789 this->insert(it,*name++);
2790 ++i;
2791 it= begin()+i;
2794 this->insert(it, L'=');
2795 ++i;
2796 it= begin()+i;
2798 while(*value)
2800 this->insert(it,*value++);
2801 ++i;
2802 it= begin()+i;
2804 baseptr = data();
2807 void CEnvironment::AddToPath(CString value)
2809 value.TrimRight(L'\\');
2810 if (value.IsEmpty())
2811 return;
2813 CString path = GetEnv(L"PATH").TrimRight(L';') + L';';
2815 // do not double add paths to %PATH%
2816 if (path.Find(value + L';') >= 0 || path.Find(value + L"\\;") >= 0)
2817 return;
2819 path += value;
2821 SetEnv(L"PATH", path);
2824 int CGit::GetShortHASHLength() const
2826 return 8;
2829 CString CGit::GetShortName(const CString& ref, REF_TYPE *out_type)
2831 CString str=ref;
2832 CString shortname;
2833 REF_TYPE type = CGit::UNKNOWN;
2835 if (CGit::GetShortName(str, shortname, L"refs/heads/"))
2836 type = CGit::LOCAL_BRANCH;
2837 else if (CGit::GetShortName(str, shortname, L"refs/remotes/"))
2838 type = CGit::REMOTE_BRANCH;
2839 else if (CStringUtils::EndsWith(str, L"^{}") && CGit::GetShortName(str, shortname, L"refs/tags/"))
2840 type = CGit::ANNOTATED_TAG;
2841 else if (CGit::GetShortName(str, shortname, L"refs/tags/"))
2842 type = CGit::TAG;
2843 else if (CGit::GetShortName(str, shortname, L"refs/stash"))
2845 type = CGit::STASH;
2846 shortname = L"stash";
2848 else if (CGit::GetShortName(str, shortname, L"refs/bisect/"))
2850 CString bisectGood;
2851 CString bisectBad;
2852 g_Git.GetBisectTerms(&bisectGood, &bisectBad);
2853 wchar_t c;
2854 if (CStringUtils::StartsWith(shortname, bisectGood) && ((c = shortname.GetAt(bisectGood.GetLength())) == '-' || c == '\0'))
2856 type = CGit::BISECT_GOOD;
2857 shortname = bisectGood;
2860 if (CStringUtils::StartsWith(shortname, bisectBad) && ((c = shortname.GetAt(bisectBad.GetLength())) == '-' || c == '\0'))
2862 type = CGit::BISECT_BAD;
2863 shortname = bisectBad;
2866 if (CStringUtils::StartsWith(shortname, L"skip") && ((c = shortname.GetAt(4)) == '-' || c == '\0'))
2868 type = CGit::BISECT_SKIP;
2869 shortname = L"skip";
2872 else if (CGit::GetShortName(str, shortname, L"refs/notes/"))
2873 type = CGit::NOTES;
2874 else if (CGit::GetShortName(str, shortname, L"refs/"))
2875 type = CGit::UNKNOWN;
2876 else
2878 type = CGit::UNKNOWN;
2879 shortname = ref;
2882 if(out_type)
2883 *out_type = type;
2885 return shortname;
2888 bool CGit::UsingLibGit2(LIBGIT2_CMD cmd) const
2890 return m_IsUseLibGit2 && ((1 << cmd) & m_IsUseLibGit2_mask) ? true : false;
2893 void CGit::SetGit2CredentialCallback(void* callback)
2895 g_Git2CredCallback = static_cast<git_credential_acquire_cb>(callback);
2898 void CGit::SetGit2CertificateCheckCertificate(void* callback)
2900 g_Git2CheckCertificateCallback = static_cast<git_transport_certificate_check_cb>(callback);
2903 CString CGit::GetUnifiedDiffCmd(const CTGitPath& path, const CString& rev1, const CString& rev2, bool bMerge, bool bCombine, int diffContext, bool bNoPrefix)
2905 CString cmd;
2906 if (rev2 == GitRev::GetWorkingCopy())
2907 cmd.Format(L"git.exe diff --stat%s -p %s --", bNoPrefix ? L" --no-prefix" : L"", static_cast<LPCWSTR>(rev1));
2908 else if (rev1 == GitRev::GetWorkingCopy())
2909 cmd.Format(L"git.exe diff -R --stat%s -p %s --", bNoPrefix ? L" --no-prefix" : L"", static_cast<LPCWSTR>(rev2));
2910 else
2912 CString merge;
2913 if (bMerge)
2914 merge += L" -m";
2916 if (bCombine)
2917 merge += L" -c";
2919 CString unified;
2920 if (diffContext >= 0)
2921 unified.Format(L" --unified=%d", diffContext);
2922 cmd.Format(L"git.exe diff-tree -r -p%s%s --stat%s %s %s --", static_cast<LPCWSTR>(merge), static_cast<LPCWSTR>(unified), bNoPrefix ? L" --no-prefix" : L"", static_cast<LPCWSTR>(rev1), static_cast<LPCWSTR>(rev2));
2925 if (!path.IsEmpty())
2927 cmd += L" \"";
2928 cmd += path.GetGitPathString();
2929 cmd += L'"';
2932 return cmd;
2935 static void UnifiedDiffStatToFile(const git_buf* text, void* payload)
2937 ATLASSERT(payload && text);
2938 auto file = static_cast<FILE*>(payload);
2939 fwrite(text->ptr, 1, text->size, file);
2940 fwrite("\n", 1, 1, file);
2943 static int UnifiedDiffToFile(const git_diff_delta * /* delta */, const git_diff_hunk * /* hunk */, const git_diff_line * line, void *payload)
2945 ATLASSERT(payload && line);
2946 auto file = static_cast<FILE*>(payload);
2947 if (line->origin == GIT_DIFF_LINE_CONTEXT || line->origin == GIT_DIFF_LINE_ADDITION || line->origin == GIT_DIFF_LINE_DELETION)
2948 fwrite(&line->origin, 1, 1, file);
2949 fwrite(line->content, 1, line->content_len, file);
2950 return 0;
2953 static int resolve_to_tree(git_repository *repo, const char *identifier, git_tree **tree)
2955 ATLASSERT(repo && identifier && tree);
2957 /* try to resolve identifier */
2958 CAutoObject obj;
2959 if (git_revparse_single(obj.GetPointer(), repo, identifier))
2960 return -1;
2962 if (obj == nullptr)
2963 return GIT_ENOTFOUND;
2965 int err = 0;
2966 switch (git_object_type(obj))
2968 case GIT_OBJECT_TREE:
2969 *tree = reinterpret_cast<git_tree*>(obj.Detach());
2970 break;
2971 case GIT_OBJECT_COMMIT:
2972 err = git_commit_tree(tree, reinterpret_cast<git_commit*>(static_cast<git_object*>(obj)));
2973 break;
2974 default:
2975 err = GIT_ENOTFOUND;
2978 return err;
2981 /* use libgit2 get unified diff */
2982 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)
2984 CStringA tree1 = CUnicodeUtils::GetUTF8(revNew);
2985 CStringA tree2 = CUnicodeUtils::GetUTF8(revOld);
2987 CAutoRepository repo(g_Git.GetGitRepository());
2988 if (!repo)
2989 return -1;
2991 int isHeadOrphan = git_repository_head_unborn(repo);
2992 if (isHeadOrphan == 1)
2993 return 0;
2994 else if (isHeadOrphan != 0)
2995 return -1;
2997 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
2998 CStringA pathA = CUnicodeUtils::GetUTF8(path.GetGitPathString());
2999 char *buf = pathA.GetBuffer();
3000 if (!pathA.IsEmpty())
3002 opts.pathspec.strings = &buf;
3003 opts.pathspec.count = 1;
3005 if (bNoPrefix)
3007 opts.new_prefix = "";
3008 opts.old_prefix = "";
3010 CAutoDiff diff;
3012 if (revNew == GitRev::GetWorkingCopy() || revOld == GitRev::GetWorkingCopy())
3014 CAutoTree t1;
3015 CAutoDiff diff2;
3017 if (revNew != GitRev::GetWorkingCopy() && resolve_to_tree(repo, tree1, t1.GetPointer()))
3018 return -1;
3020 if (revOld != GitRev::GetWorkingCopy() && resolve_to_tree(repo, tree2, t1.GetPointer()))
3021 return -1;
3023 if (git_diff_tree_to_index(diff.GetPointer(), repo, t1, nullptr, &opts))
3024 return -1;
3026 if (git_diff_index_to_workdir(diff2.GetPointer(), repo, nullptr, &opts))
3027 return -1;
3029 if (git_diff_merge(diff, diff2))
3030 return -1;
3032 else
3034 if (tree1.IsEmpty() && tree2.IsEmpty())
3035 return -1;
3037 if (tree1.IsEmpty())
3039 tree1 = tree2;
3040 tree2.Empty();
3043 CAutoTree t1;
3044 CAutoTree t2;
3045 if (!tree1.IsEmpty() && resolve_to_tree(repo, tree1, t1.GetPointer()))
3046 return -1;
3048 if (tree2.IsEmpty())
3050 /* don't check return value, there are not parent commit at first commit*/
3051 resolve_to_tree(repo, tree1 + "~1", t2.GetPointer());
3053 else if (resolve_to_tree(repo, tree2, t2.GetPointer()))
3054 return -1;
3055 if (git_diff_tree_to_tree(diff.GetPointer(), repo, t2, t1, &opts))
3056 return -1;
3059 CAutoDiffStats stats;
3060 if (git_diff_get_stats(stats.GetPointer(), diff))
3061 return -1;
3062 CAutoBuf statBuf;
3063 if (git_diff_stats_to_buf(statBuf, stats, GIT_DIFF_STATS_FULL, 0))
3064 return -1;
3065 statCallback(statBuf, data);
3067 for (size_t i = 0; i < git_diff_num_deltas(diff); ++i)
3069 CAutoPatch patch;
3070 if (git_patch_from_diff(patch.GetPointer(), diff, i))
3071 return -1;
3073 if (git_patch_print(patch, callback, data))
3074 return -1;
3077 pathA.ReleaseBuffer();
3079 return 0;
3082 int CGit::GetUnifiedDiff(const CTGitPath& path, const CString& rev1, const CString& rev2, CString patchfile, bool bMerge, bool bCombine, int diffContext, bool bNoPrefix)
3084 if (UsingLibGit2(GIT_CMD_DIFF))
3086 CAutoFILE file = _wfsopen(patchfile, L"wb", SH_DENYRW);
3087 if (!file)
3088 return -1;
3089 return GetUnifiedDiffLibGit2(path, rev1, rev2, UnifiedDiffStatToFile, UnifiedDiffToFile, file, bMerge, bNoPrefix);
3091 else
3093 CString cmd;
3094 cmd = GetUnifiedDiffCmd(path, rev1, rev2, bMerge, bCombine, diffContext, bNoPrefix);
3095 gitLastErr.Empty();
3096 return RunLogFile(cmd, patchfile, &gitLastErr);
3100 static void UnifiedDiffStatToStringA(const git_buf* text, void* payload)
3102 ATLASSERT(payload && text);
3103 auto str = static_cast<CStringA*>(payload);
3104 str->Append(text->ptr, static_cast<int>(text->size));
3105 str->AppendChar('\n');
3108 static int UnifiedDiffToStringA(const git_diff_delta * /*delta*/, const git_diff_hunk * /*hunk*/, const git_diff_line *line, void *payload)
3110 ATLASSERT(payload && line);
3111 auto str = static_cast<CStringA*>(payload);
3112 if (line->origin == GIT_DIFF_LINE_CONTEXT || line->origin == GIT_DIFF_LINE_ADDITION || line->origin == GIT_DIFF_LINE_DELETION)
3113 str->Append(&line->origin, 1);
3114 str->Append(line->content, static_cast<int>(line->content_len));
3115 return 0;
3118 int CGit::GetUnifiedDiff(const CTGitPath& path, const CString& rev1, const CString& rev2, CStringA* buffer, bool bMerge, bool bCombine, int diffContext)
3120 if (UsingLibGit2(GIT_CMD_DIFF))
3121 return GetUnifiedDiffLibGit2(path, rev1, rev2, UnifiedDiffStatToStringA, UnifiedDiffToStringA, buffer, bMerge, false);
3122 else
3124 CString cmd;
3125 cmd = GetUnifiedDiffCmd(path, rev1, rev2, bMerge, bCombine, diffContext);
3126 BYTE_VECTOR vector;
3127 int ret = Run(cmd, &vector);
3128 if (!vector.empty())
3130 vector.push_back(0); // vector is not NUL terminated
3131 buffer->Append(reinterpret_cast<const char*>(vector.data()));
3133 return ret;
3137 int CGit::GitRevert(int parent, const CGitHash &hash)
3139 if (UsingLibGit2(GIT_CMD_REVERT))
3141 CAutoRepository repo(GetGitRepository());
3142 if (!repo)
3143 return -1;
3145 CAutoCommit commit;
3146 if (git_commit_lookup(commit.GetPointer(), repo, hash))
3147 return -1;
3149 git_revert_options revert_opts = GIT_REVERT_OPTIONS_INIT;
3150 revert_opts.mainline = parent;
3151 int result = git_revert(repo, commit, &revert_opts);
3153 return !result ? 0 : -1;
3155 else
3157 CString cmd, merge;
3158 if (parent)
3159 merge.Format(L"-m %d ", parent);
3160 cmd.Format(L"git.exe revert --no-edit --no-commit %s%s", static_cast<LPCWSTR>(merge), static_cast<LPCWSTR>(hash.ToString()));
3161 gitLastErr = cmd + L'\n';
3162 if (Run(cmd, &gitLastErr, CP_UTF8))
3163 return -1;
3164 else
3166 gitLastErr.Empty();
3167 return 0;
3172 int CGit::DeleteRef(const CString& reference)
3174 if (UsingLibGit2(GIT_CMD_DELETETAGBRANCH))
3176 CAutoRepository repo(GetGitRepository());
3177 if (!repo)
3178 return -1;
3180 CStringA refA;
3181 if (CStringUtils::EndsWith(reference, L"^{}"))
3182 refA = CUnicodeUtils::GetUTF8(reference.Left(reference.GetLength() - static_cast<int>(wcslen(L"^{}"))));
3183 else
3184 refA = CUnicodeUtils::GetUTF8(reference);
3186 CAutoReference ref;
3187 if (git_reference_lookup(ref.GetPointer(), repo, refA))
3188 return -1;
3190 int result = -1;
3191 if (git_reference_is_tag(ref))
3192 result = git_tag_delete(repo, git_reference_shorthand(ref));
3193 else if (git_reference_is_branch(ref))
3194 result = git_branch_delete(ref);
3195 else if (git_reference_is_remote(ref))
3196 result = git_branch_delete(ref);
3197 else
3198 result = git_reference_delete(ref);
3200 return result;
3202 else
3204 CString cmd, shortname;
3205 if (GetShortName(reference, shortname, L"refs/heads/"))
3206 cmd.Format(L"git.exe branch -D -- %s", static_cast<LPCWSTR>(shortname));
3207 else if (GetShortName(reference, shortname, L"refs/tags/"))
3208 cmd.Format(L"git.exe tag -d -- %s", static_cast<LPCWSTR>(shortname));
3209 else if (GetShortName(reference, shortname, L"refs/remotes/"))
3210 cmd.Format(L"git.exe branch -r -D -- %s", static_cast<LPCWSTR>(shortname));
3211 else
3213 gitLastErr = L"unsupported reference type: " + reference;
3214 return -1;
3217 gitLastErr.Empty();
3218 if (Run(cmd, &gitLastErr, CP_UTF8))
3219 return -1;
3221 gitLastErr.Empty();
3222 return 0;
3226 bool CGit::LoadTextFile(const CString &filename, CString &msg)
3228 if (!PathFileExists(filename))
3229 return false;
3231 CAutoFILE pFile = _wfsopen(filename, L"rb", SH_DENYWR);
3232 if (!pFile)
3234 ::MessageBox(nullptr, L"Could not open " + filename, L"TortoiseGit", MB_ICONERROR);
3235 return true; // load no further files
3238 CStringA str;
3241 char s[8196] = { 0 };
3242 int read = static_cast<int>(fread(s, sizeof(char), sizeof(s), pFile));
3243 if (read == 0)
3244 break;
3245 str += CStringA(s, read);
3246 } while (true);
3247 msg += CUnicodeUtils::GetUnicode(str);
3248 msg.Replace(L"\r\n", L"\n");
3249 msg.TrimRight(L'\n');
3250 msg += L'\n';
3252 return true; // load no further files
3255 int CGit::GetWorkingTreeChanges(CTGitPathList& result, bool amend, const CTGitPathList* filterlist, bool includedStaged /* = false */, bool getStagingStatus /* = false */)
3257 if (IsInitRepos())
3258 return GetInitAddList(result, getStagingStatus);
3260 BYTE_VECTOR out;
3262 int count = 1;
3263 if (filterlist)
3264 count = filterlist->GetCount();
3265 ATLASSERT(count > 0);
3267 CString head = L"HEAD";
3268 if (amend)
3269 head = L"HEAD~1";
3271 CString gitStatusParams;
3272 if (ms_LastMsysGitVersion >= ConvertVersionToInt(2, 17, 0))
3273 gitStatusParams = L" --no-ahead-behind";
3275 for (int i = 0; i < count; ++i)
3277 ATLASSERT(!filterlist || !(*filterlist)[i].GetGitPathString().IsEmpty()); // pathspec must not be empty, be compatible with Git >= 2.16.0
3278 BYTE_VECTOR cmdout;
3279 CString cmd;
3280 if (ms_bCygwinGit || ms_bMsys2Git)
3282 // Prevent showing all files as modified when using cygwin's git
3283 if (!filterlist)
3284 cmd.Format(L"git.exe status%s --", static_cast<LPCWSTR>(gitStatusParams));
3285 else
3286 cmd.Format(L"git.exe status%s -- \"%s\"", static_cast<LPCWSTR>(gitStatusParams), static_cast<LPCWSTR>((*filterlist)[i].GetGitPathString()));
3287 Run(cmd, &cmdout);
3288 cmdout.clear();
3291 // also list staged files which will be in the commit
3292 if (includedStaged || !filterlist)
3293 Run(L"git.exe diff-index --cached --raw " + head + L" --numstat -C -M -z --", &cmdout);
3294 else
3296 cmd.Format(L"git.exe diff-index --cached --raw %s --numstat -C -M -z -- \"%s\"", static_cast<LPCWSTR>(head), static_cast<LPCWSTR>((*filterlist)[i].GetGitPathString()));
3297 Run(cmd, &cmdout);
3300 if (!filterlist)
3301 cmd.Format(L"git.exe diff-index --raw %s --numstat -C%d%% -M%d%% -z --", static_cast<LPCWSTR>(head), ms_iSimilarityIndexThreshold, ms_iSimilarityIndexThreshold);
3302 else
3303 cmd.Format(L"git.exe diff-index --raw %s --numstat -C%d%% -M%d%% -z -- \"%s\"", static_cast<LPCWSTR>(head), ms_iSimilarityIndexThreshold, ms_iSimilarityIndexThreshold, static_cast<LPCWSTR>((*filterlist)[i].GetGitPathString()));
3305 BYTE_VECTOR cmdErr;
3306 if (Run(cmd, &cmdout, &cmdErr))
3308 size_t last = cmdErr.RevertFind(0);
3309 CString str;
3310 if (last != BYTE_VECTOR::npos)
3311 CGit::StringAppend(&str, &cmdErr[last + 1], CP_UTF8, static_cast<int>(cmdErr.size() - last) - 1);
3312 else if (!cmdErr.empty())
3313 CGit::StringAppend(&str, cmdErr.data(), CP_UTF8, static_cast<int>(cmdErr.size()) - 1);
3314 else
3315 str.Format(L"\"%s\" exited with an error code, but did not output any error message", static_cast<LPCWSTR>(cmd));
3316 MessageBox(nullptr, str, L"TortoiseGit", MB_OK | MB_ICONERROR);
3319 out.append(cmdout, 0);
3321 result.ParserFromLog(out);
3323 if (getStagingStatus)
3325 for (int i = 0; i < count; ++i)
3327 // This will show staged files regardless of any filterlist, so that it has the same behavior that the commit window has when staging support is disabled
3328 BYTE_VECTOR cmdStagedUnfilteredOut, cmdUnstagedFilteredOut, cmdUnstagedUnfilteredOut;
3329 CString cmd = L"git.exe diff-index --cached --raw " + head + L" --numstat -C -M -z --";
3330 Run(cmd, &cmdStagedUnfilteredOut);
3332 CTGitPathList stagedUnfiltered;
3333 stagedUnfiltered.ParserFromLog(cmdStagedUnfilteredOut);
3335 cmd = L"git.exe diff-files --raw --numstat -C -M -z --";
3336 Run(cmd, &cmdUnstagedUnfilteredOut);
3338 if (filterlist)
3340 cmd.Format(L"git.exe diff-files --raw --numstat -C -M -z -- \"%s\"", static_cast<LPCTSTR>((*filterlist)[i].GetGitPathString()));
3341 Run(cmd, &cmdUnstagedFilteredOut);
3344 CTGitPathList unstagedUnfiltered, unstagedFiltered;
3345 unstagedUnfiltered.ParserFromLog(cmdUnstagedUnfilteredOut); // Necessary to detect partially staged files outside the filterlist
3346 if (filterlist)
3347 unstagedFiltered.ParserFromLog(cmdUnstagedFilteredOut);
3349 // File shows up both in the output of diff-index --cached and diff-files: partially staged
3350 // File shows up only in the output of diff-index --cached: totally staged
3351 // File shows up only in the output of diff-files: totally unstaged
3352 // TODO: This is inefficient. It would be better if ParserFromLog received the output of each command
3353 // separately and did this processing there, dropping the new function UpdateStagingStatusFromPath entirely.
3354 for (int j = 0; j < stagedUnfiltered.GetCount(); ++j)
3356 CString path = stagedUnfiltered[j].GetGitPathString();
3357 if (unstagedUnfiltered.LookForGitPath(path))
3358 result.UpdateStagingStatusFromPath(path, CTGitPath::StagingStatus::PartiallyStaged);
3359 else
3360 result.UpdateStagingStatusFromPath(path, CTGitPath::StagingStatus::TotallyStaged);
3362 const CTGitPathList& unstaged = filterlist ? unstagedFiltered : unstagedUnfiltered;
3363 for (int j = 0; j < unstaged.GetCount(); ++j)
3365 CString path = unstaged[j].GetGitPathString();
3366 if (!stagedUnfiltered.LookForGitPath(path))
3367 result.UpdateStagingStatusFromPath(path, CTGitPath::StagingStatus::TotallyUnstaged);
3369 // make sure conflicted files show up as unstaged instead of partially staged
3370 for (int j = 0; j < result.GetCount(); ++j)
3372 if (result[j].m_Action & CTGitPath::LOGACTIONS_UNMERGED)
3373 const_cast<CTGitPath&>(result[j]).m_stagingStatus = CTGitPath::StagingStatus::TotallyUnstaged;
3378 std::map<CString, int> duplicateMap;
3379 for (int i = 0; i < result.GetCount(); ++i)
3380 duplicateMap.insert(std::pair<CString, int>(result[i].GetGitPathString(), i));
3382 // handle delete conflict case, when remote : modified, local : deleted.
3383 for (int i = 0; i < count; ++i)
3385 BYTE_VECTOR cmdout;
3386 CString cmd;
3388 if (!filterlist)
3389 cmd = L"git.exe ls-files -u -t -z";
3390 else
3391 cmd.Format(L"git.exe ls-files -u -t -z -- \"%s\"", static_cast<LPCWSTR>((*filterlist)[i].GetGitPathString()));
3393 Run(cmd, &cmdout);
3395 CTGitPathList conflictlist;
3396 conflictlist.ParserFromLog(cmdout);
3397 for (int j = 0; j < conflictlist.GetCount(); ++j)
3399 auto existing = duplicateMap.find(conflictlist[j].GetGitPathString());
3400 if (existing != duplicateMap.end())
3402 CTGitPath& p = const_cast<CTGitPath&>(result[existing->second]);
3403 p.m_Action |= CTGitPath::LOGACTIONS_UNMERGED;
3405 else
3407 result.AddPath(conflictlist[j]);
3408 duplicateMap.insert(std::pair<CString, int>(result[i].GetGitPathString(), result.GetCount() - 1));
3413 // handle source files of file renames/moves (issue #860)
3414 // if a file gets renamed and the new file "git add"ed, diff-index doesn't list the source file anymore
3415 for (int i = 0; i < count; ++i)
3417 BYTE_VECTOR cmdout;
3418 CString cmd;
3420 if (!filterlist)
3421 cmd = L"git.exe ls-files -d -z";
3422 else
3423 cmd.Format(L"git.exe ls-files -d -z -- \"%s\"", static_cast<LPCWSTR>((*filterlist)[i].GetGitPathString()));
3425 Run(cmd, &cmdout);
3427 CTGitPathList deletelist;
3428 deletelist.ParserFromLog(cmdout, true);
3429 for (int j = 0; j < deletelist.GetCount(); ++j)
3431 auto existing = duplicateMap.find(deletelist[j].GetGitPathString());
3432 if (existing == duplicateMap.end())
3434 result.AddPath(deletelist[j]);
3435 duplicateMap.insert(std::pair<CString, int>(result[i].GetGitPathString(), result.GetCount() - 1));
3437 else
3439 CTGitPath& p = const_cast<CTGitPath&>(result[existing->second]);
3440 p.m_Action |= CTGitPath::LOGACTIONS_MISSING;
3445 return 0;
3448 int CGit::IsRebaseRunning()
3450 CString adminDir;
3451 if (!GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir))
3452 return -1;
3454 if (PathIsDirectory(adminDir + L"rebase-apply") || PathIsDirectory(adminDir + L"tgitrebase.active"))
3455 return 1;
3456 return 0;
3459 void CGit::GetBisectTerms(CString* good, CString* bad)
3461 static CString lastGood;
3462 static CString lastBad;
3463 static ULONGLONG lastRead = 0;
3465 SCOPE_EXIT
3467 if (bad)
3468 *bad = lastBad;
3469 if (good)
3470 *good = lastGood;
3473 #ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_H_
3474 // add some caching here, because this method might be called multiple times in a short time from LogDlg and RevisionGraph
3475 // as we only read a small file the performance effects should be negligible
3476 if (lastRead + 5000 > GetTickCount64())
3477 return;
3478 #endif
3480 lastGood = L"good";
3481 lastBad = L"bad";
3483 CString adminDir;
3484 if (!GitAdminDir::GetAdminDirPath(m_CurrentDir, adminDir))
3485 return;
3487 CString termsFile = adminDir + L"BISECT_TERMS";
3488 CAutoFILE fp;
3489 _wfopen_s(fp.GetPointer(), termsFile, L"rb");
3490 if (!fp)
3491 return;
3492 char badA[MAX_PATH] = { 0 };
3493 fgets(badA, sizeof(badA), fp);
3494 size_t len = strlen(badA);
3495 if (len > 0 && badA[len - 1] == '\n')
3496 badA[len - 1] = '\0';
3497 char goodA[MAX_PATH] = { 0 };
3498 fgets(goodA, sizeof(goodA), fp);
3499 len = strlen(goodA);
3500 if (len > 0 && goodA[len - 1] == '\n')
3501 goodA[len - 1] = '\0';
3502 lastGood = CUnicodeUtils::GetUnicode(goodA);
3503 lastBad = CUnicodeUtils::GetUnicode(badA);
3504 lastRead = GetTickCount64();
3507 int CGit::GetGitVersion(CString* versiondebug, CString* errStr)
3509 CString version, err;
3510 if (Run(L"git.exe --version", &version, &err, CP_UTF8))
3512 if (errStr)
3513 *errStr = err;
3514 return -1;
3517 int ver = 0;
3518 if (versiondebug)
3519 *versiondebug = version;
3523 int start = 0;
3524 CString str = version.Tokenize(L".", start);
3525 int space = str.ReverseFind(L' ');
3526 str = str.Mid(space + 1, start);
3527 ver = _wtol(str);
3528 ver <<= 24;
3530 version = version.Mid(start);
3531 start = 0;
3533 str = version.Tokenize(L".", start);
3534 ver |= (_wtol(str) & 0xFF) << 16;
3536 str = version.Tokenize(L".", start);
3537 ver |= (_wtol(str) & 0xFF) << 8;
3539 str = version.Tokenize(L".", start);
3540 ver |= (_wtol(str) & 0xFF);
3542 catch (...)
3544 if (!ver)
3545 return -1;
3548 return ver;
3551 int CGit::GetGitNotes(const CGitHash& hash, CString& notes)
3553 CAutoRepository repo(GetGitRepository());
3554 if (!repo)
3555 return -1;
3557 CAutoNote note;
3558 int ret = git_note_read(note.GetPointer(), repo, nullptr, hash);
3559 if (ret == GIT_ENOTFOUND)
3561 notes.Empty();
3562 return 0;
3564 else if (ret)
3565 return -1;
3566 notes = CUnicodeUtils::GetUnicode(git_note_message(note));
3567 return 0;
3570 int CGit::SetGitNotes(const CGitHash& hash, const CString& notes)
3572 CAutoRepository repo(GetGitRepository());
3573 if (!repo)
3574 return -1;
3576 CAutoSignature signature;
3577 if (git_signature_default(signature.GetPointer(), repo) < 0)
3578 return -1;
3580 git_oid oid;
3581 if (git_note_create(&oid, repo, nullptr, signature, signature, hash, CUnicodeUtils::GetUTF8(notes), 1) < 0)
3582 return -1;
3584 return 0;
3587 CGitHash CGit::GetSubmodulePointer()
3589 CGitHash hash;
3590 if (GitAdminDir::IsBareRepo(g_Git.m_CurrentDir))
3591 return {};
3592 CString superprojectRoot;
3593 GitAdminDir::HasAdminDir(g_Git.m_CurrentDir, false, &superprojectRoot);
3594 if (superprojectRoot.IsEmpty())
3595 return {};
3597 CAutoRepository repo(superprojectRoot);
3598 if (!repo)
3599 return {};
3600 CAutoIndex index;
3601 if (git_repository_index(index.GetPointer(), repo))
3602 return {};
3604 CString submodulePath;
3605 if (superprojectRoot[superprojectRoot.GetLength() - 1] == L'\\')
3606 submodulePath = g_Git.m_CurrentDir.Right(g_Git.m_CurrentDir.GetLength() - superprojectRoot.GetLength());
3607 else
3608 submodulePath = g_Git.m_CurrentDir.Right(g_Git.m_CurrentDir.GetLength() - superprojectRoot.GetLength() - 1);
3609 submodulePath.Replace(L'\\', L'/');
3610 const git_index_entry* entry = git_index_get_bypath(index, CUnicodeUtils::GetUTF8(submodulePath), 0);
3611 if (!entry)
3612 return {};
3614 return entry->id;