Git.cpp 86.2 KB
Newer Older
Sven Strickroth's avatar
Sven Strickroth committed
1 2
// TortoiseGit - a Windows shell extension for easy version control

Sven Strickroth's avatar
Sven Strickroth committed
3
// Copyright (C) 2008-2015 - TortoiseGit
Sven Strickroth's avatar
Sven Strickroth committed
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//

20
#include "stdafx.h"
李智's avatar
李智 committed
21
#include "Git.h"
李智's avatar
李智 committed
22
#include "registry.h"
23
#include "GitForWindows.h"
24
#include "UnicodeUtils.h"
25
#include "gitdll.h"
26
#include <fstream>
ch3cooli's avatar
ch3cooli committed
27
#include <iterator>
28
#include "FormatMessageWrapper.h"
29
#include "SmartHandle.h"
ch3cooli's avatar
ch3cooli committed
30
#include "MassiveGitTaskBase.h"
31
#include "git2/sys/filter.h"
Sven Strickroth's avatar
Sven Strickroth committed
32
#include "git2/sys/transport.h"
33
#include "../libgit2/filter-filter.h"
Sven Strickroth's avatar
Sven Strickroth committed
34
#include "../libgit2/ssh-wintunnel.h"
李智's avatar
李智 committed
35

36
bool CGit::ms_bCygwinGit = (CRegDWORD(_T("Software\\TortoiseGit\\CygwinHack"), FALSE) == TRUE);
37
bool CGit::ms_bMsys2Git = (CRegDWORD(_T("Software\\TortoiseGit\\Msys2Hack"), FALSE) == TRUE);
38
int CGit::m_LogEncode=CP_UTF8;
39
typedef CComCritSecLock<CComCriticalSection> CAutoLocker;
李智's avatar
李智 committed
40

41
static LPCTSTR nextpath(const wchar_t* path, wchar_t* buf, size_t buflen)
42
{
Sven Strickroth's avatar
Sven Strickroth committed
43 44
	if (path == NULL || buf == NULL || buflen == 0)
		return NULL;
45

46 47
	const wchar_t* base = path;
	wchar_t term = (*path == L'"') ? *path++ : L';';
48

Sven Strickroth's avatar
Sven Strickroth committed
49 50
	for (buflen--; *path && *path != term && buflen; buflen--)
		*buf++ = *path++;
51

Sven Strickroth's avatar
Sven Strickroth committed
52
	*buf = L'\0'; /* reserved a byte via initial subtract */
53

Sven Strickroth's avatar
Sven Strickroth committed
54
	while (*path == term || *path == L';')
55
		++path;
56

Sven Strickroth's avatar
Sven Strickroth committed
57
	return (path != base) ? path : NULL;
58 59
}

60
static CString FindFileOnPath(const CString& filename, LPCTSTR env, bool wantDirectory = false)
61
{
62
	TCHAR buf[MAX_PATH] = { 0 };
63 64

	// search in all paths defined in PATH
Sven Strickroth's avatar
Sven Strickroth committed
65
	while ((env = nextpath(env, buf, MAX_PATH - 1)) != NULL && *buf)
66
	{
67
		TCHAR *pfin = buf + _tcslen(buf) - 1;
68 69

		// ensure trailing slash
70
		if (*pfin != _T('/') && *pfin != _T('\\'))
Sven Strickroth's avatar
Sven Strickroth committed
71
			_tcscpy_s(++pfin, 2, _T("\\")); // we have enough space left, MAX_PATH-1 is used in nextpath above
72

Sven Strickroth's avatar
Sven Strickroth committed
73
		const size_t len = _tcslen(buf);
74

75 76
		if ((len + filename.GetLength()) < MAX_PATH)
			_tcscpy_s(pfin + 1, MAX_PATH - len, filename);
77 78 79
		else
			break;

80
		if (PathFileExists(buf))
81
		{
82 83 84
			if (wantDirectory)
				pfin[1] = 0;
			return buf;
85 86 87
		}
	}

88
	return _T("");
89 90
}

