Fixed Issue #395: [BUG] Infomation error when "Switch the comparison"
[TortoiseGit.git] / src / Utils / Hooks.cpp
blobf307e60329c41451053db60407e0f1d9553a960f
1 // TortoiseSVN - 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 = CTSVNPath(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.LastError)
135 return false;
136 return true;
139 bool CHooks::Remove(hookkey key)
141 return (erase(key) > 0);
144 void CHooks::Add(hooktype ht, const CTSVNPath& 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");
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("start_update_hook"))==0)
189 return start_update_hook;
190 if (s.Compare(_T("pre_update_hook"))==0)
191 return pre_update_hook;
192 if (s.Compare(_T("post_update_hook"))==0)
193 return post_update_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 CTSVNPathList& pathList)
206 CTSVNPath temppath = CTempFiles::Instance().GetTempFilePath(true);
207 pathList.WriteToFile(temppath.GetWinPathString(), true);
208 AddParam(sCmd, temppath.GetWinPathString());
211 void CHooks::AddCWDParam(CString& sCmd, const CTSVNPathList& pathList)
213 AddParam(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPathString());
216 void CHooks::AddDepthParam(CString& sCmd, svn_depth_t depth)
218 CString sTemp;
219 sTemp.Format(_T("%d"), depth);
220 AddParam(sCmd, sTemp);
223 void CHooks::AddErrorParam(CString& sCmd, const CString& error)
225 CTSVNPath tempPath;
226 tempPath = CTempFiles::Instance().GetTempFilePath(true);
227 CStringUtils::WriteStringToTextFile(tempPath.GetWinPath(), (LPCTSTR)error);
228 AddParam(sCmd, tempPath.GetWinPathString());
231 CTSVNPath CHooks::AddMessageFileParam(CString& sCmd, const CString& message)
233 CTSVNPath tempPath;
234 tempPath = CTempFiles::Instance().GetTempFilePath(true);
235 CStringUtils::WriteStringToTextFile(tempPath.GetWinPath(), (LPCTSTR)message);
236 AddParam(sCmd, tempPath.GetWinPathString());
237 return tempPath;
240 bool CHooks::StartCommit(const CTSVNPathList& pathList, CString& message, DWORD& exitcode, CString& error)
242 hookiterator it = FindItem(start_commit_hook, pathList);
243 if (it == end())
244 return false;
245 CString sCmd = it->second.commandline;
246 AddPathParam(sCmd, pathList);
247 CTSVNPath temppath = AddMessageFileParam(sCmd, message);
248 AddCWDParam(sCmd, pathList);
249 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
250 if (!exitcode && !temppath.IsEmpty())
252 CStringUtils::ReadStringFromTextFile(temppath.GetWinPathString(), message);
254 return true;
257 bool CHooks::PreCommit(const CTSVNPathList& pathList, svn_depth_t depth, const CString& message, DWORD& exitcode, CString& error)
259 hookiterator it = FindItem(pre_commit_hook, pathList);
260 if (it == end())
261 return false;
262 CString sCmd = it->second.commandline;
263 AddPathParam(sCmd, pathList);
264 AddDepthParam(sCmd, depth);
265 AddMessageFileParam(sCmd, message);
266 AddCWDParam(sCmd, pathList);
267 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
268 return true;
271 bool CHooks::PostCommit(const CTSVNPathList& pathList, svn_depth_t depth, SVNRev rev, const CString& message, DWORD& exitcode, CString& error)
273 hookiterator it = FindItem(post_commit_hook, pathList);
274 if (it == end())
275 return false;
276 CString sCmd = it->second.commandline;
277 AddPathParam(sCmd, pathList);
278 AddDepthParam(sCmd, depth);
279 AddMessageFileParam(sCmd, message);
280 AddParam(sCmd, rev.ToString());
281 AddErrorParam(sCmd, error);
282 AddCWDParam(sCmd, pathList);
283 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
284 return true;
287 bool CHooks::StartUpdate(const CTSVNPathList& pathList, DWORD& exitcode, CString& error)
289 hookiterator it = FindItem(start_update_hook, pathList);
290 if (it == end())
291 return false;
292 CString sCmd = it->second.commandline;
293 AddPathParam(sCmd, pathList);
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::PreUpdate(const CTSVNPathList& pathList, svn_depth_t depth, SVNRev rev, DWORD& exitcode, CString& error)
301 hookiterator it = FindItem(pre_update_hook, pathList);
302 if (it == end())
303 return false;
304 CString sCmd = it->second.commandline;
305 AddPathParam(sCmd, pathList);
306 AddDepthParam(sCmd, depth);
307 AddParam(sCmd, rev.ToString());
308 AddCWDParam(sCmd, pathList);
309 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
310 return true;
313 bool CHooks::PostUpdate(const CTSVNPathList& pathList, svn_depth_t depth, SVNRev rev, DWORD& exitcode, CString& error)
315 hookiterator it = FindItem(post_update_hook, pathList);
316 if (it == end())
317 return false;
318 CString sCmd = it->second.commandline;
319 AddPathParam(sCmd, pathList);
320 AddDepthParam(sCmd, depth);
321 AddParam(sCmd, rev.ToString());
322 AddErrorParam(sCmd, error);
323 AddCWDParam(sCmd, pathList);
324 exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);
325 return true;
328 hookiterator CHooks::FindItem(hooktype t, const CTSVNPathList& pathList)
330 hookkey key;
331 for (int i=0; i<pathList.GetCount(); ++i)
333 CTSVNPath path = pathList[i];
336 key.htype = t;
337 key.path = path;
338 hookiterator it = find(key);
339 if (it != end())
341 return it;
343 path = path.GetContainingDirectory();
344 } while(!path.IsEmpty());
346 // look for a script with a path as '*'
347 key.htype = t;
348 key.path = CTSVNPath(_T("*"));
349 hookiterator it = find(key);
350 if (it != end())
352 return it;
355 return end();
358 DWORD CHooks::RunScript(CString cmd, LPCTSTR currentDir, CString& error, bool bWait, bool bShow)
360 DWORD exitcode = 0;
361 SECURITY_ATTRIBUTES sa;
362 SecureZeroMemory(&sa, sizeof(sa));
363 sa.nLength = sizeof(sa);
364 sa.bInheritHandle = TRUE;
366 HANDLE hOut = INVALID_HANDLE_VALUE;
367 HANDLE hRedir = INVALID_HANDLE_VALUE;
368 HANDLE hErr = INVALID_HANDLE_VALUE;
370 // clear the error string
371 error.Empty();
373 // Create Temp File for redirection
374 TCHAR szTempPath[MAX_PATH];
375 TCHAR szOutput[MAX_PATH];
376 TCHAR szErr[MAX_PATH];
377 GetTempPath(sizeof(szTempPath)/sizeof(TCHAR),szTempPath);
378 GetTempFileName(szTempPath, _T("svn"), 0, szErr);
380 // setup redirection handles
381 // output handle must be WRITE mode, share READ
382 // redirect handle must be READ mode, share WRITE
383 hErr = CreateFile(szErr, GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, 0);
385 if (hErr == INVALID_HANDLE_VALUE)
387 return (DWORD)-1;
390 hRedir = CreateFile(szErr, GENERIC_READ, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
392 if (hRedir == INVALID_HANDLE_VALUE)
394 CloseHandle(hErr);
395 return (DWORD)-1;
398 GetTempFileName(szTempPath, _T("svn"), 0, szOutput);
399 hOut = CreateFile(szOutput, GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, 0);
401 if (hOut == INVALID_HANDLE_VALUE)
403 CloseHandle(hErr);
404 CloseHandle(hRedir);
405 return (DWORD)-1;
408 // setup startup info, set std out/err handles
409 // hide window
410 STARTUPINFO si;
411 SecureZeroMemory(&si, sizeof(si));
412 si.cb = sizeof(si);
413 if (hOut != INVALID_HANDLE_VALUE)
415 si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
416 si.hStdOutput = hOut;
417 si.hStdError = hErr;
418 si.wShowWindow = bShow ? SW_SHOW : SW_HIDE;
421 PROCESS_INFORMATION pi;
422 SecureZeroMemory(&pi, sizeof(pi));
424 DWORD dwFlags = 0;
426 if (!CreateProcess(NULL, cmd.GetBuffer(), NULL, NULL, TRUE, dwFlags, NULL, currentDir, &si, &pi))
428 int err = GetLastError(); // preserve the CreateProcess error
429 if (hErr != INVALID_HANDLE_VALUE)
431 CloseHandle(hErr);
432 CloseHandle(hRedir);
434 SetLastError(err);
435 cmd.ReleaseBuffer();
436 return (DWORD)-1;
438 cmd.ReleaseBuffer();
440 CloseHandle(pi.hThread);
442 // wait for process to finish, capture redirection and
443 // send it to the parent window/console
444 if (bWait)
446 DWORD dw;
447 char buf[256];
450 SecureZeroMemory(&buf,sizeof(buf));
451 while (ReadFile(hRedir, &buf, sizeof(buf)-1, &dw, NULL))
453 if (dw == 0)
454 break;
455 error += CString(CStringA(buf,dw));
456 SecureZeroMemory(&buf,sizeof(buf));
458 } while (WaitForSingleObject(pi.hProcess, 0) != WAIT_OBJECT_0);
460 // perform any final flushing
461 while (ReadFile(hRedir, &buf, sizeof(buf)-1, &dw, NULL))
463 if (dw == 0)
464 break;
466 error += CString(CStringA(buf, dw));
467 SecureZeroMemory(&buf,sizeof(buf));
469 WaitForSingleObject(pi.hProcess, INFINITE);
470 GetExitCodeProcess(pi.hProcess, &exitcode);
472 CloseHandle(pi.hProcess);
473 CloseHandle(hErr);
474 CloseHandle(hOut);
475 CloseHandle(hRedir);
476 DeleteFile(szOutput);
477 DeleteFile(szErr);
479 return exitcode;