1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2011-2014 - TortoiseGit
4 // Copyright (C) 2007-2008 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include "StringUtils.h"
25 #include "SmartHandle.h"
27 CHooks
* CHooks::m_pInstance
;
39 if (m_pInstance
== NULL
)
40 m_pInstance
= new CHooks();
41 CRegString
reghooks(_T("Software\\TortoiseGit\\hooks"));
42 CString strhooks
= reghooks
;
43 // now fill the map with all the hooks defined in the string
44 // the string consists of multiple lines, where one hook script is defined
46 // line 1: the hook type
47 // line 2: path to working copy where to apply the hook script
48 // line 3: command line to execute
49 // line 4: 'true' or 'false' for waiting for the script to finish
50 // line 5: 'show' or 'hide' on how to start the hook script
54 while ((pos
= strhooks
.Find('\n')) >= 0)
57 key
.htype
= GetHookType(strhooks
.Mid(0, pos
));
58 if (pos
+1 < strhooks
.GetLength())
59 strhooks
= strhooks
.Mid(pos
+1);
62 bool bComplete
= false;
63 if ((pos
= strhooks
.Find('\n')) >= 0)
66 key
.path
= CTGitPath(strhooks
.Mid(0, pos
));
67 if (pos
+1 < strhooks
.GetLength())
68 strhooks
= strhooks
.Mid(pos
+1);
71 if ((pos
= strhooks
.Find('\n')) >= 0)
74 cmd
.commandline
= strhooks
.Mid(0, pos
);
75 if (pos
+1 < strhooks
.GetLength())
76 strhooks
= strhooks
.Mid(pos
+1);
79 if ((pos
= strhooks
.Find('\n')) >= 0)
82 cmd
.bWait
= (strhooks
.Mid(0, pos
).CompareNoCase(_T("true"))==0);
83 if (pos
+1 < strhooks
.GetLength())
84 strhooks
= strhooks
.Mid(pos
+1);
87 if ((pos
= strhooks
.Find('\n')) >= 0)
90 cmd
.bShow
= (strhooks
.Mid(0, pos
).CompareNoCase(_T("show"))==0);
91 if (pos
+1 < strhooks
.GetLength())
92 strhooks
= strhooks
.Mid(pos
+1);
102 m_pInstance
->insert(std::pair
<hookkey
, hookcmd
>(key
, cmd
));
108 CHooks
& CHooks::Instance()
113 void CHooks::Destroy()
121 for (hookiterator it
= begin(); it
!= end(); ++it
)
123 strhooks
+= GetHookTypeString(it
->first
.htype
);
125 strhooks
+= it
->first
.path
.GetWinPathString();
127 strhooks
+= it
->second
.commandline
;
129 strhooks
+= (it
->second
.bWait
? _T("true") : _T("false"));
131 strhooks
+= (it
->second
.bShow
? _T("show") : _T("hide"));
134 CRegString
reghooks(_T("Software\\TortoiseGit\\hooks"));
136 if (reghooks
.GetLastError())
141 bool CHooks::Remove(const hookkey
&key
)
143 return (erase(key
) > 0);
146 void CHooks::Add(hooktype ht
, const CTGitPath
& Path
, LPCTSTR szCmd
, bool bWait
, bool bShow
)
151 hookiterator it
= find(key
);
156 cmd
.commandline
= szCmd
;
159 insert(std::pair
<hookkey
, hookcmd
>(key
, cmd
));
162 CString
CHooks::GetHookTypeString(hooktype t
)
166 case start_commit_hook
:
167 return _T("start_commit_hook");
168 case pre_commit_hook
:
169 return _T("pre_commit_hook");
170 case post_commit_hook
:
171 return _T("post_commit_hook");
173 return _T("pre_push_hook");
175 return _T("post_push_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("pre_push_hook"))==0)
189 return pre_push_hook
;
190 if (s
.Compare(_T("post_push_hook"))==0)
191 return post_push_hook
;
196 void CHooks::AddParam(CString
& sCmd
, const CString
& param
)
203 void CHooks::AddPathParam(CString
& sCmd
, const CTGitPathList
& pathList
)
205 CTGitPath temppath
= CTempFiles::Instance().GetTempFilePath(true);
206 pathList
.WriteToFile(temppath
.GetWinPathString(), true);
207 AddParam(sCmd
, temppath
.GetWinPathString());
210 void CHooks::AddCWDParam(CString
& sCmd
, const CString
& workingTree
)
212 AddParam(sCmd
, workingTree
);
215 void CHooks::AddErrorParam(CString
& sCmd
, const CString
& error
)
218 tempPath
= CTempFiles::Instance().GetTempFilePath(true);
219 CStringUtils::WriteStringToTextFile(tempPath
.GetWinPath(), (LPCTSTR
)error
);
220 AddParam(sCmd
, tempPath
.GetWinPathString());
223 CTGitPath
CHooks::AddMessageFileParam(CString
& sCmd
, const CString
& message
)
226 tempPath
= CTempFiles::Instance().GetTempFilePath(true);
227 CStringUtils::WriteStringToTextFile(tempPath
.GetWinPath(), (LPCTSTR
)message
);
228 AddParam(sCmd
, tempPath
.GetWinPathString());
232 bool CHooks::StartCommit(const CString
& workingTree
, const CTGitPathList
& pathList
, CString
& message
, DWORD
& exitcode
, CString
& error
)
234 auto it
= FindItem(start_commit_hook
, workingTree
);
237 CString sCmd
= it
->second
.commandline
;
238 AddPathParam(sCmd
, pathList
);
239 CTGitPath temppath
= AddMessageFileParam(sCmd
, message
);
240 AddCWDParam(sCmd
, workingTree
);
241 exitcode
= RunScript(sCmd
, workingTree
, error
, it
->second
.bWait
, it
->second
.bShow
);
242 if (!exitcode
&& !temppath
.IsEmpty())
244 CStringUtils::ReadStringFromTextFile(temppath
.GetWinPathString(), message
);
249 bool CHooks::PreCommit(const CString
& workingTree
, const CTGitPathList
& pathList
, CString
& message
, DWORD
& exitcode
, CString
& error
)
251 auto it
= FindItem(pre_commit_hook
, workingTree
);
254 CString sCmd
= it
->second
.commandline
;
255 AddPathParam(sCmd
, pathList
);
256 CTGitPath temppath
= AddMessageFileParam(sCmd
, message
);
257 AddCWDParam(sCmd
, workingTree
);
258 exitcode
= RunScript(sCmd
, workingTree
, error
, it
->second
.bWait
, it
->second
.bShow
);
259 if (!exitcode
&& !temppath
.IsEmpty())
260 CStringUtils::ReadStringFromTextFile(temppath
.GetWinPathString(), message
);
264 bool CHooks::PostCommit(const CString
& workingTree
, const CTGitPathList
& pathList
, const GitRev
& rev
, const CString
& message
, DWORD
& exitcode
, CString
& error
)
266 auto it
= FindItem(post_commit_hook
, workingTree
);
269 CString sCmd
= it
->second
.commandline
;
270 AddPathParam(sCmd
, pathList
);
271 AddMessageFileParam(sCmd
, message
);
272 AddParam(sCmd
, rev
.m_CommitHash
.ToString());
273 AddErrorParam(sCmd
, error
);
274 AddCWDParam(sCmd
, workingTree
);
275 exitcode
= RunScript(sCmd
, workingTree
, error
, it
->second
.bWait
, it
->second
.bShow
);
279 bool CHooks::PrePush(const CString
& workingTree
, DWORD
& exitcode
, CString
& error
)
281 auto it
= FindItem(pre_push_hook
, workingTree
);
284 CString sCmd
= it
->second
.commandline
;
285 AddPathParam(sCmd
, CTGitPathList(workingTree
));
286 AddErrorParam(sCmd
, error
);
287 AddCWDParam(sCmd
, workingTree
);
288 exitcode
= RunScript(sCmd
, workingTree
, error
, it
->second
.bWait
, it
->second
.bShow
);
292 bool CHooks::PostPush(const CString
& workingTree
, DWORD
& exitcode
, CString
& error
)
294 auto it
= FindItem(post_push_hook
, workingTree
);
297 CString sCmd
= it
->second
.commandline
;
298 AddPathParam(sCmd
, CTGitPathList(workingTree
));
299 AddErrorParam(sCmd
, error
);
300 AddCWDParam(sCmd
, workingTree
);
301 exitcode
= RunScript(sCmd
, workingTree
, error
, it
->second
.bWait
, it
->second
.bShow
);
305 bool CHooks::IsHookPresent(hooktype t
, const CString
& workingTree
) const
307 auto it
= FindItem(t
, workingTree
);
311 const_hookiterator
CHooks::FindItem(hooktype t
, const CString
& workingTree
) const
314 CTGitPath path
= workingTree
;
324 path
= path
.GetContainingDirectory();
325 } while(!path
.IsEmpty());
326 // look for a script with a path as '*'
328 key
.path
= CTGitPath(_T("*"));
338 DWORD
CHooks::RunScript(CString cmd
, LPCTSTR currentDir
, CString
& error
, bool bWait
, bool bShow
)
341 SECURITY_ATTRIBUTES sa
;
342 SecureZeroMemory(&sa
, sizeof(sa
));
343 sa
.nLength
= sizeof(sa
);
344 sa
.bInheritHandle
= TRUE
;
350 // clear the error string
353 // Create Temp File for redirection
354 TCHAR szTempPath
[MAX_PATH
] = {0};
355 TCHAR szOutput
[MAX_PATH
] = {0};
356 TCHAR szErr
[MAX_PATH
] = {0};
357 GetTortoiseGitTempPath(_countof(szTempPath
), szTempPath
);
358 GetTempFileName(szTempPath
, _T("git"), 0, szErr
);
360 // setup redirection handles
361 // output handle must be WRITE mode, share READ
362 // redirect handle must be READ mode, share WRITE
363 hErr
= CreateFile(szErr
, GENERIC_WRITE
, FILE_SHARE_READ
, &sa
, CREATE_ALWAYS
, FILE_ATTRIBUTE_TEMPORARY
, 0);
367 error
= CFormatMessageWrapper();
371 hRedir
= CreateFile(szErr
, GENERIC_READ
, FILE_SHARE_WRITE
, 0, OPEN_EXISTING
, 0, 0);
375 error
= CFormatMessageWrapper();
379 GetTempFileName(szTempPath
, _T("git"), 0, szOutput
);
380 hOut
= CreateFile(szOutput
, GENERIC_WRITE
, FILE_SHARE_READ
, &sa
, CREATE_ALWAYS
, FILE_ATTRIBUTE_TEMPORARY
, 0);
384 error
= CFormatMessageWrapper();
388 // setup startup info, set std out/err handles
391 SecureZeroMemory(&si
, sizeof(si
));
393 si
.dwFlags
= STARTF_USESTDHANDLES
| STARTF_USESHOWWINDOW
;
394 si
.hStdOutput
= hOut
;
396 si
.wShowWindow
= bShow
? SW_SHOW
: SW_HIDE
;
398 PROCESS_INFORMATION pi
;
399 SecureZeroMemory(&pi
, sizeof(pi
));
401 if (!CreateProcess(nullptr, cmd
.GetBuffer(), nullptr, nullptr, TRUE
, 0, nullptr, currentDir
, &si
, &pi
))
403 const DWORD err
= GetLastError(); // preserve the CreateProcess error
404 error
= CFormatMessageWrapper(err
);
411 CloseHandle(pi
.hThread
);
413 // wait for process to finish, capture redirection and
414 // send it to the parent window/console
418 char buf
[256] = { 0 };
421 while (ReadFile(hRedir
, &buf
, sizeof(buf
)-1, &dw
, NULL
))
425 error
+= CString(CStringA(buf
,dw
));
428 } while (WaitForSingleObject(pi
.hProcess
, 0) != WAIT_OBJECT_0
);
430 // perform any final flushing
431 while (ReadFile(hRedir
, &buf
, sizeof(buf
)-1, &dw
, NULL
))
436 error
+= CString(CStringA(buf
, dw
));
438 WaitForSingleObject(pi
.hProcess
, INFINITE
);
439 GetExitCodeProcess(pi
.hProcess
, &exitcode
);
441 CloseHandle(pi
.hProcess
);
442 DeleteFile(szOutput
);