91
static BOOL FindGitPath()
92
{
93 94 95 96
	size_t size;
	_tgetenv_s(&size, NULL, 0, _T("PATH"));
	if (!size)
		return FALSE;
97

98 99 100 101
	TCHAR* env = (TCHAR*)alloca(size * sizeof(TCHAR));
	if (!env)
		return FALSE;
	_tgetenv_s(&size, env, size, _T("PATH"));
102

103 104
	CString gitExeDirectory = FindFileOnPath(_T("git.exe"), env, true);
	if (!gitExeDirectory.IsEmpty())
105
	{
106 107
		CGit::ms_LastMsysGitDir = gitExeDirectory;
		CGit::ms_LastMsysGitDir.TrimRight(_T("\\"));
108 109 110 111
		if (CGit::ms_LastMsysGitDir.GetLength() > 12 && (CGit::ms_LastMsysGitDir.Right(12) == _T("\\mingw32\\bin") || CGit::ms_LastMsysGitDir.Right(12) == _T("\\mingw64\\bin")))
		{
			// prefer cmd directory as early Git for Windows 2.x releases only had this
			CString installRoot = CGit::ms_LastMsysGitDir.Mid(0, CGit::ms_LastMsysGitDir.GetLength() - 12) + _T("\\cmd\\git.exe");
112
			if (PathFileExists(installRoot))
113 114 115
				CGit::ms_LastMsysGitDir = CGit::ms_LastMsysGitDir.Mid(0, CGit::ms_LastMsysGitDir.GetLength() - 12) + _T("\\cmd");
		}
		if (CGit::ms_LastMsysGitDir.GetLength() > 4 && CGit::ms_LastMsysGitDir.Right(4) == _T("\\cmd"))
116 117 118 119
		{
			// often the msysgit\cmd folder is on the %PATH%, but
			// that git.exe does not work, so try to guess the bin folder
			CString binDir = CGit::ms_LastMsysGitDir.Mid(0, CGit::ms_LastMsysGitDir.GetLength() - 4) + _T("\\bin\\git.exe");
120
			if (PathFileExists(binDir))
121 122 123 124
				CGit::ms_LastMsysGitDir = CGit::ms_LastMsysGitDir.Mid(0, CGit::ms_LastMsysGitDir.GetLength() - 4) + _T("\\bin");
		}
		return TRUE;
	}
125

126 127
	return FALSE;
}
128

129 130 131
static CString FindExecutableOnPath(const CString& executable, LPCTSTR env)
{
	CString filename = executable;
132

133 134
	if (executable.GetLength() < 4 || executable.Find(_T(".exe"), executable.GetLength() - 4) != executable.GetLength() - 4)
		filename += _T(".exe");
135

136
	if (PathFileExists(filename))
137
		return filename;
138

139 140 141 142
	filename = FindFileOnPath(filename, env);
	if (!filename.IsEmpty())
		return filename;
	
143 144 145
	return executable;
}

146
static bool g_bSortLogical;
147
static bool g_bSortLocalBranchesFirst;
148
static bool g_bSortTagsReversed;
149
static git_cred_acquire_cb g_Git2CredCallback;
150
static git_transport_certificate_check_cb g_Git2CheckCertificateCallback;
151

152
static void GetSortOptions()
153 154 155 156
{
	g_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
	if (g_bSortLogical)
		g_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
157
	g_bSortLocalBranchesFirst = !CRegDWORD(L"Software\\TortoiseGit\\NoSortLocalBranchesFirst", 0, false, HKEY_CURRENT_USER);
158
	if (g_bSortLocalBranchesFirst)
159
		g_bSortLocalBranchesFirst = !CRegDWORD(L"Software\\TortoiseGit\\NoSortLocalBranchesFirst", 0, false, HKEY_LOCAL_MACHINE);
160 161 162
	g_bSortTagsReversed = !!CRegDWORD(L"Software\\TortoiseGit\\SortTagsReversed", 0, false, HKEY_LOCAL_MACHINE);
	if (!g_bSortTagsReversed)
		g_bSortTagsReversed = !!CRegDWORD(L"Software\\TortoiseGit\\SortTagsReversed", 0, false, HKEY_CURRENT_USER);
163 164
}

Sven Strickroth's avatar
Sven Strickroth committed
165
static int LogicalComparePredicate(const CString &left, const CString &right)
166 167 168 169 170
{
	if (g_bSortLogical)
		return StrCmpLogicalW(left, right) < 0;
	return StrCmpI(left, right) < 0;
}
171

172 173 174 175 176 177 178
static int LogicalCompareReversedPredicate(const CString &left, const CString &right)
{
	if (g_bSortLogical)
		return StrCmpLogicalW(left, right) > 0;
	return StrCmpI(left, right) > 0;
}

Sven Strickroth's avatar
Sven Strickroth committed
179
static int LogicalCompareBranchesPredicate(const CString &left, const CString &right)
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
{
	if (g_bSortLocalBranchesFirst)
	{
		int leftIsRemote = left.Find(_T("remotes/"));
		int rightIsRemote = right.Find(_T("remotes/"));

		if (leftIsRemote == 0 && rightIsRemote < 0)
			return false;
		else if (leftIsRemote < 0 && rightIsRemote == 0)
			return true;
	}
	if (g_bSortLogical)
		return StrCmpLogicalW(left, right) < 0;
	return StrCmpI(left, right) < 0;
}

196 197
#define CALL_OUTPUT_READ_CHUNK_SIZE 1024

198
CString CGit::ms_LastMsysGitDir;
199
CString CGit::ms_MsysGitRootDir;
200
int CGit::ms_LastMsysGitVersion = 0;
李智's avatar
李智 committed
201
CGit g_Git;
202

203

