Don't pass "--parents" to git log when full-history is enabled
[TortoiseGit.git] / src / Git / Git.cpp
blob5740cead7f4dfce748ca77fd904bdab418f9fc9f
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2022 - 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 { static_cast<LPCWSTR>(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 { static_cast<LPCWSTR>(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 { static_cast<LPCWSTR>(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 { static_cast<LPCWSTR>(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 { static_cast<LPCWSTR>(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_FULL_HISTORY)
1041 param += " --full-history";
1042 else
1043 param += " --parents"; // cf. issue #3728
1045 if(mask& CGit::LOG_INFO_FOLLOW)
1046 param += L" --follow";
1048 if(mask& CGit::LOG_INFO_SHOW_MERGEDFILE)
1049 param += L" -c";
1051 if(mask& CGit::LOG_INFO_FULL_DIFF)
1052 param += L" --full-diff";
1054 if(mask& CGit::LOG_INFO_SIMPILFY_BY_DECORATION)
1055 param += L" --simplify-by-decoration";
1057 if (mask & CGit::LOG_INFO_SPARSE)
1058 param += L" --sparse";
1060 if (Filter)
1062 if (Filter->m_NumberOfLogsScale >= CFilterData::SHOW_LAST_N_YEARS)
1064 CTime now = CTime::GetCurrentTime();
1065 CTime time = CTime(now.GetYear(), now.GetMonth(), now.GetDay(), 0, 0, 0);
1066 __time64_t substract = 86400;
1067 CString scale;
1068 switch (Filter->m_NumberOfLogsScale)
1070 case CFilterData::SHOW_LAST_N_YEARS:
1071 substract *= 365;
1072 break;
1073 case CFilterData::SHOW_LAST_N_MONTHS:
1074 substract *= 30;
1075 break;
1076 case CFilterData::SHOW_LAST_N_WEEKS:
1077 substract *= 7;
1078 break;
1080 Filter->m_From = static_cast<DWORD>(time.GetTime()) - (Filter->m_NumberOfLogs * substract);
1082 if (Filter->m_NumberOfLogsScale == CFilterData::SHOW_LAST_N_COMMITS)
1083 param.AppendFormat(L" -n%ld", Filter->m_NumberOfLogs);
1084 else if (Filter->m_NumberOfLogsScale >= CFilterData::SHOW_LAST_SEL_DATE && Filter->m_From > 0)
1085 param.AppendFormat(L" --max-age=%I64u", Filter->m_From);
1088 if( Filter && (Filter->m_To != -1))
1089 param.AppendFormat(L" --min-age=%I64u", Filter->m_To);
1091 if (logOrderBy == LOG_ORDER_TOPOORDER || (mask & CGit::LOG_ORDER_TOPOORDER))
1092 param += L" --topo-order";
1093 else if (logOrderBy == LOG_ORDER_DATEORDER)
1094 param += L" --date-order";
1096 CString cmd;
1097 CString file;
1098 if (path)
1099 file.Format(L" \"%s\"", static_cast<LPCWSTR>(path->GetGitPathString()));
1100 // gitdll.dll:setup_revisions() only looks at args[1] and greater. To account for this, pass a dummy parameter in the 0th place
1101 cmd.Format(L"-z%s %s --%s", static_cast<LPCWSTR>(param), static_cast<LPCWSTR>(range), static_cast<LPCWSTR>(file));
1103 return cmd;
1105 #define BUFSIZE 512
1106 void GetTempPath(CString &path)
1108 wchar_t lpPathBuffer[BUFSIZE] = { 0 };
1109 DWORD dwBufSize=BUFSIZE;
1110 DWORD dwRetVal = GetTortoiseGitTempPath(dwBufSize, lpPathBuffer);
1111 if (dwRetVal > dwBufSize || (dwRetVal == 0))
1112 path.Empty();
1113 path.Format(L"%s", lpPathBuffer);
1115 CString GetTempFile()
1117 wchar_t lpPathBuffer[BUFSIZE] = { 0 };
1118 DWORD dwBufSize=BUFSIZE;
1119 wchar_t szTempName[BUFSIZE] = { 0 };
1121 auto dwRetVal = GetTortoiseGitTempPath(dwBufSize, lpPathBuffer);
1122 if (dwRetVal > dwBufSize || (dwRetVal == 0))
1123 return L"";
1125 // Create a temporary file.
1126 if (!GetTempFileName(lpPathBuffer, // directory for tmp files
1127 TEXT("Patch"), // temp file name prefix
1128 0, // create unique name
1129 szTempName)) // buffer for name
1130 return L"";
1132 return CString(szTempName);
1135 DWORD GetTortoiseGitTempPath(DWORD nBufferLength, LPWSTR lpBuffer)
1137 DWORD result = ::GetTempPath(nBufferLength, lpBuffer);
1138 if (result == 0) return 0;
1139 if (!lpBuffer || (result + 13 > nBufferLength))
1141 if (lpBuffer)
1142 lpBuffer[0] = '\0';
1143 return result + 13;
1146 wcscat_s(lpBuffer, nBufferLength, L"TortoiseGit\\");
1147 CreateDirectory(lpBuffer, nullptr);
1149 return result + 13;
1152 int CGit::RunLogFile(CString cmd, const CString &filename, CString *stdErr)
1154 PROCESS_INFORMATION pi;
1155 CAutoGeneralHandle hReadErr;
1156 if (RunAsync(cmd, &pi, nullptr, hReadErr.GetPointer(), &filename))
1157 return TGIT_GIT_ERROR_CREATE_PROCESS;
1159 CAutoGeneralHandle piThread(std::move(pi.hThread));
1160 CAutoGeneralHandle piProcess(std::move(pi.hProcess));
1162 BYTE_VECTOR stderrVector;
1163 CGitCall_ByteVector pcall(L"", nullptr, &stderrVector);
1164 ASYNCREADSTDERRTHREADARGS threadArguments;
1165 threadArguments.fileHandle = hReadErr;
1166 threadArguments.pcall = &pcall;
1167 CAutoGeneralHandle thread = CreateThread(nullptr, 0, AsyncReadStdErrThread, &threadArguments, 0, nullptr);
1169 if (thread)
1171 CAutoLocker lock(m_critSecThreadMap);
1172 m_AsyncReadStdErrThreadMap[GetCurrentThreadId()] = thread;
1175 WaitForSingleObject(pi.hProcess,INFINITE);
1177 if (thread)
1179 WaitForSingleObject(thread, INFINITE);
1181 CAutoLocker lock(m_critSecThreadMap);
1182 m_AsyncReadStdErrThreadMap.erase(GetCurrentThreadId());
1185 stderrVector.push_back(0);
1186 StringAppend(stdErr, stderrVector.data(), CP_UTF8);
1188 DWORD exitcode = 0;
1189 if (!GetExitCodeProcess(pi.hProcess, &exitcode))
1191 CString err { static_cast<LPCWSTR>(CFormatMessageWrapper()) };
1192 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": could not get exit code: %s\n", static_cast<LPCWSTR>(err.Trim()));
1193 return TGIT_GIT_ERROR_GET_EXIT_CODE;
1195 else
1196 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": process exited: %d\n", exitcode);
1198 return exitcode;
1201 CAutoRepository CGit::GetGitRepository() const
1203 return CAutoRepository(GetGitPathStringA(m_CurrentDir));
1206 int CGit::GetHash(git_repository * repo, CGitHash &hash, const CString& friendname, bool skipFastCheck /* = false */)
1208 ATLASSERT(repo);
1210 // no need to parse a ref if it's already a 40-byte hash
1211 if (!skipFastCheck && CGitHash::IsValidSHA1(friendname))
1213 hash = CGitHash::FromHexStr(friendname);
1214 return 0;
1217 int isHeadOrphan = git_repository_head_unborn(repo);
1218 if (isHeadOrphan != 0)
1220 hash.Empty();
1221 if (isHeadOrphan == 1)
1223 if (friendname == GitRev::GetHead()) // special check for unborn branch: if not requesting HEAD, do normal commit lookup
1224 return 0;
1226 else
1227 return -1;
1230 CAutoObject gitObject;
1231 if (git_revparse_single(gitObject.GetPointer(), repo, CUnicodeUtils::GetUTF8(friendname)))
1232 return -1;
1234 hash = git_object_id(gitObject);
1236 return 0;
1239 int CGit::GetHash(CGitHash &hash, const CString& friendname)
1241 // no need to parse a ref if it's already a 40-byte hash
1242 if (CGitHash::IsValidSHA1(friendname))
1244 hash = CGitHash::FromHexStr(friendname);
1245 return 0;
1248 if (m_IsUseLibGit2)
1250 CAutoRepository repo(GetGitRepository());
1251 if (!repo)
1252 return -1;
1254 return GetHash(repo, hash, friendname, true);
1256 else
1258 if (friendname.IsEmpty())
1260 gitLastErr.Empty();
1261 return -1;
1263 CString branch = FixBranchName(friendname);
1264 if (friendname == L"FETCH_HEAD" && branch.IsEmpty())
1265 branch = friendname;
1266 CString cmd;
1267 cmd.Format(L"git.exe rev-parse %s", static_cast<LPCWSTR>(branch));
1268 gitLastErr.Empty();
1269 int ret = Run(cmd, &gitLastErr, nullptr, CP_UTF8);
1270 hash = CGitHash::FromHexStrTry(gitLastErr.Trim());
1271 if (ret == 0)
1272 gitLastErr.Empty();
1273 else if (friendname == L"HEAD") // special check for unborn branch
1275 CString currentbranch;
1276 if (GetCurrentBranchFromFile(m_CurrentDir, currentbranch))
1277 return -1;
1278 gitLastErr.Empty();
1279 return 0;
1281 return ret;
1285 int CGit::GetInitAddList(CTGitPathList& outputlist, bool getStagingStatus)
1287 BYTE_VECTOR cmdout;
1289 outputlist.Clear();
1290 if (Run(L"git.exe ls-files -s -t -z", &cmdout))
1291 return -1;
1293 if (outputlist.ParserFromLsFile(cmdout))
1294 return -1;
1295 for(int i = 0; i < outputlist.GetCount(); ++i)
1296 const_cast<CTGitPath&>(outputlist[i]).m_Action = CTGitPath::LOGACTIONS_ADDED;
1298 if (getStagingStatus)
1300 BYTE_VECTOR cmdunstagedout;
1301 for (int i = 0; i < outputlist.GetCount(); ++i)
1302 const_cast<CTGitPath&>(outputlist[i]).m_stagingStatus = CTGitPath::StagingStatus::TotallyStaged;
1303 if (Run(L"git.exe diff-files --raw --numstat -C -M -z --", &cmdunstagedout))
1304 return -1;
1306 CTGitPathList unstaged;
1307 unstaged.ParserFromLog(cmdunstagedout);
1308 // File shows up both in the output of ls-files and diff-files: partially staged (typically modified after being added)
1309 for (int j = 0; j < unstaged.GetCount(); ++j)
1311 CString path = unstaged[j].GetGitPathString();
1312 if (outputlist.LookForGitPath(path))
1313 outputlist.UpdateStagingStatusFromPath(path, CTGitPath::StagingStatus::PartiallyStaged); // TODO: This is inefficient
1316 return 0;
1318 int CGit::GetCommitDiffList(const CString &rev1, const CString &rev2, CTGitPathList &outputlist, bool ignoreSpaceAtEol, bool ignoreSpaceChange, bool ignoreAllSpace , bool ignoreBlankLines)
1320 CString cmd;
1321 CString ignore;
1322 if (ignoreSpaceAtEol)
1323 ignore += L" --ignore-space-at-eol";
1324 if (ignoreSpaceChange)
1325 ignore += L" --ignore-space-change";
1326 if (ignoreAllSpace)
1327 ignore += L" --ignore-all-space";
1328 if (ignoreBlankLines)
1329 ignore += L" --ignore-blank-lines";
1331 if(rev1 == GIT_REV_ZERO || rev2 == GIT_REV_ZERO)
1333 if(rev1 == GIT_REV_ZERO)
1334 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));
1335 else
1336 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));
1338 else
1339 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));
1341 BYTE_VECTOR out;
1342 if (Run(cmd, &out))
1343 return -1;
1345 return outputlist.ParserFromLog(out);
1348 int CGit::GetTagList(STRING_VECTOR &list)
1350 size_t prevCount = list.size();
1351 if (this->m_IsUseLibGit2)
1353 CAutoRepository repo(GetGitRepository());
1354 if (!repo)
1355 return -1;
1357 CAutoStrArray tag_names;
1359 if (git_tag_list(tag_names, repo))
1360 return -1;
1362 for (size_t i = 0; i < tag_names->count; ++i)
1364 list.push_back(CUnicodeUtils::GetUnicode(tag_names->strings[i]));
1367 std::sort(list.begin() + prevCount, list.end(), g_bSortTagsReversed ? LogicalCompareReversedPredicate : LogicalComparePredicate);
1369 return 0;
1371 else
1373 int ret = Run(L"git.exe tag -l", [&](const CStringA& lineA)
1375 if (lineA.IsEmpty())
1376 return;
1377 list.push_back(CUnicodeUtils::GetUnicode(lineA));
1379 if (!ret)
1380 std::sort(list.begin() + prevCount, list.end(), g_bSortTagsReversed ? LogicalCompareReversedPredicate : LogicalComparePredicate);
1381 else if (ret == 1 && IsInitRepos())
1382 return 0;
1383 return ret;
1387 CString CGit::GetGitLastErr(const CString& msg)
1389 if (this->m_IsUseLibGit2)
1390 return GetLibGit2LastErr(msg);
1391 else if (gitLastErr.IsEmpty())
1392 return msg + L"\nUnknown git.exe error.";
1393 else
1395 CString lastError = gitLastErr;
1396 gitLastErr.Empty();
1397 return msg + L'\n' + lastError;
1401 CString CGit::GetGitLastErr(const CString& msg, LIBGIT2_CMD cmd)
1403 if (UsingLibGit2(cmd))
1404 return GetLibGit2LastErr(msg);
1405 else if (gitLastErr.IsEmpty())
1406 return msg + L"\nUnknown git.exe error.";
1407 else
1409 CString lastError = gitLastErr;
1410 gitLastErr.Empty();
1411 return msg + L'\n' + lastError;
1415 CString CGit::GetLibGit2LastErr()
1417 const git_error *libgit2err = git_error_last();
1418 if (libgit2err)
1420 CString lastError = CUnicodeUtils::GetUnicode(libgit2err->message);
1421 git_error_clear();
1422 return L"libgit2 returned: " + lastError;
1424 else
1425 return L"An error occoured in libgit2, but no message is available.";
1428 CString CGit::GetLibGit2LastErr(const CString& msg)
1430 if (!msg.IsEmpty())
1431 return msg + L'\n' + GetLibGit2LastErr();
1432 return GetLibGit2LastErr();
1435 CString CGit::FixBranchName_Mod(CString& branchName)
1437 if (branchName == L"FETCH_HEAD")
1438 branchName = DerefFetchHead();
1439 return branchName;
1442 CString CGit::FixBranchName(const CString& branchName)
1444 CString tempBranchName = branchName;
1445 FixBranchName_Mod(tempBranchName);
1446 return tempBranchName;
1449 bool CGit::IsBranchTagNameUnique(const CString& name)
1451 if (m_IsUseLibGit2)
1453 CAutoRepository repo(GetGitRepository());
1454 if (!repo)
1455 return true; // TODO: optimize error reporting
1457 CAutoReference tagRef;
1458 if (git_reference_lookup(tagRef.GetPointer(), repo, CUnicodeUtils::GetUTF8(L"refs/tags/" + name)))
1459 return true;
1461 CAutoReference branchRef;
1462 if (git_reference_lookup(branchRef.GetPointer(), repo, CUnicodeUtils::GetUTF8(L"refs/heads/" + name)))
1463 return true;
1465 return false;
1467 // else
1468 CString cmd;
1469 cmd.Format(L"git.exe show-ref --tags --heads refs/heads/%s refs/tags/%s", static_cast<LPCWSTR>(name), static_cast<LPCWSTR>(name));
1471 int refCnt = 0;
1472 Run(cmd, [&](const CStringA& lineA)
1474 if (lineA.IsEmpty())
1475 return;
1476 ++refCnt;
1479 return (refCnt <= 1);
1482 bool CGit::IsLocalBranch(const CString& shortName)
1484 STRING_VECTOR list;
1485 GetBranchList(list, nullptr, CGit::BRANCH_LOCAL);
1486 return std::find(list.cbegin(), list.cend(), shortName) != list.cend();
1489 bool CGit::BranchTagExists(const CString& name, bool isBranch /*= true*/)
1491 if (m_IsUseLibGit2)
1493 CAutoRepository repo(GetGitRepository());
1494 if (!repo)
1495 return false; // TODO: optimize error reporting
1497 CString prefix;
1498 if (isBranch)
1499 prefix = L"refs/heads/";
1500 else
1501 prefix = L"refs/tags/";
1503 CAutoReference ref;
1504 if (git_reference_lookup(ref.GetPointer(), repo, CUnicodeUtils::GetUTF8(prefix + name)))
1505 return false;
1507 return true;
1509 // else
1510 CString cmd, output;
1512 cmd = L"git.exe show-ref ";
1513 if (isBranch)
1514 cmd += L"--heads ";
1515 else
1516 cmd += L"--tags ";
1518 cmd += L"refs/heads/" + name;
1519 cmd += L" refs/tags/" + name;
1521 int ret = Run(cmd, &output, nullptr, CP_UTF8);
1522 if (!ret)
1524 if (!output.IsEmpty())
1525 return true;
1528 return false;
1531 CString CGit::DerefFetchHead()
1533 CString dotGitPath;
1534 GitAdminDir::GetWorktreeAdminDirPath(m_CurrentDir, dotGitPath);
1535 std::ifstream fetchHeadFile((dotGitPath + L"FETCH_HEAD").GetString(), std::ios::in | std::ios::binary);
1536 int forMergeLineCount = 0;
1537 std::string line;
1538 std::string hashToReturn;
1539 while(getline(fetchHeadFile, line))
1541 //Tokenize this line
1542 std::string::size_type prevPos = 0;
1543 std::string::size_type pos = line.find('\t');
1544 if(pos == std::string::npos) continue; //invalid line
1546 std::string hash = line.substr(0, pos);
1547 ++pos; prevPos = pos; pos = line.find('\t', pos); if(pos == std::string::npos) continue;
1549 bool forMerge = pos == prevPos;
1550 ++pos; prevPos = pos; pos = line.size(); if(pos == std::string::npos) continue;
1552 //std::string remoteBranch = line.substr(prevPos, pos - prevPos);
1554 //Process this line
1555 if(forMerge)
1557 hashToReturn = hash;
1558 ++forMergeLineCount;
1559 if(forMergeLineCount > 1)
1560 return L""; //More then 1 branch to merge found. Octopus merge needed. Cannot pick single ref from FETCH_HEAD
1564 return CUnicodeUtils::GetUnicode(hashToReturn.c_str());
1567 int CGit::GetBranchList(STRING_VECTOR &list, int *current, BRANCH_TYPE type, bool skipCurrent /*false*/)
1569 size_t prevCount = list.size();
1570 int ret = 0;
1571 CString cur;
1572 bool headIsDetached = false;
1573 if (m_IsUseLibGit2)
1575 CAutoRepository repo(GetGitRepository());
1576 if (!repo)
1577 return -1;
1579 if (git_repository_head_detached(repo) == 1)
1580 headIsDetached = true;
1582 if ((type & (BRANCH_LOCAL | BRANCH_REMOTE)) != 0)
1584 git_branch_t flags = GIT_BRANCH_LOCAL;
1585 if ((type & BRANCH_LOCAL) && (type & BRANCH_REMOTE))
1586 flags = GIT_BRANCH_ALL;
1587 else if (type & BRANCH_REMOTE)
1588 flags = GIT_BRANCH_REMOTE;
1590 CAutoBranchIterator it;
1591 if (git_branch_iterator_new(it.GetPointer(), repo, flags))
1592 return -1;
1594 CAutoReference ref;
1595 git_branch_t branchType;
1596 while (git_branch_next(ref.GetPointer(), &branchType, it) == 0)
1598 const char * name = nullptr;
1599 if (git_branch_name(&name, ref))
1600 continue;
1602 CString branchname = CUnicodeUtils::GetUnicode(name);
1603 if (branchType & GIT_BRANCH_REMOTE)
1604 list.push_back(L"remotes/" + branchname);
1605 else
1607 if (git_branch_is_head(ref))
1609 if (skipCurrent)
1610 continue;
1611 cur = branchname;
1613 list.push_back(branchname);
1618 else
1620 CString cmd = L"git.exe branch --no-color";
1622 if ((type & BRANCH_ALL) == BRANCH_ALL)
1623 cmd += L" -a";
1624 else if (type & BRANCH_REMOTE)
1625 cmd += L" -r";
1627 ret = Run(cmd, [&](CStringA lineA)
1629 lineA.Trim(" \r\n\t");
1630 if (lineA.IsEmpty())
1631 return;
1632 if (lineA.Find(" -> ") >= 0)
1633 return; // skip something like: refs/origin/HEAD -> refs/origin/master
1635 CString branch = CUnicodeUtils::GetUnicode(lineA);
1636 if (lineA[0] == '*')
1638 if (skipCurrent)
1639 return;
1640 branch = branch.Mid(static_cast<int>(wcslen(L"* ")));
1641 cur = branch;
1643 // check whether HEAD is detached
1644 CString currentHead;
1645 if (branch[0] == L'(' && GetCurrentBranchFromFile(m_CurrentDir, currentHead) == 1)
1647 headIsDetached = true;
1648 return;
1651 else if (lineA[0] == '+') // since Git 2.23 branches that are checked out in other worktrees connected to the same repository prefixed with '+'
1652 branch = branch.Mid(static_cast<int>(wcslen(L"+ ")));
1653 if ((type & BRANCH_REMOTE) != 0 && (type & BRANCH_LOCAL) == 0)
1654 branch = L"remotes/" + branch;
1655 list.push_back(branch);
1657 if (ret == 1 && IsInitRepos())
1658 return 0;
1661 if(type & BRANCH_FETCH_HEAD && !DerefFetchHead().IsEmpty())
1662 list.push_back(L"FETCH_HEAD");
1664 std::sort(list.begin() + prevCount, list.end(), LogicalCompareBranchesPredicate);
1666 if (current && !headIsDetached && !skipCurrent)
1668 for (unsigned int i = 0; i < list.size(); ++i)
1670 if (list[i] == cur)
1672 *current = i;
1673 break;
1678 return ret;
1681 int CGit::GetRefsCommitIsOn(STRING_VECTOR& list, const CGitHash& hash, bool includeTags, bool includeBranches, BRANCH_TYPE type)
1683 if (!includeTags && !includeBranches || hash.IsEmpty())
1684 return 0;
1686 size_t prevCount = list.size();
1687 if (UsingLibGit2(GIT_CMD_BRANCH_CONTAINS))
1689 CAutoRepository repo(GetGitRepository());
1690 if (!repo)
1691 return -1;
1693 CAutoReferenceIterator it;
1694 if (git_reference_iterator_new(it.GetPointer(), repo))
1695 return -1;
1697 auto checkDescendent = [&list, &hash, &repo](const git_oid* oid, const git_reference* ref) {
1698 if (!oid)
1699 return;
1700 if (git_oid_equal(oid, hash) || git_graph_descendant_of(repo, oid, hash) == 1)
1702 const char* name = git_reference_name(ref);
1703 if (!name)
1704 return;
1706 list.push_back(CUnicodeUtils::GetUnicode(name));
1710 CAutoReference ref;
1711 while (git_reference_next(ref.GetPointer(), it) == 0)
1713 if (git_reference_is_tag(ref))
1715 if (!includeTags)
1716 continue;
1718 CAutoTag tag;
1719 if (git_tag_lookup(tag.GetPointer(), repo, git_reference_target(ref)) == 0)
1721 CAutoObject obj;
1722 if (git_tag_peel(obj.GetPointer(), tag) < 0)
1723 continue;
1724 checkDescendent(git_object_id(obj), ref);
1725 continue;
1728 else if (git_reference_is_remote(ref))
1730 if (!includeBranches || !(type & BRANCH_REMOTE))
1731 continue;
1733 else if (git_reference_is_branch(ref))
1735 if (!includeBranches || !(type & BRANCH_LOCAL))
1736 continue;
1738 else
1739 continue;
1741 if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC)
1743 CAutoReference peeledRef;
1744 if (git_reference_resolve(peeledRef.GetPointer(), ref) < 0)
1745 continue;
1747 checkDescendent(git_reference_target(peeledRef), ref);
1748 continue;
1751 checkDescendent(git_reference_target(ref), ref);
1754 else
1756 if (includeBranches)
1758 CString cmd = L"git.exe branch --no-color";
1759 if ((type & BRANCH_ALL) == BRANCH_ALL)
1760 cmd += L" -a";
1761 else if (type & BRANCH_REMOTE)
1762 cmd += L" -r";
1763 cmd += L" --contains " + hash.ToString();
1765 if (Run(cmd, [&](CStringA lineA)
1767 lineA.Trim(" \r\n\t");
1768 if (lineA.IsEmpty())
1769 return;
1770 if (lineA.Find(" -> ") >= 0) // normalize symbolic refs: "refs/origin/HEAD -> refs/origin/master" to "refs/origin/HEAD"
1771 lineA.Truncate(lineA.Find(" -> "));
1773 CString branch = CUnicodeUtils::GetUnicode(lineA);
1774 if (lineA[0] == '*')
1776 branch = branch.Mid(static_cast<int>(wcslen(L"* ")));
1777 CString currentHead;
1778 if (branch[0] == L'(' && GetCurrentBranchFromFile(m_CurrentDir, currentHead) == 1)
1779 return;
1782 if ((type & BRANCH_REMOTE) != 0 && (type & BRANCH_LOCAL) == 0)
1783 branch = L"refs/remotes/" + branch;
1784 else if (CStringUtils::StartsWith(branch, L"remotes/"))
1785 branch = L"refs/" + branch;
1786 else
1787 branch = L"refs/heads/" + branch;
1788 list.push_back(branch);
1790 return -1;
1793 if (includeTags)
1795 CString cmd = L"git.exe tag --contains " + hash.ToString();
1796 if (Run(cmd, [&list](CStringA lineA)
1798 if (lineA.IsEmpty())
1799 return;
1800 list.push_back(L"refs/tags/" + CUnicodeUtils::GetUnicode(lineA));
1802 return -1;
1806 std::sort(list.begin() + prevCount, list.end(), LogicalCompareBranchesPredicate);
1807 list.erase(std::unique(list.begin() + prevCount, list.end()), list.end());
1809 return 0;
1812 int CGit::GetRemoteList(STRING_VECTOR &list)
1814 size_t prevCount = list.size();
1815 if (this->m_IsUseLibGit2)
1817 CAutoRepository repo(GetGitRepository());
1818 if (!repo)
1819 return -1;
1821 CAutoStrArray remotes;
1822 if (git_remote_list(remotes, repo))
1823 return -1;
1825 for (size_t i = 0; i < remotes->count; ++i)
1827 list.push_back(CUnicodeUtils::GetUnicode(remotes->strings[i]));
1830 std::sort(list.begin() + prevCount, list.end(), LogicalComparePredicate);
1832 return 0;
1835 return Run(L"git.exe remote", [&](const CStringA& lineA)
1837 if (lineA.IsEmpty())
1838 return;
1839 list.push_back(CUnicodeUtils::GetUnicode(lineA));
1843 int CGit::GetRemoteRefs(const CString& remote, REF_VECTOR& list, bool includeTags, bool includeBranches)
1845 if (!includeTags && !includeBranches)
1846 return 0;
1848 size_t prevCount = list.size();
1849 if (UsingLibGit2(GIT_CMD_FETCH))
1851 CAutoRepository repo(GetGitRepository());
1852 if (!repo)
1853 return -1;
1855 CStringA remoteA = CUnicodeUtils::GetUTF8(remote);
1856 CAutoRemote gitremote;
1857 // first try with a named remote (e.g. "origin")
1858 if (git_remote_lookup(gitremote.GetPointer(), repo, remoteA) < 0)
1860 // retry with repository located at a specific url
1861 if (git_remote_create_anonymous(gitremote.GetPointer(), repo, remoteA) < 0)
1862 return -1;
1865 git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
1866 callbacks.credentials = g_Git2CredCallback;
1867 callbacks.certificate_check = g_Git2CheckCertificateCallback;
1868 git_proxy_options proxy = GIT_PROXY_OPTIONS_INIT;
1869 proxy.type = GIT_PROXY_AUTO;
1870 if (git_remote_connect(gitremote, GIT_DIRECTION_FETCH, &callbacks, &proxy, nullptr) < 0)
1871 return -1;
1873 const git_remote_head** heads = nullptr;
1874 size_t size = 0;
1875 if (git_remote_ls(&heads, &size, gitremote) < 0)
1876 return -1;
1878 for (size_t i = 0; i < size; i++)
1880 CString ref = CUnicodeUtils::GetUnicode(heads[i]->name);
1881 CString shortname;
1882 if (GetShortName(ref, shortname, L"refs/tags/"))
1884 if (!includeTags)
1885 continue;
1887 else
1889 if (!includeBranches)
1890 continue;
1891 if (!GetShortName(ref, shortname, L"refs/heads/"))
1892 shortname = ref;
1894 if (includeTags && includeBranches)
1895 list.emplace_back(TGitRef{ ref, &heads[i]->oid });
1896 else
1897 list.emplace_back(TGitRef{ shortname, &heads[i]->oid });
1899 std::sort(list.begin() + prevCount, list.end(), g_bSortTagsReversed && includeTags && !includeBranches ? LogicalCompareReversedPredicate : LogicalComparePredicate);
1900 return 0;
1903 CString cmd;
1904 cmd.Format(L"git.exe ls-remote%s \"%s\"", (includeTags && !includeBranches) ? L" -t" : L" --refs", static_cast<LPCWSTR>(remote));
1905 gitLastErr = cmd + L'\n';
1906 if (Run(
1907 cmd, [&](CStringA lineA) {
1908 CGitHash hash = CGitHash::FromHexStr(lineA.Left(GIT_HASH_SIZE * 2));
1909 lineA = lineA.Mid(GIT_HASH_SIZE * 2 + static_cast<int>(wcslen(L"\t"))); // sha1, tab
1910 if (lineA.IsEmpty())
1911 return;
1912 CString ref = CUnicodeUtils::GetUnicode(lineA);
1913 CString shortname;
1914 if (GetShortName(ref, shortname, L"refs/tags/"))
1916 if (!includeTags)
1917 return;
1919 else
1921 if (!includeBranches)
1922 return;
1923 if (!GetShortName(ref, shortname, L"refs/heads/"))
1924 shortname = ref;
1926 if (includeTags && includeBranches)
1927 list.emplace_back(TGitRef{ ref, hash });
1928 else if (includeTags && CStringUtils::EndsWith(ref, L"^{}"))
1929 list.emplace_back(TGitRef{ shortname + L"^{}", hash });
1930 else
1931 list.emplace_back(TGitRef{ shortname, hash });
1933 &gitLastErr))
1934 return -1;
1935 std::sort(list.begin() + prevCount, list.end(), g_bSortTagsReversed && includeTags && !includeBranches ? LogicalCompareReversedPredicate : LogicalComparePredicate);
1936 return 0;
1939 int CGit::DeleteRemoteRefs(const CString& sRemote, const STRING_VECTOR& list)
1941 if (UsingLibGit2(GIT_CMD_PUSH))
1943 CAutoRepository repo(GetGitRepository());
1944 if (!repo)
1945 return -1;
1947 CStringA remoteA = CUnicodeUtils::GetUTF8(sRemote);
1948 CAutoRemote remote;
1949 if (git_remote_lookup(remote.GetPointer(), repo, remoteA) < 0)
1950 return -1;
1952 git_push_options pushOpts = GIT_PUSH_OPTIONS_INIT;
1953 git_remote_callbacks& callbacks = pushOpts.callbacks;
1954 callbacks.credentials = g_Git2CredCallback;
1955 callbacks.certificate_check = g_Git2CheckCertificateCallback;
1956 std::vector<CStringA> refspecs;
1957 refspecs.reserve(list.size());
1958 std::transform(list.cbegin(), list.cend(), std::back_inserter(refspecs), [](auto& ref) { return CUnicodeUtils::GetUTF8(L":" + ref); });
1960 std::vector<char*> vc;
1961 vc.reserve(refspecs.size());
1962 std::transform(refspecs.begin(), refspecs.end(), std::back_inserter(vc), [](CStringA& s) -> char* { return s.GetBuffer(); });
1963 git_strarray specs = { vc.data(), vc.size() };
1965 if (git_remote_push(remote, &specs, &pushOpts) < 0)
1966 return -1;
1968 else
1970 CMassiveGitTaskBase mgtPush(L"push " + sRemote, FALSE);
1971 for (const auto& ref : list)
1972 mgtPush.AddFile(L':' + ref);
1974 BOOL cancel = FALSE;
1975 mgtPush.Execute(cancel);
1978 return 0;
1981 int libgit2_addto_list_each_ref_fn(git_reference *ref, void *payload)
1983 auto list = static_cast<STRING_VECTOR*>(payload);
1984 list->push_back(CUnicodeUtils::GetUnicode(git_reference_name(ref)));
1985 return 0;
1988 int CGit::GetRefList(STRING_VECTOR &list)
1990 size_t prevCount = list.size();
1991 if (this->m_IsUseLibGit2)
1993 CAutoRepository repo(GetGitRepository());
1994 if (!repo)
1995 return -1;
1997 if (git_reference_foreach(repo, libgit2_addto_list_each_ref_fn, &list))
1998 return -1;
2000 std::sort(list.begin() + prevCount, list.end(), LogicalComparePredicate);
2002 return 0;
2005 int ret = Run(L"git.exe show-ref -d", [&](const CStringA& lineA)
2007 int start = lineA.Find(L' ');
2008 ASSERT(start == 2 * GIT_HASH_SIZE);
2009 if (start <= 0)
2010 return;
2012 CString name = CUnicodeUtils::GetUnicode(lineA.Mid(start + 1));
2013 if (list.empty() || name != *list.crbegin() + L"^{}")
2014 list.push_back(name);
2016 if (!ret)
2017 std::sort(list.begin() + prevCount, list.end(), LogicalComparePredicate);
2018 else if (ret == 1 && IsInitRepos())
2019 return 0;
2020 return ret;
2023 typedef struct map_each_ref_payload {
2024 git_repository * repo;
2025 MAP_HASH_NAME * map;
2026 } map_each_ref_payload;
2028 int libgit2_addto_map_each_ref_fn(git_reference *ref, void *payload)
2030 auto payloadContent = static_cast<map_each_ref_payload*>(payload);
2032 CString str = CUnicodeUtils::GetUnicode(git_reference_name(ref));
2034 CAutoObject gitObject;
2035 if (git_revparse_single(gitObject.GetPointer(), payloadContent->repo, git_reference_name(ref)))
2036 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
2038 if (git_object_type(gitObject) == GIT_OBJECT_TAG)
2040 str += L"^{}"; // deref tag
2041 CAutoObject derefedTag;
2042 if (git_tag_target(derefedTag.GetPointer(), reinterpret_cast<git_tag*>(static_cast<git_object*>(gitObject))))
2043 return 1;
2044 gitObject.Swap(derefedTag);
2047 (*payloadContent->map)[git_object_id(gitObject)].push_back(str);
2049 return 0;
2051 int CGit::GetMapHashToFriendName(git_repository* repo, MAP_HASH_NAME &map)
2053 ATLASSERT(repo);
2055 map_each_ref_payload payloadContent = { repo, &map };
2057 if (git_reference_foreach(repo, libgit2_addto_map_each_ref_fn, &payloadContent))
2058 return -1;
2060 for (auto it = map.begin(); it != map.end(); ++it)
2062 std::sort(it->second.begin(), it->second.end());
2065 return 0;
2068 int CGit::GetMapHashToFriendName(MAP_HASH_NAME &map)
2070 if (this->m_IsUseLibGit2)
2072 CAutoRepository repo(GetGitRepository());
2073 if (!repo)
2074 return -1;
2076 return GetMapHashToFriendName(repo, map);
2079 int ret = Run(L"git.exe show-ref -d", [&](const CStringA& lineA)
2081 int start = lineA.Find(L' ');
2082 ASSERT(start == 2 * GIT_HASH_SIZE);
2083 if (start <= 0)
2084 return;
2086 CGitHash hash = CGitHash::FromHexStr(lineA);
2087 map[hash].push_back(CUnicodeUtils::GetUnicode(lineA.Mid(start + 1)));
2090 if (ret == 1 && IsInitRepos())
2091 return 0;
2092 return ret;
2095 int CGit::GuessRefForHash(CString& ref, const CGitHash& hash)
2097 MAP_HASH_NAME map;
2098 if (GetMapHashToFriendName(map))
2099 return -1;
2101 auto it = map.find(hash);
2102 if (it == map.cend())
2104 ref = hash.ToString(GetShortHASHLength());
2105 return 1;
2108 const auto& reflist = it->second;
2109 for (const auto& reftype : { L"refs/heads/", L"refs/remotes/", L"refs/tags/" })
2111 auto found = std::find_if(reflist.cbegin(), reflist.cend(), [&reftype](const auto& ref) { return CStringUtils::StartsWith(ref, reftype); });
2112 if (found == reflist.cend())
2113 continue;
2115 GetShortName(*found, ref, reftype);
2116 return 0;
2119 ref = hash.ToString(GetShortHASHLength());
2120 return 1;
2123 int CGit::GetBranchDescriptions(MAP_STRING_STRING& map)
2125 CAutoConfig config(true);
2126 if (git_config_add_file_ondisk(config, CGit::GetGitPathStringA(GetGitLocalConfig()), GIT_CONFIG_LEVEL_LOCAL, nullptr, FALSE) < 0)
2127 return -1;
2128 return git_config_foreach_match(config, "^branch\\..*\\.description$", [](const git_config_entry* entry, void* data)
2130 auto descriptions = static_cast<MAP_STRING_STRING*>(data);
2131 CString key = CUnicodeUtils::GetUnicode(entry->name);
2132 // extract branch name from config key
2133 key = key.Mid(static_cast<int>(wcslen(L"branch.")), key.GetLength() - static_cast<int>(wcslen(L"branch.")) - static_cast<int>(wcslen(L".description")));
2134 descriptions->insert(std::make_pair(key, CUnicodeUtils::GetUnicode(entry->value)));
2135 return 0;
2136 }, &map);
2139 static void SetLibGit2SearchPath(int level, const CString &value)
2141 CStringA valueA = CUnicodeUtils::GetUTF8(value);
2142 git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, level, static_cast<LPCSTR>(valueA));
2145 static void SetLibGit2TemplatePath(const CString &value)
2147 CStringA valueA = CUnicodeUtils::GetUTF8(value);
2148 git_libgit2_opts(GIT_OPT_SET_TEMPLATE_PATH, static_cast<LPCSTR>(valueA));
2151 int CGit::FindAndSetGitExePath(BOOL bFallback)
2153 CRegString msysdir = CRegString(REG_MSYSGIT_PATH, L"", FALSE);
2154 CString str = msysdir;
2155 if (!str.IsEmpty() && PathFileExists(str + L"\\git.exe"))
2157 CGit::ms_LastMsysGitDir = str;
2158 return TRUE;
2161 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": git.exe not exists: %s\n", static_cast<LPCWSTR>(CGit::ms_LastMsysGitDir));
2162 if (!bFallback)
2163 return FALSE;
2165 // first, search PATH if git/bin directory is already present
2166 if (FindGitPath())
2168 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": FindGitPath() => %s\n", static_cast<LPCWSTR>(CGit::ms_LastMsysGitDir));
2169 msysdir = CGit::ms_LastMsysGitDir;
2170 msysdir.write();
2171 return TRUE;
2174 if (FindGitForWindows(str))
2176 msysdir = str;
2177 CGit::ms_LastMsysGitDir = str;
2178 msysdir.write();
2179 return TRUE;
2182 return FALSE;
2185 BOOL CGit::CheckMsysGitDir(BOOL bFallback)
2187 if (m_bInitialized)
2188 return TRUE;
2190 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": CheckMsysGitDir(%d)\n", bFallback);
2191 this->m_Environment.clear();
2192 m_Environment.CopyProcessEnvironment();
2194 // 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,
2195 // because MSys2 changed the default to "ASCII". SO, make sure we have a proper default set
2196 if (m_Environment.GetEnv(L"LC_ALL").IsEmpty())
2197 m_Environment.SetEnv(L"LC_ALL", L"C");
2199 // set HOME if not set already
2200 size_t homesize;
2201 _wgetenv_s(&homesize, nullptr, 0, L"HOME");
2202 if (!homesize)
2203 m_Environment.SetEnv(L"HOME", GetHomeDirectory());
2205 //setup ssh client
2206 CString sshclient = CRegString(L"Software\\TortoiseGit\\SSH");
2207 if (sshclient.IsEmpty())
2208 sshclient = CRegString(L"Software\\TortoiseGit\\SSH", L"", FALSE, HKEY_LOCAL_MACHINE);
2210 if(!sshclient.IsEmpty())
2212 if (ms_bCygwinGit)
2213 sshclient.Replace(L'\\', L'/');
2214 m_Environment.SetEnv(L"GIT_SSH", sshclient);
2215 if (CStringUtils::EndsWithI(sshclient, L"tortoisegitplink") || CStringUtils::EndsWithI(sshclient, L"tortoisegitplink.exe"))
2216 m_Environment.SetEnv(L"GIT_SSH_VARIANT", L"ssh");
2217 m_Environment.SetEnv(L"SVN_SSH", sshclient);
2219 else
2221 wchar_t sPlink[MAX_PATH] = { 0 };
2222 GetModuleFileName(nullptr, sPlink, _countof(sPlink));
2223 LPWSTR ptr = wcsrchr(sPlink, L'\\');
2224 if (ptr) {
2225 wcscpy_s(ptr + 1, _countof(sPlink) - (ptr - sPlink + 1), L"TortoiseGitPlink.exe");
2226 if (ms_bCygwinGit)
2227 CPathUtils::ConvertToSlash(sPlink);
2228 m_Environment.SetEnv(L"GIT_SSH", sPlink);
2229 m_Environment.SetEnv(L"GIT_SSH_VARIANT", L"ssh");
2230 m_Environment.SetEnv(L"SVN_SSH", sPlink);
2235 wchar_t sAskPass[MAX_PATH] = { 0 };
2236 GetModuleFileName(nullptr, sAskPass, _countof(sAskPass));
2237 LPWSTR ptr = wcsrchr(sAskPass, L'\\');
2238 if (ptr)
2240 wcscpy_s(ptr + 1, _countof(sAskPass) - (ptr - sAskPass + 1), L"SshAskPass.exe");
2241 if (ms_bCygwinGit)
2242 CPathUtils::ConvertToSlash(sAskPass);
2243 m_Environment.SetEnv(L"DISPLAY",L":9999");
2244 m_Environment.SetEnv(L"SSH_ASKPASS",sAskPass);
2245 m_Environment.SetEnv(L"GIT_ASKPASS",sAskPass);
2246 m_Environment.SetEnv(L"GIT_ASK_YESNO", sAskPass);
2250 if (!FindAndSetGitExePath(bFallback))
2251 return FALSE;
2253 CString msysGitDir;
2254 PathCanonicalize(CStrBuf(msysGitDir, MAX_PATH), CGit::ms_LastMsysGitDir + L"\\..\\");
2255 static const CString prefixes[] = { L"mingw64\\etc", L"mingw32\\etc", L"etc" };
2256 static const int prefixes_len[] = { 8, 8, 0 };
2257 for (int i = 0; i < _countof(prefixes); ++i)
2259 #ifndef _WIN64
2260 if (i == 0)
2261 continue;
2262 #endif
2263 if (PathIsDirectory(msysGitDir + prefixes[i])) {
2264 msysGitDir += prefixes[i].Left(prefixes_len[i]);
2265 break;
2268 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
2269 PathCanonicalize(CStrBuf(msysGitDir, MAX_PATH), CGit::ms_LastMsysGitDir + L"\\..\\..");
2270 CGit::ms_MsysGitRootDir = msysGitDir;
2272 if (static_cast<CString>(CRegString(REG_SYSTEM_GITCONFIGPATH, L"", FALSE)) != g_Git.GetGitSystemConfig())
2273 CRegString(REG_SYSTEM_GITCONFIGPATH, L"", FALSE) = g_Git.GetGitSystemConfig();
2275 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": ms_LastMsysGitDir = %s\n", static_cast<LPCWSTR>(CGit::ms_LastMsysGitDir));
2276 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": ms_MsysGitRootDir = %s\n", static_cast<LPCWSTR>(CGit::ms_MsysGitRootDir));
2277 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": System config = %s\n", static_cast<LPCWSTR>(g_Git.GetGitSystemConfig()));
2278 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_PROGRAMDATA, L"");
2280 if (ms_bCygwinGit)
2281 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": CygwinHack: true\n");
2282 if (ms_bMsys2Git)
2283 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": Msys2Hack: true\n");
2285 // Configure libgit2 search paths
2286 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_SYSTEM, CTGitPath(g_Git.GetGitSystemConfig()).GetContainingDirectory().GetWinPathString());
2287 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_GLOBAL, g_Git.GetHomeDirectory());
2288 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_XDG, g_Git.GetGitGlobalXDGConfigPath());
2289 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 };
2290 git_transport_register("ssh", git_transport_smart, &ssh_wintunnel_subtransport_definition);
2291 git_transport_register("ssh+git", git_transport_smart, &ssh_wintunnel_subtransport_definition);
2292 git_transport_register("git+ssh", git_transport_smart, &ssh_wintunnel_subtransport_definition);
2293 git_libgit2_opts(GIT_OPT_SET_USER_AGENT, "TortoiseGit libgit2");
2294 if (!(ms_bCygwinGit || ms_bMsys2Git))
2295 SetLibGit2TemplatePath(CGit::ms_MsysGitRootDir + L"share\\git-core\\templates");
2296 else
2297 SetLibGit2TemplatePath(CGit::ms_MsysGitRootDir + L"usr\\share\\git-core\\templates");
2299 m_Environment.AddToPath(CGit::ms_LastMsysGitDir);
2300 m_Environment.AddToPath(static_cast<CString>(CRegString(REG_MSYSGIT_EXTRA_PATH, L"", FALSE)));
2302 #if !defined(TGITCACHE) && !defined(TORTOISESHELL)
2303 // register filter only once
2304 if (!git_filter_lookup("filter"))
2306 CString sh;
2307 for (const CString& binDirPrefix : { L"\\..\\usr\\bin", L"\\..\\bin", L"" })
2309 CString possibleShExe = CGit::ms_LastMsysGitDir + binDirPrefix + L"\\sh.exe";
2310 if (PathFileExists(possibleShExe))
2312 CString temp;
2313 PathCanonicalize(CStrBuf(temp, MAX_PATH), possibleShExe);
2314 sh.Format(L"\"%s\"", static_cast<LPCWSTR>(temp));
2315 // we need to put the usr\bin folder on the path for Git for Windows based on msys2
2316 m_Environment.AddToPath(temp.Left(temp.GetLength() - static_cast<int>(wcslen(L"\\sh.exe"))));
2317 break;
2321 // Add %GIT_EXEC_PATH% to %PATH% when launching libgit2 filter executable
2322 // It is possible that the filter points to a git subcommand, that is located at libexec\git-core
2323 CString gitExecPath = CGit::ms_MsysGitRootDir;
2324 gitExecPath.Append(L"libexec\\git-core");
2325 m_Environment.AddToPath(gitExecPath);
2327 if (git_filter_register("filter", git_filter_filter_new(sh, m_Environment), GIT_FILTER_DRIVER_PRIORITY))
2328 return FALSE;
2330 #endif
2332 m_bInitialized = TRUE;
2333 return true;
2336 CString CGit::GetHomeDirectory() const
2338 const wchar_t * homeDir = wget_windows_home_directory();
2339 return homeDir;
2342 CString CGit::GetGitLocalConfig() const
2344 CString path;
2345 GitAdminDir::GetAdminDirPath(m_CurrentDir, path);
2346 path += L"config";
2347 return path;
2350 CStringA CGit::GetGitPathStringA(const CString &path)
2352 return CUnicodeUtils::GetUTF8(CTGitPath(path).GetGitPathString());
2355 CString CGit::GetGitGlobalConfig() const
2357 return g_Git.GetHomeDirectory() + L"\\.gitconfig";
2360 CString CGit::GetGitGlobalXDGConfigPath() const
2362 return g_Git.GetHomeDirectory() + L"\\.config\\git";
2365 CString CGit::GetGitGlobalXDGConfig() const
2367 return g_Git.GetGitGlobalXDGConfigPath() + L"\\config";
2370 CString CGit::GetGitSystemConfig() const
2372 const wchar_t * systemConfig = wget_msysgit_etc();
2373 return systemConfig;
2376 BOOL CGit::CheckCleanWorkTree(bool stagedOk /* false */)
2378 if (UsingLibGit2(GIT_CMD_CHECK_CLEAN_WT))
2380 CAutoRepository repo(GetGitRepository());
2381 if (!repo)
2382 return FALSE;
2384 if (git_repository_head_unborn(repo))
2385 return FALSE;
2387 git_status_options statusopt = GIT_STATUS_OPTIONS_INIT;
2388 statusopt.show = stagedOk ? GIT_STATUS_SHOW_WORKDIR_ONLY : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
2389 statusopt.flags = GIT_STATUS_OPT_UPDATE_INDEX | GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
2391 CAutoStatusList status;
2392 if (git_status_list_new(status.GetPointer(), repo, &statusopt))
2393 return FALSE;
2395 return (0 == git_status_list_entrycount(status));
2398 if (Run(L"git.exe rev-parse --verify HEAD", nullptr, nullptr, CP_UTF8))
2399 return FALSE;
2401 if (Run(L"git.exe update-index --ignore-submodules --refresh", nullptr, nullptr, CP_UTF8))
2402 return FALSE;
2404 if (Run(L"git.exe diff-files --quiet --ignore-submodules", nullptr, nullptr, CP_UTF8))
2405 return FALSE;
2407 if (!stagedOk && Run(L"git.exe diff-index --cached --quiet HEAD --ignore-submodules --", nullptr, nullptr, CP_UTF8))
2408 return FALSE;
2410 return TRUE;
2413 BOOL CGit::IsResultingCommitBecomeEmpty(bool amend /* = false */)
2415 CString cmd;
2416 cmd.Format(L"git.exe diff-index --cached --quiet HEAD%s --", amend ? L"~1" : L"");
2417 return Run(cmd, nullptr, nullptr, CP_UTF8) == 0;
2420 int CGit::Revert(const CString& commit, const CTGitPathList &list, CString& err)
2422 for (int i = 0; i < list.GetCount(); ++i)
2424 if (Revert(commit, list[i], err))
2425 return -1;
2427 return 0;
2429 int CGit::Revert(const CString& commit, const CTGitPath &path, CString& err)
2431 if(path.m_Action & CTGitPath::LOGACTIONS_REPLACED && !path.GetGitOldPathString().IsEmpty())
2433 if (CTGitPath(path.GetGitOldPathString()).IsDirectory())
2435 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()));
2436 return -1;
2438 if (path.Exists())
2440 CString force;
2441 // if the filenames only differ in case, we have to pass "-f"
2442 if (path.GetGitPathString().CompareNoCase(path.GetGitOldPathString()) == 0)
2443 force = L"-f ";
2444 CString cmd;
2445 cmd.Format(L"git.exe mv %s-- \"%s\" \"%s\"", static_cast<LPCWSTR>(force), static_cast<LPCWSTR>(path.GetGitPathString()), static_cast<LPCWSTR>(path.GetGitOldPathString()));
2446 if (Run(cmd, &err, CP_UTF8))
2447 return -1;
2449 else
2451 CString cmd;
2452 cmd.Format(L"git.exe rm -f --cached -- \"%s\"", static_cast<LPCWSTR>(path.GetGitPathString()));
2453 if (Run(cmd, &err, CP_UTF8))
2454 return -1;
2457 CString cmd;
2458 cmd.Format(L"git.exe checkout %s -f -- \"%s\"", static_cast<LPCWSTR>(commit), static_cast<LPCWSTR>(path.GetGitOldPathString()));
2459 if (Run(cmd, &err, CP_UTF8))
2460 return -1;
2462 else if(path.m_Action & CTGitPath::LOGACTIONS_ADDED)
2463 { //To init git repository, there are not HEAD, so we can use git reset command
2464 CString cmd;
2465 cmd.Format(L"git.exe rm -f --cached -- \"%s\"", static_cast<LPCWSTR>(path.GetGitPathString()));
2467 if (Run(cmd, &err, CP_UTF8))
2468 return -1;
2470 else
2472 CString cmd;
2473 cmd.Format(L"git.exe checkout %s -f -- \"%s\"", static_cast<LPCWSTR>(commit), static_cast<LPCWSTR>(path.GetGitPathString()));
2474 if (Run(cmd, &err, CP_UTF8))
2475 return -1;
2478 if (path.m_Action & CTGitPath::LOGACTIONS_DELETED)
2480 CString cmd;
2481 cmd.Format(L"git.exe add -f -- \"%s\"", static_cast<LPCWSTR>(path.GetGitPathString()));
2482 if (Run(cmd, &err, CP_UTF8))
2483 return -1;
2486 return 0;
2489 int CGit::HasWorkingTreeConflicts(git_repository* repo)
2491 ATLASSERT(repo);
2493 CAutoIndex index;
2494 if (git_repository_index(index.GetPointer(), repo))
2495 return -1;
2497 return git_index_has_conflicts(index);
2500 int CGit::HasWorkingTreeConflicts()
2502 if (UsingLibGit2(GIT_CMD_CHECKCONFLICTS))
2504 CAutoRepository repo(GetGitRepository());
2505 if (!repo)
2506 return -1;
2508 return HasWorkingTreeConflicts(repo);
2511 CString output;
2512 gitLastErr.Empty();
2513 if (Run(L"git.exe ls-files -u -t -z", &output, &gitLastErr, CP_UTF8))
2514 return -1;
2516 return output.IsEmpty() ? 0 : 1;
2519 bool CGit::IsFastForward(const CString &from, const CString &to, CGitHash * commonAncestor)
2521 if (UsingLibGit2(GIT_CMD_MERGE_BASE))
2523 CAutoRepository repo(GetGitRepository());
2524 if (!repo)
2525 return false;
2527 CGitHash fromHash, toHash, baseHash;
2528 if (GetHash(repo, toHash, FixBranchName(to)))
2529 return false;
2531 if (GetHash(repo, fromHash, FixBranchName(from)))
2532 return false;
2534 git_oid baseOid;
2535 if (git_merge_base(&baseOid, repo, toHash, fromHash))
2536 return false;
2538 baseHash = baseOid;
2540 if (commonAncestor)
2541 *commonAncestor = baseHash;
2543 return fromHash == baseHash;
2545 // else
2546 CString base;
2547 CGitHash basehash,hash;
2548 CString cmd;
2549 cmd.Format(L"git.exe merge-base %s %s", static_cast<LPCWSTR>(FixBranchName(to)), static_cast<LPCWSTR>(FixBranchName(from)));
2551 gitLastErr.Empty();
2552 if (Run(cmd, &base, &gitLastErr, CP_UTF8))
2553 return false;
2554 basehash = CGitHash::FromHexStrTry(base.Trim());
2556 GetHash(hash, from);
2558 if (commonAncestor)
2559 *commonAncestor = basehash;
2561 return hash == basehash;
2564 unsigned int CGit::Hash2int(const CGitHash &hash)
2566 int ret=0;
2567 for (int i = 0; i < 4; ++i)
2569 ret = ret << 8;
2570 ret |= hash.ToRaw()[i];
2572 return ret;
2575 int CGit::RefreshGitIndex()
2577 CString adminDir;
2578 GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir);
2579 // 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
2580 if (g_Git.m_IsUseGitDLL && !PathFileExists(adminDir + L"lfs"))
2582 CAutoLocker lock(g_Git.m_critGitDllSec);
2585 g_Git.CheckAndInitDll();
2587 int result = git_update_index();
2588 git_exit_cleanup();
2589 return result;
2591 }catch(...)
2593 git_exit_cleanup();
2594 return -1;
2598 else
2599 return Run(L"git.exe update-index --refresh", nullptr, nullptr, CP_UTF8);
2602 int CGit::GetOneFile(const CString &Refname, const CTGitPath &path, const CString &outputfile)
2604 if (UsingLibGit2(GIT_CMD_GETONEFILE))
2606 CAutoRepository repo(GetGitRepository());
2607 if (!repo)
2608 return -1;
2610 CGitHash hash;
2611 if (GetHash(repo, hash, Refname + L"^{}")) // add ^{} in order to dereference signed tags
2612 return -1;
2614 CAutoCommit commit;
2615 if (git_commit_lookup(commit.GetPointer(), repo, hash))
2616 return -1;
2618 CAutoTree tree;
2619 if (git_commit_tree(tree.GetPointer(), commit))
2620 return -1;
2622 CAutoTreeEntry entry;
2623 if (auto ret = git_tree_entry_bypath(entry.GetPointer(), tree, CUnicodeUtils::GetUTF8(path.GetGitPathString())); ret)
2624 return ret;
2626 if (git_tree_entry_filemode(entry) == GIT_FILEMODE_COMMIT)
2628 git_error_set_str(GIT_ERROR_NONE, "The requested object is a submodule and not a file.");
2629 return -1;
2632 CAutoBlob blob;
2633 if (git_tree_entry_to_object(reinterpret_cast<git_object**>(blob.GetPointer()), repo, entry))
2634 return -1;
2636 CAutoFILE file = _wfsopen(outputfile, L"wb", SH_DENYWR);
2637 if (file == nullptr)
2639 git_error_set_str(GIT_ERROR_NONE, "Could not create file.");
2640 return -1;
2642 CAutoBuf buf;
2643 git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT;
2644 opts.flags &= ~static_cast<uint32_t>(GIT_BLOB_FILTER_CHECK_FOR_BINARY);
2645 if (git_blob_filter(buf, blob, CUnicodeUtils::GetUTF8(path.GetGitPathString()), &opts))
2646 return -1;
2647 if (fwrite(buf->ptr, sizeof(char), buf->size, file) != buf->size)
2649 git_error_set_str(GIT_ERROR_OS, "Could not write to file.");
2650 return -1;
2653 return 0;
2655 else if (g_Git.m_IsUseGitDLL)
2657 CAutoLocker lock(g_Git.m_critGitDllSec);
2660 g_Git.CheckAndInitDll();
2661 CStringA ref, patha, outa;
2662 ref = CUnicodeUtils::GetUTF8(Refname);
2663 patha = CUnicodeUtils::GetUTF8(path.GetGitPathString());
2664 outa = CUnicodeUtils::GetUTF8(outputfile);
2665 ::DeleteFile(outputfile);
2666 int ret = git_checkout_file(ref, patha, outa.GetBuffer());
2667 outa.ReleaseBuffer();
2668 return ret;
2671 catch (const char * msg)
2673 gitLastErr = L"gitdll.dll reports: " + CString(msg);
2674 return -1;
2676 catch (...)
2678 gitLastErr = L"An unknown gitdll.dll error occurred.";
2679 return -1;
2682 else
2684 CString cmd;
2685 cmd.Format(L"git.exe cat-file -p %s:\"%s\"", static_cast<LPCWSTR>(Refname), static_cast<LPCWSTR>(path.GetGitPathString()));
2686 gitLastErr.Empty();
2687 return RunLogFile(cmd, outputfile, &gitLastErr);
2691 void CEnvironment::clear()
2693 __super::clear();
2694 baseptr = nullptr;
2697 bool CEnvironment::empty()
2699 return size() < 3; // three is minimum for an empty environment with an empty key and empty value: "=\0\0"
2702 CEnvironment::operator LPWSTR()
2704 if (empty())
2705 return nullptr;
2706 return data();
2709 CEnvironment::operator LPWSTR*()
2711 return &baseptr;
2714 void CEnvironment::CopyProcessEnvironment()
2716 if (!empty())
2717 pop_back();
2718 wchar_t* porig = GetEnvironmentStrings();
2719 wchar_t* p = porig;
2720 while(*p !=0 || *(p+1) !=0)
2721 this->push_back(*p++);
2723 push_back(L'\0');
2724 push_back(L'\0');
2725 baseptr = data();
2727 FreeEnvironmentStrings(porig);
2730 CString CEnvironment::GetEnv(const wchar_t* name)
2732 CString str;
2733 for (size_t i = 0; i < size(); ++i)
2735 str = &(*this)[i];
2736 int start =0;
2737 CString sname = str.Tokenize(L"=", start);
2738 if(sname.CompareNoCase(name) == 0)
2739 return &(*this)[i+start];
2740 i+=str.GetLength();
2742 return L"";
2745 void CEnvironment::SetEnv(const wchar_t* name, const wchar_t* value)
2747 unsigned int i;
2748 for (i = 0; i < size(); ++i)
2750 CString str = &(*this)[i];
2751 int start =0;
2752 CString sname = str.Tokenize(L"=", start);
2753 if(sname.CompareNoCase(name) == 0)
2754 break;
2755 i+=str.GetLength();
2758 if(i == size())
2760 if (!value) // as we haven't found the variable we want to remove, just return
2761 return;
2762 if (i == 0) // make inserting into an empty environment work
2764 this->push_back(L'\0');
2765 ++i;
2767 i -= 1; // roll back terminate \0\0
2768 this->push_back(L'\0');
2771 CEnvironment::iterator it;
2772 it=this->begin();
2773 it += i;
2775 while(*it && i<size())
2777 this->erase(it);
2778 it=this->begin();
2779 it += i;
2782 if (value == nullptr) // remove the variable
2784 this->erase(it);
2785 if (empty())
2786 baseptr = nullptr;
2787 else
2788 baseptr = data();
2789 return;
2792 while(*name)
2794 this->insert(it,*name++);
2795 ++i;
2796 it= begin()+i;
2799 this->insert(it, L'=');
2800 ++i;
2801 it= begin()+i;
2803 while(*value)
2805 this->insert(it,*value++);
2806 ++i;
2807 it= begin()+i;
2809 baseptr = data();
2812 void CEnvironment::AddToPath(CString value)
2814 value.TrimRight(L'\\');
2815 if (value.IsEmpty())
2816 return;
2818 CString path = GetEnv(L"PATH").TrimRight(L';') + L';';
2820 // do not double add paths to %PATH%
2821 if (path.Find(value + L';') >= 0 || path.Find(value + L"\\;") >= 0)
2822 return;
2824 path += value;
2826 SetEnv(L"PATH", path);
2829 int CGit::GetShortHASHLength() const
2831 return 8;
2834 CString CGit::GetShortName(const CString& ref, REF_TYPE *out_type)
2836 CString str=ref;
2837 CString shortname;
2838 REF_TYPE type = CGit::UNKNOWN;
2840 if (CGit::GetShortName(str, shortname, L"refs/heads/"))
2841 type = CGit::LOCAL_BRANCH;
2842 else if (CGit::GetShortName(str, shortname, L"refs/remotes/"))
2843 type = CGit::REMOTE_BRANCH;
2844 else if (CStringUtils::EndsWith(str, L"^{}") && CGit::GetShortName(str, shortname, L"refs/tags/"))
2845 type = CGit::ANNOTATED_TAG;
2846 else if (CGit::GetShortName(str, shortname, L"refs/tags/"))
2847 type = CGit::TAG;
2848 else if (CGit::GetShortName(str, shortname, L"refs/stash"))
2850 type = CGit::STASH;
2851 shortname = L"stash";
2853 else if (CGit::GetShortName(str, shortname, L"refs/bisect/"))
2855 CString bisectGood;
2856 CString bisectBad;
2857 g_Git.GetBisectTerms(&bisectGood, &bisectBad);
2858 wchar_t c;
2859 if (CStringUtils::StartsWith(shortname, bisectGood) && ((c = shortname.GetAt(bisectGood.GetLength())) == '-' || c == '\0'))
2861 type = CGit::BISECT_GOOD;
2862 shortname = bisectGood;
2865 if (CStringUtils::StartsWith(shortname, bisectBad) && ((c = shortname.GetAt(bisectBad.GetLength())) == '-' || c == '\0'))
2867 type = CGit::BISECT_BAD;
2868 shortname = bisectBad;
2871 if (CStringUtils::StartsWith(shortname, L"skip") && ((c = shortname.GetAt(4)) == '-' || c == '\0'))
2873 type = CGit::BISECT_SKIP;
2874 shortname = L"skip";
2877 else if (CGit::GetShortName(str, shortname, L"refs/notes/"))
2878 type = CGit::NOTES;
2879 else if (CGit::GetShortName(str, shortname, L"refs/"))
2880 type = CGit::UNKNOWN;
2881 else
2883 type = CGit::UNKNOWN;
2884 shortname = ref;
2887 if(out_type)
2888 *out_type = type;
2890 return shortname;
2893 bool CGit::UsingLibGit2(LIBGIT2_CMD cmd) const
2895 return m_IsUseLibGit2 && ((1 << cmd) & m_IsUseLibGit2_mask) ? true : false;
2898 void CGit::SetGit2CredentialCallback(void* callback)
2900 g_Git2CredCallback = static_cast<git_credential_acquire_cb>(callback);
2903 void CGit::SetGit2CertificateCheckCertificate(void* callback)
2905 g_Git2CheckCertificateCallback = static_cast<git_transport_certificate_check_cb>(callback);
2908 CString CGit::GetUnifiedDiffCmd(const CTGitPath& path, const CString& rev1, const CString& rev2, bool bMerge, bool bCombine, int diffContext, bool bNoPrefix)
2910 CString cmd;
2911 if (rev2 == GitRev::GetWorkingCopy())
2912 cmd.Format(L"git.exe diff --stat%s -p %s --", bNoPrefix ? L" --no-prefix" : L"", static_cast<LPCWSTR>(rev1));
2913 else if (rev1 == GitRev::GetWorkingCopy())
2914 cmd.Format(L"git.exe diff -R --stat%s -p %s --", bNoPrefix ? L" --no-prefix" : L"", static_cast<LPCWSTR>(rev2));
2915 else
2917 CString merge;
2918 if (bMerge)
2919 merge += L" -m";
2921 if (bCombine)
2922 merge += L" -c";
2924 CString unified;
2925 if (diffContext >= 0)
2926 unified.Format(L" --unified=%d", diffContext);
2927 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));
2930 if (!path.IsEmpty())
2932 cmd += L" \"";
2933 cmd += path.GetGitPathString();
2934 cmd += L'"';
2937 return cmd;
2940 static void UnifiedDiffStatToFile(const git_buf* text, void* payload)
2942 ATLASSERT(payload && text);
2943 auto file = static_cast<FILE*>(payload);
2944 fwrite(text->ptr, 1, text->size, file);
2945 fwrite("\n", 1, 1, file);
2948 static int UnifiedDiffToFile(const git_diff_delta * /* delta */, const git_diff_hunk * /* hunk */, const git_diff_line * line, void *payload)
2950 ATLASSERT(payload && line);
2951 auto file = static_cast<FILE*>(payload);
2952 if (line->origin == GIT_DIFF_LINE_CONTEXT || line->origin == GIT_DIFF_LINE_ADDITION || line->origin == GIT_DIFF_LINE_DELETION)
2953 fwrite(&line->origin, 1, 1, file);
2954 fwrite(line->content, 1, line->content_len, file);
2955 return 0;
2958 static int resolve_to_tree(git_repository *repo, const char *identifier, git_tree **tree)
2960 ATLASSERT(repo && identifier && tree);
2962 /* try to resolve identifier */
2963 CAutoObject obj;
2964 if (git_revparse_single(obj.GetPointer(), repo, identifier))
2965 return -1;
2967 if (obj == nullptr)
2968 return GIT_ENOTFOUND;
2970 int err = 0;
2971 switch (git_object_type(obj))
2973 case GIT_OBJECT_TREE:
2974 *tree = reinterpret_cast<git_tree*>(obj.Detach());
2975 break;
2976 case GIT_OBJECT_COMMIT:
2977 err = git_commit_tree(tree, reinterpret_cast<git_commit*>(static_cast<git_object*>(obj)));
2978 break;
2979 default:
2980 err = GIT_ENOTFOUND;
2983 return err;
2986 /* use libgit2 get unified diff */
2987 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)
2989 CStringA tree1 = CUnicodeUtils::GetUTF8(revNew);
2990 CStringA tree2 = CUnicodeUtils::GetUTF8(revOld);
2992 CAutoRepository repo(g_Git.GetGitRepository());
2993 if (!repo)
2994 return -1;
2996 int isHeadOrphan = git_repository_head_unborn(repo);
2997 if (isHeadOrphan == 1)
2998 return 0;
2999 else if (isHeadOrphan != 0)
3000 return -1;
3002 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
3003 CStringA pathA = CUnicodeUtils::GetUTF8(path.GetGitPathString());
3004 char *buf = pathA.GetBuffer();
3005 if (!pathA.IsEmpty())
3007 opts.pathspec.strings = &buf;
3008 opts.pathspec.count = 1;
3010 if (bNoPrefix)
3012 opts.new_prefix = "";
3013 opts.old_prefix = "";
3015 CAutoDiff diff;
3017 if (revNew == GitRev::GetWorkingCopy() || revOld == GitRev::GetWorkingCopy())
3019 CAutoTree t1;
3020 CAutoDiff diff2;
3022 if (revNew != GitRev::GetWorkingCopy() && resolve_to_tree(repo, tree1, t1.GetPointer()))
3023 return -1;
3025 if (revOld != GitRev::GetWorkingCopy() && resolve_to_tree(repo, tree2, t1.GetPointer()))
3026 return -1;
3028 if (git_diff_tree_to_index(diff.GetPointer(), repo, t1, nullptr, &opts))
3029 return -1;
3031 if (git_diff_index_to_workdir(diff2.GetPointer(), repo, nullptr, &opts))
3032 return -1;
3034 if (git_diff_merge(diff, diff2))
3035 return -1;
3037 else
3039 if (tree1.IsEmpty() && tree2.IsEmpty())
3040 return -1;
3042 if (tree1.IsEmpty())
3044 tree1 = tree2;
3045 tree2.Empty();
3048 CAutoTree t1;
3049 CAutoTree t2;
3050 if (!tree1.IsEmpty() && resolve_to_tree(repo, tree1, t1.GetPointer()))
3051 return -1;
3053 if (tree2.IsEmpty())
3055 /* don't check return value, there are not parent commit at first commit*/
3056 resolve_to_tree(repo, tree1 + "~1", t2.GetPointer());
3058 else if (resolve_to_tree(repo, tree2, t2.GetPointer()))
3059 return -1;
3060 if (git_diff_tree_to_tree(diff.GetPointer(), repo, t2, t1, &opts))
3061 return -1;
3064 CAutoDiffStats stats;
3065 if (git_diff_get_stats(stats.GetPointer(), diff))
3066 return -1;
3067 CAutoBuf statBuf;
3068 if (git_diff_stats_to_buf(statBuf, stats, GIT_DIFF_STATS_FULL, 0))
3069 return -1;
3070 statCallback(statBuf, data);
3072 for (size_t i = 0; i < git_diff_num_deltas(diff); ++i)
3074 CAutoPatch patch;
3075 if (git_patch_from_diff(patch.GetPointer(), diff, i))
3076 return -1;
3078 if (git_patch_print(patch, callback, data))
3079 return -1;
3082 pathA.ReleaseBuffer();
3084 return 0;
3087 int CGit::GetUnifiedDiff(const CTGitPath& path, const CString& rev1, const CString& rev2, CString patchfile, bool bMerge, bool bCombine, int diffContext, bool bNoPrefix)
3089 if (UsingLibGit2(GIT_CMD_DIFF))
3091 CAutoFILE file = _wfsopen(patchfile, L"wb", SH_DENYRW);
3092 if (!file)
3093 return -1;
3094 return GetUnifiedDiffLibGit2(path, rev1, rev2, UnifiedDiffStatToFile, UnifiedDiffToFile, file, bMerge, bNoPrefix);
3096 else
3098 CString cmd;
3099 cmd = GetUnifiedDiffCmd(path, rev1, rev2, bMerge, bCombine, diffContext, bNoPrefix);
3100 gitLastErr.Empty();
3101 return RunLogFile(cmd, patchfile, &gitLastErr);
3105 static void UnifiedDiffStatToStringA(const git_buf* text, void* payload)
3107 ATLASSERT(payload && text);
3108 auto str = static_cast<CStringA*>(payload);
3109 str->Append(text->ptr, static_cast<int>(text->size));
3110 str->AppendChar('\n');
3113 static int UnifiedDiffToStringA(const git_diff_delta * /*delta*/, const git_diff_hunk * /*hunk*/, const git_diff_line *line, void *payload)
3115 ATLASSERT(payload && line);
3116 auto str = static_cast<CStringA*>(payload);
3117 if (line->origin == GIT_DIFF_LINE_CONTEXT || line->origin == GIT_DIFF_LINE_ADDITION || line->origin == GIT_DIFF_LINE_DELETION)
3118 str->Append(&line->origin, 1);
3119 str->Append(line->content, static_cast<int>(line->content_len));
3120 return 0;
3123 int CGit::GetUnifiedDiff(const CTGitPath& path, const CString& rev1, const CString& rev2, CStringA* buffer, bool bMerge, bool bCombine, int diffContext)
3125 if (UsingLibGit2(GIT_CMD_DIFF))
3126 return GetUnifiedDiffLibGit2(path, rev1, rev2, UnifiedDiffStatToStringA, UnifiedDiffToStringA, buffer, bMerge, false);
3127 else
3129 CString cmd;
3130 cmd = GetUnifiedDiffCmd(path, rev1, rev2, bMerge, bCombine, diffContext);
3131 BYTE_VECTOR vector;
3132 int ret = Run(cmd, &vector);
3133 if (!vector.empty())
3135 vector.push_back(0); // vector is not NUL terminated
3136 buffer->Append(reinterpret_cast<const char*>(vector.data()));
3138 return ret;
3142 int CGit::GitRevert(int parent, const CGitHash &hash)
3144 if (UsingLibGit2(GIT_CMD_REVERT))
3146 CAutoRepository repo(GetGitRepository());
3147 if (!repo)
3148 return -1;
3150 CAutoCommit commit;
3151 if (git_commit_lookup(commit.GetPointer(), repo, hash))
3152 return -1;
3154 git_revert_options revert_opts = GIT_REVERT_OPTIONS_INIT;
3155 revert_opts.mainline = parent;
3156 int result = git_revert(repo, commit, &revert_opts);
3158 return !result ? 0 : -1;
3160 else
3162 CString cmd, merge;
3163 if (parent)
3164 merge.Format(L"-m %d ", parent);
3165 cmd.Format(L"git.exe revert --no-edit --no-commit %s%s", static_cast<LPCWSTR>(merge), static_cast<LPCWSTR>(hash.ToString()));
3166 gitLastErr = cmd + L'\n';
3167 if (Run(cmd, &gitLastErr, CP_UTF8))
3168 return -1;
3169 else
3171 gitLastErr.Empty();
3172 return 0;
3177 int CGit::DeleteRef(const CString& reference)
3179 if (UsingLibGit2(GIT_CMD_DELETETAGBRANCH))
3181 CAutoRepository repo(GetGitRepository());
3182 if (!repo)
3183 return -1;
3185 CStringA refA;
3186 if (CStringUtils::EndsWith(reference, L"^{}"))
3187 refA = CUnicodeUtils::GetUTF8(reference.Left(reference.GetLength() - static_cast<int>(wcslen(L"^{}"))));
3188 else
3189 refA = CUnicodeUtils::GetUTF8(reference);
3191 CAutoReference ref;
3192 if (git_reference_lookup(ref.GetPointer(), repo, refA))
3193 return -1;
3195 int result = -1;
3196 if (git_reference_is_tag(ref))
3197 result = git_tag_delete(repo, git_reference_shorthand(ref));
3198 else if (git_reference_is_branch(ref))
3199 result = git_branch_delete(ref);
3200 else if (git_reference_is_remote(ref))
3201 result = git_branch_delete(ref);
3202 else
3203 result = git_reference_delete(ref);
3205 return result;
3207 else
3209 CString cmd, shortname;
3210 if (GetShortName(reference, shortname, L"refs/heads/"))
3211 cmd.Format(L"git.exe branch -D -- %s", static_cast<LPCWSTR>(shortname));
3212 else if (GetShortName(reference, shortname, L"refs/tags/"))
3213 cmd.Format(L"git.exe tag -d -- %s", static_cast<LPCWSTR>(shortname));
3214 else if (GetShortName(reference, shortname, L"refs/remotes/"))
3215 cmd.Format(L"git.exe branch -r -D -- %s", static_cast<LPCWSTR>(shortname));
3216 else
3218 gitLastErr = L"unsupported reference type: " + reference;
3219 return -1;
3222 gitLastErr.Empty();
3223 if (Run(cmd, &gitLastErr, CP_UTF8))
3224 return -1;
3226 gitLastErr.Empty();
3227 return 0;
3231 bool CGit::LoadTextFile(const CString &filename, CString &msg)
3233 if (!PathFileExists(filename))
3234 return false;
3236 CAutoFILE pFile = _wfsopen(filename, L"rb", SH_DENYWR);
3237 if (!pFile)
3239 ::MessageBox(nullptr, L"Could not open " + filename, L"TortoiseGit", MB_ICONERROR);
3240 return true; // load no further files
3243 CStringA str;
3246 char s[8196] = { 0 };
3247 int read = static_cast<int>(fread(s, sizeof(char), sizeof(s), pFile));
3248 if (read == 0)
3249 break;
3250 str += CStringA(s, read);
3251 } while (true);
3252 msg += CUnicodeUtils::GetUnicode(str);
3253 msg.Replace(L"\r\n", L"\n");
3254 msg.TrimRight(L'\n');
3255 msg += L'\n';
3257 return true; // load no further files
3260 int CGit::GetWorkingTreeChanges(CTGitPathList& result, bool amend, const CTGitPathList* filterlist, bool includedStaged /* = false */, bool getStagingStatus /* = false */)
3262 if (IsInitRepos())
3263 return GetInitAddList(result, getStagingStatus);
3265 BYTE_VECTOR out;
3267 int count = 1;
3268 if (filterlist)
3269 count = filterlist->GetCount();
3270 ATLASSERT(count > 0);
3272 CString head = L"HEAD";
3273 if (amend)
3274 head = L"HEAD~1";
3276 CString gitStatusParams;
3277 if (ms_LastMsysGitVersion >= ConvertVersionToInt(2, 17, 0))
3278 gitStatusParams = L" --no-ahead-behind";
3280 for (int i = 0; i < count; ++i)
3282 ATLASSERT(!filterlist || !(*filterlist)[i].GetGitPathString().IsEmpty()); // pathspec must not be empty, be compatible with Git >= 2.16.0
3283 BYTE_VECTOR cmdout;
3284 CString cmd;
3285 if (ms_bCygwinGit || ms_bMsys2Git)
3287 // Prevent showing all files as modified when using cygwin's git
3288 if (!filterlist)
3289 cmd.Format(L"git.exe status%s --", static_cast<LPCWSTR>(gitStatusParams));
3290 else
3291 cmd.Format(L"git.exe status%s -- \"%s\"", static_cast<LPCWSTR>(gitStatusParams), static_cast<LPCWSTR>((*filterlist)[i].GetGitPathString()));
3292 Run(cmd, &cmdout);
3293 cmdout.clear();
3296 // also list staged files which will be in the commit
3297 if (includedStaged || !filterlist)
3298 Run(L"git.exe diff-index --cached --raw " + head + L" --numstat -C -M -z --", &cmdout);
3299 else
3301 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()));
3302 Run(cmd, &cmdout);
3305 if (!filterlist)
3306 cmd.Format(L"git.exe diff-index --raw %s --numstat -C%d%% -M%d%% -z --", static_cast<LPCWSTR>(head), ms_iSimilarityIndexThreshold, ms_iSimilarityIndexThreshold);
3307 else
3308 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()));
3310 BYTE_VECTOR cmdErr;
3311 if (Run(cmd, &cmdout, &cmdErr))
3313 size_t last = cmdErr.RevertFind(0);
3314 CString str;
3315 if (last != BYTE_VECTOR::npos)
3316 CGit::StringAppend(&str, &cmdErr[last + 1], CP_UTF8, static_cast<int>(cmdErr.size() - last) - 1);
3317 else if (!cmdErr.empty())
3318 CGit::StringAppend(&str, cmdErr.data(), CP_UTF8, static_cast<int>(cmdErr.size()) - 1);
3319 else
3320 str.Format(L"\"%s\" exited with an error code, but did not output any error message", static_cast<LPCWSTR>(cmd));
3321 MessageBox(nullptr, str, L"TortoiseGit", MB_OK | MB_ICONERROR);
3324 out.append(cmdout, 0);
3326 result.ParserFromLog(out);
3328 if (getStagingStatus)
3330 for (int i = 0; i < count; ++i)
3332 // 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
3333 BYTE_VECTOR cmdStagedUnfilteredOut, cmdUnstagedFilteredOut, cmdUnstagedUnfilteredOut;
3334 CString cmd = L"git.exe diff-index --cached --raw " + head + L" --numstat -C -M -z --";
3335 Run(cmd, &cmdStagedUnfilteredOut);
3337 CTGitPathList stagedUnfiltered;
3338 stagedUnfiltered.ParserFromLog(cmdStagedUnfilteredOut);
3340 cmd = L"git.exe diff-files --raw --numstat -C -M -z --";
3341 Run(cmd, &cmdUnstagedUnfilteredOut);
3343 if (filterlist)
3345 cmd.Format(L"git.exe diff-files --raw --numstat -C -M -z -- \"%s\"", static_cast<LPCTSTR>((*filterlist)[i].GetGitPathString()));
3346 Run(cmd, &cmdUnstagedFilteredOut);
3349 CTGitPathList unstagedUnfiltered, unstagedFiltered;
3350 unstagedUnfiltered.ParserFromLog(cmdUnstagedUnfilteredOut); // Necessary to detect partially staged files outside the filterlist
3351 if (filterlist)
3352 unstagedFiltered.ParserFromLog(cmdUnstagedFilteredOut);
3354 // File shows up both in the output of diff-index --cached and diff-files: partially staged
3355 // File shows up only in the output of diff-index --cached: totally staged
3356 // File shows up only in the output of diff-files: totally unstaged
3357 // TODO: This is inefficient. It would be better if ParserFromLog received the output of each command
3358 // separately and did this processing there, dropping the new function UpdateStagingStatusFromPath entirely.
3359 for (int j = 0; j < stagedUnfiltered.GetCount(); ++j)
3361 CString path = stagedUnfiltered[j].GetGitPathString();
3362 if (unstagedUnfiltered.LookForGitPath(path))
3363 result.UpdateStagingStatusFromPath(path, CTGitPath::StagingStatus::PartiallyStaged);
3364 else
3365 result.UpdateStagingStatusFromPath(path, CTGitPath::StagingStatus::TotallyStaged);
3367 const CTGitPathList& unstaged = filterlist ? unstagedFiltered : unstagedUnfiltered;
3368 for (int j = 0; j < unstaged.GetCount(); ++j)
3370 CString path = unstaged[j].GetGitPathString();
3371 if (!stagedUnfiltered.LookForGitPath(path))
3372 result.UpdateStagingStatusFromPath(path, CTGitPath::StagingStatus::TotallyUnstaged);
3374 // make sure conflicted files show up as unstaged instead of partially staged
3375 for (int j = 0; j < result.GetCount(); ++j)
3377 if (result[j].m_Action & CTGitPath::LOGACTIONS_UNMERGED)
3378 const_cast<CTGitPath&>(result[j]).m_stagingStatus = CTGitPath::StagingStatus::TotallyUnstaged;
3383 std::map<CString, int> duplicateMap;
3384 for (int i = 0; i < result.GetCount(); ++i)
3385 duplicateMap.insert(std::pair<CString, int>(result[i].GetGitPathString(), i));
3387 // handle delete conflict case, when remote : modified, local : deleted.
3388 for (int i = 0; i < count; ++i)
3390 BYTE_VECTOR cmdout;
3391 CString cmd;
3393 if (!filterlist)
3394 cmd = L"git.exe ls-files -u -t -z";
3395 else
3396 cmd.Format(L"git.exe ls-files -u -t -z -- \"%s\"", static_cast<LPCWSTR>((*filterlist)[i].GetGitPathString()));
3398 Run(cmd, &cmdout);
3400 CTGitPathList conflictlist;
3401 conflictlist.ParserFromLog(cmdout);
3402 for (int j = 0; j < conflictlist.GetCount(); ++j)
3404 auto existing = duplicateMap.find(conflictlist[j].GetGitPathString());
3405 if (existing != duplicateMap.end())
3407 CTGitPath& p = const_cast<CTGitPath&>(result[existing->second]);
3408 p.m_Action |= CTGitPath::LOGACTIONS_UNMERGED;
3410 else
3412 result.AddPath(conflictlist[j]);
3413 duplicateMap.insert(std::pair<CString, int>(result[i].GetGitPathString(), result.GetCount() - 1));
3418 // handle source files of file renames/moves (issue #860)
3419 // if a file gets renamed and the new file "git add"ed, diff-index doesn't list the source file anymore
3420 for (int i = 0; i < count; ++i)
3422 BYTE_VECTOR cmdout;
3423 CString cmd;
3425 if (!filterlist)
3426 cmd = L"git.exe ls-files -d -z";
3427 else
3428 cmd.Format(L"git.exe ls-files -d -z -- \"%s\"", static_cast<LPCWSTR>((*filterlist)[i].GetGitPathString()));
3430 Run(cmd, &cmdout);
3432 CTGitPathList deletelist;
3433 deletelist.ParserFromLog(cmdout, true);
3434 for (int j = 0; j < deletelist.GetCount(); ++j)
3436 auto existing = duplicateMap.find(deletelist[j].GetGitPathString());
3437 if (existing == duplicateMap.end())
3439 result.AddPath(deletelist[j]);
3440 duplicateMap.insert(std::pair<CString, int>(result[i].GetGitPathString(), result.GetCount() - 1));
3442 else
3444 CTGitPath& p = const_cast<CTGitPath&>(result[existing->second]);
3445 p.m_Action |= CTGitPath::LOGACTIONS_MISSING;
3450 return 0;
3453 int CGit::IsRebaseRunning()
3455 CString adminDir;
3456 if (!GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir))
3457 return -1;
3459 if (PathIsDirectory(adminDir + L"rebase-apply") || PathIsDirectory(adminDir + L"tgitrebase.active"))
3460 return 1;
3461 return 0;
3464 void CGit::GetBisectTerms(CString* good, CString* bad)
3466 static CString lastGood;
3467 static CString lastBad;
3468 static ULONGLONG lastRead = 0;
3470 SCOPE_EXIT
3472 if (bad)
3473 *bad = lastBad;
3474 if (good)
3475 *good = lastGood;
3478 #ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_H_
3479 // add some caching here, because this method might be called multiple times in a short time from LogDlg and RevisionGraph
3480 // as we only read a small file the performance effects should be negligible
3481 if (lastRead + 5000 > GetTickCount64())
3482 return;
3483 #endif
3485 lastGood = L"good";
3486 lastBad = L"bad";
3488 CString adminDir;
3489 if (!GitAdminDir::GetAdminDirPath(m_CurrentDir, adminDir))
3490 return;
3492 CString termsFile = adminDir + L"BISECT_TERMS";
3493 CAutoFILE fp;
3494 _wfopen_s(fp.GetPointer(), termsFile, L"rb");
3495 if (!fp)
3496 return;
3497 char badA[MAX_PATH] = { 0 };
3498 fgets(badA, sizeof(badA), fp);
3499 size_t len = strlen(badA);
3500 if (len > 0 && badA[len - 1] == '\n')
3501 badA[len - 1] = '\0';
3502 char goodA[MAX_PATH] = { 0 };
3503 fgets(goodA, sizeof(goodA), fp);
3504 len = strlen(goodA);
3505 if (len > 0 && goodA[len - 1] == '\n')
3506 goodA[len - 1] = '\0';
3507 lastGood = CUnicodeUtils::GetUnicode(goodA);
3508 lastBad = CUnicodeUtils::GetUnicode(badA);
3509 lastRead = GetTickCount64();
3512 int CGit::GetGitVersion(CString* versiondebug, CString* errStr)
3514 CString version, err;
3515 if (Run(L"git.exe --version", &version, &err, CP_UTF8))
3517 if (errStr)
3518 *errStr = err;
3519 return -1;
3522 int ver = 0;
3523 if (versiondebug)
3524 *versiondebug = version;
3528 int start = 0;
3529 CString str = version.Tokenize(L".", start);
3530 int space = str.ReverseFind(L' ');
3531 str = str.Mid(space + 1, start);
3532 ver = _wtol(str);
3533 ver <<= 24;
3535 version = version.Mid(start);
3536 start = 0;
3538 str = version.Tokenize(L".", start);
3539 ver |= (_wtol(str) & 0xFF) << 16;
3541 str = version.Tokenize(L".", start);
3542 ver |= (_wtol(str) & 0xFF) << 8;
3544 str = version.Tokenize(L".", start);
3545 ver |= (_wtol(str) & 0xFF);
3547 catch (...)
3549 if (!ver)
3550 return -1;
3553 return ver;
3556 int CGit::GetGitNotes(const CGitHash& hash, CString& notes)
3558 CAutoRepository repo(GetGitRepository());
3559 if (!repo)
3560 return -1;
3562 CAutoNote note;
3563 int ret = git_note_read(note.GetPointer(), repo, nullptr, hash);
3564 if (ret == GIT_ENOTFOUND)
3566 notes.Empty();
3567 return 0;
3569 else if (ret)
3570 return -1;
3571 notes = CUnicodeUtils::GetUnicode(git_note_message(note));
3572 return 0;
3575 int CGit::SetGitNotes(const CGitHash& hash, const CString& notes)
3577 CAutoRepository repo(GetGitRepository());
3578 if (!repo)
3579 return -1;
3581 CAutoSignature signature;
3582 if (git_signature_default(signature.GetPointer(), repo) < 0)
3583 return -1;
3585 git_oid oid;
3586 if (git_note_create(&oid, repo, nullptr, signature, signature, hash, CUnicodeUtils::GetUTF8(notes), 1) < 0)
3587 return -1;
3589 return 0;
3592 CGitHash CGit::GetSubmodulePointer()
3594 CGitHash hash;
3595 if (GitAdminDir::IsBareRepo(g_Git.m_CurrentDir))
3596 return {};
3597 CString superprojectRoot;
3598 GitAdminDir::HasAdminDir(g_Git.m_CurrentDir, false, &superprojectRoot);
3599 if (superprojectRoot.IsEmpty())
3600 return {};
3602 CAutoRepository repo(superprojectRoot);
3603 if (!repo)
3604 return {};
3605 CAutoIndex index;
3606 if (git_repository_index(index.GetPointer(), repo))
3607 return {};
3609 CString submodulePath;
3610 if (superprojectRoot[superprojectRoot.GetLength() - 1] == L'\\')
3611 submodulePath = g_Git.m_CurrentDir.Right(g_Git.m_CurrentDir.GetLength() - superprojectRoot.GetLength());
3612 else
3613 submodulePath = g_Git.m_CurrentDir.Right(g_Git.m_CurrentDir.GetLength() - superprojectRoot.GetLength() - 1);
3614 submodulePath.Replace(L'\\', L'/');
3615 const git_index_entry* entry = git_index_get_bypath(index, CUnicodeUtils::GetUTF8(submodulePath), 0);
3616 if (!entry)
3617 return {};
3619 return entry->id;