Skip to content

Commit

Permalink
Handle a git worktree (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
yozhgoor authored Aug 3, 2021
1 parent cca91c2 commit 59f08f6
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 80 deletions.
103 changes: 77 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,51 +9,98 @@ 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

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://[email protected]/dtolnay/anyhow.git`
```
cargo-temp anyhow=ssh://[email protected]/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 <branch>
```

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`.
Expand All @@ -66,32 +113,36 @@ 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

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

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/
Expand Down
156 changes: 102 additions & 54 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -23,32 +23,37 @@ struct Cli {
#[clap(parse(from_str = parse_dependency))]
dependencies: Vec<Dependency>,

/// Create a library instead of a binary.
#[clap(long)]
lib: bool,

/// Name of the temporary crate.
#[clap(long = "name")]
project_name: Option<String>,

/// 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<Option<String>>,
}

#[derive(Debug, PartialEq, Eq)]
enum Dependency {
CrateIo(String, Option<String>),
Repository {
name: String,
url: String,
branch: Option<String>,
name: String,
rev: Option<String>,
url: String,
},
}

#[derive(Serialize, Deserialize)]
struct Config {
temporary_project_dir: String,
cargo_target_dir: Option<String>,
editor: Option<String>,
editor_args: Option<Vec<String>>,
temporary_project_dir: String,
}

impl Config {
Expand All @@ -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,
})
}

Expand Down Expand Up @@ -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()
Expand All @@ -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");
Expand All @@ -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()),
Expand Down Expand Up @@ -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(())
Expand Down

0 comments on commit 59f08f6

Please sign in to comment.