feat: symlink support

This commit is contained in:
Mark 2025-03-11 11:39:02 +01:00
parent 3d9f3365ba
commit ed090e5962
4 changed files with 137 additions and 10 deletions

View File

@ -136,6 +136,46 @@ pub fn apply_indexchanges_int(
fs::write(&index.join(file), index_file.save())?; fs::write(&index.join(file), index_file.save())?;
} }
} }
IndexChange::AddSymlink(file, link_target) => {
let cwd = std::env::current_dir()?;
let ok = if let Some(target) = target {
let t = target.join(file);
if let Some(p) = t.parent() {
let t = t.file_name().expect("a file should always have a filename");
std::env::set_current_dir(&p)?;
let _ = std::fs::remove_file(t);
if let Err(e) = std::os::unix::fs::symlink(&link_target, t) {
eprintln!(
"\n[warn] couldn't set file {t:?} to be a symlink to {link_target:?}: {e}"
);
false
} else {
std::env::set_current_dir(&cwd)?;
true
}
} else {
eprintln!("\n[warn] symlink path was empty");
false
}
} else {
true
};
if ok {
let index_file = index.join(file);
if let Some(p) = index_file.parent() {
std::env::set_current_dir(&p)?;
std::os::unix::fs::symlink(
link_target,
index_file
.file_name()
.expect("a file should always have a filename"),
)?;
} else {
eprintln!("\n[warn] couldn't get parent for index file's path, so could not create the symlink");
}
}
std::env::set_current_dir(&cwd)?;
}
IndexChange::RemoveFile(file) => { IndexChange::RemoveFile(file) => {
let i = index.join(file); let i = index.join(file);
let ok = if let Some(target) = target { let ok = if let Some(target) = target {

View File

@ -8,7 +8,9 @@ pub enum IndexChange {
AddDir(PathBuf, bool, u64), AddDir(PathBuf, bool, u64),
/// Add or update a file /// Add or update a file
AddFile(PathBuf, IndexFile), AddFile(PathBuf, IndexFile),
/// Remove a file /// Same as `AddFile`, just that it creates a symlink pointing to the 2nd path
AddSymlink(PathBuf, PathBuf),
/// Remove a file or symlink
RemoveFile(PathBuf), RemoveFile(PathBuf),
/// Remove a directory (recursively) /// Remove a directory (recursively)
RemoveDir(PathBuf), RemoveDir(PathBuf),

View File

@ -126,6 +126,9 @@ fn main() {
v.display(), v.display(),
f.size as f64 / (1024 * 1024 * 1024) as f64 f.size as f64 / (1024 * 1024 * 1024) as f64
), ),
IndexChange::AddSymlink(v, link_target) => {
eprintln!(" + {} (-> {})", v.display(), link_target.display())
}
IndexChange::RemoveFile(v) => eprintln!(" - {}", v.display()), IndexChange::RemoveFile(v) => eprintln!(" - {}", v.display()),
IndexChange::RemoveDir(v) => { IndexChange::RemoveDir(v) => {
let mut path_str = v.display().to_string(); let mut path_str = v.display().to_string();

View File

@ -125,8 +125,9 @@ fn rec(
e, e,
) )
})?; })?;
let entry_path = entry.path();
let rel_path = rel_path.join(entry.file_name()); let rel_path = rel_path.join(entry.file_name());
let metadata = entry.metadata(); let metadata = fs::symlink_metadata(&entry_path);
// ignore entries // ignore entries
let fs_entry = FsEntry { let fs_entry = FsEntry {
@ -137,7 +138,7 @@ fn rec(
continue; continue;
} }
let metadata = metadata.map_err(|e| ("getting metadata (you have to ignore this using a * pattern instead of + or /, because we don't know if it's a directory or not)".to_owned(), entry.path(), e))?; let metadata = metadata.map_err(|e| ("getting metadata (you have to ignore this using a * pattern instead of + or /, because we don't know if it's a directory or not)".to_owned(), entry_path.clone(), e))?;
let in_index_and_is_dir = index_entries.remove(&entry.file_name()); let in_index_and_is_dir = index_entries.remove(&entry.file_name());
if metadata.is_dir() { if metadata.is_dir() {
if let Some(false) = in_index_and_is_dir { if let Some(false) = in_index_and_is_dir {
@ -160,8 +161,88 @@ fn rec(
// is file, but was dir -> remove dir // is file, but was dir -> remove dir
removals.push(IndexChange::RemoveDir(rel_path.clone())); removals.push(IndexChange::RemoveDir(rel_path.clone()));
} }
let index_file_path = index_files.join(&rel_path);
let new_is_symlink = metadata.is_symlink();
let old_is_symlink = index_file_path
.symlink_metadata()
.is_ok_and(|meta| meta.is_symlink());
if new_is_symlink && old_is_symlink {
// cd to file's parent directory, in case of relative links, just to be sure
let cwd = std::env::current_dir()
.map_err(|e| (format!("couldn't get CWD"), entry_path.clone(), e))?;
std::env::set_current_dir(&source_files_path).map_err(|e| {
(
format!("could not set CWD to {}", source_files_path.display()),
entry_path.clone(),
e,
)
})?;
let new_link = fs::read_link(&entry_path).map_err(|e| {
(
format!("couldn't read symlink contents"),
entry_path.clone(),
e,
)
})?;
std::env::set_current_dir(&index_rel_path).map_err(|e| {
(
format!("could not set CWD to {}", index_rel_path.display()),
entry_path.clone(),
e,
)
})?;
let old_link = fs::read_link(&index_file_path).map_err(|e| {
(
format!("couldn't read indexfile symlink contents"),
entry_path.clone(),
e,
)
})?;
std::env::set_current_dir(&cwd).map_err(|e| {
(
format!("could not reset CWD to {}", cwd.display()),
entry_path.clone(),
e,
)
})?;
if new_link != old_link {
ichanges.push((0, vec![IndexChange::AddSymlink(rel_path, new_link)]));
}
} else if new_is_symlink {
let cwd = std::env::current_dir()
.map_err(|e| (format!("couldn't get CWD"), entry_path.clone(), e))?;
std::env::set_current_dir(&source_files_path).map_err(|e| {
(
format!("could not set CWD to {}", source_files_path.display()),
entry_path.clone(),
e,
)
})?;
let new_link = fs::read_link(&entry_path).map_err(|e| {
(
format!("couldn't read symlink contents"),
entry_path.clone(),
e,
)
})?;
std::env::set_current_dir(&cwd).map_err(|e| {
(
format!("could not reset CWD to {}", cwd.display()),
entry_path.clone(),
e,
)
})?;
if let Some(false) = in_index_and_is_dir {
// was file before
removals.push(IndexChange::RemoveFile(rel_path.clone()));
}
ichanges.push((0, vec![IndexChange::AddSymlink(rel_path, new_link)]));
} else {
let newif = IndexFile::new_from_metadata(&metadata); let newif = IndexFile::new_from_metadata(&metadata);
let oldif = IndexFile::from_path(&index_files.join(&rel_path)); let oldif = IndexFile::from_path(&index_file_path);
if old_is_symlink {
removals.push(IndexChange::RemoveFile(rel_path.clone()));
}
match oldif { match oldif {
Ok(Ok(oldif)) if !newif.should_be_updated(&oldif, settings) => {} Ok(Ok(oldif)) if !newif.should_be_updated(&oldif, settings) => {}
_ => { _ => {
@ -171,6 +252,7 @@ fn rec(
} }
} }
} }
}
// removals // removals
for (removed_file, is_dir) in index_entries { for (removed_file, is_dir) in index_entries {
removals.push(if is_dir { removals.push(if is_dir {