diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 880885c..cb3596a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable with: - components: rustfmt + components: rustfmt, clippy - name: Restore Cargo cache uses: Swatinem/rust-cache@v2 @@ -32,6 +32,9 @@ jobs: - name: Check formatting run: cargo fmt --all --check + - name: Run cargo clippy + run: cargo clippy --locked --all-targets -- -D warnings + - name: Run cargo check run: cargo check --locked --all-targets diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0934fa4..5c64ddd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,9 +42,34 @@ jobs: exit 1 fi + test: + name: Test + needs: verify_tag_on_main + runs-on: ubuntu-24.04 + steps: + - name: Check out repository + uses: actions/checkout@v5 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: Restore Cargo cache + uses: Swatinem/rust-cache@v2 + + - name: Check formatting + run: cargo fmt --all --check + + - name: Run cargo clippy + run: cargo clippy --locked --all-targets -- -D warnings + + - name: Run cargo test + run: cargo test --locked --release + build: name: Build ${{ matrix.target }} - needs: verify_tag_on_main + needs: [verify_tag_on_main, test] runs-on: ${{ matrix.runs_on }} strategy: fail-fast: false diff --git a/src/cli/branch/mod.rs b/src/cli/branch/mod.rs index ae52d39..08087e8 100644 --- a/src/cli/branch/mod.rs +++ b/src/cli/branch/mod.rs @@ -19,12 +19,12 @@ pub struct BranchArgs { pub fn execute(args: BranchArgs) -> io::Result { let outcome = branch::run(&args.clone().into())?; - if outcome.status.success() { - if let Some(node) = &outcome.created_node { - println!("Created and switched to '{}'.", node.branch_name); - println!(); - println!("{}", super::tree::render_branch_lineage(&outcome.lineage)); - } + if outcome.status.success() + && let Some(node) = &outcome.created_node + { + println!("Created and switched to '{}'.", node.branch_name); + println!(); + println!("{}", super::tree::render_branch_lineage(&outcome.lineage)); } Ok(CommandOutcome { diff --git a/src/cli/operation/mod.rs b/src/cli/operation/mod.rs index 26a2b7c..745d760 100644 --- a/src/cli/operation/mod.rs +++ b/src/cli/operation/mod.rs @@ -38,7 +38,7 @@ impl AnimationTerminal { pub fn finish(&mut self, frame: &str) -> io::Result<()> { self.render(frame)?; - write!(self.stdout, "{ANSI_SHOW_CURSOR}\n")?; + writeln!(self.stdout, "{ANSI_SHOW_CURSOR}")?; self.stdout.flush()?; self.active = false; Ok(()) diff --git a/src/cli/sync/mod.rs b/src/cli/sync/mod.rs index 9388bf0..a2fafec 100644 --- a/src/cli/sync/mod.rs +++ b/src/cli/sync/mod.rs @@ -1024,9 +1024,9 @@ fn format_remote_push_plan(plan: &sync::RemotePushPlan) -> String { for action in &plan.actions { let action_label = match action.kind { - RemotePushActionKind::CreateRemoteBranch => "create", - RemotePushActionKind::UpdateRemoteBranch => "push", - RemotePushActionKind::ForceUpdateRemoteBranch => "force-push", + RemotePushActionKind::Create => "create", + RemotePushActionKind::Update => "push", + RemotePushActionKind::ForceUpdate => "force-push", }; lines.push(format!( "- {action_label} {} on {}", @@ -1071,9 +1071,9 @@ fn format_partial_remote_push_output(header: &str, outcome: &RemotePushOutcome) let mut lines = vec![header.to_string()]; for action in &outcome.pushed_actions { let action_label = match action.kind { - RemotePushActionKind::CreateRemoteBranch => "created", - RemotePushActionKind::UpdateRemoteBranch => "pushed", - RemotePushActionKind::ForceUpdateRemoteBranch => "force-pushed", + RemotePushActionKind::Create => "created", + RemotePushActionKind::Update => "pushed", + RemotePushActionKind::ForceUpdate => "force-pushed", }; lines.push(format!( "- {action_label} {} on {}", diff --git a/src/cli/sync/render.rs b/src/cli/sync/render.rs index 777e907..9c9199e 100644 --- a/src/cli/sync/render.rs +++ b/src/cli/sync/render.rs @@ -358,9 +358,9 @@ fn format_status_text(status: &SyncStatus) -> String { } => format!( "{} {branch_name} on {remote_name}", match kind { - RemotePushActionKind::CreateRemoteBranch => "Creating remote branch", - RemotePushActionKind::UpdateRemoteBranch => "Pushing", - RemotePushActionKind::ForceUpdateRemoteBranch => "Force-pushing", + RemotePushActionKind::Create => "Creating remote branch", + RemotePushActionKind::Update => "Pushing", + RemotePushActionKind::ForceUpdate => "Force-pushing", } ), SyncStatus::DeletingBranch { branch_name } => format!("Deleting {branch_name}"), diff --git a/src/cli/tree/mod.rs b/src/cli/tree/mod.rs index e8945d9..2e95956 100644 --- a/src/cli/tree/mod.rs +++ b/src/cli/tree/mod.rs @@ -43,10 +43,10 @@ pub(super) fn render_focused_context_tree( ) -> io::Result { let mut view = tree::focused_context_view(branch_name)?; - if let Some((current_branch_name, suffix)) = suffix_for_current_branch { - if view.current_branch_name.as_deref() == Some(current_branch_name) { - view.current_branch_suffix = Some(suffix.to_string()); - } + if let Some((current_branch_name, suffix)) = suffix_for_current_branch + && view.current_branch_name.as_deref() == Some(current_branch_name) + { + view.current_branch_suffix = Some(suffix.to_string()); } Ok(render::render_stack_tree(&view)) diff --git a/src/cli/tree/render.rs b/src/cli/tree/render.rs index b04d2fc..7c42f3f 100644 --- a/src/cli/tree/render.rs +++ b/src/cli/tree/render.rs @@ -26,20 +26,20 @@ pub fn render_stack_tree(view: &TreeView) -> String { .collect::>() .join("\n"); - if !view.is_current_visible { - if let Some(current_branch) = &view.current_branch_name { - let label = match &view.current_branch_suffix { - Some(suffix) => format!("{current_branch} {suffix}"), - None => current_branch.clone(), - }; + if !view.is_current_visible + && let Some(current_branch) = &view.current_branch_name + { + let label = match &view.current_branch_suffix { + Some(suffix) => format!("{current_branch} {suffix}"), + None => current_branch.clone(), + }; - rendered.push_str("\n\n"); - rendered.push_str(&format!( - "{} {}", - Accent::BranchRef.paint_ansi(markers::CURRENT_BRANCH), - Accent::BranchRef.paint_ansi(&label) - )); - } + rendered.push_str("\n\n"); + rendered.push_str(&format!( + "{} {}", + Accent::BranchRef.paint_ansi(markers::CURRENT_BRANCH), + Accent::BranchRef.paint_ansi(&label) + )); } rendered diff --git a/src/core/clean/apply.rs b/src/core/clean/apply.rs index 2fa1841..03ddfc3 100644 --- a/src/core/clean/apply.rs +++ b/src/core/clean/apply.rs @@ -293,6 +293,7 @@ where }) } +#[allow(clippy::too_many_arguments)] fn process_clean_candidate( session: &mut crate::core::store::StoreSession, original_branch: &str, diff --git a/src/core/clean/plan.rs b/src/core/clean/plan.rs index 73f7ea8..9e88ef3 100644 --- a/src/core/clean/plan.rs +++ b/src/core/clean/plan.rs @@ -255,7 +255,7 @@ fn apply_deleted_step_projection( let mut projected_state = state.clone(); for step in deleted_steps { - deleted_local::simulate_deleted_local_step(&mut projected_state, &step)?; + deleted_local::simulate_deleted_local_step(&mut projected_state, step)?; } Ok(projected_state) diff --git a/src/core/commit.rs b/src/core/commit.rs index 5a7a7b1..962e3c0 100644 --- a/src/core/commit.rs +++ b/src/core/commit.rs @@ -196,11 +196,11 @@ fn parse_git_log_output(stdout: &str) -> Vec { fn parse_commit_metadata(remainder: &str) -> (bool, Vec, String) { let trimmed = remainder.trim_start(); - if let Some(decorated) = trimmed.strip_prefix('(') { - if let Some((decorations, title)) = decorated.split_once(") ") { - let (is_head, refs) = parse_decorations(decorations); - return (is_head, refs, title.to_string()); - } + if let Some(decorated) = trimmed.strip_prefix('(') + && let Some((decorations, title)) = decorated.split_once(") ") + { + let (is_head, refs) = parse_decorations(decorations); + return (is_head, refs, title.to_string()); } (false, Vec::new(), trimmed.to_string()) diff --git a/src/core/git.rs b/src/core/git.rs index b805f20..26ee0b8 100644 --- a/src/core/git.rs +++ b/src/core/git.rs @@ -241,11 +241,11 @@ where let text = String::from_utf8_lossy(&chunk[..read]); stderr_output.push_str(&text); - if let Some(progress) = parse_latest_rebase_progress(&stderr_output) { - if last_progress != Some(progress) { - on_progress(progress)?; - last_progress = Some(progress); - } + if let Some(progress) = parse_latest_rebase_progress(&stderr_output) + && last_progress != Some(progress) + { + on_progress(progress)?; + last_progress = Some(progress); } } diff --git a/src/core/merge/apply.rs b/src/core/merge/apply.rs index dc69573..8a7fda1 100644 --- a/src/core/merge/apply.rs +++ b/src/core/merge/apply.rs @@ -282,9 +282,7 @@ fn run_squash_merge( let commit_output = git::commit_with_message_file(&message_path); let remove_result = fs::remove_file(&message_path); let commit_output = commit_output?; - if let Err(err) = remove_result { - return Err(err); - } + remove_result?; Ok(commit_output) } diff --git a/src/core/reparent/plan.rs b/src/core/reparent/plan.rs index 38f62dd..c2ac26e 100644 --- a/src/core/reparent/plan.rs +++ b/src/core/reparent/plan.rs @@ -110,16 +110,16 @@ pub(crate) fn plan(options: &ReparentOptions) -> io::Result { let new_parent = branch::resolve_parent_ref(&session.state, &session.config, parent_branch_name)?; - if let ParentRef::Branch { node_id: parent_id } = new_parent { - if graph.active_descendant_ids(node.id).contains(&parent_id) { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - format!( - "cannot reparent '{}' onto descendant '{}'", - branch_name, parent_branch_name - ), - )); - } + if let ParentRef::Branch { node_id: parent_id } = new_parent + && graph.active_descendant_ids(node.id).contains(&parent_id) + { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!( + "cannot reparent '{}' onto descendant '{}'", + branch_name, parent_branch_name + ), + )); } let restack_plan = restack::previews_for_actions(&restack::plan_after_branch_reparent( diff --git a/src/core/store/types.rs b/src/core/store/types.rs index 928964e..c50e9ec 100644 --- a/src/core/store/types.rs +++ b/src/core/store/types.rs @@ -348,6 +348,7 @@ pub enum ParentRef { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] +#[allow(clippy::enum_variant_names)] pub enum DaggerEvent { BranchCreated(BranchCreatedEvent), BranchAdopted(BranchAdoptedEvent), diff --git a/src/core/sync.rs b/src/core/sync.rs index 354bd1d..a1755eb 100644 --- a/src/core/sync.rs +++ b/src/core/sync.rs @@ -127,9 +127,9 @@ pub struct SyncOutcome { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RemotePushActionKind { - CreateRemoteBranch, - UpdateRemoteBranch, - ForceUpdateRemoteBranch, + Create, + Update, + ForceUpdate, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -806,6 +806,7 @@ where ) } +#[allow(clippy::too_many_arguments)] fn execute_sync_restack_step( session: &mut crate::core::store::StoreSession, original_branch: &str, @@ -1367,10 +1368,10 @@ where kind: action.kind, })?; let push_output = match action.kind { - RemotePushActionKind::CreateRemoteBranch | RemotePushActionKind::UpdateRemoteBranch => { + RemotePushActionKind::Create | RemotePushActionKind::Update => { git::push_branch_to_remote(&action.target)? } - RemotePushActionKind::ForceUpdateRemoteBranch => { + RemotePushActionKind::ForceUpdate => { git::force_push_branch_to_remote_with_lease(&action.target)? } }; @@ -1513,7 +1514,7 @@ fn plan_remote_push_action( else { return Ok(Some(RemotePushAction { target, - kind: RemotePushActionKind::CreateRemoteBranch, + kind: RemotePushActionKind::Create, })); }; @@ -1525,7 +1526,7 @@ fn plan_remote_push_action( if allow_force_update { return Ok(Some(RemotePushAction { target, - kind: RemotePushActionKind::ForceUpdateRemoteBranch, + kind: RemotePushActionKind::ForceUpdate, })); } @@ -1534,7 +1535,7 @@ fn plan_remote_push_action( if git::merge_base(&remote_tracking_branch_ref, branch_name)? == remote_oid { return Ok(Some(RemotePushAction { target, - kind: RemotePushActionKind::UpdateRemoteBranch, + kind: RemotePushActionKind::Update, })); } @@ -1654,15 +1655,9 @@ mod tests { assert_eq!(plan.actions.len(), 2); assert_eq!(plan.actions[0].target.branch_name, "feat/auth"); - assert_eq!( - plan.actions[0].kind, - RemotePushActionKind::ForceUpdateRemoteBranch - ); + assert_eq!(plan.actions[0].kind, RemotePushActionKind::ForceUpdate); assert_eq!(plan.actions[1].target.branch_name, "feat/auth-ui"); - assert_eq!( - plan.actions[1].kind, - RemotePushActionKind::CreateRemoteBranch - ); + assert_eq!(plan.actions[1].kind, RemotePushActionKind::Create); assert!( plan.actions .iter() @@ -1690,10 +1685,7 @@ mod tests { assert_eq!(plan.actions.len(), 1); assert_eq!(plan.actions[0].target.branch_name, "feat/auth"); - assert_eq!( - plan.actions[0].kind, - RemotePushActionKind::UpdateRemoteBranch - ); + assert_eq!(plan.actions[0].kind, RemotePushActionKind::Update); }); } @@ -1908,14 +1900,14 @@ mod tests { remote_name: "origin".into(), branch_name: "feat/auth".into(), }, - kind: RemotePushActionKind::CreateRemoteBranch, + kind: RemotePushActionKind::Create, }, super::RemotePushAction { target: BranchPushTarget { remote_name: "origin".into(), branch_name: "feat/auth-ui".into(), }, - kind: RemotePushActionKind::CreateRemoteBranch, + kind: RemotePushActionKind::Create, }, ], }; @@ -1935,12 +1927,12 @@ mod tests { SyncStatus::PushingRemoteBranch { branch_name: "feat/auth".into(), remote_name: "origin".into(), - kind: RemotePushActionKind::CreateRemoteBranch, + kind: RemotePushActionKind::Create, }, SyncStatus::PushingRemoteBranch { branch_name: "feat/auth-ui".into(), remote_name: "origin".into(), - kind: RemotePushActionKind::CreateRemoteBranch, + kind: RemotePushActionKind::Create, }, ] );