OSDN Git Service

Show Ignore Sub Menu
[tortoisegit/TortoiseGitJp.git] / src / Utils / Hooks.cpp
1 // TortoiseSVN - a Windows shell extension for easy version control\r
2 \r
3 // Copyright (C) 2007-2008 - TortoiseSVN\r
4 \r
5 // This program is free software; you can redistribute it and/or\r
6 // modify it under the terms of the GNU General Public License\r
7 // as published by the Free Software Foundation; either version 2\r
8 // of the License, or (at your option) any later version.\r
9 \r
10 // This program is distributed in the hope that it will be useful,\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13 // GNU General Public License for more details.\r
14 \r
15 // You should have received a copy of the GNU General Public License\r
16 // along with this program; if not, write to the Free Software Foundation,\r
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
18 //\r
19 #include "stdafx.h"\r
20 #include "Hooks.h"\r
21 #include "registry.h"\r
22 #include "StringUtils.h"\r
23 #include "TempFile.h"\r
24 \r
25 CHooks* CHooks::m_pInstance;\r
26 \r
27 CHooks::CHooks()\r
28 {\r
29 }\r
30 \r
31 CHooks::~CHooks()\r
32 {\r
33 };\r
34 \r
35 bool CHooks::Create()\r
36 {\r
37         if (m_pInstance == NULL)\r
38                 m_pInstance = new CHooks();\r
39         CRegString reghooks = CRegString(_T("Software\\TortoiseSVN\\hooks"));\r
40         CString strhooks = reghooks;\r
41         // now fill the map with all the hooks defined in the string\r
42         // the string consists of multiple lines, where one hook script is defined\r
43         // as four lines:\r
44         // line 1: the hook type\r
45         // line 2: path to working copy where to apply the hook script\r
46         // line 3: command line to execute\r
47         // line 4: 'true' or 'false' for waiting for the script to finish\r
48         // line 5: 'show' or 'hide' on how to start the hook script\r
49         hookkey key;\r
50         int pos = 0;\r
51         hookcmd cmd;\r
52         while ((pos = strhooks.Find('\n')) >= 0)\r
53         {\r
54                 // line 1\r
55                 key.htype = GetHookType(strhooks.Mid(0, pos));\r
56                 if (pos+1 < strhooks.GetLength())\r
57                         strhooks = strhooks.Mid(pos+1);\r
58                 else\r
59                         strhooks.Empty();\r
60                 bool bComplete = false;\r
61                 if ((pos = strhooks.Find('\n')) >= 0)\r
62                 {\r
63                         // line 2\r
64                         key.path = CTSVNPath(strhooks.Mid(0, pos));\r
65                         if (pos+1 < strhooks.GetLength())\r
66                                 strhooks = strhooks.Mid(pos+1);\r
67                         else\r
68                                 strhooks.Empty();\r
69                         if ((pos = strhooks.Find('\n')) >= 0)\r
70                         {\r
71                                 // line 3\r
72                                 cmd.commandline = strhooks.Mid(0, pos);\r
73                                 if (pos+1 < strhooks.GetLength())\r
74                                         strhooks = strhooks.Mid(pos+1);\r
75                                 else\r
76                                         strhooks.Empty();\r
77                                 if ((pos = strhooks.Find('\n')) >= 0)\r
78                                 {\r
79                                         // line 4\r
80                                         cmd.bWait = (strhooks.Mid(0, pos).CompareNoCase(_T("true"))==0);\r
81                                         if (pos+1 < strhooks.GetLength())\r
82                                                 strhooks = strhooks.Mid(pos+1);\r
83                                         else\r
84                                                 strhooks.Empty();\r
85                                         if ((pos = strhooks.Find('\n')) >= 0)\r
86                                         {\r
87                                                 // line 5\r
88                                                 cmd.bShow = (strhooks.Mid(0, pos).CompareNoCase(_T("show"))==0);\r
89                                                 if (pos+1 < strhooks.GetLength())\r
90                                                         strhooks = strhooks.Mid(pos+1);\r
91                                                 else\r
92                                                         strhooks.Empty();\r
93                                                 bComplete = true;\r
94                                         }\r
95                                 }\r
96                         }\r
97                 }\r
98                 if (bComplete)\r
99                 {\r
100                         m_pInstance->insert(std::pair<hookkey, hookcmd>(key, cmd));\r
101                 }\r
102         } \r
103         return true;\r
104 }\r
105 \r
106 CHooks& CHooks::Instance()\r
107 {\r
108         return *m_pInstance;\r
109 }\r
110 \r
111 void CHooks::Destroy()\r
112 {\r
113         delete m_pInstance;\r
114 }\r
115 \r
116 bool CHooks::Save()\r
117 {\r
118         CString strhooks;\r
119         for (hookiterator it = begin(); it != end(); ++it)\r
120         {\r
121                 strhooks += GetHookTypeString(it->first.htype);\r
122                 strhooks += '\n';\r
123                 strhooks += it->first.path.GetWinPathString();\r
124                 strhooks += '\n';\r
125                 strhooks += it->second.commandline;\r
126                 strhooks += '\n';\r
127                 strhooks += (it->second.bWait ? _T("true") : _T("false"));\r
128                 strhooks += '\n';\r
129                 strhooks += (it->second.bShow ? _T("show") : _T("hide"));\r
130                 strhooks += '\n';\r
131         }\r
132         CRegString reghooks = CRegString(_T("Software\\TortoiseSVN\\hooks"));\r
133         reghooks = strhooks;\r
134         if (reghooks.LastError)\r
135                 return false;\r
136         return true;\r
137 }\r
138 \r
139 bool CHooks::Remove(hookkey key)\r
140 {\r
141         return (erase(key) > 0);\r
142 }\r
143 \r
144 void CHooks::Add(hooktype ht, const CTSVNPath& Path, LPCTSTR szCmd, bool bWait, bool bShow)\r
145 {\r
146         hookkey key;\r
147         key.htype = ht;\r
148         key.path = Path;\r
149         hookiterator it = find(key);\r
150         if (it!=end())\r
151                 erase(it);\r
152 \r
153         hookcmd cmd;\r
154         cmd.commandline = szCmd;\r
155         cmd.bWait = bWait;\r
156         cmd.bShow = bShow;\r
157         insert(std::pair<hookkey, hookcmd>(key, cmd));\r
158 }\r
159 \r
160 CString CHooks::GetHookTypeString(hooktype t)\r
161 {\r
162         switch (t)\r
163         {\r
164         case start_commit_hook:\r
165                 return _T("start_commit_hook");\r
166         case pre_commit_hook:\r
167                 return _T("pre_commit_hook");\r
168         case post_commit_hook:\r
169                 return _T("post_commit_hook");\r
170         case start_update_hook:\r
171                 return _T("start_update_hook");\r
172         case pre_update_hook:\r
173                 return _T("pre_update_hook");\r
174         case post_update_hook:\r
175                 return _T("post_update_hook");\r
176         }\r
177         return _T("");\r
178 }\r
179 \r
180 hooktype CHooks::GetHookType(const CString& s)\r
181 {\r
182         if (s.Compare(_T("start_commit_hook"))==0)\r
183                 return start_commit_hook;\r
184         if (s.Compare(_T("pre_commit_hook"))==0)\r
185                 return pre_commit_hook;\r
186         if (s.Compare(_T("post_commit_hook"))==0)\r
187                 return post_commit_hook;\r
188         if (s.Compare(_T("start_update_hook"))==0)\r
189                 return start_update_hook;\r
190         if (s.Compare(_T("pre_update_hook"))==0)\r
191                 return pre_update_hook;\r
192         if (s.Compare(_T("post_update_hook"))==0)\r
193                 return post_update_hook;\r
194         return unknown_hook;\r
195 }\r
196 \r
197 void CHooks::AddParam(CString& sCmd, const CString& param)\r
198 {\r
199         sCmd += _T(" \"");\r
200         sCmd += param;\r
201         sCmd += _T("\"");\r
202 }\r
203 \r
204 void CHooks::AddPathParam(CString& sCmd, const CTSVNPathList& pathList)\r
205 {\r
206         CTSVNPath temppath = CTempFiles::Instance().GetTempFilePath(true);\r
207         pathList.WriteToFile(temppath.GetWinPathString(), true);\r
208         AddParam(sCmd, temppath.GetWinPathString());\r
209 }\r
210 \r
211 void CHooks::AddCWDParam(CString& sCmd, const CTSVNPathList& pathList)\r
212 {\r
213         AddParam(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPathString());\r
214 }\r
215 \r
216 void CHooks::AddDepthParam(CString& sCmd, svn_depth_t depth)\r
217 {\r
218         CString sTemp;\r
219         sTemp.Format(_T("%d"), depth);\r
220         AddParam(sCmd, sTemp);\r
221 }\r
222 \r
223 void CHooks::AddErrorParam(CString& sCmd, const CString& error)\r
224 {\r
225         CTSVNPath tempPath;\r
226         tempPath = CTempFiles::Instance().GetTempFilePath(true);\r
227         CStringUtils::WriteStringToTextFile(tempPath.GetWinPath(), (LPCTSTR)error);\r
228         AddParam(sCmd, tempPath.GetWinPathString());\r
229 }\r
230 \r
231 CTSVNPath CHooks::AddMessageFileParam(CString& sCmd, const CString& message)\r
232 {\r
233         CTSVNPath tempPath;\r
234         tempPath = CTempFiles::Instance().GetTempFilePath(true);\r
235         CStringUtils::WriteStringToTextFile(tempPath.GetWinPath(), (LPCTSTR)message);\r
236         AddParam(sCmd, tempPath.GetWinPathString());\r
237         return tempPath;\r
238 }\r
239 \r
240 bool CHooks::StartCommit(const CTSVNPathList& pathList, CString& message, DWORD& exitcode, CString& error)\r
241 {\r
242         hookiterator it = FindItem(start_commit_hook, pathList);\r
243         if (it == end())\r
244                 return false;\r
245         CString sCmd = it->second.commandline;\r
246         AddPathParam(sCmd, pathList);\r
247         CTSVNPath temppath = AddMessageFileParam(sCmd, message);\r
248         AddCWDParam(sCmd, pathList);\r
249         exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);\r
250         if (!exitcode && !temppath.IsEmpty())\r
251         {\r
252                 CStringUtils::ReadStringFromTextFile(temppath.GetWinPathString(), message);\r
253         }\r
254         return true;\r
255 }\r
256 \r
257 bool CHooks::PreCommit(const CTSVNPathList& pathList, svn_depth_t depth, const CString& message, DWORD& exitcode, CString& error)\r
258 {\r
259         hookiterator it = FindItem(pre_commit_hook, pathList);\r
260         if (it == end())\r
261                 return false;\r
262         CString sCmd = it->second.commandline;\r
263         AddPathParam(sCmd, pathList);\r
264         AddDepthParam(sCmd, depth);\r
265         AddMessageFileParam(sCmd, message);\r
266         AddCWDParam(sCmd, pathList);\r
267         exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);\r
268         return true;\r
269 }\r
270 \r
271 bool CHooks::PostCommit(const CTSVNPathList& pathList, svn_depth_t depth, SVNRev rev, const CString& message, DWORD& exitcode, CString& error)\r
272 {\r
273         hookiterator it = FindItem(post_commit_hook, pathList);\r
274         if (it == end())\r
275                 return false;\r
276         CString sCmd = it->second.commandline;\r
277         AddPathParam(sCmd, pathList);\r
278         AddDepthParam(sCmd, depth);\r
279         AddMessageFileParam(sCmd, message);\r
280         AddParam(sCmd, rev.ToString());\r
281         AddErrorParam(sCmd, error);\r
282         AddCWDParam(sCmd, pathList);\r
283         exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);\r
284         return true;\r
285 }\r
286 \r
287 bool CHooks::StartUpdate(const CTSVNPathList& pathList, DWORD& exitcode, CString& error)\r
288 {\r
289         hookiterator it = FindItem(start_update_hook, pathList);\r
290         if (it == end())\r
291                 return false;\r
292         CString sCmd = it->second.commandline;\r
293         AddPathParam(sCmd, pathList);\r
294         AddCWDParam(sCmd, pathList);\r
295         exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);\r
296         return true;\r
297 }\r
298 \r
299 bool CHooks::PreUpdate(const CTSVNPathList& pathList, svn_depth_t depth, SVNRev rev, DWORD& exitcode, CString& error)\r
300 {\r
301         hookiterator it = FindItem(pre_update_hook, pathList);\r
302         if (it == end())\r
303                 return false;\r
304         CString sCmd = it->second.commandline;\r
305         AddPathParam(sCmd, pathList);\r
306         AddDepthParam(sCmd, depth);\r
307         AddParam(sCmd, rev.ToString());\r
308         AddCWDParam(sCmd, pathList);\r
309         exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);\r
310         return true;\r
311 }\r
312 \r
313 bool CHooks::PostUpdate(const CTSVNPathList& pathList, svn_depth_t depth, SVNRev rev, DWORD& exitcode, CString& error)\r
314 {\r
315         hookiterator it = FindItem(post_update_hook, pathList);\r
316         if (it == end())\r
317                 return false;\r
318         CString sCmd = it->second.commandline;\r
319         AddPathParam(sCmd, pathList);\r
320         AddDepthParam(sCmd, depth);\r
321         AddParam(sCmd, rev.ToString());\r
322         AddErrorParam(sCmd, error);\r
323         AddCWDParam(sCmd, pathList);\r
324         exitcode = RunScript(sCmd, pathList.GetCommonRoot().GetDirectory().GetWinPath(), error, it->second.bWait, it->second.bShow);\r
325         return true;\r
326 }\r
327 \r
328 hookiterator CHooks::FindItem(hooktype t, const CTSVNPathList& pathList)\r
329 {\r
330         hookkey key;\r
331         for (int i=0; i<pathList.GetCount(); ++i)\r
332         {\r
333                 CTSVNPath path = pathList[i];\r
334                 do \r
335                 {\r
336                         key.htype = t;\r
337                         key.path = path;\r
338                         hookiterator it = find(key);\r
339                         if (it != end())\r
340                         {\r
341                                 return it;\r
342                         }\r
343                         path = path.GetContainingDirectory();\r
344                 } while(!path.IsEmpty());\r
345         }\r
346         // look for a script with a path as '*'\r
347         key.htype = t;\r
348         key.path = CTSVNPath(_T("*"));\r
349         hookiterator it = find(key);\r
350         if (it != end())\r
351         {\r
352                 return it;\r
353         }\r
354 \r
355         return end();\r
356 }\r
357 \r
358 DWORD CHooks::RunScript(CString cmd, LPCTSTR currentDir, CString& error, bool bWait, bool bShow)\r
359 {\r
360         DWORD exitcode = 0;\r
361         SECURITY_ATTRIBUTES sa;\r
362         SecureZeroMemory(&sa, sizeof(sa));\r
363         sa.nLength = sizeof(sa);\r
364         sa.bInheritHandle = TRUE;\r
365 \r
366         HANDLE hOut   = INVALID_HANDLE_VALUE;\r
367         HANDLE hRedir = INVALID_HANDLE_VALUE;\r
368         HANDLE hErr   = INVALID_HANDLE_VALUE;\r
369 \r
370         // clear the error string\r
371         error.Empty();\r
372 \r
373         // Create Temp File for redirection\r
374         TCHAR szTempPath[MAX_PATH];\r
375         TCHAR szOutput[MAX_PATH];\r
376         TCHAR szErr[MAX_PATH];\r
377         GetTempPath(sizeof(szTempPath)/sizeof(TCHAR),szTempPath);\r
378         GetTempFileName(szTempPath, _T("svn"), 0, szErr);\r
379 \r
380         // setup redirection handles\r
381         // output handle must be WRITE mode, share READ\r
382         // redirect handle must be READ mode, share WRITE\r
383         hErr   = CreateFile(szErr, GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY,        0);\r
384 \r
385         if (hErr  == INVALID_HANDLE_VALUE) \r
386         {\r
387                 return (DWORD)-1;\r
388         }\r
389 \r
390         hRedir = CreateFile(szErr, GENERIC_READ, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);\r
391 \r
392         if (hRedir  == INVALID_HANDLE_VALUE) \r
393         {\r
394                 CloseHandle(hErr);\r
395                 return (DWORD)-1;\r
396         }\r
397 \r
398         GetTempFileName(szTempPath, _T("svn"), 0, szOutput);\r
399         hOut   = CreateFile(szOutput, GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY,     0);\r
400 \r
401         if (hOut  == INVALID_HANDLE_VALUE) \r
402         {\r
403                 CloseHandle(hErr);\r
404                 CloseHandle(hRedir);\r
405                 return (DWORD)-1;\r
406         }\r
407 \r
408         // setup startup info, set std out/err handles\r
409         // hide window\r
410         STARTUPINFO si;\r
411         SecureZeroMemory(&si, sizeof(si));\r
412         si.cb = sizeof(si);\r
413         if (hOut  != INVALID_HANDLE_VALUE) \r
414         {\r
415                 si.dwFlags     = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;\r
416                 si.hStdOutput  = hOut;\r
417                 si.hStdError   = hErr;\r
418                 si.wShowWindow = bShow ? SW_SHOW : SW_HIDE;\r
419         }\r
420 \r
421         PROCESS_INFORMATION pi;\r
422         SecureZeroMemory(&pi, sizeof(pi));\r
423 \r
424         DWORD dwFlags = 0;\r
425 \r
426         if (!CreateProcess(NULL, cmd.GetBuffer(), NULL, NULL, TRUE, dwFlags, NULL, currentDir, &si, &pi)) \r
427         {\r
428                         int err = GetLastError();  // preserve the CreateProcess error\r
429                         if (hErr != INVALID_HANDLE_VALUE) \r
430                         {\r
431                                 CloseHandle(hErr);\r
432                                 CloseHandle(hRedir);\r
433                         }\r
434                         SetLastError(err);\r
435                         cmd.ReleaseBuffer();\r
436                         return (DWORD)-1;\r
437         }\r
438         cmd.ReleaseBuffer();\r
439 \r
440         CloseHandle(pi.hThread);\r
441 \r
442         // wait for process to finish, capture redirection and\r
443         // send it to the parent window/console\r
444         if (bWait)\r
445         {\r
446                 DWORD dw;\r
447                 char buf[256];\r
448                 do \r
449                 {\r
450                         SecureZeroMemory(&buf,sizeof(buf));\r
451                         while (ReadFile(hRedir, &buf, sizeof(buf)-1, &dw, NULL)) \r
452                         {\r
453                                 if (dw == 0) \r
454                                         break;\r
455                                 error += CString(CStringA(buf,dw));\r
456                                 SecureZeroMemory(&buf,sizeof(buf));\r
457                         }\r
458                 } while (WaitForSingleObject(pi.hProcess, 0) != WAIT_OBJECT_0);\r
459 \r
460                 // perform any final flushing\r
461                 while (ReadFile(hRedir, &buf, sizeof(buf)-1, &dw, NULL)) \r
462                 {\r
463                         if (dw == 0) \r
464                                 break;\r
465 \r
466                         error += CString(CStringA(buf, dw));\r
467                         SecureZeroMemory(&buf,sizeof(buf));\r
468                 }\r
469                 WaitForSingleObject(pi.hProcess, INFINITE);\r
470                 GetExitCodeProcess(pi.hProcess, &exitcode);\r
471         }\r
472         CloseHandle(pi.hProcess);\r
473         CloseHandle(hErr);\r
474         CloseHandle(hOut);\r
475         CloseHandle(hRedir);\r
476         DeleteFile(szOutput);\r
477         DeleteFile(szErr);\r
478 \r
479         return exitcode;\r
480 }\r
481 \r