Skip to content

Commit c363251

Browse files
committed
refactor(bitbucket): extract shared helpers to pr parent module
Move duplicated set_spinner_msg and format_state from list.rs, read.rs, and create.rs into pr/mod.rs as pub(crate) functions. Unify field-level attributes from #[clap(..)] to #[arg(..)] and remove dead Serialize derives and unused variables in the bitbucket PR files.
1 parent 8796429 commit c363251

3 files changed

Lines changed: 45 additions & 56 deletions

File tree

crates/mcptools/src/atlassian/bitbucket/pr/list.rs

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,18 @@ use indicatif::{ProgressBar, ProgressStyle};
55
use mcptools_core::atlassian::bitbucket::{
66
transform_pr_list_response, BitbucketPRListResponse, PRListOutput,
77
};
8-
use serde::{Deserialize, Serialize};
8+
use serde::Deserialize;
99

1010
/// Options for listing Bitbucket PRs
11-
#[derive(Debug, clap::Args, Serialize, Deserialize, Clone)]
11+
#[derive(Debug, clap::Args, Deserialize, Clone)]
1212
pub struct ListOptions {
1313
/// Repository in workspace/repo_slug format (e.g., "myworkspace/myrepo")
14-
#[clap(long, short = 'r')]
14+
#[arg(long, short = 'r')]
1515
pub repo: String,
1616

1717
/// Filter by PR state (can be repeated: --state OPEN --state MERGED)
1818
/// Valid values: OPEN, MERGED, DECLINED, SUPERSEDED
19-
#[clap(long, value_name = "STATE")]
19+
#[arg(long, value_name = "STATE")]
2020
pub state: Option<Vec<String>>,
2121

2222
/// Maximum number of results to return per page
@@ -28,7 +28,7 @@ pub struct ListOptions {
2828
pub next_page: Option<String>,
2929

3030
/// Bitbucket API base URL (overrides BITBUCKET_BASE_URL env var)
31-
#[clap(long)]
31+
#[arg(long)]
3232
pub base_url: Option<String>,
3333

3434
/// Output as JSON
@@ -53,13 +53,6 @@ pub struct ListPRParams {
5353
pub app_password_override: Option<String>,
5454
}
5555

56-
/// Helper to set spinner message if spinner is present
57-
fn set_spinner_msg(spinner: Option<&ProgressBar>, msg: impl Into<String>) {
58-
if let Some(s) = spinner {
59-
s.set_message(msg.into());
60-
}
61-
}
62-
6356
/// Fetch PR list from Bitbucket API
6457
///
6558
/// This function fetches a paginated list of pull requests from the specified repository.
@@ -102,7 +95,7 @@ pub async fn list_pr_data(
10295
}
10396
};
10497

105-
set_spinner_msg(spinner, format!("Fetching PRs from {}...", repo));
98+
super::set_spinner_msg(spinner, format!("Fetching PRs from {}...", repo));
10699
let response = client
107100
.get(&url)
108101
.send()
@@ -187,13 +180,11 @@ pub async fn handler(options: ListOptions, global: crate::Global) -> Result<()>
187180
]);
188181

