Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions src/cmds/git/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1275,6 +1275,9 @@ fn run_branch(args: &[String], verbose: u8, global_args: &[String]) -> Result<i3
|| a == "--points-at"
|| a.starts_with("--points-at=")
});
let wants_native_branch_list = args
.iter()
.any(|a| matches!(a.as_str(), "-a" | "--all" | "-r" | "--remotes"));

// Detect positional arguments (not flags) — indicates branch creation
let has_positional_arg = args.iter().any(|a| !a.starts_with('-'));
Expand Down Expand Up @@ -1343,6 +1346,39 @@ fn run_branch(args: &[String], verbose: u8, global_args: &[String]) -> Result<i3
return Ok(0);
}

// Explicit all/remotes flags are often used to inspect exact ref names, so
// preserve git's native listing instead of collapsing remote-tracking refs.
if wants_native_branch_list {
let mut cmd = git_cmd(global_args);
cmd.arg("branch");
cmd.arg("--no-color");
for arg in args {
cmd.arg(arg);
}
let result = exec_capture(&mut cmd).context("Failed to run git branch")?;
let combined = result.combined();

timer.track(
&format!("git branch {}", args.join(" ")),
&format!("rtk git branch {} (passthrough)", args.join(" ")),
&combined,
&result.stdout,
);

if result.success() {
print!("{}", result.stdout);
} else {
if !result.stdout.is_empty() {
print!("{}", result.stdout);
}
if !result.stderr.is_empty() {
eprint!("{}", result.stderr);
}
return Ok(result.exit_code);
}
return Ok(0);
}

// List mode: show compact branch list
let mut cmd = git_cmd(global_args);
cmd.arg("branch");
Expand Down
150 changes: 150 additions & 0 deletions tests/git_branch_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#![cfg(unix)]

use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;

fn rtk() -> Command {
Command::new(env!("CARGO_BIN_EXE_rtk"))
}

fn git(repo: impl AsRef<Path>, args: &[&str]) {
let output = Command::new("git")
.current_dir(repo)
.args(args)
.output()
.expect("run git");
assert!(
output.status.success(),
"git {:?} failed\nstdout:\n{}\nstderr:\n{}",
args,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}

fn setup_remote_repo() -> (tempfile::TempDir, PathBuf) {
let dir = tempfile::tempdir().expect("tempdir");
let origin = dir.path().join("origin.git");
let seed = dir.path().join("seed");
let client = dir.path().join("client");

let output = Command::new("git")
.args(["init", "--bare", origin.to_str().unwrap()])
.output()
.expect("git init --bare");
assert!(output.status.success());

let output = Command::new("git")
.args(["init", seed.to_str().unwrap()])
.output()
.expect("git init seed");
assert!(output.status.success());

git(&seed, &["config", "user.name", "Test"]);
git(&seed, &["config", "user.email", "test@example.com"]);
fs::write(seed.join("README.md"), "hello\n").expect("write README");
git(&seed, &["add", "README.md"]);
git(&seed, &["commit", "-m", "init"]);
git(&seed, &["branch", "-M", "main"]);
git(
&seed,
&["remote", "add", "origin", origin.to_str().unwrap()],
);
git(&seed, &["push", "-u", "origin", "main"]);

git(&seed, &["checkout", "-b", "feature"]);
fs::write(seed.join("feature.txt"), "feature\n").expect("write feature");
git(&seed, &["add", "feature.txt"]);
git(&seed, &["commit", "-m", "feature"]);
git(&seed, &["push", "-u", "origin", "feature"]);

let output = Command::new("git")
.arg("--git-dir")
.arg(&origin)
.args(["symbolic-ref", "HEAD", "refs/heads/main"])
.output()
.expect("set origin HEAD");
assert!(output.status.success());

let output = Command::new("git")
.args(["clone", origin.to_str().unwrap(), client.to_str().unwrap()])
.output()
.expect("git clone");
assert!(
output.status.success(),
"git clone failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);

(dir, client)
}

#[test]
fn branch_all_preserves_remote_tracking_refs() {
let (_dir, client) = setup_remote_repo();

let output = rtk()
.current_dir(client)
.args(["git", "branch", "-a"])
.output()
.expect("rtk git branch -a");
let stdout = String::from_utf8_lossy(&output.stdout);

assert!(
output.status.success(),
"rtk git branch -a failed\nstdout:\n{stdout}\nstderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
assert!(
stdout.contains("remotes/origin/HEAD -> origin/main"),
"missing origin HEAD in branch -a output:\n{stdout}"
);
assert!(
stdout.contains("remotes/origin/main"),
"missing origin/main in branch -a output:\n{stdout}"
);
assert!(
stdout.contains("remotes/origin/feature"),
"missing origin/feature in branch -a output:\n{stdout}"
);
assert!(
!stdout.contains("remote-only"),
"explicit -a should not collapse remotes into a summary:\n{stdout}"
);
}

#[test]
fn branch_remotes_preserves_native_remote_listing() {
let (_dir, client) = setup_remote_repo();

let output = rtk()
.current_dir(client)
.args(["git", "branch", "-r"])
.output()
.expect("rtk git branch -r");
let stdout = String::from_utf8_lossy(&output.stdout);

assert!(
output.status.success(),
"rtk git branch -r failed\nstdout:\n{stdout}\nstderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
assert!(
!stdout.starts_with("* \n"),
"explicit -r should not inject a blank current branch:\n{stdout}"
);
assert!(
stdout.contains("origin/HEAD -> origin/main"),
"missing origin HEAD in branch -r output:\n{stdout}"
);
assert!(
stdout.contains("origin/main"),
"missing origin/main in branch -r output:\n{stdout}"
);
assert!(
stdout.contains("origin/feature"),
"missing origin/feature in branch -r output:\n{stdout}"
);
}
Loading