use std::path::PathBuf;
use prikk_store::{
DeclarationRecordOutcome, RepoPath, RepositoryLayout, record_rename_declaration,
};
use crate::args::current_dir;
use crate::commands::CliError;
use crate::open_repository;
use crate::stdout::println;
pub(crate) fn run_mv(args: Vec<String>) -> std::result::Result<(), CliError> {
let mut positionals = Vec::with_capacity(2);
for arg in args {
if arg.starts_with('-') {
return Err(unknown_mv_argument(&arg));
}
positionals.push(arg);
}
let mut iter = positionals.into_iter();
let (Some(old_arg), Some(new_arg)) = (iter.next(), iter.next()) else {
return Err(CliError::Usage(
"mv requires exactly two arguments: <old> <new>".to_string(),
));
};
if let Some(extra) = iter.next() {
return Err(CliError::Usage(format!("unknown mv argument: {extra}")));
}
let old_repo_path = RepoPath::parse(&old_arg).map_err(|err| err.to_string())?;
let new_repo_path = RepoPath::parse(&new_arg).map_err(|err| err.to_string())?;
let root = current_dir()?;
let layout = open_repository(root)?;
layout
.require_current_format()
.map_err(|err| err.to_string())?;
let old_disk_path = worktree_path(&layout, &old_repo_path);
let new_disk_path = worktree_path(&layout, &new_repo_path);
let old_exists = std::fs::symlink_metadata(&old_disk_path).is_ok();
let new_exists = std::fs::symlink_metadata(&new_disk_path).is_ok();
let outcome = match (old_exists, new_exists) {
(true, false) => {
std::fs::rename(&old_disk_path, &new_disk_path)
.map_err(|err| format!("failed to rename {old_arg} to {new_arg}: {err}"))?;
let outcome =
record_rename_declaration(&layout, old_repo_path.as_str(), new_repo_path.as_str())
.map_err(|err| err.to_string())?;
println!("moved {old_arg} -> {new_arg}");
outcome
}
(false, true) => {
let outcome =
record_rename_declaration(&layout, old_repo_path.as_str(), new_repo_path.as_str())
.map_err(|err| err.to_string())?;
println!("declared {old_arg} -> {new_arg} (already moved on disk; no bytes touched)");
outcome
}
(true, true) => {
return Err(format!(
"refusing to move {old_arg} to {new_arg}: both paths exist, and which one is the \
tracked node is not prikk's to guess"
)
.into());
}
(false, false) => {
return Err(format!(
"refusing to move {old_arg} to {new_arg}: neither path exists in the worktree"
)
.into());
}
};
match outcome {
DeclarationRecordOutcome::Recorded { .. } => {
println!(
"note: this declaration is authored into the next `prikk commit`; see `prikk \
worktree-status` to review it first"
);
}
DeclarationRecordOutcome::NetsToNoMove { origin, via } => {
println!("declaration {origin} -> {via} -> {origin}: nets to no move, dropped");
}
}
Ok(())
}
fn worktree_path(layout: &RepositoryLayout, path: &RepoPath) -> PathBuf {
let mut out = layout.root().to_path_buf();
for component in path.as_str().split('/') {
out.push(component);
}
out
}
fn unknown_mv_argument(arg: &str) -> CliError {
CliError::Usage(format!("unknown mv argument: {arg}"))
}