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
13pub 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
47pub 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}