1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2011-2016 - 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"
28 CHooks
* CHooks::m_pInstance
;
41 m_pInstance
= new CHooks();
42 CRegString
reghooks(L
"Software\\TortoiseGit\\hooks");
43 CString strhooks
= reghooks
;
44 // now fill the map with all the hooks defined in the string
45 // the string consists of multiple lines, where one hook script is defined
47 // line 1: the hook type
48 // line 2: path to working copy where to apply the hook script, if it starts with "!" this hook is disabled (this should provide backward and forward compatibility)
49 // line 3: command line to execute
50 // line 4: 'true' or 'false' for waiting for the script to finish
51 // line 5: 'show' or 'hide' on how to start the hook script
55 while ((pos
= strhooks
.Find('\n')) >= 0)
58 key
.htype
= GetHookType(strhooks
.Mid(0, pos
));
59 if (pos
+1 < strhooks
.GetLength())
60 strhooks
= strhooks
.Mid(pos
+1);
63 bool bComplete
= false;
64 if ((pos
= strhooks
.Find('\n')) >= 0)
68 if (strhooks
[0] == L
'!' && pos
> 1)
71 strhooks
= strhooks
.Mid(1);
74 key
.path
= CTGitPath(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
.commandline
= strhooks
.Mid(0, pos
);
83 if (pos
+1 < strhooks
.GetLength())
84 strhooks
= strhooks
.Mid(pos
+1);
87 if ((pos
= strhooks
.Find('\n')) >= 0)
90 cmd
.bWait
= (strhooks
.Mid(0, pos
).CompareNoCase(L
"true") == 0);
91 if (pos
+1 < strhooks
.GetLength())
92 strhooks
= strhooks
.Mid(pos
+1);
95 if ((pos
= strhooks
.Find('\n')) >= 0)
98 cmd
.bShow
= (strhooks
.Mid(0, pos
).CompareNoCase(L
"show") == 0);
99 if (pos
+1 < strhooks
.GetLength())
100 strhooks
= strhooks
.Mid(pos
+1);
110 m_pInstance
->insert(std::pair
<hookkey
, hookcmd
>(key
, cmd
));
116 CHooks
& CHooks::Instance()
121 void CHooks::Destroy()
129 for (hookiterator it
= begin(); it
!= end(); ++it
)
131 strhooks
+= GetHookTypeString(it
->first
.htype
);
133 if (!it
->second
.bEnabled
)
135 strhooks
+= it
->first
.path
.GetWinPathString();
137 strhooks
+= it
->second
.commandline
;
139 strhooks
+= (it
->second
.bWait
? L
"true" : L
"false");
141 strhooks
+= (it
->second
.bShow
? L
"show" : L
"hide");
144 CRegString
reghooks(L
"Software\\TortoiseGit\\hooks");
146 if (reghooks
.GetLastError())
151 bool CHooks::Remove(const hookkey
&key
)
153 return (erase(key
) > 0);
156 void CHooks::Add(hooktype ht
, const CTGitPath
& Path
, LPCTSTR szCmd
, bool bWait
, bool bShow
, bool bEnabled
)
161 hookiterator it
= find(key
);
166 cmd
.commandline
= szCmd
;
169 cmd
.bEnabled
= bEnabled
;
170 insert(std::pair
<hookkey
, hookcmd
>(key
, cmd
));
173 bool CHooks::SetEnabled(const hookkey
& k
, bool bEnabled
)
178 if (it
->second
.bEnabled
== bEnabled
)
180 it
->second
.bEnabled
= bEnabled
;
184 CString
CHooks::GetHookTypeString(hooktype t
)
188 case start_commit_hook
:
189 return L
"start_commit_hook";
190 case pre_commit_hook
:
191 return L
"pre_commit_hook";
192 case post_commit_hook
:
193 return L
"post_commit_hook";
195 return L
"pre_push_hook";
197 return L
"post_push_hook";
198 case pre_rebase_hook
:
199 return L
"pre_rebase_hook";
204 hooktype
CHooks::GetHookType(const CString
& s
)
206 if (s
.Compare(L
"start_commit_hook") == 0)
207 return start_commit_hook
;
208 if (s
.Compare(L
"pre_commit_hook") == 0)
209 return pre_commit_hook
;
210 if (s
.Compare(L
"post_commit_hook") == 0)
211 return post_commit_hook
;
212 if (s
.Compare(L
"pre_push_hook") == 0)
213 return pre_push_hook
;
214 if (s
.Compare(L
"post_push_hook") == 0)
215 return post_push_hook
;
216 if (s
.Compare(L
"pre_rebase_hook") == 0)
217 return pre_rebase_hook
;
222 void CHooks::AddParam(CString
& sCmd
, const CString
& param
)
229 void CHooks::AddPathParam(CString
& sCmd
, const CTGitPathList
& pathList
)
231 CTGitPath temppath
= CTempFiles::Instance().GetTempFilePath(true);
232 pathList
.WriteToFile(temppath
.GetWinPathString(), true);
233 AddParam(sCmd
, temppath
.GetWinPathString());
236 void CHooks::AddCWDParam(CString
& sCmd
, const CString
& workingTree
)
238 AddParam(sCmd
, workingTree
);
241 void CHooks::AddErrorParam(CString
& sCmd
, const CString
& error
)
244 tempPath
= CTempFiles::Instance().GetTempFilePath(true);
245 CStringUtils::WriteStringToTextFile(tempPath
.GetWinPath(), (LPCTSTR
)error
);
246 AddParam(sCmd
, tempPath
.GetWinPathString());
249 CTGitPath
CHooks::AddMessageFileParam(CString
& sCmd
, const CString
& message
)
252 tempPath
= CTempFiles::Instance().GetTempFilePath(true);
253 CStringUtils::WriteStringToTextFile(tempPath
.GetWinPath(), (LPCTSTR
)message
);
254 AddParam(sCmd
, tempPath
.GetWinPathString());
258 bool CHooks::StartCommit(const CString
& workingTree
, const CTGitPathList
& pathList
, CString
& message
, DWORD
& exitcode
, CString
& error
)
260 auto it
= FindItem(start_commit_hook
, workingTree
);
263 CString sCmd
= it
->second
.commandline
;
264 AddPathParam(sCmd
, pathList
);
265 CTGitPath temppath
= AddMessageFileParam(sCmd
, message
);
266 AddCWDParam(sCmd
, workingTree
);
267 exitcode
= RunScript(sCmd
, workingTree
, error
, it
->second
.bWait
, it
->second
.bShow
);
268 if (!exitcode
&& !temppath
.IsEmpty())
270 CStringUtils::ReadStringFromTextFile(temppath
.GetWinPathString(), message
);
275 bool CHooks::PreCommit(const CString
& workingTree
, const CTGitPathList
& pathList
, CString
& message
, DWORD
& exitcode
, CString
& error
)
277 auto it
= FindItem(pre_commit_hook
, workingTree
);
280 CString sCmd
= it
->second
.commandline
;
281 AddPathParam(sCmd
, pathList
);
282 CTGitPath temppath
= AddMessageFileParam(sCmd
, message
);
283 AddCWDParam(sCmd
, workingTree
);
284 exitcode
= RunScript(sCmd
, workingTree
, error
, it
->second
.bWait
, it
->second
.bShow
);
285 if (!exitcode
&& !temppath
.IsEmpty())
286 CStringUtils::ReadStringFromTextFile(temppath
.GetWinPathString(), message
);
290 bool CHooks::PostCommit(const CString
& workingTree
, bool amend
, DWORD
& exitcode
, CString
& error
)
292 auto it
= FindItem(post_commit_hook
, workingTree
);
295 CString sCmd
= it
->second
.commandline
;
296 AddCWDParam(sCmd
, workingTree
);
298 AddParam(sCmd
, L
"true");
300 AddParam(sCmd
, L
"false");
301 exitcode
= RunScript(sCmd
, workingTree
, error
, it
->second
.bWait
, it
->second
.bShow
);
305 bool CHooks::PrePush(const CString
& workingTree
, DWORD
& exitcode
, CString
& error
)
307 auto it
= FindItem(pre_push_hook
, workingTree
);
310 CString sCmd
= it
->second
.commandline
;
311 AddErrorParam(sCmd
, error
);
312 AddCWDParam(sCmd
, workingTree
);
313 exitcode
= RunScript(sCmd
, workingTree
, error
, it
->second
.bWait
, it
->second
.bShow
);
317 bool CHooks::PostPush(const CString
& workingTree
, DWORD
& exitcode
, CString
& error
)
319 auto it
= FindItem(post_push_hook
, workingTree
);
322 CString sCmd
= it
->second
.commandline
;
323 AddErrorParam(sCmd
, error
);
324 AddCWDParam(sCmd
, workingTree
);
325 exitcode
= RunScript(sCmd
, workingTree
, error
, it
->second
.bWait
, it
->second
.bShow
);
329 bool CHooks::PreRebase(const CString
& workingTree
, const CString
& upstream
, const CString
& rebasedBranch
, DWORD
& exitcode
, CString
& error
)
331 auto it
= FindItem(pre_rebase_hook
, workingTree
);
334 CString sCmd
= it
->second
.commandline
;
335 AddParam(sCmd
, upstream
);
336 AddParam(sCmd
, rebasedBranch
);
337 AddErrorParam(sCmd
, error
);
338 AddCWDParam(sCmd
, workingTree
);
339 exitcode
= RunScript(sCmd
, workingTree
, error
, it
->second
.bWait
, it
->second
.bShow
);
343 bool CHooks::IsHookPresent(hooktype t
, const CString
& workingTree
) const
345 auto it
= FindItem(t
, workingTree
);
349 const_hookiterator
CHooks::FindItem(hooktype t
, const CString
& workingTree
) const
352 CTGitPath path
= workingTree
;
358 if (it
!= end() && it
->second
.bEnabled
)
360 path
= path
.GetContainingDirectory();
361 } while(!path
.IsEmpty());
362 // look for a script with a path as '*'
364 key
.path
= CTGitPath(L
"*");
366 if (it
!= end() && it
->second
.bEnabled
)
374 DWORD
CHooks::RunScript(CString cmd
, LPCTSTR currentDir
, CString
& error
, bool bWait
, bool bShow
)
377 SECURITY_ATTRIBUTES sa
= { 0 };
378 sa
.nLength
= sizeof(sa
);
379 sa
.bInheritHandle
= TRUE
;
385 // clear the error string
388 // Create Temp File for redirection
389 TCHAR szTempPath
[MAX_PATH
] = {0};
390 TCHAR szOutput
[MAX_PATH
] = {0};
391 TCHAR szErr
[MAX_PATH
] = {0};
392 GetTortoiseGitTempPath(_countof(szTempPath
), szTempPath
);
393 GetTempFileName(szTempPath
, L
"git", 0, szErr
);
395 // setup redirection handles
396 // output handle must be WRITE mode, share READ
397 // redirect handle must be READ mode, share WRITE
398 hErr
= CreateFile(szErr
, GENERIC_WRITE
, FILE_SHARE_READ
, &sa
, CREATE_ALWAYS
, FILE_ATTRIBUTE_TEMPORARY
, nullptr);
402 error
= CFormatMessageWrapper();
406 hRedir
= CreateFile(szErr
, GENERIC_READ
, FILE_SHARE_WRITE
, nullptr, OPEN_EXISTING
, 0, nullptr);
410 error
= CFormatMessageWrapper();
414 GetTempFileName(szTempPath
, L
"git", 0, szOutput
);
415 hOut
= CreateFile(szOutput
, GENERIC_WRITE
, FILE_SHARE_READ
, &sa
, CREATE_ALWAYS
, FILE_ATTRIBUTE_TEMPORARY
, nullptr);
419 error
= CFormatMessageWrapper();
423 // setup startup info, set std out/err handles
425 STARTUPINFO si
= { 0 };
427 si
.dwFlags
= STARTF_USESTDHANDLES
| STARTF_USESHOWWINDOW
;
428 si
.hStdOutput
= hOut
;
430 si
.wShowWindow
= bShow
? SW_SHOW
: SW_HIDE
;
432 PROCESS_INFORMATION pi
= { 0 };
433 if (!CreateProcess(nullptr, cmd
.GetBuffer(), nullptr, nullptr, TRUE
, CREATE_UNICODE_ENVIRONMENT
, nullptr, currentDir
, &si
, &pi
))
435 const DWORD err
= GetLastError(); // preserve the CreateProcess error
436 error
= CFormatMessageWrapper(err
);
443 CloseHandle(pi
.hThread
);
445 // wait for process to finish, capture redirection and
446 // send it to the parent window/console
450 char buf
[256] = { 0 };
453 while (ReadFile(hRedir
, &buf
, sizeof(buf
) - 1, &dw
, nullptr))
457 error
+= CString(CStringA(buf
,dw
));
460 } while (WaitForSingleObject(pi
.hProcess
, 0) != WAIT_OBJECT_0
);
462 // perform any final flushing
463 while (ReadFile(hRedir
, &buf
, sizeof(buf
) - 1, &dw
, nullptr))
468 error
+= CString(CStringA(buf
, dw
));
470 WaitForSingleObject(pi
.hProcess
, INFINITE
);
471 GetExitCodeProcess(pi
.hProcess
, &exitcode
);
473 CloseHandle(pi
.hProcess
);
474 DeleteFile(szOutput
);