forked from jj-vcs/jj
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfile_util.rs
134 lines (119 loc) · 4.17 KB
/
file_util.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// Copyright 2021 The Jujutsu Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::fs::File;
use std::iter;
use std::path::{Component, Path, PathBuf};
use tempfile::{NamedTempFile, PersistError};
/// Turns the given `to` path into relative path starting from the `from` path.
///
/// Both `from` and `to` paths are supposed to be absolute and normalized in the
/// same manner.
pub fn relative_path(from: &Path, to: &Path) -> PathBuf {
// Find common prefix.
for (i, base) in from.ancestors().enumerate() {
if let Ok(suffix) = to.strip_prefix(base) {
if i == 0 && suffix.as_os_str().is_empty() {
return ".".into();
} else {
let mut result = PathBuf::from_iter(iter::repeat("..").take(i));
result.push(suffix);
return result;
}
}
}
// No common prefix found. Return the original (absolute) path.
to.to_owned()
}
/// Consumes as much `..` and `.` as possible without considering symlinks.
pub fn normalize_path(path: &Path) -> PathBuf {
let mut result = PathBuf::new();
for c in path.components() {
match c {
Component::CurDir => {}
Component::ParentDir
if matches!(result.components().next_back(), Some(Component::Normal(_))) =>
{
// Do not pop ".."
let popped = result.pop();
assert!(popped);
}
_ => {
result.push(c);
}
}
}
if result.as_os_str().is_empty() {
".".into()
} else {
result
}
}
// Like NamedTempFile::persist(), but also succeeds if the target already
// exists.
pub fn persist_content_addressed_temp_file<P: AsRef<Path>>(
temp_file: NamedTempFile,
new_path: P,
) -> Result<File, PersistError> {
match temp_file.persist(&new_path) {
Ok(file) => Ok(file),
Err(PersistError { error, file }) => {
if let Ok(existing_file) = File::open(new_path) {
Ok(existing_file)
} else {
Err(PersistError { error, file })
}
}
}
}
#[cfg(test)]
mod tests {
use std::io::Write;
use test_case::test_case;
use super::*;
#[test]
fn normalize_too_many_dot_dot() {
assert_eq!(normalize_path(Path::new("foo/..")), Path::new("."));
assert_eq!(normalize_path(Path::new("foo/../..")), Path::new(".."));
assert_eq!(
normalize_path(Path::new("foo/../../..")),
Path::new("../..")
);
assert_eq!(
normalize_path(Path::new("foo/../../../bar/baz/..")),
Path::new("../../bar")
);
}
#[test]
fn test_persist_no_existing_file() {
let temp_dir = testutils::new_temp_dir();
let target = temp_dir.path().join("file");
let mut temp_file = NamedTempFile::new_in(&temp_dir).unwrap();
temp_file.write_all(b"contents").unwrap();
assert!(persist_content_addressed_temp_file(temp_file, target).is_ok());
}
#[test_case(false ; "existing file open")]
#[test_case(true ; "existing file closed")]
fn test_persist_target_exists(existing_file_closed: bool) {
let temp_dir = testutils::new_temp_dir();
let target = temp_dir.path().join("file");
let mut temp_file = NamedTempFile::new_in(&temp_dir).unwrap();
temp_file.write_all(b"contents").unwrap();
let mut file = File::create(&target).unwrap();
file.write_all(b"contents").unwrap();
if existing_file_closed {
drop(file);
}
assert!(persist_content_addressed_temp_file(temp_file, &target).is_ok());
}
}