Reputation: 69
In my everyday work I use TortoiseGit and I'm trying to write a post-checkout hook. I prefer to work in Windows-environment so the only thing the hook-file does is call a standard windows .bat-file:
#!/bin/sh
echo "The Git post-checkout Linux Shell-file has now started to execute"
cmd.exe "/c post-checkout.bat"
echo "The Git post-checkout Linux Shell-file has now finished executing"
Inside my standard Windows .bat-file, I do the following:
@echo off
echo --------------------------------------------------------------------------
echo post-checkout.bat in repository root has now started to execute
START /b notepad.exe
echo post-checkout.bat in repository root has now finished executing
echo --------------------------------------------------------------------------
cmd /c exit 0
When I choose Switch/Checkout in TortoiseGit, my hook-file is successfully executed and Notepad starts. However, the strange thing is that the TortoiseGit Git Command Progress dialog hangs until I close Notepad. Please note that I can see "The Git post-checkout Linux Shell-file has now finished executing" in the TortoiseGit Git Command Progress dialog before I close Notepad. If I checkout using the C:\Program Files\Git\bin\bash.exe command window, then I get no hanging issue. Does anybody know how to solve this?
Edit: Putting the following directly in the Linux hook file (i.e. forgetting completely about the Window bat-file) produces the exact same result, the TortoiseGit Git Command Progress dialog hangs until I close Notepad:
#!/bin/sh
echo "The Git post-checkout Linux Shell-file has now started to execute"
notepad &
echo "The Git post-checkout Linux Shell-file has now finished executing"
Upvotes: 1
Views: 598
Reputation: 69
In the source code https://gitlab.com/tortoisegit/tortoisegit/-/blob/master/src/Utils/Hooks.cpp it can be seen that a hook-file is executed in a new process that is created using CreateProcess and then it waits for the process and all its children to finish by calling WaitForSingleObject (see e.g. this explanation How to start a process and make it 'independent'). So the behavior is very much expected. One solution can be found here https://stackoverflow.com/a/1569941/2075678 which refers to this webpage https://svn.haxx.se/users/archive-2008-11/0301.shtml. I rewrote it slightly:
#include "stdafx.h"
#include <windows.h>
int getFirstIndexOfChar(WCHAR* stringToInvestigate, int startIndex, WCHAR charToLookFor);
int getLastIndexOfChar(WCHAR* stringToInvestigate, int startIndex, WCHAR charToLookFor);
int _tmain(int argc, _TCHAR* argv[])
{
WCHAR* pOriginalCmd = ::GetCommandLine();
// Test code (modify paths to RunDetached.exe and MyFile.txt appropriately)
// pOriginalCmd = _T("\"D:\\My Visual Studio Projects\\RunDetached\\debug\\RunDetached.exe\" \"C:\\Windows\\System32\\notepad.exe\" \"D:\\1.txt\"");
int CmdLen = (int)wcslen(pOriginalCmd);
// Determine where certain characters are located (excl means the particular index is not included, e.g.
// if indexExcl is 5 then index 4 is the last included index).
int beginningOf1stArg = getFirstIndexOfChar(pOriginalCmd, 0, L'\"');
int endOf1stArgExcl = getFirstIndexOfChar(pOriginalCmd, beginningOf1stArg + 1, L'\"') + 1;
int beginningOf2ndArg = getFirstIndexOfChar(pOriginalCmd, endOf1stArgExcl + 1, L'\"');
int endOf2ndArgExcl = getFirstIndexOfChar(pOriginalCmd, beginningOf2ndArg + 1, L'\"') + 1;
int beginningOf3rdArg = getFirstIndexOfChar(pOriginalCmd, endOf2ndArgExcl + 1, L'\"');
int endOfLastArgExcl = getLastIndexOfChar (pOriginalCmd, CmdLen - 1, L'\"') + 1;
int beginningOfFileName = getLastIndexOfChar (pOriginalCmd, endOf2ndArgExcl - 2, L'\\') + 1;
int endOfFileNameExcl = endOf2ndArgExcl - 1;
if ((beginningOf1stArg < 0) || (endOf1stArgExcl < 0) || (beginningOf2ndArg < 0) || (endOf2ndArgExcl < 0) ||
(endOfLastArgExcl < 0) || (beginningOfFileName < 0) || (endOfFileNameExcl < 0))
{
return -1;
}
// Determine the application to execute including full path. E.g. for notepad this should be:
// C:\Windows\System32\notepad.exe (without any double-quotes)
int lengthOfApplicationNameAndPathInChars = (endOf2ndArgExcl -1) - (beginningOf2ndArg + 1); // Skip double-quotes
WCHAR* lpApplicationNameAndPath = (WCHAR*)malloc(sizeof(WCHAR) * (lengthOfApplicationNameAndPathInChars + 1));
memcpy(lpApplicationNameAndPath, &pOriginalCmd[beginningOf2ndArg + 1], sizeof(WCHAR) * (lengthOfApplicationNameAndPathInChars));
lpApplicationNameAndPath[lengthOfApplicationNameAndPathInChars] = (WCHAR)0; // Null terminate
// Determine the command argument. Must be in modifyable memory and should start with the
// application name without the path. E.g. for notepad with command argument D:\MyFile.txt:
// "notepad.exe" "D:\MyFile.txt" (with the double-quotes).
WCHAR* modifiedCmd = NULL;
if (0 < beginningOf3rdArg)
{
int lengthOfApplicationNameInChars = endOfFileNameExcl - beginningOfFileName; // Application name without path
int lengthOfRestOfCmdInChars = CmdLen - beginningOf3rdArg;
int neededCmdLengthInChars = 1 + lengthOfApplicationNameInChars + 2 + lengthOfRestOfCmdInChars; // Two double-quotes and one space extra
modifiedCmd = (WCHAR*)malloc(sizeof(WCHAR) * (neededCmdLengthInChars + 1)); // Extra char is null-terminator
modifiedCmd[0] = L'\"'; // Start with double-quoute
memcpy(&modifiedCmd[1], &pOriginalCmd[beginningOfFileName], sizeof(WCHAR) * (lengthOfApplicationNameInChars));
modifiedCmd[1 + (lengthOfApplicationNameInChars)] = L'\"';
modifiedCmd[1 + (lengthOfApplicationNameInChars) + 1] = L' ';
memcpy(&modifiedCmd[1 + (lengthOfApplicationNameInChars) + 2], &pOriginalCmd[beginningOf3rdArg], sizeof(WCHAR) * lengthOfRestOfCmdInChars);
modifiedCmd[neededCmdLengthInChars] = (WCHAR)0;
}
STARTUPINFO si;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
PROCESS_INFORMATION pi;
ZeroMemory( &pi, sizeof(pi) );
BOOL result = CreateProcess // Start the detached process.
(
lpApplicationNameAndPath, // Module name and full path
modifiedCmd, // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set bInheritHandles to FALSE
DETACHED_PROCESS, // Detach process
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi // Pointer to PROCESS_INFORMATION structure (returned)
);
free(lpApplicationNameAndPath);
if (modifiedCmd != NULL)
{
free(modifiedCmd);
}
if (result) return 0;
wchar_t msg[2048];
FormatMessage
(
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
::GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_SYS_DEFAULT),
msg, sizeof(msg),
NULL
);
fputws(msg, stderr);
_flushall();
return -1;
}
int getFirstIndexOfChar(WCHAR* stringToInvestigate, int startIndex, WCHAR charToLookFor)
{
int stringLen = (int)wcslen(stringToInvestigate);
if (5000 < stringLen) // Sanity check
{
return -1;
}
for (int i = startIndex; i < stringLen; i++)
{
if (stringToInvestigate[i] == charToLookFor)
{
return i;
}
}
return -1;
}
int getLastIndexOfChar(WCHAR* stringToInvestigate, int startIndex, WCHAR charToLookFor)
{
int stringLen = (int)wcslen(stringToInvestigate);
if (5000 < stringLen) // Sanity check
{
return -1;
}
for (int i = min(stringLen - 1, startIndex); 0 <= i; i--)
{
if (stringToInvestigate[i] == charToLookFor)
{
return i;
}
}
return -1;
}
After I had compiled the above, I modified my .bat-file and now it works great:
@echo off
echo --------------------------------------------------------------------------
echo post-checkout.bat in repository root has now started to execute
RunDetached notepad
echo post-checkout.bat in repository root has now finished executing
echo --------------------------------------------------------------------------
exit 0
Upvotes: 0