mirror of
https://github.com/nivekuil/rip
synced 2024-11-22 11:43:20 +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]
|
[package]
|
||||||
name = "rm-improved"
|
name = "rm-improved"
|
||||||
version = "0.10.2"
|
version = "0.11.0"
|
||||||
authors = ["mail@nivekuil.com"]
|
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"
|
repository = "https://github.com/nivekuil/rip"
|
||||||
license = "GPL-3.0+"
|
license = "GPL-3.0+"
|
||||||
include = [
|
include = [
|
||||||
|
@ -13,7 +13,7 @@ include = [
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = "2"
|
clap = "2"
|
||||||
walkdir = "0.1"
|
walkdir = "0.1"
|
||||||
libc = "0.2"
|
time = "0.1"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|
20
README.org
20
README.org
|
@ -45,7 +45,7 @@ $ rip dir1/ file1
|
||||||
Undo the last deletion
|
Undo the last deletion
|
||||||
#+BEGIN_EXAMPLE
|
#+BEGIN_EXAMPLE
|
||||||
$ rip -u
|
$ 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
|
#+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
|
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
|
#+BEGIN_EXAMPLE
|
||||||
|
@ -58,28 +58,28 @@ Send file1 to the graveyard? (y/n) y
|
||||||
Print files that were deleted from under the current directory
|
Print files that were deleted from under the current directory
|
||||||
#+BEGIN_EXAMPLE
|
#+BEGIN_EXAMPLE
|
||||||
$ rip -s
|
$ rip -s
|
||||||
/tmp/.graveyard/home/jack/file1
|
/tmp/graveyard-jack/home/jack/file1
|
||||||
/tmp/.graveyard/home/jack/dir1
|
/tmp/graveyard-jack/home/jack/dir1
|
||||||
#+END_EXAMPLE
|
#+END_EXAMPLE
|
||||||
Name conflicts are resolved
|
Name conflicts are resolved
|
||||||
#+BEGIN_EXAMPLE
|
#+BEGIN_EXAMPLE
|
||||||
$ touch file1
|
$ touch file1
|
||||||
$ rip file1
|
$ rip file1
|
||||||
$ rip -s
|
$ rip -s
|
||||||
/tmp/.graveyard/home/jack/dir1
|
/tmp/graveyard-jack/home/jack/dir1
|
||||||
/tmp/.graveyard/home/jack/file1
|
/tmp/graveyard-jack/home/jack/file1
|
||||||
/tmp/.graveyard/home/jack/file1~1
|
/tmp/graveyard-jack/home/jack/file1~1
|
||||||
#+END_EXAMPLE
|
#+END_EXAMPLE
|
||||||
-u also takes the path of a file in the graveyard
|
-u also takes the path of a file in the graveyard
|
||||||
#+BEGIN_EXAMPLE
|
#+BEGIN_EXAMPLE
|
||||||
$ rip -u /tmp/.graveyard/home/jack/file1
|
$ rip -u /tmp/graveyard-jack/home/jack/file1
|
||||||
Returned /tmp/.graveyard/home/jack/file1 to /home/jack/file1
|
Returned /tmp/graveyard-jack/home/jack/file1 to /home/jack/file1
|
||||||
#+END_EXAMPLE
|
#+END_EXAMPLE
|
||||||
Combine -u and -s to restore everything printed by -s
|
Combine -u and -s to restore everything printed by -s
|
||||||
#+BEGIN_EXAMPLE
|
#+BEGIN_EXAMPLE
|
||||||
$ rip -su
|
$ rip -su
|
||||||
Returned /tmp/.graveyard/home/jack/dir1 to /home/jack/dir1
|
Returned /tmp/graveyard-jack/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/file1~1 to /home/jack/file1~1
|
||||||
#+END_EXAMPLE
|
#+END_EXAMPLE
|
||||||
** ⚰ Notes
|
** ⚰ 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.
|
- 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!())
|
.version(crate_version!())
|
||||||
.author(crate_authors!())
|
.author(crate_authors!())
|
||||||
.about("Rm ImProved
|
.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")
|
.arg(Arg::with_name("TARGET")
|
||||||
.help("File or directory to remove")
|
.help("File or directory to remove")
|
||||||
.multiple(true)
|
.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")) {
|
let graveyard = &match (matches.value_of("graveyard"), env::var("GRAVEYARD")) {
|
||||||
(Some(flag), _) => PathBuf::from(flag),
|
(Some(flag), _) => PathBuf::from(flag),
|
||||||
(_, Ok(env)) => PathBuf::from(env),
|
(_, Ok(env)) => PathBuf::from(env),
|
||||||
_ => PathBuf::from(GRAVEYARD)
|
_ => PathBuf::from(format!("{}-{}", GRAVEYARD, get_user()))
|
||||||
};
|
};
|
||||||
|
|
||||||
if matches.is_present("decompose") {
|
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 record: &Path = &graveyard.join(RECORD);
|
||||||
let cwd: PathBuf = env::current_dir().expect("Failed to get current dir");
|
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") {
|
if let Some(t) = matches.values_of("unbury") {
|
||||||
// Vector to hold the grave path of items we want to 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.
|
// If that didn't work, then copy and rm.
|
||||||
// All parent directories are created with open permissions so that
|
let parent = dest.parent().ok_or(io::Error::last_os_error())?;
|
||||||
// other users can rip things into the same graveyard
|
fs::create_dir_all(parent)?;
|
||||||
let parent = dest.parent().expect("Trying to delete root?");
|
|
||||||
fs::DirBuilder::new().mode(0o777).recursive(true).create(parent)?;
|
|
||||||
|
|
||||||
if fs::symlink_metadata(source)?.is_dir() {
|
if fs::symlink_metadata(source)?.is_dir() {
|
||||||
// Walk the source, creating directories and copying files as needed
|
// 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)
|
let orphan: &Path = entry.path().strip_prefix(source)
|
||||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
|
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
|
||||||
if entry.file_type().is_dir() {
|
if entry.file_type().is_dir() {
|
||||||
let mode = entry.metadata()?.permissions().mode();
|
if let Err(e) = fs::create_dir_all(dest.join(orphan)) {
|
||||||
if let Err(e) = fs::DirBuilder::new()
|
|
||||||
.mode(mode)
|
|
||||||
.create(dest.join(orphan)) {
|
|
||||||
println!("Failed to create {} in {}",
|
println!("Failed to create {} in {}",
|
||||||
entry.path().display(),
|
entry.path().display(),
|
||||||
dest.join(orphan).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(())
|
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
|
/// As a side effect, any valid last files that are found in the record but
|
||||||
/// not on the filesystem are removed from the record.
|
/// not on the filesystem are removed from the record.
|
||||||
fn get_last_bury<R: AsRef<Path>>(record: R) -> io::Result<String> {
|
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)?;
|
f.read_to_string(&mut contents)?;
|
||||||
|
|
||||||
// This could be cleaned up more if/when for loops can return a value
|
// This could be cleaned up more if/when for loops can return a value
|
||||||
for entry in contents.lines().rev()
|
for entry in contents.lines().rev().map(record_entry) {
|
||||||
.map(record_entry)
|
// Check that the file is still in the graveyard.
|
||||||
.filter(|x| x.user == get_user()) {
|
// If it is, return the corresponding line.
|
||||||
// Check that the file is still in the graveyard.
|
if symlink_exists(entry.dest) {
|
||||||
// If it is, return the corresponding line.
|
if !graves_to_exhume.is_empty() {
|
||||||
if symlink_exists(entry.dest) {
|
delete_lines_from_record(f, record, graves_to_exhume)?;
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
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() {
|
if !graves_to_exhume.is_empty() {
|
||||||
delete_lines_from_record(f, record, graves_to_exhume)?;
|
delete_lines_from_record(f, record, graves_to_exhume)?;
|
||||||
|
|
Loading…
Reference in a new issue