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.
22 #include "StringUtils.h"
24 #include "SmartHandle.h"
26 CHooks
* CHooks::m_pInstance
;
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
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
53 while ((pos
= strhooks
.Find('\n')) >= 0)
56 key
.htype
= GetHookType(strhooks
.Mid(0, pos
));
57 if (pos
+1 < strhooks
.GetLength())
58 strhooks
= strhooks
.Mid(pos
+1);
61 bool bComplete
= false;
62 if ((pos
= strhooks
.Find('\n')) >= 0)
65 key
.path
= CTGitPath(strhooks
.Mid(0, pos
));
66 if (pos
+1 < strhooks
.GetLength())
67 strhooks
= strhooks
.Mid(pos
+1);
70 if ((pos
= strhooks
.Find('\n')) >= 0)
73 cmd
.commandline
= strhooks
.Mid(0, pos
);
74 if (pos
+1 < strhooks
.GetLength())
75 strhooks
= strhooks
.Mid(pos
+1);
78 if ((pos
= strhooks
.Find('\n')) >= 0)
81 cmd
.bWait
= (strhooks
.Mid(0, pos
).CompareNoCase(_T("true"))==0);
82 if (pos
+1 < strhooks
.GetLength())
83 strhooks
= strhooks
.Mid(pos
+1);
86 if ((pos
= strhooks
.Find('\n')) >= 0)
89 cmd
.bShow
= (strhooks
.Mid(0, pos
).CompareNoCase(_T("show"))==0);
90 if (pos
+1 < strhooks
.GetLength())
91 strhooks
= strhooks
.Mid(pos
+1);
101 m_pInstance
->insert(std::pair
<hookkey
, hookcmd
>(key
, cmd
));
107 CHooks
& CHooks::Instance()
112 void CHooks::Destroy()
120 for (hookiterator it
= begin(); it
!= end(); ++it
)
122 strhooks
+= GetHookTypeString(it
->first
.htype
);
124 strhooks
+= it
->first
.path
.GetWinPathString();
126 strhooks
+= it
->second
.commandline
;
128 strhooks
+= (it
->second
.bWait
? _T("true") : _T("false"));
130 strhooks
+= (it
->second
.bShow
? _T("show") : _T("hide"));
133 CRegString reghooks
= CRegString(_T("Software\\TortoiseGit\\hooks"));
135 if (reghooks
.GetLastError())
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
)
150 hookiterator it
= find(key
);
155 cmd
.commandline
= szCmd
;
158 insert(std::pair
<hookkey
, hookcmd
>(key
, cmd
));
161 CString
CHooks::GetHookTypeString(hooktype 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");
178 return _T("pre_push_hook");
180 return _T("post_push_hook");
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
;
207 void CHooks::AddParam(CString
& sCmd
, const CString
& param
)
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
)
229 sTemp
.Format(_T("%d"), depth
);
230 AddParam(sCmd
, sTemp
);
233 void CHooks::AddErrorParam(CString
& sCmd
, const CString
& error
)
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
)
244 tempPath
= CTempFiles::Instance().GetTempFilePath(true);
245 CStringUtils::WriteStringToTextFile(tempPath
.GetWinPath(), (LPCTSTR
)message
);
246 AddParam(sCmd
, tempPath
.GetWinPathString());
250 bool CHooks::StartCommit(const CTGitPathList
& pathList
, CString
& message
, DWORD
& exitcode
, CString
& error
)
252 hookiterator it
= FindItem(start_commit_hook
, pathList
);
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
);
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
);
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
);
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
);
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
);
297 bool CHooks::StartUpdate(const CTGitPathList
& pathList
, DWORD
& exitcode
, CString
& error
)
299 hookiterator it
= FindItem(start_update_hook
, pathList
);
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
);
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
);
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
);
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
);
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
);
338 bool CHooks::PrePush(const CTGitPathList
& pathList
,DWORD
& exitcode
, CString
& error
)
340 hookiterator it
= FindItem(pre_push_hook
, pathList
);
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
);
351 bool CHooks::PostPush(const CTGitPathList
& pathList
,DWORD
& exitcode
, CString
& error
)
353 hookiterator it
= FindItem(post_push_hook
, pathList
);
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
);
364 hookiterator
CHooks::FindItem(hooktype t
, const CTGitPathList
& pathList
)
367 for (int i
=0; i
<pathList
.GetCount(); ++i
)
369 CTGitPath path
= pathList
[i
];
374 hookiterator it
= find(key
);
379 path
= path
.GetContainingDirectory();
380 } while(!path
.IsEmpty());
382 // look for a script with a path as '*'
384 key
.path
= CTGitPath(_T("*"));
385 hookiterator it
= find(key
);
394 DWORD
CHooks::RunScript(CString cmd
, LPCTSTR currentDir
, CString
& error
, bool bWait
, bool bShow
)
397 SECURITY_ATTRIBUTES sa
;
398 SecureZeroMemory(&sa
, sizeof(sa
));
399 sa
.nLength
= sizeof(sa
);
400 sa
.bInheritHandle
= TRUE
;
406 // clear the error string
409 // Create Temp File for redirection
410 TCHAR szTempPath
[MAX_PATH
];
411 TCHAR szOutput
[MAX_PATH
];
412 TCHAR szErr
[MAX_PATH
];
413 GetTempPath(_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);
424 hRedir
= CreateFile(szErr
, GENERIC_READ
, FILE_SHARE_WRITE
, 0, OPEN_EXISTING
, 0, 0);
429 GetTempFileName(szTempPath
, _T("git"), 0, szOutput
);
430 hOut
= CreateFile(szOutput
, GENERIC_WRITE
, FILE_SHARE_READ
, &sa
, CREATE_ALWAYS
, FILE_ATTRIBUTE_TEMPORARY
, 0);
435 // setup startup info, set std out/err handles
438 SecureZeroMemory(&si
, sizeof(si
));
440 if (hOut
!= INVALID_HANDLE_VALUE
)
442 si
.dwFlags
= STARTF_USESTDHANDLES
| STARTF_USESHOWWINDOW
;
443 si
.hStdOutput
= hOut
;
445 si
.wShowWindow
= bShow
? SW_SHOW
: SW_HIDE
;
448 PROCESS_INFORMATION pi
;
449 SecureZeroMemory(&pi
, sizeof(pi
));
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
);
463 CloseHandle(pi
.hThread
);
465 // wait for process to finish, capture redirection and
466 // send it to the parent window/console
473 SecureZeroMemory(&buf
,sizeof(buf
));
474 while (ReadFile(hRedir
, &buf
, sizeof(buf
)-1, &dw
, NULL
))
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
))
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
);