grit_lib/worktree/
io_error.rs1use std::io;
7
8use crate::error::{Error, Result};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12#[non_exhaustive]
13pub enum WorktreePathOperation {
14 Read,
16 Replace,
18 Remove,
20 Create,
22 Metadata,
24}
25
26impl WorktreePathOperation {
27 fn verb(self) -> &'static str {
28 match self {
29 Self::Read => "read",
30 Self::Replace => "replace",
31 Self::Remove => "remove",
32 Self::Create => "create",
33 Self::Metadata => "inspect",
34 }
35 }
36}
37
38#[must_use]
40pub fn is_file_in_use(err: &io::Error) -> bool {
41 if err.kind() == io::ErrorKind::WouldBlock {
42 return true;
43 }
44 if let Some(raw) = err.raw_os_error() {
45 #[cfg(windows)]
46 if raw == 32 {
47 return true;
48 }
49 if raw == libc::EBUSY {
50 return true;
51 }
52 #[cfg(windows)]
53 if raw == 33 {
54 return true;
56 }
57 }
58 false
59}
60
61#[must_use]
63pub fn format_worktree_path_io_message(
64 path: &str,
65 operation: WorktreePathOperation,
66 source: &io::Error,
67) -> String {
68 if is_file_in_use(source) {
69 format!(
70 "cannot {} '{path}': file is in use by another process (close it and retry)",
71 operation.verb()
72 )
73 } else {
74 format!("cannot {} '{path}': {source}", operation.verb())
75 }
76}
77
78#[must_use]
80pub fn map_worktree_io(
81 path: impl Into<String>,
82 operation: WorktreePathOperation,
83 source: io::Error,
84) -> Error {
85 Error::WorktreePathIo {
86 path: path.into(),
87 operation,
88 source,
89 }
90}
91
92pub fn io_with_path<T>(
94 path: impl Into<String>,
95 operation: WorktreePathOperation,
96 result: io::Result<T>,
97) -> Result<T> {
98 result.map_err(|source| map_worktree_io(path, operation, source))
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 #[cfg(windows)]
107 fn windows_sharing_violation_maps_to_in_use_message() {
108 let err = io::Error::from_raw_os_error(32);
109 let msg = format_worktree_path_io_message("b.txt", WorktreePathOperation::Replace, &err);
110 assert!(msg.contains("b.txt"));
111 assert!(msg.contains("in use by another process"));
112 assert!(!msg.contains("os error 32"));
113 }
114
115 #[test]
116 fn ebusy_maps_to_in_use_message() {
117 let err = io::Error::from_raw_os_error(libc::EBUSY as i32);
118 let msg = format_worktree_path_io_message("a.txt", WorktreePathOperation::Read, &err);
119 assert!(msg.contains("in use by another process"));
120 }
121
122 #[test]
123 fn other_io_keeps_source_kind() {
124 let err = io::Error::new(io::ErrorKind::NotFound, "gone");
125 let msg = format_worktree_path_io_message("x", WorktreePathOperation::Remove, &err);
126 assert!(msg.contains("cannot remove 'x'"));
127 assert!(msg.contains("gone"));
128 }
129}