##// END OF EJS Templates
rhg: Propogate manifest parse errors instead of panicking...
Simon Sapin -
r49165:10c32e1b default
parent child Browse files
Show More
@@ -1,115 +1,119 b''
1 1 // list_tracked_files.rs
2 2 //
3 3 // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 use crate::repo::Repo;
9 9 use crate::revlog::revlog::RevlogError;
10 10 use crate::revlog::Node;
11 11
12 12 use crate::utils::hg_path::HgPath;
13 13
14 use crate::errors::HgError;
14 15 use itertools::put_back;
15 16 use itertools::PutBack;
16 17 use std::cmp::Ordering;
17 18
18 19 pub struct CatOutput<'a> {
19 20 /// Whether any file in the manifest matched the paths given as CLI
20 21 /// arguments
21 22 pub found_any: bool,
22 23 /// The contents of matching files, in manifest order
23 24 pub results: Vec<(&'a HgPath, Vec<u8>)>,
24 25 /// Which of the CLI arguments did not match any manifest file
25 26 pub missing: Vec<&'a HgPath>,
26 27 /// The node ID that the given revset was resolved to
27 28 pub node: Node,
28 29 }
29 30
30 31 // Find an item in an iterator over a sorted collection.
31 fn find_item<'a, 'b, 'c, D, I: Iterator<Item = (&'a HgPath, D)>>(
32 fn find_item<'a, D, I: Iterator<Item = Result<(&'a HgPath, D), HgError>>>(
32 33 i: &mut PutBack<I>,
33 needle: &'b HgPath,
34 ) -> Option<D> {
34 needle: &HgPath,
35 ) -> Result<Option<D>, HgError> {
35 36 loop {
36 37 match i.next() {
37 None => return None,
38 Some(val) => match needle.as_bytes().cmp(val.0.as_bytes()) {
39 Ordering::Less => {
40 i.put_back(val);
41 return None;
38 None => return Ok(None),
39 Some(result) => {
40 let (path, value) = result?;
41 match needle.as_bytes().cmp(path.as_bytes()) {
42 Ordering::Less => {
43 i.put_back(Ok((path, value)));
44 return Ok(None);
45 }
46 Ordering::Greater => continue,
47 Ordering::Equal => return Ok(Some(value)),
42 48 }
43 Ordering::Greater => continue,
44 Ordering::Equal => return Some(val.1),
45 },
49 }
46 50 }
47 51 }
48 52 }
49 53
50 54 fn find_files_in_manifest<
51 55 'manifest,
52 56 'query,
53 57 Data,
54 Manifest: Iterator<Item = (&'manifest HgPath, Data)>,
58 Manifest: Iterator<Item = Result<(&'manifest HgPath, Data), HgError>>,
55 59 Query: Iterator<Item = &'query HgPath>,
56 60 >(
57 61 manifest: Manifest,
58 62 query: Query,
59 ) -> (Vec<(&'query HgPath, Data)>, Vec<&'query HgPath>) {
63 ) -> Result<(Vec<(&'query HgPath, Data)>, Vec<&'query HgPath>), HgError> {
60 64 let mut manifest = put_back(manifest);
61 65 let mut res = vec![];
62 66 let mut missing = vec![];
63 67
64 68 for file in query {
65 match find_item(&mut manifest, file) {
69 match find_item(&mut manifest, file)? {
66 70 None => missing.push(file),
67 71 Some(item) => res.push((file, item)),
68 72 }
69 73 }
70 return (res, missing);
74 return Ok((res, missing));
71 75 }
72 76
73 77 /// Output the given revision of files
74 78 ///
75 79 /// * `root`: Repository root
76 80 /// * `rev`: The revision to cat the files from.
77 81 /// * `files`: The files to output.
78 82 pub fn cat<'a>(
79 83 repo: &Repo,
80 84 revset: &str,
81 85 mut files: Vec<&'a HgPath>,
82 86 ) -> Result<CatOutput<'a>, RevlogError> {
83 87 let rev = crate::revset::resolve_single(revset, repo)?;
84 88 let manifest = repo.manifest_for_rev(rev)?;
85 89 let node = *repo
86 90 .changelog()?
87 91 .node_from_rev(rev)
88 92 .expect("should succeed when repo.manifest did");
89 93 let mut results: Vec<(&'a HgPath, Vec<u8>)> = vec![];
90 94 let mut found_any = false;
91 95
92 96 files.sort_unstable();
93 97
94 98 let (found, missing) = find_files_in_manifest(
95 99 manifest.files_with_nodes(),
96 100 files.into_iter().map(|f| f.as_ref()),
97 );
101 )?;
98 102
99 103 for (file_path, node_bytes) in found {
100 104 found_any = true;
101 105 let file_log = repo.filelog(file_path)?;
102 106 let file_node = Node::from_hex_for_repo(node_bytes)?;
103 107 results.push((
104 108 file_path,
105 109 file_log.data_for_node(file_node)?.into_data()?,
106 110 ));
107 111 }
108 112
109 113 Ok(CatOutput {
110 114 found_any,
111 115 results,
112 116 missing,
113 117 node,
114 118 })
115 119 }
@@ -1,82 +1,82 b''
1 1 // list_tracked_files.rs
2 2 //
3 3 // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 use crate::dirstate::parsers::parse_dirstate_entries;
9 9 use crate::dirstate_tree::on_disk::{for_each_tracked_path, read_docket};
10 10 use crate::errors::HgError;
11 11 use crate::repo::Repo;
12 12 use crate::revlog::manifest::Manifest;
13 13 use crate::revlog::revlog::RevlogError;
14 14 use crate::utils::hg_path::HgPath;
15 15 use crate::DirstateError;
16 16 use rayon::prelude::*;
17 17
18 18 /// List files under Mercurial control in the working directory
19 19 /// by reading the dirstate
20 20 pub struct Dirstate {
21 21 /// The `dirstate` content.
22 22 content: Vec<u8>,
23 23 v2_metadata: Option<Vec<u8>>,
24 24 }
25 25
26 26 impl Dirstate {
27 27 pub fn new(repo: &Repo) -> Result<Self, HgError> {
28 28 let mut content = repo.hg_vfs().read("dirstate")?;
29 29 let v2_metadata = if repo.has_dirstate_v2() {
30 30 let docket = read_docket(&content)?;
31 31 let meta = docket.tree_metadata().to_vec();
32 32 content = repo.hg_vfs().read(docket.data_filename())?;
33 33 Some(meta)
34 34 } else {
35 35 None
36 36 };
37 37 Ok(Self {
38 38 content,
39 39 v2_metadata,
40 40 })
41 41 }
42 42
43 43 pub fn tracked_files(&self) -> Result<Vec<&HgPath>, DirstateError> {
44 44 let mut files = Vec::new();
45 45 if !self.content.is_empty() {
46 46 if let Some(meta) = &self.v2_metadata {
47 47 for_each_tracked_path(&self.content, meta, |path| {
48 48 files.push(path)
49 49 })?
50 50 } else {
51 51 let _parents = parse_dirstate_entries(
52 52 &self.content,
53 53 |path, entry, _copy_source| {
54 54 if entry.state().is_tracked() {
55 55 files.push(path)
56 56 }
57 57 Ok(())
58 58 },
59 59 )?;
60 60 }
61 61 }
62 62 files.par_sort_unstable();
63 63 Ok(files)
64 64 }
65 65 }
66 66
67 67 /// List files under Mercurial control at a given revision.
68 68 pub fn list_rev_tracked_files(
69 69 repo: &Repo,
70 70 revset: &str,
71 71 ) -> Result<FilesForRev, RevlogError> {
72 72 let rev = crate::revset::resolve_single(revset, repo)?;
73 73 Ok(FilesForRev(repo.manifest_for_rev(rev)?))
74 74 }
75 75
76 76 pub struct FilesForRev(Manifest);
77 77
78 78 impl FilesForRev {
79 pub fn iter(&self) -> impl Iterator<Item = &HgPath> {
79 pub fn iter(&self) -> impl Iterator<Item = Result<&HgPath, HgError>> {
80 80 self.0.files()
81 81 }
82 82 }
@@ -1,101 +1,104 b''
1 1 use crate::errors::HgError;
2 2 use crate::repo::Repo;
3 3 use crate::revlog::revlog::{Revlog, RevlogError};
4 4 use crate::revlog::Revision;
5 5 use crate::revlog::{Node, NodePrefix};
6 6 use crate::utils::hg_path::HgPath;
7 7
8 8 /// A specialized `Revlog` to work with `manifest` data format.
9 9 pub struct Manifestlog {
10 10 /// The generic `revlog` format.
11 11 revlog: Revlog,
12 12 }
13 13
14 14 impl Manifestlog {
15 15 /// Open the `manifest` of a repository given by its root.
16 16 pub fn open(repo: &Repo) -> Result<Self, HgError> {
17 17 let revlog = Revlog::open(repo, "00manifest.i", None)?;
18 18 Ok(Self { revlog })
19 19 }
20 20
21 21 /// Return the `Manifest` for the given node ID.
22 22 ///
23 23 /// Note: this is a node ID in the manifestlog, typically found through
24 24 /// `ChangelogEntry::manifest_node`. It is *not* the node ID of any
25 25 /// changeset.
26 26 ///
27 27 /// See also `Repo::manifest_for_node`
28 28 pub fn data_for_node(
29 29 &self,
30 30 node: NodePrefix,
31 31 ) -> Result<Manifest, RevlogError> {
32 32 let rev = self.revlog.rev_from_node(node)?;
33 33 self.data_for_rev(rev)
34 34 }
35 35
36 36 /// Return the `Manifest` of a given revision number.
37 37 ///
38 38 /// Note: this is a revision number in the manifestlog, *not* of any
39 39 /// changeset.
40 40 ///
41 41 /// See also `Repo::manifest_for_rev`
42 42 pub fn data_for_rev(
43 43 &self,
44 44 rev: Revision,
45 45 ) -> Result<Manifest, RevlogError> {
46 46 let bytes = self.revlog.get_rev_data(rev)?;
47 47 Ok(Manifest { bytes })
48 48 }
49 49 }
50 50
51 51 /// `Manifestlog` entry which knows how to interpret the `manifest` data bytes.
52 52 #[derive(Debug)]
53 53 pub struct Manifest {
54 54 bytes: Vec<u8>,
55 55 }
56 56
57 57 impl Manifest {
58 58 /// Return an iterator over the lines of the entry.
59 59 pub fn lines(&self) -> impl Iterator<Item = &[u8]> {
60 60 self.bytes
61 61 .split(|b| b == &b'\n')
62 62 .filter(|line| !line.is_empty())
63 63 }
64 64
65 65 /// Return an iterator over the files of the entry.
66 pub fn files(&self) -> impl Iterator<Item = &HgPath> {
66 pub fn files(&self) -> impl Iterator<Item = Result<&HgPath, HgError>> {
67 67 self.lines().filter(|line| !line.is_empty()).map(|line| {
68 let pos = line
69 .iter()
70 .position(|x| x == &b'\0')
71 .expect("manifest line should contain \\0");
72 HgPath::new(&line[..pos])
68 let pos =
69 line.iter().position(|x| x == &b'\0').ok_or_else(|| {
70 HgError::corrupted("manifest line should contain \\0")
71 })?;
72 Ok(HgPath::new(&line[..pos]))
73 73 })
74 74 }
75 75
76 76 /// Return an iterator over the files of the entry.
77 pub fn files_with_nodes(&self) -> impl Iterator<Item = (&HgPath, &[u8])> {
77 pub fn files_with_nodes(
78 &self,
79 ) -> impl Iterator<Item = Result<(&HgPath, &[u8]), HgError>> {
78 80 self.lines().filter(|line| !line.is_empty()).map(|line| {
79 let pos = line
80 .iter()
81 .position(|x| x == &b'\0')
82 .expect("manifest line should contain \\0");
81 let pos =
82 line.iter().position(|x| x == &b'\0').ok_or_else(|| {
83 HgError::corrupted("manifest line should contain \\0")
84 })?;
83 85 let hash_start = pos + 1;
84 86 let hash_end = hash_start + 40;
85 (HgPath::new(&line[..pos]), &line[hash_start..hash_end])
87 Ok((HgPath::new(&line[..pos]), &line[hash_start..hash_end]))
86 88 })
87 89 }
88 90
89 91 /// If the given path is in this manifest, return its filelog node ID
90 92 pub fn find_file(&self, path: &HgPath) -> Result<Option<Node>, HgError> {
91 93 // TODO: use binary search instead of linear scan. This may involve
92 94 // building (and caching) an index of the byte indicex of each manifest
93 95 // line.
94 for (manifest_path, node) in self.files_with_nodes() {
96 for entry in self.files_with_nodes() {
97 let (manifest_path, node) = entry?;
95 98 if manifest_path == path {
96 99 return Ok(Some(Node::from_hex_for_repo(node)?));
97 100 }
98 101 }
99 102 Ok(None)
100 103 }
101 104 }
@@ -1,71 +1,72 b''
1 1 use crate::error::CommandError;
2 2 use crate::ui::Ui;
3 3 use crate::ui::UiError;
4 4 use crate::utils::path_utils::relativize_paths;
5 5 use clap::Arg;
6 use hg::errors::HgError;
6 7 use hg::operations::list_rev_tracked_files;
7 8 use hg::operations::Dirstate;
8 9 use hg::repo::Repo;
9 10 use hg::utils::hg_path::HgPath;
10 11 use std::borrow::Cow;
11 12
12 13 pub const HELP_TEXT: &str = "
13 14 List tracked files.
14 15
15 16 Returns 0 on success.
16 17 ";
17 18
18 19 pub fn args() -> clap::App<'static, 'static> {
19 20 clap::SubCommand::with_name("files")
20 21 .arg(
21 22 Arg::with_name("rev")
22 23 .help("search the repository as it is in REV")
23 24 .short("-r")
24 25 .long("--revision")
25 26 .value_name("REV")
26 27 .takes_value(true),
27 28 )
28 29 .about(HELP_TEXT)
29 30 }
30 31
31 32 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
32 33 let relative = invocation.config.get(b"ui", b"relative-paths");
33 34 if relative.is_some() {
34 35 return Err(CommandError::unsupported(
35 36 "non-default ui.relative-paths",
36 37 ));
37 38 }
38 39
39 40 let rev = invocation.subcommand_args.value_of("rev");
40 41
41 42 let repo = invocation.repo?;
42 43 if let Some(rev) = rev {
43 44 let files = list_rev_tracked_files(repo, rev).map_err(|e| (e, rev))?;
44 45 display_files(invocation.ui, repo, files.iter())
45 46 } else {
46 47 let distate = Dirstate::new(repo)?;
47 48 let files = distate.tracked_files()?;
48 display_files(invocation.ui, repo, files)
49 display_files(invocation.ui, repo, files.into_iter().map(Ok))
49 50 }
50 51 }
51 52
52 53 fn display_files<'a>(
53 54 ui: &Ui,
54 55 repo: &Repo,
55 files: impl IntoIterator<Item = &'a HgPath>,
56 files: impl IntoIterator<Item = Result<&'a HgPath, HgError>>,
56 57 ) -> Result<(), CommandError> {
57 58 let mut stdout = ui.stdout_buffer();
58 59 let mut any = false;
59 60
60 61 relativize_paths(repo, files, |path: Cow<[u8]>| -> Result<(), UiError> {
61 62 any = true;
62 63 stdout.write_all(path.as_ref())?;
63 64 stdout.write_all(b"\n")
64 65 })?;
65 66 stdout.flush()?;
66 67 if any {
67 68 Ok(())
68 69 } else {
69 70 Err(CommandError::Unsuccessful)
70 71 }
71 72 }
@@ -1,324 +1,324 b''
1 1 // status.rs
2 2 //
3 3 // Copyright 2020, Georges Racinet <georges.racinets@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 use crate::error::CommandError;
9 9 use crate::ui::{Ui, UiError};
10 10 use crate::utils::path_utils::relativize_paths;
11 11 use clap::{Arg, SubCommand};
12 12 use hg;
13 13 use hg::config::Config;
14 14 use hg::dirstate::TruncatedTimestamp;
15 15 use hg::errors::HgError;
16 16 use hg::manifest::Manifest;
17 17 use hg::matchers::AlwaysMatcher;
18 18 use hg::repo::Repo;
19 19 use hg::utils::hg_path::{hg_path_to_os_string, HgPath};
20 20 use hg::{HgPathCow, StatusOptions};
21 21 use log::{info, warn};
22 22 use std::borrow::Cow;
23 23
24 24 pub const HELP_TEXT: &str = "
25 25 Show changed files in the working directory
26 26
27 27 This is a pure Rust version of `hg status`.
28 28
29 29 Some options might be missing, check the list below.
30 30 ";
31 31
32 32 pub fn args() -> clap::App<'static, 'static> {
33 33 SubCommand::with_name("status")
34 34 .alias("st")
35 35 .about(HELP_TEXT)
36 36 .arg(
37 37 Arg::with_name("all")
38 38 .help("show status of all files")
39 39 .short("-A")
40 40 .long("--all"),
41 41 )
42 42 .arg(
43 43 Arg::with_name("modified")
44 44 .help("show only modified files")
45 45 .short("-m")
46 46 .long("--modified"),
47 47 )
48 48 .arg(
49 49 Arg::with_name("added")
50 50 .help("show only added files")
51 51 .short("-a")
52 52 .long("--added"),
53 53 )
54 54 .arg(
55 55 Arg::with_name("removed")
56 56 .help("show only removed files")
57 57 .short("-r")
58 58 .long("--removed"),
59 59 )
60 60 .arg(
61 61 Arg::with_name("clean")
62 62 .help("show only clean files")
63 63 .short("-c")
64 64 .long("--clean"),
65 65 )
66 66 .arg(
67 67 Arg::with_name("deleted")
68 68 .help("show only deleted files")
69 69 .short("-d")
70 70 .long("--deleted"),
71 71 )
72 72 .arg(
73 73 Arg::with_name("unknown")
74 74 .help("show only unknown (not tracked) files")
75 75 .short("-u")
76 76 .long("--unknown"),
77 77 )
78 78 .arg(
79 79 Arg::with_name("ignored")
80 80 .help("show only ignored files")
81 81 .short("-i")
82 82 .long("--ignored"),
83 83 )
84 84 }
85 85
86 86 /// Pure data type allowing the caller to specify file states to display
87 87 #[derive(Copy, Clone, Debug)]
88 88 pub struct DisplayStates {
89 89 pub modified: bool,
90 90 pub added: bool,
91 91 pub removed: bool,
92 92 pub clean: bool,
93 93 pub deleted: bool,
94 94 pub unknown: bool,
95 95 pub ignored: bool,
96 96 }
97 97
98 98 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
99 99 modified: true,
100 100 added: true,
101 101 removed: true,
102 102 clean: false,
103 103 deleted: true,
104 104 unknown: true,
105 105 ignored: false,
106 106 };
107 107
108 108 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
109 109 modified: true,
110 110 added: true,
111 111 removed: true,
112 112 clean: true,
113 113 deleted: true,
114 114 unknown: true,
115 115 ignored: true,
116 116 };
117 117
118 118 impl DisplayStates {
119 119 pub fn is_empty(&self) -> bool {
120 120 !(self.modified
121 121 || self.added
122 122 || self.removed
123 123 || self.clean
124 124 || self.deleted
125 125 || self.unknown
126 126 || self.ignored)
127 127 }
128 128 }
129 129
130 130 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
131 131 let status_enabled_default = false;
132 132 let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
133 133 if !status_enabled.unwrap_or(status_enabled_default) {
134 134 return Err(CommandError::unsupported(
135 135 "status is experimental in rhg (enable it with 'rhg.status = true' \
136 136 or enable fallback with 'rhg.on-unsupported = fallback')"
137 137 ));
138 138 }
139 139
140 140 // TODO: lift these limitations
141 141 if invocation.config.get_bool(b"ui", b"tweakdefaults")? {
142 142 return Err(CommandError::unsupported(
143 143 "ui.tweakdefaults is not yet supported with rhg status",
144 144 ));
145 145 }
146 146 if invocation.config.get_bool(b"ui", b"statuscopies")? {
147 147 return Err(CommandError::unsupported(
148 148 "ui.statuscopies is not yet supported with rhg status",
149 149 ));
150 150 }
151 151 if invocation
152 152 .config
153 153 .get(b"commands", b"status.terse")
154 154 .is_some()
155 155 {
156 156 return Err(CommandError::unsupported(
157 157 "status.terse is not yet supported with rhg status",
158 158 ));
159 159 }
160 160
161 161 let ui = invocation.ui;
162 162 let config = invocation.config;
163 163 let args = invocation.subcommand_args;
164 164 let display_states = if args.is_present("all") {
165 165 // TODO when implementing `--quiet`: it excludes clean files
166 166 // from `--all`
167 167 ALL_DISPLAY_STATES
168 168 } else {
169 169 let requested = DisplayStates {
170 170 modified: args.is_present("modified"),
171 171 added: args.is_present("added"),
172 172 removed: args.is_present("removed"),
173 173 clean: args.is_present("clean"),
174 174 deleted: args.is_present("deleted"),
175 175 unknown: args.is_present("unknown"),
176 176 ignored: args.is_present("ignored"),
177 177 };
178 178 if requested.is_empty() {
179 179 DEFAULT_DISPLAY_STATES
180 180 } else {
181 181 requested
182 182 }
183 183 };
184 184
185 185 let repo = invocation.repo?;
186 186 let mut dmap = repo.dirstate_map_mut()?;
187 187
188 188 let options = StatusOptions {
189 189 // TODO should be provided by the dirstate parsing and
190 190 // hence be stored on dmap. Using a value that assumes we aren't
191 191 // below the time resolution granularity of the FS and the
192 192 // dirstate.
193 193 last_normal_time: TruncatedTimestamp::new_truncate(0, 0),
194 194 // we're currently supporting file systems with exec flags only
195 195 // anyway
196 196 check_exec: true,
197 197 list_clean: display_states.clean,
198 198 list_unknown: display_states.unknown,
199 199 list_ignored: display_states.ignored,
200 200 collect_traversed_dirs: false,
201 201 };
202 202 let ignore_file = repo.working_directory_vfs().join(".hgignore"); // TODO hardcoded
203 203 let (mut ds_status, pattern_warnings) = dmap.status(
204 204 &AlwaysMatcher,
205 205 repo.working_directory_path().to_owned(),
206 206 vec![ignore_file],
207 207 options,
208 208 )?;
209 209 if !pattern_warnings.is_empty() {
210 210 warn!("Pattern warnings: {:?}", &pattern_warnings);
211 211 }
212 212
213 213 if !ds_status.bad.is_empty() {
214 214 warn!("Bad matches {:?}", &(ds_status.bad))
215 215 }
216 216 if !ds_status.unsure.is_empty() {
217 217 info!(
218 218 "Files to be rechecked by retrieval from filelog: {:?}",
219 219 &ds_status.unsure
220 220 );
221 221 }
222 222 if !ds_status.unsure.is_empty()
223 223 && (display_states.modified || display_states.clean)
224 224 {
225 225 let p1 = repo.dirstate_parents()?.p1;
226 226 let manifest = repo.manifest_for_node(p1).map_err(|e| {
227 227 CommandError::from((e, &*format!("{:x}", p1.short())))
228 228 })?;
229 229 for to_check in ds_status.unsure {
230 230 if cat_file_is_modified(repo, &manifest, &to_check)? {
231 231 if display_states.modified {
232 232 ds_status.modified.push(to_check);
233 233 }
234 234 } else {
235 235 if display_states.clean {
236 236 ds_status.clean.push(to_check);
237 237 }
238 238 }
239 239 }
240 240 }
241 241 if display_states.modified {
242 242 display_status_paths(ui, repo, config, &mut ds_status.modified, b"M")?;
243 243 }
244 244 if display_states.added {
245 245 display_status_paths(ui, repo, config, &mut ds_status.added, b"A")?;
246 246 }
247 247 if display_states.removed {
248 248 display_status_paths(ui, repo, config, &mut ds_status.removed, b"R")?;
249 249 }
250 250 if display_states.deleted {
251 251 display_status_paths(ui, repo, config, &mut ds_status.deleted, b"!")?;
252 252 }
253 253 if display_states.unknown {
254 254 display_status_paths(ui, repo, config, &mut ds_status.unknown, b"?")?;
255 255 }
256 256 if display_states.ignored {
257 257 display_status_paths(ui, repo, config, &mut ds_status.ignored, b"I")?;
258 258 }
259 259 if display_states.clean {
260 260 display_status_paths(ui, repo, config, &mut ds_status.clean, b"C")?;
261 261 }
262 262 Ok(())
263 263 }
264 264
265 265 // Probably more elegant to use a Deref or Borrow trait rather than
266 266 // harcode HgPathBuf, but probably not really useful at this point
267 267 fn display_status_paths(
268 268 ui: &Ui,
269 269 repo: &Repo,
270 270 config: &Config,
271 271 paths: &mut [HgPathCow],
272 272 status_prefix: &[u8],
273 273 ) -> Result<(), CommandError> {
274 274 paths.sort_unstable();
275 275 let mut relative: bool = config.get_bool(b"ui", b"relative-paths")?;
276 276 relative = config
277 277 .get_option(b"commands", b"status.relative")?
278 278 .unwrap_or(relative);
279 279 if relative && !ui.plain() {
280 280 relativize_paths(
281 281 repo,
282 paths,
282 paths.iter().map(Ok),
283 283 |path: Cow<[u8]>| -> Result<(), UiError> {
284 284 ui.write_stdout(
285 285 &[status_prefix, b" ", path.as_ref(), b"\n"].concat(),
286 286 )
287 287 },
288 288 )?;
289 289 } else {
290 290 for path in paths {
291 291 // Same TODO as in commands::root
292 292 let bytes: &[u8] = path.as_bytes();
293 293 // TODO optim, probably lots of unneeded copies here, especially
294 294 // if out stream is buffered
295 295 ui.write_stdout(&[status_prefix, b" ", bytes, b"\n"].concat())?;
296 296 }
297 297 }
298 298 Ok(())
299 299 }
300 300
301 301 /// Check if a file is modified by comparing actual repo store and file system.
302 302 ///
303 303 /// This meant to be used for those that the dirstate cannot resolve, due
304 304 /// to time resolution limits.
305 305 ///
306 306 /// TODO: detect permission bits and similar metadata modifications
307 307 fn cat_file_is_modified(
308 308 repo: &Repo,
309 309 manifest: &Manifest,
310 310 hg_path: &HgPath,
311 311 ) -> Result<bool, HgError> {
312 312 let file_node = manifest
313 313 .find_file(hg_path)?
314 314 .expect("ambgious file not in p1");
315 315 let filelog = repo.filelog(hg_path)?;
316 316 let filelog_entry = filelog.data_for_node(file_node).map_err(|_| {
317 317 HgError::corrupted("filelog missing node from manifest")
318 318 })?;
319 319 let contents_in_p1 = filelog_entry.data()?;
320 320
321 321 let fs_path = hg_path_to_os_string(hg_path).expect("HgPath conversion");
322 322 let fs_contents = repo.working_directory_vfs().read(fs_path)?;
323 323 return Ok(contents_in_p1 != &*fs_contents);
324 324 }
@@ -1,48 +1,49 b''
1 1 // path utils module
2 2 //
3 3 // This software may be used and distributed according to the terms of the
4 4 // GNU General Public License version 2 or any later version.
5 5
6 6 use crate::error::CommandError;
7 7 use crate::ui::UiError;
8 use hg::errors::HgError;
8 9 use hg::repo::Repo;
9 10 use hg::utils::current_dir;
10 11 use hg::utils::files::{get_bytes_from_path, relativize_path};
11 12 use hg::utils::hg_path::HgPath;
12 13 use hg::utils::hg_path::HgPathBuf;
13 14 use std::borrow::Cow;
14 15
15 16 pub fn relativize_paths(
16 17 repo: &Repo,
17 paths: impl IntoIterator<Item = impl AsRef<HgPath>>,
18 paths: impl IntoIterator<Item = Result<impl AsRef<HgPath>, HgError>>,
18 19 mut callback: impl FnMut(Cow<[u8]>) -> Result<(), UiError>,
19 20 ) -> Result<(), CommandError> {
20 21 let cwd = current_dir()?;
21 22 let repo_root = repo.working_directory_path();
22 23 let repo_root = cwd.join(repo_root); // Make it absolute
23 24 let repo_root_hgpath =
24 25 HgPathBuf::from(get_bytes_from_path(repo_root.to_owned()));
25 26 let outside_repo: bool;
26 27 let cwd_hgpath: HgPathBuf;
27 28
28 29 if let Ok(cwd_relative_to_repo) = cwd.strip_prefix(&repo_root) {
29 30 // The current directory is inside the repo, so we can work with
30 31 // relative paths
31 32 outside_repo = false;
32 33 cwd_hgpath =
33 34 HgPathBuf::from(get_bytes_from_path(cwd_relative_to_repo));
34 35 } else {
35 36 outside_repo = true;
36 37 cwd_hgpath = HgPathBuf::from(get_bytes_from_path(cwd));
37 38 }
38 39
39 40 for file in paths {
40 41 if outside_repo {
41 let file = repo_root_hgpath.join(file.as_ref());
42 let file = repo_root_hgpath.join(file?.as_ref());
42 43 callback(relativize_path(&file, &cwd_hgpath))?;
43 44 } else {
44 callback(relativize_path(file.as_ref(), &cwd_hgpath))?;
45 callback(relativize_path(file?.as_ref(), &cwd_hgpath))?;
45 46 }
46 47 }
47 48 Ok(())
48 49 }
General Comments 0
You need to be logged in to leave comments. Login now