fixed the ContextMenuStash.png screenshot
[TortoiseGit.git] / src / Utils / Hooks.cpp
blob034e1a79e582656a500b92e0af320328b22e9e76
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"
25 CHooks* CHooks::m_pInstance;
27 CHooks::CHooks()
31 CHooks::~CHooks()
35 bool CHooks::Create()
37 if (m_pInstance == NULL)
38 m_pInstance = new CHooks();
39 CRegString reghooks = CRegString(_T("Software\\TortoiseGit\\hooks"));
40 CString strhooks = reghooks;
41 // now fill the map with all the hooks defined in the string
42 // the string consists of multiple lines, where one hook script is defined
43 // as four lines:
44 // line 1: the hook type
45 // line 2: path to working copy where to apply the hook script
46 // line 3: command line to execute
47 // line 4: 'true' or 'false' for waiting for the script to finish
48 // line 5: 'show' or 'hide' on how to start the hook script
49 hookkey key;
50 int pos = 0;
51 hookcmd cmd;
52 while ((pos = strhooks.Find('\n')) >= 0)
54 // line 1
55 key.htype = GetHookType(strhooks.Mid(0, pos));
56 if (pos+1 < strhooks.GetLength())
57 strhooks = strhooks.Mid(pos+1);
58 else
59 strhooks.Empty();
60 bool bComplete = false;
61 if ((pos = strhooks.Find('\n')) >= 0)
63 // line 2
64 key.path = CTGitPath(strhooks.Mid(0, pos));
65 if (pos+1 < strhooks.GetLength())
66 strhooks = strhooks.Mid(pos+1);
67 else
68 strhooks.Empty();
69 if ((pos = strhooks.Find('\n')) >= 0)
71 // line 3
72 cmd.commandline = strhooks.Mid(0, pos);
73 if (pos+1 < strhooks.GetLength())
74 strhooks = strhooks.Mid(pos+1);
75 else
76 strhooks.Empty();
77 if ((pos = strhooks.Find('\n')) >= 0)
79 // line 4
80 cmd.bWait = (strhooks.Mid(0, pos).CompareNoCase(_T("true"))==0);
81 if (pos+1 < strhooks.GetLength())
82 strhooks = strhooks.Mid(pos+1);
83 else
84 strhooks.Empty();
85 if ((pos = strhooks.Find('\n')) >= 0)
87 // line 5
88 cmd.bShow = (strhooks.Mid(0, pos).CompareNoCase(_T("show"))==0);
89 if (pos+1 < strhooks.GetLength())
90 strhooks = strhooks.Mid(pos+1);
91 else
92 strhooks.Empty();
93 bComplete = true;
98 if (bComplete)
100 m_pInstance->insert(std::pair<hookkey, hookcmd>(key, cmd));
103 return true;
106 CHooks& CHooks::Instance()
108 return *m_pInstance;
111 void CHooks::Destroy()
113 delete m_pInstance;
116 bool CHooks::Save()
118 CString strhooks;
119 for (hookiterator it = begin(); it != end(); ++it)
121 strhooks += GetHookTypeString(it->first.htype);
122 strhooks += '\n';
123 strhooks += it->first.path.GetWinPathString();
124 strhooks += '\n';
125 strhooks += it->second.commandline;
126 strhooks += '\n';
127 strhooks += (it->second.bWait ? _T("true") : _T("false"));
128 strhooks += '\n';
129 strhooks += (it->second.bShow ? _T("show") : _T("hide"));
130 strhooks += '\n';
132 CRegString reghooks = CRegString(_T("Software\\TortoiseGit\\hooks"));
133 reghooks = strhooks;
134 if (reghooks.GetLastError())
135 return false;
136 return true;
139 bool CHooks::Remove(hookkey key)
141 return (erase(key) > 0);
144 void CHooks::Add(hooktype ht, const CTGitPath& Path, LPCTSTR szCmd, bool bWait, bool bShow)
146 hookkey key;
147 key.htype = ht;
148 key.path = Path;
149 hookiterator it = find(key);
150 if (it!=end())
151 erase(it);
153 hookcmd cmd;
154 cmd.commandline = szCmd;
155 cmd.bWait = bWait;
156 cmd.bShow = bShow;
157 insert(std::pair<hookkey, hookcmd>(key, cmd));
160 CString CHooks::GetHookTypeString(hooktype t)
162 switch (t)
164 case start_commit_hook:
165 return _T("start_commit_hook");
166 case pre_commit_hook:
167 return _T("pre_commit_hook");
168 case post_commit_hook:
169 return _T("post_commit_hook");
170 case start_update_hook:
171 return _T("start_update_hook");
172 case pre_update_hook:
173 return _T("pre_update_hook");
174 case post_update_hook:
175 return _T("post_update_hook");
176 case pre_push_hook:
177 return _T("pre_push_hook");
178 case post_push_hook:
179 return _T("post_push_hook");
181 return _T("");
184 hooktype CHooks::GetHookType(const CString& s)
186 if (s.Compare(_T("start_commit_hook"))==0)
187 return start_commit_hook;
188 if (s.Compare(_T("pre_commit_hook"))==0)
189 return pre_commit_hook;
190 if (s.Compare(_T("post_commit_hook"))==0)
191 return post_commit_hook;
192 if (s.Compare(_T("start_update_hook"))==0)
193 return start_update_hook;
194 if (s.Compare(_T("pre_update_hook"))==0)
195 return pre_update_hook;
196 if (s.Compare(_T("post_update_hook"))==0)
197 return post_update_hook;
198 if (s.Compare(_T("pre_push_hook"))==0)
199 return pre_push_hook;
200 if (s.Compare(_T("post_push_hook"))==0)
201 return post_push_hook;
203 return unknown_hook;
206 void CHooks::AddParam(CString& sCmd, const CString& param)
208 sCmd += _T(" \"");
209 sCmd += param;
210 sCmd += _T("\"");
213 void CHooks::AddPathParam(CString& sCmd, const CTGitPathList& pathList)
215 CTGitPath temppath = CTempFiles::Instance().GetTempFilePath(true);
216 pathList.WriteToFile(temppath.GetWinPathString(), true);
217 AddParam(sCmd, temppath.GetWinPathString());
220 void CHooks::AddCWDParam(CString& sCmd, const CTGitPathList& pathList)
222 AddParam(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPathString());
225 void CHooks::AddDepthParam(CString& sCmd, git_depth_t depth)
227 CString sTemp;
228 sTemp.Format(_T("%d"), depth);
229 AddParam(sCmd, sTemp);
232 void CHooks::AddErrorParam(CString& sCmd, const CString& error)
234 CTGitPath tempPath;
235 tempPath = CTempFiles::Instance().GetTempFilePath(true);
236 CStringUtils::WriteStringToTextFile(tempPath.GetWinPath(), (LPCTSTR)error);
237 AddParam(sCmd, tempPath.GetWinPathString());
240 CTGitPath CHooks::AddMessageFileParam(CString& sCmd, const CString& message)
242 CTGitPath tempPath;
243 tempPath = CTempFiles::Instance().GetTempFilePath(true);
244 CStringUtils::WriteStringToTextFile(tempPath.GetWinPath(), (LPCTSTR)message);
245 AddParam(sCmd, tempPath.GetWinPathString());
246 return tempPath;
249 bool CHooks::StartCommit(const CTGitPathList& pathList, CString& message, DWORD& exitcode, CString& error)
251 hookiterator it = FindItem(start_commit_hook, pathList);
252 if (it == end())
253 return false;
254 CString sCmd = it->second.commandline;
255 AddPathParam(sCmd, pathList);
256 CTGitPath temppath = AddMessageFileParam(sCmd, message);
257 AddCWDParam(sCmd, pathList);
258 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
259 if (!exitcode && !temppath.IsEmpty())
261 CStringUtils::ReadStringFromTextFile(temppath.GetWinPathString(), message);
263 return true;
266 bool CHooks::PreCommit(const CTGitPathList& pathList, git_depth_t depth, const CString& message, DWORD& exitcode, CString& error)
268 hookiterator it = FindItem(pre_commit_hook, pathList);
269 if (it == end())
270 return false;
271 CString sCmd = it->second.commandline;
272 AddPathParam(sCmd, pathList);
273 AddDepthParam(sCmd, depth);
274 AddMessageFileParam(sCmd, message);
275 AddCWDParam(sCmd, pathList);
276 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
277 return true;
280 bool CHooks::PostCommit(const CTGitPathList& pathList, git_depth_t depth, GitRev rev, const CString& message, DWORD& exitcode, CString& error)
282 hookiterator it = FindItem(post_commit_hook, pathList);
283 if (it == end())
284 return false;
285 CString sCmd = it->second.commandline;
286 AddPathParam(sCmd, pathList);
287 AddDepthParam(sCmd, depth);
288 AddMessageFileParam(sCmd, message);
289 AddParam(sCmd, rev.m_CommitHash.ToString());
290 AddErrorParam(sCmd, error);
291 AddCWDParam(sCmd, pathList);
292 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
293 return true;
296 bool CHooks::StartUpdate(const CTGitPathList& pathList, DWORD& exitcode, CString& error)
298 hookiterator it = FindItem(start_update_hook, pathList);
299 if (it == end())
300 return false;
301 CString sCmd = it->second.commandline;
302 AddPathParam(sCmd, pathList);
303 AddCWDParam(sCmd, pathList);
304 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
305 return true;
308 bool CHooks::PreUpdate(const CTGitPathList& pathList, git_depth_t depth, GitRev rev, DWORD& exitcode, CString& error)
310 hookiterator it = FindItem(pre_update_hook, pathList);
311 if (it == end())
312 return false;
313 CString sCmd = it->second.commandline;
314 AddPathParam(sCmd, pathList);
315 AddDepthParam(sCmd, depth);
316 AddParam(sCmd, rev.m_CommitHash.ToString());
317 AddCWDParam(sCmd, pathList);
318 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
319 return true;
322 bool CHooks::PostUpdate(const CTGitPathList& pathList, git_depth_t depth, GitRev rev, DWORD& exitcode, CString& error)
324 hookiterator it = FindItem(post_update_hook, pathList);
325 if (it == end())
326 return false;
327 CString sCmd = it->second.commandline;
328 AddPathParam(sCmd, pathList);
329 AddDepthParam(sCmd, depth);
330 AddParam(sCmd, rev.m_CommitHash.ToString());
331 AddErrorParam(sCmd, error);
332 AddCWDParam(sCmd, pathList);
333 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
334 return true;
337 bool CHooks::PrePush(const CTGitPathList& pathList,DWORD& exitcode, CString& error)
339 hookiterator it = FindItem(pre_push_hook, pathList);
340 if (it == end())
341 return false;
342 CString sCmd = it->second.commandline;
343 AddPathParam(sCmd, pathList);
344 AddErrorParam(sCmd, error);
345 AddCWDParam(sCmd, pathList);
346 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
347 return true;
350 bool CHooks::PostPush(const CTGitPathList& pathList,DWORD& exitcode, CString& error)
352 hookiterator it = FindItem(post_push_hook, pathList);
353 if (it == end())
354 return false;
355 CString sCmd = it->second.commandline;
356 AddPathParam(sCmd, pathList);
357 AddErrorParam(sCmd, error);
358 AddCWDParam(sCmd, pathList);
359 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
360 return true;
363 hookiterator CHooks::FindItem(hooktype t, const CTGitPathList& pathList)
365 hookkey key;
366 for (int i=0; i<pathList.GetCount(); ++i)
368 CTGitPath path = pathList[i];
371 key.htype = t;
372 key.path = path;
373 hookiterator it = find(key);
374 if (it != end())
376 return it;
378 path = path.GetContainingDirectory();
379 } while(!path.IsEmpty());
381 // look for a script with a path as '*'
382 key.htype = t;
383 key.path = CTGitPath(_T("*"));
384 hookiterator it = find(key);
385 if (it != end())
387 return it;
390 return end();
393 DWORD CHooks::RunScript(CString cmd, LPCTSTR currentDir, CString& error, bool bWait, bool bShow)
395 DWORD exitcode = 0;
396 SECURITY_ATTRIBUTES sa;
397 SecureZeroMemory(&sa, sizeof(sa));
398 sa.nLength = sizeof(sa);
399 sa.bInheritHandle = TRUE;
401 HANDLE hOut = INVALID_HANDLE_VALUE;
402 HANDLE hRedir = INVALID_HANDLE_VALUE;
403 HANDLE hErr = INVALID_HANDLE_VALUE;
405 // clear the error string
406 error.Empty();
408 // Create Temp File for redirection
409 TCHAR szTempPath[MAX_PATH];
410 TCHAR szOutput[MAX_PATH];
411 TCHAR szErr[MAX_PATH];
412 GetTempPath(_countof(szTempPath), szTempPath);
413 GetTempFileName(szTempPath, _T("git"), 0, szErr);
415 // setup redirection handles
416 // output handle must be WRITE mode, share READ
417 // redirect handle must be READ mode, share WRITE
418 hErr = CreateFile(szErr, GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, 0);
420 if (hErr == INVALID_HANDLE_VALUE)
422 return (DWORD)-1;
425 hRedir = CreateFile(szErr, GENERIC_READ, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
427 if (hRedir == INVALID_HANDLE_VALUE)
429 CloseHandle(hErr);
430 return (DWORD)-1;
433 GetTempFileName(szTempPath, _T("git"), 0, szOutput);
434 hOut = CreateFile(szOutput, GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, 0);
436 if (hOut == INVALID_HANDLE_VALUE)
438 CloseHandle(hErr);
439 CloseHandle(hRedir);
440 return (DWORD)-1;
443 // setup startup info, set std out/err handles
444 // hide window
445 STARTUPINFO si;
446 SecureZeroMemory(&si, sizeof(si));
447 si.cb = sizeof(si);
448 if (hOut != INVALID_HANDLE_VALUE)
450 si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
451 si.hStdOutput = hOut;
452 si.hStdError = hErr;
453 si.wShowWindow = bShow ? SW_SHOW : SW_HIDE;
456 PROCESS_INFORMATION pi;
457 SecureZeroMemory(&pi, sizeof(pi));
459 DWORD dwFlags = 0;
461 if (!CreateProcess(NULL, cmd.GetBuffer(), NULL, NULL, TRUE, dwFlags, NULL, currentDir, &si, &pi))
463 int err = GetLastError(); // preserve the CreateProcess error
464 if (hErr != INVALID_HANDLE_VALUE)
466 CloseHandle(hErr);
467 CloseHandle(hRedir);
469 SetLastError(err);
470 cmd.ReleaseBuffer();
471 return (DWORD)-1;
473 cmd.ReleaseBuffer();
475 CloseHandle(pi.hThread);
477 // wait for process to finish, capture redirection and
478 // send it to the parent window/console
479 if (bWait)
481 DWORD dw;
482 char buf[256];
485 SecureZeroMemory(&buf,sizeof(buf));
486 while (ReadFile(hRedir, &buf, sizeof(buf)-1, &dw, NULL))
488 if (dw == 0)
489 break;
490 error += CString(CStringA(buf,dw));
491 SecureZeroMemory(&buf,sizeof(buf));
493 } while (WaitForSingleObject(pi.hProcess, 0) != WAIT_OBJECT_0);
495 // perform any final flushing
496 while (ReadFile(hRedir, &buf, sizeof(buf)-1, &dw, NULL))
498 if (dw == 0)
499 break;
501 error += CString(CStringA(buf, dw));
502 SecureZeroMemory(&buf,sizeof(buf));
504 WaitForSingleObject(pi.hProcess, INFINITE);
505 GetExitCodeProcess(pi.hProcess, &exitcode);
507 CloseHandle(pi.hProcess);
508 CloseHandle(hErr);
509 CloseHandle(hOut);
510 CloseHandle(hRedir);
511 DeleteFile(szOutput);
512 DeleteFile(szErr);
514 return exitcode;