Skip to main content

gix_fs/
lib.rs

1//! A crate with file-system specific utilities.
2//!
3//! ## Examples
4//!
5//! ```
6//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
7//! use std::path::{Path, PathBuf};
8//!
9//! use gix_fs::{
10//!     stack::{Delegate, ToNormalPathComponents},
11//!     Stack,
12//! };
13//!
14//! let components = "src/lib.rs"
15//!     .to_normal_path_components()
16//!     .collect::<Result<Vec<_>, _>>()?;
17//! assert_eq!(
18//!     components
19//!         .into_iter()
20//!         .map(|component| component.to_string_lossy().into_owned())
21//!         .collect::<Vec<_>>(),
22//!     vec!["src", "lib.rs"]
23//! );
24//!
25//! #[derive(Default)]
26//! struct Recorder {
27//!     directories: Vec<PathBuf>,
28//!     paths: Vec<PathBuf>,
29//! }
30//!
31//! impl Delegate for Recorder {
32//!     fn push_directory(&mut self, stack: &Stack) -> std::io::Result<()> {
33//!         self.directories.push(stack.current_relative().to_path_buf());
34//!         Ok(())
35//!     }
36//!
37//!     fn push(&mut self, _is_last_component: bool, stack: &Stack) -> std::io::Result<()> {
38//!         self.paths.push(stack.current_relative().to_path_buf());
39//!         Ok(())
40//!     }
41//!
42//!     fn pop_directory(&mut self) {}
43//! }
44//!
45//! # let dir = tempfile::tempdir()?;
46//! let capabilities = gix_fs::Capabilities::probe_dir(dir.path());
47//! let mut stack = Stack::new(dir.path().to_path_buf());
48//! let mut recorder = Recorder::default();
49//! stack.make_relative_path_current("src/lib.rs", &mut recorder)?;
50//!
51//! assert_eq!(stack.current_relative(), Path::new("src/lib.rs"));
52//! assert_eq!(recorder.directories[0], Path::new(""));
53//! assert_eq!(recorder.paths, vec![PathBuf::from("src"), PathBuf::from("src/lib.rs")]);
54//! assert_eq!(gix_fs::current_dir(capabilities.precompose_unicode)?, std::env::current_dir()?);
55//! # Ok(()) }
56//! ```
57#![deny(rust_2018_idioms, missing_docs)]
58#![forbid(unsafe_code)]
59
60use std::path::PathBuf;
61
62/// Common knowledge about the worktree that is needed across most interactions with the work tree
63#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
64#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
65pub struct Capabilities {
66    /// If `true`, the filesystem will consider the precomposed umlaut `ä` similar to its decomposed form `"a\u{308}"` and consider them the same.
67    /// If `false`, the filesystem will only see bytes which means that the above example could live side-by-side.
68    ///
69    /// Even though a filesystem that treats both forms the same will still reproduce the exact same byte sequence during traversal for instance,
70    /// this might also mean that we see paths in their decomposed form (this happens when creating directory `ä` in MacOS Finder for example).
71    ///
72    /// If Git would store such decomposed paths in the repository, which only sees bytes, on linux this might mean the path will look strange
73    /// at best, which is why it prefers to store precomposed unicode on systems where it matters, like MacOS and Windows.
74    ///
75    /// For best compatibility, and with this value being `true`, we will turn decomposed paths and input like command-line arguments into their
76    /// precomposed forms, so no decomposed byte sequences should end up in storage.
77    pub precompose_unicode: bool,
78    /// If true, the filesystem ignores the case of input, which makes `A` the same file as `a`.
79    /// This is also called case-folding.
80    pub ignore_case: bool,
81    /// If true, we assume the executable bit is honored as part of the files mode. If false, we assume the file system
82    /// ignores the executable bit, hence it will be reported as 'off' even though we just tried to set it to be on.
83    pub executable_bit: bool,
84    /// If true, the file system supports symbolic links and we should try to create them. Otherwise symbolic links will be checked
85    /// out as files which contain the link as text.
86    pub symlink: bool,
87}
88mod capabilities;
89
90mod snapshot;
91pub use snapshot::{FileSnapshot, SharedFileSnapshot, SharedFileSnapshotMut};
92
93///
94pub mod symlink;
95
96///
97pub mod read_dir;
98pub use read_dir::function::read_dir;
99
100///
101pub mod dir;
102
103/// Like [`std::env::current_dir()`], but it will `precompose_unicode` if that value is true, if the current directory
104/// is valid unicode and if there are decomposed unicode codepoints.
105///
106/// Thus, it will turn `"a\u{308}"` into `ä` if `true`.
107/// Keeping it `false` will not alter the output.
108///
109/// Note that `precompose_unicode` most be set using the `core.precomposeUnicode` git configuration.
110pub fn current_dir(precompose_unicode: bool) -> std::io::Result<PathBuf> {
111    let cwd = std::env::current_dir()?;
112    Ok(if precompose_unicode {
113        gix_utils::str::precompose_path(cwd.into()).into_owned()
114    } else {
115        cwd
116    })
117}
118
119/// A stack of path components with the delegation of side-effects as the currently set path changes, component by component.
120#[derive(Clone)]
121pub struct Stack {
122    /// The prefix/root for all paths we handle.
123    root: PathBuf,
124    /// the most recent known cached that we know is valid.
125    current: PathBuf,
126    /// The relative portion of `valid` that was added previously.
127    current_relative: PathBuf,
128    /// The amount of path components of 'current' beyond the roots components.
129    valid_components: usize,
130    /// If set, we assume the `current` element is a directory to affect calls to `(push|pop)_directory()`.
131    current_is_directory: bool,
132}
133
134#[cfg(unix)]
135/// Returns whether a file has the executable permission set.
136pub fn is_executable(metadata: &std::fs::Metadata) -> bool {
137    use std::os::unix::fs::MetadataExt;
138    (metadata.mode() & 0o111) != 0
139}
140
141/// Classifiers for IO-errors.
142pub mod io_err {
143    use std::io::ErrorKind;
144
145    /// Return `true` if `err` indicates that the entry doesn't exist on disk. `raw` is used as well
146    /// for additional checks while the variants are outside the MSRV.
147    pub fn is_not_found(err: ErrorKind, raw_err: Option<i32>) -> bool {
148        // TODO: use variant once MSRV is 1.83
149        err == ErrorKind::NotFound || raw_err == Some(20)
150    }
151}
152
153#[cfg(not(unix))]
154/// Returns whether a file has the executable permission set.
155pub fn is_executable(_metadata: &std::fs::Metadata) -> bool {
156    false
157}
158
159///
160pub mod stack;