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.
22 #include "StringUtils.h"
25 CHooks
* CHooks::m_pInstance
;
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
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
52 while ((pos
= strhooks
.Find('\n')) >= 0)
55 key
.htype
= GetHookType(strhooks
.Mid(0, pos
));
56 if (pos
+1 < strhooks
.GetLength())
57 strhooks
= strhooks
.Mid(pos
+1);
60 bool bComplete
= false;
61 if ((pos
= strhooks
.Find('\n')) >= 0)
64 key
.path
= CTSVNPath(strhooks
.Mid(0, pos
));
65 if (pos
+1 < strhooks
.GetLength())
66 strhooks
= strhooks
.Mid(pos
+1);
69 if ((pos
= strhooks
.Find('\n')) >= 0)
72 cmd
.commandline
= strhooks
.Mid(0, pos
);
73 if (pos
+1 < strhooks
.GetLength())
74 strhooks
= strhooks
.Mid(pos
+1);
77 if ((pos
= strhooks
.Find('\n')) >= 0)
80 cmd
.bWait
= (strhooks
.Mid(0, pos
).CompareNoCase(_T("true"))==0);
81 if (pos
+1 < strhooks
.GetLength())
82 strhooks
= strhooks
.Mid(pos
+1);
85 if ((pos
= strhooks
.Find('\n')) >= 0)
88 cmd
.bShow
= (strhooks
.Mid(0, pos
).CompareNoCase(_T("show"))==0);
89 if (pos
+1 < strhooks
.GetLength())
90 strhooks
= strhooks
.Mid(pos
+1);
100 m_pInstance
->insert(std::pair
<hookkey
, hookcmd
>(key
, cmd
));
106 CHooks
& CHooks::Instance()
111 void CHooks::Destroy()
119 for (hookiterator it
= begin(); it
!= end(); ++it
)
121 strhooks
+= GetHookTypeString(it
->first
.htype
);
123 strhooks
+= it
->first
.path
.GetWinPathString();
125 strhooks
+= it
->second
.commandline
;
127 strhooks
+= (it
->second
.bWait
? _T("true") : _T("false"));
129 strhooks
+= (it
->second
.bShow
? _T("show") : _T("hide"));
132 CRegString reghooks
= CRegString(_T("Software\\TortoiseGit\\hooks"));
134 if (reghooks
.LastError
)
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
)
149 hookiterator it
= find(key
);
154 cmd
.commandline
= szCmd
;
157 insert(std::pair
<hookkey
, hookcmd
>(key
, cmd
));
160 CString
CHooks::GetHookTypeString(hooktype 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");
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
;
197 void CHooks::AddParam(CString
& sCmd
, const CString
& param
)
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
)
219 sTemp
.Format(_T("%d"), depth
);
220 AddParam(sCmd
, sTemp
);
223 void CHooks::AddErrorParam(CString
& sCmd
, const CString
& error
)
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
)
234 tempPath
= CTempFiles::Instance().GetTempFilePath(true);
235 CStringUtils::WriteStringToTextFile(tempPath
.GetWinPath(), (LPCTSTR
)message
);
236 AddParam(sCmd
, tempPath
.GetWinPathString());
240 bool CHooks::StartCommit(const CTSVNPathList
& pathList
, CString
& message
, DWORD
& exitcode
, CString
& error
)
242 hookiterator it
= FindItem(start_commit_hook
, pathList
);
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
);
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
);
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
);
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
);
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
);
287 bool CHooks::StartUpdate(const CTSVNPathList
& pathList
, DWORD
& exitcode
, CString
& error
)
289 hookiterator it
= FindItem(start_update_hook
, pathList
);
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
);
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
);
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
);
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
);
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
);
328 hookiterator
CHooks::FindItem(hooktype t
, const CTSVNPathList
& pathList
)
331 for (int i
=0; i
<pathList
.GetCount(); ++i
)
333 CTSVNPath path
= pathList
[i
];
338 hookiterator it
= find(key
);
343 path
= path
.GetContainingDirectory();
344 } while(!path
.IsEmpty());
346 // look for a script with a path as '*'
348 key
.path
= CTSVNPath(_T("*"));
349 hookiterator it
= find(key
);
358 DWORD
CHooks::RunScript(CString cmd
, LPCTSTR currentDir
, CString
& error
, bool bWait
, bool bShow
)
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
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
)
390 hRedir
= CreateFile(szErr
, GENERIC_READ
, FILE_SHARE_WRITE
, 0, OPEN_EXISTING
, 0, 0);
392 if (hRedir
== INVALID_HANDLE_VALUE
)
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
)
408 // setup startup info, set std out/err handles
411 SecureZeroMemory(&si
, sizeof(si
));
413 if (hOut
!= INVALID_HANDLE_VALUE
)
415 si
.dwFlags
= STARTF_USESTDHANDLES
| STARTF_USESHOWWINDOW
;
416 si
.hStdOutput
= hOut
;
418 si
.wShowWindow
= bShow
? SW_SHOW
: SW_HIDE
;
421 PROCESS_INFORMATION pi
;
422 SecureZeroMemory(&pi
, sizeof(pi
));
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
)
440 CloseHandle(pi
.hThread
);
442 // wait for process to finish, capture redirection and
443 // send it to the parent window/console
450 SecureZeroMemory(&buf
,sizeof(buf
));
451 while (ReadFile(hRedir
, &buf
, sizeof(buf
)-1, &dw
, NULL
))
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
))
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
);
476 DeleteFile(szOutput
);