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); | ||||
} | } |