Updated Tortoise.pot
[TortoiseGit.git] / src / Utils / Hooks.cpp
blob8fbb321ef430556330f3c320f3e9968e867db1fb
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2007-2008 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "stdafx.h"
20 #include "Hooks.h"
21 #include "registry.h"
22 #include "StringUtils.h"
23 #include "TempFile.h"
24 #include "SmartHandle.h"
26 CHooks* CHooks::m_pInstance;
28 CHooks::CHooks()
32 CHooks::~CHooks()
36 bool CHooks::Create()
38 if (m_pInstance == NULL)
39 m_pInstance = new CHooks();
40 CRegString reghooks = CRegString(_T("Software\\TortoiseGit\\hooks"));
41 CString strhooks = reghooks;
42 // now fill the map with all the hooks defined in the string
43 // the string consists of multiple lines, where one hook script is defined
44 // as four lines:
45 // line 1: the hook type
46 // line 2: path to working copy where to apply the hook script
47 // line 3: command line to execute
48 // line 4: 'true' or 'false' for waiting for the script to finish
49 // line 5: 'show' or 'hide' on how to start the hook script
50 hookkey key;
51 int pos = 0;
52 hookcmd cmd;
53 while ((pos = strhooks.Find('\n')) >= 0)
55 // line 1
56 key.htype = GetHookType(strhooks.Mid(0, pos));
57 if (pos+1 < strhooks.GetLength())
58 strhooks = strhooks.Mid(pos+1);
59 else
60 strhooks.Empty();
61 bool bComplete = false;
62 if ((pos = strhooks.Find('\n')) >= 0)
64 // line 2
65 key.path = CTGitPath(strhooks.Mid(0, pos));
66 if (pos+1 < strhooks.GetLength())
67 strhooks = strhooks.Mid(pos+1);
68 else
69 strhooks.Empty();
70 if ((pos = strhooks.Find('\n')) >= 0)
72 // line 3
73 cmd.commandline = strhooks.Mid(0, pos);
74 if (pos+1 < strhooks.GetLength())
75 strhooks = strhooks.Mid(pos+1);
76 else
77 strhooks.Empty();
78 if ((pos = strhooks.Find('\n')) >= 0)
80 // line 4
81 cmd.bWait = (strhooks.Mid(0, pos).CompareNoCase(_T("true"))==0);
82 if (pos+1 < strhooks.GetLength())
83 strhooks = strhooks.Mid(pos+1);
84 else
85 strhooks.Empty();
86 if ((pos = strhooks.Find('\n')) >= 0)
88 // line 5
89 cmd.bShow = (strhooks.Mid(0, pos).CompareNoCase(_T("show"))==0);
90 if (pos+1 < strhooks.GetLength())
91 strhooks = strhooks.Mid(pos+1);
92 else
93 strhooks.Empty();
94 bComplete = true;
99 if (bComplete)
101 m_pInstance->insert(std::pair<hookkey, hookcmd>(key, cmd));
104 return true;
107 CHooks& CHooks::Instance()
109 return *m_pInstance;
112 void CHooks::Destroy()
114 delete m_pInstance;
117 bool CHooks::Save()
119 CString strhooks;
120 for (hookiterator it = begin(); it != end(); ++it)
122 strhooks += GetHookTypeString(it->first.htype);
123 strhooks += '\n';
124 strhooks += it->first.path.GetWinPathString();
125 strhooks += '\n';
126 strhooks += it->second.commandline;
127 strhooks += '\n';
128 strhooks += (it->second.bWait ? _T("true") : _T("false"));
129 strhooks += '\n';
130 strhooks += (it->second.bShow ? _T("show") : _T("hide"));
131 strhooks += '\n';
133 CRegString reghooks = CRegString(_T("Software\\TortoiseGit\\hooks"));
134 reghooks = strhooks;
135 if (reghooks.GetLastError())
136 return false;
137 return true;
140 bool CHooks::Remove(hookkey key)
142 return (erase(key) > 0);
145 void CHooks::Add(hooktype ht, const CTGitPath& Path, LPCTSTR szCmd, bool bWait, bool bShow)
147 hookkey key;
148 key.htype = ht;
149 key.path = Path;
150 hookiterator it = find(key);
151 if (it!=end())
152 erase(it);
154 hookcmd cmd;
155 cmd.commandline = szCmd;
156 cmd.bWait = bWait;
157 cmd.bShow = bShow;
158 insert(std::pair<hookkey, hookcmd>(key, cmd));
161 CString CHooks::GetHookTypeString(hooktype t)
163 switch (t)
165 case start_commit_hook:
166 return _T("start_commit_hook");
167 case pre_commit_hook:
168 return _T("pre_commit_hook");
169 case post_commit_hook:
170 return _T("post_commit_hook");
171 case start_update_hook:
172 return _T("start_update_hook");
173 case pre_update_hook:
174 return _T("pre_update_hook");
175 case post_update_hook:
176 return _T("post_update_hook");
177 case pre_push_hook:
178 return _T("pre_push_hook");
179 case post_push_hook:
180 return _T("post_push_hook");
182 return _T("");
185 hooktype CHooks::GetHookType(const CString& s)
187 if (s.Compare(_T("start_commit_hook"))==0)
188 return start_commit_hook;
189 if (s.Compare(_T("pre_commit_hook"))==0)
190 return pre_commit_hook;
191 if (s.Compare(_T("post_commit_hook"))==0)
192 return post_commit_hook;
193 if (s.Compare(_T("start_update_hook"))==0)
194 return start_update_hook;
195 if (s.Compare(_T("pre_update_hook"))==0)
196 return pre_update_hook;
197 if (s.Compare(_T("post_update_hook"))==0)
198 return post_update_hook;
199 if (s.Compare(_T("pre_push_hook"))==0)
200 return pre_push_hook;
201 if (s.Compare(_T("post_push_hook"))==0)
202 return post_push_hook;
204 return unknown_hook;
207 void CHooks::AddParam(CString& sCmd, const CString& param)
209 sCmd += _T(" \"");
210 sCmd += param;
211 sCmd += _T("\"");
214 void CHooks::AddPathParam(CString& sCmd, const CTGitPathList& pathList)
216 CTGitPath temppath = CTempFiles::Instance().GetTempFilePath(true);
217 pathList.WriteToFile(temppath.GetWinPathString(), true);
218 AddParam(sCmd, temppath.GetWinPathString());
221 void CHooks::AddCWDParam(CString& sCmd, const CTGitPathList& pathList)
223 AddParam(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPathString());
226 void CHooks::AddDepthParam(CString& sCmd, git_depth_t depth)
228 CString sTemp;
229 sTemp.Format(_T("%d"), depth);
230 AddParam(sCmd, sTemp);
233 void CHooks::AddErrorParam(CString& sCmd, const CString& error)
235 CTGitPath tempPath;
236 tempPath = CTempFiles::Instance().GetTempFilePath(true);
237 CStringUtils::WriteStringToTextFile(tempPath.GetWinPath(), (LPCTSTR)error);
238 AddParam(sCmd, tempPath.GetWinPathString());
241 CTGitPath CHooks::AddMessageFileParam(CString& sCmd, const CString& message)
243 CTGitPath tempPath;
244 tempPath = CTempFiles::Instance().GetTempFilePath(true);
245 CStringUtils::WriteStringToTextFile(tempPath.GetWinPath(), (LPCTSTR)message);
246 AddParam(sCmd, tempPath.GetWinPathString());
247 return tempPath;
250 bool CHooks::StartCommit(const CTGitPathList& pathList, CString& message, DWORD& exitcode, CString& error)
252 hookiterator it = FindItem(start_commit_hook, pathList);
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, pathList);
259 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
260 if (!exitcode && !temppath.IsEmpty())
262 CStringUtils::ReadStringFromTextFile(temppath.GetWinPathString(), message);
264 return true;
267 bool CHooks::PreCommit(const CTGitPathList& pathList, git_depth_t depth, const CString& message, DWORD& exitcode, CString& error)
269 hookiterator it = FindItem(pre_commit_hook, pathList);
270 if (it == end())
271 return false;
272 CString sCmd = it->second.commandline;
273 AddPathParam(sCmd, pathList);
274 AddDepthParam(sCmd, depth);
275 AddMessageFileParam(sCmd, message);
276 AddCWDParam(sCmd, pathList);
277 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
278 return true;
281 bool CHooks::PostCommit(const CTGitPathList& pathList, git_depth_t depth, GitRev rev, const CString& message, DWORD& exitcode, CString& error)
283 hookiterator it = FindItem(post_commit_hook, pathList);
284 if (it == end())
285 return false;
286 CString sCmd = it->second.commandline;
287 AddPathParam(sCmd, pathList);
288 AddDepthParam(sCmd, depth);
289 AddMessageFileParam(sCmd, message);
290 AddParam(sCmd, rev.m_CommitHash.ToString());
291 AddErrorParam(sCmd, error);
292 AddCWDParam(sCmd, pathList);
293 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
294 return true;
297 bool CHooks::StartUpdate(const CTGitPathList& pathList, DWORD& exitcode, CString& error)
299 hookiterator it = FindItem(start_update_hook, pathList);
300 if (it == end())
301 return false;
302 CString sCmd = it->second.commandline;
303 AddPathParam(sCmd, pathList);
304 AddCWDParam(sCmd, pathList);
305 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
306 return true;
309 bool CHooks::PreUpdate(const CTGitPathList& pathList, git_depth_t depth, GitRev rev, DWORD& exitcode, CString& error)
311 hookiterator it = FindItem(pre_update_hook, pathList);
312 if (it == end())
313 return false;
314 CString sCmd = it->second.commandline;
315 AddPathParam(sCmd, pathList);
316 AddDepthParam(sCmd, depth);
317 AddParam(sCmd, rev.m_CommitHash.ToString());
318 AddCWDParam(sCmd, pathList);
319 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
320 return true;
323 bool CHooks::PostUpdate(const CTGitPathList& pathList, git_depth_t depth, GitRev rev, DWORD& exitcode, CString& error)
325 hookiterator it = FindItem(post_update_hook, pathList);
326 if (it == end())
327 return false;
328 CString sCmd = it->second.commandline;
329 AddPathParam(sCmd, pathList);
330 AddDepthParam(sCmd, depth);
331 AddParam(sCmd, rev.m_CommitHash.ToString());
332 AddErrorParam(sCmd, error);
333 AddCWDParam(sCmd, pathList);
334 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
335 return true;
338 bool CHooks::PrePush(const CTGitPathList& pathList,DWORD& exitcode, CString& error)
340 hookiterator it = FindItem(pre_push_hook, pathList);
341 if (it == end())
342 return false;
343 CString sCmd = it->second.commandline;
344 AddPathParam(sCmd, pathList);
345 AddErrorParam(sCmd, error);
346 AddCWDParam(sCmd, pathList);
347 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
348 return true;
351 bool CHooks::PostPush(const CTGitPathList& pathList,DWORD& exitcode, CString& error)
353 hookiterator it = FindItem(post_push_hook, pathList);
354 if (it == end())
355 return false;
356 CString sCmd = it->second.commandline;
357 AddPathParam(sCmd, pathList);
358 AddErrorParam(sCmd, error);
359 AddCWDParam(sCmd, pathList);
360 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
361 return true;
364 hookiterator CHooks::FindItem(hooktype t, const CTGitPathList& pathList)
366 hookkey key;
367 for (int i=0; i<pathList.GetCount(); ++i)
369 CTGitPath path = pathList[i];
372 key.htype = t;
373 key.path = path;
374 hookiterator it = find(key);
375 if (it != end())
377 return it;
379 path = path.GetContainingDirectory();
380 } while(!path.IsEmpty());
382 // look for a script with a path as '*'
383 key.htype = t;
384 key.path = CTGitPath(_T("*"));
385 hookiterator it = find(key);
386 if (it != end())
388 return it;
391 return end();
394 DWORD CHooks::RunScript(CString cmd, LPCTSTR currentDir, CString& error, bool bWait, bool bShow)
396 DWORD exitcode = 0;
397 SECURITY_ATTRIBUTES sa;
398 SecureZeroMemory(&sa, sizeof(sa));
399 sa.nLength = sizeof(sa);
400 sa.bInheritHandle = TRUE;
402 CAutoFile hOut ;
403 CAutoFile hRedir;
404 CAutoFile hErr;
406 // clear the error string
407 error.Empty();
409 // Create Temp File for redirection
410 TCHAR szTempPath[MAX_PATH];
411 TCHAR szOutput[MAX_PATH];
412 TCHAR szErr[MAX_PATH];
413 GetTortoiseGitTempPath(_countof(szTempPath), szTempPath);
414 GetTempFileName(szTempPath, _T("git"), 0, szErr);
416 // setup redirection handles
417 // output handle must be WRITE mode, share READ
418 // redirect handle must be READ mode, share WRITE
419 hErr = CreateFile(szErr, GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, 0);
421 if (!hErr)
422 return (DWORD)-1;
424 hRedir = CreateFile(szErr, GENERIC_READ, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
426 if (!hRedir)
427 return (DWORD)-1;
429 GetTempFileName(szTempPath, _T("git"), 0, szOutput);
430 hOut = CreateFile(szOutput, GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, 0);
432 if (!hOut)
433 return (DWORD)-1;
435 // setup startup info, set std out/err handles
436 // hide window
437 STARTUPINFO si;
438 SecureZeroMemory(&si, sizeof(si));
439 si.cb = sizeof(si);
440 if (hOut != INVALID_HANDLE_VALUE)
442 si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
443 si.hStdOutput = hOut;
444 si.hStdError = hErr;
445 si.wShowWindow = bShow ? SW_SHOW : SW_HIDE;
448 PROCESS_INFORMATION pi;
449 SecureZeroMemory(&pi, sizeof(pi));
451 DWORD dwFlags = 0;
453 if (!CreateProcess(NULL, cmd.GetBuffer(), NULL, NULL, TRUE, dwFlags, NULL, currentDir, &si, &pi))
455 int err = GetLastError(); // preserve the CreateProcess error
456 error = CFormatMessageWrapper(err);
457 SetLastError(err);
458 cmd.ReleaseBuffer();
459 return (DWORD)-1;
461 cmd.ReleaseBuffer();
463 CloseHandle(pi.hThread);
465 // wait for process to finish, capture redirection and
466 // send it to the parent window/console
467 if (bWait)
469 DWORD dw;
470 char buf[256];
473 SecureZeroMemory(&buf,sizeof(buf));
474 while (ReadFile(hRedir, &buf, sizeof(buf)-1, &dw, NULL))
476 if (dw == 0)
477 break;
478 error += CString(CStringA(buf,dw));
479 SecureZeroMemory(&buf,sizeof(buf));
481 } while (WaitForSingleObject(pi.hProcess, 0) != WAIT_OBJECT_0);
483 // perform any final flushing
484 while (ReadFile(hRedir, &buf, sizeof(buf)-1, &dw, NULL))
486 if (dw == 0)
487 break;
489 error += CString(CStringA(buf, dw));
490 SecureZeroMemory(&buf,sizeof(buf));
492 WaitForSingleObject(pi.hProcess, INFINITE);
493 GetExitCodeProcess(pi.hProcess, &exitcode);
495 CloseHandle(pi.hProcess);
496 DeleteFile(szOutput);
497 DeleteFile(szErr);
499 return exitcode;