Detect msysgit installation with user-rights
[TortoiseGit.git] / src / Git / Git.cpp
blobf751e96a538b811e508cbb90994ddb398390cc31
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2013 - 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 "atlconv.h"
23 #include "GitRev.h"
24 #include "registry.h"
25 #include "GitConfig.h"
26 #include <map>
27 #include "UnicodeUtils.h"
28 #include "gitdll.h"
29 #include <fstream>
30 #include "git2.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];
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 return TRUE;
102 return FALSE;
105 static bool g_bSortLogical;
106 static bool g_bSortLocalBranchesFirst;
108 static void GetSortOptions()
110 g_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
111 if (g_bSortLogical)
112 g_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
113 g_bSortLocalBranchesFirst = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoSortLocalBranchesFirst", 0, false, HKEY_CURRENT_USER);
114 if (g_bSortLocalBranchesFirst)
115 g_bSortLocalBranchesFirst = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoSortLocalBranchesFirst", 0, false, HKEY_LOCAL_MACHINE);
118 static int LogicalComparePredicate(CString &left, CString &right)
120 if (g_bSortLogical)
121 return StrCmpLogicalW(left, right) < 0;
122 return StrCmpI(left, right) < 0;
125 static int LogicalCompareBranchesPredicate(CString &left, CString &right)
127 if (g_bSortLocalBranchesFirst)
129 int leftIsRemote = left.Find(_T("remotes/"));
130 int rightIsRemote = right.Find(_T("remotes/"));
132 if (leftIsRemote == 0 && rightIsRemote < 0)
133 return false;
134 else if (leftIsRemote < 0 && rightIsRemote == 0)
135 return true;
137 if (g_bSortLogical)
138 return StrCmpLogicalW(left, right) < 0;
139 return StrCmpI(left, right) < 0;
142 #define MAX_DIRBUFFER 1000
143 #define CALL_OUTPUT_READ_CHUNK_SIZE 1024
145 CString CGit::ms_LastMsysGitDir;
146 CGit g_Git;
149 CGit::CGit(void)
151 GetCurrentDirectory(MAX_DIRBUFFER,m_CurrentDir.GetBuffer(MAX_DIRBUFFER));
152 m_CurrentDir.ReleaseBuffer();
153 m_IsGitDllInited = false;
154 m_GitDiff=0;
155 m_GitSimpleListDiff=0;
156 m_IsUseGitDLL = !!CRegDWORD(_T("Software\\TortoiseGit\\UsingGitDLL"),1);
157 m_IsUseLibGit2 = !!CRegDWORD(_T("Software\\TortoiseGit\\UseLibgit2"), TRUE);
158 GetSortOptions();
159 this->m_bInitialized =false;
160 CheckMsysGitDir();
161 m_critGitDllSec.Init();
164 CGit::~CGit(void)
166 if(this->m_GitDiff)
168 git_close_diff(m_GitDiff);
169 m_GitDiff=0;
171 if(this->m_GitSimpleListDiff)
173 git_close_diff(m_GitSimpleListDiff);
174 m_GitSimpleListDiff=0;
178 bool CGit::IsBranchNameValid(CString branchname)
180 if (branchname.IsEmpty())
181 return false;
183 for(int i=0; i < branchname.GetLength(); i++)
185 TCHAR c = branchname.GetAt(i);
186 if (c <= ' ' || c == '~' || c == '^' || c == ':' || c == '\\' || c == '?' || c == '[')
187 return false;
190 if (branchname.Find(L".") == 0 || branchname.Find(L"/.") >= 0 || branchname.Find(L"..") >= 0 || branchname.Find(L"@{") >= 0 || branchname.ReverseFind('*') >= 0)
191 return false;
193 CString reverseBranchname = branchname.MakeReverse();
194 if (branchname.Find(L'.') == 0 || branchname.Find(L'/') == 0 || reverseBranchname.Find(L"kcol.") >= 0)
195 return false;
197 return true;
200 static char g_Buffer[4096];
202 int CGit::RunAsync(CString cmd, PROCESS_INFORMATION *piOut, HANDLE *hReadOut, HANDLE *hErrReadOut, CString *StdioFile)
204 SECURITY_ATTRIBUTES sa;
205 HANDLE hRead, hWrite, hReadErr = NULL, hWriteErr = NULL;
206 HANDLE hStdioFile = NULL;
208 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
209 sa.lpSecurityDescriptor=NULL;
210 sa.bInheritHandle=TRUE;
211 if(!CreatePipe(&hRead,&hWrite,&sa,0))
213 return TGIT_GIT_ERROR_OPEN_PIP;
215 if (hErrReadOut && !CreatePipe(&hReadErr, &hWriteErr, &sa, 0))
216 return TGIT_GIT_ERROR_OPEN_PIP;
218 if(StdioFile)
220 hStdioFile=CreateFile(*StdioFile,GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,
221 &sa,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
224 STARTUPINFO si;
225 PROCESS_INFORMATION pi;
226 si.cb=sizeof(STARTUPINFO);
227 GetStartupInfo(&si);
229 if (hErrReadOut)
230 si.hStdError = hWriteErr;
231 else
232 si.hStdError = hWrite;
233 if(StdioFile)
234 si.hStdOutput=hStdioFile;
235 else
236 si.hStdOutput=hWrite;
238 si.wShowWindow=SW_HIDE;
239 si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
241 LPTSTR pEnv = (!m_Environment.empty()) ? &m_Environment[0]: NULL;
242 DWORD dwFlags = pEnv ? CREATE_UNICODE_ENVIRONMENT : 0;
244 //DETACHED_PROCESS make ssh recognize that it has no console to launch askpass to input password.
245 dwFlags |= DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP;
247 memset(&this->m_CurrentGitPi,0,sizeof(PROCESS_INFORMATION));
248 memset(&pi, 0, sizeof(PROCESS_INFORMATION));
250 if(cmd.Find(_T("git")) == 0)
252 int firstSpace = cmd.Find(_T(" "));
253 if (firstSpace > 0)
254 cmd = _T('"')+CGit::ms_LastMsysGitDir+_T("\\")+ cmd.Left(firstSpace) + _T('"')+ cmd.Mid(firstSpace);
255 else
256 cmd=_T('"')+CGit::ms_LastMsysGitDir+_T("\\")+cmd+_T('"');
259 if(!CreateProcess(NULL,(LPWSTR)cmd.GetString(), NULL,NULL,TRUE,dwFlags,pEnv,(LPWSTR)m_CurrentDir.GetString(),&si,&pi))
261 LPVOID lpMsgBuf;
262 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
263 NULL,GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
264 (LPTSTR)&lpMsgBuf,
265 0,NULL);
266 return TGIT_GIT_ERROR_CREATE_PROCESS;
269 m_CurrentGitPi = pi;
271 CloseHandle(hWrite);
272 if (hErrReadOut)
273 CloseHandle(hWriteErr);
274 if(piOut)
275 *piOut=pi;
276 if(hReadOut)
277 *hReadOut=hRead;
278 if(hErrReadOut)
279 *hErrReadOut = hReadErr;
280 return 0;
283 //Must use sperate function to convert ANSI str to union code string
284 //Becuase A2W use stack as internal convert buffer.
285 void CGit::StringAppend(CString *str,BYTE *p,int code,int length)
287 //USES_CONVERSION;
288 //str->Append(A2W_CP((LPCSTR)p,code));
289 if(str == NULL)
290 return ;
292 WCHAR * buf;
294 int len ;
295 if(length<0)
296 len = (int)strlen((const char*)p);
297 else
298 len=length;
299 //if (len==0)
300 // return ;
301 //buf = new WCHAR[len*4 + 1];
302 buf = str->GetBuffer(len*4+1+str->GetLength())+str->GetLength();
303 SecureZeroMemory(buf, (len*4 + 1)*sizeof(WCHAR));
304 MultiByteToWideChar(code, 0, (LPCSTR)p, len, buf, len*4);
305 str->ReleaseBuffer();
306 //str->Append(buf);
307 //delete buf;
310 BOOL CGit::IsOrphanBranch(CString ref)
312 if (ref.IsEmpty())
313 ref = _T("HEAD");
315 CString cmdout;
316 if (Run(_T("git.exe rev-parse --revs-only ") + ref, &cmdout, CP_UTF8))
318 return TRUE;
320 if(cmdout.IsEmpty())
321 return TRUE;
323 return FALSE;
326 BOOL CGit::IsInitRepos()
328 return IsOrphanBranch(_T("HEAD"));
331 DWORD WINAPI CGit::AsyncReadStdErrThread(LPVOID lpParam)
333 PASYNCREADSTDERRTHREADARGS pDataArray;
334 pDataArray = (PASYNCREADSTDERRTHREADARGS)lpParam;
336 DWORD readnumber;
337 BYTE data[CALL_OUTPUT_READ_CHUNK_SIZE];
338 while (ReadFile(pDataArray->fileHandle, data, CALL_OUTPUT_READ_CHUNK_SIZE, &readnumber, NULL))
340 if (pDataArray->pcall->OnOutputErrData(data,readnumber))
341 break;
344 return 0;
347 int CGit::Run(CGitCall* pcall)
349 PROCESS_INFORMATION pi;
350 HANDLE hRead, hReadErr;
351 if(RunAsync(pcall->GetCmd(),&pi,&hRead, &hReadErr))
352 return TGIT_GIT_ERROR_CREATE_PROCESS;
354 HANDLE thread;
355 ASYNCREADSTDERRTHREADARGS threadArguments;
356 threadArguments.fileHandle = hReadErr;
357 threadArguments.pcall = pcall;
358 thread = CreateThread(NULL, 0, AsyncReadStdErrThread, &threadArguments, 0, NULL);
360 DWORD readnumber;
361 BYTE data[CALL_OUTPUT_READ_CHUNK_SIZE];
362 bool bAborted=false;
363 while(ReadFile(hRead,data,CALL_OUTPUT_READ_CHUNK_SIZE,&readnumber,NULL))
365 // TODO: when OnOutputData() returns 'true', abort git-command. Send CTRL-C signal?
366 if(!bAborted)//For now, flush output when command aborted.
367 if(pcall->OnOutputData(data,readnumber))
368 bAborted=true;
370 if(!bAborted)
371 pcall->OnEnd();
373 if (thread)
374 WaitForSingleObject(thread, INFINITE);
376 CloseHandle(pi.hThread);
378 WaitForSingleObject(pi.hProcess, INFINITE);
379 DWORD exitcode =0;
381 if(!GetExitCodeProcess(pi.hProcess,&exitcode))
383 return TGIT_GIT_ERROR_GET_EXIT_CODE;
386 CloseHandle(pi.hProcess);
388 CloseHandle(hRead);
389 CloseHandle(hReadErr);
390 return exitcode;
392 class CGitCall_ByteVector : public CGitCall
394 public:
395 CGitCall_ByteVector(CString cmd,BYTE_VECTOR* pvector, BYTE_VECTOR* pvectorErr = NULL):CGitCall(cmd),m_pvector(pvector),m_pvectorErr(pvectorErr){}
396 virtual bool OnOutputData(const BYTE* data, size_t size)
398 if (size == 0)
399 return false;
400 size_t oldsize=m_pvector->size();
401 m_pvector->resize(m_pvector->size()+size);
402 memcpy(&*(m_pvector->begin()+oldsize),data,size);
403 return false;
405 virtual bool OnOutputErrData(const BYTE* data, size_t size)
407 if (!m_pvectorErr || size == 0)
408 return false;
409 size_t oldsize = m_pvectorErr->size();
410 m_pvectorErr->resize(m_pvectorErr->size() + size);
411 memcpy(&*(m_pvectorErr->begin() + oldsize), data, size);
412 return false;
414 BYTE_VECTOR* m_pvector;
415 BYTE_VECTOR* m_pvectorErr;
418 int CGit::Run(CString cmd,BYTE_VECTOR *vector, BYTE_VECTOR *vectorErr)
420 CGitCall_ByteVector call(cmd, vector, vectorErr);
421 return Run(&call);
423 int CGit::Run(CString cmd, CString* output, int code)
425 CString err;
426 int ret;
427 ret = Run(cmd, output, &err, code);
429 if (output && !err.IsEmpty())
431 if (!output->IsEmpty())
432 *output += _T("\n");
433 *output += err;
436 return ret;
438 int CGit::Run(CString cmd, CString* output, CString* outputErr, int code)
440 BYTE_VECTOR vector, vectorErr;
441 int ret;
442 if (outputErr)
443 ret = Run(cmd, &vector, &vectorErr);
444 else
445 ret = Run(cmd, &vector);
447 vector.push_back(0);
448 StringAppend(output, &(vector[0]), code);
450 if (outputErr)
452 vectorErr.push_back(0);
453 StringAppend(outputErr, &(vectorErr[0]), code);
456 return ret;
459 CString CGit::GetUserName(void)
461 CEnvironment env;
462 env.CopyProcessEnvironment();
463 CString envname = env.GetEnv(_T("GIT_AUTHOR_NAME"));
464 if (!envname.IsEmpty())
465 return envname;
466 return GetConfigValue(L"user.name", this->GetGitEncode(L"i18n.commitencoding"));
468 CString CGit::GetUserEmail(void)
470 CEnvironment env;
471 env.CopyProcessEnvironment();
472 CString envmail = env.GetEnv(_T("GIT_AUTHOR_EMAIL"));
473 if (!envmail.IsEmpty())
474 return envmail;
476 return GetConfigValue(L"user.email");
479 CString CGit::GetConfigValue(CString name,int encoding, CString *GitPath, BOOL RemoveCR)
481 CString configValue;
482 int start = 0;
483 if(this->m_IsUseGitDLL)
485 CString *git_path=NULL;
487 CAutoLocker lock(g_Git.m_critGitDllSec);
491 CTGitPath path;
493 CheckAndInitDll();
494 git_path = GitPath;
496 }catch(...)
499 CStringA key, value;
500 key = CUnicodeUtils::GetMulti(name, encoding);
501 CStringA p;
502 if(git_path)
503 p = CUnicodeUtils::GetMulti(*GitPath, CP_UTF8);
505 if(git_get_config(key.GetBuffer(), value.GetBufferSetLength(4096), 4096, p.GetBuffer()))
506 return CString();
507 else
509 StringAppend(&configValue,(BYTE*)value.GetBuffer(),encoding);
510 if(RemoveCR)
511 return configValue.Tokenize(_T("\n"),start);
512 return configValue;
516 else
518 CString cmd;
519 cmd.Format(L"git.exe config %s", name);
520 Run(cmd, &configValue, NULL, encoding);
521 if(RemoveCR)
522 return configValue.Tokenize(_T("\n"),start);
523 return configValue;
527 int CGit::SetConfigValue(CString key, CString value, CONFIG_TYPE type, int encoding, CString *GitPath)
529 if(this->m_IsUseGitDLL)
531 CAutoLocker lock(g_Git.m_critGitDllSec);
535 CheckAndInitDll();
537 }catch(...)
540 CStringA keya, valuea;
541 keya = CUnicodeUtils::GetMulti(key, CP_UTF8);
542 valuea = CUnicodeUtils::GetMulti(value, encoding);
543 CStringA p;
544 if(GitPath)
545 p = CUnicodeUtils::GetMulti(*GitPath, CP_UTF8);
547 return get_set_config(keya.GetBuffer(), valuea.GetBuffer(), type, p.GetBuffer());
550 else
552 CString cmd;
553 CString option;
554 switch(type)
556 case CONFIG_GLOBAL:
557 option = _T("--global");
558 break;
559 case CONFIG_SYSTEM:
560 option = _T("--system");
561 break;
562 default:
563 break;
565 cmd.Format(_T("git.exe config %s %s \"%s\""), option, key, value);
566 CString out;
567 if (Run(cmd, &out, NULL, encoding))
569 return -1;
572 return 0;
575 int CGit::UnsetConfigValue(CString key, CONFIG_TYPE type, int encoding, CString *GitPath)
577 if(this->m_IsUseGitDLL)
579 CAutoLocker lock(g_Git.m_critGitDllSec);
583 CheckAndInitDll();
584 }catch(...)
587 CStringA keya;
588 keya = CUnicodeUtils::GetMulti(key, CP_UTF8);
589 CStringA p;
590 if(GitPath)
591 p=CUnicodeUtils::GetMulti(*GitPath,CP_ACP);
593 return get_set_config(keya.GetBuffer(), NULL, type, p.GetBuffer());
595 else
597 CString cmd;
598 CString option;
599 switch(type)
601 case CONFIG_GLOBAL:
602 option = _T("--global");
603 break;
604 case CONFIG_SYSTEM:
605 option = _T("--system");
606 break;
607 default:
608 break;
610 cmd.Format(_T("git.exe config %s --unset %s"), option, key);
611 CString out;
612 if (Run(cmd, &out, NULL, encoding))
614 return -1;
617 return 0;
620 CString CGit::GetCurrentBranch(void)
622 CString output;
623 //Run(_T("git.exe branch"),&branch);
625 if(this->GetCurrentBranchFromFile(this->m_CurrentDir,output))
627 return _T("(no branch)");
629 else
630 return output;
634 CString CGit::GetSymbolicRef(const wchar_t* symbolicRefName, bool bStripRefsHeads)
636 CString refName;
637 if(this->m_IsUseGitDLL)
639 unsigned char sha1[20];
640 int flag;
642 CAutoLocker lock(g_Git.m_critGitDllSec);
643 const char *refs_heads_master = git_resolve_ref(CUnicodeUtils::GetUTF8(CString(symbolicRefName)), sha1, 0, &flag);
644 if(refs_heads_master && (flag&REF_ISSYMREF))
646 StringAppend(&refName,(BYTE*)refs_heads_master);
647 if(bStripRefsHeads)
648 refName = StripRefName(refName);
652 else
654 CString cmd;
655 cmd.Format(L"git symbolic-ref %s", symbolicRefName);
656 if (Run(cmd, &refName, NULL, CP_UTF8) != 0)
657 return CString();//Error
658 int iStart = 0;
659 refName = refName.Tokenize(L"\n", iStart);
660 if(bStripRefsHeads)
661 refName = StripRefName(refName);
663 return refName;
666 CString CGit::GetFullRefName(CString shortRefName)
668 CString refName;
669 CString cmd;
670 cmd.Format(L"git rev-parse --symbolic-full-name %s", shortRefName);
671 if (Run(cmd, &refName, NULL, CP_UTF8) != 0)
672 return CString();//Error
673 int iStart = 0;
674 return refName.Tokenize(L"\n", iStart);
677 CString CGit::StripRefName(CString refName)
679 if(wcsncmp(refName, L"refs/heads/", 11) == 0)
680 refName = refName.Mid(11);
681 else if(wcsncmp(refName, L"refs/", 5) == 0)
682 refName = refName.Mid(5);
683 int start =0;
684 return refName.Tokenize(_T("\n"),start);
687 int CGit::GetCurrentBranchFromFile(const CString &sProjectRoot, CString &sBranchOut)
689 // read current branch name like git-gui does, by parsing the .git/HEAD file directly
691 if ( sProjectRoot.IsEmpty() )
692 return -1;
694 CString sDotGitPath;
695 if (!g_GitAdminDir.GetAdminDirPath(sProjectRoot, sDotGitPath))
696 return -1;
698 CString sHeadFile = sDotGitPath + _T("HEAD");
700 FILE *pFile;
701 _tfopen_s(&pFile, sHeadFile.GetString(), _T("r"));
703 if (!pFile)
705 return -1;
708 char s[256] = {0};
709 fgets(s, sizeof(s), pFile);
711 fclose(pFile);
713 const char *pfx = "ref: refs/heads/";
714 const int len = 16;//strlen(pfx)
716 if ( !strncmp(s, pfx, len) )
718 //# We're on a branch. It might not exist. But
719 //# HEAD looks good enough to be a branch.
720 CStringA utf8Branch(s + len);
721 sBranchOut = CUnicodeUtils::GetUnicode(utf8Branch);
722 sBranchOut.TrimRight(_T(" \r\n\t"));
724 if ( sBranchOut.IsEmpty() )
725 return -1;
727 else
729 //# Assume this is a detached head.
730 sBranchOut = "HEAD";
732 return 1;
735 return 0;
738 int CGit::BuildOutputFormat(CString &format,bool IsFull)
740 CString log;
741 log.Format(_T("#<%c>%%x00"),LOG_REV_ITEM_BEGIN);
742 format += log;
743 if(IsFull)
745 log.Format(_T("#<%c>%%an%%x00"),LOG_REV_AUTHOR_NAME);
746 format += log;
747 log.Format(_T("#<%c>%%ae%%x00"),LOG_REV_AUTHOR_EMAIL);
748 format += log;
749 log.Format(_T("#<%c>%%ai%%x00"),LOG_REV_AUTHOR_DATE);
750 format += log;
751 log.Format(_T("#<%c>%%cn%%x00"),LOG_REV_COMMIT_NAME);
752 format += log;
753 log.Format(_T("#<%c>%%ce%%x00"),LOG_REV_COMMIT_EMAIL);
754 format += log;
755 log.Format(_T("#<%c>%%ci%%x00"),LOG_REV_COMMIT_DATE);
756 format += log;
757 log.Format(_T("#<%c>%%b%%x00"),LOG_REV_COMMIT_BODY);
758 format += log;
761 log.Format(_T("#<%c>%%m%%H%%x00"),LOG_REV_COMMIT_HASH);
762 format += log;
763 log.Format(_T("#<%c>%%P%%x00"),LOG_REV_COMMIT_PARENT);
764 format += log;
765 log.Format(_T("#<%c>%%s%%x00"),LOG_REV_COMMIT_SUBJECT);
766 format += log;
768 if(IsFull)
770 log.Format(_T("#<%c>%%x00"),LOG_REV_COMMIT_FILE);
771 format += log;
773 return 0;
776 int CGit::GetLog(BYTE_VECTOR& logOut, const CString &hash, CTGitPath *path ,int count,int mask,CString *from,CString *to)
778 CGitCall_ByteVector gitCall(CString(), &logOut, NULL);
779 return GetLog(&gitCall,hash,path,count,mask,from,to);
782 CString CGit::GetLogCmd( const CString &hash, CTGitPath *path, int count, int mask,CString *from,CString *to,bool paramonly,
783 CFilterData *Filter)
785 CString cmd;
786 CString num;
787 CString since;
789 CString file;
791 if(path)
792 file.Format(_T(" -- \"%s\""),path->GetGitPathString());
794 if(count>0)
795 num.Format(_T("-n%d"),count);
797 CString param;
799 if(mask& LOG_INFO_STAT )
800 param += _T(" --numstat ");
801 if(mask& LOG_INFO_FILESTATE)
802 param += _T(" --raw ");
804 if(mask& LOG_INFO_FULLHISTORY)
805 param += _T(" --full-history ");
807 if(mask& LOG_INFO_BOUNDARY)
808 param += _T(" --left-right --boundary ");
810 if(mask& CGit::LOG_INFO_ALL_BRANCH)
811 param += _T(" --all ");
813 if(mask& CGit::LOG_INFO_DETECT_COPYRENAME)
814 param += _T(" -C ");
816 if(mask& CGit::LOG_INFO_DETECT_RENAME )
817 param += _T(" -M ");
819 if(mask& CGit::LOG_INFO_FIRST_PARENT )
820 param += _T(" --first-parent ");
822 if(mask& CGit::LOG_INFO_NO_MERGE )
823 param += _T(" --no-merges ");
825 if(mask& CGit::LOG_INFO_FOLLOW)
826 param += _T(" --follow ");
828 if(mask& CGit::LOG_INFO_SHOW_MERGEDFILE)
829 param += _T(" -c ");
831 if(mask& CGit::LOG_INFO_FULL_DIFF)
832 param += _T(" --full-diff ");
834 if(mask& CGit::LOG_INFO_SIMPILFY_BY_DECORATION)
835 param += _T(" --simplify-by-decoration ");
837 if(from != NULL && to != NULL)
839 CString range;
840 range.Format(_T(" %s..%s "),FixBranchName(*from),FixBranchName(*to));
841 param += range;
843 else if(to != NULL && hash.IsEmpty())
845 CString range;
846 range.Format(_T(" %s "), FixBranchName(*to));
847 param += range;
849 param+=hash;
851 CString st1,st2;
853 if( Filter && (Filter->m_From != -1))
855 st1.Format(_T(" --max-age=%I64u "), Filter->m_From);
856 param += st1;
859 if( Filter && (Filter->m_To != -1))
861 st2.Format(_T(" --min-age=%I64u "), Filter->m_To);
862 param += st2;
865 bool isgrep = false;
866 if( Filter && (!Filter->m_Author.IsEmpty()))
868 st1.Format(_T(" --author=\"%s\"" ),Filter->m_Author);
869 param += st1;
870 isgrep = true;
873 if( Filter && (!Filter->m_Committer.IsEmpty()))
875 st1.Format(_T(" --committer=\"%s\"" ),Filter->m_Author);
876 param += st1;
877 isgrep = true;
880 if( Filter && (!Filter->m_MessageFilter.IsEmpty()))
882 st1.Format(_T(" --grep=\"%s\"" ),Filter->m_MessageFilter);
883 param += st1;
884 isgrep = true;
887 if(Filter && isgrep)
889 if(!Filter->m_IsRegex)
890 param += _T(" --fixed-strings ");
892 param += _T(" --regexp-ignore-case --extended-regexp ");
895 if (CRegDWORD(_T("Software\\TortoiseGit\\LogTopoOrder"), TRUE))
896 param += _T(" --topo-order");
898 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
899 cmd.Format(_T("--ignore-this-parameter %s -z %s --parents "), num, param);
900 else
902 CString log;
903 BuildOutputFormat(log,!(mask&CGit::LOG_INFO_ONLY_HASH));
904 cmd.Format(_T("git.exe log %s -z %s --parents --pretty=format:\"%s\""),
905 num,param,log);
908 cmd += file;
910 return cmd;
912 //int CGit::GetLog(CGitCall* pgitCall, const CString &hash, CTGitPath *path ,int count,int mask)
913 int CGit::GetLog(CGitCall* pgitCall, const CString &hash, CTGitPath *path, int count, int mask,CString *from,CString *to)
915 pgitCall->SetCmd( GetLogCmd(hash,path,count,mask,from,to) );
917 return Run(pgitCall);
918 // return Run(cmd,&logOut);
921 #define BUFSIZE 512
922 void GetTempPath(CString &path)
924 TCHAR lpPathBuffer[BUFSIZE];
925 DWORD dwRetVal;
926 DWORD dwBufSize=BUFSIZE;
927 dwRetVal = GetTortoiseGitTempPath(dwBufSize, // length of the buffer
928 lpPathBuffer); // buffer for path
929 if (dwRetVal > dwBufSize || (dwRetVal == 0))
931 path=_T("");
933 path.Format(_T("%s"),lpPathBuffer);
935 CString GetTempFile()
937 TCHAR lpPathBuffer[BUFSIZE];
938 DWORD dwRetVal;
939 DWORD dwBufSize=BUFSIZE;
940 TCHAR szTempName[BUFSIZE];
941 UINT uRetVal;
943 dwRetVal = GetTortoiseGitTempPath(dwBufSize, // length of the buffer
944 lpPathBuffer); // buffer for path
945 if (dwRetVal > dwBufSize || (dwRetVal == 0))
947 return _T("");
949 // Create a temporary file.
950 uRetVal = GetTempFileName(lpPathBuffer, // directory for tmp files
951 TEXT("Patch"), // temp file name prefix
952 0, // create unique name
953 szTempName); // buffer for name
956 if (uRetVal == 0)
958 return _T("");
961 return CString(szTempName);
965 DWORD GetTortoiseGitTempPath(DWORD nBufferLength, LPTSTR lpBuffer)
967 DWORD result = ::GetTempPath(nBufferLength, lpBuffer);
968 if (result == 0) return 0;
969 if (lpBuffer == NULL || (result + 13 > nBufferLength))
971 if (lpBuffer)
972 lpBuffer[0] = '\0';
973 return result + 13;
976 _tcscat(lpBuffer, _T("TortoiseGit\\"));
977 CreateDirectory(lpBuffer, NULL);
979 return result + 13;
982 int CGit::RunLogFile(CString cmd,const CString &filename)
984 STARTUPINFO si;
985 PROCESS_INFORMATION pi;
986 si.cb=sizeof(STARTUPINFO);
987 GetStartupInfo(&si);
989 SECURITY_ATTRIBUTES psa={sizeof(psa),NULL,TRUE};;
990 psa.bInheritHandle=TRUE;
992 HANDLE houtfile=CreateFile(filename,GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,
993 &psa,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
995 si.wShowWindow = SW_HIDE;
996 si.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
997 si.hStdOutput = houtfile;
999 LPTSTR pEnv = (!m_Environment.empty()) ? &m_Environment[0]: NULL;
1000 DWORD dwFlags = pEnv ? CREATE_UNICODE_ENVIRONMENT : 0;
1002 if(cmd.Find(_T("git")) == 0)
1003 cmd=CGit::ms_LastMsysGitDir+_T("\\")+cmd;
1005 if(!CreateProcess(NULL,(LPWSTR)cmd.GetString(), NULL,NULL,TRUE,dwFlags,pEnv,(LPWSTR)m_CurrentDir.GetString(),&si,&pi))
1007 LPVOID lpMsgBuf;
1008 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
1009 NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
1010 (LPTSTR)&lpMsgBuf,
1011 0,NULL);
1012 return TGIT_GIT_ERROR_CREATE_PROCESS;
1015 WaitForSingleObject(pi.hProcess,INFINITE);
1017 CloseHandle(pi.hThread);
1018 CloseHandle(pi.hProcess);
1019 CloseHandle(houtfile);
1020 return TGIT_GIT_SUCCESS;
1021 // return 0;
1024 int CGit::GetHash(CGitHash &hash, TCHAR* friendname)
1026 // no need to parse a ref if it's already a 40-byte hash
1027 if (CGitHash::IsValidSHA1(friendname))
1029 CString sHash(friendname);
1030 hash = CGitHash(sHash);
1031 return 0;
1034 if (m_IsUseLibGit2)
1036 git_repository *repo = NULL;
1037 CStringA gitdirA = CUnicodeUtils::GetMulti(CTGitPath(m_CurrentDir).GetGitPathString(), CP_UTF8);
1038 if (git_repository_open(&repo, gitdirA.GetBuffer()))
1040 gitdirA.ReleaseBuffer();
1041 return -1;
1043 gitdirA.ReleaseBuffer();
1045 int isHeadOrphan = git_repository_head_orphan(repo);
1046 if (isHeadOrphan != 0)
1048 git_repository_free(repo);
1049 hash.Empty();
1050 if (isHeadOrphan == 1)
1051 return 0;
1052 else
1053 return -1;
1056 CStringA refnameA = CUnicodeUtils::GetMulti(friendname, CP_UTF8);
1058 git_object * gitObject = NULL;
1059 if (git_revparse_single(&gitObject, repo, refnameA.GetBuffer()))
1061 refnameA.ReleaseBuffer();
1062 git_repository_free(repo);
1063 return -1;
1065 refnameA.ReleaseBuffer();
1067 const git_oid * oid = git_object_id(gitObject);
1068 if (oid == NULL)
1070 git_object_free(gitObject);
1071 git_repository_free(repo);
1072 return -1;
1075 hash = CGitHash((char *)oid->id);
1077 git_object_free(gitObject); // also frees oid
1078 git_repository_free(repo);
1080 return 0;
1082 else
1084 CString cmd;
1085 cmd.Format(_T("git.exe rev-parse %s" ),FixBranchName(friendname));
1086 int ret = Run(cmd, &gitLastErr, NULL, CP_UTF8);
1087 hash = CGitHash(gitLastErr.Trim());
1088 return ret;
1092 int CGit::GetInitAddList(CTGitPathList &outputlist)
1094 CString cmd;
1095 BYTE_VECTOR cmdout;
1097 cmd=_T("git.exe ls-files -s -t -z");
1098 outputlist.Clear();
1099 if (Run(cmd, &cmdout))
1100 return -1;
1102 outputlist.ParserFromLsFile(cmdout);
1103 for(int i=0;i<outputlist.GetCount();i++)
1104 const_cast<CTGitPath&>(outputlist[i]).m_Action = CTGitPath::LOGACTIONS_ADDED;
1106 return 0;
1108 int CGit::GetCommitDiffList(const CString &rev1,const CString &rev2,CTGitPathList &outputlist)
1110 CString cmd;
1112 if(rev1 == GIT_REV_ZERO || rev2 == GIT_REV_ZERO)
1114 //rev1=+_T("");
1115 if(rev1 == GIT_REV_ZERO)
1116 cmd.Format(_T("git.exe diff -r --raw -C -M --numstat -z %s"),rev2);
1117 else
1118 cmd.Format(_T("git.exe diff -r -R --raw -C -M --numstat -z %s"),rev1);
1120 else
1122 cmd.Format(_T("git.exe diff-tree -r --raw -C -M --numstat -z %s %s"),rev2,rev1);
1125 BYTE_VECTOR out;
1126 if (Run(cmd, &out))
1127 return -1;
1129 outputlist.ParserFromLog(out);
1131 return 0;
1134 int addto_list_each_ref_fn(const char *refname, const unsigned char * /*sha1*/, int /*flags*/, void *cb_data)
1136 STRING_VECTOR *list = (STRING_VECTOR*)cb_data;
1137 CString str;
1138 g_Git.StringAppend(&str, (BYTE*)refname, CP_UTF8);
1139 list->push_back(str);
1140 return 0;
1143 int CGit::GetTagList(STRING_VECTOR &list)
1145 if (this->m_IsUseLibGit2)
1147 git_repository *repo = NULL;
1149 CStringA gitdir = CUnicodeUtils::GetMulti(CTGitPath(m_CurrentDir).GetGitPathString(), CP_UTF8);
1150 if (git_repository_open(&repo, gitdir.GetBuffer()))
1152 gitdir.ReleaseBuffer();
1153 return -1;
1155 gitdir.ReleaseBuffer();
1157 git_strarray tag_names;
1159 if (git_tag_list(&tag_names, repo))
1161 git_repository_free(repo);
1162 return -1;
1165 for (size_t i = 0; i < tag_names.count; i++)
1167 CStringA tagName(tag_names.strings[i]);
1168 list.push_back(CUnicodeUtils::GetUnicode(tagName));
1171 git_strarray_free(&tag_names);
1173 git_repository_free(repo);
1175 std::sort(list.begin(), list.end(), LogicalComparePredicate);
1177 return 0;
1179 else
1181 CString cmd, output;
1182 cmd=_T("git.exe tag -l");
1183 int ret = Run(cmd, &output, NULL, CP_UTF8);
1184 if(!ret)
1186 int pos=0;
1187 CString one;
1188 while( pos>=0 )
1190 one=output.Tokenize(_T("\n"),pos);
1191 if (!one.IsEmpty())
1192 list.push_back(one);
1194 std::sort(list.begin(), list.end(), LogicalComparePredicate);
1196 return ret;
1200 CString CGit::GetGitLastErr(CString msg)
1202 if (this->m_IsUseLibGit2)
1204 const git_error *libgit2err = giterr_last();
1205 if (libgit2err)
1207 CString lastError = CUnicodeUtils::GetUnicode(CStringA(libgit2err->message));
1208 giterr_clear();
1209 return msg + _T("\nlibgit2 returned: ") + lastError;
1211 else
1213 return msg + _T("\nUnknown libgit2 error.");
1216 else if (gitLastErr.IsEmpty())
1217 return msg + _T("\nUnknown git.exe error.");
1218 else
1220 CString lastError = gitLastErr;
1221 gitLastErr.Empty();
1222 return msg + _T("\n") + lastError;
1226 CString CGit::FixBranchName_Mod(CString& branchName)
1228 if(branchName == "FETCH_HEAD")
1229 branchName = DerefFetchHead();
1230 return branchName;
1233 CString CGit::FixBranchName(const CString& branchName)
1235 CString tempBranchName = branchName;
1236 FixBranchName_Mod(tempBranchName);
1237 return tempBranchName;
1240 bool CGit::IsBranchTagNameUnique(const CString& name)
1242 CString output;
1244 CString cmd;
1245 cmd.Format(_T("git show-ref --tags --heads refs/heads/%s refs/tags/%s"), name, name);
1246 int ret = Run(cmd, &output, NULL, CP_UTF8);
1247 if (!ret)
1249 int i = 0, pos = 0;
1250 while (pos >= 0)
1252 if (!output.Tokenize(_T("\n"), pos).IsEmpty())
1253 i++;
1255 if (i >= 2)
1256 return false;
1259 return true;
1263 Checks if a branch or tag with the given name exists
1264 isBranch is true -> branch, tag otherwise
1266 bool CGit::BranchTagExists(const CString& name, bool isBranch /*= true*/)
1268 CString cmd, output;
1270 cmd = _T("git show-ref ");
1271 if (isBranch)
1272 cmd += _T("--heads ");
1273 else
1274 cmd += _T("--tags ");
1276 cmd += _T("refs/heads/") + name;
1277 cmd += _T(" refs/tags/") + name;
1279 int ret = Run(cmd, &output, NULL, CP_UTF8);
1280 if (!ret)
1282 if (!output.IsEmpty())
1283 return true;
1286 return false;
1289 CString CGit::DerefFetchHead()
1291 using namespace std;
1292 CString dotGitPath;
1293 g_GitAdminDir.GetAdminDirPath(m_CurrentDir, dotGitPath);
1294 ifstream fetchHeadFile((dotGitPath + L"FETCH_HEAD").GetString(), ios::in | ios::binary);
1295 int forMergeLineCount = 0;
1296 string line;
1297 string hashToReturn;
1298 while(getline(fetchHeadFile, line))
1300 //Tokenize this line
1301 string::size_type prevPos = 0;
1302 string::size_type pos = line.find('\t');
1303 if(pos == string::npos) continue; //invalid line
1305 string hash = line.substr(0, pos);
1306 ++pos; prevPos = pos; pos = line.find('\t', pos); if(pos == string::npos) continue;
1308 bool forMerge = pos == prevPos;
1309 ++pos; prevPos = pos; pos = line.size(); if(pos == string::npos) continue;
1311 string remoteBranch = line.substr(prevPos, pos - prevPos);
1313 //Process this line
1314 if(forMerge)
1316 hashToReturn = hash;
1317 ++forMergeLineCount;
1318 if(forMergeLineCount > 1)
1319 return L""; //More then 1 branch to merge found. Octopus merge needed. Cannot pick single ref from FETCH_HEAD
1323 return CUnicodeUtils::GetUnicode(hashToReturn.c_str());
1326 int CGit::GetBranchList(STRING_VECTOR &list,int *current,BRANCH_TYPE type)
1328 int ret;
1329 CString cmd, output, cur;
1330 cmd = _T("git.exe branch --no-color");
1332 if((type&BRANCH_ALL) == BRANCH_ALL)
1333 cmd += _T(" -a");
1334 else if(type&BRANCH_REMOTE)
1335 cmd += _T(" -r");
1337 ret = Run(cmd, &output, NULL, CP_UTF8);
1338 if(!ret)
1340 int pos=0;
1341 CString one;
1342 while( pos>=0 )
1344 one=output.Tokenize(_T("\n"),pos);
1345 one.Trim(L" \r\n\t");
1346 if(one.Find(L" -> ") >= 0 || one.IsEmpty())
1347 continue; // skip something like: refs/origin/HEAD -> refs/origin/master
1348 if(one[0] == _T('*'))
1350 one = one.Mid(2);
1351 cur = one;
1353 if (one != _T("(no branch)"))
1354 list.push_back(one);
1358 if(type & BRANCH_FETCH_HEAD && !DerefFetchHead().IsEmpty())
1359 list.push_back(L"FETCH_HEAD");
1361 std::sort(list.begin(), list.end(), LogicalCompareBranchesPredicate);
1363 if (current && cur != _T("(no branch)"))
1365 for (unsigned int i = 0; i < list.size(); i++)
1367 if (list[i] == cur)
1369 *current = i;
1370 break;
1375 return ret;
1378 int CGit::GetRemoteList(STRING_VECTOR &list)
1380 if (this->m_IsUseLibGit2)
1382 git_repository *repo = NULL;
1384 CStringA gitdir = CUnicodeUtils::GetMulti(CTGitPath(m_CurrentDir).GetGitPathString(), CP_UTF8);
1385 if (git_repository_open(&repo, gitdir.GetBuffer()))
1387 gitdir.ReleaseBuffer();
1388 return -1;
1390 gitdir.ReleaseBuffer();
1392 git_strarray remotes;
1394 if (git_remote_list(&remotes, repo))
1396 git_repository_free(repo);
1397 return -1;
1400 for (size_t i = 0; i < remotes.count; i++)
1402 CStringA remote(remotes.strings[i]);
1403 list.push_back(CUnicodeUtils::GetUnicode(remote));
1406 git_strarray_free(&remotes);
1408 git_repository_free(repo);
1410 std::sort(list.begin(), list.end());
1412 return 0;
1414 else
1416 int ret;
1417 CString cmd, output;
1418 cmd=_T("git.exe remote");
1419 ret = Run(cmd, &output, NULL, CP_UTF8);
1420 if(!ret)
1422 int pos=0;
1423 CString one;
1424 while( pos>=0 )
1426 one=output.Tokenize(_T("\n"),pos);
1427 if (!one.IsEmpty())
1428 list.push_back(one);
1431 return ret;
1435 int CGit::GetRemoteTags(CString remote, STRING_VECTOR &list)
1437 CString cmd, out, err;
1438 cmd.Format(_T("git.exe ls-remote -t \"%s\""), remote);
1439 if (Run(cmd, &out, &err, CP_UTF8))
1441 MessageBox(NULL, err, _T("TortoiseGit"), MB_ICONERROR);
1442 return -1;
1445 int pos = 0;
1446 while (pos >= 0)
1448 CString one = out.Tokenize(_T("\n"), pos).Mid(51).Trim(); // sha1, tab + refs/tags/
1449 // dot not include annotated tags twice; this works, because an annotated tag appears twice (one normal tag and one with ^{} at the end)
1450 if (one.Find(_T("^{}")) >= 1)
1451 continue;
1452 if (!one.IsEmpty())
1453 list.push_back(one);
1455 std::sort(list.begin(), list.end(), LogicalComparePredicate);
1456 return 0;
1459 int libgit2_addto_list_each_ref_fn(const char *refname, void *payload)
1461 STRING_VECTOR *list = (STRING_VECTOR*)payload;
1462 CString str;
1463 g_Git.StringAppend(&str, (BYTE*)refname, CP_UTF8);
1464 list->push_back(str);
1465 return 0;
1468 int CGit::GetRefList(STRING_VECTOR &list)
1470 if (this->m_IsUseLibGit2)
1472 git_repository *repo = NULL;
1474 CStringA gitdir = CUnicodeUtils::GetMulti(CTGitPath(m_CurrentDir).GetGitPathString(), CP_UTF8);
1475 if (git_repository_open(&repo, gitdir.GetBuffer()))
1477 gitdir.ReleaseBuffer();
1478 return -1;
1480 gitdir.ReleaseBuffer();
1482 if (git_reference_foreach(repo, GIT_REF_LISTALL, libgit2_addto_list_each_ref_fn, &list))
1484 git_repository_free(repo);
1485 return -1;
1488 git_repository_free(repo);
1490 std::sort(list.begin(), list.end(), LogicalComparePredicate);
1492 return 0;
1494 else
1496 CString cmd, output;
1497 cmd=_T("git.exe show-ref -d");
1498 int ret = Run(cmd, &output, NULL, CP_UTF8);
1499 if(!ret)
1501 int pos=0;
1502 CString one;
1503 while( pos>=0 )
1505 one=output.Tokenize(_T("\n"),pos);
1506 int start=one.Find(_T(" "),0);
1507 if(start>0)
1509 CString name;
1510 name=one.Right(one.GetLength()-start-1);
1511 list.push_back(name);
1514 std::sort(list.begin(), list.end(), LogicalComparePredicate);
1516 return ret;
1520 typedef struct map_each_ref_payload {
1521 git_repository * repo;
1522 MAP_HASH_NAME * map;
1523 } map_each_ref_payload;
1525 int libgit2_addto_map_each_ref_fn(const char *refname, void *payload)
1527 map_each_ref_payload *payloadContent = (map_each_ref_payload*)payload;
1529 CString str;
1530 g_Git.StringAppend(&str, (BYTE*)refname, CP_UTF8);
1532 git_object * gitObject = NULL;
1536 if (git_revparse_single(&gitObject, payloadContent->repo, refname))
1538 break;
1541 if (git_object_type(gitObject) == GIT_OBJ_TAG)
1543 str += _T("^{}"); // deref tag
1544 git_object * derefedTag = NULL;
1545 if (git_object_peel(&derefedTag, gitObject, GIT_OBJ_ANY))
1547 break;
1549 git_object_free(gitObject);
1550 gitObject = derefedTag;
1553 const git_oid * oid = git_object_id(gitObject);
1554 if (oid == NULL)
1556 break;
1559 CGitHash hash((char *)oid->id);
1560 (*payloadContent->map)[hash].push_back(str);
1561 } while (false);
1563 if (gitObject)
1565 git_object_free(gitObject);
1566 return 0;
1569 return 1;
1572 int CGit::GetMapHashToFriendName(MAP_HASH_NAME &map)
1574 if (this->m_IsUseLibGit2)
1576 git_repository *repo = NULL;
1578 CStringA gitdir = CUnicodeUtils::GetMulti(CTGitPath(m_CurrentDir).GetGitPathString(), CP_UTF8);
1579 if (git_repository_open(&repo, gitdir.GetBuffer()))
1581 gitdir.ReleaseBuffer();
1582 return -1;
1584 gitdir.ReleaseBuffer();
1586 map_each_ref_payload payloadContent = { repo, &map };
1588 if (git_reference_foreach(repo, GIT_REF_LISTALL, libgit2_addto_map_each_ref_fn, &payloadContent))
1590 git_repository_free(repo);
1591 return -1;
1594 git_repository_free(repo);
1596 for (auto it = map.begin(); it != map.end(); ++it)
1598 std::sort(it->second.begin(), it->second.end());
1601 return 0;
1603 else
1605 CString cmd, output;
1606 cmd=_T("git.exe show-ref -d");
1607 int ret = Run(cmd, &output, NULL, CP_UTF8);
1608 if(!ret)
1610 int pos=0;
1611 CString one;
1612 while( pos>=0 )
1614 one=output.Tokenize(_T("\n"),pos);
1615 int start=one.Find(_T(" "),0);
1616 if(start>0)
1618 CString name;
1619 name=one.Right(one.GetLength()-start-1);
1621 CString hash;
1622 hash=one.Left(start);
1624 map[CGitHash(hash)].push_back(name);
1628 return ret;
1632 BOOL CGit::CheckMsysGitDir()
1634 if (m_bInitialized)
1636 return TRUE;
1639 this->m_Environment.clear();
1640 m_Environment.CopyProcessEnvironment();
1642 TCHAR *oldpath;
1643 size_t homesize,size;
1645 // set HOME if not set already
1646 _tgetenv_s(&homesize, NULL, 0, _T("HOME"));
1647 if (!homesize)
1649 CString home = GetHomeDirectory();
1650 m_Environment.SetEnv(_T("HOME"), home.GetBuffer());
1651 home.ReleaseBuffer();
1653 CString str;
1655 //setup ssh client
1656 CString sshclient=CRegString(_T("Software\\TortoiseGit\\SSH"));
1658 if(!sshclient.IsEmpty())
1660 m_Environment.SetEnv(_T("GIT_SSH"), sshclient.GetBuffer());
1661 m_Environment.SetEnv(_T("SVN_SSH"), sshclient.GetBuffer());
1663 else
1665 TCHAR sPlink[MAX_PATH];
1666 GetModuleFileName(NULL, sPlink, _countof(sPlink));
1667 LPTSTR ptr = _tcsrchr(sPlink, _T('\\'));
1668 if (ptr) {
1669 _tcscpy_s(ptr + 1, MAX_PATH - (ptr - sPlink + 1), _T("TortoiseGitPLink.exe"));
1670 m_Environment.SetEnv(_T("GIT_SSH"), sPlink);
1671 m_Environment.SetEnv(_T("SVN_SSH"), sPlink);
1676 TCHAR sAskPass[MAX_PATH];
1677 GetModuleFileName(NULL, sAskPass, _countof(sAskPass));
1678 LPTSTR ptr = _tcsrchr(sAskPass, _T('\\'));
1679 if (ptr)
1681 _tcscpy_s(ptr + 1, MAX_PATH - (ptr - sAskPass + 1), _T("SshAskPass.exe"));
1682 m_Environment.SetEnv(_T("DISPLAY"),_T(":9999"));
1683 m_Environment.SetEnv(_T("SSH_ASKPASS"),sAskPass);
1684 m_Environment.SetEnv(_T("GIT_ASKPASS"),sAskPass);
1688 // add git/bin path to PATH
1690 CRegString msysdir=CRegString(REG_MSYSGIT_PATH,_T(""),FALSE);
1691 str=msysdir;
1692 if(str.IsEmpty() || !FileExists(str + _T("\\git.exe")))
1694 CRegString msyslocalinstalldir = CRegString(REG_MSYSGIT_INSTALL32, _T(""), FALSE, HKEY_CURRENT_USER);
1695 str = msyslocalinstalldir;
1696 str.TrimRight(_T("\\"));
1697 if (str.IsEmpty())
1699 CRegString msysinstalldir = CRegString(REG_MSYSGIT_INSTALL, _T(""), FALSE, HKEY_LOCAL_MACHINE);
1700 str = msysinstalldir;
1701 str.TrimRight(_T("\\"));
1703 if ( !str.IsEmpty() )
1705 str += "\\bin";
1706 msysdir=str;
1707 CGit::ms_LastMsysGitDir = str;
1708 msysdir.write();
1710 else
1712 // search PATH if git/bin directory is already present
1713 if ( FindGitPath() )
1715 m_bInitialized = TRUE;
1716 msysdir = CGit::ms_LastMsysGitDir;
1717 msysdir.write();
1718 return TRUE;
1721 return FALSE;
1724 else
1726 CGit::ms_LastMsysGitDir = str;
1729 // check for git.exe existance (maybe it was deinstalled in the meantime)
1730 if (!FileExists(CGit::ms_LastMsysGitDir + _T("\\git.exe")))
1731 return FALSE;
1733 //set path
1734 _tdupenv_s(&oldpath,&size,_T("PATH"));
1736 CString path;
1737 path.Format(_T("%s;%s"),oldpath,str + _T(";")+ (CString)CRegString(REG_MSYSGIT_EXTRA_PATH,_T(""),FALSE));
1739 m_Environment.SetEnv(_T("PATH"),path.GetBuffer());
1741 CString str1 = m_Environment.GetEnv(_T("PATH"));
1743 CString sOldPath = oldpath;
1744 free(oldpath);
1746 m_bInitialized = TRUE;
1747 return true;
1750 CString CGit::GetHomeDirectory()
1752 const wchar_t * homeDir = wget_windows_home_directory();
1753 return CString(homeDir, (int)wcslen(homeDir));
1756 CString CGit::GetGitLocalConfig()
1758 CString path;
1759 g_GitAdminDir.GetAdminDirPath(g_Git.m_CurrentDir, path);
1760 path += _T("config");
1761 return path;
1764 CString CGit::GetGitGlobalConfig()
1766 return g_Git.GetHomeDirectory() + _T("\\.gitconfig");
1769 CString CGit::GetGitGlobalXDGConfigPath()
1771 return g_Git.GetHomeDirectory() + _T("\\.config\\git");
1774 CString CGit::GetGitGlobalXDGConfig()
1776 return g_Git.GetGitGlobalXDGConfigPath() + _T("\\config");
1779 CString CGit::GetGitSystemConfig()
1781 const wchar_t * systemConfig = wget_msysgit_etc();
1782 return CString(systemConfig, (int)wcslen(systemConfig));
1785 BOOL CGit::CheckCleanWorkTree()
1787 CString out;
1788 CString cmd;
1789 cmd=_T("git.exe rev-parse --verify HEAD");
1791 if(Run(cmd,&out,CP_UTF8))
1792 return FALSE;
1794 cmd=_T("git.exe update-index --ignore-submodules --refresh");
1795 if(Run(cmd,&out,CP_UTF8))
1796 return FALSE;
1798 cmd=_T("git.exe diff-files --quiet --ignore-submodules");
1799 if(Run(cmd,&out,CP_UTF8))
1800 return FALSE;
1802 cmd=_T("git diff-index --cached --quiet HEAD --ignore-submodules");
1803 if(Run(cmd,&out,CP_UTF8))
1804 return FALSE;
1806 return TRUE;
1808 int CGit::Revert(CString commit, CTGitPathList &list, bool)
1810 int ret;
1811 for(int i=0;i<list.GetCount();i++)
1813 ret = Revert(commit, (CTGitPath&)list[i]);
1814 if(ret)
1815 return ret;
1817 return 0;
1819 int CGit::Revert(CString commit, CTGitPath &path)
1821 CString cmd, out;
1823 if(path.m_Action & CTGitPath::LOGACTIONS_REPLACED && !path.GetGitOldPathString().IsEmpty())
1825 if (CTGitPath(path.GetGitOldPathString()).IsDirectory())
1827 CString err;
1828 err.Format(_T("Cannot revert renaming of \"%s\". A directory with the old name \"%s\" exists."), path.GetGitPathString(), path.GetGitOldPathString());
1829 ::MessageBox(NULL, err, _T("TortoiseGit"), MB_OK|MB_ICONERROR);
1830 return -1;
1832 CString force;
1833 // if the filenames only differ in case, we have to pass "-f"
1834 if (path.GetGitPathString().CompareNoCase(path.GetGitOldPathString()) == 0)
1835 force = _T("-f ");
1836 cmd.Format(_T("git.exe mv %s-- \"%s\" \"%s\""), force, path.GetGitPathString(), path.GetGitOldPathString());
1837 if (Run(cmd, &out, CP_UTF8))
1839 ::MessageBox(NULL, out, _T("TortoiseGit"), MB_OK|MB_ICONERROR);
1840 return -1;
1843 cmd.Format(_T("git.exe checkout %s -f -- \"%s\""), commit, path.GetGitOldPathString());
1844 if (Run(cmd, &out, CP_UTF8))
1846 ::MessageBox(NULL, out, _T("TortoiseGit"), MB_OK|MB_ICONERROR);
1847 return -1;
1851 else if(path.m_Action & CTGitPath::LOGACTIONS_ADDED)
1852 { //To init git repository, there are not HEAD, so we can use git reset command
1853 cmd.Format(_T("git.exe rm -f --cached -- \"%s\""),path.GetGitPathString());
1855 if (Run(cmd, &out, CP_UTF8))
1857 ::MessageBox(NULL, out, _T("TortoiseGit"), MB_OK|MB_ICONERROR);
1858 return -1;
1861 else
1863 cmd.Format(_T("git.exe checkout %s -f -- \"%s\""), commit, path.GetGitPathString());
1864 if (Run(cmd, &out, CP_UTF8))
1866 ::MessageBox(NULL, out, _T("TortoiseGit"), MB_OK|MB_ICONERROR);
1867 return -1;
1871 if (path.m_Action & CTGitPath::LOGACTIONS_DELETED)
1873 cmd.Format(_T("git.exe add -f -- \"%s\""), path.GetGitPathString());
1874 if (Run(cmd, &out, CP_UTF8))
1876 ::MessageBox(NULL, out, _T("TortoiseGit"), MB_OK|MB_ICONERROR);
1877 return -1;
1881 return 0;
1884 int CGit::ListConflictFile(CTGitPathList &list,CTGitPath *path)
1886 BYTE_VECTOR vector;
1888 CString cmd;
1889 if(path)
1890 cmd.Format(_T("git.exe ls-files -u -t -z -- \"%s\""),path->GetGitPathString());
1891 else
1892 cmd=_T("git.exe ls-files -u -t -z");
1894 if (Run(cmd, &vector))
1896 return -1;
1899 list.ParserFromLsFile(vector);
1901 return 0;
1904 bool CGit::IsFastForward(const CString &from, const CString &to, CGitHash * commonAncestor)
1906 CString base;
1907 CGitHash basehash,hash;
1908 CString cmd, err;
1909 cmd.Format(_T("git.exe merge-base %s %s"), FixBranchName(to), FixBranchName(from));
1911 if (Run(cmd, &base, &err, CP_UTF8))
1913 return false;
1915 basehash = base.Trim();
1917 GetHash(hash, from);
1919 if (commonAncestor)
1920 *commonAncestor = basehash;
1922 return hash == basehash;
1925 unsigned int CGit::Hash2int(CGitHash &hash)
1927 int ret=0;
1928 for(int i=0;i<4;i++)
1930 ret = ret << 8;
1931 ret |= hash.m_hash[i];
1933 return ret;
1936 int CGit::RefreshGitIndex()
1938 if(g_Git.m_IsUseGitDLL)
1940 CAutoLocker lock(g_Git.m_critGitDllSec);
1943 return git_run_cmd("update-index","update-index -q --refresh");
1945 }catch(...)
1947 return -1;
1951 else
1953 CString cmd,output;
1954 cmd=_T("git.exe update-index --refresh");
1955 return Run(cmd, &output, CP_UTF8);
1959 int CGit::GetOneFile(CString Refname, CTGitPath &path, const CString &outputfile)
1961 if(g_Git.m_IsUseGitDLL)
1963 CAutoLocker lock(g_Git.m_critGitDllSec);
1966 g_Git.CheckAndInitDll();
1967 CStringA ref, patha, outa;
1968 ref = CUnicodeUtils::GetMulti(Refname, CP_UTF8);
1969 patha = CUnicodeUtils::GetMulti(path.GetGitPathString(), CP_UTF8);
1970 outa = CUnicodeUtils::GetMulti(outputfile, CP_UTF8);
1971 ::DeleteFile(outputfile);
1972 return git_checkout_file((const char*)ref.GetBuffer(),(const char*)patha.GetBuffer(),(const char*)outa.GetBuffer());
1974 }catch(...)
1976 return -1;
1979 else
1981 CString cmd;
1982 cmd.Format(_T("git.exe cat-file -p %s:\"%s\""), Refname, path.GetGitPathString());
1983 return RunLogFile(cmd,outputfile);
1986 void CEnvironment::CopyProcessEnvironment()
1988 TCHAR *p = GetEnvironmentStrings();
1989 while(*p !=0 || *(p+1) !=0)
1990 this->push_back(*p++);
1992 push_back(_T('\0'));
1993 push_back(_T('\0'));
1996 CString CEnvironment::GetEnv(TCHAR *name)
1998 CString str;
1999 for (size_t i = 0; i < size(); i++)
2001 str = &(*this)[i];
2002 int start =0;
2003 CString sname = str.Tokenize(_T("="),start);
2004 if(sname.CompareNoCase(name) == 0)
2006 return &(*this)[i+start];
2008 i+=str.GetLength();
2010 return _T("");
2013 void CEnvironment::SetEnv(TCHAR *name, TCHAR* value)
2015 unsigned int i;
2016 for( i=0;i<size();i++)
2018 CString str = &(*this)[i];
2019 int start =0;
2020 CString sname = str.Tokenize(_T("="),start);
2021 if(sname.CompareNoCase(name) == 0)
2023 break;
2025 i+=str.GetLength();
2028 if(i == size())
2030 i -= 1; // roll back terminate \0\0
2031 this->push_back(_T('\0'));
2034 CEnvironment::iterator it;
2035 it=this->begin();
2036 it += i;
2038 while(*it && i<size())
2040 this->erase(it);
2041 it=this->begin();
2042 it += i;
2045 while(*name)
2047 this->insert(it,*name++);
2048 i++;
2049 it= begin()+i;
2052 this->insert(it, _T('='));
2053 i++;
2054 it= begin()+i;
2056 while(*value)
2058 this->insert(it,*value++);
2059 i++;
2060 it= begin()+i;
2065 int CGit::GetGitEncode(TCHAR* configkey)
2067 CString str=GetConfigValue(configkey);
2069 if(str.IsEmpty())
2070 return CP_UTF8;
2072 return CUnicodeUtils::GetCPCode(str);
2076 int CGit::GetDiffPath(CTGitPathList *PathList, CGitHash *hash1, CGitHash *hash2, char *arg)
2078 GIT_FILE file=0;
2079 int ret=0;
2080 GIT_DIFF diff=0;
2082 CAutoLocker lock(g_Git.m_critGitDllSec);
2084 if(arg == NULL)
2085 diff = GetGitDiff();
2086 else
2087 git_open_diff(&diff, arg);
2089 if(diff ==NULL)
2090 return -1;
2092 bool isStat = 0;
2093 if(arg == NULL)
2094 isStat = true;
2095 else
2096 isStat = !!strstr(arg, "stat");
2098 int count=0;
2100 if(hash2 == NULL)
2101 ret = git_root_diff(diff, hash1->m_hash, &file, &count,isStat);
2102 else
2103 ret = git_diff(diff,hash2->m_hash,hash1->m_hash,&file,&count,isStat);
2105 if(ret)
2106 return -1;
2108 CTGitPath path;
2109 CString strnewname;
2110 CString stroldname;
2112 for(int j=0;j<count;j++)
2114 path.Reset();
2115 char *newname;
2116 char *oldname;
2118 strnewname.Empty();
2119 stroldname.Empty();
2121 int mode=0,IsBin=0,inc=0,dec=0;
2122 git_get_diff_file(diff,file,j,&newname,&oldname,
2123 &mode,&IsBin,&inc,&dec);
2125 StringAppend(&strnewname, (BYTE*)newname, CP_UTF8);
2126 StringAppend(&stroldname, (BYTE*)oldname, CP_UTF8);
2128 path.SetFromGit(strnewname,&stroldname);
2129 path.ParserAction((BYTE)mode);
2131 if(IsBin)
2133 path.m_StatAdd=_T("-");
2134 path.m_StatDel=_T("-");
2136 else
2138 path.m_StatAdd.Format(_T("%d"),inc);
2139 path.m_StatDel.Format(_T("%d"),dec);
2141 PathList->AddPath(path);
2143 git_diff_flush(diff);
2145 if(arg)
2146 git_close_diff(diff);
2148 return 0;
2151 int CGit::GetShortHASHLength()
2153 return 7;
2156 CString CGit::GetShortName(CString ref, REF_TYPE *out_type)
2158 CString str=ref;
2159 CString shortname;
2160 REF_TYPE type = CGit::UNKNOWN;
2162 if (CGit::GetShortName(str, shortname, _T("refs/heads/")))
2164 type = CGit::LOCAL_BRANCH;
2167 else if (CGit::GetShortName(str, shortname, _T("refs/remotes/")))
2169 type = CGit::REMOTE_BRANCH;
2171 else if (CGit::GetShortName(str, shortname, _T("refs/tags/")))
2173 type = CGit::TAG;
2175 else if (CGit::GetShortName(str, shortname, _T("refs/stash")))
2177 type = CGit::STASH;
2178 shortname=_T("stash");
2180 else if (CGit::GetShortName(str, shortname, _T("refs/bisect/")))
2182 if (shortname.Find(_T("good")) == 0)
2184 type = CGit::BISECT_GOOD;
2185 shortname = _T("good");
2188 if (shortname.Find(_T("bad")) == 0)
2190 type = CGit::BISECT_BAD;
2191 shortname = _T("bad");
2194 else if (CGit::GetShortName(str, shortname, _T("refs/notes/")))
2196 type = CGit::NOTES;
2198 else
2200 type = CGit::UNKNOWN;
2201 shortname = _T("Unknown");
2204 if(out_type)
2205 *out_type = type;
2207 return shortname;