【问题标题】:Why does TortoiseGit Git Command Progress dialog hang after post-checkout hook starts Notepad?为什么在结帐后挂钩启动记事本后 TortoiseGit Git 命令进度对话框挂起?
【发布时间】:2021-05-12 09:48:58
【问题描述】:

在我的日常工作中,我使用 TortoiseGit,并且我正在尝试编写一个 post-checkout hook。我更喜欢在 Windows 环境中工作,所以挂钩文件所做的唯一事情就是调用标准的 Windows .bat 文件:

#!/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"

在我的标准 Windows .bat 文件中,我执行以下操作:

@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

当我在 TortoiseGit 中选择 Switch/Checkout 时,我的钩子文件成功执行并且记事本启动。然而,奇怪的是 TortoiseGit Git 命令进度对话框一直挂起,直到我关闭记事本。请注意,在我关闭记事本之前,我可以在 TortoiseGit Git 命令进度对话框中看到“Git 签出后 Linux Shell 文件现已完成执行”。如果我使用 C:\Program Files\Git\bin\bash.exe 命令窗口结帐,则不会出现挂起问题。有人知道怎么解决吗?

编辑:将以下内容直接放入 Linux 挂钩文件(即完全忘记 Window bat 文件)会产生完全相同的结果,TortoiseGit Git 命令进度对话框会挂起,直到我关闭记事本:

#!/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"

【问题讨论】:

  • 你为什么使用/b?如果不这样做会怎样?
  • 我是否包含 /b 没有区别。根据ss64.com/nt/start.html /B 执行以下操作,我认为这是不必要的(记事本有自己的窗口,在我的实际应用程序中,我不需要窗口,因为它是控制台应用程序):“在不创建新窗口的情况下启动应用程序。在这种情况下 Ctrl-C 将被忽略 - 让 Ctrl-Break 作为中断应用程序的唯一方法。"
  • 你要么想打开notepad.exe,要么不想打开,记事本,没有窗口,真的没用吗?如果您不想打开 notepad.exe,并且您只向我们提供了一个有问题的命令,那就是打开 notepad.exe,那么您并没有真正接受为我们提供足够的信息来重现和复制的想法。帮助你!
  • 在我的实际问题中,我正在尝试执行我自己编写的控制台应用程序,但为了简化问题描述,我将其更改为记事本。无论我尝试执行我的控制台应用程序还是记事本,我都会遇到完全相同的问题,但记事本在编程世界中更为人所知。无论我是否包含 /b(或 /B),记事本都会完全可见地启动。
  • 所以你对一个我们从未听说过、见过、使用过、可以反编译或阅读其源代码的控制台应用程序有疑问,并且你认为它是一个名为记事本的 GUI 应用程序.exe,是获得所需帮助的明显方式!您可能需要提供更多信息,因为在批处理文件中,start notepad.exe 将打开记事本,并继续执行脚本,它不会等待 notepad.exe 关闭就继续。

标签: batch-file tortoisegit post-checkout-hook


【解决方案1】:

在源代码https://gitlab.com/tortoisegit/tortoisegit/-/blob/master/src/Utils/Hooks.cpp 中可以看出,挂钩文件在使用 CreateProcess 创建的新进程中执行,然后它通过调用 WaitForSingleObject 等待进程及其所有子进程完成(参见例如 this解释How to start a process and make it 'independent')。所以这种行为是非常值得期待的。一个解决方案可以在这里找到https://stackoverflow.com/a/1569941/2075678,它指的是这个网页https://svn.haxx.se/users/archive-2008-11/0301.shtml。我稍微改写了一下:

#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;
}

编译完上述内容后,我修改了我的 .bat 文件,现在效果很好:

@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

【讨论】:

    猜你喜欢
    • 2016-02-12
    • 1970-01-01
    • 2011-01-09
    • 2020-12-19
    • 1970-01-01
    • 2012-09-23
    • 1970-01-01
    • 2012-05-17
    • 2015-09-10
    相关资源
    最近更新 更多