李智's avatar
李智 committed
204 205
CGit::CGit(void)
{
206
	GetCurrentDirectory(MAX_PATH, CStrBuf(m_CurrentDir, MAX_PATH));
李智's avatar
李智 committed
207
	m_IsGitDllInited = false;
208
	m_GitDiff=0;
209
	m_GitSimpleListDiff=0;
210
	m_IsUseGitDLL = !!CRegDWORD(_T("Software\\TortoiseGit\\UsingGitDLL"),1);
Sven Strickroth's avatar
Sven Strickroth committed
211
	m_IsUseLibGit2 = !!CRegDWORD(_T("Software\\TortoiseGit\\UseLibgit2"), TRUE);
212
	m_IsUseLibGit2_mask = CRegDWORD(_T("Software\\TortoiseGit\\UseLibgit2_mask"), DEFAULT_USE_LIBGIT2_MASK);
李智's avatar
李智 committed
213

Sven Strickroth's avatar
Sven Strickroth committed
214 215
	SecureZeroMemory(&m_CurrentGitPi, sizeof(PROCESS_INFORMATION));

216
	GetSortOptions();
217
	this->m_bInitialized =false;
218
	CheckMsysGitDir();
李智's avatar
李智 committed
219
	m_critGitDllSec.Init();
李智's avatar
李智 committed
220 221 222 223
}

CGit::~CGit(void)
{
224 225 226 227 228
	if(this->m_GitDiff)
	{
		git_close_diff(m_GitDiff);
		m_GitDiff=0;
	}
229 230 231 232 233
	if(this->m_GitSimpleListDiff)
	{
		git_close_diff(m_GitSimpleListDiff);
		m_GitSimpleListDiff=0;
	}
李智's avatar
李智 committed
234 235
}

Sven Strickroth's avatar
Sven Strickroth committed
236
bool CGit::IsBranchNameValid(const CString& branchname)
237
{
238
	if (branchname.Left(1) == _T("-")) // branch names starting with a dash are discouraged when used with git.exe, see https://github.com/git/git/commit/6348624010888bd2353e5cebdc2b5329490b0f6d
239 240 241
		return false;
	if (branchname.FindOneOf(_T("\"|<>")) >= 0) // not valid on Windows
		return false;
242 243
	CStringA branchA = CUnicodeUtils::GetUTF8(_T("refs/heads/") + branchname);
	return !!git_reference_is_valid_name(branchA);
244 245
}

246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
static bool IsPowerShell(CString cmd)
{
	cmd.MakeLower();
	int powerShellPos = cmd.Find(_T("powershell"));
	if (powerShellPos < 0)
		return false;

	// found the word powershell, check that it is the command and not any parameter
	int end = cmd.GetLength();
	if (cmd.Find(_T('"')) == 0)
	{
		int secondDoubleQuote = cmd.Find(_T('"'), 1);
		if (secondDoubleQuote > 0)
			end = secondDoubleQuote;
	}
	else
	{
		int firstSpace = cmd.Find(_T(' '));
		if (firstSpace > 0)
			end = firstSpace;
	}

	return (end - 4 - 10 == powerShellPos || end - 10 == powerShellPos); // len(".exe")==4, len("powershell")==10
}

Sven Strickroth's avatar
Sven Strickroth committed
271
int CGit::RunAsync(CString cmd, PROCESS_INFORMATION *piOut, HANDLE *hReadOut, HANDLE *hErrReadOut, CString *StdioFile)
李智's avatar
李智 committed
272 273
{
	SECURITY_ATTRIBUTES sa;
274 275
	CAutoGeneralHandle hRead, hWrite, hReadErr, hWriteErr;
	CAutoGeneralHandle hStdioFile;
李智's avatar
李智 committed
276 277 278 279

	sa.nLength = sizeof(SECURITY_ATTRIBUTES);
	sa.lpSecurityDescriptor=NULL;
	sa.bInheritHandle=TRUE;
280
	if (!CreatePipe(hRead.GetPointer(), hWrite.GetPointer(), &sa, 0))
李智's avatar
李智 committed
281
	{
282
		CString err = CFormatMessageWrapper();
283
		CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": could not open stdout pipe: %s\n"), (LPCTSTR)err.Trim());
284
		return TGIT_GIT_ERROR_OPEN_PIP;
李智's avatar
李智 committed
285
	}
286
	if (hErrReadOut && !CreatePipe(hReadErr.GetPointer(), hWriteErr.GetPointer(), &sa, 0))
287 288
	{
		CString err = CFormatMessageWrapper();
289
		CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": could not open stderr pipe: %s\n"), (LPCTSTR)err.Trim());
Sven Strickroth's avatar
Sven Strickroth committed
290
		return TGIT_GIT_ERROR_OPEN_PIP;
291
	}
292

