##// END OF EJS Templates
copies-rust: use immutable "OrdMap" to store copies information...
copies-rust: use immutable "OrdMap" to store copies information The large majority of time is currently spent coping and merging directories. the `IM` crate offer "immutable" Map, that use "copy on write" internally. The new object use the same API as the standard HashMap. So switching to it is trivial and it reduce copying cost significantly. More importantly, using immutable structure will unlock new possibility for a massive speed up of the "merging" part. This will came in a later changesets. Performance wise, we get very significant boost in the worst case. Below is some highlight of how we fare compared to the previous changeset. Repo Cases Source-Rev Dest-Rev Old-Time New-Time Difference Factor ------------------------------------------------------------------------------------------------------------------------------------ pypy x0000_revs_x_added_0_copies d1defd0dc478 c9cb1334cc78 : 62.468362 s, 33.527067 s, -28.941295 s, × 0.5367 mozilla-central x000_revs_x000_added_x000_copies 7c97034feb78 4407bd0c6330 : 3.619850 s, 0.963905 s, -2.655945 s, × 0.2663 mozilla-central x0000_revs_xx000_added_x000_copies f78c615a656c 96a38b690156 : 11.926587 s, 4.217003 s, -7.709584 s, × 0.3536 mozilla-try x0000_revs_x_added_0_copies 63519bfd42ee a36a2a865d92 : 10.674920 s, 1.114864 s, -9.560056 s, × 0.1044 mozilla-try x00000_revs_x00000_added_0_copies dc8a3ca7010e d16fde900c9c : 19.647038 s, 1.442793 s, -18.204245 s, × 0.0734 And we sometimes catch up with the performance of the python code as highlighted below: Repo Cases Source-Rev Dest-Rev Py-time Rust-time Difference Factor ------------------------------------------------------------------------------------------------------------------------------------ mozilla-try x00000_revs_x00000_added_0_copies dc8a3ca7010e d16fde900c9c : 1.074593 s, 1.442793 s, +0.368200 s, × 1.3426 However, multiple case remains significantly slower, as highlighted below Repo Cases Source-Rev Dest-Rev Py-time Rust-time Difference Factor ------------------------------------------------------------------------------------------------------------------------------------ mozilla-central x000_revs_x000_added_x000_copies 7c97034feb78 4407bd0c6330 : 0.190133 s, 0.963905 s, +0.773772 s, × 5.0696 mozilla-central x0000_revs_xx000_added_x000_copies f78c615a656c 96a38b690156 : 0.440694 s, 4.217003 s, +3.776309 s, × 9.5690 mozilla-try x0000_revs_x_added_0_copies 63519bfd42ee a36a2a865d92 : 0.370675 s, 1.114864 s, +0.744189 s, × 3.0077 pypy x0000_revs_x_added_0_copies d1defd0dc478 c9cb1334cc78 : 3.581556 s, 33.527067 s, +29.945511 s, × 9.3610 Below are two different tables for full performance comparison - this changeset against the previous one (spoiler: it is much better) - this changeset against the python code (spoiler: still slower, but it gets more comparable) This changeset compared to the previous one =========================================== Repo Cases Source-Rev Dest-Rev Old-Time New-Time Difference Factor ------------------------------------------------------------------------------------------------------------------------------------ mercurial x_revs_x_added_0_copies ad6b123de1c7 39cfcef4f463 : 0.000046 s, 0.000049 s, +0.000003 s, × 1.0652 mercurial x_revs_x_added_x_copies 2b1c78674230 0c1d10351869 : 0.000173 s, 0.000179 s, +0.000006 s, × 1.0347 mercurial x000_revs_x000_added_x_copies 81f8ff2a9bf2 dd3267698d84 : 0.006303 s, 0.006494 s, +0.000191 s, × 1.0303 pypy x_revs_x_added_0_copies aed021ee8ae8 099ed31b181b : 0.000229 s, 0.000339 s, +0.000110 s, × 1.4803 pypy x_revs_x000_added_0_copies 4aa4e1f8e19a 359343b9ac0e : 0.000056 s, 0.000057 s, +0.000001 s, × 1.0179 pypy x_revs_x_added_x_copies ac52eb7bbbb0 72e022663155 : 0.000143 s, 0.000299 s, +0.000156 s, × 2.0909 pypy x_revs_x00_added_x_copies c3b14617fbd7 ace7255d9a26 : 0.001166 s, 0.001200 s, +0.000034 s, × 1.0292 pypy x_revs_x000_added_x000_copies df6f7a526b60 a83dc6a2d56f : 0.022931 s, 0.025120 s, +0.002189 s, × 1.0955 pypy x000_revs_xx00_added_0_copies 89a76aede314 2f22446ff07e : 0.852446 s, 0.506921 s, -0.345525 s, × 0.5947 pypy x000_revs_x000_added_x_copies 8a3b5bfd266e 2c68e87c3efe : 2.221824 s, 1.272060 s, -0.949764 s, × 0.5725 pypy x000_revs_x000_added_x000_copies 89a76aede314 7b3dda341c84 : 1.194162 s, 0.690941 s, -0.503221 s, × 0.5786 pypy x0000_revs_x_added_0_copies d1defd0dc478 c9cb1334cc78 : 62.468362 s, 33.527067 s, -28.941295 s, × 0.5367 pypy x0000_revs_xx000_added_0_copies bf2c629d0071 4ffed77c095c : 0.022116 s, 0.021970 s, -0.000146 s, × 0.9934 pypy x0000_revs_xx000_added_x000_copies 08ea3258278e d9fa043f30c0 : 2.972788 s, 1.772094 s, -1.200694 s, × 0.5961 netbeans x_revs_x_added_0_copies fb0955ffcbcd a01e9239f9e7 : 0.000180 s, 0.000185 s, +0.000005 s, × 1.0278 netbeans x_revs_x000_added_0_copies 6f360122949f 20eb231cc7d0 : 0.000123 s, 0.000135 s, +0.000012 s, × 1.0976 netbeans x_revs_x_added_x_copies 1ada3faf6fb6 5a39d12eecf4 : 0.000315 s, 0.000329 s, +0.000014 s, × 1.0444 netbeans x_revs_x00_added_x_copies 35be93ba1e2c 9eec5e90c05f : 0.001297 s, 0.001343 s, +0.000046 s, × 1.0355 netbeans x000_revs_xx00_added_0_copies eac3045b4fdd 51d4ae7f1290 : 0.024884 s, 0.029396 s, +0.004512 s, × 1.1813 netbeans x000_revs_x000_added_x_copies e2063d266acd 6081d72689dc : 0.032653 s, 0.040210 s, +0.007557 s, × 1.2314 netbeans x000_revs_x000_added_x000_copies ff453e9fee32 411350406ec2 : 4.230118 s, 4.556794 s, +0.326676 s, × 1.0772 mozilla-central x_revs_x_added_0_copies 3697f962bb7b 7015fcdd43a2 : 0.000197 s, 0.000199 s, +0.000002 s, × 1.0102 mozilla-central x_revs_x000_added_0_copies dd390860c6c9 40d0c5bed75d : 0.000622 s, 0.000639 s, +0.000017 s, × 1.0273 mozilla-central x_revs_x_added_x_copies 8d198483ae3b 14207ffc2b2f : 0.000296 s, 0.000542 s, +0.000246 s, × 1.8311 mozilla-central x_revs_x00_added_x_copies 98cbc58cc6bc 446a150332c3 : 0.001626 s, 0.001685 s, +0.000059 s, × 1.0363 mozilla-central x_revs_x000_added_x000_copies 3c684b4b8f68 0a5e72d1b479 : 0.006218 s, 0.006954 s, +0.000736 s, × 1.1184 mozilla-central x_revs_x0000_added_x0000_copies effb563bb7e5 c07a39dc4e80 : 0.132760 s, 0.132938 s, +0.000178 s, × 1.0013 mozilla-central x000_revs_xx00_added_0_copies 6100d773079a 04a55431795e : 0.029001 s, 0.008683 s, -0.020318 s, × 0.2994 mozilla-central x000_revs_x000_added_x_copies 9f17a6fc04f9 2d37b966abed : 0.005886 s, 0.005956 s, +0.000070 s, × 1.0119 mozilla-central x000_revs_x000_added_x000_copies 7c97034feb78 4407bd0c6330 : 3.619850 s, 0.963905 s, -2.655945 s, × 0.2663 mozilla-central x0000_revs_xx000_added_0_copies 9eec5917337d 67118cc6dcad : 0.058678 s, 0.049239 s, -0.009439 s, × 0.8391 mozilla-central x0000_revs_xx000_added_x000_copies f78c615a656c 96a38b690156 : 11.926587 s, 4.217003 s, -7.709584 s, × 0.3536 mozilla-try x_revs_x_added_0_copies aaf6dde0deb8 9790f499805a : 0.001204 s, 0.001197 s, -0.000007 s, × 0.9942 mozilla-try x_revs_x000_added_0_copies d8d0222927b4 5bb8ce8c7450 : 0.001217 s, 0.001213 s, -0.000004 s, × 0.9967 mozilla-try x_revs_x_added_x_copies 092fcca11bdb 936255a0384a : 0.000605 s, 0.000762 s, +0.000157 s, × 1.2595 mozilla-try x_revs_x00_added_x_copies b53d2fadbdb5 017afae788ec : 0.001876 s, 0.001909 s, +0.000033 s, × 1.0176 mozilla-try x_revs_x000_added_x000_copies 20408ad61ce5 6f0ee96e21ad : 0.078190 s, 0.093021 s, +0.014831 s, × 1.1897 mozilla-try x_revs_x0000_added_x0000_copies effb563bb7e5 c07a39dc4e80 : 0.135428 s, 0.134536 s, -0.000892 s, × 0.9934 mozilla-try x000_revs_xx00_added_0_copies 6100d773079a 04a55431795e : 0.029123 s, 0.009071 s, -0.020052 s, × 0.3115 mozilla-try x000_revs_x000_added_x_copies 9f17a6fc04f9 2d37b966abed : 0.006141 s, 0.006206 s, +0.000065 s, × 1.0106 mozilla-try x000_revs_x000_added_x000_copies 1346fd0130e4 4c65cbdabc1f : 4.857827 s, 1.150502 s, -3.707325 s, × 0.2368 mozilla-try x0000_revs_x_added_0_copies 63519bfd42ee a36a2a865d92 : 10.674920 s, 1.114864 s, -9.560056 s, × 0.1044 mozilla-try x0000_revs_x_added_x_copies 9fe69ff0762d bcabf2a78927 : 9.789462 s, 1.042658 s, -8.746804 s, × 0.1065 mozilla-try x0000_revs_xx000_added_x_copies 156f6e2674f2 4d0f2c178e66 : 1.087890 s, 0.447402 s, -0.640488 s, × 0.4113 mozilla-try x0000_revs_xx000_added_0_copies 9eec5917337d 67118cc6dcad : 0.060556 s, 0.051132 s, -0.009424 s, × 0.8444 mozilla-try x0000_revs_xx000_added_x000_copies 89294cd501d9 7ccb2fc7ccb5 : killed , 83.508590 s mozilla-try x0000_revs_x0000_added_x0000_copies e928c65095ed e951f4ad123a : killed , 55.079813 s mozilla-try x00000_revs_x00000_added_0_copies dc8a3ca7010e d16fde900c9c : 19.647038 s, 1.442793 s, -18.204245 s, × 0.0734 This changeset compared to the Python Code ========================================== Repo Cases Source-Rev Dest-Rev Py-time Rust-time Difference Factor ------------------------------------------------------------------------------------------------------------------------------------ mercurial x_revs_x_added_0_copies ad6b123de1c7 39cfcef4f463 : 0.000044 s, 0.000049 s, +0.000005 s, × 1.1136 mercurial x_revs_x_added_x_copies 2b1c78674230 0c1d10351869 : 0.000138 s, 0.000179 s, +0.000041 s, × 1.2971 mercurial x000_revs_x000_added_x_copies 81f8ff2a9bf2 dd3267698d84 : 0.005052 s, 0.006494 s, +0.001442 s, × 1.2854 pypy x_revs_x_added_0_copies aed021ee8ae8 099ed31b181b : 0.000219 s, 0.000339 s, +0.000120 s, × 1.5479 pypy x_revs_x000_added_0_copies 4aa4e1f8e19a 359343b9ac0e : 0.000055 s, 0.000057 s, +0.000002 s, × 1.0364 pypy x_revs_x_added_x_copies ac52eb7bbbb0 72e022663155 : 0.000128 s, 0.000299 s, +0.000171 s, × 2.3359 pypy x_revs_x00_added_x_copies c3b14617fbd7 ace7255d9a26 : 0.001089 s, 0.001200 s, +0.000111 s, × 1.1019 pypy x_revs_x000_added_x000_copies df6f7a526b60 a83dc6a2d56f : 0.017407 s, 0.025120 s, +0.007713 s, × 1.4431 pypy x000_revs_xx00_added_0_copies 89a76aede314 2f22446ff07e : 0.094175 s, 0.506921 s, +0.412746 s, × 5.3828 pypy x000_revs_x000_added_x_copies 8a3b5bfd266e 2c68e87c3efe : 0.238009 s, 1.272060 s, +1.034051 s, × 5.3446 pypy x000_revs_x000_added_x000_copies 89a76aede314 7b3dda341c84 : 0.125876 s, 0.690941 s, +0.565065 s, × 5.4891 pypy x0000_revs_x_added_0_copies d1defd0dc478 c9cb1334cc78 : 3.581556 s, 33.527067 s, +29.945511 s, × 9.3610 pypy x0000_revs_xx000_added_0_copies bf2c629d0071 4ffed77c095c : 0.016721 s, 0.021970 s, +0.005249 s, × 1.3139 pypy x0000_revs_xx000_added_x000_copies 08ea3258278e d9fa043f30c0 : 0.242367 s, 1.772094 s, +1.529727 s, × 7.3116 netbeans x_revs_x_added_0_copies fb0955ffcbcd a01e9239f9e7 : 0.000165 s, 0.000185 s, +0.000020 s, × 1.1212 netbeans x_revs_x000_added_0_copies 6f360122949f 20eb231cc7d0 : 0.000114 s, 0.000135 s, +0.000021 s, × 1.1842 netbeans x_revs_x_added_x_copies 1ada3faf6fb6 5a39d12eecf4 : 0.000296 s, 0.000329 s, +0.000033 s, × 1.1115 netbeans x_revs_x00_added_x_copies 35be93ba1e2c 9eec5e90c05f : 0.001124 s, 0.001343 s, +0.000219 s, × 1.1948 netbeans x000_revs_xx00_added_0_copies eac3045b4fdd 51d4ae7f1290 : 0.013060 s, 0.029396 s, +0.016336 s, × 2.2508 netbeans x000_revs_x000_added_x_copies e2063d266acd 6081d72689dc : 0.017112 s, 0.040210 s, +0.023098 s, × 2.3498 netbeans x000_revs_x000_added_x000_copies ff453e9fee32 411350406ec2 : 0.660350 s, 4.556794 s, +3.896444 s, × 6.9006 netbeans x0000_revs_xx000_added_x000_copies 588c2d1ced70 1aad62e59ddd : 10.032499 s, killed mozilla-central x_revs_x_added_0_copies 3697f962bb7b 7015fcdd43a2 : 0.000189 s, 0.000199 s, +0.000010 s, × 1.0529 mozilla-central x_revs_x000_added_0_copies dd390860c6c9 40d0c5bed75d : 0.000462 s, 0.000639 s, +0.000177 s, × 1.3831 mozilla-central x_revs_x_added_x_copies 8d198483ae3b 14207ffc2b2f : 0.000270 s, 0.000542 s, +0.000272 s, × 2.0074 mozilla-central x_revs_x00_added_x_copies 98cbc58cc6bc 446a150332c3 : 0.001474 s, 0.001685 s, +0.000211 s, × 1.1431 mozilla-central x_revs_x000_added_x000_copies 3c684b4b8f68 0a5e72d1b479 : 0.004806 s, 0.006954 s, +0.002148 s, × 1.4469 mozilla-central x_revs_x0000_added_x0000_copies effb563bb7e5 c07a39dc4e80 : 0.085150 s, 0.132938 s, +0.047788 s, × 1.5612 mozilla-central x000_revs_xx00_added_0_copies 6100d773079a 04a55431795e : 0.007064 s, 0.008683 s, +0.001619 s, × 1.2292 mozilla-central x000_revs_x000_added_x_copies 9f17a6fc04f9 2d37b966abed : 0.004741 s, 0.005956 s, +0.001215 s, × 1.2563 mozilla-central x000_revs_x000_added_x000_copies 7c97034feb78 4407bd0c6330 : 0.190133 s, 0.963905 s, +0.773772 s, × 5.0696 mozilla-central x0000_revs_xx000_added_0_copies 9eec5917337d 67118cc6dcad : 0.035651 s, 0.049239 s, +0.013588 s, × 1.3811 mozilla-central x0000_revs_xx000_added_x000_copies f78c615a656c 96a38b690156 : 0.440694 s, 4.217003 s, +3.776309 s, × 9.5690 mozilla-central x00000_revs_x0000_added_x0000_copies 6832ae71433c 4c222a1d9a00 : 18.454163 s, killed mozilla-central x00000_revs_x00000_added_x000_copies 76caed42cf7c 1daa622bbe42 : 31.562719 s, killed mozilla-try x_revs_x_added_0_copies aaf6dde0deb8 9790f499805a : 0.001189 s, 0.001197 s, +0.000008 s, × 1.0067 mozilla-try x_revs_x000_added_0_copies d8d0222927b4 5bb8ce8c7450 : 0.001204 s, 0.001213 s, +0.000009 s, × 1.0075 mozilla-try x_revs_x_added_x_copies 092fcca11bdb 936255a0384a : 0.000586 s, 0.000762 s, +0.000176 s, × 1.3003 mozilla-try x_revs_x00_added_x_copies b53d2fadbdb5 017afae788ec : 0.001845 s, 0.001909 s, +0.000064 s, × 1.0347 mozilla-try x_revs_x000_added_x000_copies 20408ad61ce5 6f0ee96e21ad : 0.063822 s, 0.093021 s, +0.029199 s, × 1.4575 mozilla-try x_revs_x0000_added_x0000_copies effb563bb7e5 c07a39dc4e80 : 0.088038 s, 0.134536 s, +0.046498 s, × 1.5282 mozilla-try x000_revs_xx00_added_0_copies 6100d773079a 04a55431795e : 0.007389 s, 0.009071 s, +0.001682 s, × 1.2276 mozilla-try x000_revs_x000_added_x_copies 9f17a6fc04f9 2d37b966abed : 0.004868 s, 0.006206 s, +0.001338 s, × 1.2749 mozilla-try x000_revs_x000_added_x000_copies 1346fd0130e4 4c65cbdabc1f : 0.222450 s, 1.150502 s, +0.928052 s, × 5.1720 mozilla-try x0000_revs_x_added_0_copies 63519bfd42ee a36a2a865d92 : 0.370675 s, 1.114864 s, +0.744189 s, × 3.0077 mozilla-try x0000_revs_x_added_x_copies 9fe69ff0762d bcabf2a78927 : 0.358020 s, 1.042658 s, +0.684638 s, × 2.9123 mozilla-try x0000_revs_xx000_added_x_copies 156f6e2674f2 4d0f2c178e66 : 0.145235 s, 0.447402 s, +0.302167 s, × 3.0805 mozilla-try x0000_revs_xx000_added_0_copies 9eec5917337d 67118cc6dcad : 0.037606 s, 0.051132 s, +0.013526 s, × 1.3597 mozilla-try x0000_revs_xx000_added_x000_copies 89294cd501d9 7ccb2fc7ccb5 : 7.382439 s, 83.508590 s, +76.126151 s, × 11.3118 mozilla-try x0000_revs_x0000_added_x0000_copies e928c65095ed e951f4ad123a : 7.273506 s, 55.079813 s, +47.806307 s, × 7.5727 mozilla-try x00000_revs_x00000_added_0_copies dc8a3ca7010e d16fde900c9c : 1.074593 s, 1.442793 s, +0.368200 s, × 1.3426 mozilla-try x00000_revs_x0000_added_x0000_copies 8d3fafa80d4b eb884023b810 : 27.746195 s, killed Differential Revision: https://phab.mercurial-scm.org/D9300