189182
for pr in &data.pull_requests {
190-
let branch_info = format!("{} → {}", pr.source_branch, pr.destination_branch);
191-
192183
table.add_row(prettytable::row![
193184
pr.id.to_string().bright_yellow(),
194185
pr.title.bright_white(),
195186
pr.author.bright_magenta(),
196-
format_state(&pr.state),
187+
super::format_state(&pr.state),
197188
format!(
198189
"{} → {}",
199190
pr.source_branch.bright_green(),
@@ -219,14 +210,3 @@ pub async fn handler(options: ListOptions, global: crate::Global) -> Result<()>
219210

220211
Ok(())
221212
}
222-
223-
/// Format PR state with appropriate color
224-
fn format_state(state: &str) -> String {
225-
match state.to_uppercase().as_str() {
226-
"OPEN" => state.bright_green().to_string(),
227-
"MERGED" => state.bright_magenta().to_string(),
228-
"DECLINED" => state.bright_red().to_string(),
229-
"SUPERSEDED" => state.bright_yellow().to_string(),
230-
_ => state.to_string(),
231-
}
232-
}

crates/mcptools/src/atlassian/bitbucket/pr/mod.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
pub mod create;
12
pub mod list;
23
pub mod read;
34

45
use crate::prelude::{println, *};
6+
use color_eyre::owo_colors::OwoColorize;
7+
use indicatif::ProgressBar;
58

69
/// Pull request commands
710
#[derive(Debug, clap::Subcommand)]
@@ -13,6 +16,10 @@ pub enum Commands {
1316
/// Read pull request details, comments, and diff link
1417
#[clap(name = "read")]
1518
Read(read::ReadOptions),
19+
20+
/// Create a new pull request
21+
#[clap(name = "create")]
22+
Create(create::CreateOptions),
1623
}
1724

1825
/// Run PR commands
@@ -24,5 +31,24 @@ pub async fn run(cmd: Commands, global: crate::Global) -> Result<()> {
2431
match cmd {
2532
Commands::List(options) => list::handler(options, global).await,
2633
Commands::Read(options) => read::handler(options, global).await,
34+
Commands::Create(options) => create::handler(options, global).await,
35+
}
36+
}
37+
38+
/// Helper to set spinner message if spinner is present
39+
pub(crate) fn set_spinner_msg(spinner: Option<&ProgressBar>, msg: impl Into<String>) {
40+
if let Some(s) = spinner {
41+
s.set_message(msg.into());
42+
}
43+
}
44+
45+
/// Format PR state with appropriate color
46+
pub(crate) fn format_state(state: &str) -> String {
47+
match state.to_uppercase().as_str() {
48+
"OPEN" => state.bright_green().to_string(),
49+
"MERGED" => state.bright_magenta().to_string(),
50+
"DECLINED" => state.bright_red().to_string(),
51+
"SUPERSEDED" => state.bright_yellow().to_string(),
52+
_ => state.to_string(),
2753
}
2854
}

crates/mcptools/src/atlassian/bitbucket/pr/read.rs

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,21 @@ use mcptools_core::atlassian::bitbucket::{
66
transform_pr_response, BitbucketComment, BitbucketCommentsResponse, BitbucketDiffstat,
77
BitbucketDiffstatResponse, BitbucketPRResponse, PROutput,
88
};
9-
use serde::{Deserialize, Serialize};
9+
use serde::Deserialize;
1010

1111
/// Options for reading a Bitbucket PR
12-
#[derive(Debug, clap::Args, Serialize, Deserialize, Clone)]
12+
#[derive(Debug, clap::Args, Deserialize, Clone)]
1313
pub struct ReadOptions {
1414
/// Repository in workspace/repo_slug format (e.g., "myworkspace/myrepo")
15-
#[clap(long, short = 'r')]
15+
#[arg(long, short = 'r')]
1616
pub repo: String,
1717

1818
/// Pull request number
19-
#[clap(value_name = "PR_NUMBER")]
19+
#[arg(value_name = "PR_NUMBER")]
2020
pub pr_number: u64,
2121

2222
/// Bitbucket API base URL (overrides BITBUCKET_BASE_URL env var)
23-
#[clap(long)]
23+
#[arg(long)]
2424
pub base_url: Option<String>,
2525

2626
/// Maximum number of comments per page (default: 100)
@@ -75,13 +75,6 @@ pub struct ReadPRParams {
7575
pub no_diff: bool,
7676
}
7777

78-
/// Helper to set spinner message if spinner is present
79-
fn set_spinner_msg(spinner: Option<&ProgressBar>, msg: impl Into<String>) {
80-
if let Some(s) = spinner {
81-
s.set_message(msg.into());
82-
}
83-
}
84-
8578
/// Fetch PR data from Bitbucket API
8679
///
8780
/// This function fetches PR details, comments, diffstats, and optionally diff content.
@@ -104,7 +97,7 @@ pub async fn read_pr_data(params: ReadPRParams, spinner: Option<&ProgressBar>) -
10497
let base_url = config.base_url.trim_end_matches('/');
10598

10699
// Fetch PR details
107-
set_spinner_msg(
100+
super::set_spinner_msg(
108101
spinner,
109102
format!("Fetching PR #{} from {}...", pr_number, repo),
110103
);
@@ -145,7 +138,7 @@ pub async fn read_pr_data(params: ReadPRParams, spinner: Option<&ProgressBar>) -
145138
.ok_or_else(|| eyre!("PR response missing destination commit hash"))?;
146139

147140
// Fetch diffstats (auto-paginate by default)
148-
set_spinner_msg(spinner, "Fetching diffstats...");
141+
super::set_spinner_msg(spinner, "Fetching diffstats...");
149142
let diffstats = fetch_all_diffstats(
150143
&client,
151144
base_url,
@@ -162,12 +155,12 @@ pub async fn read_pr_data(params: ReadPRParams, spinner: Option<&ProgressBar>) -
162155
let diff_content = if no_diff {
163156
None
164157
} else {
165-
set_spinner_msg(spinner, "Fetching diff content...");
158+
super::set_spinner_msg(spinner, "Fetching diff content...");
166159
Some(fetch_diff_content(&client, base_url, &repo, pr_number).await?)
167160
};
168161

169162
// Fetch ALL comments (auto-paginate by default)
170-
set_spinner_msg(spinner, "Fetching comments...");
163+
super::set_spinner_msg(spinner, "Fetching comments...");
171164
let comments = fetch_all_comments(
172165
&client,
173166
base_url,
@@ -245,7 +238,7 @@ async fn fetch_all_diffstats(
245238
let mut page = 1;
246239
while let Some(url) = next_url {
247240
if page > 1 {
248-
set_spinner_msg(spinner, format!("Fetching diffstats (page {})...", page));
241+
super::set_spinner_msg(spinner, format!("Fetching diffstats (page {})...", page));
249242
}
250243
let response = client
251244
.get(&url)
@@ -299,7 +292,7 @@ async fn fetch_all_comments(
299292
let mut page = 1;
300293
while let Some(url) = next_url {
301294
if page > 1 {
302-
set_spinner_msg(
295+
super::set_spinner_msg(
303296
spinner,
304297
format!(
305298
"Fetching comments (page {}, {} found)...",
@@ -389,7 +382,7 @@ pub async fn handler(options: ReadOptions, global: crate::Global) -> Result<()>
389382
let mut table = crate::prelude::new_table();
390383
table.add_row(prettytable::row![
391384
"State".bold().cyan(),
392-
format_state(&pr.state)
385+
super::format_state(&pr.state)
393386
]);
394387
table.add_row(prettytable::row![
395388
"Author".bold().cyan(),
@@ -601,13 +594,3 @@ fn render_change_bar(additions: u32, deletions: u32) -> String {
601594

602595
format!("{}{}", add_bar, del_bar)
603596
}
604-
605-
fn format_state(state: &str) -> String {
606-
match state.to_uppercase().as_str() {
607-
"OPEN" => state.bright_green().to_string(),
608-
"MERGED" => state.bright_magenta().to_string(),
609-
"DECLINED" => state.bright_red().to_string(),
610-
"SUPERSEDED" => state.bright_yellow().to_string(),
611-
_ => state.to_string(),
612-
}
613-
}

0 commit comments

Comments
 (0)