small cleanup
[TortoiseGit.git] / src / Git / Git.cpp
blobf26e709deb2541ac4d520d90023ad6a9b9a3e806
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2011 - TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "StdAfx.h"
21 #include "Git.h"
22 #include "atlconv.h"
23 #include "GitRev.h"
24 #include "registry.h"
25 #include "GitConfig.h"
26 #include <map>
27 #include "UnicodeUtils.h"
28 #include "gitdll.h"
29 #include <fstream>
31 int CGit::m_LogEncode=CP_UTF8;
32 typedef CComCritSecLock<CComCriticalSection> CAutoLocker;
34 static LPTSTR nextpath(LPCTSTR src, LPTSTR dst, UINT maxlen)
36 LPCTSTR orgsrc;
38 while (*src == _T(';'))
39 src++;
41 orgsrc = src;
43 if (!--maxlen)
44 goto nullterm;
46 while (*src && *src != _T(';'))
48 if (*src != _T('"'))
50 *dst++ = *src++;
51 if (!--maxlen)
53 orgsrc = src;
54 goto nullterm;
57 else
59 src++;
60 while (*src && *src != _T('"'))
62 *dst++ = *src++;
63 if (!--maxlen)
65 orgsrc = src;
66 goto nullterm;
70 if (*src)
71 src++;
75 while (*src == _T(';'))
76 src++;
78 nullterm:
80 *dst = 0;
82 return (orgsrc != src) ? (LPTSTR)src : NULL;
85 static inline BOOL FileExists(LPCTSTR lpszFileName)
87 struct _stat st;
88 return _tstat(lpszFileName, &st) == 0;
91 static BOOL FindGitPath()
93 size_t size;
94 _tgetenv_s(&size, NULL, 0, _T("PATH"));
96 if (!size)
98 return FALSE;
101 TCHAR *env = (TCHAR*)alloca(size * sizeof(TCHAR));
102 _tgetenv_s(&size, env, size, _T("PATH"));
104 TCHAR buf[_MAX_PATH];
106 // search in all paths defined in PATH
107 while ((env = nextpath(env, buf, _MAX_PATH-1)) && *buf)
109 TCHAR *pfin = buf + _tcslen(buf)-1;
111 // ensure trailing slash
112 if (*pfin != _T('/') && *pfin != _T('\\'))
113 _tcscpy(++pfin, _T("\\"));
115 const int len = _tcslen(buf);
117 if ((len + 7) < _MAX_PATH)
118 _tcscpy(pfin+1, _T("git.exe"));
119 else
120 break;
122 if ( FileExists(buf) )
124 // dir found
125 pfin[1] = 0;
126 CGit::ms_LastMsysGitDir = buf;
127 return TRUE;
131 return FALSE;
135 #define MAX_DIRBUFFER 1000
136 #define CALL_OUTPUT_READ_CHUNK_SIZE 1024
138 CString CGit::ms_LastMsysGitDir;
139 CGit g_Git;
142 CGit::CGit(void)
144 GetCurrentDirectory(MAX_DIRBUFFER,m_CurrentDir.GetBuffer(MAX_DIRBUFFER));
145 m_CurrentDir.ReleaseBuffer();
146 m_IsGitDllInited = false;
147 m_GitDiff=0;
148 m_GitSimpleListDiff=0;
149 m_IsUseGitDLL = !!CRegDWORD(_T("Software\\TortoiseGit\\UsingGitDLL"),1);
150 this->m_bInitialized =false;
151 CheckMsysGitDir();
152 m_critGitDllSec.Init();
155 CGit::~CGit(void)
157 if(this->m_GitDiff)
159 git_close_diff(m_GitDiff);
160 m_GitDiff=0;
162 if(this->m_GitSimpleListDiff)
164 git_close_diff(m_GitSimpleListDiff);
165 m_GitSimpleListDiff=0;
169 bool CGit::IsBranchNameValid(CString branchname)
171 if (branchname.IsEmpty())
172 return false;
174 for(int i=0; i < branchname.GetLength(); i++)
176 TCHAR c = branchname.GetAt(i);
177 if (c <= ' ' || c == '~' || c == '^' || c == ':' || c == '\\' || c == '?' || c == '[')
178 return false;
181 if (branchname.Find(L".") == 0 || branchname.Find(L"/.") >= 0 || branchname.Find(L"..") >= 0 || branchname.Find(L"@{") >= 0 || branchname.ReverseFind('*') >= 0)
182 return false;
184 CString reverseBranchname = branchname.MakeReverse();
185 if (branchname.Find(L'.') == 0 || branchname.Find(L'/') == 0 || reverseBranchname.Find(L"kcol.") >= 0)
186 return false;
188 return true;
191 static char g_Buffer[4096];
193 int CGit::RunAsync(CString cmd,PROCESS_INFORMATION *piOut,HANDLE *hReadOut,CString *StdioFile)
195 SECURITY_ATTRIBUTES sa;
196 HANDLE hRead, hWrite;
197 HANDLE hStdioFile = NULL;
199 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
200 sa.lpSecurityDescriptor=NULL;
201 sa.bInheritHandle=TRUE;
202 if(!CreatePipe(&hRead,&hWrite,&sa,0))
204 return GIT_ERROR_OPEN_PIP;
207 if(StdioFile)
209 hStdioFile=CreateFile(*StdioFile,GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,
210 &sa,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
213 STARTUPINFO si;
214 PROCESS_INFORMATION pi;
215 si.cb=sizeof(STARTUPINFO);
216 GetStartupInfo(&si);
218 si.hStdError=hWrite;
219 if(StdioFile)
220 si.hStdOutput=hStdioFile;
221 else
222 si.hStdOutput=hWrite;
224 si.wShowWindow=SW_HIDE;
225 si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
227 LPTSTR pEnv = m_Environment.size()? &m_Environment[0]: NULL;
228 DWORD dwFlags = pEnv ? CREATE_UNICODE_ENVIRONMENT : 0;
230 //DETACHED_PROCESS make ssh recognize that it has no console to launch askpass to input password.
231 dwFlags |= DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP;
233 memset(&this->m_CurrentGitPi,0,sizeof(PROCESS_INFORMATION));
234 memset(&pi, 0, sizeof(PROCESS_INFORMATION));
236 if(cmd.Find(_T("git")) == 0)
238 int firstSpace = cmd.Find(_T(" "));
239 if (firstSpace > 0)
240 cmd = _T('"')+CGit::ms_LastMsysGitDir+_T("\\")+ cmd.Left(firstSpace) + _T('"')+ cmd.Mid(firstSpace);
241 else
242 cmd=_T('"')+CGit::ms_LastMsysGitDir+_T("\\")+cmd+_T('"');
245 if(!CreateProcess(NULL,(LPWSTR)cmd.GetString(), NULL,NULL,TRUE,dwFlags,pEnv,(LPWSTR)m_CurrentDir.GetString(),&si,&pi))
247 LPVOID lpMsgBuf;
248 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
249 NULL,GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
250 (LPTSTR)&lpMsgBuf,
251 0,NULL);
252 return GIT_ERROR_CREATE_PROCESS;
255 m_CurrentGitPi = pi;
257 CloseHandle(hWrite);
258 if(piOut)
259 *piOut=pi;
260 if(hReadOut)
261 *hReadOut=hRead;
263 return 0;
266 //Must use sperate function to convert ANSI str to union code string
267 //Becuase A2W use stack as internal convert buffer.
268 void CGit::StringAppend(CString *str,BYTE *p,int code,int length)
270 //USES_CONVERSION;
271 //str->Append(A2W_CP((LPCSTR)p,code));
272 if(str == NULL)
273 return ;
275 WCHAR * buf;
277 int len ;
278 if(length<0)
279 len= strlen((const char*)p);
280 else
281 len=length;
282 //if (len==0)
283 // return ;
284 //buf = new WCHAR[len*4 + 1];
285 buf = str->GetBuffer(len*4+1+str->GetLength())+str->GetLength();
286 SecureZeroMemory(buf, (len*4 + 1)*sizeof(WCHAR));
287 MultiByteToWideChar(code, 0, (LPCSTR)p, len, buf, len*4);
288 str->ReleaseBuffer();
289 //str->Append(buf);
290 //delete buf;
292 BOOL CGit::IsInitRepos()
294 CString cmdout;
295 cmdout.Empty();
296 if(g_Git.Run(_T("git.exe rev-parse --revs-only HEAD"),&cmdout,CP_UTF8))
298 // CMessageBox::Show(NULL,cmdout,_T("TortoiseGit"),MB_OK);
299 return TRUE;
301 if(cmdout.IsEmpty())
302 return TRUE;
304 return FALSE;
306 int CGit::Run(CGitCall* pcall)
308 PROCESS_INFORMATION pi;
309 HANDLE hRead;
310 if(RunAsync(pcall->GetCmd(),&pi,&hRead))
311 return GIT_ERROR_CREATE_PROCESS;
313 DWORD readnumber;
314 BYTE data[CALL_OUTPUT_READ_CHUNK_SIZE];
315 bool bAborted=false;
316 while(ReadFile(hRead,data,CALL_OUTPUT_READ_CHUNK_SIZE,&readnumber,NULL))
318 // TODO: when OnOutputData() returns 'true', abort git-command. Send CTRL-C signal?
319 if(!bAborted)//For now, flush output when command aborted.
320 if(pcall->OnOutputData(data,readnumber))
321 bAborted=true;
323 if(!bAborted)
324 pcall->OnEnd();
326 CloseHandle(pi.hThread);
328 WaitForSingleObject(pi.hProcess, INFINITE);
329 DWORD exitcode =0;
331 if(!GetExitCodeProcess(pi.hProcess,&exitcode))
333 return GIT_ERROR_GET_EXIT_CODE;
336 CloseHandle(pi.hProcess);
338 CloseHandle(hRead);
339 return exitcode;
341 class CGitCall_ByteVector : public CGitCall
343 public:
344 CGitCall_ByteVector(CString cmd,BYTE_VECTOR* pvector):CGitCall(cmd),m_pvector(pvector){}
345 virtual bool OnOutputData(const BYTE* data, size_t size)
347 size_t oldsize=m_pvector->size();
348 m_pvector->resize(m_pvector->size()+size);
349 memcpy(&*(m_pvector->begin()+oldsize),data,size);
350 return false;
352 BYTE_VECTOR* m_pvector;
355 int CGit::Run(CString cmd,BYTE_VECTOR *vector)
357 CGitCall_ByteVector call(cmd,vector);
358 return Run(&call);
360 int CGit::Run(CString cmd, CString* output,int code)
362 BYTE_VECTOR vector;
363 int ret;
364 ret=Run(cmd,&vector);
366 vector.push_back(0);
368 StringAppend(output,&(vector[0]),code);
369 return ret;
372 CString CGit::GetUserName(void)
374 return GetConfigValue(L"user.name", this->GetGitEncode(L"i18n.commitencoding"));
376 CString CGit::GetUserEmail(void)
378 return GetConfigValue(L"user.email");
381 CString CGit::GetConfigValue(CString name,int encoding, CString *GitPath, BOOL RemoveCR)
383 CString configValue;
384 int start = 0;
385 if(this->m_IsUseGitDLL)
387 CString *git_path=NULL;
391 CTGitPath path;
393 CheckAndInitDll();
394 git_path = GitPath;
396 }catch(...)
399 CStringA key, value;
400 key = CUnicodeUtils::GetMulti(name, encoding);
401 CStringA p;
402 if(git_path)
403 p=CUnicodeUtils::GetMulti(*GitPath,CP_ACP);
405 if(git_get_config(key.GetBuffer(), value.GetBufferSetLength(4096), 4096, p.GetBuffer()))
406 return CString();
407 else
409 g_Git.StringAppend(&configValue,(BYTE*)value.GetBuffer(),encoding);
410 if(RemoveCR)
411 return configValue.Tokenize(_T("\n"),start);
412 return configValue;
415 }else
417 CString cmd;
418 cmd.Format(L"git.exe config %s", name);
419 Run(cmd,&configValue,encoding);
420 if(RemoveCR)
421 return configValue.Tokenize(_T("\n"),start);
422 return configValue;
426 int CGit::SetConfigValue(CString key, CString value, CONFIG_TYPE type, int encoding, CString *GitPath)
428 if(this->m_IsUseGitDLL)
432 CheckAndInitDll();
434 }catch(...)
437 CStringA keya, valuea;
438 keya = CUnicodeUtils::GetMulti(key, CP_UTF8);
439 valuea = CUnicodeUtils::GetMulti(value, encoding);
440 CStringA p;
441 if(GitPath)
442 p=CUnicodeUtils::GetMulti(*GitPath,CP_ACP);
444 return get_set_config(keya.GetBuffer(), valuea.GetBuffer(), type, p.GetBuffer());
446 }else
448 CString cmd;
449 CString option;
450 switch(type)
452 case CONFIG_GLOBAL:
453 option = _T("--global");
454 break;
455 case CONFIG_SYSTEM:
456 option = _T("--system");
457 break;
458 default:
459 break;
461 cmd.Format(_T("git.exe config %s %s \"%s\""), option, key, value);
462 CString out;
463 if(Run(cmd,&out,encoding))
465 return -1;
468 return 0;
472 CString CGit::GetCurrentBranch(void)
474 CString output;
475 //Run(_T("git.exe branch"),&branch);
477 if(this->GetCurrentBranchFromFile(this->m_CurrentDir,output))
479 return _T("(no branch)");
480 }else
481 return output;
485 CString CGit::GetSymbolicRef(const wchar_t* symbolicRefName, bool bStripRefsHeads)
487 CString refName;
488 if(this->m_IsUseGitDLL)
490 unsigned char sha1[20];
491 int flag;
493 const char *refs_heads_master = git_resolve_ref(CUnicodeUtils::GetUTF8(CString(symbolicRefName)), sha1, 0, &flag);
494 if(refs_heads_master && (flag&REF_ISSYMREF))
496 g_Git.StringAppend(&refName,(BYTE*)refs_heads_master);
497 if(bStripRefsHeads)
498 refName = StripRefName(refName);
501 }else
503 CString cmd;
504 cmd.Format(L"git symbolic-ref %s", symbolicRefName);
505 if(Run(cmd, &refName, CP_UTF8) != 0)
506 return CString();//Error
507 int iStart = 0;
508 refName = refName.Tokenize(L"\n", iStart);
509 if(bStripRefsHeads)
510 refName = StripRefName(refName);
512 return refName;
515 CString CGit::GetFullRefName(CString shortRefName)
517 CString refName;
518 CString cmd;
519 cmd.Format(L"git rev-parse --symbolic-full-name %s", shortRefName);
520 if(Run(cmd, &refName, CP_UTF8) != 0)
521 return CString();//Error
522 int iStart = 0;
523 return refName.Tokenize(L"\n", iStart);
526 CString CGit::StripRefName(CString refName)
528 if(wcsncmp(refName, L"refs/heads/", 11) == 0)
529 refName = refName.Mid(11);
530 else if(wcsncmp(refName, L"refs/", 5) == 0)
531 refName = refName.Mid(5);
532 int start =0;
533 return refName.Tokenize(_T("\n"),start);
536 int CGit::GetCurrentBranchFromFile(const CString &sProjectRoot, CString &sBranchOut)
538 // read current branch name like git-gui does, by parsing the .git/HEAD file directly
540 if ( sProjectRoot.IsEmpty() )
541 return -1;
543 CString sHeadFile = sProjectRoot + _T("\\") + g_GitAdminDir.GetAdminDirName() + _T("\\HEAD");
545 FILE *pFile;
546 _tfopen_s(&pFile, sHeadFile.GetString(), _T("r"));
548 if (!pFile)
550 return -1;
553 char s[256] = {0};
554 fgets(s, sizeof(s), pFile);
556 fclose(pFile);
558 const char *pfx = "ref: refs/heads/";
559 const int len = 16;//strlen(pfx)
561 if ( !strncmp(s, pfx, len) )
563 //# We're on a branch. It might not exist. But
564 //# HEAD looks good enough to be a branch.
565 sBranchOut = s + len;
566 sBranchOut.TrimRight(_T(" \r\n\t"));
568 if ( sBranchOut.IsEmpty() )
569 return -1;
571 else
573 //# Assume this is a detached head.
574 sBranchOut = "HEAD";
576 return 1;
579 return 0;
582 int CGit::BuildOutputFormat(CString &format,bool IsFull)
584 CString log;
585 log.Format(_T("#<%c>%%x00"),LOG_REV_ITEM_BEGIN);
586 format += log;
587 if(IsFull)
589 log.Format(_T("#<%c>%%an%%x00"),LOG_REV_AUTHOR_NAME);
590 format += log;
591 log.Format(_T("#<%c>%%ae%%x00"),LOG_REV_AUTHOR_EMAIL);
592 format += log;
593 log.Format(_T("#<%c>%%ai%%x00"),LOG_REV_AUTHOR_DATE);
594 format += log;
595 log.Format(_T("#<%c>%%cn%%x00"),LOG_REV_COMMIT_NAME);
596 format += log;
597 log.Format(_T("#<%c>%%ce%%x00"),LOG_REV_COMMIT_EMAIL);
598 format += log;
599 log.Format(_T("#<%c>%%ci%%x00"),LOG_REV_COMMIT_DATE);
600 format += log;
601 log.Format(_T("#<%c>%%b%%x00"),LOG_REV_COMMIT_BODY);
602 format += log;
605 log.Format(_T("#<%c>%%m%%H%%x00"),LOG_REV_COMMIT_HASH);
606 format += log;
607 log.Format(_T("#<%c>%%P%%x00"),LOG_REV_COMMIT_PARENT);
608 format += log;
609 log.Format(_T("#<%c>%%s%%x00"),LOG_REV_COMMIT_SUBJECT);
610 format += log;
612 if(IsFull)
614 log.Format(_T("#<%c>%%x00"),LOG_REV_COMMIT_FILE);
615 format += log;
617 return 0;
620 int CGit::GetLog(BYTE_VECTOR& logOut, const CString &hash, CTGitPath *path ,int count,int mask,CString *from,CString *to)
622 CGitCall_ByteVector gitCall(CString(),&logOut);
623 return GetLog(&gitCall,hash,path,count,mask,from,to);
626 CString CGit::GetLogCmd( const CString &hash, CTGitPath *path, int count, int mask,CString *from,CString *to,bool paramonly,
627 CFilterData *Filter)
629 CString cmd;
630 CString num;
631 CString since;
633 CString file;
635 if(path)
636 file.Format(_T(" -- \"%s\""),path->GetGitPathString());
638 if(count>0)
639 num.Format(_T("-n%d"),count);
641 CString param;
643 if(mask& LOG_INFO_STAT )
644 param += _T(" --numstat ");
645 if(mask& LOG_INFO_FILESTATE)
646 param += _T(" --raw ");
648 if(mask& LOG_INFO_FULLHISTORY)
649 param += _T(" --full-history ");
651 if(mask& LOG_INFO_BOUNDARY)
652 param += _T(" --left-right --boundary ");
654 if(mask& CGit::LOG_INFO_ALL_BRANCH)
655 param += _T(" --all ");
657 if(mask& CGit::LOG_INFO_DETECT_COPYRENAME)
658 param += _T(" -C ");
660 if(mask& CGit::LOG_INFO_DETECT_RENAME )
661 param += _T(" -M ");
663 if(mask& CGit::LOG_INFO_FIRST_PARENT )
664 param += _T(" --first-parent ");
666 if(mask& CGit::LOG_INFO_NO_MERGE )
667 param += _T(" --no-merges ");
669 if(mask& CGit::LOG_INFO_FOLLOW)
670 param += _T(" --follow ");
672 if(mask& CGit::LOG_INFO_SHOW_MERGEDFILE)
673 param += _T(" -c ");
675 if(mask& CGit::LOG_INFO_FULL_DIFF)
676 param += _T(" --full-diff ");
678 if(from != NULL && to != NULL)
680 CString range;
681 range.Format(_T(" %s..%s "),FixBranchName(*from),FixBranchName(*to));
682 param += range;
684 param+=hash;
686 CString st1,st2;
688 if( Filter && (Filter->m_From != -1))
690 st1.Format(_T(" --max-age=%I64u "), Filter->m_From);
691 param += st1;
694 if( Filter && (Filter->m_To != -1))
696 st2.Format(_T(" --min-age=%I64u "), Filter->m_To);
697 param += st2;
700 bool isgrep = false;
701 if( Filter && (!Filter->m_Author.IsEmpty()))
703 st1.Format(_T(" --author=\"%s\"" ),Filter->m_Author);
704 param += st1;
705 isgrep = true;
708 if( Filter && (!Filter->m_Committer.IsEmpty()))
710 st1.Format(_T(" --committer=\"%s\"" ),Filter->m_Author);
711 param += st1;
712 isgrep = true;
715 if( Filter && (!Filter->m_MessageFilter.IsEmpty()))
717 st1.Format(_T(" --grep=\"%s\"" ),Filter->m_MessageFilter);
718 param += st1;
719 isgrep = true;
722 if(Filter && isgrep)
724 if(!Filter->m_IsRegex)
725 param+=_T(" --fixed-strings ");
727 param += _T(" --regexp-ignore-case --extended-regexp ");
730 if(paramonly) //tgit.dll.Git.cpp:setup_revisions() only looks at args[1] and greater. To account for this, pass a dummy parameter in the 0th place
731 cmd.Format(_T("--ignore-this-parameter %s -z %s --parents "), num, param);
732 else
734 CString log;
735 BuildOutputFormat(log,!(mask&CGit::LOG_INFO_ONLY_HASH));
736 cmd.Format(_T("git.exe log %s -z %s --parents --pretty=format:\"%s\""),
737 num,param,log);
740 cmd += file;
742 return cmd;
744 //int CGit::GetLog(CGitCall* pgitCall, const CString &hash, CTGitPath *path ,int count,int mask)
745 int CGit::GetLog(CGitCall* pgitCall, const CString &hash, CTGitPath *path, int count, int mask,CString *from,CString *to)
747 pgitCall->SetCmd( GetLogCmd(hash,path,count,mask,from,to) );
749 return Run(pgitCall);
750 // return Run(cmd,&logOut);
753 #define BUFSIZE 512
754 void GetTempPath(CString &path)
756 TCHAR lpPathBuffer[BUFSIZE];
757 DWORD dwRetVal;
758 DWORD dwBufSize=BUFSIZE;
759 dwRetVal = GetTempPath(dwBufSize, // length of the buffer
760 lpPathBuffer); // buffer for path
761 if (dwRetVal > dwBufSize || (dwRetVal == 0))
763 path=_T("");
765 path.Format(_T("%s"),lpPathBuffer);
767 CString GetTempFile()
769 TCHAR lpPathBuffer[BUFSIZE];
770 DWORD dwRetVal;
771 DWORD dwBufSize=BUFSIZE;
772 TCHAR szTempName[BUFSIZE];
773 UINT uRetVal;
775 dwRetVal = GetTempPath(dwBufSize, // length of the buffer
776 lpPathBuffer); // buffer for path
777 if (dwRetVal > dwBufSize || (dwRetVal == 0))
779 return _T("");
781 // Create a temporary file.
782 uRetVal = GetTempFileName(lpPathBuffer, // directory for tmp files
783 TEXT("Patch"), // temp file name prefix
784 0, // create unique name
785 szTempName); // buffer for name
788 if (uRetVal == 0)
790 return _T("");
793 return CString(szTempName);
797 int CGit::RunLogFile(CString cmd,const CString &filename)
799 STARTUPINFO si;
800 PROCESS_INFORMATION pi;
801 si.cb=sizeof(STARTUPINFO);
802 GetStartupInfo(&si);
804 SECURITY_ATTRIBUTES psa={sizeof(psa),NULL,TRUE};;
805 psa.bInheritHandle=TRUE;
807 HANDLE houtfile=CreateFile(filename,GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,
808 &psa,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
810 si.wShowWindow = SW_HIDE;
811 si.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
812 si.hStdOutput = houtfile;
814 LPTSTR pEnv = m_Environment.size()? &m_Environment[0]: NULL;
815 DWORD dwFlags = pEnv ? CREATE_UNICODE_ENVIRONMENT : 0;
817 if(cmd.Find(_T("git")) == 0)
818 cmd=CGit::ms_LastMsysGitDir+_T("\\")+cmd;
820 if(!CreateProcess(NULL,(LPWSTR)cmd.GetString(), NULL,NULL,TRUE,dwFlags,pEnv,(LPWSTR)m_CurrentDir.GetString(),&si,&pi))
822 LPVOID lpMsgBuf;
823 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
824 NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
825 (LPTSTR)&lpMsgBuf,
826 0,NULL);
827 return GIT_ERROR_CREATE_PROCESS;
830 WaitForSingleObject(pi.hProcess,INFINITE);
832 CloseHandle(pi.hThread);
833 CloseHandle(pi.hProcess);
834 CloseHandle(houtfile);
835 return GIT_SUCCESS;
836 // return 0;
839 CGitHash CGit::GetHash(TCHAR* friendname)
841 if(this->m_IsUseGitDLL)
843 this->CheckAndInitDll();
845 CGitHash hash;
846 CStringA ref;
847 ref = CUnicodeUtils::GetMulti(FixBranchName(friendname),CP_ACP);
850 git_get_sha1(ref, hash.m_hash);
852 }catch(...)
855 return hash;
857 }else
859 CString cmd;
860 CString out;
861 cmd.Format(_T("git.exe rev-parse %s" ),FixBranchName(friendname));
862 Run(cmd,&out,CP_UTF8);
863 // int pos=out.ReverseFind(_T('\n'));
864 out.FindOneOf(_T("\r\n"));
865 return CGitHash(out);
869 int CGit::GetInitAddList(CTGitPathList &outputlist)
871 CString cmd;
872 BYTE_VECTOR cmdout;
874 cmd=_T("git.exe ls-files -s -t -z");
875 outputlist.Clear();
876 if(g_Git.Run(cmd,&cmdout))
877 return -1;
879 outputlist.ParserFromLsFile(cmdout);
880 for(int i=0;i<outputlist.GetCount();i++)
881 ((int)outputlist[i].m_Action) = CTGitPath::LOGACTIONS_ADDED;
883 return 0;
885 int CGit::GetCommitDiffList(const CString &rev1,const CString &rev2,CTGitPathList &outputlist)
887 CString cmd;
889 if(rev1 == GIT_REV_ZERO || rev2 == GIT_REV_ZERO)
891 //rev1=+_T("");
892 if(rev1 == GIT_REV_ZERO)
893 cmd.Format(_T("git.exe diff -r --raw -C -M --numstat -z %s"),rev2);
894 else
895 cmd.Format(_T("git.exe diff -r -R --raw -C -M --numstat -z %s"),rev1);
896 }else
898 cmd.Format(_T("git.exe diff-tree -r --raw -C -M --numstat -z %s %s"),rev2,rev1);
901 BYTE_VECTOR out;
902 if(g_Git.Run(cmd,&out))
903 return -1;
905 outputlist.ParserFromLog(out);
907 return 0;
910 int addto_list_each_ref_fn(const char *refname, const unsigned char * /*sha1*/, int /*flags*/, void *cb_data)
912 STRING_VECTOR *list = (STRING_VECTOR*)cb_data;
913 CString str;
914 g_Git.StringAppend(&str,(BYTE*)refname,CP_ACP);
915 list->push_back(str);
916 return 0;
919 int CGit::GetTagList(STRING_VECTOR &list)
921 int ret;
923 if(this->m_IsUseGitDLL)
925 return git_for_each_ref_in("refs/tags/",addto_list_each_ref_fn, &list);
927 }else
929 CString cmd,output;
930 cmd=_T("git.exe tag -l");
931 int i=0;
932 ret=g_Git.Run(cmd,&output,CP_UTF8);
933 if(!ret)
935 int pos=0;
936 CString one;
937 while( pos>=0 )
939 i++;
940 one=output.Tokenize(_T("\n"),pos);
941 list.push_back(one);
945 return ret;
948 CString CGit::FixBranchName_Mod(CString& branchName)
950 if(branchName == "FETCH_HEAD")
951 branchName = DerefFetchHead();
952 return branchName;
955 CString CGit::FixBranchName(const CString& branchName)
957 CString tempBranchName = branchName;
958 FixBranchName_Mod(tempBranchName);
959 return tempBranchName;
963 CString CGit::DerefFetchHead()
965 using namespace std;
966 ifstream fetchHeadFile((m_CurrentDir + L"\\.git\\FETCH_HEAD").GetString(), ios::in | ios::binary);
967 int forMergeLineCount = 0;
968 string line;
969 string hashToReturn;
970 while(getline(fetchHeadFile, line))
972 //Tokenize this line
973 string::size_type prevPos = 0;
974 string::size_type pos = line.find('\t');
975 if(pos == string::npos) continue; //invalid line
977 string hash = line.substr(0, pos);
978 ++pos; prevPos = pos; pos = line.find('\t', pos); if(pos == string::npos) continue;
980 bool forMerge = pos == prevPos;
981 ++pos; prevPos = pos; pos = line.size(); if(pos == string::npos) continue;
983 string remoteBranch = line.substr(prevPos, pos - prevPos);
985 //Process this line
986 if(forMerge)
988 hashToReturn = hash;
989 ++forMergeLineCount;
990 if(forMergeLineCount > 1)
991 return L""; //More then 1 branch to merge found. Octopus merge needed. Cannot pick single ref from FETCH_HEAD
995 return CUnicodeUtils::GetUnicode(hashToReturn.c_str());
998 int CGit::GetBranchList(STRING_VECTOR &list,int *current,BRANCH_TYPE type)
1000 int ret;
1001 CString cmd,output;
1002 cmd=_T("git.exe branch --no-color");
1004 if((type&BRANCH_ALL) == BRANCH_ALL)
1005 cmd+=_T(" -a");
1006 else if(type&BRANCH_REMOTE)
1007 cmd+=_T(" -r");
1009 int i=0;
1010 ret=g_Git.Run(cmd,&output,CP_UTF8);
1011 if(!ret)
1013 int pos=0;
1014 CString one;
1015 while( pos>=0 )
1017 one=output.Tokenize(_T("\n"),pos);
1018 one.Trim(L" \r\n\t");
1019 if(one.Find(L" -> ") >= 0 || one.IsEmpty())
1020 continue; // skip something like: refs/origin/HEAD -> refs/origin/master
1021 if(one[0] == _T('*'))
1023 if(current)
1024 *current=i;
1025 one = one.Mid(2);
1027 list.push_back(one);
1028 i++;
1032 if(type & BRANCH_FETCH_HEAD && !DerefFetchHead().IsEmpty())
1033 list.push_back(L"FETCH_HEAD");
1035 return ret;
1038 int CGit::GetRemoteList(STRING_VECTOR &list)
1040 int ret;
1041 CString cmd,output;
1042 cmd=_T("git.exe remote");
1043 ret=g_Git.Run(cmd,&output,CP_UTF8);
1044 if(!ret)
1046 int pos=0;
1047 CString one;
1048 while( pos>=0 )
1050 one=output.Tokenize(_T("\n"),pos);
1051 list.push_back(one);
1054 return ret;
1057 int CGit::GetRefList(STRING_VECTOR &list)
1059 int ret;
1060 if(this->m_IsUseGitDLL)
1062 return git_for_each_ref_in("",addto_list_each_ref_fn, &list);
1064 }else
1066 CString cmd,output;
1067 cmd=_T("git.exe show-ref -d");
1068 ret=g_Git.Run(cmd,&output,CP_UTF8);
1069 if(!ret)
1071 int pos=0;
1072 CString one;
1073 while( pos>=0 )
1075 one=output.Tokenize(_T("\n"),pos);
1076 int start=one.Find(_T(" "),0);
1077 if(start>0)
1079 CString name;
1080 name=one.Right(one.GetLength()-start-1);
1081 list.push_back(name);
1086 return ret;
1089 int addto_map_each_ref_fn(const char *refname, const unsigned char *sha1, int /*flags*/, void *cb_data)
1091 MAP_HASH_NAME *map = (MAP_HASH_NAME*)cb_data;
1092 CString str;
1093 g_Git.StringAppend(&str,(BYTE*)refname,CP_ACP);
1094 CGitHash hash((char*)sha1);
1096 (*map)[hash].push_back(str);
1098 if(strncmp(refname, "refs/tags", 9) == 0)
1100 GIT_HASH refhash;
1101 if(!git_deref_tag(sha1, refhash))
1103 (*map)[(char*)refhash].push_back(str+_T("^{}"));
1106 return 0;
1109 int CGit::GetMapHashToFriendName(MAP_HASH_NAME &map)
1111 int ret;
1112 if(this->m_IsUseGitDLL)
1114 return git_for_each_ref_in("",addto_map_each_ref_fn, &map);
1116 }else
1118 CString cmd,output;
1119 cmd=_T("git.exe show-ref -d");
1120 ret=g_Git.Run(cmd,&output,CP_UTF8);
1121 if(!ret)
1123 int pos=0;
1124 CString one;
1125 while( pos>=0 )
1127 one=output.Tokenize(_T("\n"),pos);
1128 int start=one.Find(_T(" "),0);
1129 if(start>0)
1131 CString name;
1132 name=one.Right(one.GetLength()-start-1);
1134 CString hash;
1135 hash=one.Left(start);
1137 map[CGitHash(hash)].push_back(name);
1142 return ret;
1145 BOOL CGit::CheckMsysGitDir()
1147 if (m_bInitialized)
1149 return TRUE;
1152 this->m_Environment.clear();
1153 m_Environment.CopyProcessEnvironment();
1155 TCHAR *oldpath,*home;
1156 size_t homesize,size,httpsize;
1158 // set HOME if not set already
1159 _tgetenv_s(&homesize, NULL, 0, _T("HOME"));
1160 if (!homesize)
1162 if ( (!_tdupenv_s(&home,&size,_T("USERPROFILE"))) && (home != NULL) )
1164 m_Environment.SetEnv(_T("HOME"),home);
1165 free(home);
1167 else
1169 ATLTRACE("CGit::CheckMsysGitDir Unable to SetEnv HOME\n");
1172 CString str;
1174 //set http_proxy
1175 _tgetenv_s(&httpsize, NULL, 0, _T("http_proxy"));
1176 if (!httpsize)
1178 CString regServeraddress_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-host"), _T(""));
1179 CString regServerport_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-port"), _T(""));
1180 CString regUsername_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-username"), _T(""));
1181 CString regPassword_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-password"), _T(""));
1182 CString regTimeout_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-timeout"), _T(""));
1183 CString regExceptions_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-exceptions"), _T(""));
1185 CString http_proxy;
1186 if(!regServeraddress_copy.IsEmpty())
1188 if(regServeraddress_copy.Left(4) != _T("http"))
1189 http_proxy=_T("http://");
1191 if(!regUsername_copy.IsEmpty())
1193 http_proxy += regUsername_copy;
1194 http_proxy += _T(":")+regPassword_copy;
1195 http_proxy += _T("@");
1197 http_proxy+=regServeraddress_copy;
1198 if(!regServerport_copy.IsEmpty())
1200 http_proxy +=_T(":")+regServerport_copy;
1202 m_Environment.SetEnv(_T("http_proxy"),(LPTSTR)http_proxy.GetBuffer());
1205 //setup ssh client
1206 CString sshclient=CRegString(_T("Software\\TortoiseGit\\SSH"));
1208 if(!sshclient.IsEmpty())
1210 m_Environment.SetEnv(_T("GIT_SSH"),sshclient.GetBuffer());
1212 //Setup SVN_SSH
1213 CString ssh=sshclient;
1214 ssh.Replace(_T("/"),_T("\\"));
1215 ssh.Replace(_T("\\"),_T("\\\\"));
1216 ssh=CString(_T("\""))+ssh+_T('\"');
1217 m_Environment.SetEnv(_T("SVN_SSH"),ssh.GetBuffer());
1219 }else
1221 TCHAR sPlink[MAX_PATH];
1222 GetModuleFileName(NULL, sPlink, _countof(sPlink));
1223 LPTSTR ptr = _tcsrchr(sPlink, _T('\\'));
1224 if (ptr) {
1225 _tcscpy(ptr + 1, _T("TortoisePlink.exe"));
1226 m_Environment.SetEnv(_T("GIT_SSH"), sPlink);
1228 //Setup SVN_SSH
1229 CString ssh=sPlink;
1230 ssh.Replace(_T("/"),_T("\\"));
1231 ssh.Replace(_T("\\"),_T("\\\\"));
1232 ssh=CString(_T("\""))+ssh+_T('\"');
1233 m_Environment.SetEnv(_T("SVN_SSH"),ssh.GetBuffer());
1238 TCHAR sAskPass[MAX_PATH];
1239 GetModuleFileName(NULL, sAskPass, _countof(sAskPass));
1240 LPTSTR ptr = _tcsrchr(sAskPass, _T('\\'));
1241 if (ptr)
1243 _tcscpy(ptr + 1, _T("SshAskPass.exe"));
1244 m_Environment.SetEnv(_T("DISPLAY"),_T(":9999"));
1245 m_Environment.SetEnv(_T("SSH_ASKPASS"),sAskPass);
1246 m_Environment.SetEnv(_T("GIT_ASKPASS"),sAskPass);
1250 // add git/bin path to PATH
1252 CRegString msysdir=CRegString(REG_MSYSGIT_PATH,_T(""),FALSE);
1253 str=msysdir;
1254 if(str.IsEmpty())
1256 CRegString msysinstalldir=CRegString(REG_MSYSGIT_INSTALL,_T(""),FALSE,HKEY_LOCAL_MACHINE);
1257 str=msysinstalldir;
1258 if ( !str.IsEmpty() )
1260 str += (str[str.GetLength()-1] != '\\') ? "\\bin" : "bin";
1261 msysdir=str;
1262 CGit::ms_LastMsysGitDir = str;
1263 msysdir.write();
1265 else
1267 // search PATH if git/bin directory is already present
1268 if ( FindGitPath() )
1270 m_bInitialized = TRUE;
1271 return TRUE;
1274 return FALSE;
1276 }else
1278 CGit::ms_LastMsysGitDir = str;
1281 // check for git.exe existance (maybe it was deinstalled in the meantime)
1282 if (!FileExists(CGit::ms_LastMsysGitDir + _T("\\git.exe")))
1283 return FALSE;
1285 //set path
1286 _tdupenv_s(&oldpath,&size,_T("PATH"));
1288 CString path;
1289 path.Format(_T("%s;%s"),oldpath,str + _T(";")+ (CString)CRegString(REG_MSYSGIT_EXTRA_PATH,_T(""),FALSE));
1291 m_Environment.SetEnv(_T("PATH"),path.GetBuffer());
1293 CString str1 = m_Environment.GetEnv(_T("PATH"));
1295 CString sOldPath = oldpath;
1296 free(oldpath);
1298 m_bInitialized = TRUE;
1299 return true;
1302 class CGitCall_EnumFiles : public CGitCall
1304 public:
1305 CGitCall_EnumFiles(const TCHAR *pszProjectPath, const TCHAR *pszSubPath, unsigned int nFlags, WGENUMFILECB *pEnumCb, void *pUserData)
1306 : m_pszProjectPath(pszProjectPath),
1307 m_pszSubPath(pszSubPath),
1308 m_nFlags(nFlags),
1309 m_pEnumCb(pEnumCb),
1310 m_pUserData(pUserData)
1314 typedef std::map<CStringA,char> TStrCharMap;
1316 const TCHAR * m_pszProjectPath;
1317 const TCHAR * m_pszSubPath;
1318 unsigned int m_nFlags;
1319 WGENUMFILECB * m_pEnumCb;
1320 void * m_pUserData;
1322 BYTE_VECTOR m_DataCollector;
1324 virtual bool OnOutputData(const BYTE* data, size_t size)
1326 m_DataCollector.append(data,size);
1327 while(true)
1329 // lines from igit.exe are 0 terminated
1330 int found=m_DataCollector.findData((const BYTE*)"",1);
1331 if(found<0)
1332 return false;
1333 OnSingleLine( (LPCSTR)&*m_DataCollector.begin() );
1334 m_DataCollector.erase(m_DataCollector.begin(), m_DataCollector.begin()+found+1);
1336 return false;//Should never reach this
1338 virtual void OnEnd()
1342 BYTE HexChar(char ch)
1344 if (ch >= '0' && ch <= '9')
1345 return (UINT)(ch - '0');
1346 else if (ch >= 'A' && ch <= 'F')
1347 return (UINT)(ch - 'A') + 10;
1348 else if (ch >= 'a' && ch <= 'f')
1349 return (UINT)(ch - 'a') + 10;
1350 else
1351 return 0;
1354 bool OnSingleLine(LPCSTR line)
1356 //Parse single line
1358 wgFile_s fileStatus;
1360 // file/dir type
1362 fileStatus.nFlags = 0;
1363 if (*line == 'D')
1364 fileStatus.nFlags |= WGFF_Directory;
1365 else if (*line != 'F')
1366 // parse error
1367 return false;
1368 line += 2;
1370 // status
1372 fileStatus.nStatus = WGFS_Unknown;
1373 switch (*line)
1375 case 'N': fileStatus.nStatus = WGFS_Normal; break;
1376 case 'M': fileStatus.nStatus = WGFS_Modified; break;
1377 case 'S': fileStatus.nStatus = WGFS_Staged; break;
1378 case 'A': fileStatus.nStatus = WGFS_Added; break;
1379 case 'C': fileStatus.nStatus = WGFS_Conflicted; break;
1380 case 'D': fileStatus.nStatus = WGFS_Deleted; break;
1381 case 'I': fileStatus.nStatus = WGFS_Ignored; break;
1382 case 'U': fileStatus.nStatus = WGFS_Unversioned; break;
1383 case 'E': fileStatus.nStatus = WGFS_Empty; break;
1384 case '?': fileStatus.nStatus = WGFS_Unknown; break;
1385 default:
1386 // parse error
1387 return false;
1389 line += 2;
1391 // file sha1
1393 BYTE sha1[20];
1394 fileStatus.sha1 = NULL;
1395 if ( !(fileStatus.nFlags & WGFF_Directory) )
1397 for (int i=0; i<20; i++)
1399 sha1[i] = (HexChar(line[0])<<4)&0xF0;
1400 sha1[i] |= HexChar(line[1])&0xF;
1402 line += 2;
1405 line++;
1408 // filename
1409 int len = strlen(line);
1410 if (len && len < 2048)
1412 WCHAR *buf = (WCHAR*)alloca((len*4+2)*sizeof(WCHAR));
1413 *buf = 0;
1414 MultiByteToWideChar(CP_ACP, 0, line, len+1, buf, len*4+1);
1415 fileStatus.sFileName = buf;
1417 if (*buf && (*m_pEnumCb)(&fileStatus,m_pUserData))
1418 return false;
1421 return true;
1425 BOOL CGit::EnumFiles(const TCHAR *pszProjectPath, const TCHAR *pszSubPath, unsigned int nFlags, WGENUMFILECB *pEnumCb, void *pUserData)
1427 if(!pszProjectPath || *pszProjectPath=='\0')
1428 return FALSE;
1430 CGitCall_EnumFiles W_GitCall(pszProjectPath,pszSubPath,nFlags,pEnumCb,pUserData);
1431 CString cmd;
1433 /* char W_szToDir[MAX_PATH];
1434 strncpy(W_szToDir,pszProjectPath,sizeof(W_szToDir)-1);
1435 if(W_szToDir[strlen(W_szToDir)-1]!='\\')
1436 strncat(W_szToDir,"\\",sizeof(W_szToDir)-1);
1438 SetCurrentDirectoryA(W_szToDir);
1439 GetCurrentDirectoryA(sizeof(W_szToDir)-1,W_szToDir);
1441 SetCurrentDir(pszProjectPath);
1443 CString sMode;
1444 if (nFlags)
1446 if (nFlags & WGEFF_NoRecurse) sMode += _T("r");
1447 if (nFlags & WGEFF_FullPath) sMode += _T("f");
1448 if (nFlags & WGEFF_DirStatusDelta) sMode += _T("d");
1449 if (nFlags & WGEFF_DirStatusAll) sMode += _T("D");
1450 if (nFlags & WGEFF_EmptyAsNormal) sMode += _T("e");
1451 if (nFlags & WGEFF_SingleFile) sMode += _T("s");
1453 else
1455 sMode = _T("-");
1458 // NOTE: there seems to be some issue with msys based app receiving backslash on commandline, at least
1459 // if followed by " like for example 'igit "C:\"', the commandline igit receives is 'igit.exe C:" status' with
1460 // the 'C:" status' part as a single arg, Maybe it uses unix style processing. In order to avoid this just
1461 // use forward slashes for supplied project and sub paths
1463 CString sProjectPath = pszProjectPath;
1464 sProjectPath.Replace(_T('\\'), _T('/'));
1466 if (pszSubPath)
1468 CString sSubPath = pszSubPath;
1469 sSubPath.Replace(_T('\\'), _T('/'));
1471 cmd.Format(_T("tgit.exe statusex \"%s\" status %s \"%s\""), sProjectPath, sMode, sSubPath);
1473 else
1475 cmd.Format(_T("tgit.exe statusex \"%s\" status %s"), sProjectPath, sMode);
1478 //OutputDebugStringA("---");OutputDebugStringW(cmd);OutputDebugStringA("\r\n");
1480 W_GitCall.SetCmd(cmd);
1481 // NOTE: should igit get added as a part of msysgit then use below line instead of the above one
1482 //W_GitCall.SetCmd(CGit::ms_LastMsysGitDir + cmd);
1484 if ( Run(&W_GitCall) )
1485 return FALSE;
1487 return TRUE;
1490 BOOL CGit::CheckCleanWorkTree()
1492 CString out;
1493 CString cmd;
1494 cmd=_T("git.exe rev-parse --verify HEAD");
1496 if(g_Git.Run(cmd,&out,CP_UTF8))
1497 return FALSE;
1499 cmd=_T("git.exe update-index --ignore-submodules --refresh");
1500 if(g_Git.Run(cmd,&out,CP_UTF8))
1501 return FALSE;
1503 cmd=_T("git.exe diff-files --quiet --ignore-submodules");
1504 if(g_Git.Run(cmd,&out,CP_UTF8))
1505 return FALSE;
1507 cmd=_T("git diff-index --cached --quiet HEAD --ignore-submodules");
1508 if(g_Git.Run(cmd,&out,CP_UTF8))
1509 return FALSE;
1511 return TRUE;
1513 int CGit::Revert(CString commit, CTGitPathList &list,bool keep)
1515 int ret;
1516 for(int i=0;i<list.GetCount();i++)
1518 ret = Revert(commit, (CTGitPath&)list[i]);
1519 if(ret)
1520 return ret;
1522 return 0;
1524 int CGit::Revert(CString commit, CTGitPath &path)
1526 CString cmd, out;
1528 if(path.m_Action & CTGitPath::LOGACTIONS_REPLACED )
1530 cmd.Format(_T("git.exe mv -- \"%s\" \"%s\""),path.GetGitPathString(),path.GetGitOldPathString());
1531 if(g_Git.Run(cmd,&out,CP_ACP))
1532 return -1;
1534 cmd.Format(_T("git.exe checkout %s -f -- \"%s\""), commit, path.GetGitOldPathString());
1535 if(g_Git.Run(cmd,&out,CP_ACP))
1536 return -1;
1538 }else if(path.m_Action & CTGitPath::LOGACTIONS_ADDED)
1539 { //To init git repository, there are not HEAD, so we can use git reset command
1540 cmd.Format(_T("git.exe rm --cached -- \"%s\""),path.GetGitPathString());
1542 if(g_Git.Run(cmd,&out,CP_ACP))
1543 return -1;
1545 else
1547 cmd.Format(_T("git.exe checkout %s -f -- \"%s\""), commit, path.GetGitPathString());
1548 if(g_Git.Run(cmd,&out,CP_ACP))
1549 return -1;
1551 return 0;
1554 int CGit::ListConflictFile(CTGitPathList &list,CTGitPath *path)
1556 BYTE_VECTOR vector;
1558 CString cmd;
1559 if(path)
1560 cmd.Format(_T("git.exe ls-files -u -t -z -- \"%s\""),path->GetGitPathString());
1561 else
1562 cmd=_T("git.exe ls-files -u -t -z");
1564 if(g_Git.Run(cmd,&vector))
1566 return -1;
1569 list.ParserFromLsFile(vector);
1571 return 0;
1574 bool CGit::IsFastForward(const CString &from, const CString &to)
1576 CString base;
1577 CGitHash basehash,hash;
1578 CString cmd;
1579 cmd.Format(_T("git.exe merge-base %s %s"), FixBranchName(to), FixBranchName(from));
1581 if(g_Git.Run(cmd,&base,CP_ACP))
1583 //CMessageBox::Show(NULL,base,_T("TortoiseGit"),MB_OK|MB_ICONERROR);
1584 return false;
1586 basehash = base;
1588 hash=g_Git.GetHash(from);
1590 return hash == basehash;
1593 unsigned int CGit::Hash2int(CGitHash &hash)
1595 int ret=0;
1596 for(int i=0;i<4;i++)
1598 ret = ret << 8;
1599 ret |= hash.m_hash[i];
1601 return ret;
1604 int CGit::RefreshGitIndex()
1606 if(g_Git.m_IsUseGitDLL)
1610 return git_run_cmd("update-index","update-index -q --refresh");
1612 }catch(...)
1614 return -1;
1617 }else
1619 CString cmd,output;
1620 cmd=_T("git.exe update-index --refresh");
1621 return Run(cmd,&output,CP_ACP);
1625 int CGit::GetOneFile(CString Refname, CTGitPath &path, const CString &outputfile)
1627 if(g_Git.m_IsUseGitDLL)
1631 g_Git.CheckAndInitDll();
1632 CStringA ref, patha, outa;
1633 ref = CUnicodeUtils::GetMulti(Refname,CP_ACP);
1634 patha = CUnicodeUtils::GetMulti(path.GetGitPathString(), CP_ACP);
1635 outa = CUnicodeUtils::GetMulti(outputfile,CP_ACP);
1636 ::DeleteFile(outputfile);
1637 return git_checkout_file((const char*)ref.GetBuffer(),(const char*)patha.GetBuffer(),(const char*)outa.GetBuffer());
1639 }catch(...)
1641 return -1;
1643 }else
1645 CString cmd;
1646 cmd.Format(_T("git.exe cat-file -p %s:\"%s\""), Refname, path.GetGitPathString());
1647 return g_Git.RunLogFile(cmd,outputfile);
1650 void CEnvironment::CopyProcessEnvironment()
1652 TCHAR *p = GetEnvironmentStrings();
1653 while(*p !=0 || *(p+1) !=0)
1654 this->push_back(*p++);
1656 push_back(_T('\0'));
1657 push_back(_T('\0'));
1660 CString CEnvironment::GetEnv(TCHAR *name)
1662 CString str;
1663 for(int i=0;i<size();i++)
1665 str = &(*this)[i];
1666 int start =0;
1667 CString sname = str.Tokenize(_T("="),start);
1668 if(sname.CompareNoCase(name) == 0)
1670 return &(*this)[i+start+1];
1672 i+=str.GetLength();
1674 return _T("");
1677 void CEnvironment::SetEnv(TCHAR *name, TCHAR* value)
1679 unsigned int i;
1680 for( i=0;i<size();i++)
1682 CString str = &(*this)[i];
1683 int start =0;
1684 CString sname = str.Tokenize(_T("="),start);
1685 if(sname.CompareNoCase(name) == 0)
1687 break;
1689 i+=str.GetLength();
1692 if(i == size())
1694 i -= 1; // roll back terminate \0\0
1695 this->push_back(_T('\0'));
1698 CEnvironment::iterator it;
1699 it=this->begin();
1700 it += i;
1702 while(*it && i<size())
1704 this->erase(it);
1705 it=this->begin();
1706 it += i;
1709 while(*name)
1711 this->insert(it,*name++);
1712 i++;
1713 it= begin()+i;
1716 this->insert(it, _T('='));
1717 i++;
1718 it= begin()+i;
1720 while(*value)
1722 this->insert(it,*value++);
1723 i++;
1724 it= begin()+i;
1729 int CGit::GetGitEncode(TCHAR* configkey)
1731 CString str=GetConfigValue(configkey);
1733 if(str.IsEmpty())
1734 return CP_UTF8;
1736 return CUnicodeUtils::GetCPCode(str);
1740 int CGit::GetDiffPath(CTGitPathList *PathList, CGitHash *hash1, CGitHash *hash2, char *arg)
1742 GIT_FILE file=0;
1743 int ret=0;
1744 GIT_DIFF diff=0;
1746 CAutoLocker lock(g_Git.m_critGitDllSec);
1748 if(arg == NULL)
1749 diff = GetGitDiff();
1750 else
1751 git_open_diff(&diff, arg);
1753 if(diff ==NULL)
1754 return -1;
1756 bool isStat = 0;
1757 if(arg == NULL)
1758 isStat = true;
1759 else
1760 isStat = !!strstr(arg, "stat");
1762 int count=0;
1764 if(hash2 == NULL)
1765 ret = git_root_diff(diff, hash1->m_hash, &file, &count,isStat);
1766 else
1767 ret = git_diff(diff,hash2->m_hash,hash1->m_hash,&file,&count,isStat);
1769 if(ret)
1770 return -1;
1772 CTGitPath path;
1773 CString strnewname;
1774 CString stroldname;
1776 for(int j=0;j<count;j++)
1778 path.Reset();
1779 char *newname;
1780 char *oldname;
1782 strnewname.Empty();
1783 stroldname.Empty();
1785 int mode=0,IsBin=0,inc=0,dec=0;
1786 git_get_diff_file(diff,file,j,&newname,&oldname,
1787 &mode,&IsBin,&inc,&dec);
1789 StringAppend(&strnewname,(BYTE*)newname,CP_ACP);
1790 StringAppend(&stroldname,(BYTE*)oldname,CP_ACP);
1792 path.SetFromGit(strnewname,&stroldname);
1793 path.ParserAction((BYTE)mode);
1795 if(IsBin)
1797 path.m_StatAdd=_T("-");
1798 path.m_StatDel=_T("-");
1799 }else
1801 path.m_StatAdd.Format(_T("%d"),inc);
1802 path.m_StatDel.Format(_T("%d"),dec);
1804 PathList->AddPath(path);
1806 git_diff_flush(diff);
1808 if(arg)
1809 git_close_diff(diff);
1811 return 0;