@@ -10,28 +10,31 @@ use std::borrow::Cow;
1010use std:: cmp:: Ordering ;
1111use std:: ffi:: { OsStr , OsString } ;
1212use std:: fs:: { self , Metadata } ;
13- use std:: io;
13+ use std:: io:: { self , Write } ;
1414use std:: path:: { Path , PathBuf } ;
1515
1616use crate :: diff:: { compare_files, Outcome } ;
1717use crate :: params:: { MissingFile , Params } ;
1818use crate :: utils:: {
1919 fold_file_name_case, quote_file_name, quote_shell_argument, report_failure_to_read_input_file,
20+ single_quote,
2021} ;
2122
22- /// Compares the directories `params.from` and `params.to`. `option_args` are
23- /// the command line options, repeated in the header printed before the
24- /// differences of each pair of files.
25- pub fn compare_directories ( params : & Params , option_args : & [ OsString ] ) -> Outcome {
23+ /// Compares `params.from` and `params.to` according to their types: the
24+ /// entries of directories, the targets of symbolic links that are not
25+ /// followed, or the contents of files. `option_args` are the command line
26+ /// options, repeated in the header printed before the differences of each
27+ /// pair of files found in directories.
28+ pub fn compare ( params : & Params , option_args : & [ OsString ] ) -> Outcome {
2629 let mut walker = DirectoryWalker {
2730 params,
2831 option_args,
2932 ancestors : Vec :: new ( ) ,
3033 } ;
31- walker. compare_directories ( & params. from , & params. to , Level :: Top )
34+ walker. compare_entries ( & params. from , & params. to , Level :: Top )
3235}
3336
34- /// Whether directories are the operands of diff, or nested in them .
37+ /// Whether files are the operands of diff, or found in directories .
3538#[ derive( Clone , Copy , Eq , PartialEq ) ]
3639enum Level {
3740 Top ,
@@ -83,17 +86,17 @@ impl DirectoryWalker<'_> {
8386 for entry in entries {
8487 let entry_outcome = match entry {
8588 Entry :: Both ( from, to) => {
86- self . compare_entries ( & join ( from_dir, from) , & join ( to_dir, to) )
89+ self . compare_entries ( & join ( from_dir, from) , & join ( to_dir, to) , Level :: Nested )
8790 }
8891 Entry :: FromOnly ( name) if self . params . missing_file == MissingFile :: Empty => {
89- self . compare_entries ( & join ( from_dir, name) , & join ( to_dir, name) )
92+ self . compare_entries ( & join ( from_dir, name) , & join ( to_dir, name) , Level :: Nested )
9093 }
9194 Entry :: FromOnly ( name) => report_only_in ( from_dir, name) ,
9295 Entry :: ToOnly ( name) if self . params . missing_file == MissingFile :: Error => {
9396 report_only_in ( to_dir, name)
9497 }
9598 Entry :: ToOnly ( name) => {
96- self . compare_entries ( & join ( from_dir, name) , & join ( to_dir, name) )
99+ self . compare_entries ( & join ( from_dir, name) , & join ( to_dir, name) , Level :: Nested )
97100 }
98101 } ;
99102 outcome = outcome. max ( entry_outcome) ;
@@ -148,7 +151,7 @@ impl DirectoryWalker<'_> {
148151
149152 /// Compares two entries of the compared directories, one of which may be
150153 /// missing when missing files are treated as empty.
151- fn compare_entries ( & mut self , from : & OsStr , to : & OsStr ) -> Outcome {
154+ fn compare_entries ( & mut self , from : & OsStr , to : & OsStr , level : Level ) -> Outcome {
152155 let ( Ok ( from_metadata) , Ok ( to_metadata) ) = ( self . metadata ( from) , self . metadata ( to) ) else {
153156 return Outcome :: Trouble ;
154157 } ;
@@ -158,8 +161,8 @@ impl DirectoryWalker<'_> {
158161 {
159162 if same_file:: is_same_file ( from, to) . unwrap_or ( false ) {
160163 Outcome :: Identical
161- } else if self . params . recursive {
162- self . compare_directories ( from, to, Level :: Nested )
164+ } else if level == Level :: Top || self . params . recursive {
165+ self . compare_directories ( from, to, level )
163166 } else {
164167 println ! (
165168 "Common subdirectories: {} and {}" ,
@@ -169,6 +172,11 @@ impl DirectoryWalker<'_> {
169172 Outcome :: Identical
170173 }
171174 }
175+ ( Some ( from_metadata) , Some ( to_metadata) )
176+ if from_metadata. is_symlink ( ) && to_metadata. is_symlink ( ) =>
177+ {
178+ self . compare_symbolic_links ( from, to)
179+ }
172180 ( Some ( from_metadata) , Some ( to_metadata) )
173181 if !( from_metadata. is_file ( ) && to_metadata. is_file ( ) ) =>
174182 {
@@ -194,10 +202,53 @@ impl DirectoryWalker<'_> {
194202 Outcome :: Identical
195203 }
196204 }
205+ // The operands themselves are compared without a header, as when
206+ // they are not directories.
207+ _ if level == Level :: Top => compare_files ( self . params , None ) ,
197208 _ => self . compare_files ( from, to) ,
198209 }
199210 }
200211
212+ /// Compares the targets of two symbolic links.
213+ fn compare_symbolic_links ( & self , from : & OsStr , to : & OsStr ) -> Outcome {
214+ let ( from_target, to_target) = match ( fs:: read_link ( from) , fs:: read_link ( to) ) {
215+ ( Ok ( from_target) , Ok ( to_target) ) => ( from_target, to_target) ,
216+ ( Err ( error) , _) => {
217+ self . report_error ( from, & error) ;
218+ return Outcome :: Trouble ;
219+ }
220+ ( _, Err ( error) ) => {
221+ self . report_error ( to, & error) ;
222+ return Outcome :: Trouble ;
223+ }
224+ } ;
225+ if from_target == to_target {
226+ if self . params . report_identical_files {
227+ println ! (
228+ "Files {} and {} are identical" ,
229+ from. to_string_lossy( ) ,
230+ to. to_string_lossy( )
231+ ) ;
232+ }
233+ return Outcome :: Identical ;
234+ }
235+ let mut message = b"Symbolic links " . to_vec ( ) ;
236+ for ( link, target, separator) in [
237+ ( from, from_target. as_os_str ( ) , & b" and " [ ..] ) ,
238+ ( to, to_target. as_os_str ( ) , b" differ\n " ) ,
239+ ] {
240+ message. extend ( single_quote ( link. as_encoded_bytes ( ) ) ) ;
241+ message. extend_from_slice ( b" -> " ) ;
242+ message. extend ( single_quote ( target. as_encoded_bytes ( ) ) ) ;
243+ message. extend_from_slice ( separator) ;
244+ }
245+ if io:: stdout ( ) . write_all ( & message) . is_err ( ) {
246+ return Outcome :: Trouble ;
247+ }
248+ Outcome :: Different
249+ }
250+
251+ /// Compares two files found in the compared directories.
201252 fn compare_files ( & self , from : & OsStr , to : & OsStr ) -> Outcome {
202253 let params = Params {
203254 from : from. to_os_string ( ) ,
@@ -220,7 +271,12 @@ impl DirectoryWalker<'_> {
220271 /// Returns the metadata of `path`, `None` if it is missing on purpose
221272 /// (-N), or an error after reporting it.
222273 fn metadata ( & self , path : & OsStr ) -> Result < Option < Metadata > , ( ) > {
223- match fs:: metadata ( path) {
274+ let metadata = if self . params . no_dereference {
275+ fs:: symlink_metadata ( path)
276+ } else {
277+ fs:: metadata ( path)
278+ } ;
279+ match metadata {
224280 Ok ( metadata) => Ok ( Some ( metadata) ) ,
225281 Err ( error)
226282 if error. kind ( ) == io:: ErrorKind :: NotFound
@@ -305,6 +361,10 @@ fn join(dir: &OsStr, name: &OsStr) -> OsString {
305361/// Describes the type of a file, as in "File X is a fifo while ...".
306362fn file_kind ( metadata : & Metadata ) -> & ' static str {
307363 let file_type = metadata. file_type ( ) ;
364+ // Only seen with --no-dereference, as symbolic links are followed otherwise.
365+ if file_type. is_symlink ( ) {
366+ return "symbolic link" ;
367+ }
308368 if file_type. is_dir ( ) {
309369 return "directory" ;
310370 }
0 commit comments