File last commit:

r46109:2d5dfc8f default
r46577:0d99778a default
Show More
hg_path.rs
773 lines | 24.2 KiB | application/rls-services+xml | RustLexer
// hg_path.rs
//
// Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
//
// This software may be used and distributed according to the terms of the
// GNU General Public License version 2 or any later version.
use std::borrow::Borrow;
use std::convert::TryFrom;
use std::ffi::{OsStr, OsString};
use std::fmt;
use std::ops::Deref;
use std::path::{Path, PathBuf};
#[derive(Debug, Eq, PartialEq)]
pub enum HgPathError {
/// Bytes from the invalid `HgPath`
LeadingSlash(Vec<u8>),
ConsecutiveSlashes {
bytes: Vec<u8>,
second_slash_index: usize,
},
ContainsNullByte {
bytes: Vec<u8>,
null_byte_index: usize,
},
/// Bytes
DecodeError(Vec<u8>),
/// The rest come from audit errors
EndsWithSlash(HgPathBuf),
ContainsIllegalComponent(HgPathBuf),
/// Path is inside the `.hg` folder
InsideDotHg(HgPathBuf),
IsInsideNestedRepo {
path: HgPathBuf,
nested_repo: HgPathBuf,
},
TraversesSymbolicLink {
path: HgPathBuf,
symlink: HgPathBuf,
},
NotFsCompliant(HgPathBuf),
/// `path` is the smallest invalid path
NotUnderRoot {
path: PathBuf,
root: PathBuf,
},
}
impl ToString for HgPathError {
fn to_string(&self) -> String {
match self {
HgPathError::LeadingSlash(bytes) => {
format!("Invalid HgPath '{:?}': has a leading slash.", bytes)
}
HgPathError::ConsecutiveSlashes {
bytes,
second_slash_index: pos,
} => format!(
"Invalid HgPath '{:?}': consecutive slashes at pos {}.",
bytes, pos
),
HgPathError::ContainsNullByte {
bytes,
null_byte_index: pos,
} => format!(
"Invalid HgPath '{:?}': contains null byte at pos {}.",
bytes, pos
),
HgPathError::DecodeError(bytes) => {
format!("Invalid HgPath '{:?}': could not be decoded.", bytes)
}
HgPathError::EndsWithSlash(path) => {
format!("Audit failed for '{}': ends with a slash.", path)
}
HgPathError::ContainsIllegalComponent(path) => format!(
"Audit failed for '{}': contains an illegal component.",
path
),
HgPathError::InsideDotHg(path) => format!(
"Audit failed for '{}': is inside the '.hg' folder.",
path
),
HgPathError::IsInsideNestedRepo {
path,
nested_repo: nested,
} => format!(
"Audit failed for '{}': is inside a nested repository '{}'.",
path, nested
),
HgPathError::TraversesSymbolicLink { path, symlink } => format!(
"Audit failed for '{}': traverses symbolic link '{}'.",
path, symlink
),
HgPathError::NotFsCompliant(path) => format!(
"Audit failed for '{}': cannot be turned into a \
filesystem path.",
path
),
HgPathError::NotUnderRoot { path, root } => format!(
"Audit failed for '{}': not under root {}.",
path.display(),
root.display()
),
}
}
}
impl From<HgPathError> for std::io::Error {
fn from(e: HgPathError) -> Self {
std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())
}
}
/// This is a repository-relative path (or canonical path):
/// - no null characters
/// - `/` separates directories
/// - no consecutive slashes
/// - no leading slash,
/// - no `.` nor `..` of special meaning
/// - stored in repository and shared across platforms
///
/// Note: there is no guarantee of any `HgPath` being well-formed at any point
/// in its lifetime for performance reasons and to ease ergonomics. It is
/// however checked using the `check_state` method before any file-system
/// operation.
///
/// This allows us to be encoding-transparent as much as possible, until really
/// needed; `HgPath` can be transformed into a platform-specific path (`OsStr`
/// or `Path`) whenever more complex operations are needed:
/// On Unix, it's just byte-to-byte conversion. On Windows, it has to be
/// decoded from MBCS to WTF-8. If WindowsUTF8Plan is implemented, the source
/// character encoding will be determined on a per-repository basis.
//
// FIXME: (adapted from a comment in the stdlib)
// `HgPath::new()` current implementation relies on `Slice` being
// layout-compatible with `[u8]`.
// When attribute privacy is implemented, `Slice` should be annotated as
// `#[repr(transparent)]`.
// Anyway, `Slice` representation and layout are considered implementation
// detail, are not documented and must not be relied upon.
#[derive(Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct HgPath {
inner: [u8],
}
impl HgPath {
pub fn new<S: AsRef<[u8]> + ?Sized>(s: &S) -> &Self {
unsafe { &*(s.as_ref() as *const [u8] as *const Self) }
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn len(&self) -> usize {
self.inner.len()
}
fn to_hg_path_buf(&self) -> HgPathBuf {
HgPathBuf {
inner: self.inner.to_owned(),
}
}
pub fn bytes(&self) -> std::slice::Iter<u8> {
self.inner.iter()
}
pub fn to_ascii_uppercase(&self) -> HgPathBuf {
HgPathBuf::from(self.inner.to_ascii_uppercase())
}
pub fn to_ascii_lowercase(&self) -> HgPathBuf {
HgPathBuf::from(self.inner.to_ascii_lowercase())
}
pub fn as_bytes(&self) -> &[u8] {
&self.inner
}
pub fn contains(&self, other: u8) -> bool {
self.inner.contains(&other)
}
pub fn starts_with(&self, needle: impl AsRef<Self>) -> bool {
self.inner.starts_with(needle.as_ref().as_bytes())
}
pub fn trim_trailing_slash(&self) -> &Self {
Self::new(if self.inner.last() == Some(&b'/') {
&self.inner[..self.inner.len() - 1]
} else {
&self.inner[..]
})
}
/// Returns a tuple of slices `(base, filename)` resulting from the split
/// at the rightmost `/`, if any.
///
/// # Examples:
///
/// ```
/// use hg::utils::hg_path::HgPath;
///
/// let path = HgPath::new(b"cool/hg/path").split_filename();
/// assert_eq!(path, (HgPath::new(b"cool/hg"), HgPath::new(b"path")));
///
/// let path = HgPath::new(b"pathwithoutsep").split_filename();
/// assert_eq!(path, (HgPath::new(b""), HgPath::new(b"pathwithoutsep")));
/// ```
pub fn split_filename(&self) -> (&Self, &Self) {
match &self.inner.iter().rposition(|c| *c == b'/') {
None => (HgPath::new(""), &self),
Some(size) => (
HgPath::new(&self.inner[..*size]),
HgPath::new(&self.inner[*size + 1..]),
),
}
}
pub fn join<T: ?Sized + AsRef<Self>>(&self, other: &T) -> HgPathBuf {
let mut inner = self.inner.to_owned();
if !inner.is_empty() && inner.last() != Some(&b'/') {
inner.push(b'/');
}
inner.extend(other.as_ref().bytes());
HgPathBuf::from_bytes(&inner)
}
pub fn parent(&self) -> &Self {
let inner = self.as_bytes();
HgPath::new(match inner.iter().rposition(|b| *b == b'/') {
Some(pos) => &inner[..pos],
None => &[],
})
}
/// Given a base directory, returns the slice of `self` relative to the
/// base directory. If `base` is not a directory (does not end with a
/// `b'/'`), returns `None`.
pub fn relative_to(&self, base: impl AsRef<Self>) -> Option<&Self> {
let base = base.as_ref();
if base.is_empty() {
return Some(self);
}
let is_dir = base.as_bytes().ends_with(b"/");
if is_dir && self.starts_with(base) {
Some(Self::new(&self.inner[base.len()..]))
} else {
None
}
}
#[cfg(windows)]
/// Copied from the Python stdlib's `os.path.splitdrive` implementation.
///
/// Split a pathname into drive/UNC sharepoint and relative path
/// specifiers. Returns a 2-tuple (drive_or_unc, path); either part may
/// be empty.
///
/// If you assign
/// result = split_drive(p)
/// It is always true that:
/// result[0] + result[1] == p
///
/// If the path contained a drive letter, drive_or_unc will contain
/// everything up to and including the colon.
/// e.g. split_drive("c:/dir") returns ("c:", "/dir")
///
/// If the path contained a UNC path, the drive_or_unc will contain the
/// host name and share up to but not including the fourth directory
/// separator character.
/// e.g. split_drive("//host/computer/dir") returns ("//host/computer",
/// "/dir")
///
/// Paths cannot contain both a drive letter and a UNC path.
pub fn split_drive<'a>(&self) -> (&HgPath, &HgPath) {
let bytes = self.as_bytes();
let is_sep = |b| std::path::is_separator(b as char);
if self.len() < 2 {
(HgPath::new(b""), &self)
} else if is_sep(bytes[0])
&& is_sep(bytes[1])
&& (self.len() == 2 || !is_sep(bytes[2]))
{
// Is a UNC path:
// vvvvvvvvvvvvvvvvvvvv drive letter or UNC path
// \\machine\mountpoint\directory\etc\...
// directory ^^^^^^^^^^^^^^^
let machine_end_index = bytes[2..].iter().position(|b| is_sep(*b));
let mountpoint_start_index = if let Some(i) = machine_end_index {
i + 2
} else {
return (HgPath::new(b""), &self);
};
match bytes[mountpoint_start_index + 1..]
.iter()
.position(|b| is_sep(*b))
{
// A UNC path can't have two slashes in a row
// (after the initial two)
Some(0) => (HgPath::new(b""), &self),
Some(i) => {
let (a, b) =
bytes.split_at(mountpoint_start_index + 1 + i);
(HgPath::new(a), HgPath::new(b))
}
None => (&self, HgPath::new(b"")),
}
} else if bytes[1] == b':' {
// Drive path c:\directory
let (a, b) = bytes.split_at(2);
(HgPath::new(a), HgPath::new(b))
} else {
(HgPath::new(b""), &self)
}
}
#[cfg(unix)]
/// Split a pathname into drive and path. On Posix, drive is always empty.
pub fn split_drive(&self) -> (&HgPath, &HgPath) {
(HgPath::new(b""), &self)
}
/// Checks for errors in the path, short-circuiting at the first one.
/// This generates fine-grained errors useful for debugging.
/// To simply check if the path is valid during tests, use `is_valid`.
pub fn check_state(&self) -> Result<(), HgPathError> {
if self.is_empty() {
return Ok(());
}
let bytes = self.as_bytes();
let mut previous_byte = None;
if bytes[0] == b'/' {
return Err(HgPathError::LeadingSlash(bytes.to_vec()));
}
for (index, byte) in bytes.iter().enumerate() {
match byte {
0 => {
return Err(HgPathError::ContainsNullByte {
bytes: bytes.to_vec(),
null_byte_index: index,
})
}
b'/' => {
if previous_byte.is_some() && previous_byte == Some(b'/') {
return Err(HgPathError::ConsecutiveSlashes {
bytes: bytes.to_vec(),
second_slash_index: index,
});
}
}
_ => (),
};
previous_byte = Some(*byte);
}
Ok(())
}
#[cfg(test)]
/// Only usable during tests to force developers to handle invalid states
fn is_valid(&self) -> bool {
self.check_state().is_ok()
}
}
impl fmt::Debug for HgPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "HgPath({:?})", String::from_utf8_lossy(&self.inner))
}
}
impl fmt::Display for HgPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", String::from_utf8_lossy(&self.inner))
}
}
#[derive(Default, Eq, Ord, Clone, PartialEq, PartialOrd, Hash)]
pub struct HgPathBuf {
inner: Vec<u8>,
}
impl HgPathBuf {
pub fn new() -> Self {
Default::default()
}
pub fn push(&mut self, byte: u8) {
self.inner.push(byte);
}
pub fn from_bytes(s: &[u8]) -> HgPathBuf {
HgPath::new(s).to_owned()
}
pub fn into_vec(self) -> Vec<u8> {
self.inner
}
}
impl fmt::Debug for HgPathBuf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "HgPathBuf({:?})", String::from_utf8_lossy(&self.inner))
}
}
impl fmt::Display for HgPathBuf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", String::from_utf8_lossy(&self.inner))
}
}
impl Deref for HgPathBuf {
type Target = HgPath;
#[inline]
fn deref(&self) -> &HgPath {
&HgPath::new(&self.inner)
}
}
impl From<Vec<u8>> for HgPathBuf {
fn from(vec: Vec<u8>) -> Self {
Self { inner: vec }
}
}
impl<T: ?Sized + AsRef<HgPath>> From<&T> for HgPathBuf {
fn from(s: &T) -> HgPathBuf {
s.as_ref().to_owned()
}
}
impl Into<Vec<u8>> for HgPathBuf {
fn into(self) -> Vec<u8> {
self.inner
}
}
impl Borrow<HgPath> for HgPathBuf {
fn borrow(&self) -> &HgPath {
&HgPath::new(self.as_bytes())
}
}
impl ToOwned for HgPath {
type Owned = HgPathBuf;
fn to_owned(&self) -> HgPathBuf {
self.to_hg_path_buf()
}
}
impl AsRef<HgPath> for HgPath {
fn as_ref(&self) -> &HgPath {
self
}
}
impl AsRef<HgPath> for HgPathBuf {
fn as_ref(&self) -> &HgPath {
self
}
}
impl Extend<u8> for HgPathBuf {
fn extend<T: IntoIterator<Item = u8>>(&mut self, iter: T) {
self.inner.extend(iter);
}
}
/// TODO: Once https://www.mercurial-scm.org/wiki/WindowsUTF8Plan is
/// implemented, these conversion utils will have to work differently depending
/// on the repository encoding: either `UTF-8` or `MBCS`.
pub fn hg_path_to_os_string<P: AsRef<HgPath>>(
hg_path: P,
) -> Result<OsString, HgPathError> {
hg_path.as_ref().check_state()?;
let os_str;
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
os_str = std::ffi::OsStr::from_bytes(&hg_path.as_ref().as_bytes());
}
// TODO Handle other platforms
// TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
Ok(os_str.to_os_string())
}
pub fn hg_path_to_path_buf<P: AsRef<HgPath>>(
hg_path: P,
) -> Result<PathBuf, HgPathError> {
Ok(Path::new(&hg_path_to_os_string(hg_path)?).to_path_buf())
}
pub fn os_string_to_hg_path_buf<S: AsRef<OsStr>>(
os_string: S,
) -> Result<HgPathBuf, HgPathError> {
let buf;
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
buf = HgPathBuf::from_bytes(&os_string.as_ref().as_bytes());
}
// TODO Handle other platforms
// TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
buf.check_state()?;
Ok(buf)
}
pub fn path_to_hg_path_buf<P: AsRef<Path>>(
path: P,
) -> Result<HgPathBuf, HgPathError> {
let buf;
let os_str = path.as_ref().as_os_str();
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
buf = HgPathBuf::from_bytes(&os_str.as_bytes());
}
// TODO Handle other platforms
// TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
buf.check_state()?;
Ok(buf)
}
impl TryFrom<PathBuf> for HgPathBuf {
type Error = HgPathError;
fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
path_to_hg_path_buf(path)
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_path_states() {
assert_eq!(
Err(HgPathError::LeadingSlash(b"/".to_vec())),
HgPath::new(b"/").check_state()
);
assert_eq!(
Err(HgPathError::ConsecutiveSlashes {
bytes: b"a/b//c".to_vec(),
second_slash_index: 4
}),
HgPath::new(b"a/b//c").check_state()
);
assert_eq!(
Err(HgPathError::ContainsNullByte {
bytes: b"a/b/\0c".to_vec(),
null_byte_index: 4
}),
HgPath::new(b"a/b/\0c").check_state()
);
// TODO test HgPathError::DecodeError for the Windows implementation.
assert_eq!(true, HgPath::new(b"").is_valid());
assert_eq!(true, HgPath::new(b"a/b/c").is_valid());
// Backslashes in paths are not significant, but allowed
assert_eq!(true, HgPath::new(br"a\b/c").is_valid());
// Dots in paths are not significant, but allowed
assert_eq!(true, HgPath::new(b"a/b/../c/").is_valid());
assert_eq!(true, HgPath::new(b"./a/b/../c/").is_valid());
}
#[test]
fn test_iter() {
let path = HgPath::new(b"a");
let mut iter = path.bytes();
assert_eq!(Some(&b'a'), iter.next());
assert_eq!(None, iter.next_back());
assert_eq!(None, iter.next());
let path = HgPath::new(b"a");
let mut iter = path.bytes();
assert_eq!(Some(&b'a'), iter.next_back());
assert_eq!(None, iter.next_back());
assert_eq!(None, iter.next());
let path = HgPath::new(b"abc");
let mut iter = path.bytes();
assert_eq!(Some(&b'a'), iter.next());
assert_eq!(Some(&b'c'), iter.next_back());
assert_eq!(Some(&b'b'), iter.next_back());
assert_eq!(None, iter.next_back());
assert_eq!(None, iter.next());
let path = HgPath::new(b"abc");
let mut iter = path.bytes();
assert_eq!(Some(&b'a'), iter.next());
assert_eq!(Some(&b'b'), iter.next());
assert_eq!(Some(&b'c'), iter.next());
assert_eq!(None, iter.next_back());
assert_eq!(None, iter.next());
let path = HgPath::new(b"abc");
let iter = path.bytes();
let mut vec = Vec::new();
vec.extend(iter);
assert_eq!(vec![b'a', b'b', b'c'], vec);
let path = HgPath::new(b"abc");
let mut iter = path.bytes();
assert_eq!(Some(2), iter.rposition(|c| *c == b'c'));
let path = HgPath::new(b"abc");
let mut iter = path.bytes();
assert_eq!(None, iter.rposition(|c| *c == b'd'));
}
#[test]
fn test_join() {
let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"b"));
assert_eq!(b"a/b", path.as_bytes());
let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"b/c"));
assert_eq!(b"a/b/c", path.as_bytes());
// No leading slash if empty before join
let path = HgPathBuf::new().join(HgPath::new(b"b/c"));
assert_eq!(b"b/c", path.as_bytes());
// The leading slash is an invalid representation of an `HgPath`, but
// it can happen. This creates another invalid representation of
// consecutive bytes.
// TODO What should be done in this case? Should we silently remove
// the extra slash? Should we change the signature to a problematic
// `Result<HgPathBuf, HgPathError>`, or should we just keep it so and
// let the error happen upon filesystem interaction?
let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"/b"));
assert_eq!(b"a//b", path.as_bytes());
let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"/b"));
assert_eq!(b"a//b", path.as_bytes());
}
#[test]
fn test_relative_to() {
let path = HgPath::new(b"");
let base = HgPath::new(b"");
assert_eq!(Some(path), path.relative_to(base));
let path = HgPath::new(b"path");
let base = HgPath::new(b"");
assert_eq!(Some(path), path.relative_to(base));
let path = HgPath::new(b"a");
let base = HgPath::new(b"b");
assert_eq!(None, path.relative_to(base));
let path = HgPath::new(b"a/b");
let base = HgPath::new(b"a");
assert_eq!(None, path.relative_to(base));
let path = HgPath::new(b"a/b");
let base = HgPath::new(b"a/");
assert_eq!(Some(HgPath::new(b"b")), path.relative_to(base));
let path = HgPath::new(b"nested/path/to/b");
let base = HgPath::new(b"nested/path/");
assert_eq!(Some(HgPath::new(b"to/b")), path.relative_to(base));
let path = HgPath::new(b"ends/with/dir/");
let base = HgPath::new(b"ends/");
assert_eq!(Some(HgPath::new(b"with/dir/")), path.relative_to(base));
}
#[test]
#[cfg(unix)]
fn test_split_drive() {
// Taken from the Python stdlib's tests
assert_eq!(
HgPath::new(br"/foo/bar").split_drive(),
(HgPath::new(b""), HgPath::new(br"/foo/bar"))
);
assert_eq!(
HgPath::new(br"foo:bar").split_drive(),
(HgPath::new(b""), HgPath::new(br"foo:bar"))
);
assert_eq!(
HgPath::new(br":foo:bar").split_drive(),
(HgPath::new(b""), HgPath::new(br":foo:bar"))
);
// Also try NT paths; should not split them
assert_eq!(
HgPath::new(br"c:\foo\bar").split_drive(),
(HgPath::new(b""), HgPath::new(br"c:\foo\bar"))
);
assert_eq!(
HgPath::new(b"c:/foo/bar").split_drive(),
(HgPath::new(b""), HgPath::new(br"c:/foo/bar"))
);
assert_eq!(
HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
(
HgPath::new(b""),
HgPath::new(br"\\conky\mountpoint\foo\bar")
)
);
}
#[test]
#[cfg(windows)]
fn test_split_drive() {
assert_eq!(
HgPath::new(br"c:\foo\bar").split_drive(),
(HgPath::new(br"c:"), HgPath::new(br"\foo\bar"))
);
assert_eq!(
HgPath::new(b"c:/foo/bar").split_drive(),
(HgPath::new(br"c:"), HgPath::new(br"/foo/bar"))
);
assert_eq!(
HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
(
HgPath::new(br"\\conky\mountpoint"),
HgPath::new(br"\foo\bar")
)
);
assert_eq!(
HgPath::new(br"//conky/mountpoint/foo/bar").split_drive(),
(
HgPath::new(br"//conky/mountpoint"),
HgPath::new(br"/foo/bar")
)
);
assert_eq!(
HgPath::new(br"\\\conky\mountpoint\foo\bar").split_drive(),
(
HgPath::new(br""),
HgPath::new(br"\\\conky\mountpoint\foo\bar")
)
);
assert_eq!(
HgPath::new(br"///conky/mountpoint/foo/bar").split_drive(),
(
HgPath::new(br""),
HgPath::new(br"///conky/mountpoint/foo/bar")
)
);
assert_eq!(
HgPath::new(br"\\conky\\mountpoint\foo\bar").split_drive(),
(
HgPath::new(br""),
HgPath::new(br"\\conky\\mountpoint\foo\bar")
)
);
assert_eq!(
HgPath::new(br"//conky//mountpoint/foo/bar").split_drive(),
(
HgPath::new(br""),
HgPath::new(br"//conky//mountpoint/foo/bar")
)
);
// UNC part containing U+0130
assert_eq!(
HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT/foo/bar").split_drive(),
(
HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT"),
HgPath::new(br"/foo/bar")
)
);
}
#[test]
fn test_parent() {
let path = HgPath::new(b"");
assert_eq!(path.parent(), path);
let path = HgPath::new(b"a");
assert_eq!(path.parent(), HgPath::new(b""));
let path = HgPath::new(b"a/b");
assert_eq!(path.parent(), HgPath::new(b"a"));
let path = HgPath::new(b"a/other/b");
assert_eq!(path.parent(), HgPath::new(b"a/other"));
}
}