BrowseRefs: Context menu enhancements
[TortoiseGit.git] / src / Git / Git.cpp
blob03a1cffde4e85830714d17099a3dd7717256c6ca
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"
11 static LPTSTR nextpath(LPCTSTR src, LPTSTR dst, UINT maxlen)
13 LPCTSTR orgsrc;
15 while (*src == _T(';'))
16 src++;
18 orgsrc = src;
20 if (!--maxlen)
21 goto nullterm;
23 while (*src && *src != _T(';'))
25 if (*src != _T('"'))
27 *dst++ = *src++;
28 if (!--maxlen)
30 orgsrc = src;
31 goto nullterm;
34 else
36 src++;
37 while (*src && *src != _T('"'))
39 *dst++ = *src++;
40 if (!--maxlen)
42 orgsrc = src;
43 goto nullterm;
47 if (*src)
48 src++;
52 while (*src == _T(';'))
53 src++;
55 nullterm:
57 *dst = 0;
59 return (orgsrc != src) ? (LPTSTR)src : NULL;
62 static inline BOOL FileExists(LPCTSTR lpszFileName)
64 struct _stat st;
65 return _tstat(lpszFileName, &st) == 0;
68 static BOOL FindGitPath()
70 size_t size;
71 _tgetenv_s(&size, NULL, 0, _T("PATH"));
73 if (!size)
75 return FALSE;
78 TCHAR *env = (TCHAR*)alloca(size * sizeof(TCHAR));
79 _tgetenv_s(&size, env, size, _T("PATH"));
81 TCHAR buf[_MAX_PATH];
83 // search in all paths defined in PATH
84 while ((env = nextpath(env, buf, _MAX_PATH-1)) && *buf)
86 TCHAR *pfin = buf + _tcslen(buf)-1;
88 // ensure trailing slash
89 if (*pfin != _T('/') && *pfin != _T('\\'))
90 _tcscpy(++pfin, _T("\\"));
92 const int len = _tcslen(buf);
94 if ((len + 7) < _MAX_PATH)
95 _tcscpy(pfin+1, _T("git.exe"));
96 else
97 break;
99 if ( FileExists(buf) )
101 // dir found
102 pfin[1] = 0;
103 CGit::ms_LastMsysGitDir = buf;
104 return TRUE;
108 return FALSE;
112 #define MAX_DIRBUFFER 1000
113 #define CALL_OUTPUT_READ_CHUNK_SIZE 1024
115 CString CGit::ms_LastMsysGitDir;
116 CGit g_Git;
118 // contains system environment that should be used by child processes (RunAsync)
119 // initialized by CheckMsysGitDir
120 static LPTSTR l_processEnv = NULL;
124 CGit::CGit(void)
126 GetCurrentDirectory(MAX_DIRBUFFER,m_CurrentDir.GetBuffer(MAX_DIRBUFFER));
127 m_CurrentDir.ReleaseBuffer();
129 CheckMsysGitDir();
132 CGit::~CGit(void)
136 static char g_Buffer[4096];
138 int CGit::RunAsync(CString cmd,PROCESS_INFORMATION *piOut,HANDLE *hReadOut,CString *StdioFile)
140 SECURITY_ATTRIBUTES sa;
141 HANDLE hRead, hWrite;
142 HANDLE hStdioFile = NULL;
144 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
145 sa.lpSecurityDescriptor=NULL;
146 sa.bInheritHandle=TRUE;
147 if(!CreatePipe(&hRead,&hWrite,&sa,0))
149 return GIT_ERROR_OPEN_PIP;
152 if(StdioFile)
154 hStdioFile=CreateFile(*StdioFile,GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,
155 &sa,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
158 STARTUPINFO si;
159 PROCESS_INFORMATION pi;
160 si.cb=sizeof(STARTUPINFO);
161 GetStartupInfo(&si);
163 si.hStdError=hWrite;
164 if(StdioFile)
165 si.hStdOutput=hStdioFile;
166 else
167 si.hStdOutput=hWrite;
169 si.wShowWindow=SW_HIDE;
170 si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
172 LPTSTR pEnv = l_processEnv;
173 DWORD dwFlags = pEnv ? CREATE_UNICODE_ENVIRONMENT : 0;
175 //DETACHED_PROCESS make ssh recognize that it has no console to launch askpass to input password.
176 dwFlags |= DETACHED_PROCESS;
178 if(!CreateProcess(NULL,(LPWSTR)cmd.GetString(), NULL,NULL,TRUE,dwFlags,pEnv,(LPWSTR)m_CurrentDir.GetString(),&si,&pi))
180 LPVOID lpMsgBuf;
181 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
182 NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
183 (LPTSTR)&lpMsgBuf,
184 0,NULL);
185 return GIT_ERROR_CREATE_PROCESS;
188 CloseHandle(hWrite);
189 if(piOut)
190 *piOut=pi;
191 if(hReadOut)
192 *hReadOut=hRead;
194 return 0;
197 //Must use sperate function to convert ANSI str to union code string
198 //Becuase A2W use stack as internal convert buffer.
199 void CGit::StringAppend(CString *str,BYTE *p,int code,int length)
201 //USES_CONVERSION;
202 //str->Append(A2W_CP((LPCSTR)p,code));
203 if(str == NULL)
204 return ;
206 WCHAR * buf;
208 int len ;
209 if(length<0)
210 len= strlen((const char*)p);
211 else
212 len=length;
213 //if (len==0)
214 // return ;
215 //buf = new WCHAR[len*4 + 1];
216 buf = str->GetBuffer(len*4+1+str->GetLength())+str->GetLength();
217 SecureZeroMemory(buf, (len*4 + 1)*sizeof(WCHAR));
218 MultiByteToWideChar(code, 0, (LPCSTR)p, len, buf, len*4);
219 str->ReleaseBuffer();
220 //str->Append(buf);
221 //delete buf;
223 BOOL CGit::IsInitRepos()
225 CString cmdout;
226 cmdout.Empty();
227 if(g_Git.Run(_T("git.exe rev-parse --revs-only HEAD"),&cmdout,CP_UTF8))
229 // CMessageBox::Show(NULL,cmdout,_T("TortoiseGit"),MB_OK);
230 return TRUE;
232 if(cmdout.IsEmpty())
233 return TRUE;
235 return FALSE;
237 int CGit::Run(CGitCall* pcall)
239 PROCESS_INFORMATION pi;
240 HANDLE hRead;
241 if(RunAsync(pcall->GetCmd(),&pi,&hRead))
242 return GIT_ERROR_CREATE_PROCESS;
244 DWORD readnumber;
245 BYTE data[CALL_OUTPUT_READ_CHUNK_SIZE];
246 bool bAborted=false;
247 while(ReadFile(hRead,data,CALL_OUTPUT_READ_CHUNK_SIZE,&readnumber,NULL))
249 //Todo: when OnOutputData() returns 'true', abort git-command. Send CTRL-C signal?
250 if(!bAborted)//For now, flush output when command aborted.
251 if(pcall->OnOutputData(data,readnumber))
252 bAborted=true;
254 if(!bAborted)
255 pcall->OnEnd();
258 CloseHandle(pi.hThread);
260 WaitForSingleObject(pi.hProcess, INFINITE);
261 DWORD exitcode =0;
263 if(!GetExitCodeProcess(pi.hProcess,&exitcode))
265 return GIT_ERROR_GET_EXIT_CODE;
268 CloseHandle(pi.hProcess);
270 CloseHandle(hRead);
271 return exitcode;
273 class CGitCall_ByteVector : public CGitCall
275 public:
276 CGitCall_ByteVector(CString cmd,BYTE_VECTOR* pvector):CGitCall(cmd),m_pvector(pvector){}
277 virtual bool OnOutputData(const BYTE* data, size_t size)
279 size_t oldsize=m_pvector->size();
280 m_pvector->resize(m_pvector->size()+size);
281 memcpy(&*(m_pvector->begin()+oldsize),data,size);
282 return false;
284 BYTE_VECTOR* m_pvector;
287 int CGit::Run(CString cmd,BYTE_VECTOR *vector)
289 CGitCall_ByteVector call(cmd,vector);
290 return Run(&call);
292 int CGit::Run(CString cmd, CString* output,int code)
294 BYTE_VECTOR vector;
295 int ret;
296 ret=Run(cmd,&vector);
298 vector.push_back(0);
300 StringAppend(output,&(vector[0]),code);
301 return ret;
304 CString CGit::GetUserName(void)
306 CString UserName;
307 Run(_T("git.exe config user.name"),&UserName,CP_UTF8);
308 int start = 0;
309 return UserName.Tokenize(_T("\n"),start);
311 CString CGit::GetUserEmail(void)
313 CString UserName;
314 Run(_T("git.exe config user.email"),&UserName,CP_UTF8);
315 int start = 0;
316 return UserName.Tokenize(_T("\n"),start);
319 CString CGit::GetCurrentBranch(void)
321 CString output;
322 //Run(_T("git.exe branch"),&branch);
324 int ret=g_Git.Run(_T("git.exe branch"),&output,CP_UTF8);
325 if(!ret)
327 int pos=0;
328 CString one;
329 while( pos>=0 )
331 //i++;
332 one=output.Tokenize(_T("\n"),pos);
333 //list.push_back(one.Right(one.GetLength()-2));
334 if(one[0] == _T('*'))
335 return one.Right(one.GetLength()-2);
338 return CString("");
341 int CGit::GetCurrentBranchFromFile(const CString &sProjectRoot, CString &sBranchOut)
343 // read current branch name like git-gui does, by parsing the .git/HEAD file directly
345 if ( sProjectRoot.IsEmpty() )
346 return -1;
348 CString sHeadFile = sProjectRoot + _T("\\") + g_GitAdminDir.GetAdminDirName() + _T("\\HEAD");
350 FILE *pFile;
351 _tfopen_s(&pFile, sHeadFile.GetString(), _T("r"));
353 if (!pFile)
355 return -1;
358 char s[256] = {0};
359 fgets(s, sizeof(s), pFile);
361 fclose(pFile);
363 const char *pfx = "ref: refs/heads/";
364 const int len = 16;//strlen(pfx)
366 if ( !strncmp(s, pfx, len) )
368 //# We're on a branch. It might not exist. But
369 //# HEAD looks good enough to be a branch.
370 sBranchOut = s + len;
371 sBranchOut.TrimRight(_T(" \r\n\t"));
373 if ( sBranchOut.IsEmpty() )
374 return -1;
376 else
378 //# Assume this is a detached head.
379 sBranchOut = "HEAD";
381 return 1;
384 return 0;
387 int CGit::BuildOutputFormat(CString &format,bool IsFull)
389 CString log;
390 log.Format(_T("#<%c>%%x00"),LOG_REV_ITEM_BEGIN);
391 format += log;
392 if(IsFull)
394 log.Format(_T("#<%c>%%an%%x00"),LOG_REV_AUTHOR_NAME);
395 format += log;
396 log.Format(_T("#<%c>%%ae%%x00"),LOG_REV_AUTHOR_EMAIL);
397 format += log;
398 log.Format(_T("#<%c>%%ai%%x00"),LOG_REV_AUTHOR_DATE);
399 format += log;
400 log.Format(_T("#<%c>%%cn%%x00"),LOG_REV_COMMIT_NAME);
401 format += log;
402 log.Format(_T("#<%c>%%ce%%x00"),LOG_REV_COMMIT_EMAIL);
403 format += log;
404 log.Format(_T("#<%c>%%ci%%x00"),LOG_REV_COMMIT_DATE);
405 format += log;
406 log.Format(_T("#<%c>%%b%%x00"),LOG_REV_COMMIT_BODY);
407 format += log;
410 log.Format(_T("#<%c>%%m%%H%%x00"),LOG_REV_COMMIT_HASH);
411 format += log;
412 log.Format(_T("#<%c>%%P%%x00"),LOG_REV_COMMIT_PARENT);
413 format += log;
414 log.Format(_T("#<%c>%%s%%x00"),LOG_REV_COMMIT_SUBJECT);
415 format += log;
417 if(IsFull)
419 log.Format(_T("#<%c>%%x00"),LOG_REV_COMMIT_FILE);
420 format += log;
422 return 0;
425 int CGit::GetLog(BYTE_VECTOR& logOut, CString &hash, CTGitPath *path ,int count,int mask,CString *from,CString *to)
427 CGitCall_ByteVector gitCall(CString(),&logOut);
428 return GetLog(&gitCall,hash,path,count,mask,from,to);
431 //int CGit::GetLog(CGitCall* pgitCall, CString &hash, CTGitPath *path ,int count,int mask)
432 int CGit::GetLog(CGitCall* pgitCall, CString &hash, CTGitPath *path, int count, int mask,CString *from,CString *to)
435 CString cmd;
436 CString log;
437 CString num;
438 CString since;
440 CString file;
442 if(path)
443 file.Format(_T(" -- \"%s\""),path->GetGitPathString());
445 if(count>0)
446 num.Format(_T("-n%d"),count);
448 CString param;
450 if(mask& LOG_INFO_STAT )
451 param += _T(" --numstat ");
452 if(mask& LOG_INFO_FILESTATE)
453 param += _T(" --raw ");
455 if(mask& LOG_INFO_FULLHISTORY)
456 param += _T(" --full-history ");
458 if(mask& LOG_INFO_BOUNDARY)
459 param += _T(" --left-right --boundary ");
461 if(mask& CGit::LOG_INFO_ALL_BRANCH)
462 param += _T(" --all ");
464 if(mask& CGit::LOG_INFO_DETECT_COPYRENAME)
465 param += _T(" -C ");
467 if(mask& CGit::LOG_INFO_DETECT_RENAME )
468 param += _T(" -M ");
470 if(mask& CGit::LOG_INFO_FIRST_PARENT )
471 param += _T(" --first-parent ");
473 if(mask& CGit::LOG_INFO_NO_MERGE )
474 param += _T(" --no-merges ");
476 if(mask& CGit::LOG_INFO_FOLLOW)
477 param += _T(" --follow ");
479 if(mask& CGit::LOG_INFO_SHOW_MERGEDFILE)
480 param += _T(" -c ");
482 if(from != NULL && to != NULL)
484 CString range;
485 range.Format(_T(" %s..%s "),*from,*to);
486 param += range;
488 param+=hash;
490 cmd.Format(_T("git.exe log %s -z --topo-order %s --parents --pretty=format:\""),
491 num,param);
493 BuildOutputFormat(log,!(mask&CGit::LOG_INFO_ONLY_HASH));
495 cmd += log;
496 cmd += CString(_T("\" "))+hash+file;
498 pgitCall->SetCmd(cmd);
500 return Run(pgitCall);
501 // return Run(cmd,&logOut);
504 #if 0
505 int CGit::GetShortLog(CString &logOut,CTGitPath * path, int count)
507 CString cmd;
508 CString log;
509 int n;
510 if(count<0)
511 n=100;
512 else
513 n=count;
514 cmd.Format(_T("git.exe log --left-right --boundary --topo-order -n%d --pretty=format:\""),n);
515 BuildOutputFormat(log,false);
516 cmd += log+_T("\"");
517 if (path)
518 cmd+= _T(" -- \"")+path->GetGitPathString()+_T("\"");
519 //cmd += CString(_T("\" HEAD~40..HEAD"));
520 return Run(cmd,&logOut);
522 #endif
524 #define BUFSIZE 512
525 void GetTempPath(CString &path)
527 TCHAR lpPathBuffer[BUFSIZE];
528 DWORD dwRetVal;
529 DWORD dwBufSize=BUFSIZE;
530 dwRetVal = GetTempPath(dwBufSize, // length of the buffer
531 lpPathBuffer); // buffer for path
532 if (dwRetVal > dwBufSize || (dwRetVal == 0))
534 path=_T("");
536 path.Format(_T("%s"),lpPathBuffer);
538 CString GetTempFile()
540 TCHAR lpPathBuffer[BUFSIZE];
541 DWORD dwRetVal;
542 DWORD dwBufSize=BUFSIZE;
543 TCHAR szTempName[BUFSIZE];
544 UINT uRetVal;
546 dwRetVal = GetTempPath(dwBufSize, // length of the buffer
547 lpPathBuffer); // buffer for path
548 if (dwRetVal > dwBufSize || (dwRetVal == 0))
550 return _T("");
552 // Create a temporary file.
553 uRetVal = GetTempFileName(lpPathBuffer, // directory for tmp files
554 TEXT("Patch"), // temp file name prefix
555 0, // create unique name
556 szTempName); // buffer for name
559 if (uRetVal == 0)
561 return _T("");
564 return CString(szTempName);
568 int CGit::RunLogFile(CString cmd,CString &filename)
570 STARTUPINFO si;
571 PROCESS_INFORMATION pi;
572 si.cb=sizeof(STARTUPINFO);
573 GetStartupInfo(&si);
575 SECURITY_ATTRIBUTES psa={sizeof(psa),NULL,TRUE};;
576 psa.bInheritHandle=TRUE;
578 HANDLE houtfile=CreateFile(filename,GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,
579 &psa,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
582 si.wShowWindow=SW_HIDE;
583 si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
584 si.hStdOutput = houtfile;
586 if(!CreateProcess(NULL,(LPWSTR)cmd.GetString(), NULL,NULL,TRUE,NULL,NULL,(LPWSTR)m_CurrentDir.GetString(),&si,&pi))
588 LPVOID lpMsgBuf;
589 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
590 NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
591 (LPTSTR)&lpMsgBuf,
592 0,NULL);
593 return GIT_ERROR_CREATE_PROCESS;
596 WaitForSingleObject(pi.hProcess,INFINITE);
598 CloseHandle(pi.hThread);
599 CloseHandle(pi.hProcess);
600 CloseHandle(houtfile);
601 return GIT_SUCCESS;
602 // return 0;
605 git_revnum_t CGit::GetHash(CString &friendname)
607 CString cmd;
608 CString out;
609 cmd.Format(_T("git.exe rev-parse %s" ),friendname);
610 Run(cmd,&out,CP_UTF8);
611 // int pos=out.ReverseFind(_T('\n'));
612 int pos=out.FindOneOf(_T("\r\n"));
613 if(pos>0)
614 return out.Left(pos);
615 return out;
618 int CGit::GetTagList(STRING_VECTOR &list)
620 int ret;
621 CString cmd,output;
622 cmd=_T("git.exe tag -l");
623 int i=0;
624 ret=g_Git.Run(cmd,&output,CP_UTF8);
625 if(!ret)
627 int pos=0;
628 CString one;
629 while( pos>=0 )
631 i++;
632 one=output.Tokenize(_T("\n"),pos);
633 list.push_back(one);
636 return ret;
639 int CGit::GetBranchList(STRING_VECTOR &list,int *current,BRANCH_TYPE type)
641 int ret;
642 CString cmd,output;
643 cmd=_T("git.exe branch");
645 if(type==(BRANCH_LOCAL|BRANCH_REMOTE))
646 cmd+=_T(" -a");
647 else if(type==BRANCH_REMOTE)
648 cmd+=_T(" -r");
650 int i=0;
651 ret=g_Git.Run(cmd,&output,CP_UTF8);
652 if(!ret)
654 int pos=0;
655 CString one;
656 while( pos>=0 )
658 one=output.Tokenize(_T("\n"),pos);
659 list.push_back(one.Right(one.GetLength()-2));
660 if(one[0] == _T('*'))
661 if(current)
662 *current=i;
663 i++;
666 return ret;
669 int CGit::GetRemoteList(STRING_VECTOR &list)
671 int ret;
672 CString cmd,output;
673 cmd=_T("git.exe config --get-regexp remote.*.url");
674 ret=g_Git.Run(cmd,&output,CP_UTF8);
675 if(!ret)
677 int pos=0;
678 CString one;
679 while( pos>=0 )
681 one=output.Tokenize(_T("\n"),pos);
682 int start=one.Find(_T("."),0);
683 if(start>0)
685 CString url;
686 url=one.Right(one.GetLength()-start-1);
687 one=url;
688 one=one.Left(one.Find(_T("."),0));
689 list.push_back(one);
693 return ret;
696 int CGit::GetRefList(STRING_VECTOR &list)
698 int ret;
699 CString cmd,output;
700 cmd=_T("git show-ref -d");
701 ret=g_Git.Run(cmd,&output,CP_UTF8);
702 if(!ret)
704 int pos=0;
705 CString one;
706 while( pos>=0 )
708 one=output.Tokenize(_T("\n"),pos);
709 int start=one.Find(_T(" "),0);
710 if(start>0)
712 CString name;
713 name=one.Right(one.GetLength()-start-1);
714 list.push_back(name);
718 return ret;
720 int CGit::GetMapHashToFriendName(MAP_HASH_NAME &map)
722 int ret;
723 CString cmd,output;
724 cmd=_T("git show-ref -d");
725 ret=g_Git.Run(cmd,&output,CP_UTF8);
726 if(!ret)
728 int pos=0;
729 CString one;
730 while( pos>=0 )
732 one=output.Tokenize(_T("\n"),pos);
733 int start=one.Find(_T(" "),0);
734 if(start>0)
736 CString name;
737 name=one.Right(one.GetLength()-start-1);
739 CString hash;
740 hash=one.Left(start);
742 map[hash].push_back(name);
746 return ret;
749 BOOL CGit::CheckMsysGitDir()
751 static BOOL bInitialized = FALSE;
753 if (bInitialized)
755 return TRUE;
758 TCHAR *oldpath,*home;
759 size_t size;
761 // set HOME if not set already
762 _tgetenv_s(&size, NULL, 0, _T("HOME"));
763 if (!size)
765 _tdupenv_s(&home,&size,_T("USERPROFILE"));
766 _tputenv_s(_T("HOME"),home);
767 free(home);
770 //setup ssh client
771 CString sshclient=CRegString(_T("Software\\TortoiseGit\\SSH"));
773 if(!sshclient.IsEmpty())
775 _tputenv_s(_T("GIT_SSH"),sshclient);
776 }else
778 TCHAR sPlink[MAX_PATH];
779 GetModuleFileName(NULL, sPlink, _countof(sPlink));
780 LPTSTR ptr = _tcsrchr(sPlink, _T('\\'));
781 if (ptr) {
782 _tcscpy(ptr + 1, _T("TortoisePlink.exe"));
783 _tputenv_s(_T("GIT_SSH"), sPlink);
788 TCHAR sAskPass[MAX_PATH];
789 GetModuleFileName(NULL, sAskPass, _countof(sAskPass));
790 LPTSTR ptr = _tcsrchr(sAskPass, _T('\\'));
791 if (ptr)
793 _tcscpy(ptr + 1, _T("SshAskPass.exe"));
794 _tputenv_s(_T("DISPLAY"),_T(":9999"));
795 _tputenv_s(_T("SSH_ASKPASS"),sAskPass);
798 // search PATH if git/bin directory is alredy present
799 if ( FindGitPath() )
801 bInitialized = TRUE;
802 return TRUE;
805 // add git/bin path to PATH
807 CRegString msysdir=CRegString(REG_MSYSGIT_PATH,_T(""),FALSE);
808 CString str=msysdir;
809 if(str.IsEmpty())
811 CRegString msysinstalldir=CRegString(REG_MSYSGIT_INSTALL,_T(""),FALSE,HKEY_LOCAL_MACHINE);
812 str=msysinstalldir;
813 if ( !str.IsEmpty() )
815 str += (str[str.GetLength()-1] != '\\') ? "\\bin" : "bin";
816 msysdir=str;
817 msysdir.write();
819 else
821 return false;
824 //CGit::m_MsysGitPath=str;
826 //set path
828 _tdupenv_s(&oldpath,&size,_T("PATH"));
830 CString path;
831 path.Format(_T("%s;%s"),oldpath,str);
833 _tputenv_s(_T("PATH"),path);
835 CString sOldPath = oldpath;
836 free(oldpath);
839 if( !FindGitPath() )
841 return false;
843 else
845 #ifdef _TORTOISESHELL
846 l_processEnv = GetEnvironmentStrings();
847 // updated environment is now duplicated for use in CreateProcess, restore original PATH for current process
848 _tputenv_s(_T("PATH"),sOldPath);
849 #endif
851 bInitialized = TRUE;
852 return true;
857 class CGitCall_EnumFiles : public CGitCall
859 public:
860 CGitCall_EnumFiles(const TCHAR *pszProjectPath, const TCHAR *pszSubPath, unsigned int nFlags, WGENUMFILECB *pEnumCb, void *pUserData)
861 : m_pszProjectPath(pszProjectPath),
862 m_pszSubPath(pszSubPath),
863 m_nFlags(nFlags),
864 m_pEnumCb(pEnumCb),
865 m_pUserData(pUserData)
869 typedef std::map<CStringA,char> TStrCharMap;
871 const TCHAR * m_pszProjectPath;
872 const TCHAR * m_pszSubPath;
873 unsigned int m_nFlags;
874 WGENUMFILECB * m_pEnumCb;
875 void * m_pUserData;
877 BYTE_VECTOR m_DataCollector;
879 virtual bool OnOutputData(const BYTE* data, size_t size)
881 m_DataCollector.append(data,size);
882 while(true)
884 // lines from igit.exe are 0 terminated
885 int found=m_DataCollector.findData((const BYTE*)"",1);
886 if(found<0)
887 return false;
888 OnSingleLine( (LPCSTR)&*m_DataCollector.begin() );
889 m_DataCollector.erase(m_DataCollector.begin(), m_DataCollector.begin()+found+1);
891 return false;//Should never reach this
893 virtual void OnEnd()
897 BYTE HexChar(char ch)
899 if (ch >= '0' && ch <= '9')
900 return (UINT)(ch - '0');
901 else if (ch >= 'A' && ch <= 'F')
902 return (UINT)(ch - 'A') + 10;
903 else if (ch >= 'a' && ch <= 'f')
904 return (UINT)(ch - 'a') + 10;
905 else
906 return 0;
909 bool OnSingleLine(LPCSTR line)
911 //Parse single line
913 wgFile_s fileStatus;
915 // file/dir type
917 fileStatus.nFlags = 0;
918 if (*line == 'D')
919 fileStatus.nFlags |= WGFF_Directory;
920 else if (*line != 'F')
921 // parse error
922 return false;
923 line += 2;
925 // status
927 fileStatus.nStatus = WGFS_Unknown;
928 switch (*line)
930 case 'N': fileStatus.nStatus = WGFS_Normal; break;
931 case 'M': fileStatus.nStatus = WGFS_Modified; break;
932 case 'S': fileStatus.nStatus = WGFS_Staged; break;
933 case 'A': fileStatus.nStatus = WGFS_Added; break;
934 case 'C': fileStatus.nStatus = WGFS_Conflicted; break;
935 case 'D': fileStatus.nStatus = WGFS_Deleted; break;
936 case 'I': fileStatus.nStatus = WGFS_Ignored; break;
937 case 'U': fileStatus.nStatus = WGFS_Unversioned; break;
938 case 'E': fileStatus.nStatus = WGFS_Empty; break;
939 case '?': fileStatus.nStatus = WGFS_Unknown; break;
940 default:
941 // parse error
942 return false;
944 line += 2;
946 // file sha1
948 BYTE sha1[20];
949 fileStatus.sha1 = NULL;
950 if ( !(fileStatus.nFlags & WGFF_Directory) )
952 for (int i=0; i<20; i++)
954 sha1[i] = (HexChar(line[0])<<4)&0xF0;
955 sha1[i] |= HexChar(line[1])&0xF;
957 line += 2;
960 line++;
963 // filename
964 int len = strlen(line);
965 if (len && len < 2048)
967 WCHAR *buf = (WCHAR*)alloca((len*4+2)*sizeof(WCHAR));
968 *buf = 0;
969 MultiByteToWideChar(CP_ACP, 0, line, len+1, buf, len*4+1);
970 fileStatus.sFileName = buf;
972 if (*buf && (*m_pEnumCb)(&fileStatus,m_pUserData))
973 return false;
976 return true;
980 BOOL CGit::EnumFiles(const TCHAR *pszProjectPath, const TCHAR *pszSubPath, unsigned int nFlags, WGENUMFILECB *pEnumCb, void *pUserData)
982 if(!pszProjectPath || *pszProjectPath=='\0')
983 return FALSE;
985 CGitCall_EnumFiles W_GitCall(pszProjectPath,pszSubPath,nFlags,pEnumCb,pUserData);
986 CString cmd;
988 /* char W_szToDir[MAX_PATH];
989 strncpy(W_szToDir,pszProjectPath,sizeof(W_szToDir)-1);
990 if(W_szToDir[strlen(W_szToDir)-1]!='\\')
991 strncat(W_szToDir,"\\",sizeof(W_szToDir)-1);
993 SetCurrentDirectoryA(W_szToDir);
994 GetCurrentDirectoryA(sizeof(W_szToDir)-1,W_szToDir);
996 SetCurrentDir(pszProjectPath);
998 CString sMode;
999 if (nFlags)
1001 if (nFlags & WGEFF_NoRecurse) sMode += _T("r");
1002 if (nFlags & WGEFF_FullPath) sMode += _T("f");
1003 if (nFlags & WGEFF_DirStatusDelta) sMode += _T("d");
1004 if (nFlags & WGEFF_DirStatusAll) sMode += _T("D");
1005 if (nFlags & WGEFF_EmptyAsNormal) sMode += _T("e");
1006 if (nFlags & WGEFF_SingleFile) sMode += _T("s");
1008 else
1010 sMode = _T("-");
1013 if (pszSubPath)
1014 cmd.Format(_T("igit.exe \"%s\" status %s \"%s\""), pszProjectPath, sMode, pszSubPath);
1015 else
1016 cmd.Format(_T("igit.exe \"%s\" status %s"), pszProjectPath, sMode);
1018 W_GitCall.SetCmd(cmd);
1019 // NOTE: should igit get added as a part of msysgit then use below line instead of the above one
1020 //W_GitCall.SetCmd(CGit::ms_LastMsysGitDir + cmd);
1022 if ( Run(&W_GitCall) )
1023 return FALSE;
1025 return TRUE;
1028 BOOL CGit::CheckCleanWorkTree()
1030 CString out;
1031 CString cmd;
1032 cmd=_T("git.exe rev-parse --verify HEAD");
1034 if(g_Git.Run(cmd,&out,CP_UTF8))
1035 return FALSE;
1037 cmd=_T("git.exe update-index --ignore-submodules --refresh");
1038 if(g_Git.Run(cmd,&out,CP_UTF8))
1039 return FALSE;
1041 cmd=_T("git.exe diff-files --quiet --ignore-submodules");
1042 if(g_Git.Run(cmd,&out,CP_UTF8))
1043 return FALSE;
1045 cmd=_T("git diff-index --cached --quiet HEAD --ignore-submodules");
1046 if(g_Git.Run(cmd,&out,CP_UTF8))
1047 return FALSE;
1049 return TRUE;
1051 int CGit::Revert(CTGitPathList &list,bool keep)
1053 int ret;
1054 for(int i=0;i<list.GetCount();i++)
1056 ret = Revert((CTGitPath&)list[i],keep);
1057 if(ret)
1058 return ret;
1060 return 0;
1062 int CGit::Revert(CTGitPath &path,bool keep)
1064 CString cmd, out;
1065 if(path.m_Action & CTGitPath::LOGACTIONS_ADDED)
1066 { //To init git repository, there are not HEAD, so we can use git reset command
1067 cmd.Format(_T("git.exe rm --cache -- \"%s\""),path.GetGitPathString());
1068 if(g_Git.Run(cmd,&out,CP_ACP))
1069 return -1;
1071 else if(path.m_Action & CTGitPath::LOGACTIONS_REPLACED )
1073 cmd.Format(_T("git.exe mv \"%s\" \"%s\""),path.GetGitPathString(),path.GetGitOldPathString());
1074 if(g_Git.Run(cmd,&out,CP_ACP))
1075 return -1;
1077 cmd.Format(_T("git.exe checkout HEAD -f -- \"%s\""),path.GetGitOldPathString());
1078 if(g_Git.Run(cmd,&out,CP_ACP))
1079 return -1;
1081 else
1083 cmd.Format(_T("git.exe checkout HEAD -f -- \"%s\""),path.GetGitPathString());
1084 if(g_Git.Run(cmd,&out,CP_ACP))
1085 return -1;
1087 return 0;
1090 int CGit::ListConflictFile(CTGitPathList &list,CTGitPath *path)
1092 BYTE_VECTOR vector;
1094 CString cmd;
1095 if(path)
1096 cmd.Format(_T("git.exe ls-files -u -t -z -- \"%s\""),path->GetGitPathString());
1097 else
1098 cmd=_T("git.exe ls-files -u -t -z");
1100 if(g_Git.Run(cmd,&vector))
1102 return -1;
1105 list.ParserFromLsFile(vector);
1107 return 0;