李智's avatar
李智 committed
293 294
	if(StdioFile)
	{
295 296
		hStdioFile=CreateFile(*StdioFile,GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,
								&sa,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
李智's avatar
李智 committed
297 298
	}

299
	STARTUPINFO si = { 0 };
李智's avatar
李智 committed
300 301 302
	PROCESS_INFORMATION pi;
	si.cb=sizeof(STARTUPINFO);

Sven Strickroth's avatar
Sven Strickroth committed
303 304 305 306
	if (hErrReadOut)
		si.hStdError = hWriteErr;
	else
		si.hStdError = hWrite;
李智's avatar
李智 committed
307 308 309 310 311
	if(StdioFile)
		si.hStdOutput=hStdioFile;
	else
		si.hStdOutput=hWrite;

李智's avatar
李智 committed
312 313 314
	si.wShowWindow=SW_HIDE;
	si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;

315
	LPTSTR pEnv = m_Environment;
316
	DWORD dwFlags = pEnv ? CREATE_UNICODE_ENVIRONMENT : 0;
317

318 319 320 321 322 323 324 325 326
	dwFlags |= CREATE_NEW_PROCESS_GROUP;

	// CREATE_NEW_CONSOLE makes git (but not ssh.exe, see issue #2257) recognize that it has no console in order to launch askpass to ask for the password,
	// DETACHED_PROCESS which was originally used here has the same effect (but works with git.exe AND ssh.exe), however, it prevents PowerShell from working (cf. issue #2143)
	// => we keep using DETACHED_PROCESS as the default, but if cmd contains pwershell we use CREATE_NEW_CONSOLE
	if (IsPowerShell(cmd))
		dwFlags |= CREATE_NEW_CONSOLE;
	else
		dwFlags |= DETACHED_PROCESS;
327 328

	memset(&this->m_CurrentGitPi,0,sizeof(PROCESS_INFORMATION));
329
	memset(&pi, 0, sizeof(PROCESS_INFORMATION));
330

331 332 333 334 335 336 337
	if (ms_bMsys2Git && cmd.Find(_T("git")) == 0 && cmd.Find(L"git.exe config ") == -1)
	{
		cmd.Replace(_T("\\"), _T("\\\\\\\\"));
		cmd.Replace(_T("\""), _T("\\\""));
		cmd = _T('"') + CGit::ms_LastMsysGitDir + _T("\\bash.exe\" -c \"/usr/bin/") + cmd + _T('"');
	}
	else if (ms_bCygwinGit && cmd.Find(_T("git")) == 0 && cmd.Find(L"git.exe config ") == -1)
338 339 340 341 342 343
	{
		cmd.Replace(_T('\\'), _T('/'));
		cmd.Replace(_T("\""), _T("\\\""));
		cmd = _T('"') + CGit::ms_LastMsysGitDir + _T("\\bash.exe\" -c \"/bin/") + cmd + _T('"');
	}
	else if(cmd.Find(_T("git")) == 0)
344 345 346 347 348 349 350
	{
		int firstSpace = cmd.Find(_T(" "));
		if (firstSpace > 0)
			cmd = _T('"')+CGit::ms_LastMsysGitDir+_T("\\")+ cmd.Left(firstSpace) + _T('"')+ cmd.Mid(firstSpace);
		else
			cmd=_T('"')+CGit::ms_LastMsysGitDir+_T("\\")+cmd+_T('"');
	}
351

352 353
	CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": executing %s\n"), (LPCTSTR)cmd);
	if(!CreateProcess(nullptr, cmd.GetBuffer(), nullptr, nullptr, TRUE, dwFlags, pEnv, m_CurrentDir.GetBuffer(), &si, &pi))
李智's avatar
李智 committed
354
	{
355
		CString err = CFormatMessageWrapper();
356
		CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": error while executing command: %s\n"), (LPCTSTR)err.Trim());
357
		return TGIT_GIT_ERROR_CREATE_PROCESS;
李智's avatar
李智 committed
358
	}
359

360
	m_CurrentGitPi = pi;
361

李智's avatar
李智 committed
362 363 364
	if(piOut)
		*piOut=pi;
	if(hReadOut)
365
		*hReadOut = hRead.Detach();
Sven Strickroth's avatar
Sven Strickroth committed
366
	if(hErrReadOut)
367
		*hErrReadOut = hReadErr.Detach();
李智's avatar
李智 committed
368 369 370
	return 0;

}
李智's avatar
李智 committed
371
//Must use sperate function to convert ANSI str to union code string
372
//Becuase A2W use stack as internal convert buffer.
Sven Strickroth's avatar
Sven Strickroth committed
373
void CGit::StringAppend(CString *str, const BYTE *p, int code,int length)
李智's avatar
李智 committed
374
{
375 376 377
	if(str == NULL)
		return ;

李智's avatar
李智 committed
378 379
	int len ;
	if(length<0)
Sven Strickroth's avatar
Sven Strickroth committed
380
		len = (int)strlen((const char*)p);
李智's avatar
李智 committed
381 382
	else
		len=length;
383 384
	if (len == 0)
		return;
385
	int currentContentLen = str->GetLength();
Sven Strickroth's avatar
Sven Strickroth committed
386
	WCHAR * buf = str->GetBuffer(len * 4 + 1 + currentContentLen) + currentContentLen;
387 388
	int appendedLen = MultiByteToWideChar(code, 0, (LPCSTR)p, len, buf, len * 4);
	str->ReleaseBuffer(currentContentLen + appendedLen); // no - 1 because MultiByteToWideChar is called with a fixed length (thus no nul char included)
389
}
390

