Skip to content

Commit cd074f4

Browse files
committed
Allow configuring fork push remote from CLI
1 parent fddd4c1 commit cd074f4

6 files changed

Lines changed: 100 additions & 9 deletions

File tree

crates/but/src/args/config.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,18 @@ pub enum Subcommands {
9696
/// ```text
9797
/// but config target origin/main
9898
/// ```
99+
///
100+
/// Set a target branch and push branches to a fork:
101+
///
102+
/// ```text
103+
/// but config target upstream/main --push-remote origin
104+
/// ```
99105
Target {
100106
/// New target branch to set (e.g., "origin/main")
101107
branch: Option<String>,
108+
/// Remote to push branches to (e.g., "origin" for a fork).
109+
#[clap(long, value_name = "REMOTE", requires = "branch")]
110+
push_remote: Option<String>,
102111
},
103112

104113
/// View or set metrics collection.

crates/but/src/args/tests.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,50 @@ fn output_format_parses_agent() {
453453
assert!(matches!(args.format.format, OutputFormat::Agent));
454454
}
455455

456+
mod config_target {
457+
use clap::Parser;
458+
459+
use crate::args::{
460+
Args, Subcommands,
461+
config::{Platform as ConfigPlatform, Subcommands as ConfigCmd},
462+
};
463+
464+
#[test]
465+
fn parses_push_remote_for_fork() {
466+
let args = Args::try_parse_from([
467+
"but",
468+
"config",
469+
"target",
470+
"upstream/main",
471+
"--push-remote",
472+
"origin",
473+
])
474+
.expect("parse args");
475+
476+
match args.cmd.expect("subcommand") {
477+
Subcommands::Config(ConfigPlatform {
478+
cmd:
479+
Some(ConfigCmd::Target {
480+
branch,
481+
push_remote,
482+
}),
483+
}) => {
484+
assert_eq!(branch.as_deref(), Some("upstream/main"));
485+
assert_eq!(push_remote.as_deref(), Some("origin"));
486+
}
487+
_ => panic!("unexpected command shape"),
488+
}
489+
}
490+
491+
#[test]
492+
fn push_remote_requires_target_branch() {
493+
let err = Args::try_parse_from(["but", "config", "target", "--push-remote", "origin"])
494+
.expect_err("push remote requires a target branch");
495+
496+
assert_eq!(err.kind(), clap::error::ErrorKind::MissingRequiredArgument);
497+
}
498+
}
499+
456500
#[test]
457501
fn output_format_agent_is_text_without_human_ui() {
458502
let format = OutputFormat::Agent;

crates/but/src/command/config.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,10 @@ pub async fn exec(
8989
) -> Result<()> {
9090
match cmd {
9191
Some(Subcommands::User { cmd }) => user_config(ctx, out, cmd).await,
92-
Some(Subcommands::Target { branch }) => target_config(ctx, out, branch).await,
92+
Some(Subcommands::Target {
93+
branch,
94+
push_remote,
95+
}) => target_config(ctx, out, branch, push_remote).await,
9396
Some(Subcommands::Forge { cmd }) => forge_config(out, cmd).await,
9497
Some(Subcommands::Metrics { status }) => metrics_config(out, status).await,
9598
Some(Subcommands::Ai { local, global, cmd }) => {
@@ -1764,6 +1767,7 @@ async fn target_config(
17641767
ctx: &mut Context,
17651768
out: &mut OutputChannel,
17661769
branch: Option<String>,
1770+
push_remote: Option<String>,
17671771
) -> Result<()> {
17681772
let t = theme::get();
17691773
match branch {
@@ -1866,17 +1870,26 @@ async fn target_config(
18661870
)?;
18671871
}
18681872

1869-
// from the new_branch string, we need to parse out the remote name and branch name
1873+
if let Some(push_remote) = push_remote.as_deref() {
1874+
ctx.repo
1875+
.get()?
1876+
.find_remote(push_remote)
1877+
.with_context(|| format!("Failed to find push remote '{push_remote}'"))?;
1878+
}
1879+
1880+
let target_ref: gix::refs::FullName = format!("refs/remotes/{new_branch}")
1881+
.try_into()
1882+
.context("Invalid target branch name")?;
1883+
drop((guard, ws));
18701884
cfg_if! {
18711885
if #[cfg(feature = "legacy")] {
1872-
drop((guard, ws));
1873-
but_api::legacy::virtual_branches::set_base_branch(
1886+
but_api::workspace::set_target_ref_and_init_project(
18741887
ctx,
1875-
new_branch.clone(),
1876-
None,
1888+
target_ref.as_ref(),
1889+
push_remote,
18771890
)?;
18781891
} else {
1879-
anyhow::bail!("Cannot yet set the base-branch without legacy functions - needs port")
1892+
anyhow::bail!("Cannot yet set the target branch without legacy functions")
18801893
}
18811894
};
18821895
}

crates/but/tests/but/command/config.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,28 @@
11
use crate::utils::{CommandExt as _, Sandbox};
22
use snapbox::str;
33

4+
#[cfg(feature = "legacy")]
5+
#[test]
6+
fn target_configures_distinct_push_remote_for_fork() {
7+
let env = Sandbox::open_with_default_settings("repo-with-remote-and-head");
8+
env.but("setup").assert().success();
9+
env.invoke_git("remote add upstream .");
10+
env.invoke_git("update-ref refs/remotes/upstream/main refs/remotes/origin/main");
11+
12+
env.but("config target upstream/main --push-remote origin")
13+
.assert()
14+
.success();
15+
16+
assert_eq!(
17+
env.invoke_git("config --local --get gitbutler.project.targetRef"),
18+
"refs/remotes/upstream/main"
19+
);
20+
assert_eq!(
21+
env.invoke_git("config --local --get gitbutler.project.pushRemote"),
22+
"origin"
23+
);
24+
}
25+
426
#[test]
527
fn ai_openai_defaults_to_global_config() {
628
let env = Sandbox::empty();

crates/gitbutler-branch-actions/tests/branch-actions/driverless.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ pub fn writable_context(script_name: &str, repo_name: &str) -> Result<(Context,
2323
let (tmp, _) = gix_testtools::scripted_fixture_writable_with_args_with_post(
2424
script_name.clone(),
2525
None::<String>,
26-
if script_name == "reorder.sh" || script_name == "workspace-commit.sh" {
26+
if script_name == "reorder.sh"
27+
|| script_name == "workspace-commit.sh"
28+
|| script_name == "for-listing.sh"
29+
{
2730
gix_testtools::Creation::Execute
2831
} else {
2932
gix_testtools::Creation::CopyFromReadOnly

e2e/playwright/tests/singleBranch/commitActions.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ function git(pathToRepo: string, args: string[]): string {
409409
function localBranches(pathToRepo: string): string[] {
410410
return git(pathToRepo, ["for-each-ref", "--format=%(refname:short)", "refs/heads"])
411411
.split("\n")
412-
.filter(Boolean);
412+
.filter((branch) => branch && !branch.startsWith("gitbutler/"));
413413
}
414414

415415
async function createGeneratedBranch(

0 commit comments

Comments
 (0)