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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
//! What:
//! The exclusive creation of one destination entry from the private stage,
//! the identity captured right after it exists,
//! and the creation of the unselected parent directories an entry needs.
//! Why:
//! A regular file is installed as a hard link to its staged copy,
//! so it appears with its final bytes and mode in one step;
//! the identity captured after creation is the only proof later rollback accepts.
//!
//! In TS you'd write (pseudocode):
//! ```ts
//! await ensureParents({ destinationRoot, entry, created });
//! await createSelectedEntry({ snapshot, destinationRoot, entry });
//! created.push(await captureInstalledPath({ destinationPath, relativePath, selected: true }));
//! ```
use super::worktree_copy_compare::lstat_or_absent;
use super::worktree_copy_error::{WorktreeCopyError, failure_with_cause, raw_failure};
use super::worktree_copy_manifest::{EntryKind, WorktreeCopyEntry};
use super::worktree_copy_selection::filesystem_path;
use super::worktree_copy_stage::copy_file_exclusive;
use crate::json_record::quote;
use std::os::unix::fs::{DirBuilderExt, MetadataExt};
use std::path::{Path, PathBuf};
/// What:
/// The permission bits of a directory cli-git creates for a selected entry, before modes are applied.
/// Why:
/// Restrictive modes can block the children installation still has to create, so modes come last.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// const PRIVATE_DIRECTORY_MODE = 0o700;
/// ```
const PRIVATE_DIRECTORY_MODE: u32 = 0o700;
/// What:
/// The operating-system errors that mean the filesystem cannot hard-link the staged file.
/// Why:
/// Only these refusals fall back to a copy; any other failure is reported as it is.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// const LINK_UNSUPPORTED_CODES = new Set(['EMLINK', 'ENOSYS', 'ENOTSUP', 'EOPNOTSUPP', 'EPERM', 'EXDEV']);
/// ```
const LINK_UNSUPPORTED_CODES: [i32; 6] = [
libc::EMLINK,
libc::ENOSYS,
libc::ENOTSUP,
libc::EOPNOTSUPP,
libc::EPERM,
libc::EXDEV,
];
/// What:
/// One path cli-git created, with the identity the filesystem gave it.
/// Why:
/// Rollback removes a path only when its device and inode still match this record.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// type InstalledWorktreePath = { device: string; inode: string; relativePath: string; selected: boolean };
/// ```
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct InstalledPath {
/// Device number reported by the filesystem after creation.
pub device: u64,
/// Inode number reported by the filesystem after creation.
pub inode: u64,
/// Slash-separated repository path.
pub relative_path: String,
/// Whether the path came from the selected source manifest rather than being parent scaffolding.
pub selected: bool,
}
/// What:
/// The no-follow identity of a path, read right after its creation.
/// Why:
/// The identity names the exact object cli-git made, not whatever later sits at the same path.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// async function captureInstalledPath({ destinationPath, relativePath, selected }): Promise<InstalledWorktreePath>;
/// ```
pub fn capture_installed_path(
destination_path: &Path,
relative_path: &str,
selected: bool,
) -> Result<InstalledPath, WorktreeCopyError> {
let metadata: std::fs::Metadata =
std::fs::symlink_metadata(destination_path).map_err(|error| return raw_failure(&error))?;
return Ok(InstalledPath {
device: metadata.dev(),
inode: metadata.ino(),
relative_path: String::from(relative_path),
selected,
});
}
/// What:
/// Creates each missing unselected parent directory of one entry, shallow first, recording what it created.
/// Why:
/// An entry's parents are scaffolding that rollback must be able to remove again, so each one is recorded.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// async function ensureParents({ destinationRoot, entry, created }): Promise<void>;
/// ```
pub fn ensure_parents(
destination_root: &Path,
entry: &WorktreeCopyEntry,
created: &mut Vec<InstalledPath>,
) -> Result<(), WorktreeCopyError> {
let components: Vec<&str> = entry.relative_path.split('/').collect();
let parent_count: usize = components.len().saturating_sub(1);
for depth in 1..=parent_count {
let current: String = components[..depth].join("/");
let destination_path: PathBuf = filesystem_path(destination_root, current.as_str())?;
// `match` on the existing entry: a directory is kept, anything else is a refusal.
match lstat_or_absent(destination_path.as_path())? {
Some(metadata) => {
if !metadata.is_dir() {
let message: String = format!(
"cli-git: ignored-state parent is not a directory: {} in {}.",
quote(current.as_str()),
quote(destination_root.to_string_lossy().as_ref())
);
return Err(failure_with_cause(message.as_str(), None));
}
}
None => {
std::fs::create_dir(destination_path.as_path())
.map_err(|error| return raw_failure(&error))?;
created.push(capture_installed_path(
destination_path.as_path(),
current.as_str(),
false,
)?);
}
}
}
return Ok(());
}
/// What:
/// Whether a hard-link failure means the filesystem cannot link the staged file.
/// Why:
/// Only a refusal of the link itself falls back to a copy; a missing source or a full disk does not.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// function isLinkUnsupported(error: unknown): boolean;
/// ```
fn is_link_unsupported(error: &std::io::Error) -> bool {
return error
.raw_os_error()
.is_some_and(|code| return LINK_UNSUPPORTED_CODES.contains(&code));
}
/// What:
/// Installs one staged regular file: a hard link when the filesystem allows it, otherwise an exclusive copy with the entry's mode.
/// Why:
/// The link shares the staged bytes and mode in one step; the copy is the fallback the contract requires.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// async function installFile({ stagePath, destinationPath, mode }): Promise<void>;
/// ```
fn install_file(
stage_path: &Path,
destination_path: &Path,
mode: u32,
) -> Result<(), WorktreeCopyError> {
// `match` on the link result: success ends here, a refusal copies, and any other failure is reported.
match std::fs::hard_link(stage_path, destination_path) {
Ok(()) => return Ok(()),
Err(error) if is_link_unsupported(&error) => {}
Err(error) => return Err(raw_failure(&error)),
}
copy_file_exclusive(stage_path, destination_path)?;
let applied: std::io::Result<()> = std::fs::set_permissions(
destination_path,
std::os::unix::fs::PermissionsExt::from_mode(mode),
);
if let Err(error) = applied {
// A copy that cannot take its mode is removed, so the next attempt starts from the absent path.
let _ignored_removal: std::io::Result<()> = std::fs::remove_file(destination_path);
return Err(raw_failure(&error));
}
return Ok(());
}
/// What:
/// Creates one absent selected entry from the private stage, as its kind requires.
/// Why:
/// Directories are created private and changed later; links are recreated with their exact target text.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// async function createSelectedEntry({ snapshot, destinationRoot, entry }): Promise<void>;
/// ```
pub fn create_selected_entry(
stage_root: &Path,
destination_root: &Path,
entry: &WorktreeCopyEntry,
) -> Result<(), WorktreeCopyError> {
let stage_path: PathBuf = filesystem_path(stage_root, entry.relative_path.as_str())?;
let destination_path: PathBuf =
filesystem_path(destination_root, entry.relative_path.as_str())?;
// `match` on the kind selects the one creation that applies to it.
match entry.kind {
EntryKind::Directory => {
return std::fs::DirBuilder::new()
.mode(PRIVATE_DIRECTORY_MODE)
.create(destination_path.as_path())
.map_err(|error| return raw_failure(&error));
}
EntryKind::File => {
return install_file(stage_path.as_path(), destination_path.as_path(), entry.mode);
}
EntryKind::Symlink => {
let target: PathBuf = std::fs::read_link(stage_path.as_path())
.map_err(|error| return raw_failure(&error))?;
return std::os::unix::fs::symlink(target, destination_path.as_path())
.map_err(|error| return raw_failure(&error));
}
}
}
/// Entry-creation controls stay out of the release executable.
#[cfg(test)]
#[path = "worktree_copy_install_entry_tests.rs"]
mod tests;