void_core/workspace/
move_path.rs1use std::fs;
4
5use crate::VoidContext;
6use crate::index::{index_entry_from_file, write_workspace_index, IndexEntry};
7use crate::{Result, VoidError};
8
9use super::stage::load_index_or_empty;
10
11#[derive(Clone)]
13pub struct MoveOptions {
14 pub ctx: VoidContext,
15 pub source: String,
16 pub dest: String,
17}
18
19#[derive(Debug, Clone)]
21pub struct MoveResult {
22 pub from: String,
23 pub to: String,
24}
25
26pub fn move_path(opts: MoveOptions) -> Result<MoveResult> {
36 let root = &opts.ctx.paths.root;
37
38 let mut index = load_index_or_empty(&opts.ctx)?;
39
40 let source = opts
42 .source
43 .replace('\\', "/")
44 .trim_start_matches('/')
45 .to_string();
46 let mut dest = opts
47 .dest
48 .replace('\\', "/")
49 .trim_start_matches('/')
50 .to_string();
51
52 let source_entry = index.get(&source).cloned().ok_or_else(|| {
54 VoidError::NotFound(format!("pathspec '{}' did not match any files", source))
55 })?;
56
57 let source_path = crate::util::safe_join(opts.ctx.paths.root.as_std_path(), &source)?;
59 if !source_path.exists() {
60 return Err(VoidError::NotFound(format!(
61 "source file '{}' does not exist",
62 source
63 )));
64 }
65
66 let dest_path = crate::util::safe_join(opts.ctx.paths.root.as_std_path(), &dest)?;
68
69 if dest_path.is_dir() {
71 let filename = source
72 .rsplit('/')
73 .next()
74 .ok_or_else(|| VoidError::InvalidPattern("invalid source path".to_string()))?;
75 dest = format!("{}/{}", dest.trim_end_matches('/'), filename);
76 }
77
78 let final_dest_path = crate::util::safe_join(opts.ctx.paths.root.as_std_path(), &dest)?;
80
81 if final_dest_path.exists() && final_dest_path != source_path {
83 if index.get(&dest).is_some() {
85 return Err(VoidError::InvalidPattern(format!(
86 "destination '{}' already exists in index",
87 dest
88 )));
89 }
90 }
91
92 if let Some(parent) = final_dest_path.parent() {
94 fs::create_dir_all(parent)?;
95 }
96
97 fs::rename(&source_path, &final_dest_path)?;
99
100 index.entries.retain(|e| e.path != source);
102
103 let new_entry = index_entry_from_file(root, &dest)?;
105 let entry = IndexEntry {
106 path: dest.clone(),
107 content_hash: source_entry.content_hash, mtime_secs: new_entry.mtime_secs,
109 mtime_nanos: new_entry.mtime_nanos,
110 size: new_entry.size,
111 };
112 index.upsert_entry(entry);
113
114 write_workspace_index(opts.ctx.paths.workspace_dir.as_std_path(), opts.ctx.crypto.vault.index_key()?, &index)?;
115
116 Ok(MoveResult {
117 from: source,
118 to: dest,
119 })
120}