1 use crate::errors::{HgError, IoResultExt}; |
1 use crate::errors::{HgError, IoResultExt}; |
2 use crate::requirements; |
2 use crate::requirements; |
|
3 use crate::utils::files::get_path_from_bytes; |
3 use memmap::{Mmap, MmapOptions}; |
4 use memmap::{Mmap, MmapOptions}; |
|
5 use std::collections::HashSet; |
4 use std::path::{Path, PathBuf}; |
6 use std::path::{Path, PathBuf}; |
5 |
7 |
6 /// A repository on disk |
8 /// A repository on disk |
7 pub struct Repo { |
9 pub struct Repo { |
8 working_directory: PathBuf, |
10 working_directory: PathBuf, |
9 dot_hg: PathBuf, |
11 dot_hg: PathBuf, |
10 store: PathBuf, |
12 store: PathBuf, |
|
13 requirements: HashSet<String>, |
11 } |
14 } |
12 |
15 |
13 #[derive(Debug, derive_more::From)] |
16 #[derive(Debug, derive_more::From)] |
14 pub enum RepoFindError { |
17 pub enum RepoFindError { |
15 NotFoundInCurrentDirectoryOrAncestors { |
18 NotFoundInCurrentDirectoryOrAncestors { |
30 /// a working directory that contains a `.hg` sub-directory. |
33 /// a working directory that contains a `.hg` sub-directory. |
31 pub fn find() -> Result<Self, RepoFindError> { |
34 pub fn find() -> Result<Self, RepoFindError> { |
32 let current_directory = crate::utils::current_dir()?; |
35 let current_directory = crate::utils::current_dir()?; |
33 // ancestors() is inclusive: it first yields `current_directory` as-is. |
36 // ancestors() is inclusive: it first yields `current_directory` as-is. |
34 for ancestor in current_directory.ancestors() { |
37 for ancestor in current_directory.ancestors() { |
35 let dot_hg = ancestor.join(".hg"); |
38 if ancestor.join(".hg").is_dir() { |
36 if dot_hg.is_dir() { |
39 return Ok(Self::new_at_path(ancestor.to_owned())?); |
37 let repo = Self { |
|
38 store: dot_hg.join("store"), |
|
39 dot_hg, |
|
40 working_directory: ancestor.to_owned(), |
|
41 }; |
|
42 requirements::check(&repo)?; |
|
43 return Ok(repo); |
|
44 } |
40 } |
45 } |
41 } |
46 Err(RepoFindError::NotFoundInCurrentDirectoryOrAncestors { |
42 Err(RepoFindError::NotFoundInCurrentDirectoryOrAncestors { |
47 current_directory, |
43 current_directory, |
48 }) |
44 }) |
49 } |
45 } |
50 |
46 |
|
47 /// To be called after checking that `.hg` is a sub-directory |
|
48 fn new_at_path(working_directory: PathBuf) -> Result<Self, HgError> { |
|
49 let dot_hg = working_directory.join(".hg"); |
|
50 let hg_vfs = Vfs { base: &dot_hg }; |
|
51 let reqs = requirements::load_if_exists(hg_vfs)?; |
|
52 let relative = |
|
53 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT); |
|
54 let shared = |
|
55 reqs.contains(requirements::SHARED_REQUIREMENT) || relative; |
|
56 let store_path; |
|
57 if !shared { |
|
58 store_path = dot_hg.join("store"); |
|
59 } else { |
|
60 let bytes = hg_vfs.read("sharedpath")?; |
|
61 let mut shared_path = get_path_from_bytes(&bytes).to_owned(); |
|
62 if relative { |
|
63 shared_path = dot_hg.join(shared_path) |
|
64 } |
|
65 if !shared_path.is_dir() { |
|
66 return Err(HgError::corrupted(format!( |
|
67 ".hg/sharedpath points to nonexistent directory {}", |
|
68 shared_path.display() |
|
69 ))); |
|
70 } |
|
71 |
|
72 store_path = shared_path.join("store"); |
|
73 } |
|
74 |
|
75 let repo = Self { |
|
76 requirements: reqs, |
|
77 working_directory, |
|
78 store: store_path, |
|
79 dot_hg, |
|
80 }; |
|
81 |
|
82 requirements::check(&repo)?; |
|
83 |
|
84 Ok(repo) |
|
85 } |
|
86 |
51 pub fn working_directory_path(&self) -> &Path { |
87 pub fn working_directory_path(&self) -> &Path { |
52 &self.working_directory |
88 &self.working_directory |
|
89 } |
|
90 |
|
91 pub fn requirements(&self) -> &HashSet<String> { |
|
92 &self.requirements |
53 } |
93 } |
54 |
94 |
55 /// For accessing repository files (in `.hg`), except for the store |
95 /// For accessing repository files (in `.hg`), except for the store |
56 /// (`.hg/store`). |
96 /// (`.hg/store`). |
57 pub(crate) fn hg_vfs(&self) -> Vfs<'_> { |
97 pub(crate) fn hg_vfs(&self) -> Vfs<'_> { |