Unified · Split
| @@ -141,18 +141,30 @@ impl file::Store { · 140 unmodified lines | ||
| 141 | 141 | /// |
| 142 | 142 | /// Otherwise it's similar to [`loose_iter()`][file::Store::loose_iter()]. |
| 143 | 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 | 152 | if prefix.is_absolute() { |
| 146 | 153 | return Err(std::io::Error::new( |
| 147 | 154 | std::io::ErrorKind::InvalidInput, |
| 148 | 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())) | |
| 152 | − } | |
| 153 | − | |
| 154 | − pub(in crate::store::file) fn refs_dir(&self) -> PathBuf { | |
| 155 | − self.base.join("refs") | |
| 158 | + for component in prefix.components() { | |
| 159 | + use std::path::Component::*; | |
| 160 | + if matches!(component, CurDir | ParentDir) { | |
| 161 | + return Err(std::io::Error::new( | |
| 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 | 3 | store::{file, packed}, |
| 4 | 4 | }; |
| 5 | 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 | 14 | /// An iterator stepping through sorted input of loose references and packed references, preferring loose refs over otherwise |
| 9 | 15 | /// equivalent packed references. |
| @@ -141,6 +147,32 @@ impl file::Store { · 131 unmodified lines | ||
| 141 | 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 | 178 | mod error { |
| @@ -173,3 +205,4 @@ mod error { · 26 unmodified lines | ||
| 173 | 205 | } |
| 174 | 206 | } |
| 175 | 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 | 123 | #[test] |
| 124 | −fn overlay_iter() { | |
| 124 | +fn overlay_iter() -> crate::Result { | |
| 125 | 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 | 128 | let ref_names = store |
| 129 | − .iter(&store.packed().unwrap().expect("packed-refs")) | |
| 130 | − .unwrap() | |
| 129 | + .iter(&store.packed()?.expect("packed-refs"))? | |
| 131 | 130 | .map(|r| { |
| 132 | 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<_>, _>>() | |
| 141 | − .unwrap(); | |
| 139 | + .collect::<Result<Vec<_>, _>>()?; | |
| 142 | 140 | let c1 = hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03"); |
| 143 | 141 | let c2 = hex_to_id("9902e3c3e8f0c569b4ab295ddf473e6de763e1e7"); |
| 144 | 142 | assert_eq!( |
| @@ -148,7 +146,7 @@ fn overlay_iter() { · 3 unmodified lines | ||
| 148 | 146 | ("refs/heads/newer-as-loose".into(), Peeled(c2), false), |
| 149 | 147 | ( |
| 150 | 148 | "refs/remotes/origin/HEAD".into(), |
| 151 | − Symbolic("refs/remotes/origin/main".try_into().unwrap()), | |
| 149 | + Symbolic("refs/remotes/origin/main".try_into()?), | |
| 152 | 150 | false |
| 153 | 151 | ), |
| 154 | 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 | } |