1use anyhow::{anyhow, Result};
2use fsevent::EventStream;
3use futures::{Stream, StreamExt};
4use language::NewlineStyle;
5use smol::io::{AsyncReadExt, AsyncWriteExt};
6use std::{
7 io,
8 os::unix::fs::MetadataExt,
9 path::{Component, Path, PathBuf},
10 pin::Pin,
11 time::{Duration, SystemTime},
12};
13use text::Rope;
14
15#[async_trait::async_trait]
16pub trait Fs: Send + Sync {
17 async fn create_dir(&self, path: &Path) -> Result<()>;
18 async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()>;
19 async fn copy(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()>;
20 async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()>;
21 async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()>;
22 async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()>;
23 async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>>;
24 async fn load(&self, path: &Path) -> Result<String>;
25 async fn save(&self, path: &Path, text: &Rope, newline_style: NewlineStyle) -> Result<()>;
26 async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
27 async fn is_file(&self, path: &Path) -> bool;
28 async fn metadata(&self, path: &Path) -> Result<Option<Metadata>>;
29 async fn read_dir(
30 &self,
31 path: &Path,
32 ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>>;
33 async fn watch(
34 &self,
35 path: &Path,
36 latency: Duration,
37 ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>>;
38 fn is_fake(&self) -> bool;
39 #[cfg(any(test, feature = "test-support"))]
40 fn as_fake(&self) -> &FakeFs;
41}
42
43#[derive(Copy, Clone, Default)]
44pub struct CreateOptions {
45 pub overwrite: bool,
46 pub ignore_if_exists: bool,
47}
48
49#[derive(Copy, Clone, Default)]
50pub struct CopyOptions {
51 pub overwrite: bool,
52 pub ignore_if_exists: bool,
53}
54
55#[derive(Copy, Clone, Default)]
56pub struct RenameOptions {
57 pub overwrite: bool,
58 pub ignore_if_exists: bool,
59}
60
61#[derive(Copy, Clone, Default)]
62pub struct RemoveOptions {
63 pub recursive: bool,
64 pub ignore_if_not_exists: bool,
65}
66
67#[derive(Clone, Debug)]
68pub struct Metadata {
69 pub inode: u64,
70 pub mtime: SystemTime,
71 pub is_symlink: bool,
72 pub is_dir: bool,
73}
74
75pub struct RealFs;
76
77#[async_trait::async_trait]
78impl Fs for RealFs {
79 async fn create_dir(&self, path: &Path) -> Result<()> {
80 Ok(smol::fs::create_dir_all(path).await?)
81 }
82
83 async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
84 let mut open_options = smol::fs::OpenOptions::new();
85 open_options.write(true).create(true);
86 if options.overwrite {
87 open_options.truncate(true);
88 } else if !options.ignore_if_exists {
89 open_options.create_new(true);
90 }
91 open_options.open(path).await?;
92 Ok(())
93 }
94
95 async fn copy(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> {
96 if !options.overwrite && smol::fs::metadata(target).await.is_ok() {
97 if options.ignore_if_exists {
98 return Ok(());
99 } else {
100 return Err(anyhow!("{target:?} already exists"));
101 }
102 }
103
104 let metadata = smol::fs::metadata(source).await?;
105 let _ = smol::fs::remove_dir_all(target).await;
106 if metadata.is_dir() {
107 self.create_dir(target).await?;
108 let mut children = smol::fs::read_dir(source).await?;
109 while let Some(child) = children.next().await {
110 if let Ok(child) = child {
111 let child_source_path = child.path();
112 let child_target_path = target.join(child.file_name());
113 self.copy(&child_source_path, &child_target_path, options)
114 .await?;
115 }
116 }
117 } else {
118 smol::fs::copy(source, target).await?;
119 }
120
121 Ok(())
122 }
123
124 async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()> {
125 if !options.overwrite && smol::fs::metadata(target).await.is_ok() {
126 if options.ignore_if_exists {
127 return Ok(());
128 } else {
129 return Err(anyhow!("{target:?} already exists"));
130 }
131 }
132
133 smol::fs::rename(source, target).await?;
134 Ok(())
135 }
136
137 async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
138 let result = if options.recursive {
139 smol::fs::remove_dir_all(path).await
140 } else {
141 smol::fs::remove_dir(path).await
142 };
143 match result {
144 Ok(()) => Ok(()),
145 Err(err) if err.kind() == io::ErrorKind::NotFound && options.ignore_if_not_exists => {
146 Ok(())
147 }
148 Err(err) => Err(err)?,
149 }
150 }
151
152 async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
153 match smol::fs::remove_file(path).await {
154 Ok(()) => Ok(()),
155 Err(err) if err.kind() == io::ErrorKind::NotFound && options.ignore_if_not_exists => {
156 Ok(())
157 }
158 Err(err) => Err(err)?,
159 }
160 }
161
162 async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>> {
163 Ok(Box::new(std::fs::File::open(path)?))
164 }
165
166 async fn load(&self, path: &Path) -> Result<String> {
167 let mut file = smol::fs::File::open(path).await?;
168 let mut text = String::new();
169 file.read_to_string(&mut text).await?;
170 Ok(text)
171 }
172
173 async fn save(&self, path: &Path, text: &Rope, newline_style: NewlineStyle) -> Result<()> {
174 let buffer_size = text.summary().bytes.min(10 * 1024);
175 let file = smol::fs::File::create(path).await?;
176 let mut writer = smol::io::BufWriter::with_capacity(buffer_size, file);
177 let mut newline = false;
178 for chunk in text.chunks() {
179 for line in chunk.split('\n') {
180 if newline {
181 writer.write_all(newline_style.as_str().as_bytes()).await?;
182 }
183 writer.write_all(line.as_bytes()).await?;
184 newline = true;
185 }
186 }
187 writer.flush().await?;
188 Ok(())
189 }
190
191 async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
192 Ok(smol::fs::canonicalize(path).await?)
193 }
194
195 async fn is_file(&self, path: &Path) -> bool {
196 smol::fs::metadata(path)
197 .await
198 .map_or(false, |metadata| metadata.is_file())
199 }
200
201 async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
202 let symlink_metadata = match smol::fs::symlink_metadata(path).await {
203 Ok(metadata) => metadata,
204 Err(err) => {
205 return match (err.kind(), err.raw_os_error()) {
206 (io::ErrorKind::NotFound, _) => Ok(None),
207 (io::ErrorKind::Other, Some(libc::ENOTDIR)) => Ok(None),
208 _ => Err(anyhow::Error::new(err)),
209 }
210 }
211 };
212
213 let is_symlink = symlink_metadata.file_type().is_symlink();
214 let metadata = if is_symlink {
215 smol::fs::metadata(path).await?
216 } else {
217 symlink_metadata
218 };
219 Ok(Some(Metadata {
220 inode: metadata.ino(),
221 mtime: metadata.modified().unwrap(),
222 is_symlink,
223 is_dir: metadata.file_type().is_dir(),
224 }))
225 }
226
227 async fn read_dir(
228 &self,
229 path: &Path,
230 ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
231 let result = smol::fs::read_dir(path).await?.map(|entry| match entry {
232 Ok(entry) => Ok(entry.path()),
233 Err(error) => Err(anyhow!("failed to read dir entry {:?}", error)),
234 });
235 Ok(Box::pin(result))
236 }
237
238 async fn watch(
239 &self,
240 path: &Path,
241 latency: Duration,
242 ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>> {
243 let (tx, rx) = smol::channel::unbounded();
244 let (stream, handle) = EventStream::new(&[path], latency);
245 std::mem::forget(handle);
246 std::thread::spawn(move || {
247 stream.run(move |events| smol::block_on(tx.send(events)).is_ok());
248 });
249 Box::pin(rx)
250 }
251
252 fn is_fake(&self) -> bool {
253 false
254 }
255 #[cfg(any(test, feature = "test-support"))]
256 fn as_fake(&self) -> &FakeFs {
257 panic!("called `RealFs::as_fake`")
258 }
259}
260
261#[cfg(any(test, feature = "test-support"))]
262#[derive(Clone, Debug)]
263struct FakeFsEntry {
264 metadata: Metadata,
265 content: Option<String>,
266}
267
268#[cfg(any(test, feature = "test-support"))]
269struct FakeFsState {
270 entries: std::collections::BTreeMap<PathBuf, FakeFsEntry>,
271 next_inode: u64,
272 event_txs: Vec<smol::channel::Sender<Vec<fsevent::Event>>>,
273}
274
275#[cfg(any(test, feature = "test-support"))]
276impl FakeFsState {
277 fn validate_path(&self, path: &Path) -> Result<()> {
278 if path.is_absolute()
279 && path
280 .parent()
281 .and_then(|path| self.entries.get(path))
282 .map_or(false, |e| e.metadata.is_dir)
283 {
284 Ok(())
285 } else {
286 Err(anyhow!("invalid path {:?}", path))
287 }
288 }
289
290 async fn emit_event<I, T>(&mut self, paths: I)
291 where
292 I: IntoIterator<Item = T>,
293 T: Into<PathBuf>,
294 {
295 let events = paths
296 .into_iter()
297 .map(|path| fsevent::Event {
298 event_id: 0,
299 flags: fsevent::StreamFlags::empty(),
300 path: path.into(),
301 })
302 .collect::<Vec<_>>();
303
304 self.event_txs.retain(|tx| {
305 let _ = tx.try_send(events.clone());
306 !tx.is_closed()
307 });
308 }
309}
310
311#[cfg(any(test, feature = "test-support"))]
312pub struct FakeFs {
313 // Use an unfair lock to ensure tests are deterministic.
314 state: futures::lock::Mutex<FakeFsState>,
315 executor: std::sync::Weak<gpui::executor::Background>,
316}
317
318#[cfg(any(test, feature = "test-support"))]
319impl FakeFs {
320 pub fn new(executor: std::sync::Arc<gpui::executor::Background>) -> std::sync::Arc<Self> {
321 let mut entries = std::collections::BTreeMap::new();
322 entries.insert(
323 Path::new("/").to_path_buf(),
324 FakeFsEntry {
325 metadata: Metadata {
326 inode: 0,
327 mtime: SystemTime::now(),
328 is_dir: true,
329 is_symlink: false,
330 },
331 content: None,
332 },
333 );
334 std::sync::Arc::new(Self {
335 executor: std::sync::Arc::downgrade(&executor),
336 state: futures::lock::Mutex::new(FakeFsState {
337 entries,
338 next_inode: 1,
339 event_txs: Default::default(),
340 }),
341 })
342 }
343
344 pub async fn insert_dir(&self, path: impl AsRef<Path>) {
345 let mut state = self.state.lock().await;
346 let path = path.as_ref();
347 state.validate_path(path).unwrap();
348
349 let inode = state.next_inode;
350 state.next_inode += 1;
351 state.entries.insert(
352 path.to_path_buf(),
353 FakeFsEntry {
354 metadata: Metadata {
355 inode,
356 mtime: SystemTime::now(),
357 is_dir: true,
358 is_symlink: false,
359 },
360 content: None,
361 },
362 );
363 state.emit_event(&[path]).await;
364 }
365
366 pub async fn insert_file(&self, path: impl AsRef<Path>, content: String) {
367 let mut state = self.state.lock().await;
368 let path = path.as_ref();
369 state.validate_path(path).unwrap();
370
371 let inode = state.next_inode;
372 state.next_inode += 1;
373 state.entries.insert(
374 path.to_path_buf(),
375 FakeFsEntry {
376 metadata: Metadata {
377 inode,
378 mtime: SystemTime::now(),
379 is_dir: false,
380 is_symlink: false,
381 },
382 content: Some(content),
383 },
384 );
385 state.emit_event(&[path]).await;
386 }
387
388 #[must_use]
389 pub fn insert_tree<'a>(
390 &'a self,
391 path: impl 'a + AsRef<Path> + Send,
392 tree: serde_json::Value,
393 ) -> futures::future::BoxFuture<'a, ()> {
394 use futures::FutureExt as _;
395 use serde_json::Value::*;
396
397 async move {
398 let path = path.as_ref();
399
400 match tree {
401 Object(map) => {
402 self.insert_dir(path).await;
403 for (name, contents) in map {
404 let mut path = PathBuf::from(path);
405 path.push(name);
406 self.insert_tree(&path, contents).await;
407 }
408 }
409 Null => {
410 self.insert_dir(&path).await;
411 }
412 String(contents) => {
413 self.insert_file(&path, contents).await;
414 }
415 _ => {
416 panic!("JSON object must contain only objects, strings, or null");
417 }
418 }
419 }
420 .boxed()
421 }
422
423 pub async fn files(&self) -> Vec<PathBuf> {
424 self.state
425 .lock()
426 .await
427 .entries
428 .iter()
429 .filter_map(|(path, entry)| entry.content.as_ref().map(|_| path.clone()))
430 .collect()
431 }
432
433 async fn simulate_random_delay(&self) {
434 self.executor
435 .upgrade()
436 .expect("executor has been dropped")
437 .simulate_random_delay()
438 .await;
439 }
440}
441
442#[cfg(any(test, feature = "test-support"))]
443#[async_trait::async_trait]
444impl Fs for FakeFs {
445 async fn create_dir(&self, path: &Path) -> Result<()> {
446 self.simulate_random_delay().await;
447 let state = &mut *self.state.lock().await;
448 let path = normalize_path(path);
449 let mut ancestor_path = PathBuf::new();
450 let mut created_dir_paths = Vec::new();
451 for component in path.components() {
452 ancestor_path.push(component);
453 let entry = state
454 .entries
455 .entry(ancestor_path.clone())
456 .or_insert_with(|| {
457 let inode = state.next_inode;
458 state.next_inode += 1;
459 created_dir_paths.push(ancestor_path.clone());
460 FakeFsEntry {
461 metadata: Metadata {
462 inode,
463 mtime: SystemTime::now(),
464 is_dir: true,
465 is_symlink: false,
466 },
467 content: None,
468 }
469 });
470 if !entry.metadata.is_dir {
471 return Err(anyhow!(
472 "cannot create directory because {:?} is a file",
473 ancestor_path
474 ));
475 }
476 }
477 state.emit_event(&created_dir_paths).await;
478
479 Ok(())
480 }
481
482 async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
483 self.simulate_random_delay().await;
484 let mut state = self.state.lock().await;
485 let path = normalize_path(path);
486 state.validate_path(&path)?;
487 if let Some(entry) = state.entries.get_mut(&path) {
488 if entry.metadata.is_dir || entry.metadata.is_symlink {
489 return Err(anyhow!(
490 "cannot create file because {:?} is a dir or a symlink",
491 path
492 ));
493 }
494
495 if options.overwrite {
496 entry.metadata.mtime = SystemTime::now();
497 entry.content = Some(Default::default());
498 } else if !options.ignore_if_exists {
499 return Err(anyhow!(
500 "cannot create file because {:?} already exists",
501 &path
502 ));
503 }
504 } else {
505 let inode = state.next_inode;
506 state.next_inode += 1;
507 let entry = FakeFsEntry {
508 metadata: Metadata {
509 inode,
510 mtime: SystemTime::now(),
511 is_dir: false,
512 is_symlink: false,
513 },
514 content: Some(Default::default()),
515 };
516 state.entries.insert(path.to_path_buf(), entry);
517 }
518 state.emit_event(&[path]).await;
519
520 Ok(())
521 }
522
523 async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()> {
524 let source = normalize_path(source);
525 let target = normalize_path(target);
526
527 let mut state = self.state.lock().await;
528 state.validate_path(&source)?;
529 state.validate_path(&target)?;
530
531 if !options.overwrite && state.entries.contains_key(&target) {
532 if options.ignore_if_exists {
533 return Ok(());
534 } else {
535 return Err(anyhow!("{target:?} already exists"));
536 }
537 }
538
539 let mut removed = Vec::new();
540 state.entries.retain(|path, entry| {
541 if let Ok(relative_path) = path.strip_prefix(&source) {
542 removed.push((relative_path.to_path_buf(), entry.clone()));
543 false
544 } else {
545 true
546 }
547 });
548
549 for (relative_path, entry) in removed {
550 let new_path = normalize_path(&target.join(relative_path));
551 state.entries.insert(new_path, entry);
552 }
553
554 state.emit_event(&[source, target]).await;
555 Ok(())
556 }
557
558 async fn copy(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> {
559 let source = normalize_path(source);
560 let target = normalize_path(target);
561
562 let mut state = self.state.lock().await;
563 state.validate_path(&source)?;
564 state.validate_path(&target)?;
565
566 if !options.overwrite && state.entries.contains_key(&target) {
567 if options.ignore_if_exists {
568 return Ok(());
569 } else {
570 return Err(anyhow!("{target:?} already exists"));
571 }
572 }
573
574 let mut new_entries = Vec::new();
575 for (path, entry) in &state.entries {
576 if let Ok(relative_path) = path.strip_prefix(&source) {
577 new_entries.push((relative_path.to_path_buf(), entry.clone()));
578 }
579 }
580
581 let mut events = Vec::new();
582 for (relative_path, entry) in new_entries {
583 let new_path = normalize_path(&target.join(relative_path));
584 events.push(new_path.clone());
585 state.entries.insert(new_path, entry);
586 }
587
588 state.emit_event(&events).await;
589 Ok(())
590 }
591
592 async fn remove_dir(&self, dir_path: &Path, options: RemoveOptions) -> Result<()> {
593 let dir_path = normalize_path(dir_path);
594 let mut state = self.state.lock().await;
595 state.validate_path(&dir_path)?;
596 if let Some(entry) = state.entries.get(&dir_path) {
597 if !entry.metadata.is_dir {
598 return Err(anyhow!(
599 "cannot remove {dir_path:?} because it is not a dir"
600 ));
601 }
602
603 if !options.recursive {
604 let descendants = state
605 .entries
606 .keys()
607 .filter(|path| path.starts_with(path))
608 .count();
609 if descendants > 1 {
610 return Err(anyhow!("{dir_path:?} is not empty"));
611 }
612 }
613
614 state.entries.retain(|path, _| !path.starts_with(&dir_path));
615 state.emit_event(&[dir_path]).await;
616 } else if !options.ignore_if_not_exists {
617 return Err(anyhow!("{dir_path:?} does not exist"));
618 }
619
620 Ok(())
621 }
622
623 async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
624 let path = normalize_path(path);
625 let mut state = self.state.lock().await;
626 state.validate_path(&path)?;
627 if let Some(entry) = state.entries.get(&path) {
628 if entry.metadata.is_dir {
629 return Err(anyhow!("cannot remove {path:?} because it is not a file"));
630 }
631
632 state.entries.remove(&path);
633 state.emit_event(&[path]).await;
634 } else if !options.ignore_if_not_exists {
635 return Err(anyhow!("{path:?} does not exist"));
636 }
637 Ok(())
638 }
639
640 async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>> {
641 let text = self.load(path).await?;
642 Ok(Box::new(io::Cursor::new(text)))
643 }
644
645 async fn load(&self, path: &Path) -> Result<String> {
646 let path = normalize_path(path);
647 self.simulate_random_delay().await;
648 let state = self.state.lock().await;
649 let text = state
650 .entries
651 .get(&path)
652 .and_then(|e| e.content.as_ref())
653 .ok_or_else(|| anyhow!("file {:?} does not exist", path))?;
654 Ok(text.clone())
655 }
656
657 async fn save(&self, path: &Path, text: &Rope, newline_style: NewlineStyle) -> Result<()> {
658 self.simulate_random_delay().await;
659 let mut state = self.state.lock().await;
660 let path = normalize_path(path);
661 state.validate_path(&path)?;
662 if let Some(entry) = state.entries.get_mut(&path) {
663 if entry.metadata.is_dir {
664 Err(anyhow!("cannot overwrite a directory with a file"))
665 } else {
666 entry.content = Some(
667 text.chunks()
668 .map(|chunk| chunk.replace('\n', newline_style.as_str()))
669 .collect(),
670 );
671 entry.metadata.mtime = SystemTime::now();
672 state.emit_event(&[path]).await;
673 Ok(())
674 }
675 } else {
676 let inode = state.next_inode;
677 state.next_inode += 1;
678 let entry = FakeFsEntry {
679 metadata: Metadata {
680 inode,
681 mtime: SystemTime::now(),
682 is_dir: false,
683 is_symlink: false,
684 },
685 content: Some(text.chunks().collect()),
686 };
687 state.entries.insert(path.to_path_buf(), entry);
688 state.emit_event(&[path]).await;
689 Ok(())
690 }
691 }
692
693 async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
694 self.simulate_random_delay().await;
695 Ok(normalize_path(path))
696 }
697
698 async fn is_file(&self, path: &Path) -> bool {
699 let path = normalize_path(path);
700 self.simulate_random_delay().await;
701 let state = self.state.lock().await;
702 state
703 .entries
704 .get(&path)
705 .map_or(false, |entry| !entry.metadata.is_dir)
706 }
707
708 async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
709 self.simulate_random_delay().await;
710 let state = self.state.lock().await;
711 let path = normalize_path(path);
712 Ok(state.entries.get(&path).map(|entry| entry.metadata.clone()))
713 }
714
715 async fn read_dir(
716 &self,
717 abs_path: &Path,
718 ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
719 use futures::{future, stream};
720 self.simulate_random_delay().await;
721 let state = self.state.lock().await;
722 let abs_path = normalize_path(abs_path);
723 Ok(Box::pin(stream::iter(state.entries.clone()).filter_map(
724 move |(child_path, _)| {
725 future::ready(if child_path.parent() == Some(&abs_path) {
726 Some(Ok(child_path))
727 } else {
728 None
729 })
730 },
731 )))
732 }
733
734 async fn watch(
735 &self,
736 path: &Path,
737 _: Duration,
738 ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>> {
739 let mut state = self.state.lock().await;
740 self.simulate_random_delay().await;
741 let (tx, rx) = smol::channel::unbounded();
742 state.event_txs.push(tx);
743 let path = path.to_path_buf();
744 let executor = self.executor.clone();
745 Box::pin(futures::StreamExt::filter(rx, move |events| {
746 let result = events.iter().any(|event| event.path.starts_with(&path));
747 let executor = executor.clone();
748 async move {
749 if let Some(executor) = executor.clone().upgrade() {
750 executor.simulate_random_delay().await;
751 }
752 result
753 }
754 }))
755 }
756
757 fn is_fake(&self) -> bool {
758 true
759 }
760
761 #[cfg(any(test, feature = "test-support"))]
762 fn as_fake(&self) -> &FakeFs {
763 self
764 }
765}
766
767pub fn normalize_path(path: &Path) -> PathBuf {
768 let mut components = path.components().peekable();
769 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
770 components.next();
771 PathBuf::from(c.as_os_str())
772 } else {
773 PathBuf::new()
774 };
775
776 for component in components {
777 match component {
778 Component::Prefix(..) => unreachable!(),
779 Component::RootDir => {
780 ret.push(component.as_os_str());
781 }
782 Component::CurDir => {}
783 Component::ParentDir => {
784 ret.pop();
785 }
786 Component::Normal(c) => {
787 ret.push(c);
788 }
789 }
790 }
791 ret
792}