1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

feature(ln): implement -r (#1540)

* bump the minimal version of rustc to 1.32

* feature(ln): implement -r

* fix two issues

* Use cow

* rustfmt the change

* with cargo.lock 1.31

* try to unbreak windows
This commit is contained in:
Sylvestre Ledru 2020-06-18 09:54:18 +02:00 committed by GitHub
parent 87af997c7a
commit f17a112781
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 115 additions and 10 deletions

8
Cargo.lock generated
View file

@ -1,5 +1,3 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]] [[package]]
name = "advapi32-sys" name = "advapi32-sys"
version = "0.2.0" version = "0.2.0"
@ -171,7 +169,7 @@ dependencies = [
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
"unindent 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "unindent 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"users 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "users 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"uu_arch 0.0.1", "uu_arch 0.0.1",
@ -1078,7 +1076,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "unindent" name = "unindent"
version = "0.1.5" version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
@ -2284,7 +2282,7 @@ dependencies = [
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
"checksum unindent 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "63f18aa3b0e35fed5a0048f029558b1518095ffe2a0a31fb87c93dece93a4993" "checksum unindent 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "af41d708427f8fd0e915dcebb2cae0f0e6acb2a939b2d399c265c39a38a18942"
"checksum unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6aa2700417c405c38f5e6902d699345241c28c0b7ade4abaad71e35a87eb1564" "checksum unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6aa2700417c405c38f5e6902d699345241c28c0b7ade4abaad71e35a87eb1564"
"checksum users 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4227e95324a443c9fcb06e03d4d85e91aabe9a5a02aa818688b6918b6af486" "checksum users 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4227e95324a443c9fcb06e03d4d85e91aabe9a5a02aa818688b6918b6af486"
"checksum uucore 0.0.4 (git+https://github.com/uutils/uucore.git?branch=canary)" = "<none>" "checksum uucore 0.0.4 (git+https://github.com/uutils/uucore.git?branch=canary)" = "<none>"

View file

@ -10,13 +10,17 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use std::borrow::Cow;
use std::ffi::OsStr;
use std::fs; use std::fs;
use std::io::{stdin, Result}; use std::io::{stdin, Result};
#[cfg(any(unix, target_os = "redox"))] #[cfg(any(unix, target_os = "redox"))]
use std::os::unix::fs::symlink; use std::os::unix::fs::symlink;
#[cfg(windows)] #[cfg(windows)]
use std::os::windows::fs::{symlink_dir, symlink_file}; use std::os::windows::fs::{symlink_dir, symlink_file};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use uucore::fs::{canonicalize, CanonicalizeMode};
static NAME: &str = "ln"; static NAME: &str = "ln";
static SUMMARY: &str = ""; static SUMMARY: &str = "";
@ -36,6 +40,7 @@ pub struct Settings {
backup: BackupMode, backup: BackupMode,
suffix: String, suffix: String,
symbolic: bool, symbolic: bool,
relative: bool,
target_dir: Option<String>, target_dir: Option<String>,
no_target_dir: bool, no_target_dir: bool,
verbose: bool, verbose: bool,
@ -92,7 +97,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
// TODO: opts.optflag("n", "no-dereference", "treat LINK_NAME as a normal file if it is a \ // TODO: opts.optflag("n", "no-dereference", "treat LINK_NAME as a normal file if it is a \
// symbolic link to a directory"); // symbolic link to a directory");
// TODO: opts.optflag("P", "physical", "make hard links directly to symbolic links"); // TODO: opts.optflag("P", "physical", "make hard links directly to symbolic links");
// TODO: opts.optflag("r", "relative", "create symbolic links relative to link location");
.optflag("s", "symbolic", "make symbolic links instead of hard links") .optflag("s", "symbolic", "make symbolic links instead of hard links")
.optopt("S", "suffix", "override the usual backup suffix", "SUFFIX") .optopt("S", "suffix", "override the usual backup suffix", "SUFFIX")
.optopt( .optopt(
@ -106,6 +110,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
"no-target-directory", "no-target-directory",
"treat LINK_NAME as a normal file always", "treat LINK_NAME as a normal file always",
) )
.optflag(
"r",
"relative",
"create symbolic links relative to link location",
)
.optflag("v", "verbose", "print name of each linked file") .optflag("v", "verbose", "print name of each linked file")
.parse(args); .parse(args);
@ -168,6 +177,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
backup: backup_mode, backup: backup_mode,
suffix: backup_suffix, suffix: backup_suffix,
symbolic: matches.opt_present("s"), symbolic: matches.opt_present("s"),
relative: matches.opt_present("r"),
target_dir: matches.opt_str("t"), target_dir: matches.opt_str("t"),
no_target_dir: matches.opt_present("T"), no_target_dir: matches.opt_present("T"),
verbose: matches.opt_present("v"), verbose: matches.opt_present("v"),
@ -279,8 +289,33 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &PathBuf, settings: &Setting
} }
} }
fn relative_path<'a>(src: &PathBuf, dst: &PathBuf) -> Result<Cow<'a, Path>> {
let abssrc = canonicalize(src, CanonicalizeMode::Normal)?;
let absdst = canonicalize(dst, CanonicalizeMode::Normal)?;
let suffix_pos = abssrc
.components()
.zip(absdst.components())
.take_while(|(s, d)| s == d)
.count();
let srciter = abssrc.components().skip(suffix_pos).map(|x| x.as_os_str());
let result: PathBuf = absdst
.components()
.skip(suffix_pos + 1)
.map(|_| OsStr::new(".."))
.chain(srciter)
.collect();
Ok(result.into())
}
fn link(src: &PathBuf, dst: &PathBuf, settings: &Settings) -> Result<()> { fn link(src: &PathBuf, dst: &PathBuf, settings: &Settings) -> Result<()> {
let mut backup_path = None; let mut backup_path = None;
let source: Cow<'_, Path> = if settings.relative {
relative_path(&src, dst)?
} else {
src.into()
};
if is_symlink(dst) || dst.exists() { if is_symlink(dst) || dst.exists() {
match settings.overwrite { match settings.overwrite {
@ -307,13 +342,13 @@ fn link(src: &PathBuf, dst: &PathBuf, settings: &Settings) -> Result<()> {
} }
if settings.symbolic { if settings.symbolic {
symlink(src, dst)?; symlink(&source, dst)?;
} else { } else {
fs::hard_link(src, dst)?; fs::hard_link(&source, dst)?;
} }
if settings.verbose { if settings.verbose {
print!("'{}' -> '{}'", dst.display(), src.display()); print!("'{}' -> '{}'", dst.display(), &source.display());
match backup_path { match backup_path {
Some(path) => println!(" (backup: '{}')", path.display()), Some(path) => println!(" (backup: '{}')", path.display()),
None => println!(), None => println!(),
@ -359,7 +394,7 @@ fn existing_backup_path(path: &PathBuf, suffix: &str) -> PathBuf {
} }
#[cfg(windows)] #[cfg(windows)]
pub fn symlink<P: AsRef<Path>>(src: P, dst: P) -> Result<()> { pub fn symlink<P1: AsRef<Path>, P2: AsRef<Path>>(src: P1, dst: P2) -> Result<()> {
if src.as_ref().is_dir() { if src.as_ref().is_dir() {
symlink_dir(src, dst) symlink_dir(src, dst)
} else { } else {

View file

@ -416,3 +416,75 @@ fn test_symlink_missing_destination() {
file file
)); ));
} }
#[test]
fn test_symlink_relative() {
let (at, mut ucmd) = at_and_ucmd!();
let file_a = "test_symlink_relative_a";
let link = "test_symlink_relative_link";
at.touch(file_a);
// relative symlink
ucmd.args(&["-r", "-s", file_a, link]).succeeds();
assert!(at.is_symlink(link));
assert_eq!(at.resolve_link(link), file_a);
}
#[test]
fn test_hardlink_relative() {
let (at, mut ucmd) = at_and_ucmd!();
let file_a = "test_hardlink_relative_a";
let link = "test_hardlink_relative_link";
at.touch(file_a);
// relative hardlink
ucmd.args(&["-r", "-v", file_a, link])
.succeeds()
.stdout_only(format!("'{}' -> '{}'\n", link, file_a));
}
#[test]
fn test_symlink_relative_path() {
let (at, mut ucmd) = at_and_ucmd!();
let dir = "test_symlink_existing_dir";
let file_a = "test_symlink_relative_a";
let link = "test_symlink_relative_link";
let multi_dir =
"test_symlink_existing_dir/../test_symlink_existing_dir/../test_symlink_existing_dir/../";
let p = PathBuf::from(multi_dir).join(file_a);
at.mkdir(dir);
// relative symlink
// Thanks to -r, all the ../ should be resolved to a single file
ucmd.args(&["-r", "-s", "-v", &p.to_string_lossy(), link])
.succeeds()
.stdout_only(format!("'{}' -> '{}'\n", link, file_a));
assert!(at.is_symlink(link));
assert_eq!(at.resolve_link(link), file_a);
// Run the same command without -r to verify that we keep the full
// crazy path
let (at, mut ucmd) = at_and_ucmd!();
ucmd.args(&["-s", "-v", &p.to_string_lossy(), link])
.succeeds()
.stdout_only(format!("'{}' -> '{}'\n", link, &p.to_string_lossy()));
assert!(at.is_symlink(link));
assert_eq!(at.resolve_link(link), p.to_string_lossy());
}
#[test]
fn test_symlink_relative_dir() {
let (at, mut ucmd) = at_and_ucmd!();
let dir = "test_symlink_existing_dir";
let link = "test_symlink_existing_dir_link";
at.mkdir(dir);
ucmd.args(&["-s", "-r", dir, link]).succeeds().no_stderr();
assert!(at.dir_exists(dir));
assert!(at.is_symlink(link));
assert_eq!(at.resolve_link(link), dir);
}