├── .gitattributes ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE.md ├── README.md ├── benches └── benchmark.rs ├── create-release-package.sh ├── resources ├── Fork.RI └── install.bat ├── src ├── fork.rs ├── main.rs └── wsl.rs └── tests └── integration.rs /.gitattributes: -------------------------------------------------------------------------------- 1 | Fork.RI text eol=lf 2 | install.bat text eol=crlf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | *.log 4 | *.patch 5 | .vscode/ 6 | release/ 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # WSLGit Changelog 2 | 3 | ## [1.2.0] - 2022-12-30 4 | 5 | ### Added 6 | 7 | - Support setting preferred WSL distribution 8 | - Detect and use WSL distribution specified in UNC paths 9 | 10 | ### Fixed 11 | 12 | - Escape special characters `<`, `>`, and `!` 13 | 14 | 15 | ## [1.1.1] - 2022-03-22 16 | 17 | ### Fixed 18 | 19 | - Use standard bash executable (#122) 20 | 21 | 22 | ## [1.1.0] - 2022-03-13 23 | 24 | ### Changed 25 | 26 | - Use full path to bash (#119) 27 | 28 | ### Fixed 29 | 30 | - Fix test errors 31 | 32 | 33 | ## [1.0.1] - 2020-08-25 34 | 35 | ### Fixed 36 | 37 | - Fix install script for paths with spaces (#110). 38 | 39 | 40 | ## [1.0.0] - 2020-08-22 41 | 42 | ### Added 43 | 44 | - Add install script to create binaries and directory structure 45 | similar to Git for Windows. This enables tools to auto-detect Git, 46 | if the created directory is added to the Windows `Path`. 47 | - Add proxy to call `Fork.RI` from WSL. 48 | 49 | ### Changed 50 | 51 | - Add `ls-remote` to commands that use an interactive bash shell (#101). 52 | - Treat file arguments after ` -- ` as relative paths (#102). 53 | - Include version number in logging output (#105). 54 | - Invoke `wsl` without default shell (#107). 55 | 56 | ### Fixed 57 | 58 | - Fix translation of URLs that start with a transport protocol (#103). 59 | 60 | 61 | ## [0.9.0] - 2020-01-10 62 | 63 | ### Added 64 | 65 | - New `WSLGIT` environment variable that is set to `1` by `wslgit` and 66 | shared to the WSL environment. 67 | 68 | ### Changed 69 | 70 | - Use `wslpath` to translate paths between Windows and Linux (#12, #71). 71 | - New default `smart` for `WSLGIT_USE_INTERACTIVE_SHELL` - only uses an 72 | interactive bash shell for `clone`, `fetch`, `pull` and `push`. 73 | 74 | ### Removed 75 | 76 | - Remove `WSLGIT_MOUNT_ROOT` environment variable, this is handled by `wslpath` now. 77 | 78 | 79 | ## [0.8.0] - 2019-10-11 80 | 81 | ### Added 82 | 83 | - New environment variable `WSLGIT_MOUNT_ROOT` to configure the 84 | WSL mount root (#78). 85 | 86 | ### Fixed 87 | 88 | - Improve shell escaping of invalid characters (#27, #54, #73), 89 | fixed by #74 and #76. 90 | - Support flags for `BASH_ENV` in `WSLENV` environment variable (#56), 91 | fixed by #78. 92 | - Fixed translating to windows path from the root of a mounted drive (#80). 93 | - Support empty command line arguments (#84). 94 | 95 | ### Changed 96 | 97 | - Format code using `rustfmt`. 98 | - Unify interactive/non-interactive configurations, both use `bash -c` now. 99 | - Expand tests and add integration tests (#76). 100 | - `WSLGIT_USE_INTERACTIVE_SHELL` now has higher priority than a 101 | `BASH_ENV`/`WSLENV` configuration (#78). 102 | 103 | 104 | ## [0.7.0] - 2019-01-24 105 | 106 | ### Added 107 | 108 | - Support for relative paths as arguments. 109 | - Translate paths in long form arguments, e.g. `--file=C:\some\path` 110 | 111 | ### Fixed 112 | 113 | - Support git commands in any argument position when deciding wether to 114 | translate paths in the output of the command. 115 | - Fix incorrectly quoted arguments with spaces in non-interactive setup. 116 | 117 | ### Changed 118 | 119 | - To support manually mounted network drives, the working directory inside WSL 120 | is now explicitly changed to the current working directory of `wslgit` 121 | in Windows. 122 | 123 | 124 | ## [0.6.0] - 2018-04-24 125 | 126 | ### Added 127 | 128 | - Allow running bash in non-interactive mode (#16, #23). 129 | 130 | ### Fixed 131 | 132 | - Unix paths inside file contents are not being erroneously translated anymore (#19). 133 | - Do not assume valid UTF-8 output from git (#29). 134 | - Fix running `wslgit` without arguments (#26). 135 | - Escape `\n` newlines in arguments to git (#27). 136 | 137 | ### Changed 138 | 139 | - Change to `wsl.exe` to call into the WSL environment. 140 | - Apply path translation only to output of `rev-parse` and `remote`. 141 | 142 | 143 | ## [0.5.0] - 2018-01-11 144 | 145 | ### Added 146 | 147 | - Return exit code from git subprocess. 148 | 149 | ### Fixed 150 | 151 | - Fix superfluous empty `.git` source control providers. 152 | 153 | 154 | ## [0.4.0] - 2017-12-18 155 | 156 | ### Fixed 157 | 158 | - Compatibility with VS Code 1.19, which now requires proper Windows paths 159 | (with backslashes) and a lowercase drive letter. 160 | 161 | 162 | ## [0.3.0] - 2017-11-08 163 | 164 | ### Added 165 | 166 | - Add proper license (MIT). 167 | 168 | ### Fixed 169 | 170 | - Git waiting for input when called from VS Code to check if `git --version` 171 | works. 172 | 173 | 174 | ## [0.2.0] - 2017-07-27 175 | 176 | ### Added 177 | 178 | - Properly handle input via stdin (for commit messages). 179 | 180 | 181 | ## [0.1.0] - 2017-07-26 182 | 183 | ### Added 184 | 185 | - Initial version of `wslgit` with basic functionality. 186 | 187 | 188 | [0.1.0]: # 189 | [0.2.0]: https://github.com/andy-5/wslgit/releases/tag/v0.2.0 190 | [0.3.0]: https://github.com/andy-5/wslgit/releases/tag/v0.3.0 191 | [0.4.0]: https://github.com/andy-5/wslgit/releases/tag/v0.4.0 192 | [0.5.0]: https://github.com/andy-5/wslgit/releases/tag/v0.5.0 193 | [0.6.0]: https://github.com/andy-5/wslgit/releases/tag/v0.6.0 194 | [0.7.0]: https://github.com/andy-5/wslgit/releases/tag/v0.7.0 195 | [0.8.0]: https://github.com/andy-5/wslgit/releases/tag/v0.8.0 196 | [0.9.0]: https://github.com/andy-5/wslgit/releases/tag/v0.9.0 197 | [1.0.0]: https://github.com/andy-5/wslgit/releases/tag/v1.0.0 198 | [1.0.1]: https://github.com/andy-5/wslgit/releases/tag/v1.0.1 199 | [1.1.0]: https://github.com/andy-5/wslgit/releases/tag/v1.1.0 200 | [1.1.1]: https://github.com/andy-5/wslgit/releases/tag/v1.1.1 201 | [1.2.0]: https://github.com/andy-5/wslgit/releases/tag/v1.2.0 202 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "assert_cmd" 16 | version = "2.0.7" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "fa3d466004a8b4cb1bc34044240a2fd29d17607e2e3bd613eb44fd48e8100da3" 19 | dependencies = [ 20 | "bstr", 21 | "doc-comment", 22 | "predicates", 23 | "predicates-core", 24 | "predicates-tree", 25 | "wait-timeout", 26 | ] 27 | 28 | [[package]] 29 | name = "autocfg" 30 | version = "0.1.4" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "0e49efa51329a5fd37e7c79db4621af617cd4e3e5bc224939808d076077077bf" 33 | 34 | [[package]] 35 | name = "bstr" 36 | version = "1.1.0" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "b45ea9b00a7b3f2988e9a65ad3917e62123c38dba709b666506207be96d1790b" 39 | dependencies = [ 40 | "memchr", 41 | "once_cell", 42 | "regex-automata", 43 | "serde", 44 | ] 45 | 46 | [[package]] 47 | name = "difflib" 48 | version = "0.4.0" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 51 | 52 | [[package]] 53 | name = "doc-comment" 54 | version = "0.3.1" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "923dea538cea0aa3025e8685b20d6ee21ef99c4f77e954a30febbaac5ec73a97" 57 | 58 | [[package]] 59 | name = "either" 60 | version = "1.8.0" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" 63 | 64 | [[package]] 65 | name = "float-cmp" 66 | version = "0.9.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" 69 | dependencies = [ 70 | "num-traits", 71 | ] 72 | 73 | [[package]] 74 | name = "itertools" 75 | version = "0.10.5" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 78 | dependencies = [ 79 | "either", 80 | ] 81 | 82 | [[package]] 83 | name = "lazy_static" 84 | version = "1.4.0" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 87 | 88 | [[package]] 89 | name = "libc" 90 | version = "0.2.76" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3" 93 | 94 | [[package]] 95 | name = "memchr" 96 | version = "2.4.1" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 99 | 100 | [[package]] 101 | name = "normalize-line-endings" 102 | version = "0.3.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" 105 | 106 | [[package]] 107 | name = "num-traits" 108 | version = "0.2.8" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" 111 | dependencies = [ 112 | "autocfg", 113 | ] 114 | 115 | [[package]] 116 | name = "once_cell" 117 | version = "1.17.0" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" 120 | 121 | [[package]] 122 | name = "predicates" 123 | version = "2.1.5" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" 126 | dependencies = [ 127 | "difflib", 128 | "float-cmp", 129 | "itertools", 130 | "normalize-line-endings", 131 | "predicates-core", 132 | "regex", 133 | ] 134 | 135 | [[package]] 136 | name = "predicates-core" 137 | version = "1.0.0" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "06075c3a3e92559ff8929e7a280684489ea27fe44805174c3ebd9328dcb37178" 140 | 141 | [[package]] 142 | name = "predicates-tree" 143 | version = "1.0.0" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "8e63c4859013b38a76eca2414c64911fba30def9e3202ac461a2d22831220124" 146 | dependencies = [ 147 | "predicates-core", 148 | "treeline", 149 | ] 150 | 151 | [[package]] 152 | name = "regex" 153 | version = "1.7.0" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" 156 | dependencies = [ 157 | "aho-corasick", 158 | "memchr", 159 | "regex-syntax", 160 | ] 161 | 162 | [[package]] 163 | name = "regex-automata" 164 | version = "0.1.10" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 167 | 168 | [[package]] 169 | name = "regex-syntax" 170 | version = "0.6.28" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" 173 | 174 | [[package]] 175 | name = "serde" 176 | version = "1.0.152" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" 179 | 180 | [[package]] 181 | name = "treeline" 182 | version = "0.1.0" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" 185 | 186 | [[package]] 187 | name = "wait-timeout" 188 | version = "0.2.0" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" 191 | dependencies = [ 192 | "libc", 193 | ] 194 | 195 | [[package]] 196 | name = "wslgit" 197 | version = "1.2.0" 198 | dependencies = [ 199 | "assert_cmd", 200 | "lazy_static", 201 | "predicates", 202 | "regex", 203 | ] 204 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wslgit" 3 | version = "1.2.0" 4 | authors = ["Andreas Riffnaller-Schiefer "] 5 | license = "MIT" 6 | 7 | [dependencies] 8 | regex = "1.7.0" 9 | lazy_static = "1.4" 10 | 11 | [dev-dependencies] 12 | assert_cmd = "2.0.7" 13 | predicates = "2.1.5" 14 | 15 | [profile.release] 16 | strip = true 17 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Andreas Riffnaller-Schiefer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WSLGit 2 | 3 | This project provides a small executable that forwards all arguments 4 | to `git` running inside Bash on Windows/Windows Subsystem for Linux (WSL). 5 | 6 | The primary reason for this tool is to make the Git plugin in 7 | Visual Studio Code (VSCode) work with the `git` command installed in WSL. 8 | For these two to interoperate, this tool translates paths 9 | between the Windows (`C:\Foo\Bar`) and Linux (`/mnt/c/Foo/Bar`) 10 | representations. 11 | 12 | ## Installation 13 | 14 | The latest binary release can be downloaded from the 15 | [releases page](https://github.com/andy-5/wslgit/releases). 16 | 17 | **[Optional 1]** Run the `install.bat` script *as administrator*. 18 | The `install.bat` script creates a folder structure similar to the one used by `Git For Windows`, 19 | and creates some useful symbolic links in the `wslgit\cmd` and `wslgit\bin` folders. 20 | 21 | **[Optional 2]** Add the `wslgit\cmd` directory to your Windows `Path` 22 | environment variable (user or system). 23 | To change the environment variable, type 24 | `Edit environment variables for your account` into Start menu/Windows search 25 | and use that tool to edit `Path`. 26 | 27 | You may also need to install the latest 28 | [*Microsoft Visual C++ Redistributable for Visual Studio 2017*](https://aka.ms/vs/15/release/vc_redist.x64.exe). 29 | 30 | ## WSL distributions and file systems 31 | 32 | When accessing files on the filesystem in a WSL distribution using the UNC path 33 | (`\\wsl$\dist\path` or `\\wsl.localhost\dist\path`) then the distribution used 34 | is extracted from the path. This means that git must be setup correctly in all 35 | distributions that you intend to access. 36 | 37 | When accessing files on the Windows filesystem or a mapped network drive the 38 | default WSL distribution is used unless the 39 | [`WSLGIT_DEFAULT_DIST`](#wslgit_default_dist) environment variable is set. 40 | 41 | If the default WSL distribution is of WSL2 type then it is highly recommended to 42 | set the `WSLGIT_DEFAULT_DIST` to the name of a WSL1 instance since WSL1 is both 43 | quicker at accessing the Windows filesystem and can access mapped network drives 44 | which WSL2 cannot. 45 | 46 | > Tip: use symlinks to map files and folders in all distributions to a common 47 | > directory to avoid having to maintain multiple copies, for example you can 48 | > link the `~/.ssh` folder in all WSL dists to the `.ssh` folder in your Windows 49 | > home folder. 50 | 51 | ## Usage in VSCode 52 | 53 | VSCode will find the `git` executable automatically if the two optional installation steps were taken. 54 | 55 | If not, set the appropriate path in your VSCode `settings.json`: 56 | 57 | ``` 58 | { 59 | "git.path": "C:\\CHANGE\\TO\\PATH\\TO\\wslgit\\cmd\\wslgit.exe" 60 | } 61 | ``` 62 | 63 | Also make sure that you use an SSH key without password to access your 64 | git repositories, or that your SSH key is added to a SSH agent running 65 | within WSL before starting VSCode. 66 | *You cannot enter your passphrase in VSCode!* 67 | 68 | If you use a SSH agent, make sure that it does not print any text 69 | (like e.g. *Agent pid 123*) during startup of an interactive bash shell. 70 | If there is any additional output when your bash shell starts, the VSCode 71 | Git plugin cannot correctly parse the output. 72 | 73 | 74 | ## Usage from the command line 75 | 76 | If you did the two optional installation steps then 77 | you can then just run any git command from a Windows console 78 | by running `wslgit COMMAND` or `git COMMAND` and it uses the Git version 79 | installed in WSL. 80 | 81 | ## Usage in Fork 82 | 83 | To make [Fork](https://fork.dev) use `git from WSL` you must have done the first optional installation step (run `install.bat`). Then go to the `Fork` preferences and select a custom git instance where you point it to the `git.exe` in the `wslgit\bin` folder (**not** the *cmd* folder!). 84 | 85 | If getting an error message about not being able to execute `Fork.RI` then make 86 | sure that the `Fork.RI` script is executable inside WSL (run `chmod +x Fork.RI` 87 | if needed). 88 | 89 | ## Remarks 90 | 91 | Currently, the path translation and shell escaping is very limited, 92 | just enough to make it work in VSCode. 93 | 94 | All absolute paths are translated, but relative paths are only 95 | translated if they point to existing files or directories. 96 | Otherwise it would be impossible to detect if an 97 | argument is a relative path or just some other string. 98 | VSCode always uses forward slashes for relative paths, so no 99 | translation is necessary in this case. 100 | 101 | Additionally, be careful with special characters interpreted by the shell. 102 | Only spaces and newlines in arguments are currently handled. 103 | 104 | 105 | ## Advanced Usage 106 | 107 | ### WSLGIT_USE_INTERACTIVE_SHELL 108 | To automatically support the common case where `ssh-agent` or similar tools are 109 | setup by `.bashrc` in interactive mode then, per default, `wslgit` executes `git` 110 | inside the WSL environment through `bash` started in interactive mode for some 111 | commands (`clone`, `fetch`, `pull` and `push`), and `bash` started in non-interactive 112 | mode for all other commands. 113 | 114 | The behavior can be selected by setting an environment variable in Windows 115 | named `WSLGIT_USE_INTERACTIVE_SHELL` to one of the following values: 116 | * `false` or `0` - Force `wslgit` to **always** start in **_non_-interactive** mode. 117 | * `true`, `1`, or empty value - Force `wslgit` to **always** start in **interactive** mode. 118 | * `smart` (default) - Interactive mode for `clone`, `fetch`, `pull`, `push`, 119 | non-interactive mode for all other commands. This is the default if the variable is not set. 120 | 121 | Alternatively, if `WSLGIT_USE_INTERACTIVE_SHELL` is **not** set but the Windows 122 | environment variable `BASH_ENV` is set to a bash startup script and the environment 123 | variable `WSLENV` contains the string `"BASH_ENV"`, then `wslgit` assumes that 124 | the forced startup script from `BASH_ENV` contains everything you need, and 125 | therefore also starts bash in non-interactive mode. 126 | 127 | This feature is only available in Windows 10 builds 17063 and later. 128 | 129 | ### WSLGIT_DEFAULT_DIST 130 | 131 | Set a Windows environment variable called `WSLGIT_DEFAULT_DIST` to the name of a 132 | WSL distribution to use instead of the WSL default distribution when accessing 133 | files on the Windows filesystem or from mapped network shares. 134 | 135 | > Note, to access files on a mapped network drive a WSL1 distribution must be used. 136 | 137 | ### WSLGIT 138 | `wslgit` set a variable called `WSLGIT` to `1` and shares it to WSL. This variable can be used in `.bashrc` to 139 | determine if WSL was invoked by `wslgit`, and for example if set then just do the absolute minimum of initialization 140 | needed for `git` to function. 141 | Combined with `WSLGIT_USE_INTERACTIVE_SHELL=smart` (default) this can make every git command execute with as little overhead as possible. 142 | 143 | This feature is only available in Windows 10 builds 17063 and later. 144 | 145 | ## Building from source 146 | 147 | First, install Rust from https://www.rust-lang.org. Rust on Windows also 148 | requires Visual Studio or the Visual C++ Build Tools for linking. 149 | 150 | The final executable can then be build by running 151 | 152 | ``` 153 | cargo build --release 154 | ``` 155 | 156 | inside the root directory of this project. The resulting binary will 157 | be located in `./target/release/`. 158 | 159 | Tests **must** be run using one test thread because of race conditions when changing environment variables: 160 | ```bash 161 | # Run all tests 162 | cargo test -- --test-threads=1 163 | # Run only unit tests 164 | cargo test test -- --test-threads=1 165 | # Run only integration tests 166 | cargo test integration -- --test-threads=1 167 | # Run benchmarks (requires nightly toolchain!) 168 | cargo +nightly bench 169 | ``` 170 | -------------------------------------------------------------------------------- /benches/benchmark.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | extern crate test; 3 | 4 | #[cfg(test)] 5 | mod bench { 6 | use std::env; 7 | use std::process::Command; 8 | use test::Bencher; 9 | 10 | #[bench] 11 | fn no_translation(b: &mut Bencher) { 12 | b.iter(|| { 13 | Command::new("wslgit") 14 | .args(&["--version"]) 15 | .env("WSLGIT_USE_INTERACTIVE_SHELL", "false") 16 | .output() 17 | }) 18 | } 19 | 20 | #[bench] 21 | fn translate_absolute_argument(b: &mut Bencher) { 22 | let p = env::current_dir() 23 | .unwrap() 24 | .as_path() 25 | .join("src\\main.rs") 26 | .as_path() 27 | .to_string_lossy() 28 | .into_owned(); 29 | let file_path = p.as_str(); 30 | 31 | b.iter(|| { 32 | Command::new("wslgit") 33 | .args(&["log", "-n1", "--oneline", "--", file_path]) 34 | .env("WSLGIT_USE_INTERACTIVE_SHELL", "false") 35 | .output() 36 | }) 37 | } 38 | 39 | #[bench] 40 | fn translate_relative_argument(b: &mut Bencher) { 41 | let file_path = "src\\main.rs"; 42 | 43 | b.iter(|| { 44 | Command::new("wslgit") 45 | .args(&["log", "-n1", "--oneline", "--", file_path]) 46 | .env("WSLGIT_USE_INTERACTIVE_SHELL", "false") 47 | .output() 48 | }) 49 | } 50 | 51 | #[bench] 52 | fn translate_output(b: &mut Bencher) { 53 | b.iter(|| { 54 | Command::new("wslgit") 55 | .args(&["rev-parse", "--show-toplevel"]) 56 | .env("WSLGIT_USE_INTERACTIVE_SHELL", "false") 57 | .output() 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /create-release-package.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | WSLGIT_BINARY=target/release/wslgit.exe 4 | OUTPUT_DIR=release/wslgit 5 | OUTPUT_CMD_DIR=$OUTPUT_DIR/cmd 6 | 7 | [[ ! -f "$WSLGIT_BINARY" ]] && echo "Release not built!" && exit 1 8 | 9 | rm -rf release/* || exit 1 10 | mkdir -p $OUTPUT_CMD_DIR || exit 1 11 | 12 | cp "$WSLGIT_BINARY" "$OUTPUT_CMD_DIR" || exit 1 13 | cp resources/Fork.RI "$OUTPUT_CMD_DIR" || exit 1 14 | cp resources/install.bat "$OUTPUT_DIR" || exit 1 15 | 16 | cd release && zip -r wslgit.zip ./* 17 | -------------------------------------------------------------------------------- /resources/Fork.RI: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Used as a proxy for calling Fork.RI.exe from WSL with the path to `git-rebase-todo` 3 | # converted to a windows path. 4 | # Expects the environment variable FORK_RI_EXE_PATH to contain the path to Fork.RI.exe. 5 | 6 | UNAME="$(uname -a)" 7 | # 'uname -a' returns: 8 | # WSL1: Linux PCNAME 4.4.0-17134-Microsoft #706-Microsoft Mon Apr 01 18:13:00 PST 2019 x86_64 x86_64 x86_64 GNU/Linux 9 | # WSL2: Linux DESKTOP-4P30KCU 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux 10 | # MINGW64: MINGW64_NT-10.0 PCNAME 2.11.2(0.329/5/3) 2018-11-26 09:22 x86_64 Msys 11 | # MINGW32: MINGW32_NT-10.0 PCNAME 2.11.2(0.329/5/3) 2018-11-26 09:22 x86_64 Msys 12 | # MSYS: MSYS_NT-10.0 PCNAME 2.11.2(0.329/5/3) 2018-11-26 09:22 x86_64 Msys 13 | 14 | if [[ $UNAME == *icrosoft* ]]; then 15 | # in a wsl shell 16 | WSL_BUILD=$(cmd.exe /c "systeminfo" | grep -E -i "build [0-9]+" | sed -E 's/^.*build ([0-9]+).*$/\1/I') 17 | if [ -z "$WSL_BUILD" ]; then 18 | WSL_BUILD=0 19 | fi 20 | 21 | if [ $WSL_BUILD -ge 17046 ] || [ -n "$WSL_INTEROP" ]; then 22 | # WSLPATH is available since WSL build 17046 23 | 24 | # Make sure that FORK_RI_EXE_PATH does not contain any badly escaped spaces. 25 | # It can happen when using Windows' short paths inside Fork's custom git instance path 26 | # Example: Fork is configured with "C:\Users\SURNAM~1\wslgit\bin\git.exe" instead of "C:\Users\Surname Lastname\wslgit\bin\git.exe" 27 | # which may be a valid use case as is does not support spaces in paths. 28 | # FORK_RI_EXE_PATH would contain "/mnt/c/Users/Surname/ Lastname/wslgit/bin/Fork.RI.exe" before the following sed call 29 | FORK_RI_EXE_PATH="$(sed 's/\/ / /g' <<< "$FORK_RI_EXE_PATH")" 30 | 31 | # Make sure that Fork.RI.exe is executable. 32 | chmod +x "$FORK_RI_EXE_PATH" 33 | 34 | # Call Fork.RI.exe with the path to the REBASE-TODO converted to a windows path. 35 | ARGS=$(wslpath -w "$@") 36 | "$FORK_RI_EXE_PATH" "$ARGS" 37 | exit $? 38 | else 39 | # ! No WSLPATH available. 40 | exit 1 41 | fi 42 | else 43 | # unknown shell 44 | exit 1 45 | fi 46 | -------------------------------------------------------------------------------- /resources/install.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | net session >nul 2>&1 3 | if %ERRORLEVEL% neq 0 ( 4 | echo. 5 | echo This script must be run as administrator to work properly! 6 | echo Right click on the script and select "Run as administrator". 7 | echo. 8 | goto :error 9 | ) 10 | 11 | set CWD=%~dp0 12 | set BINDIR=%CWD%bin 13 | set CMDDIR=%CWD%cmd 14 | cd %CWD% 15 | 16 | echo. 17 | if exist "%CMDDIR%\git.exe" ( 18 | echo 'git.exe' already exist. 19 | ) else ( 20 | echo Create 'git.exe' symlink... 21 | mklink "%CMDDIR%\git.exe" "%CMDDIR%\wslgit.exe" 22 | if %ERRORLEVEL% neq 0 ( 23 | echo ERROR! Failed to create symlink '%CMDDIR%\git.exe'. 24 | goto :error 25 | ) else ( 26 | echo OK. 27 | ) 28 | ) 29 | 30 | echo. 31 | if not exist "%BINDIR%" ( 32 | echo Create 'bin' directory... 33 | mkdir "%BINDIR%" 34 | if %ERRORLEVEL% neq 0 ( 35 | echo ERROR! Failed to create directory '%BINDIR%'. 36 | goto :error 37 | ) else ( 38 | echo OK. 39 | ) 40 | ) 41 | 42 | echo. 43 | if exist "%BINDIR%\git.exe" ( 44 | echo 'bin\git.exe' already exist. 45 | ) else ( 46 | echo Create 'bin\git.exe' symlink... 47 | mklink "%BINDIR%\git.exe" "%CMDDIR%\wslgit.exe" 48 | if %ERRORLEVEL% neq 0 ( 49 | echo ERROR! Failed to create symlink '%BINDIR%\git.exe'. 50 | goto :error 51 | ) else ( 52 | echo OK. 53 | ) 54 | ) 55 | 56 | echo. 57 | if exist "%BINDIR%\Fork.RI" ( 58 | echo 'bin\Fork.RI' already exist. 59 | ) else ( 60 | echo Create 'bin\Fork.RI' symlink... 61 | mklink "%BINDIR%\Fork.RI" "%CMDDIR%\Fork.RI" 62 | if %ERRORLEVEL% neq 0 ( 63 | echo ERROR! Failed to create symlink '%BINDIR%\Fork.RI'. 64 | goto :error 65 | ) else ( 66 | echo OK. 67 | ) 68 | ) 69 | 70 | echo. 71 | if exist "%BINDIR%\sh.exe" ( 72 | echo 'bin\sh.exe' already exist. 73 | ) else ( 74 | echo Create 'bin\sh.exe' symlink... 75 | mklink "%BINDIR%\sh.exe" "C:\Windows\System32\wsl.exe" 76 | if %ERRORLEVEL% neq 0 ( 77 | echo ERROR! Failed to create symlink '%BINDIR%\sh.exe'. 78 | goto :error 79 | ) else ( 80 | echo OK. 81 | ) 82 | ) 83 | 84 | echo. 85 | if exist "%BINDIR%\bash.exe" ( 86 | echo 'bin\bash.exe' already exist. 87 | ) else ( 88 | echo Create 'bin\bash.exe' symlink... 89 | mklink "%BINDIR%\bash.exe" "C:\Windows\System32\wsl.exe" 90 | if %ERRORLEVEL% neq 0 ( 91 | echo ERROR! Failed to create symlink '%BINDIR%\bash.exe'. 92 | goto :error 93 | ) else ( 94 | echo OK. 95 | ) 96 | ) 97 | 98 | echo. 99 | echo Installation successful! 100 | echo. 101 | echo (Optional) Add to the Windows Path environment variable: 102 | echo %CMDDIR% 103 | echo. 104 | pause 105 | exit /B 0 106 | 107 | :error 108 | pause 109 | exit /B 1 110 | -------------------------------------------------------------------------------- /src/fork.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use wsl; 4 | 5 | /// Returns `true` if the process was invoked by `Fork.exe`. 6 | pub fn needs_patching() -> bool { 7 | env::vars() 8 | .position(|var| "FORK_PROCESS_ID" == var.0) 9 | .is_some() 10 | } 11 | 12 | /// Patches the argument for Fork's interactive-rebase GUI. 13 | /// 14 | /// If the argument is an editor and the editor is `Fork.RI.exe` then replace the 15 | /// argument path with the path to the `Fork.RI` script and pass the path to 16 | /// `Fork.RI.exe` to WSL using the `FORK_RI_EXE_PATH` environment variable. 17 | /// The `Fork.RI` script is executed in WSL and will call `Fork.RI.exe` with 18 | /// the path to `git-rebase-todo` converted to a Windows-path. 19 | pub fn patch_argument(arg: String) -> String { 20 | lazy_static! { 21 | // "xxx.editor=xxx\Fork.RI.exe" 22 | static ref FORK_RI_EXE_PATH_EX: regex::Regex = regex::Regex::new( 23 | r"(?P\.editor=)(?P.*Fork\.RI\.exe)" 24 | ) 25 | .expect("Failed to compile FORK_RI_EXE_PATH_EX regex"); 26 | } 27 | 28 | match FORK_RI_EXE_PATH_EX.captures(arg.as_str()) { 29 | Some(caps) => { 30 | let fork_ri_exe_path = caps.name("fork_ri_exe_path").unwrap().as_str(); 31 | wsl::share_val("FORK_RI_EXE_PATH", fork_ri_exe_path, true); 32 | 33 | let fork_ri_script_path = match env::current_exe() { 34 | Ok(p) => p 35 | .parent() 36 | .unwrap() 37 | .join("Fork.RI") 38 | .to_string_lossy() 39 | .into_owned(), 40 | Err(e) => { 41 | eprintln!("Failed to get current exe path: {}", e); 42 | panic!(); 43 | } 44 | }; 45 | 46 | let new_editor = format!("${{prefix}}{}", fork_ri_script_path); 47 | return FORK_RI_EXE_PATH_EX 48 | .replace_all(arg.as_str(), new_editor.as_str()) 49 | .into_owned(); 50 | } 51 | None => return arg, 52 | } 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use super::*; 58 | 59 | #[test] 60 | fn invoked_by_fork() { 61 | env::set_var("FORK_PROCESS_ID", "5"); 62 | assert_eq!(true, needs_patching()); 63 | 64 | env::remove_var("FORK_PROCESS_ID"); 65 | assert_eq!(false, needs_patching()); 66 | } 67 | 68 | #[test] 69 | fn patch_argument_for_fork() { 70 | // The Fork.RI script is located in the same directory as the wslgit executable. 71 | let fork_ri_script_path: String = match env::current_exe() { 72 | Ok(p) => p 73 | .parent() 74 | .unwrap() 75 | .join("Fork.RI") 76 | .to_string_lossy() 77 | .into_owned(), 78 | Err(e) => { 79 | eprintln!("Failed to get current exe path: {}", e); 80 | panic!(); 81 | } 82 | }; 83 | 84 | env::set_var("FORK_PROCESS_ID", "42"); 85 | 86 | assert_eq!( 87 | patch_argument("core.editor=C:\\one\\Fork.RI.exe".to_owned()), 88 | format!("core.editor={}", fork_ri_script_path) 89 | ); 90 | assert!(env::vars() 91 | .position(|var| "FORK_RI_EXE_PATH" == var.0 && "C:\\one\\Fork.RI.exe" == var.1) 92 | .is_some()); 93 | env::remove_var("FORK_RI_EXE_PATH"); 94 | 95 | assert_eq!( 96 | patch_argument("sequence.editor=C:\\two\\Fork.RI.exe".to_owned()), 97 | format!("sequence.editor={}", fork_ri_script_path) 98 | ); 99 | assert!(env::vars() 100 | .position(|var| "FORK_RI_EXE_PATH" == var.0 && "C:\\two\\Fork.RI.exe" == var.1) 101 | .is_some()); 102 | env::remove_var("FORK_RI_EXE_PATH"); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use std::fs::OpenOptions; 4 | use std::io::{self, Write}; 5 | use std::path::{Path, PathBuf}; 6 | use std::process::{Command, Stdio}; 7 | 8 | #[macro_use] 9 | extern crate lazy_static; 10 | extern crate regex; 11 | use regex::bytes::Regex; 12 | 13 | mod fork; 14 | mod wsl; 15 | 16 | const VERSION: &'static str = env!("CARGO_PKG_VERSION"); 17 | 18 | const BASH_EXECUTABLE: &str = "/bin/bash"; 19 | 20 | static mut DOUBLE_DASH_FOUND: bool = false; 21 | 22 | fn translate_path_to_unix(argument: String) -> String { 23 | let argument = argument.as_bytes(); 24 | 25 | // An absolute or UNC path must: 26 | // 1. Be at the beginning of the string, or after a whitespace, colon, equal-sign or file://. 27 | // 2. Begin with :\, :/, \\ or //. 28 | // 3. Consist of 0 or more path components that does not contain the characters <>:|?'"\/ or newline, 29 | // and are delimited by \ or /. 30 | lazy_static! { 31 | static ref ABS_WINPATH_RE: Regex = Regex::new( 32 | r#"(?-u)(?P
^|[[:space:]]|:|=)(?P([A-Za-z]:[\\/]|\\\\|//)([^<>:|?'"\\/\n]+[\\/]?)*)"#
 33 |         )
 34 |         .expect("Failed to compile ABS_WINPATH_RE regex.");
 35 |     }
 36 | 
 37 |     lazy_static! {
 38 |         static ref FILE_ABS_WINPATH_RE: Regex = Regex::new(
 39 |             r#"(?-u)(?P
^file://)(?P([A-Za-z]:[\\/]|\\\\|//)([^<>:|?'"\\/\n]+[\\/]?)*)"#
 40 |         )
 41 |         .expect("Failed to compile FILE_ABS_WINPATH_RE regex.");
 42 |     }
 43 | 
 44 |     lazy_static! {
 45 |         static ref TRANSPORT_PROTOCOL_RE: Regex =
 46 |             Regex::new(r#"(?-u)^(ssh|git|https?|ftps?|file)://"#)
 47 |                 .expect("Failed to compile TRANSPORT_PROTOCOL_RE regex.");
 48 |     }
 49 | 
 50 |     let has_file_prefix = argument.starts_with(b"file://");
 51 |     let has_transport_protocol_prefix = TRANSPORT_PROTOCOL_RE.is_match(argument);
 52 | 
 53 |     let argument = if !has_transport_protocol_prefix {
 54 |         ABS_WINPATH_RE
 55 |             .replace_all(argument, &b"${pre}$(wslpath '${path}')"[..])
 56 |             .into_owned()
 57 |     } else if has_file_prefix {
 58 |         FILE_ABS_WINPATH_RE
 59 |             .replace_all(argument, &b"${pre}$(wslpath '${path}')"[..])
 60 |             .into_owned()
 61 |     } else {
 62 |         argument.to_vec()
 63 |     };
 64 | 
 65 |     // Relative paths that needs to have their slashes changed must:
 66 |     // 1. Be at the beginning of the string, or after a whitespace, colon, or equal-sign.
 67 |     // 2. Begin with a string of valid characters (except \)...
 68 |     // 3. Followed by one \
 69 |     // 4. And then any number of valid characters (including \).
 70 |     lazy_static! {
 71 |         static ref REL_WINPATH_RE: Regex = Regex::new(
 72 |             r#"(?-u)^(?P[^\\]+([[:space:]]|:|=))?(?P([^<>:|?'"\n\\]+)\\([^<>:|?'"\n]*))(?P.*)"#
 73 |         )
 74 |         .expect("Failed to compile REL_WINPATH_RE regex.");
 75 |     }
 76 | 
 77 |     if REL_WINPATH_RE.is_match(&argument) {
 78 |         let caps = REL_WINPATH_RE.captures(&argument).unwrap();
 79 |         let path_cap = caps.name("path").unwrap();
 80 |         let path = std::str::from_utf8(&path_cap.as_bytes()).unwrap();
 81 | 
 82 |         let double_dash_found = unsafe { DOUBLE_DASH_FOUND };
 83 | 
 84 |         // If the path in the argument exists then it is definitely a relative path,
 85 |         // or if the argument is after double-dashes then it is very likely a relative path.
 86 |         let translate_relative_path =
 87 |             has_file_prefix || double_dash_found || Path::new(path).exists();
 88 | 
 89 |         if translate_relative_path {
 90 |             let wsl_path = path.replace("\\", "/");
 91 | 
 92 |             let before = match caps.name("before") {
 93 |                 Some(s) => std::str::from_utf8(&s.as_bytes()).unwrap(),
 94 |                 None => "",
 95 |             };
 96 |             let after = match caps.name("after") {
 97 |                 Some(s) => std::str::from_utf8(&s.as_bytes()).unwrap(),
 98 |                 None => "",
 99 |             };
100 | 
101 |             return format!("{}{}{}", before, wsl_path, after);
102 |         }
103 |     }
104 | 
105 |     std::str::from_utf8(&argument).unwrap().to_string()
106 | }
107 | 
108 | fn translate_path_to_win(line: &[u8]) -> Vec {
109 |     // Windows can handle both / and \ as path separator so there is no need to convert relative paths.
110 | 
111 |     // An absolute Unix path must:
112 |     // 1. Be at the beginning of the string or after a whitespace.
113 |     // 2. Begin with /
114 |     // 3. Not contain the characters: <>:|?'* or newline.
115 |     // Note that when an absolute path is found then the rest of the line is passed to wslpath as argument!
116 |     // The only exception to this is lines ending in ` (fetch)` or ` (push)`, as in the output of `git remote -v`.
117 |     lazy_static! {
118 |         static ref WSLPATH_RE: Regex =
119 |             Regex::new(r"(?m)(?P
^|[[:space:]])(?P/([^<>:|?'*\n]*/?)*)")
120 |                 .expect("Failed to compile WSLPATH_RE regex");
121 |     }
122 | 
123 |     if WSLPATH_RE.is_match(line) {
124 |         // Use wslpath to convert the path to a windows path.
125 |         let line = WSLPATH_RE
126 |             .replace_all(line, &b"${pre}$(wslpath -w '${path}')"[..])
127 |             .into_owned();
128 | 
129 |         // Fixup output of `git remote -v`, i.e. lines ending in
130 |         // ` (fetch)` or ` (push)` - move remote types outside the wslpath call.
131 |         lazy_static! {
132 |             static ref REMOTE_FIX_RE: Regex =
133 |                 Regex::new(r"(?m)\s(?P(\(fetch\))|(\(push\)))'\)")
134 |                     .expect("Failed to compile REMOTE_FIX_RE regex");
135 |         }
136 |         let line = REMOTE_FIX_RE
137 |             .replace_all(&line, &b"') ${type}"[..])
138 |             .into_owned();
139 |         let line = std::str::from_utf8(&line).unwrap();
140 | 
141 |         let echo_cmd = format!("echo -n \"{}\"", line);
142 |         let output = Command::new("wsl")
143 |             .arg("-e")
144 |             .arg(BASH_EXECUTABLE)
145 |             .arg("-c")
146 |             .arg(&echo_cmd)
147 |             .output()
148 |             .expect("failed to execute echo_cmd");
149 |         if enable_logging() {
150 |             log(format!(
151 |                 "{:?} -> {} -> {:?}",
152 |                 line,
153 |                 echo_cmd,
154 |                 std::str::from_utf8(&output.stdout).unwrap()
155 |             ));
156 |         }
157 |         return output.stdout;
158 |     }
159 |     line.to_vec()
160 | }
161 | 
162 | fn escape_characters(arg: String) -> String {
163 |     arg.replace("\n", "$'\n'")
164 |         .replace("\"", "\\\"")
165 |         .replace("<", "\\<")
166 |         .replace(">", "\\>")
167 |         .replace("!", "\\!")
168 | }
169 | 
170 | fn invalid_characters(ch: char) -> bool {
171 |     match ch {
172 |         ' ' | '(' | ')' | '|' => true,
173 |         _ => false,
174 |     }
175 | }
176 | 
177 | fn quote_argument(arg: String) -> String {
178 |     if arg.contains(invalid_characters) || arg.is_empty() {
179 |         return format!("\"{}\"", arg);
180 |     } else {
181 |         return arg;
182 |     }
183 | }
184 | 
185 | fn format_argument(arg: String) -> String {
186 |     if arg == "--" {
187 |         unsafe {
188 |             DOUBLE_DASH_FOUND = true;
189 |         };
190 |         return arg;
191 |     } else {
192 |         let mut arg = arg;
193 |         if fork::needs_patching() {
194 |             arg = fork::patch_argument(arg);
195 |         }
196 |         arg = translate_path_to_unix(arg);
197 |         arg = escape_characters(arg);
198 |         arg = quote_argument(arg);
199 |         arg
200 |     }
201 | }
202 | 
203 | /// Return `true` if the git command can access remotes and therefore might need
204 | /// the setup of an interactive shell.
205 | fn git_command_needs_interactive_shell() -> bool {
206 |     const CMDS: &[&str] = &["clone", "fetch", "pull", "push", "ls-remote"];
207 |     env::args()
208 |         .skip(1)
209 |         .position(|arg| CMDS.iter().position(|&tcmd| tcmd == arg).is_some())
210 |         .is_some()
211 | }
212 | 
213 | fn use_interactive_shell() -> bool {
214 |     // check for explicit environment variable setting
215 |     if let Ok(interactive_flag) = env::var("WSLGIT_USE_INTERACTIVE_SHELL") {
216 |         if interactive_flag == "false" || interactive_flag == "0" {
217 |             return false;
218 |         } else if interactive_flag == "smart" {
219 |             return git_command_needs_interactive_shell();
220 |         } else {
221 |             return true;
222 |         }
223 |     }
224 |     // check for advanced usage indicated by BASH_ENV and WSLENV contains BASH_ENV
225 |     else if env::var("BASH_ENV").is_ok() {
226 |         if let Ok(wslenv) = env::var("WSLENV") {
227 |             lazy_static! {
228 |                 // BASH_ENV can be first or after another variable.
229 |                 // It can be followed by flags, another variable or be last.
230 |                 static ref BASH_ENV_RE: Regex = Regex::new(r"(?-u)(^|:)BASH_ENV(/|:|$)")
231 |                     .expect("Failed to compile BASH_ENV regex");
232 |             }
233 |             if BASH_ENV_RE.is_match(wslenv.as_bytes()) {
234 |                 return false;
235 |             }
236 |         }
237 |     }
238 |     // default
239 |     git_command_needs_interactive_shell()
240 | }
241 | 
242 | /// Find the working directory by starting from the current directory and applying
243 | /// any paths from `-C` or `--work-tree` arguments.
244 | ///
245 | /// `--git-dir` is ignored, it is assumed that both the work-tree and git-dir
246 | /// are on the same file system/same wsl distribution.
247 | ///
248 | /// Returns the working directory as a String.
249 | fn get_working_directory(current_dir: PathBuf, args: &[String]) -> String {
250 |     let mut working_dir = current_dir;
251 |     let mut work_tree = env::var("GIT_WORK_TREE").unwrap_or("".to_string());
252 | 
253 |     let mut skip_next = false;
254 |     let mut next_is_path = false;
255 | 
256 |     for arg in args {
257 |         if skip_next {
258 |             skip_next = false;
259 |         } else if next_is_path {
260 |             next_is_path = false;
261 |             // let path = PathBuf::from(arg);
262 |             working_dir.push(arg);
263 |         } else if arg == "-c" {
264 |             // `-c` expects a second argument, so skip next argument.
265 |             skip_next = true;
266 |         } else if arg == "-C" {
267 |             // `-C` expects a second argument that is a path
268 |             next_is_path = true;
269 |         } else if arg.starts_with("--work-tree=") {
270 |             work_tree = arg[12..].to_string();
271 |         } else if !arg.starts_with("-") {
272 |             // First argument that doesn't start with '-' (or '--') is the git
273 |             // command; clone, commit etc.
274 |             break;
275 |         }
276 |     }
277 | 
278 |     // Finally apply the path from any "--work-tree" argument on the current working dir
279 |     if work_tree.len() > 0 {
280 |         working_dir.push(work_tree);
281 |     }
282 | 
283 |     return working_dir.to_str().unwrap().to_string();
284 | }
285 | 
286 | /// Try to find the WSL distribution name from the provided `path`.
287 | ///
288 | /// An UNC prefix consists of \\server\share\, which is then followed by a path.
289 | /// When accessing a WSL filesystem using the `\\wsl$\dist\` UNC prefix then the
290 | /// distribution name can be extracted from the second component of the UNC
291 | /// prefix.
292 | ///
293 | /// Returns the distribution name or None.
294 | fn get_wsl_dist_name(path: &str) -> Option {
295 |     const UNC_SERVER_WSL: &str = "\\\\wsl$\\";
296 |     const UNC_SERVER_WSL_LOCALHOST: &str = "\\\\wsl.localhost\\";
297 | 
298 |     let unc_path_without_server: Option<&str> = if path.starts_with(UNC_SERVER_WSL) {
299 |         Some(&path[UNC_SERVER_WSL.len()..])
300 |     } else if path.starts_with(UNC_SERVER_WSL_LOCALHOST) {
301 |         Some(&path[UNC_SERVER_WSL_LOCALHOST.len()..])
302 |     } else {
303 |         None
304 |     };
305 | 
306 |     let wsl_dist_name = match unc_path_without_server {
307 |         Some(p) => {
308 |             // the string p starts with the UNC 'share', which is the wsl dist name
309 |             let (dist_name, _) = p.split_once('\\').unwrap();
310 |             Some(dist_name.to_string())
311 |         }
312 |         None => {
313 |             if let Ok(default_dist) = env::var("WSLGIT_DEFAULT_DIST") {
314 |                 Some(default_dist)
315 |             } else {
316 |                 // Use wsl default dist
317 |                 None
318 |             }
319 |         }
320 |     };
321 | 
322 |     return wsl_dist_name;
323 | }
324 | 
325 | fn enable_logging() -> bool {
326 |     if let Ok(enable_log_flag) = env::var("WSLGIT_ENABLE_LOGGING") {
327 |         if enable_log_flag == "true" || enable_log_flag == "1" {
328 |             return true;
329 |         }
330 |     }
331 |     false
332 | }
333 | 
334 | fn log_arguments(out_args: &Vec) {
335 |     let in_args = env::args().collect::>();
336 |     log(format!("{:?} -> {:?}", in_args, out_args));
337 | }
338 | 
339 | fn log(message: String) {
340 |     let logfile = match env::current_exe() {
341 |         Ok(exe_path) => exe_path
342 |             .parent()
343 |             .unwrap()
344 |             .join("wslgit.log")
345 |             .to_string_lossy()
346 |             .into_owned(),
347 |         Err(e) => {
348 |             eprintln!("Failed to get current exe path: {}", e);
349 |             Path::new("wslgit.log").to_string_lossy().into_owned()
350 |         }
351 |     };
352 | 
353 |     let f = OpenOptions::new()
354 |         .append(true)
355 |         .create(true)
356 |         .open(logfile)
357 |         .unwrap();
358 |     write!(&f, "{}\n", message).unwrap();
359 | }
360 | 
361 | fn main() {
362 |     let mut cmd_args = Vec::new();
363 |     let mut git_args: Vec = vec![String::from("git")];
364 |     git_args.extend(env::args().skip(1).map(format_argument));
365 | 
366 |     let git_cmd: String = git_args.join(" ");
367 | 
368 |     let curr_dir = env::current_dir().unwrap();
369 |     // Assumes that the first element in args is the executable
370 |     let args: Vec = env::args().skip(1).collect();
371 |     let working_directory = get_working_directory(curr_dir, &args);
372 |     match get_wsl_dist_name(&working_directory) {
373 |         Some(wsl_dist) => {
374 |             cmd_args.push("--distribution".to_string());
375 |             cmd_args.push(wsl_dist.to_string());
376 |         }
377 |         None => {}
378 |     }
379 | 
380 |     // build the command arguments that are passed to wsl.exe
381 |     cmd_args.push("-e".to_string());
382 |     cmd_args.push(BASH_EXECUTABLE.to_string());
383 |     if use_interactive_shell() {
384 |         cmd_args.push("-ic".to_string());
385 |     } else {
386 |         cmd_args.push("-c".to_string());
387 |     }
388 |     cmd_args.push(git_cmd.clone());
389 | 
390 |     if enable_logging() {
391 |         log(format!(
392 |             "wslgit version {}, current_dir {}",
393 |             VERSION,
394 |             env::current_dir().unwrap().to_str().unwrap().to_string()
395 |         ));
396 |         log_arguments(&cmd_args);
397 |     }
398 | 
399 |     wsl::share_val("WSLGIT", "1", false);
400 | 
401 |     // setup the git subprocess launched inside WSL
402 |     let mut git_proc_setup = Command::new("wsl");
403 |     git_proc_setup.args(&cmd_args);
404 | 
405 |     let status;
406 | 
407 |     // add git commands that must use translate_path_to_win
408 |     const TRANSLATED_CMDS: &[&str] = &["rev-parse", "remote", "init"];
409 | 
410 |     let translate_output = env::args()
411 |         .skip(1)
412 |         .position(|arg| {
413 |             TRANSLATED_CMDS
414 |                 .iter()
415 |                 .position(|&tcmd| tcmd == arg)
416 |                 .is_some()
417 |         })
418 |         .is_some();
419 | 
420 |     if translate_output {
421 |         // run the subprocess and capture its output
422 |         let git_proc = git_proc_setup
423 |             .stdout(Stdio::piped())
424 |             .spawn()
425 |             .expect(&format!("Failed to execute command '{}'", &git_cmd));
426 |         let output = git_proc
427 |             .wait_with_output()
428 |             .expect(&format!("Failed to wait for git call '{}'", &git_cmd));
429 |         status = output.status;
430 |         let output_bytes = output.stdout;
431 |         let mut stdout = io::stdout();
432 |         stdout
433 |             .write_all(&translate_path_to_win(&output_bytes))
434 |             .expect("Failed to write git output");
435 |         stdout.flush().expect("Failed to flush output");
436 |     } else {
437 |         // run the subprocess without capturing its output
438 |         // the output of the subprocess is passed through unchanged
439 |         status = git_proc_setup
440 |             .status()
441 |             .expect(&format!("Failed to execute command '{}'", &git_cmd));
442 |     }
443 | 
444 |     // forward any exit code
445 |     if let Some(exit_code) = status.code() {
446 |         std::process::exit(exit_code);
447 |     }
448 | }
449 | 
450 | #[cfg(test)]
451 | mod tests {
452 |     use super::*;
453 | 
454 |     #[test]
455 |     fn use_interactive_shell_test() {
456 |         // default
457 |         env::remove_var("WSLGIT_USE_INTERACTIVE_SHELL");
458 |         env::remove_var("BASH_ENV");
459 |         env::remove_var("WSLENV");
460 | 
461 |         // It is not possible to change env::args, so the arguments that are matched
462 |         // in git_command_needs_interactive_shell() are the arguments to cargo,
463 |         // which does not match any of the git commands that needs interactive shell.
464 |         let default_value = false;
465 | 
466 |         assert_eq!(use_interactive_shell(), default_value);
467 | 
468 |         // disable using WSLGIT_USE_INTERACTIVE_SHELL set to 'false' or '0'
469 |         env::set_var("WSLGIT_USE_INTERACTIVE_SHELL", "false");
470 |         assert_eq!(use_interactive_shell(), false);
471 |         env::set_var("WSLGIT_USE_INTERACTIVE_SHELL", "0");
472 |         assert_eq!(use_interactive_shell(), false);
473 | 
474 |         // enable using WSLGIT_USE_INTERACTIVE_SHELL set to anything but 'false' and '0'
475 |         env::set_var("WSLGIT_USE_INTERACTIVE_SHELL", "true");
476 |         assert_eq!(use_interactive_shell(), true);
477 |         env::set_var("WSLGIT_USE_INTERACTIVE_SHELL", "1");
478 |         assert_eq!(use_interactive_shell(), true);
479 | 
480 |         env::remove_var("WSLGIT_USE_INTERACTIVE_SHELL");
481 | 
482 |         // just having BASH_ENV is not enough
483 |         env::set_var("BASH_ENV", "something");
484 |         assert_eq!(use_interactive_shell(), default_value);
485 | 
486 |         // BASH_ENV must also be in WSLENV
487 |         env::set_var("WSLENV", "BASH_ENV");
488 |         assert_eq!(use_interactive_shell(), false);
489 |         env::set_var("WSLENV", "BASH_ENV/up");
490 |         assert_eq!(use_interactive_shell(), false);
491 |         env::set_var("WSLENV", "BASH_ENV:TMP");
492 |         assert_eq!(use_interactive_shell(), false);
493 |         env::set_var("WSLENV", "BASH_ENV/up:TMP");
494 |         assert_eq!(use_interactive_shell(), false);
495 |         env::set_var("WSLENV", "TMP:BASH_ENV");
496 |         assert_eq!(use_interactive_shell(), false);
497 |         env::set_var("WSLENV", "TMP:BASH_ENV/up");
498 |         assert_eq!(use_interactive_shell(), false);
499 |         env::set_var("WSLENV", "TMP:BASH_ENV:TMP");
500 |         assert_eq!(use_interactive_shell(), false);
501 |         env::set_var("WSLENV", "TMP:BASH_ENV/up:TMP");
502 |         assert_eq!(use_interactive_shell(), false);
503 | 
504 |         // WSLGIT_USE_INTERACTIVE_SHELL overrides BASH_ENV
505 |         env::set_var("WSLGIT_USE_INTERACTIVE_SHELL", "true");
506 |         assert_eq!(use_interactive_shell(), true);
507 |         env::remove_var("WSLGIT_USE_INTERACTIVE_SHELL");
508 | 
509 |         env::set_var("WSLENV", "NOT_BASH_ENV/up");
510 |         assert_eq!(use_interactive_shell(), default_value);
511 |     }
512 | 
513 |     #[test]
514 |     fn escape_characters() {
515 |         assert_eq!(
516 |             super::escape_characters("ab\ncdef".to_string()),
517 |             "ab$\'\n\'cdef"
518 |         );
519 |         assert_eq!(
520 |             super::escape_characters("ab\ncd ef".to_string()),
521 |             "ab$\'\n\'cd ef"
522 |         );
523 |         assert_eq!(
524 |             super::escape_characters("--pretty=format:%H±.%aN±.%aE±.%at±.%cN±.%cE±.%ct±.%P±.%B".to_string()),
525 |             "--pretty=format:%H±.%aN±.%aE±.%at±.%cN±.%cE±.%ct±.%P±.%B\\<\\!--RevisionMessageEnd--\\>"
526 |         );
527 |         // Long arguments with newlines...
528 |         assert_eq!(
529 |             super::escape_characters("--ab\ncdef".to_string()),
530 |             "--ab$\'\n\'cdef"
531 |         );
532 |         assert_eq!(
533 |             super::escape_characters("--ab\ncd ef".to_string()),
534 |             "--ab$\'\n\'cd ef"
535 |         );
536 |         assert_eq!(
537 |             super::escape_characters("ab\"cd ef\"".to_string()),
538 |             "ab\\\"cd ef\\\""
539 |         );
540 |     }
541 | 
542 |     #[test]
543 |     fn quote_argument_with_invalid_character() {
544 |         assert_eq!(quote_argument("abc def".to_string()), "\"abc def\"");
545 |         assert_eq!(quote_argument("abc(def".to_string()), "\"abc(def\"");
546 |         assert_eq!(quote_argument("abc)def".to_string()), "\"abc)def\"");
547 |         assert_eq!(quote_argument("abc|def".to_string()), "\"abc|def\"");
548 |         assert_eq!(
549 |             quote_argument("\\\"abc def\\\"".to_string()),
550 |             "\"\\\"abc def\\\"\""
551 |         );
552 |         assert_eq!(
553 |             quote_argument("user.(name|email)".to_string()),
554 |             "\"user.(name|email)\""
555 |         );
556 |     }
557 | 
558 |     #[test]
559 |     fn quote_long_argument_with_invalid_character() {
560 |         assert_eq!(quote_argument("--abc def".to_string()), "\"--abc def\"");
561 |         assert_eq!(quote_argument("--abc=def".to_string()), "--abc=def");
562 |         assert_eq!(quote_argument("--abc=d ef".to_string()), "\"--abc=d ef\"");
563 |         assert_eq!(quote_argument("--abc=d(ef".to_string()), "\"--abc=d(ef\"");
564 |         assert_eq!(quote_argument("--abc=d)ef".to_string()), "\"--abc=d)ef\"");
565 |         assert_eq!(quote_argument("--abc=d|ef".to_string()), "\"--abc=d|ef\"");
566 |         assert_eq!(
567 |             quote_argument("--pretty=format:a(b|c)d".to_string()),
568 |             "\"--pretty=format:a(b|c)d\""
569 |         );
570 |         assert_eq!(
571 |             quote_argument("--pretty=format:a (b | c) d".to_string()),
572 |             "\"--pretty=format:a (b | c) d\""
573 |         );
574 |         // Long arguments with invalid characters in argument name
575 |         assert_eq!(quote_argument("--abc(def".to_string()), "\"--abc(def\"");
576 |         assert_eq!(quote_argument("--abc)def".to_string()), "\"--abc)def\"");
577 |         assert_eq!(quote_argument("--abc|def".to_string()), "\"--abc|def\"");
578 |     }
579 | 
580 |     #[test]
581 |     fn quote_empty_argument() {
582 |         assert_eq!(quote_argument("".to_string()), "\"\"");
583 |     }
584 | 
585 |     #[test]
586 |     fn win_to_unix_path_trans() {
587 |         assert_eq!(
588 |             translate_path_to_unix("D:\\test\\file.txt".to_string()),
589 |             "$(wslpath 'D:\\test\\file.txt')"
590 |         );
591 |         assert_eq!(
592 |             translate_path_to_unix("D:/test/file.txt".to_string()),
593 |             "$(wslpath 'D:/test/file.txt')"
594 |         );
595 |         assert_eq!(
596 |             translate_path_to_unix(" D:\\test\\file.txt".to_string()),
597 |             " $(wslpath 'D:\\test\\file.txt')"
598 |         );
599 |         assert_eq!(
600 |             translate_path_to_unix(" D:/test/file.txt".to_string()),
601 |             " $(wslpath 'D:/test/file.txt')"
602 |         );
603 |         assert_eq!(
604 |             translate_path_to_unix(":main:D:\\test\\file.txt".to_string()),
605 |             ":main:$(wslpath 'D:\\test\\file.txt')"
606 |         );
607 |         assert_eq!(
608 |             translate_path_to_unix(":main:D:/test/file.txt".to_string()),
609 |             ":main:$(wslpath 'D:/test/file.txt')"
610 |         );
611 |         assert_eq!(
612 |             translate_path_to_unix("1,1:D:\\test\\file.txt".to_string()),
613 |             "1,1:$(wslpath 'D:\\test\\file.txt')"
614 |         );
615 |         assert_eq!(
616 |             translate_path_to_unix("1,1:D:/test/file.txt".to_string()),
617 |             "1,1:$(wslpath 'D:/test/file.txt')"
618 |         );
619 |         assert_eq!(
620 |             translate_path_to_unix("C:\\Users\\test user\\my file.txt".to_string()),
621 |             "$(wslpath 'C:\\Users\\test user\\my file.txt')"
622 |         );
623 |         assert_eq!(
624 |             translate_path_to_unix("C:/Users/test user/my file.txt".to_string()),
625 |             "$(wslpath 'C:/Users/test user/my file.txt')"
626 |         );
627 |         assert_eq!(
628 |             translate_path_to_unix("\\\\path\\to\\file.txt".to_string()),
629 |             "$(wslpath '\\\\path\\to\\file.txt')"
630 |         );
631 |         // $ git commit --file="//wsl$/Ubuntu-20.04/home/"
632 |         assert_eq!(
633 |             translate_path_to_unix("\\\\wsl$\\Ubuntu-20.04\\home".to_string()),
634 |             "$(wslpath '\\\\wsl$\\Ubuntu-20.04\\home')"
635 |         );
636 |         assert_eq!(
637 |             translate_path_to_unix("//wsl$/Ubuntu-20.04/home".to_string()),
638 |             "$(wslpath '//wsl$/Ubuntu-20.04/home')"
639 |         );
640 |     }
641 | 
642 |     #[test]
643 |     fn unix_to_win_path_trans() {
644 |         let check_wslpath = Command::new("wsl")
645 |             .arg("-e")
646 |             .arg(BASH_EXECUTABLE)
647 |             .arg("-c")
648 |             .arg("wslpath C:\\")
649 |             .output();
650 |         let prefix_bytes = translate_path_to_win(b"/");
651 |         let prefix = std::str::from_utf8(&prefix_bytes).unwrap();
652 |         if check_wslpath.is_err()
653 |             || !check_wslpath.expect("bash output").status.success()
654 |             || prefix == ""
655 |         {
656 |             // Skip test if `wslpath` is not available (e.g. in CI).
657 |             // Either bash was not found, or running `wslpath` returned an error
658 |             // code or an empty string.
659 |             print!("SKIPPING TEST ... ");
660 |             return;
661 |         }
662 |         // Since Windows 10 2004 `wslpath` can only translate existing
663 |         // unix paths to windows paths, so we need to test real filenames.
664 |         // (see https://github.com/microsoft/WSL/issues/4908)
665 |         Command::new("wsl")
666 |             .arg("-e")
667 |             .arg(BASH_EXECUTABLE)
668 |             .arg("-c")
669 |             .arg("touch '/tmp/wslgit test file'")
670 |             .output()
671 |             .expect("creating tmp test file");
672 |         assert_eq!(
673 |             std::str::from_utf8(&translate_path_to_win(b"/tmp/wslgit test file")).unwrap(),
674 |             format!("{}tmp\\wslgit test file", prefix)
675 |         );
676 |         assert_eq!(
677 |             std::str::from_utf8(&translate_path_to_win(
678 |                 b"origin  /tmp/wslgit test file (fetch)"
679 |             ))
680 |             .unwrap(),
681 |             format!("origin  {}tmp\\wslgit test file (fetch)", prefix)
682 |         );
683 |         assert_eq!(
684 |             std::str::from_utf8(&translate_path_to_win(b"mirror  /tmp/wslgit test file (fetch)\nmirror  /tmp/wslgit test file (push)\n")).unwrap(),
685 |             format!("mirror  {0}tmp\\wslgit test file (fetch)\nmirror  {0}tmp\\wslgit test file (push)\n", prefix)
686 |         );
687 |         Command::new("wsl")
688 |             .arg("-e")
689 |             .arg(BASH_EXECUTABLE)
690 |             .arg("-c")
691 |             .arg("rm '/tmp/wslgit test file'")
692 |             .output()
693 |             .expect("deleting tmp test file");
694 |     }
695 | 
696 |     #[test]
697 |     fn relative_path_translation() {
698 |         unsafe {
699 |             DOUBLE_DASH_FOUND = false;
700 |         }
701 | 
702 |         assert_eq!(
703 |             translate_path_to_unix("src\\main.rs".to_string()),
704 |             "src/main.rs"
705 |         );
706 |         assert_eq!(
707 |             translate_path_to_unix("src/main.rs".to_string()),
708 |             "src/main.rs"
709 |         );
710 |         assert_eq!(
711 |             translate_path_to_unix(".\\src\\main.rs".to_string()),
712 |             "./src/main.rs"
713 |         );
714 |         assert_eq!(
715 |             translate_path_to_unix("./src/main.rs".to_string()),
716 |             "./src/main.rs"
717 |         );
718 |         assert_eq!(
719 |             translate_path_to_unix("..\\wslgit\\src\\main.rs".to_string()),
720 |             "../wslgit/src/main.rs"
721 |         );
722 |         assert_eq!(
723 |             translate_path_to_unix("../wslgit/src/main.rs".to_string()),
724 |             "../wslgit/src/main.rs"
725 |         );
726 | 
727 |         assert_eq!(
728 |             translate_path_to_unix("prefix:..\\wslgit\\src\\main.rs:postfix".to_string()),
729 |             "prefix:../wslgit/src/main.rs:postfix"
730 |         );
731 | 
732 |         assert_eq!(
733 |             translate_path_to_unix("^remote\\..*".to_string()),
734 |             "^remote\\..*"
735 |         );
736 | 
737 |         assert_eq!(
738 |             translate_path_to_unix("\"prefix:..\\wslgit\\src\\main.rs\"".to_string()),
739 |             "\"prefix:../wslgit/src/main.rs\""
740 |         );
741 |     }
742 | 
743 |     #[test]
744 |     fn relative_path_after_double_dash() {
745 |         unsafe {
746 |             DOUBLE_DASH_FOUND = false;
747 |         }
748 |         assert_eq!(format_argument("--".to_string()), "--");
749 |         assert_eq!(unsafe { DOUBLE_DASH_FOUND }, true);
750 | 
751 |         unsafe {
752 |             DOUBLE_DASH_FOUND = false;
753 |         }
754 |         assert_eq!(format_argument("-".to_string()), "-");
755 |         assert_eq!(unsafe { DOUBLE_DASH_FOUND }, false);
756 | 
757 |         unsafe {
758 |             DOUBLE_DASH_FOUND = false;
759 |         }
760 |         assert_eq!(
761 |             format_argument("path\\to\\nonexisting\\file.txt".to_string()),
762 |             "path\\to\\nonexisting\\file.txt"
763 |         );
764 | 
765 |         unsafe {
766 |             DOUBLE_DASH_FOUND = true;
767 |         }
768 |         assert_eq!(
769 |             format_argument("path\\to\\nonexisting\\file.txt".to_string()),
770 |             "path/to/nonexisting/file.txt"
771 |         );
772 |     }
773 | 
774 |     #[test]
775 |     fn git_url_translation() {
776 |         // URLs with ssh, git, http[s] or ftp[s] prefix should not be translated
777 |         assert_eq!(
778 |             translate_path_to_unix("ssh://user@host.xz:22/path/to/repo.git/".to_string()),
779 |             "ssh://user@host.xz:22/path/to/repo.git/"
780 |         );
781 |         assert_eq!(
782 |             translate_path_to_unix("ssh://user@host.xz/path/to/repo.git/".to_string()),
783 |             "ssh://user@host.xz/path/to/repo.git/"
784 |         );
785 |         assert_eq!(
786 |             translate_path_to_unix("ssh://host.xz/path/to/repo.git/".to_string()),
787 |             "ssh://host.xz/path/to/repo.git/"
788 |         );
789 |         assert_eq!(
790 |             translate_path_to_unix("user@host.xz/path/to/repo.git/".to_string()),
791 |             "user@host.xz/path/to/repo.git/"
792 |         );
793 |         assert_eq!(
794 |             translate_path_to_unix("host.xz/path/to/repo.git/".to_string()),
795 |             "host.xz/path/to/repo.git/"
796 |         );
797 | 
798 |         assert_eq!(
799 |             translate_path_to_unix("git://host.xz/path/to/repo.git/".to_string()),
800 |             "git://host.xz/path/to/repo.git/"
801 |         );
802 |         assert_eq!(
803 |             translate_path_to_unix("http://host.xz/path/to/repo.git/".to_string()),
804 |             "http://host.xz/path/to/repo.git/"
805 |         );
806 |         assert_eq!(
807 |             translate_path_to_unix("https://host.xz/path/to/repo.git/".to_string()),
808 |             "https://host.xz/path/to/repo.git/"
809 |         );
810 |         assert_eq!(
811 |             translate_path_to_unix("ftp://host.xz/path/to/repo.git/".to_string()),
812 |             "ftp://host.xz/path/to/repo.git/"
813 |         );
814 |         assert_eq!(
815 |             translate_path_to_unix("ftps://host.xz/path/to/repo.git/".to_string()),
816 |             "ftps://host.xz/path/to/repo.git/"
817 |         );
818 | 
819 |         assert_eq!(
820 |             translate_path_to_unix("file:///path/to/repo.git/".to_string()),
821 |             "file:///path/to/repo.git/"
822 |         );
823 |         assert_eq!(
824 |             translate_path_to_unix("file://C:/path/to/repo.git/".to_string()),
825 |             "file://$(wslpath 'C:/path/to/repo.git/')"
826 |         );
827 |         assert_eq!(
828 |             translate_path_to_unix("file://C:\\path\\to\\repo.git\\".to_string()),
829 |             "file://$(wslpath 'C:\\path\\to\\repo.git\\')"
830 |         );
831 | 
832 |         assert_eq!(
833 |             translate_path_to_unix("file://path/to/repo.git/".to_string()),
834 |             "file://path/to/repo.git/"
835 |         );
836 |         assert_eq!(
837 |             translate_path_to_unix("file://path\\to\\repo.git\\".to_string()),
838 |             "file://path/to/repo.git/"
839 |         );
840 |     }
841 | 
842 |     #[test]
843 |     fn arguments_path_translation() {
844 |         assert_eq!(
845 |             translate_path_to_unix("--file=C:\\some\\path.txt".to_owned()),
846 |             "--file=$(wslpath 'C:\\some\\path.txt')"
847 |         );
848 |         assert_eq!(
849 |             translate_path_to_unix("--file=C:/some/path.txt".to_owned()),
850 |             "--file=$(wslpath 'C:/some/path.txt')"
851 |         );
852 | 
853 |         assert_eq!(
854 |             translate_path_to_unix("-c core.editor=C:\\some\\editor.exe".to_owned()),
855 |             "-c core.editor=$(wslpath 'C:\\some\\editor.exe')"
856 |         );
857 |         assert_eq!(
858 |             translate_path_to_unix("-c core.editor=C:/some/editor.exe".to_owned()),
859 |             "-c core.editor=$(wslpath 'C:/some/editor.exe')"
860 |         );
861 | 
862 |         assert_eq!(
863 |             translate_path_to_unix(
864 |                 "-c \"credential.helper=C:/Program Files/SmartGit/lib/credentials.cmd\"".to_owned()
865 |             ),
866 |             "-c \"credential.helper=$(wslpath 'C:/Program Files/SmartGit/lib/credentials.cmd')\""
867 |         );
868 |     }
869 | 
870 |     #[test]
871 |     fn get_working_directory_test() {
872 |         env::remove_var("GIT_WORK_TREE");
873 | 
874 |         let args: Vec = vec![];
875 |         assert_eq!(
876 |             get_working_directory(PathBuf::from("C:\\repo\\"), &args),
877 |             "C:\\repo\\".to_string()
878 |         );
879 |         assert_eq!(
880 |             get_working_directory(PathBuf::from("\\\\wsl$\\dist-name\\repo\\"), &args),
881 |             "\\\\wsl$\\dist-name\\repo\\".to_string()
882 |         );
883 | 
884 |         let args: Vec = vec!["cmd".into()];
885 |         assert_eq!(
886 |             get_working_directory(PathBuf::from("C:\\repo\\"), &args),
887 |             "C:\\repo\\".to_string()
888 |         );
889 | 
890 |         let args: Vec = vec!["-c".into(), "arg".into(), "cmd".into()];
891 |         assert_eq!(
892 |             get_working_directory(PathBuf::from("C:\\repo\\"), &args),
893 |             "C:\\repo\\".to_string()
894 |         );
895 | 
896 |         let args: Vec = vec![
897 |             "-c".into(),
898 |             "arg".into(),
899 |             "-C".into(),
900 |             "relative".into(),
901 |             "cmd".into(),
902 |         ];
903 |         assert_eq!(
904 |             get_working_directory(PathBuf::from("C:\\repo\\"), &args),
905 |             "C:\\repo\\relative".to_string()
906 |         );
907 | 
908 |         let args: Vec = vec![
909 |             "-c".into(),
910 |             "arg".into(),
911 |             "-C".into(),
912 |             "C:\\absolute".into(),
913 |             "cmd".into(),
914 |         ];
915 |         assert_eq!(
916 |             get_working_directory(PathBuf::from("C:\\repo\\"), &args),
917 |             "C:\\absolute".to_string()
918 |         );
919 | 
920 |         let args: Vec = vec![
921 |             "-c".into(),
922 |             "arg".into(),
923 |             "-C".into(),
924 |             "a".into(),
925 |             "-C".into(),
926 |             "b".into(),
927 |             "cmd".into(),
928 |         ];
929 |         assert_eq!(
930 |             get_working_directory(PathBuf::from("C:\\repo\\"), &args),
931 |             "C:\\repo\\a\\b".to_string()
932 |         );
933 | 
934 |         env::set_var("GIT_WORK_TREE", "b");
935 |         let args: Vec = vec![
936 |             "-c".into(),
937 |             "arg".into(),
938 |             "-C".into(),
939 |             "a".into(),
940 |             "cmd".into(),
941 |         ];
942 |         assert_eq!(
943 |             get_working_directory(PathBuf::from("C:\\repo\\"), &args),
944 |             "C:\\repo\\a\\b".to_string()
945 |         );
946 | 
947 |         env::set_var("GIT_WORK_TREE", "b");
948 |         let args: Vec = vec![
949 |             "-c".into(),
950 |             "arg".into(),
951 |             "-C".into(),
952 |             "a".into(),
953 |             "--work-tree=c".into(),
954 |             "cmd".into(),
955 |         ];
956 |         assert_eq!(
957 |             get_working_directory(PathBuf::from("C:\\repo\\"), &args),
958 |             "C:\\repo\\a\\c".to_string()
959 |         );
960 |     }
961 | 
962 |     #[test]
963 |     fn wsl_dist_name() {
964 |         env::remove_var("WSLGIT_DEFAULT_DIST");
965 |         assert_eq!(
966 |             get_wsl_dist_name(&r"\\wsl$\dist-name\a\b\c".to_string()),
967 |             Some("dist-name".to_string())
968 |         );
969 |         assert_eq!(
970 |             get_wsl_dist_name(&r"\\wsl.localhost\dist-name\a\b\c".to_string()),
971 |             Some("dist-name".to_string())
972 |         );
973 |         assert_eq!(
974 |             get_wsl_dist_name(&r"\\server\dist-name\a\b\c".to_string()),
975 |             None
976 |         );
977 |         assert_eq!(get_wsl_dist_name(&r"C:\a\b\c".to_string()), None);
978 |     }
979 | 
980 |     #[test]
981 |     fn wsl_default_dist_name() {
982 |         env::set_var("WSLGIT_DEFAULT_DIST", "some-dist");
983 |         assert_eq!(
984 |             get_wsl_dist_name(&r"\\wsl$\dist-name\a\b\c".to_string()),
985 |             Some("dist-name".to_string())
986 |         );
987 |         assert_eq!(
988 |             get_wsl_dist_name(&r"\\server\dist-name\a\b\c".to_string()),
989 |             Some("some-dist".to_string())
990 |         );
991 |         assert_eq!(
992 |             get_wsl_dist_name(&r"C:\a\b\c".to_string()),
993 |             Some("some-dist".to_string())
994 |         );
995 |     }
996 | }
997 | 


--------------------------------------------------------------------------------
/src/wsl.rs:
--------------------------------------------------------------------------------
 1 | use std::env;
 2 | 
 3 | /// Share a value to WSL by using an environment variable and `WSLENV`.
 4 | ///
 5 | /// * `key` - Name to use for the environment variable.
 6 | /// * `value` - The value of the environment variable.
 7 | /// * `translate_path` - If `true` will append `/p` to the variable name when added to `WSLENV`.
 8 | pub fn share_val(key: &str, value: &str, translate_path: bool) {
 9 |     env::set_var(key, value);
10 | 
11 |     let wslenv_key = if translate_path {
12 |         format!("{}/p", key)
13 |     } else {
14 |         key.to_owned()
15 |     };
16 | 
17 |     let wslenv = match env::var("WSLENV") {
18 |         Ok(original_wslenv) => {
19 |             // WSLENV exists, add new variable only once
20 |             let re: regex::Regex =
21 |                 regex::Regex::new(format!(r"(^|:){}(/|:|$)", wslenv_key).as_str())
22 |                     .expect("Failed to compile regex");
23 | 
24 |             if original_wslenv.is_empty() {
25 |                 format!("{}", wslenv_key)
26 |             } else if re.is_match(original_wslenv.as_str()) == false {
27 |                 format!("{}:{}", original_wslenv, wslenv_key)
28 |             } else {
29 |                 // Don't add anything to WSLENV
30 |                 original_wslenv
31 |             }
32 |         }
33 |         Err(_e) => {
34 |             // No WSLENV
35 |             format!("{}", wslenv_key)
36 |         }
37 |     };
38 | 
39 |     env::set_var("WSLENV", wslenv);
40 | }
41 | 
42 | #[cfg(test)]
43 | mod tests {
44 |     use super::*;
45 | 
46 |     #[test]
47 |     fn share_variable_to_wsl() {
48 |         // No WSLENV
49 |         env::remove_var("WSLENV");
50 |         share_val("VAR1", "1", false);
51 |         assert_eq!("1", env::var("VAR1").unwrap());
52 |         assert_eq!("VAR1", env::var("WSLENV").unwrap());
53 | 
54 |         // Empty WSLENV
55 |         env::set_var("WSLENV", "");
56 |         share_val("VAR2", "2", false);
57 |         assert_eq!("2", env::var("VAR2").unwrap());
58 |         assert_eq!("VAR2", env::var("WSLENV").unwrap());
59 | 
60 |         // Non-empty WSLENV
61 |         env::set_var("WSLENV", "A");
62 |         share_val("VAR3", "3", false);
63 |         assert_eq!("3", env::var("VAR3").unwrap());
64 |         assert_eq!("A:VAR3", env::var("WSLENV").unwrap());
65 | 
66 |         // Variable exists and already in WSLENV
67 |         env::set_var("VAR4", "0");
68 |         env::set_var("WSLENV", "VAR1:VAR2:VAR3:VAR4:VAR5");
69 |         share_val("VAR4", "4", false);
70 |         assert_eq!("4", env::var("VAR4").unwrap());
71 |         assert_eq!("VAR1:VAR2:VAR3:VAR4:VAR5", env::var("WSLENV").unwrap());
72 | 
73 |         // Variable exists and already in WSLENV but without /p flag
74 |         env::set_var("VAR5", "0");
75 |         env::set_var("WSLENV", "VAR1:VAR2:VAR3:VAR4:VAR5");
76 |         share_val("VAR5", "5", true);
77 |         assert_eq!("5", env::var("VAR5").unwrap());
78 |         assert_eq!(
79 |             "VAR1:VAR2:VAR3:VAR4:VAR5:VAR5/p",
80 |             env::var("WSLENV").unwrap()
81 |         );
82 |     }
83 | }
84 | 


--------------------------------------------------------------------------------
/tests/integration.rs:
--------------------------------------------------------------------------------
  1 | extern crate assert_cmd;
  2 | extern crate predicates;
  3 | 
  4 | #[cfg(test)]
  5 | mod integration {
  6 |     use assert_cmd::prelude::*;
  7 |     use predicates::prelude::*;
  8 |     use std::env;
  9 |     use std::process::Command;
 10 | 
 11 |     #[test]
 12 |     fn simple_argument() {
 13 |         Command::cargo_bin(env!("CARGO_PKG_NAME"))
 14 |             .unwrap()
 15 |             .arg("--version")
 16 |             .env("WSLGIT_USE_INTERACTIVE_SHELL", "false")
 17 |             .assert()
 18 |             .success()
 19 |             .stdout(predicate::str::contains("git version"));
 20 |     }
 21 | 
 22 |     #[test]
 23 |     fn argument_with_invalid_characters() {
 24 |         // https://github.com/andy-5/wslgit/issues/54
 25 |         Command::cargo_bin(env!("CARGO_PKG_NAME"))
 26 |             .unwrap()
 27 |             .args(&["config", "--get-regex", "user.(name|email)"])
 28 |             .env("WSLGIT_USE_INTERACTIVE_SHELL", "false")
 29 |             .assert()
 30 |             .success()
 31 |             .stdout(predicate::str::contains("user.name"))
 32 |             .stdout(predicate::str::contains("user.email"));
 33 |     }
 34 | 
 35 |     #[test]
 36 |     fn quote_characters_in_argument() {
 37 |         Command::cargo_bin(env!("CARGO_PKG_NAME"))
 38 |             .unwrap()
 39 |             .args(&["log", "-n1", "--pretty=format:\"(X|Y)\""])
 40 |             .env("WSLGIT_USE_INTERACTIVE_SHELL", "false")
 41 |             .assert()
 42 |             .success()
 43 |             .stdout("\"(X|Y)\"");
 44 |     }
 45 | 
 46 |     #[test]
 47 |     fn quote_characters_and_spaces() {
 48 |         Command::cargo_bin(env!("CARGO_PKG_NAME"))
 49 |             .unwrap()
 50 |             .args(&["log", "-n1", "--pretty=format:\"( X | Y )\""])
 51 |             .env("WSLGIT_USE_INTERACTIVE_SHELL", "false")
 52 |             .assert()
 53 |             .success()
 54 |             .stdout("\"( X | Y )\"");
 55 |     }
 56 | 
 57 |     #[test]
 58 |     fn argument_with_newline() {
 59 |         Command::cargo_bin(env!("CARGO_PKG_NAME"))
 60 |             .unwrap()
 61 |             .args(&["log", "-n1", "--pretty=format:ab\ncd"])
 62 |             .env("WSLGIT_USE_INTERACTIVE_SHELL", "false")
 63 |             .assert()
 64 |             .success()
 65 |             .stdout("ab\ncd");
 66 |     }
 67 | 
 68 |     #[test]
 69 |     fn short_argument_with_parameter_after_space() {
 70 |         // This is really stupid, hopefully first line of Cargo.toml won't change.
 71 |         Command::cargo_bin(env!("CARGO_PKG_NAME"))
 72 |             .unwrap()
 73 |             .args(&["log", "-n1", "-L 1,1:Cargo.toml"])
 74 |             .env("WSLGIT_USE_INTERACTIVE_SHELL", "false")
 75 |             .assert()
 76 |             .success()
 77 |             .stdout(predicate::str::contains(
 78 |                 "diff --git a/Cargo.toml b/Cargo.toml",
 79 |             ))
 80 |             .stdout(predicate::str::contains("@@ -0,0 +1,1 @@"));
 81 |     }
 82 | 
 83 |     #[test]
 84 |     fn long_argument_with_invalid_characters_and_spaces() {
 85 |         Command::cargo_bin(env!("CARGO_PKG_NAME"))
 86 |             .unwrap()
 87 |             .args(&["log", "-n1", "--pretty=format:"])
 88 |             .env("WSLGIT_USE_INTERACTIVE_SHELL", "false")
 89 |             .assert()
 90 |             .success()
 91 |             .stdout("");
 92 | 
 93 |         Command::cargo_bin(env!("CARGO_PKG_NAME"))
 94 |             .unwrap()
 95 |             .args(&["log", "-n1", "--pretty=format:a ( b | c )"])
 96 |             .env("WSLGIT_USE_INTERACTIVE_SHELL", "false")
 97 |             .assert()
 98 |             .success()
 99 |             .stdout("a ( b | c )");
100 | 
101 |         Command::cargo_bin(env!("CARGO_PKG_NAME"))
102 |             .unwrap()
103 |             .args(&[
104 |                 "for-each-ref",
105 |                 "refs/tags",
106 |                 "--format=%(refname) %(objectname)",
107 |             ])
108 |             .env("WSLGIT_USE_INTERACTIVE_SHELL", "false")
109 |             .assert()
110 |             .success()
111 |             .stdout(predicate::str::contains(
112 |                 "refs/tags/v0.1.0 c313ea9f9667e346ace079b47dc0d9f991fb5ab7",
113 |             ))
114 |             .stdout(predicate::str::contains(
115 |                 "refs/tags/v0.2.0 43e0817f6c711abbcc5fe20bf7656fd26193fc0f",
116 |             ));
117 |     }
118 | 
119 |     #[test]
120 |     fn long_argument_with_invalid_characters_no_spaces() {
121 |         Command::cargo_bin(env!("CARGO_PKG_NAME"))
122 |             .unwrap()
123 |             .args(&["log", "-n1", "--pretty=format:a(b|c)"])
124 |             .env("WSLGIT_USE_INTERACTIVE_SHELL", "false")
125 |             .assert()
126 |             .success()
127 |             .stdout("a(b|c)");
128 | 
129 |         Command::cargo_bin(env!("CARGO_PKG_NAME"))
130 |             .unwrap()
131 |             .args(&[
132 |                 "for-each-ref",
133 |                 "refs/tags",
134 |                 "--format=%(refname)%(objectname)",
135 |             ])
136 |             .env("WSLGIT_USE_INTERACTIVE_SHELL", "false")
137 |             .assert()
138 |             .success()
139 |             .stdout(predicate::str::contains(
140 |                 "refs/tags/v0.1.0c313ea9f9667e346ace079b47dc0d9f991fb5ab7",
141 |             ))
142 |             .stdout(predicate::str::contains(
143 |                 "refs/tags/v0.2.043e0817f6c711abbcc5fe20bf7656fd26193fc0f",
144 |             ));
145 |     }
146 | 
147 |     #[test]
148 |     fn long_argument() {
149 |         // https://github.com/andy-5/wslgit/issues/46
150 |         Command::cargo_bin(env!("CARGO_PKG_NAME"))
151 |             .unwrap()
152 |             .args(&[
153 |                 "log",
154 |                 "-n1",
155 |                 "--format=%x3c%x2ff%x3e%n%x3cr%x3e 01234%n%x3ca%x3e abcd",
156 |             ])
157 |             .env("WSLGIT_USE_INTERACTIVE_SHELL", "false")
158 |             .assert()
159 |             .success()
160 |             .stdout("\n 01234\n abcd\n");
161 |     }
162 | 
163 |     #[test]
164 |     fn translate_arguments() {
165 |         let src_main_rel = "src\\main.rs";
166 |         let p = env::current_dir()
167 |             .unwrap()
168 |             .as_path()
169 |             .join(src_main_rel)
170 |             .as_path()
171 |             .to_string_lossy()
172 |             .into_owned();
173 |         let src_main_abs = p.as_str();
174 | 
175 |         Command::cargo_bin(env!("CARGO_PKG_NAME"))
176 |             .unwrap()
177 |             .args(&["log", "-n1", "--oneline", "--", src_main_rel])
178 |             .env("WSLGIT_USE_INTERACTIVE_SHELL", "false")
179 |             .assert()
180 |             .success()
181 |             .stdout(predicate::str::is_empty().not());
182 | 
183 |         Command::cargo_bin(env!("CARGO_PKG_NAME"))
184 |             .unwrap()
185 |             .args(&["log", "-n1", "--oneline", "--", src_main_abs])
186 |             .env("WSLGIT_USE_INTERACTIVE_SHELL", "false")
187 |             .assert()
188 |             .success()
189 |             .stdout(predicate::str::is_empty().not());
190 | 
191 |         Command::cargo_bin(env!("CARGO_PKG_NAME"))
192 |             .unwrap()
193 |             .args(&["config", "--get-regexp", "^remote\\..*"])
194 |             .env("WSLGIT_USE_INTERACTIVE_SHELL", "false")
195 |             .assert()
196 |             .success()
197 |             .stdout(predicate::str::is_empty().not());
198 | 
199 |         Command::cargo_bin(env!("CARGO_PKG_NAME"))
200 |             .unwrap()
201 |             .args(&["log", "-n1", "-L", format!("1,1:{}", src_main_rel).as_str()])
202 |             .env("WSLGIT_USE_INTERACTIVE_SHELL", "false")
203 |             .assert()
204 |             .success()
205 |             .stdout(predicate::str::contains(
206 |                 "diff --git a/src/main.rs b/src/main.rs",
207 |             ))
208 |             .stdout(predicate::str::contains("@@ -0,0 +1,1 @@"));
209 | 
210 |         Command::cargo_bin(env!("CARGO_PKG_NAME"))
211 |             .unwrap()
212 |             .args(&["log", "-n1", "-L", format!("1,1:{}", src_main_abs).as_str()])
213 |             .env("WSLGIT_USE_INTERACTIVE_SHELL", "false")
214 |             .assert()
215 |             .success()
216 |             .stdout(predicate::str::contains(
217 |                 "diff --git a/src/main.rs b/src/main.rs",
218 |             ))
219 |             .stdout(predicate::str::contains("@@ -0,0 +1,1 @@"));
220 | 
221 |         Command::cargo_bin(env!("CARGO_PKG_NAME"))
222 |             .unwrap()
223 |             .args(&["log", "-L", format!(":main:{}", src_main_rel).as_str()])
224 |             .env("WSLGIT_USE_INTERACTIVE_SHELL", "false")
225 |             .assert()
226 |             .success()
227 |             .stdout(predicate::str::contains(
228 |                 "diff --git a/src/main.rs b/src/main.rs",
229 |             ))
230 |             .stdout(predicate::str::contains("fn main() {"));
231 | 
232 |         Command::cargo_bin(env!("CARGO_PKG_NAME"))
233 |             .unwrap()
234 |             .args(&["log", "-L", format!(":main:{}", src_main_abs).as_str()])
235 |             .env("WSLGIT_USE_INTERACTIVE_SHELL", "false")
236 |             .assert()
237 |             .success()
238 |             .stdout(predicate::str::contains(
239 |                 "diff --git a/src/main.rs b/src/main.rs",
240 |             ))
241 |             .stdout(predicate::str::contains("fn main() {"));
242 |     }
243 | 
244 |     #[test]
245 |     fn translate_output() {
246 |         let cwd = format!(
247 |             "{}\n",
248 |             env::current_dir()
249 |                 .unwrap()
250 |                 .as_path()
251 |                 .to_string_lossy()
252 |                 .into_owned()
253 |         );
254 | 
255 |         Command::cargo_bin(env!("CARGO_PKG_NAME"))
256 |             .unwrap()
257 |             .args(&["rev-parse", "--show-toplevel"])
258 |             .env("WSLGIT_USE_INTERACTIVE_SHELL", "false")
259 |             .assert()
260 |             .success()
261 |             .stdout(cwd);
262 |     }
263 | 
264 |     #[test]
265 |     fn wslgit_environment_variable() {
266 |         Command::cargo_bin(env!("CARGO_PKG_NAME"))
267 |             .unwrap()
268 |             // Use pretty format to call 'env'
269 |             .args(&["log", "-1", "--pretty=format:$(env)"])
270 |             .env("WSLGIT_USE_INTERACTIVE_SHELL", "false")
271 |             .env("WSLENV", "")
272 |             .assert()
273 |             .success()
274 |             .stdout(predicate::str::contains("WSLGIT=1"))
275 |             .stdout(predicate::str::contains("WSLENV=WSLGIT"));
276 | 
277 |         Command::cargo_bin(env!("CARGO_PKG_NAME"))
278 |             .unwrap()
279 |             // Use pretty format to call 'env'
280 |             .args(&["log", "-1", "--pretty=format:$(env)"])
281 |             .env("WSLGIT_USE_INTERACTIVE_SHELL", "false")
282 |             .env("WSLENV", "hello")
283 |             .assert()
284 |             .success()
285 |             .stdout(predicate::str::contains("WSLGIT=1"))
286 |             .stdout(predicate::str::contains("WSLENV=hello:WSLGIT"));
287 |     }
288 | 
289 |     #[test]
290 |     fn shell_environment_variable() {
291 |         Command::cargo_bin(env!("CARGO_PKG_NAME"))
292 |             .unwrap()
293 |             // Use pretty format to call 'printenv SHELL'
294 |             .args(&["log", "-1", "--pretty=format:$(printenv SHELL)"])
295 |             .assert()
296 |             .success()
297 |             .stdout(predicate::str::contains("/bin/bash"));
298 |     }
299 | }
300 | 


--------------------------------------------------------------------------------