Show More
@@ -1,115 +1,115 b'' | |||||
1 | // list_tracked_files.rs |
|
1 | // list_tracked_files.rs | |
2 | // |
|
2 | // | |
3 | // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net> |
|
3 | // Copyright 2020 Antoine Cezar <antoine.cezar@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::repo::Repo; |
|
8 | use crate::repo::Repo; | |
9 | use crate::revlog::revlog::RevlogError; |
|
9 | use crate::revlog::revlog::RevlogError; | |
10 | use crate::revlog::Node; |
|
10 | use crate::revlog::Node; | |
11 |
|
11 | |||
12 | use crate::utils::hg_path::HgPath; |
|
12 | use crate::utils::hg_path::HgPath; | |
13 | use crate::utils::hg_path::HgPathBuf; |
|
|||
14 |
|
13 | |||
15 | use itertools::put_back; |
|
14 | use itertools::put_back; | |
16 | use itertools::PutBack; |
|
15 | use itertools::PutBack; | |
17 | use std::cmp::Ordering; |
|
16 | use std::cmp::Ordering; | |
18 |
|
17 | |||
19 | pub struct CatOutput { |
|
18 | pub struct CatOutput<'a> { | |
20 | /// Whether any file in the manifest matched the paths given as CLI |
|
19 | /// Whether any file in the manifest matched the paths given as CLI | |
21 | /// arguments |
|
20 | /// arguments | |
22 | pub found_any: bool, |
|
21 | pub found_any: bool, | |
23 | /// The contents of matching files, in manifest order |
|
22 | /// The contents of matching files, in manifest order | |
24 | pub concatenated: Vec<u8>, |
|
23 | pub results: Vec<(&'a HgPath, Vec<u8>)>, | |
25 | /// Which of the CLI arguments did not match any manifest file |
|
24 | /// Which of the CLI arguments did not match any manifest file | |
26 |
pub missing: Vec<HgPath |
|
25 | pub missing: Vec<&'a HgPath>, | |
27 | /// The node ID that the given revset was resolved to |
|
26 | /// The node ID that the given revset was resolved to | |
28 | pub node: Node, |
|
27 | pub node: Node, | |
29 | } |
|
28 | } | |
30 |
|
29 | |||
31 | // Find an item in an iterator over a sorted collection. |
|
30 | // Find an item in an iterator over a sorted collection. | |
32 | fn find_item<'a, 'b, 'c, D, I: Iterator<Item = (&'a HgPath, D)>>( |
|
31 | fn find_item<'a, 'b, 'c, D, I: Iterator<Item = (&'a HgPath, D)>>( | |
33 | i: &mut PutBack<I>, |
|
32 | i: &mut PutBack<I>, | |
34 | needle: &'b HgPath, |
|
33 | needle: &'b HgPath, | |
35 |
) -> Option< |
|
34 | ) -> Option<D> { | |
36 | loop { |
|
35 | loop { | |
37 | match i.next() { |
|
36 | match i.next() { | |
38 | None => return None, |
|
37 | None => return None, | |
39 | Some(val) => match needle.as_bytes().cmp(val.0.as_bytes()) { |
|
38 | Some(val) => match needle.as_bytes().cmp(val.0.as_bytes()) { | |
40 | Ordering::Less => { |
|
39 | Ordering::Less => { | |
41 | i.put_back(val); |
|
40 | i.put_back(val); | |
42 | return None; |
|
41 | return None; | |
43 | } |
|
42 | } | |
44 | Ordering::Greater => continue, |
|
43 | Ordering::Greater => continue, | |
45 | Ordering::Equal => return Some(val), |
|
44 | Ordering::Equal => return Some(val.1), | |
46 | }, |
|
45 | }, | |
47 | } |
|
46 | } | |
48 | } |
|
47 | } | |
49 | } |
|
48 | } | |
50 |
|
49 | |||
51 | fn find_files_in_manifest< |
|
50 | fn find_files_in_manifest< | |
52 | 'a, |
|
51 | 'manifest, | |
53 |
' |
|
52 | 'query, | |
54 | D, |
|
53 | Data, | |
55 |
|
|
54 | Manifest: Iterator<Item = (&'manifest HgPath, Data)>, | |
56 |
|
|
55 | Query: Iterator<Item = &'query HgPath>, | |
57 | >( |
|
56 | >( | |
58 |
manifest: |
|
57 | manifest: Manifest, | |
59 | files: J, |
|
58 | query: Query, | |
60 |
) -> (Vec<(&' |
|
59 | ) -> (Vec<(&'query HgPath, Data)>, Vec<&'query HgPath>) { | |
61 | let mut manifest = put_back(manifest); |
|
60 | let mut manifest = put_back(manifest); | |
62 | let mut res = vec![]; |
|
61 | let mut res = vec![]; | |
63 | let mut missing = vec![]; |
|
62 | let mut missing = vec![]; | |
64 |
|
63 | |||
65 |
for file in |
|
64 | for file in query { | |
66 | match find_item(&mut manifest, file) { |
|
65 | match find_item(&mut manifest, file) { | |
67 | None => missing.push(file), |
|
66 | None => missing.push(file), | |
68 | Some(item) => res.push(item), |
|
67 | Some(item) => res.push((file, item)), | |
69 | } |
|
68 | } | |
70 | } |
|
69 | } | |
71 | return (res, missing); |
|
70 | return (res, missing); | |
72 | } |
|
71 | } | |
73 |
|
72 | |||
74 | /// Output the given revision of files |
|
73 | /// Output the given revision of files | |
75 | /// |
|
74 | /// | |
76 | /// * `root`: Repository root |
|
75 | /// * `root`: Repository root | |
77 | /// * `rev`: The revision to cat the files from. |
|
76 | /// * `rev`: The revision to cat the files from. | |
78 | /// * `files`: The files to output. |
|
77 | /// * `files`: The files to output. | |
79 | pub fn cat<'a>( |
|
78 | pub fn cat<'a>( | |
80 | repo: &Repo, |
|
79 | repo: &Repo, | |
81 | revset: &str, |
|
80 | revset: &str, | |
82 |
mut files: Vec<HgPath |
|
81 | mut files: Vec<&'a HgPath>, | |
83 | ) -> Result<CatOutput, RevlogError> { |
|
82 | ) -> Result<CatOutput<'a>, RevlogError> { | |
84 | let rev = crate::revset::resolve_single(revset, repo)?; |
|
83 | let rev = crate::revset::resolve_single(revset, repo)?; | |
85 | let manifest = repo.manifest_for_rev(rev)?; |
|
84 | let manifest = repo.manifest_for_rev(rev)?; | |
86 | let node = *repo |
|
85 | let node = *repo | |
87 | .changelog()? |
|
86 | .changelog()? | |
88 | .node_from_rev(rev) |
|
87 | .node_from_rev(rev) | |
89 | .expect("should succeed when repo.manifest did"); |
|
88 | .expect("should succeed when repo.manifest did"); | |
90 |
let mut |
|
89 | let mut results: Vec<(&'a HgPath, Vec<u8>)> = vec![]; | |
91 | let mut found_any = false; |
|
90 | let mut found_any = false; | |
92 |
|
91 | |||
93 | files.sort_unstable(); |
|
92 | files.sort_unstable(); | |
94 |
|
93 | |||
95 | let (found, missing) = find_files_in_manifest( |
|
94 | let (found, missing) = find_files_in_manifest( | |
96 | manifest.files_with_nodes(), |
|
95 | manifest.files_with_nodes(), | |
97 | files.iter().map(|f| f.as_ref()), |
|
96 | files.into_iter().map(|f| f.as_ref()), | |
98 | ); |
|
97 | ); | |
99 |
|
98 | |||
100 |
for ( |
|
99 | for (file_path, node_bytes) in found { | |
101 | found_any = true; |
|
100 | found_any = true; | |
102 |
let file_log = repo.filelog( |
|
101 | let file_log = repo.filelog(file_path)?; | |
103 | let file_node = Node::from_hex_for_repo(node_bytes)?; |
|
102 | let file_node = Node::from_hex_for_repo(node_bytes)?; | |
104 | bytes.extend(file_log.data_for_node(file_node)?.data()?); |
|
103 | results.push(( | |
|
104 | file_path, | |||
|
105 | file_log.data_for_node(file_node)?.into_data()?, | |||
|
106 | )); | |||
105 | } |
|
107 | } | |
106 |
|
108 | |||
107 | let missing: Vec<HgPathBuf> = |
|
|||
108 | missing.iter().map(|file| (*file).to_owned()).collect(); |
|
|||
109 | Ok(CatOutput { |
|
109 | Ok(CatOutput { | |
110 | found_any, |
|
110 | found_any, | |
111 | concatenated: bytes, |
|
111 | results, | |
112 | missing, |
|
112 | missing, | |
113 | node, |
|
113 | node, | |
114 | }) |
|
114 | }) | |
115 | } |
|
115 | } |
@@ -1,79 +1,98 b'' | |||||
1 | use crate::errors::HgError; |
|
1 | use crate::errors::HgError; | |
2 | use crate::repo::Repo; |
|
2 | use crate::repo::Repo; | |
3 | use crate::revlog::path_encode::path_encode; |
|
3 | use crate::revlog::path_encode::path_encode; | |
4 | use crate::revlog::revlog::{Revlog, RevlogError}; |
|
4 | use crate::revlog::revlog::{Revlog, RevlogError}; | |
5 | use crate::revlog::NodePrefix; |
|
5 | use crate::revlog::NodePrefix; | |
6 | use crate::revlog::Revision; |
|
6 | use crate::revlog::Revision; | |
7 | use crate::utils::files::get_path_from_bytes; |
|
7 | use crate::utils::files::get_path_from_bytes; | |
8 | use crate::utils::hg_path::HgPath; |
|
8 | use crate::utils::hg_path::HgPath; | |
9 | use crate::utils::SliceExt; |
|
9 | use crate::utils::SliceExt; | |
10 | use std::borrow::Cow; |
|
|||
11 | use std::path::PathBuf; |
|
10 | use std::path::PathBuf; | |
12 |
|
11 | |||
13 | /// A specialized `Revlog` to work with file data logs. |
|
12 | /// A specialized `Revlog` to work with file data logs. | |
14 | pub struct Filelog { |
|
13 | pub struct Filelog { | |
15 | /// The generic `revlog` format. |
|
14 | /// The generic `revlog` format. | |
16 | revlog: Revlog, |
|
15 | revlog: Revlog, | |
17 | } |
|
16 | } | |
18 |
|
17 | |||
19 | impl Filelog { |
|
18 | impl Filelog { | |
20 | pub fn open(repo: &Repo, file_path: &HgPath) -> Result<Self, HgError> { |
|
19 | pub fn open(repo: &Repo, file_path: &HgPath) -> Result<Self, HgError> { | |
21 | let index_path = store_path(file_path, b".i"); |
|
20 | let index_path = store_path(file_path, b".i"); | |
22 | let data_path = store_path(file_path, b".d"); |
|
21 | let data_path = store_path(file_path, b".d"); | |
23 | let revlog = Revlog::open(repo, index_path, Some(&data_path))?; |
|
22 | let revlog = Revlog::open(repo, index_path, Some(&data_path))?; | |
24 | Ok(Self { revlog }) |
|
23 | Ok(Self { revlog }) | |
25 | } |
|
24 | } | |
26 |
|
25 | |||
27 | /// The given node ID is that of the file as found in a manifest, not of a |
|
26 | /// The given node ID is that of the file as found in a manifest, not of a | |
28 | /// changeset. |
|
27 | /// changeset. | |
29 | pub fn data_for_node( |
|
28 | pub fn data_for_node( | |
30 | &self, |
|
29 | &self, | |
31 | file_node: impl Into<NodePrefix>, |
|
30 | file_node: impl Into<NodePrefix>, | |
32 | ) -> Result<FilelogEntry, RevlogError> { |
|
31 | ) -> Result<FilelogEntry, RevlogError> { | |
33 | let file_rev = self.revlog.rev_from_node(file_node.into())?; |
|
32 | let file_rev = self.revlog.rev_from_node(file_node.into())?; | |
34 | self.data_for_rev(file_rev) |
|
33 | self.data_for_rev(file_rev) | |
35 | } |
|
34 | } | |
36 |
|
35 | |||
37 | /// The given revision is that of the file as found in a manifest, not of a |
|
36 | /// The given revision is that of the file as found in a manifest, not of a | |
38 | /// changeset. |
|
37 | /// changeset. | |
39 | pub fn data_for_rev( |
|
38 | pub fn data_for_rev( | |
40 | &self, |
|
39 | &self, | |
41 | file_rev: Revision, |
|
40 | file_rev: Revision, | |
42 | ) -> Result<FilelogEntry, RevlogError> { |
|
41 | ) -> Result<FilelogEntry, RevlogError> { | |
43 | let data = self.revlog.get_rev_data(file_rev)?; |
|
42 | let data: Vec<u8> = self.revlog.get_rev_data(file_rev)?; | |
44 | Ok(FilelogEntry(data.into())) |
|
43 | Ok(FilelogEntry(data.into())) | |
45 | } |
|
44 | } | |
46 | } |
|
45 | } | |
47 |
|
46 | |||
48 | fn store_path(hg_path: &HgPath, suffix: &[u8]) -> PathBuf { |
|
47 | fn store_path(hg_path: &HgPath, suffix: &[u8]) -> PathBuf { | |
49 | let encoded_bytes = |
|
48 | let encoded_bytes = | |
50 | path_encode(&[b"data/", hg_path.as_bytes(), suffix].concat()); |
|
49 | path_encode(&[b"data/", hg_path.as_bytes(), suffix].concat()); | |
51 | get_path_from_bytes(&encoded_bytes).into() |
|
50 | get_path_from_bytes(&encoded_bytes).into() | |
52 | } |
|
51 | } | |
53 |
|
52 | |||
54 |
pub struct FilelogEntry< |
|
53 | pub struct FilelogEntry(Vec<u8>); | |
55 |
|
54 | |||
56 |
impl |
|
55 | impl FilelogEntry { | |
57 | /// Split into metadata and data |
|
56 | /// Split into metadata and data | |
58 | pub fn split(&self) -> Result<(Option<&[u8]>, &[u8]), HgError> { |
|
57 | /// Returns None if there is no metadata, so the entire entry is data. | |
|
58 | fn split_metadata(&self) -> Result<Option<(&[u8], &[u8])>, HgError> { | |||
59 | const DELIMITER: &[u8; 2] = &[b'\x01', b'\n']; |
|
59 | const DELIMITER: &[u8; 2] = &[b'\x01', b'\n']; | |
60 |
|
60 | |||
61 | if let Some(rest) = self.0.drop_prefix(DELIMITER) { |
|
61 | if let Some(rest) = self.0.drop_prefix(DELIMITER) { | |
62 | if let Some((metadata, data)) = rest.split_2_by_slice(DELIMITER) { |
|
62 | if let Some((metadata, data)) = rest.split_2_by_slice(DELIMITER) { | |
63 |
Ok( |
|
63 | Ok(Some((metadata, data))) | |
64 | } else { |
|
64 | } else { | |
65 | Err(HgError::corrupted( |
|
65 | Err(HgError::corrupted( | |
66 | "Missing metadata end delimiter in filelog entry", |
|
66 | "Missing metadata end delimiter in filelog entry", | |
67 | )) |
|
67 | )) | |
68 | } |
|
68 | } | |
69 | } else { |
|
69 | } else { | |
|
70 | Ok(None) | |||
|
71 | } | |||
|
72 | } | |||
|
73 | ||||
|
74 | /// Split into metadata and data | |||
|
75 | pub fn split(&self) -> Result<(Option<&[u8]>, &[u8]), HgError> { | |||
|
76 | if let Some((metadata, data)) = self.split_metadata()? { | |||
|
77 | Ok((Some(metadata), data)) | |||
|
78 | } else { | |||
70 | Ok((None, &self.0)) |
|
79 | Ok((None, &self.0)) | |
71 | } |
|
80 | } | |
72 | } |
|
81 | } | |
73 |
|
82 | |||
74 | /// Returns the file contents at this revision, stripped of any metadata |
|
83 | /// Returns the file contents at this revision, stripped of any metadata | |
75 | pub fn data(&self) -> Result<&[u8], HgError> { |
|
84 | pub fn data(&self) -> Result<&[u8], HgError> { | |
76 | let (_metadata, data) = self.split()?; |
|
85 | let (_metadata, data) = self.split()?; | |
77 | Ok(data) |
|
86 | Ok(data) | |
78 | } |
|
87 | } | |
|
88 | ||||
|
89 | /// Consume the entry, and convert it into data, discarding any metadata, | |||
|
90 | /// if present. | |||
|
91 | pub fn into_data(self) -> Result<Vec<u8>, HgError> { | |||
|
92 | if let Some((_metadata, data)) = self.split_metadata()? { | |||
|
93 | Ok(data.to_owned()) | |||
|
94 | } else { | |||
|
95 | Ok(self.0) | |||
|
96 | } | |||
|
97 | } | |||
79 | } |
|
98 | } |
@@ -1,93 +1,96 b'' | |||||
1 | use crate::error::CommandError; |
|
1 | use crate::error::CommandError; | |
2 | use clap::Arg; |
|
2 | use clap::Arg; | |
3 | use format_bytes::format_bytes; |
|
3 | use format_bytes::format_bytes; | |
4 | use hg::operations::cat; |
|
4 | use hg::operations::cat; | |
5 | use hg::utils::hg_path::HgPathBuf; |
|
5 | use hg::utils::hg_path::HgPathBuf; | |
6 | use micro_timer::timed; |
|
6 | use micro_timer::timed; | |
7 | use std::convert::TryFrom; |
|
7 | use std::convert::TryFrom; | |
8 |
|
8 | |||
9 | pub const HELP_TEXT: &str = " |
|
9 | pub const HELP_TEXT: &str = " | |
10 | Output the current or given revision of files |
|
10 | Output the current or given revision of files | |
11 | "; |
|
11 | "; | |
12 |
|
12 | |||
13 | pub fn args() -> clap::App<'static, 'static> { |
|
13 | pub fn args() -> clap::App<'static, 'static> { | |
14 | clap::SubCommand::with_name("cat") |
|
14 | clap::SubCommand::with_name("cat") | |
15 | .arg( |
|
15 | .arg( | |
16 | Arg::with_name("rev") |
|
16 | Arg::with_name("rev") | |
17 | .help("search the repository as it is in REV") |
|
17 | .help("search the repository as it is in REV") | |
18 | .short("-r") |
|
18 | .short("-r") | |
19 | .long("--rev") |
|
19 | .long("--rev") | |
20 | .value_name("REV") |
|
20 | .value_name("REV") | |
21 | .takes_value(true), |
|
21 | .takes_value(true), | |
22 | ) |
|
22 | ) | |
23 | .arg( |
|
23 | .arg( | |
24 | clap::Arg::with_name("files") |
|
24 | clap::Arg::with_name("files") | |
25 | .required(true) |
|
25 | .required(true) | |
26 | .multiple(true) |
|
26 | .multiple(true) | |
27 | .empty_values(false) |
|
27 | .empty_values(false) | |
28 | .value_name("FILE") |
|
28 | .value_name("FILE") | |
29 | .help("Files to output"), |
|
29 | .help("Files to output"), | |
30 | ) |
|
30 | ) | |
31 | .about(HELP_TEXT) |
|
31 | .about(HELP_TEXT) | |
32 | } |
|
32 | } | |
33 |
|
33 | |||
34 | #[timed] |
|
34 | #[timed] | |
35 | pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> { |
|
35 | pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> { | |
36 | let rev = invocation.subcommand_args.value_of("rev"); |
|
36 | let rev = invocation.subcommand_args.value_of("rev"); | |
37 | let file_args = match invocation.subcommand_args.values_of("files") { |
|
37 | let file_args = match invocation.subcommand_args.values_of("files") { | |
38 | Some(files) => files.collect(), |
|
38 | Some(files) => files.collect(), | |
39 | None => vec![], |
|
39 | None => vec![], | |
40 | }; |
|
40 | }; | |
41 |
|
41 | |||
42 | let repo = invocation.repo?; |
|
42 | let repo = invocation.repo?; | |
43 | let cwd = hg::utils::current_dir()?; |
|
43 | let cwd = hg::utils::current_dir()?; | |
44 | let working_directory = repo.working_directory_path(); |
|
44 | let working_directory = repo.working_directory_path(); | |
45 | let working_directory = cwd.join(working_directory); // Make it absolute |
|
45 | let working_directory = cwd.join(working_directory); // Make it absolute | |
46 |
|
46 | |||
47 | let mut files = vec![]; |
|
47 | let mut files = vec![]; | |
48 | for file in file_args.iter() { |
|
48 | for file in file_args.iter() { | |
49 | if file.starts_with("set:") { |
|
49 | if file.starts_with("set:") { | |
50 | let message = "fileset"; |
|
50 | let message = "fileset"; | |
51 | return Err(CommandError::unsupported(message)); |
|
51 | return Err(CommandError::unsupported(message)); | |
52 | } |
|
52 | } | |
53 |
|
53 | |||
54 | let normalized = cwd.join(&file); |
|
54 | let normalized = cwd.join(&file); | |
55 | // TODO: actually normalize `..` path segments etc? |
|
55 | // TODO: actually normalize `..` path segments etc? | |
56 | let dotted = normalized.components().any(|c| c.as_os_str() == ".."); |
|
56 | let dotted = normalized.components().any(|c| c.as_os_str() == ".."); | |
57 | if file == &"." || dotted { |
|
57 | if file == &"." || dotted { | |
58 | let message = "`..` or `.` path segment"; |
|
58 | let message = "`..` or `.` path segment"; | |
59 | return Err(CommandError::unsupported(message)); |
|
59 | return Err(CommandError::unsupported(message)); | |
60 | } |
|
60 | } | |
61 | let stripped = normalized |
|
61 | let stripped = normalized | |
62 | .strip_prefix(&working_directory) |
|
62 | .strip_prefix(&working_directory) | |
63 | // TODO: error message for path arguments outside of the repo |
|
63 | // TODO: error message for path arguments outside of the repo | |
64 | .map_err(|_| CommandError::abort(""))?; |
|
64 | .map_err(|_| CommandError::abort(""))?; | |
65 | let hg_file = HgPathBuf::try_from(stripped.to_path_buf()) |
|
65 | let hg_file = HgPathBuf::try_from(stripped.to_path_buf()) | |
66 | .map_err(|e| CommandError::abort(e.to_string()))?; |
|
66 | .map_err(|e| CommandError::abort(e.to_string()))?; | |
67 | files.push(hg_file); |
|
67 | files.push(hg_file); | |
68 | } |
|
68 | } | |
|
69 | let files = files.iter().map(|file| file.as_ref()).collect(); | |||
69 | // TODO probably move this to a util function like `repo.default_rev` or |
|
70 | // TODO probably move this to a util function like `repo.default_rev` or | |
70 | // something when it's used somewhere else |
|
71 | // something when it's used somewhere else | |
71 | let rev = match rev { |
|
72 | let rev = match rev { | |
72 | Some(r) => r.to_string(), |
|
73 | Some(r) => r.to_string(), | |
73 | None => format!("{:x}", repo.dirstate_parents()?.p1), |
|
74 | None => format!("{:x}", repo.dirstate_parents()?.p1), | |
74 | }; |
|
75 | }; | |
75 |
|
76 | |||
76 | let output = cat(&repo, &rev, files).map_err(|e| (e, rev.as_str()))?; |
|
77 | let output = cat(&repo, &rev, files).map_err(|e| (e, rev.as_str()))?; | |
77 | invocation.ui.write_stdout(&output.concatenated)?; |
|
78 | for (_file, contents) in output.results { | |
|
79 | invocation.ui.write_stdout(&contents)?; | |||
|
80 | } | |||
78 | if !output.missing.is_empty() { |
|
81 | if !output.missing.is_empty() { | |
79 | let short = format!("{:x}", output.node.short()).into_bytes(); |
|
82 | let short = format!("{:x}", output.node.short()).into_bytes(); | |
80 | for path in &output.missing { |
|
83 | for path in &output.missing { | |
81 | invocation.ui.write_stderr(&format_bytes!( |
|
84 | invocation.ui.write_stderr(&format_bytes!( | |
82 | b"{}: no such file in rev {}\n", |
|
85 | b"{}: no such file in rev {}\n", | |
83 | path.as_bytes(), |
|
86 | path.as_bytes(), | |
84 | short |
|
87 | short | |
85 | ))?; |
|
88 | ))?; | |
86 | } |
|
89 | } | |
87 | } |
|
90 | } | |
88 | if output.found_any { |
|
91 | if output.found_any { | |
89 | Ok(()) |
|
92 | Ok(()) | |
90 | } else { |
|
93 | } else { | |
91 | Err(CommandError::Unsuccessful) |
|
94 | Err(CommandError::Unsuccessful) | |
92 | } |
|
95 | } | |
93 | } |
|
96 | } |
General Comments 0
You need to be logged in to leave comments.
Login now