391 392
// This method was originally used to check for orphaned branches
BOOL CGit::CanParseRev(CString ref)
393
{
394 395 396
	if (ref.IsEmpty())
		ref = _T("HEAD");

397
	CString cmdout;
398
	if (Run(_T("git.exe rev-parse --revs-only ") + ref, &cmdout, CP_UTF8))
399
	{
400
		return FALSE;
401 402
	}
	if(cmdout.IsEmpty())
403
		return FALSE;
李智's avatar
李智 committed
404

405
	return TRUE;
406
}
Sven Strickroth's avatar
Sven Strickroth committed
407

408
// Checks if we have an orphaned HEAD
409 410
BOOL CGit::IsInitRepos()
{
411 412
	CGitHash hash;
	if (GetHash(hash, _T("HEAD")) != 0)
Sven Strickroth's avatar
Sven Strickroth committed
413
		return FALSE;
414
	return hash.IsEmpty() ? TRUE : FALSE;
415 416
}

Sven Strickroth's avatar
Sven Strickroth committed
417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432
DWORD WINAPI CGit::AsyncReadStdErrThread(LPVOID lpParam)
{
	PASYNCREADSTDERRTHREADARGS pDataArray;
	pDataArray = (PASYNCREADSTDERRTHREADARGS)lpParam;

	DWORD readnumber;
	BYTE data[CALL_OUTPUT_READ_CHUNK_SIZE];
	while (ReadFile(pDataArray->fileHandle, data, CALL_OUTPUT_READ_CHUNK_SIZE, &readnumber, NULL))
	{
		if (pDataArray->pcall->OnOutputErrData(data,readnumber))
			break;
	}

	return 0;
}

433
int CGit::Run(CGitCall* pcall)
李智's avatar
李智 committed
434 435
{
	PROCESS_INFORMATION pi;
436 437
	CAutoGeneralHandle hRead, hReadErr;
	if (RunAsync(pcall->GetCmd(), &pi, hRead.GetPointer(), hReadErr.GetPointer()))
438
		return TGIT_GIT_ERROR_CREATE_PROCESS;
李智's avatar
李智 committed
439

440 441 442
	CAutoGeneralHandle piThread(pi.hThread);
	CAutoGeneralHandle piProcess(pi.hProcess);

Sven Strickroth's avatar
Sven Strickroth committed
443 444 445
	ASYNCREADSTDERRTHREADARGS threadArguments;
	threadArguments.fileHandle = hReadErr;
	threadArguments.pcall = pcall;
446
	CAutoGeneralHandle thread = CreateThread(NULL, 0, AsyncReadStdErrThread, &threadArguments, 0, NULL);
Sven Strickroth's avatar
Sven Strickroth committed
447

李智's avatar
李智 committed
448
	DWORD readnumber;
449
	BYTE data[CALL_OUTPUT_READ_CHUNK_SIZE];
450
	bool bAborted=false;
451
	while(ReadFile(hRead,data,CALL_OUTPUT_READ_CHUNK_SIZE,&readnumber,NULL))
李智's avatar
李智 committed
452
	{
453
		// TODO: when OnOutputData() returns 'true', abort git-command. Send CTRL-C signal?
454 455 456
		if(!bAborted)//For now, flush output when command aborted.
			if(pcall->OnOutputData(data,readnumber))
				bAborted=true;
李智's avatar
李智 committed
457
	}
458 459
	if(!bAborted)
		pcall->OnEnd();
李智's avatar
李智 committed
460

Sven Strickroth's avatar
Sven Strickroth committed
461 462 463
	if (thread)
		WaitForSingleObject(thread, INFINITE);

李智's avatar
李智 committed
464 465 466 467 468
	WaitForSingleObject(pi.hProcess, INFINITE);
	DWORD exitcode =0;

	if(!GetExitCodeProcess(pi.hProcess,&exitcode))
	{
469
		CString err = CFormatMessageWrapper();
470
		CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": could not get exit code: %s\n"), (LPCTSTR)err.Trim());
471
		return TGIT_GIT_ERROR_GET_EXIT_CODE;
李智's avatar
李智 committed
472
	}
473 474
	else
		CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": process exited: %d\n"), exitcode);
李智's avatar
李智 committed
475 476

	return exitcode;
