Drives the user's installed `git` via std::process::Command — the same approach as the TS app's git-sys backend — so no libgit2/git2 C dependency enters the workspace. Covers repo discovery / init, working-tree status, staging, commit, branch list / create / switch, history + diff, remotes, SSH keys and credential storage. Adds a worktree-isolated merge orchestrator: a branch merge runs in a throwaway detached worktree so a conflicting .op merge never writes conflict markers into the live document — a clean merge is fast-forwarded back, a conflicting one is reported as a file-granular ConflictBag with the live tree left pristine.
69 lines
2.2 KiB
Rust
69 lines
2.2 KiB
Rust
//! Branch listing, creation, deletion and switching.
|
|
|
|
use crate::{GitError, GitRepo};
|
|
|
|
/// A local branch.
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct Branch {
|
|
/// Short branch name (`main`, `feature/x`).
|
|
pub name: String,
|
|
/// Whether this is the currently checked-out branch.
|
|
pub is_current: bool,
|
|
}
|
|
|
|
impl GitRepo {
|
|
/// The currently checked-out branch, or `None` on a detached
|
|
/// `HEAD` (or a fresh repo with no commits yet).
|
|
pub fn current_branch(&self) -> Result<Option<String>, GitError> {
|
|
let out = self.run(&["branch", "--show-current"])?;
|
|
let name = out.trim();
|
|
Ok(if name.is_empty() {
|
|
None
|
|
} else {
|
|
Some(name.to_string())
|
|
})
|
|
}
|
|
|
|
/// Every local branch, each flagged with whether it is current.
|
|
pub fn branches(&self) -> Result<Vec<Branch>, GitError> {
|
|
let current = self.current_branch()?;
|
|
let out = self.run(&["for-each-ref", "--format=%(refname:short)", "refs/heads"])?;
|
|
Ok(out
|
|
.lines()
|
|
.map(str::trim)
|
|
.filter(|l| !l.is_empty())
|
|
.map(|name| Branch {
|
|
is_current: current.as_deref() == Some(name),
|
|
name: name.to_string(),
|
|
})
|
|
.collect())
|
|
}
|
|
|
|
/// Create a branch `name` at the current `HEAD`, without
|
|
/// switching to it.
|
|
pub fn create_branch(&self, name: &str) -> Result<(), GitError> {
|
|
self.run(&["branch", name])?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Delete branch `name`. Refuses (with [`GitError::Command`]) to
|
|
/// delete a branch whose commits are not merged elsewhere, so
|
|
/// work cannot be silently lost.
|
|
pub fn delete_branch(&self, name: &str) -> Result<(), GitError> {
|
|
self.run(&["branch", "-d", name])?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Switch the working tree to branch `name`.
|
|
pub fn switch_branch(&self, name: &str) -> Result<(), GitError> {
|
|
self.run(&["switch", name])?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Create branch `name` and switch to it in one step.
|
|
pub fn create_and_switch_branch(&self, name: &str) -> Result<(), GitError> {
|
|
self.run(&["switch", "--create", name])?;
|
|
Ok(())
|
|
}
|
|
}
|