PageRenderTime 44ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Utils/Hooks.cpp

https://gitlab.com/cjeight/tortoisegit
C++ | 458 lines | 370 code | 49 blank | 39 comment | 59 complexity | 7c8abdfe6c46e9bb3d2a0501903edce4 MD5 | raw file
  1. // TortoiseGit - a Windows shell extension for easy version control
  2. // Copyright (C) 2011-2016 - TortoiseGit
  3. // Copyright (C) 2007-2008 - TortoiseSVN
  4. // This program is free software; you can redistribute it and/or
  5. // modify it under the terms of the GNU General Public License
  6. // as published by the Free Software Foundation; either version 2
  7. // of the License, or (at your option) any later version.
  8. // This program is distributed in the hope that it will be useful,
  9. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. // GNU General Public License for more details.
  12. // You should have received a copy of the GNU General Public License
  13. // along with this program; if not, write to the Free Software Foundation,
  14. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  15. //
  16. #include "stdafx.h"
  17. #include "Hooks.h"
  18. #include "registry.h"
  19. #include "StringUtils.h"
  20. #include "TempFile.h"
  21. #include "SmartHandle.h"
  22. #include "Git.h"
  23. CHooks* CHooks::m_pInstance;
  24. CHooks::CHooks()
  25. {
  26. }
  27. CHooks::~CHooks()
  28. {
  29. };
  30. bool CHooks::Create()
  31. {
  32. if (!m_pInstance)
  33. m_pInstance = new CHooks();
  34. CRegString reghooks(_T("Software\\TortoiseGit\\hooks"));
  35. CString strhooks = reghooks;
  36. // now fill the map with all the hooks defined in the string
  37. // the string consists of multiple lines, where one hook script is defined
  38. // as four lines:
  39. // line 1: the hook type
  40. // line 2: path to working copy where to apply the hook script
  41. // line 3: command line to execute
  42. // line 4: 'true' or 'false' for waiting for the script to finish
  43. // line 5: 'show' or 'hide' on how to start the hook script
  44. hookkey key;
  45. int pos = 0;
  46. hookcmd cmd;
  47. while ((pos = strhooks.Find('\n')) >= 0)
  48. {
  49. // line 1
  50. key.htype = GetHookType(strhooks.Mid(0, pos));
  51. if (pos+1 < strhooks.GetLength())
  52. strhooks = strhooks.Mid(pos+1);
  53. else
  54. strhooks.Empty();
  55. bool bComplete = false;
  56. if ((pos = strhooks.Find('\n')) >= 0)
  57. {
  58. // line 2
  59. key.path = CTGitPath(strhooks.Mid(0, pos));
  60. if (pos+1 < strhooks.GetLength())
  61. strhooks = strhooks.Mid(pos+1);
  62. else
  63. strhooks.Empty();
  64. if ((pos = strhooks.Find('\n')) >= 0)
  65. {
  66. // line 3
  67. cmd.commandline = strhooks.Mid(0, pos);
  68. if (pos+1 < strhooks.GetLength())
  69. strhooks = strhooks.Mid(pos+1);
  70. else
  71. strhooks.Empty();
  72. if ((pos = strhooks.Find('\n')) >= 0)
  73. {
  74. // line 4
  75. cmd.bWait = (strhooks.Mid(0, pos).CompareNoCase(_T("true"))==0);
  76. if (pos+1 < strhooks.GetLength())
  77. strhooks = strhooks.Mid(pos+1);
  78. else
  79. strhooks.Empty();
  80. if ((pos = strhooks.Find('\n')) >= 0)
  81. {
  82. // line 5
  83. cmd.bShow = (strhooks.Mid(0, pos).CompareNoCase(_T("show"))==0);
  84. if (pos+1 < strhooks.GetLength())
  85. strhooks = strhooks.Mid(pos+1);
  86. else
  87. strhooks.Empty();
  88. bComplete = true;
  89. }
  90. }
  91. }
  92. }
  93. if (bComplete)
  94. {
  95. m_pInstance->insert(std::pair<hookkey, hookcmd>(key, cmd));
  96. }
  97. }
  98. return true;
  99. }
  100. CHooks& CHooks::Instance()
  101. {
  102. return *m_pInstance;
  103. }
  104. void CHooks::Destroy()
  105. {
  106. delete m_pInstance;
  107. }
  108. bool CHooks::Save()
  109. {
  110. CString strhooks;
  111. for (hookiterator it = begin(); it != end(); ++it)
  112. {
  113. strhooks += GetHookTypeString(it->first.htype);
  114. strhooks += '\n';
  115. strhooks += it->first.path.GetWinPathString();
  116. strhooks += '\n';
  117. strhooks += it->second.commandline;
  118. strhooks += '\n';
  119. strhooks += (it->second.bWait ? _T("true") : _T("false"));
  120. strhooks += '\n';
  121. strhooks += (it->second.bShow ? _T("show") : _T("hide"));
  122. strhooks += '\n';
  123. }
  124. CRegString reghooks(_T("Software\\TortoiseGit\\hooks"));
  125. reghooks = strhooks;
  126. if (reghooks.GetLastError())
  127. return false;
  128. return true;
  129. }
  130. bool CHooks::Remove(const hookkey &key)
  131. {
  132. return (erase(key) > 0);
  133. }
  134. void CHooks::Add(hooktype ht, const CTGitPath& Path, LPCTSTR szCmd, bool bWait, bool bShow)
  135. {
  136. hookkey key;
  137. key.htype = ht;
  138. key.path = Path;
  139. hookiterator it = find(key);
  140. if (it!=end())
  141. erase(it);
  142. hookcmd cmd;
  143. cmd.commandline = szCmd;
  144. cmd.bWait = bWait;
  145. cmd.bShow = bShow;
  146. insert(std::pair<hookkey, hookcmd>(key, cmd));
  147. }
  148. CString CHooks::GetHookTypeString(hooktype t)
  149. {
  150. switch (t)
  151. {
  152. case start_commit_hook:
  153. return _T("start_commit_hook");
  154. case pre_commit_hook:
  155. return _T("pre_commit_hook");
  156. case post_commit_hook:
  157. return _T("post_commit_hook");
  158. case pre_push_hook:
  159. return _T("pre_push_hook");
  160. case post_push_hook:
  161. return _T("post_push_hook");
  162. case pre_rebase_hook:
  163. return L"pre_rebase_hook";
  164. }
  165. return _T("");
  166. }
  167. hooktype CHooks::GetHookType(const CString& s)
  168. {
  169. if (s.Compare(_T("start_commit_hook"))==0)
  170. return start_commit_hook;
  171. if (s.Compare(_T("pre_commit_hook"))==0)
  172. return pre_commit_hook;
  173. if (s.Compare(_T("post_commit_hook"))==0)
  174. return post_commit_hook;
  175. if (s.Compare(_T("pre_push_hook"))==0)
  176. return pre_push_hook;
  177. if (s.Compare(_T("post_push_hook"))==0)
  178. return post_push_hook;
  179. if (s.Compare(L"pre_rebase_hook") == 0)
  180. return pre_rebase_hook;
  181. return unknown_hook;
  182. }
  183. void CHooks::AddParam(CString& sCmd, const CString& param)
  184. {
  185. sCmd += _T(" \"");
  186. sCmd += param;
  187. sCmd += _T("\"");
  188. }
  189. void CHooks::AddPathParam(CString& sCmd, const CTGitPathList& pathList)
  190. {
  191. CTGitPath temppath = CTempFiles::Instance().GetTempFilePath(true);
  192. pathList.WriteToFile(temppath.GetWinPathString(), true);
  193. AddParam(sCmd, temppath.GetWinPathString());
  194. }
  195. void CHooks::AddCWDParam(CString& sCmd, const CString& workingTree)
  196. {
  197. AddParam(sCmd, workingTree);
  198. }
  199. void CHooks::AddErrorParam(CString& sCmd, const CString& error)
  200. {
  201. CTGitPath tempPath;
  202. tempPath = CTempFiles::Instance().GetTempFilePath(true);
  203. CStringUtils::WriteStringToTextFile(tempPath.GetWinPath(), (LPCTSTR)error);
  204. AddParam(sCmd, tempPath.GetWinPathString());
  205. }
  206. CTGitPath CHooks::AddMessageFileParam(CString& sCmd, const CString& message)
  207. {
  208. CTGitPath tempPath;
  209. tempPath = CTempFiles::Instance().GetTempFilePath(true);
  210. CStringUtils::WriteStringToTextFile(tempPath.GetWinPath(), (LPCTSTR)message);
  211. AddParam(sCmd, tempPath.GetWinPathString());
  212. return tempPath;
  213. }
  214. bool CHooks::StartCommit(const CString& workingTree, const CTGitPathList& pathList, CString& message, DWORD& exitcode, CString& error)
  215. {
  216. auto it = FindItem(start_commit_hook, workingTree);
  217. if (it == end())
  218. return false;
  219. CString sCmd = it->second.commandline;
  220. AddPathParam(sCmd, pathList);
  221. CTGitPath temppath = AddMessageFileParam(sCmd, message);
  222. AddCWDParam(sCmd, workingTree);
  223. exitcode = RunScript(sCmd, workingTree, error, it->second.bWait, it->second.bShow);
  224. if (!exitcode && !temppath.IsEmpty())
  225. {
  226. CStringUtils::ReadStringFromTextFile(temppath.GetWinPathString(), message);
  227. }
  228. return true;
  229. }
  230. bool CHooks::PreCommit(const CString& workingTree, const CTGitPathList& pathList, CString& message, DWORD& exitcode, CString& error)
  231. {
  232. auto it = FindItem(pre_commit_hook, workingTree);
  233. if (it == end())
  234. return false;
  235. CString sCmd = it->second.commandline;
  236. AddPathParam(sCmd, pathList);
  237. CTGitPath temppath = AddMessageFileParam(sCmd, message);
  238. AddCWDParam(sCmd, workingTree);
  239. exitcode = RunScript(sCmd, workingTree, error, it->second.bWait, it->second.bShow);
  240. if (!exitcode && !temppath.IsEmpty())
  241. CStringUtils::ReadStringFromTextFile(temppath.GetWinPathString(), message);
  242. return true;
  243. }
  244. bool CHooks::PostCommit(const CString& workingTree, bool amend, DWORD& exitcode, CString& error)
  245. {
  246. auto it = FindItem(post_commit_hook, workingTree);
  247. if (it == end())
  248. return false;
  249. CString sCmd = it->second.commandline;
  250. AddCWDParam(sCmd, workingTree);
  251. if (amend)
  252. AddParam(sCmd, L"true");
  253. else
  254. AddParam(sCmd, L"false");
  255. exitcode = RunScript(sCmd, workingTree, error, it->second.bWait, it->second.bShow);
  256. return true;
  257. }
  258. bool CHooks::PrePush(const CString& workingTree, DWORD& exitcode, CString& error)
  259. {
  260. auto it = FindItem(pre_push_hook, workingTree);
  261. if (it == end())
  262. return false;
  263. CString sCmd = it->second.commandline;
  264. AddErrorParam(sCmd, error);
  265. AddCWDParam(sCmd, workingTree);
  266. exitcode = RunScript(sCmd, workingTree, error, it->second.bWait, it->second.bShow);
  267. return true;
  268. }
  269. bool CHooks::PostPush(const CString& workingTree, DWORD& exitcode, CString& error)
  270. {
  271. auto it = FindItem(post_push_hook, workingTree);
  272. if (it == end())
  273. return false;
  274. CString sCmd = it->second.commandline;
  275. AddErrorParam(sCmd, error);
  276. AddCWDParam(sCmd, workingTree);
  277. exitcode = RunScript(sCmd, workingTree, error, it->second.bWait, it->second.bShow);
  278. return true;
  279. }
  280. bool CHooks::PreRebase(const CString& workingTree, const CString& upstream, const CString& rebasedBranch, DWORD& exitcode, CString& error)
  281. {
  282. auto it = FindItem(pre_rebase_hook, workingTree);
  283. if (it == end())
  284. return false;
  285. CString sCmd = it->second.commandline;
  286. AddParam(sCmd, upstream);
  287. AddParam(sCmd, rebasedBranch);
  288. AddErrorParam(sCmd, error);
  289. AddCWDParam(sCmd, workingTree);
  290. exitcode = RunScript(sCmd, workingTree, error, it->second.bWait, it->second.bShow);
  291. return true;
  292. }
  293. bool CHooks::IsHookPresent(hooktype t, const CString& workingTree) const
  294. {
  295. auto it = FindItem(t, workingTree);
  296. return it != end();
  297. }
  298. const_hookiterator CHooks::FindItem(hooktype t, const CString& workingTree) const
  299. {
  300. hookkey key;
  301. CTGitPath path = workingTree;
  302. do
  303. {
  304. key.htype = t;
  305. key.path = path;
  306. auto it = find(key);
  307. if (it != end())
  308. return it;
  309. path = path.GetContainingDirectory();
  310. } while(!path.IsEmpty());
  311. // look for a script with a path as '*'
  312. key.htype = t;
  313. key.path = CTGitPath(_T("*"));
  314. auto it = find(key);
  315. if (it != end())
  316. {
  317. return it;
  318. }
  319. return end();
  320. }
  321. DWORD CHooks::RunScript(CString cmd, LPCTSTR currentDir, CString& error, bool bWait, bool bShow)
  322. {
  323. DWORD exitcode = 0;
  324. SECURITY_ATTRIBUTES sa = { 0 };
  325. sa.nLength = sizeof(sa);
  326. sa.bInheritHandle = TRUE;
  327. CAutoFile hOut ;
  328. CAutoFile hRedir;
  329. CAutoFile hErr;
  330. // clear the error string
  331. error.Empty();
  332. // Create Temp File for redirection
  333. TCHAR szTempPath[MAX_PATH] = {0};
  334. TCHAR szOutput[MAX_PATH] = {0};
  335. TCHAR szErr[MAX_PATH] = {0};
  336. GetTortoiseGitTempPath(_countof(szTempPath), szTempPath);
  337. GetTempFileName(szTempPath, _T("git"), 0, szErr);
  338. // setup redirection handles
  339. // output handle must be WRITE mode, share READ
  340. // redirect handle must be READ mode, share WRITE
  341. hErr = CreateFile(szErr, GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, nullptr);
  342. if (!hErr)
  343. {
  344. error = CFormatMessageWrapper();
  345. return (DWORD)-1;
  346. }
  347. hRedir = CreateFile(szErr, GENERIC_READ, FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
  348. if (!hRedir)
  349. {
  350. error = CFormatMessageWrapper();
  351. return (DWORD)-1;
  352. }
  353. GetTempFileName(szTempPath, _T("git"), 0, szOutput);
  354. hOut = CreateFile(szOutput, GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, nullptr);
  355. if (!hOut)
  356. {
  357. error = CFormatMessageWrapper();
  358. return (DWORD)-1;
  359. }
  360. // setup startup info, set std out/err handles
  361. // hide window
  362. STARTUPINFO si = { 0 };
  363. si.cb = sizeof(si);
  364. si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
  365. si.hStdOutput = hOut;
  366. si.hStdError = hErr;
  367. si.wShowWindow = bShow ? SW_SHOW : SW_HIDE;
  368. PROCESS_INFORMATION pi = { 0 };
  369. if (!CreateProcess(nullptr, cmd.GetBuffer(), nullptr, nullptr, TRUE, CREATE_UNICODE_ENVIRONMENT, nullptr, currentDir, &si, &pi))
  370. {
  371. const DWORD err = GetLastError(); // preserve the CreateProcess error
  372. error = CFormatMessageWrapper(err);
  373. SetLastError(err);
  374. cmd.ReleaseBuffer();
  375. return (DWORD)-1;
  376. }
  377. cmd.ReleaseBuffer();
  378. CloseHandle(pi.hThread);
  379. // wait for process to finish, capture redirection and
  380. // send it to the parent window/console
  381. if (bWait)
  382. {
  383. DWORD dw;
  384. char buf[256] = { 0 };
  385. do
  386. {
  387. while (ReadFile(hRedir, &buf, sizeof(buf) - 1, &dw, nullptr))
  388. {
  389. if (dw == 0)
  390. break;
  391. error += CString(CStringA(buf,dw));
  392. }
  393. Sleep(150);
  394. } while (WaitForSingleObject(pi.hProcess, 0) != WAIT_OBJECT_0);
  395. // perform any final flushing
  396. while (ReadFile(hRedir, &buf, sizeof(buf) - 1, &dw, nullptr))
  397. {
  398. if (dw == 0)
  399. break;
  400. error += CString(CStringA(buf, dw));
  401. }
  402. WaitForSingleObject(pi.hProcess, INFINITE);
  403. GetExitCodeProcess(pi.hProcess, &exitcode);
  404. }
  405. CloseHandle(pi.hProcess);
  406. DeleteFile(szOutput);
  407. DeleteFile(szErr);
  408. return exitcode;
  409. }