use mcport::{RequestContext, ToolReply, Value, json};
use super::{
arguments,
output::{commit_value, entry_kind, lower_debug, page_result, path, status_kind, tree_entry},
};
use crate::{HistoryOptions, Repository};
pub fn head(context: &RequestContext, repository: &Repository, arguments: &Value) -> ToolReply {
reply((|| {
arguments::object(arguments)?;
active(context)?;
let head = repository.head().map_err(message)?;
Ok(json!({
"repository": repository.work_dir().unwrap_or_else(|| repository.git_dir()).display().to_string(),
"gitDirectory": repository.git_dir().display().to_string(),
"hash": lower_debug(repository.hash_kind()),
"symbolic": head.symbolic,
"target": head.target.map(|id| id.to_string()),
"packCount": repository.pack_count()
}))
})())
}
pub fn history(context: &RequestContext, repository: &Repository, arguments: &Value) -> ToolReply {
reply((|| {
let revision = arguments::revision(arguments, "revision", Some("HEAD"))?;
let page = arguments::page(arguments, 50, 200)?;
let first_parent = arguments::boolean(arguments, "firstParent", false)?;
let include_message = arguments::boolean(arguments, "includeMessage", false)?;
let since = arguments::optional_i64(arguments, "since")?;
let until = arguments::optional_i64(arguments, "until")?;
let requested = page
.cursor
.checked_add(page.limit)
.and_then(|value| value.checked_add(1))
.ok_or_else(|| "cursor and limit overflow".to_owned())?;
if requested > repository.limits().max_history_commits {
return Err("cursor exceeds the bounded history window".to_owned());
}
active(context)?;
progress(context, 0.0, "reading commit history")?;
let start = repository.resolve(&revision).map_err(message)?;
let records = repository
.history(
start,
HistoryOptions {
max_commits: requested,
first_parent,
since,
until,
},
)
.map_err(message)?;
active(context)?;
let total = records.len();
let commits = records
.into_iter()
.skip(page.cursor)
.take(page.limit)
.enumerate()
.map(|(index, record)| {
periodic(context, index)?;
Ok(commit_value(&record.commit, include_message))
})
.collect::<Result<Vec<_>, String>>()?;
progress(context, 1.0, "history ready")?;
let total_known = total <= page.cursor + page.limit;
Ok(page_result(
revision,
commits,
page.cursor,
total,
total_known,
"commits",
))
})())
}
pub fn diff(context: &RequestContext, repository: &Repository, arguments: &Value) -> ToolReply {
reply((|| {
let old = arguments::revision(arguments, "old", None)?;
let new = arguments::revision(arguments, "new", Some("HEAD"))?;
let page = arguments::page(arguments, 200, 1000)?;
active(context)?;
progress(context, 0.0, "comparing trees")?;
let old_id = repository.resolve(&old).map_err(message)?;
let new_id = repository.resolve(&new).map_err(message)?;
let changes = repository.diff_commits(old_id, new_id).map_err(message)?;
active(context)?;
let total = changes.len();
let values = changes
.into_iter()
.skip(page.cursor)
.take(page.limit)
.enumerate()
.map(|(index, change)| {
periodic(context, index)?;
let (path, path_hex) = path(&change.path);
Ok(json!({
"path": path,
"pathHex": path_hex,
"kind": lower_debug(change.kind),
"old": change.old.as_ref().map(tree_entry),
"new": change.new.as_ref().map(tree_entry)
}))
})
.collect::<Result<Vec<_>, String>>()?;
progress(context, 1.0, "diff ready")?;
let mut result = page_result(new, values, page.cursor, total, true, "changes");
result["old"] = Value::String(old);
Ok(result)
})())
}
pub fn status(context: &RequestContext, repository: &Repository, arguments: &Value) -> ToolReply {
reply((|| {
let page = arguments::page(arguments, 200, 1000)?;
active(context)?;
progress(context, 0.0, "reading tracked status")?;
let entries = repository.status().map_err(message)?;
active(context)?;
let total = entries.len();
let values = entries
.into_iter()
.skip(page.cursor)
.take(page.limit)
.enumerate()
.map(|(index, entry)| {
periodic(context, index)?;
let (path, path_hex) = path(&entry.path);
Ok(json!({
"path": path,
"pathHex": path_hex,
"index": status_kind(entry.index),
"worktree": status_kind(entry.worktree)
}))
})
.collect::<Result<Vec<_>, String>>()?;
progress(context, 1.0, "status ready")?;
Ok(page_result(
"tracked",
values,
page.cursor,
total,
true,
"entries",
))
})())
}
pub fn snapshot(context: &RequestContext, repository: &Repository, arguments: &Value) -> ToolReply {
reply((|| {
let revision = arguments::revision(arguments, "revision", Some("HEAD"))?;
let page = arguments::page(arguments, 200, 1000)?;
active(context)?;
progress(context, 0.0, "building immutable manifest")?;
let snapshot = repository.snapshot(&revision).map_err(message)?;
active(context)?;
let total = snapshot.entries.len();
let entries = snapshot
.entries
.into_iter()
.skip(page.cursor)
.take(page.limit)
.enumerate()
.map(|(index, entry)| {
periodic(context, index)?;
let (path, path_hex) = path(&entry.path);
Ok(json!({
"path": path,
"pathHex": path_hex,
"mode": entry.mode,
"id": entry.id.to_string(),
"kind": entry_kind(entry.kind)
}))
})
.collect::<Result<Vec<_>, String>>()?;
progress(context, 1.0, "snapshot ready")?;
let mut result = page_result(revision, entries, page.cursor, total, true, "entries");
result["commit"] = Value::String(snapshot.commit.to_string());
result["tree"] = Value::String(snapshot.tree.to_string());
Ok(result)
})())
}
fn active(context: &RequestContext) -> Result<(), String> {
if context.is_cancelled() {
Err("request cancelled or deadline elapsed".to_owned())
} else {
Ok(())
}
}
fn periodic(context: &RequestContext, index: usize) -> Result<(), String> {
if index.is_multiple_of(64) {
active(context)
} else {
Ok(())
}
}
fn progress(context: &RequestContext, value: f64, message: &str) -> Result<(), String> {
context
.report_progress(value, Some(1.0), Some(message))
.map(|_| ())
.map_err(|error| error.to_string())
}
fn reply(result: Result<Value, String>) -> ToolReply {
match result {
Ok(value) => ToolReply::structured(value),
Err(error) => ToolReply::error(error),
}
}
fn message(error: impl std::fmt::Display) -> String {
error.to_string()
}