-#include "StdAfx.h"\r
-#include "Git.h"\r
-#include "atlconv.h"\r
-#include "GitRev.h"\r
-#include "registry.h"\r
-#include "GitConfig.h"\r
-#include <map>\r
-#include "UnicodeUtils.h"\r
-\r
-int CGit::m_LogEncode=CP_UTF8;\r
-\r
-static LPTSTR nextpath(LPCTSTR src, LPTSTR dst, UINT maxlen)\r
-{\r
- LPCTSTR orgsrc;\r
-\r
- while (*src == _T(';'))\r
- src++;\r
-\r
- orgsrc = src;\r
-\r
- if (!--maxlen)\r
- goto nullterm;\r
-\r
- while (*src && *src != _T(';'))\r
- {\r
- if (*src != _T('"'))\r
- {\r
- *dst++ = *src++;\r
- if (!--maxlen)\r
- {\r
- orgsrc = src;\r
- goto nullterm;\r
- }\r
- }\r
- else\r
- {\r
- src++;\r
- while (*src && *src != _T('"'))\r
- {\r
- *dst++ = *src++;\r
- if (!--maxlen)\r
- {\r
- orgsrc = src;\r
- goto nullterm;\r
- }\r
- }\r
-\r
- if (*src)\r
- src++;\r
- }\r
- }\r
-\r
- while (*src == _T(';'))\r
- src++;\r
-\r
-nullterm:\r
-\r
- *dst = 0;\r
-\r
- return (orgsrc != src) ? (LPTSTR)src : NULL;\r
-}\r
-\r
-static inline BOOL FileExists(LPCTSTR lpszFileName)\r
-{\r
- struct _stat st;\r
- return _tstat(lpszFileName, &st) == 0;\r
-}\r
-\r
-static BOOL FindGitPath()\r
-{\r
- size_t size;\r
- _tgetenv_s(&size, NULL, 0, _T("PATH"));\r
-\r
- if (!size)\r
- {\r
- return FALSE;\r
- }\r
-\r
- TCHAR *env = (TCHAR*)alloca(size * sizeof(TCHAR));\r
- _tgetenv_s(&size, env, size, _T("PATH"));\r
-\r
- TCHAR buf[_MAX_PATH];\r
-\r
- // search in all paths defined in PATH\r
- while ((env = nextpath(env, buf, _MAX_PATH-1)) && *buf)\r
- {\r
- TCHAR *pfin = buf + _tcslen(buf)-1;\r
-\r
- // ensure trailing slash\r
- if (*pfin != _T('/') && *pfin != _T('\\'))\r
- _tcscpy(++pfin, _T("\\"));\r
-\r
- const int len = _tcslen(buf);\r
-\r
- if ((len + 7) < _MAX_PATH)\r
- _tcscpy(pfin+1, _T("git.exe"));\r
- else\r
- break;\r
-\r
- if ( FileExists(buf) )\r
- {\r
- // dir found\r
- pfin[1] = 0;\r
- CGit::ms_LastMsysGitDir = buf;\r
- return TRUE;\r
- }\r
- }\r
-\r
- return FALSE;\r
-}\r
-\r
-\r
-#define MAX_DIRBUFFER 1000\r
-#define CALL_OUTPUT_READ_CHUNK_SIZE 1024\r
-\r
-CString CGit::ms_LastMsysGitDir;\r
-CGit g_Git;\r
-\r
-// contains system environment that should be used by child processes (RunAsync)\r
-// initialized by CheckMsysGitDir\r
-static LPTSTR l_processEnv = NULL;\r
-\r
-\r
-\r
-CGit::CGit(void)\r
-{\r
- GetCurrentDirectory(MAX_DIRBUFFER,m_CurrentDir.GetBuffer(MAX_DIRBUFFER));\r
- m_CurrentDir.ReleaseBuffer();\r
-\r
- CheckMsysGitDir();\r
-}\r
-\r
-CGit::~CGit(void)\r
-{\r
-}\r
-\r
-static char g_Buffer[4096];\r
-\r
-int CGit::RunAsync(CString cmd,PROCESS_INFORMATION *piOut,HANDLE *hReadOut,CString *StdioFile)\r
-{\r
- SECURITY_ATTRIBUTES sa;\r
- HANDLE hRead, hWrite;\r
- HANDLE hStdioFile = NULL;\r
-\r
- sa.nLength = sizeof(SECURITY_ATTRIBUTES);\r
- sa.lpSecurityDescriptor=NULL;\r
- sa.bInheritHandle=TRUE;\r
- if(!CreatePipe(&hRead,&hWrite,&sa,0))\r
- {\r
- return GIT_ERROR_OPEN_PIP;\r
- }\r
- \r
- if(StdioFile)\r
- {\r
- hStdioFile=CreateFile(*StdioFile,GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE, \r
- &sa,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); \r
- }\r
-\r
- STARTUPINFO si;\r
- PROCESS_INFORMATION pi;\r
- si.cb=sizeof(STARTUPINFO);\r
- GetStartupInfo(&si);\r
-\r
- si.hStdError=hWrite;\r
- if(StdioFile)\r
- si.hStdOutput=hStdioFile;\r
- else\r
- si.hStdOutput=hWrite;\r
-\r
- si.wShowWindow=SW_HIDE;\r
- si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;\r
-\r
- LPTSTR pEnv = l_processEnv;\r
- DWORD dwFlags = pEnv ? CREATE_UNICODE_ENVIRONMENT : 0;\r
- \r
- //DETACHED_PROCESS make ssh recognize that it has no console to launch askpass to input password. \r
- dwFlags |= DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP; \r
-\r
- memset(&this->m_CurrentGitPi,0,sizeof(PROCESS_INFORMATION));\r
-\r
- if(!CreateProcess(NULL,(LPWSTR)cmd.GetString(), NULL,NULL,TRUE,dwFlags,pEnv,(LPWSTR)m_CurrentDir.GetString(),&si,&pi))\r
- {\r
- LPVOID lpMsgBuf;\r
- FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,\r
- NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),\r
- (LPTSTR)&lpMsgBuf,\r
- 0,NULL);\r
- return GIT_ERROR_CREATE_PROCESS;\r
- }\r
- \r
- m_CurrentGitPi = pi;\r
- \r
- CloseHandle(hWrite);\r
- if(piOut)\r
- *piOut=pi;\r
- if(hReadOut)\r
- *hReadOut=hRead;\r
- \r
- return 0;\r
-\r
-}\r
-//Must use sperate function to convert ANSI str to union code string\r
-//Becuase A2W use stack as internal convert buffer. \r
-void CGit::StringAppend(CString *str,BYTE *p,int code,int length)\r
-{\r
- //USES_CONVERSION;\r
- //str->Append(A2W_CP((LPCSTR)p,code));\r
- if(str == NULL)\r
- return ;\r
-\r
- WCHAR * buf;\r
-\r
- int len ;\r
- if(length<0)\r
- len= strlen((const char*)p);\r
- else\r
- len=length;\r
- //if (len==0)\r
- // return ;\r
- //buf = new WCHAR[len*4 + 1];\r
- buf = str->GetBuffer(len*4+1+str->GetLength())+str->GetLength();\r
- SecureZeroMemory(buf, (len*4 + 1)*sizeof(WCHAR));\r
- MultiByteToWideChar(code, 0, (LPCSTR)p, len, buf, len*4);\r
- str->ReleaseBuffer();\r
- //str->Append(buf);\r
- //delete buf;\r
-} \r
-BOOL CGit::IsInitRepos()\r
-{\r
- CString cmdout;\r
- cmdout.Empty();\r
- if(g_Git.Run(_T("git.exe rev-parse --revs-only HEAD"),&cmdout,CP_UTF8))\r
- {\r
- // CMessageBox::Show(NULL,cmdout,_T("TortoiseGit"),MB_OK);\r
- return TRUE;\r
- }\r
- if(cmdout.IsEmpty())\r
- return TRUE;\r
-\r
- return FALSE;\r
-}\r
-int CGit::Run(CGitCall* pcall)\r
-{\r
- PROCESS_INFORMATION pi;\r
- HANDLE hRead;\r
- if(RunAsync(pcall->GetCmd(),&pi,&hRead))\r
- return GIT_ERROR_CREATE_PROCESS;\r
-\r
- DWORD readnumber;\r
- BYTE data[CALL_OUTPUT_READ_CHUNK_SIZE];\r
- bool bAborted=false;\r
- while(ReadFile(hRead,data,CALL_OUTPUT_READ_CHUNK_SIZE,&readnumber,NULL))\r
- {\r
- //Todo: when OnOutputData() returns 'true', abort git-command. Send CTRL-C signal?\r
- if(!bAborted)//For now, flush output when command aborted.\r
- if(pcall->OnOutputData(data,readnumber))\r
- bAborted=true;\r
- }\r
- if(!bAborted)\r
- pcall->OnEnd();\r
-\r
- \r
- CloseHandle(pi.hThread);\r
-\r
- WaitForSingleObject(pi.hProcess, INFINITE);\r
- DWORD exitcode =0;\r
-\r
- if(!GetExitCodeProcess(pi.hProcess,&exitcode))\r
- {\r
- return GIT_ERROR_GET_EXIT_CODE;\r
- }\r
-\r
- CloseHandle(pi.hProcess);\r
-\r
- CloseHandle(hRead);\r
- return exitcode;\r
-}\r
-class CGitCall_ByteVector : public CGitCall\r
-{\r
-public:\r
- CGitCall_ByteVector(CString cmd,BYTE_VECTOR* pvector):CGitCall(cmd),m_pvector(pvector){}\r
- virtual bool OnOutputData(const BYTE* data, size_t size)\r
- {\r
- size_t oldsize=m_pvector->size();\r
- m_pvector->resize(m_pvector->size()+size);\r
- memcpy(&*(m_pvector->begin()+oldsize),data,size);\r
- return false;\r
- }\r
- BYTE_VECTOR* m_pvector;\r
-\r
-};\r
-int CGit::Run(CString cmd,BYTE_VECTOR *vector)\r
-{\r
- CGitCall_ByteVector call(cmd,vector);\r
- return Run(&call);\r
-}\r
-int CGit::Run(CString cmd, CString* output,int code)\r
-{\r
- BYTE_VECTOR vector;\r
- int ret;\r
- ret=Run(cmd,&vector);\r
-\r
- vector.push_back(0);\r
- \r
- StringAppend(output,&(vector[0]),code);\r
- return ret;\r
-}\r
-\r
-CString CGit::GetUserName(void)\r
-{\r
- return GetConfigValue(L"user.name");\r
-}\r
-CString CGit::GetUserEmail(void)\r
-{\r
- return GetConfigValue(L"user.email");\r
-}\r
-\r
-CString CGit::GetConfigValue(CString name)\r
-{\r
- CString configValue;\r
- CString cmd;\r
- cmd.Format(L"git.exe config %s", name);\r
- Run(cmd,&configValue,CP_UTF8);\r
- int start = 0;\r
- return configValue.Tokenize(_T("\n"),start);\r
-}\r
-\r
-\r
-CString CGit::GetCurrentBranch(void)\r
-{\r
- CString output;\r
- //Run(_T("git.exe branch"),&branch);\r
-\r
- int ret=g_Git.Run(_T("git.exe branch"),&output,CP_UTF8);\r
- if(!ret)\r
- { \r
- int pos=0;\r
- CString one;\r
- while( pos>=0 )\r
- {\r
- //i++;\r
- one=output.Tokenize(_T("\n"),pos);\r
- //list.push_back(one.Right(one.GetLength()-2));\r
- if(one[0] == _T('*'))\r
- return one.Right(one.GetLength()-2);\r
- }\r
- }\r
- return CString("");\r
-}\r
-\r
-CString CGit::GetSymbolicRef(const wchar_t* symbolicRefName, bool bStripRefsHeads)\r
-{\r
- CString refName;\r
- CString cmd;\r
- cmd.Format(L"git symbolic-ref %s", symbolicRefName);\r
- if(Run(cmd, &refName, CP_UTF8) != 0)\r
- return CString();//Error\r
- int iStart = 0;\r
- refName = refName.Tokenize(L"\n", iStart);\r
- if(bStripRefsHeads)\r
- refName = StripRefName(refName);\r
- return refName;\r
-}\r
-\r
-CString CGit::GetFullRefName(CString shortRefName)\r
-{\r
- CString refName;\r
- CString cmd;\r
- cmd.Format(L"git rev-parse --symbolic-full-name %s", shortRefName);\r
- if(Run(cmd, &refName, CP_UTF8) != 0)\r
- return CString();//Error\r
- int iStart = 0;\r
- return refName.Tokenize(L"\n", iStart);\r
-}\r
-\r
-CString CGit::StripRefName(CString refName)\r
-{\r
- if(wcsncmp(refName, L"refs/heads/", 11) == 0)\r
- refName = refName.Mid(11);\r
- else if(wcsncmp(refName, L"refs/", 5) == 0)\r
- refName = refName.Mid(5);\r
- return refName;\r
-}\r
-\r
-int CGit::GetCurrentBranchFromFile(const CString &sProjectRoot, CString &sBranchOut)\r
-{\r
- // read current branch name like git-gui does, by parsing the .git/HEAD file directly\r
-\r
- if ( sProjectRoot.IsEmpty() )\r
- return -1;\r
-\r
- CString sHeadFile = sProjectRoot + _T("\\") + g_GitAdminDir.GetAdminDirName() + _T("\\HEAD");\r
-\r
- FILE *pFile;\r
- _tfopen_s(&pFile, sHeadFile.GetString(), _T("r"));\r
-\r
- if (!pFile)\r
- {\r
- return -1;\r
- }\r
-\r
- char s[256] = {0};\r
- fgets(s, sizeof(s), pFile);\r
-\r
- fclose(pFile);\r
-\r
- const char *pfx = "ref: refs/heads/";\r
- const int len = 16;//strlen(pfx)\r
-\r
- if ( !strncmp(s, pfx, len) )\r
- {\r
- //# We're on a branch. It might not exist. But\r
- //# HEAD looks good enough to be a branch.\r
- sBranchOut = s + len;\r
- sBranchOut.TrimRight(_T(" \r\n\t"));\r
-\r
- if ( sBranchOut.IsEmpty() )\r
- return -1;\r
- }\r
- else\r
- {\r
- //# Assume this is a detached head.\r
- sBranchOut = "HEAD";\r
-\r
- return 1;\r
- }\r
-\r
- return 0;\r
-}\r
-\r
-int CGit::BuildOutputFormat(CString &format,bool IsFull)\r
-{\r
- CString log;\r
- log.Format(_T("#<%c>%%x00"),LOG_REV_ITEM_BEGIN);\r
- format += log;\r
- if(IsFull)\r
- {\r
- log.Format(_T("#<%c>%%an%%x00"),LOG_REV_AUTHOR_NAME);\r
- format += log;\r
- log.Format(_T("#<%c>%%ae%%x00"),LOG_REV_AUTHOR_EMAIL);\r
- format += log;\r
- log.Format(_T("#<%c>%%ai%%x00"),LOG_REV_AUTHOR_DATE);\r
- format += log;\r
- log.Format(_T("#<%c>%%cn%%x00"),LOG_REV_COMMIT_NAME);\r
- format += log;\r
- log.Format(_T("#<%c>%%ce%%x00"),LOG_REV_COMMIT_EMAIL);\r
- format += log;\r
- log.Format(_T("#<%c>%%ci%%x00"),LOG_REV_COMMIT_DATE);\r
- format += log;\r
- log.Format(_T("#<%c>%%b%%x00"),LOG_REV_COMMIT_BODY);\r
- format += log;\r
- }\r
- \r
- log.Format(_T("#<%c>%%m%%H%%x00"),LOG_REV_COMMIT_HASH);\r
- format += log;\r
- log.Format(_T("#<%c>%%P%%x00"),LOG_REV_COMMIT_PARENT);\r
- format += log;\r
- log.Format(_T("#<%c>%%s%%x00"),LOG_REV_COMMIT_SUBJECT);\r
- format += log;\r
-\r
- if(IsFull)\r
- {\r
- log.Format(_T("#<%c>%%x00"),LOG_REV_COMMIT_FILE);\r
- format += log;\r
- }\r
- return 0;\r
-}\r
-\r
-int CGit::GetLog(BYTE_VECTOR& logOut, CString &hash, CTGitPath *path ,int count,int mask,CString *from,CString *to)\r
-{\r
- CGitCall_ByteVector gitCall(CString(),&logOut);\r
- return GetLog(&gitCall,hash,path,count,mask,from,to);\r
-}\r
-\r
-//int CGit::GetLog(CGitCall* pgitCall, CString &hash, CTGitPath *path ,int count,int mask)\r
-int CGit::GetLog(CGitCall* pgitCall, CString &hash, CTGitPath *path, int count, int mask,CString *from,CString *to)\r
-{\r
-\r
- CString cmd;\r
- CString log;\r
- CString num;\r
- CString since;\r
-\r
- CString file;\r
-\r
- if(path)\r
- file.Format(_T(" -- \"%s\""),path->GetGitPathString());\r
- \r
- if(count>0)\r
- num.Format(_T("-n%d"),count);\r
-\r
- CString param;\r
-\r
- if(mask& LOG_INFO_STAT )\r
- param += _T(" --numstat ");\r
- if(mask& LOG_INFO_FILESTATE)\r
- param += _T(" --raw ");\r
-\r
- if(mask& LOG_INFO_FULLHISTORY)\r
- param += _T(" --full-history ");\r
-\r
- if(mask& LOG_INFO_BOUNDARY)\r
- param += _T(" --left-right --boundary ");\r
-\r
- if(mask& CGit::LOG_INFO_ALL_BRANCH)\r
- param += _T(" --all ");\r
-\r
- if(mask& CGit::LOG_INFO_DETECT_COPYRENAME)\r
- param += _T(" -C ");\r
- \r
- if(mask& CGit::LOG_INFO_DETECT_RENAME )\r
- param += _T(" -M ");\r
-\r
- if(mask& CGit::LOG_INFO_FIRST_PARENT )\r
- param += _T(" --first-parent ");\r
- \r
- if(mask& CGit::LOG_INFO_NO_MERGE )\r
- param += _T(" --no-merges ");\r
-\r
- if(mask& CGit::LOG_INFO_FOLLOW)\r
- param += _T(" --follow ");\r
-\r
- if(mask& CGit::LOG_INFO_SHOW_MERGEDFILE)\r
- param += _T(" -c ");\r
-\r
- if(from != NULL && to != NULL)\r
- {\r
- CString range;\r
- range.Format(_T(" %s..%s "),*from,*to);\r
- param += range;\r
- }\r
- param+=hash;\r
-\r
- cmd.Format(_T("git.exe log %s -z --topo-order %s --parents --pretty=format:\""),\r
- num,param);\r
-\r
- BuildOutputFormat(log,!(mask&CGit::LOG_INFO_ONLY_HASH));\r
-\r
- cmd += log;\r
- cmd += CString(_T("\" "))+hash+file;\r
-\r
- pgitCall->SetCmd(cmd);\r
-\r
- return Run(pgitCall);\r
-// return Run(cmd,&logOut);\r
-}\r
-\r
-#if 0\r
-int CGit::GetShortLog(CString &logOut,CTGitPath * path, int count)\r
-{\r
- CString cmd;\r
- CString log;\r
- int n;\r
- if(count<0)\r
- n=100;\r
- else\r
- n=count;\r
- cmd.Format(_T("git.exe log --left-right --boundary --topo-order -n%d --pretty=format:\""),n);\r
- BuildOutputFormat(log,false);\r
- cmd += log+_T("\"");\r
- if (path)\r
- cmd+= _T(" -- \"")+path->GetGitPathString()+_T("\"");\r
- //cmd += CString(_T("\" HEAD~40..HEAD"));\r
- return Run(cmd,&logOut);\r
-}\r
-#endif\r
-\r
-#define BUFSIZE 512\r
-void GetTempPath(CString &path)\r
-{\r
- TCHAR lpPathBuffer[BUFSIZE];\r
- DWORD dwRetVal;\r
- DWORD dwBufSize=BUFSIZE;\r
- dwRetVal = GetTempPath(dwBufSize, // length of the buffer\r
- lpPathBuffer); // buffer for path \r
- if (dwRetVal > dwBufSize || (dwRetVal == 0))\r
- {\r
- path=_T("");\r
- }\r
- path.Format(_T("%s"),lpPathBuffer);\r
-}\r
-CString GetTempFile()\r
-{\r
- TCHAR lpPathBuffer[BUFSIZE];\r
- DWORD dwRetVal;\r
- DWORD dwBufSize=BUFSIZE;\r
- TCHAR szTempName[BUFSIZE]; \r
- UINT uRetVal;\r
-\r
- dwRetVal = GetTempPath(dwBufSize, // length of the buffer\r
- lpPathBuffer); // buffer for path \r
- if (dwRetVal > dwBufSize || (dwRetVal == 0))\r
- {\r
- return _T("");\r
- }\r
- // Create a temporary file. \r
- uRetVal = GetTempFileName(lpPathBuffer, // directory for tmp files\r
- TEXT("Patch"), // temp file name prefix \r
- 0, // create unique name \r
- szTempName); // buffer for name \r
-\r
-\r
- if (uRetVal == 0)\r
- {\r
- return _T("");\r
- }\r
-\r
- return CString(szTempName);\r
-\r
-}\r
-\r
-int CGit::RunLogFile(CString cmd,CString &filename)\r
-{\r
- STARTUPINFO si;\r
- PROCESS_INFORMATION pi;\r
- si.cb=sizeof(STARTUPINFO);\r
- GetStartupInfo(&si);\r
-\r
- SECURITY_ATTRIBUTES psa={sizeof(psa),NULL,TRUE};; \r
- psa.bInheritHandle=TRUE; \r
- \r
- HANDLE houtfile=CreateFile(filename,GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE, \r
- &psa,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); \r
-\r
-\r
- si.wShowWindow=SW_HIDE;\r
- si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;\r
- si.hStdOutput = houtfile; \r
- \r
- if(!CreateProcess(NULL,(LPWSTR)cmd.GetString(), NULL,NULL,TRUE,NULL,NULL,(LPWSTR)m_CurrentDir.GetString(),&si,&pi))\r
- {\r
- LPVOID lpMsgBuf;\r
- FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,\r
- NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),\r
- (LPTSTR)&lpMsgBuf,\r
- 0,NULL);\r
- return GIT_ERROR_CREATE_PROCESS;\r
- }\r
- \r
- WaitForSingleObject(pi.hProcess,INFINITE); \r
- \r
- CloseHandle(pi.hThread);\r
- CloseHandle(pi.hProcess);\r
- CloseHandle(houtfile);\r
- return GIT_SUCCESS;\r
-// return 0;\r
-}\r
-\r
-git_revnum_t CGit::GetHash(const CString &friendname)\r
-{\r
- CString cmd;\r
- CString out;\r
- cmd.Format(_T("git.exe rev-parse %s" ),friendname);\r
- Run(cmd,&out,CP_UTF8);\r
-// int pos=out.ReverseFind(_T('\n'));\r
- int pos=out.FindOneOf(_T("\r\n"));\r
- if(pos>0)\r
- return out.Left(pos);\r
- return out;\r
-}\r
-\r
-int CGit::GetCommitDiffList(CString &rev1,CString &rev2,CTGitPathList &outputlist)\r
-{\r
- CString cmd;\r
- \r
- if(rev1 == GIT_REV_ZERO || rev2 == GIT_REV_ZERO)\r
- {\r
- //rev1=+_T("");\r
- if(rev1 == GIT_REV_ZERO)\r
- cmd.Format(_T("git.exe diff -r --raw -C -M --numstat -z %s"),rev2);\r
- else\r
- cmd.Format(_T("git.exe diff -r -R --raw -C -M --numstat -z %s"),rev1);\r
- }else\r
- {\r
- cmd.Format(_T("git.exe diff-tree -r --raw -C -M --numstat -z %s %s"),rev2,rev1);\r
- }\r
-\r
- BYTE_VECTOR out;\r
- if(g_Git.Run(cmd,&out))\r
- return -1;\r
-\r
- outputlist.ParserFromLog(out);\r
-\r
-}\r
-\r
-int CGit::GetTagList(STRING_VECTOR &list)\r
-{\r
- int ret;\r
- CString cmd,output;\r
- cmd=_T("git.exe tag -l");\r
- int i=0;\r
- ret=g_Git.Run(cmd,&output,CP_UTF8);\r
- if(!ret)\r
- { \r
- int pos=0;\r
- CString one;\r
- while( pos>=0 )\r
- {\r
- i++;\r
- one=output.Tokenize(_T("\n"),pos);\r
- list.push_back(one);\r
- }\r
- }\r
- return ret;\r
-}\r
-\r
-int CGit::GetBranchList(STRING_VECTOR &list,int *current,BRANCH_TYPE type)\r
-{\r
- int ret;\r
- CString cmd,output;\r
- cmd=_T("git.exe branch");\r
-\r
- if(type==(BRANCH_LOCAL|BRANCH_REMOTE))\r
- cmd+=_T(" -a");\r
- else if(type==BRANCH_REMOTE)\r
- cmd+=_T(" -r");\r
-\r
- int i=0;\r
- ret=g_Git.Run(cmd,&output,CP_UTF8);\r
- if(!ret)\r
- { \r
- int pos=0;\r
- CString one;\r
- while( pos>=0 )\r
- {\r
- one=output.Tokenize(_T("\n"),pos);\r
- one.Trim(L" \r\n\t");\r
- if(one.Find(L" -> ") >= 0 || one.IsEmpty())\r
- continue; // skip something like: refs/origin/HEAD -> refs/origin/master\r
- if(one[0] == _T('*'))\r
- {\r
- if(current)\r
- *current=i;\r
- one = one.Mid(2);\r
- }\r
- list.push_back(one);\r
- i++;\r
- }\r
- }\r
- return ret;\r
-}\r
-\r
-int CGit::GetRemoteList(STRING_VECTOR &list)\r
-{\r
- int ret;\r
- CString cmd,output;\r
- cmd=_T("git.exe config --get-regexp remote.*.url");\r
- ret=g_Git.Run(cmd,&output,CP_UTF8);\r
- if(!ret)\r
- {\r
- int pos=0;\r
- CString one;\r
- while( pos>=0 )\r
- {\r
- one=output.Tokenize(_T("\n"),pos);\r
- int start=one.Find(_T("."),0);\r
- if(start>0)\r
- {\r
- CString url;\r
- url=one.Right(one.GetLength()-start-1);\r
- one=url;\r
- one=one.Left(one.Find(_T("."),0));\r
- list.push_back(one);\r
- }\r
- }\r
- }\r
- return ret;\r
-}\r
-\r
-int CGit::GetRefList(STRING_VECTOR &list)\r
-{\r
- int ret;\r
- CString cmd,output;\r
- cmd=_T("git show-ref -d");\r
- ret=g_Git.Run(cmd,&output,CP_UTF8);\r
- if(!ret)\r
- {\r
- int pos=0;\r
- CString one;\r
- while( pos>=0 )\r
- {\r
- one=output.Tokenize(_T("\n"),pos);\r
- int start=one.Find(_T(" "),0);\r
- if(start>0)\r
- {\r
- CString name;\r
- name=one.Right(one.GetLength()-start-1);\r
- list.push_back(name);\r
- }\r
- }\r
- }\r
- return ret;\r
-}\r
-int CGit::GetMapHashToFriendName(MAP_HASH_NAME &map)\r
-{\r
- int ret;\r
- CString cmd,output;\r
- cmd=_T("git show-ref -d");\r
- ret=g_Git.Run(cmd,&output,CP_UTF8);\r
- if(!ret)\r
- {\r
- int pos=0;\r
- CString one;\r
- while( pos>=0 )\r
- {\r
- one=output.Tokenize(_T("\n"),pos);\r
- int start=one.Find(_T(" "),0);\r
- if(start>0)\r
- {\r
- CString name;\r
- name=one.Right(one.GetLength()-start-1);\r
-\r
- CString hash;\r
- hash=one.Left(start);\r
-\r
- map[hash].push_back(name);\r
- }\r
- }\r
- }\r
- return ret;\r
-}\r
-\r
-BOOL CGit::CheckMsysGitDir()\r
-{\r
- static BOOL bInitialized = FALSE;\r
-\r
- if (bInitialized)\r
- {\r
- return TRUE;\r
- }\r
-\r
- TCHAR *oldpath,*home;\r
- size_t homesize,size,httpsize;\r
-\r
- // set HOME if not set already\r
- _tgetenv_s(&homesize, NULL, 0, _T("HOME"));\r
- if (!homesize)\r
- {\r
- _tdupenv_s(&home,&size,_T("USERPROFILE")); \r
- _tputenv_s(_T("HOME"),home);\r
- free(home);\r
- }\r
- CString str;\r
-\r
-#ifndef _TORTOISESHELL\r
- //set http_proxy\r
- _tgetenv_s(&httpsize, NULL, 0, _T("http_proxy"));\r
- if (!httpsize)\r
- {\r
- CString regServeraddress_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-host"), _T(""));\r
- CString regServerport_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-port"), _T(""));\r
- CString regUsername_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-username"), _T(""));\r
- CString regPassword_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-password"), _T(""));\r
- CString regTimeout_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-timeout"), _T(""));\r
- CString regExceptions_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-exceptions"), _T(""));\r
-\r
- CString http_proxy;\r
- if(!regServeraddress_copy.IsEmpty())\r
- {\r
- if(regServeraddress_copy.Left(4) != _T("http"))\r
- http_proxy=_T("http://");\r
-\r
- if(!regUsername_copy.IsEmpty())\r
- {\r
- http_proxy += regUsername_copy;\r
- http_proxy += _T(":")+regPassword_copy;\r
- http_proxy += _T("@");\r
- }\r
- http_proxy+=regServeraddress_copy;\r
- if(!regServerport_copy.IsEmpty())\r
- {\r
- http_proxy +=_T(":")+regServerport_copy;\r
- }\r
- _tputenv_s(_T("http_proxy"),http_proxy);\r
- }\r
- }\r
- //setup ssh client\r
- CString sshclient=CRegString(_T("Software\\TortoiseGit\\SSH"));\r
-\r
- if(!sshclient.IsEmpty())\r
- {\r
- _tputenv_s(_T("GIT_SSH"),sshclient);\r
- \r
- //Setup SVN_SSH\r
- CString ssh=sshclient;\r
- ssh.Replace(_T("/"),_T("\\"));\r
- ssh.Replace(_T("\\"),_T("\\\\"));\r
- ssh=CString(_T("\""))+ssh+_T('\"');\r
- _tputenv_s(_T("SVN_SSH"),ssh);\r
-\r
- }else\r
- {\r
- TCHAR sPlink[MAX_PATH];\r
- GetModuleFileName(NULL, sPlink, _countof(sPlink));\r
- LPTSTR ptr = _tcsrchr(sPlink, _T('\\'));\r
- if (ptr) {\r
- _tcscpy(ptr + 1, _T("TortoisePlink.exe"));\r
- _tputenv_s(_T("GIT_SSH"), sPlink);\r
-\r
- //Setup SVN_SSH\r
- CString ssh=sPlink;\r
- ssh.Replace(_T("/"),_T("\\"));\r
- ssh.Replace(_T("\\"),_T("\\\\"));\r
- ssh=CString(_T("\""))+ssh+_T('\"');\r
- _tputenv_s(_T("SVN_SSH"),ssh);\r
- }\r
- }\r
-\r
- {\r
- TCHAR sAskPass[MAX_PATH];\r
- GetModuleFileName(NULL, sAskPass, _countof(sAskPass));\r
- LPTSTR ptr = _tcsrchr(sAskPass, _T('\\'));\r
- if (ptr) \r
- {\r
- _tcscpy(ptr + 1, _T("SshAskPass.exe"));\r
- _tputenv_s(_T("DISPLAY"),_T(":9999"));\r
- _tputenv_s(_T("SSH_ASKPASS"),sAskPass);\r
- }\r
- }\r
- // search PATH if git/bin directory is alredy present\r
- if ( FindGitPath() )\r
- {\r
- bInitialized = TRUE;\r
- return TRUE;\r
- }\r
-\r
- // add git/bin path to PATH\r
-\r
- CRegString msysdir=CRegString(REG_MSYSGIT_PATH,_T(""),FALSE);\r
- str=msysdir;\r
- if(str.IsEmpty())\r
- {\r
- CRegString msysinstalldir=CRegString(REG_MSYSGIT_INSTALL,_T(""),FALSE,HKEY_LOCAL_MACHINE);\r
- str=msysinstalldir;\r
- if ( !str.IsEmpty() )\r
- {\r
- str += (str[str.GetLength()-1] != '\\') ? "\\bin" : "bin";\r
- msysdir=str;\r
- msysdir.write();\r
- }\r
- else\r
- {\r
- return false;\r
- }\r
- }\r
-#endif\r
- //CGit::m_MsysGitPath=str;\r
-\r
- //set path\r
-\r
- _tdupenv_s(&oldpath,&size,_T("PATH")); \r
-\r
- CString path;\r
- path.Format(_T("%s;%s"),oldpath,str);\r
-\r
- _tputenv_s(_T("PATH"),path);\r
-\r
- CString sOldPath = oldpath;\r
- free(oldpath);\r
-\r
-\r
- if( !FindGitPath() )\r
- {\r
- return false;\r
- }\r
- else\r
- {\r
-#ifdef _TORTOISESHELL\r
- l_processEnv = GetEnvironmentStrings();\r
- // updated environment is now duplicated for use in CreateProcess, restore original PATH for current process\r
- _tputenv_s(_T("PATH"),sOldPath);\r
- if(!homesize)\r
- {\r
- _tputenv_s(_T("HOME"),_T(""));\r
- }\r
-#endif\r
-\r
- bInitialized = TRUE;\r
- return true;\r
- }\r
-}\r
-\r
-\r
-class CGitCall_EnumFiles : public CGitCall\r
-{\r
-public:\r
- CGitCall_EnumFiles(const TCHAR *pszProjectPath, const TCHAR *pszSubPath, unsigned int nFlags, WGENUMFILECB *pEnumCb, void *pUserData)\r
- : m_pszProjectPath(pszProjectPath),\r
- m_pszSubPath(pszSubPath),\r
- m_nFlags(nFlags),\r
- m_pEnumCb(pEnumCb),\r
- m_pUserData(pUserData)\r
- {\r
- }\r
-\r
- typedef std::map<CStringA,char> TStrCharMap;\r
-\r
- const TCHAR * m_pszProjectPath;\r
- const TCHAR * m_pszSubPath;\r
- unsigned int m_nFlags;\r
- WGENUMFILECB * m_pEnumCb;\r
- void * m_pUserData;\r
-\r
- BYTE_VECTOR m_DataCollector;\r
-\r
- virtual bool OnOutputData(const BYTE* data, size_t size)\r
- {\r
- m_DataCollector.append(data,size);\r
- while(true)\r
- {\r
- // lines from igit.exe are 0 terminated\r
- int found=m_DataCollector.findData((const BYTE*)"",1);\r
- if(found<0)\r
- return false;\r
- OnSingleLine( (LPCSTR)&*m_DataCollector.begin() );\r
- m_DataCollector.erase(m_DataCollector.begin(), m_DataCollector.begin()+found+1);\r
- }\r
- return false;//Should never reach this\r
- }\r
- virtual void OnEnd()\r
- {\r
- }\r
-\r
- BYTE HexChar(char ch)\r
- {\r
- if (ch >= '0' && ch <= '9')\r
- return (UINT)(ch - '0');\r
- else if (ch >= 'A' && ch <= 'F')\r
- return (UINT)(ch - 'A') + 10;\r
- else if (ch >= 'a' && ch <= 'f')\r
- return (UINT)(ch - 'a') + 10;\r
- else\r
- return 0;\r
- }\r
-\r
- bool OnSingleLine(LPCSTR line)\r
- {\r
- //Parse single line\r
-\r
- wgFile_s fileStatus;\r
-\r
- // file/dir type\r
-\r
- fileStatus.nFlags = 0;\r
- if (*line == 'D')\r
- fileStatus.nFlags |= WGFF_Directory;\r
- else if (*line != 'F')\r
- // parse error\r
- return false;\r
- line += 2;\r
-\r
- // status\r
-\r
- fileStatus.nStatus = WGFS_Unknown;\r
- switch (*line)\r
- {\r
- case 'N': fileStatus.nStatus = WGFS_Normal; break;\r
- case 'M': fileStatus.nStatus = WGFS_Modified; break;\r
- case 'S': fileStatus.nStatus = WGFS_Staged; break;\r
- case 'A': fileStatus.nStatus = WGFS_Added; break;\r
- case 'C': fileStatus.nStatus = WGFS_Conflicted; break;\r
- case 'D': fileStatus.nStatus = WGFS_Deleted; break;\r
- case 'I': fileStatus.nStatus = WGFS_Ignored; break;\r
- case 'U': fileStatus.nStatus = WGFS_Unversioned; break;\r
- case 'E': fileStatus.nStatus = WGFS_Empty; break;\r
- case '?': fileStatus.nStatus = WGFS_Unknown; break;\r
- default:\r
- // parse error\r
- return false;\r
- }\r
- line += 2;\r
-\r
- // file sha1\r
-\r
- BYTE sha1[20];\r
- fileStatus.sha1 = NULL;\r
- if ( !(fileStatus.nFlags & WGFF_Directory) )\r
- {\r
- for (int i=0; i<20; i++)\r
- {\r
- sha1[i] = (HexChar(line[0])<<4)&0xF0;\r
- sha1[i] |= HexChar(line[1])&0xF;\r
-\r
- line += 2;\r
- }\r
-\r
- line++;\r
- }\r
-\r
- // filename\r
- int len = strlen(line);\r
- if (len && len < 2048)\r
- {\r
- WCHAR *buf = (WCHAR*)alloca((len*4+2)*sizeof(WCHAR));\r
- *buf = 0;\r
- MultiByteToWideChar(CP_ACP, 0, line, len+1, buf, len*4+1);\r
- fileStatus.sFileName = buf;\r
-\r
- if (*buf && (*m_pEnumCb)(&fileStatus,m_pUserData))\r
- return false;\r
- }\r
-\r
- return true;\r
- }\r
-};\r
-\r
-BOOL CGit::EnumFiles(const TCHAR *pszProjectPath, const TCHAR *pszSubPath, unsigned int nFlags, WGENUMFILECB *pEnumCb, void *pUserData)\r
-{\r
- if(!pszProjectPath || *pszProjectPath=='\0')\r
- return FALSE;\r
-\r
- CGitCall_EnumFiles W_GitCall(pszProjectPath,pszSubPath,nFlags,pEnumCb,pUserData);\r
- CString cmd;\r
-\r
-/* char W_szToDir[MAX_PATH];\r
- strncpy(W_szToDir,pszProjectPath,sizeof(W_szToDir)-1);\r
- if(W_szToDir[strlen(W_szToDir)-1]!='\\')\r
- strncat(W_szToDir,"\\",sizeof(W_szToDir)-1);\r
-\r
- SetCurrentDirectoryA(W_szToDir);\r
- GetCurrentDirectoryA(sizeof(W_szToDir)-1,W_szToDir);\r
-*/\r
- SetCurrentDir(pszProjectPath);\r
-\r
- CString sMode;\r
- if (nFlags)\r
- {\r
- if (nFlags & WGEFF_NoRecurse) sMode += _T("r");\r
- if (nFlags & WGEFF_FullPath) sMode += _T("f");\r
- if (nFlags & WGEFF_DirStatusDelta) sMode += _T("d");\r
- if (nFlags & WGEFF_DirStatusAll) sMode += _T("D");\r
- if (nFlags & WGEFF_EmptyAsNormal) sMode += _T("e");\r
- if (nFlags & WGEFF_SingleFile) sMode += _T("s");\r
- }\r
- else\r
- {\r
- sMode = _T("-");\r
- }\r
-\r
- // NOTE: there seems to be some issue with msys based app receiving backslash on commandline, at least\r
- // if followed by " like for example 'igit "C:\"', the commandline igit receives is 'igit.exe C:" status' with\r
- // the 'C:" status' part as a single arg, Maybe it uses unix style processing. In order to avoid this just\r
- // use forward slashes for supplied project and sub paths\r
-\r
- CString sProjectPath = pszProjectPath;\r
- sProjectPath.Replace(_T('\\'), _T('/'));\r
-\r
- if (pszSubPath)\r
- {\r
- CString sSubPath = pszSubPath;\r
- sSubPath.Replace(_T('\\'), _T('/'));\r
-\r
- cmd.Format(_T("igit.exe \"%s\" status %s \"%s\""), sProjectPath, sMode, sSubPath);\r
- }\r
- else\r
- {\r
- cmd.Format(_T("igit.exe \"%s\" status %s"), sProjectPath, sMode);\r
- }\r
-\r
- //OutputDebugStringA("---");OutputDebugStringW(cmd);OutputDebugStringA("\r\n");\r
-\r
- W_GitCall.SetCmd(cmd);\r
- // NOTE: should igit get added as a part of msysgit then use below line instead of the above one\r
- //W_GitCall.SetCmd(CGit::ms_LastMsysGitDir + cmd);\r
-\r
- if ( Run(&W_GitCall) )\r
- return FALSE;\r
-\r
- return TRUE;\r
-}\r
-\r
-BOOL CGit::CheckCleanWorkTree()\r
-{\r
- CString out;\r
- CString cmd;\r
- cmd=_T("git.exe rev-parse --verify HEAD");\r
-\r
- if(g_Git.Run(cmd,&out,CP_UTF8))\r
- return FALSE;\r
-\r
- cmd=_T("git.exe update-index --ignore-submodules --refresh");\r
- if(g_Git.Run(cmd,&out,CP_UTF8))\r
- return FALSE;\r
-\r
- cmd=_T("git.exe diff-files --quiet --ignore-submodules");\r
- if(g_Git.Run(cmd,&out,CP_UTF8))\r
- return FALSE;\r
-\r
- cmd=_T("git diff-index --cached --quiet HEAD --ignore-submodules");\r
- if(g_Git.Run(cmd,&out,CP_UTF8))\r
- return FALSE;\r
-\r
- return TRUE;\r
-}\r
-int CGit::Revert(CTGitPathList &list,bool keep)\r
-{\r
- int ret;\r
- for(int i=0;i<list.GetCount();i++)\r
- { \r
- ret = Revert((CTGitPath&)list[i],keep);\r
- if(ret)\r
- return ret;\r
- }\r
- return 0;\r
-}\r
-int CGit::Revert(CTGitPath &path,bool keep)\r
-{\r
- CString cmd, out;\r
- if(path.m_Action & CTGitPath::LOGACTIONS_ADDED)\r
- { //To init git repository, there are not HEAD, so we can use git reset command\r
- cmd.Format(_T("git.exe rm --cached -- \"%s\""),path.GetGitPathString());\r
-\r
- if(g_Git.Run(cmd,&out,CP_ACP))\r
- return -1;\r
- }\r
- else if(path.m_Action & CTGitPath::LOGACTIONS_REPLACED )\r
- {\r
- cmd.Format(_T("git.exe mv -- \"%s\" \"%s\""),path.GetGitPathString(),path.GetGitOldPathString());\r
- if(g_Git.Run(cmd,&out,CP_ACP))\r
- return -1;\r
- \r
- cmd.Format(_T("git.exe checkout HEAD -f -- \"%s\""),path.GetGitOldPathString());\r
- if(g_Git.Run(cmd,&out,CP_ACP))\r
- return -1;\r
- }\r
- else\r
- {\r
- cmd.Format(_T("git.exe checkout HEAD -f -- \"%s\""),path.GetGitPathString());\r
- if(g_Git.Run(cmd,&out,CP_ACP))\r
- return -1;\r
- }\r
- return 0;\r
-}\r
-\r
-int CGit::ListConflictFile(CTGitPathList &list,CTGitPath *path)\r
-{\r
- BYTE_VECTOR vector;\r
-\r
- CString cmd;\r
- if(path)\r
- cmd.Format(_T("git.exe ls-files -u -t -z -- \"%s\""),path->GetGitPathString());\r
- else\r
- cmd=_T("git.exe ls-files -u -t -z");\r
-\r
- if(g_Git.Run(cmd,&vector))\r
- {\r
- return -1;\r
- }\r
-\r
- list.ParserFromLsFile(vector);\r
-\r
- return 0;\r
-}\r
-\r
-bool CGit::IsFastForward(CString &from, CString &to)\r
-{\r
- CString base,hash;\r
- CString cmd;\r
- cmd.Format(_T("git.exe merge-base %s %s"), to,from);\r
-\r
- if(g_Git.Run(cmd,&base,CP_ACP))\r
- {\r
- //CMessageBox::Show(NULL,base,_T("TortoiseGit"),MB_OK|MB_ICONERROR);\r
- return false;\r
- }\r
- base=base.Left(40);\r
-\r
- hash=g_Git.GetHash(from);\r
-\r
- hash=hash.Left(40);\r
- \r
- return hash == base;\r
-}\r
-\r
-unsigned int CGit::Hash2int(CString &hash)\r
-{\r
- int ret=0;\r
- for(int i=0;i<8;i++)\r
- {\r
- ret =ret <<4;\r
- if(hash[i]>=_T('a'))\r
- ret |= (hash[i]-_T('a')+10)&0xFF;\r
- else if(hash[i]>=_T('A'))\r
- ret |= (hash[i]-_T('A')+10)&0xFF;\r
- else\r
- ret |= (hash[i]-_T('0'))&0xFF; \r
- \r
- }\r
- return ret;\r
+#include "StdAfx.h"
+#include "Git.h"
+#include "atlconv.h"
+#include "GitRev.h"
+#include "registry.h"
+#include "GitConfig.h"
+#include <map>
+#include "UnicodeUtils.h"
+
+int CGit::m_LogEncode=CP_UTF8;
+
+static LPTSTR nextpath(LPCTSTR src, LPTSTR dst, UINT maxlen)
+{
+ LPCTSTR orgsrc;
+
+ while (*src == _T(';'))
+ src++;
+
+ orgsrc = src;
+
+ if (!--maxlen)
+ goto nullterm;
+
+ while (*src && *src != _T(';'))
+ {
+ if (*src != _T('"'))
+ {
+ *dst++ = *src++;
+ if (!--maxlen)
+ {
+ orgsrc = src;
+ goto nullterm;
+ }
+ }
+ else
+ {
+ src++;
+ while (*src && *src != _T('"'))
+ {
+ *dst++ = *src++;
+ if (!--maxlen)
+ {
+ orgsrc = src;
+ goto nullterm;
+ }
+ }
+
+ if (*src)
+ src++;
+ }
+ }
+
+ while (*src == _T(';'))
+ src++;
+
+nullterm:
+
+ *dst = 0;
+
+ return (orgsrc != src) ? (LPTSTR)src : NULL;
+}
+
+static inline BOOL FileExists(LPCTSTR lpszFileName)
+{
+ struct _stat st;
+ return _tstat(lpszFileName, &st) == 0;
+}
+
+static BOOL FindGitPath()
+{
+ size_t size;
+ _tgetenv_s(&size, NULL, 0, _T("PATH"));
+
+ if (!size)
+ {
+ return FALSE;
+ }
+
+ TCHAR *env = (TCHAR*)alloca(size * sizeof(TCHAR));
+ _tgetenv_s(&size, env, size, _T("PATH"));
+
+ TCHAR buf[_MAX_PATH];
+
+ // search in all paths defined in PATH
+ while ((env = nextpath(env, buf, _MAX_PATH-1)) && *buf)
+ {
+ TCHAR *pfin = buf + _tcslen(buf)-1;
+
+ // ensure trailing slash
+ if (*pfin != _T('/') && *pfin != _T('\\'))
+ _tcscpy(++pfin, _T("\\"));
+
+ const int len = _tcslen(buf);
+
+ if ((len + 7) < _MAX_PATH)
+ _tcscpy(pfin+1, _T("git.exe"));
+ else
+ break;
+
+ if ( FileExists(buf) )
+ {
+ // dir found
+ pfin[1] = 0;
+ CGit::ms_LastMsysGitDir = buf;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+
+#define MAX_DIRBUFFER 1000
+#define CALL_OUTPUT_READ_CHUNK_SIZE 1024
+
+CString CGit::ms_LastMsysGitDir;
+CGit g_Git;
+
+// contains system environment that should be used by child processes (RunAsync)
+// initialized by CheckMsysGitDir
+static LPTSTR l_processEnv = NULL;
+
+
+
+CGit::CGit(void)
+{
+ GetCurrentDirectory(MAX_DIRBUFFER,m_CurrentDir.GetBuffer(MAX_DIRBUFFER));
+ m_CurrentDir.ReleaseBuffer();
+
+ CheckMsysGitDir();
+}
+
+CGit::~CGit(void)
+{
+}
+
+static char g_Buffer[4096];
+
+int CGit::RunAsync(CString cmd,PROCESS_INFORMATION *piOut,HANDLE *hReadOut,CString *StdioFile)
+{
+ SECURITY_ATTRIBUTES sa;
+ HANDLE hRead, hWrite;
+ HANDLE hStdioFile = NULL;
+
+ sa.nLength = sizeof(SECURITY_ATTRIBUTES);
+ sa.lpSecurityDescriptor=NULL;
+ sa.bInheritHandle=TRUE;
+ if(!CreatePipe(&hRead,&hWrite,&sa,0))
+ {
+ return GIT_ERROR_OPEN_PIP;
+ }
+
+ if(StdioFile)
+ {
+ hStdioFile=CreateFile(*StdioFile,GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,
+ &sa,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
+ }
+
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+ si.cb=sizeof(STARTUPINFO);
+ GetStartupInfo(&si);
+
+ si.hStdError=hWrite;
+ if(StdioFile)
+ si.hStdOutput=hStdioFile;
+ else
+ si.hStdOutput=hWrite;
+
+ si.wShowWindow=SW_HIDE;
+ si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
+
+ LPTSTR pEnv = l_processEnv;
+ DWORD dwFlags = pEnv ? CREATE_UNICODE_ENVIRONMENT : 0;
+
+ //DETACHED_PROCESS make ssh recognize that it has no console to launch askpass to input password.
+ dwFlags |= DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP;
+
+ memset(&this->m_CurrentGitPi,0,sizeof(PROCESS_INFORMATION));
+
+ if(!CreateProcess(NULL,(LPWSTR)cmd.GetString(), NULL,NULL,TRUE,dwFlags,pEnv,(LPWSTR)m_CurrentDir.GetString(),&si,&pi))
+ {
+ LPVOID lpMsgBuf;
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
+ NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR)&lpMsgBuf,
+ 0,NULL);
+ return GIT_ERROR_CREATE_PROCESS;
+ }
+
+ m_CurrentGitPi = pi;
+
+ CloseHandle(hWrite);
+ if(piOut)
+ *piOut=pi;
+ if(hReadOut)
+ *hReadOut=hRead;
+
+ return 0;
+
+}
+//Must use sperate function to convert ANSI str to union code string
+//Becuase A2W use stack as internal convert buffer.
+void CGit::StringAppend(CString *str,BYTE *p,int code,int length)
+{
+ //USES_CONVERSION;
+ //str->Append(A2W_CP((LPCSTR)p,code));
+ if(str == NULL)
+ return ;
+
+ WCHAR * buf;
+
+ int len ;
+ if(length<0)
+ len= strlen((const char*)p);
+ else
+ len=length;
+ //if (len==0)
+ // return ;
+ //buf = new WCHAR[len*4 + 1];
+ buf = str->GetBuffer(len*4+1+str->GetLength())+str->GetLength();
+ SecureZeroMemory(buf, (len*4 + 1)*sizeof(WCHAR));
+ MultiByteToWideChar(code, 0, (LPCSTR)p, len, buf, len*4);
+ str->ReleaseBuffer();
+ //str->Append(buf);
+ //delete buf;
+}
+BOOL CGit::IsInitRepos()
+{
+ CString cmdout;
+ cmdout.Empty();
+ if(g_Git.Run(_T("git.exe rev-parse --revs-only HEAD"),&cmdout,CP_UTF8))
+ {
+ // CMessageBox::Show(NULL,cmdout,_T("TortoiseGit"),MB_OK);
+ return TRUE;
+ }
+ if(cmdout.IsEmpty())
+ return TRUE;
+
+ return FALSE;
+}
+int CGit::Run(CGitCall* pcall)
+{
+ PROCESS_INFORMATION pi;
+ HANDLE hRead;
+ if(RunAsync(pcall->GetCmd(),&pi,&hRead))
+ return GIT_ERROR_CREATE_PROCESS;
+
+ DWORD readnumber;
+ BYTE data[CALL_OUTPUT_READ_CHUNK_SIZE];
+ bool bAborted=false;
+ while(ReadFile(hRead,data,CALL_OUTPUT_READ_CHUNK_SIZE,&readnumber,NULL))
+ {
+ //Todo: when OnOutputData() returns 'true', abort git-command. Send CTRL-C signal?
+ if(!bAborted)//For now, flush output when command aborted.
+ if(pcall->OnOutputData(data,readnumber))
+ bAborted=true;
+ }
+ if(!bAborted)
+ pcall->OnEnd();
+
+
+ CloseHandle(pi.hThread);
+
+ WaitForSingleObject(pi.hProcess, INFINITE);
+ DWORD exitcode =0;
+
+ if(!GetExitCodeProcess(pi.hProcess,&exitcode))
+ {
+ return GIT_ERROR_GET_EXIT_CODE;
+ }
+
+ CloseHandle(pi.hProcess);
+
+ CloseHandle(hRead);
+ return exitcode;
+}
+class CGitCall_ByteVector : public CGitCall
+{
+public:
+ CGitCall_ByteVector(CString cmd,BYTE_VECTOR* pvector):CGitCall(cmd),m_pvector(pvector){}
+ virtual bool OnOutputData(const BYTE* data, size_t size)
+ {
+ size_t oldsize=m_pvector->size();
+ m_pvector->resize(m_pvector->size()+size);
+ memcpy(&*(m_pvector->begin()+oldsize),data,size);
+ return false;
+ }
+ BYTE_VECTOR* m_pvector;
+
+};
+int CGit::Run(CString cmd,BYTE_VECTOR *vector)
+{
+ CGitCall_ByteVector call(cmd,vector);
+ return Run(&call);
+}
+int CGit::Run(CString cmd, CString* output,int code)
+{
+ BYTE_VECTOR vector;
+ int ret;
+ ret=Run(cmd,&vector);
+
+ vector.push_back(0);
+
+ StringAppend(output,&(vector[0]),code);
+ return ret;
+}
+
+CString CGit::GetUserName(void)
+{
+ return GetConfigValue(L"user.name");
+}
+CString CGit::GetUserEmail(void)
+{
+ return GetConfigValue(L"user.email");
+}
+
+CString CGit::GetConfigValue(CString name)
+{
+ CString configValue;
+ CString cmd;
+ cmd.Format(L"git.exe config %s", name);
+ Run(cmd,&configValue,CP_UTF8);
+ int start = 0;
+ return configValue.Tokenize(_T("\n"),start);
+}
+
+
+CString CGit::GetCurrentBranch(void)
+{
+ CString output;
+ //Run(_T("git.exe branch"),&branch);
+
+ int ret=g_Git.Run(_T("git.exe branch --no-color"),&output,CP_UTF8);
+ if(!ret)
+ {
+ int pos=0;
+ CString one;
+ while( pos>=0 )
+ {
+ //i++;
+ one=output.Tokenize(_T("\n"),pos);
+ //list.push_back(one.Right(one.GetLength()-2));
+ if(one[0] == _T('*'))
+ return one.Right(one.GetLength()-2);
+ }
+ }
+ return CString("");
+}
+
+CString CGit::GetSymbolicRef(const wchar_t* symbolicRefName, bool bStripRefsHeads)
+{
+ CString refName;
+ CString cmd;
+ cmd.Format(L"git symbolic-ref %s", symbolicRefName);
+ if(Run(cmd, &refName, CP_UTF8) != 0)
+ return CString();//Error
+ int iStart = 0;
+ refName = refName.Tokenize(L"\n", iStart);
+ if(bStripRefsHeads)
+ refName = StripRefName(refName);
+ return refName;
+}
+
+CString CGit::GetFullRefName(CString shortRefName)
+{
+ CString refName;
+ CString cmd;
+ cmd.Format(L"git rev-parse --symbolic-full-name %s", shortRefName);
+ if(Run(cmd, &refName, CP_UTF8) != 0)
+ return CString();//Error
+ int iStart = 0;
+ return refName.Tokenize(L"\n", iStart);
+}
+
+CString CGit::StripRefName(CString refName)
+{
+ if(wcsncmp(refName, L"refs/heads/", 11) == 0)
+ refName = refName.Mid(11);
+ else if(wcsncmp(refName, L"refs/", 5) == 0)
+ refName = refName.Mid(5);
+ return refName;
+}
+
+int CGit::GetCurrentBranchFromFile(const CString &sProjectRoot, CString &sBranchOut)
+{
+ // read current branch name like git-gui does, by parsing the .git/HEAD file directly
+
+ if ( sProjectRoot.IsEmpty() )
+ return -1;
+
+ CString sHeadFile = sProjectRoot + _T("\\") + g_GitAdminDir.GetAdminDirName() + _T("\\HEAD");
+
+ FILE *pFile;
+ _tfopen_s(&pFile, sHeadFile.GetString(), _T("r"));
+
+ if (!pFile)
+ {
+ return -1;
+ }
+
+ char s[256] = {0};
+ fgets(s, sizeof(s), pFile);
+
+ fclose(pFile);
+
+ const char *pfx = "ref: refs/heads/";
+ const int len = 16;//strlen(pfx)
+
+ if ( !strncmp(s, pfx, len) )
+ {
+ //# We're on a branch. It might not exist. But
+ //# HEAD looks good enough to be a branch.
+ sBranchOut = s + len;
+ sBranchOut.TrimRight(_T(" \r\n\t"));
+
+ if ( sBranchOut.IsEmpty() )
+ return -1;
+ }
+ else
+ {
+ //# Assume this is a detached head.
+ sBranchOut = "HEAD";
+
+ return 1;
+ }
+
+ return 0;
+}
+
+int CGit::BuildOutputFormat(CString &format,bool IsFull)
+{
+ CString log;
+ log.Format(_T("#<%c>%%x00"),LOG_REV_ITEM_BEGIN);
+ format += log;
+ if(IsFull)
+ {
+ log.Format(_T("#<%c>%%an%%x00"),LOG_REV_AUTHOR_NAME);
+ format += log;
+ log.Format(_T("#<%c>%%ae%%x00"),LOG_REV_AUTHOR_EMAIL);
+ format += log;
+ log.Format(_T("#<%c>%%ai%%x00"),LOG_REV_AUTHOR_DATE);
+ format += log;
+ log.Format(_T("#<%c>%%cn%%x00"),LOG_REV_COMMIT_NAME);
+ format += log;
+ log.Format(_T("#<%c>%%ce%%x00"),LOG_REV_COMMIT_EMAIL);
+ format += log;
+ log.Format(_T("#<%c>%%ci%%x00"),LOG_REV_COMMIT_DATE);
+ format += log;
+ log.Format(_T("#<%c>%%b%%x00"),LOG_REV_COMMIT_BODY);
+ format += log;
+ }
+
+ log.Format(_T("#<%c>%%m%%H%%x00"),LOG_REV_COMMIT_HASH);
+ format += log;
+ log.Format(_T("#<%c>%%P%%x00"),LOG_REV_COMMIT_PARENT);
+ format += log;
+ log.Format(_T("#<%c>%%s%%x00"),LOG_REV_COMMIT_SUBJECT);
+ format += log;
+
+ if(IsFull)
+ {
+ log.Format(_T("#<%c>%%x00"),LOG_REV_COMMIT_FILE);
+ format += log;
+ }
+ return 0;
+}
+
+int CGit::GetLog(BYTE_VECTOR& logOut, CString &hash, CTGitPath *path ,int count,int mask,CString *from,CString *to)
+{
+ CGitCall_ByteVector gitCall(CString(),&logOut);
+ return GetLog(&gitCall,hash,path,count,mask,from,to);
+}
+
+//int CGit::GetLog(CGitCall* pgitCall, CString &hash, CTGitPath *path ,int count,int mask)
+int CGit::GetLog(CGitCall* pgitCall, CString &hash, CTGitPath *path, int count, int mask,CString *from,CString *to)
+{
+
+ CString cmd;
+ CString log;
+ CString num;
+ CString since;
+
+ CString file;
+
+ if(path)
+ file.Format(_T(" -- \"%s\""),path->GetGitPathString());
+
+ if(count>0)
+ num.Format(_T("-n%d"),count);
+
+ CString param;
+
+ if(mask& LOG_INFO_STAT )
+ param += _T(" --numstat ");
+ if(mask& LOG_INFO_FILESTATE)
+ param += _T(" --raw ");
+
+ if(mask& LOG_INFO_FULLHISTORY)
+ param += _T(" --full-history ");
+
+ if(mask& LOG_INFO_BOUNDARY)
+ param += _T(" --left-right --boundary ");
+
+ if(mask& CGit::LOG_INFO_ALL_BRANCH)
+ param += _T(" --all ");
+
+ if(mask& CGit::LOG_INFO_DETECT_COPYRENAME)
+ param += _T(" -C ");
+
+ if(mask& CGit::LOG_INFO_DETECT_RENAME )
+ param += _T(" -M ");
+
+ if(mask& CGit::LOG_INFO_FIRST_PARENT )
+ param += _T(" --first-parent ");
+
+ if(mask& CGit::LOG_INFO_NO_MERGE )
+ param += _T(" --no-merges ");
+
+ if(mask& CGit::LOG_INFO_FOLLOW)
+ param += _T(" --follow ");
+
+ if(mask& CGit::LOG_INFO_SHOW_MERGEDFILE)
+ param += _T(" -c ");
+
+ if(mask& CGit::LOG_INFO_FULL_DIFF)
+ param += _T(" --full-diff ");
+
+ if(from != NULL && to != NULL)
+ {
+ CString range;
+ range.Format(_T(" %s..%s "),*from,*to);
+ param += range;
+ }
+ param+=hash;
+
+ cmd.Format(_T("git.exe log %s -z --topo-order %s --parents --pretty=format:\""),
+ num,param);
+
+ BuildOutputFormat(log,!(mask&CGit::LOG_INFO_ONLY_HASH));
+
+ cmd += log;
+ cmd += CString(_T("\" "))+hash+file;
+
+ pgitCall->SetCmd(cmd);
+
+ return Run(pgitCall);
+// return Run(cmd,&logOut);
+}
+
+#if 0
+int CGit::GetShortLog(CString &logOut,CTGitPath * path, int count)
+{
+ CString cmd;
+ CString log;
+ int n;
+ if(count<0)
+ n=100;
+ else
+ n=count;
+ cmd.Format(_T("git.exe log --left-right --boundary --topo-order -n%d --pretty=format:\""),n);
+ BuildOutputFormat(log,false);
+ cmd += log+_T("\"");
+ if (path)
+ cmd+= _T(" -- \"")+path->GetGitPathString()+_T("\"");
+ //cmd += CString(_T("\" HEAD~40..HEAD"));
+ return Run(cmd,&logOut);
+}
+#endif
+
+#define BUFSIZE 512
+void GetTempPath(CString &path)
+{
+ TCHAR lpPathBuffer[BUFSIZE];
+ DWORD dwRetVal;
+ DWORD dwBufSize=BUFSIZE;
+ dwRetVal = GetTempPath(dwBufSize, // length of the buffer
+ lpPathBuffer); // buffer for path
+ if (dwRetVal > dwBufSize || (dwRetVal == 0))
+ {
+ path=_T("");
+ }
+ path.Format(_T("%s"),lpPathBuffer);
+}
+CString GetTempFile()
+{
+ TCHAR lpPathBuffer[BUFSIZE];
+ DWORD dwRetVal;
+ DWORD dwBufSize=BUFSIZE;
+ TCHAR szTempName[BUFSIZE];
+ UINT uRetVal;
+
+ dwRetVal = GetTempPath(dwBufSize, // length of the buffer
+ lpPathBuffer); // buffer for path
+ if (dwRetVal > dwBufSize || (dwRetVal == 0))
+ {
+ return _T("");
+ }
+ // Create a temporary file.
+ uRetVal = GetTempFileName(lpPathBuffer, // directory for tmp files
+ TEXT("Patch"), // temp file name prefix
+ 0, // create unique name
+ szTempName); // buffer for name
+
+
+ if (uRetVal == 0)
+ {
+ return _T("");
+ }
+
+ return CString(szTempName);
+
+}
+
+int CGit::RunLogFile(CString cmd,CString &filename)
+{
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+ si.cb=sizeof(STARTUPINFO);
+ GetStartupInfo(&si);
+
+ SECURITY_ATTRIBUTES psa={sizeof(psa),NULL,TRUE};;
+ psa.bInheritHandle=TRUE;
+
+ HANDLE houtfile=CreateFile(filename,GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,
+ &psa,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
+
+
+ si.wShowWindow=SW_HIDE;
+ si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
+ si.hStdOutput = houtfile;
+
+ if(!CreateProcess(NULL,(LPWSTR)cmd.GetString(), NULL,NULL,TRUE,NULL,NULL,(LPWSTR)m_CurrentDir.GetString(),&si,&pi))
+ {
+ LPVOID lpMsgBuf;
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
+ NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR)&lpMsgBuf,
+ 0,NULL);
+ return GIT_ERROR_CREATE_PROCESS;
+ }
+
+ WaitForSingleObject(pi.hProcess,INFINITE);
+
+ CloseHandle(pi.hThread);
+ CloseHandle(pi.hProcess);
+ CloseHandle(houtfile);
+ return GIT_SUCCESS;
+// return 0;
+}
+
+git_revnum_t CGit::GetHash(const CString &friendname)
+{
+ CString cmd;
+ CString out;
+ cmd.Format(_T("git.exe rev-parse %s" ),friendname);
+ Run(cmd,&out,CP_UTF8);
+// int pos=out.ReverseFind(_T('\n'));
+ int pos=out.FindOneOf(_T("\r\n"));
+ if(pos>0)
+ return out.Left(pos);
+ return out;
+}
+
+int CGit::GetCommitDiffList(CString &rev1,CString &rev2,CTGitPathList &outputlist)
+{
+ CString cmd;
+
+ if(rev1 == GIT_REV_ZERO || rev2 == GIT_REV_ZERO)
+ {
+ //rev1=+_T("");
+ if(rev1 == GIT_REV_ZERO)
+ cmd.Format(_T("git.exe diff -r --raw -C -M --numstat -z %s"),rev2);
+ else
+ cmd.Format(_T("git.exe diff -r -R --raw -C -M --numstat -z %s"),rev1);
+ }else
+ {
+ cmd.Format(_T("git.exe diff-tree -r --raw -C -M --numstat -z %s %s"),rev2,rev1);
+ }
+
+ BYTE_VECTOR out;
+ if(g_Git.Run(cmd,&out))
+ return -1;
+
+ outputlist.ParserFromLog(out);
+
+}
+
+int CGit::GetTagList(STRING_VECTOR &list)
+{
+ int ret;
+ CString cmd,output;
+ cmd=_T("git.exe tag -l");
+ int i=0;
+ ret=g_Git.Run(cmd,&output,CP_UTF8);
+ if(!ret)
+ {
+ int pos=0;
+ CString one;
+ while( pos>=0 )
+ {
+ i++;
+ one=output.Tokenize(_T("\n"),pos);
+ list.push_back(one);
+ }
+ }
+ return ret;
+}
+
+int CGit::GetBranchList(STRING_VECTOR &list,int *current,BRANCH_TYPE type)
+{
+ int ret;
+ CString cmd,output;
+ cmd=_T("git.exe branch --no-color");
+
+ if(type==(BRANCH_LOCAL|BRANCH_REMOTE))
+ cmd+=_T(" -a");
+ else if(type==BRANCH_REMOTE)
+ cmd+=_T(" -r");
+
+ int i=0;
+ ret=g_Git.Run(cmd,&output,CP_UTF8);
+ if(!ret)
+ {
+ int pos=0;
+ CString one;
+ while( pos>=0 )
+ {
+ one=output.Tokenize(_T("\n"),pos);
+ one.Trim(L" \r\n\t");
+ if(one.Find(L" -> ") >= 0 || one.IsEmpty())
+ continue; // skip something like: refs/origin/HEAD -> refs/origin/master
+ if(one[0] == _T('*'))
+ {
+ if(current)
+ *current=i;
+ one = one.Mid(2);
+ }
+ list.push_back(one);
+ i++;
+ }
+ }
+ return ret;
+}
+
+int CGit::GetRemoteList(STRING_VECTOR &list)
+{
+ int ret;
+ CString cmd,output;
+ cmd=_T("git.exe config --get-regexp '^^remote[.].*[.]url'");
+ ret=g_Git.Run(cmd,&output,CP_UTF8);
+ if(!ret)
+ {
+ int pos=0;
+ CString one;
+ while( pos>=0 )
+ {
+ one=output.Tokenize(_T("\n"),pos);
+ int start=one.Find(_T("."),0);
+ if(start>0)
+ {
+ CString url;
+ url=one.Right(one.GetLength()-start-1);
+ one=url;
+ one=one.Left(one.Find(_T("."),0));
+ list.push_back(one);
+ }
+ }
+ }
+ return ret;
+}
+
+int CGit::GetRefList(STRING_VECTOR &list)
+{
+ int ret;
+ CString cmd,output;
+ cmd=_T("git show-ref -d");
+ ret=g_Git.Run(cmd,&output,CP_UTF8);
+ if(!ret)
+ {
+ int pos=0;
+ CString one;
+ while( pos>=0 )
+ {
+ one=output.Tokenize(_T("\n"),pos);
+ int start=one.Find(_T(" "),0);
+ if(start>0)
+ {
+ CString name;
+ name=one.Right(one.GetLength()-start-1);
+ list.push_back(name);
+ }
+ }
+ }
+ return ret;
+}
+int CGit::GetMapHashToFriendName(MAP_HASH_NAME &map)
+{
+ int ret;
+ CString cmd,output;
+ cmd=_T("git show-ref -d");
+ ret=g_Git.Run(cmd,&output,CP_UTF8);
+ if(!ret)
+ {
+ int pos=0;
+ CString one;
+ while( pos>=0 )
+ {
+ one=output.Tokenize(_T("\n"),pos);
+ int start=one.Find(_T(" "),0);
+ if(start>0)
+ {
+ CString name;
+ name=one.Right(one.GetLength()-start-1);
+
+ CString hash;
+ hash=one.Left(start);
+
+ map[hash].push_back(name);
+ }
+ }
+ }
+ return ret;
+}
+
+BOOL CGit::CheckMsysGitDir()
+{
+ static BOOL bInitialized = FALSE;
+
+ if (bInitialized)
+ {
+ return TRUE;
+ }
+
+ TCHAR *oldpath,*home;
+ size_t homesize,size,httpsize;
+
+ // set HOME if not set already
+ _tgetenv_s(&homesize, NULL, 0, _T("HOME"));
+ if (!homesize)
+ {
+ _tdupenv_s(&home,&size,_T("USERPROFILE"));
+ _tputenv_s(_T("HOME"),home);
+ free(home);
+ }
+ CString str;
+
+#ifndef _TORTOISESHELL
+ //set http_proxy
+ _tgetenv_s(&httpsize, NULL, 0, _T("http_proxy"));
+ if (!httpsize)
+ {
+ CString regServeraddress_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-host"), _T(""));
+ CString regServerport_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-port"), _T(""));
+ CString regUsername_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-username"), _T(""));
+ CString regPassword_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-password"), _T(""));
+ CString regTimeout_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-timeout"), _T(""));
+ CString regExceptions_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-exceptions"), _T(""));
+
+ CString http_proxy;
+ if(!regServeraddress_copy.IsEmpty())
+ {
+ if(regServeraddress_copy.Left(4) != _T("http"))
+ http_proxy=_T("http://");
+
+ if(!regUsername_copy.IsEmpty())
+ {
+ http_proxy += regUsername_copy;
+ http_proxy += _T(":")+regPassword_copy;
+ http_proxy += _T("@");
+ }
+ http_proxy+=regServeraddress_copy;
+ if(!regServerport_copy.IsEmpty())
+ {
+ http_proxy +=_T(":")+regServerport_copy;
+ }
+ _tputenv_s(_T("http_proxy"),http_proxy);
+ }
+ }
+ //setup ssh client
+ CString sshclient=CRegString(_T("Software\\TortoiseGit\\SSH"));
+
+ if(!sshclient.IsEmpty())
+ {
+ _tputenv_s(_T("GIT_SSH"),sshclient);
+
+ //Setup SVN_SSH
+ CString ssh=sshclient;
+ ssh.Replace(_T("/"),_T("\\"));
+ ssh.Replace(_T("\\"),_T("\\\\"));
+ ssh=CString(_T("\""))+ssh+_T('\"');
+ _tputenv_s(_T("SVN_SSH"),ssh);
+
+ }else
+ {
+ TCHAR sPlink[MAX_PATH];
+ GetModuleFileName(NULL, sPlink, _countof(sPlink));
+ LPTSTR ptr = _tcsrchr(sPlink, _T('\\'));
+ if (ptr) {
+ _tcscpy(ptr + 1, _T("TortoisePlink.exe"));
+ _tputenv_s(_T("GIT_SSH"), sPlink);
+
+ //Setup SVN_SSH
+ CString ssh=sPlink;
+ ssh.Replace(_T("/"),_T("\\"));
+ ssh.Replace(_T("\\"),_T("\\\\"));
+ ssh=CString(_T("\""))+ssh+_T('\"');
+ _tputenv_s(_T("SVN_SSH"),ssh);
+ }
+ }
+
+ {
+ TCHAR sAskPass[MAX_PATH];
+ GetModuleFileName(NULL, sAskPass, _countof(sAskPass));
+ LPTSTR ptr = _tcsrchr(sAskPass, _T('\\'));
+ if (ptr)
+ {
+ _tcscpy(ptr + 1, _T("SshAskPass.exe"));
+ _tputenv_s(_T("DISPLAY"),_T(":9999"));
+ _tputenv_s(_T("SSH_ASKPASS"),sAskPass);
+ }
+ }
+ // search PATH if git/bin directory is alredy present
+ if ( FindGitPath() )
+ {
+ bInitialized = TRUE;
+ return TRUE;
+ }
+
+ // add git/bin path to PATH
+
+ CRegString msysdir=CRegString(REG_MSYSGIT_PATH,_T(""),FALSE);
+ str=msysdir;
+ if(str.IsEmpty())
+ {
+ CRegString msysinstalldir=CRegString(REG_MSYSGIT_INSTALL,_T(""),FALSE,HKEY_LOCAL_MACHINE);
+ str=msysinstalldir;
+ if ( !str.IsEmpty() )
+ {
+ str += (str[str.GetLength()-1] != '\\') ? "\\bin" : "bin";
+ msysdir=str;
+ msysdir.write();
+ }
+ else
+ {
+ return false;
+ }
+ }
+#endif
+ //CGit::m_MsysGitPath=str;
+
+ //set path
+
+ _tdupenv_s(&oldpath,&size,_T("PATH"));
+
+ CString path;
+ path.Format(_T("%s;%s"),oldpath,str);
+
+ _tputenv_s(_T("PATH"),path);
+
+ CString sOldPath = oldpath;
+ free(oldpath);
+
+
+ if( !FindGitPath() )
+ {
+ if(!homesize)
+ {
+ _tputenv_s(_T("HOME"),_T(""));
+ }
+ return false;
+ }
+ else
+ {
+#ifdef _TORTOISESHELL
+ l_processEnv = GetEnvironmentStrings();
+ // updated environment is now duplicated for use in CreateProcess, restore original PATH for current process
+ _tputenv_s(_T("PATH"),sOldPath);
+ if(!homesize)
+ {
+ _tputenv_s(_T("HOME"),_T(""));
+ }
+#endif
+
+ bInitialized = TRUE;
+ return true;
+ }
+}
+
+
+class CGitCall_EnumFiles : public CGitCall
+{
+public:
+ CGitCall_EnumFiles(const TCHAR *pszProjectPath, const TCHAR *pszSubPath, unsigned int nFlags, WGENUMFILECB *pEnumCb, void *pUserData)
+ : m_pszProjectPath(pszProjectPath),
+ m_pszSubPath(pszSubPath),
+ m_nFlags(nFlags),
+ m_pEnumCb(pEnumCb),
+ m_pUserData(pUserData)
+ {
+ }
+
+ typedef std::map<CStringA,char> TStrCharMap;
+
+ const TCHAR * m_pszProjectPath;
+ const TCHAR * m_pszSubPath;
+ unsigned int m_nFlags;
+ WGENUMFILECB * m_pEnumCb;
+ void * m_pUserData;
+
+ BYTE_VECTOR m_DataCollector;
+
+ virtual bool OnOutputData(const BYTE* data, size_t size)
+ {
+ m_DataCollector.append(data,size);
+ while(true)
+ {
+ // lines from igit.exe are 0 terminated
+ int found=m_DataCollector.findData((const BYTE*)"",1);
+ if(found<0)
+ return false;
+ OnSingleLine( (LPCSTR)&*m_DataCollector.begin() );
+ m_DataCollector.erase(m_DataCollector.begin(), m_DataCollector.begin()+found+1);
+ }
+ return false;//Should never reach this
+ }
+ virtual void OnEnd()
+ {
+ }
+
+ BYTE HexChar(char ch)
+ {
+ if (ch >= '0' && ch <= '9')
+ return (UINT)(ch - '0');
+ else if (ch >= 'A' && ch <= 'F')
+ return (UINT)(ch - 'A') + 10;
+ else if (ch >= 'a' && ch <= 'f')
+ return (UINT)(ch - 'a') + 10;
+ else
+ return 0;
+ }
+
+ bool OnSingleLine(LPCSTR line)
+ {
+ //Parse single line
+
+ wgFile_s fileStatus;
+
+ // file/dir type
+
+ fileStatus.nFlags = 0;
+ if (*line == 'D')
+ fileStatus.nFlags |= WGFF_Directory;
+ else if (*line != 'F')
+ // parse error
+ return false;
+ line += 2;
+
+ // status
+
+ fileStatus.nStatus = WGFS_Unknown;
+ switch (*line)
+ {
+ case 'N': fileStatus.nStatus = WGFS_Normal; break;
+ case 'M': fileStatus.nStatus = WGFS_Modified; break;
+ case 'S': fileStatus.nStatus = WGFS_Staged; break;
+ case 'A': fileStatus.nStatus = WGFS_Added; break;
+ case 'C': fileStatus.nStatus = WGFS_Conflicted; break;
+ case 'D': fileStatus.nStatus = WGFS_Deleted; break;
+ case 'I': fileStatus.nStatus = WGFS_Ignored; break;
+ case 'U': fileStatus.nStatus = WGFS_Unversioned; break;
+ case 'E': fileStatus.nStatus = WGFS_Empty; break;
+ case '?': fileStatus.nStatus = WGFS_Unknown; break;
+ default:
+ // parse error
+ return false;
+ }
+ line += 2;
+
+ // file sha1
+
+ BYTE sha1[20];
+ fileStatus.sha1 = NULL;
+ if ( !(fileStatus.nFlags & WGFF_Directory) )
+ {
+ for (int i=0; i<20; i++)
+ {
+ sha1[i] = (HexChar(line[0])<<4)&0xF0;
+ sha1[i] |= HexChar(line[1])&0xF;
+
+ line += 2;
+ }
+
+ line++;
+ }
+
+ // filename
+ int len = strlen(line);
+ if (len && len < 2048)
+ {
+ WCHAR *buf = (WCHAR*)alloca((len*4+2)*sizeof(WCHAR));
+ *buf = 0;
+ MultiByteToWideChar(CP_ACP, 0, line, len+1, buf, len*4+1);
+ fileStatus.sFileName = buf;
+
+ if (*buf && (*m_pEnumCb)(&fileStatus,m_pUserData))
+ return false;
+ }
+
+ return true;
+ }
+};
+
+BOOL CGit::EnumFiles(const TCHAR *pszProjectPath, const TCHAR *pszSubPath, unsigned int nFlags, WGENUMFILECB *pEnumCb, void *pUserData)
+{
+ if(!pszProjectPath || *pszProjectPath=='\0')
+ return FALSE;
+
+ CGitCall_EnumFiles W_GitCall(pszProjectPath,pszSubPath,nFlags,pEnumCb,pUserData);
+ CString cmd;
+
+/* char W_szToDir[MAX_PATH];
+ strncpy(W_szToDir,pszProjectPath,sizeof(W_szToDir)-1);
+ if(W_szToDir[strlen(W_szToDir)-1]!='\\')
+ strncat(W_szToDir,"\\",sizeof(W_szToDir)-1);
+
+ SetCurrentDirectoryA(W_szToDir);
+ GetCurrentDirectoryA(sizeof(W_szToDir)-1,W_szToDir);
+*/
+ SetCurrentDir(pszProjectPath);
+
+ CString sMode;
+ if (nFlags)
+ {
+ if (nFlags & WGEFF_NoRecurse) sMode += _T("r");
+ if (nFlags & WGEFF_FullPath) sMode += _T("f");
+ if (nFlags & WGEFF_DirStatusDelta) sMode += _T("d");
+ if (nFlags & WGEFF_DirStatusAll) sMode += _T("D");
+ if (nFlags & WGEFF_EmptyAsNormal) sMode += _T("e");
+ if (nFlags & WGEFF_SingleFile) sMode += _T("s");
+ }
+ else
+ {
+ sMode = _T("-");
+ }
+
+ // NOTE: there seems to be some issue with msys based app receiving backslash on commandline, at least
+ // if followed by " like for example 'igit "C:\"', the commandline igit receives is 'igit.exe C:" status' with
+ // the 'C:" status' part as a single arg, Maybe it uses unix style processing. In order to avoid this just
+ // use forward slashes for supplied project and sub paths
+
+ CString sProjectPath = pszProjectPath;
+ sProjectPath.Replace(_T('\\'), _T('/'));
+
+ if (pszSubPath)
+ {
+ CString sSubPath = pszSubPath;
+ sSubPath.Replace(_T('\\'), _T('/'));
+
+ cmd.Format(_T("igit.exe \"%s\" status %s \"%s\""), sProjectPath, sMode, sSubPath);
+ }
+ else
+ {
+ cmd.Format(_T("igit.exe \"%s\" status %s"), sProjectPath, sMode);
+ }
+
+ //OutputDebugStringA("---");OutputDebugStringW(cmd);OutputDebugStringA("\r\n");
+
+ W_GitCall.SetCmd(cmd);
+ // NOTE: should igit get added as a part of msysgit then use below line instead of the above one
+ //W_GitCall.SetCmd(CGit::ms_LastMsysGitDir + cmd);
+
+ if ( Run(&W_GitCall) )
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL CGit::CheckCleanWorkTree()
+{
+ CString out;
+ CString cmd;
+ cmd=_T("git.exe rev-parse --verify HEAD");
+
+ if(g_Git.Run(cmd,&out,CP_UTF8))
+ return FALSE;
+
+ cmd=_T("git.exe update-index --ignore-submodules --refresh");
+ if(g_Git.Run(cmd,&out,CP_UTF8))
+ return FALSE;
+
+ cmd=_T("git.exe diff-files --quiet --ignore-submodules");
+ if(g_Git.Run(cmd,&out,CP_UTF8))
+ return FALSE;
+
+ cmd=_T("git diff-index --cached --quiet HEAD --ignore-submodules");
+ if(g_Git.Run(cmd,&out,CP_UTF8))
+ return FALSE;
+
+ return TRUE;
+}
+int CGit::Revert(CTGitPathList &list,bool keep)
+{
+ int ret;
+ for(int i=0;i<list.GetCount();i++)
+ {
+ ret = Revert((CTGitPath&)list[i],keep);
+ if(ret)
+ return ret;
+ }
+ return 0;
+}
+int CGit::Revert(CTGitPath &path,bool keep)
+{
+ CString cmd, out;
+ if(path.m_Action & CTGitPath::LOGACTIONS_ADDED)
+ { //To init git repository, there are not HEAD, so we can use git reset command
+ cmd.Format(_T("git.exe rm --cached -- \"%s\""),path.GetGitPathString());
+
+ if(g_Git.Run(cmd,&out,CP_ACP))
+ return -1;
+ }
+ else if(path.m_Action & CTGitPath::LOGACTIONS_REPLACED )
+ {
+ cmd.Format(_T("git.exe mv -- \"%s\" \"%s\""),path.GetGitPathString(),path.GetGitOldPathString());
+ if(g_Git.Run(cmd,&out,CP_ACP))
+ return -1;
+
+ cmd.Format(_T("git.exe checkout HEAD -f -- \"%s\""),path.GetGitOldPathString());
+ if(g_Git.Run(cmd,&out,CP_ACP))
+ return -1;
+ }
+ else
+ {
+ cmd.Format(_T("git.exe checkout HEAD -f -- \"%s\""),path.GetGitPathString());
+ if(g_Git.Run(cmd,&out,CP_ACP))
+ return -1;
+ }
+ return 0;
+}
+
+int CGit::ListConflictFile(CTGitPathList &list,CTGitPath *path)
+{
+ BYTE_VECTOR vector;
+
+ CString cmd;
+ if(path)
+ cmd.Format(_T("git.exe ls-files -u -t -z -- \"%s\""),path->GetGitPathString());
+ else
+ cmd=_T("git.exe ls-files -u -t -z");
+
+ if(g_Git.Run(cmd,&vector))
+ {
+ return -1;
+ }
+
+ list.ParserFromLsFile(vector);
+
+ return 0;
+}
+
+bool CGit::IsFastForward(CString &from, CString &to)
+{
+ CString base,hash;
+ CString cmd;
+ cmd.Format(_T("git.exe merge-base %s %s"), to,from);
+
+ if(g_Git.Run(cmd,&base,CP_ACP))
+ {
+ //CMessageBox::Show(NULL,base,_T("TortoiseGit"),MB_OK|MB_ICONERROR);
+ return false;
+ }
+ base=base.Left(40);
+
+ hash=g_Git.GetHash(from);
+
+ hash=hash.Left(40);
+
+ return hash == base;
+}
+
+unsigned int CGit::Hash2int(CString &hash)
+{
+ int ret=0;
+ for(int i=0;i<8;i++)
+ {
+ ret =ret <<4;
+ if(hash[i]>=_T('a'))
+ ret |= (hash[i]-_T('a')+10)&0xFF;
+ else if(hash[i]>=_T('A'))
+ ret |= (hash[i]-_T('A')+10)&0xFF;
+ else
+ ret |= (hash[i]-_T('0'))&0xFF;
+
+ }
+ return ret;
}
\ No newline at end of file