477 478 479 480
}
class CGitCall_ByteVector : public CGitCall
{
public:
481
	CGitCall_ByteVector(CString cmd,BYTE_VECTOR* pvector, BYTE_VECTOR* pvectorErr = NULL):CGitCall(cmd),m_pvector(pvector),m_pvectorErr(pvectorErr){}
482 483
	virtual bool OnOutputData(const BYTE* data, size_t size)
	{
484
		if (!m_pvector || size == 0)
Sven Strickroth's avatar
Sven Strickroth committed
485
			return false;
486 487 488 489 490
		size_t oldsize=m_pvector->size();
		m_pvector->resize(m_pvector->size()+size);
		memcpy(&*(m_pvector->begin()+oldsize),data,size);
		return false;
	}
Sven Strickroth's avatar
Sven Strickroth committed
491 492
	virtual bool OnOutputErrData(const BYTE* data, size_t size)
	{
Sven Strickroth's avatar
Sven Strickroth committed
493
		if (!m_pvectorErr || size == 0)
494
			return false;
Sven Strickroth's avatar
Sven Strickroth committed
495 496 497 498 499
		size_t oldsize = m_pvectorErr->size();
		m_pvectorErr->resize(m_pvectorErr->size() + size);
		memcpy(&*(m_pvectorErr->begin() + oldsize), data, size);
		return false;
	}
500
	BYTE_VECTOR* m_pvector;
Sven Strickroth's avatar
Sven Strickroth committed
501
	BYTE_VECTOR* m_pvectorErr;
李智's avatar
李智 committed
502

503
};
Sven Strickroth's avatar
Sven Strickroth committed
504
int CGit::Run(CString cmd,BYTE_VECTOR *vector, BYTE_VECTOR *vectorErr)
505
{
Sven Strickroth's avatar
Sven Strickroth committed
506
	CGitCall_ByteVector call(cmd, vector, vectorErr);
507
	return Run(&call);
李智's avatar
李智 committed
508
}
Sven Strickroth's avatar
Sven Strickroth committed
509
int CGit::Run(CString cmd, CString* output, int code)
李智's avatar
李智 committed
510
{
Sven Strickroth's avatar
Sven Strickroth committed
511
	CString err;
李智's avatar
李智 committed
512
	int ret;
Sven Strickroth's avatar
Sven Strickroth committed
513
	ret = Run(cmd, output, &err, code);
李智's avatar
李智 committed
514

Sven Strickroth's avatar
Sven Strickroth committed
515
	if (output && !err.IsEmpty())
Sven Strickroth's avatar
Sven Strickroth committed
516
	{
Sven Strickroth's avatar
Sven Strickroth committed
517 518 519
		if (!output->IsEmpty())
			*output += _T("\n");
		*output += err;
Sven Strickroth's avatar
Sven Strickroth committed
520 521 522 523 524 525 526 527
	}

	return ret;
}
int CGit::Run(CString cmd, CString* output, CString* outputErr, int code)
{
	BYTE_VECTOR vector, vectorErr;
	int ret;
528 529 530 531
	if (outputErr)
		ret = Run(cmd, &vector, &vectorErr);
	else
		ret = Run(cmd, &vector);
Sven Strickroth's avatar
Sven Strickroth committed
532 533 534 535

	vector.push_back(0);
	StringAppend(output, &(vector[0]), code);

536 537 538 539 540
	if (outputErr)
	{
		vectorErr.push_back(0);
		StringAppend(outputErr, &(vectorErr[0]), code);
	}
541

542
	return ret;
李智's avatar
李智 committed
543 544
}

545 546 547 548 549 550 551 552 553 554 555
class CGitCallCb : public CGitCall
{
public:
	CGitCallCb(CString cmd, const GitReceiverFunc& recv): CGitCall(cmd), m_recv(recv) {}

	virtual bool OnOutputData(const BYTE* data, size_t size) override
	{
		// Add data
		if (size == 0)
			return false;
		int oldEndPos = m_buffer.GetLength();
556
		memcpy(m_buffer.GetBuffer(oldEndPos + (int)size) + oldEndPos, data, size);
557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591
		m_buffer.ReleaseBuffer(oldEndPos + (int)size);

		// Break into lines and feed to m_recv
		int eolPos;
		while ((eolPos = m_buffer.Find('\n')) >= 0)
		{
			m_recv(m_buffer.Left(eolPos));
			m_buffer = m_buffer.Mid(eolPos + 1);
		}
		return false;
	}

	virtual bool OnOutputErrData(const BYTE*, size_t) override
	{
		return false; // Ignore error output for now
	}

	virtual void OnEnd() override
	{
		if (!m_buffer.IsEmpty())
			m_recv(m_buffer);
		m_buffer.Empty(); // Just for sure
	}

private:
	GitReceiverFunc m_recv;
	CStringA m_buffer;
};

