Skip to content

Commit a697939

Browse files
authored
Merge pull request #12 from ozankasikci/interactive-mode
interactive mode
2 parents de333b1 + 537f191 commit a697939

File tree

14 files changed

+2463
-64
lines changed

14 files changed

+2463
-64
lines changed

Cargo.lock

Lines changed: 395 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ clap = { version = "4.5", features = ["derive"] }
1616
color-eyre = "0.6"
1717
owo-colors = { version = "4.0", features = ["supports-colors"] }
1818
git2 = "0.20"
19+
crossterm = "0.27"
20+
ratatui = { version = "0.26", default-features = false, features = ["crossterm"] }
1921
serde = { version = "1.0", features = ["derive"] }
2022
serde_json = "1.0"
2123

src/cli/mod.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::{
99
commands::{
1010
cd::CdCommand,
1111
create::CreateCommand,
12+
interactive,
1213
list::ListCommand,
1314
merge_pr_github::MergePrGithubCommand,
1415
pr_github::{PrGithubCommand, PrGithubOptions},
@@ -31,6 +32,9 @@ enum Commands {
3132
Ls,
3233
/// Open a shell in the given worktree.
3334
Cd(CdArgs),
35+
/// Interactively browse and open worktrees.
36+
#[command(alias = "i")]
37+
Interactive,
3438
/// Remove a worktree tracked in `.rsworktree`.
3539
Rm(RmArgs),
3640
/// Create a GitHub pull request for the worktree's branch using the GitHub CLI.
@@ -97,6 +101,9 @@ struct PrGithubArgs {
97101
struct MergePrGithubArgs {
98102
/// Name of the worktree to merge the PR for (defaults to the current worktree)
99103
name: Option<String>,
104+
/// Remove the remote branch after merging
105+
#[arg(long = "remove")]
106+
remove_remote: bool,
100107
}
101108

102109
pub fn run() -> color_eyre::Result<()> {
@@ -116,6 +123,9 @@ pub fn run() -> color_eyre::Result<()> {
116123
let command = CdCommand::new(args.name, args.print);
117124
command.execute(&repo)?;
118125
}
126+
Commands::Interactive => {
127+
interactive::run(&repo)?;
128+
}
119129
Commands::Rm(args) => {
120130
let command = RemoveCommand::new(args.name, args.force);
121131
command.execute(&repo)?;
@@ -138,6 +148,9 @@ pub fn run() -> color_eyre::Result<()> {
138148
Commands::MergePrGithub(args) => {
139149
let worktree_name = resolve_worktree_name(args.name, &repo, "merge-pr-github")?;
140150
let mut command = MergePrGithubCommand::new(worktree_name);
151+
if args.remove_remote {
152+
command.enable_remove_remote();
153+
}
141154
command.execute(&repo)?;
142155
}
143156
}
@@ -195,6 +208,7 @@ mod tests {
195208
use super::*;
196209
use std::{env, fs, path::Path, process::Command as StdCommand};
197210

211+
use clap::Parser;
198212
use color_eyre::eyre::{self, WrapErr};
199213

200214
use tempfile::TempDir;
@@ -266,6 +280,19 @@ mod tests {
266280
Ok(())
267281
}
268282

283+
#[test]
284+
fn parses_interactive_command_and_alias() -> color_eyre::Result<()> {
285+
let interactive = Cli::try_parse_from(["rsworktree", "interactive"])
286+
.expect("interactive subcommand should parse");
287+
assert!(matches!(interactive.command, Commands::Interactive));
288+
289+
let alias =
290+
Cli::try_parse_from(["rsworktree", "i"]).expect("interactive alias should parse");
291+
assert!(matches!(alias.command, Commands::Interactive));
292+
293+
Ok(())
294+
}
295+
269296
#[test]
270297
fn resolve_worktree_name_infers_from_cwd_inside_worktree() -> color_eyre::Result<()> {
271298
let repo_dir = TempDir::new()?;

src/commands/create/mod.rs

Lines changed: 64 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,59 @@ pub struct CreateCommand {
1414
base: Option<String>,
1515
}
1616

17+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18+
pub enum CreateOutcome {
19+
AlreadyExists,
20+
Created,
21+
}
22+
1723
impl CreateCommand {
1824
pub fn new(name: String, base: Option<String>) -> Self {
1925
Self { name, base }
2026
}
2127

2228
pub fn execute(&self, repo: &Repo) -> color_eyre::Result<()> {
29+
let outcome = self.create_internal(repo, false)?;
30+
match outcome {
31+
CreateOutcome::Created | CreateOutcome::AlreadyExists => self.enter_worktree(repo),
32+
}
33+
}
34+
35+
pub fn create_without_enter(
36+
&self,
37+
repo: &Repo,
38+
quiet: bool,
39+
) -> color_eyre::Result<CreateOutcome> {
40+
self.create_internal(repo, quiet)
41+
}
42+
43+
fn enter_worktree(&self, repo: &Repo) -> color_eyre::Result<()> {
44+
CdCommand::new(self.name.clone(), false).execute(repo)
45+
}
46+
47+
fn create_internal(&self, repo: &Repo, quiet: bool) -> color_eyre::Result<CreateOutcome> {
2348
let worktrees_dir = repo.ensure_worktrees_dir()?;
2449
let worktree_path = worktrees_dir.join(&self.name);
2550
let target_branch = self.name.as_str();
2651
let base_branch = self.base.as_deref();
2752

2853
if worktree_path.exists() {
29-
let name = format!(
30-
"{}",
31-
self.name
32-
.as_str()
33-
.if_supports_color(Stream::Stdout, |text| {
34-
format!("{}", text.cyan().bold())
35-
})
36-
);
37-
println!(
38-
"Worktree `{}` already exists at `{}`.",
39-
name,
40-
worktree_path.display()
41-
);
42-
return self.enter_worktree(repo);
54+
if !quiet {
55+
let name = format!(
56+
"{}",
57+
self.name
58+
.as_str()
59+
.if_supports_color(Stream::Stdout, |text| {
60+
format!("{}", text.cyan().bold())
61+
})
62+
);
63+
println!(
64+
"Worktree `{}` already exists at `{}`.",
65+
name,
66+
worktree_path.display()
67+
);
68+
}
69+
return Ok(CreateOutcome::AlreadyExists);
4370
}
4471

4572
if let Some(parent) = worktree_path.parent() {
@@ -63,36 +90,34 @@ impl CreateCommand {
6390
)
6491
})?;
6592

66-
let name = format!(
67-
"{}",
68-
target_branch.if_supports_color(Stream::Stdout, |text| {
69-
format!("{}", text.green().bold())
70-
})
71-
);
72-
let path_raw = format!("{}", worktree_path.display());
73-
let path = format!(
74-
"{}",
75-
path_raw
76-
.as_str()
77-
.if_supports_color(Stream::Stdout, |text| { format!("{}", text.blue()) })
78-
);
79-
if let Some(base) = base_branch {
80-
let base = format!(
93+
if !quiet {
94+
let name = format!(
8195
"{}",
82-
base.if_supports_color(Stream::Stdout, |text| {
83-
format!("{}", text.magenta().bold())
96+
target_branch.if_supports_color(Stream::Stdout, |text| {
97+
format!("{}", text.green().bold())
8498
})
8599
);
86-
println!("Created worktree `{}` at `{}` from `{}`.", name, path, base);
87-
} else {
88-
println!("Created worktree `{}` at `{}`.", name, path);
100+
let path_raw = format!("{}", worktree_path.display());
101+
let path = format!(
102+
"{}",
103+
path_raw
104+
.as_str()
105+
.if_supports_color(Stream::Stdout, |text| { format!("{}", text.blue()) })
106+
);
107+
if let Some(base) = base_branch {
108+
let base = format!(
109+
"{}",
110+
base.if_supports_color(Stream::Stdout, |text| {
111+
format!("{}", text.magenta().bold())
112+
})
113+
);
114+
println!("Created worktree `{}` at `{}` from `{}`.", name, path, base);
115+
} else {
116+
println!("Created worktree `{}` at `{}`.", name, path);
117+
}
89118
}
90119

91-
self.enter_worktree(repo)
92-
}
93-
94-
fn enter_worktree(&self, repo: &Repo) -> color_eyre::Result<()> {
95-
CdCommand::new(self.name.clone(), false).execute(repo)
120+
Ok(CreateOutcome::Created)
96121
}
97122
}
98123

0 commit comments

Comments
 (0)