mirror of
https://github.com/uutils/coreutils
synced 2024-12-13 06:42:42 +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:
parent
87af997c7a
commit
f17a112781
3 changed files with 115 additions and 10 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -1,5 +1,3 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "advapi32-sys"
|
||||
version = "0.2.0"
|
||||
|
@ -171,7 +169,7 @@ dependencies = [
|
|||
"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)",
|
||||
"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)",
|
||||
"users 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"uu_arch 0.0.1",
|
||||
|
@ -1078,7 +1076,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
|
||||
[[package]]
|
||||
name = "unindent"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
|
@ -2284,7 +2282,7 @@ dependencies = [
|
|||
"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.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 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>"
|
||||
|
|
|
@ -10,13 +10,17 @@
|
|||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
|
||||
use std::io::{stdin, Result};
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
use std::os::unix::fs::symlink;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::fs::{symlink_dir, symlink_file};
|
||||
use std::path::{Path, PathBuf};
|
||||
use uucore::fs::{canonicalize, CanonicalizeMode};
|
||||
|
||||
static NAME: &str = "ln";
|
||||
static SUMMARY: &str = "";
|
||||
|
@ -36,6 +40,7 @@ pub struct Settings {
|
|||
backup: BackupMode,
|
||||
suffix: String,
|
||||
symbolic: bool,
|
||||
relative: bool,
|
||||
target_dir: Option<String>,
|
||||
no_target_dir: 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 \
|
||||
// symbolic link to a directory");
|
||||
// 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")
|
||||
.optopt("S", "suffix", "override the usual backup suffix", "SUFFIX")
|
||||
.optopt(
|
||||
|
@ -106,6 +110,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
"no-target-directory",
|
||||
"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")
|
||||
.parse(args);
|
||||
|
||||
|
@ -168,6 +177,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
backup: backup_mode,
|
||||
suffix: backup_suffix,
|
||||
symbolic: matches.opt_present("s"),
|
||||
relative: matches.opt_present("r"),
|
||||
target_dir: matches.opt_str("t"),
|
||||
no_target_dir: matches.opt_present("T"),
|
||||
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<()> {
|
||||
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() {
|
||||
match settings.overwrite {
|
||||
|
@ -307,13 +342,13 @@ fn link(src: &PathBuf, dst: &PathBuf, settings: &Settings) -> Result<()> {
|
|||
}
|
||||
|
||||
if settings.symbolic {
|
||||
symlink(src, dst)?;
|
||||
symlink(&source, dst)?;
|
||||
} else {
|
||||
fs::hard_link(src, dst)?;
|
||||
fs::hard_link(&source, dst)?;
|
||||
}
|
||||
|
||||
if settings.verbose {
|
||||
print!("'{}' -> '{}'", dst.display(), src.display());
|
||||
print!("'{}' -> '{}'", dst.display(), &source.display());
|
||||
match backup_path {
|
||||
Some(path) => println!(" (backup: '{}')", path.display()),
|
||||
None => println!(),
|
||||
|
@ -359,7 +394,7 @@ fn existing_backup_path(path: &PathBuf, suffix: &str) -> PathBuf {
|
|||
}
|
||||
|
||||
#[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() {
|
||||
symlink_dir(src, dst)
|
||||
} else {
|
||||
|
|
|
@ -416,3 +416,75 @@ fn test_symlink_missing_destination() {
|
|||
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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue