diff --git a/src/apply_indexchanges.rs b/src/apply_indexchanges.rs index 7d13e31..0d35b08 100755 --- a/src/apply_indexchanges.rs +++ b/src/apply_indexchanges.rs @@ -136,6 +136,46 @@ pub fn apply_indexchanges_int( 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) => { let i = index.join(file); let ok = if let Some(target) = target { diff --git a/src/indexchanges.rs b/src/indexchanges.rs index 217912b..9a85de7 100755 --- a/src/indexchanges.rs +++ b/src/indexchanges.rs @@ -8,7 +8,9 @@ pub enum IndexChange { AddDir(PathBuf, bool, u64), /// Add or update a file 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), /// Remove a directory (recursively) RemoveDir(PathBuf), diff --git a/src/main.rs b/src/main.rs index 5f641c7..a73c692 100755 --- a/src/main.rs +++ b/src/main.rs @@ -126,6 +126,9 @@ fn main() { v.display(), 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::RemoveDir(v) => { let mut path_str = v.display().to_string(); diff --git a/src/update_index.rs b/src/update_index.rs index 05f73ed..0a51caa 100755 --- a/src/update_index.rs +++ b/src/update_index.rs @@ -125,8 +125,9 @@ fn rec( e, ) })?; + let entry_path = entry.path(); let rel_path = rel_path.join(entry.file_name()); - let metadata = entry.metadata(); + let metadata = fs::symlink_metadata(&entry_path); // ignore entries let fs_entry = FsEntry { @@ -137,7 +138,7 @@ fn rec( 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()); if metadata.is_dir() { if let Some(false) = in_index_and_is_dir { @@ -160,13 +161,94 @@ fn rec( // is file, but was dir -> remove dir removals.push(IndexChange::RemoveDir(rel_path.clone())); } - let newif = IndexFile::new_from_metadata(&metadata); - let oldif = IndexFile::from_path(&index_files.join(&rel_path)); - match oldif { - Ok(Ok(oldif)) if !newif.should_be_updated(&oldif, settings) => {} - _ => { - total_size += newif.size; - ichanges.push((newif.size, vec![IndexChange::AddFile(rel_path, newif)])); + 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 oldif = IndexFile::from_path(&index_file_path); + if old_is_symlink { + removals.push(IndexChange::RemoveFile(rel_path.clone())); + } + match oldif { + Ok(Ok(oldif)) if !newif.should_be_updated(&oldif, settings) => {} + _ => { + total_size += newif.size; + ichanges.push((newif.size, vec![IndexChange::AddFile(rel_path, newif)])); + } } } }