Fix build error of tortoisedll
[TortoiseGit.git] / src / Git / Git.cpp
blobe1e1dcc8b0e509157bc4c62fdbe25a3fa6037020
1 #include "StdAfx.h"
2 #include "Git.h"
3 #include "atlconv.h"
4 #include "GitRev.h"
5 #include "registry.h"
6 #include "GitConfig.h"
7 #include <map>
8 #include "UnicodeUtils.h"
9 #include "gitdll.h"
11 int CGit::m_LogEncode=CP_UTF8;
14 static LPTSTR nextpath(LPCTSTR src, LPTSTR dst, UINT maxlen)
16 LPCTSTR orgsrc;
18 while (*src == _T(';'))
19 src++;
21 orgsrc = src;
23 if (!--maxlen)
24 goto nullterm;
26 while (*src && *src != _T(';'))
28 if (*src != _T('"'))
30 *dst++ = *src++;
31 if (!--maxlen)
33 orgsrc = src;
34 goto nullterm;
37 else
39 src++;
40 while (*src && *src != _T('"'))
42 *dst++ = *src++;
43 if (!--maxlen)
45 orgsrc = src;
46 goto nullterm;
50 if (*src)
51 src++;
55 while (*src == _T(';'))
56 src++;
58 nullterm:
60 *dst = 0;
62 return (orgsrc != src) ? (LPTSTR)src : NULL;
65 static inline BOOL FileExists(LPCTSTR lpszFileName)
67 struct _stat st;
68 return _tstat(lpszFileName, &st) == 0;
71 static BOOL FindGitPath()
73 size_t size;
74 _tgetenv_s(&size, NULL, 0, _T("PATH"));
76 if (!size)
78 return FALSE;
81 TCHAR *env = (TCHAR*)alloca(size * sizeof(TCHAR));
82 _tgetenv_s(&size, env, size, _T("PATH"));
84 TCHAR buf[_MAX_PATH];
86 // search in all paths defined in PATH
87 while ((env = nextpath(env, buf, _MAX_PATH-1)) && *buf)
89 TCHAR *pfin = buf + _tcslen(buf)-1;
91 // ensure trailing slash
92 if (*pfin != _T('/') && *pfin != _T('\\'))
93 _tcscpy(++pfin, _T("\\"));
95 const int len = _tcslen(buf);
97 if ((len + 7) < _MAX_PATH)
98 _tcscpy(pfin+1, _T("git.exe"));
99 else
100 break;
102 if ( FileExists(buf) )
104 // dir found
105 pfin[1] = 0;
106 CGit::ms_LastMsysGitDir = buf;
107 return TRUE;
111 return FALSE;
115 #define MAX_DIRBUFFER 1000
116 #define CALL_OUTPUT_READ_CHUNK_SIZE 1024
118 CString CGit::ms_LastMsysGitDir;
119 CGit g_Git;
122 CGit::CGit(void)
124 GetCurrentDirectory(MAX_DIRBUFFER,m_CurrentDir.GetBuffer(MAX_DIRBUFFER));
125 m_CurrentDir.ReleaseBuffer();
126 m_IsGitDllInited = false;
127 m_GitDiff=0;
128 m_IsUseGitDLL = CRegDWORD(_T("Software\\TortoiseGit\\UsingGitDLL"),1);
129 this->m_bInitialized =false;
130 CheckMsysGitDir();
131 m_critGitDllSec.Init();
134 CGit::~CGit(void)
136 if(this->m_GitDiff)
138 git_close_diff(m_GitDiff);
139 m_GitDiff=0;
143 static char g_Buffer[4096];
145 int CGit::RunAsync(CString cmd,PROCESS_INFORMATION *piOut,HANDLE *hReadOut,CString *StdioFile)
147 SECURITY_ATTRIBUTES sa;
148 HANDLE hRead, hWrite;
149 HANDLE hStdioFile = NULL;
151 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
152 sa.lpSecurityDescriptor=NULL;
153 sa.bInheritHandle=TRUE;
154 if(!CreatePipe(&hRead,&hWrite,&sa,0))
156 return GIT_ERROR_OPEN_PIP;
159 if(StdioFile)
161 hStdioFile=CreateFile(*StdioFile,GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,
162 &sa,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
165 STARTUPINFO si;
166 PROCESS_INFORMATION pi;
167 si.cb=sizeof(STARTUPINFO);
168 GetStartupInfo(&si);
170 si.hStdError=hWrite;
171 if(StdioFile)
172 si.hStdOutput=hStdioFile;
173 else
174 si.hStdOutput=hWrite;
176 si.wShowWindow=SW_HIDE;
177 si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
179 LPTSTR pEnv = m_Environment.size()? &m_Environment[0]: NULL;
180 DWORD dwFlags = pEnv ? CREATE_UNICODE_ENVIRONMENT : 0;
182 //DETACHED_PROCESS make ssh recognize that it has no console to launch askpass to input password.
183 dwFlags |= DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP;
185 memset(&this->m_CurrentGitPi,0,sizeof(PROCESS_INFORMATION));
187 if(cmd.Find(_T("git") == 0))
188 cmd=CGit::ms_LastMsysGitDir+_T("\\")+cmd;
190 if(!CreateProcess(NULL,(LPWSTR)cmd.GetString(), NULL,NULL,TRUE,dwFlags,pEnv,(LPWSTR)m_CurrentDir.GetString(),&si,&pi))
192 LPVOID lpMsgBuf;
193 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
194 NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
195 (LPTSTR)&lpMsgBuf,
196 0,NULL);
197 return GIT_ERROR_CREATE_PROCESS;
200 m_CurrentGitPi = pi;
202 CloseHandle(hWrite);
203 if(piOut)
204 *piOut=pi;
205 if(hReadOut)
206 *hReadOut=hRead;
208 return 0;
211 //Must use sperate function to convert ANSI str to union code string
212 //Becuase A2W use stack as internal convert buffer.
213 void CGit::StringAppend(CString *str,BYTE *p,int code,int length)
215 //USES_CONVERSION;
216 //str->Append(A2W_CP((LPCSTR)p,code));
217 if(str == NULL)
218 return ;
220 WCHAR * buf;
222 int len ;
223 if(length<0)
224 len= strlen((const char*)p);
225 else
226 len=length;
227 //if (len==0)
228 // return ;
229 //buf = new WCHAR[len*4 + 1];
230 buf = str->GetBuffer(len*4+1+str->GetLength())+str->GetLength();
231 SecureZeroMemory(buf, (len*4 + 1)*sizeof(WCHAR));
232 MultiByteToWideChar(code, 0, (LPCSTR)p, len, buf, len*4);
233 str->ReleaseBuffer();
234 //str->Append(buf);
235 //delete buf;
237 BOOL CGit::IsInitRepos()
239 CString cmdout;
240 cmdout.Empty();
241 if(g_Git.Run(_T("git.exe rev-parse --revs-only HEAD"),&cmdout,CP_UTF8))
243 // CMessageBox::Show(NULL,cmdout,_T("TortoiseGit"),MB_OK);
244 return TRUE;
246 if(cmdout.IsEmpty())
247 return TRUE;
249 return FALSE;
251 int CGit::Run(CGitCall* pcall)
253 PROCESS_INFORMATION pi;
254 HANDLE hRead;
255 if(RunAsync(pcall->GetCmd(),&pi,&hRead))
256 return GIT_ERROR_CREATE_PROCESS;
258 DWORD readnumber;
259 BYTE data[CALL_OUTPUT_READ_CHUNK_SIZE];
260 bool bAborted=false;
261 while(ReadFile(hRead,data,CALL_OUTPUT_READ_CHUNK_SIZE,&readnumber,NULL))
263 //Todo: when OnOutputData() returns 'true', abort git-command. Send CTRL-C signal?
264 if(!bAborted)//For now, flush output when command aborted.
265 if(pcall->OnOutputData(data,readnumber))
266 bAborted=true;
268 if(!bAborted)
269 pcall->OnEnd();
272 CloseHandle(pi.hThread);
274 WaitForSingleObject(pi.hProcess, INFINITE);
275 DWORD exitcode =0;
277 if(!GetExitCodeProcess(pi.hProcess,&exitcode))
279 return GIT_ERROR_GET_EXIT_CODE;
282 CloseHandle(pi.hProcess);
284 CloseHandle(hRead);
285 return exitcode;
287 class CGitCall_ByteVector : public CGitCall
289 public:
290 CGitCall_ByteVector(CString cmd,BYTE_VECTOR* pvector):CGitCall(cmd),m_pvector(pvector){}
291 virtual bool OnOutputData(const BYTE* data, size_t size)
293 size_t oldsize=m_pvector->size();
294 m_pvector->resize(m_pvector->size()+size);
295 memcpy(&*(m_pvector->begin()+oldsize),data,size);
296 return false;
298 BYTE_VECTOR* m_pvector;
301 int CGit::Run(CString cmd,BYTE_VECTOR *vector)
303 CGitCall_ByteVector call(cmd,vector);
304 return Run(&call);
306 int CGit::Run(CString cmd, CString* output,int code)
308 BYTE_VECTOR vector;
309 int ret;
310 ret=Run(cmd,&vector);
312 vector.push_back(0);
314 StringAppend(output,&(vector[0]),code);
315 return ret;
318 CString CGit::GetUserName(void)
320 return GetConfigValue(L"user.name", CP_ACP);
322 CString CGit::GetUserEmail(void)
324 return GetConfigValue(L"user.email");
327 CString CGit::GetConfigValue(CString name,int encoding)
329 CString configValue;
330 CString cmd;
331 cmd.Format(L"git.exe config %s", name);
332 Run(cmd,&configValue,encoding);
333 int start = 0;
334 return configValue.Tokenize(_T("\n"),start);
338 CString CGit::GetCurrentBranch(void)
340 CString output;
341 //Run(_T("git.exe branch"),&branch);
343 int ret=g_Git.Run(_T("git.exe branch --no-color"),&output,CP_UTF8);
344 if(!ret)
346 int pos=0;
347 CString one;
348 while( pos>=0 )
350 //i++;
351 one=output.Tokenize(_T("\n"),pos);
352 //list.push_back(one.Right(one.GetLength()-2));
353 if(one[0] == _T('*'))
354 return one.Right(one.GetLength()-2);
357 return CString("");
360 CString CGit::GetSymbolicRef(const wchar_t* symbolicRefName, bool bStripRefsHeads)
362 CString refName;
363 if(this->m_IsUseGitDLL)
365 unsigned char sha1[20];
366 int flag;
368 const char *refs_heads_master = git_resolve_ref(CUnicodeUtils::GetUTF8(CString(symbolicRefName)), sha1, 0, &flag);
369 if(refs_heads_master && (flag&REF_ISSYMREF))
371 g_Git.StringAppend(&refName,(BYTE*)refs_heads_master);
372 if(bStripRefsHeads)
373 refName = StripRefName(refName);
376 }else
378 CString cmd;
379 cmd.Format(L"git symbolic-ref %s", symbolicRefName);
380 if(Run(cmd, &refName, CP_UTF8) != 0)
381 return CString();//Error
382 int iStart = 0;
383 refName = refName.Tokenize(L"\n", iStart);
384 if(bStripRefsHeads)
385 refName = StripRefName(refName);
387 return refName;
390 CString CGit::GetFullRefName(CString shortRefName)
392 CString refName;
393 CString cmd;
394 cmd.Format(L"git rev-parse --symbolic-full-name %s", shortRefName);
395 if(Run(cmd, &refName, CP_UTF8) != 0)
396 return CString();//Error
397 int iStart = 0;
398 return refName.Tokenize(L"\n", iStart);
401 CString CGit::StripRefName(CString refName)
403 if(wcsncmp(refName, L"refs/heads/", 11) == 0)
404 refName = refName.Mid(11);
405 else if(wcsncmp(refName, L"refs/", 5) == 0)
406 refName = refName.Mid(5);
407 int start =0;
408 return refName.Tokenize(_T("\n"),start);
411 int CGit::GetCurrentBranchFromFile(const CString &sProjectRoot, CString &sBranchOut)
413 // read current branch name like git-gui does, by parsing the .git/HEAD file directly
415 if ( sProjectRoot.IsEmpty() )
416 return -1;
418 CString sHeadFile = sProjectRoot + _T("\\") + g_GitAdminDir.GetAdminDirName() + _T("\\HEAD");
420 FILE *pFile;
421 _tfopen_s(&pFile, sHeadFile.GetString(), _T("r"));
423 if (!pFile)
425 return -1;
428 char s[256] = {0};
429 fgets(s, sizeof(s), pFile);
431 fclose(pFile);
433 const char *pfx = "ref: refs/heads/";
434 const int len = 16;//strlen(pfx)
436 if ( !strncmp(s, pfx, len) )
438 //# We're on a branch. It might not exist. But
439 //# HEAD looks good enough to be a branch.
440 sBranchOut = s + len;
441 sBranchOut.TrimRight(_T(" \r\n\t"));
443 if ( sBranchOut.IsEmpty() )
444 return -1;
446 else
448 //# Assume this is a detached head.
449 sBranchOut = "HEAD";
451 return 1;
454 return 0;
457 int CGit::BuildOutputFormat(CString &format,bool IsFull)
459 CString log;
460 log.Format(_T("#<%c>%%x00"),LOG_REV_ITEM_BEGIN);
461 format += log;
462 if(IsFull)
464 log.Format(_T("#<%c>%%an%%x00"),LOG_REV_AUTHOR_NAME);
465 format += log;
466 log.Format(_T("#<%c>%%ae%%x00"),LOG_REV_AUTHOR_EMAIL);
467 format += log;
468 log.Format(_T("#<%c>%%ai%%x00"),LOG_REV_AUTHOR_DATE);
469 format += log;
470 log.Format(_T("#<%c>%%cn%%x00"),LOG_REV_COMMIT_NAME);
471 format += log;
472 log.Format(_T("#<%c>%%ce%%x00"),LOG_REV_COMMIT_EMAIL);
473 format += log;
474 log.Format(_T("#<%c>%%ci%%x00"),LOG_REV_COMMIT_DATE);
475 format += log;
476 log.Format(_T("#<%c>%%b%%x00"),LOG_REV_COMMIT_BODY);
477 format += log;
480 log.Format(_T("#<%c>%%m%%H%%x00"),LOG_REV_COMMIT_HASH);
481 format += log;
482 log.Format(_T("#<%c>%%P%%x00"),LOG_REV_COMMIT_PARENT);
483 format += log;
484 log.Format(_T("#<%c>%%s%%x00"),LOG_REV_COMMIT_SUBJECT);
485 format += log;
487 if(IsFull)
489 log.Format(_T("#<%c>%%x00"),LOG_REV_COMMIT_FILE);
490 format += log;
492 return 0;
495 int CGit::GetLog(BYTE_VECTOR& logOut, CString &hash, CTGitPath *path ,int count,int mask,CString *from,CString *to)
497 CGitCall_ByteVector gitCall(CString(),&logOut);
498 return GetLog(&gitCall,hash,path,count,mask,from,to);
501 CString CGit::GetLogCmd( CString &hash, CTGitPath *path, int count, int mask,CString *from,CString *to,bool paramonly)
503 CString cmd;
504 CString log;
505 CString num;
506 CString since;
508 CString file;
510 if(path)
511 file.Format(_T(" -- \"%s\""),path->GetGitPathString());
513 if(count>0)
514 num.Format(_T("-n%d"),count);
516 CString param;
518 if(mask& LOG_INFO_STAT )
519 param += _T(" --numstat ");
520 if(mask& LOG_INFO_FILESTATE)
521 param += _T(" --raw ");
523 if(mask& LOG_INFO_FULLHISTORY)
524 param += _T(" --full-history ");
526 if(mask& LOG_INFO_BOUNDARY)
527 param += _T(" --left-right --boundary ");
529 if(mask& CGit::LOG_INFO_ALL_BRANCH)
530 param += _T(" --all ");
532 if(mask& CGit::LOG_INFO_DETECT_COPYRENAME)
533 param += _T(" -C ");
535 if(mask& CGit::LOG_INFO_DETECT_RENAME )
536 param += _T(" -M ");
538 if(mask& CGit::LOG_INFO_FIRST_PARENT )
539 param += _T(" --first-parent ");
541 if(mask& CGit::LOG_INFO_NO_MERGE )
542 param += _T(" --no-merges ");
544 if(mask& CGit::LOG_INFO_FOLLOW)
545 param += _T(" --follow ");
547 if(mask& CGit::LOG_INFO_SHOW_MERGEDFILE)
548 param += _T(" -c ");
550 if(mask& CGit::LOG_INFO_FULL_DIFF)
551 param += _T(" --full-diff ");
553 if(from != NULL && to != NULL)
555 CString range;
556 range.Format(_T(" %s..%s "),*from,*to);
557 param += range;
559 param+=hash;
561 if(paramonly)
562 cmd.Format(_T("%s -z --topo-order %s --parents "),
563 num,param);
564 else
565 cmd.Format(_T("git.exe log %s -z --topo-order %s --parents --pretty=format:\""),
566 num,param);
568 BuildOutputFormat(log,!(mask&CGit::LOG_INFO_ONLY_HASH));
570 if(paramonly)
572 cmd += _T("-- ")+file;
573 }else
575 cmd += log;
576 cmd += CString(_T("\" -- "))+file;
579 return cmd;
581 //int CGit::GetLog(CGitCall* pgitCall, CString &hash, CTGitPath *path ,int count,int mask)
582 int CGit::GetLog(CGitCall* pgitCall, CString &hash, CTGitPath *path, int count, int mask,CString *from,CString *to)
584 pgitCall->SetCmd( GetLogCmd(hash,path,count,mask,from,to) );
586 return Run(pgitCall);
587 // return Run(cmd,&logOut);
590 #define BUFSIZE 512
591 void GetTempPath(CString &path)
593 TCHAR lpPathBuffer[BUFSIZE];
594 DWORD dwRetVal;
595 DWORD dwBufSize=BUFSIZE;
596 dwRetVal = GetTempPath(dwBufSize, // length of the buffer
597 lpPathBuffer); // buffer for path
598 if (dwRetVal > dwBufSize || (dwRetVal == 0))
600 path=_T("");
602 path.Format(_T("%s"),lpPathBuffer);
604 CString GetTempFile()
606 TCHAR lpPathBuffer[BUFSIZE];
607 DWORD dwRetVal;
608 DWORD dwBufSize=BUFSIZE;
609 TCHAR szTempName[BUFSIZE];
610 UINT uRetVal;
612 dwRetVal = GetTempPath(dwBufSize, // length of the buffer
613 lpPathBuffer); // buffer for path
614 if (dwRetVal > dwBufSize || (dwRetVal == 0))
616 return _T("");
618 // Create a temporary file.
619 uRetVal = GetTempFileName(lpPathBuffer, // directory for tmp files
620 TEXT("Patch"), // temp file name prefix
621 0, // create unique name
622 szTempName); // buffer for name
625 if (uRetVal == 0)
627 return _T("");
630 return CString(szTempName);
634 int CGit::RunLogFile(CString cmd,CString &filename)
636 STARTUPINFO si;
637 PROCESS_INFORMATION pi;
638 si.cb=sizeof(STARTUPINFO);
639 GetStartupInfo(&si);
641 SECURITY_ATTRIBUTES psa={sizeof(psa),NULL,TRUE};;
642 psa.bInheritHandle=TRUE;
644 HANDLE houtfile=CreateFile(filename,GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,
645 &psa,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
648 si.wShowWindow=SW_HIDE;
649 si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
650 si.hStdOutput = houtfile;
652 LPTSTR pEnv = m_Environment.size()? &m_Environment[0]: NULL;
653 DWORD dwFlags = pEnv ? CREATE_UNICODE_ENVIRONMENT : 0;
655 if(cmd.Find(_T("git") == 0))
656 cmd=CGit::ms_LastMsysGitDir+_T("\\")+cmd;
658 if(!CreateProcess(NULL,(LPWSTR)cmd.GetString(), NULL,NULL,TRUE,dwFlags,pEnv,(LPWSTR)m_CurrentDir.GetString(),&si,&pi))
660 LPVOID lpMsgBuf;
661 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
662 NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
663 (LPTSTR)&lpMsgBuf,
664 0,NULL);
665 return GIT_ERROR_CREATE_PROCESS;
668 WaitForSingleObject(pi.hProcess,INFINITE);
670 CloseHandle(pi.hThread);
671 CloseHandle(pi.hProcess);
672 CloseHandle(houtfile);
673 return GIT_SUCCESS;
674 // return 0;
677 git_revnum_t CGit::GetHash(const CString &friendname)
679 CString cmd;
680 CString out;
681 cmd.Format(_T("git.exe rev-parse %s" ),friendname);
682 Run(cmd,&out,CP_UTF8);
683 // int pos=out.ReverseFind(_T('\n'));
684 int pos=out.FindOneOf(_T("\r\n"));
685 if(pos>0)
686 return out.Left(pos);
687 return out;
690 int CGit::GetInitAddList(CTGitPathList &outputlist)
692 CString cmd;
693 BYTE_VECTOR cmdout;
695 cmd=_T("git.exe ls-files -s -t -z");
696 outputlist.Clear();
697 if(g_Git.Run(cmd,&cmdout))
698 return -1;
700 outputlist.ParserFromLsFile(cmdout);
701 for(int i=0;i<outputlist.GetCount();i++)
702 ((int)outputlist[i].m_Action) = CTGitPath::LOGACTIONS_ADDED;
704 return 0;
706 int CGit::GetCommitDiffList(CString &rev1,CString &rev2,CTGitPathList &outputlist)
708 CString cmd;
710 if(rev1 == GIT_REV_ZERO || rev2 == GIT_REV_ZERO)
712 //rev1=+_T("");
713 if(rev1 == GIT_REV_ZERO)
714 cmd.Format(_T("git.exe diff -r --raw -C -M --numstat -z %s"),rev2);
715 else
716 cmd.Format(_T("git.exe diff -r -R --raw -C -M --numstat -z %s"),rev1);
717 }else
719 cmd.Format(_T("git.exe diff-tree -r --raw -C -M --numstat -z %s %s"),rev2,rev1);
722 BYTE_VECTOR out;
723 if(g_Git.Run(cmd,&out))
724 return -1;
726 outputlist.ParserFromLog(out);
730 int addto_list_each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
732 STRING_VECTOR *list = (STRING_VECTOR*)cb_data;
733 CString str;
734 g_Git.StringAppend(&str,(BYTE*)refname,CP_ACP);
735 list->push_back(str);
736 return 0;
739 int CGit::GetTagList(STRING_VECTOR &list)
741 int ret;
743 if(this->m_IsUseGitDLL)
745 return git_for_each_ref_in("refs/tags/",addto_list_each_ref_fn, &list);
747 }else
749 CString cmd,output;
750 cmd=_T("git.exe tag -l");
751 int i=0;
752 ret=g_Git.Run(cmd,&output,CP_UTF8);
753 if(!ret)
755 int pos=0;
756 CString one;
757 while( pos>=0 )
759 i++;
760 one=output.Tokenize(_T("\n"),pos);
761 list.push_back(one);
765 return ret;
768 int CGit::GetBranchList(STRING_VECTOR &list,int *current,BRANCH_TYPE type)
770 int ret;
771 CString cmd,output;
772 cmd=_T("git.exe branch --no-color");
774 if(type==(BRANCH_LOCAL|BRANCH_REMOTE))
775 cmd+=_T(" -a");
776 else if(type==BRANCH_REMOTE)
777 cmd+=_T(" -r");
779 int i=0;
780 ret=g_Git.Run(cmd,&output,CP_UTF8);
781 if(!ret)
783 int pos=0;
784 CString one;
785 while( pos>=0 )
787 one=output.Tokenize(_T("\n"),pos);
788 one.Trim(L" \r\n\t");
789 if(one.Find(L" -> ") >= 0 || one.IsEmpty())
790 continue; // skip something like: refs/origin/HEAD -> refs/origin/master
791 if(one[0] == _T('*'))
793 if(current)
794 *current=i;
795 one = one.Mid(2);
797 list.push_back(one);
798 i++;
801 return ret;
804 int CGit::GetRemoteList(STRING_VECTOR &list)
806 int ret;
807 CString cmd,output;
808 cmd=_T("git.exe config --get-regexp \"^^remote[.].*[.]url\"");
809 ret=g_Git.Run(cmd,&output,CP_UTF8);
810 if(!ret)
812 int pos=0;
813 CString one;
814 while( pos>=0 )
816 one=output.Tokenize(_T("\n"),pos);
817 int start=one.Find(_T("."),0);
818 if(start>0)
820 CString url;
821 url=one.Right(one.GetLength()-start-1);
822 one=url;
823 one=one.Left(one.Find(_T("."),0));
824 list.push_back(one);
828 return ret;
831 int CGit::GetRefList(STRING_VECTOR &list)
833 int ret;
834 if(this->m_IsUseGitDLL)
836 return git_for_each_ref_in("",addto_list_each_ref_fn, &list);
838 }else
840 CString cmd,output;
841 cmd=_T("git.exe show-ref -d");
842 ret=g_Git.Run(cmd,&output,CP_UTF8);
843 if(!ret)
845 int pos=0;
846 CString one;
847 while( pos>=0 )
849 one=output.Tokenize(_T("\n"),pos);
850 int start=one.Find(_T(" "),0);
851 if(start>0)
853 CString name;
854 name=one.Right(one.GetLength()-start-1);
855 list.push_back(name);
860 return ret;
863 int addto_map_each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
865 MAP_HASH_NAME *map = (MAP_HASH_NAME*)cb_data;
866 CString str;
867 g_Git.StringAppend(&str,(BYTE*)refname,CP_ACP);
868 CGitHash hash((char*)sha1);
870 (*map)[hash].push_back(str);
871 return 0;
874 int CGit::GetMapHashToFriendName(MAP_HASH_NAME &map)
876 int ret;
877 if(this->m_IsUseGitDLL)
879 return git_for_each_ref_in("",addto_map_each_ref_fn, &map);
881 }else
883 CString cmd,output;
884 cmd=_T("git.exe show-ref -d");
885 ret=g_Git.Run(cmd,&output,CP_UTF8);
886 if(!ret)
888 int pos=0;
889 CString one;
890 while( pos>=0 )
892 one=output.Tokenize(_T("\n"),pos);
893 int start=one.Find(_T(" "),0);
894 if(start>0)
896 CString name;
897 name=one.Right(one.GetLength()-start-1);
899 CString hash;
900 hash=one.Left(start);
902 map[CGitHash(hash)].push_back(name);
907 return ret;
910 BOOL CGit::CheckMsysGitDir()
912 if (m_bInitialized)
914 return TRUE;
917 this->m_Environment.clear();
918 m_Environment.CopyProcessEnvironment();
920 TCHAR *oldpath,*home;
921 size_t homesize,size,httpsize;
923 // set HOME if not set already
924 _tgetenv_s(&homesize, NULL, 0, _T("HOME"));
925 if (!homesize)
927 if ( (!_tdupenv_s(&home,&size,_T("USERPROFILE"))) && (home != NULL) )
929 m_Environment.SetEnv(_T("HOME"),home);
930 free(home);
932 else
934 ATLTRACE("CGit::CheckMsysGitDir Unable to SetEnv HOME\n");
937 CString str;
939 //set http_proxy
940 _tgetenv_s(&httpsize, NULL, 0, _T("http_proxy"));
941 if (!httpsize)
943 CString regServeraddress_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-host"), _T(""));
944 CString regServerport_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-port"), _T(""));
945 CString regUsername_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-username"), _T(""));
946 CString regPassword_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-password"), _T(""));
947 CString regTimeout_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-timeout"), _T(""));
948 CString regExceptions_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-exceptions"), _T(""));
950 CString http_proxy;
951 if(!regServeraddress_copy.IsEmpty())
953 if(regServeraddress_copy.Left(4) != _T("http"))
954 http_proxy=_T("http://");
956 if(!regUsername_copy.IsEmpty())
958 http_proxy += regUsername_copy;
959 http_proxy += _T(":")+regPassword_copy;
960 http_proxy += _T("@");
962 http_proxy+=regServeraddress_copy;
963 if(!regServerport_copy.IsEmpty())
965 http_proxy +=_T(":")+regServerport_copy;
967 m_Environment.SetEnv(_T("http_proxy"),(LPTSTR)http_proxy.GetBuffer());
970 //setup ssh client
971 CString sshclient=CRegString(_T("Software\\TortoiseGit\\SSH"));
973 if(!sshclient.IsEmpty())
975 m_Environment.SetEnv(_T("GIT_SSH"),sshclient.GetBuffer());
977 //Setup SVN_SSH
978 CString ssh=sshclient;
979 ssh.Replace(_T("/"),_T("\\"));
980 ssh.Replace(_T("\\"),_T("\\\\"));
981 ssh=CString(_T("\""))+ssh+_T('\"');
982 m_Environment.SetEnv(_T("SVN_SSH"),ssh.GetBuffer());
984 }else
986 TCHAR sPlink[MAX_PATH];
987 GetModuleFileName(NULL, sPlink, _countof(sPlink));
988 LPTSTR ptr = _tcsrchr(sPlink, _T('\\'));
989 if (ptr) {
990 _tcscpy(ptr + 1, _T("TortoisePlink.exe"));
991 m_Environment.SetEnv(_T("GIT_SSH"), sPlink);
993 //Setup SVN_SSH
994 CString ssh=sPlink;
995 ssh.Replace(_T("/"),_T("\\"));
996 ssh.Replace(_T("\\"),_T("\\\\"));
997 ssh=CString(_T("\""))+ssh+_T('\"');
998 m_Environment.SetEnv(_T("SVN_SSH"),ssh.GetBuffer());
1003 TCHAR sAskPass[MAX_PATH];
1004 GetModuleFileName(NULL, sAskPass, _countof(sAskPass));
1005 LPTSTR ptr = _tcsrchr(sAskPass, _T('\\'));
1006 if (ptr)
1008 _tcscpy(ptr + 1, _T("SshAskPass.exe"));
1009 m_Environment.SetEnv(_T("DISPLAY"),_T(":9999"));
1010 m_Environment.SetEnv(_T("SSH_ASKPASS"),sAskPass);
1011 m_Environment.SetEnv(_T("GIT_ASKPASS"),sAskPass);
1015 // add git/bin path to PATH
1017 CRegString msysdir=CRegString(REG_MSYSGIT_PATH,_T(""),FALSE);
1018 str=msysdir;
1019 if(str.IsEmpty())
1021 CRegString msysinstalldir=CRegString(REG_MSYSGIT_INSTALL,_T(""),FALSE,HKEY_LOCAL_MACHINE);
1022 str=msysinstalldir;
1023 if ( !str.IsEmpty() )
1025 str += (str[str.GetLength()-1] != '\\') ? "\\bin" : "bin";
1026 msysdir=str;
1027 CGit::ms_LastMsysGitDir = str;
1028 msysdir.write();
1030 else
1032 // search PATH if git/bin directory is alredy present
1033 if ( FindGitPath() )
1035 m_bInitialized = TRUE;
1036 return TRUE;
1039 return false;
1041 }else
1043 CGit::ms_LastMsysGitDir = str;
1045 //set path
1047 _tdupenv_s(&oldpath,&size,_T("PATH"));
1049 CString path;
1050 path.Format(_T("%s;%s"),oldpath,str + _T(";")+ (CString)CRegString(REG_MSYSGIT_EXTRA_PATH,_T(""),FALSE));
1052 m_Environment.SetEnv(_T("PATH"),path.GetBuffer());
1054 CString str1 = m_Environment.GetEnv(_T("PATH"));
1056 CString sOldPath = oldpath;
1057 free(oldpath);
1059 m_bInitialized = TRUE;
1060 return true;
1065 class CGitCall_EnumFiles : public CGitCall
1067 public:
1068 CGitCall_EnumFiles(const TCHAR *pszProjectPath, const TCHAR *pszSubPath, unsigned int nFlags, WGENUMFILECB *pEnumCb, void *pUserData)
1069 : m_pszProjectPath(pszProjectPath),
1070 m_pszSubPath(pszSubPath),
1071 m_nFlags(nFlags),
1072 m_pEnumCb(pEnumCb),
1073 m_pUserData(pUserData)
1077 typedef std::map<CStringA,char> TStrCharMap;
1079 const TCHAR * m_pszProjectPath;
1080 const TCHAR * m_pszSubPath;
1081 unsigned int m_nFlags;
1082 WGENUMFILECB * m_pEnumCb;
1083 void * m_pUserData;
1085 BYTE_VECTOR m_DataCollector;
1087 virtual bool OnOutputData(const BYTE* data, size_t size)
1089 m_DataCollector.append(data,size);
1090 while(true)
1092 // lines from igit.exe are 0 terminated
1093 int found=m_DataCollector.findData((const BYTE*)"",1);
1094 if(found<0)
1095 return false;
1096 OnSingleLine( (LPCSTR)&*m_DataCollector.begin() );
1097 m_DataCollector.erase(m_DataCollector.begin(), m_DataCollector.begin()+found+1);
1099 return false;//Should never reach this
1101 virtual void OnEnd()
1105 BYTE HexChar(char ch)
1107 if (ch >= '0' && ch <= '9')
1108 return (UINT)(ch - '0');
1109 else if (ch >= 'A' && ch <= 'F')
1110 return (UINT)(ch - 'A') + 10;
1111 else if (ch >= 'a' && ch <= 'f')
1112 return (UINT)(ch - 'a') + 10;
1113 else
1114 return 0;
1117 bool OnSingleLine(LPCSTR line)
1119 //Parse single line
1121 wgFile_s fileStatus;
1123 // file/dir type
1125 fileStatus.nFlags = 0;
1126 if (*line == 'D')
1127 fileStatus.nFlags |= WGFF_Directory;
1128 else if (*line != 'F')
1129 // parse error
1130 return false;
1131 line += 2;
1133 // status
1135 fileStatus.nStatus = WGFS_Unknown;
1136 switch (*line)
1138 case 'N': fileStatus.nStatus = WGFS_Normal; break;
1139 case 'M': fileStatus.nStatus = WGFS_Modified; break;
1140 case 'S': fileStatus.nStatus = WGFS_Staged; break;
1141 case 'A': fileStatus.nStatus = WGFS_Added; break;
1142 case 'C': fileStatus.nStatus = WGFS_Conflicted; break;
1143 case 'D': fileStatus.nStatus = WGFS_Deleted; break;
1144 case 'I': fileStatus.nStatus = WGFS_Ignored; break;
1145 case 'U': fileStatus.nStatus = WGFS_Unversioned; break;
1146 case 'E': fileStatus.nStatus = WGFS_Empty; break;
1147 case '?': fileStatus.nStatus = WGFS_Unknown; break;
1148 default:
1149 // parse error
1150 return false;
1152 line += 2;
1154 // file sha1
1156 BYTE sha1[20];
1157 fileStatus.sha1 = NULL;
1158 if ( !(fileStatus.nFlags & WGFF_Directory) )
1160 for (int i=0; i<20; i++)
1162 sha1[i] = (HexChar(line[0])<<4)&0xF0;
1163 sha1[i] |= HexChar(line[1])&0xF;
1165 line += 2;
1168 line++;
1171 // filename
1172 int len = strlen(line);
1173 if (len && len < 2048)
1175 WCHAR *buf = (WCHAR*)alloca((len*4+2)*sizeof(WCHAR));
1176 *buf = 0;
1177 MultiByteToWideChar(CP_ACP, 0, line, len+1, buf, len*4+1);
1178 fileStatus.sFileName = buf;
1180 if (*buf && (*m_pEnumCb)(&fileStatus,m_pUserData))
1181 return false;
1184 return true;
1188 BOOL CGit::EnumFiles(const TCHAR *pszProjectPath, const TCHAR *pszSubPath, unsigned int nFlags, WGENUMFILECB *pEnumCb, void *pUserData)
1190 if(!pszProjectPath || *pszProjectPath=='\0')
1191 return FALSE;
1193 CGitCall_EnumFiles W_GitCall(pszProjectPath,pszSubPath,nFlags,pEnumCb,pUserData);
1194 CString cmd;
1196 /* char W_szToDir[MAX_PATH];
1197 strncpy(W_szToDir,pszProjectPath,sizeof(W_szToDir)-1);
1198 if(W_szToDir[strlen(W_szToDir)-1]!='\\')
1199 strncat(W_szToDir,"\\",sizeof(W_szToDir)-1);
1201 SetCurrentDirectoryA(W_szToDir);
1202 GetCurrentDirectoryA(sizeof(W_szToDir)-1,W_szToDir);
1204 SetCurrentDir(pszProjectPath);
1206 CString sMode;
1207 if (nFlags)
1209 if (nFlags & WGEFF_NoRecurse) sMode += _T("r");
1210 if (nFlags & WGEFF_FullPath) sMode += _T("f");
1211 if (nFlags & WGEFF_DirStatusDelta) sMode += _T("d");
1212 if (nFlags & WGEFF_DirStatusAll) sMode += _T("D");
1213 if (nFlags & WGEFF_EmptyAsNormal) sMode += _T("e");
1214 if (nFlags & WGEFF_SingleFile) sMode += _T("s");
1216 else
1218 sMode = _T("-");
1221 // NOTE: there seems to be some issue with msys based app receiving backslash on commandline, at least
1222 // if followed by " like for example 'igit "C:\"', the commandline igit receives is 'igit.exe C:" status' with
1223 // the 'C:" status' part as a single arg, Maybe it uses unix style processing. In order to avoid this just
1224 // use forward slashes for supplied project and sub paths
1226 CString sProjectPath = pszProjectPath;
1227 sProjectPath.Replace(_T('\\'), _T('/'));
1229 if (pszSubPath)
1231 CString sSubPath = pszSubPath;
1232 sSubPath.Replace(_T('\\'), _T('/'));
1234 cmd.Format(_T("tgit.exe statusex \"%s\" status %s \"%s\""), sProjectPath, sMode, sSubPath);
1236 else
1238 cmd.Format(_T("tgit.exe statusex \"%s\" status %s"), sProjectPath, sMode);
1241 //OutputDebugStringA("---");OutputDebugStringW(cmd);OutputDebugStringA("\r\n");
1243 W_GitCall.SetCmd(cmd);
1244 // NOTE: should igit get added as a part of msysgit then use below line instead of the above one
1245 //W_GitCall.SetCmd(CGit::ms_LastMsysGitDir + cmd);
1247 if ( Run(&W_GitCall) )
1248 return FALSE;
1250 return TRUE;
1253 BOOL CGit::CheckCleanWorkTree()
1255 CString out;
1256 CString cmd;
1257 cmd=_T("git.exe rev-parse --verify HEAD");
1259 if(g_Git.Run(cmd,&out,CP_UTF8))
1260 return FALSE;
1262 cmd=_T("git.exe update-index --ignore-submodules --refresh");
1263 if(g_Git.Run(cmd,&out,CP_UTF8))
1264 return FALSE;
1266 cmd=_T("git.exe diff-files --quiet --ignore-submodules");
1267 if(g_Git.Run(cmd,&out,CP_UTF8))
1268 return FALSE;
1270 cmd=_T("git diff-index --cached --quiet HEAD --ignore-submodules");
1271 if(g_Git.Run(cmd,&out,CP_UTF8))
1272 return FALSE;
1274 return TRUE;
1276 int CGit::Revert(CTGitPathList &list,bool keep)
1278 int ret;
1279 for(int i=0;i<list.GetCount();i++)
1281 ret = Revert((CTGitPath&)list[i],keep);
1282 if(ret)
1283 return ret;
1285 return 0;
1287 int CGit::Revert(CTGitPath &path,bool keep)
1289 CString cmd, out;
1291 if(path.m_Action & CTGitPath::LOGACTIONS_REPLACED )
1293 cmd.Format(_T("git.exe mv -- \"%s\" \"%s\""),path.GetGitPathString(),path.GetGitOldPathString());
1294 if(g_Git.Run(cmd,&out,CP_ACP))
1295 return -1;
1297 cmd.Format(_T("git.exe checkout HEAD -f -- \"%s\""),path.GetGitOldPathString());
1298 if(g_Git.Run(cmd,&out,CP_ACP))
1299 return -1;
1301 }else if(path.m_Action & CTGitPath::LOGACTIONS_ADDED)
1302 { //To init git repository, there are not HEAD, so we can use git reset command
1303 cmd.Format(_T("git.exe rm --cached -- \"%s\""),path.GetGitPathString());
1305 if(g_Git.Run(cmd,&out,CP_ACP))
1306 return -1;
1308 else
1310 cmd.Format(_T("git.exe checkout HEAD -f -- \"%s\""),path.GetGitPathString());
1311 if(g_Git.Run(cmd,&out,CP_ACP))
1312 return -1;
1314 return 0;
1317 int CGit::ListConflictFile(CTGitPathList &list,CTGitPath *path)
1319 BYTE_VECTOR vector;
1321 CString cmd;
1322 if(path)
1323 cmd.Format(_T("git.exe ls-files -u -t -z -- \"%s\""),path->GetGitPathString());
1324 else
1325 cmd=_T("git.exe ls-files -u -t -z");
1327 if(g_Git.Run(cmd,&vector))
1329 return -1;
1332 list.ParserFromLsFile(vector);
1334 return 0;
1337 bool CGit::IsFastForward(CString &from, CString &to)
1339 CString base,hash;
1340 CString cmd;
1341 cmd.Format(_T("git.exe merge-base %s %s"), to,from);
1343 if(g_Git.Run(cmd,&base,CP_ACP))
1345 //CMessageBox::Show(NULL,base,_T("TortoiseGit"),MB_OK|MB_ICONERROR);
1346 return false;
1348 base=base.Left(40);
1350 hash=g_Git.GetHash(from);
1352 hash=hash.Left(40);
1354 return hash == base;
1357 unsigned int CGit::Hash2int(CString &hash)
1359 int ret=0;
1360 for(int i=0;i<8;i++)
1362 ret =ret <<4;
1363 if(hash[i]>=_T('a'))
1364 ret |= (hash[i]-_T('a')+10)&0xFF;
1365 else if(hash[i]>=_T('A'))
1366 ret |= (hash[i]-_T('A')+10)&0xFF;
1367 else
1368 ret |= (hash[i]-_T('0'))&0xFF;
1371 return ret;
1374 int CGit::RefreshGitIndex()
1376 CString cmd,output;
1377 cmd=_T("git.exe update-index --refresh");
1378 return Run(cmd,&output,CP_ACP);
1381 void CEnvironment::CopyProcessEnvironment()
1383 TCHAR *p = GetEnvironmentStrings();
1384 while(*p !=0 || *(p+1) !=0)
1385 this->push_back(*p++);
1387 push_back(_T('\0'));
1388 push_back(_T('\0'));
1391 CString CEnvironment::GetEnv(TCHAR *name)
1393 CString str;
1394 for(int i=0;i<size();i++)
1396 str = &(*this)[i];
1397 int start =0;
1398 CString sname = str.Tokenize(_T("="),start);
1399 if(sname.CompareNoCase(name) == 0)
1401 return &(*this)[i+start+1];
1403 i+=str.GetLength();
1405 return _T("");
1408 void CEnvironment::SetEnv(TCHAR *name, TCHAR* value)
1410 int i;
1411 for( i=0;i<size();i++)
1413 CString str = &(*this)[i];
1414 int start =0;
1415 CString sname = str.Tokenize(_T("="),start);
1416 if(sname.CompareNoCase(name) == 0)
1418 break;
1420 i+=str.GetLength();
1423 if(i == size())
1425 i -= 1; // roll back terminate \0\0
1426 this->push_back(_T('\0'));
1429 CEnvironment::iterator it;
1430 it=this->begin();
1431 it += i;
1433 while(*it && i<size())
1435 this->erase(it);
1436 it=this->begin();
1437 it += i;
1440 while(*name)
1442 this->insert(it,*name++);
1443 i++;
1444 it= begin()+i;
1447 this->insert(it, _T('='));
1448 i++;
1449 it= begin()+i;
1451 while(*value)
1453 this->insert(it,*value++);
1454 i++;
1455 it= begin()+i;