Created
April 1, 2023 12:23
-
-
Save jimklimov/291b7ee632a729e9412cb823c69c531a to your computer and use it in GitHub Desktop.
WSL2 wrapper for `git difftool`
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Add these lines to your ~/.gitconfig, to use wsl_wrapper | |
# program and your helper script to call winmerge GUI: | |
[mergetool "winmerge"] | |
cmd = \"$HOME/bin/winmerge.sh\" -u -maximize -wl -wr -fm -dl 'Mine: '\"$LOCAL\" -dm 'Merged: '\"$BASE\" -dr 'Theirs: '\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$REMOTE\" -o \"$MERGED\" -am | |
trustExitCode = true | |
[difftool "winmerge"] | |
cmd = \"$HOME/bin/winmerge.sh\" \"$LOCAL\" \"$REMOTE\" | |
[diff] | |
tool = winmerge | |
[merge] | |
tool = winmerge |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright (C) 2023 by Jim Klimov, licensed under terms of MIT license | |
all: wsl_wrapper | |
wsl_wrapper: wsl_wrapper.cpp | |
g++ "$<" -o "$@" | |
install: wsl_wrapper | |
mkdir -p "$HOME/bin" | |
cp -pf wsl_wrapper "$HOME/bin/" | |
chmod +x "$HOME/bin/wsl_wrapper" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# Copyright (C) 2023 by Jim Klimov, licensed under terms of MIT license | |
# Example script to wrap WinMergeU.exe with wsl_wrapper as a "git difftool" | |
# install as $HOME/bin/winmerge.sh | |
# Of course can wrap another preferred tool similarly | |
# The symlink name of "wsl_wrapper" must be same as your Windows tool without the ".exe" | |
PATH="$HOME/bin:/c/Program Files/WinMerge:$PATH" | |
export PATH | |
if [ -s "$HOME/bin/wsl_wrapper" ] && [ ! -e "$HOME/bin/WinMergeU" ] ; then | |
ln -fs wsl_wrapper "$HOME/bin/WinMergeU" | |
fi | |
WinMergeU "$@" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Derived from https://stackoverflow.com/a/74977336/4715872 | |
* by https://stackoverflow.com/users/40756/die-in-sente | |
* Slight fixes 2023 by Jim Klimov | |
* Build: g++ wsl_wrapper.cpp -o "$HOME/bin/wsl_wrapper" | |
*/ | |
// Problem: | |
// It's difficult to launch a Windows GUI program from WSL. | |
// Take p4merge for example. | |
// I have the Windows version of p4merge installed, and I would | |
// like to set that as my git difftool, for comparing versions | |
// of source files. | |
// If I install it as my git difftool | |
// git config --global --add diff.tool p4merge | |
// Git will try to launch it, but it won't work because the | |
// file paths passed as part of the command are WSL paths, | |
// that the Winodws app can't open. | |
// p4merge will fail with an error, e.g.: | |
// '/tmp/vT1r3J_foo.cpp' is (or points to) an invalid file. | |
// | |
// Solution: | |
// This helper program can be installed on the WSL Ubuntu | |
// $PATH as p4merge. Or you can create a symlink in your | |
// $PATH named p4merge that points to this program. | |
// | |
// This will scan all of the command line arguemments and translate | |
// any file paths to paths that Windows apps can open, then | |
// launch the target with the modified command line. | |
// | |
// You can use this wrapper around any windows GUI program. | |
// It will figure out what target to launch from argv[0]. | |
// HOWEVER! If this wrapper is in the $PATH and the target | |
// is also in the $PATH, they CANNOT HAVE THE SAME NAME! | |
// That would cause an infinite recursion where the wrapper | |
// kept launching itself. | |
// Recommended usage: Have "program" (without extension) in | |
// your path pointing to the wrapper, and "program.exe" in | |
// your path pointing to the Windows GUI target. | |
// This wrapper will automatically append the ".exe" suffix | |
// to the name in argv[0], and will quit without launching | |
// if argv[0] already contains an ".exe" extension. | |
// | |
// Note: When the linux shell launches a program via a symlink, | |
// argv[0] will have the name of the link, not the link target. | |
// | |
// Any argument on the command line that looks like a file or | |
// path will be translated if necessary. A path that is | |
// already in Windows format will just be copied. | |
// | |
// The WSL file system is not directly part of your computer's | |
// Windows namespace. The WSL file system is accessed as if | |
// it was a network file. | |
// A path that starts with "/" -- the WSL root directory translates | |
// to \\wsl$\${WSL_DISTRO_NAME}\ | |
// A path that starts with "~" -- translates to | |
// \\wsl$\${WSL_DISTRO_NAME}\${HOME} | |
// And of course paths that start with "." translate to | |
// \\wsl$\${WSL_DISTRO_NAME}\${PWD} | |
// | |
// The Windows file system is accessable from linux as mounted | |
// devices. A path like /mnt/<drive-letter>/... translates to | |
// <drive-letter>:\... | |
// | |
// All forward-slashes are translated to back-slashes. | |
// | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <stdint.h> | |
#include <string.h> | |
#include <ctype.h> | |
#include <string> | |
#include <sstream> | |
// This really oughta be in libc | |
// | |
char * stristr (const char * text, const char * match) | |
{ | |
char * strText = strdup (text); | |
for (char * p = strText; !!(*p); ++p) { | |
*p = tolower (*p); | |
} | |
char * strMatch = strdup (match); | |
for (char * p = strMatch; !!(*p); ++p) { | |
*p = tolower (*p); | |
} | |
char * result = strstr (strText, strMatch); | |
if (result) { | |
result = (char *) text + (result - strText); | |
} | |
free (strText); | |
free (strMatch); | |
return result; | |
} | |
int main (int argc, char ** argv) { | |
for (int i = 0; i < argc; ++i) { | |
fprintf (stdout, "arg[%2d] : %s\n", i, argv[i]); | |
} | |
fflush (stdout); | |
struct _local { | |
const char * env_WSL_DISTRO_NAME; | |
const char * env_HOME; | |
const char * env_PWD; | |
char * target; | |
std::stringstream command; | |
_local() | |
: env_WSL_DISTRO_NAME (nullptr) | |
, env_HOME (nullptr) | |
, env_PWD (nullptr) | |
, target (nullptr) | |
{} | |
~_local() { | |
if (target) { | |
auto ptr = target; | |
target = nullptr; | |
free (ptr); | |
} | |
} | |
static void get_env (const char * & member, const char * name) { | |
member = getenv (name); | |
fprintf (stdout, "%s=%s\n", (const char *) name, | |
member ? member : ""); | |
} | |
void get_target (const char * me) { | |
const char * tmp = basename (me); | |
size_t n = strlen (tmp) + 5; | |
n = (n + 32) & 0x1f; | |
target = (char *) malloc (n); | |
memset (target, 0, n); | |
--n; | |
strncpy (target, tmp, n); | |
n -= strlen (target); | |
strncat (target, ".exe", n); | |
command << target; | |
} | |
void translate (const char * arg) { | |
if (! arg) return; | |
char c = *(arg++); | |
if ('~' == c) { | |
translate (env_HOME); | |
} else if ('.' == c && ('\0' == *arg || '\\' == *arg || '/' == *arg)) { | |
translate (env_PWD); | |
++arg; | |
} else if ('/' == c) { | |
if (0 == strncmp ("mnt/", arg, 4)) { | |
c = toupper (*(arg += 4)); | |
command << c; | |
command << ":"; | |
arg+=2; | |
} else { | |
command << "\\\\\\\\wsl$\\\\"; | |
command << env_WSL_DISTRO_NAME; | |
} | |
} | |
for (c = arg[-1]; !!c; c = *(arg++)) { | |
if ('/' == c) { | |
command << "\\\\"; | |
} else { | |
command << c; | |
} | |
} | |
} | |
void do_arg (const char * arg) { | |
command << ' '; | |
translate (arg); | |
} | |
} local; | |
local.get_env (local.env_WSL_DISTRO_NAME, "WSL_DISTRO_NAME"); | |
local.get_env (local.env_HOME, "HOME"); | |
local.get_env (local.env_PWD, "PWD"); | |
if (stristr (argv[0], ".exe")) { | |
fprintf (stderr, "argv[0] = \"%s\"\n", (const char *) argv[0]); | |
fputs ("The wsl_wrapper has the same name as the implied target.\n" | |
"This would trigger infinite launch recursion!\n", stderr); | |
return 1; | |
} | |
local.get_target (argv[0]); | |
for (int i = 1; i < argc; ++i) { | |
local.do_arg (argv[i]); | |
} | |
fputs ("\033[1;33m", stdout); // YELLOW | |
fputs (local.command.str().c_str(), stdout); | |
fputs ("\033[0m\n", stdout); | |
fflush (stdout); | |
int result = system (local.command.str().c_str()); | |
if (result == -1) { | |
fputs ("\033[1;31m", stdout); // RED | |
perror ("Failed to launch:"); | |
fputs ("\033[0m\n", stdout); | |
} | |
// fputc ('\n', stdout); | |
return result; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment