openpencil/crates/op-git/src/branch.rs
Kayshen-X 64a5934dcf feat(desktop): add op-git crate for in-app version control
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.
2026-05-18 21:45:00 +08:00

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(())
}
}