Skip to content

Commit fe1d543

Browse files
authored
Merge pull request #11070 from gitbutlerapp/copilot/fix-session-timer-issue
Add JSON output support for `but branch new` command
2 parents 874afac + 043374e commit fe1d543

File tree

15 files changed

+160
-98
lines changed

15 files changed

+160
-98
lines changed

crates/but-api/src/commands/stack.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ pub fn remove_branch(
197197
},
198198
)?;
199199
} else {
200-
gitbutler_branch_actions::stack::remove_branch(&ctx, stack_id, branch_name)?;
200+
gitbutler_branch_actions::stack::remove_branch(&ctx, stack_id, &branch_name)?;
201201
}
202202
Ok(())
203203
}

crates/but/src/base.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub enum Subcommands {
1919
Update,
2020
}
2121

22-
pub fn handle(cmd: &Subcommands, project: &Project, json: bool) -> anyhow::Result<()> {
22+
pub fn handle(cmd: Subcommands, project: &Project, json: bool) -> anyhow::Result<()> {
2323
match cmd {
2424
Subcommands::Check => {
2525
if !json {

crates/but/src/branch/mod.rs

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,17 @@ use std::io::{self, Write};
77

88
mod list;
99

10+
mod json {
11+
use serde::Serialize;
12+
13+
#[derive(Debug, Serialize)]
14+
pub struct BranchNewOutput {
15+
pub branch: String,
16+
#[serde(skip_serializing_if = "Option::is_none")]
17+
pub anchor: Option<String>,
18+
}
19+
}
20+
1021
#[derive(Debug, clap::Parser)]
1122
pub struct Platform {
1223
#[clap(subcommand)]
@@ -48,40 +59,33 @@ pub enum Subcommands {
4859
},
4960
}
5061

51-
pub async fn handle(
52-
cmd: &Option<Subcommands>,
53-
project: &Project,
54-
_json: bool,
55-
) -> anyhow::Result<()> {
62+
pub async fn handle(cmd: Option<Subcommands>, project: &Project, json: bool) -> anyhow::Result<()> {
5663
match cmd {
5764
None => {
5865
let local = false;
5966
list::list(project, local).await
6067
}
61-
Some(Subcommands::List { local }) => list::list(project, *local).await,
68+
Some(Subcommands::List { local }) => list::list(project, local).await,
6269
Some(Subcommands::New {
6370
branch_name,
6471
anchor,
6572
}) => {
6673
let ctx =
6774
CommandContext::open(project, AppSettings::load_from_default_path_creating()?)?;
6875
// Get branch name or use canned name
69-
let branch_name = if let Some(name) = branch_name {
70-
let repo = ctx.gix_repo()?;
71-
if repo.try_find_reference(name)?.is_some() {
72-
println!("Branch '{name}' already exists");
73-
return Ok(());
74-
}
75-
name.clone()
76-
} else {
77-
but_api::workspace::canned_branch_name(project.id)?
78-
};
76+
let branch_name = branch_name
77+
.map(Ok::<_, but_api::error::Error>)
78+
.unwrap_or_else(|| but_api::workspace::canned_branch_name(project.id))?;
79+
80+
// Store anchor string for JSON output
81+
let anchor_for_json = anchor.clone();
82+
7983
let anchor = if let Some(anchor_str) = anchor {
8084
// Use the new create_reference API when anchor is provided
8185
let mut ctx = ctx; // Make mutable for CliId resolution
8286

8387
// Resolve the anchor string to a CliId
84-
let anchor_ids = crate::id::CliId::from_str(&mut ctx, anchor_str)?;
88+
let anchor_ids = crate::id::CliId::from_str(&mut ctx, &anchor_str)?;
8589
if anchor_ids.is_empty() {
8690
return Err(anyhow::anyhow!("Could not find anchor: {}", anchor_str));
8791
}
@@ -126,7 +130,14 @@ pub async fn handle(
126130
anchor,
127131
},
128132
)?;
129-
if atty::is(Stream::Stdout) {
133+
134+
if json {
135+
let response = json::BranchNewOutput {
136+
branch: branch_name,
137+
anchor: anchor_for_json,
138+
};
139+
println!("{}", serde_json::to_string_pretty(&response)?);
140+
} else if atty::is(Stream::Stdout) {
130141
println!("Created branch {branch_name}");
131142
} else {
132143
println!("{branch_name}");
@@ -147,7 +158,7 @@ pub async fn handle(
147158
}
148159

149160
if let Some(sid) = stack_entry.id {
150-
return confirm_branch_deletion(project, sid, branch_name, force);
161+
return confirm_branch_deletion(project, sid, &branch_name, force);
151162
}
152163
}
153164

@@ -182,7 +193,7 @@ fn confirm_unapply_stack(
182193
project: &Project,
183194
sid: StackId,
184195
stack_entry: &StackEntry,
185-
force: &bool,
196+
force: bool,
186197
) -> Result<(), anyhow::Error> {
187198
let branches = stack_entry
188199
.heads
@@ -219,8 +230,8 @@ fn confirm_unapply_stack(
219230
fn confirm_branch_deletion(
220231
project: &Project,
221232
sid: StackId,
222-
branch_name: &String,
223-
force: &bool,
233+
branch_name: &str,
234+
force: bool,
224235
) -> Result<(), anyhow::Error> {
225236
if !force {
226237
println!(
@@ -239,7 +250,7 @@ fn confirm_branch_deletion(
239250
}
240251
}
241252

242-
but_api::stack::remove_branch(project.id, sid, branch_name.clone())?;
253+
but_api::stack::remove_branch(project.id, sid, branch_name.to_owned())?;
243254
println!("Deleted branch {branch_name}");
244255
Ok(())
245256
}

crates/but/src/forge/integration.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ pub enum Subcommands {
1818
},
1919
}
2020

21-
pub async fn handle(cmd: &Subcommands) -> anyhow::Result<()> {
21+
pub async fn handle(cmd: Subcommands) -> anyhow::Result<()> {
2222
match cmd {
2323
Subcommands::Auth => auth_github().await,
2424
Subcommands::ListUsers => list_github_users().await,
25-
Subcommands::Forget { username } => forget_github_username(username).await,
25+
Subcommands::Forget { username } => forget_github_username(&username).await,
2626
}
2727
}
2828

crates/but/src/forge/review.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ pub enum Subcommands {
4747

4848
pub async fn publish_reviews(
4949
project: &Project,
50-
branch: &Option<String>,
50+
branch: Option<String>,
5151
skip_force_push_protection: bool,
5252
with_force: bool,
5353
run_hooks: bool,
@@ -59,7 +59,7 @@ pub async fn publish_reviews(
5959
but_api::workspace::stacks(project.id, Some(but_workspace::StacksFilter::InWorkspace))?;
6060
match branch {
6161
Some(branch_id) => {
62-
let branch_names = get_branch_names(project, branch_id)?;
62+
let branch_names = get_branch_names(project, &branch_id)?;
6363
handle_multiple_branches_in_workspace(
6464
project,
6565
&review_map,

crates/but/src/lib.rs

Lines changed: 32 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ pub async fn handle_args(args: impl Iterator<Item = OsString>) -> Result<()> {
5555
return Ok(());
5656
}
5757

58-
let args: Args = clap::Parser::parse_from(args);
58+
let mut args: Args = clap::Parser::parse_from(args);
5959
let app_settings = AppSettings::load_from_default_path_creating()?;
6060

6161
if args.trace > 0 {
@@ -67,7 +67,7 @@ pub async fn handle_args(args: impl Iterator<Item = OsString>) -> Result<()> {
6767
let start = std::time::Instant::now();
6868

6969
// If no subcommand is provided but we have source and target, default to rub
70-
match &args.cmd {
70+
match args.cmd.take() {
7171
None if args.source.is_some() && args.target.is_some() => {
7272
// Default to rub when two arguments are provided without a subcommand
7373
let source = args
@@ -92,19 +92,19 @@ pub async fn handle_args(args: impl Iterator<Item = OsString>) -> Result<()> {
9292
print_grouped_help();
9393
Ok(())
9494
}
95-
Some(cmd) => match_subcommand(cmd, &args, app_settings, start).await,
95+
Some(cmd) => match_subcommand(cmd, args, app_settings, start).await,
9696
}
9797
}
9898

9999
async fn match_subcommand(
100-
cmd: &Subcommands,
101-
args: &Args,
100+
cmd: Subcommands,
101+
args: Args,
102102
app_settings: AppSettings,
103103
start: std::time::Instant,
104104
) -> Result<()> {
105105
match cmd {
106106
Subcommands::Mcp { internal } => {
107-
if *internal {
107+
if internal {
108108
mcp_internal::start(app_settings).await
109109
} else {
110110
mcp::start(app_settings).await
@@ -115,9 +115,8 @@ async fn match_subcommand(
115115
description,
116116
handler,
117117
}) => {
118-
let handler = *handler;
119118
let project = get_or_init_project(&args.current_dir)?;
120-
command::handle_changes(&project, args.json, handler, description)
119+
command::handle_changes(&project, args.json, handler, &description)
121120
}
122121
None => {
123122
let project = get_or_init_project(&args.current_dir)?;
@@ -128,8 +127,8 @@ async fn match_subcommand(
128127
command_name,
129128
props,
130129
} => {
131-
let event = &mut Event::new((*command_name).into());
132-
if let Ok(props) = Props::from_json_string(props) {
130+
let event = &mut Event::new(command_name.into());
131+
if let Ok(props) = Props::from_json_string(&props) {
133132
props.update_event(event);
134133
}
135134
Metrics::capture_blocking(&app_settings, event.clone()).await;
@@ -170,7 +169,7 @@ async fn match_subcommand(
170169
Ok(())
171170
}
172171
cursor::Subcommands::Stop { nightly } => {
173-
let result = but_cursor::handle_stop(*nightly).await;
172+
let result = but_cursor::handle_stop(nightly).await;
174173
let p = props(start, &result);
175174
println!("{}", serde_json::to_string(&result?)?);
176175
metrics_if_configured(app_settings, CommandName::CursorStop, p).ok();
@@ -179,27 +178,23 @@ async fn match_subcommand(
179178
},
180179
Subcommands::Base(base::Platform { cmd }) => {
181180
let project = get_or_init_project(&args.current_dir)?;
181+
let metrics_cmd = match cmd {
182+
base::Subcommands::Check => CommandName::BaseCheck,
183+
base::Subcommands::Update => CommandName::BaseUpdate,
184+
};
182185
let result = base::handle(cmd, &project, args.json);
183-
metrics_if_configured(
184-
app_settings,
185-
match cmd {
186-
base::Subcommands::Check => CommandName::BaseCheck,
187-
base::Subcommands::Update => CommandName::BaseUpdate,
188-
},
189-
props(start, &result),
190-
)
191-
.ok();
186+
metrics_if_configured(app_settings, metrics_cmd, props(start, &result)).ok();
192187
Ok(())
193188
}
194189
Subcommands::Branch(branch::Platform { cmd }) => {
195190
let project = get_or_init_project(&args.current_dir)?;
196-
let result = branch::handle(cmd, &project, args.json).await;
197191
let metrics_command = match cmd {
198192
None | Some(branch::Subcommands::List { .. }) => CommandName::BranchList,
199193
Some(branch::Subcommands::New { .. }) => CommandName::BranchNew,
200194
Some(branch::Subcommands::Delete { .. }) => CommandName::BranchDelete,
201195
Some(branch::Subcommands::Unapply { .. }) => CommandName::BranchUnapply,
202196
};
197+
let result = branch::handle(cmd, &project, args.json).await;
203198
metrics_if_configured(app_settings, metrics_command, props(start, &result)).ok();
204199
result
205200
}
@@ -222,21 +217,20 @@ async fn match_subcommand(
222217
review,
223218
} => {
224219
let project = get_or_init_project(&args.current_dir)?;
225-
let result =
226-
status::worktree(&project, args.json, *show_files, *verbose, *review).await;
220+
let result = status::worktree(&project, args.json, show_files, verbose, review).await;
227221
metrics_if_configured(app_settings, CommandName::Status, props(start, &result)).ok();
228222
result
229223
}
230224
Subcommands::Stf { verbose, review } => {
231225
let project = get_or_init_project(&args.current_dir)?;
232-
let result = status::worktree(&project, args.json, true, *verbose, *review).await;
226+
let result = status::worktree(&project, args.json, true, verbose, review).await;
233227
metrics_if_configured(app_settings, CommandName::Stf, props(start, &result)).ok();
234228
result
235229
}
236230
Subcommands::Rub { source, target } => {
237231
let project = get_or_init_project(&args.current_dir)?;
238232
let result =
239-
rub::handle(&project, args.json, source, target).context("Rubbed the wrong way.");
233+
rub::handle(&project, args.json, &source, &target).context("Rubbed the wrong way.");
240234
if let Err(e) = &result {
241235
eprintln!("{} {}", e, e.root_cause());
242236
}
@@ -245,7 +239,7 @@ async fn match_subcommand(
245239
}
246240
Subcommands::Mark { target, delete } => {
247241
let project = get_or_init_project(&args.current_dir)?;
248-
let result = mark::handle(&project, args.json, target, *delete)
242+
let result = mark::handle(&project, args.json, &target, delete)
249243
.context("Can't mark this. Taaaa-na-na-na. Can't mark this.");
250244
if let Err(e) = &result {
251245
eprintln!("{} {}", e, e.root_cause());
@@ -280,8 +274,8 @@ async fn match_subcommand(
280274
args.json,
281275
message.as_deref(),
282276
branch.as_deref(),
283-
*only,
284-
*create,
277+
only,
278+
create,
285279
);
286280
metrics_if_configured(app_settings, CommandName::Commit, props(start, &result)).ok();
287281
result
@@ -294,13 +288,13 @@ async fn match_subcommand(
294288
}
295289
Subcommands::New { target } => {
296290
let project = get_or_init_project(&args.current_dir)?;
297-
let result = commit::insert_blank_commit(&project, args.json, target);
291+
let result = commit::insert_blank_commit(&project, args.json, &target);
298292
metrics_if_configured(app_settings, CommandName::New, props(start, &result)).ok();
299293
result
300294
}
301295
Subcommands::Describe { target } => {
302296
let project = get_or_init_project(&args.current_dir)?;
303-
let result = describe::describe_target(&project, args.json, target);
297+
let result = describe::describe_target(&project, args.json, &target);
304298
metrics_if_configured(app_settings, CommandName::Describe, props(start, &result)).ok();
305299
result
306300
}
@@ -312,7 +306,7 @@ async fn match_subcommand(
312306
}
313307
Subcommands::Restore { oplog_sha, force } => {
314308
let project = get_or_init_project(&args.current_dir)?;
315-
let result = oplog::restore_to_oplog(&project, args.json, oplog_sha, *force);
309+
let result = oplog::restore_to_oplog(&project, args.json, &oplog_sha, force);
316310
metrics_if_configured(app_settings, CommandName::Restore, props(start, &result)).ok();
317311
result
318312
}
@@ -334,15 +328,15 @@ async fn match_subcommand(
334328
metrics_if_configured(app_settings, CommandName::Snapshot, props(start, &result)).ok();
335329
result
336330
}
337-
Subcommands::Init { repo } => init::repo(&args.current_dir, args.json, *repo)
331+
Subcommands::Init { repo } => init::repo(&args.current_dir, args.json, repo)
338332
.context("Failed to initialize GitButler project."),
339333
Subcommands::Forge(forge::integration::Platform { cmd }) => {
340-
let result = forge::integration::handle(cmd).await;
341334
let metrics_cmd = match cmd {
342335
forge::integration::Subcommands::Auth => CommandName::ForgeAuth,
343336
forge::integration::Subcommands::ListUsers => CommandName::ForgeListUsers,
344337
forge::integration::Subcommands::Forget { .. } => CommandName::ForgeForget,
345338
};
339+
let result = forge::integration::handle(cmd).await;
346340
metrics_if_configured(app_settings, metrics_cmd, props(start, &result)).ok();
347341
result
348342
}
@@ -358,10 +352,10 @@ async fn match_subcommand(
358352
let result = forge::review::publish_reviews(
359353
&project,
360354
branch,
361-
*skip_force_push_protection,
362-
*with_force,
363-
*run_hooks,
364-
*default,
355+
skip_force_push_protection,
356+
with_force,
357+
run_hooks,
358+
default,
365359
args.json,
366360
)
367361
.await
@@ -375,7 +369,7 @@ async fn match_subcommand(
375369
result
376370
}
377371
},
378-
Subcommands::Completions { shell } => completions::generate_completions(*shell),
372+
Subcommands::Completions { shell } => completions::generate_completions(shell),
379373
}
380374
}
381375

0 commit comments

Comments
 (0)