OSDN Git Service

Fix Issue 236: CGit::GetRemoteList uses bad regular expression
[tortoisegit/TortoiseGitJp.git] / src / Git / Git.cpp
1 #include "StdAfx.h"
2 #include "Git.h"
3 #include "atlconv.h"
4 #include "GitRev.h"
5 #include "registry.h"
6 #include "GitConfig.h"
7 #include <map>
8 #include "UnicodeUtils.h"
9
10 int CGit::m_LogEncode=CP_UTF8;
11
12 static LPTSTR nextpath(LPCTSTR src, LPTSTR dst, UINT maxlen)
13 {
14         LPCTSTR orgsrc;
15
16         while (*src == _T(';'))
17                 src++;
18
19         orgsrc = src;
20
21         if (!--maxlen)
22                 goto nullterm;
23
24         while (*src && *src != _T(';'))
25         {
26                 if (*src != _T('"'))
27                 {
28                         *dst++ = *src++;
29                         if (!--maxlen)
30                         {
31                                 orgsrc = src;
32                                 goto nullterm;
33                         }
34                 }
35                 else
36                 {
37                         src++;
38                         while (*src && *src != _T('"'))
39                         {
40                                 *dst++ = *src++;
41                                 if (!--maxlen)
42                                 {
43                                         orgsrc = src;
44                                         goto nullterm;
45                                 }
46                         }
47
48                         if (*src)
49                                 src++;
50                 }
51         }
52
53         while (*src == _T(';'))
54                 src++;
55
56 nullterm:
57
58         *dst = 0;
59
60         return (orgsrc != src) ? (LPTSTR)src : NULL;
61 }
62
63 static inline BOOL FileExists(LPCTSTR lpszFileName)
64 {
65         struct _stat st;
66         return _tstat(lpszFileName, &st) == 0;
67 }
68
69 static BOOL FindGitPath()
70 {
71         size_t size;
72         _tgetenv_s(&size, NULL, 0, _T("PATH"));
73
74         if (!size)
75         {
76                 return FALSE;
77         }
78
79         TCHAR *env = (TCHAR*)alloca(size * sizeof(TCHAR));
80         _tgetenv_s(&size, env, size, _T("PATH"));
81
82         TCHAR buf[_MAX_PATH];
83
84         // search in all paths defined in PATH
85         while ((env = nextpath(env, buf, _MAX_PATH-1)) && *buf)
86         {
87                 TCHAR *pfin = buf + _tcslen(buf)-1;
88
89                 // ensure trailing slash
90                 if (*pfin != _T('/') && *pfin != _T('\\'))
91                         _tcscpy(++pfin, _T("\\"));
92
93                 const int len = _tcslen(buf);
94
95                 if ((len + 7) < _MAX_PATH)
96                         _tcscpy(pfin+1, _T("git.exe"));
97                 else
98                         break;
99
100                 if ( FileExists(buf) )
101                 {
102                         // dir found
103                         pfin[1] = 0;
104                         CGit::ms_LastMsysGitDir = buf;
105                         return TRUE;
106                 }
107         }
108
109         return FALSE;
110 }
111
112
113 #define MAX_DIRBUFFER 1000
114 #define CALL_OUTPUT_READ_CHUNK_SIZE 1024
115
116 CString CGit::ms_LastMsysGitDir;
117 CGit g_Git;
118
119 // contains system environment that should be used by child processes (RunAsync)
120 // initialized by CheckMsysGitDir
121 static LPTSTR l_processEnv = NULL;
122
123
124
125 CGit::CGit(void)
126 {
127         GetCurrentDirectory(MAX_DIRBUFFER,m_CurrentDir.GetBuffer(MAX_DIRBUFFER));
128         m_CurrentDir.ReleaseBuffer();
129
130         CheckMsysGitDir();
131 }
132
133 CGit::~CGit(void)
134 {
135 }
136
137 static char g_Buffer[4096];
138
139 int CGit::RunAsync(CString cmd,PROCESS_INFORMATION *piOut,HANDLE *hReadOut,CString *StdioFile)
140 {
141         SECURITY_ATTRIBUTES sa;
142         HANDLE hRead, hWrite;
143         HANDLE hStdioFile = NULL;
144
145         sa.nLength = sizeof(SECURITY_ATTRIBUTES);
146         sa.lpSecurityDescriptor=NULL;
147         sa.bInheritHandle=TRUE;
148         if(!CreatePipe(&hRead,&hWrite,&sa,0))
149         {
150                 return GIT_ERROR_OPEN_PIP;
151         }
152         
153         if(StdioFile)
154         {
155                 hStdioFile=CreateFile(*StdioFile,GENERIC_WRITE,FILE_SHARE_READ   |   FILE_SHARE_WRITE,   
156                         &sa,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);  
157         }
158
159         STARTUPINFO si;
160         PROCESS_INFORMATION pi;
161         si.cb=sizeof(STARTUPINFO);
162         GetStartupInfo(&si);
163
164         si.hStdError=hWrite;
165         if(StdioFile)
166                 si.hStdOutput=hStdioFile;
167         else
168                 si.hStdOutput=hWrite;
169
170         si.wShowWindow=SW_HIDE;
171         si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
172
173         LPTSTR pEnv = l_processEnv;
174         DWORD dwFlags = pEnv ? CREATE_UNICODE_ENVIRONMENT : 0;
175         
176         //DETACHED_PROCESS make ssh recognize that it has no console to launch askpass to input password. 
177         dwFlags |= DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP; 
178
179         memset(&this->m_CurrentGitPi,0,sizeof(PROCESS_INFORMATION));
180
181         if(!CreateProcess(NULL,(LPWSTR)cmd.GetString(), NULL,NULL,TRUE,dwFlags,pEnv,(LPWSTR)m_CurrentDir.GetString(),&si,&pi))
182         {
183                 LPVOID lpMsgBuf;
184                 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
185                         NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
186                         (LPTSTR)&lpMsgBuf,
187                         0,NULL);
188                 return GIT_ERROR_CREATE_PROCESS;
189         }
190         
191         m_CurrentGitPi = pi;
192         
193         CloseHandle(hWrite);
194         if(piOut)
195                 *piOut=pi;
196         if(hReadOut)
197                 *hReadOut=hRead;
198         
199         return 0;
200
201 }
202 //Must use sperate function to convert ANSI str to union code string
203 //Becuase A2W use stack as internal convert buffer. 
204 void CGit::StringAppend(CString *str,BYTE *p,int code,int length)
205 {
206      //USES_CONVERSION;
207          //str->Append(A2W_CP((LPCSTR)p,code));
208         if(str == NULL)
209                 return ;
210
211         WCHAR * buf;
212
213         int len ;
214         if(length<0)
215                 len= strlen((const char*)p);
216         else
217                 len=length;
218         //if (len==0)
219         //      return ;
220         //buf = new WCHAR[len*4 + 1];
221         buf = str->GetBuffer(len*4+1+str->GetLength())+str->GetLength();
222         SecureZeroMemory(buf, (len*4 + 1)*sizeof(WCHAR));
223         MultiByteToWideChar(code, 0, (LPCSTR)p, len, buf, len*4);
224         str->ReleaseBuffer();
225         //str->Append(buf);
226         //delete buf;
227 }       
228 BOOL CGit::IsInitRepos()
229 {
230         CString cmdout;
231         cmdout.Empty();
232         if(g_Git.Run(_T("git.exe rev-parse --revs-only HEAD"),&cmdout,CP_UTF8))
233         {
234         //      CMessageBox::Show(NULL,cmdout,_T("TortoiseGit"),MB_OK);
235                 return TRUE;
236         }
237         if(cmdout.IsEmpty())
238                 return TRUE;
239
240         return FALSE;
241 }
242 int CGit::Run(CGitCall* pcall)
243 {
244         PROCESS_INFORMATION pi;
245         HANDLE hRead;
246         if(RunAsync(pcall->GetCmd(),&pi,&hRead))
247                 return GIT_ERROR_CREATE_PROCESS;
248
249         DWORD readnumber;
250         BYTE data[CALL_OUTPUT_READ_CHUNK_SIZE];
251         bool bAborted=false;
252         while(ReadFile(hRead,data,CALL_OUTPUT_READ_CHUNK_SIZE,&readnumber,NULL))
253         {
254                 //Todo: when OnOutputData() returns 'true', abort git-command. Send CTRL-C signal?
255                 if(!bAborted)//For now, flush output when command aborted.
256                         if(pcall->OnOutputData(data,readnumber))
257                                 bAborted=true;
258         }
259         if(!bAborted)
260                 pcall->OnEnd();
261
262         
263         CloseHandle(pi.hThread);
264
265         WaitForSingleObject(pi.hProcess, INFINITE);
266         DWORD exitcode =0;
267
268         if(!GetExitCodeProcess(pi.hProcess,&exitcode))
269         {
270                 return GIT_ERROR_GET_EXIT_CODE;
271         }
272
273         CloseHandle(pi.hProcess);
274
275         CloseHandle(hRead);
276         return exitcode;
277 }
278 class CGitCall_ByteVector : public CGitCall
279 {
280 public:
281         CGitCall_ByteVector(CString cmd,BYTE_VECTOR* pvector):CGitCall(cmd),m_pvector(pvector){}
282         virtual bool OnOutputData(const BYTE* data, size_t size)
283         {
284                 size_t oldsize=m_pvector->size();
285                 m_pvector->resize(m_pvector->size()+size);
286                 memcpy(&*(m_pvector->begin()+oldsize),data,size);
287                 return false;
288         }
289         BYTE_VECTOR* m_pvector;
290
291 };
292 int CGit::Run(CString cmd,BYTE_VECTOR *vector)
293 {
294         CGitCall_ByteVector call(cmd,vector);
295         return Run(&call);
296 }
297 int CGit::Run(CString cmd, CString* output,int code)
298 {
299         BYTE_VECTOR vector;
300         int ret;
301         ret=Run(cmd,&vector);
302
303         vector.push_back(0);
304         
305         StringAppend(output,&(vector[0]),code);
306         return ret;
307 }
308
309 CString CGit::GetUserName(void)
310 {
311         return GetConfigValue(L"user.name");
312 }
313 CString CGit::GetUserEmail(void)
314 {
315         return GetConfigValue(L"user.email");
316 }
317
318 CString CGit::GetConfigValue(CString name)
319 {
320         CString configValue;
321         CString cmd;
322         cmd.Format(L"git.exe config %s", name);
323         Run(cmd,&configValue,CP_UTF8);
324         int start = 0;
325         return configValue.Tokenize(_T("\n"),start);
326 }
327
328
329 CString CGit::GetCurrentBranch(void)
330 {
331         CString output;
332         //Run(_T("git.exe branch"),&branch);
333
334         int ret=g_Git.Run(_T("git.exe branch --no-color"),&output,CP_UTF8);
335         if(!ret)
336         {               
337                 int pos=0;
338                 CString one;
339                 while( pos>=0 )
340                 {
341                         //i++;
342                         one=output.Tokenize(_T("\n"),pos);
343                         //list.push_back(one.Right(one.GetLength()-2));
344                         if(one[0] == _T('*'))
345                                 return one.Right(one.GetLength()-2);
346                 }
347         }
348         return CString("");
349 }
350
351 CString CGit::GetSymbolicRef(const wchar_t* symbolicRefName, bool bStripRefsHeads)
352 {
353         CString refName;
354         CString cmd;
355         cmd.Format(L"git symbolic-ref %s", symbolicRefName);
356         if(Run(cmd, &refName, CP_UTF8) != 0)
357                 return CString();//Error
358         int iStart = 0;
359         refName = refName.Tokenize(L"\n", iStart);
360         if(bStripRefsHeads)
361                 refName = StripRefName(refName);
362         return refName;
363 }
364
365 CString CGit::GetFullRefName(CString shortRefName)
366 {
367         CString refName;
368         CString cmd;
369         cmd.Format(L"git rev-parse --symbolic-full-name %s", shortRefName);
370         if(Run(cmd, &refName, CP_UTF8) != 0)
371                 return CString();//Error
372         int iStart = 0;
373         return refName.Tokenize(L"\n", iStart);
374 }
375
376 CString CGit::StripRefName(CString refName)
377 {
378         if(wcsncmp(refName, L"refs/heads/", 11) == 0)
379                 refName = refName.Mid(11);
380         else if(wcsncmp(refName, L"refs/", 5) == 0)
381                 refName = refName.Mid(5);
382         return refName;
383 }
384
385 int CGit::GetCurrentBranchFromFile(const CString &sProjectRoot, CString &sBranchOut)
386 {
387         // read current branch name like git-gui does, by parsing the .git/HEAD file directly
388
389         if ( sProjectRoot.IsEmpty() )
390                 return -1;
391
392         CString sHeadFile = sProjectRoot + _T("\\") + g_GitAdminDir.GetAdminDirName() + _T("\\HEAD");
393
394         FILE *pFile;
395         _tfopen_s(&pFile, sHeadFile.GetString(), _T("r"));
396
397         if (!pFile)
398         {
399                 return -1;
400         }
401
402         char s[256] = {0};
403     fgets(s, sizeof(s), pFile);
404
405         fclose(pFile);
406
407         const char *pfx = "ref: refs/heads/";
408         const int len = 16;//strlen(pfx)
409
410         if ( !strncmp(s, pfx, len) )
411         {
412                 //# We're on a branch.  It might not exist.  But
413                 //# HEAD looks good enough to be a branch.
414                 sBranchOut = s + len;
415                 sBranchOut.TrimRight(_T(" \r\n\t"));
416
417                 if ( sBranchOut.IsEmpty() )
418                         return -1;
419         }
420         else
421         {
422                 //# Assume this is a detached head.
423                 sBranchOut = "HEAD";
424
425                 return 1;
426         }
427
428         return 0;
429 }
430
431 int CGit::BuildOutputFormat(CString &format,bool IsFull)
432 {
433         CString log;
434         log.Format(_T("#<%c>%%x00"),LOG_REV_ITEM_BEGIN);
435         format += log;
436         if(IsFull)
437         {
438                 log.Format(_T("#<%c>%%an%%x00"),LOG_REV_AUTHOR_NAME);
439                 format += log;
440                 log.Format(_T("#<%c>%%ae%%x00"),LOG_REV_AUTHOR_EMAIL);
441                 format += log;
442                 log.Format(_T("#<%c>%%ai%%x00"),LOG_REV_AUTHOR_DATE);
443                 format += log;
444                 log.Format(_T("#<%c>%%cn%%x00"),LOG_REV_COMMIT_NAME);
445                 format += log;
446                 log.Format(_T("#<%c>%%ce%%x00"),LOG_REV_COMMIT_EMAIL);
447                 format += log;
448                 log.Format(_T("#<%c>%%ci%%x00"),LOG_REV_COMMIT_DATE);
449                 format += log;
450                 log.Format(_T("#<%c>%%b%%x00"),LOG_REV_COMMIT_BODY);
451                 format += log;
452         }
453         
454         log.Format(_T("#<%c>%%m%%H%%x00"),LOG_REV_COMMIT_HASH);
455         format += log;
456         log.Format(_T("#<%c>%%P%%x00"),LOG_REV_COMMIT_PARENT);
457         format += log;
458         log.Format(_T("#<%c>%%s%%x00"),LOG_REV_COMMIT_SUBJECT);
459         format += log;
460
461         if(IsFull)
462         {
463                 log.Format(_T("#<%c>%%x00"),LOG_REV_COMMIT_FILE);
464                 format += log;
465         }
466         return 0;
467 }
468
469 int CGit::GetLog(BYTE_VECTOR& logOut, CString &hash,  CTGitPath *path ,int count,int mask,CString *from,CString *to)
470 {
471         CGitCall_ByteVector gitCall(CString(),&logOut);
472         return GetLog(&gitCall,hash,path,count,mask,from,to);
473 }
474
475 //int CGit::GetLog(CGitCall* pgitCall, CString &hash,  CTGitPath *path ,int count,int mask)
476 int CGit::GetLog(CGitCall* pgitCall, CString &hash, CTGitPath *path, int count, int mask,CString *from,CString *to)
477 {
478
479         CString cmd;
480         CString log;
481         CString num;
482         CString since;
483
484         CString file;
485
486         if(path)
487                 file.Format(_T(" -- \"%s\""),path->GetGitPathString());
488         
489         if(count>0)
490                 num.Format(_T("-n%d"),count);
491
492         CString param;
493
494         if(mask& LOG_INFO_STAT )
495                 param += _T(" --numstat ");
496         if(mask& LOG_INFO_FILESTATE)
497                 param += _T(" --raw ");
498
499         if(mask& LOG_INFO_FULLHISTORY)
500                 param += _T(" --full-history ");
501
502         if(mask& LOG_INFO_BOUNDARY)
503                 param += _T(" --left-right --boundary ");
504
505         if(mask& CGit::LOG_INFO_ALL_BRANCH)
506                 param += _T(" --all ");
507
508         if(mask& CGit::LOG_INFO_DETECT_COPYRENAME)
509                 param += _T(" -C ");
510         
511         if(mask& CGit::LOG_INFO_DETECT_RENAME )
512                 param += _T(" -M ");
513
514         if(mask& CGit::LOG_INFO_FIRST_PARENT )
515                 param += _T(" --first-parent ");
516         
517         if(mask& CGit::LOG_INFO_NO_MERGE )
518                 param += _T(" --no-merges ");
519
520         if(mask& CGit::LOG_INFO_FOLLOW)
521                 param += _T(" --follow ");
522
523         if(mask& CGit::LOG_INFO_SHOW_MERGEDFILE)
524                 param += _T(" -c ");
525
526         if(mask& CGit::LOG_INFO_FULL_DIFF)
527                 param += _T(" --full-diff ");
528
529         if(from != NULL && to != NULL)
530         {
531                 CString range;
532                 range.Format(_T(" %s..%s "),*from,*to);
533                 param += range;
534         }
535         param+=hash;
536
537         cmd.Format(_T("git.exe log %s -z --topo-order %s --parents --pretty=format:\""),
538                                 num,param);
539
540         BuildOutputFormat(log,!(mask&CGit::LOG_INFO_ONLY_HASH));
541
542         cmd += log;
543         cmd += CString(_T("\"  "))+hash+file;
544
545         pgitCall->SetCmd(cmd);
546
547         return Run(pgitCall);
548 //      return Run(cmd,&logOut);
549 }
550
551 #if 0
552 int CGit::GetShortLog(CString &logOut,CTGitPath * path, int count)
553 {
554         CString cmd;
555         CString log;
556         int n;
557         if(count<0)
558                 n=100;
559         else
560                 n=count;
561         cmd.Format(_T("git.exe log --left-right --boundary --topo-order -n%d --pretty=format:\""),n);
562         BuildOutputFormat(log,false);
563         cmd += log+_T("\"");
564         if (path)
565                 cmd+= _T("  -- \"")+path->GetGitPathString()+_T("\"");
566         //cmd += CString(_T("\" HEAD~40..HEAD"));
567         return Run(cmd,&logOut);
568 }
569 #endif
570
571 #define BUFSIZE 512
572 void GetTempPath(CString &path)
573 {
574         TCHAR lpPathBuffer[BUFSIZE];
575         DWORD dwRetVal;
576         DWORD dwBufSize=BUFSIZE;
577         dwRetVal = GetTempPath(dwBufSize,     // length of the buffer
578                            lpPathBuffer); // buffer for path 
579     if (dwRetVal > dwBufSize || (dwRetVal == 0))
580     {
581         path=_T("");
582     }
583         path.Format(_T("%s"),lpPathBuffer);
584 }
585 CString GetTempFile()
586 {
587         TCHAR lpPathBuffer[BUFSIZE];
588         DWORD dwRetVal;
589     DWORD dwBufSize=BUFSIZE;
590         TCHAR szTempName[BUFSIZE];  
591         UINT uRetVal;
592
593         dwRetVal = GetTempPath(dwBufSize,     // length of the buffer
594                            lpPathBuffer); // buffer for path 
595     if (dwRetVal > dwBufSize || (dwRetVal == 0))
596     {
597         return _T("");
598     }
599          // Create a temporary file. 
600     uRetVal = GetTempFileName(lpPathBuffer, // directory for tmp files
601                               TEXT("Patch"),  // temp file name prefix 
602                               0,            // create unique name 
603                               szTempName);  // buffer for name 
604
605
606     if (uRetVal == 0)
607     {
608         return _T("");
609     }
610
611         return CString(szTempName);
612
613 }
614
615 int CGit::RunLogFile(CString cmd,CString &filename)
616 {
617         STARTUPINFO si;
618         PROCESS_INFORMATION pi;
619         si.cb=sizeof(STARTUPINFO);
620         GetStartupInfo(&si);
621
622         SECURITY_ATTRIBUTES   psa={sizeof(psa),NULL,TRUE};;   
623         psa.bInheritHandle=TRUE;   
624     
625         HANDLE   houtfile=CreateFile(filename,GENERIC_WRITE,FILE_SHARE_READ   |   FILE_SHARE_WRITE,   
626                         &psa,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);   
627
628
629         si.wShowWindow=SW_HIDE;
630         si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
631         si.hStdOutput   =   houtfile; 
632         
633         if(!CreateProcess(NULL,(LPWSTR)cmd.GetString(), NULL,NULL,TRUE,NULL,NULL,(LPWSTR)m_CurrentDir.GetString(),&si,&pi))
634         {
635                 LPVOID lpMsgBuf;
636                 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
637                         NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
638                         (LPTSTR)&lpMsgBuf,
639                         0,NULL);
640                 return GIT_ERROR_CREATE_PROCESS;
641         }
642         
643         WaitForSingleObject(pi.hProcess,INFINITE);   
644         
645         CloseHandle(pi.hThread);
646         CloseHandle(pi.hProcess);
647         CloseHandle(houtfile);
648         return GIT_SUCCESS;
649 //      return 0;
650 }
651
652 git_revnum_t CGit::GetHash(const CString &friendname)
653 {
654         CString cmd;
655         CString out;
656         cmd.Format(_T("git.exe rev-parse %s" ),friendname);
657         Run(cmd,&out,CP_UTF8);
658 //      int pos=out.ReverseFind(_T('\n'));
659         int pos=out.FindOneOf(_T("\r\n"));
660         if(pos>0)
661                 return out.Left(pos);
662         return out;
663 }
664
665 int CGit::GetCommitDiffList(CString &rev1,CString &rev2,CTGitPathList &outputlist)
666 {
667         CString cmd;
668         
669         if(rev1 == GIT_REV_ZERO || rev2 == GIT_REV_ZERO)
670         {
671                 //rev1=+_T("");
672                 if(rev1 == GIT_REV_ZERO)
673                         cmd.Format(_T("git.exe diff -r --raw -C -M --numstat -z %s"),rev2);
674                 else
675                         cmd.Format(_T("git.exe diff -r -R --raw -C -M --numstat -z %s"),rev1);
676         }else
677         {
678                 cmd.Format(_T("git.exe diff-tree -r --raw -C -M --numstat -z %s %s"),rev2,rev1);
679         }
680
681         BYTE_VECTOR out;
682         if(g_Git.Run(cmd,&out))
683                 return -1;
684
685         outputlist.ParserFromLog(out);
686
687 }
688
689 int CGit::GetTagList(STRING_VECTOR &list)
690 {
691         int ret;
692         CString cmd,output;
693         cmd=_T("git.exe tag -l");
694         int i=0;
695         ret=g_Git.Run(cmd,&output,CP_UTF8);
696         if(!ret)
697         {               
698                 int pos=0;
699                 CString one;
700                 while( pos>=0 )
701                 {
702                         i++;
703                         one=output.Tokenize(_T("\n"),pos);
704                         list.push_back(one);
705                 }
706         }
707         return ret;
708 }
709
710 int CGit::GetBranchList(STRING_VECTOR &list,int *current,BRANCH_TYPE type)
711 {
712         int ret;
713         CString cmd,output;
714         cmd=_T("git.exe branch --no-color");
715
716         if(type==(BRANCH_LOCAL|BRANCH_REMOTE))
717                 cmd+=_T(" -a");
718         else if(type==BRANCH_REMOTE)
719                 cmd+=_T(" -r");
720
721         int i=0;
722         ret=g_Git.Run(cmd,&output,CP_UTF8);
723         if(!ret)
724         {               
725                 int pos=0;
726                 CString one;
727                 while( pos>=0 )
728                 {
729                         one=output.Tokenize(_T("\n"),pos);
730                         one.Trim(L" \r\n\t");
731                         if(one.Find(L" -> ") >= 0 || one.IsEmpty())
732                                 continue; // skip something like: refs/origin/HEAD -> refs/origin/master
733                         if(one[0] == _T('*'))
734                         {
735                                 if(current)
736                                         *current=i;
737                                 one = one.Mid(2);
738                         }
739                         list.push_back(one);
740                         i++;
741                 }
742         }
743         return ret;
744 }
745
746 int CGit::GetRemoteList(STRING_VECTOR &list)
747 {
748         int ret;
749         CString cmd,output;
750         cmd=_T("git.exe config  --get-regexp '^^remote[.].*[.]url'");
751         ret=g_Git.Run(cmd,&output,CP_UTF8);
752         if(!ret)
753         {
754                 int pos=0;
755                 CString one;
756                 while( pos>=0 )
757                 {
758                         one=output.Tokenize(_T("\n"),pos);
759                         int start=one.Find(_T("."),0);
760                         if(start>0)
761                         {
762                                 CString url;
763                                 url=one.Right(one.GetLength()-start-1);
764                                 one=url;
765                                 one=one.Left(one.Find(_T("."),0));
766                                 list.push_back(one);
767                         }
768                 }
769         }
770         return ret;
771 }
772
773 int CGit::GetRefList(STRING_VECTOR &list)
774 {
775         int ret;
776         CString cmd,output;
777         cmd=_T("git show-ref -d");
778         ret=g_Git.Run(cmd,&output,CP_UTF8);
779         if(!ret)
780         {
781                 int pos=0;
782                 CString one;
783                 while( pos>=0 )
784                 {
785                         one=output.Tokenize(_T("\n"),pos);
786                         int start=one.Find(_T(" "),0);
787                         if(start>0)
788                         {
789                                 CString name;
790                                 name=one.Right(one.GetLength()-start-1);
791                                 list.push_back(name);
792                         }
793                 }
794         }
795         return ret;
796 }
797 int CGit::GetMapHashToFriendName(MAP_HASH_NAME &map)
798 {
799         int ret;
800         CString cmd,output;
801         cmd=_T("git show-ref -d");
802         ret=g_Git.Run(cmd,&output,CP_UTF8);
803         if(!ret)
804         {
805                 int pos=0;
806                 CString one;
807                 while( pos>=0 )
808                 {
809                         one=output.Tokenize(_T("\n"),pos);
810                         int start=one.Find(_T(" "),0);
811                         if(start>0)
812                         {
813                                 CString name;
814                                 name=one.Right(one.GetLength()-start-1);
815
816                                 CString hash;
817                                 hash=one.Left(start);
818
819                                 map[hash].push_back(name);
820                         }
821                 }
822         }
823         return ret;
824 }
825
826 BOOL CGit::CheckMsysGitDir()
827 {
828         static BOOL bInitialized = FALSE;
829
830         if (bInitialized)
831         {
832                 return TRUE;
833         }
834
835         TCHAR *oldpath,*home;
836         size_t homesize,size,httpsize;
837
838         // set HOME if not set already
839         _tgetenv_s(&homesize, NULL, 0, _T("HOME"));
840         if (!homesize)
841         {
842                 _tdupenv_s(&home,&size,_T("USERPROFILE")); 
843                 _tputenv_s(_T("HOME"),home);
844                 free(home);
845         }
846         CString str;
847
848 #ifndef _TORTOISESHELL
849         //set http_proxy
850         _tgetenv_s(&httpsize, NULL, 0, _T("http_proxy"));
851         if (!httpsize)
852         {
853                 CString regServeraddress_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-host"), _T(""));
854                 CString regServerport_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-port"), _T(""));
855                 CString regUsername_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-username"), _T(""));
856                 CString regPassword_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-password"), _T(""));
857                 CString regTimeout_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-timeout"), _T(""));
858                 CString regExceptions_copy = CRegString(_T("Software\\TortoiseGit\\Servers\\global\\http-proxy-exceptions"), _T(""));
859
860                 CString http_proxy;
861                 if(!regServeraddress_copy.IsEmpty())
862                 {
863                         if(regServeraddress_copy.Left(4) != _T("http"))
864                                 http_proxy=_T("http://");
865
866                         if(!regUsername_copy.IsEmpty())
867                         {
868                                 http_proxy += regUsername_copy;
869                                 http_proxy += _T(":")+regPassword_copy;
870                                 http_proxy += _T("@");
871                         }
872                         http_proxy+=regServeraddress_copy;
873                         if(!regServerport_copy.IsEmpty())
874                         {
875                                 http_proxy +=_T(":")+regServerport_copy;
876                         }
877                         _tputenv_s(_T("http_proxy"),http_proxy);
878                 }
879         }
880         //setup ssh client
881         CString sshclient=CRegString(_T("Software\\TortoiseGit\\SSH"));
882
883         if(!sshclient.IsEmpty())
884         {
885                 _tputenv_s(_T("GIT_SSH"),sshclient);
886                 
887                 //Setup SVN_SSH
888                 CString ssh=sshclient;
889                 ssh.Replace(_T("/"),_T("\\"));
890                 ssh.Replace(_T("\\"),_T("\\\\"));
891                 ssh=CString(_T("\""))+ssh+_T('\"');
892                 _tputenv_s(_T("SVN_SSH"),ssh);
893
894         }else
895         {
896                 TCHAR sPlink[MAX_PATH];
897                 GetModuleFileName(NULL, sPlink, _countof(sPlink));
898                 LPTSTR ptr = _tcsrchr(sPlink, _T('\\'));
899                 if (ptr) {
900                         _tcscpy(ptr + 1, _T("TortoisePlink.exe"));
901                         _tputenv_s(_T("GIT_SSH"), sPlink);
902
903                         //Setup SVN_SSH
904                         CString ssh=sPlink;
905                         ssh.Replace(_T("/"),_T("\\"));
906                         ssh.Replace(_T("\\"),_T("\\\\"));
907                         ssh=CString(_T("\""))+ssh+_T('\"');
908                         _tputenv_s(_T("SVN_SSH"),ssh);
909                 }
910         }
911
912         {
913                 TCHAR sAskPass[MAX_PATH];
914                 GetModuleFileName(NULL, sAskPass, _countof(sAskPass));
915                 LPTSTR ptr = _tcsrchr(sAskPass, _T('\\'));
916                 if (ptr) 
917                 {
918                         _tcscpy(ptr + 1, _T("SshAskPass.exe"));
919                         _tputenv_s(_T("DISPLAY"),_T(":9999"));
920                         _tputenv_s(_T("SSH_ASKPASS"),sAskPass);
921                 }
922         }
923         // search PATH if git/bin directory is alredy present
924         if ( FindGitPath() )
925         {
926                 bInitialized = TRUE;
927                 return TRUE;
928         }
929
930         // add git/bin path to PATH
931
932         CRegString msysdir=CRegString(REG_MSYSGIT_PATH,_T(""),FALSE);
933         str=msysdir;
934         if(str.IsEmpty())
935         {
936                 CRegString msysinstalldir=CRegString(REG_MSYSGIT_INSTALL,_T(""),FALSE,HKEY_LOCAL_MACHINE);
937                 str=msysinstalldir;
938                 if ( !str.IsEmpty() )
939                 {
940                         str += (str[str.GetLength()-1] != '\\') ? "\\bin" : "bin";
941                         msysdir=str;
942                         msysdir.write();
943                 }
944                 else
945                 {
946                         return false;
947                 }
948         }
949 #endif
950         //CGit::m_MsysGitPath=str;
951
952         //set path
953
954         _tdupenv_s(&oldpath,&size,_T("PATH")); 
955
956         CString path;
957         path.Format(_T("%s;%s"),oldpath,str);
958
959         _tputenv_s(_T("PATH"),path);
960
961         CString sOldPath = oldpath;
962         free(oldpath);
963
964
965     if( !FindGitPath() )
966         {
967                 if(!homesize)
968                 {
969                         _tputenv_s(_T("HOME"),_T(""));
970                 }
971                 return false;
972         }
973         else
974         {
975 #ifdef _TORTOISESHELL
976                 l_processEnv = GetEnvironmentStrings();
977                 // updated environment is now duplicated for use in CreateProcess, restore original PATH for current process
978                 _tputenv_s(_T("PATH"),sOldPath);
979                 if(!homesize)
980                 {
981                         _tputenv_s(_T("HOME"),_T(""));
982                 }
983 #endif
984
985                 bInitialized = TRUE;
986                 return true;
987         }
988 }
989
990
991 class CGitCall_EnumFiles : public CGitCall
992 {
993 public:
994         CGitCall_EnumFiles(const TCHAR *pszProjectPath, const TCHAR *pszSubPath, unsigned int nFlags, WGENUMFILECB *pEnumCb, void *pUserData)
995         :       m_pszProjectPath(pszProjectPath),
996                 m_pszSubPath(pszSubPath),
997                 m_nFlags(nFlags),
998                 m_pEnumCb(pEnumCb),
999                 m_pUserData(pUserData)
1000         {
1001         }
1002
1003         typedef std::map<CStringA,char> TStrCharMap;
1004
1005         const TCHAR *   m_pszProjectPath;
1006         const TCHAR *   m_pszSubPath;
1007         unsigned int    m_nFlags;
1008         WGENUMFILECB *  m_pEnumCb;
1009         void *                  m_pUserData;
1010
1011         BYTE_VECTOR             m_DataCollector;
1012
1013         virtual bool    OnOutputData(const BYTE* data, size_t size)
1014         {
1015                 m_DataCollector.append(data,size);
1016                 while(true)
1017                 {
1018                         // lines from igit.exe are 0 terminated
1019                         int found=m_DataCollector.findData((const BYTE*)"",1);
1020                         if(found<0)
1021                                 return false;
1022                         OnSingleLine( (LPCSTR)&*m_DataCollector.begin() );
1023                         m_DataCollector.erase(m_DataCollector.begin(), m_DataCollector.begin()+found+1);
1024                 }
1025                 return false;//Should never reach this
1026         }
1027         virtual void    OnEnd()
1028         {
1029         }
1030
1031         BYTE HexChar(char ch)
1032         {
1033                 if (ch >= '0' && ch <= '9')
1034                         return (UINT)(ch - '0');
1035                 else if (ch >= 'A' && ch <= 'F')
1036                         return (UINT)(ch - 'A') + 10;
1037                 else if (ch >= 'a' && ch <= 'f')
1038                         return (UINT)(ch - 'a') + 10;
1039                 else
1040                         return 0;
1041         }
1042
1043         bool OnSingleLine(LPCSTR line)
1044         {
1045                 //Parse single line
1046
1047                 wgFile_s fileStatus;
1048
1049                 // file/dir type
1050
1051                 fileStatus.nFlags = 0;
1052                 if (*line == 'D')
1053                         fileStatus.nFlags |= WGFF_Directory;
1054                 else if (*line != 'F')
1055                         // parse error
1056                         return false;
1057                 line += 2;
1058
1059                 // status
1060
1061                 fileStatus.nStatus = WGFS_Unknown;
1062                 switch (*line)
1063                 {
1064                 case 'N': fileStatus.nStatus = WGFS_Normal; break;
1065                 case 'M': fileStatus.nStatus = WGFS_Modified; break;
1066                 case 'S': fileStatus.nStatus = WGFS_Staged; break;
1067                 case 'A': fileStatus.nStatus = WGFS_Added; break;
1068                 case 'C': fileStatus.nStatus = WGFS_Conflicted; break;
1069                 case 'D': fileStatus.nStatus = WGFS_Deleted; break;
1070                 case 'I': fileStatus.nStatus = WGFS_Ignored; break;
1071                 case 'U': fileStatus.nStatus = WGFS_Unversioned; break;
1072                 case 'E': fileStatus.nStatus = WGFS_Empty; break;
1073                 case '?': fileStatus.nStatus = WGFS_Unknown; break;
1074                 default:
1075                         // parse error
1076                         return false;
1077                 }
1078                 line += 2;
1079
1080                 // file sha1
1081
1082                 BYTE sha1[20];
1083                 fileStatus.sha1 = NULL;
1084                 if ( !(fileStatus.nFlags & WGFF_Directory) )
1085                 {
1086                         for (int i=0; i<20; i++)
1087                         {
1088                                 sha1[i]  = (HexChar(line[0])<<4)&0xF0;
1089                                 sha1[i] |= HexChar(line[1])&0xF;
1090
1091                                 line += 2;
1092                         }
1093
1094                         line++;
1095                 }
1096
1097                 // filename
1098                 int len = strlen(line);
1099                 if (len && len < 2048)
1100                 {
1101                         WCHAR *buf = (WCHAR*)alloca((len*4+2)*sizeof(WCHAR));
1102                         *buf = 0;
1103                         MultiByteToWideChar(CP_ACP, 0, line, len+1, buf, len*4+1);
1104                         fileStatus.sFileName = buf;
1105
1106                         if (*buf && (*m_pEnumCb)(&fileStatus,m_pUserData))
1107                                 return false;
1108                 }
1109
1110                 return true;
1111         }
1112 };
1113
1114 BOOL CGit::EnumFiles(const TCHAR *pszProjectPath, const TCHAR *pszSubPath, unsigned int nFlags, WGENUMFILECB *pEnumCb, void *pUserData)
1115 {
1116         if(!pszProjectPath || *pszProjectPath=='\0')
1117                 return FALSE;
1118
1119         CGitCall_EnumFiles W_GitCall(pszProjectPath,pszSubPath,nFlags,pEnumCb,pUserData);
1120         CString cmd;
1121
1122 /*      char W_szToDir[MAX_PATH];
1123         strncpy(W_szToDir,pszProjectPath,sizeof(W_szToDir)-1);
1124         if(W_szToDir[strlen(W_szToDir)-1]!='\\')
1125                 strncat(W_szToDir,"\\",sizeof(W_szToDir)-1);
1126
1127         SetCurrentDirectoryA(W_szToDir);
1128         GetCurrentDirectoryA(sizeof(W_szToDir)-1,W_szToDir);
1129 */
1130         SetCurrentDir(pszProjectPath);
1131
1132         CString sMode;
1133         if (nFlags)
1134         {
1135                 if (nFlags & WGEFF_NoRecurse) sMode += _T("r");
1136                 if (nFlags & WGEFF_FullPath) sMode += _T("f");
1137                 if (nFlags & WGEFF_DirStatusDelta) sMode += _T("d");
1138                 if (nFlags & WGEFF_DirStatusAll) sMode += _T("D");
1139                 if (nFlags & WGEFF_EmptyAsNormal) sMode += _T("e");
1140                 if (nFlags & WGEFF_SingleFile) sMode += _T("s");
1141         }
1142         else
1143         {
1144                 sMode = _T("-");
1145         }
1146
1147         // NOTE: there seems to be some issue with msys based app receiving backslash on commandline, at least
1148         // if followed by " like for example 'igit "C:\"', the commandline igit receives is 'igit.exe C:" status' with
1149         // the 'C:" status' part as a single arg, Maybe it uses unix style processing. In order to avoid this just
1150         // use forward slashes for supplied project and sub paths
1151
1152         CString sProjectPath = pszProjectPath;
1153         sProjectPath.Replace(_T('\\'), _T('/'));
1154
1155         if (pszSubPath)
1156         {
1157                 CString sSubPath = pszSubPath;
1158                 sSubPath.Replace(_T('\\'), _T('/'));
1159
1160                 cmd.Format(_T("igit.exe \"%s\" status %s \"%s\""), sProjectPath, sMode, sSubPath);
1161         }
1162         else
1163         {
1164                 cmd.Format(_T("igit.exe \"%s\" status %s"), sProjectPath, sMode);
1165         }
1166
1167         //OutputDebugStringA("---");OutputDebugStringW(cmd);OutputDebugStringA("\r\n");
1168
1169         W_GitCall.SetCmd(cmd);
1170         // NOTE: should igit get added as a part of msysgit then use below line instead of the above one
1171         //W_GitCall.SetCmd(CGit::ms_LastMsysGitDir + cmd);
1172
1173         if ( Run(&W_GitCall) )
1174                 return FALSE;
1175
1176         return TRUE;
1177 }
1178
1179 BOOL CGit::CheckCleanWorkTree()
1180 {
1181         CString out;
1182         CString cmd;
1183         cmd=_T("git.exe rev-parse --verify HEAD");
1184
1185         if(g_Git.Run(cmd,&out,CP_UTF8))
1186                 return FALSE;
1187
1188         cmd=_T("git.exe update-index --ignore-submodules --refresh");
1189         if(g_Git.Run(cmd,&out,CP_UTF8))
1190                 return FALSE;
1191
1192         cmd=_T("git.exe diff-files --quiet --ignore-submodules");
1193         if(g_Git.Run(cmd,&out,CP_UTF8))
1194                 return FALSE;
1195
1196         cmd=_T("git diff-index --cached --quiet HEAD --ignore-submodules");
1197         if(g_Git.Run(cmd,&out,CP_UTF8))
1198                 return FALSE;
1199
1200         return TRUE;
1201 }
1202 int CGit::Revert(CTGitPathList &list,bool keep)
1203 {
1204         int ret;
1205         for(int i=0;i<list.GetCount();i++)
1206         {       
1207                 ret = Revert((CTGitPath&)list[i],keep);
1208                 if(ret)
1209                         return ret;
1210         }
1211         return 0;
1212 }
1213 int CGit::Revert(CTGitPath &path,bool keep)
1214 {
1215         CString cmd, out;
1216         if(path.m_Action & CTGitPath::LOGACTIONS_ADDED)
1217         {       //To init git repository, there are not HEAD, so we can use git reset command
1218                 cmd.Format(_T("git.exe rm --cached -- \"%s\""),path.GetGitPathString());
1219
1220                 if(g_Git.Run(cmd,&out,CP_ACP))
1221                         return -1;
1222         }
1223         else if(path.m_Action & CTGitPath::LOGACTIONS_REPLACED )
1224         {
1225                 cmd.Format(_T("git.exe mv -- \"%s\" \"%s\""),path.GetGitPathString(),path.GetGitOldPathString());
1226                 if(g_Git.Run(cmd,&out,CP_ACP))
1227                         return -1;
1228                 
1229                 cmd.Format(_T("git.exe checkout HEAD -f -- \"%s\""),path.GetGitOldPathString());
1230                 if(g_Git.Run(cmd,&out,CP_ACP))
1231                         return -1;
1232         }
1233         else
1234         {
1235                 cmd.Format(_T("git.exe checkout HEAD -f -- \"%s\""),path.GetGitPathString());
1236                 if(g_Git.Run(cmd,&out,CP_ACP))
1237                         return -1;
1238         }
1239         return 0;
1240 }
1241
1242 int CGit::ListConflictFile(CTGitPathList &list,CTGitPath *path)
1243 {
1244         BYTE_VECTOR vector;
1245
1246         CString cmd;
1247         if(path)
1248                 cmd.Format(_T("git.exe ls-files -u -t -z -- \"%s\""),path->GetGitPathString());
1249         else
1250                 cmd=_T("git.exe ls-files -u -t -z");
1251
1252         if(g_Git.Run(cmd,&vector))
1253         {
1254                 return -1;
1255         }
1256
1257         list.ParserFromLsFile(vector);
1258
1259         return 0;
1260 }
1261
1262 bool CGit::IsFastForward(CString &from, CString &to)
1263 {
1264         CString base,hash;
1265         CString cmd;
1266         cmd.Format(_T("git.exe merge-base %s %s"), to,from);
1267
1268         if(g_Git.Run(cmd,&base,CP_ACP))
1269         {
1270                 //CMessageBox::Show(NULL,base,_T("TortoiseGit"),MB_OK|MB_ICONERROR);
1271                 return false;
1272         }
1273         base=base.Left(40);
1274
1275         hash=g_Git.GetHash(from);
1276
1277         hash=hash.Left(40);
1278         
1279         return hash == base;
1280 }
1281
1282 unsigned int CGit::Hash2int(CString &hash)
1283 {
1284         int ret=0;
1285         for(int i=0;i<8;i++)
1286         {
1287                 ret =ret <<4;
1288                 if(hash[i]>=_T('a'))
1289                         ret |= (hash[i]-_T('a')+10)&0xFF;
1290                 else if(hash[i]>=_T('A'))
1291                         ret |= (hash[i]-_T('A')+10)&0xFF;
1292                 else
1293                         ret |= (hash[i]-_T('0'))&0xFF;          
1294                 
1295         }
1296         return ret;
1297 }