Skip to content

Make it possible to run mingw64\bin\git.exe directly #2283

Closed
@dscho

Description

@dscho

This is a copy-edited version of my advice to somebody who asked for it on Gitter (only to refuse to do any work).

There is a reason for bin\git.exe and cmd\git.exe to be identical: they are both copies of the same wrapper (or proxy, or primer). The idea here is to provide a consistent location for external tooling. However, they still need to spawn the real git.exe.

Note that there are two "real" git.exe: mingw64\bin\git.exe and mingw64\libexec\git-core\git.exe. Those two git.exe files are hash-identical because the latter is a hard link to the former. There are many more hard links in mingw64\libexec\git-core, and the reason is : these are hard-linked versions of git.exe, provided for backwards-compatibility (for scripts that still call something like git-rev-list despite over a decade of deprecation: they should call git rev-list instead, i.e. with a space after git instead of a dash).

Currently, the only supported way is to call cmd\git.exe, not mingw64\bin\git.exe directly. Calling cmd\git.exe will be guaranteed to work for any future Git for Windows version.

Now, there is a really interesting question: why can't cmd\git.exe be a hard-link to mingw64\bin\git.exe in the first place?

Let's have a look at what cmd\git.exe does actually: https://github.com/git-for-windows/MINGW-packages/blob/master/mingw-w64-git/git-wrapper.c. You will see that this wrapper is used for more things than just for cmd\git.exe. It is used, for example, as cmd\gitk.exe, too, and also for git-bash.exe. The real interesting part for cmd\git.exe (or for that matter, bin\git.exe) is here: https://github.com/git-for-windows/MINGW-packages/blob/c29792d7d6419489d66e443559e1881737e9c55e/mingw-w64-git/git-wrapper.c#L695-L706:
We first detect the top-level directory (from the .exe's location, as queried via GetModuleFileName()). Then, we set exe to <top-level>\mingw64\bin\git.exe and test whether that file exists. If not, we set exe to <top-level>\bin\git.exe.

So now comes the real important part of the Git wrapper: https://github.com/git-for-windows/MINGW-packages/blob/c29792d7d6419489d66e443559e1881737e9c55e/mingw-w64-git/git-wrapper.c#L708-L714: In setup_environment(), we set the environment variables MSYSTEM, PLINK_PROTOCOL, HOME (unless it is set already), and we augment PATH. After that, we merely replace the first component of the command-line by the path of the actual git.exe, spawn that and wait until it is done.

Back to the interesting question why don't we just teach the real git.exe do do those tricks and to replace cmd\git.exe (and bin\git.exe) by a hard-link to mingw64\bin\git.exe?

This is not only an interesting question, but also a good idea, but we do have to teach git.exe those tricks (i.e. setting the environment variables) before we can do that. We also have to be a bit careful about this, as we do not really want git.exe to extend the PATH over and over and over again, possibly running into the length limitation (PATH can only contain 32k characters) for nested git invocations.

The good news is that HOME is already taken care of: https://github.com/git-for-windows/git/blob/v2.22.0.windows.1/compat/mingw.c#L2950-L2972

(Why don't we remove this from the Git wrapper, then? Because it is also needed for git-bash.exe.)

The PATH extension is not there, but a similar thing is there: it adds mingw64\libexec\git-core to the PATH. To understand why, you have to learn a bit about Git's history. The original idea of Linus Torvalds was to ship Git as a set of Unix-y tools that do one thing, and one thing only, and as a set of Unix shell scripts that combine these functionalities. That seemed to be a really smart idea right up until we ran into portability, scale and performance issues, not to mention the difficulties with proper error handling. But by that time, we already had shipped several Git versions with e.g. git-rev-list and git-log in the bin directory. Also, this cluttered the tab completion with low-level commands. So the low-level commands were moved into libexec/git-core and the top-level git was kept in bin. This required the PATH to be extended to find the low-level commands, and subsequently a lot of them were turned into "built-ins", i.e. handled by the top-level git executable directly. However, for backwards-compatibility, we still needed the "dashed" forms (i.e. an executable called git-rev-list that does the same as git with the first argument rev-list).

This PATH extension is performed in setup_path().

But setup_path() appends the libexec path to PATH, while we need to insert something in the beginning of it instead. So here is how I would go about it if I wanted to bring about this change:

  • I would use the presence of the environment variable MSYSTEM as a tell-tale that all of this has been done already, i.e. guard at least the PATH munging.
  • I would insert all the new code into compat\mingw.c, just after HOME has been handled.

The new code would look somewhat like this:

wchar_t buf[32768];

[...]

if (!GetEnvironmentVariableW(L"PLINK_PROTOCOL", buf, ARRAY_SIZE(buf)))
    SetEnvironmentVariableW(L"PLINK_PROTOCOL", L"ssh");

if (!GetEnvironmentVariableW(L"MSYSTEM", buf, ARRAY_SIZE(buf))) {
    int bitness = (int)(sizeof(void *) * 8);
#ifdef RUNTIME_PREFIX
    wchar_t home[MAX_PATH], top_level[MAX_PATH], path[32768];
    size_t off = 0;

    if ([... get HOME variable ...])
        off += swprintf(buf + off, ARRAY_SIZE(buf) - off - 1, L"%s\\bin;", home);
    if ([... GetModuleFileNameW() ...]) {
        /* strip trailing `git.exe`, then `cmd` or `mingw64\bin` or `mingw32\bin` or `bin` or `libexec\git-core` */
        off += swprintf(buf + off, ARRAY_SIZE(buf) - off - 1, L"%s\\mingw%d\\bin;%s\\usr\\bin;",
            top_level, bitness, top_level);
    }
    if ([... get PATH variable ...])
        wcsncat(buf + off, ARRAY_SIZE(buf) - off, path);
    else if (off > 0)
        buf[off - 1] = L'\0'; /* strip trailing ';' */
    SetEnvironmentVariableW(L"PATH", buf);
#endif

    swprintf(buf, ARRAY_SIZE(buf), L"MINGW%d", bitness);
    SetEnvironmentVariableW(L"MSYSTEM", buf);
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions