Includes cleanup
[TortoiseGit.git] / src / Utils / Hooks.cpp
blob09affaa8705cb16f8024f3927923d05159aaedd0
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2011-2015 - TortoiseGit
4 // Copyright (C) 2007-2008 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "Hooks.h"
22 #include "registry.h"
23 #include "StringUtils.h"
24 #include "TempFile.h"
25 #include "SmartHandle.h"
26 #include "Git.h"
28 CHooks* CHooks::m_pInstance;
30 CHooks::CHooks()
34 CHooks::~CHooks()
38 bool CHooks::Create()
40 if (m_pInstance == NULL)
41 m_pInstance = new CHooks();
42 CRegString reghooks(_T("Software\\TortoiseGit\\hooks"));
43 CString strhooks = reghooks;
44 // now fill the map with all the hooks defined in the string
45 // the string consists of multiple lines, where one hook script is defined
46 // as four lines:
47 // line 1: the hook type
48 // line 2: path to working copy where to apply the hook script
49 // line 3: command line to execute
50 // line 4: 'true' or 'false' for waiting for the script to finish
51 // line 5: 'show' or 'hide' on how to start the hook script
52 hookkey key;
53 int pos = 0;
54 hookcmd cmd;
55 while ((pos = strhooks.Find('\n')) >= 0)
57 // line 1
58 key.htype = GetHookType(strhooks.Mid(0, pos));
59 if (pos+1 < strhooks.GetLength())
60 strhooks = strhooks.Mid(pos+1);
61 else
62 strhooks.Empty();
63 bool bComplete = false;
64 if ((pos = strhooks.Find('\n')) >= 0)
66 // line 2
67 key.path = CTGitPath(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)
74 // line 3
75 cmd.commandline = strhooks.Mid(0, pos);
76 if (pos+1 < strhooks.GetLength())
77 strhooks = strhooks.Mid(pos+1);
78 else
79 strhooks.Empty();
80 if ((pos = strhooks.Find('\n')) >= 0)
82 // line 4
83 cmd.bWait = (strhooks.Mid(0, pos).CompareNoCase(_T("true"))==0);
84 if (pos+1 < strhooks.GetLength())
85 strhooks = strhooks.Mid(pos+1);
86 else
87 strhooks.Empty();
88 if ((pos = strhooks.Find('\n')) >= 0)
90 // line 5
91 cmd.bShow = (strhooks.Mid(0, pos).CompareNoCase(_T("show"))==0);
92 if (pos+1 < strhooks.GetLength())
93 strhooks = strhooks.Mid(pos+1);
94 else
95 strhooks.Empty();
96 bComplete = true;
101 if (bComplete)
103 m_pInstance->insert(std::pair<hookkey, hookcmd>(key, cmd));
106 return true;
109 CHooks& CHooks::Instance()
111 return *m_pInstance;
114 void CHooks::Destroy()
116 delete m_pInstance;
119 bool CHooks::Save()
121 CString strhooks;
122 for (hookiterator it = begin(); it != end(); ++it)
124 strhooks += GetHookTypeString(it->first.htype);
125 strhooks += '\n';
126 strhooks += it->first.path.GetWinPathString();
127 strhooks += '\n';
128 strhooks += it->second.commandline;
129 strhooks += '\n';
130 strhooks += (it->second.bWait ? _T("true") : _T("false"));
131 strhooks += '\n';
132 strhooks += (it->second.bShow ? _T("show") : _T("hide"));
133 strhooks += '\n';
135 CRegString reghooks(_T("Software\\TortoiseGit\\hooks"));
136 reghooks = strhooks;
137 if (reghooks.GetLastError())
138 return false;
139 return true;
142 bool CHooks::Remove(const hookkey &key)
144 return (erase(key) > 0);
147 void CHooks::Add(hooktype ht, const CTGitPath& Path, LPCTSTR szCmd, bool bWait, bool bShow)
149 hookkey key;
150 key.htype = ht;
151 key.path = Path;
152 hookiterator it = find(key);
153 if (it!=end())
154 erase(it);
156 hookcmd cmd;
157 cmd.commandline = szCmd;
158 cmd.bWait = bWait;
159 cmd.bShow = bShow;
160 insert(std::pair<hookkey, hookcmd>(key, cmd));
163 CString CHooks::GetHookTypeString(hooktype t)
165 switch (t)
167 case start_commit_hook:
168 return _T("start_commit_hook");
169 case pre_commit_hook:
170 return _T("pre_commit_hook");
171 case post_commit_hook:
172 return _T("post_commit_hook");
173 case pre_push_hook:
174 return _T("pre_push_hook");
175 case post_push_hook:
176 return _T("post_push_hook");
178 return _T("");
181 hooktype CHooks::GetHookType(const CString& s)
183 if (s.Compare(_T("start_commit_hook"))==0)
184 return start_commit_hook;
185 if (s.Compare(_T("pre_commit_hook"))==0)
186 return pre_commit_hook;
187 if (s.Compare(_T("post_commit_hook"))==0)
188 return post_commit_hook;
189 if (s.Compare(_T("pre_push_hook"))==0)
190 return pre_push_hook;
191 if (s.Compare(_T("post_push_hook"))==0)
192 return post_push_hook;
194 return unknown_hook;
197 void CHooks::AddParam(CString& sCmd, const CString& param)
199 sCmd += _T(" \"");
200 sCmd += param;
201 sCmd += _T("\"");
204 void CHooks::AddPathParam(CString& sCmd, const CTGitPathList& pathList)
206 CTGitPath temppath = CTempFiles::Instance().GetTempFilePath(true);
207 pathList.WriteToFile(temppath.GetWinPathString(), true);
208 AddParam(sCmd, temppath.GetWinPathString());
211 void CHooks::AddCWDParam(CString& sCmd, const CString& workingTree)
213 AddParam(sCmd, workingTree);
216 void CHooks::AddErrorParam(CString& sCmd, const CString& error)
218 CTGitPath tempPath;
219 tempPath = CTempFiles::Instance().GetTempFilePath(true);
220 CStringUtils::WriteStringToTextFile(tempPath.GetWinPath(), (LPCTSTR)error);
221 AddParam(sCmd, tempPath.GetWinPathString());
224 CTGitPath CHooks::AddMessageFileParam(CString& sCmd, const CString& message)
226 CTGitPath tempPath;
227 tempPath = CTempFiles::Instance().GetTempFilePath(true);
228 CStringUtils::WriteStringToTextFile(tempPath.GetWinPath(), (LPCTSTR)message);
229 AddParam(sCmd, tempPath.GetWinPathString());
230 return tempPath;
233 bool CHooks::StartCommit(const CString& workingTree, const CTGitPathList& pathList, CString& message, DWORD& exitcode, CString& error)
235 auto it = FindItem(start_commit_hook, workingTree);
236 if (it == end())
237 return false;
238 CString sCmd = it->second.commandline;
239 AddPathParam(sCmd, pathList);
240 CTGitPath temppath = AddMessageFileParam(sCmd, message);
241 AddCWDParam(sCmd, workingTree);
242 exitcode = RunScript(sCmd, workingTree, error, it->second.bWait, it->second.bShow);
243 if (!exitcode && !temppath.IsEmpty())
245 CStringUtils::ReadStringFromTextFile(temppath.GetWinPathString(), message);
247 return true;
250 bool CHooks::PreCommit(const CString& workingTree, const CTGitPathList& pathList, CString& message, DWORD& exitcode, CString& error)
252 auto it = FindItem(pre_commit_hook, workingTree);
253 if (it == end())
254 return false;
255 CString sCmd = it->second.commandline;
256 AddPathParam(sCmd, pathList);
257 CTGitPath temppath = AddMessageFileParam(sCmd, message);
258 AddCWDParam(sCmd, workingTree);
259 exitcode = RunScript(sCmd, workingTree, error, it->second.bWait, it->second.bShow);
260 if (!exitcode && !temppath.IsEmpty())
261 CStringUtils::ReadStringFromTextFile(temppath.GetWinPathString(), message);
262 return true;
265 bool CHooks::PostCommit(const CString& workingTree, const CTGitPathList& pathList, const GitRev& rev, const CString& message, DWORD& exitcode, CString& error)
267 auto it = FindItem(post_commit_hook, workingTree);
268 if (it == end())
269 return false;
270 CString sCmd = it->second.commandline;
271 AddPathParam(sCmd, pathList);
272 AddMessageFileParam(sCmd, message);
273 AddParam(sCmd, rev.m_CommitHash.ToString());
274 AddErrorParam(sCmd, error);
275 AddCWDParam(sCmd, workingTree);
276 exitcode = RunScript(sCmd, workingTree, error, it->second.bWait, it->second.bShow);
277 return true;
280 bool CHooks::PrePush(const CString& workingTree, DWORD& exitcode, CString& error)
282 auto it = FindItem(pre_push_hook, workingTree);
283 if (it == end())
284 return false;
285 CString sCmd = it->second.commandline;
286 AddPathParam(sCmd, CTGitPathList(workingTree));
287 AddErrorParam(sCmd, error);
288 AddCWDParam(sCmd, workingTree);
289 exitcode = RunScript(sCmd, workingTree, error, it->second.bWait, it->second.bShow);
290 return true;
293 bool CHooks::PostPush(const CString& workingTree, DWORD& exitcode, CString& error)
295 auto it = FindItem(post_push_hook, workingTree);
296 if (it == end())
297 return false;
298 CString sCmd = it->second.commandline;
299 AddPathParam(sCmd, CTGitPathList(workingTree));
300 AddErrorParam(sCmd, error);
301 AddCWDParam(sCmd, workingTree);
302 exitcode = RunScript(sCmd, workingTree, error, it->second.bWait, it->second.bShow);
303 return true;
306 bool CHooks::IsHookPresent(hooktype t, const CString& workingTree) const
308 auto it = FindItem(t, workingTree);
309 return it != end();
312 const_hookiterator CHooks::FindItem(hooktype t, const CString& workingTree) const
314 hookkey key;
315 CTGitPath path = workingTree;
318 key.htype = t;
319 key.path = path;
320 auto it = find(key);
321 if (it != end())
323 return it;
325 path = path.GetContainingDirectory();
326 } while(!path.IsEmpty());
327 // look for a script with a path as '*'
328 key.htype = t;
329 key.path = CTGitPath(_T("*"));
330 auto it = find(key);
331 if (it != end())
333 return it;
336 return end();
339 DWORD CHooks::RunScript(CString cmd, LPCTSTR currentDir, CString& error, bool bWait, bool bShow)
341 DWORD exitcode = 0;
342 SECURITY_ATTRIBUTES sa;
343 SecureZeroMemory(&sa, sizeof(sa));
344 sa.nLength = sizeof(sa);
345 sa.bInheritHandle = TRUE;
347 CAutoFile hOut ;
348 CAutoFile hRedir;
349 CAutoFile hErr;
351 // clear the error string
352 error.Empty();
354 // Create Temp File for redirection
355 TCHAR szTempPath[MAX_PATH] = {0};
356 TCHAR szOutput[MAX_PATH] = {0};
357 TCHAR szErr[MAX_PATH] = {0};
358 GetTortoiseGitTempPath(_countof(szTempPath), szTempPath);
359 GetTempFileName(szTempPath, _T("git"), 0, szErr);
361 // setup redirection handles
362 // output handle must be WRITE mode, share READ
363 // redirect handle must be READ mode, share WRITE
364 hErr = CreateFile(szErr, GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, 0);
366 if (!hErr)
368 error = CFormatMessageWrapper();
369 return (DWORD)-1;
372 hRedir = CreateFile(szErr, GENERIC_READ, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
374 if (!hRedir)
376 error = CFormatMessageWrapper();
377 return (DWORD)-1;
380 GetTempFileName(szTempPath, _T("git"), 0, szOutput);
381 hOut = CreateFile(szOutput, GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, 0);
383 if (!hOut)
385 error = CFormatMessageWrapper();
386 return (DWORD)-1;
389 // setup startup info, set std out/err handles
390 // hide window
391 STARTUPINFO si;
392 SecureZeroMemory(&si, sizeof(si));
393 si.cb = sizeof(si);
394 si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
395 si.hStdOutput = hOut;
396 si.hStdError = hErr;
397 si.wShowWindow = bShow ? SW_SHOW : SW_HIDE;
399 PROCESS_INFORMATION pi;
400 SecureZeroMemory(&pi, sizeof(pi));
402 if (!CreateProcess(nullptr, cmd.GetBuffer(), nullptr, nullptr, TRUE, 0, nullptr, currentDir, &si, &pi))
404 const DWORD err = GetLastError(); // preserve the CreateProcess error
405 error = CFormatMessageWrapper(err);
406 SetLastError(err);
407 cmd.ReleaseBuffer();
408 return (DWORD)-1;
410 cmd.ReleaseBuffer();
412 CloseHandle(pi.hThread);
414 // wait for process to finish, capture redirection and
415 // send it to the parent window/console
416 if (bWait)
418 DWORD dw;
419 char buf[256] = { 0 };
422 while (ReadFile(hRedir, &buf, sizeof(buf)-1, &dw, NULL))
424 if (dw == 0)
425 break;
426 error += CString(CStringA(buf,dw));
428 Sleep(150);
429 } while (WaitForSingleObject(pi.hProcess, 0) != WAIT_OBJECT_0);
431 // perform any final flushing
432 while (ReadFile(hRedir, &buf, sizeof(buf)-1, &dw, NULL))
434 if (dw == 0)
435 break;
437 error += CString(CStringA(buf, dw));
439 WaitForSingleObject(pi.hProcess, INFINITE);
440 GetExitCodeProcess(pi.hProcess, &exitcode);
442 CloseHandle(pi.hProcess);
443 DeleteFile(szOutput);
444 DeleteFile(szErr);
446 return exitcode;