proper error handling needs more bounds on error E which can be done but really is a lot of typing. Nothing to be done here except for doing it I guess.
Unified · Split
| @@ -1,6 +1,7 @@ | |||
| 1 | use super::Cache; | 1 | use super::Cache; |
| 2 | use crate::fs; | 2 | use crate::fs; |
| 3 | use crate::fs::PathIdMapping; | 3 | use crate::fs::PathOidMapping; |
| 4 | use git_hash::oid; | ||
| 4 | use std::path::{Path, PathBuf}; | 5 | use std::path::{Path, PathBuf}; |
| 5 | 6 | ||
| 6 | #[derive(Clone)] | 7 | #[derive(Clone)] |
| @@ -66,7 +67,7 @@ impl<'paths> Cache<'paths> { · 59 unmodified lines | |||
| 66 | state: State, | 67 | state: State, |
| 67 | case: git_glob::pattern::Case, | 68 | case: git_glob::pattern::Case, |
| 68 | buf: Vec<u8>, | 69 | buf: Vec<u8>, |
| 69 | attribute_files_in_index: Vec<PathIdMapping<'paths>>, | 70 | attribute_files_in_index: Vec<PathOidMapping<'paths>>, |
| 70 | ) -> Self { | 71 | ) -> Self { |
| 71 | let root = worktree_root.into(); | 72 | let root = worktree_root.into(); |
| 72 | Cache { | 73 | Cache { |
| @@ -83,15 +84,21 @@ impl<'paths> Cache<'paths> { · 10 unmodified lines | |||
| 83 | /// path is created as directory. If it's not known it is assumed to be a file. | 84 | /// path is created as directory. If it's not known it is assumed to be a file. |
| 84 | /// | 85 | /// |
| 85 | /// Provide access to cached information for that `relative` entry via the platform returned. | 86 | /// Provide access to cached information for that `relative` entry via the platform returned. |
| 86 | pub fn at_entry( | 87 | pub fn at_entry<Find, E>( |
| 87 | &mut self, | 88 | &mut self, |
| 88 | relative: impl AsRef<Path>, | 89 | relative: impl AsRef<Path>, |
| 89 | is_dir: Option<bool>, | 90 | is_dir: Option<bool>, |
| 90 | ) -> std::io::Result<Platform<'_, 'paths>> { | 91 | find: Find, |
| 92 | ) -> std::io::Result<Platform<'_, 'paths>> | ||
| 93 | where | ||
| 94 | Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Result<git_object::BlobRef<'a>, E>, | ||
| 95 | { | ||
| 91 | let mut platform = platform::StackDelegate { | 96 | let mut platform = platform::StackDelegate { |
| 92 | state: &mut self.state, | 97 | state: &mut self.state, |
| 93 | buf: &mut self.buf, | 98 | buf: &mut self.buf, |
| 94 | is_dir, | 99 | is_dir, |
| 100 | attribute_files_in_index: &self.attribute_files_in_index, | ||
| 101 | find, | ||
| 95 | }; | 102 | }; |
| 96 | self.stack.make_relative_path_current(relative, &mut platform)?; | 103 | self.stack.make_relative_path_current(relative, &mut platform)?; |
| 97 | Ok(Platform { parent: self, is_dir }) | 104 | Ok(Platform { parent: self, is_dir }) |
| @@ -1,5 +1,7 @@ | |||
| 1 | use crate::fs; | 1 | use crate::fs; |
| 2 | use crate::fs::cache::{Platform, State}; | 2 | use crate::fs::cache::{Platform, State}; |
| 3 | use crate::fs::PathOidMapping; | ||
| 4 | use git_hash::oid; | ||
| 3 | use std::path::Path; | 5 | use std::path::Path; |
| 4 | 6 | ||
| 5 | impl<'a, 'paths> Platform<'a, 'paths> { | 7 | impl<'a, 'paths> Platform<'a, 'paths> { |
| @@ -43,13 +45,18 @@ impl<'a, 'paths> std::fmt::Debug for Platform<'a, 'paths> { · 37 unmodified lines | |||
| 43 | } | 45 | } |
| 44 | } | 46 | } |
| 45 | 47 | ||
| 46 | pub struct StackDelegate<'a> { | 48 | pub struct StackDelegate<'a, 'paths, Find> { |
| 47 | pub state: &'a mut State, | 49 | pub state: &'a mut State, |
| 48 | pub buf: &'a mut Vec<u8>, | 50 | pub buf: &'a mut Vec<u8>, |
| 49 | pub is_dir: Option<bool>, | 51 | pub is_dir: Option<bool>, |
| 52 | pub attribute_files_in_index: &'a Vec<PathOidMapping<'paths>>, | ||
| 53 | pub find: Find, | ||
| 50 | } | 54 | } |
| 51 | 55 | ||
| 52 | impl<'a> fs::stack::Delegate for StackDelegate<'a> { | 56 | impl<'a, 'paths, Find, E> fs::stack::Delegate for StackDelegate<'a, 'paths, Find> |
| 57 | where | ||
| 58 | Find: for<'b> FnMut(&oid, &'b mut Vec<u8>) -> Result<git_object::BlobRef<'b>, E>, | ||
| 59 | { | ||
| 53 | fn push(&mut self, is_last_component: bool, stack: &fs::Stack) -> std::io::Result<()> { | 60 | fn push(&mut self, is_last_component: bool, stack: &fs::Stack) -> std::io::Result<()> { |
| 54 | match &mut self.state { | 61 | match &mut self.state { |
| 55 | State::CreateDirectoryAndAttributesStack { | 62 | State::CreateDirectoryAndAttributesStack { |
| @@ -77,11 +84,15 @@ impl<'a> fs::stack::Delegate for StackDelegate<'a> { · 21 unmodified lines | |||
| 77 | &stack.root, | 84 | &stack.root, |
| 78 | stack.current.parent().expect("component was just pushed"), | 85 | stack.current.parent().expect("component was just pushed"), |
| 79 | self.buf, | 86 | self.buf, |
| 87 | self.attribute_files_in_index, | ||
| 88 | &mut self.find, | ||
| 80 | )?, | 89 | )?, |
| 81 | State::IgnoreStack(ignore) => ignore.push( | 90 | State::IgnoreStack(ignore) => ignore.push( |
| 82 | &stack.root, | 91 | &stack.root, |
| 83 | stack.current.parent().expect("component was just pushed"), | 92 | stack.current.parent().expect("component was just pushed"), |
| 84 | self.buf, | 93 | self.buf, |
| 94 | self.attribute_files_in_index, | ||
| 95 | &mut self.find, | ||
| 85 | )?, | 96 | )?, |
| 86 | } | 97 | } |
| 87 | Ok(()) | 98 | Ok(()) |
| @@ -1,7 +1,8 @@ | |||
| 1 | use crate::fs::cache::{state, State}; | 1 | use crate::fs::cache::{state, State}; |
| 2 | use crate::fs::PathIdMapping; | 2 | use crate::fs::PathOidMapping; |
| 3 | use bstr::{BStr, BString, ByteSlice}; | 3 | use bstr::{BStr, BString, ByteSlice}; |
| 4 | use git_glob::pattern::Case; | 4 | use git_glob::pattern::Case; |
| 5 | use git_hash::oid; | ||
| 5 | use std::path::Path; | 6 | use std::path::Path; |
| 6 | 7 | ||
| 7 | type AttributeMatchGroup = git_attributes::MatchGroup<git_attributes::Attributes>; | 8 | type AttributeMatchGroup = git_attributes::MatchGroup<git_attributes::Attributes>; |
| @@ -51,16 +52,40 @@ impl Ignore { · 43 unmodified lines | |||
| 51 | } | 52 | } |
| 52 | } | 53 | } |
| 53 | 54 | ||
| 54 | pub fn push(&mut self, root: &Path, dir: &Path, buf: &mut Vec<u8>) -> std::io::Result<()> { | 55 | pub fn push<Find, E>( |
| 55 | let follow_symlinks = true; | 56 | &mut self, |
| 57 | root: &Path, | ||
| 58 | dir: &Path, | ||
| 59 | buf: &mut Vec<u8>, | ||
| 60 | attribute_files_in_index: &Vec<PathOidMapping<'_>>, | ||
| 61 | _find: Find, | ||
| 62 | ) -> std::io::Result<()> | ||
| 63 | where | ||
| 64 | Find: for<'b> FnMut(&oid, &'b mut Vec<u8>) -> Result<git_object::BlobRef<'b>, E>, | ||
| 65 | { | ||
| 66 | let ignore_path = git_features::path::convert::to_unix_separators( | ||
| 67 | git_features::path::into_bytes_or_panic_on_windows(dir.join(".gitignore")), | ||
| 68 | ); | ||
| 69 | let ignore_file_in_index = attribute_files_in_index.binary_search_by(|t| t.0.cmp(ignore_path.as_bstr())); | ||
| 70 | let ignore_path = git_features::path::from_byte_vec_or_panic_on_windows(ignore_path); | ||
| 71 | let follow_symlinks = ignore_file_in_index.is_err(); | ||
| 56 | if !self | 72 | if !self |
| 57 | .stack | 73 | .stack |
| 58 | .add_patterns_file(dir.join(".gitignore"), follow_symlinks, Some(root), buf)? | 74 | .add_patterns_file(ignore_path, follow_symlinks, Some(root), buf)? |
| 59 | { | 75 | { |
| 60 | // Need one stack level per component so push and pop matches. | 76 | match ignore_file_in_index { |
| 61 | self.stack.patterns.push(Default::default()); | 77 | Ok(_idx) => { |
| 78 | // let ignore_blob = find(&attribute_files_in_index[0].1, buf) | ||
| 79 | // .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?; | ||
| 80 | // self.stack.add_patterns_buffer(ignore_blob.data); | ||
| 81 | todo!() | ||
| 82 | } | ||
| 83 | Err(_) => { | ||
| 84 | // Need one stack level per component so push and pop matches. | ||
| 85 | self.stack.patterns.push(Default::default()) | ||
| 86 | } | ||
| 87 | } | ||
| 62 | } | 88 | } |
| 63 | // TODO: from index | ||
| 64 | Ok(()) | 89 | Ok(()) |
| 65 | } | 90 | } |
| 66 | } | 91 | } |
| @@ -108,12 +133,12 @@ impl State { · 41 unmodified lines | |||
| 108 | /// - ignores entries which aren't blobs | 133 | /// - ignores entries which aren't blobs |
| 109 | /// - ignores ignore entries which are not skip-worktree | 134 | /// - ignores ignore entries which are not skip-worktree |
| 110 | /// - within merges, picks 'our' stage both for ignore and attribute files. | 135 | /// - within merges, picks 'our' stage both for ignore and attribute files. |
| 111 | pub(crate) fn build_attribute_list<'paths>( | 136 | pub fn build_attribute_list<'paths>( |
| 112 | &self, | 137 | &self, |
| 113 | index: &git_index::State, | 138 | index: &git_index::State, |
| 114 | paths: &'paths git_index::PathStorage, | 139 | paths: &'paths git_index::PathStorage, |
| 115 | case: git_glob::pattern::Case, | 140 | case: git_glob::pattern::Case, |
| 116 | ) -> Vec<PathIdMapping<'paths>> { | 141 | ) -> Vec<PathOidMapping<'paths>> { |
| 117 | let a1_backing; | 142 | let a1_backing; |
| 118 | let a2_backing; | 143 | let a2_backing; |
| 119 | let names = match self { | 144 | let names = match self { |
| @@ -65,10 +65,10 @@ pub struct Cache<'paths> { · 64 unmodified lines | |||
| 65 | /// If case folding should happen when looking up attributes or exclusions. | 65 | /// If case folding should happen when looking up attributes or exclusions. |
| 66 | case: git_glob::pattern::Case, | 66 | case: git_glob::pattern::Case, |
| 67 | /// A lookup table for object ids to read from in some situations when looking up attributes or exclusions. | 67 | /// A lookup table for object ids to read from in some situations when looking up attributes or exclusions. |
| 68 | attribute_files_in_index: Vec<PathIdMapping<'paths>>, | 68 | attribute_files_in_index: Vec<PathOidMapping<'paths>>, |
| 69 | } | 69 | } |
| 70 | 70 | ||
| 71 | pub(crate) type PathIdMapping<'paths> = (&'paths BStr, git_hash::ObjectId); | 71 | pub(crate) type PathOidMapping<'paths> = (&'paths BStr, git_hash::ObjectId); |
| 72 | 72 | ||
| 73 | /// | 73 | /// |
| 74 | pub mod cache; | 74 | pub mod cache; |
| @@ -38,7 +38,7 @@ where · 37 unmodified lines | |||
| 38 | path: entry_path.to_owned(), | 38 | path: entry_path.to_owned(), |
| 39 | })?; | 39 | })?; |
| 40 | let is_dir = Some(entry.mode == git_index::entry::Mode::COMMIT || entry.mode == git_index::entry::Mode::DIR); | 40 | let is_dir = Some(entry.mode == git_index::entry::Mode::COMMIT || entry.mode == git_index::entry::Mode::DIR); |
| 41 | let dest = path_cache.at_entry(dest_relative, is_dir)?.path(); | 41 | let dest = path_cache.at_entry(dest_relative, is_dir, &mut *find)?.path(); |
| 42 | 42 | ||
| 43 | let object_size = match entry.mode { | 43 | let object_size = match entry.mode { |
| 44 | git_index::entry::Mode::FILE | git_index::entry::Mode::FILE_EXECUTABLE => { | 44 | git_index::entry::Mode::FILE | git_index::entry::Mode::FILE_EXECUTABLE => { |
| @@ -4,6 +4,13 @@ mod create_directory { · 3 unmodified lines | |||
| 4 | use git_worktree::fs; | 4 | use git_worktree::fs; |
| 5 | use tempfile::{tempdir, TempDir}; | 5 | use tempfile::{tempdir, TempDir}; |
| 6 | 6 | ||
| 7 | fn panic_on_find<'buf>( | ||
| 8 | _oid: &git_hash::oid, | ||
| 9 | _buf: &'buf mut Vec<u8>, | ||
| 10 | ) -> std::io::Result<git_object::BlobRef<'buf>> { | ||
| 11 | unreachable!("find should nto be called") | ||
| 12 | } | ||
| 13 | |||
| 7 | #[test] | 14 | #[test] |
| 8 | fn root_is_assumed_to_exist_and_files_in_root_do_not_create_directory() { | 15 | fn root_is_assumed_to_exist_and_files_in_root_do_not_create_directory() { |
| 9 | let dir = tempdir().unwrap(); | 16 | let dir = tempdir().unwrap(); |
| @@ -16,7 +23,7 @@ mod create_directory { · 6 unmodified lines | |||
| 16 | ); | 23 | ); |
| 17 | assert_eq!(cache.num_mkdir_calls(), 0); | 24 | assert_eq!(cache.num_mkdir_calls(), 0); |
| 18 | 25 | ||
| 19 | let path = cache.at_entry("hello", Some(false)).unwrap().path(); | 26 | let path = cache.at_entry("hello", Some(false), panic_on_find).unwrap().path(); |
| 20 | assert!(!path.parent().unwrap().exists(), "prefix itself is never created"); | 27 | assert!(!path.parent().unwrap().exists(), "prefix itself is never created"); |
| 21 | assert_eq!(cache.num_mkdir_calls(), 0); | 28 | assert_eq!(cache.num_mkdir_calls(), 0); |
| 22 | } | 29 | } |
| @@ -32,7 +39,10 @@ mod create_directory { · 9 unmodified lines | |||
| 32 | ("exe", Some(false)), | 39 | ("exe", Some(false)), |
| 33 | ("link", None), | 40 | ("link", None), |
| 34 | ] { | 41 | ] { |
| 35 | let path = cache.at_entry(Path::new("dir").join(name), *is_dir).unwrap().path(); | 42 | let path = cache |
| 43 | .at_entry(Path::new("dir").join(name), *is_dir, panic_on_find) | ||
| 44 | .unwrap() | ||
| 45 | .path(); | ||
| 36 | assert!(path.parent().unwrap().is_dir(), "dir exists"); | 46 | assert!(path.parent().unwrap().is_dir(), "dir exists"); |
| 37 | } | 47 | } |
| 38 | 48 | ||
| @@ -44,7 +54,7 @@ mod create_directory { · 5 unmodified lines | |||
| 44 | let (mut cache, tmp) = new_cache(); | 54 | let (mut cache, tmp) = new_cache(); |
| 45 | std::fs::create_dir(tmp.path().join("dir")).unwrap(); | 55 | std::fs::create_dir(tmp.path().join("dir")).unwrap(); |
| 46 | 56 | ||
| 47 | let path = cache.at_entry("dir/file", Some(false)).unwrap().path(); | 57 | let path = cache.at_entry("dir/file", Some(false), panic_on_find).unwrap().path(); |
| 48 | assert!(path.parent().unwrap().is_dir(), "directory is still present"); | 58 | assert!(path.parent().unwrap().is_dir(), "directory is still present"); |
| 49 | assert!(!path.exists(), "it won't create the file"); | 59 | assert!(!path.exists(), "it won't create the file"); |
| 50 | assert_eq!(cache.num_mkdir_calls(), 1); | 60 | assert_eq!(cache.num_mkdir_calls(), 1); |
| @@ -62,7 +72,10 @@ mod create_directory { · 11 unmodified lines | |||
| 62 | cache.unlink_on_collision(false); | 72 | cache.unlink_on_collision(false); |
| 63 | let relative_path = format!("{}/file", dirname); | 73 | let relative_path = format!("{}/file", dirname); |
| 64 | assert_eq!( | 74 | assert_eq!( |
| 65 | cache.at_entry(&relative_path, Some(false)).unwrap_err().kind(), | 75 | cache |
| 76 | .at_entry(&relative_path, Some(false), panic_on_find) | ||
| 77 | .unwrap_err() | ||
| 78 | .kind(), | ||
| 66 | std::io::ErrorKind::AlreadyExists | 79 | std::io::ErrorKind::AlreadyExists |
| 67 | ); | 80 | ); |
| 68 | } | 81 | } |
| @@ -75,7 +88,10 @@ mod create_directory { · 6 unmodified lines | |||
| 75 | for dirname in &["link-to-dir", "file-in-dir"] { | 88 | for dirname in &["link-to-dir", "file-in-dir"] { |
| 76 | cache.unlink_on_collision(true); | 89 | cache.unlink_on_collision(true); |
| 77 | let relative_path = format!("{}/file", dirname); | 90 | let relative_path = format!("{}/file", dirname); |
| 78 | let path = cache.at_entry(&relative_path, Some(false)).unwrap().path(); | 91 | let path = cache |
| 92 | .at_entry(&relative_path, Some(false), panic_on_find) | ||
| 93 | .unwrap() | ||
| 94 | .path(); | ||
| 79 | assert!(path.parent().unwrap().is_dir(), "directory was forcefully created"); | 95 | assert!(path.parent().unwrap().is_dir(), "directory was forcefully created"); |
| 80 | assert!(!path.exists()); | 96 | assert!(!path.exists()); |
| 81 | } | 97 | } |
| @@ -105,6 +121,8 @@ mod ignore_and_attributes { · 23 unmodified lines | |||
| 105 | use std::path::Path; | 121 | use std::path::Path; |
| 106 | 122 | ||
| 107 | use git_index::entry::Mode; | 123 | use git_index::entry::Mode; |
| 124 | use git_odb::pack::bundle::write::Options; | ||
| 125 | use git_odb::FindExt; | ||
| 108 | use git_worktree::fs; | 126 | use git_worktree::fs; |
| 109 | use tempfile::{tempdir, TempDir}; | 127 | use tempfile::{tempdir, TempDir}; |
| 110 | 128 | ||
| @@ -134,7 +152,6 @@ mod ignore_and_attributes { · 23 unmodified lines | |||
| 134 | } | 152 | } |
| 135 | 153 | ||
| 136 | #[test] | 154 | #[test] |
| 137 | #[ignore] | ||
| 138 | fn check_against_baseline() { | 155 | fn check_against_baseline() { |
| 139 | let dir = git_testtools::scripted_fixture_repo_read_only("make_ignore_and_attributes_setup.sh").unwrap(); | 156 | let dir = git_testtools::scripted_fixture_repo_read_only("make_ignore_and_attributes_setup.sh").unwrap(); |
| 140 | let worktree_dir = dir.join("repo"); | 157 | let worktree_dir = dir.join("repo"); |
| @@ -144,20 +161,21 @@ mod ignore_and_attributes { · 3 unmodified lines | |||
| 144 | let user_exclude_path = dir.join("user.exclude"); | 161 | let user_exclude_path = dir.join("user.exclude"); |
| 145 | assert!(user_exclude_path.is_file()); | 162 | assert!(user_exclude_path.is_file()); |
| 146 | 163 | ||
| 147 | let mut cache = fs::Cache::new( | 164 | let mut index = git_index::File::at(git_dir.join("index"), Default::default()).unwrap(); |
| 148 | &worktree_dir, | 165 | let odb = git_odb::at(git_dir.join("objects")).unwrap(); |
| 149 | git_worktree::fs::cache::State::for_add( | 166 | let state = git_worktree::fs::cache::State::for_add( |
| 150 | Default::default(), | 167 | Default::default(), // TODO: attribute tests |
| 151 | git_worktree::fs::cache::state::Ignore::new( | 168 | git_worktree::fs::cache::state::Ignore::new( |
| 152 | git_attributes::MatchGroup::from_overrides(vec!["!force-include"]), | 169 | git_attributes::MatchGroup::from_overrides(vec!["!force-include"]), |
| 153 | git_attributes::MatchGroup::from_git_dir(&git_dir, Some(user_exclude_path), &mut buf).unwrap(), | 170 | git_attributes::MatchGroup::from_git_dir(&git_dir, Some(user_exclude_path), &mut buf).unwrap(), |
| 154 | None, | 171 | None, |
| 155 | ), | ||
| 156 | ), | 172 | ), |
| 157 | Default::default(), | ||
| 158 | buf, | ||
| 159 | Default::default(), // TODO: get the index, use it to ask state to get the attributes files LUT | ||
| 160 | ); | 173 | ); |
| 174 | let case = git_glob::pattern::Case::Sensitive; | ||
| 175 | let paths_storage = index.take_path_backing(); | ||
| 176 | let attribute_files_in_index = state.build_attribute_list(&index.state, &paths_storage, case); | ||
| 177 | assert_eq!(attribute_files_in_index, vec![]); | ||
| 178 | let mut cache = fs::Cache::new(&worktree_dir, state, case, buf, attribute_files_in_index); | ||
| 161 | 179 | ||
| 162 | for (relative_path, source_and_line) in (IgnoreExpectations { | 180 | for (relative_path, source_and_line) in (IgnoreExpectations { |
| 163 | lines: baseline.lines(), | 181 | lines: baseline.lines(), |
| @@ -168,7 +186,9 @@ mod ignore_and_attributes { · 4 unmodified lines | |||
| 168 | // TODO: ignore file in index only | 186 | // TODO: ignore file in index only |
| 169 | // TODO: dir-excludes | 187 | // TODO: dir-excludes |
| 170 | // TODO: a sibling dir to exercise pop() impl. | 188 | // TODO: a sibling dir to exercise pop() impl. |
| 171 | let platform = cache.at_entry(relative_path, is_dir).unwrap(); | 189 | let platform = cache |
| 190 | .at_entry(relative_path, is_dir, |oid, buf| odb.find_blob(oid, buf)) | ||
| 191 | .unwrap(); | ||
| 172 | 192 | ||
| 173 | let match_ = platform.matching_exclude_pattern(); | 193 | let match_ = platform.matching_exclude_pattern(); |
| 174 | let is_excluded = platform.is_excluded(); | 194 | let is_excluded = platform.is_excluded(); |