Skip to main content

iso_code/platform/
mod.rs

1use std::path::Path;
2
3use crate::error::WorktreeError;
4use crate::types::{CopyOutcome, ReflinkMode};
5
6#[cfg(target_os = "macos")]
7mod macos;
8#[cfg(target_os = "linux")]
9mod linux;
10#[cfg(target_os = "windows")]
11mod windows;
12
13/// Copy a single file from `src` to `dst` respecting the given `ReflinkMode`.
14///
15/// - `Required`: use CoW only; return `ReflinkNotSupported` if the FS doesn't support it.
16/// - `Preferred` (default): try CoW, fall back to standard copy.
17/// - `Disabled`: always use standard copy.
18///
19/// Returns the `CopyOutcome` describing what actually happened.
20pub fn copy_file(
21    src: &Path,
22    dst: &Path,
23    mode: ReflinkMode,
24) -> Result<CopyOutcome, WorktreeError> {
25    match mode {
26        ReflinkMode::Required => {
27            reflink_copy::reflink(src, dst).map_err(|_| WorktreeError::ReflinkNotSupported)?;
28            Ok(CopyOutcome::Reflinked)
29        }
30        ReflinkMode::Preferred => {
31            match reflink_copy::reflink_or_copy(src, dst).map_err(WorktreeError::Io)? {
32                None => Ok(CopyOutcome::Reflinked),
33                Some(bytes) => Ok(CopyOutcome::StandardCopy {
34                    bytes_written: bytes,
35                }),
36            }
37        }
38        ReflinkMode::Disabled => {
39            let bytes = std::fs::copy(src, dst).map_err(WorktreeError::Io)?;
40            Ok(CopyOutcome::StandardCopy {
41                bytes_written: bytes,
42            })
43        }
44    }
45}
46
47/// Copy all files from `source_worktree` into `target_worktree`, preserving
48/// directory structure and respecting `ReflinkMode`. Skips `.git/` directories.
49///
50/// This is used after `git worktree add` to CoW-copy large build artifacts,
51/// node_modules, or other non-tracked files that an EcosystemAdapter might need.
52pub fn copy_worktree_files(
53    source_worktree: &Path,
54    target_worktree: &Path,
55    paths: &[&Path],
56    mode: ReflinkMode,
57) -> Result<CopyOutcome, WorktreeError> {
58    if paths.is_empty() {
59        return Ok(CopyOutcome::None);
60    }
61
62    let mut total_bytes: u64 = 0;
63    let mut any_reflinked = false;
64
65    for rel_path in paths {
66        let src = source_worktree.join(rel_path);
67        let dst = target_worktree.join(rel_path);
68
69        if !src.exists() {
70            continue;
71        }
72
73        if src.is_dir() {
74            copy_dir_recursive(&src, &dst, mode, &mut total_bytes, &mut any_reflinked)?;
75        } else {
76            if let Some(parent) = dst.parent() {
77                std::fs::create_dir_all(parent).map_err(WorktreeError::Io)?;
78            }
79            match copy_file(&src, &dst, mode)? {
80                CopyOutcome::Reflinked => any_reflinked = true,
81                CopyOutcome::StandardCopy { bytes_written } => total_bytes += bytes_written,
82                CopyOutcome::None => {}
83            }
84        }
85    }
86
87    if total_bytes == 0 && !any_reflinked {
88        Ok(CopyOutcome::None)
89    } else if any_reflinked && total_bytes == 0 {
90        Ok(CopyOutcome::Reflinked)
91    } else {
92        Ok(CopyOutcome::StandardCopy {
93            bytes_written: total_bytes,
94        })
95    }
96}
97
98fn copy_dir_recursive(
99    src: &Path,
100    dst: &Path,
101    mode: ReflinkMode,
102    total_bytes: &mut u64,
103    any_reflinked: &mut bool,
104) -> Result<(), WorktreeError> {
105    std::fs::create_dir_all(dst).map_err(WorktreeError::Io)?;
106
107    for entry in jwalk::WalkDir::new(src)
108        .process_read_dir(|_, _, _, children| {
109            children.retain(|child| {
110                child
111                    .as_ref()
112                    .map(|e| e.file_name().to_string_lossy() != ".git")
113                    .unwrap_or(true)
114            });
115        })
116        .into_iter()
117        .flatten()
118    {
119        let entry_path = entry.path();
120        let rel = entry_path.strip_prefix(src).unwrap_or(&entry_path);
121        let target = dst.join(rel);
122
123        if entry_path.is_dir() {
124            std::fs::create_dir_all(&target).map_err(WorktreeError::Io)?;
125        } else {
126            if let Some(parent) = target.parent() {
127                std::fs::create_dir_all(parent).map_err(WorktreeError::Io)?;
128            }
129            match copy_file(&entry_path, &target, mode)? {
130                CopyOutcome::Reflinked => *any_reflinked = true,
131                CopyOutcome::StandardCopy { bytes_written } => *total_bytes += bytes_written,
132                CopyOutcome::None => {}
133            }
134        }
135    }
136    Ok(())
137}