##// END OF EJS Templates
rhg: implement checkexec to support weird filesystems...
Arseniy Alekseyev -
r50789:678588b0 default
parent child Browse files
Show More
@@ -0,0 +1,111 b''
1 use std::fs;
2 use std::io;
3 use std::os::unix::fs::{MetadataExt, PermissionsExt};
4 use std::path::Path;
5
6 // This is a rust rewrite of [checkexec] function from [posix.py]
7
8 const EXECFLAGS: u32 = 0o111;
9
10 fn is_executable(path: impl AsRef<Path>) -> Result<bool, io::Error> {
11 let metadata = fs::metadata(path)?;
12 let mode = metadata.mode();
13 Ok(mode & EXECFLAGS != 0)
14 }
15
16 fn make_executable(path: impl AsRef<Path>) -> Result<(), io::Error> {
17 let mode = fs::metadata(path.as_ref())?.mode();
18 fs::set_permissions(
19 path,
20 fs::Permissions::from_mode((mode & 0o777) | EXECFLAGS),
21 )?;
22 Ok(())
23 }
24
25 fn copy_mode(
26 src: impl AsRef<Path>,
27 dst: impl AsRef<Path>,
28 ) -> Result<(), io::Error> {
29 let mode = match fs::symlink_metadata(src) {
30 Ok(metadata) => metadata.mode(),
31 Err(e) if e.kind() == io::ErrorKind::NotFound =>
32 // copymode in python has a more complicated handling of FileNotFound
33 // error, which we don't need because all it does is applying
34 // umask, which the OS already does when we mkdir.
35 {
36 return Ok(())
37 }
38 Err(e) => return Err(e),
39 };
40 fs::set_permissions(dst, fs::Permissions::from_mode(mode))?;
41 Ok(())
42 }
43
44 fn check_exec_impl(path: impl AsRef<Path>) -> Result<bool, io::Error> {
45 let basedir = path.as_ref().join(".hg");
46 let cachedir = basedir.join("wcache");
47 let storedir = basedir.join("store");
48
49 if !cachedir.exists() {
50 fs::create_dir(&cachedir)
51 .and_then(|()| {
52 if storedir.exists() {
53 copy_mode(&storedir, &cachedir)
54 } else {
55 copy_mode(&basedir, &cachedir)
56 }
57 })
58 .ok();
59 }
60
61 let leave_file: bool;
62 let checkdir: &Path;
63 let checkisexec = cachedir.join("checkisexec");
64 let checknoexec = cachedir.join("checknoexec");
65 if cachedir.is_dir() {
66 match is_executable(&checkisexec) {
67 Err(e) if e.kind() == io::ErrorKind::NotFound => (),
68 Err(e) => return Err(e),
69 Ok(is_exec) => {
70 if is_exec {
71 let noexec_is_exec = match is_executable(&checknoexec) {
72 Err(e) if e.kind() == io::ErrorKind::NotFound => {
73 fs::write(&checknoexec, "")?;
74 is_executable(&checknoexec)?
75 }
76 Err(e) => return Err(e),
77 Ok(exec) => exec,
78 };
79 if !noexec_is_exec {
80 // check-exec is exec and check-no-exec is not exec
81 return Ok(true);
82 }
83 fs::remove_file(&checknoexec)?;
84 }
85 fs::remove_file(&checkisexec)?;
86 }
87 }
88 checkdir = &cachedir;
89 leave_file = true;
90 } else {
91 checkdir = path.as_ref();
92 leave_file = false;
93 };
94
95 let tmp_file = tempfile::NamedTempFile::new_in(checkdir)?;
96 if !is_executable(tmp_file.path())? {
97 make_executable(tmp_file.path())?;
98 if is_executable(tmp_file.path())? {
99 if leave_file {
100 tmp_file.persist(checkisexec).ok();
101 }
102 return Ok(true);
103 }
104 }
105
106 Ok(false)
107 }
108
109 pub fn check_exec(path: impl AsRef<Path>) -> bool {
110 check_exec_impl(path).unwrap_or(false)
111 }
@@ -1,139 +1,140 b''
1 // Copyright 2018-2020 Georges Racinet <georges.racinet@octobus.net>
1 // Copyright 2018-2020 Georges Racinet <georges.racinet@octobus.net>
2 // and Mercurial contributors
2 // and Mercurial contributors
3 //
3 //
4 // This software may be used and distributed according to the terms of the
4 // This software may be used and distributed according to the terms of the
5 // GNU General Public License version 2 or any later version.
5 // GNU General Public License version 2 or any later version.
6
6
7 mod ancestors;
7 mod ancestors;
8 pub mod dagops;
8 pub mod dagops;
9 pub mod errors;
9 pub mod errors;
10 pub mod narrow;
10 pub mod narrow;
11 pub mod sparse;
11 pub mod sparse;
12 pub use ancestors::{AncestorsIterator, MissingAncestors};
12 pub use ancestors::{AncestorsIterator, MissingAncestors};
13 pub mod dirstate;
13 pub mod dirstate;
14 pub mod dirstate_tree;
14 pub mod dirstate_tree;
15 pub mod discovery;
15 pub mod discovery;
16 pub mod exit_codes;
16 pub mod exit_codes;
17 pub mod requirements;
17 pub mod requirements;
18 pub mod testing; // unconditionally built, for use from integration tests
18 pub mod testing; // unconditionally built, for use from integration tests
19 pub use dirstate::{
19 pub use dirstate::{
20 dirs_multiset::{DirsMultiset, DirsMultisetIter},
20 dirs_multiset::{DirsMultiset, DirsMultisetIter},
21 status::{
21 status::{
22 BadMatch, BadType, DirstateStatus, HgPathCow, StatusError,
22 BadMatch, BadType, DirstateStatus, HgPathCow, StatusError,
23 StatusOptions,
23 StatusOptions,
24 },
24 },
25 DirstateEntry, DirstateParents, EntryState,
25 DirstateEntry, DirstateParents, EntryState,
26 };
26 };
27 pub mod copy_tracing;
27 pub mod copy_tracing;
28 mod filepatterns;
28 mod filepatterns;
29 pub mod matchers;
29 pub mod matchers;
30 pub mod repo;
30 pub mod repo;
31 pub mod revlog;
31 pub mod revlog;
32 pub use revlog::*;
32 pub use revlog::*;
33 pub mod checkexec;
33 pub mod config;
34 pub mod config;
34 pub mod lock;
35 pub mod lock;
35 pub mod logging;
36 pub mod logging;
36 pub mod operations;
37 pub mod operations;
37 pub mod revset;
38 pub mod revset;
38 pub mod utils;
39 pub mod utils;
39 pub mod vfs;
40 pub mod vfs;
40
41
41 use crate::utils::hg_path::{HgPathBuf, HgPathError};
42 use crate::utils::hg_path::{HgPathBuf, HgPathError};
42 pub use filepatterns::{
43 pub use filepatterns::{
43 parse_pattern_syntax, read_pattern_file, IgnorePattern,
44 parse_pattern_syntax, read_pattern_file, IgnorePattern,
44 PatternFileWarning, PatternSyntax,
45 PatternFileWarning, PatternSyntax,
45 };
46 };
46 use std::collections::HashMap;
47 use std::collections::HashMap;
47 use std::fmt;
48 use std::fmt;
48 use twox_hash::RandomXxHashBuilder64;
49 use twox_hash::RandomXxHashBuilder64;
49
50
50 /// This is a contract between the `micro-timer` crate and us, to expose
51 /// This is a contract between the `micro-timer` crate and us, to expose
51 /// the `log` crate as `crate::log`.
52 /// the `log` crate as `crate::log`.
52 use log;
53 use log;
53
54
54 pub type LineNumber = usize;
55 pub type LineNumber = usize;
55
56
56 /// Rust's default hasher is too slow because it tries to prevent collision
57 /// Rust's default hasher is too slow because it tries to prevent collision
57 /// attacks. We are not concerned about those: if an ill-minded person has
58 /// attacks. We are not concerned about those: if an ill-minded person has
58 /// write access to your repository, you have other issues.
59 /// write access to your repository, you have other issues.
59 pub type FastHashMap<K, V> = HashMap<K, V, RandomXxHashBuilder64>;
60 pub type FastHashMap<K, V> = HashMap<K, V, RandomXxHashBuilder64>;
60
61
61 // TODO: should this be the default `FastHashMap` for all of hg-core, not just
62 // TODO: should this be the default `FastHashMap` for all of hg-core, not just
62 // dirstate_tree? How does XxHash compare with AHash, hashbrown’s default?
63 // dirstate_tree? How does XxHash compare with AHash, hashbrown’s default?
63 pub type FastHashbrownMap<K, V> =
64 pub type FastHashbrownMap<K, V> =
64 hashbrown::HashMap<K, V, RandomXxHashBuilder64>;
65 hashbrown::HashMap<K, V, RandomXxHashBuilder64>;
65
66
66 #[derive(Debug, PartialEq)]
67 #[derive(Debug, PartialEq)]
67 pub enum DirstateMapError {
68 pub enum DirstateMapError {
68 PathNotFound(HgPathBuf),
69 PathNotFound(HgPathBuf),
69 EmptyPath,
70 EmptyPath,
70 InvalidPath(HgPathError),
71 InvalidPath(HgPathError),
71 }
72 }
72
73
73 impl fmt::Display for DirstateMapError {
74 impl fmt::Display for DirstateMapError {
74 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
75 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
75 match self {
76 match self {
76 DirstateMapError::PathNotFound(_) => {
77 DirstateMapError::PathNotFound(_) => {
77 f.write_str("expected a value, found none")
78 f.write_str("expected a value, found none")
78 }
79 }
79 DirstateMapError::EmptyPath => {
80 DirstateMapError::EmptyPath => {
80 f.write_str("Overflow in dirstate.")
81 f.write_str("Overflow in dirstate.")
81 }
82 }
82 DirstateMapError::InvalidPath(path_error) => path_error.fmt(f),
83 DirstateMapError::InvalidPath(path_error) => path_error.fmt(f),
83 }
84 }
84 }
85 }
85 }
86 }
86
87
87 #[derive(Debug, derive_more::From)]
88 #[derive(Debug, derive_more::From)]
88 pub enum DirstateError {
89 pub enum DirstateError {
89 Map(DirstateMapError),
90 Map(DirstateMapError),
90 Common(errors::HgError),
91 Common(errors::HgError),
91 }
92 }
92
93
93 impl fmt::Display for DirstateError {
94 impl fmt::Display for DirstateError {
94 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
95 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
95 match self {
96 match self {
96 DirstateError::Map(error) => error.fmt(f),
97 DirstateError::Map(error) => error.fmt(f),
97 DirstateError::Common(error) => error.fmt(f),
98 DirstateError::Common(error) => error.fmt(f),
98 }
99 }
99 }
100 }
100 }
101 }
101
102
102 #[derive(Debug, derive_more::From)]
103 #[derive(Debug, derive_more::From)]
103 pub enum PatternError {
104 pub enum PatternError {
104 #[from]
105 #[from]
105 Path(HgPathError),
106 Path(HgPathError),
106 UnsupportedSyntax(String),
107 UnsupportedSyntax(String),
107 UnsupportedSyntaxInFile(String, String, usize),
108 UnsupportedSyntaxInFile(String, String, usize),
108 TooLong(usize),
109 TooLong(usize),
109 #[from]
110 #[from]
110 IO(std::io::Error),
111 IO(std::io::Error),
111 /// Needed a pattern that can be turned into a regex but got one that
112 /// Needed a pattern that can be turned into a regex but got one that
112 /// can't. This should only happen through programmer error.
113 /// can't. This should only happen through programmer error.
113 NonRegexPattern(IgnorePattern),
114 NonRegexPattern(IgnorePattern),
114 }
115 }
115
116
116 impl fmt::Display for PatternError {
117 impl fmt::Display for PatternError {
117 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
118 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
118 match self {
119 match self {
119 PatternError::UnsupportedSyntax(syntax) => {
120 PatternError::UnsupportedSyntax(syntax) => {
120 write!(f, "Unsupported syntax {}", syntax)
121 write!(f, "Unsupported syntax {}", syntax)
121 }
122 }
122 PatternError::UnsupportedSyntaxInFile(syntax, file_path, line) => {
123 PatternError::UnsupportedSyntaxInFile(syntax, file_path, line) => {
123 write!(
124 write!(
124 f,
125 f,
125 "{}:{}: unsupported syntax {}",
126 "{}:{}: unsupported syntax {}",
126 file_path, line, syntax
127 file_path, line, syntax
127 )
128 )
128 }
129 }
129 PatternError::TooLong(size) => {
130 PatternError::TooLong(size) => {
130 write!(f, "matcher pattern is too long ({} bytes)", size)
131 write!(f, "matcher pattern is too long ({} bytes)", size)
131 }
132 }
132 PatternError::IO(error) => error.fmt(f),
133 PatternError::IO(error) => error.fmt(f),
133 PatternError::Path(error) => error.fmt(f),
134 PatternError::Path(error) => error.fmt(f),
134 PatternError::NonRegexPattern(pattern) => {
135 PatternError::NonRegexPattern(pattern) => {
135 write!(f, "'{:?}' cannot be turned into a regex", pattern)
136 write!(f, "'{:?}' cannot be turned into a regex", pattern)
136 }
137 }
137 }
138 }
138 }
139 }
139 }
140 }
@@ -1,636 +1,650 b''
1 // status.rs
1 // status.rs
2 //
2 //
3 // Copyright 2020, Georges Racinet <georges.racinets@octobus.net>
3 // Copyright 2020, Georges Racinet <georges.racinets@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 use crate::error::CommandError;
8 use crate::error::CommandError;
9 use crate::ui::Ui;
9 use crate::ui::Ui;
10 use crate::utils::path_utils::RelativizePaths;
10 use crate::utils::path_utils::RelativizePaths;
11 use clap::Arg;
11 use clap::Arg;
12 use format_bytes::format_bytes;
12 use format_bytes::format_bytes;
13 use hg::config::Config;
13 use hg::config::Config;
14 use hg::dirstate::has_exec_bit;
14 use hg::dirstate::has_exec_bit;
15 use hg::dirstate::status::StatusPath;
15 use hg::dirstate::status::StatusPath;
16 use hg::dirstate::TruncatedTimestamp;
16 use hg::dirstate::TruncatedTimestamp;
17 use hg::errors::{HgError, IoResultExt};
17 use hg::errors::{HgError, IoResultExt};
18 use hg::lock::LockError;
18 use hg::lock::LockError;
19 use hg::manifest::Manifest;
19 use hg::manifest::Manifest;
20 use hg::matchers::{AlwaysMatcher, IntersectionMatcher};
20 use hg::matchers::{AlwaysMatcher, IntersectionMatcher};
21 use hg::repo::Repo;
21 use hg::repo::Repo;
22 use hg::utils::files::get_bytes_from_os_string;
22 use hg::utils::files::get_bytes_from_os_string;
23 use hg::utils::files::get_bytes_from_path;
23 use hg::utils::files::get_bytes_from_path;
24 use hg::utils::files::get_path_from_bytes;
24 use hg::utils::files::get_path_from_bytes;
25 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath};
25 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath};
26 use hg::DirstateStatus;
26 use hg::DirstateStatus;
27 use hg::PatternFileWarning;
27 use hg::PatternFileWarning;
28 use hg::StatusError;
28 use hg::StatusError;
29 use hg::StatusOptions;
29 use hg::StatusOptions;
30 use hg::{self, narrow, sparse};
30 use hg::{self, narrow, sparse};
31 use log::info;
31 use log::info;
32 use rayon::prelude::*;
32 use rayon::prelude::*;
33 use std::io;
33 use std::io;
34 use std::path::PathBuf;
34 use std::path::PathBuf;
35
35
36 pub const HELP_TEXT: &str = "
36 pub const HELP_TEXT: &str = "
37 Show changed files in the working directory
37 Show changed files in the working directory
38
38
39 This is a pure Rust version of `hg status`.
39 This is a pure Rust version of `hg status`.
40
40
41 Some options might be missing, check the list below.
41 Some options might be missing, check the list below.
42 ";
42 ";
43
43
44 pub fn args() -> clap::Command {
44 pub fn args() -> clap::Command {
45 clap::command!("status")
45 clap::command!("status")
46 .alias("st")
46 .alias("st")
47 .about(HELP_TEXT)
47 .about(HELP_TEXT)
48 .arg(
48 .arg(
49 Arg::new("all")
49 Arg::new("all")
50 .help("show status of all files")
50 .help("show status of all files")
51 .short('A')
51 .short('A')
52 .action(clap::ArgAction::SetTrue)
52 .action(clap::ArgAction::SetTrue)
53 .long("all"),
53 .long("all"),
54 )
54 )
55 .arg(
55 .arg(
56 Arg::new("modified")
56 Arg::new("modified")
57 .help("show only modified files")
57 .help("show only modified files")
58 .short('m')
58 .short('m')
59 .action(clap::ArgAction::SetTrue)
59 .action(clap::ArgAction::SetTrue)
60 .long("modified"),
60 .long("modified"),
61 )
61 )
62 .arg(
62 .arg(
63 Arg::new("added")
63 Arg::new("added")
64 .help("show only added files")
64 .help("show only added files")
65 .short('a')
65 .short('a')
66 .action(clap::ArgAction::SetTrue)
66 .action(clap::ArgAction::SetTrue)
67 .long("added"),
67 .long("added"),
68 )
68 )
69 .arg(
69 .arg(
70 Arg::new("removed")
70 Arg::new("removed")
71 .help("show only removed files")
71 .help("show only removed files")
72 .short('r')
72 .short('r')
73 .action(clap::ArgAction::SetTrue)
73 .action(clap::ArgAction::SetTrue)
74 .long("removed"),
74 .long("removed"),
75 )
75 )
76 .arg(
76 .arg(
77 Arg::new("clean")
77 Arg::new("clean")
78 .help("show only clean files")
78 .help("show only clean files")
79 .short('c')
79 .short('c')
80 .action(clap::ArgAction::SetTrue)
80 .action(clap::ArgAction::SetTrue)
81 .long("clean"),
81 .long("clean"),
82 )
82 )
83 .arg(
83 .arg(
84 Arg::new("deleted")
84 Arg::new("deleted")
85 .help("show only deleted files")
85 .help("show only deleted files")
86 .short('d')
86 .short('d')
87 .action(clap::ArgAction::SetTrue)
87 .action(clap::ArgAction::SetTrue)
88 .long("deleted"),
88 .long("deleted"),
89 )
89 )
90 .arg(
90 .arg(
91 Arg::new("unknown")
91 Arg::new("unknown")
92 .help("show only unknown (not tracked) files")
92 .help("show only unknown (not tracked) files")
93 .short('u')
93 .short('u')
94 .action(clap::ArgAction::SetTrue)
94 .action(clap::ArgAction::SetTrue)
95 .long("unknown"),
95 .long("unknown"),
96 )
96 )
97 .arg(
97 .arg(
98 Arg::new("ignored")
98 Arg::new("ignored")
99 .help("show only ignored files")
99 .help("show only ignored files")
100 .short('i')
100 .short('i')
101 .action(clap::ArgAction::SetTrue)
101 .action(clap::ArgAction::SetTrue)
102 .long("ignored"),
102 .long("ignored"),
103 )
103 )
104 .arg(
104 .arg(
105 Arg::new("copies")
105 Arg::new("copies")
106 .help("show source of copied files (DEFAULT: ui.statuscopies)")
106 .help("show source of copied files (DEFAULT: ui.statuscopies)")
107 .short('C')
107 .short('C')
108 .action(clap::ArgAction::SetTrue)
108 .action(clap::ArgAction::SetTrue)
109 .long("copies"),
109 .long("copies"),
110 )
110 )
111 .arg(
111 .arg(
112 Arg::new("no-status")
112 Arg::new("no-status")
113 .help("hide status prefix")
113 .help("hide status prefix")
114 .short('n')
114 .short('n')
115 .action(clap::ArgAction::SetTrue)
115 .action(clap::ArgAction::SetTrue)
116 .long("no-status"),
116 .long("no-status"),
117 )
117 )
118 .arg(
118 .arg(
119 Arg::new("verbose")
119 Arg::new("verbose")
120 .help("enable additional output")
120 .help("enable additional output")
121 .short('v')
121 .short('v')
122 .action(clap::ArgAction::SetTrue)
122 .action(clap::ArgAction::SetTrue)
123 .long("verbose"),
123 .long("verbose"),
124 )
124 )
125 }
125 }
126
126
127 /// Pure data type allowing the caller to specify file states to display
127 /// Pure data type allowing the caller to specify file states to display
128 #[derive(Copy, Clone, Debug)]
128 #[derive(Copy, Clone, Debug)]
129 pub struct DisplayStates {
129 pub struct DisplayStates {
130 pub modified: bool,
130 pub modified: bool,
131 pub added: bool,
131 pub added: bool,
132 pub removed: bool,
132 pub removed: bool,
133 pub clean: bool,
133 pub clean: bool,
134 pub deleted: bool,
134 pub deleted: bool,
135 pub unknown: bool,
135 pub unknown: bool,
136 pub ignored: bool,
136 pub ignored: bool,
137 }
137 }
138
138
139 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
139 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
140 modified: true,
140 modified: true,
141 added: true,
141 added: true,
142 removed: true,
142 removed: true,
143 clean: false,
143 clean: false,
144 deleted: true,
144 deleted: true,
145 unknown: true,
145 unknown: true,
146 ignored: false,
146 ignored: false,
147 };
147 };
148
148
149 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
149 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
150 modified: true,
150 modified: true,
151 added: true,
151 added: true,
152 removed: true,
152 removed: true,
153 clean: true,
153 clean: true,
154 deleted: true,
154 deleted: true,
155 unknown: true,
155 unknown: true,
156 ignored: true,
156 ignored: true,
157 };
157 };
158
158
159 impl DisplayStates {
159 impl DisplayStates {
160 pub fn is_empty(&self) -> bool {
160 pub fn is_empty(&self) -> bool {
161 !(self.modified
161 !(self.modified
162 || self.added
162 || self.added
163 || self.removed
163 || self.removed
164 || self.clean
164 || self.clean
165 || self.deleted
165 || self.deleted
166 || self.unknown
166 || self.unknown
167 || self.ignored)
167 || self.ignored)
168 }
168 }
169 }
169 }
170
170
171 fn has_unfinished_merge(repo: &Repo) -> Result<bool, CommandError> {
171 fn has_unfinished_merge(repo: &Repo) -> Result<bool, CommandError> {
172 return Ok(repo.dirstate_parents()?.is_merge());
172 return Ok(repo.dirstate_parents()?.is_merge());
173 }
173 }
174
174
175 fn has_unfinished_state(repo: &Repo) -> Result<bool, CommandError> {
175 fn has_unfinished_state(repo: &Repo) -> Result<bool, CommandError> {
176 // These are all the known values for the [fname] argument of
176 // These are all the known values for the [fname] argument of
177 // [addunfinished] function in [state.py]
177 // [addunfinished] function in [state.py]
178 let known_state_files: &[&str] = &[
178 let known_state_files: &[&str] = &[
179 "bisect.state",
179 "bisect.state",
180 "graftstate",
180 "graftstate",
181 "histedit-state",
181 "histedit-state",
182 "rebasestate",
182 "rebasestate",
183 "shelvedstate",
183 "shelvedstate",
184 "transplant/journal",
184 "transplant/journal",
185 "updatestate",
185 "updatestate",
186 ];
186 ];
187 if has_unfinished_merge(repo)? {
187 if has_unfinished_merge(repo)? {
188 return Ok(true);
188 return Ok(true);
189 };
189 };
190 for f in known_state_files {
190 for f in known_state_files {
191 if repo.hg_vfs().join(f).exists() {
191 if repo.hg_vfs().join(f).exists() {
192 return Ok(true);
192 return Ok(true);
193 }
193 }
194 }
194 }
195 return Ok(false);
195 return Ok(false);
196 }
196 }
197
197
198 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
198 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
199 // TODO: lift these limitations
199 // TODO: lift these limitations
200 if invocation
200 if invocation
201 .config
201 .config
202 .get(b"commands", b"status.terse")
202 .get(b"commands", b"status.terse")
203 .is_some()
203 .is_some()
204 {
204 {
205 return Err(CommandError::unsupported(
205 return Err(CommandError::unsupported(
206 "status.terse is not yet supported with rhg status",
206 "status.terse is not yet supported with rhg status",
207 ));
207 ));
208 }
208 }
209
209
210 let ui = invocation.ui;
210 let ui = invocation.ui;
211 let config = invocation.config;
211 let config = invocation.config;
212 let args = invocation.subcommand_args;
212 let args = invocation.subcommand_args;
213
213
214 // TODO add `!args.get_flag("print0") &&` when we support `print0`
214 // TODO add `!args.get_flag("print0") &&` when we support `print0`
215 let verbose = args.get_flag("verbose")
215 let verbose = args.get_flag("verbose")
216 || config.get_bool(b"ui", b"verbose")?
216 || config.get_bool(b"ui", b"verbose")?
217 || config.get_bool(b"commands", b"status.verbose")?;
217 || config.get_bool(b"commands", b"status.verbose")?;
218
218
219 let all = args.get_flag("all");
219 let all = args.get_flag("all");
220 let display_states = if all {
220 let display_states = if all {
221 // TODO when implementing `--quiet`: it excludes clean files
221 // TODO when implementing `--quiet`: it excludes clean files
222 // from `--all`
222 // from `--all`
223 ALL_DISPLAY_STATES
223 ALL_DISPLAY_STATES
224 } else {
224 } else {
225 let requested = DisplayStates {
225 let requested = DisplayStates {
226 modified: args.get_flag("modified"),
226 modified: args.get_flag("modified"),
227 added: args.get_flag("added"),
227 added: args.get_flag("added"),
228 removed: args.get_flag("removed"),
228 removed: args.get_flag("removed"),
229 clean: args.get_flag("clean"),
229 clean: args.get_flag("clean"),
230 deleted: args.get_flag("deleted"),
230 deleted: args.get_flag("deleted"),
231 unknown: args.get_flag("unknown"),
231 unknown: args.get_flag("unknown"),
232 ignored: args.get_flag("ignored"),
232 ignored: args.get_flag("ignored"),
233 };
233 };
234 if requested.is_empty() {
234 if requested.is_empty() {
235 DEFAULT_DISPLAY_STATES
235 DEFAULT_DISPLAY_STATES
236 } else {
236 } else {
237 requested
237 requested
238 }
238 }
239 };
239 };
240 let no_status = args.get_flag("no-status");
240 let no_status = args.get_flag("no-status");
241 let list_copies = all
241 let list_copies = all
242 || args.get_flag("copies")
242 || args.get_flag("copies")
243 || config.get_bool(b"ui", b"statuscopies")?;
243 || config.get_bool(b"ui", b"statuscopies")?;
244
244
245 let repo = invocation.repo?;
245 let repo = invocation.repo?;
246
246
247 if verbose {
247 if verbose {
248 if has_unfinished_state(repo)? {
248 if has_unfinished_state(repo)? {
249 return Err(CommandError::unsupported(
249 return Err(CommandError::unsupported(
250 "verbose status output is not supported by rhg (and is needed because we're in an unfinished operation)",
250 "verbose status output is not supported by rhg (and is needed because we're in an unfinished operation)",
251 ));
251 ));
252 };
252 };
253 }
253 }
254
254
255 let mut dmap = repo.dirstate_map_mut()?;
255 let mut dmap = repo.dirstate_map_mut()?;
256
256
257 let check_exec = hg::checkexec::check_exec(repo.working_directory_path());
258
257 let options = StatusOptions {
259 let options = StatusOptions {
258 // we're currently supporting file systems with exec flags only
260 check_exec,
259 // anyway
260 check_exec: true,
261 list_clean: display_states.clean,
261 list_clean: display_states.clean,
262 list_unknown: display_states.unknown,
262 list_unknown: display_states.unknown,
263 list_ignored: display_states.ignored,
263 list_ignored: display_states.ignored,
264 list_copies,
264 list_copies,
265 collect_traversed_dirs: false,
265 collect_traversed_dirs: false,
266 };
266 };
267
267
268 type StatusResult<'a> =
268 type StatusResult<'a> =
269 Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>;
269 Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>;
270
270
271 let after_status = |res: StatusResult| -> Result<_, CommandError> {
271 let after_status = |res: StatusResult| -> Result<_, CommandError> {
272 let (mut ds_status, pattern_warnings) = res?;
272 let (mut ds_status, pattern_warnings) = res?;
273 for warning in pattern_warnings {
273 for warning in pattern_warnings {
274 ui.write_stderr(&print_pattern_file_warning(&warning, &repo))?;
274 ui.write_stderr(&print_pattern_file_warning(&warning, &repo))?;
275 }
275 }
276
276
277 for (path, error) in ds_status.bad {
277 for (path, error) in ds_status.bad {
278 let error = match error {
278 let error = match error {
279 hg::BadMatch::OsError(code) => {
279 hg::BadMatch::OsError(code) => {
280 std::io::Error::from_raw_os_error(code).to_string()
280 std::io::Error::from_raw_os_error(code).to_string()
281 }
281 }
282 hg::BadMatch::BadType(ty) => {
282 hg::BadMatch::BadType(ty) => {
283 format!("unsupported file type (type is {})", ty)
283 format!("unsupported file type (type is {})", ty)
284 }
284 }
285 };
285 };
286 ui.write_stderr(&format_bytes!(
286 ui.write_stderr(&format_bytes!(
287 b"{}: {}\n",
287 b"{}: {}\n",
288 path.as_bytes(),
288 path.as_bytes(),
289 error.as_bytes()
289 error.as_bytes()
290 ))?
290 ))?
291 }
291 }
292 if !ds_status.unsure.is_empty() {
292 if !ds_status.unsure.is_empty() {
293 info!(
293 info!(
294 "Files to be rechecked by retrieval from filelog: {:?}",
294 "Files to be rechecked by retrieval from filelog: {:?}",
295 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
295 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
296 );
296 );
297 }
297 }
298 let mut fixup = Vec::new();
298 let mut fixup = Vec::new();
299 if !ds_status.unsure.is_empty()
299 if !ds_status.unsure.is_empty()
300 && (display_states.modified || display_states.clean)
300 && (display_states.modified || display_states.clean)
301 {
301 {
302 let p1 = repo.dirstate_parents()?.p1;
302 let p1 = repo.dirstate_parents()?.p1;
303 let manifest = repo.manifest_for_node(p1).map_err(|e| {
303 let manifest = repo.manifest_for_node(p1).map_err(|e| {
304 CommandError::from((e, &*format!("{:x}", p1.short())))
304 CommandError::from((e, &*format!("{:x}", p1.short())))
305 })?;
305 })?;
306 let working_directory_vfs = repo.working_directory_vfs();
306 let working_directory_vfs = repo.working_directory_vfs();
307 let store_vfs = repo.store_vfs();
307 let store_vfs = repo.store_vfs();
308 let res: Vec<_> = ds_status
308 let res: Vec<_> = ds_status
309 .unsure
309 .unsure
310 .into_par_iter()
310 .into_par_iter()
311 .map(|to_check| {
311 .map(|to_check| {
312 unsure_is_modified(
312 unsure_is_modified(
313 working_directory_vfs,
313 working_directory_vfs,
314 store_vfs,
314 store_vfs,
315 check_exec,
315 &manifest,
316 &manifest,
316 &to_check.path,
317 &to_check.path,
317 )
318 )
318 .map(|modified| (to_check, modified))
319 .map(|modified| (to_check, modified))
319 })
320 })
320 .collect::<Result<_, _>>()?;
321 .collect::<Result<_, _>>()?;
321 for (status_path, is_modified) in res.into_iter() {
322 for (status_path, is_modified) in res.into_iter() {
322 if is_modified {
323 if is_modified {
323 if display_states.modified {
324 if display_states.modified {
324 ds_status.modified.push(status_path);
325 ds_status.modified.push(status_path);
325 }
326 }
326 } else {
327 } else {
327 if display_states.clean {
328 if display_states.clean {
328 ds_status.clean.push(status_path.clone());
329 ds_status.clean.push(status_path.clone());
329 }
330 }
330 fixup.push(status_path.path.into_owned())
331 fixup.push(status_path.path.into_owned())
331 }
332 }
332 }
333 }
333 }
334 }
334 let relative_paths = config
335 let relative_paths = config
335 .get_option(b"commands", b"status.relative")?
336 .get_option(b"commands", b"status.relative")?
336 .unwrap_or(config.get_bool(b"ui", b"relative-paths")?);
337 .unwrap_or(config.get_bool(b"ui", b"relative-paths")?);
337 let output = DisplayStatusPaths {
338 let output = DisplayStatusPaths {
338 ui,
339 ui,
339 no_status,
340 no_status,
340 relativize: if relative_paths {
341 relativize: if relative_paths {
341 Some(RelativizePaths::new(repo)?)
342 Some(RelativizePaths::new(repo)?)
342 } else {
343 } else {
343 None
344 None
344 },
345 },
345 };
346 };
346 if display_states.modified {
347 if display_states.modified {
347 output.display(b"M ", "status.modified", ds_status.modified)?;
348 output.display(b"M ", "status.modified", ds_status.modified)?;
348 }
349 }
349 if display_states.added {
350 if display_states.added {
350 output.display(b"A ", "status.added", ds_status.added)?;
351 output.display(b"A ", "status.added", ds_status.added)?;
351 }
352 }
352 if display_states.removed {
353 if display_states.removed {
353 output.display(b"R ", "status.removed", ds_status.removed)?;
354 output.display(b"R ", "status.removed", ds_status.removed)?;
354 }
355 }
355 if display_states.deleted {
356 if display_states.deleted {
356 output.display(b"! ", "status.deleted", ds_status.deleted)?;
357 output.display(b"! ", "status.deleted", ds_status.deleted)?;
357 }
358 }
358 if display_states.unknown {
359 if display_states.unknown {
359 output.display(b"? ", "status.unknown", ds_status.unknown)?;
360 output.display(b"? ", "status.unknown", ds_status.unknown)?;
360 }
361 }
361 if display_states.ignored {
362 if display_states.ignored {
362 output.display(b"I ", "status.ignored", ds_status.ignored)?;
363 output.display(b"I ", "status.ignored", ds_status.ignored)?;
363 }
364 }
364 if display_states.clean {
365 if display_states.clean {
365 output.display(b"C ", "status.clean", ds_status.clean)?;
366 output.display(b"C ", "status.clean", ds_status.clean)?;
366 }
367 }
367
368
368 let dirstate_write_needed = ds_status.dirty;
369 let dirstate_write_needed = ds_status.dirty;
369 let filesystem_time_at_status_start =
370 let filesystem_time_at_status_start =
370 ds_status.filesystem_time_at_status_start;
371 ds_status.filesystem_time_at_status_start;
371
372
372 Ok((
373 Ok((
373 fixup,
374 fixup,
374 dirstate_write_needed,
375 dirstate_write_needed,
375 filesystem_time_at_status_start,
376 filesystem_time_at_status_start,
376 ))
377 ))
377 };
378 };
378 let (narrow_matcher, narrow_warnings) = narrow::matcher(repo)?;
379 let (narrow_matcher, narrow_warnings) = narrow::matcher(repo)?;
379 let (sparse_matcher, sparse_warnings) = sparse::matcher(repo)?;
380 let (sparse_matcher, sparse_warnings) = sparse::matcher(repo)?;
380 let matcher = match (repo.has_narrow(), repo.has_sparse()) {
381 let matcher = match (repo.has_narrow(), repo.has_sparse()) {
381 (true, true) => {
382 (true, true) => {
382 Box::new(IntersectionMatcher::new(narrow_matcher, sparse_matcher))
383 Box::new(IntersectionMatcher::new(narrow_matcher, sparse_matcher))
383 }
384 }
384 (true, false) => narrow_matcher,
385 (true, false) => narrow_matcher,
385 (false, true) => sparse_matcher,
386 (false, true) => sparse_matcher,
386 (false, false) => Box::new(AlwaysMatcher),
387 (false, false) => Box::new(AlwaysMatcher),
387 };
388 };
388
389
389 for warning in narrow_warnings.into_iter().chain(sparse_warnings) {
390 for warning in narrow_warnings.into_iter().chain(sparse_warnings) {
390 match &warning {
391 match &warning {
391 sparse::SparseWarning::RootWarning { context, line } => {
392 sparse::SparseWarning::RootWarning { context, line } => {
392 let msg = format_bytes!(
393 let msg = format_bytes!(
393 b"warning: {} profile cannot use paths \"
394 b"warning: {} profile cannot use paths \"
394 starting with /, ignoring {}\n",
395 starting with /, ignoring {}\n",
395 context,
396 context,
396 line
397 line
397 );
398 );
398 ui.write_stderr(&msg)?;
399 ui.write_stderr(&msg)?;
399 }
400 }
400 sparse::SparseWarning::ProfileNotFound { profile, rev } => {
401 sparse::SparseWarning::ProfileNotFound { profile, rev } => {
401 let msg = format_bytes!(
402 let msg = format_bytes!(
402 b"warning: sparse profile '{}' not found \"
403 b"warning: sparse profile '{}' not found \"
403 in rev {} - ignoring it\n",
404 in rev {} - ignoring it\n",
404 profile,
405 profile,
405 rev
406 rev
406 );
407 );
407 ui.write_stderr(&msg)?;
408 ui.write_stderr(&msg)?;
408 }
409 }
409 sparse::SparseWarning::Pattern(e) => {
410 sparse::SparseWarning::Pattern(e) => {
410 ui.write_stderr(&print_pattern_file_warning(e, &repo))?;
411 ui.write_stderr(&print_pattern_file_warning(e, &repo))?;
411 }
412 }
412 }
413 }
413 }
414 }
414 let (fixup, mut dirstate_write_needed, filesystem_time_at_status_start) =
415 let (fixup, mut dirstate_write_needed, filesystem_time_at_status_start) =
415 dmap.with_status(
416 dmap.with_status(
416 matcher.as_ref(),
417 matcher.as_ref(),
417 repo.working_directory_path().to_owned(),
418 repo.working_directory_path().to_owned(),
418 ignore_files(repo, config),
419 ignore_files(repo, config),
419 options,
420 options,
420 after_status,
421 after_status,
421 )?;
422 )?;
422
423
423 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
424 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
424 && !dirstate_write_needed
425 && !dirstate_write_needed
425 {
426 {
426 // Nothing to update
427 // Nothing to update
427 return Ok(());
428 return Ok(());
428 }
429 }
429
430
430 // Update the dirstate on disk if we can
431 // Update the dirstate on disk if we can
431 let with_lock_result =
432 let with_lock_result =
432 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
433 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
433 if let Some(mtime_boundary) = filesystem_time_at_status_start {
434 if let Some(mtime_boundary) = filesystem_time_at_status_start {
434 for hg_path in fixup {
435 for hg_path in fixup {
435 use std::os::unix::fs::MetadataExt;
436 use std::os::unix::fs::MetadataExt;
436 let fs_path = hg_path_to_path_buf(&hg_path)
437 let fs_path = hg_path_to_path_buf(&hg_path)
437 .expect("HgPath conversion");
438 .expect("HgPath conversion");
438 // Specifically do not reuse `fs_metadata` from
439 // Specifically do not reuse `fs_metadata` from
439 // `unsure_is_clean` which was needed before reading
440 // `unsure_is_clean` which was needed before reading
440 // contents. Here we access metadata again after reading
441 // contents. Here we access metadata again after reading
441 // content, in case it changed in the meantime.
442 // content, in case it changed in the meantime.
442 let fs_metadata = repo
443 let fs_metadata = repo
443 .working_directory_vfs()
444 .working_directory_vfs()
444 .symlink_metadata(&fs_path)?;
445 .symlink_metadata(&fs_path)?;
445 if let Some(mtime) =
446 if let Some(mtime) =
446 TruncatedTimestamp::for_reliable_mtime_of(
447 TruncatedTimestamp::for_reliable_mtime_of(
447 &fs_metadata,
448 &fs_metadata,
448 &mtime_boundary,
449 &mtime_boundary,
449 )
450 )
450 .when_reading_file(&fs_path)?
451 .when_reading_file(&fs_path)?
451 {
452 {
452 let mode = fs_metadata.mode();
453 let mode = fs_metadata.mode();
453 let size = fs_metadata.len();
454 let size = fs_metadata.len();
454 dmap.set_clean(&hg_path, mode, size as u32, mtime)?;
455 dmap.set_clean(&hg_path, mode, size as u32, mtime)?;
455 dirstate_write_needed = true
456 dirstate_write_needed = true
456 }
457 }
457 }
458 }
458 }
459 }
459 drop(dmap); // Avoid "already mutably borrowed" RefCell panics
460 drop(dmap); // Avoid "already mutably borrowed" RefCell panics
460 if dirstate_write_needed {
461 if dirstate_write_needed {
461 repo.write_dirstate()?
462 repo.write_dirstate()?
462 }
463 }
463 Ok(())
464 Ok(())
464 });
465 });
465 match with_lock_result {
466 match with_lock_result {
466 Ok(closure_result) => closure_result?,
467 Ok(closure_result) => closure_result?,
467 Err(LockError::AlreadyHeld) => {
468 Err(LockError::AlreadyHeld) => {
468 // Not updating the dirstate is not ideal but not critical:
469 // Not updating the dirstate is not ideal but not critical:
469 // don’t keep our caller waiting until some other Mercurial
470 // don’t keep our caller waiting until some other Mercurial
470 // process releases the lock.
471 // process releases the lock.
471 }
472 }
472 Err(LockError::Other(HgError::IoError { error, .. }))
473 Err(LockError::Other(HgError::IoError { error, .. }))
473 if error.kind() == io::ErrorKind::PermissionDenied =>
474 if error.kind() == io::ErrorKind::PermissionDenied =>
474 {
475 {
475 // `hg status` on a read-only repository is fine
476 // `hg status` on a read-only repository is fine
476 }
477 }
477 Err(LockError::Other(error)) => {
478 Err(LockError::Other(error)) => {
478 // Report other I/O errors
479 // Report other I/O errors
479 Err(error)?
480 Err(error)?
480 }
481 }
481 }
482 }
482 Ok(())
483 Ok(())
483 }
484 }
484
485
485 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
486 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
486 let mut ignore_files = Vec::new();
487 let mut ignore_files = Vec::new();
487 let repo_ignore = repo.working_directory_vfs().join(".hgignore");
488 let repo_ignore = repo.working_directory_vfs().join(".hgignore");
488 if repo_ignore.exists() {
489 if repo_ignore.exists() {
489 ignore_files.push(repo_ignore)
490 ignore_files.push(repo_ignore)
490 }
491 }
491 for (key, value) in config.iter_section(b"ui") {
492 for (key, value) in config.iter_section(b"ui") {
492 if key == b"ignore" || key.starts_with(b"ignore.") {
493 if key == b"ignore" || key.starts_with(b"ignore.") {
493 let path = get_path_from_bytes(value);
494 let path = get_path_from_bytes(value);
494 // TODO:Β expand "~/" and environment variable here, like Python
495 // TODO:Β expand "~/" and environment variable here, like Python
495 // does with `os.path.expanduser` and `os.path.expandvars`
496 // does with `os.path.expanduser` and `os.path.expandvars`
496
497
497 let joined = repo.working_directory_path().join(path);
498 let joined = repo.working_directory_path().join(path);
498 ignore_files.push(joined);
499 ignore_files.push(joined);
499 }
500 }
500 }
501 }
501 ignore_files
502 ignore_files
502 }
503 }
503
504
504 struct DisplayStatusPaths<'a> {
505 struct DisplayStatusPaths<'a> {
505 ui: &'a Ui,
506 ui: &'a Ui,
506 no_status: bool,
507 no_status: bool,
507 relativize: Option<RelativizePaths>,
508 relativize: Option<RelativizePaths>,
508 }
509 }
509
510
510 impl DisplayStatusPaths<'_> {
511 impl DisplayStatusPaths<'_> {
511 // Probably more elegant to use a Deref or Borrow trait rather than
512 // Probably more elegant to use a Deref or Borrow trait rather than
512 // harcode HgPathBuf, but probably not really useful at this point
513 // harcode HgPathBuf, but probably not really useful at this point
513 fn display(
514 fn display(
514 &self,
515 &self,
515 status_prefix: &[u8],
516 status_prefix: &[u8],
516 label: &'static str,
517 label: &'static str,
517 mut paths: Vec<StatusPath<'_>>,
518 mut paths: Vec<StatusPath<'_>>,
518 ) -> Result<(), CommandError> {
519 ) -> Result<(), CommandError> {
519 paths.sort_unstable();
520 paths.sort_unstable();
520 // TODO: get the stdout lock once for the whole loop
521 // TODO: get the stdout lock once for the whole loop
521 // instead of in each write
522 // instead of in each write
522 for StatusPath { path, copy_source } in paths {
523 for StatusPath { path, copy_source } in paths {
523 let relative;
524 let relative;
524 let path = if let Some(relativize) = &self.relativize {
525 let path = if let Some(relativize) = &self.relativize {
525 relative = relativize.relativize(&path);
526 relative = relativize.relativize(&path);
526 &*relative
527 &*relative
527 } else {
528 } else {
528 path.as_bytes()
529 path.as_bytes()
529 };
530 };
530 // TODO: Add a way to use `write_bytes!` instead of `format_bytes!`
531 // TODO: Add a way to use `write_bytes!` instead of `format_bytes!`
531 // in order to stream to stdout instead of allocating an
532 // in order to stream to stdout instead of allocating an
532 // itermediate `Vec<u8>`.
533 // itermediate `Vec<u8>`.
533 if !self.no_status {
534 if !self.no_status {
534 self.ui.write_stdout_labelled(status_prefix, label)?
535 self.ui.write_stdout_labelled(status_prefix, label)?
535 }
536 }
536 self.ui
537 self.ui
537 .write_stdout_labelled(&format_bytes!(b"{}\n", path), label)?;
538 .write_stdout_labelled(&format_bytes!(b"{}\n", path), label)?;
538 if let Some(source) = copy_source {
539 if let Some(source) = copy_source {
539 let label = "status.copied";
540 let label = "status.copied";
540 self.ui.write_stdout_labelled(
541 self.ui.write_stdout_labelled(
541 &format_bytes!(b" {}\n", source.as_bytes()),
542 &format_bytes!(b" {}\n", source.as_bytes()),
542 label,
543 label,
543 )?
544 )?
544 }
545 }
545 }
546 }
546 Ok(())
547 Ok(())
547 }
548 }
548 }
549 }
549
550
550 /// Check if a file is modified by comparing actual repo store and file system.
551 /// Check if a file is modified by comparing actual repo store and file system.
551 ///
552 ///
552 /// This meant to be used for those that the dirstate cannot resolve, due
553 /// This meant to be used for those that the dirstate cannot resolve, due
553 /// to time resolution limits.
554 /// to time resolution limits.
554 fn unsure_is_modified(
555 fn unsure_is_modified(
555 working_directory_vfs: hg::vfs::Vfs,
556 working_directory_vfs: hg::vfs::Vfs,
556 store_vfs: hg::vfs::Vfs,
557 store_vfs: hg::vfs::Vfs,
558 check_exec: bool,
557 manifest: &Manifest,
559 manifest: &Manifest,
558 hg_path: &HgPath,
560 hg_path: &HgPath,
559 ) -> Result<bool, HgError> {
561 ) -> Result<bool, HgError> {
560 let vfs = working_directory_vfs;
562 let vfs = working_directory_vfs;
561 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
563 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
562 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
564 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
563 let is_symlink = fs_metadata.file_type().is_symlink();
565 let is_symlink = fs_metadata.file_type().is_symlink();
566
567 let entry = manifest
568 .find_by_path(hg_path)?
569 .expect("ambgious file not in p1");
570
564 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
571 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
565 // dirstate
572 // dirstate
566 let fs_flags = if is_symlink {
573 let fs_flags = if is_symlink {
567 Some(b'l')
574 Some(b'l')
568 } else if has_exec_bit(&fs_metadata) {
575 } else if check_exec && has_exec_bit(&fs_metadata) {
569 Some(b'x')
576 Some(b'x')
570 } else {
577 } else {
571 None
578 None
572 };
579 };
573
580
574 let entry = manifest
581 let entry_flags = if check_exec {
575 .find_by_path(hg_path)?
582 entry.flags
576 .expect("ambgious file not in p1");
583 } else {
577 if entry.flags != fs_flags {
584 if entry.flags == Some(b'x') {
585 None
586 } else {
587 entry.flags
588 }
589 };
590
591 if entry_flags != fs_flags {
578 return Ok(true);
592 return Ok(true);
579 }
593 }
580 let filelog = hg::filelog::Filelog::open_vfs(&store_vfs, hg_path)?;
594 let filelog = hg::filelog::Filelog::open_vfs(&store_vfs, hg_path)?;
581 let fs_len = fs_metadata.len();
595 let fs_len = fs_metadata.len();
582 let file_node = entry.node_id()?;
596 let file_node = entry.node_id()?;
583 let filelog_entry = filelog.entry_for_node(file_node).map_err(|_| {
597 let filelog_entry = filelog.entry_for_node(file_node).map_err(|_| {
584 HgError::corrupted(format!(
598 HgError::corrupted(format!(
585 "filelog missing node {:?} from manifest",
599 "filelog missing node {:?} from manifest",
586 file_node
600 file_node
587 ))
601 ))
588 })?;
602 })?;
589 if filelog_entry.file_data_len_not_equal_to(fs_len) {
603 if filelog_entry.file_data_len_not_equal_to(fs_len) {
590 // No need to read file contents:
604 // No need to read file contents:
591 // it cannot be equal if it has a different length.
605 // it cannot be equal if it has a different length.
592 return Ok(true);
606 return Ok(true);
593 }
607 }
594
608
595 let p1_filelog_data = filelog_entry.data()?;
609 let p1_filelog_data = filelog_entry.data()?;
596 let p1_contents = p1_filelog_data.file_data()?;
610 let p1_contents = p1_filelog_data.file_data()?;
597 if p1_contents.len() as u64 != fs_len {
611 if p1_contents.len() as u64 != fs_len {
598 // No need to read file contents:
612 // No need to read file contents:
599 // it cannot be equal if it has a different length.
613 // it cannot be equal if it has a different length.
600 return Ok(true);
614 return Ok(true);
601 }
615 }
602
616
603 let fs_contents = if is_symlink {
617 let fs_contents = if is_symlink {
604 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
618 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
605 } else {
619 } else {
606 vfs.read(fs_path)?
620 vfs.read(fs_path)?
607 };
621 };
608 Ok(p1_contents != &*fs_contents)
622 Ok(p1_contents != &*fs_contents)
609 }
623 }
610
624
611 fn print_pattern_file_warning(
625 fn print_pattern_file_warning(
612 warning: &PatternFileWarning,
626 warning: &PatternFileWarning,
613 repo: &Repo,
627 repo: &Repo,
614 ) -> Vec<u8> {
628 ) -> Vec<u8> {
615 match warning {
629 match warning {
616 PatternFileWarning::InvalidSyntax(path, syntax) => format_bytes!(
630 PatternFileWarning::InvalidSyntax(path, syntax) => format_bytes!(
617 b"{}: ignoring invalid syntax '{}'\n",
631 b"{}: ignoring invalid syntax '{}'\n",
618 get_bytes_from_path(path),
632 get_bytes_from_path(path),
619 &*syntax
633 &*syntax
620 ),
634 ),
621 PatternFileWarning::NoSuchFile(path) => {
635 PatternFileWarning::NoSuchFile(path) => {
622 let path = if let Ok(relative) =
636 let path = if let Ok(relative) =
623 path.strip_prefix(repo.working_directory_path())
637 path.strip_prefix(repo.working_directory_path())
624 {
638 {
625 relative
639 relative
626 } else {
640 } else {
627 &*path
641 &*path
628 };
642 };
629 format_bytes!(
643 format_bytes!(
630 b"skipping unreadable pattern file '{}': \
644 b"skipping unreadable pattern file '{}': \
631 No such file or directory\n",
645 No such file or directory\n",
632 get_bytes_from_path(path),
646 get_bytes_from_path(path),
633 )
647 )
634 }
648 }
635 }
649 }
636 }
650 }
General Comments 0
You need to be logged in to leave comments. Login now