//! Commands that act on the local repository and working copy. use super::format::{deps_line, plural, short_id, signature_line, when}; use super::open; use anyhow::Result; use oxy_core::repo::{render_change, ChangeStatus, Repo}; use std::path::PathBuf; pub fn init(path: Option) -> Result { let path = path.unwrap_or(std::env::current_dir()?); let repo = Repo::init(&path)?; println!("Initialized oxy repository in {}", repo.root().display()); println!("Bookmark: {} Author: {} <{}>", repo.config.bookmark, repo.config.author_name, repo.config.author_email); Ok(0) } fn status_tag(s: ChangeStatus) -> &'static str { match s { ChangeStatus::Added => "A", ChangeStatus::Modified => "M", ChangeStatus::Removed => "D", ChangeStatus::ModeChanged => "X", ChangeStatus::ConflictEdited => "C!", ChangeStatus::Resolved => "R", } } pub fn status() -> Result { let repo = open()?; let snap = repo.snapshot("oxy status")?; let heads = repo.bookmark_heads()?; println!("Working copy: {} on {} ({})", snap.wc.change_id.short(8), repo.config.bookmark, plural(heads.len(), "head")); if snap.statuses.is_empty() { println!("The working copy is clean."); } for s in &snap.statuses { println!("{:2} {}", status_tag(s.status), s.path); } if !snap.base_snapshot.is_conflicted() { return Ok(0); } println!("\nConflicts in the base state (edit the marked files, or the .oxy-conflict-N candidates, then commit):"); for c in &snap.base_snapshot.tree.conflicts { println!(" tree: {c:?}"); } for (p, f) in &snap.base_snapshot.files { if f.is_conflicted() { let state = match snap.statuses.iter().find(|s| s.path == *p).map(|s| s.status) { Some(ChangeStatus::Resolved) => "resolved in working copy", Some(ChangeStatus::ConflictEdited) => "markers remain", _ => "unresolved", }; println!(" {p} ({state})"); } } Ok(2) } pub fn commit(message: &str, depends_on: &[String]) -> Result { let repo = open()?; let mut deps = Vec::new(); for d in depends_on { deps.push(repo.resolve_change(d)?); } let h = repo.commit(message, &deps)?; let c = repo.store().get_change(&h)?; println!("Committed {} #{}: {}", short_id(&c), h.short(12), message.lines().next().unwrap_or("")); Ok(0) } pub fn log() -> Result { let repo = open()?; repo.snapshot("oxy log")?; for e in repo.log()? { let c = &e.change; let mut flags = Vec::new(); if e.is_working_copy { flags.push("working copy"); } if e.is_head { flags.push("head"); } let flags = if flags.is_empty() { String::new() } else { format!(" ({})", flags.join(", ")) }; println!("{} #{}{}", short_id(c), e.hash.short(12), flags); println!(" {}", when(c.hlc.wall_ms)); if !c.deps.is_empty() { println!(" deps: {}", deps_line(c)); } let msg = if c.message.is_empty() { "(no description)" } else { &c.message }; for line in msg.lines() { println!(" {line}"); } println!(); } Ok(0) } pub fn diff() -> Result { let repo = open()?; let snap = repo.snapshot("oxy diff")?; print!("{}", render_change(&snap.wc, &snap.base, &snap.base_snapshot)); Ok(0) } pub fn show(change: &str) -> Result { let repo = open()?; repo.snapshot("oxy show")?; let h = repo.resolve_change(change)?; let c = repo.store().get_change(&h)?; println!("change {} #{}", short_id(&c), h); println!("kind: {:?}", c.kind); println!("date: {}", when(c.hlc.wall_ms)); println!("deps: {}", deps_line(&c)); println!("sig: {}", signature_line(&repo, &h)?); println!("\n{}\n", if c.message.is_empty() { "(no description)" } else { &c.message }); print!("{}", repo.describe_change(&h)?); Ok(0) } pub fn checkout(changes: &[String], discard: bool) -> Result { let repo = open()?; let heads = if changes.is_empty() { repo.bookmark_heads()? } else { changes.iter().map(|c| repo.resolve_change(c)).collect::, _>>()? }; let spec = if changes.is_empty() { repo.config.bookmark.clone() } else { changes.join(" ") }; let r = repo.checkout(&heads, discard, &format!("oxy checkout {spec}"))?; println!("Working copy now on {spec}: {} written, {} removed", plural(r.written, "file"), r.removed); if r.conflicted { println!("This state has conflicts; see oxy status."); } Ok(0) } pub fn resolve() -> Result { let repo = open()?; let snap = repo.snapshot("oxy resolve")?; if !snap.base_snapshot.is_conflicted() { println!("The working copy's base has no conflicts."); return Ok(0); } let mut remaining = 0; for (p, f) in &snap.base_snapshot.files { if !f.is_conflicted() { continue; } match snap.statuses.iter().find(|s| s.path == *p).map(|s| s.status) { Some(ChangeStatus::Resolved) => println!("resolved {p}"), Some(ChangeStatus::ConflictEdited) => { remaining += 1; println!("markers {p}"); } _ => { remaining += 1; println!("unresolved {p}"); } } } for c in &snap.base_snapshot.tree.conflicts { remaining += 1; println!("tree {c:?}"); } if remaining == 0 { println!("All conflicts are resolved; `oxy commit` will record the resolution."); Ok(0) } else { Ok(2) } } pub fn undo() -> Result { let repo = open()?; let op = repo.undo()?; println!("Undid the last operation; now at: {}", op.command); Ok(0) } pub fn op_log() -> Result { let repo = open()?; for (i, (r, op)) in repo.op_log()?.into_iter().enumerate() { println!("{}{} {} {}", if i == 0 { "@ " } else { " " }, r.short(12), when(op.hlc.wall_ms), op.command); } Ok(0) }