Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,27 @@ and use that tool to edit `Path`.
You may also need to install the latest
[*Microsoft Visual C++ Redistributable for Visual Studio 2017*](https://aka.ms/vs/15/release/vc_redist.x64.exe).

## WSL distributions and file systems

When accessing files on the filesystem in a WSL distribution using the UNC path
(`\\wsl$\dist\path` or `\\wsl.localhost\dist\path`) then the distribution used
is extracted from the path. This means that git must be setup correctly in all
distributions that you intend to access.

When accessing files on the Windows filesystem or a mapped network drive the
default WSL distribution is used unless the
[`WSLGIT_DEFAULT_DIST`](#wslgit_default_dist) environment variable is set.

If the default WSL distribution is of WSL2 type then it is highly recommended to
set the `WSLGIT_DEFAULT_DIST` to the name of a WSL1 instance since WSL1 is both
quicker at accessing the Windows filesystem and can access mapped network drives
which WSL2 cannot.

> Tip: use symlinks to map files and folders in all distributions to a common
> directory to avoid having to maintain multiple copies, for example you can
> link the `~/.ssh` folder in all WSL dists to the `.ssh` folder in your Windows
> home folder.

## Usage in VSCode

VSCode will find the `git` executable automatically if the two optional installation steps were taken.
Expand Down Expand Up @@ -105,6 +126,14 @@ therefore also starts bash in non-interactive mode.

This feature is only available in Windows 10 builds 17063 and later.

### WSLGIT_DEFAULT_DIST

Set a Windows environment variable called `WSLGIT_DEFAULT_DIST` to the name of a
WSL distribution to use instead of the WSL default distribution when accessing
files on the Windows filesystem or from mapped network shares.

> Note, to access files on a mapped network drive a WSL1 distribution must be used.

### WSLGIT
`wslgit` set a variable called `WSLGIT` to `1` and shares it to WSL. This variable can be used in `.bashrc` to
determine if WSL was invoked by `wslgit`, and for example if set then just do the absolute minimum of initialization
Expand Down
231 changes: 228 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::env;

use std::fs::OpenOptions;
use std::io::{self, Write};
use std::path::Path;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};

#[macro_use]
Expand Down Expand Up @@ -235,6 +235,89 @@ fn use_interactive_shell() -> bool {
git_command_needs_interactive_shell()
}

/// Find the working directory by starting from the current directory and applying
/// any paths from `-C` or `--work-tree` arguments.
///
/// `--git-dir` is ignored, it is assumed that both the work-tree and git-dir
/// are on the same file system/same wsl distribution.
///
/// Returns the working directory as a String.
fn get_working_directory(current_dir: PathBuf, args: &[String]) -> String {
let mut working_dir = current_dir;
let mut work_tree = env::var("GIT_WORK_TREE").unwrap_or("".to_string());

let mut skip_next = false;
let mut next_is_path = false;

for arg in args {
if skip_next {
skip_next = false;
} else if next_is_path {
next_is_path = false;
// let path = PathBuf::from(arg);
working_dir.push(arg);
} else if arg == "-c" {
// `-c` expects a second argument, so skip next argument.
skip_next = true;
} else if arg == "-C" {
// `-C` expects a second argument that is a path
next_is_path = true;
} else if arg.starts_with("--work-tree=") {
work_tree = arg[12..].to_string();
} else if !arg.starts_with("-") {
// First argument that doesn't start with '-' (or '--') is the git
// command; clone, commit etc.
break;
}
}

// Finally apply the path from any "--work-tree" argument on the current working dir
if work_tree.len() > 0 {
working_dir.push(work_tree);
}

return working_dir.to_str().unwrap().to_string();
}

/// Try to find the WSL distribution name from the provided `path`.
///
/// An UNC prefix consists of \\server\share\, which is then followed by a path.
/// When accessing a WSL filesystem using the `\\wsl$\dist\` UNC prefix then the
/// distribution name can be extracted from the second component of the UNC
/// prefix.
///
/// Returns the distribution name or None.
fn get_wsl_dist_name(path: &str) -> Option<String> {
const UNC_SERVER_WSL: &str = "\\\\wsl$\\";
const UNC_SERVER_WSL_LOCALHOST: &str = "\\\\wsl.localhost\\";

let unc_path_without_server: Option<&str> = if path.starts_with(UNC_SERVER_WSL) {
Some(&path[UNC_SERVER_WSL.len()..])
} else if path.starts_with(UNC_SERVER_WSL_LOCALHOST) {
Some(&path[UNC_SERVER_WSL_LOCALHOST.len()..])
} else {
None
};

let wsl_dist_name = match unc_path_without_server {
Some(p) => {
// the string p starts with the UNC 'share', which is the wsl dist name
let (dist_name, _) = p.split_once('\\').unwrap();
Some(dist_name.to_string())
}
None => {
if let Ok(default_dist) = env::var("WSLGIT_DEFAULT_DIST") {
Some(default_dist)
} else {
// Use wsl default dist
None
}
}
};

return wsl_dist_name;
}

fn enable_logging() -> bool {
if let Ok(enable_log_flag) = env::var("WSLGIT_ENABLE_LOGGING") {
if enable_log_flag == "true" || enable_log_flag == "1" {
Expand Down Expand Up @@ -274,11 +357,22 @@ fn log(message: String) {
fn main() {
let mut cmd_args = Vec::new();
let mut git_args: Vec<String> = vec![String::from("git")];

git_args.extend(env::args().skip(1).map(format_argument));

let git_cmd: String = git_args.join(" ");

let curr_dir = env::current_dir().unwrap();
// Assumes that the first element in args is the executable
let args: Vec<String> = env::args().skip(1).collect();
let working_directory = get_working_directory(curr_dir, &args);
match get_wsl_dist_name(&working_directory) {
Some(wsl_dist) => {
cmd_args.push("--distribution".to_string());
cmd_args.push(wsl_dist.to_string());
}
None => {}
}

// build the command arguments that are passed to wsl.exe
cmd_args.push("-e".to_string());
cmd_args.push(BASH_EXECUTABLE.to_string());
Expand All @@ -290,7 +384,11 @@ fn main() {
cmd_args.push(git_cmd.clone());

if enable_logging() {
log(format!("wslgit version {}", VERSION));
log(format!(
"wslgit version {}, current_dir {}",
VERSION,
env::current_dir().unwrap().to_str().unwrap().to_string()
));
log_arguments(&cmd_args);
}

Expand Down Expand Up @@ -760,4 +858,131 @@ mod tests {
"-c \"credential.helper=$(wslpath 'C:/Program Files/SmartGit/lib/credentials.cmd')\""
);
}

#[test]
fn get_working_directory_test() {
env::remove_var("GIT_WORK_TREE");

let args: Vec<String> = vec![];
assert_eq!(
get_working_directory(PathBuf::from("C:\\repo\\"), &args),
"C:\\repo\\".to_string()
);
assert_eq!(
get_working_directory(PathBuf::from("\\\\wsl$\\dist-name\\repo\\"), &args),
"\\\\wsl$\\dist-name\\repo\\".to_string()
);

let args: Vec<String> = vec!["cmd".into()];
assert_eq!(
get_working_directory(PathBuf::from("C:\\repo\\"), &args),
"C:\\repo\\".to_string()
);

let args: Vec<String> = vec!["-c".into(), "arg".into(), "cmd".into()];
assert_eq!(
get_working_directory(PathBuf::from("C:\\repo\\"), &args),
"C:\\repo\\".to_string()
);

let args: Vec<String> = vec![
"-c".into(),
"arg".into(),
"-C".into(),
"relative".into(),
"cmd".into(),
];
assert_eq!(
get_working_directory(PathBuf::from("C:\\repo\\"), &args),
"C:\\repo\\relative".to_string()
);

let args: Vec<String> = vec![
"-c".into(),
"arg".into(),
"-C".into(),
"C:\\absolute".into(),
"cmd".into(),
];
assert_eq!(
get_working_directory(PathBuf::from("C:\\repo\\"), &args),
"C:\\absolute".to_string()
);

let args: Vec<String> = vec![
"-c".into(),
"arg".into(),
"-C".into(),
"a".into(),
"-C".into(),
"b".into(),
"cmd".into(),
];
assert_eq!(
get_working_directory(PathBuf::from("C:\\repo\\"), &args),
"C:\\repo\\a\\b".to_string()
);

env::set_var("GIT_WORK_TREE", "b");
let args: Vec<String> = vec![
"-c".into(),
"arg".into(),
"-C".into(),
"a".into(),
"cmd".into(),
];
assert_eq!(
get_working_directory(PathBuf::from("C:\\repo\\"), &args),
"C:\\repo\\a\\b".to_string()
);

env::set_var("GIT_WORK_TREE", "b");
let args: Vec<String> = vec![
"-c".into(),
"arg".into(),
"-C".into(),
"a".into(),
"--work-tree=c".into(),
"cmd".into(),
];
assert_eq!(
get_working_directory(PathBuf::from("C:\\repo\\"), &args),
"C:\\repo\\a\\c".to_string()
);
}

#[test]
fn wsl_dist_name() {
env::remove_var("WSLGIT_DEFAULT_DIST");
assert_eq!(
get_wsl_dist_name(&r"\\wsl$\dist-name\a\b\c".to_string()),
Some("dist-name".to_string())
);
assert_eq!(
get_wsl_dist_name(&r"\\wsl.localhost\dist-name\a\b\c".to_string()),
Some("dist-name".to_string())
);
assert_eq!(
get_wsl_dist_name(&r"\\server\dist-name\a\b\c".to_string()),
None
);
assert_eq!(get_wsl_dist_name(&r"C:\a\b\c".to_string()), None);
}

#[test]
fn wsl_default_dist_name() {
env::set_var("WSLGIT_DEFAULT_DIST", "some-dist");
assert_eq!(
get_wsl_dist_name(&r"\\wsl$\dist-name\a\b\c".to_string()),
Some("dist-name".to_string())
);
assert_eq!(
get_wsl_dist_name(&r"\\server\dist-name\a\b\c".to_string()),
Some("some-dist".to_string())
);
assert_eq!(
get_wsl_dist_name(&r"C:\a\b\c".to_string()),
Some("some-dist".to_string())
);
}
}