Patchwork D11774: rhg: Fix status desambiguation of symlinks and executable files

login
register
mail settings
Submitter phabricator
Date Nov. 23, 2021, 7:12 p.m.
Message ID <differential-rev-PHID-DREV-iakb53gflo6pn2ab2xup-req@mercurial-scm.org>
Download mbox | patch
Permalink /patch/50100/
State Superseded
Headers show

Comments

phabricator - Nov. 23, 2021, 7:12 p.m.
SimonSapin created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D11774

AFFECTED FILES
  rust/hg-core/src/dirstate/entry.rs
  rust/hg-core/src/vfs.rs
  rust/rhg/src/commands/status.rs
  tests/test-execute-bit.t
  tests/test-merge-exec.t
  tests/test-merge-types.t
  tests/test-symlinks.t

CHANGE DETAILS




To: SimonSapin, #hg-reviewers
Cc: mercurial-patches, mercurial-devel

Patch

diff --git a/tests/test-symlinks.t b/tests/test-symlinks.t
--- a/tests/test-symlinks.t
+++ b/tests/test-symlinks.t
@@ -11,10 +11,6 @@ 
   > EOF
 #endif
 
-TODO: fix rhg bugs that make this test fail when status is enabled
-  $ unset RHG_STATUS
-
-
 == tests added in 0.7 ==
 
   $ hg init test-symlinks-0.7; cd test-symlinks-0.7;
diff --git a/tests/test-merge-types.t b/tests/test-merge-types.t
--- a/tests/test-merge-types.t
+++ b/tests/test-merge-types.t
@@ -1,9 +1,5 @@ 
 #require symlink execbit
 
-TODO: fix rhg bugs that make this test fail when status is enabled
-  $ unset RHG_STATUS
-
-
   $ tellmeabout() {
   > if [ -h $1 ]; then
   >     echo $1 is a symlink:
diff --git a/tests/test-merge-exec.t b/tests/test-merge-exec.t
--- a/tests/test-merge-exec.t
+++ b/tests/test-merge-exec.t
@@ -4,10 +4,6 @@ 
 
 #require execbit
 
-TODO: fix rhg bugs that make this test fail when status is enabled
-  $ unset RHG_STATUS
-
-
 Initial setup
 ==============
 
diff --git a/tests/test-execute-bit.t b/tests/test-execute-bit.t
--- a/tests/test-execute-bit.t
+++ b/tests/test-execute-bit.t
@@ -1,9 +1,5 @@ 
 #require execbit
 
-TODO: fix rhg bugs that make this test fail when status is enabled
-  $ unset RHG_STATUS
-
-
   $ hg init
   $ echo a > a
   $ hg ci -Am'not executable'
diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs
--- a/rust/rhg/src/commands/status.rs
+++ b/rust/rhg/src/commands/status.rs
@@ -11,11 +11,12 @@ 
 use clap::{Arg, SubCommand};
 use hg;
 use hg::config::Config;
-use hg::dirstate::TruncatedTimestamp;
+use hg::dirstate::{has_exec_bit, TruncatedTimestamp};
 use hg::errors::HgError;
 use hg::manifest::Manifest;
 use hg::matchers::AlwaysMatcher;
 use hg::repo::Repo;
+use hg::utils::files::get_bytes_from_os_string;
 use hg::utils::hg_path::{hg_path_to_os_string, HgPath};
 use hg::{HgPathCow, StatusOptions};
 use log::{info, warn};
@@ -302,16 +303,29 @@ 
 ///
 /// This meant to be used for those that the dirstate cannot resolve, due
 /// to time resolution limits.
-///
-/// TODO: detect permission bits and similar metadata modifications
 fn unsure_is_modified(
     repo: &Repo,
     manifest: &Manifest,
     hg_path: &HgPath,
 ) -> Result<bool, HgError> {
+    let vfs = repo.working_directory_vfs();
+    let fs_path = hg_path_to_os_string(hg_path).expect("HgPath conversion");
+    let fs_metadata = vfs.symlink_metadata(&fs_path)?;
+    let is_symlink = fs_metadata.file_type().is_symlink();
+    let fs_flags = if is_symlink {
+        Some(b'l')
+    } else if has_exec_bit(&fs_metadata) {
+        Some(b'x')
+    } else {
+        None
+    };
+
     let entry = manifest
         .find_file(hg_path)?
         .expect("ambgious file not in p1");
+    if entry.flags != fs_flags {
+        return Ok(true);
+    }
     let filelog = repo.filelog(hg_path)?;
     let filelog_entry =
         filelog.data_for_node(entry.node_id()?).map_err(|_| {
@@ -319,7 +333,10 @@ 
         })?;
     let contents_in_p1 = filelog_entry.data()?;
 
-    let fs_path = hg_path_to_os_string(hg_path).expect("HgPath conversion");
-    let fs_contents = repo.working_directory_vfs().read(fs_path)?;
+    let fs_contents = if is_symlink {
+        get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
+    } else {
+        vfs.read(fs_path)?
+    };
     return Ok(contents_in_p1 != &*fs_contents);
 }
diff --git a/rust/hg-core/src/vfs.rs b/rust/hg-core/src/vfs.rs
--- a/rust/hg-core/src/vfs.rs
+++ b/rust/hg-core/src/vfs.rs
@@ -16,6 +16,22 @@ 
         self.base.join(relative_path)
     }
 
+    pub fn symlink_metadata(
+        &self,
+        relative_path: impl AsRef<Path>,
+    ) -> Result<std::fs::Metadata, HgError> {
+        let path = self.join(relative_path);
+        std::fs::symlink_metadata(&path).when_reading_file(&path)
+    }
+
+    pub fn read_link(
+        &self,
+        relative_path: impl AsRef<Path>,
+    ) -> Result<PathBuf, HgError> {
+        let path = self.join(relative_path);
+        std::fs::read_link(&path).when_reading_file(&path)
+    }
+
     pub fn read(
         &self,
         relative_path: impl AsRef<Path>,
diff --git a/rust/hg-core/src/dirstate/entry.rs b/rust/hg-core/src/dirstate/entry.rs
--- a/rust/hg-core/src/dirstate/entry.rs
+++ b/rust/hg-core/src/dirstate/entry.rs
@@ -580,10 +580,8 @@ 
         &self,
         filesystem_metadata: &std::fs::Metadata,
     ) -> bool {
-        use std::os::unix::fs::MetadataExt;
-        const EXEC_BIT_MASK: u32 = 0o100;
-        let dirstate_exec_bit = (self.mode() as u32) & EXEC_BIT_MASK;
-        let fs_exec_bit = filesystem_metadata.mode() & EXEC_BIT_MASK;
+        let dirstate_exec_bit = (self.mode() as u32 & EXEC_BIT_MASK) != 0;
+        let fs_exec_bit = has_exec_bit(filesystem_metadata);
         dirstate_exec_bit != fs_exec_bit
     }
 
@@ -641,3 +639,11 @@ 
         }
     }
 }
+
+const EXEC_BIT_MASK: u32 = 0o100;
+
+pub fn has_exec_bit(metadata: &std::fs::Metadata) -> bool {
+    // TODO: How to handle executable permissions on Windows?
+    use std::os::unix::fs::MetadataExt;
+    (metadata.mode() & EXEC_BIT_MASK) != 0
+}