mirror of
https://github.com/nivekuil/rip
synced 2024-11-22 03:33:05 +00:00
Use a different graveyard per user by default
This also obviates the need for umask/permissions juju.
This commit is contained in:
parent
208ca3db68
commit
43711e6fa1
3 changed files with 30 additions and 41 deletions
|
@ -1,8 +1,8 @@
|
|||
[package]
|
||||
name = "rm-improved"
|
||||
version = "0.10.2"
|
||||
version = "0.11.0"
|
||||
authors = ["mail@nivekuil.com"]
|
||||
description = "rip: a safety-focused alternative to rm."
|
||||
description = "rip: a safe and ergonomic alternative to rm"
|
||||
repository = "https://github.com/nivekuil/rip"
|
||||
license = "GPL-3.0+"
|
||||
include = [
|
||||
|
@ -13,7 +13,7 @@ include = [
|
|||
[dependencies]
|
||||
clap = "2"
|
||||
walkdir = "0.1"
|
||||
libc = "0.2"
|
||||
time = "0.1"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
|
20
README.org
20
README.org
|
@ -45,7 +45,7 @@ $ rip dir1/ file1
|
|||
Undo the last deletion
|
||||
#+BEGIN_EXAMPLE
|
||||
$ rip -u
|
||||
Returned /tmp/.graveyard/home/jack/file1 to /home/jack/file1
|
||||
Returned /tmp/graveyard-jack/home/jack/file1 to /home/jack/file1
|
||||
#+END_EXAMPLE
|
||||
Print some info (size and first few lines in a file, total size and first few files in a directory) about the target and then prompt for deletion
|
||||
#+BEGIN_EXAMPLE
|
||||
|
@ -58,28 +58,28 @@ Send file1 to the graveyard? (y/n) y
|
|||
Print files that were deleted from under the current directory
|
||||
#+BEGIN_EXAMPLE
|
||||
$ rip -s
|
||||
/tmp/.graveyard/home/jack/file1
|
||||
/tmp/.graveyard/home/jack/dir1
|
||||
/tmp/graveyard-jack/home/jack/file1
|
||||
/tmp/graveyard-jack/home/jack/dir1
|
||||
#+END_EXAMPLE
|
||||
Name conflicts are resolved
|
||||
#+BEGIN_EXAMPLE
|
||||
$ touch file1
|
||||
$ rip file1
|
||||
$ rip -s
|
||||
/tmp/.graveyard/home/jack/dir1
|
||||
/tmp/.graveyard/home/jack/file1
|
||||
/tmp/.graveyard/home/jack/file1~1
|
||||
/tmp/graveyard-jack/home/jack/dir1
|
||||
/tmp/graveyard-jack/home/jack/file1
|
||||
/tmp/graveyard-jack/home/jack/file1~1
|
||||
#+END_EXAMPLE
|
||||
-u also takes the path of a file in the graveyard
|
||||
#+BEGIN_EXAMPLE
|
||||
$ rip -u /tmp/.graveyard/home/jack/file1
|
||||
Returned /tmp/.graveyard/home/jack/file1 to /home/jack/file1
|
||||
$ rip -u /tmp/graveyard-jack/home/jack/file1
|
||||
Returned /tmp/graveyard-jack/home/jack/file1 to /home/jack/file1
|
||||
#+END_EXAMPLE
|
||||
Combine -u and -s to restore everything printed by -s
|
||||
#+BEGIN_EXAMPLE
|
||||
$ rip -su
|
||||
Returned /tmp/.graveyard/home/jack/dir1 to /home/jack/dir1
|
||||
Returned /tmp/.graveyard/home/jack/file1~1 to /home/jack/file1~1
|
||||
Returned /tmp/graveyard-jack/home/jack/dir1 to /home/jack/dir1
|
||||
Returned /tmp/graveyard-jack/home/jack/file1~1 to /home/jack/file1~1
|
||||
#+END_EXAMPLE
|
||||
** ⚰ Notes
|
||||
- You probably shouldn't alias =rm= to =rip=. Unlearning muscle memory is hard, but it's harder to ensure that every =rm= you make (as different users, from different machines and application environments) is the aliased one.
|
||||
|
|
45
src/main.rs
45
src/main.rs
|
@ -36,7 +36,7 @@ fn main() {
|
|||
.version(crate_version!())
|
||||
.author(crate_authors!())
|
||||
.about("Rm ImProved
|
||||
Send files to the graveyard (/tmp/.graveyard by default) instead of unlinking them.")
|
||||
Send files to the graveyard (/tmp/graveyard-$USER by default) instead of unlinking them.")
|
||||
.arg(Arg::with_name("TARGET")
|
||||
.help("File or directory to remove")
|
||||
.multiple(true)
|
||||
|
@ -70,7 +70,7 @@ Send files to the graveyard (/tmp/.graveyard by default) instead of unlinking th
|
|||
let graveyard = &match (matches.value_of("graveyard"), env::var("GRAVEYARD")) {
|
||||
(Some(flag), _) => PathBuf::from(flag),
|
||||
(_, Ok(env)) => PathBuf::from(env),
|
||||
_ => PathBuf::from(GRAVEYARD)
|
||||
_ => PathBuf::from(format!("{}-{}", GRAVEYARD, get_user()))
|
||||
};
|
||||
|
||||
if matches.is_present("decompose") {
|
||||
|
@ -84,10 +84,6 @@ Send files to the graveyard (/tmp/.graveyard by default) instead of unlinking th
|
|||
|
||||
let record: &Path = &graveyard.join(RECORD);
|
||||
let cwd: PathBuf = env::current_dir().expect("Failed to get current dir");
|
||||
// Disable umask so rip can create a globally writable graveyard
|
||||
unsafe {
|
||||
libc::umask(0);
|
||||
}
|
||||
|
||||
if let Some(t) = matches.values_of("unbury") {
|
||||
// Vector to hold the grave path of items we want to unbury.
|
||||
|
@ -262,10 +258,8 @@ fn bury<S: AsRef<Path>, D: AsRef<Path>>(source: S, dest: D) -> io::Result<()> {
|
|||
}
|
||||
|
||||
// If that didn't work, then copy and rm.
|
||||
// All parent directories are created with open permissions so that
|
||||
// other users can rip things into the same graveyard
|
||||
let parent = dest.parent().expect("Trying to delete root?");
|
||||
fs::DirBuilder::new().mode(0o777).recursive(true).create(parent)?;
|
||||
let parent = dest.parent().ok_or(io::Error::last_os_error())?;
|
||||
fs::create_dir_all(parent)?;
|
||||
|
||||
if fs::symlink_metadata(source)?.is_dir() {
|
||||
// Walk the source, creating directories and copying files as needed
|
||||
|
@ -274,10 +268,7 @@ fn bury<S: AsRef<Path>, D: AsRef<Path>>(source: S, dest: D) -> io::Result<()> {
|
|||
let orphan: &Path = entry.path().strip_prefix(source)
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
|
||||
if entry.file_type().is_dir() {
|
||||
let mode = entry.metadata()?.permissions().mode();
|
||||
if let Err(e) = fs::DirBuilder::new()
|
||||
.mode(mode)
|
||||
.create(dest.join(orphan)) {
|
||||
if let Err(e) = fs::create_dir_all(dest.join(orphan)) {
|
||||
println!("Failed to create {} in {}",
|
||||
entry.path().display(),
|
||||
dest.join(orphan).display());
|
||||
|
@ -340,7 +331,7 @@ fn copy_file<S: AsRef<Path>, D: AsRef<Path>>(source: S, dest: D) -> io::Result<(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the path in the graveyard of the last file buried by the user.
|
||||
/// Return the path in the graveyard of the last file to be buried.
|
||||
/// As a side effect, any valid last files that are found in the record but
|
||||
/// not on the filesystem are removed from the record.
|
||||
fn get_last_bury<R: AsRef<Path>>(record: R) -> io::Result<String> {
|
||||
|
@ -351,21 +342,19 @@ fn get_last_bury<R: AsRef<Path>>(record: R) -> io::Result<String> {
|
|||
f.read_to_string(&mut contents)?;
|
||||
|
||||
// This could be cleaned up more if/when for loops can return a value
|
||||
for entry in contents.lines().rev()
|
||||
.map(record_entry)
|
||||
.filter(|x| x.user == get_user()) {
|
||||
// Check that the file is still in the graveyard.
|
||||
// If it is, return the corresponding line.
|
||||
if symlink_exists(entry.dest) {
|
||||
if !graves_to_exhume.is_empty() {
|
||||
delete_lines_from_record(f, record, graves_to_exhume)?;
|
||||
}
|
||||
return Ok(String::from(entry.dest))
|
||||
} else {
|
||||
// File is gone, mark the grave to be removed from the record
|
||||
graves_to_exhume.push(String::from(entry.dest));
|
||||
for entry in contents.lines().rev().map(record_entry) {
|
||||
// Check that the file is still in the graveyard.
|
||||
// If it is, return the corresponding line.
|
||||
if symlink_exists(entry.dest) {
|
||||
if !graves_to_exhume.is_empty() {
|
||||
delete_lines_from_record(f, record, graves_to_exhume)?;
|
||||
}
|
||||
return Ok(String::from(entry.dest))
|
||||
} else {
|
||||
// File is gone, mark the grave to be removed from the record
|
||||
graves_to_exhume.push(String::from(entry.dest));
|
||||
}
|
||||
}
|
||||
|
||||
if !graves_to_exhume.is_empty() {
|
||||
delete_lines_from_record(f, record, graves_to_exhume)?;
|
||||
|
|
Loading…
Reference in a new issue