Dropped code for SVN specific hooks
[TortoiseGit.git] / src / Utils / Hooks.cpp
blob8a6755d0fe2befe96b58bac0b08cb486b2aa2335
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2011-2013 - 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"
27 CHooks* CHooks::m_pInstance;
29 CHooks::CHooks()
33 CHooks::~CHooks()
37 bool CHooks::Create()
39 if (m_pInstance == NULL)
40 m_pInstance = new CHooks();
41 CRegString reghooks = CRegString(_T("Software\\TortoiseGit\\hooks"));
42 CString strhooks = reghooks;
43 // now fill the map with all the hooks defined in the string
44 // the string consists of multiple lines, where one hook script is defined
45 // as four lines:
46 // line 1: the hook type
47 // line 2: path to working copy where to apply the hook script
48 // line 3: command line to execute
49 // line 4: 'true' or 'false' for waiting for the script to finish
50 // line 5: 'show' or 'hide' on how to start the hook script
51 hookkey key;
52 int pos = 0;
53 hookcmd cmd;
54 while ((pos = strhooks.Find('\n')) >= 0)
56 // line 1
57 key.htype = GetHookType(strhooks.Mid(0, pos));
58 if (pos+1 < strhooks.GetLength())
59 strhooks = strhooks.Mid(pos+1);
60 else
61 strhooks.Empty();
62 bool bComplete = false;
63 if ((pos = strhooks.Find('\n')) >= 0)
65 // line 2
66 key.path = CTGitPath(strhooks.Mid(0, pos));
67 if (pos+1 < strhooks.GetLength())
68 strhooks = strhooks.Mid(pos+1);
69 else
70 strhooks.Empty();
71 if ((pos = strhooks.Find('\n')) >= 0)
73 // line 3
74 cmd.commandline = strhooks.Mid(0, pos);
75 if (pos+1 < strhooks.GetLength())
76 strhooks = strhooks.Mid(pos+1);
77 else
78 strhooks.Empty();
79 if ((pos = strhooks.Find('\n')) >= 0)
81 // line 4
82 cmd.bWait = (strhooks.Mid(0, pos).CompareNoCase(_T("true"))==0);
83 if (pos+1 < strhooks.GetLength())
84 strhooks = strhooks.Mid(pos+1);
85 else
86 strhooks.Empty();
87 if ((pos = strhooks.Find('\n')) >= 0)
89 // line 5
90 cmd.bShow = (strhooks.Mid(0, pos).CompareNoCase(_T("show"))==0);
91 if (pos+1 < strhooks.GetLength())
92 strhooks = strhooks.Mid(pos+1);
93 else
94 strhooks.Empty();
95 bComplete = true;
100 if (bComplete)
102 m_pInstance->insert(std::pair<hookkey, hookcmd>(key, cmd));
105 return true;
108 CHooks& CHooks::Instance()
110 return *m_pInstance;
113 void CHooks::Destroy()
115 delete m_pInstance;
118 bool CHooks::Save()
120 CString strhooks;
121 for (hookiterator it = begin(); it != end(); ++it)
123 strhooks += GetHookTypeString(it->first.htype);
124 strhooks += '\n';
125 strhooks += it->first.path.GetWinPathString();
126 strhooks += '\n';
127 strhooks += it->second.commandline;
128 strhooks += '\n';
129 strhooks += (it->second.bWait ? _T("true") : _T("false"));
130 strhooks += '\n';
131 strhooks += (it->second.bShow ? _T("show") : _T("hide"));
132 strhooks += '\n';
134 CRegString reghooks = CRegString(_T("Software\\TortoiseGit\\hooks"));
135 reghooks = strhooks;
136 if (reghooks.GetLastError())
137 return false;
138 return true;
141 bool CHooks::Remove(hookkey key)
143 return (erase(key) > 0);
146 void CHooks::Add(hooktype ht, const CTGitPath& Path, LPCTSTR szCmd, bool bWait, bool bShow)
148 hookkey key;
149 key.htype = ht;
150 key.path = Path;
151 hookiterator it = find(key);
152 if (it!=end())
153 erase(it);
155 hookcmd cmd;
156 cmd.commandline = szCmd;
157 cmd.bWait = bWait;
158 cmd.bShow = bShow;
159 insert(std::pair<hookkey, hookcmd>(key, cmd));
162 CString CHooks::GetHookTypeString(hooktype t)
164 switch (t)
166 case start_commit_hook:
167 return _T("start_commit_hook");
168 case pre_commit_hook:
169 return _T("pre_commit_hook");
170 case post_commit_hook:
171 return _T("post_commit_hook");
172 case pre_push_hook:
173 return _T("pre_push_hook");
174 case post_push_hook:
175 return _T("post_push_hook");
177 return _T("");
180 hooktype CHooks::GetHookType(const CString& s)
182 if (s.Compare(_T("start_commit_hook"))==0)
183 return start_commit_hook;
184 if (s.Compare(_T("pre_commit_hook"))==0)
185 return pre_commit_hook;
186 if (s.Compare(_T("post_commit_hook"))==0)
187 return post_commit_hook;
188 if (s.Compare(_T("pre_push_hook"))==0)
189 return pre_push_hook;
190 if (s.Compare(_T("post_push_hook"))==0)
191 return post_push_hook;
193 return unknown_hook;
196 void CHooks::AddParam(CString& sCmd, const CString& param)
198 sCmd += _T(" \"");
199 sCmd += param;
200 sCmd += _T("\"");
203 void CHooks::AddPathParam(CString& sCmd, const CTGitPathList& pathList)
205 CTGitPath temppath = CTempFiles::Instance().GetTempFilePath(true);
206 pathList.WriteToFile(temppath.GetWinPathString(), true);
207 AddParam(sCmd, temppath.GetWinPathString());
210 void CHooks::AddCWDParam(CString& sCmd, const CTGitPathList& pathList)
212 AddParam(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPathString());
215 void CHooks::AddDepthParam(CString& sCmd, git_depth_t depth)
217 CString sTemp;
218 sTemp.Format(_T("%d"), depth);
219 AddParam(sCmd, sTemp);
222 void CHooks::AddErrorParam(CString& sCmd, const CString& error)
224 CTGitPath tempPath;
225 tempPath = CTempFiles::Instance().GetTempFilePath(true);
226 CStringUtils::WriteStringToTextFile(tempPath.GetWinPath(), (LPCTSTR)error);
227 AddParam(sCmd, tempPath.GetWinPathString());
230 CTGitPath CHooks::AddMessageFileParam(CString& sCmd, const CString& message)
232 CTGitPath tempPath;
233 tempPath = CTempFiles::Instance().GetTempFilePath(true);
234 CStringUtils::WriteStringToTextFile(tempPath.GetWinPath(), (LPCTSTR)message);
235 AddParam(sCmd, tempPath.GetWinPathString());
236 return tempPath;
239 bool CHooks::StartCommit(const CTGitPathList& pathList, CString& message, DWORD& exitcode, CString& error)
241 hookiterator it = FindItem(start_commit_hook, pathList);
242 if (it == end())
243 return false;
244 CString sCmd = it->second.commandline;
245 AddPathParam(sCmd, pathList);
246 CTGitPath temppath = AddMessageFileParam(sCmd, message);
247 AddCWDParam(sCmd, pathList);
248 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
249 if (!exitcode && !temppath.IsEmpty())
251 CStringUtils::ReadStringFromTextFile(temppath.GetWinPathString(), message);
253 return true;
256 bool CHooks::PreCommit(const CTGitPathList& pathList, git_depth_t depth, const CString& message, DWORD& exitcode, CString& error)
258 hookiterator it = FindItem(pre_commit_hook, pathList);
259 if (it == end())
260 return false;
261 CString sCmd = it->second.commandline;
262 AddPathParam(sCmd, pathList);
263 AddDepthParam(sCmd, depth);
264 AddMessageFileParam(sCmd, message);
265 AddCWDParam(sCmd, pathList);
266 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
267 return true;
270 bool CHooks::PostCommit(const CTGitPathList& pathList, git_depth_t depth, GitRev rev, const CString& message, DWORD& exitcode, CString& error)
272 hookiterator it = FindItem(post_commit_hook, pathList);
273 if (it == end())
274 return false;
275 CString sCmd = it->second.commandline;
276 AddPathParam(sCmd, pathList);
277 AddDepthParam(sCmd, depth);
278 AddMessageFileParam(sCmd, message);
279 AddParam(sCmd, rev.m_CommitHash.ToString());
280 AddErrorParam(sCmd, error);
281 AddCWDParam(sCmd, pathList);
282 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
283 return true;
286 bool CHooks::PrePush(const CTGitPathList& pathList,DWORD& exitcode, CString& error)
288 hookiterator it = FindItem(pre_push_hook, pathList);
289 if (it == end())
290 return false;
291 CString sCmd = it->second.commandline;
292 AddPathParam(sCmd, pathList);
293 AddErrorParam(sCmd, error);
294 AddCWDParam(sCmd, pathList);
295 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
296 return true;
299 bool CHooks::PostPush(const CTGitPathList& pathList,DWORD& exitcode, CString& error)
301 hookiterator it = FindItem(post_push_hook, pathList);
302 if (it == end())
303 return false;
304 CString sCmd = it->second.commandline;
305 AddPathParam(sCmd, pathList);
306 AddErrorParam(sCmd, error);
307 AddCWDParam(sCmd, pathList);
308 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
309 return true;
312 hookiterator CHooks::FindItem(hooktype t, const CTGitPathList& pathList)
314 hookkey key;
315 for (int i=0; i<pathList.GetCount(); ++i)
317 CTGitPath path = pathList[i];
320 key.htype = t;
321 key.path = path;
322 hookiterator it = find(key);
323 if (it != end())
325 return it;
327 path = path.GetContainingDirectory();
328 } while(!path.IsEmpty());
330 // look for a script with a path as '*'
331 key.htype = t;
332 key.path = CTGitPath(_T("*"));
333 hookiterator it = find(key);
334 if (it != end())
336 return it;
339 return end();
342 DWORD CHooks::RunScript(CString cmd, LPCTSTR currentDir, CString& error, bool bWait, bool bShow)
344 DWORD exitcode = 0;
345 SECURITY_ATTRIBUTES sa;
346 SecureZeroMemory(&sa, sizeof(sa));
347 sa.nLength = sizeof(sa);
348 sa.bInheritHandle = TRUE;
350 CAutoFile hOut ;
351 CAutoFile hRedir;
352 CAutoFile hErr;
354 // clear the error string
355 error.Empty();
357 // Create Temp File for redirection
358 TCHAR szTempPath[MAX_PATH];
359 TCHAR szOutput[MAX_PATH];
360 TCHAR szErr[MAX_PATH];
361 GetTortoiseGitTempPath(_countof(szTempPath), szTempPath);
362 GetTempFileName(szTempPath, _T("git"), 0, szErr);
364 // setup redirection handles
365 // output handle must be WRITE mode, share READ
366 // redirect handle must be READ mode, share WRITE
367 hErr = CreateFile(szErr, GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, 0);
369 if (!hErr)
370 return (DWORD)-1;
372 hRedir = CreateFile(szErr, GENERIC_READ, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
374 if (!hRedir)
375 return (DWORD)-1;
377 GetTempFileName(szTempPath, _T("git"), 0, szOutput);
378 hOut = CreateFile(szOutput, GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, 0);
380 if (!hOut)
381 return (DWORD)-1;
383 // setup startup info, set std out/err handles
384 // hide window
385 STARTUPINFO si;
386 SecureZeroMemory(&si, sizeof(si));
387 si.cb = sizeof(si);
388 if (hOut != INVALID_HANDLE_VALUE)
390 si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
391 si.hStdOutput = hOut;
392 si.hStdError = hErr;
393 si.wShowWindow = bShow ? SW_SHOW : SW_HIDE;
396 PROCESS_INFORMATION pi;
397 SecureZeroMemory(&pi, sizeof(pi));
399 DWORD dwFlags = 0;
401 if (!CreateProcess(NULL, cmd.GetBuffer(), NULL, NULL, TRUE, dwFlags, NULL, currentDir, &si, &pi))
403 int err = GetLastError(); // preserve the CreateProcess error
404 error = CFormatMessageWrapper(err);
405 SetLastError(err);
406 cmd.ReleaseBuffer();
407 return (DWORD)-1;
409 cmd.ReleaseBuffer();
411 CloseHandle(pi.hThread);
413 // wait for process to finish, capture redirection and
414 // send it to the parent window/console
415 if (bWait)
417 DWORD dw;
418 char buf[256];
421 SecureZeroMemory(&buf,sizeof(buf));
422 while (ReadFile(hRedir, &buf, sizeof(buf)-1, &dw, NULL))
424 if (dw == 0)
425 break;
426 error += CString(CStringA(buf,dw));
427 SecureZeroMemory(&buf,sizeof(buf));
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));
438 SecureZeroMemory(&buf,sizeof(buf));
440 WaitForSingleObject(pi.hProcess, INFINITE);
441 GetExitCodeProcess(pi.hProcess, &exitcode);
443 CloseHandle(pi.hProcess);
444 DeleteFile(szOutput);
445 DeleteFile(szErr);
447 return exitcode;