Details
Details
- Reviewers
Alphare - Group Reviewers
hg-reviewers - Commits
- rHGeb428010aad2: rhg: Also parse flags in the manifest parser
Diff Detail
Diff Detail
- Repository
- rHG Mercurial
- Branch
- default
- Lint
No Linters Available - Unit
No Unit Test Coverage
( )
| Alphare |
| hg-reviewers |
| No Linters Available |
| No Unit Test Coverage |
| Path | Packages | |||
|---|---|---|---|---|
| M | rust/hg-core/src/operations/cat.rs (37 lines) | |||
| M | rust/hg-core/src/operations/list_tracked_files.rs (2 lines) | |||
| M | rust/hg-core/src/revlog/manifest.rs (77 lines) | |||
| M | rust/rhg/src/commands/status.rs (9 lines) |
| Commit | Parents | Author | Summary | Date |
|---|---|---|---|---|
| bbd7c13076ae | a6f32caae77a | Simon Sapin | Nov 23 2021, 1:39 PM |
| Status | Author | Revision | |
|---|---|---|---|
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin |
| // list_tracked_files.rs | // list_tracked_files.rs | ||||
| // | // | ||||
| // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net> | // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net> | ||||
| // | // | ||||
| // This software may be used and distributed according to the terms of the | // This software may be used and distributed according to the terms of the | ||||
| // GNU General Public License version 2 or any later version. | // GNU General Public License version 2 or any later version. | ||||
| use crate::repo::Repo; | use crate::repo::Repo; | ||||
| use crate::revlog::revlog::RevlogError; | use crate::revlog::revlog::RevlogError; | ||||
| use crate::revlog::Node; | use crate::revlog::Node; | ||||
| use crate::utils::hg_path::HgPath; | use crate::utils::hg_path::HgPath; | ||||
| use crate::errors::HgError; | use crate::errors::HgError; | ||||
| use crate::manifest::Manifest; | |||||
| use crate::manifest::ManifestEntry; | |||||
| use itertools::put_back; | use itertools::put_back; | ||||
| use itertools::PutBack; | use itertools::PutBack; | ||||
| use std::cmp::Ordering; | use std::cmp::Ordering; | ||||
| pub struct CatOutput<'a> { | pub struct CatOutput<'a> { | ||||
| /// Whether any file in the manifest matched the paths given as CLI | /// Whether any file in the manifest matched the paths given as CLI | ||||
| /// arguments | /// arguments | ||||
| pub found_any: bool, | pub found_any: bool, | ||||
| /// The contents of matching files, in manifest order | /// The contents of matching files, in manifest order | ||||
| pub results: Vec<(&'a HgPath, Vec<u8>)>, | pub results: Vec<(&'a HgPath, Vec<u8>)>, | ||||
| /// Which of the CLI arguments did not match any manifest file | /// Which of the CLI arguments did not match any manifest file | ||||
| pub missing: Vec<&'a HgPath>, | pub missing: Vec<&'a HgPath>, | ||||
| /// The node ID that the given revset was resolved to | /// The node ID that the given revset was resolved to | ||||
| pub node: Node, | pub node: Node, | ||||
| } | } | ||||
| // Find an item in an iterator over a sorted collection. | // Find an item in an iterator over a sorted collection. | ||||
| fn find_item<'a, D, I: Iterator<Item = Result<(&'a HgPath, D), HgError>>>( | fn find_item<'a>( | ||||
| i: &mut PutBack<I>, | i: &mut PutBack<impl Iterator<Item = Result<ManifestEntry<'a>, HgError>>>, | ||||
| needle: &HgPath, | needle: &HgPath, | ||||
| ) -> Result<Option<D>, HgError> { | ) -> Result<Option<Node>, HgError> { | ||||
| loop { | loop { | ||||
| match i.next() { | match i.next() { | ||||
| None => return Ok(None), | None => return Ok(None), | ||||
| Some(result) => { | Some(result) => { | ||||
| let (path, value) = result?; | let entry = result?; | ||||
| match needle.as_bytes().cmp(path.as_bytes()) { | match needle.as_bytes().cmp(entry.path.as_bytes()) { | ||||
| Ordering::Less => { | Ordering::Less => { | ||||
| i.put_back(Ok((path, value))); | i.put_back(Ok(entry)); | ||||
| return Ok(None); | return Ok(None); | ||||
| } | } | ||||
| Ordering::Greater => continue, | Ordering::Greater => continue, | ||||
| Ordering::Equal => return Ok(Some(value)), | Ordering::Equal => return Ok(Some(entry.node_id()?)), | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| fn find_files_in_manifest< | fn find_files_in_manifest<'query>( | ||||
| 'manifest, | manifest: &Manifest, | ||||
| 'query, | query: impl Iterator<Item = &'query HgPath>, | ||||
| Data, | ) -> Result<(Vec<(&'query HgPath, Node)>, Vec<&'query HgPath>), HgError> { | ||||
| Manifest: Iterator<Item = Result<(&'manifest HgPath, Data), HgError>>, | let mut manifest = put_back(manifest.iter()); | ||||
| Query: Iterator<Item = &'query HgPath>, | |||||
| >( | |||||
| manifest: Manifest, | |||||
| query: Query, | |||||
| ) -> Result<(Vec<(&'query HgPath, Data)>, Vec<&'query HgPath>), HgError> { | |||||
| let mut manifest = put_back(manifest); | |||||
| let mut res = vec![]; | let mut res = vec![]; | ||||
| let mut missing = vec![]; | let mut missing = vec![]; | ||||
| for file in query { | for file in query { | ||||
| match find_item(&mut manifest, file)? { | match find_item(&mut manifest, file)? { | ||||
| None => missing.push(file), | None => missing.push(file), | ||||
| Some(item) => res.push((file, item)), | Some(item) => res.push((file, item)), | ||||
| } | } | ||||
| .node_from_rev(rev) | .node_from_rev(rev) | ||||
| .expect("should succeed when repo.manifest did"); | .expect("should succeed when repo.manifest did"); | ||||
| let mut results: Vec<(&'a HgPath, Vec<u8>)> = vec![]; | let mut results: Vec<(&'a HgPath, Vec<u8>)> = vec![]; | ||||
| let mut found_any = false; | let mut found_any = false; | ||||
| files.sort_unstable(); | files.sort_unstable(); | ||||
| let (found, missing) = find_files_in_manifest( | let (found, missing) = find_files_in_manifest( | ||||
| manifest.files_with_nodes(), | &manifest, | ||||
| files.into_iter().map(|f| f.as_ref()), | files.into_iter().map(|f| f.as_ref()), | ||||
| )?; | )?; | ||||
| for (file_path, node_bytes) in found { | for (file_path, file_node) in found { | ||||
| found_any = true; | found_any = true; | ||||
| let file_log = repo.filelog(file_path)?; | let file_log = repo.filelog(file_path)?; | ||||
| let file_node = Node::from_hex_for_repo(node_bytes)?; | |||||
| results.push(( | results.push(( | ||||
| file_path, | file_path, | ||||
| file_log.data_for_node(file_node)?.into_data()?, | file_log.data_for_node(file_node)?.into_data()?, | ||||
| )); | )); | ||||
| } | } | ||||
| Ok(CatOutput { | Ok(CatOutput { | ||||
| found_any, | found_any, | ||||
| results, | results, | ||||
| missing, | missing, | ||||
| node, | node, | ||||
| }) | }) | ||||
| } | } | ||||
| let rev = crate::revset::resolve_single(revset, repo)?; | let rev = crate::revset::resolve_single(revset, repo)?; | ||||
| Ok(FilesForRev(repo.manifest_for_rev(rev)?)) | Ok(FilesForRev(repo.manifest_for_rev(rev)?)) | ||||
| } | } | ||||
| pub struct FilesForRev(Manifest); | pub struct FilesForRev(Manifest); | ||||
| impl FilesForRev { | impl FilesForRev { | ||||
| pub fn iter(&self) -> impl Iterator<Item = Result<&HgPath, HgError>> { | pub fn iter(&self) -> impl Iterator<Item = Result<&HgPath, HgError>> { | ||||
| self.0.files() | self.0.iter().map(|entry| Ok(entry?.path)) | ||||
| } | } | ||||
| } | } | ||||
| use crate::errors::HgError; | use crate::errors::HgError; | ||||
| use crate::repo::Repo; | use crate::repo::Repo; | ||||
| use crate::revlog::revlog::{Revlog, RevlogError}; | use crate::revlog::revlog::{Revlog, RevlogError}; | ||||
| use crate::revlog::Revision; | use crate::revlog::Revision; | ||||
| use crate::revlog::{Node, NodePrefix}; | use crate::revlog::{Node, NodePrefix}; | ||||
| use crate::utils::hg_path::HgPath; | use crate::utils::hg_path::HgPath; | ||||
| use crate::utils::SliceExt; | |||||
| /// A specialized `Revlog` to work with `manifest` data format. | /// A specialized `Revlog` to work with `manifest` data format. | ||||
| pub struct Manifestlog { | pub struct Manifestlog { | ||||
| /// The generic `revlog` format. | /// The generic `revlog` format. | ||||
| revlog: Revlog, | revlog: Revlog, | ||||
| } | } | ||||
| impl Manifestlog { | impl Manifestlog { | ||||
| /// `Manifestlog` entry which knows how to interpret the `manifest` data bytes. | /// `Manifestlog` entry which knows how to interpret the `manifest` data bytes. | ||||
| #[derive(Debug)] | #[derive(Debug)] | ||||
| pub struct Manifest { | pub struct Manifest { | ||||
| bytes: Vec<u8>, | bytes: Vec<u8>, | ||||
| } | } | ||||
| impl Manifest { | impl Manifest { | ||||
| /// Return an iterator over the lines of the entry. | pub fn iter( | ||||
| pub fn lines(&self) -> impl Iterator<Item = &[u8]> { | &self, | ||||
| ) -> impl Iterator<Item = Result<ManifestEntry, HgError>> { | |||||
| self.bytes | self.bytes | ||||
| .split(|b| b == &b'\n') | .split(|b| b == &b'\n') | ||||
| .filter(|line| !line.is_empty()) | .filter(|line| !line.is_empty()) | ||||
| } | .map(|line| { | ||||
| let (path, rest) = line.split_2(b'\0').ok_or_else(|| { | |||||
| /// Return an iterator over the files of the entry. | |||||
| pub fn files(&self) -> impl Iterator<Item = Result<&HgPath, HgError>> { | |||||
| self.lines().filter(|line| !line.is_empty()).map(|line| { | |||||
| let pos = | |||||
| line.iter().position(|x| x == &b'\0').ok_or_else(|| { | |||||
| HgError::corrupted("manifest line should contain \\0") | HgError::corrupted("manifest line should contain \\0") | ||||
| })?; | })?; | ||||
| Ok(HgPath::new(&line[..pos])) | let path = HgPath::new(path); | ||||
| let (hex_node_id, flags) = match rest.split_last() { | |||||
| Some((&b'x', rest)) => (rest, Some(b'x')), | |||||
| Some((&b'l', rest)) => (rest, Some(b'l')), | |||||
| Some((&b't', rest)) => (rest, Some(b't')), | |||||
| _ => (rest, None), | |||||
| }; | |||||
| Ok(ManifestEntry { | |||||
| path, | |||||
| hex_node_id, | |||||
| flags, | |||||
| }) | }) | ||||
| } | |||||
| /// Return an iterator over the files of the entry. | |||||
| pub fn files_with_nodes( | |||||
| &self, | |||||
| ) -> impl Iterator<Item = Result<(&HgPath, &[u8]), HgError>> { | |||||
| self.lines().filter(|line| !line.is_empty()).map(|line| { | |||||
| let pos = | |||||
| line.iter().position(|x| x == &b'\0').ok_or_else(|| { | |||||
| HgError::corrupted("manifest line should contain \\0") | |||||
| })?; | |||||
| let hash_start = pos + 1; | |||||
| let hash_end = hash_start + 40; | |||||
| Ok((HgPath::new(&line[..pos]), &line[hash_start..hash_end])) | |||||
| }) | }) | ||||
| } | } | ||||
| /// If the given path is in this manifest, return its filelog node ID | /// If the given path is in this manifest, return its filelog node ID | ||||
| pub fn find_file(&self, path: &HgPath) -> Result<Option<Node>, HgError> { | pub fn find_file( | ||||
| &self, | |||||
| path: &HgPath, | |||||
| ) -> Result<Option<ManifestEntry>, HgError> { | |||||
| // TODO: use binary search instead of linear scan. This may involve | // TODO: use binary search instead of linear scan. This may involve | ||||
| // building (and caching) an index of the byte indicex of each manifest | // building (and caching) an index of the byte indicex of each manifest | ||||
| // line. | // line. | ||||
| for entry in self.files_with_nodes() { | |||||
| let (manifest_path, node) = entry?; | // TODO: use try_find when available (if still using linear scan) | ||||
| if manifest_path == path { | // https://github.com/rust-lang/rust/issues/63178 | ||||
| return Ok(Some(Node::from_hex_for_repo(node)?)); | for entry in self.iter() { | ||||
| let entry = entry?; | |||||
| if entry.path == path { | |||||
| return Ok(Some(entry)); | |||||
| } | } | ||||
| } | } | ||||
| Ok(None) | Ok(None) | ||||
| } | } | ||||
| } | } | ||||
| /// `Manifestlog` entry which knows how to interpret the `manifest` data bytes. | |||||
| #[derive(Debug)] | |||||
| pub struct ManifestEntry<'manifest> { | |||||
| pub path: &'manifest HgPath, | |||||
| pub hex_node_id: &'manifest [u8], | |||||
| /// `Some` values are b'x', b'l', or 't' | |||||
| pub flags: Option<u8>, | |||||
| } | |||||
| impl ManifestEntry<'_> { | |||||
| pub fn node_id(&self) -> Result<Node, HgError> { | |||||
| Node::from_hex_for_repo(self.hex_node_id) | |||||
| } | |||||
| } | |||||
| /// to time resolution limits. | /// to time resolution limits. | ||||
| /// | /// | ||||
| /// TODO: detect permission bits and similar metadata modifications | /// TODO: detect permission bits and similar metadata modifications | ||||
| fn cat_file_is_modified( | fn cat_file_is_modified( | ||||
| repo: &Repo, | repo: &Repo, | ||||
| manifest: &Manifest, | manifest: &Manifest, | ||||
| hg_path: &HgPath, | hg_path: &HgPath, | ||||
| ) -> Result<bool, HgError> { | ) -> Result<bool, HgError> { | ||||
| let file_node = manifest | let entry = manifest | ||||
| .find_file(hg_path)? | .find_file(hg_path)? | ||||
| .expect("ambgious file not in p1"); | .expect("ambgious file not in p1"); | ||||
| let filelog = repo.filelog(hg_path)?; | let filelog = repo.filelog(hg_path)?; | ||||
| let filelog_entry = filelog.data_for_node(file_node).map_err(|_| { | let filelog_entry = | ||||
| filelog.data_for_node(entry.node_id()?).map_err(|_| { | |||||
| HgError::corrupted("filelog missing node from manifest") | HgError::corrupted("filelog missing node from manifest") | ||||
| })?; | })?; | ||||
| let contents_in_p1 = filelog_entry.data()?; | let contents_in_p1 = filelog_entry.data()?; | ||||
| let fs_path = hg_path_to_os_string(hg_path).expect("HgPath conversion"); | 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 = repo.working_directory_vfs().read(fs_path)?; | ||||
| return Ok(contents_in_p1 != &*fs_contents); | return Ok(contents_in_p1 != &*fs_contents); | ||||
| } | } | ||||