use anyhow::{Context, Result};
use git2::Repository;
use std::process::Command;
const METADATA_REF_PREFIX: &str = "refs/branch-metadata/";
const STAX_TRUNK_REF: &str = "refs/stax/trunk";
const STAX_PREV_BRANCH_REF: &str = "refs/stax/prev-branch";
pub fn read_metadata(repo: &Repository, branch: &str) -> Result<Option<String>> {
let ref_name = format!("{}{}", METADATA_REF_PREFIX, branch);
match repo.find_reference(&ref_name) {
Ok(reference) => {
let oid = reference.target().context("Reference has no target")?;
let blob = repo.find_blob(oid)?;
let content = std::str::from_utf8(blob.content())?;
Ok(Some(content.to_string()))
}
Err(e) if e.code() == git2::ErrorCode::NotFound => Ok(None),
Err(e) => Err(e.into()),
}
}
pub fn write_metadata(repo: &Repository, branch: &str, json: &str) -> Result<()> {
let workdir = repo
.workdir()
.context("Repository has no working directory")?;
let mut child = Command::new("git")
.args(["hash-object", "-w", "--stdin"])
.current_dir(workdir)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.spawn()?;
if let Some(stdin) = child.stdin.as_mut() {
use std::io::Write;
stdin.write_all(json.as_bytes())?;
}
let output = child.wait_with_output()?;
let hash = String::from_utf8(output.stdout)?.trim().to_string();
let ref_name = format!("{}{}", METADATA_REF_PREFIX, branch);
let status = Command::new("git")
.args(["update-ref", &ref_name, &hash])
.current_dir(workdir)
.status()
.context("Failed to update ref")?;
if !status.success() {
anyhow::bail!("Failed to update ref {}", ref_name);
}
Ok(())
}
pub fn delete_metadata(repo: &Repository, branch: &str) -> Result<()> {
let ref_name = format!("{}{}", METADATA_REF_PREFIX, branch);
let workdir = repo
.workdir()
.context("Repository has no working directory")?;
let status = Command::new("git")
.args(["update-ref", "-d", &ref_name])
.current_dir(workdir)
.status()
.context("Failed to delete ref")?;
if !status.success() {
anyhow::bail!("Failed to delete ref {}", ref_name);
}
Ok(())
}
pub fn list_metadata_branches(repo: &Repository) -> Result<Vec<String>> {
let mut branches = Vec::new();
for reference in repo.references_glob(&format!("{}*", METADATA_REF_PREFIX))? {
let reference = reference?;
if let Some(name) = reference.name() {
let branch = name.strip_prefix(METADATA_REF_PREFIX).unwrap_or(name);
branches.push(branch.to_string());
}
}
Ok(branches)
}
pub fn is_initialized(repo: &Repository) -> bool {
repo.find_reference(STAX_TRUNK_REF).is_ok()
}
pub fn read_trunk(repo: &Repository) -> Result<Option<String>> {
match repo.find_reference(STAX_TRUNK_REF) {
Ok(reference) => {
let oid = reference.target().context("Reference has no target")?;
let blob = repo.find_blob(oid)?;
let content = std::str::from_utf8(blob.content())?;
Ok(Some(content.trim().to_string()))
}
Err(e) if e.code() == git2::ErrorCode::NotFound => Ok(None),
Err(e) => Err(e.into()),
}
}
pub fn write_trunk(repo: &Repository, trunk: &str) -> Result<()> {
let workdir = repo
.workdir()
.context("Repository has no working directory")?;
let mut child = Command::new("git")
.args(["hash-object", "-w", "--stdin"])
.current_dir(workdir)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.spawn()?;
if let Some(stdin) = child.stdin.as_mut() {
use std::io::Write;
stdin.write_all(trunk.as_bytes())?;
}
let output = child.wait_with_output()?;
let hash = String::from_utf8(output.stdout)?.trim().to_string();
Command::new("git")
.args(["update-ref", STAX_TRUNK_REF, &hash])
.current_dir(workdir)
.status()
.context("Failed to update trunk ref")?;
Ok(())
}
pub fn read_prev_branch(repo: &Repository) -> Result<Option<String>> {
match repo.find_reference(STAX_PREV_BRANCH_REF) {
Ok(reference) => {
let oid = reference.target().context("Reference has no target")?;
let blob = repo.find_blob(oid)?;
let content = std::str::from_utf8(blob.content())?;
Ok(Some(content.trim().to_string()))
}
Err(e) if e.code() == git2::ErrorCode::NotFound => Ok(None),
Err(e) => Err(e.into()),
}
}
pub fn write_prev_branch(repo: &Repository, branch: &str) -> Result<()> {
let workdir = repo
.workdir()
.context("Repository has no working directory")?;
let mut child = Command::new("git")
.args(["hash-object", "-w", "--stdin"])
.current_dir(workdir)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.spawn()?;
if let Some(stdin) = child.stdin.as_mut() {
use std::io::Write;
stdin.write_all(branch.as_bytes())?;
}
let output = child.wait_with_output()?;
let hash = String::from_utf8(output.stdout)?.trim().to_string();
Command::new("git")
.args(["update-ref", STAX_PREV_BRANCH_REF, &hash])
.current_dir(workdir)
.status()
.context("Failed to update prev-branch ref")?;
Ok(())
}