use std::path::Path;
use crate::error::Result;
use crate::git::cli::{GitCli, GitOutput};
pub(crate) fn worktree_add(
git: &dyn GitCli,
root: &Path,
target: &str,
branch: &str,
) -> Result<String> {
git.run(root, &["worktree", "add", target, branch])
}
pub(crate) fn worktree_add_branch(
git: &dyn GitCli,
root: &Path,
branch: &str,
target: &str,
start: &str,
no_track: bool,
) -> Result<String> {
let mut argv = vec!["worktree", "add"];
if no_track {
argv.push("--no-track");
}
argv.extend(["-b", branch, target, start]);
git.run(root, &argv)
}
pub(crate) fn worktree_remove(
git: &dyn GitCli,
root: &Path,
path: &str,
force: bool,
) -> Result<String> {
let mut argv = vec!["worktree", "remove"];
if force {
argv.push("--force");
}
argv.push(path);
git.run(root, &argv)
}
pub(crate) fn worktree_prune(git: &dyn GitCli, root: &Path) -> Result<String> {
git.run(root, &["worktree", "prune"])
}
pub(crate) fn delete_branch(
git: &dyn GitCli,
root: &Path,
branch: &str,
force: bool,
) -> Result<GitOutput> {
let flag = if force { "-D" } else { "-d" };
git.run_raw(root, &["branch", flag, branch])
}
pub(crate) fn set_branch_ref(
git: &dyn GitCli,
root: &Path,
branch: &str,
to: &str,
) -> Result<String> {
git.run(root, &["branch", "-f", branch, to])
}
pub(crate) fn set_upstream(
git: &dyn GitCli,
root: &Path,
branch: &str,
upstream: &str,
) -> Result<String> {
git.run(root, &["branch", "-u", upstream, branch])
}
pub(crate) fn fetch(git: &dyn GitCli, dir: &Path, remote: &str) -> Result<GitOutput> {
git.run_raw(dir, &["fetch", remote])
}
pub(crate) fn fetch_refspec(
git: &dyn GitCli,
dir: &Path,
remote: &str,
refspec: &str,
) -> Result<String> {
git.run(dir, &["fetch", remote, refspec])
}
pub(crate) fn merge_ff_only(git: &dyn GitCli, dir: &Path, tracking_ref: &str) -> Result<String> {
git.run(dir, &["merge", "--ff-only", tracking_ref])
}
pub(crate) fn push(git: &dyn GitCli, dir: &Path, remote: &str, branch: &str) -> Result<GitOutput> {
git.run_raw(dir, &["push", remote, branch])
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
use std::path::PathBuf;
struct RecordingGit {
calls: RefCell<Vec<Vec<String>>>,
output: GitOutput,
}
impl RecordingGit {
fn new() -> Self {
RecordingGit {
calls: RefCell::new(Vec::new()),
output: GitOutput {
success: true,
stdout: String::new(),
stderr: String::new(),
},
}
}
fn last(&self) -> Vec<String> {
self.calls.borrow().last().cloned().unwrap_or_default()
}
}
impl GitCli for RecordingGit {
fn run_raw(&self, _repo: &Path, args: &[&str]) -> Result<GitOutput> {
self.calls
.borrow_mut()
.push(args.iter().map(|s| s.to_string()).collect());
Ok(self.output.clone())
}
}
fn root() -> PathBuf {
PathBuf::from("/r")
}
#[test]
fn worktree_add_builds_plain_add() {
let git = RecordingGit::new();
worktree_add(&git, &root(), "/wt/x", "topic").unwrap();
assert_eq!(git.last(), ["worktree", "add", "/wt/x", "topic"]);
}
#[test]
fn worktree_add_branch_adds_no_track_flag_only_when_asked() {
let git = RecordingGit::new();
worktree_add_branch(&git, &root(), "topic", "/wt/x", "main", true).unwrap();
assert_eq!(
git.last(),
[
"worktree",
"add",
"--no-track",
"-b",
"topic",
"/wt/x",
"main"
]
);
worktree_add_branch(&git, &root(), "topic", "/wt/x", "FETCH_HEAD", false).unwrap();
assert_eq!(
git.last(),
["worktree", "add", "-b", "topic", "/wt/x", "FETCH_HEAD"]
);
}
#[test]
fn worktree_remove_adds_force_flag_only_when_asked() {
let git = RecordingGit::new();
worktree_remove(&git, &root(), "/wt/x", false).unwrap();
assert_eq!(git.last(), ["worktree", "remove", "/wt/x"]);
worktree_remove(&git, &root(), "/wt/x", true).unwrap();
assert_eq!(git.last(), ["worktree", "remove", "--force", "/wt/x"]);
}
#[test]
fn worktree_prune_builds_prune() {
let git = RecordingGit::new();
worktree_prune(&git, &root()).unwrap();
assert_eq!(git.last(), ["worktree", "prune"]);
}
#[test]
fn delete_branch_selects_capital_d_only_under_force() {
let git = RecordingGit::new();
delete_branch(&git, &root(), "topic", false).unwrap();
assert_eq!(git.last(), ["branch", "-d", "topic"]);
delete_branch(&git, &root(), "topic", true).unwrap();
assert_eq!(git.last(), ["branch", "-D", "topic"]);
}
#[test]
fn set_branch_ref_forces_the_named_branch() {
let git = RecordingGit::new();
set_branch_ref(&git, &root(), "main", "refs/remotes/origin/main").unwrap();
assert_eq!(
git.last(),
["branch", "-f", "main", "refs/remotes/origin/main"]
);
}
#[test]
fn set_upstream_passes_upstream_before_branch() {
let git = RecordingGit::new();
set_upstream(&git, &root(), "feat", "origin/main").unwrap();
assert_eq!(git.last(), ["branch", "-u", "origin/main", "feat"]);
}
#[test]
fn fetch_builds_bare_fetch() {
let git = RecordingGit::new();
fetch(&git, &root(), "origin").unwrap();
assert_eq!(git.last(), ["fetch", "origin"]);
}
#[test]
fn fetch_refspec_appends_the_refspec() {
let git = RecordingGit::new();
fetch_refspec(&git, &root(), "origin", "pull/7/head").unwrap();
assert_eq!(git.last(), ["fetch", "origin", "pull/7/head"]);
}
#[test]
fn merge_ff_only_builds_fast_forward_merge() {
let git = RecordingGit::new();
merge_ff_only(&git, &root(), "refs/remotes/origin/main").unwrap();
assert_eq!(
git.last(),
["merge", "--ff-only", "refs/remotes/origin/main"]
);
}
#[test]
fn push_builds_explicit_remote_branch_push() {
let git = RecordingGit::new();
push(&git, &root(), "origin", "feature/x").unwrap();
assert_eq!(git.last(), ["push", "origin", "feature/x"]);
}
}