int CGit::Run(CString cmd, const GitReceiverFunc& recv)
{
	CGitCallCb call(cmd, recv);
	return Run(&call);
}

李智's avatar
李智 committed
592 593
CString CGit::GetUserName(void)
{
594 595 596 597 598
	CEnvironment env;
	env.CopyProcessEnvironment();
	CString envname = env.GetEnv(_T("GIT_AUTHOR_NAME"));
	if (!envname.IsEmpty())
		return envname;
599
	return GetConfigValue(L"user.name");
李智's avatar
李智 committed
600 601 602
}
CString CGit::GetUserEmail(void)
{
603 604 605 606 607 608
	CEnvironment env;
	env.CopyProcessEnvironment();
	CString envmail = env.GetEnv(_T("GIT_AUTHOR_EMAIL"));
	if (!envmail.IsEmpty())
		return envmail;

609 610 611
	return GetConfigValue(L"user.email");
}

612
CString CGit::GetConfigValue(const CString& name)
613 614
{
	CString configValue;
李智's avatar
李智 committed
615 616
	if(this->m_IsUseGitDLL)
	{
617 618
		CAutoLocker lock(g_Git.m_critGitDllSec);

619 620 621 622 623 624
		try
		{
			CheckAndInitDll();
		}catch(...)
		{
		}
李智's avatar
李智 committed
625
		CStringA key, value;
626
		key =  CUnicodeUtils::GetUTF8(name);
李智's avatar
李智 committed
627

628
		try
李智's avatar
李智 committed
629
		{
630
			if (git_get_config(key, CStrBufA(value, 4096), 4096))
631 632 633 634 635 636
				return CString();
		}
		catch (const char *msg)
		{
			::MessageBox(NULL, _T("Could not get config.\nlibgit reports:\n") + CString(msg), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
			return CString();
李智's avatar
李智 committed
637 638
		}

639
		StringAppend(&configValue, (BYTE*)(LPCSTR)value);
640
		return configValue;
641 642
	}
	else
李智's avatar
李智 committed
643
	{
644
		CString cmd;
645
		cmd.Format(L"git.exe config %s", (LPCTSTR)name);
646
		Run(cmd, &configValue, nullptr, CP_UTF8);
647 648 649
		if (configValue.IsEmpty())
			return configValue;
		return configValue.Left(configValue.GetLength() - 1); // strip last newline character
李智's avatar
李智 committed
650 651 652
	}
}

Sven Strickroth's avatar
Sven Strickroth committed
653
bool CGit::GetConfigValueBool(const CString& name)
654
{
655
	CString configValue = GetConfigValue(name);
656 657 658 659 660 661 662 663
	configValue.MakeLower();
	configValue.Trim();
	if(configValue == _T("true") || configValue == _T("on") || configValue == _T("yes") || StrToInt(configValue) != 0)
		return true;
	else
		return false;
}

664 665 666 667 668 669 670 671 672
int CGit::GetConfigValueInt32(const CString& name, int def)
{
	CString configValue = GetConfigValue(name);
	int value = def;
	if (!git_config_parse_int32(&value, CUnicodeUtils::GetUTF8(configValue)))
		return value;
	return def;
}

673
int CGit::SetConfigValue(const CString& key, const CString& value, CONFIG_TYPE type)
李智's avatar
李智 committed
674 675 676
{
	if(this->m_IsUseGitDLL)
	{
677 678
		CAutoLocker lock(g_Git.m_critGitDllSec);

679 680 681 682 683 684 685
		try
		{
			CheckAndInitDll();

		}catch(...)
		{
		}
李智's avatar
李智 committed
686 687
		CStringA keya, valuea;
		keya = CUnicodeUtils::GetMulti(key, CP_UTF8);
688
		valuea = CUnicodeUtils::GetUTF8(value);
李智's avatar
李智 committed
689

690 691
		try
		{
ch3cooli's avatar
ch3cooli committed
692
			return [=]() { return get_set_config(keya, valuea, type); }();
693 694 695 696 697 698
		}
		catch (const char *msg)
		{
			::MessageBox(NULL, _T("Could not set config.\nlibgit reports:\n") + CString(msg), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
			return -1;
		}
699 700
	}
	else
李智's avatar
李智 committed
701 702 703 704 705 706 707 708 709 710 711 712 713 714
	{
		CString cmd;
		CString option;
		switch(type)
		{
		case CONFIG_GLOBAL:
			option = _T("--global");
			break;
		case CONFIG_SYSTEM:
			option = _T("--system");
			break;
		default:
			break;
		}
715 716 717
		CString mangledValue = value;
		mangledValue.Replace(_T("\\\""), _T("\\\\\""));
		mangledValue.Replace(_T("\""), _T("\\\""));
718
		cmd.Format(_T("git.exe config %s %s \"%s\""), (LPCTSTR)option, (LPCTSTR)key, (LPCTSTR)mangledValue);
719
		CString out;
720
		if (Run(cmd, &out, nullptr, CP_UTF8))
李智's avatar
李智 committed
721 722 723 724 725
		{
			return -1;
		}
	}
	return 0;
李智's avatar
李智 committed
726
}
李智's avatar
李智 committed
727

728
int CGit::UnsetConfigValue(const CString& key, CONFIG_TYPE type)
729 730 731
{
	if(this->m_IsUseGitDLL)
	{
732 733
		CAutoLocker lock(g_Git.m_critGitDllSec);

734 735 736 737 738 739 740 741 742
		try
		{
			CheckAndInitDll();
		}catch(...)
		{
		}
		CStringA keya;
		keya = CUnicodeUtils::GetMulti(key, CP_UTF8);

743 744
		try
		{
ch3cooli's avatar
ch3cooli committed
745
			return [=]() { return get_set_config(keya, nullptr, type); }();
746 747 748 749 750 751
		}
		catch (const char *msg)
		{
			::MessageBox(NULL, _T("Could not unset config.\nlibgit reports:\n") + CString(msg), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
			return -1;
		}
752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767
	}
	else
	{
		CString cmd;
		CString option;
		switch(type)
		{
		case CONFIG_GLOBAL:
			option = _T("--global");
			break;
		case CONFIG_SYSTEM:
			option = _T("--system");
			break;
		default:
			break;
		}
768
		cmd.Format(_T("git.exe config %s --unset %s"), (LPCTSTR)option, (LPCTSTR)key);
769
		CString out;
770
		if (Run(cmd, &out, nullptr, CP_UTF8))
771 772 773 774 775 776
		{
			return -1;
		}
	}
	return 0;
}
777

778
CString CGit::GetCurrentBranch(bool fallback)
李智's avatar
李智 committed
779
{
780 781 782
	CString output;
	//Run(_T("git.exe branch"),&branch);

783 784
	int result = GetCurrentBranchFromFile(m_CurrentDir, output, fallback);
	if (result != 0 && ((result == 1 && !fallback) || result != 1))
785 786
	{
		return _T("(no branch)");
787 788
	}
	else
789 790
		return output;

李智's avatar
李智 committed
791
}
李智's avatar
李智 committed
792

793 794 795 796 797 798
void CGit::GetRemoteTrackedBranch(const CString& localBranch, CString& pullRemote, CString& pullBranch)
{
	if (localBranch.IsEmpty())
		return;

	CString configName;
799
	configName.Format(L"branch.%s.remote", (LPCTSTR)localBranch);
800 801 802
	pullRemote =  GetConfigValue(configName);

	//Select pull-branch from current branch
803
	configName.Format(L"branch.%s.merge", (LPCTSTR)localBranch);
804 805 806 807
	pullBranch = StripRefName(GetConfigValue(configName));
}

void CGit::GetRemoteTrackedBranchForHEAD(CString& remote, CString& branch)
808 809
{
	CString refName;
810 811 812
	if (GetCurrentBranchFromFile(m_CurrentDir, refName))
		return;
	GetRemoteTrackedBranch(StripRefName(refName), remote, branch);
813 814
}

Sven Strickroth's avatar
Sven Strickroth committed
815
CString CGit::GetFullRefName(const CString& shortRefName)
816 817
{
	CString refName;
818
	CString cmd;
819
	cmd.Format(L"git.exe rev-parse --symbolic-full-name %s", (LPCTSTR)shortRefName);
820
	if (Run(cmd, &refName, NULL, CP_UTF8) != 0)
821 822 823 824 825
		return CString();//Error
	int iStart = 0;
	return refName.Tokenize(L"\n", iStart);
}

826 827 828 829 830 831
CString CGit::StripRefName(CString refName)
{
	if(wcsncmp(refName, L"refs/heads/", 11) == 0)
		refName = refName.Mid(11);
	else if(wcsncmp(refName, L"refs/", 5) == 0)
		refName = refName.Mid(5);
832 833
	int start =0;
	return refName.Tokenize(_T("\n"),start);
834 835
}

836
int CGit::GetCurrentBranchFromFile(const CString &sProjectRoot, CString &sBranchOut, bool fallback)
837 838 839 840 841 842
{
	// read current branch name like git-gui does, by parsing the .git/HEAD file directly

	if ( sProjectRoot.IsEmpty() )
		return -1;

843
	CString sDotGitPath;
844
	if (!GitAdminDir::GetAdminDirPath(sProjectRoot, sDotGitPath))
845 846 847
		return -1;

	CString sHeadFile = sDotGitPath + _T("HEAD");
848 849 850 851 852 853 854 855 856 857

	FILE *pFile;
	_tfopen_s(&pFile, sHeadFile.GetString(), _T("r"));

	if (!pFile)
	{
		return -1;
	}

	char s[256] = {0};
858
	fgets(s, sizeof(s), pFile);
859 860 861 862 863 864 865 866 867