Fixed issue #385: Bug In properties->Git dialog
[TortoiseGit.git] / src / Git / Git.cpp
blob5820f5a0a362b38c2aba66ea290b08714cf490f9
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 this->m_bInitialized =false;
129 CheckMsysGitDir();
130 m_critGitDllSec.Init();
133 CGit::~CGit(void)
135 if(this->m_GitDiff)
137 git_close_diff(m_GitDiff);
138 m_GitDiff=0;
142 static char g_Buffer[4096];
144 int CGit::RunAsync(CString cmd,PROCESS_INFORMATION *piOut,HANDLE *hReadOut,CString *StdioFile)
146 SECURITY_ATTRIBUTES sa;
147 HANDLE hRead, hWrite;
148 HANDLE hStdioFile = NULL;
150 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
151 sa.lpSecurityDescriptor=NULL;
152 sa.bInheritHandle=TRUE;
153 if(!CreatePipe(&hRead,&hWrite,&sa,0))
155 return GIT_ERROR_OPEN_PIP;
158 if(StdioFile)
160 hStdioFile=CreateFile(*StdioFile,GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,
161 &sa,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
164 STARTUPINFO si;
165 PROCESS_INFORMATION pi;
166 si.cb=sizeof(STARTUPINFO);
167 GetStartupInfo(&si);
169 si.hStdError=hWrite;
170 if(StdioFile)
171 si.hStdOutput=hStdioFile;
172 else
173 si.hStdOutput=hWrite;
175 si.wShowWindow=SW_HIDE;
176 si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
178 LPTSTR pEnv = m_Environment.size()? &m_Environment[0]: NULL;
179 DWORD dwFlags = pEnv ? CREATE_UNICODE_ENVIRONMENT : 0;
181 //DETACHED_PROCESS make ssh recognize that it has no console to launch askpass to input password.
182 dwFlags |= DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP;
184 memset(&this->m_CurrentGitPi,0,sizeof(PROCESS_INFORMATION));
186 if(cmd.Find(_T("git") == 0))
187 cmd=CGit::ms_LastMsysGitDir+_T("\\")+cmd;
189 if(!CreateProcess(NULL,(LPWSTR)cmd.GetString(), NULL,NULL,TRUE,dwFlags,pEnv,(LPWSTR)m_CurrentDir.GetString(),&si,&pi))
191 LPVOID lpMsgBuf;
192 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
193 NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
194 (LPTSTR)&lpMsgBuf,
195 0,NULL);
196 return GIT_ERROR_CREATE_PROCESS;
199 m_CurrentGitPi = pi;
201 CloseHandle(hWrite);
202 if(piOut)
203 *piOut=pi;
204 if(hReadOut)
205 *hReadOut=hRead;
207 return 0;
210 //Must use sperate function to convert ANSI str to union code string
211 //Becuase A2W use stack as internal convert buffer.
212 void CGit::StringAppend(CString *str,BYTE *p,int code,int length)
214 //USES_CONVERSION;
215 //str->Append(A2W_CP((LPCSTR)p,code));
216 if(str == NULL)
217 return ;
219 WCHAR * buf;
221 int len ;
222 if(length<0)
223 len= strlen((const char*)p);
224 else
225 len=length;
226 //if (len==0)
227 // return ;
228 //buf = new WCHAR[len*4 + 1];
229 buf = str->GetBuffer(len*4+1+str->GetLength())+str->GetLength();
230 SecureZeroMemory(buf, (len*4 + 1)*sizeof(WCHAR));
231 MultiByteToWideChar(code, 0, (LPCSTR)p, len, buf, len*4);
232 str->ReleaseBuffer();
233 //str->Append(buf);
234 //delete buf;
236 BOOL CGit::IsInitRepos()
238 CString cmdout;
239 cmdout.Empty();
240 if(g_Git.Run(_T("git.exe rev-parse --revs-only HEAD"),&cmdout,CP_UTF8))
242 // CMessageBox::Show(NULL,cmdout,_T("TortoiseGit"),MB_OK);
243 return TRUE;
245 if(cmdout.IsEmpty())
246 return TRUE;
248 return FALSE;
250 int CGit::Run(CGitCall* pcall)
252 PROCESS_INFORMATION pi;
253 HANDLE hRead;
254 if(RunAsync(pcall->GetCmd(),&pi,&hRead))
255 return GIT_ERROR_CREATE_PROCESS;
257 DWORD readnumber;
258 BYTE data[CALL_OUTPUT_READ_CHUNK_SIZE];
259 bool bAborted=false;
260 while(ReadFile(hRead,data,CALL_OUTPUT_READ_CHUNK_SIZE,&readnumber,NULL))
262 //Todo: when OnOutputData() returns 'true', abort git-command. Send CTRL-C signal?
263 if(!bAborted)//For now, flush output when command aborted.
264 if(pcall->OnOutputData(data,readnumber))
265 bAborted=true;
267 if(!bAborted)
268 pcall->OnEnd();
271 CloseHandle(pi.hThread);
273 WaitForSingleObject(pi.hProcess, INFINITE);
274 DWORD exitcode =0;
276 if(!GetExitCodeProcess(pi.hProcess,&exitcode))
278 return GIT_ERROR_GET_EXIT_CODE;
281 CloseHandle(pi.hProcess);
283 CloseHandle(hRead);
284 return exitcode;
286 class CGitCall_ByteVector : public CGitCall
288 public:
289 CGitCall_ByteVector(CString cmd,BYTE_VECTOR* pvector):CGitCall(cmd),m_pvector(pvector){}
290 virtual bool OnOutputData(const BYTE* data, size_t size)
292 size_t oldsize=m_pvector->size();
293 m_pvector->resize(m_pvector->size()+size);
294 memcpy(&*(m_pvector->begin()+oldsize),data,size);
295 return false;
297 BYTE_VECTOR* m_pvector;
300 int CGit::Run(CString cmd,BYTE_VECTOR *vector)
302 CGitCall_ByteVector call(cmd,vector);
303 return Run(&call);
305 int CGit::Run(CString cmd, CString* output,int code)
307 BYTE_VECTOR vector;
308 int ret;
309 ret=Run(cmd,&vector);
311 vector.push_back(0);
313 StringAppend(output,&(vector[0]),code);
314 return ret;
317 CString CGit::GetUserName(void)
319 return GetConfigValue(L"user.name");
321 CString CGit::GetUserEmail(void)
323 return GetConfigValue(L"user.email");
326 CString CGit::GetConfigValue(CString name)
328 CString configValue;
329 CString cmd;
330 cmd.Format(L"git.exe config %s", name);
331 Run(cmd,&configValue,CP_UTF8);
332 int start = 0;
333 return configValue.Tokenize(_T("\n"),start);
337 CString CGit::GetCurrentBranch(void)
339 CString output;
340 //Run(_T("git.exe branch"),&branch);
342 int ret=g_Git.Run(_T("git.exe branch --no-color"),&output,CP_UTF8);
343 if(!ret)
345 int pos=0;
346 CString one;
347 while( pos>=0 )
349 //i++;
350 one=output.Tokenize(_T("\n"),pos);
351 //list.push_back(one.Right(one.GetLength()-2));
352 if(one[0] == _T('*'))
353 return one.Right(one.GetLength()-2);
356 return CString("");
359 CString CGit::GetSymbolicRef(const wchar_t* symbolicRefName, bool bStripRefsHeads)
361 CString refName;
362 CString cmd;
363 cmd.Format(L"git symbolic-ref %s", symbolicRefName);
364 if(Run(cmd, &refName, CP_UTF8) != 0)
365 return CString();//Error
366 int iStart = 0;
367 refName = refName.Tokenize(L"\n", iStart);
368 if(bStripRefsHeads)
369 refName = StripRefName(refName);
370 return refName;
373 CString CGit::GetFullRefName(CString shortRefName)
375 CString refName;
376 CString cmd;
377 cmd.Format(L"git rev-parse --symbolic-full-name %s", shortRefName);
378 if(Run(cmd, &refName, CP_UTF8) != 0)
379 return CString();//Error
380 int iStart = 0;
381 return refName.Tokenize(L"\n", iStart);
384 CString CGit::StripRefName(CString refName)
386 if(wcsncmp(refName, L"refs/heads/", 11) == 0)
387 refName = refName.Mid(11);
388 else if(wcsncmp(refName, L"refs/", 5) == 0)
389 refName = refName.Mid(5);
390 int start =0;
391 return refName.Tokenize(_T("\n"),start);
394 int CGit::GetCurrentBranchFromFile(const CString &sProjectRoot, CString &sBranchOut)
396 // read current branch name like git-gui does, by parsing the .git/HEAD file directly
398 if ( sProjectRoot.IsEmpty() )
399 return -1;
401 CString sHeadFile = sProjectRoot + _T("\\") + g_GitAdminDir.GetAdminDirName() + _T("\\HEAD");
403 FILE *pFile;
404 _tfopen_s(&pFile, sHeadFile.GetString(), _T("r"));
406 if (!pFile)
408 return -1;
411 char s[256] = {0};
412 fgets(s, sizeof(s), pFile);
414 fclose(pFile);
416 const char *pfx = "ref: refs/heads/";
417 const int len = 16;//strlen(pfx)
419 if ( !strncmp(s, pfx, len) )
421 //# We're on a branch. It might not exist. But
422 //# HEAD looks good enough to be a branch.
423 sBranchOut = s + len;
424 sBranchOut.TrimRight(_T(" \r\n\t"));
426 if ( sBranchOut.IsEmpty() )
427 return -1;
429 else
431 //# Assume this is a detached head.
432 sBranchOut = "HEAD";
434 return 1;
437 return 0;
440 int CGit::BuildOutputFormat(CString &format,bool IsFull)
442 CString log;
443 log.Format(_T("#<%c>%%x00"),LOG_REV_ITEM_BEGIN);
444 format += log;
445 if(IsFull)
447 log.Format(_T("#<%c>%%an%%x00"),LOG_REV_AUTHOR_NAME);
448 format += log;
449 log.Format(_T("#<%c>%%ae%%x00"),LOG_REV_AUTHOR_EMAIL);
450 format += log;
451 log.Format(_T("#<%c>%%ai%%x00"),LOG_REV_AUTHOR_DATE);
452 format += log;
453 log.Format(_T("#<%c>%%cn%%x00"),LOG_REV_COMMIT_NAME);
454 format += log;
455 log.Format(_T("#<%c>%%ce%%x00"),LOG_REV_COMMIT_EMAIL);
456 format += log;
457 log.Format(_T("#<%c>%%ci%%x00"),LOG_REV_COMMIT_DATE);
458 format += log;
459 log.Format(_T("#<%c>%%b%%x00"),LOG_REV_COMMIT_BODY);
460 format += log;
463 log.Format(_T("#<%c>%%m%%H%%x00"),LOG_REV_COMMIT_HASH);
464 format += log;
465 log.Format(_T("#<%c>%%P%%x00"),LOG_REV_COMMIT_PARENT);
466 format += log;
467 log.Format(_T("#<%c>%%s%%x00"),LOG_REV_COMMIT_SUBJECT);
468 format += log;
470 if(IsFull)
472 log.Format(_T("#<%c>%%x00"),LOG_REV_COMMIT_FILE);
473 format += log;
475 return 0;
478 int CGit::GetLog(BYTE_VECTOR& logOut, CString &hash, CTGitPath *path ,int count,int mask,CString *from,CString *to)
480 CGitCall_ByteVector gitCall(CString(),&logOut);
481 return GetLog(&gitCall,hash,path,count,mask,from,to);
484 CString CGit::GetLogCmd( CString &hash, CTGitPath *path, int count, int mask,CString *from,CString *to,bool paramonly)
486 CString cmd;
487 CString log;
488 CString num;
489 CString since;
491 CString file;
493 if(path)
494 file.Format(_T(" -- \"%s\""),path->GetGitPathString());
496 if(count>0)
497 num.Format(_T("-n%d"),count);
499 CString param;
501 if(mask& LOG_INFO_STAT )
502 param += _T(" --numstat ");
503 if(mask& LOG_INFO_FILESTATE)
504 param += _T(" --raw ");
506 if(mask& LOG_INFO_FULLHISTORY)
507 param += _T(" --full-history ");
509 if(mask& LOG_INFO_BOUNDARY)
510 param += _T(" --left-right --boundary ");
512 if(mask& CGit::LOG_INFO_ALL_BRANCH)
513 param += _T(" --all ");
515 if(mask& CGit::LOG_INFO_DETECT_COPYRENAME)
516 param += _T(" -C ");
518 if(mask& CGit::LOG_INFO_DETECT_RENAME )
519 param += _T(" -M ");
521 if(mask& CGit::LOG_INFO_FIRST_PARENT )
522 param += _T(" --first-parent ");
524 if(mask& CGit::LOG_INFO_NO_MERGE )
525 param += _T(" --no-merges ");
527 if(mask& CGit::LOG_INFO_FOLLOW)
528 param += _T(" --follow ");
530 if(mask& CGit::LOG_INFO_SHOW_MERGEDFILE)
531 param += _T(" -c ");
533 if(mask& CGit::LOG_INFO_FULL_DIFF)
534 param += _T(" --full-diff ");
536 if(from != NULL && to != NULL)
538 CString range;
539 range.Format(_T(" %s..%s "),*from,*to);
540 param += range;
542 param+=hash;
544 if(paramonly)
545 cmd.Format(_T("%s -z --topo-order %s --parents "),
546 num,param);
547 else
548 cmd.Format(_T("git.exe log %s -z --topo-order %s --parents --pretty=format:\""),
549 num,param);
551 BuildOutputFormat(log,!(mask&CGit::LOG_INFO_ONLY_HASH));
553 if(paramonly)
555 cmd += _T("-- ")+file;
556 }else
558 cmd += log;
559 cmd += CString(_T("\" -- "))+file;
562 return cmd;
564 //int CGit::GetLog(CGitCall* pgitCall, CString &hash, CTGitPath *path ,int count,int mask)
565 int CGit::GetLog(CGitCall* pgitCall, CString &hash, CTGitPath *path, int count, int mask,CString *from,CString *to)
567 pgitCall->SetCmd( GetLogCmd(hash,path,count,mask,from,to) );
569 return Run(pgitCall);
570 // return Run(cmd,&logOut);
573 #define BUFSIZE 512
574 void GetTempPath(CString &path)
576 TCHAR lpPathBuffer[BUFSIZE];
577 DWORD dwRetVal;
578 DWORD dwBufSize=BUFSIZE;
579 dwRetVal = GetTempPath(dwBufSize, // length of the buffer
580 lpPathBuffer); // buffer for path
581 if (dwRetVal > dwBufSize || (dwRetVal == 0))
583 path=_T("");
585 path.Format(_T("%s"),lpPathBuffer);
587 CString GetTempFile()
589 TCHAR lpPathBuffer[BUFSIZE];
590 DWORD dwRetVal;
591 DWORD dwBufSize=BUFSIZE;
592 TCHAR szTempName[BUFSIZE];
593 UINT uRetVal;
595 dwRetVal = GetTempPath(dwBufSize, // length of the buffer
596 lpPathBuffer); // buffer for path
597 if (dwRetVal > dwBufSize || (dwRetVal == 0))
599 return _T("");
601 // Create a temporary file.
602 uRetVal = GetTempFileName(lpPathBuffer, // directory for tmp files
603 TEXT("Patch"), // temp file name prefix
604 0, // create unique name
605 szTempName); // buffer for name
608 if (uRetVal == 0)
610 return _T("");
613 return CString(szTempName);
617 int CGit::RunLogFile(CString cmd,CString &filename)
619 STARTUPINFO si;
620 PROCESS_INFORMATION pi;
621 si.cb=sizeof(STARTUPINFO);
622 GetStartupInfo(&si);
624 SECURITY_ATTRIBUTES psa={sizeof(psa),NULL,TRUE};;
625 psa.bInheritHandle=TRUE;
627 HANDLE houtfile=CreateFile(filename,GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,
628 &psa,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
631 si.wShowWindow=SW_HIDE;
632 si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
633 si.hStdOutput = houtfile;
635 LPTSTR pEnv = m_Environment.size()? &m_Environment[0]: NULL;
636 DWORD dwFlags = pEnv ? CREATE_UNICODE_ENVIRONMENT : 0;
638 if(cmd.Find(_T("git") == 0))
639 cmd=CGit::ms_LastMsysGitDir+_T("\\")+cmd;
641 if(!CreateProcess(NULL,(LPWSTR)cmd.GetString(), NULL,NULL,TRUE,dwFlags,pEnv,(LPWSTR)m_CurrentDir.GetString(),&si,&pi))
643 LPVOID lpMsgBuf;
644 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
645 NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
646 (LPTSTR)&lpMsgBuf,
647 0,NULL);
648 return GIT_ERROR_CREATE_PROCESS;
651 WaitForSingleObject(pi.hProcess,INFINITE);
653 CloseHandle(pi.hThread);
654 CloseHandle(pi.hProcess);
655 CloseHandle(houtfile);
656 return GIT_SUCCESS;
657 // return 0;
660 git_revnum_t CGit::GetHash(const CString &friendname)
662 CString cmd;
663 CString out;
664 cmd.Format(_T("git.exe rev-parse %s" ),friendname);
665 Run(cmd,&out,CP_UTF8);
666 // int pos=out.ReverseFind(_T('\n'));
667 int pos=out.FindOneOf(_T("\r\n"));
668 if(pos>0)
669 return out.Left(pos);
670 return out;
673 int CGit::GetInitAddList(CTGitPathList &outputlist)
675 CString cmd;
676 BYTE_VECTOR cmdout;
678 cmd=_T("git.exe ls-files -s -t -z");
679 outputlist.Clear();
680 if(g_Git.Run(cmd,&cmdout))
681 return -1;
683 outputlist.ParserFromLsFile(cmdout);
684 for(int i=0;i<outputlist.GetCount();i++)
685 ((int)outputlist[i].m_Action) = CTGitPath::LOGACTIONS_ADDED;
687 return 0;
689 int CGit::GetCommitDiffList(CString &rev1,CString &rev2,CTGitPathList &outputlist)
691 CString cmd;
693 if(rev1 == GIT_REV_ZERO || rev2 == GIT_REV_ZERO)
695 //rev1=+_T("");
696 if(rev1 == GIT_REV_ZERO)
697 cmd.Format(_T("git.exe diff -r --raw -C -M --numstat -z %s"),rev2);
698 else
699 cmd.Format(_T("git.exe diff -r -R --raw -C -M --numstat -z %s"),rev1);
700 }else
702 cmd.Format(_T("git.exe diff-tree -r --raw -C -M --numstat -z %s %s"),rev2,rev1);
705 BYTE_VECTOR out;
706 if(g_Git.Run(cmd,&out))
707 return -1;
709 outputlist.ParserFromLog(out);
713 int CGit::GetTagList(STRING_VECTOR &list)
715 int ret;
716 CString cmd,output;
717 cmd=_T("git.exe tag -l");
718 int i=0;
719 ret=g_Git.Run(cmd,&output,CP_UTF8);
720 if(!ret)
722 int pos=0;
723 CString one;
724 while( pos>=0 )
726 i++;
727 one=output.Tokenize(_T("\n"),pos);
728 list.push_back(one);
731 return ret;
734 int CGit::GetBranchList(STRING_VECTOR &list,int *current,BRANCH_TYPE type)
736 int ret;
737 CString cmd,output;
738 cmd=_T("git.exe branch --no-color");
740 if(type==(BRANCH_LOCAL|BRANCH_REMOTE))
741 cmd+=_T(" -a");
742 else if(type==BRANCH_REMOTE)
743 cmd+=_T(" -r");
745 int i=0;
746 ret=g_Git.Run(cmd,&output,CP_UTF8);
747 if(!ret)
749 int pos=0;
750 CString one;
751 while( pos>=0 )
753 one=output.Tokenize(_T("\n"),pos);
754 one.Trim(L" \r\n\t");
755 if(one.Find(L" -> ") >= 0 || one.IsEmpty())
756 continue; // skip something like: refs/origin/HEAD -> refs/origin/master
757 if(one[0] == _T('*'))
759 if(current)
760 *current=i;
761 one = one.Mid(2);
763 list.push_back(one);
764 i++;
767 return ret;
770 int CGit::GetRemoteList(STRING_VECTOR &list)
772 int ret;
773 CString cmd,output;
774 cmd=_T("git.exe config --get-regexp \"^^remote[.].*[.]url\"");
775 ret=g_Git.Run(cmd,&output,CP_UTF8);
776 if(!ret)
778 int pos=0;
779 CString one;
780 while( pos>=0 )
782 one=output.Tokenize(_T("\n"),pos);
783 int start=one.Find(_T("."),0);
784 if(start>0)
786 CString url;
787 url=one.Right(one.GetLength()-start-1);
788 one=url;
789 one=one.Left(one.Find(_T("."),0));
790 list.push_back(one);
794 return ret;
797 int CGit::GetRefList(STRING_VECTOR &list)
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);
815 list.push_back(name);
819 return ret;
821 int CGit::GetMapHashToFriendName(MAP_HASH_NAME &map)
823 int ret;
824 CString cmd,output;
825 cmd=_T("git show-ref -d");
826 ret=g_Git.Run(cmd,&output,CP_UTF8);
827 if(!ret)
829 int pos=0;
830 CString one;
831 while( pos>=0 )
833 one=output.Tokenize(_T("\n"),pos);
834 int start=one.Find(_T(" "),0);
835 if(start>0)
837 CString name;
838 name=one.Right(one.GetLength()-start-1);
840 CString hash;
841 hash=one.Left(start);
843 map[hash].push_back(name);
847 return ret;
850 BOOL CGit::CheckMsysGitDir()
852 if (m_bInitialized)
854 return TRUE;
857 this->m_Environment.clear();
858 m_Environment.CopyProcessEnvironment();
860 TCHAR *oldpath,*home;
861 size_t homesize,size,httpsize;
863 // set HOME if not set already
864 _tgetenv_s(&homesize, NULL, 0, _T("HOME"));
865 if (!homesize)
867 _tdupenv_s(&home,&size,_T("USERPROFILE"));
868 m_Environment.SetEnv(_T("HOME"),home);
869 free(home);
871 CString str;
873 //set http_proxy
874 _tgetenv_s(&httpsize, NULL, 0, _T("http_proxy"));
875 if (!httpsize)
877 CString regServeraddress_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-host"), _T(""));
878 CString regServerport_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-port"), _T(""));
879 CString regUsername_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-username"), _T(""));
880 CString regPassword_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-password"), _T(""));
881 CString regTimeout_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-timeout"), _T(""));
882 CString regExceptions_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-exceptions"), _T(""));
884 CString http_proxy;
885 if(!regServeraddress_copy.IsEmpty())
887 if(regServeraddress_copy.Left(4) != _T("http"))
888 http_proxy=_T("http://");
890 if(!regUsername_copy.IsEmpty())
892 http_proxy += regUsername_copy;
893 http_proxy += _T(":")+regPassword_copy;
894 http_proxy += _T("@");
896 http_proxy+=regServeraddress_copy;
897 if(!regServerport_copy.IsEmpty())
899 http_proxy +=_T(":")+regServerport_copy;
901 m_Environment.SetEnv(_T("http_proxy"),(LPTSTR)http_proxy.GetBuffer());
904 //setup ssh client
905 CString sshclient=CRegString(_T("Software\\TortoiseGit\\SSH"));
907 if(!sshclient.IsEmpty())
909 m_Environment.SetEnv(_T("GIT_SSH"),sshclient.GetBuffer());
911 //Setup SVN_SSH
912 CString ssh=sshclient;
913 ssh.Replace(_T("/"),_T("\\"));
914 ssh.Replace(_T("\\"),_T("\\\\"));
915 ssh=CString(_T("\""))+ssh+_T('\"');
916 m_Environment.SetEnv(_T("SVN_SSH"),ssh.GetBuffer());
918 }else
920 TCHAR sPlink[MAX_PATH];
921 GetModuleFileName(NULL, sPlink, _countof(sPlink));
922 LPTSTR ptr = _tcsrchr(sPlink, _T('\\'));
923 if (ptr) {
924 _tcscpy(ptr + 1, _T("TortoisePlink.exe"));
925 m_Environment.SetEnv(_T("GIT_SSH"), sPlink);
927 //Setup SVN_SSH
928 CString ssh=sPlink;
929 ssh.Replace(_T("/"),_T("\\"));
930 ssh.Replace(_T("\\"),_T("\\\\"));
931 ssh=CString(_T("\""))+ssh+_T('\"');
932 m_Environment.SetEnv(_T("SVN_SSH"),ssh.GetBuffer());
937 TCHAR sAskPass[MAX_PATH];
938 GetModuleFileName(NULL, sAskPass, _countof(sAskPass));
939 LPTSTR ptr = _tcsrchr(sAskPass, _T('\\'));
940 if (ptr)
942 _tcscpy(ptr + 1, _T("SshAskPass.exe"));
943 m_Environment.SetEnv(_T("DISPLAY"),_T(":9999"));
944 m_Environment.SetEnv(_T("SSH_ASKPASS"),sAskPass);
948 // add git/bin path to PATH
950 CRegString msysdir=CRegString(REG_MSYSGIT_PATH,_T(""),FALSE);
951 str=msysdir;
952 if(str.IsEmpty())
954 CRegString msysinstalldir=CRegString(REG_MSYSGIT_INSTALL,_T(""),FALSE,HKEY_LOCAL_MACHINE);
955 str=msysinstalldir;
956 if ( !str.IsEmpty() )
958 str += (str[str.GetLength()-1] != '\\') ? "\\bin" : "bin";
959 msysdir=str;
960 CGit::ms_LastMsysGitDir = str;
961 msysdir.write();
963 else
965 // search PATH if git/bin directory is alredy present
966 if ( FindGitPath() )
968 m_bInitialized = TRUE;
969 return TRUE;
972 return false;
974 }else
976 CGit::ms_LastMsysGitDir = str;
978 //set path
980 _tdupenv_s(&oldpath,&size,_T("PATH"));
982 CString path;
983 path.Format(_T("%s;%s"),oldpath,str);
985 m_Environment.SetEnv(_T("PATH"),path.GetBuffer());
987 CString str1 = m_Environment.GetEnv(_T("PATH"));
989 CString sOldPath = oldpath;
990 free(oldpath);
992 m_bInitialized = TRUE;
993 return true;
998 class CGitCall_EnumFiles : public CGitCall
1000 public:
1001 CGitCall_EnumFiles(const TCHAR *pszProjectPath, const TCHAR *pszSubPath, unsigned int nFlags, WGENUMFILECB *pEnumCb, void *pUserData)
1002 : m_pszProjectPath(pszProjectPath),
1003 m_pszSubPath(pszSubPath),
1004 m_nFlags(nFlags),
1005 m_pEnumCb(pEnumCb),
1006 m_pUserData(pUserData)
1010 typedef std::map<CStringA,char> TStrCharMap;
1012 const TCHAR * m_pszProjectPath;
1013 const TCHAR * m_pszSubPath;
1014 unsigned int m_nFlags;
1015 WGENUMFILECB * m_pEnumCb;
1016 void * m_pUserData;
1018 BYTE_VECTOR m_DataCollector;
1020 virtual bool OnOutputData(const BYTE* data, size_t size)
1022 m_DataCollector.append(data,size);
1023 while(true)
1025 // lines from igit.exe are 0 terminated
1026 int found=m_DataCollector.findData((const BYTE*)"",1);
1027 if(found<0)
1028 return false;
1029 OnSingleLine( (LPCSTR)&*m_DataCollector.begin() );
1030 m_DataCollector.erase(m_DataCollector.begin(), m_DataCollector.begin()+found+1);
1032 return false;//Should never reach this
1034 virtual void OnEnd()
1038 BYTE HexChar(char ch)
1040 if (ch >= '0' && ch <= '9')
1041 return (UINT)(ch - '0');
1042 else if (ch >= 'A' && ch <= 'F')
1043 return (UINT)(ch - 'A') + 10;
1044 else if (ch >= 'a' && ch <= 'f')
1045 return (UINT)(ch - 'a') + 10;
1046 else
1047 return 0;
1050 bool OnSingleLine(LPCSTR line)
1052 //Parse single line
1054 wgFile_s fileStatus;
1056 // file/dir type
1058 fileStatus.nFlags = 0;
1059 if (*line == 'D')
1060 fileStatus.nFlags |= WGFF_Directory;
1061 else if (*line != 'F')
1062 // parse error
1063 return false;
1064 line += 2;
1066 // status
1068 fileStatus.nStatus = WGFS_Unknown;
1069 switch (*line)
1071 case 'N': fileStatus.nStatus = WGFS_Normal; break;
1072 case 'M': fileStatus.nStatus = WGFS_Modified; break;
1073 case 'S': fileStatus.nStatus = WGFS_Staged; break;
1074 case 'A': fileStatus.nStatus = WGFS_Added; break;
1075 case 'C': fileStatus.nStatus = WGFS_Conflicted; break;
1076 case 'D': fileStatus.nStatus = WGFS_Deleted; break;
1077 case 'I': fileStatus.nStatus = WGFS_Ignored; break;
1078 case 'U': fileStatus.nStatus = WGFS_Unversioned; break;
1079 case 'E': fileStatus.nStatus = WGFS_Empty; break;
1080 case '?': fileStatus.nStatus = WGFS_Unknown; break;
1081 default:
1082 // parse error
1083 return false;
1085 line += 2;
1087 // file sha1
1089 BYTE sha1[20];
1090 fileStatus.sha1 = NULL;
1091 if ( !(fileStatus.nFlags & WGFF_Directory) )
1093 for (int i=0; i<20; i++)
1095 sha1[i] = (HexChar(line[0])<<4)&0xF0;
1096 sha1[i] |= HexChar(line[1])&0xF;
1098 line += 2;
1101 line++;
1104 // filename
1105 int len = strlen(line);
1106 if (len && len < 2048)
1108 WCHAR *buf = (WCHAR*)alloca((len*4+2)*sizeof(WCHAR));
1109 *buf = 0;
1110 MultiByteToWideChar(CP_ACP, 0, line, len+1, buf, len*4+1);
1111 fileStatus.sFileName = buf;
1113 if (*buf && (*m_pEnumCb)(&fileStatus,m_pUserData))
1114 return false;
1117 return true;
1121 BOOL CGit::EnumFiles(const TCHAR *pszProjectPath, const TCHAR *pszSubPath, unsigned int nFlags, WGENUMFILECB *pEnumCb, void *pUserData)
1123 if(!pszProjectPath || *pszProjectPath=='\0')
1124 return FALSE;
1126 CGitCall_EnumFiles W_GitCall(pszProjectPath,pszSubPath,nFlags,pEnumCb,pUserData);
1127 CString cmd;
1129 /* char W_szToDir[MAX_PATH];
1130 strncpy(W_szToDir,pszProjectPath,sizeof(W_szToDir)-1);
1131 if(W_szToDir[strlen(W_szToDir)-1]!='\\')
1132 strncat(W_szToDir,"\\",sizeof(W_szToDir)-1);
1134 SetCurrentDirectoryA(W_szToDir);
1135 GetCurrentDirectoryA(sizeof(W_szToDir)-1,W_szToDir);
1137 SetCurrentDir(pszProjectPath);
1139 CString sMode;
1140 if (nFlags)
1142 if (nFlags & WGEFF_NoRecurse) sMode += _T("r");
1143 if (nFlags & WGEFF_FullPath) sMode += _T("f");
1144 if (nFlags & WGEFF_DirStatusDelta) sMode += _T("d");
1145 if (nFlags & WGEFF_DirStatusAll) sMode += _T("D");
1146 if (nFlags & WGEFF_EmptyAsNormal) sMode += _T("e");
1147 if (nFlags & WGEFF_SingleFile) sMode += _T("s");
1149 else
1151 sMode = _T("-");
1154 // NOTE: there seems to be some issue with msys based app receiving backslash on commandline, at least
1155 // if followed by " like for example 'igit "C:\"', the commandline igit receives is 'igit.exe C:" status' with
1156 // the 'C:" status' part as a single arg, Maybe it uses unix style processing. In order to avoid this just
1157 // use forward slashes for supplied project and sub paths
1159 CString sProjectPath = pszProjectPath;
1160 sProjectPath.Replace(_T('\\'), _T('/'));
1162 if (pszSubPath)
1164 CString sSubPath = pszSubPath;
1165 sSubPath.Replace(_T('\\'), _T('/'));
1167 cmd.Format(_T("tgit.exe statusex \"%s\" status %s \"%s\""), sProjectPath, sMode, sSubPath);
1169 else
1171 cmd.Format(_T("tgit.exe statusex \"%s\" status %s"), sProjectPath, sMode);
1174 //OutputDebugStringA("---");OutputDebugStringW(cmd);OutputDebugStringA("\r\n");
1176 W_GitCall.SetCmd(cmd);
1177 // NOTE: should igit get added as a part of msysgit then use below line instead of the above one
1178 //W_GitCall.SetCmd(CGit::ms_LastMsysGitDir + cmd);
1180 if ( Run(&W_GitCall) )
1181 return FALSE;
1183 return TRUE;
1186 BOOL CGit::CheckCleanWorkTree()
1188 CString out;
1189 CString cmd;
1190 cmd=_T("git.exe rev-parse --verify HEAD");
1192 if(g_Git.Run(cmd,&out,CP_UTF8))
1193 return FALSE;
1195 cmd=_T("git.exe update-index --ignore-submodules --refresh");
1196 if(g_Git.Run(cmd,&out,CP_UTF8))
1197 return FALSE;
1199 cmd=_T("git.exe diff-files --quiet --ignore-submodules");
1200 if(g_Git.Run(cmd,&out,CP_UTF8))
1201 return FALSE;
1203 cmd=_T("git diff-index --cached --quiet HEAD --ignore-submodules");
1204 if(g_Git.Run(cmd,&out,CP_UTF8))
1205 return FALSE;
1207 return TRUE;
1209 int CGit::Revert(CTGitPathList &list,bool keep)
1211 int ret;
1212 for(int i=0;i<list.GetCount();i++)
1214 ret = Revert((CTGitPath&)list[i],keep);
1215 if(ret)
1216 return ret;
1218 return 0;
1220 int CGit::Revert(CTGitPath &path,bool keep)
1222 CString cmd, out;
1223 if(path.m_Action & CTGitPath::LOGACTIONS_ADDED)
1224 { //To init git repository, there are not HEAD, so we can use git reset command
1225 cmd.Format(_T("git.exe rm --cached -- \"%s\""),path.GetGitPathString());
1227 if(g_Git.Run(cmd,&out,CP_ACP))
1228 return -1;
1230 else if(path.m_Action & CTGitPath::LOGACTIONS_REPLACED )
1232 cmd.Format(_T("git.exe mv -- \"%s\" \"%s\""),path.GetGitPathString(),path.GetGitOldPathString());
1233 if(g_Git.Run(cmd,&out,CP_ACP))
1234 return -1;
1236 cmd.Format(_T("git.exe checkout HEAD -f -- \"%s\""),path.GetGitOldPathString());
1237 if(g_Git.Run(cmd,&out,CP_ACP))
1238 return -1;
1240 else
1242 cmd.Format(_T("git.exe checkout HEAD -f -- \"%s\""),path.GetGitPathString());
1243 if(g_Git.Run(cmd,&out,CP_ACP))
1244 return -1;
1246 return 0;
1249 int CGit::ListConflictFile(CTGitPathList &list,CTGitPath *path)
1251 BYTE_VECTOR vector;
1253 CString cmd;
1254 if(path)
1255 cmd.Format(_T("git.exe ls-files -u -t -z -- \"%s\""),path->GetGitPathString());
1256 else
1257 cmd=_T("git.exe ls-files -u -t -z");
1259 if(g_Git.Run(cmd,&vector))
1261 return -1;
1264 list.ParserFromLsFile(vector);
1266 return 0;
1269 bool CGit::IsFastForward(CString &from, CString &to)
1271 CString base,hash;
1272 CString cmd;
1273 cmd.Format(_T("git.exe merge-base %s %s"), to,from);
1275 if(g_Git.Run(cmd,&base,CP_ACP))
1277 //CMessageBox::Show(NULL,base,_T("TortoiseGit"),MB_OK|MB_ICONERROR);
1278 return false;
1280 base=base.Left(40);
1282 hash=g_Git.GetHash(from);
1284 hash=hash.Left(40);
1286 return hash == base;
1289 unsigned int CGit::Hash2int(CString &hash)
1291 int ret=0;
1292 for(int i=0;i<8;i++)
1294 ret =ret <<4;
1295 if(hash[i]>=_T('a'))
1296 ret |= (hash[i]-_T('a')+10)&0xFF;
1297 else if(hash[i]>=_T('A'))
1298 ret |= (hash[i]-_T('A')+10)&0xFF;
1299 else
1300 ret |= (hash[i]-_T('0'))&0xFF;
1303 return ret;
1306 int CGit::RefreshGitIndex()
1308 CString cmd,output;
1309 cmd=_T("git.exe update-index --refresh");
1310 return Run(cmd,&output,CP_ACP);
1313 void CEnvironment::CopyProcessEnvironment()
1315 TCHAR *p = GetEnvironmentStrings();
1316 while(*p !=0 || *(p+1) !=0)
1317 this->push_back(*p++);
1319 push_back(_T('\0'));
1320 push_back(_T('\0'));
1323 CString CEnvironment::GetEnv(TCHAR *name)
1325 CString str;
1326 for(int i=0;i<size();i++)
1328 str = &(*this)[i];
1329 int start =0;
1330 CString sname = str.Tokenize(_T("="),start);
1331 if(sname.CompareNoCase(name) == 0)
1333 return &(*this)[i+start+1];
1335 i+=str.GetLength();
1337 return _T("");
1340 void CEnvironment::SetEnv(TCHAR *name, TCHAR* value)
1342 int i;
1343 for( i=0;i<size();i++)
1345 CString str = &(*this)[i];
1346 int start =0;
1347 CString sname = str.Tokenize(_T("="),start);
1348 if(sname.CompareNoCase(name) == 0)
1350 break;
1352 i+=str.GetLength();
1355 if(i == size())
1357 i -= 1; // roll back terminate \0\0
1358 this->push_back(_T('\0'));
1361 CEnvironment::iterator it;
1362 it=this->begin();
1363 it += i;
1365 while(*it && i<size())
1367 this->erase(it);
1368 it=this->begin();
1369 it += i;
1372 while(*name)
1374 this->insert(it,*name++);
1375 i++;
1376 it= begin()+i;
1379 this->insert(it, _T('='));
1380 i++;
1381 it= begin()+i;
1383 while(*value)
1385 this->insert(it,*value++);
1386 i++;
1387 it= begin()+i;