Can sort tags in reversed order because newer versions are more useful
[TortoiseGit.git] / src / Git / Git.cpp
blob2d1961b023653eac4e5cc599cebe8ba64ad636c1
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2014 - 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 <map>
26 #include "UnicodeUtils.h"
27 #include "gitdll.h"
28 #include <fstream>
29 #include "FormatMessageWrapper.h"
30 #include "SmartHandle.h"
32 int CGit::m_LogEncode=CP_UTF8;
33 typedef CComCritSecLock<CComCriticalSection> CAutoLocker;
35 static LPTSTR nextpath(wchar_t *path, wchar_t *buf, size_t buflen)
37 wchar_t term, *base = path;
39 if (path == NULL || buf == NULL || buflen == 0)
40 return NULL;
42 term = (*path == L'"') ? *path++ : L';';
44 for (buflen--; *path && *path != term && buflen; buflen--)
45 *buf++ = *path++;
47 *buf = L'\0'; /* reserved a byte via initial subtract */
49 while (*path == term || *path == L';')
50 ++path;
52 return (path != base) ? path : NULL;
55 static inline BOOL FileExists(LPCTSTR lpszFileName)
57 struct _stat st;
58 return _tstat(lpszFileName, &st) == 0;
61 static BOOL FindGitPath()
63 size_t size;
64 _tgetenv_s(&size, NULL, 0, _T("PATH"));
66 if (!size)
68 return FALSE;
71 TCHAR *env = (TCHAR*)alloca(size * sizeof(TCHAR));
72 _tgetenv_s(&size, env, size, _T("PATH"));
74 TCHAR buf[MAX_PATH] = {0};
76 // search in all paths defined in PATH
77 while ((env = nextpath(env, buf, MAX_PATH - 1)) != NULL && *buf)
79 TCHAR *pfin = buf + _tcslen(buf)-1;
81 // ensure trailing slash
82 if (*pfin != _T('/') && *pfin != _T('\\'))
83 _tcscpy_s(++pfin, 2, _T("\\")); // we have enough space left, MAX_PATH-1 is used in nextpath above
85 const size_t len = _tcslen(buf);
87 if ((len + 7) < MAX_PATH)
88 _tcscpy_s(pfin + 1, MAX_PATH - len, _T("git.exe"));
89 else
90 break;
92 if ( FileExists(buf) )
94 // dir found
95 pfin[1] = 0;
96 CGit::ms_LastMsysGitDir = buf;
97 CGit::ms_LastMsysGitDir.TrimRight(_T("\\"));
98 if (CGit::ms_LastMsysGitDir.GetLength() > 4)
100 // often the msysgit\cmd folder is on the %PATH%, but
101 // that git.exe does not work, so try to guess the bin folder
102 CString binDir = CGit::ms_LastMsysGitDir.Mid(0, CGit::ms_LastMsysGitDir.GetLength() - 4) + _T("\\bin\\git.exe");
103 if (FileExists(binDir))
104 CGit::ms_LastMsysGitDir = CGit::ms_LastMsysGitDir.Mid(0, CGit::ms_LastMsysGitDir.GetLength() - 4) + _T("\\bin");
106 return TRUE;
110 return FALSE;
113 static bool g_bSortLogical;
114 static bool g_bSortLocalBranchesFirst;
115 static bool g_bSortTagsReversed;
117 static void GetSortOptions()
119 g_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
120 if (g_bSortLogical)
121 g_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
122 g_bSortLocalBranchesFirst = !CRegDWORD(L"Software\\TortoiseGit\\NoSortLocalBranchesFirst", 0, false, HKEY_CURRENT_USER);
123 if (g_bSortLocalBranchesFirst)
124 g_bSortLocalBranchesFirst = !CRegDWORD(L"Software\\TortoiseGit\\NoSortLocalBranchesFirst", 0, false, HKEY_LOCAL_MACHINE);
125 g_bSortTagsReversed = !!CRegDWORD(L"Software\\TortoiseGit\\SortTagsReversed", 0, false, HKEY_LOCAL_MACHINE);
126 if (!g_bSortTagsReversed)
127 g_bSortTagsReversed = !!CRegDWORD(L"Software\\TortoiseGit\\SortTagsReversed", 0, false, HKEY_CURRENT_USER);
130 static int LogicalComparePredicate(const CString &left, const CString &right)
132 if (g_bSortLogical)
133 return StrCmpLogicalW(left, right) < 0;
134 return StrCmpI(left, right) < 0;
137 static int LogicalCompareReversedPredicate(const CString &left, const CString &right)
139 if (g_bSortLogical)
140 return StrCmpLogicalW(left, right) > 0;
141 return StrCmpI(left, right) > 0;
144 static int LogicalCompareBranchesPredicate(const CString &left, const CString &right)
146 if (g_bSortLocalBranchesFirst)
148 int leftIsRemote = left.Find(_T("remotes/"));
149 int rightIsRemote = right.Find(_T("remotes/"));
151 if (leftIsRemote == 0 && rightIsRemote < 0)
152 return false;
153 else if (leftIsRemote < 0 && rightIsRemote == 0)
154 return true;
156 if (g_bSortLogical)
157 return StrCmpLogicalW(left, right) < 0;
158 return StrCmpI(left, right) < 0;
161 #define CALL_OUTPUT_READ_CHUNK_SIZE 1024
163 CString CGit::ms_LastMsysGitDir;
164 int CGit::ms_LastMsysGitVersion = 0;
165 CGit g_Git;
168 CGit::CGit(void)
170 GetCurrentDirectory(MAX_PATH, m_CurrentDir.GetBuffer(MAX_PATH));
171 m_CurrentDir.ReleaseBuffer();
172 m_IsGitDllInited = false;
173 m_GitDiff=0;
174 m_GitSimpleListDiff=0;
175 m_IsUseGitDLL = !!CRegDWORD(_T("Software\\TortoiseGit\\UsingGitDLL"),1);
176 m_IsUseLibGit2 = !!CRegDWORD(_T("Software\\TortoiseGit\\UseLibgit2"), TRUE);
177 m_IsUseLibGit2_mask = CRegDWORD(_T("Software\\TortoiseGit\\UseLibgit2_mask"), (1 << GIT_CMD_MERGE_BASE) | (1 << GIT_CMD_DELETETAGBRANCH) | (1 << GIT_CMD_GETONEFILE));
179 SecureZeroMemory(&m_CurrentGitPi, sizeof(PROCESS_INFORMATION));
181 GetSortOptions();
182 this->m_bInitialized =false;
183 CheckMsysGitDir();
184 m_critGitDllSec.Init();
187 CGit::~CGit(void)
189 if(this->m_GitDiff)
191 git_close_diff(m_GitDiff);
192 m_GitDiff=0;
194 if(this->m_GitSimpleListDiff)
196 git_close_diff(m_GitSimpleListDiff);
197 m_GitSimpleListDiff=0;
201 bool CGit::IsBranchNameValid(const CString& branchname)
203 if (branchname.Left(1) == _T("-")) // branch names starting with a dash are discouraged when used with git.exe, see https://github.com/git/git/commit/6348624010888bd2353e5cebdc2b5329490b0f6d
204 return false;
205 if (branchname.FindOneOf(_T("\"|<>")) >= 0) // not valid on Windows
206 return false;
207 CStringA branchA = CUnicodeUtils::GetUTF8(_T("refs/heads/") + branchname);
208 return !!git_reference_is_valid_name(branchA);
211 int CGit::RunAsync(CString cmd, PROCESS_INFORMATION *piOut, HANDLE *hReadOut, HANDLE *hErrReadOut, CString *StdioFile)
213 SECURITY_ATTRIBUTES sa;
214 CAutoGeneralHandle hRead, hWrite, hReadErr, hWriteErr;
215 CAutoGeneralHandle hStdioFile;
217 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
218 sa.lpSecurityDescriptor=NULL;
219 sa.bInheritHandle=TRUE;
220 if (!CreatePipe(hRead.GetPointer(), hWrite.GetPointer(), &sa, 0))
222 CString err = CFormatMessageWrapper();
223 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": could not open stdout pipe: %s\n"), err.Trim());
224 return TGIT_GIT_ERROR_OPEN_PIP;
226 if (hErrReadOut && !CreatePipe(hReadErr.GetPointer(), hWriteErr.GetPointer(), &sa, 0))
228 CString err = CFormatMessageWrapper();
229 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": could not open stderr pipe: %s\n"), err.Trim());
230 return TGIT_GIT_ERROR_OPEN_PIP;
233 if(StdioFile)
235 hStdioFile=CreateFile(*StdioFile,GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,
236 &sa,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
239 STARTUPINFO si;
240 PROCESS_INFORMATION pi;
241 si.cb=sizeof(STARTUPINFO);
242 GetStartupInfo(&si);
244 if (hErrReadOut)
245 si.hStdError = hWriteErr;
246 else
247 si.hStdError = hWrite;
248 if(StdioFile)
249 si.hStdOutput=hStdioFile;
250 else
251 si.hStdOutput=hWrite;
253 si.wShowWindow=SW_HIDE;
254 si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
256 LPTSTR pEnv = (!m_Environment.empty()) ? &m_Environment[0]: NULL;
257 DWORD dwFlags = pEnv ? CREATE_UNICODE_ENVIRONMENT : 0;
259 // CREATE_NEW_CONSOLE makes ssh recognize that it has no console in order to launch askpass to ask for the password.
260 // DETACHED_PROCESS which was originally used here has the same effect, however, it prevents PowerShell from working (cf. issue #2143)
261 dwFlags |= CREATE_NEW_PROCESS_GROUP | CREATE_NEW_CONSOLE;
263 memset(&this->m_CurrentGitPi,0,sizeof(PROCESS_INFORMATION));
264 memset(&pi, 0, sizeof(PROCESS_INFORMATION));
266 if(cmd.Find(_T("git")) == 0)
268 int firstSpace = cmd.Find(_T(" "));
269 if (firstSpace > 0)
270 cmd = _T('"')+CGit::ms_LastMsysGitDir+_T("\\")+ cmd.Left(firstSpace) + _T('"')+ cmd.Mid(firstSpace);
271 else
272 cmd=_T('"')+CGit::ms_LastMsysGitDir+_T("\\")+cmd+_T('"');
275 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": executing %s\n"), cmd);
276 if(!CreateProcess(NULL,(LPWSTR)cmd.GetString(), NULL,NULL,TRUE,dwFlags,pEnv,(LPWSTR)m_CurrentDir.GetString(),&si,&pi))
278 CString err = CFormatMessageWrapper();
279 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": error while executing command: %s\n"), err.Trim());
280 return TGIT_GIT_ERROR_CREATE_PROCESS;
283 m_CurrentGitPi = pi;
285 if(piOut)
286 *piOut=pi;
287 if(hReadOut)
288 *hReadOut = hRead.Detach();
289 if(hErrReadOut)
290 *hErrReadOut = hReadErr.Detach();
291 return 0;
294 //Must use sperate function to convert ANSI str to union code string
295 //Becuase A2W use stack as internal convert buffer.
296 void CGit::StringAppend(CString *str, const BYTE *p, int code,int length)
298 if(str == NULL)
299 return ;
301 int len ;
302 if(length<0)
303 len = (int)strlen((const char*)p);
304 else
305 len=length;
306 if (len == 0)
307 return;
308 int currentContentLen = str->GetLength();
309 WCHAR * buf = str->GetBuffer(len * 4 + 1 + currentContentLen) + currentContentLen;
310 int appendedLen = MultiByteToWideChar(code, 0, (LPCSTR)p, len, buf, len * 4);
311 str->ReleaseBuffer(currentContentLen + appendedLen); // no - 1 because MultiByteToWideChar is called with a fixed length (thus no nul char included)
314 // This method was originally used to check for orphaned branches
315 BOOL CGit::CanParseRev(CString ref)
317 if (ref.IsEmpty())
318 ref = _T("HEAD");
320 CString cmdout;
321 if (Run(_T("git.exe rev-parse --revs-only ") + ref, &cmdout, CP_UTF8))
323 return FALSE;
325 if(cmdout.IsEmpty())
326 return FALSE;
328 return TRUE;
331 // Checks if we have an orphaned HEAD
332 BOOL CGit::IsInitRepos()
334 CGitHash hash;
335 // handle error on reading HEAD hash as init repo
336 if (GetHash(hash, _T("HEAD")) != 0)
337 return TRUE;
338 return hash.IsEmpty() ? TRUE : FALSE;
341 DWORD WINAPI CGit::AsyncReadStdErrThread(LPVOID lpParam)
343 PASYNCREADSTDERRTHREADARGS pDataArray;
344 pDataArray = (PASYNCREADSTDERRTHREADARGS)lpParam;
346 DWORD readnumber;
347 BYTE data[CALL_OUTPUT_READ_CHUNK_SIZE];
348 while (ReadFile(pDataArray->fileHandle, data, CALL_OUTPUT_READ_CHUNK_SIZE, &readnumber, NULL))
350 if (pDataArray->pcall->OnOutputErrData(data,readnumber))
351 break;
354 return 0;
357 int CGit::Run(CGitCall* pcall)
359 PROCESS_INFORMATION pi;
360 CAutoGeneralHandle hRead, hReadErr;
361 if (RunAsync(pcall->GetCmd(), &pi, hRead.GetPointer(), hReadErr.GetPointer()))
362 return TGIT_GIT_ERROR_CREATE_PROCESS;
364 CAutoGeneralHandle piThread(pi.hThread);
365 CAutoGeneralHandle piProcess(pi.hProcess);
367 ASYNCREADSTDERRTHREADARGS threadArguments;
368 threadArguments.fileHandle = hReadErr;
369 threadArguments.pcall = pcall;
370 CAutoGeneralHandle thread = CreateThread(NULL, 0, AsyncReadStdErrThread, &threadArguments, 0, NULL);
372 DWORD readnumber;
373 BYTE data[CALL_OUTPUT_READ_CHUNK_SIZE];
374 bool bAborted=false;
375 while(ReadFile(hRead,data,CALL_OUTPUT_READ_CHUNK_SIZE,&readnumber,NULL))
377 // TODO: when OnOutputData() returns 'true', abort git-command. Send CTRL-C signal?
378 if(!bAborted)//For now, flush output when command aborted.
379 if(pcall->OnOutputData(data,readnumber))
380 bAborted=true;
382 if(!bAborted)
383 pcall->OnEnd();
385 if (thread)
386 WaitForSingleObject(thread, INFINITE);
388 WaitForSingleObject(pi.hProcess, INFINITE);
389 DWORD exitcode =0;
391 if(!GetExitCodeProcess(pi.hProcess,&exitcode))
393 CString err = CFormatMessageWrapper();
394 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": could not get exit code: %s\n"), err.Trim());
395 return TGIT_GIT_ERROR_GET_EXIT_CODE;
397 else
398 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": process exited: %d\n"), exitcode);
400 return exitcode;
402 class CGitCall_ByteVector : public CGitCall
404 public:
405 CGitCall_ByteVector(CString cmd,BYTE_VECTOR* pvector, BYTE_VECTOR* pvectorErr = NULL):CGitCall(cmd),m_pvector(pvector),m_pvectorErr(pvectorErr){}
406 virtual bool OnOutputData(const BYTE* data, size_t size)
408 if (!m_pvector || size == 0)
409 return false;
410 size_t oldsize=m_pvector->size();
411 m_pvector->resize(m_pvector->size()+size);
412 memcpy(&*(m_pvector->begin()+oldsize),data,size);
413 return false;
415 virtual bool OnOutputErrData(const BYTE* data, size_t size)
417 if (!m_pvectorErr || size == 0)
418 return false;
419 size_t oldsize = m_pvectorErr->size();
420 m_pvectorErr->resize(m_pvectorErr->size() + size);
421 memcpy(&*(m_pvectorErr->begin() + oldsize), data, size);
422 return false;
424 BYTE_VECTOR* m_pvector;
425 BYTE_VECTOR* m_pvectorErr;
428 int CGit::Run(CString cmd,BYTE_VECTOR *vector, BYTE_VECTOR *vectorErr)
430 CGitCall_ByteVector call(cmd, vector, vectorErr);
431 return Run(&call);
433 int CGit::Run(CString cmd, CString* output, int code)
435 CString err;
436 int ret;
437 ret = Run(cmd, output, &err, code);
439 if (output && !err.IsEmpty())
441 if (!output->IsEmpty())
442 *output += _T("\n");
443 *output += err;
446 return ret;
448 int CGit::Run(CString cmd, CString* output, CString* outputErr, int code)
450 BYTE_VECTOR vector, vectorErr;
451 int ret;
452 if (outputErr)
453 ret = Run(cmd, &vector, &vectorErr);
454 else
455 ret = Run(cmd, &vector);
457 vector.push_back(0);
458 StringAppend(output, &(vector[0]), code);
460 if (outputErr)
462 vectorErr.push_back(0);
463 StringAppend(outputErr, &(vectorErr[0]), code);
466 return ret;
469 class CGitCallCb : public CGitCall
471 public:
472 CGitCallCb(CString cmd, const GitReceiverFunc& recv): CGitCall(cmd), m_recv(recv) {}
474 virtual bool OnOutputData(const BYTE* data, size_t size) override
476 // Add data
477 if (size == 0)
478 return false;
479 int oldEndPos = m_buffer.GetLength();
480 memcpy(m_buffer.GetBufferSetLength(oldEndPos + (int)size) + oldEndPos, data, size);
481 m_buffer.ReleaseBuffer(oldEndPos + (int)size);
483 // Break into lines and feed to m_recv
484 int eolPos;
485 while ((eolPos = m_buffer.Find('\n')) >= 0)
487 m_recv(m_buffer.Left(eolPos));
488 m_buffer = m_buffer.Mid(eolPos + 1);
490 return false;
493 virtual bool OnOutputErrData(const BYTE*, size_t) override
495 return false; // Ignore error output for now
498 virtual void OnEnd() override
500 if (!m_buffer.IsEmpty())
501 m_recv(m_buffer);
502 m_buffer.Empty(); // Just for sure
505 private:
506 GitReceiverFunc m_recv;
507 CStringA m_buffer;
510 int CGit::Run(CString cmd, const GitReceiverFunc& recv)
512 CGitCallCb call(cmd, recv);
513 return Run(&call);
516 CString CGit::GetUserName(void)
518 CEnvironment env;
519 env.CopyProcessEnvironment();
520 CString envname = env.GetEnv(_T("GIT_AUTHOR_NAME"));
521 if (!envname.IsEmpty())
522 return envname;
523 return GetConfigValue(L"user.name");
525 CString CGit::GetUserEmail(void)
527 CEnvironment env;
528 env.CopyProcessEnvironment();
529 CString envmail = env.GetEnv(_T("GIT_AUTHOR_EMAIL"));
530 if (!envmail.IsEmpty())
531 return envmail;
533 return GetConfigValue(L"user.email");
536 CString CGit::GetConfigValue(const CString& name)
538 CString configValue;
539 int start = 0;
540 if(this->m_IsUseGitDLL)
542 CAutoLocker lock(g_Git.m_critGitDllSec);
546 CheckAndInitDll();
547 }catch(...)
550 CStringA key, value;
551 key = CUnicodeUtils::GetUTF8(name);
555 if (git_get_config(key, value.GetBufferSetLength(4096), 4096))
556 return CString();
558 catch (const char *msg)
560 ::MessageBox(NULL, _T("Could not get config.\nlibgit reports:\n") + CString(msg), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
561 return CString();
564 StringAppend(&configValue, (BYTE*)(LPCSTR)value);
565 return configValue.Tokenize(_T("\n"),start);
567 else
569 CString cmd;
570 cmd.Format(L"git.exe config %s", name);
571 Run(cmd, &configValue, nullptr, CP_UTF8);
572 return configValue.Tokenize(_T("\n"),start);
576 bool CGit::GetConfigValueBool(const CString& name)
578 CString configValue = GetConfigValue(name);
579 configValue.MakeLower();
580 configValue.Trim();
581 if(configValue == _T("true") || configValue == _T("on") || configValue == _T("yes") || StrToInt(configValue) != 0)
582 return true;
583 else
584 return false;
587 int CGit::SetConfigValue(const CString& key, const CString& value, CONFIG_TYPE type)
589 if(this->m_IsUseGitDLL)
591 CAutoLocker lock(g_Git.m_critGitDllSec);
595 CheckAndInitDll();
597 }catch(...)
600 CStringA keya, valuea;
601 keya = CUnicodeUtils::GetMulti(key, CP_UTF8);
602 valuea = CUnicodeUtils::GetUTF8(value);
606 return [=]() { return get_set_config(keya, valuea, type); }();
608 catch (const char *msg)
610 ::MessageBox(NULL, _T("Could not set config.\nlibgit reports:\n") + CString(msg), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
611 return -1;
614 else
616 CString cmd;
617 CString option;
618 switch(type)
620 case CONFIG_GLOBAL:
621 option = _T("--global");
622 break;
623 case CONFIG_SYSTEM:
624 option = _T("--system");
625 break;
626 default:
627 break;
629 cmd.Format(_T("git.exe config %s %s \"%s\""), option, key, value);
630 CString out;
631 if (Run(cmd, &out, nullptr, CP_UTF8))
633 return -1;
636 return 0;
639 int CGit::UnsetConfigValue(const CString& key, CONFIG_TYPE type)
641 if(this->m_IsUseGitDLL)
643 CAutoLocker lock(g_Git.m_critGitDllSec);
647 CheckAndInitDll();
648 }catch(...)
651 CStringA keya;
652 keya = CUnicodeUtils::GetMulti(key, CP_UTF8);
656 return [=]() { return get_set_config(keya, nullptr, type); }();
658 catch (const char *msg)
660 ::MessageBox(NULL, _T("Could not unset config.\nlibgit reports:\n") + CString(msg), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
661 return -1;
664 else
666 CString cmd;
667 CString option;
668 switch(type)
670 case CONFIG_GLOBAL:
671 option = _T("--global");
672 break;
673 case CONFIG_SYSTEM:
674 option = _T("--system");
675 break;
676 default:
677 break;
679 cmd.Format(_T("git.exe config %s --unset %s"), option, key);
680 CString out;
681 if (Run(cmd, &out, nullptr, CP_UTF8))
683 return -1;
686 return 0;
689 CString CGit::GetCurrentBranch(bool fallback)
691 CString output;
692 //Run(_T("git.exe branch"),&branch);
694 int result = GetCurrentBranchFromFile(m_CurrentDir, output, fallback);
695 if (result != 0 && ((result == 1 && !fallback) || result != 1))
697 return _T("(no branch)");
699 else
700 return output;
704 void CGit::GetRemoteTrackedBranch(const CString& localBranch, CString& pullRemote, CString& pullBranch)
706 if (localBranch.IsEmpty())
707 return;
709 CString configName;
710 configName.Format(L"branch.%s.remote", localBranch);
711 pullRemote = GetConfigValue(configName);
713 //Select pull-branch from current branch
714 configName.Format(L"branch.%s.merge", localBranch);
715 pullBranch = StripRefName(GetConfigValue(configName));
718 void CGit::GetRemoteTrackedBranchForHEAD(CString& remote, CString& branch)
720 CString refName;
721 if (GetCurrentBranchFromFile(m_CurrentDir, refName))
722 return;
723 GetRemoteTrackedBranch(StripRefName(refName), remote, branch);
726 CString CGit::GetFullRefName(const CString& shortRefName)
728 CString refName;
729 CString cmd;
730 cmd.Format(L"git.exe rev-parse --symbolic-full-name %s", shortRefName);
731 if (Run(cmd, &refName, NULL, CP_UTF8) != 0)
732 return CString();//Error
733 int iStart = 0;
734 return refName.Tokenize(L"\n", iStart);
737 CString CGit::StripRefName(CString refName)
739 if(wcsncmp(refName, L"refs/heads/", 11) == 0)
740 refName = refName.Mid(11);
741 else if(wcsncmp(refName, L"refs/", 5) == 0)
742 refName = refName.Mid(5);
743 int start =0;
744 return refName.Tokenize(_T("\n"),start);
747 int CGit::GetCurrentBranchFromFile(const CString &sProjectRoot, CString &sBranchOut, bool fallback)
749 // read current branch name like git-gui does, by parsing the .git/HEAD file directly
751 if ( sProjectRoot.IsEmpty() )
752 return -1;
754 CString sDotGitPath;
755 if (!g_GitAdminDir.GetAdminDirPath(sProjectRoot, sDotGitPath))
756 return -1;
758 CString sHeadFile = sDotGitPath + _T("HEAD");
760 FILE *pFile;
761 _tfopen_s(&pFile, sHeadFile.GetString(), _T("r"));
763 if (!pFile)
765 return -1;
768 char s[256] = {0};
769 fgets(s, sizeof(s), pFile);
771 fclose(pFile);
773 const char *pfx = "ref: refs/heads/";
774 const int len = 16;//strlen(pfx)
776 if ( !strncmp(s, pfx, len) )
778 //# We're on a branch. It might not exist. But
779 //# HEAD looks good enough to be a branch.
780 CStringA utf8Branch(s + len);
781 sBranchOut = CUnicodeUtils::GetUnicode(utf8Branch);
782 sBranchOut.TrimRight(_T(" \r\n\t"));
784 if ( sBranchOut.IsEmpty() )
785 return -1;
787 else if (fallback)
789 CStringA utf8Hash(s);
790 CString unicodeHash = CUnicodeUtils::GetUnicode(utf8Hash);
791 unicodeHash.TrimRight(_T(" \r\n\t"));
792 if (CGitHash::IsValidSHA1(unicodeHash))
793 sBranchOut = unicodeHash;
794 else
795 //# Assume this is a detached head.
796 sBranchOut = _T("HEAD");
797 return 1;
799 else
801 //# Assume this is a detached head.
802 sBranchOut = "HEAD";
804 return 1;
807 return 0;
810 int CGit::BuildOutputFormat(CString &format,bool IsFull)
812 CString log;
813 log.Format(_T("#<%c>%%x00"),LOG_REV_ITEM_BEGIN);
814 format += log;
815 if(IsFull)
817 log.Format(_T("#<%c>%%an%%x00"),LOG_REV_AUTHOR_NAME);
818 format += log;
819 log.Format(_T("#<%c>%%ae%%x00"),LOG_REV_AUTHOR_EMAIL);
820 format += log;
821 log.Format(_T("#<%c>%%ai%%x00"),LOG_REV_AUTHOR_DATE);
822 format += log;
823 log.Format(_T("#<%c>%%cn%%x00"),LOG_REV_COMMIT_NAME);
824 format += log;
825 log.Format(_T("#<%c>%%ce%%x00"),LOG_REV_COMMIT_EMAIL);
826 format += log;
827 log.Format(_T("#<%c>%%ci%%x00"),LOG_REV_COMMIT_DATE);
828 format += log;
829 log.Format(_T("#<%c>%%b%%x00"),LOG_REV_COMMIT_BODY);
830 format += log;
833 log.Format(_T("#<%c>%%m%%H%%x00"),LOG_REV_COMMIT_HASH);
834 format += log;
835 log.Format(_T("#<%c>%%P%%x00"),LOG_REV_COMMIT_PARENT);
836 format += log;
837 log.Format(_T("#<%c>%%s%%x00"),LOG_REV_COMMIT_SUBJECT);
838 format += log;
840 if(IsFull)
842 log.Format(_T("#<%c>%%x00"),LOG_REV_COMMIT_FILE);
843 format += log;
845 return 0;
848 CString CGit::GetLogCmd(const CString &range, const CTGitPath *path, int count, int mask, bool paramonly,
849 CFilterData *Filter)
851 CString cmd;
852 CString num;
853 CString since;
855 CString file = _T(" --");
857 if(path)
858 file.Format(_T(" -- \"%s\""),path->GetGitPathString());
860 if(count>0)
861 num.Format(_T("-n%d"),count);
863 CString param;
865 if(mask& LOG_INFO_STAT )
866 param += _T(" --numstat ");
867 if(mask& LOG_INFO_FILESTATE)
868 param += _T(" --raw ");
870 if(mask& LOG_INFO_FULLHISTORY)
871 param += _T(" --full-history ");
873 if(mask& LOG_INFO_BOUNDARY)
874 param += _T(" --left-right --boundary ");
876 if(mask& CGit::LOG_INFO_ALL_BRANCH)
877 param += _T(" --all ");
879 if(mask & CGit::LOG_INFO_LOCAL_BRANCHES)
880 param += _T(" --branches ");
882 if(mask& CGit::LOG_INFO_DETECT_COPYRENAME)
883 param += _T(" -C ");
885 if(mask& CGit::LOG_INFO_DETECT_RENAME )
886 param += _T(" -M ");
888 if(mask& CGit::LOG_INFO_FIRST_PARENT )
889 param += _T(" --first-parent ");
891 if(mask& CGit::LOG_INFO_NO_MERGE )
892 param += _T(" --no-merges ");
894 if(mask& CGit::LOG_INFO_FOLLOW)
895 param += _T(" --follow ");
897 if(mask& CGit::LOG_INFO_SHOW_MERGEDFILE)
898 param += _T(" -c ");
900 if(mask& CGit::LOG_INFO_FULL_DIFF)
901 param += _T(" --full-diff ");
903 if(mask& CGit::LOG_INFO_SIMPILFY_BY_DECORATION)
904 param += _T(" --simplify-by-decoration ");
906 param += range;
908 CString st1,st2;
910 if( Filter && (Filter->m_From != -1))
912 st1.Format(_T(" --max-age=%I64u "), Filter->m_From);
913 param += st1;
916 if( Filter && (Filter->m_To != -1))
918 st2.Format(_T(" --min-age=%I64u "), Filter->m_To);
919 param += st2;
922 bool isgrep = false;
923 if( Filter && (!Filter->m_Author.IsEmpty()))
925 st1.Format(_T(" --author=\"%s\"" ),Filter->m_Author);
926 param += st1;
927 isgrep = true;
930 if( Filter && (!Filter->m_Committer.IsEmpty()))
932 st1.Format(_T(" --committer=\"%s\"" ),Filter->m_Author);
933 param += st1;
934 isgrep = true;
937 if( Filter && (!Filter->m_MessageFilter.IsEmpty()))
939 st1.Format(_T(" --grep=\"%s\"" ),Filter->m_MessageFilter);
940 param += st1;
941 isgrep = true;
944 if(Filter && isgrep)
946 if(!Filter->m_IsRegex)
947 param += _T(" --fixed-strings ");
949 param += _T(" --regexp-ignore-case --extended-regexp ");
952 DWORD logOrderBy = CRegDWORD(_T("Software\\TortoiseGit\\LogOrderBy"), LOG_ORDER_TOPOORDER);
953 if (logOrderBy == LOG_ORDER_TOPOORDER)
954 param += _T(" --topo-order");
955 else if (logOrderBy == LOG_ORDER_DATEORDER)
956 param += _T(" --date-order");
958 if(paramonly) //tgit.dll.Git.cpp:setup_revisions() only looks at args[1] and greater. To account for this, pass a dummy parameter in the 0th place
959 cmd.Format(_T("--ignore-this-parameter %s -z %s --parents "), num, param);
960 else
962 CString log;
963 BuildOutputFormat(log,!(mask&CGit::LOG_INFO_ONLY_HASH));
964 cmd.Format(_T("git.exe log %s -z %s --parents --pretty=format:\"%s\""),
965 num,param,log);
968 cmd += file;
970 return cmd;
972 #define BUFSIZE 512
973 void GetTempPath(CString &path)
975 TCHAR lpPathBuffer[BUFSIZE] = { 0 };
976 DWORD dwRetVal;
977 DWORD dwBufSize=BUFSIZE;
978 dwRetVal = GetTortoiseGitTempPath(dwBufSize, // length of the buffer
979 lpPathBuffer); // buffer for path
980 if (dwRetVal > dwBufSize || (dwRetVal == 0))
982 path=_T("");
984 path.Format(_T("%s"),lpPathBuffer);
986 CString GetTempFile()
988 TCHAR lpPathBuffer[BUFSIZE] = { 0 };
989 DWORD dwRetVal;
990 DWORD dwBufSize=BUFSIZE;
991 TCHAR szTempName[BUFSIZE] = { 0 };
992 UINT uRetVal;
994 dwRetVal = GetTortoiseGitTempPath(dwBufSize, // length of the buffer
995 lpPathBuffer); // buffer for path
996 if (dwRetVal > dwBufSize || (dwRetVal == 0))
998 return _T("");
1000 // Create a temporary file.
1001 uRetVal = GetTempFileName(lpPathBuffer, // directory for tmp files
1002 TEXT("Patch"), // temp file name prefix
1003 0, // create unique name
1004 szTempName); // buffer for name
1007 if (uRetVal == 0)
1009 return _T("");
1012 return CString(szTempName);
1016 DWORD GetTortoiseGitTempPath(DWORD nBufferLength, LPTSTR lpBuffer)
1018 DWORD result = ::GetTempPath(nBufferLength, lpBuffer);
1019 if (result == 0) return 0;
1020 if (lpBuffer == NULL || (result + 13 > nBufferLength))
1022 if (lpBuffer)
1023 lpBuffer[0] = '\0';
1024 return result + 13;
1027 _tcscat_s(lpBuffer, nBufferLength, _T("TortoiseGit\\"));
1028 CreateDirectory(lpBuffer, NULL);
1030 return result + 13;
1033 int CGit::RunLogFile(CString cmd, const CString &filename, CString *stdErr)
1035 STARTUPINFO si;
1036 PROCESS_INFORMATION pi;
1037 si.cb=sizeof(STARTUPINFO);
1038 GetStartupInfo(&si);
1040 SECURITY_ATTRIBUTES psa={sizeof(psa),NULL,TRUE};;
1041 psa.bInheritHandle=TRUE;
1043 HANDLE hReadErr, hWriteErr;
1044 if (!CreatePipe(&hReadErr, &hWriteErr, &psa, 0))
1046 CString err = CFormatMessageWrapper();
1047 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": could not open stderr pipe: %s\n"), err.Trim());
1048 return TGIT_GIT_ERROR_OPEN_PIP;
1051 HANDLE houtfile=CreateFile(filename,GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,
1052 &psa,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
1054 if (houtfile == INVALID_HANDLE_VALUE)
1056 CString err = CFormatMessageWrapper();
1057 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": could not open stdout pipe: %s\n"), err.Trim());
1058 CloseHandle(hReadErr);
1059 CloseHandle(hWriteErr);
1060 return TGIT_GIT_ERROR_OPEN_PIP;
1063 si.wShowWindow = SW_HIDE;
1064 si.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
1065 si.hStdOutput = houtfile;
1066 si.hStdError = hWriteErr;
1068 LPTSTR pEnv = (!m_Environment.empty()) ? &m_Environment[0]: NULL;
1069 DWORD dwFlags = pEnv ? CREATE_UNICODE_ENVIRONMENT : 0;
1071 if(cmd.Find(_T("git")) == 0)
1072 cmd=CGit::ms_LastMsysGitDir+_T("\\")+cmd;
1074 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": executing %s\n"), cmd);
1075 if(!CreateProcess(NULL,(LPWSTR)cmd.GetString(), NULL,NULL,TRUE,dwFlags,pEnv,(LPWSTR)m_CurrentDir.GetString(),&si,&pi))
1077 CString err = CFormatMessageWrapper();
1078 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": failed to create Process: %s\n"), err.Trim());
1079 stdErr = &err;
1080 CloseHandle(hReadErr);
1081 CloseHandle(hWriteErr);
1082 CloseHandle(houtfile);
1083 return TGIT_GIT_ERROR_CREATE_PROCESS;
1086 BYTE_VECTOR stderrVector;
1087 CGitCall_ByteVector pcall(L"", nullptr, &stderrVector);
1088 HANDLE thread;
1089 ASYNCREADSTDERRTHREADARGS threadArguments;
1090 threadArguments.fileHandle = hReadErr;
1091 threadArguments.pcall = &pcall;
1092 thread = CreateThread(nullptr, 0, AsyncReadStdErrThread, &threadArguments, 0, nullptr);
1094 WaitForSingleObject(pi.hProcess,INFINITE);
1096 CloseHandle(hWriteErr);
1097 CloseHandle(hReadErr);
1099 if (thread)
1100 WaitForSingleObject(thread, INFINITE);
1102 stderrVector.push_back(0);
1103 StringAppend(stdErr, &(stderrVector[0]), CP_UTF8);
1105 DWORD exitcode = 0;
1106 if (!GetExitCodeProcess(pi.hProcess, &exitcode))
1108 CString err = CFormatMessageWrapper();
1109 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": could not get exit code: %s\n"), err.Trim());
1110 return TGIT_GIT_ERROR_GET_EXIT_CODE;
1112 else
1113 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": process exited: %d\n"), exitcode);
1115 CloseHandle(pi.hThread);
1116 CloseHandle(pi.hProcess);
1117 CloseHandle(houtfile);
1118 return exitcode;
1121 git_repository * CGit::GetGitRepository() const
1123 git_repository * repo = nullptr;
1124 git_repository_open(&repo, GetGitPathStringA(m_CurrentDir));
1125 return repo;
1128 int CGit::GetHash(git_repository * repo, CGitHash &hash, const CString& friendname, bool skipFastCheck /* = false */)
1130 ATLASSERT(repo);
1132 // no need to parse a ref if it's already a 40-byte hash
1133 if (!skipFastCheck && CGitHash::IsValidSHA1(friendname))
1135 hash = CGitHash(friendname);
1136 return 0;
1139 int isHeadOrphan = git_repository_head_unborn(repo);
1140 if (isHeadOrphan != 0)
1142 hash.Empty();
1143 if (isHeadOrphan == 1)
1144 return 0;
1145 else
1146 return -1;
1149 CAutoObject gitObject;
1150 if (git_revparse_single(gitObject.GetPointer(), repo, CUnicodeUtils::GetUTF8(friendname)))
1151 return -1;
1153 const git_oid * oid = git_object_id(gitObject);
1154 if (!oid)
1155 return -1;
1157 hash = CGitHash((char *)oid->id);
1159 return 0;
1162 int CGit::GetHash(CGitHash &hash, const CString& friendname)
1164 // no need to parse a ref if it's already a 40-byte hash
1165 if (CGitHash::IsValidSHA1(friendname))
1167 hash = CGitHash(friendname);
1168 return 0;
1171 if (m_IsUseLibGit2)
1173 CAutoRepository repo(GetGitRepository());
1174 if (!repo)
1175 return -1;
1177 return GetHash(repo, hash, friendname, true);
1179 else
1181 CString cmd;
1182 cmd.Format(_T("git.exe rev-parse %s" ),FixBranchName(friendname));
1183 gitLastErr.Empty();
1184 int ret = Run(cmd, &gitLastErr, NULL, CP_UTF8);
1185 hash = CGitHash(gitLastErr.Trim());
1186 if (ret == 0)
1187 gitLastErr.Empty();
1188 return ret;
1192 int CGit::GetInitAddList(CTGitPathList &outputlist)
1194 CString cmd;
1195 BYTE_VECTOR cmdout;
1197 cmd=_T("git.exe ls-files -s -t -z");
1198 outputlist.Clear();
1199 if (Run(cmd, &cmdout))
1200 return -1;
1202 if (outputlist.ParserFromLsFile(cmdout))
1203 return -1;
1204 for(int i = 0; i < outputlist.GetCount(); ++i)
1205 const_cast<CTGitPath&>(outputlist[i]).m_Action = CTGitPath::LOGACTIONS_ADDED;
1207 return 0;
1209 int CGit::GetCommitDiffList(const CString &rev1, const CString &rev2, CTGitPathList &outputlist, bool ignoreSpaceAtEol, bool ignoreSpaceChange, bool ignoreAllSpace , bool ignoreBlankLines)
1211 CString cmd;
1212 CString ignore;
1213 if (ignoreSpaceAtEol)
1214 ignore += _T(" --ignore-space-at-eol");
1215 if (ignoreSpaceChange)
1216 ignore += _T(" --ignore-space-change");
1217 if (ignoreAllSpace)
1218 ignore += _T(" --ignore-all-space");
1219 if (ignoreBlankLines)
1220 ignore += _T(" --ignore-blank-lines");
1222 if(rev1 == GIT_REV_ZERO || rev2 == GIT_REV_ZERO)
1224 //rev1=+_T("");
1225 if(rev1 == GIT_REV_ZERO)
1226 cmd.Format(_T("git.exe diff -r --raw -C -M --numstat -z %s %s --"), ignore, rev2);
1227 else
1228 cmd.Format(_T("git.exe diff -r -R --raw -C -M --numstat -z %s %s --"), ignore, rev1);
1230 else
1232 cmd.Format(_T("git.exe diff-tree -r --raw -C -M --numstat -z %s %s %s --"), ignore, rev2, rev1);
1235 BYTE_VECTOR out;
1236 if (Run(cmd, &out))
1237 return -1;
1239 outputlist.ParserFromLog(out);
1241 return 0;
1244 int addto_list_each_ref_fn(const char *refname, const unsigned char * /*sha1*/, int /*flags*/, void *cb_data)
1246 STRING_VECTOR *list = (STRING_VECTOR*)cb_data;
1247 CString str;
1248 g_Git.StringAppend(&str, (BYTE*)refname, CP_UTF8);
1249 list->push_back(str);
1250 return 0;
1253 int CGit::GetTagList(STRING_VECTOR &list)
1255 if (this->m_IsUseLibGit2)
1257 CAutoRepository repo(GetGitRepository());
1258 if (!repo)
1259 return -1;
1261 CAutoStrArray tag_names;
1263 if (git_tag_list(tag_names, repo))
1264 return -1;
1266 for (size_t i = 0; i < tag_names->count; ++i)
1268 CStringA tagName(tag_names->strings[i]);
1269 list.push_back(CUnicodeUtils::GetUnicode(tagName));
1272 std::sort(list.begin(), list.end(), g_bSortTagsReversed ? LogicalCompareReversedPredicate : LogicalComparePredicate);
1274 return 0;
1276 else
1278 CString cmd, output;
1279 cmd=_T("git.exe tag -l");
1280 int ret = Run(cmd, &output, NULL, CP_UTF8);
1281 if(!ret)
1283 int pos=0;
1284 CString one;
1285 while( pos>=0 )
1287 one=output.Tokenize(_T("\n"),pos);
1288 if (!one.IsEmpty())
1289 list.push_back(one);
1291 std::sort(list.begin(), list.end(), g_bSortTagsReversed ? LogicalCompareReversedPredicate : LogicalComparePredicate);
1293 return ret;
1297 CString CGit::GetGitLastErr(const CString& msg)
1299 if (this->m_IsUseLibGit2)
1300 return GetLibGit2LastErr(msg);
1301 else if (gitLastErr.IsEmpty())
1302 return msg + _T("\nUnknown git.exe error.");
1303 else
1305 CString lastError = gitLastErr;
1306 gitLastErr.Empty();
1307 return msg + _T("\n") + lastError;
1311 CString CGit::GetGitLastErr(const CString& msg, LIBGIT2_CMD cmd)
1313 if (UsingLibGit2(cmd))
1314 return GetLibGit2LastErr(msg);
1315 else if (gitLastErr.IsEmpty())
1316 return msg + _T("\nUnknown git.exe error.");
1317 else
1319 CString lastError = gitLastErr;
1320 gitLastErr.Empty();
1321 return msg + _T("\n") + lastError;
1325 CString CGit::GetLibGit2LastErr()
1327 const git_error *libgit2err = giterr_last();
1328 if (libgit2err)
1330 CString lastError = CUnicodeUtils::GetUnicode(CStringA(libgit2err->message));
1331 giterr_clear();
1332 return _T("libgit2 returned: ") + lastError;
1334 else
1335 return _T("An error occoured in libgit2, but no message is available.");
1338 CString CGit::GetLibGit2LastErr(const CString& msg)
1340 if (!msg.IsEmpty())
1341 return msg + _T("\n") + GetLibGit2LastErr();
1342 return GetLibGit2LastErr();
1345 CString CGit::FixBranchName_Mod(CString& branchName)
1347 if(branchName == "FETCH_HEAD")
1348 branchName = DerefFetchHead();
1349 return branchName;
1352 CString CGit::FixBranchName(const CString& branchName)
1354 CString tempBranchName = branchName;
1355 FixBranchName_Mod(tempBranchName);
1356 return tempBranchName;
1359 bool CGit::IsBranchTagNameUnique(const CString& name)
1361 if (m_IsUseLibGit2)
1363 CAutoRepository repo(GetGitRepository());
1364 if (!repo)
1365 return true; // TODO: optimize error reporting
1367 CAutoReference tagRef;
1368 if (git_reference_lookup(tagRef.GetPointer(), repo, CUnicodeUtils::GetUTF8(L"refs/tags/" + name)))
1369 return true;
1371 CAutoReference branchRef;
1372 if (git_reference_lookup(branchRef.GetPointer(), repo, CUnicodeUtils::GetUTF8(L"refs/heads/" + name)))
1373 return true;
1375 return false;
1377 // else
1378 CString output;
1380 CString cmd;
1381 cmd.Format(_T("git.exe show-ref --tags --heads refs/heads/%s refs/tags/%s"), name, name);
1382 int ret = Run(cmd, &output, NULL, CP_UTF8);
1383 if (!ret)
1385 int i = 0, pos = 0;
1386 while (pos >= 0)
1388 if (!output.Tokenize(_T("\n"), pos).IsEmpty())
1389 ++i;
1391 if (i >= 2)
1392 return false;
1395 return true;
1398 bool CGit::BranchTagExists(const CString& name, bool isBranch /*= true*/)
1400 if (m_IsUseLibGit2)
1402 CAutoRepository repo(GetGitRepository());
1403 if (!repo)
1404 return false; // TODO: optimize error reporting
1406 CString prefix;
1407 if (isBranch)
1408 prefix = _T("refs/heads/");
1409 else
1410 prefix = _T("refs/tags/");
1412 CAutoReference ref;
1413 if (git_reference_lookup(ref.GetPointer(), repo, CUnicodeUtils::GetUTF8(prefix + name)))
1414 return false;
1416 return true;
1418 // else
1419 CString cmd, output;
1421 cmd = _T("git.exe show-ref ");
1422 if (isBranch)
1423 cmd += _T("--heads ");
1424 else
1425 cmd += _T("--tags ");
1427 cmd += _T("refs/heads/") + name;
1428 cmd += _T(" refs/tags/") + name;
1430 int ret = Run(cmd, &output, NULL, CP_UTF8);
1431 if (!ret)
1433 if (!output.IsEmpty())
1434 return true;
1437 return false;
1440 CString CGit::DerefFetchHead()
1442 CString dotGitPath;
1443 g_GitAdminDir.GetAdminDirPath(m_CurrentDir, dotGitPath);
1444 std::ifstream fetchHeadFile((dotGitPath + L"FETCH_HEAD").GetString(), std::ios::in | std::ios::binary);
1445 int forMergeLineCount = 0;
1446 std::string line;
1447 std::string hashToReturn;
1448 while(getline(fetchHeadFile, line))
1450 //Tokenize this line
1451 std::string::size_type prevPos = 0;
1452 std::string::size_type pos = line.find('\t');
1453 if(pos == std::string::npos) continue; //invalid line
1455 std::string hash = line.substr(0, pos);
1456 ++pos; prevPos = pos; pos = line.find('\t', pos); if(pos == std::string::npos) continue;
1458 bool forMerge = pos == prevPos;
1459 ++pos; prevPos = pos; pos = line.size(); if(pos == std::string::npos) continue;
1461 std::string remoteBranch = line.substr(prevPos, pos - prevPos);
1463 //Process this line
1464 if(forMerge)
1466 hashToReturn = hash;
1467 ++forMergeLineCount;
1468 if(forMergeLineCount > 1)
1469 return L""; //More then 1 branch to merge found. Octopus merge needed. Cannot pick single ref from FETCH_HEAD
1473 return CUnicodeUtils::GetUnicode(hashToReturn.c_str());
1476 int CGit::GetBranchList(STRING_VECTOR &list,int *current,BRANCH_TYPE type)
1478 int ret = 0;
1479 CString cur;
1480 if (m_IsUseLibGit2)
1482 CAutoRepository repo(GetGitRepository());
1483 if (!repo)
1484 return -1;
1486 if (git_repository_head_detached(repo) == 1)
1487 cur = _T("(detached from ");
1489 if ((type & (BRANCH_LOCAL | BRANCH_REMOTE)) != 0)
1491 git_branch_t flags = GIT_BRANCH_LOCAL;
1492 if ((type & BRANCH_LOCAL) && (type & BRANCH_REMOTE))
1493 flags = GIT_BRANCH_ALL;
1494 else if (type & BRANCH_REMOTE)
1495 flags = GIT_BRANCH_REMOTE;
1497 CAutoBranchIterator it;
1498 if (git_branch_iterator_new(it.GetPointer(), repo, flags))
1499 return -1;
1501 git_reference * ref = nullptr;
1502 git_branch_t branchType;
1503 while (git_branch_next(&ref, &branchType, it) == 0)
1505 CAutoReference autoRef(ref);
1506 const char * name = nullptr;
1507 if (git_branch_name(&name, ref))
1508 continue;
1510 CString branchname = CUnicodeUtils::GetUnicode(name);
1511 if (branchType & GIT_BRANCH_REMOTE)
1512 list.push_back(_T("remotes/") + branchname);
1513 else
1515 if (git_branch_is_head(ref))
1516 cur = branchname;
1517 list.push_back(branchname);
1522 else
1524 CString cmd, output;
1525 cmd = _T("git.exe branch --no-color");
1527 if ((type & BRANCH_ALL) == BRANCH_ALL)
1528 cmd += _T(" -a");
1529 else if (type & BRANCH_REMOTE)
1530 cmd += _T(" -r");
1532 ret = Run(cmd, &output, nullptr, CP_UTF8);
1533 if (!ret)
1535 int pos = 0;
1536 CString one;
1537 while (pos >= 0)
1539 one = output.Tokenize(_T("\n"), pos);
1540 one.Trim(L" \r\n\t");
1541 if (one.Find(L" -> ") >= 0 || one.IsEmpty())
1542 continue; // skip something like: refs/origin/HEAD -> refs/origin/master
1543 if (one[0] == _T('*'))
1545 one = one.Mid(2);
1546 cur = one;
1548 if (one.Left(10) != _T("(no branch") && one.Left(15) != _T("(detached from "))
1549 list.push_back(one);
1554 if(type & BRANCH_FETCH_HEAD && !DerefFetchHead().IsEmpty())
1555 list.push_back(L"FETCH_HEAD");
1557 std::sort(list.begin(), list.end(), LogicalCompareBranchesPredicate);
1559 if (current && cur.Left(10) != _T("(no branch") && cur.Left(15) != _T("(detached from "))
1561 for (unsigned int i = 0; i < list.size(); ++i)
1563 if (list[i] == cur)
1565 *current = i;
1566 break;
1571 return ret;
1574 int CGit::GetRemoteList(STRING_VECTOR &list)
1576 if (this->m_IsUseLibGit2)
1578 CAutoRepository repo(GetGitRepository());
1579 if (!repo)
1580 return -1;
1582 CAutoStrArray remotes;
1583 if (git_remote_list(remotes, repo))
1584 return -1;
1586 for (size_t i = 0; i < remotes->count; ++i)
1588 CStringA remote(remotes->strings[i]);
1589 list.push_back(CUnicodeUtils::GetUnicode(remote));
1592 std::sort(list.begin(), list.end());
1594 return 0;
1596 else
1598 int ret;
1599 CString cmd, output;
1600 cmd=_T("git.exe remote");
1601 ret = Run(cmd, &output, NULL, CP_UTF8);
1602 if(!ret)
1604 int pos=0;
1605 CString one;
1606 while( pos>=0 )
1608 one=output.Tokenize(_T("\n"),pos);
1609 if (!one.IsEmpty())
1610 list.push_back(one);
1613 return ret;
1617 int CGit::GetRemoteTags(const CString& remote, STRING_VECTOR &list)
1619 CString cmd, out, err;
1620 cmd.Format(_T("git.exe ls-remote -t \"%s\""), remote);
1621 if (Run(cmd, &out, &err, CP_UTF8))
1623 MessageBox(NULL, err, _T("TortoiseGit"), MB_ICONERROR);
1624 return -1;
1627 int pos = 0;
1628 while (pos >= 0)
1630 CString one = out.Tokenize(_T("\n"), pos).Mid(51).Trim(); // sha1, tab + refs/tags/
1631 // dot not include annotated tags twice; this works, because an annotated tag appears twice (one normal tag and one with ^{} at the end)
1632 if (one.Find(_T("^{}")) >= 1)
1633 continue;
1634 if (!one.IsEmpty())
1635 list.push_back(one);
1637 std::sort(list.begin(), list.end(), g_bSortTagsReversed ? LogicalCompareReversedPredicate : LogicalComparePredicate);
1638 return 0;
1641 int libgit2_addto_list_each_ref_fn(git_reference *ref, void *payload)
1643 STRING_VECTOR *list = (STRING_VECTOR*)payload;
1644 CString str;
1645 g_Git.StringAppend(&str, (BYTE*)git_reference_name(ref), CP_UTF8);
1646 list->push_back(str);
1647 return 0;
1650 int CGit::GetRefList(STRING_VECTOR &list)
1652 if (this->m_IsUseLibGit2)
1654 CAutoRepository repo(GetGitRepository());
1655 if (!repo)
1656 return -1;
1658 if (git_reference_foreach(repo, libgit2_addto_list_each_ref_fn, &list))
1659 return -1;
1661 std::sort(list.begin(), list.end(), LogicalComparePredicate);
1663 return 0;
1665 else
1667 CString cmd, output;
1668 cmd=_T("git.exe show-ref -d");
1669 int ret = Run(cmd, &output, NULL, CP_UTF8);
1670 if(!ret)
1672 int pos=0;
1673 CString one;
1674 while( pos>=0 )
1676 one=output.Tokenize(_T("\n"),pos);
1677 int start=one.Find(_T(" "),0);
1678 if(start>0)
1680 CString name;
1681 name=one.Right(one.GetLength()-start-1);
1682 list.push_back(name);
1685 std::sort(list.begin(), list.end(), LogicalComparePredicate);
1687 return ret;
1691 typedef struct map_each_ref_payload {
1692 git_repository * repo;
1693 MAP_HASH_NAME * map;
1694 } map_each_ref_payload;
1696 int libgit2_addto_map_each_ref_fn(git_reference *ref, void *payload)
1698 map_each_ref_payload *payloadContent = (map_each_ref_payload*)payload;
1700 CString str;
1701 g_Git.StringAppend(&str, (BYTE*)git_reference_name(ref), CP_UTF8);
1703 CAutoObject gitObject;
1704 if (git_revparse_single(gitObject.GetPointer(), payloadContent->repo, git_reference_name(ref)))
1705 return 1;
1707 if (git_object_type(gitObject) == GIT_OBJ_TAG)
1709 str += _T("^{}"); // deref tag
1710 CAutoObject derefedTag;
1711 if (git_object_peel(derefedTag.GetPointer(), gitObject, GIT_OBJ_ANY))
1712 return 1;
1713 gitObject.Swap(derefedTag);
1716 const git_oid * oid = git_object_id(gitObject);
1717 if (oid == NULL)
1718 return 1;
1720 CGitHash hash((char *)oid->id);
1721 (*payloadContent->map)[hash].push_back(str);
1723 return 0;
1725 int CGit::GetMapHashToFriendName(git_repository* repo, MAP_HASH_NAME &map)
1727 ATLASSERT(repo);
1729 map_each_ref_payload payloadContent = { repo, &map };
1731 if (git_reference_foreach(repo, libgit2_addto_map_each_ref_fn, &payloadContent))
1732 return -1;
1734 for (auto it = map.begin(); it != map.end(); ++it)
1736 std::sort(it->second.begin(), it->second.end());
1739 return 0;
1742 int CGit::GetMapHashToFriendName(MAP_HASH_NAME &map)
1744 if (this->m_IsUseLibGit2)
1746 CAutoRepository repo(GetGitRepository());
1747 if (!repo)
1748 return -1;
1750 return GetMapHashToFriendName(repo, map);
1752 else
1754 CString cmd, output;
1755 cmd=_T("git.exe show-ref -d");
1756 int ret = Run(cmd, &output, NULL, CP_UTF8);
1757 if(!ret)
1759 int pos=0;
1760 CString one;
1761 while( pos>=0 )
1763 one=output.Tokenize(_T("\n"),pos);
1764 int start=one.Find(_T(" "),0);
1765 if(start>0)
1767 CString name;
1768 name=one.Right(one.GetLength()-start-1);
1770 CString hash;
1771 hash=one.Left(start);
1773 map[CGitHash(hash)].push_back(name);
1777 return ret;
1781 static void SetLibGit2SearchPath(int level, const CString &value)
1783 CStringA valueA = CUnicodeUtils::GetMulti(value, CP_UTF8);
1784 git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, level, valueA);
1787 static void SetLibGit2TemplatePath(const CString &value)
1789 CStringA valueA = CUnicodeUtils::GetMulti(value, CP_UTF8);
1790 git_libgit2_opts(GIT_OPT_SET_TEMPLATE_PATH, valueA);
1793 BOOL CGit::CheckMsysGitDir(BOOL bFallback)
1795 if (m_bInitialized)
1797 return TRUE;
1800 this->m_Environment.clear();
1801 m_Environment.CopyProcessEnvironment();
1803 TCHAR *oldpath;
1804 size_t homesize,size;
1806 // set HOME if not set already
1807 _tgetenv_s(&homesize, NULL, 0, _T("HOME"));
1808 if (!homesize)
1809 m_Environment.SetEnv(_T("HOME"), GetHomeDirectory());
1811 //setup ssh client
1812 CString sshclient=CRegString(_T("Software\\TortoiseGit\\SSH"));
1813 if (sshclient.IsEmpty())
1814 sshclient = CRegString(_T("Software\\TortoiseGit\\SSH"), _T(""), FALSE, HKEY_LOCAL_MACHINE);
1816 if(!sshclient.IsEmpty())
1818 m_Environment.SetEnv(_T("GIT_SSH"), sshclient);
1819 m_Environment.SetEnv(_T("SVN_SSH"), sshclient);
1821 else
1823 TCHAR sPlink[MAX_PATH] = {0};
1824 GetModuleFileName(NULL, sPlink, _countof(sPlink));
1825 LPTSTR ptr = _tcsrchr(sPlink, _T('\\'));
1826 if (ptr) {
1827 _tcscpy_s(ptr + 1, MAX_PATH - (ptr - sPlink + 1), _T("TortoiseGitPLink.exe"));
1828 m_Environment.SetEnv(_T("GIT_SSH"), sPlink);
1829 m_Environment.SetEnv(_T("SVN_SSH"), sPlink);
1834 TCHAR sAskPass[MAX_PATH] = {0};
1835 GetModuleFileName(NULL, sAskPass, _countof(sAskPass));
1836 LPTSTR ptr = _tcsrchr(sAskPass, _T('\\'));
1837 if (ptr)
1839 _tcscpy_s(ptr + 1, MAX_PATH - (ptr - sAskPass + 1), _T("SshAskPass.exe"));
1840 m_Environment.SetEnv(_T("DISPLAY"),_T(":9999"));
1841 m_Environment.SetEnv(_T("SSH_ASKPASS"),sAskPass);
1842 m_Environment.SetEnv(_T("GIT_ASKPASS"),sAskPass);
1846 // add git/bin path to PATH
1848 CRegString msysdir=CRegString(REG_MSYSGIT_PATH,_T(""),FALSE);
1849 CString str = msysdir;
1850 if(str.IsEmpty() || !FileExists(str + _T("\\git.exe")))
1852 if (!bFallback)
1853 return FALSE;
1855 CRegString msyslocalinstalldir = CRegString(REG_MSYSGIT_INSTALL_LOCAL, _T(""), FALSE, HKEY_CURRENT_USER);
1856 str = msyslocalinstalldir;
1857 str.TrimRight(_T("\\"));
1858 if (str.IsEmpty())
1860 CRegString msysinstalldir = CRegString(REG_MSYSGIT_INSTALL, _T(""), FALSE, HKEY_LOCAL_MACHINE);
1861 str = msysinstalldir;
1862 str.TrimRight(_T("\\"));
1864 if ( !str.IsEmpty() )
1866 str += "\\bin";
1867 msysdir=str;
1868 CGit::ms_LastMsysGitDir = str;
1869 msysdir.write();
1871 else
1873 // search PATH if git/bin directory is already present
1874 if ( FindGitPath() )
1876 m_bInitialized = TRUE;
1877 msysdir = CGit::ms_LastMsysGitDir;
1878 msysdir.write();
1879 return TRUE;
1882 return FALSE;
1885 else
1887 CGit::ms_LastMsysGitDir = str;
1890 // check for git.exe existance (maybe it was deinstalled in the meantime)
1891 if (!FileExists(CGit::ms_LastMsysGitDir + _T("\\git.exe")))
1892 return FALSE;
1894 // Configure libgit2 search paths
1895 CString msysGitDir;
1896 PathCanonicalize(msysGitDir.GetBufferSetLength(MAX_PATH), CGit::ms_LastMsysGitDir + _T("\\..\\etc"));
1897 msysGitDir.ReleaseBuffer();
1898 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_SYSTEM, msysGitDir);
1899 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_GLOBAL, g_Git.GetHomeDirectory());
1900 SetLibGit2SearchPath(GIT_CONFIG_LEVEL_XDG, g_Git.GetGitGlobalXDGConfigPath());
1901 CString msysGitTemplateDir;
1902 PathCanonicalize(msysGitTemplateDir.GetBufferSetLength(MAX_PATH), CGit::ms_LastMsysGitDir + _T("\\..\\share\\git-core\\templates"));
1903 msysGitTemplateDir.ReleaseBuffer();
1904 SetLibGit2TemplatePath(msysGitTemplateDir);
1906 //set path
1907 _tdupenv_s(&oldpath,&size,_T("PATH"));
1909 CString path;
1910 path.Format(_T("%s;%s"),oldpath,str + _T(";")+ (CString)CRegString(REG_MSYSGIT_EXTRA_PATH,_T(""),FALSE));
1912 m_Environment.SetEnv(_T("PATH"), path);
1914 CString str1 = m_Environment.GetEnv(_T("PATH"));
1916 CString sOldPath = oldpath;
1917 free(oldpath);
1919 m_bInitialized = TRUE;
1920 return true;
1923 CString CGit::GetHomeDirectory() const
1925 const wchar_t * homeDir = wget_windows_home_directory();
1926 return CString(homeDir, (int)wcslen(homeDir));
1929 CString CGit::GetGitLocalConfig() const
1931 CString path;
1932 g_GitAdminDir.GetAdminDirPath(m_CurrentDir, path);
1933 path += _T("config");
1934 return path;
1937 CStringA CGit::GetGitPathStringA(const CString &path)
1939 return CUnicodeUtils::GetUTF8(CTGitPath(path).GetGitPathString());
1942 CString CGit::GetGitGlobalConfig() const
1944 return g_Git.GetHomeDirectory() + _T("\\.gitconfig");
1947 CString CGit::GetGitGlobalXDGConfigPath() const
1949 return g_Git.GetHomeDirectory() + _T("\\.config\\git");
1952 CString CGit::GetGitGlobalXDGConfig() const
1954 return g_Git.GetGitGlobalXDGConfigPath() + _T("\\config");
1957 CString CGit::GetGitSystemConfig() const
1959 const wchar_t * systemConfig = wget_msysgit_etc();
1960 return CString(systemConfig, (int)wcslen(systemConfig));
1963 BOOL CGit::CheckCleanWorkTree()
1965 CString out;
1966 CString cmd;
1967 cmd=_T("git.exe rev-parse --verify HEAD");
1969 if(Run(cmd,&out,CP_UTF8))
1970 return FALSE;
1972 cmd=_T("git.exe update-index --ignore-submodules --refresh");
1973 if(Run(cmd,&out,CP_UTF8))
1974 return FALSE;
1976 cmd=_T("git.exe diff-files --quiet --ignore-submodules");
1977 if(Run(cmd,&out,CP_UTF8))
1978 return FALSE;
1980 cmd = _T("git.exe diff-index --cached --quiet HEAD --ignore-submodules --");
1981 if(Run(cmd,&out,CP_UTF8))
1982 return FALSE;
1984 return TRUE;
1986 int CGit::Revert(const CString& commit, const CTGitPathList &list, bool)
1988 int ret;
1989 for (int i = 0; i < list.GetCount(); ++i)
1991 ret = Revert(commit, (CTGitPath&)list[i]);
1992 if(ret)
1993 return ret;
1995 return 0;
1997 int CGit::Revert(const CString& commit, const CTGitPath &path)
1999 CString cmd, out;
2001 if(path.m_Action & CTGitPath::LOGACTIONS_REPLACED && !path.GetGitOldPathString().IsEmpty())
2003 if (CTGitPath(path.GetGitOldPathString()).IsDirectory())
2005 CString err;
2006 err.Format(_T("Cannot revert renaming of \"%s\". A directory with the old name \"%s\" exists."), path.GetGitPathString(), path.GetGitOldPathString());
2007 ::MessageBox(NULL, err, _T("TortoiseGit"), MB_OK|MB_ICONERROR);
2008 return -1;
2010 CString force;
2011 // if the filenames only differ in case, we have to pass "-f"
2012 if (path.GetGitPathString().CompareNoCase(path.GetGitOldPathString()) == 0)
2013 force = _T("-f ");
2014 cmd.Format(_T("git.exe mv %s-- \"%s\" \"%s\""), force, path.GetGitPathString(), path.GetGitOldPathString());
2015 if (Run(cmd, &out, CP_UTF8))
2017 ::MessageBox(NULL, out, _T("TortoiseGit"), MB_OK|MB_ICONERROR);
2018 return -1;
2021 cmd.Format(_T("git.exe checkout %s -f -- \"%s\""), commit, path.GetGitOldPathString());
2022 if (Run(cmd, &out, CP_UTF8))
2024 ::MessageBox(NULL, out, _T("TortoiseGit"), MB_OK|MB_ICONERROR);
2025 return -1;
2029 else if(path.m_Action & CTGitPath::LOGACTIONS_ADDED)
2030 { //To init git repository, there are not HEAD, so we can use git reset command
2031 cmd.Format(_T("git.exe rm -f --cached -- \"%s\""),path.GetGitPathString());
2033 if (Run(cmd, &out, CP_UTF8))
2035 ::MessageBox(NULL, out, _T("TortoiseGit"), MB_OK|MB_ICONERROR);
2036 return -1;
2039 else
2041 cmd.Format(_T("git.exe checkout %s -f -- \"%s\""), commit, path.GetGitPathString());
2042 if (Run(cmd, &out, CP_UTF8))
2044 ::MessageBox(NULL, out, _T("TortoiseGit"), MB_OK|MB_ICONERROR);
2045 return -1;
2049 if (path.m_Action & CTGitPath::LOGACTIONS_DELETED)
2051 cmd.Format(_T("git.exe add -f -- \"%s\""), path.GetGitPathString());
2052 if (Run(cmd, &out, CP_UTF8))
2054 ::MessageBox(NULL, out, _T("TortoiseGit"), MB_OK|MB_ICONERROR);
2055 return -1;
2059 return 0;
2062 int CGit::ListConflictFile(CTGitPathList &list, const CTGitPath *path)
2064 BYTE_VECTOR vector;
2066 CString cmd;
2067 if(path)
2068 cmd.Format(_T("git.exe ls-files -u -t -z -- \"%s\""),path->GetGitPathString());
2069 else
2070 cmd=_T("git.exe ls-files -u -t -z");
2072 if (Run(cmd, &vector))
2074 return -1;
2077 if (list.ParserFromLsFile(vector))
2078 return -1;
2080 return 0;
2083 bool CGit::IsFastForward(const CString &from, const CString &to, CGitHash * commonAncestor)
2085 if (UsingLibGit2(GIT_CMD_MERGE_BASE))
2087 CAutoRepository repo(GetGitRepository());
2088 if (!repo)
2089 return false;
2091 CGitHash fromHash, toHash, baseHash;
2092 if (GetHash(repo, toHash, FixBranchName(to)))
2093 return false;
2095 if (GetHash(repo, fromHash, FixBranchName(from)))
2096 return false;
2098 git_oid baseOid;
2099 if (git_merge_base(&baseOid, repo, (const git_oid*)toHash.m_hash, (const git_oid*)fromHash.m_hash))
2100 return false;
2102 baseHash = baseOid.id;
2104 if (commonAncestor)
2105 *commonAncestor = baseHash;
2107 return fromHash == baseHash;
2109 // else
2110 CString base;
2111 CGitHash basehash,hash;
2112 CString cmd, err;
2113 cmd.Format(_T("git.exe merge-base %s %s"), FixBranchName(to), FixBranchName(from));
2115 if (Run(cmd, &base, &err, CP_UTF8))
2117 return false;
2119 basehash = base.Trim();
2121 GetHash(hash, from);
2123 if (commonAncestor)
2124 *commonAncestor = basehash;
2126 return hash == basehash;
2129 unsigned int CGit::Hash2int(const CGitHash &hash)
2131 int ret=0;
2132 for (int i = 0; i < 4; ++i)
2134 ret = ret << 8;
2135 ret |= hash.m_hash[i];
2137 return ret;
2140 int CGit::RefreshGitIndex()
2142 if(g_Git.m_IsUseGitDLL)
2144 CAutoLocker lock(g_Git.m_critGitDllSec);
2147 return [] { return git_run_cmd("update-index","update-index -q --refresh"); }();
2149 }catch(...)
2151 return -1;
2155 else
2157 CString cmd,output;
2158 cmd=_T("git.exe update-index --refresh");
2159 return Run(cmd, &output, CP_UTF8);
2163 int CGit::GetOneFile(const CString &Refname, const CTGitPath &path, const CString &outputfile)
2165 if (UsingLibGit2(GIT_CMD_GETONEFILE))
2167 CAutoRepository repo(GetGitRepository());
2168 if (!repo)
2169 return -1;
2171 CGitHash hash;
2172 if (GetHash(repo, hash, Refname))
2173 return -1;
2175 CAutoCommit commit;
2176 if (git_commit_lookup(commit.GetPointer(), repo, (const git_oid *)hash.m_hash))
2177 return -1;
2179 CAutoTree tree;
2180 if (git_commit_tree(tree.GetPointer(), commit))
2181 return -1;
2183 CAutoTreeEntry entry;
2184 if (git_tree_entry_bypath(entry.GetPointer(), tree, CUnicodeUtils::GetUTF8(path.GetGitPathString())))
2185 return -1;
2187 CAutoBlob blob;
2188 if (git_tree_entry_to_object((git_object**)blob.GetPointer(), repo, entry))
2189 return -1;
2191 FILE *file = nullptr;
2192 _tfopen_s(&file, outputfile, _T("w"));
2193 if (file == nullptr)
2195 giterr_set_str(GITERR_NONE, "Could not create file.");
2196 return -1;
2198 CAutoBuf buf;
2199 if (git_blob_filtered_content(buf, blob, CUnicodeUtils::GetUTF8(path.GetGitPathString()), 0))
2201 fclose(file);
2202 return -1;
2204 if (fwrite(buf->ptr, sizeof(char), buf->size, file) != buf->size)
2206 giterr_set_str(GITERR_OS, "Could not write to file.");
2207 fclose(file);
2209 return -1;
2211 fclose(file);
2213 return 0;
2215 else if (g_Git.m_IsUseGitDLL)
2217 CAutoLocker lock(g_Git.m_critGitDllSec);
2220 g_Git.CheckAndInitDll();
2221 CStringA ref, patha, outa;
2222 ref = CUnicodeUtils::GetMulti(Refname, CP_UTF8);
2223 patha = CUnicodeUtils::GetMulti(path.GetGitPathString(), CP_UTF8);
2224 outa = CUnicodeUtils::GetMulti(outputfile, CP_UTF8);
2225 ::DeleteFile(outputfile);
2226 return git_checkout_file(ref, patha, outa);
2229 catch (const char * msg)
2231 gitLastErr = L"gitdll.dll reports: " + CString(msg);
2232 return -1;
2234 catch (...)
2236 gitLastErr = L"An unknown gitdll.dll error occurred.";
2237 return -1;
2240 else
2242 CString cmd;
2243 cmd.Format(_T("git.exe cat-file -p %s:\"%s\""), Refname, path.GetGitPathString());
2244 return RunLogFile(cmd, outputfile, &gitLastErr);
2247 void CEnvironment::CopyProcessEnvironment()
2249 TCHAR *porig = GetEnvironmentStrings();
2250 TCHAR *p = porig;
2251 while(*p !=0 || *(p+1) !=0)
2252 this->push_back(*p++);
2254 push_back(_T('\0'));
2255 push_back(_T('\0'));
2257 FreeEnvironmentStrings(porig);
2260 CString CEnvironment::GetEnv(const TCHAR *name)
2262 CString str;
2263 for (size_t i = 0; i < size(); ++i)
2265 str = &(*this)[i];
2266 int start =0;
2267 CString sname = str.Tokenize(_T("="),start);
2268 if(sname.CompareNoCase(name) == 0)
2270 return &(*this)[i+start];
2272 i+=str.GetLength();
2274 return _T("");
2277 void CEnvironment::SetEnv(const TCHAR *name, const TCHAR* value)
2279 unsigned int i;
2280 for (i = 0; i < size(); ++i)
2282 CString str = &(*this)[i];
2283 int start =0;
2284 CString sname = str.Tokenize(_T("="),start);
2285 if(sname.CompareNoCase(name) == 0)
2287 break;
2289 i+=str.GetLength();
2292 if(i == size())
2294 i -= 1; // roll back terminate \0\0
2295 this->push_back(_T('\0'));
2298 CEnvironment::iterator it;
2299 it=this->begin();
2300 it += i;
2302 while(*it && i<size())
2304 this->erase(it);
2305 it=this->begin();
2306 it += i;
2309 while(*name)
2311 this->insert(it,*name++);
2312 ++i;
2313 it= begin()+i;
2316 this->insert(it, _T('='));
2317 ++i;
2318 it= begin()+i;
2320 while(*value)
2322 this->insert(it,*value++);
2323 ++i;
2324 it= begin()+i;
2329 int CGit::GetGitEncode(TCHAR* configkey)
2331 CString str=GetConfigValue(configkey);
2333 if(str.IsEmpty())
2334 return CP_UTF8;
2336 return CUnicodeUtils::GetCPCode(str);
2340 int CGit::GetDiffPath(CTGitPathList *PathList, CGitHash *hash1, CGitHash *hash2, char *arg)
2342 GIT_FILE file=0;
2343 int ret=0;
2344 GIT_DIFF diff=0;
2346 CAutoLocker lock(g_Git.m_critGitDllSec);
2348 if(arg == NULL)
2349 diff = GetGitDiff();
2350 else
2351 git_open_diff(&diff, arg);
2353 if(diff ==NULL)
2354 return -1;
2356 bool isStat = 0;
2357 if(arg == NULL)
2358 isStat = true;
2359 else
2360 isStat = !!strstr(arg, "stat");
2362 int count=0;
2364 if(hash2 == NULL)
2365 ret = git_root_diff(diff, hash1->m_hash, &file, &count,isStat);
2366 else
2367 ret = git_do_diff(diff,hash2->m_hash,hash1->m_hash,&file,&count,isStat);
2369 if(ret)
2370 return -1;
2372 CTGitPath path;
2373 CString strnewname;
2374 CString stroldname;
2376 for (int j = 0; j < count; ++j)
2378 path.Reset();
2379 char *newname;
2380 char *oldname;
2382 strnewname.Empty();
2383 stroldname.Empty();
2385 int mode=0,IsBin=0,inc=0,dec=0;
2386 git_get_diff_file(diff,file,j,&newname,&oldname,
2387 &mode,&IsBin,&inc,&dec);
2389 StringAppend(&strnewname, (BYTE*)newname, CP_UTF8);
2390 StringAppend(&stroldname, (BYTE*)oldname, CP_UTF8);
2392 path.SetFromGit(strnewname,&stroldname);
2393 path.ParserAction((BYTE)mode);
2395 if(IsBin)
2397 path.m_StatAdd=_T("-");
2398 path.m_StatDel=_T("-");
2400 else
2402 path.m_StatAdd.Format(_T("%d"),inc);
2403 path.m_StatDel.Format(_T("%d"),dec);
2405 PathList->AddPath(path);
2407 git_diff_flush(diff);
2409 if(arg)
2410 git_close_diff(diff);
2412 return 0;
2415 int CGit::GetShortHASHLength() const
2417 return 7;
2420 CString CGit::GetShortName(const CString& ref, REF_TYPE *out_type)
2422 CString str=ref;
2423 CString shortname;
2424 REF_TYPE type = CGit::UNKNOWN;
2426 if (CGit::GetShortName(str, shortname, _T("refs/heads/")))
2428 type = CGit::LOCAL_BRANCH;
2431 else if (CGit::GetShortName(str, shortname, _T("refs/remotes/")))
2433 type = CGit::REMOTE_BRANCH;
2435 else if (CGit::GetShortName(str, shortname, _T("refs/tags/")))
2437 type = CGit::TAG;
2439 else if (CGit::GetShortName(str, shortname, _T("refs/stash")))
2441 type = CGit::STASH;
2442 shortname=_T("stash");
2444 else if (CGit::GetShortName(str, shortname, _T("refs/bisect/")))
2446 if (shortname.Find(_T("good")) == 0)
2448 type = CGit::BISECT_GOOD;
2449 shortname = _T("good");
2452 if (shortname.Find(_T("bad")) == 0)
2454 type = CGit::BISECT_BAD;
2455 shortname = _T("bad");
2458 else if (CGit::GetShortName(str, shortname, _T("refs/notes/")))
2460 type = CGit::NOTES;
2462 else if (CGit::GetShortName(str, shortname, _T("refs/")))
2464 type = CGit::UNKNOWN;
2466 else
2468 type = CGit::UNKNOWN;
2469 shortname = ref;
2472 if(out_type)
2473 *out_type = type;
2475 return shortname;
2478 bool CGit::UsingLibGit2(LIBGIT2_CMD cmd) const
2480 return m_IsUseLibGit2 && ((1 << cmd) & m_IsUseLibGit2_mask) ? true : false;
2483 CString CGit::GetUnifiedDiffCmd(const CTGitPath& path, const git_revnum_t& rev1, const git_revnum_t& rev2, bool bMerge, bool bCombine, int diffContext)
2485 CString cmd;
2486 if (rev2 == GitRev::GetWorkingCopy())
2487 cmd.Format(_T("git.exe diff --stat -p %s --"), rev1);
2488 else if (rev1 == GitRev::GetWorkingCopy())
2489 cmd.Format(_T("git.exe diff -R --stat -p %s --"), rev2);
2490 else
2492 CString merge;
2493 if (bMerge)
2494 merge += _T(" -m");
2496 if (bCombine)
2497 merge += _T(" -c");
2499 CString unified;
2500 if (diffContext > 0)
2501 unified.Format(_T(" --unified=%d"), diffContext);
2502 cmd.Format(_T("git.exe diff-tree -r -p %s %s --stat %s %s --"), merge, unified, rev1, rev2);
2505 if (!path.IsEmpty())
2507 cmd += _T(" \"");
2508 cmd += path.GetGitPathString();
2509 cmd += _T("\"");
2512 return cmd;
2515 static int UnifiedDiffToFile(const git_diff_delta * /* delta */, const git_diff_hunk * /* hunk */, const git_diff_line * line, void *payload)
2517 ATLASSERT(payload && line);
2518 if (line->origin == GIT_DIFF_LINE_CONTEXT || line->origin == GIT_DIFF_LINE_ADDITION || line->origin == GIT_DIFF_LINE_DELETION)
2519 fwrite(&line->origin, 1, 1, (FILE *)payload);
2520 fwrite(line->content, 1, line->content_len, (FILE *)payload);
2521 return 0;
2524 static int resolve_to_tree(git_repository *repo, const char *identifier, git_tree **tree)
2526 ATLASSERT(repo && identifier && tree);
2528 /* try to resolve identifier */
2529 CAutoObject obj;
2530 if (git_revparse_single(obj.GetPointer(), repo, identifier))
2531 return -1;
2533 if (obj == nullptr)
2534 return GIT_ENOTFOUND;
2536 int err = 0;
2537 switch (git_object_type(obj))
2539 case GIT_OBJ_TREE:
2540 *tree = (git_tree *)obj.Detach();
2541 break;
2542 case GIT_OBJ_COMMIT:
2543 err = git_commit_tree(tree, (git_commit *)(git_object*)obj);
2544 break;
2545 default:
2546 err = GIT_ENOTFOUND;
2549 return err;
2552 /* use libgit2 get unified diff */
2553 static int GetUnifiedDiffLibGit2(const CTGitPath& path, const git_revnum_t& revOld, const git_revnum_t& revNew, git_diff_line_cb callback, void *data, bool /* bMerge */)
2555 CStringA tree1 = CUnicodeUtils::GetMulti(revNew, CP_UTF8);
2556 CStringA tree2 = CUnicodeUtils::GetMulti(revOld, CP_UTF8);
2558 CAutoRepository repo(g_Git.GetGitRepository());
2559 if (!repo)
2560 return -1;
2562 int isHeadOrphan = git_repository_head_unborn(repo);
2563 if (isHeadOrphan == 1)
2564 return 0;
2565 else if (isHeadOrphan != 0)
2566 return -1;
2568 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
2569 CStringA pathA = CUnicodeUtils::GetMulti(path.GetGitPathString(), CP_UTF8);
2570 char *buf = pathA.GetBuffer();
2571 if (!pathA.IsEmpty())
2573 opts.pathspec.strings = &buf;
2574 opts.pathspec.count = 1;
2576 CAutoDiff diff;
2578 if (revNew == GitRev::GetWorkingCopy() || revOld == GitRev::GetWorkingCopy())
2580 CAutoTree t1;
2581 CAutoDiff diff2;
2583 if (revNew != GitRev::GetWorkingCopy() && resolve_to_tree(repo, tree1, t1.GetPointer()))
2584 return -1;
2586 if (revOld != GitRev::GetWorkingCopy() && resolve_to_tree(repo, tree2, t1.GetPointer()))
2587 return -1;
2589 if (git_diff_tree_to_index(diff.GetPointer(), repo, t1, nullptr, &opts))
2590 return -1;
2592 if (git_diff_index_to_workdir(diff2.GetPointer(), repo, nullptr, &opts))
2593 return -1;
2595 if (git_diff_merge(diff, diff2))
2596 return -1;
2598 else
2600 if (tree1.IsEmpty() && tree2.IsEmpty())
2601 return -1;
2603 if (tree1.IsEmpty())
2605 tree1 = tree2;
2606 tree2.Empty();
2609 CAutoTree t1;
2610 CAutoTree t2;
2611 if (!tree1.IsEmpty() && resolve_to_tree(repo, tree1, t1.GetPointer()))
2612 return -1;
2614 if (tree2.IsEmpty())
2616 /* don't check return value, there are not parent commit at first commit*/
2617 resolve_to_tree(repo, tree1 + "~1", t2.GetPointer());
2619 else if (resolve_to_tree(repo, tree2, t2.GetPointer()))
2620 return -1;
2621 if (git_diff_tree_to_tree(diff.GetPointer(), repo, t2, t1, &opts))
2622 return -1;
2625 for (size_t i = 0; i < git_diff_num_deltas(diff); ++i)
2627 CAutoPatch patch;
2628 if (git_patch_from_diff(patch.GetPointer(), diff, i))
2629 return -1;
2631 if (git_patch_print(patch, callback, data))
2632 return -1;
2635 pathA.ReleaseBuffer();
2637 return 0;
2640 int CGit::GetUnifiedDiff(const CTGitPath& path, const git_revnum_t& rev1, const git_revnum_t& rev2, CString patchfile, bool bMerge, bool bCombine, int diffContext)
2642 if (UsingLibGit2(GIT_CMD_DIFF))
2644 FILE *file = nullptr;
2645 _tfopen_s(&file, patchfile, _T("w"));
2646 if (file == nullptr)
2647 return -1;
2648 int ret = GetUnifiedDiffLibGit2(path, rev1, rev2, UnifiedDiffToFile, file, bMerge);
2649 fclose(file);
2650 return ret;
2652 else
2654 CString cmd;
2655 cmd = GetUnifiedDiffCmd(path, rev1, rev2, bMerge, bCombine, diffContext);
2656 return RunLogFile(cmd, patchfile, &gitLastErr);
2660 static int UnifiedDiffToStringA(const git_diff_delta * /*delta*/, const git_diff_hunk * /*hunk*/, const git_diff_line *line, void *payload)
2662 ATLASSERT(payload && line);
2663 CStringA *str = (CStringA*) payload;
2664 if (line->origin == GIT_DIFF_LINE_CONTEXT || line->origin == GIT_DIFF_LINE_ADDITION || line->origin == GIT_DIFF_LINE_DELETION)
2665 str->Append(&line->origin, 1);
2666 str->Append(line->content, (int)line->content_len);
2667 return 0;
2670 int CGit::GetUnifiedDiff(const CTGitPath& path, const git_revnum_t& rev1, const git_revnum_t& rev2, CStringA * buffer, bool bMerge, bool bCombine, int diffContext)
2672 if (UsingLibGit2(GIT_CMD_DIFF))
2673 return GetUnifiedDiffLibGit2(path, rev1, rev2, UnifiedDiffToStringA, buffer, bMerge);
2674 else
2676 CString cmd;
2677 cmd = GetUnifiedDiffCmd(path, rev1, rev2, bMerge, bCombine, diffContext);
2678 BYTE_VECTOR vector;
2679 int ret = Run(cmd, &vector);
2680 if (!vector.empty())
2682 vector.push_back(0); // vector is not NUL terminated
2683 buffer->Append((char *)&vector[0]);
2685 return ret;
2689 int CGit::GitRevert(int parent, const CGitHash &hash)
2691 if (UsingLibGit2(GIT_CMD_REVERT))
2693 CAutoRepository repo(GetGitRepository());
2694 if (!repo)
2695 return -1;
2697 CAutoCommit commit;
2698 if (git_commit_lookup(commit.GetPointer(), repo, (const git_oid *)hash.m_hash))
2699 return -1;
2701 git_revert_options revert_opts = GIT_REVERT_OPTIONS_INIT;
2702 revert_opts.mainline = parent;
2703 int result = git_revert(repo, commit, &revert_opts);
2705 return !result ? 0 : -1;
2707 else
2709 CString cmd, merge;
2710 if (parent)
2711 merge.Format(_T("-m %d "), parent);
2712 cmd.Format(_T("git.exe revert --no-edit --no-commit %s%s"), merge, hash.ToString());
2713 gitLastErr = cmd + _T("\n");
2714 if (Run(cmd, &gitLastErr, CP_UTF8))
2716 return -1;
2718 else
2720 gitLastErr.Empty();
2721 return 0;
2726 int CGit::DeleteRef(const CString& reference)
2728 if (UsingLibGit2(GIT_CMD_DELETETAGBRANCH))
2730 CAutoRepository repo(GetGitRepository());
2731 if (!repo)
2732 return -1;
2734 CStringA refA;
2735 if (reference.Right(3) == _T("^{}"))
2736 refA = CUnicodeUtils::GetUTF8(reference.Left(reference.GetLength() - 3));
2737 else
2738 refA = CUnicodeUtils::GetUTF8(reference);
2740 CAutoReference ref;
2741 if (git_reference_lookup(ref.GetPointer(), repo, refA))
2742 return -1;
2744 int result = -1;
2745 if (git_reference_is_tag(ref))
2746 result = git_tag_delete(repo, git_reference_shorthand(ref));
2747 else if (git_reference_is_branch(ref))
2748 result = git_branch_delete(ref);
2749 else if (git_reference_is_remote(ref))
2750 result = git_branch_delete(ref);
2751 else
2752 giterr_set_str(GITERR_REFERENCE, CUnicodeUtils::GetUTF8(L"unsupported reference type: " + reference));
2754 return result;
2756 else
2758 CString cmd, shortname;
2759 if (GetShortName(reference, shortname, _T("refs/heads/")))
2760 cmd.Format(_T("git.exe branch -D -- %s"), shortname);
2761 else if (GetShortName(reference, shortname, _T("refs/tags/")))
2762 cmd.Format(_T("git.exe tag -d -- %s"), shortname);
2763 else if (GetShortName(reference, shortname, _T("refs/remotes/")))
2764 cmd.Format(_T("git.exe branch -r -D -- %s"), shortname);
2765 else
2767 gitLastErr = L"unsupported reference type: " + reference;
2768 return -1;
2771 if (Run(cmd, &gitLastErr, CP_UTF8))
2772 return -1;
2774 gitLastErr.Empty();
2775 return 0;
2779 bool CGit::LoadTextFile(const CString &filename, CString &msg)
2781 if (PathFileExists(filename))
2783 FILE *pFile = nullptr;
2784 _tfopen_s(&pFile, filename, _T("r"));
2785 if (pFile)
2787 CStringA str;
2790 char s[8196] = { 0 };
2791 int read = (int)fread(s, sizeof(char), sizeof(s), pFile);
2792 if (read == 0)
2793 break;
2794 str += CStringA(s, read);
2795 } while (true);
2796 fclose(pFile);
2797 msg = CUnicodeUtils::GetUnicode(str);
2798 msg.Replace(_T("\r\n"), _T("\n"));
2799 msg.TrimRight(_T("\n"));
2800 msg += _T("\n");
2802 else
2803 ::MessageBox(nullptr, _T("Could not open ") + filename, _T("TortoiseGit"), MB_ICONERROR);
2804 return true; // load no further files
2806 return false;