Show More
@@ -1,105 +1,105 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 std::path::PathBuf; |
|
9 | 9 | |
|
10 | 10 | use crate::repo::Repo; |
|
11 | 11 | use crate::revlog::changelog::Changelog; |
|
12 | use crate::revlog::manifest::Manifest; | |
|
12 | use crate::revlog::manifest::Manifestlog; | |
|
13 | 13 | use crate::revlog::path_encode::path_encode; |
|
14 | 14 | use crate::revlog::revlog::Revlog; |
|
15 | 15 | use crate::revlog::revlog::RevlogError; |
|
16 | 16 | use crate::revlog::Node; |
|
17 | 17 | use crate::utils::files::get_path_from_bytes; |
|
18 | 18 | use crate::utils::hg_path::{HgPath, HgPathBuf}; |
|
19 | 19 | |
|
20 | 20 | pub struct CatOutput { |
|
21 | 21 | /// Whether any file in the manifest matched the paths given as CLI |
|
22 | 22 | /// arguments |
|
23 | 23 | pub found_any: bool, |
|
24 | 24 | /// The contents of matching files, in manifest order |
|
25 | 25 | pub concatenated: Vec<u8>, |
|
26 | 26 | /// Which of the CLI arguments did not match any manifest file |
|
27 | 27 | pub missing: Vec<HgPathBuf>, |
|
28 | 28 | /// The node ID that the given revset was resolved to |
|
29 | 29 | pub node: Node, |
|
30 | 30 | } |
|
31 | 31 | |
|
32 | 32 | const METADATA_DELIMITER: [u8; 2] = [b'\x01', b'\n']; |
|
33 | 33 | |
|
34 | 34 | /// Output the given revision of files |
|
35 | 35 | /// |
|
36 | 36 | /// * `root`: Repository root |
|
37 | 37 | /// * `rev`: The revision to cat the files from. |
|
38 | 38 | /// * `files`: The files to output. |
|
39 | 39 | pub fn cat<'a>( |
|
40 | 40 | repo: &Repo, |
|
41 | 41 | revset: &str, |
|
42 | 42 | files: &'a [HgPathBuf], |
|
43 | 43 | ) -> Result<CatOutput, RevlogError> { |
|
44 | 44 | let rev = crate::revset::resolve_single(revset, repo)?; |
|
45 | 45 | let changelog = Changelog::open(repo)?; |
|
46 | let manifest = Manifest::open(repo)?; | |
|
46 | let manifest = Manifestlog::open(repo)?; | |
|
47 | 47 | let changelog_entry = changelog.get_rev(rev)?; |
|
48 | 48 | let node = *changelog |
|
49 | 49 | .node_from_rev(rev) |
|
50 | 50 | .expect("should succeed when changelog.get_rev did"); |
|
51 | 51 | let manifest_node = |
|
52 | 52 | Node::from_hex_for_repo(&changelog_entry.manifest_node()?)?; |
|
53 | 53 | let manifest_entry = manifest.get_node(manifest_node.into())?; |
|
54 | 54 | let mut bytes = vec![]; |
|
55 | 55 | let mut matched = vec![false; files.len()]; |
|
56 | 56 | let mut found_any = false; |
|
57 | 57 | |
|
58 | 58 | for (manifest_file, node_bytes) in manifest_entry.files_with_nodes() { |
|
59 | 59 | for (cat_file, is_matched) in files.iter().zip(&mut matched) { |
|
60 | 60 | if cat_file.as_bytes() == manifest_file.as_bytes() { |
|
61 | 61 | *is_matched = true; |
|
62 | 62 | found_any = true; |
|
63 | 63 | let index_path = store_path(manifest_file, b".i"); |
|
64 | 64 | let data_path = store_path(manifest_file, b".d"); |
|
65 | 65 | |
|
66 | 66 | let file_log = |
|
67 | 67 | Revlog::open(repo, &index_path, Some(&data_path))?; |
|
68 | 68 | let file_node = Node::from_hex_for_repo(node_bytes)?; |
|
69 | 69 | let file_rev = file_log.get_node_rev(file_node.into())?; |
|
70 | 70 | let data = file_log.get_rev_data(file_rev)?; |
|
71 | 71 | if data.starts_with(&METADATA_DELIMITER) { |
|
72 | 72 | let end_delimiter_position = data |
|
73 | 73 | [METADATA_DELIMITER.len()..] |
|
74 | 74 | .windows(METADATA_DELIMITER.len()) |
|
75 | 75 | .position(|bytes| bytes == METADATA_DELIMITER); |
|
76 | 76 | if let Some(position) = end_delimiter_position { |
|
77 | 77 | let offset = METADATA_DELIMITER.len() * 2; |
|
78 | 78 | bytes.extend(data[position + offset..].iter()); |
|
79 | 79 | } |
|
80 | 80 | } else { |
|
81 | 81 | bytes.extend(data); |
|
82 | 82 | } |
|
83 | 83 | } |
|
84 | 84 | } |
|
85 | 85 | } |
|
86 | 86 | |
|
87 | 87 | let missing: Vec<_> = files |
|
88 | 88 | .iter() |
|
89 | 89 | .zip(&matched) |
|
90 | 90 | .filter(|pair| !*pair.1) |
|
91 | 91 | .map(|pair| pair.0.clone()) |
|
92 | 92 | .collect(); |
|
93 | 93 | Ok(CatOutput { |
|
94 | 94 | found_any, |
|
95 | 95 | concatenated: bytes, |
|
96 | 96 | missing, |
|
97 | 97 | node, |
|
98 | 98 | }) |
|
99 | 99 | } |
|
100 | 100 | |
|
101 | 101 | fn store_path(hg_path: &HgPath, suffix: &[u8]) -> PathBuf { |
|
102 | 102 | let encoded_bytes = |
|
103 | 103 | path_encode(&[b"data/", hg_path.as_bytes(), suffix].concat()); |
|
104 | 104 | get_path_from_bytes(&encoded_bytes).into() |
|
105 | 105 | } |
@@ -1,90 +1,90 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::changelog::Changelog; |
|
13 |
use crate::revlog::manifest::{Manifest, Manifest |
|
|
13 | use crate::revlog::manifest::{Manifest, Manifestlog}; | |
|
14 | 14 | use crate::revlog::node::Node; |
|
15 | 15 | use crate::revlog::revlog::RevlogError; |
|
16 | 16 | use crate::utils::hg_path::HgPath; |
|
17 | 17 | use crate::DirstateError; |
|
18 | 18 | use rayon::prelude::*; |
|
19 | 19 | |
|
20 | 20 | /// List files under Mercurial control in the working directory |
|
21 | 21 | /// by reading the dirstate |
|
22 | 22 | pub struct Dirstate { |
|
23 | 23 | /// The `dirstate` content. |
|
24 | 24 | content: Vec<u8>, |
|
25 | 25 | v2_metadata: Option<Vec<u8>>, |
|
26 | 26 | } |
|
27 | 27 | |
|
28 | 28 | impl Dirstate { |
|
29 | 29 | pub fn new(repo: &Repo) -> Result<Self, HgError> { |
|
30 | 30 | let mut content = repo.hg_vfs().read("dirstate")?; |
|
31 | 31 | let v2_metadata = if repo.has_dirstate_v2() { |
|
32 | 32 | let docket = read_docket(&content)?; |
|
33 | 33 | let meta = docket.tree_metadata().to_vec(); |
|
34 | 34 | content = repo.hg_vfs().read(docket.data_filename())?; |
|
35 | 35 | Some(meta) |
|
36 | 36 | } else { |
|
37 | 37 | None |
|
38 | 38 | }; |
|
39 | 39 | Ok(Self { |
|
40 | 40 | content, |
|
41 | 41 | v2_metadata, |
|
42 | 42 | }) |
|
43 | 43 | } |
|
44 | 44 | |
|
45 | 45 | pub fn tracked_files(&self) -> Result<Vec<&HgPath>, DirstateError> { |
|
46 | 46 | let mut files = Vec::new(); |
|
47 | 47 | if !self.content.is_empty() { |
|
48 | 48 | if let Some(meta) = &self.v2_metadata { |
|
49 | 49 | for_each_tracked_path(&self.content, meta, |path| { |
|
50 | 50 | files.push(path) |
|
51 | 51 | })? |
|
52 | 52 | } else { |
|
53 | 53 | let _parents = parse_dirstate_entries( |
|
54 | 54 | &self.content, |
|
55 | 55 | |path, entry, _copy_source| { |
|
56 | 56 | if entry.state.is_tracked() { |
|
57 | 57 | files.push(path) |
|
58 | 58 | } |
|
59 | 59 | Ok(()) |
|
60 | 60 | }, |
|
61 | 61 | )?; |
|
62 | 62 | } |
|
63 | 63 | } |
|
64 | 64 | files.par_sort_unstable(); |
|
65 | 65 | Ok(files) |
|
66 | 66 | } |
|
67 | 67 | } |
|
68 | 68 | |
|
69 | 69 | /// List files under Mercurial control at a given revision. |
|
70 | 70 | pub fn list_rev_tracked_files( |
|
71 | 71 | repo: &Repo, |
|
72 | 72 | revset: &str, |
|
73 | 73 | ) -> Result<FilesForRev, RevlogError> { |
|
74 | 74 | let rev = crate::revset::resolve_single(revset, repo)?; |
|
75 | 75 | let changelog = Changelog::open(repo)?; |
|
76 | let manifest = Manifest::open(repo)?; | |
|
76 | let manifest = Manifestlog::open(repo)?; | |
|
77 | 77 | let changelog_entry = changelog.get_rev(rev)?; |
|
78 | 78 | let manifest_node = |
|
79 | 79 | Node::from_hex_for_repo(&changelog_entry.manifest_node()?)?; |
|
80 | 80 | let manifest_entry = manifest.get_node(manifest_node.into())?; |
|
81 | 81 | Ok(FilesForRev(manifest_entry)) |
|
82 | 82 | } |
|
83 | 83 | |
|
84 |
pub struct FilesForRev(Manifest |
|
|
84 | pub struct FilesForRev(Manifest); | |
|
85 | 85 | |
|
86 | 86 | impl FilesForRev { |
|
87 | 87 | pub fn iter(&self) -> impl Iterator<Item = &HgPath> { |
|
88 | 88 | self.0.files() |
|
89 | 89 | } |
|
90 | 90 | } |
@@ -1,76 +1,70 b'' | |||
|
1 | 1 | use crate::repo::Repo; |
|
2 | 2 | use crate::revlog::revlog::{Revlog, RevlogError}; |
|
3 | 3 | use crate::revlog::NodePrefix; |
|
4 | 4 | use crate::revlog::Revision; |
|
5 | 5 | use crate::utils::hg_path::HgPath; |
|
6 | 6 | |
|
7 | 7 | /// A specialized `Revlog` to work with `manifest` data format. |
|
8 | pub struct Manifest { | |
|
8 | pub struct Manifestlog { | |
|
9 | 9 | /// The generic `revlog` format. |
|
10 | 10 | revlog: Revlog, |
|
11 | 11 | } |
|
12 | 12 | |
|
13 | impl Manifest { | |
|
13 | impl Manifestlog { | |
|
14 | 14 | /// Open the `manifest` of a repository given by its root. |
|
15 | 15 | pub fn open(repo: &Repo) -> Result<Self, RevlogError> { |
|
16 | 16 | let revlog = Revlog::open(repo, "00manifest.i", None)?; |
|
17 | 17 | Ok(Self { revlog }) |
|
18 | 18 | } |
|
19 | 19 | |
|
20 | 20 | /// Return the `ManifestEntry` of a given node id. |
|
21 | pub fn get_node( | |
|
22 | &self, | |
|
23 | node: NodePrefix, | |
|
24 | ) -> Result<ManifestEntry, RevlogError> { | |
|
21 | pub fn get_node(&self, node: NodePrefix) -> Result<Manifest, RevlogError> { | |
|
25 | 22 | let rev = self.revlog.get_node_rev(node)?; |
|
26 | 23 | self.get_rev(rev) |
|
27 | 24 | } |
|
28 | 25 | |
|
29 | 26 | /// Return the `ManifestEntry` of a given node revision. |
|
30 | pub fn get_rev( | |
|
31 | &self, | |
|
32 | rev: Revision, | |
|
33 | ) -> Result<ManifestEntry, RevlogError> { | |
|
27 | pub fn get_rev(&self, rev: Revision) -> Result<Manifest, RevlogError> { | |
|
34 | 28 | let bytes = self.revlog.get_rev_data(rev)?; |
|
35 |
Ok(Manifest |
|
|
29 | Ok(Manifest { bytes }) | |
|
36 | 30 | } |
|
37 | 31 | } |
|
38 | 32 | |
|
39 | /// `Manifest` entry which knows how to interpret the `manifest` data bytes. | |
|
33 | /// `Manifestlog` entry which knows how to interpret the `manifest` data bytes. | |
|
40 | 34 | #[derive(Debug)] |
|
41 |
pub struct Manifest |
|
|
35 | pub struct Manifest { | |
|
42 | 36 | bytes: Vec<u8>, |
|
43 | 37 | } |
|
44 | 38 | |
|
45 |
impl Manifest |
|
|
39 | impl Manifest { | |
|
46 | 40 | /// Return an iterator over the lines of the entry. |
|
47 | 41 | pub fn lines(&self) -> impl Iterator<Item = &[u8]> { |
|
48 | 42 | self.bytes |
|
49 | 43 | .split(|b| b == &b'\n') |
|
50 | 44 | .filter(|line| !line.is_empty()) |
|
51 | 45 | } |
|
52 | 46 | |
|
53 | 47 | /// Return an iterator over the files of the entry. |
|
54 | 48 | pub fn files(&self) -> impl Iterator<Item = &HgPath> { |
|
55 | 49 | self.lines().filter(|line| !line.is_empty()).map(|line| { |
|
56 | 50 | let pos = line |
|
57 | 51 | .iter() |
|
58 | 52 | .position(|x| x == &b'\0') |
|
59 | 53 | .expect("manifest line should contain \\0"); |
|
60 | 54 | HgPath::new(&line[..pos]) |
|
61 | 55 | }) |
|
62 | 56 | } |
|
63 | 57 | |
|
64 | 58 | /// Return an iterator over the files of the entry. |
|
65 | 59 | pub fn files_with_nodes(&self) -> impl Iterator<Item = (&HgPath, &[u8])> { |
|
66 | 60 | self.lines().filter(|line| !line.is_empty()).map(|line| { |
|
67 | 61 | let pos = line |
|
68 | 62 | .iter() |
|
69 | 63 | .position(|x| x == &b'\0') |
|
70 | 64 | .expect("manifest line should contain \\0"); |
|
71 | 65 | let hash_start = pos + 1; |
|
72 | 66 | let hash_end = hash_start + 40; |
|
73 | 67 | (HgPath::new(&line[..pos]), &line[hash_start..hash_end]) |
|
74 | 68 | }) |
|
75 | 69 | } |
|
76 | 70 | } |
General Comments 0
You need to be logged in to leave comments.
Login now