use crate::Caveats;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
pub(super) fn authenticate(path: &OsStr, caveats: &Caveats) -> Result<PathBuf, ()> {
authenticate_program(path, caveats, "git")
}
fn authenticate_program(path: &OsStr, caveats: &Caveats, name: &str) -> Result<PathBuf, ()> {
crate::exec_grants::trusted_program(path, caveats, name)
}
pub(super) fn resolve(caveats: &Caveats) -> Result<PathBuf, ()> {
resolve_program(caveats, "git")
}
fn resolve_program(caveats: &Caveats, name: &str) -> Result<PathBuf, ()> {
let path = crate::exec_grants::dispatch_path().ok_or(())?;
if name == "git" {
authenticate(&path, caveats)
} else {
authenticate_program(&path, caveats, name)
}
}
pub(super) fn argument(word: &str) -> Option<String> {
if matches!(word, "." | "'.'" | "\".\"") {
Some(".".into())
} else {
super::literal(word)
}
}
pub(super) fn read_only(args: &[&str]) -> bool {
matches!(
args,
["branch", "--show-current"]
| ["worktree", "list"]
| ["rev-parse", "HEAD" | "--show-toplevel"]
| ["rev-parse", "--abbrev-ref", "HEAD"]
)
}
fn creation_source(
command: &agent_bridle::InspectedCommand,
words: &[String],
) -> Result<String, ()> {
let Some(args) = super::worktree_add_args(words) else {
return Ok(command.source.clone());
};
let args: Vec<_> = args.iter().map(String::as_str).collect();
if !matches!(args.as_slice(), ["-b", _, _, "."] | [_, "-b", _, "."]) {
return Ok(command.source.clone());
}
let mut rest = command.source.as_str();
for word in &command.argv[..command.argv.len() - 1] {
rest = rest.trim_start().strip_prefix(word).ok_or(())?;
}
rest = rest.trim_start();
let offset = command.source.len() - rest.len();
let dot = command.argv.last().ok_or(())?;
let suffix = rest.strip_prefix(dot).ok_or(())?;
Ok(format!("{}HEAD{suffix}", &command.source[..offset]))
}
pub(super) fn pin(source: &str, git: &Path, caveats: &Caveats) -> Result<String, ()> {
static GAP: std::sync::LazyLock<regex::Regex> = std::sync::LazyLock::new(|| {
regex::Regex::new(r"\A(?:\s|;|&&|\|\||\||[0-9]*[<>]&[0-9]+)*\z")
.expect("fixed shell gap pattern")
});
let inspection = agent_bridle::inspect_shell(source).map_err(|_| ())?;
if !inspection.constructs.is_empty() {
return Err(());
}
let quoted = crate::mcp::shell_quote_arg(git.to_str().ok_or(())?);
let mut rest = source;
let mut pinned = String::new();
for command in inspection.commands {
let offset = rest.find(&command.source).ok_or(())?;
let gap = &rest[..offset];
let word = command.argv.first().ok_or(())?;
if !GAP.is_match(gap) || !command.source.starts_with(word) {
return Err(());
}
pinned.push_str(gap);
if command.program.as_deref() == Some("git") {
let words = command
.argv
.iter()
.map(|s| argument(s))
.collect::<Option<Vec<_>>>()
.ok_or(())?;
let source = creation_source(&command, &words)?;
pinned.push_str("ed);
if read_only(&words.iter().skip(1).map(String::as_str).collect::<Vec<_>>()) {
pinned.push_str(" --no-pager --no-optional-locks");
}
pinned.push_str(&source[word.len()..]);
} else if command.program.as_deref() == Some("tail") {
let tail = resolve_program(caveats, "tail")?;
pinned.push_str(&crate::mcp::shell_quote_arg(tail.to_str().ok_or(())?));
pinned.push_str(&command.source[word.len()..]);
} else {
pinned.push_str(&command.source);
}
rest = &rest[offset + command.source.len()..];
}
if !GAP.is_match(rest) {
return Err(());
}
pinned.push_str(rest);
Ok(pinned)
}