Local workflow, smart-HTTP clone/fetch/push and public HTTPS clone/fetch match native git in stdout and exit codes. Only WASI imports (wasi:http, exit-with-code).
113 lines
4.4 KiB
Rust
113 lines
4.4 KiB
Rust
//! Repository layout and shared operation helpers. Persistent state is ordinary Git files only.
|
|
use crate::{Error, Result};
|
|
use std::path::PathBuf;
|
|
|
|
/// Add operation context to Git and filesystem errors.
|
|
pub fn error(operation: &str, error: impl std::fmt::Display) -> Error {
|
|
Error::new(-32000, format!("{operation}: {error}"))
|
|
}
|
|
|
|
/// Resolve a legacy name or a literal absolute path under /plugins/ or /repos/.
|
|
pub fn repo_path(name: &str) -> Result<PathBuf> {
|
|
Ok(PathBuf::from(name))
|
|
}
|
|
|
|
/// Resolve metadata without opening another repository or acquiring another lock.
|
|
/// Missing .git is retained for the existing non-bare init/clone lock key.
|
|
fn git_dir(path: &std::path::Path) -> Result<PathBuf> {
|
|
let dot_git = path.join(".git");
|
|
if dot_git.is_file() {
|
|
let text = std::fs::read_to_string(&dot_git).map_err(|e| error("gitfile", e))?;
|
|
let target = text
|
|
.trim_end_matches(['\r', '\n'])
|
|
.strip_prefix("gitdir: ")
|
|
.filter(|p| !p.is_empty() && !p.contains(['\r', '\n', '\0']))
|
|
.ok_or_else(|| error("gitfile", "expected gitdir: <path>"))?;
|
|
return std::fs::canonicalize(path.join(target)).map_err(|e| error("gitfile target", e));
|
|
}
|
|
if !dot_git.exists() && path.join("HEAD").is_file() && path.join("objects").is_dir() {
|
|
return Ok(path.to_owned());
|
|
}
|
|
Ok(dot_git)
|
|
}
|
|
|
|
/// Common-dir advisory lock key, shared by the main and all linked worktrees.
|
|
/// A plain .git directory retains exactly the legacy lock path.
|
|
pub fn lock_path(name: &str) -> Result<PathBuf> {
|
|
let git_dir = git_dir(&repo_path(name)?)?;
|
|
let common = match std::fs::read_to_string(git_dir.join("commondir")) {
|
|
Ok(text) => text,
|
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(git_dir),
|
|
Err(e) => return Err(error("commondir", e)),
|
|
};
|
|
let common = common.trim_end_matches(['\r', '\n']);
|
|
if common.is_empty() || common.contains(['\r', '\n', '\0']) {
|
|
return Err(error("commondir", "expected one nonempty path"));
|
|
}
|
|
std::fs::canonicalize(git_dir.join(common)).map_err(|e| error("commondir target", e))
|
|
}
|
|
|
|
/// Open only the addressed worktree or bare directory, never discover an ancestor.
|
|
pub fn open(name: &str) -> Result<gix::Repository> {
|
|
let path = repo_path(name)?;
|
|
let dot_git = path.join(".git");
|
|
let mut repo = if dot_git.is_file() {
|
|
// gix follows gitfiles and commondir, including the private HEAD/index.
|
|
git_dir(&path)?; // Give malformed gitfiles a useful error before opening.
|
|
let repo = gix::open(&path).map_err(|e| error("open", e))?;
|
|
// gix 0.90 reports is_bare()==true from the common config even though
|
|
// it resolves the workdir and private index correctly. Check workdir,
|
|
// not that cached flag; status and our tree writer work without config edits.
|
|
worktree(&repo)?;
|
|
repo
|
|
} else if dot_git.is_dir() {
|
|
let repo = gix::open(&dot_git).map_err(|e| error("open", e))?;
|
|
if repo.workdir().is_none() || repo.is_bare() {
|
|
return Err(error(
|
|
"open",
|
|
"expected worktree metadata in .git; address bare repositories by their bare directory",
|
|
));
|
|
}
|
|
repo
|
|
} else {
|
|
let repo = gix::open_opts(&path, gix::open::Options::default().open_path_as_is(true))
|
|
.map_err(|e| error("open bare", e))?;
|
|
if !repo.is_bare() {
|
|
return Err(error(
|
|
"open",
|
|
"expected a worktree root or bare repository directory",
|
|
));
|
|
}
|
|
repo
|
|
};
|
|
// gix-ref otherwise uses unsupported mmap for packed-refs above 32 KiB.
|
|
#[cfg(target_os = "wasi")]
|
|
repo.refs.set_packed_buffer_mmap_threshold(u64::MAX);
|
|
Ok(repo)
|
|
}
|
|
|
|
/// Require a worktree before any status/index/worktree-style operation.
|
|
pub fn worktree(repo: &gix::Repository) -> Result<&std::path::Path> {
|
|
repo.workdir().ok_or_else(|| {
|
|
error(
|
|
"worktree",
|
|
"no worktree: operation is not available on a bare repository",
|
|
)
|
|
})
|
|
}
|
|
|
|
/// Validate a literal repository-relative path (not a pathspec).
|
|
pub fn validate_path(path: &str) -> Result<()> {
|
|
if path.is_empty()
|
|
|| path
|
|
.split('/')
|
|
.any(|c| c.is_empty() || c == "." || c == "..")
|
|
{
|
|
return Err(error(
|
|
"path",
|
|
"expected a nonempty repository-relative literal path",
|
|
));
|
|
}
|
|
Ok(())
|
|
}
|