From 59f08f623e1d80f383260563ac4f38cb70304c0e Mon Sep 17 00:00:00 2001 From: Yohan Boogaert Date: Tue, 3 Aug 2021 17:59:17 +0200 Subject: [PATCH] Handle a git worktree (#33) --- README.md | 103 +++++++++++++++++++++++++--------- src/main.rs | 156 ++++++++++++++++++++++++++++++++++------------------ 2 files changed, 179 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 43e73ec..8af1a5f 100644 --- a/README.md +++ b/README.md @@ -9,22 +9,40 @@ with already installed dependencies. Requires Rust 1.51. -`cargo install cargo-temp` +``` +cargo install cargo-temp +``` ## Usage Create a new temporary project: * With no additional dependencies: - `$ cargo-temp` - + ``` + cargo-temp + ``` + * With multiple dependencies: - `$ cargo-temp rand tokio` - + ``` + cargo-temp rand tokio + ``` + * When specifying a version: - `$ cargo-temp anyhow=1.0` - * Using the [cargo's comparison requirements][comparison]: - `$ cargo-temp anyhow==1.0.13` + ``` + cargo-temp anyhow=1.0` + ``` + +Using the [cargo's comparison requirements][comparison]: + +* Exact version: + ``` + cargo-temp anyhow==1.0.13 + ``` + +* Maximal version: + ``` + cargo-temp anyhow=<1.0.2 + ``` ### Repositories @@ -32,28 +50,57 @@ You can add repositories to your `Cargo.toml`. Examples: -* HTTP - `$ cargo-temp anyhow=https://github.com/dtolnay/anyhow.git` +* HTTP: + ``` + cargo-temp anyhow=https://github.com/dtolnay/anyhow.git + ``` * SSH - `$ cargo-temp anyhow=ssh://git@github.com/dtolnay/anyhow.git` + ``` + cargo-temp anyhow=ssh://git@github.com/dtolnay/anyhow.git + ``` To choose a branch or a revision: -* Branch - `$ cargo-temp anyhow=https://github.com/dtolnay/anyhow.git#branch=master` +* Branch: + ``` + cargo-temp anyhow=https://github.com/dtolnay/anyhow.git#branch=master + ``` -* Revision - `$ cargo-temp anyhow=https://github.com/dtolnay/anyhow.git#rev=7e0f77a38` +* Revision: + ``` + cargo-temp anyhow=https://github.com/dtolnay/anyhow.git#rev=7e0f77a38 + ``` -Without a branch or a revision, cargo will use the default branch of the repository. +Without a branch or a revision, cargo will use the default branch of the +repository. ## Features +### The TO_DELETE file + If you change your mind and decide to keep the project you can just delete the `TO_DELETE` file and the directory will not be deleted when the shell or the editor exits. +### Git Working Tree + +You can create a git worktree from the current repository using: + +``` +cargo-temp --worktree +``` + +This will create a new working tree at the current HEAD. +You can specify a branch like this: + +``` +cargo-temp --worktree +``` + +When exiting the shell (or your editor) the working tree will be cleaned up. +Equivalent to `git worktree prune`. + ## Settings The config file is located at `{CONFIG_DIR}/cargo-temp/config.toml`. @@ -66,7 +113,9 @@ and the [Known Folder system][knownfolder] on Windows. The path where the temporary projects are created. Set on the cache directory by default. -`temporary_project_dir = "/home/name/.cache/cargo-temp/"` +```toml +temporary_project_dir = "/home/name/.cache/cargo-temp/" +``` ### Cargo target directory @@ -74,7 +123,9 @@ Cargo's target directory override. This setting is unset by default and will be ignored if the `CARGO_TARGET_DIR` environment variable is already set. -`temporary_project_dir = "/home/name/repos/tmp"` +```toml +temporary_project_dir = "/home/name/repos/tmp" +``` ### Editor @@ -82,16 +133,16 @@ You can use `editor` to start an IDE instead of a shell and `editor_args` to provide its arguments. These settings are unset by default. * Example to run VS Code on Unix -```toml -editor = "/usr/bin/code" -editor_args = [ "--wait", "--new-window" ] -``` + ```toml + editor = "/usr/bin/code" + editor_args = [ "--wait", "--new-window" ] + ``` * Example to run VS Code on Windows -```toml -editor = "C:\\Program Files\\Microsoft VS Code\\Code.exe" -editor_args = [ "--wait", "--new-window" ] -``` + ```toml + editor = "C:\\Program Files\\Microsoft VS Code\\Code.exe" + editor_args = [ "--wait", "--new-window" ] + ``` [comparison]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#comparison-requirements [xdg]: https://docs.rs/xdg/2.2.0/xdg/ diff --git a/src/main.rs b/src/main.rs index a95bdd7..798ef48 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use anyhow::{bail, Context, Result}; +use anyhow::{ensure, Context, Result}; use clap::Clap; use once_cell::sync::Lazy; use regex::Regex; @@ -23,32 +23,37 @@ struct Cli { #[clap(parse(from_str = parse_dependency))] dependencies: Vec, + /// Create a library instead of a binary. + #[clap(long)] + lib: bool, + /// Name of the temporary crate. #[clap(long = "name")] project_name: Option, - /// Create a library instead of a binary. - #[clap(long)] - lib: bool, + /// Create a temporary Git working tree based on the repository in the + /// current directory + #[clap(long = "worktree")] + worktree_branch: Option>, } #[derive(Debug, PartialEq, Eq)] enum Dependency { CrateIo(String, Option), Repository { - name: String, - url: String, branch: Option, + name: String, rev: Option, + url: String, }, } #[derive(Serialize, Deserialize)] struct Config { - temporary_project_dir: String, cargo_target_dir: Option, editor: Option, editor_args: Option>, + temporary_project_dir: String, } impl Config { @@ -65,14 +70,16 @@ impl Config { .context("Could not get cache directory")? .join(env!("CARGO_PKG_NAME")); + let temporary_project_dir = cache_dir + .to_str() + .context("Cannot convert temporary project path into str")? + .to_string(); + Ok(Self { - temporary_project_dir: cache_dir - .to_str() - .context("Could not convert cache path into str")? - .to_string(), cargo_target_dir: None, editor: None, editor_args: None, + temporary_project_dir, }) } @@ -117,9 +124,20 @@ fn main() -> Result<()> { // Read configuration from disk or generate a default one. let config = Config::get_or_create()?; let _ = fs::create_dir(&config.temporary_project_dir); - let tmp_dir = Builder::new() - .prefix("tmp-") - .tempdir_in(&config.temporary_project_dir)?; + + // Create the temporary directory + let tmp_dir = { + let mut builder = Builder::new(); + + if cli.worktree_branch.is_some() { + builder.prefix("wk-"); + } else { + builder.prefix("tmp-"); + } + + builder.tempdir_in(&config.temporary_project_dir)? + }; + let project_name = cli.project_name.unwrap_or_else(|| { tmp_dir .path() @@ -129,17 +147,63 @@ fn main() -> Result<()> { .to_lowercase() }); - // Generate the temporary project - let mut command = process::Command::new("cargo"); - command - .current_dir(&tmp_dir) - .args(&["init", "--name", project_name.as_str()]); - if cli.lib { - command.arg("--lib"); + // Generate the temporary project or temporary worktree + if let Some(maybe_branch) = cli.worktree_branch.as_ref() { + let mut command = process::Command::new("git"); + command.args(["worktree", "add"]); + + match maybe_branch { + Some(branch) => command.arg(tmp_dir.path()).arg(branch), + None => command.arg("-d").arg(tmp_dir.path()), + }; + + ensure!( + command.status().context("Could not start git")?.success(), + "Cannot create working tree" + ); + } else { + let mut command = process::Command::new("cargo"); + command + .current_dir(&tmp_dir) + .args(["init", "--name", project_name.as_str()]); + + if cli.lib { + command.arg("--lib"); + } + + ensure!( + command.status().context("Could not start cargo")?.success(), + "Cargo command failed" + ); + + // Add dependencies to Cargo.toml from arguments given by the user + let mut toml = fs::OpenOptions::new() + .append(true) + .open(tmp_dir.path().join("Cargo.toml"))?; + for dependency in cli.dependencies.iter() { + match dependency { + Dependency::CrateIo(s, v) => match &v { + Some(version) => writeln!(toml, "{} = \"{}\"", s, version)?, + None => writeln!(toml, "{} = \"*\"", s)?, + }, + Dependency::Repository { + name, + url, + branch, + rev, + } => { + write!(toml, "{name} = {{ git = {url:?}", name = name, url = url)?; + if let Some(branch) = branch { + write!(toml, ", branch = {:?}", branch)?; + } + if let Some(rev) = rev { + write!(toml, ", rev = {:?}", rev)?; + } + writeln!(toml, " }}")?; + } + } + } } - if !command.status().context("Could not start cargo")?.success() { - bail!("Cargo command failed"); - }; // Generate the `TO_DELETE` file let delete_file = tmp_dir.path().join("TO_DELETE"); @@ -148,35 +212,6 @@ fn main() -> Result<()> { "Delete this file if you want to preserve this project", )?; - // Add dependencies to Cargo.toml from arguments given by the user - let mut toml = fs::OpenOptions::new() - .append(true) - .open(tmp_dir.path().join("Cargo.toml"))?; - for dependency in cli.dependencies.iter() { - match dependency { - Dependency::CrateIo(s, v) => match &v { - Some(version) => writeln!(toml, "{} = \"{}\"", s, version)?, - None => writeln!(toml, "{} = \"*\"", s)?, - }, - Dependency::Repository { - name, - url, - branch, - rev, - } => { - write!(toml, "{name} = {{ git = {url:?}", name = name, url = url)?; - if let Some(branch) = branch { - write!(toml, ", branch = {:?}", branch)?; - } - if let Some(rev) = rev { - write!(toml, ", rev = {:?}", rev)?; - } - writeln!(toml, " }}")?; - } - } - } - drop(toml); - // Prepare a new shell or an editor if its set in the config file let mut shell_process = match config.editor { None => process::Command::new(get_shell()), @@ -208,7 +243,20 @@ fn main() -> Result<()> { } if !delete_file.exists() { - println!("Project preserved at: {}", tmp_dir.into_path().display()); + println!( + "Project directory preserved at: {}", + tmp_dir.into_path().display() + ); + } else if cli.worktree_branch.is_some() { + let mut command = process::Command::new("git"); + command + .args(["worktree", "remove"]) + .arg(&tmp_dir.path()) + .arg("--force"); + ensure!( + command.status().context("Could not start git")?.success(), + "Cannot remove working tree" + ); } Ok(())