Fixed issue #914: unifiled diff always show wrong changes (base files compare with...
[TortoiseGit.git] / src / Git / Git.cpp
blob5716fc643fee7c995e812a66342df7c43b02e587
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 ((unsigned 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 //setup ssh client
1175 CString sshclient=CRegString(_T("Software\\TortoiseGit\\SSH"));
1177 if(!sshclient.IsEmpty())
1179 m_Environment.SetEnv(_T("GIT_SSH"),sshclient.GetBuffer());
1181 //Setup SVN_SSH
1182 CString ssh=sshclient;
1183 ssh.Replace(_T("/"),_T("\\"));
1184 ssh.Replace(_T("\\"),_T("\\\\"));
1185 ssh=CString(_T("\""))+ssh+_T('\"');
1186 m_Environment.SetEnv(_T("SVN_SSH"),ssh.GetBuffer());
1188 }else
1190 TCHAR sPlink[MAX_PATH];
1191 GetModuleFileName(NULL, sPlink, _countof(sPlink));
1192 LPTSTR ptr = _tcsrchr(sPlink, _T('\\'));
1193 if (ptr) {
1194 _tcscpy(ptr + 1, _T("TortoisePlink.exe"));
1195 m_Environment.SetEnv(_T("GIT_SSH"), sPlink);
1197 //Setup SVN_SSH
1198 CString ssh=sPlink;
1199 ssh.Replace(_T("/"),_T("\\"));
1200 ssh.Replace(_T("\\"),_T("\\\\"));
1201 ssh=CString(_T("\""))+ssh+_T('\"');
1202 m_Environment.SetEnv(_T("SVN_SSH"),ssh.GetBuffer());
1207 TCHAR sAskPass[MAX_PATH];
1208 GetModuleFileName(NULL, sAskPass, _countof(sAskPass));
1209 LPTSTR ptr = _tcsrchr(sAskPass, _T('\\'));
1210 if (ptr)
1212 _tcscpy(ptr + 1, _T("SshAskPass.exe"));
1213 m_Environment.SetEnv(_T("DISPLAY"),_T(":9999"));
1214 m_Environment.SetEnv(_T("SSH_ASKPASS"),sAskPass);
1215 m_Environment.SetEnv(_T("GIT_ASKPASS"),sAskPass);
1219 // add git/bin path to PATH
1221 CRegString msysdir=CRegString(REG_MSYSGIT_PATH,_T(""),FALSE);
1222 str=msysdir;
1223 if(str.IsEmpty())
1225 CRegString msysinstalldir=CRegString(REG_MSYSGIT_INSTALL,_T(""),FALSE,HKEY_LOCAL_MACHINE);
1226 str=msysinstalldir;
1227 if ( !str.IsEmpty() )
1229 str += (str[str.GetLength()-1] != '\\') ? "\\bin" : "bin";
1230 msysdir=str;
1231 CGit::ms_LastMsysGitDir = str;
1232 msysdir.write();
1234 else
1236 // search PATH if git/bin directory is already present
1237 if ( FindGitPath() )
1239 m_bInitialized = TRUE;
1240 return TRUE;
1243 return FALSE;
1245 }else
1247 CGit::ms_LastMsysGitDir = str;
1250 // check for git.exe existance (maybe it was deinstalled in the meantime)
1251 if (!FileExists(CGit::ms_LastMsysGitDir + _T("\\git.exe")))
1252 return FALSE;
1254 //set path
1255 _tdupenv_s(&oldpath,&size,_T("PATH"));
1257 CString path;
1258 path.Format(_T("%s;%s"),oldpath,str + _T(";")+ (CString)CRegString(REG_MSYSGIT_EXTRA_PATH,_T(""),FALSE));
1260 m_Environment.SetEnv(_T("PATH"),path.GetBuffer());
1262 CString str1 = m_Environment.GetEnv(_T("PATH"));
1264 CString sOldPath = oldpath;
1265 free(oldpath);
1267 m_bInitialized = TRUE;
1268 return true;
1271 class CGitCall_EnumFiles : public CGitCall
1273 public:
1274 CGitCall_EnumFiles(const TCHAR *pszProjectPath, const TCHAR *pszSubPath, unsigned int nFlags, WGENUMFILECB *pEnumCb, void *pUserData)
1275 : m_pszProjectPath(pszProjectPath),
1276 m_pszSubPath(pszSubPath),
1277 m_nFlags(nFlags),
1278 m_pEnumCb(pEnumCb),
1279 m_pUserData(pUserData)
1283 typedef std::map<CStringA,char> TStrCharMap;
1285 const TCHAR * m_pszProjectPath;
1286 const TCHAR * m_pszSubPath;
1287 unsigned int m_nFlags;
1288 WGENUMFILECB * m_pEnumCb;
1289 void * m_pUserData;
1291 BYTE_VECTOR m_DataCollector;
1293 virtual bool OnOutputData(const BYTE* data, size_t size)
1295 m_DataCollector.append(data,size);
1296 while(true)
1298 // lines from igit.exe are 0 terminated
1299 int found=m_DataCollector.findData((const BYTE*)"",1);
1300 if(found<0)
1301 return false;
1302 OnSingleLine( (LPCSTR)&*m_DataCollector.begin() );
1303 m_DataCollector.erase(m_DataCollector.begin(), m_DataCollector.begin()+found+1);
1305 return false;//Should never reach this
1307 virtual void OnEnd()
1311 BYTE HexChar(char ch)
1313 if (ch >= '0' && ch <= '9')
1314 return (UINT)(ch - '0');
1315 else if (ch >= 'A' && ch <= 'F')
1316 return (UINT)(ch - 'A') + 10;
1317 else if (ch >= 'a' && ch <= 'f')
1318 return (UINT)(ch - 'a') + 10;
1319 else
1320 return 0;
1323 bool OnSingleLine(LPCSTR line)
1325 //Parse single line
1327 wgFile_s fileStatus;
1329 // file/dir type
1331 fileStatus.nFlags = 0;
1332 if (*line == 'D')
1333 fileStatus.nFlags |= WGFF_Directory;
1334 else if (*line != 'F')
1335 // parse error
1336 return false;
1337 line += 2;
1339 // status
1341 fileStatus.nStatus = WGFS_Unknown;
1342 switch (*line)
1344 case 'N': fileStatus.nStatus = WGFS_Normal; break;
1345 case 'M': fileStatus.nStatus = WGFS_Modified; break;
1346 case 'S': fileStatus.nStatus = WGFS_Staged; break;
1347 case 'A': fileStatus.nStatus = WGFS_Added; break;
1348 case 'C': fileStatus.nStatus = WGFS_Conflicted; break;
1349 case 'D': fileStatus.nStatus = WGFS_Deleted; break;
1350 case 'I': fileStatus.nStatus = WGFS_Ignored; break;
1351 case 'U': fileStatus.nStatus = WGFS_Unversioned; break;
1352 case 'E': fileStatus.nStatus = WGFS_Empty; break;
1353 case '?': fileStatus.nStatus = WGFS_Unknown; break;
1354 default:
1355 // parse error
1356 return false;
1358 line += 2;
1360 // file sha1
1362 BYTE sha1[20];
1363 fileStatus.sha1 = NULL;
1364 if ( !(fileStatus.nFlags & WGFF_Directory) )
1366 for (int i=0; i<20; i++)
1368 sha1[i] = (HexChar(line[0])<<4)&0xF0;
1369 sha1[i] |= HexChar(line[1])&0xF;
1371 line += 2;
1374 line++;
1377 // filename
1378 int len = strlen(line);
1379 if (len && len < 2048)
1381 WCHAR *buf = (WCHAR*)alloca((len*4+2)*sizeof(WCHAR));
1382 *buf = 0;
1383 MultiByteToWideChar(CP_ACP, 0, line, len+1, buf, len*4+1);
1384 fileStatus.sFileName = buf;
1386 if (*buf && (*m_pEnumCb)(&fileStatus,m_pUserData))
1387 return false;
1390 return true;
1394 BOOL CGit::EnumFiles(const TCHAR *pszProjectPath, const TCHAR *pszSubPath, unsigned int nFlags, WGENUMFILECB *pEnumCb, void *pUserData)
1396 if(!pszProjectPath || *pszProjectPath=='\0')
1397 return FALSE;
1399 CGitCall_EnumFiles W_GitCall(pszProjectPath,pszSubPath,nFlags,pEnumCb,pUserData);
1400 CString cmd;
1402 /* char W_szToDir[MAX_PATH];
1403 strncpy(W_szToDir,pszProjectPath,sizeof(W_szToDir)-1);
1404 if(W_szToDir[strlen(W_szToDir)-1]!='\\')
1405 strncat(W_szToDir,"\\",sizeof(W_szToDir)-1);
1407 SetCurrentDirectoryA(W_szToDir);
1408 GetCurrentDirectoryA(sizeof(W_szToDir)-1,W_szToDir);
1410 SetCurrentDir(pszProjectPath);
1412 CString sMode;
1413 if (nFlags)
1415 if (nFlags & WGEFF_NoRecurse) sMode += _T("r");
1416 if (nFlags & WGEFF_FullPath) sMode += _T("f");
1417 if (nFlags & WGEFF_DirStatusDelta) sMode += _T("d");
1418 if (nFlags & WGEFF_DirStatusAll) sMode += _T("D");
1419 if (nFlags & WGEFF_EmptyAsNormal) sMode += _T("e");
1420 if (nFlags & WGEFF_SingleFile) sMode += _T("s");
1422 else
1424 sMode = _T("-");
1427 // NOTE: there seems to be some issue with msys based app receiving backslash on commandline, at least
1428 // if followed by " like for example 'igit "C:\"', the commandline igit receives is 'igit.exe C:" status' with
1429 // the 'C:" status' part as a single arg, Maybe it uses unix style processing. In order to avoid this just
1430 // use forward slashes for supplied project and sub paths
1432 CString sProjectPath = pszProjectPath;
1433 sProjectPath.Replace(_T('\\'), _T('/'));
1435 if (pszSubPath)
1437 CString sSubPath = pszSubPath;
1438 sSubPath.Replace(_T('\\'), _T('/'));
1440 cmd.Format(_T("tgit.exe statusex \"%s\" status %s \"%s\""), sProjectPath, sMode, sSubPath);
1442 else
1444 cmd.Format(_T("tgit.exe statusex \"%s\" status %s"), sProjectPath, sMode);
1447 //OutputDebugStringA("---");OutputDebugStringW(cmd);OutputDebugStringA("\r\n");
1449 W_GitCall.SetCmd(cmd);
1450 // NOTE: should igit get added as a part of msysgit then use below line instead of the above one
1451 //W_GitCall.SetCmd(CGit::ms_LastMsysGitDir + cmd);
1453 if ( Run(&W_GitCall) )
1454 return FALSE;
1456 return TRUE;
1459 BOOL CGit::CheckCleanWorkTree()
1461 CString out;
1462 CString cmd;
1463 cmd=_T("git.exe rev-parse --verify HEAD");
1465 if(g_Git.Run(cmd,&out,CP_UTF8))
1466 return FALSE;
1468 cmd=_T("git.exe update-index --ignore-submodules --refresh");
1469 if(g_Git.Run(cmd,&out,CP_UTF8))
1470 return FALSE;
1472 cmd=_T("git.exe diff-files --quiet --ignore-submodules");
1473 if(g_Git.Run(cmd,&out,CP_UTF8))
1474 return FALSE;
1476 cmd=_T("git diff-index --cached --quiet HEAD --ignore-submodules");
1477 if(g_Git.Run(cmd,&out,CP_UTF8))
1478 return FALSE;
1480 return TRUE;
1482 int CGit::Revert(CString commit, CTGitPathList &list,bool keep)
1484 int ret;
1485 for(int i=0;i<list.GetCount();i++)
1487 ret = Revert(commit, (CTGitPath&)list[i]);
1488 if(ret)
1489 return ret;
1491 return 0;
1493 int CGit::Revert(CString commit, CTGitPath &path)
1495 CString cmd, out;
1497 if(path.m_Action & CTGitPath::LOGACTIONS_REPLACED && !path.GetGitOldPathString().IsEmpty())
1499 cmd.Format(_T("git.exe mv -- \"%s\" \"%s\""),path.GetGitPathString(),path.GetGitOldPathString());
1500 if(g_Git.Run(cmd,&out,CP_ACP))
1501 return -1;
1503 cmd.Format(_T("git.exe checkout %s -f -- \"%s\""), commit, path.GetGitOldPathString());
1504 if(g_Git.Run(cmd,&out,CP_ACP))
1505 return -1;
1507 }else if(path.m_Action & CTGitPath::LOGACTIONS_ADDED)
1508 { //To init git repository, there are not HEAD, so we can use git reset command
1509 cmd.Format(_T("git.exe rm --cached -- \"%s\""),path.GetGitPathString());
1511 if(g_Git.Run(cmd,&out,CP_ACP))
1512 return -1;
1514 else
1516 cmd.Format(_T("git.exe checkout %s -f -- \"%s\""), commit, path.GetGitPathString());
1517 if(g_Git.Run(cmd,&out,CP_ACP))
1518 return -1;
1520 return 0;
1523 int CGit::ListConflictFile(CTGitPathList &list,CTGitPath *path)
1525 BYTE_VECTOR vector;
1527 CString cmd;
1528 if(path)
1529 cmd.Format(_T("git.exe ls-files -u -t -z -- \"%s\""),path->GetGitPathString());
1530 else
1531 cmd=_T("git.exe ls-files -u -t -z");
1533 if(g_Git.Run(cmd,&vector))
1535 return -1;
1538 list.ParserFromLsFile(vector);
1540 return 0;
1543 bool CGit::IsFastForward(const CString &from, const CString &to)
1545 CString base;
1546 CGitHash basehash,hash;
1547 CString cmd;
1548 cmd.Format(_T("git.exe merge-base %s %s"), FixBranchName(to), FixBranchName(from));
1550 if(g_Git.Run(cmd,&base,CP_ACP))
1552 //CMessageBox::Show(NULL,base,_T("TortoiseGit"),MB_OK|MB_ICONERROR);
1553 return false;
1555 basehash = base;
1557 hash=g_Git.GetHash(from);
1559 return hash == basehash;
1562 unsigned int CGit::Hash2int(CGitHash &hash)
1564 int ret=0;
1565 for(int i=0;i<4;i++)
1567 ret = ret << 8;
1568 ret |= hash.m_hash[i];
1570 return ret;
1573 int CGit::RefreshGitIndex()
1575 if(g_Git.m_IsUseGitDLL)
1579 return git_run_cmd("update-index","update-index -q --refresh");
1581 }catch(...)
1583 return -1;
1586 }else
1588 CString cmd,output;
1589 cmd=_T("git.exe update-index --refresh");
1590 return Run(cmd,&output,CP_ACP);
1594 int CGit::GetOneFile(CString Refname, CTGitPath &path, const CString &outputfile)
1596 if(g_Git.m_IsUseGitDLL)
1600 g_Git.CheckAndInitDll();
1601 CStringA ref, patha, outa;
1602 ref = CUnicodeUtils::GetMulti(Refname,CP_ACP);
1603 patha = CUnicodeUtils::GetMulti(path.GetGitPathString(), CP_ACP);
1604 outa = CUnicodeUtils::GetMulti(outputfile,CP_ACP);
1605 ::DeleteFile(outputfile);
1606 return git_checkout_file((const char*)ref.GetBuffer(),(const char*)patha.GetBuffer(),(const char*)outa.GetBuffer());
1608 }catch(...)
1610 return -1;
1612 }else
1614 CString cmd;
1615 cmd.Format(_T("git.exe cat-file -p %s:\"%s\""), Refname, path.GetGitPathString());
1616 return g_Git.RunLogFile(cmd,outputfile);
1619 void CEnvironment::CopyProcessEnvironment()
1621 TCHAR *p = GetEnvironmentStrings();
1622 while(*p !=0 || *(p+1) !=0)
1623 this->push_back(*p++);
1625 push_back(_T('\0'));
1626 push_back(_T('\0'));
1629 CString CEnvironment::GetEnv(TCHAR *name)
1631 CString str;
1632 for(int i=0;i<size();i++)
1634 str = &(*this)[i];
1635 int start =0;
1636 CString sname = str.Tokenize(_T("="),start);
1637 if(sname.CompareNoCase(name) == 0)
1639 return &(*this)[i+start+1];
1641 i+=str.GetLength();
1643 return _T("");
1646 void CEnvironment::SetEnv(TCHAR *name, TCHAR* value)
1648 unsigned int i;
1649 for( i=0;i<size();i++)
1651 CString str = &(*this)[i];
1652 int start =0;
1653 CString sname = str.Tokenize(_T("="),start);
1654 if(sname.CompareNoCase(name) == 0)
1656 break;
1658 i+=str.GetLength();
1661 if(i == size())
1663 i -= 1; // roll back terminate \0\0
1664 this->push_back(_T('\0'));
1667 CEnvironment::iterator it;
1668 it=this->begin();
1669 it += i;
1671 while(*it && i<size())
1673 this->erase(it);
1674 it=this->begin();
1675 it += i;
1678 while(*name)
1680 this->insert(it,*name++);
1681 i++;
1682 it= begin()+i;
1685 this->insert(it, _T('='));
1686 i++;
1687 it= begin()+i;
1689 while(*value)
1691 this->insert(it,*value++);
1692 i++;
1693 it= begin()+i;
1698 int CGit::GetGitEncode(TCHAR* configkey)
1700 CString str=GetConfigValue(configkey);
1702 if(str.IsEmpty())
1703 return CP_UTF8;
1705 return CUnicodeUtils::GetCPCode(str);
1709 int CGit::GetDiffPath(CTGitPathList *PathList, CGitHash *hash1, CGitHash *hash2, char *arg)
1711 GIT_FILE file=0;
1712 int ret=0;
1713 GIT_DIFF diff=0;
1715 CAutoLocker lock(g_Git.m_critGitDllSec);
1717 if(arg == NULL)
1718 diff = GetGitDiff();
1719 else
1720 git_open_diff(&diff, arg);
1722 if(diff ==NULL)
1723 return -1;
1725 bool isStat = 0;
1726 if(arg == NULL)
1727 isStat = true;
1728 else
1729 isStat = !!strstr(arg, "stat");
1731 int count=0;
1733 if(hash2 == NULL)
1734 ret = git_root_diff(diff, hash1->m_hash, &file, &count,isStat);
1735 else
1736 ret = git_diff(diff,hash2->m_hash,hash1->m_hash,&file,&count,isStat);
1738 if(ret)
1739 return -1;
1741 CTGitPath path;
1742 CString strnewname;
1743 CString stroldname;
1745 for(int j=0;j<count;j++)
1747 path.Reset();
1748 char *newname;
1749 char *oldname;
1751 strnewname.Empty();
1752 stroldname.Empty();
1754 int mode=0,IsBin=0,inc=0,dec=0;
1755 git_get_diff_file(diff,file,j,&newname,&oldname,
1756 &mode,&IsBin,&inc,&dec);
1758 StringAppend(&strnewname,(BYTE*)newname,CP_ACP);
1759 StringAppend(&stroldname,(BYTE*)oldname,CP_ACP);
1761 path.SetFromGit(strnewname,&stroldname);
1762 path.ParserAction((BYTE)mode);
1764 if(IsBin)
1766 path.m_StatAdd=_T("-");
1767 path.m_StatDel=_T("-");
1768 }else
1770 path.m_StatAdd.Format(_T("%d"),inc);
1771 path.m_StatDel.Format(_T("%d"),dec);
1773 PathList->AddPath(path);
1775 git_diff_flush(diff);
1777 if(arg)
1778 git_close_diff(diff);
1780 return 0;