Adjust the background icon size for high dpi monitors
[TortoiseGit.git] / src / Utils / Hooks.cpp
blob090b06b616a7aee5b8d1e7beff33419d8bb8ac36
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.
20 #include "stdafx.h"
21 #include "Hooks.h"
22 #include "registry.h"
23 #include "StringUtils.h"
24 #include "TempFile.h"
25 #include "SmartHandle.h"
26 #include "Git.h"
28 CHooks* CHooks::m_pInstance;
30 CHooks::CHooks()
34 CHooks::~CHooks()
38 bool CHooks::Create()
40 if (!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
46 // as four lines:
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
52 hookkey key;
53 int pos = 0;
54 hookcmd cmd;
55 while ((pos = strhooks.Find('\n')) >= 0)
57 // line 1
58 key.htype = GetHookType(strhooks.Mid(0, pos));
59 if (pos+1 < strhooks.GetLength())
60 strhooks = strhooks.Mid(pos+1);
61 else
62 strhooks.Empty();
63 bool bComplete = false;
64 if ((pos = strhooks.Find('\n')) >= 0)
66 // line 2
67 cmd.bEnabled = true;
68 if (strhooks[0] == L'!' && pos > 1)
70 cmd.bEnabled = false;
71 strhooks = strhooks.Mid(1);
72 --pos;
74 key.path = CTGitPath(strhooks.Mid(0, pos));
75 if (pos+1 < strhooks.GetLength())
76 strhooks = strhooks.Mid(pos+1);
77 else
78 strhooks.Empty();
79 if ((pos = strhooks.Find('\n')) >= 0)
81 // line 3
82 cmd.commandline = strhooks.Mid(0, pos);
83 if (pos+1 < strhooks.GetLength())
84 strhooks = strhooks.Mid(pos+1);
85 else
86 strhooks.Empty();
87 if ((pos = strhooks.Find('\n')) >= 0)
89 // line 4
90 cmd.bWait = (strhooks.Mid(0, pos).CompareNoCase(L"true") == 0);
91 if (pos+1 < strhooks.GetLength())
92 strhooks = strhooks.Mid(pos+1);
93 else
94 strhooks.Empty();
95 if ((pos = strhooks.Find('\n')) >= 0)
97 // line 5
98 cmd.bShow = (strhooks.Mid(0, pos).CompareNoCase(L"show") == 0);
99 if (pos+1 < strhooks.GetLength())
100 strhooks = strhooks.Mid(pos+1);
101 else
102 strhooks.Empty();
103 bComplete = true;
108 if (bComplete)
110 m_pInstance->insert(std::pair<hookkey, hookcmd>(key, cmd));
113 return true;
116 CHooks& CHooks::Instance()
118 return *m_pInstance;
121 void CHooks::Destroy()
123 delete m_pInstance;
126 bool CHooks::Save()
128 CString strhooks;
129 for (hookiterator it = begin(); it != end(); ++it)
131 strhooks += GetHookTypeString(it->first.htype);
132 strhooks += '\n';
133 if (!it->second.bEnabled)
134 strhooks += '!';
135 strhooks += it->first.path.GetWinPathString();
136 strhooks += '\n';
137 strhooks += it->second.commandline;
138 strhooks += '\n';
139 strhooks += (it->second.bWait ? L"true" : L"false");
140 strhooks += '\n';
141 strhooks += (it->second.bShow ? L"show" : L"hide");
142 strhooks += '\n';
144 CRegString reghooks(L"Software\\TortoiseGit\\hooks");
145 reghooks = strhooks;
146 if (reghooks.GetLastError())
147 return false;
148 return true;
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)
158 hookkey key;
159 key.htype = ht;
160 key.path = Path;
161 hookiterator it = find(key);
162 if (it!=end())
163 erase(it);
165 hookcmd cmd;
166 cmd.commandline = szCmd;
167 cmd.bWait = bWait;
168 cmd.bShow = bShow;
169 cmd.bEnabled = bEnabled;
170 insert(std::pair<hookkey, hookcmd>(key, cmd));
173 bool CHooks::SetEnabled(const hookkey& k, bool bEnabled)
175 auto it = find(k);
176 if (it == end())
177 return false;
178 if (it->second.bEnabled == bEnabled)
179 return false;
180 it->second.bEnabled = bEnabled;
181 return true;
184 CString CHooks::GetHookTypeString(hooktype t)
186 switch (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";
194 case pre_push_hook:
195 return L"pre_push_hook";
196 case post_push_hook:
197 return L"post_push_hook";
198 case pre_rebase_hook:
199 return L"pre_rebase_hook";
201 return L"";
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;
219 return unknown_hook;
222 void CHooks::AddParam(CString& sCmd, const CString& param)
224 sCmd += L" \"";
225 sCmd += param;
226 sCmd += L'"';
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)
243 CTGitPath tempPath;
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)
251 CTGitPath tempPath;
252 tempPath = CTempFiles::Instance().GetTempFilePath(true);
253 CStringUtils::WriteStringToTextFile(tempPath.GetWinPath(), (LPCTSTR)message);
254 AddParam(sCmd, tempPath.GetWinPathString());
255 return tempPath;
258 bool CHooks::StartCommit(const CString& workingTree, const CTGitPathList& pathList, CString& message, DWORD& exitcode, CString& error)
260 auto it = FindItem(start_commit_hook, workingTree);
261 if (it == end())
262 return false;
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);
272 return true;
275 bool CHooks::PreCommit(const CString& workingTree, const CTGitPathList& pathList, CString& message, DWORD& exitcode, CString& error)
277 auto it = FindItem(pre_commit_hook, workingTree);
278 if (it == end())
279 return false;
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);
287 return true;
290 bool CHooks::PostCommit(const CString& workingTree, bool amend, DWORD& exitcode, CString& error)
292 auto it = FindItem(post_commit_hook, workingTree);
293 if (it == end())
294 return false;
295 CString sCmd = it->second.commandline;
296 AddCWDParam(sCmd, workingTree);
297 if (amend)
298 AddParam(sCmd, L"true");
299 else
300 AddParam(sCmd, L"false");
301 exitcode = RunScript(sCmd, workingTree, error, it->second.bWait, it->second.bShow);
302 return true;
305 bool CHooks::PrePush(const CString& workingTree, DWORD& exitcode, CString& error)
307 auto it = FindItem(pre_push_hook, workingTree);
308 if (it == end())
309 return false;
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);
314 return true;
317 bool CHooks::PostPush(const CString& workingTree, DWORD& exitcode, CString& error)
319 auto it = FindItem(post_push_hook, workingTree);
320 if (it == end())
321 return false;
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);
326 return true;
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);
332 if (it == end())
333 return false;
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);
340 return true;
343 bool CHooks::IsHookPresent(hooktype t, const CString& workingTree) const
345 auto it = FindItem(t, workingTree);
346 return it != end();
349 const_hookiterator CHooks::FindItem(hooktype t, const CString& workingTree) const
351 hookkey key;
352 CTGitPath path = workingTree;
355 key.htype = t;
356 key.path = path;
357 auto it = find(key);
358 if (it != end() && it->second.bEnabled)
359 return it;
360 path = path.GetContainingDirectory();
361 } while(!path.IsEmpty());
362 // look for a script with a path as '*'
363 key.htype = t;
364 key.path = CTGitPath(L"*");
365 auto it = find(key);
366 if (it != end() && it->second.bEnabled)
368 return it;
371 return end();
374 DWORD CHooks::RunScript(CString cmd, LPCTSTR currentDir, CString& error, bool bWait, bool bShow)
376 DWORD exitcode = 0;
377 SECURITY_ATTRIBUTES sa = { 0 };
378 sa.nLength = sizeof(sa);
379 sa.bInheritHandle = TRUE;
381 CAutoFile hOut ;
382 CAutoFile hRedir;
383 CAutoFile hErr;
385 // clear the error string
386 error.Empty();
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);
400 if (!hErr)
402 error = CFormatMessageWrapper();
403 return (DWORD)-1;
406 hRedir = CreateFile(szErr, GENERIC_READ, FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
408 if (!hRedir)
410 error = CFormatMessageWrapper();
411 return (DWORD)-1;
414 GetTempFileName(szTempPath, L"git", 0, szOutput);
415 hOut = CreateFile(szOutput, GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, nullptr);
417 if (!hOut)
419 error = CFormatMessageWrapper();
420 return (DWORD)-1;
423 // setup startup info, set std out/err handles
424 // hide window
425 STARTUPINFO si = { 0 };
426 si.cb = sizeof(si);
427 si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
428 si.hStdOutput = hOut;
429 si.hStdError = hErr;
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);
437 SetLastError(err);
438 cmd.ReleaseBuffer();
439 return (DWORD)-1;
441 cmd.ReleaseBuffer();
443 CloseHandle(pi.hThread);
445 // wait for process to finish, capture redirection and
446 // send it to the parent window/console
447 if (bWait)
449 DWORD dw;
450 char buf[256] = { 0 };
453 while (ReadFile(hRedir, &buf, sizeof(buf) - 1, &dw, nullptr))
455 if (dw == 0)
456 break;
457 error += CString(CStringA(buf,dw));
459 Sleep(150);
460 } while (WaitForSingleObject(pi.hProcess, 0) != WAIT_OBJECT_0);
462 // perform any final flushing
463 while (ReadFile(hRedir, &buf, sizeof(buf) - 1, &dw, nullptr))
465 if (dw == 0)
466 break;
468 error += CString(CStringA(buf, dw));
470 WaitForSingleObject(pi.hProcess, INFINITE);
471 GetExitCodeProcess(pi.hProcess, &exitcode);
473 CloseHandle(pi.hProcess);
474 DeleteFile(szOutput);
475 DeleteFile(szErr);
477 return exitcode;