Unified · Split
| @@ -141,18 +141,30 @@ impl file::Store { · 140 unmodified lines | |||
| 141 | /// | 141 | /// |
| 142 | /// Otherwise it's similar to [`loose_iter()`][file::Store::loose_iter()]. | 142 | /// Otherwise it's similar to [`loose_iter()`][file::Store::loose_iter()]. |
| 143 | pub fn loose_iter_prefixed(&self, prefix: impl AsRef<Path>) -> std::io::Result<Loose<'_>> { | 143 | pub fn loose_iter_prefixed(&self, prefix: impl AsRef<Path>) -> std::io::Result<Loose<'_>> { |
| 144 | let prefix = prefix.as_ref(); | 144 | let prefix = self.validate_prefix(prefix.as_ref())?; |
| 145 | Ok(Loose::at_root(self, self.base.join(prefix), self.base.clone())) | ||
| 146 | } | ||
| 147 | |||
| 148 | pub(in crate::store::file) fn refs_dir(&self) -> PathBuf { | ||
| 149 | self.base.join("refs") | ||
| 150 | } | ||
| 151 | pub(in crate::store::file) fn validate_prefix<'a>(&self, prefix: &'a Path) -> std::io::Result<&'a Path> { | ||
| 145 | if prefix.is_absolute() { | 152 | if prefix.is_absolute() { |
| 146 | return Err(std::io::Error::new( | 153 | return Err(std::io::Error::new( |
| 147 | std::io::ErrorKind::InvalidInput, | 154 | std::io::ErrorKind::InvalidInput, |
| 148 | "prefix must be a relative path, like 'refs/heads'", | 155 | "prefix must be a relative path, like 'refs/heads'", |
| 149 | )); | 156 | )); |
| 150 | } | 157 | } |
| 151 | Ok(Loose::at_root(self, self.base.join(prefix), self.base.clone())) | 158 | for component in prefix.components() { |
| 152 | } | 159 | use std::path::Component::*; |
| 153 | 160 | if matches!(component, CurDir | ParentDir) { | |
| 154 | pub(in crate::store::file) fn refs_dir(&self) -> PathBuf { | 161 | return Err(std::io::Error::new( |
| 155 | self.base.join("refs") | 162 | std::io::ErrorKind::InvalidInput, |
| 163 | "Refusing to handle prefixes with relative path components", | ||
| 164 | )); | ||
| 165 | } | ||
| 166 | } | ||
| 167 | Ok(prefix) | ||
| 156 | } | 168 | } |
| 157 | } | 169 | } |
| 158 | 170 | ||
| @@ -3,7 +3,13 @@ use crate::{ · 2 unmodified lines | |||
| 3 | store::{file, packed}, | 3 | store::{file, packed}, |
| 4 | }; | 4 | }; |
| 5 | use bstr::BString; | 5 | use bstr::BString; |
| 6 | use std::{cmp::Ordering, convert::TryInto, io::Read, iter::Peekable, path::PathBuf}; | 6 | use std::{ |
| 7 | cmp::Ordering, | ||
| 8 | convert::TryInto, | ||
| 9 | io::Read, | ||
| 10 | iter::Peekable, | ||
| 11 | path::{Path, PathBuf}, | ||
| 12 | }; | ||
| 7 | 13 | ||
| 8 | /// An iterator stepping through sorted input of loose references and packed references, preferring loose refs over otherwise | 14 | /// An iterator stepping through sorted input of loose references and packed references, preferring loose refs over otherwise |
| 9 | /// equivalent packed references. | 15 | /// equivalent packed references. |
| @@ -141,6 +147,32 @@ impl file::Store { · 131 unmodified lines | |||
| 141 | buf: Vec::new(), | 147 | buf: Vec::new(), |
| 142 | }) | 148 | }) |
| 143 | } | 149 | } |
| 150 | |||
| 151 | /// As [`iter(…)`][file::Store::iter()], but filters by `prefix`, i.e. "refs/heads". | ||
| 152 | /// | ||
| 153 | /// Please note that "refs/heads` or "refs\\heads" is equivalent to "refs/heads/" | ||
| 154 | pub fn iter_prefixed<'p, 's>( | ||
| 155 | &'s self, | ||
| 156 | packed: &'p packed::Buffer, | ||
| 157 | prefix: impl AsRef<Path>, | ||
| 158 | ) -> std::io::Result<LooseThenPacked<'p, 's>> { | ||
| 159 | let prefix = self.validate_prefix(prefix.as_ref())?; | ||
| 160 | let packed_prefix = prefix.to_raw_bytes(); | ||
| 161 | #[cfg(windows)] | ||
| 162 | let packed_prefix = packed_prefix.replace(b"\\", b"/"); | ||
| 163 | #[cfg(not(windows))] | ||
| 164 | let packed_prefix = packed_prefix.into_owned(); | ||
| 165 | Ok(LooseThenPacked { | ||
| 166 | parent: self, | ||
| 167 | packed: packed | ||
| 168 | .iter_prefixed(packed_prefix) | ||
| 169 | .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))? | ||
| 170 | .peekable(), | ||
| 171 | loose: file::loose::iter::SortedLoosePaths::at_root_with_names(self.base.join(prefix), self.base.clone()) | ||
| 172 | .peekable(), | ||
| 173 | buf: Vec::new(), | ||
| 174 | }) | ||
| 175 | } | ||
| 144 | } | 176 | } |
| 145 | 177 | ||
| 146 | mod error { | 178 | mod error { |
| @@ -173,3 +205,4 @@ mod error { · 26 unmodified lines | |||
| 173 | } | 205 | } |
| 174 | } | 206 | } |
| 175 | pub use error::Error; | 207 | pub use error::Error; |
| 208 | use os_str_bytes::OsStrBytes; | ||
| @@ -121,13 +121,12 @@ fn loose_iter_with_prefix() -> crate::Result { · 120 unmodified lines | |||
| 121 | } | 121 | } |
| 122 | 122 | ||
| 123 | #[test] | 123 | #[test] |
| 124 | fn overlay_iter() { | 124 | fn overlay_iter() -> crate::Result { |
| 125 | use git_ref::mutable::Target::*; | 125 | use git_ref::mutable::Target::*; |
| 126 | 126 | ||
| 127 | let store = store_at("make_packed_ref_repository_for_overlay.sh").unwrap(); | 127 | let store = store_at("make_packed_ref_repository_for_overlay.sh")?; |
| 128 | let ref_names = store | 128 | let ref_names = store |
| 129 | .iter(&store.packed().unwrap().expect("packed-refs")) | 129 | .iter(&store.packed()?.expect("packed-refs"))? |
| 130 | .unwrap() | ||
| 131 | .map(|r| { | 130 | .map(|r| { |
| 132 | r.map(|r| { | 131 | r.map(|r| { |
| 133 | ( | 132 | ( |
| @@ -137,8 +136,7 @@ fn overlay_iter() { · 3 unmodified lines | |||
| 137 | ) | 136 | ) |
| 138 | }) | 137 | }) |
| 139 | }) | 138 | }) |
| 140 | .collect::<Result<Vec<_>, _>>() | 139 | .collect::<Result<Vec<_>, _>>()?; |
| 141 | .unwrap(); | ||
| 142 | let c1 = hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03"); | 140 | let c1 = hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03"); |
| 143 | let c2 = hex_to_id("9902e3c3e8f0c569b4ab295ddf473e6de763e1e7"); | 141 | let c2 = hex_to_id("9902e3c3e8f0c569b4ab295ddf473e6de763e1e7"); |
| 144 | assert_eq!( | 142 | assert_eq!( |
| @@ -148,7 +146,7 @@ fn overlay_iter() { · 3 unmodified lines | |||
| 148 | ("refs/heads/newer-as-loose".into(), Peeled(c2), false), | 146 | ("refs/heads/newer-as-loose".into(), Peeled(c2), false), |
| 149 | ( | 147 | ( |
| 150 | "refs/remotes/origin/HEAD".into(), | 148 | "refs/remotes/origin/HEAD".into(), |
| 151 | Symbolic("refs/remotes/origin/main".try_into().unwrap()), | 149 | Symbolic("refs/remotes/origin/main".try_into()?), |
| 152 | false | 150 | false |
| 153 | ), | 151 | ), |
| 154 | ("refs/remotes/origin/main".into(), Peeled(c1), true), | 152 | ("refs/remotes/origin/main".into(), Peeled(c1), true), |
| @@ -159,4 +157,51 @@ fn overlay_iter() { · 4 unmodified lines | |||
| 159 | ) | 157 | ) |
| 160 | ] | 158 | ] |
| 161 | ); | 159 | ); |
| 160 | Ok(()) | ||
| 161 | } | ||
| 162 | |||
| 163 | #[test] | ||
| 164 | fn overlay_iter_with_prefix_wont_allow_absolute_paths() -> crate::Result { | ||
| 165 | let store = store_with_packed_refs()?; | ||
| 166 | #[cfg(not(windows))] | ||
| 167 | let abs_path = "/hello"; | ||
| 168 | #[cfg(windows)] | ||
| 169 | let abs_path = "c:\\hello"; | ||
| 170 | |||
| 171 | match store.iter_prefixed(&store.packed()?.expect("pacekd-refs"), abs_path) { | ||
| 172 | Ok(_) => unreachable!("absolute paths aren't allowed"), | ||
| 173 | Err(err) => assert_eq!(err.to_string(), "prefix must be a relative path, like 'refs/heads'"), | ||
| 174 | } | ||
| 175 | Ok(()) | ||
| 176 | } | ||
| 177 | |||
| 178 | #[test] | ||
| 179 | fn overlay_prefixed_iter() { | ||
| 180 | use git_ref::mutable::Target::*; | ||
| 181 | |||
| 182 | let store = store_at("make_packed_ref_repository_for_overlay.sh").unwrap(); | ||
| 183 | let packed = store.packed().unwrap().expect("packed-refs"); | ||
| 184 | let ref_names = store | ||
| 185 | .iter_prefixed(&packed, "refs/heads") | ||
| 186 | .unwrap() | ||
| 187 | .map(|r| { | ||
| 188 | r.map(|r| { | ||
| 189 | ( | ||
| 190 | r.name().expect("valid names only").into_inner(), | ||
| 191 | r.target(), | ||
| 192 | r.is_packed(), | ||
| 193 | ) | ||
| 194 | }) | ||
| 195 | }) | ||
| 196 | .collect::<Result<Vec<_>, _>>() | ||
| 197 | .unwrap(); | ||
| 198 | let c1 = hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03"); | ||
| 199 | let c2 = hex_to_id("9902e3c3e8f0c569b4ab295ddf473e6de763e1e7"); | ||
| 200 | assert_eq!( | ||
| 201 | ref_names, | ||
| 202 | vec![ | ||
| 203 | (b"refs/heads/main".as_bstr().to_owned(), Peeled(c1), true), | ||
| 204 | ("refs/heads/newer-as-loose".into(), Peeled(c2), false), | ||
| 205 | ] | ||
| 206 | ); | ||
| 162 | } | 207 | } |