add symlink handling

Summary:
Symlinks are treated as files with "special" mode and link target in contents.

`hg diff --git` and `git` behaviours differ wrt to replacing files with
symlinks:
 * git renders that as two operations: removing a file and adding a symlink
 * hg renders that as file modification

 I'm choosing the `hg --git` behaviour for now (as it was easier to implement)

Reviewed By: krallin

Differential Revision: D18169625

fbshipit-source-id: 9ef746c7d242f7142a5ac89924a3e9bed08cb383
This commit is contained in:
Mateusz Kwapich 2019-10-30 05:02:05 -07:00 committed by Facebook Github Bot
parent c43c91d281
commit 93ad9cffee
3 changed files with 76 additions and 20 deletions

View File

@ -31,6 +31,10 @@ struct Opt {
#[structopt(short, long)]
move_: bool,
/// Do not follow symlinks - compare them instead (POSIX-only)
#[structopt(short, long)]
symlink: bool,
/// Number of lines of unified context (default: 3)
#[structopt(short = "U", long, default_value = "3")]
unified: usize,
@ -40,17 +44,30 @@ fn main() -> Result<(), std::io::Error> {
let opt = Opt::from_args();
#[cfg(target_family = "unix")]
fn file_mode(path: &Path) -> Result<FileType, std::io::Error> {
if (path.metadata()?.permissions().mode() & EXEC_BIT) > 0 {
Ok(FileType::Executable)
fn file_mode_and_contents(
opt: &Opt,
path: &Path,
) -> Result<(FileType, Vec<u8>), std::io::Error> {
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
if opt.symlink && path.symlink_metadata()?.file_type().is_symlink() {
let dest = path.read_link()?;
let dest: &OsStr = dest.as_ref();
Ok((FileType::Symlink, dest.as_bytes().to_owned()))
} else if (path.metadata()?.permissions().mode() & EXEC_BIT) > 0 {
Ok((FileType::Executable, fs::read(path)?))
} else {
Ok(FileType::Regular)
Ok((FileType::Regular, fs::read(path)?))
}
}
#[cfg(target_family = "windows")]
fn file_mode(path: &Path) -> Result<FileType, std::io::Error> {
Ok(FileType::Regular)
fn file_mode_and_contents(
_opt: &Opt,
path: &Path,
) -> Result<(FileType, Vec<u8>), std::io::Error> {
Ok((FileType::Regular, fs::read(path)?))
}
let copy_info = match (opt.copy, opt.move_) {
@ -62,21 +79,15 @@ fn main() -> Result<(), std::io::Error> {
let a_path_str = opt.file_a.to_string_lossy();
let a = if opt.file_a.is_file() {
Some(DiffFile::new(
a_path_str.as_bytes(),
fs::read(&opt.file_a)?,
file_mode(&opt.file_a)?,
))
let (mode, contents) = file_mode_and_contents(&opt, &opt.file_a)?;
Some(DiffFile::new(a_path_str.as_bytes(), contents, mode))
} else {
None
};
let b_path_str = opt.file_b.to_string_lossy();
let b = if opt.file_b.is_file() {
Some(DiffFile::new(
b_path_str.as_bytes(),
fs::read(&opt.file_b)?,
file_mode(&opt.file_b)?,
))
let (mode, contents) = file_mode_and_contents(&opt, &opt.file_b)?;
Some(DiffFile::new(b_path_str.as_bytes(), contents, mode))
} else {
None
};

View File

@ -491,10 +491,10 @@ where
reduce,
};
fn file_type_to_mode(file_type: FileType) -> &'static [u8] {
if let FileType::Executable = file_type {
b"100755"
} else {
b"100644"
match file_type {
FileType::Executable => b"100755",
FileType::Symlink => b"120000",
FileType::Regular => b"100644",
}
}
if let (None, None) = (&old_file, &new_file) {

View File

@ -136,3 +136,48 @@ Test with binary file
diff --git a/binary_b b/binary_b
deleted file mode 100644
Binary file binary_b has changed
Test symlinks
$ ln -s a link_to_a
$ ln -s b link_to_b
$ ln -s a_exec link_to_a_exec
$ xdiff -s link_to_a link_to_b
diff --git a/link_to_a b/link_to_b
--- a/link_to_a
+++ b/link_to_b
@@ -1,1 +1,1 @@
-a
\ No newline at end of file
+b
\ No newline at end of file
$ xdiff -s link_to_a link_to_a
$ xdiff -s link_to_a link_to_a_exec
diff --git a/link_to_a b/link_to_a_exec
--- a/link_to_a
+++ b/link_to_a_exec
@@ -1,1 +1,1 @@
-a
\ No newline at end of file
+a_exec
\ No newline at end of file
$ xdiff -s link_to_a a
diff --git a/link_to_a b/a
old mode 120000
new mode 100644
--- a/link_to_a
+++ b/a
@@ -1,1 +1,6 @@
-a
\ No newline at end of file
+a
+b
+c
+d
+e
+f
$ xdiff link_to_a a