Use VC build tgit replace igit to fetch status
[TortoiseGit.git] / src / Git / Git.cpp
blob5e0810bdea365007f1d7690ef98cefda3ccef843
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"
10 int CGit::m_LogEncode=CP_UTF8;
12 static LPTSTR nextpath(LPCTSTR src, LPTSTR dst, UINT maxlen)
14 LPCTSTR orgsrc;
16 while (*src == _T(';'))
17 src++;
19 orgsrc = src;
21 if (!--maxlen)
22 goto nullterm;
24 while (*src && *src != _T(';'))
26 if (*src != _T('"'))
28 *dst++ = *src++;
29 if (!--maxlen)
31 orgsrc = src;
32 goto nullterm;
35 else
37 src++;
38 while (*src && *src != _T('"'))
40 *dst++ = *src++;
41 if (!--maxlen)
43 orgsrc = src;
44 goto nullterm;
48 if (*src)
49 src++;
53 while (*src == _T(';'))
54 src++;
56 nullterm:
58 *dst = 0;
60 return (orgsrc != src) ? (LPTSTR)src : NULL;
63 static inline BOOL FileExists(LPCTSTR lpszFileName)
65 struct _stat st;
66 return _tstat(lpszFileName, &st) == 0;
69 static BOOL FindGitPath()
71 size_t size;
72 _tgetenv_s(&size, NULL, 0, _T("PATH"));
74 if (!size)
76 return FALSE;
79 TCHAR *env = (TCHAR*)alloca(size * sizeof(TCHAR));
80 _tgetenv_s(&size, env, size, _T("PATH"));
82 TCHAR buf[_MAX_PATH];
84 // search in all paths defined in PATH
85 while ((env = nextpath(env, buf, _MAX_PATH-1)) && *buf)
87 TCHAR *pfin = buf + _tcslen(buf)-1;
89 // ensure trailing slash
90 if (*pfin != _T('/') && *pfin != _T('\\'))
91 _tcscpy(++pfin, _T("\\"));
93 const int len = _tcslen(buf);
95 if ((len + 7) < _MAX_PATH)
96 _tcscpy(pfin+1, _T("git.exe"));
97 else
98 break;
100 if ( FileExists(buf) )
102 // dir found
103 pfin[1] = 0;
104 CGit::ms_LastMsysGitDir = buf;
105 return TRUE;
109 return FALSE;
113 #define MAX_DIRBUFFER 1000
114 #define CALL_OUTPUT_READ_CHUNK_SIZE 1024
116 CString CGit::ms_LastMsysGitDir;
117 CGit g_Git;
119 // contains system environment that should be used by child processes (RunAsync)
120 // initialized by CheckMsysGitDir
121 static LPTSTR l_processEnv = NULL;
125 CGit::CGit(void)
127 GetCurrentDirectory(MAX_DIRBUFFER,m_CurrentDir.GetBuffer(MAX_DIRBUFFER));
128 m_CurrentDir.ReleaseBuffer();
130 CheckMsysGitDir();
133 CGit::~CGit(void)
137 static char g_Buffer[4096];
139 int CGit::RunAsync(CString cmd,PROCESS_INFORMATION *piOut,HANDLE *hReadOut,CString *StdioFile)
141 SECURITY_ATTRIBUTES sa;
142 HANDLE hRead, hWrite;
143 HANDLE hStdioFile = NULL;
145 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
146 sa.lpSecurityDescriptor=NULL;
147 sa.bInheritHandle=TRUE;
148 if(!CreatePipe(&hRead,&hWrite,&sa,0))
150 return GIT_ERROR_OPEN_PIP;
153 if(StdioFile)
155 hStdioFile=CreateFile(*StdioFile,GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,
156 &sa,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
159 STARTUPINFO si;
160 PROCESS_INFORMATION pi;
161 si.cb=sizeof(STARTUPINFO);
162 GetStartupInfo(&si);
164 si.hStdError=hWrite;
165 if(StdioFile)
166 si.hStdOutput=hStdioFile;
167 else
168 si.hStdOutput=hWrite;
170 si.wShowWindow=SW_HIDE;
171 si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
173 LPTSTR pEnv = l_processEnv;
174 DWORD dwFlags = pEnv ? CREATE_UNICODE_ENVIRONMENT : 0;
176 //DETACHED_PROCESS make ssh recognize that it has no console to launch askpass to input password.
177 dwFlags |= DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP;
179 memset(&this->m_CurrentGitPi,0,sizeof(PROCESS_INFORMATION));
181 if(!CreateProcess(NULL,(LPWSTR)cmd.GetString(), NULL,NULL,TRUE,dwFlags,pEnv,(LPWSTR)m_CurrentDir.GetString(),&si,&pi))
183 LPVOID lpMsgBuf;
184 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
185 NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
186 (LPTSTR)&lpMsgBuf,
187 0,NULL);
188 return GIT_ERROR_CREATE_PROCESS;
191 m_CurrentGitPi = pi;
193 CloseHandle(hWrite);
194 if(piOut)
195 *piOut=pi;
196 if(hReadOut)
197 *hReadOut=hRead;
199 return 0;
202 //Must use sperate function to convert ANSI str to union code string
203 //Becuase A2W use stack as internal convert buffer.
204 void CGit::StringAppend(CString *str,BYTE *p,int code,int length)
206 //USES_CONVERSION;
207 //str->Append(A2W_CP((LPCSTR)p,code));
208 if(str == NULL)
209 return ;
211 WCHAR * buf;
213 int len ;
214 if(length<0)
215 len= strlen((const char*)p);
216 else
217 len=length;
218 //if (len==0)
219 // return ;
220 //buf = new WCHAR[len*4 + 1];
221 buf = str->GetBuffer(len*4+1+str->GetLength())+str->GetLength();
222 SecureZeroMemory(buf, (len*4 + 1)*sizeof(WCHAR));
223 MultiByteToWideChar(code, 0, (LPCSTR)p, len, buf, len*4);
224 str->ReleaseBuffer();
225 //str->Append(buf);
226 //delete buf;
228 BOOL CGit::IsInitRepos()
230 CString cmdout;
231 cmdout.Empty();
232 if(g_Git.Run(_T("git.exe rev-parse --revs-only HEAD"),&cmdout,CP_UTF8))
234 // CMessageBox::Show(NULL,cmdout,_T("TortoiseGit"),MB_OK);
235 return TRUE;
237 if(cmdout.IsEmpty())
238 return TRUE;
240 return FALSE;
242 int CGit::Run(CGitCall* pcall)
244 PROCESS_INFORMATION pi;
245 HANDLE hRead;
246 if(RunAsync(pcall->GetCmd(),&pi,&hRead))
247 return GIT_ERROR_CREATE_PROCESS;
249 DWORD readnumber;
250 BYTE data[CALL_OUTPUT_READ_CHUNK_SIZE];
251 bool bAborted=false;
252 while(ReadFile(hRead,data,CALL_OUTPUT_READ_CHUNK_SIZE,&readnumber,NULL))
254 //Todo: when OnOutputData() returns 'true', abort git-command. Send CTRL-C signal?
255 if(!bAborted)//For now, flush output when command aborted.
256 if(pcall->OnOutputData(data,readnumber))
257 bAborted=true;
259 if(!bAborted)
260 pcall->OnEnd();
263 CloseHandle(pi.hThread);
265 WaitForSingleObject(pi.hProcess, INFINITE);
266 DWORD exitcode =0;
268 if(!GetExitCodeProcess(pi.hProcess,&exitcode))
270 return GIT_ERROR_GET_EXIT_CODE;
273 CloseHandle(pi.hProcess);
275 CloseHandle(hRead);
276 return exitcode;
278 class CGitCall_ByteVector : public CGitCall
280 public:
281 CGitCall_ByteVector(CString cmd,BYTE_VECTOR* pvector):CGitCall(cmd),m_pvector(pvector){}
282 virtual bool OnOutputData(const BYTE* data, size_t size)
284 size_t oldsize=m_pvector->size();
285 m_pvector->resize(m_pvector->size()+size);
286 memcpy(&*(m_pvector->begin()+oldsize),data,size);
287 return false;
289 BYTE_VECTOR* m_pvector;
292 int CGit::Run(CString cmd,BYTE_VECTOR *vector)
294 CGitCall_ByteVector call(cmd,vector);
295 return Run(&call);
297 int CGit::Run(CString cmd, CString* output,int code)
299 BYTE_VECTOR vector;
300 int ret;
301 ret=Run(cmd,&vector);
303 vector.push_back(0);
305 StringAppend(output,&(vector[0]),code);
306 return ret;
309 CString CGit::GetUserName(void)
311 return GetConfigValue(L"user.name");
313 CString CGit::GetUserEmail(void)
315 return GetConfigValue(L"user.email");
318 CString CGit::GetConfigValue(CString name)
320 CString configValue;
321 CString cmd;
322 cmd.Format(L"git.exe config %s", name);
323 Run(cmd,&configValue,CP_UTF8);
324 int start = 0;
325 return configValue.Tokenize(_T("\n"),start);
329 CString CGit::GetCurrentBranch(void)
331 CString output;
332 //Run(_T("git.exe branch"),&branch);
334 int ret=g_Git.Run(_T("git.exe branch --no-color"),&output,CP_UTF8);
335 if(!ret)
337 int pos=0;
338 CString one;
339 while( pos>=0 )
341 //i++;
342 one=output.Tokenize(_T("\n"),pos);
343 //list.push_back(one.Right(one.GetLength()-2));
344 if(one[0] == _T('*'))
345 return one.Right(one.GetLength()-2);
348 return CString("");
351 CString CGit::GetSymbolicRef(const wchar_t* symbolicRefName, bool bStripRefsHeads)
353 CString refName;
354 CString cmd;
355 cmd.Format(L"git symbolic-ref %s", symbolicRefName);
356 if(Run(cmd, &refName, CP_UTF8) != 0)
357 return CString();//Error
358 int iStart = 0;
359 refName = refName.Tokenize(L"\n", iStart);
360 if(bStripRefsHeads)
361 refName = StripRefName(refName);
362 return refName;
365 CString CGit::GetFullRefName(CString shortRefName)
367 CString refName;
368 CString cmd;
369 cmd.Format(L"git rev-parse --symbolic-full-name %s", shortRefName);
370 if(Run(cmd, &refName, CP_UTF8) != 0)
371 return CString();//Error
372 int iStart = 0;
373 return refName.Tokenize(L"\n", iStart);
376 CString CGit::StripRefName(CString refName)
378 if(wcsncmp(refName, L"refs/heads/", 11) == 0)
379 refName = refName.Mid(11);
380 else if(wcsncmp(refName, L"refs/", 5) == 0)
381 refName = refName.Mid(5);
382 return refName;
385 int CGit::GetCurrentBranchFromFile(const CString &sProjectRoot, CString &sBranchOut)
387 // read current branch name like git-gui does, by parsing the .git/HEAD file directly
389 if ( sProjectRoot.IsEmpty() )
390 return -1;
392 CString sHeadFile = sProjectRoot + _T("\\") + g_GitAdminDir.GetAdminDirName() + _T("\\HEAD");
394 FILE *pFile;
395 _tfopen_s(&pFile, sHeadFile.GetString(), _T("r"));
397 if (!pFile)
399 return -1;
402 char s[256] = {0};
403 fgets(s, sizeof(s), pFile);
405 fclose(pFile);
407 const char *pfx = "ref: refs/heads/";
408 const int len = 16;//strlen(pfx)
410 if ( !strncmp(s, pfx, len) )
412 //# We're on a branch. It might not exist. But
413 //# HEAD looks good enough to be a branch.
414 sBranchOut = s + len;
415 sBranchOut.TrimRight(_T(" \r\n\t"));
417 if ( sBranchOut.IsEmpty() )
418 return -1;
420 else
422 //# Assume this is a detached head.
423 sBranchOut = "HEAD";
425 return 1;
428 return 0;
431 int CGit::BuildOutputFormat(CString &format,bool IsFull)
433 CString log;
434 log.Format(_T("#<%c>%%x00"),LOG_REV_ITEM_BEGIN);
435 format += log;
436 if(IsFull)
438 log.Format(_T("#<%c>%%an%%x00"),LOG_REV_AUTHOR_NAME);
439 format += log;
440 log.Format(_T("#<%c>%%ae%%x00"),LOG_REV_AUTHOR_EMAIL);
441 format += log;
442 log.Format(_T("#<%c>%%ai%%x00"),LOG_REV_AUTHOR_DATE);
443 format += log;
444 log.Format(_T("#<%c>%%cn%%x00"),LOG_REV_COMMIT_NAME);
445 format += log;
446 log.Format(_T("#<%c>%%ce%%x00"),LOG_REV_COMMIT_EMAIL);
447 format += log;
448 log.Format(_T("#<%c>%%ci%%x00"),LOG_REV_COMMIT_DATE);
449 format += log;
450 log.Format(_T("#<%c>%%b%%x00"),LOG_REV_COMMIT_BODY);
451 format += log;
454 log.Format(_T("#<%c>%%m%%H%%x00"),LOG_REV_COMMIT_HASH);
455 format += log;
456 log.Format(_T("#<%c>%%P%%x00"),LOG_REV_COMMIT_PARENT);
457 format += log;
458 log.Format(_T("#<%c>%%s%%x00"),LOG_REV_COMMIT_SUBJECT);
459 format += log;
461 if(IsFull)
463 log.Format(_T("#<%c>%%x00"),LOG_REV_COMMIT_FILE);
464 format += log;
466 return 0;
469 int CGit::GetLog(BYTE_VECTOR& logOut, CString &hash, CTGitPath *path ,int count,int mask,CString *from,CString *to)
471 CGitCall_ByteVector gitCall(CString(),&logOut);
472 return GetLog(&gitCall,hash,path,count,mask,from,to);
475 //int CGit::GetLog(CGitCall* pgitCall, CString &hash, CTGitPath *path ,int count,int mask)
476 int CGit::GetLog(CGitCall* pgitCall, CString &hash, CTGitPath *path, int count, int mask,CString *from,CString *to)
479 CString cmd;
480 CString log;
481 CString num;
482 CString since;
484 CString file;
486 if(path)
487 file.Format(_T(" -- \"%s\""),path->GetGitPathString());
489 if(count>0)
490 num.Format(_T("-n%d"),count);
492 CString param;
494 if(mask& LOG_INFO_STAT )
495 param += _T(" --numstat ");
496 if(mask& LOG_INFO_FILESTATE)
497 param += _T(" --raw ");
499 if(mask& LOG_INFO_FULLHISTORY)
500 param += _T(" --full-history ");
502 if(mask& LOG_INFO_BOUNDARY)
503 param += _T(" --left-right --boundary ");
505 if(mask& CGit::LOG_INFO_ALL_BRANCH)
506 param += _T(" --all ");
508 if(mask& CGit::LOG_INFO_DETECT_COPYRENAME)
509 param += _T(" -C ");
511 if(mask& CGit::LOG_INFO_DETECT_RENAME )
512 param += _T(" -M ");
514 if(mask& CGit::LOG_INFO_FIRST_PARENT )
515 param += _T(" --first-parent ");
517 if(mask& CGit::LOG_INFO_NO_MERGE )
518 param += _T(" --no-merges ");
520 if(mask& CGit::LOG_INFO_FOLLOW)
521 param += _T(" --follow ");
523 if(mask& CGit::LOG_INFO_SHOW_MERGEDFILE)
524 param += _T(" -c ");
526 if(mask& CGit::LOG_INFO_FULL_DIFF)
527 param += _T(" --full-diff ");
529 if(from != NULL && to != NULL)
531 CString range;
532 range.Format(_T(" %s..%s "),*from,*to);
533 param += range;
535 param+=hash;
537 cmd.Format(_T("git.exe log %s -z --topo-order %s --parents --pretty=format:\""),
538 num,param);
540 BuildOutputFormat(log,!(mask&CGit::LOG_INFO_ONLY_HASH));
542 cmd += log;
543 cmd += CString(_T("\" "))+hash+file;
545 pgitCall->SetCmd(cmd);
547 return Run(pgitCall);
548 // return Run(cmd,&logOut);
551 #if 0
552 int CGit::GetShortLog(CString &logOut,CTGitPath * path, int count)
554 CString cmd;
555 CString log;
556 int n;
557 if(count<0)
558 n=100;
559 else
560 n=count;
561 cmd.Format(_T("git.exe log --left-right --boundary --topo-order -n%d --pretty=format:\""),n);
562 BuildOutputFormat(log,false);
563 cmd += log+_T("\"");
564 if (path)
565 cmd+= _T(" -- \"")+path->GetGitPathString()+_T("\"");
566 //cmd += CString(_T("\" HEAD~40..HEAD"));
567 return Run(cmd,&logOut);
569 #endif
571 #define BUFSIZE 512
572 void GetTempPath(CString &path)
574 TCHAR lpPathBuffer[BUFSIZE];
575 DWORD dwRetVal;
576 DWORD dwBufSize=BUFSIZE;
577 dwRetVal = GetTempPath(dwBufSize, // length of the buffer
578 lpPathBuffer); // buffer for path
579 if (dwRetVal > dwBufSize || (dwRetVal == 0))
581 path=_T("");
583 path.Format(_T("%s"),lpPathBuffer);
585 CString GetTempFile()
587 TCHAR lpPathBuffer[BUFSIZE];
588 DWORD dwRetVal;
589 DWORD dwBufSize=BUFSIZE;
590 TCHAR szTempName[BUFSIZE];
591 UINT uRetVal;
593 dwRetVal = GetTempPath(dwBufSize, // length of the buffer
594 lpPathBuffer); // buffer for path
595 if (dwRetVal > dwBufSize || (dwRetVal == 0))
597 return _T("");
599 // Create a temporary file.
600 uRetVal = GetTempFileName(lpPathBuffer, // directory for tmp files
601 TEXT("Patch"), // temp file name prefix
602 0, // create unique name
603 szTempName); // buffer for name
606 if (uRetVal == 0)
608 return _T("");
611 return CString(szTempName);
615 int CGit::RunLogFile(CString cmd,CString &filename)
617 STARTUPINFO si;
618 PROCESS_INFORMATION pi;
619 si.cb=sizeof(STARTUPINFO);
620 GetStartupInfo(&si);
622 SECURITY_ATTRIBUTES psa={sizeof(psa),NULL,TRUE};;
623 psa.bInheritHandle=TRUE;
625 HANDLE houtfile=CreateFile(filename,GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,
626 &psa,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
629 si.wShowWindow=SW_HIDE;
630 si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
631 si.hStdOutput = houtfile;
633 if(!CreateProcess(NULL,(LPWSTR)cmd.GetString(), NULL,NULL,TRUE,NULL,NULL,(LPWSTR)m_CurrentDir.GetString(),&si,&pi))
635 LPVOID lpMsgBuf;
636 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
637 NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
638 (LPTSTR)&lpMsgBuf,
639 0,NULL);
640 return GIT_ERROR_CREATE_PROCESS;
643 WaitForSingleObject(pi.hProcess,INFINITE);
645 CloseHandle(pi.hThread);
646 CloseHandle(pi.hProcess);
647 CloseHandle(houtfile);
648 return GIT_SUCCESS;
649 // return 0;
652 git_revnum_t CGit::GetHash(const CString &friendname)
654 CString cmd;
655 CString out;
656 cmd.Format(_T("git.exe rev-parse %s" ),friendname);
657 Run(cmd,&out,CP_UTF8);
658 // int pos=out.ReverseFind(_T('\n'));
659 int pos=out.FindOneOf(_T("\r\n"));
660 if(pos>0)
661 return out.Left(pos);
662 return out;
665 int CGit::GetCommitDiffList(CString &rev1,CString &rev2,CTGitPathList &outputlist)
667 CString cmd;
669 if(rev1 == GIT_REV_ZERO || rev2 == GIT_REV_ZERO)
671 //rev1=+_T("");
672 if(rev1 == GIT_REV_ZERO)
673 cmd.Format(_T("git.exe diff -r --raw -C -M --numstat -z %s"),rev2);
674 else
675 cmd.Format(_T("git.exe diff -r -R --raw -C -M --numstat -z %s"),rev1);
676 }else
678 cmd.Format(_T("git.exe diff-tree -r --raw -C -M --numstat -z %s %s"),rev2,rev1);
681 BYTE_VECTOR out;
682 if(g_Git.Run(cmd,&out))
683 return -1;
685 outputlist.ParserFromLog(out);
689 int CGit::GetTagList(STRING_VECTOR &list)
691 int ret;
692 CString cmd,output;
693 cmd=_T("git.exe tag -l");
694 int i=0;
695 ret=g_Git.Run(cmd,&output,CP_UTF8);
696 if(!ret)
698 int pos=0;
699 CString one;
700 while( pos>=0 )
702 i++;
703 one=output.Tokenize(_T("\n"),pos);
704 list.push_back(one);
707 return ret;
710 int CGit::GetBranchList(STRING_VECTOR &list,int *current,BRANCH_TYPE type)
712 int ret;
713 CString cmd,output;
714 cmd=_T("git.exe branch --no-color");
716 if(type==(BRANCH_LOCAL|BRANCH_REMOTE))
717 cmd+=_T(" -a");
718 else if(type==BRANCH_REMOTE)
719 cmd+=_T(" -r");
721 int i=0;
722 ret=g_Git.Run(cmd,&output,CP_UTF8);
723 if(!ret)
725 int pos=0;
726 CString one;
727 while( pos>=0 )
729 one=output.Tokenize(_T("\n"),pos);
730 one.Trim(L" \r\n\t");
731 if(one.Find(L" -> ") >= 0 || one.IsEmpty())
732 continue; // skip something like: refs/origin/HEAD -> refs/origin/master
733 if(one[0] == _T('*'))
735 if(current)
736 *current=i;
737 one = one.Mid(2);
739 list.push_back(one);
740 i++;
743 return ret;
746 int CGit::GetRemoteList(STRING_VECTOR &list)
748 int ret;
749 CString cmd,output;
750 cmd=_T("git.exe config --get-regexp \"^^remote[.].*[.]url\"");
751 ret=g_Git.Run(cmd,&output,CP_UTF8);
752 if(!ret)
754 int pos=0;
755 CString one;
756 while( pos>=0 )
758 one=output.Tokenize(_T("\n"),pos);
759 int start=one.Find(_T("."),0);
760 if(start>0)
762 CString url;
763 url=one.Right(one.GetLength()-start-1);
764 one=url;
765 one=one.Left(one.Find(_T("."),0));
766 list.push_back(one);
770 return ret;
773 int CGit::GetRefList(STRING_VECTOR &list)
775 int ret;
776 CString cmd,output;
777 cmd=_T("git show-ref -d");
778 ret=g_Git.Run(cmd,&output,CP_UTF8);
779 if(!ret)
781 int pos=0;
782 CString one;
783 while( pos>=0 )
785 one=output.Tokenize(_T("\n"),pos);
786 int start=one.Find(_T(" "),0);
787 if(start>0)
789 CString name;
790 name=one.Right(one.GetLength()-start-1);
791 list.push_back(name);
795 return ret;
797 int CGit::GetMapHashToFriendName(MAP_HASH_NAME &map)
799 int ret;
800 CString cmd,output;
801 cmd=_T("git show-ref -d");
802 ret=g_Git.Run(cmd,&output,CP_UTF8);
803 if(!ret)
805 int pos=0;
806 CString one;
807 while( pos>=0 )
809 one=output.Tokenize(_T("\n"),pos);
810 int start=one.Find(_T(" "),0);
811 if(start>0)
813 CString name;
814 name=one.Right(one.GetLength()-start-1);
816 CString hash;
817 hash=one.Left(start);
819 map[hash].push_back(name);
823 return ret;
826 BOOL CGit::CheckMsysGitDir()
828 static BOOL bInitialized = FALSE;
830 if (bInitialized)
832 return TRUE;
835 TCHAR *oldpath,*home;
836 size_t homesize,size,httpsize;
838 // set HOME if not set already
839 _tgetenv_s(&homesize, NULL, 0, _T("HOME"));
840 if (!homesize)
842 _tdupenv_s(&home,&size,_T("USERPROFILE"));
843 _tputenv_s(_T("HOME"),home);
844 free(home);
846 CString str;
848 #ifndef _TORTOISESHELL
849 //set http_proxy
850 _tgetenv_s(&httpsize, NULL, 0, _T("http_proxy"));
851 if (!httpsize)
853 CString regServeraddress_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-host"), _T(""));
854 CString regServerport_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-port"), _T(""));
855 CString regUsername_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-username"), _T(""));
856 CString regPassword_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-password"), _T(""));
857 CString regTimeout_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-timeout"), _T(""));
858 CString regExceptions_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-exceptions"), _T(""));
860 CString http_proxy;
861 if(!regServeraddress_copy.IsEmpty())
863 if(regServeraddress_copy.Left(4) != _T("http"))
864 http_proxy=_T("http://");
866 if(!regUsername_copy.IsEmpty())
868 http_proxy += regUsername_copy;
869 http_proxy += _T(":")+regPassword_copy;
870 http_proxy += _T("@");
872 http_proxy+=regServeraddress_copy;
873 if(!regServerport_copy.IsEmpty())
875 http_proxy +=_T(":")+regServerport_copy;
877 _tputenv_s(_T("http_proxy"),http_proxy);
880 //setup ssh client
881 CString sshclient=CRegString(_T("Software\\TortoiseGit\\SSH"));
883 if(!sshclient.IsEmpty())
885 _tputenv_s(_T("GIT_SSH"),sshclient);
887 //Setup SVN_SSH
888 CString ssh=sshclient;
889 ssh.Replace(_T("/"),_T("\\"));
890 ssh.Replace(_T("\\"),_T("\\\\"));
891 ssh=CString(_T("\""))+ssh+_T('\"');
892 _tputenv_s(_T("SVN_SSH"),ssh);
894 }else
896 TCHAR sPlink[MAX_PATH];
897 GetModuleFileName(NULL, sPlink, _countof(sPlink));
898 LPTSTR ptr = _tcsrchr(sPlink, _T('\\'));
899 if (ptr) {
900 _tcscpy(ptr + 1, _T("TortoisePlink.exe"));
901 _tputenv_s(_T("GIT_SSH"), sPlink);
903 //Setup SVN_SSH
904 CString ssh=sPlink;
905 ssh.Replace(_T("/"),_T("\\"));
906 ssh.Replace(_T("\\"),_T("\\\\"));
907 ssh=CString(_T("\""))+ssh+_T('\"');
908 _tputenv_s(_T("SVN_SSH"),ssh);
913 TCHAR sAskPass[MAX_PATH];
914 GetModuleFileName(NULL, sAskPass, _countof(sAskPass));
915 LPTSTR ptr = _tcsrchr(sAskPass, _T('\\'));
916 if (ptr)
918 _tcscpy(ptr + 1, _T("SshAskPass.exe"));
919 _tputenv_s(_T("DISPLAY"),_T(":9999"));
920 _tputenv_s(_T("SSH_ASKPASS"),sAskPass);
923 // search PATH if git/bin directory is alredy present
924 if ( FindGitPath() )
926 bInitialized = TRUE;
927 return TRUE;
930 // add git/bin path to PATH
932 CRegString msysdir=CRegString(REG_MSYSGIT_PATH,_T(""),FALSE);
933 str=msysdir;
934 if(str.IsEmpty())
936 CRegString msysinstalldir=CRegString(REG_MSYSGIT_INSTALL,_T(""),FALSE,HKEY_LOCAL_MACHINE);
937 str=msysinstalldir;
938 if ( !str.IsEmpty() )
940 str += (str[str.GetLength()-1] != '\\') ? "\\bin" : "bin";
941 msysdir=str;
942 msysdir.write();
944 else
946 return false;
949 #endif
950 //CGit::m_MsysGitPath=str;
952 //set path
954 _tdupenv_s(&oldpath,&size,_T("PATH"));
956 CString path;
957 path.Format(_T("%s;%s"),oldpath,str);
959 _tputenv_s(_T("PATH"),path);
961 CString sOldPath = oldpath;
962 free(oldpath);
965 if( !FindGitPath() )
967 if(!homesize)
969 _tputenv_s(_T("HOME"),_T(""));
971 return false;
973 else
975 #ifdef _TORTOISESHELL
976 l_processEnv = GetEnvironmentStrings();
977 // updated environment is now duplicated for use in CreateProcess, restore original PATH for current process
978 _tputenv_s(_T("PATH"),sOldPath);
979 if(!homesize)
981 _tputenv_s(_T("HOME"),_T(""));
983 #endif
985 bInitialized = TRUE;
986 return true;
991 class CGitCall_EnumFiles : public CGitCall
993 public:
994 CGitCall_EnumFiles(const TCHAR *pszProjectPath, const TCHAR *pszSubPath, unsigned int nFlags, WGENUMFILECB *pEnumCb, void *pUserData)
995 : m_pszProjectPath(pszProjectPath),
996 m_pszSubPath(pszSubPath),
997 m_nFlags(nFlags),
998 m_pEnumCb(pEnumCb),
999 m_pUserData(pUserData)
1003 typedef std::map<CStringA,char> TStrCharMap;
1005 const TCHAR * m_pszProjectPath;
1006 const TCHAR * m_pszSubPath;
1007 unsigned int m_nFlags;
1008 WGENUMFILECB * m_pEnumCb;
1009 void * m_pUserData;
1011 BYTE_VECTOR m_DataCollector;
1013 virtual bool OnOutputData(const BYTE* data, size_t size)
1015 m_DataCollector.append(data,size);
1016 while(true)
1018 // lines from igit.exe are 0 terminated
1019 int found=m_DataCollector.findData((const BYTE*)"",1);
1020 if(found<0)
1021 return false;
1022 OnSingleLine( (LPCSTR)&*m_DataCollector.begin() );
1023 m_DataCollector.erase(m_DataCollector.begin(), m_DataCollector.begin()+found+1);
1025 return false;//Should never reach this
1027 virtual void OnEnd()
1031 BYTE HexChar(char ch)
1033 if (ch >= '0' && ch <= '9')
1034 return (UINT)(ch - '0');
1035 else if (ch >= 'A' && ch <= 'F')
1036 return (UINT)(ch - 'A') + 10;
1037 else if (ch >= 'a' && ch <= 'f')
1038 return (UINT)(ch - 'a') + 10;
1039 else
1040 return 0;
1043 bool OnSingleLine(LPCSTR line)
1045 //Parse single line
1047 wgFile_s fileStatus;
1049 // file/dir type
1051 fileStatus.nFlags = 0;
1052 if (*line == 'D')
1053 fileStatus.nFlags |= WGFF_Directory;
1054 else if (*line != 'F')
1055 // parse error
1056 return false;
1057 line += 2;
1059 // status
1061 fileStatus.nStatus = WGFS_Unknown;
1062 switch (*line)
1064 case 'N': fileStatus.nStatus = WGFS_Normal; break;
1065 case 'M': fileStatus.nStatus = WGFS_Modified; break;
1066 case 'S': fileStatus.nStatus = WGFS_Staged; break;
1067 case 'A': fileStatus.nStatus = WGFS_Added; break;
1068 case 'C': fileStatus.nStatus = WGFS_Conflicted; break;
1069 case 'D': fileStatus.nStatus = WGFS_Deleted; break;
1070 case 'I': fileStatus.nStatus = WGFS_Ignored; break;
1071 case 'U': fileStatus.nStatus = WGFS_Unversioned; break;
1072 case 'E': fileStatus.nStatus = WGFS_Empty; break;
1073 case '?': fileStatus.nStatus = WGFS_Unknown; break;
1074 default:
1075 // parse error
1076 return false;
1078 line += 2;
1080 // file sha1
1082 BYTE sha1[20];
1083 fileStatus.sha1 = NULL;
1084 if ( !(fileStatus.nFlags & WGFF_Directory) )
1086 for (int i=0; i<20; i++)
1088 sha1[i] = (HexChar(line[0])<<4)&0xF0;
1089 sha1[i] |= HexChar(line[1])&0xF;
1091 line += 2;
1094 line++;
1097 // filename
1098 int len = strlen(line);
1099 if (len && len < 2048)
1101 WCHAR *buf = (WCHAR*)alloca((len*4+2)*sizeof(WCHAR));
1102 *buf = 0;
1103 MultiByteToWideChar(CP_ACP, 0, line, len+1, buf, len*4+1);
1104 fileStatus.sFileName = buf;
1106 if (*buf && (*m_pEnumCb)(&fileStatus,m_pUserData))
1107 return false;
1110 return true;
1114 BOOL CGit::EnumFiles(const TCHAR *pszProjectPath, const TCHAR *pszSubPath, unsigned int nFlags, WGENUMFILECB *pEnumCb, void *pUserData)
1116 if(!pszProjectPath || *pszProjectPath=='\0')
1117 return FALSE;
1119 CGitCall_EnumFiles W_GitCall(pszProjectPath,pszSubPath,nFlags,pEnumCb,pUserData);
1120 CString cmd;
1122 /* char W_szToDir[MAX_PATH];
1123 strncpy(W_szToDir,pszProjectPath,sizeof(W_szToDir)-1);
1124 if(W_szToDir[strlen(W_szToDir)-1]!='\\')
1125 strncat(W_szToDir,"\\",sizeof(W_szToDir)-1);
1127 SetCurrentDirectoryA(W_szToDir);
1128 GetCurrentDirectoryA(sizeof(W_szToDir)-1,W_szToDir);
1130 SetCurrentDir(pszProjectPath);
1132 CString sMode;
1133 if (nFlags)
1135 if (nFlags & WGEFF_NoRecurse) sMode += _T("r");
1136 if (nFlags & WGEFF_FullPath) sMode += _T("f");
1137 if (nFlags & WGEFF_DirStatusDelta) sMode += _T("d");
1138 if (nFlags & WGEFF_DirStatusAll) sMode += _T("D");
1139 if (nFlags & WGEFF_EmptyAsNormal) sMode += _T("e");
1140 if (nFlags & WGEFF_SingleFile) sMode += _T("s");
1142 else
1144 sMode = _T("-");
1147 // NOTE: there seems to be some issue with msys based app receiving backslash on commandline, at least
1148 // if followed by " like for example 'igit "C:\"', the commandline igit receives is 'igit.exe C:" status' with
1149 // the 'C:" status' part as a single arg, Maybe it uses unix style processing. In order to avoid this just
1150 // use forward slashes for supplied project and sub paths
1152 CString sProjectPath = pszProjectPath;
1153 sProjectPath.Replace(_T('\\'), _T('/'));
1155 if (pszSubPath)
1157 CString sSubPath = pszSubPath;
1158 sSubPath.Replace(_T('\\'), _T('/'));
1160 cmd.Format(_T("tgit.exe statusex \"%s\" status %s \"%s\""), sProjectPath, sMode, sSubPath);
1162 else
1164 cmd.Format(_T("tgit.exe statusex \"%s\" status %s"), sProjectPath, sMode);
1167 //OutputDebugStringA("---");OutputDebugStringW(cmd);OutputDebugStringA("\r\n");
1169 W_GitCall.SetCmd(cmd);
1170 // NOTE: should igit get added as a part of msysgit then use below line instead of the above one
1171 //W_GitCall.SetCmd(CGit::ms_LastMsysGitDir + cmd);
1173 if ( Run(&W_GitCall) )
1174 return FALSE;
1176 return TRUE;
1179 BOOL CGit::CheckCleanWorkTree()
1181 CString out;
1182 CString cmd;
1183 cmd=_T("git.exe rev-parse --verify HEAD");
1185 if(g_Git.Run(cmd,&out,CP_UTF8))
1186 return FALSE;
1188 cmd=_T("git.exe update-index --ignore-submodules --refresh");
1189 if(g_Git.Run(cmd,&out,CP_UTF8))
1190 return FALSE;
1192 cmd=_T("git.exe diff-files --quiet --ignore-submodules");
1193 if(g_Git.Run(cmd,&out,CP_UTF8))
1194 return FALSE;
1196 cmd=_T("git diff-index --cached --quiet HEAD --ignore-submodules");
1197 if(g_Git.Run(cmd,&out,CP_UTF8))
1198 return FALSE;
1200 return TRUE;
1202 int CGit::Revert(CTGitPathList &list,bool keep)
1204 int ret;
1205 for(int i=0;i<list.GetCount();i++)
1207 ret = Revert((CTGitPath&)list[i],keep);
1208 if(ret)
1209 return ret;
1211 return 0;
1213 int CGit::Revert(CTGitPath &path,bool keep)
1215 CString cmd, out;
1216 if(path.m_Action & CTGitPath::LOGACTIONS_ADDED)
1217 { //To init git repository, there are not HEAD, so we can use git reset command
1218 cmd.Format(_T("git.exe rm --cached -- \"%s\""),path.GetGitPathString());
1220 if(g_Git.Run(cmd,&out,CP_ACP))
1221 return -1;
1223 else if(path.m_Action & CTGitPath::LOGACTIONS_REPLACED )
1225 cmd.Format(_T("git.exe mv -- \"%s\" \"%s\""),path.GetGitPathString(),path.GetGitOldPathString());
1226 if(g_Git.Run(cmd,&out,CP_ACP))
1227 return -1;
1229 cmd.Format(_T("git.exe checkout HEAD -f -- \"%s\""),path.GetGitOldPathString());
1230 if(g_Git.Run(cmd,&out,CP_ACP))
1231 return -1;
1233 else
1235 cmd.Format(_T("git.exe checkout HEAD -f -- \"%s\""),path.GetGitPathString());
1236 if(g_Git.Run(cmd,&out,CP_ACP))
1237 return -1;
1239 return 0;
1242 int CGit::ListConflictFile(CTGitPathList &list,CTGitPath *path)
1244 BYTE_VECTOR vector;
1246 CString cmd;
1247 if(path)
1248 cmd.Format(_T("git.exe ls-files -u -t -z -- \"%s\""),path->GetGitPathString());
1249 else
1250 cmd=_T("git.exe ls-files -u -t -z");
1252 if(g_Git.Run(cmd,&vector))
1254 return -1;
1257 list.ParserFromLsFile(vector);
1259 return 0;
1262 bool CGit::IsFastForward(CString &from, CString &to)
1264 CString base,hash;
1265 CString cmd;
1266 cmd.Format(_T("git.exe merge-base %s %s"), to,from);
1268 if(g_Git.Run(cmd,&base,CP_ACP))
1270 //CMessageBox::Show(NULL,base,_T("TortoiseGit"),MB_OK|MB_ICONERROR);
1271 return false;
1273 base=base.Left(40);
1275 hash=g_Git.GetHash(from);
1277 hash=hash.Left(40);
1279 return hash == base;
1282 unsigned int CGit::Hash2int(CString &hash)
1284 int ret=0;
1285 for(int i=0;i<8;i++)
1287 ret =ret <<4;
1288 if(hash[i]>=_T('a'))
1289 ret |= (hash[i]-_T('a')+10)&0xFF;
1290 else if(hash[i]>=_T('A'))
1291 ret |= (hash[i]-_T('A')+10)&0xFF;
1292 else
1293 ret |= (hash[i]-_T('0'))&0xFF;
1296 return ret;
1299 int CGit::RefreshGitIndex()
1301 CString cmd,output;
1302 cmd=_T("git.exe update-index --refresh");
1303 return Run(cmd,&output,CP_ACP);