Unified · Split
| @@ -6,6 +6,7 @@ pub struct Options { · 5 unmodified lines | |||
| 6 | 6 | ||
| 7 | #[cfg(feature = "serde")] | 7 | #[cfg(feature = "serde")] |
| 8 | mod serde_only { | 8 | mod serde_only { |
| 9 | use gix::index::entry::Stage; | ||
| 9 | 10 | ||
| 10 | mod ext { | 11 | mod ext { |
| 11 | #[derive(serde::Serialize)] | 12 | #[derive(serde::Serialize)] |
| @@ -115,11 +116,10 @@ mod serde_only { · 103 unmodified lines | |||
| 115 | let (mut intent_to_add, mut skip_worktree) = (0, 0); | 116 | let (mut intent_to_add, mut skip_worktree) = (0, 0); |
| 116 | for entry in f.entries() { | 117 | for entry in f.entries() { |
| 117 | match entry.flags.stage() { | 118 | match entry.flags.stage() { |
| 118 | 0 => stage_0_merged += 1, | 119 | Stage::Unconflicted => stage_0_merged += 1, |
| 119 | 1 => stage_1_base += 1, | 120 | Stage::Base => stage_1_base += 1, |
| 120 | 2 => stage_2_ours += 1, | 121 | Stage::Ours => stage_2_ours += 1, |
| 121 | 3 => stage_3_theirs += 1, | 122 | Stage::Theirs => stage_3_theirs += 1, |
| 122 | invalid => anyhow::bail!("Invalid stage {} encountered", invalid), | ||
| 123 | } | 123 | } |
| 124 | match entry.mode { | 124 | match entry.mode { |
| 125 | gix::index::entry::Mode::DIR => dir += 1, | 125 | gix::index::entry::Mode::DIR => dir += 1, |
| @@ -262,6 +262,8 @@ pub(crate) mod function { · 261 unmodified lines | |||
| 262 | } | 262 | } |
| 263 | 263 | ||
| 264 | #[derive(Debug)] | 264 | #[derive(Debug)] |
| 265 | // See note on `Mismatch` | ||
| 266 | #[allow(dead_code)] | ||
| 265 | pub struct ExcludeLocation { | 267 | pub struct ExcludeLocation { |
| 266 | pub line: usize, | 268 | pub line: usize, |
| 267 | pub rela_source_file: String, | 269 | pub rela_source_file: String, |
| @@ -269,6 +271,9 @@ pub(crate) mod function { · 1 unmodified line | |||
| 269 | } | 271 | } |
| 270 | 272 | ||
| 271 | #[derive(Debug)] | 273 | #[derive(Debug)] |
| 274 | // We debug-print this structure, which makes all fields 'used', but it doesn't count. | ||
| 275 | // TODO: find a way to not have to do more work, but make the warning go away. | ||
| 276 | #[allow(dead_code)] | ||
| 272 | pub enum Mismatch { | 277 | pub enum Mismatch { |
| 273 | Attributes { | 278 | Attributes { |
| 274 | actual: Vec<gix::attrs::Assignment>, | 279 | actual: Vec<gix::attrs::Assignment>, |
| @@ -281,6 +286,8 @@ pub(crate) mod function { · 6 unmodified lines | |||
| 281 | } | 286 | } |
| 282 | 287 | ||
| 283 | #[derive(Debug)] | 288 | #[derive(Debug)] |
| 289 | // See note on `Mismatch` | ||
| 290 | #[allow(dead_code)] | ||
| 284 | pub struct ExcludeMatch { | 291 | pub struct ExcludeMatch { |
| 285 | pub pattern: gix::glob::Pattern, | 292 | pub pattern: gix::glob::Pattern, |
| 286 | pub source: Option<PathBuf>, | 293 | pub source: Option<PathBuf>, |
| @@ -23,6 +23,7 @@ pub(crate) mod function { · 22 unmodified lines | |||
| 23 | io::{BufWriter, Write}, | 23 | io::{BufWriter, Write}, |
| 24 | }; | 24 | }; |
| 25 | 25 | ||
| 26 | use gix::index::entry::Stage; | ||
| 26 | use gix::{ | 27 | use gix::{ |
| 27 | bstr::{BStr, BString}, | 28 | bstr::{BStr, BString}, |
| 28 | worktree::IndexPersistedOrInMemory, | 29 | worktree::IndexPersistedOrInMemory, |
| @@ -392,11 +393,10 @@ pub(crate) mod function { · 363 unmodified lines | |||
| 392 | out, | 393 | out, |
| 393 | "{} {}{:?} {} {}{}{}", | 394 | "{} {}{:?} {} {}{}{}", |
| 394 | match entry.flags.stage() { | 395 | match entry.flags.stage() { |
| 395 | 0 => " ", | 396 | Stage::Unconflicted => " ", |
| 396 | 1 => "BASE ", | 397 | Stage::Base => "BASE ", |
| 397 | 2 => "OURS ", | 398 | Stage::Ours => "OURS ", |
| 398 | 3 => "THEIRS ", | 399 | Stage::Theirs => "THEIRS ", |
| 399 | _ => "UNKNOWN", | ||
| 400 | }, | 400 | }, |
| 401 | if entry.flags.is_empty() { | 401 | if entry.flags.is_empty() { |
| 402 | "".to_string() | 402 | "".to_string() |
| @@ -99,7 +99,14 @@ fn append_followed_includes_recursively( · 98 unmodified lines | |||
| 99 | } | 99 | } |
| 100 | 100 | ||
| 101 | buf.clear(); | 101 | buf.clear(); |
| 102 | std::io::copy(&mut std::fs::File::open(&config_path)?, buf)?; | 102 | std::io::copy( |
| 103 | &mut std::fs::File::open(&config_path).map_err(|err| Error::Io { | ||
| 104 | source: err, | ||
| 105 | path: config_path.to_owned(), | ||
| 106 | })?, | ||
| 107 | buf, | ||
| 108 | ) | ||
| 109 | .map_err(Error::CopyBuffer)?; | ||
| 103 | let config_meta = Metadata { | 110 | let config_meta = Metadata { |
| 104 | path: Some(config_path), | 111 | path: Some(config_path), |
| 105 | trust: meta.trust, | 112 | trust: meta.trust, |
| @@ -1,11 +1,14 @@ | |||
| 1 | use crate::{parse, path::interpolate}; | 1 | use crate::{parse, path::interpolate}; |
| 2 | use std::path::PathBuf; | ||
| 2 | 3 | ||
| 3 | /// The error returned when following includes. | 4 | /// The error returned when following includes. |
| 4 | #[derive(Debug, thiserror::Error)] | 5 | #[derive(Debug, thiserror::Error)] |
| 5 | #[allow(missing_docs)] | 6 | #[allow(missing_docs)] |
| 6 | pub enum Error { | 7 | pub enum Error { |
| 7 | #[error(transparent)] | 8 | #[error("Failed to copy configuration file into buffer")] |
| 8 | Io(#[from] std::io::Error), | 9 | CopyBuffer(#[source] std::io::Error), |
| 10 | #[error("Could not read included configuration file at '{}'", path.display())] | ||
| 11 | Io { path: PathBuf, source: std::io::Error }, | ||
| 9 | #[error(transparent)] | 12 | #[error(transparent)] |
| 10 | Parse(#[from] parse::Error), | 13 | Parse(#[from] parse::Error), |
| 11 | #[error(transparent)] | 14 | #[error(transparent)] |
| @@ -3,6 +3,7 @@ use std::{cmp::Ordering, ops::Range}; · 2 unmodified lines | |||
| 3 | use bstr::{BStr, ByteSlice, ByteVec}; | 3 | use bstr::{BStr, ByteSlice, ByteVec}; |
| 4 | use filetime::FileTime; | 4 | use filetime::FileTime; |
| 5 | 5 | ||
| 6 | use crate::entry::{Stage, StageRaw}; | ||
| 6 | use crate::{entry, extension, AccelerateLookup, Entry, PathStorage, PathStorageRef, State, Version}; | 7 | use crate::{entry, extension, AccelerateLookup, Entry, PathStorage, PathStorageRef, State, Version}; |
| 7 | 8 | ||
| 8 | // TODO: integrate this somehow, somewhere, depending on later usage. | 9 | // TODO: integrate this somehow, somewhere, depending on later usage. |
| @@ -81,7 +82,7 @@ impl State { · 72 unmodified lines | |||
| 81 | res | 82 | res |
| 82 | }) | 83 | }) |
| 83 | .ok()?; | 84 | .ok()?; |
| 84 | self.entry_index_by_idx_and_stage(path, idx, stage, stage_cmp) | 85 | self.entry_index_by_idx_and_stage(path, idx, stage as StageRaw, stage_cmp) |
| 85 | } | 86 | } |
| 86 | 87 | ||
| 87 | /// Walk as far in `direction` as possible, with [`Ordering::Greater`] towards higher stages, and [`Ordering::Less`] | 88 | /// Walk as far in `direction` as possible, with [`Ordering::Greater`] towards higher stages, and [`Ordering::Less`] |
| @@ -112,7 +113,7 @@ impl State { · 24 unmodified lines | |||
| 112 | &self, | 113 | &self, |
| 113 | path: &BStr, | 114 | path: &BStr, |
| 114 | idx: usize, | 115 | idx: usize, |
| 115 | wanted_stage: entry::Stage, | 116 | wanted_stage: entry::StageRaw, |
| 116 | stage_cmp: Ordering, | 117 | stage_cmp: Ordering, |
| 117 | ) -> Option<usize> { | 118 | ) -> Option<usize> { |
| 118 | match stage_cmp { | 119 | match stage_cmp { |
| @@ -121,7 +122,7 @@ impl State { · 2 unmodified lines | |||
| 121 | .enumerate() | 122 | .enumerate() |
| 122 | .rev() | 123 | .rev() |
| 123 | .take_while(|(_, e)| e.path(self) == path) | 124 | .take_while(|(_, e)| e.path(self) == path) |
| 124 | .find_map(|(idx, e)| (e.stage() == wanted_stage).then_some(idx)), | 125 | .find_map(|(idx, e)| (e.stage_raw() == wanted_stage).then_some(idx)), |
| 125 | Ordering::Equal => Some(idx), | 126 | Ordering::Equal => Some(idx), |
| 126 | Ordering::Less => self | 127 | Ordering::Less => self |
| 127 | .entries | 128 | .entries |
| @@ -129,7 +130,7 @@ impl State { · 1 unmodified line | |||
| 129 | .iter() | 130 | .iter() |
| 130 | .enumerate() | 131 | .enumerate() |
| 131 | .take_while(|(_, e)| e.path(self) == path) | 132 | .take_while(|(_, e)| e.path(self) == path) |
| 132 | .find_map(|(ofs, e)| (e.stage() == wanted_stage).then_some(idx + ofs + 1)), | 133 | .find_map(|(ofs, e)| (e.stage_raw() == wanted_stage).then_some(idx + ofs + 1)), |
| 133 | } | 134 | } |
| 134 | } | 135 | } |
| 135 | 136 | ||
| @@ -291,7 +292,7 @@ impl State { · 155 unmodified lines | |||
| 291 | .binary_search_by(|e| { | 292 | .binary_search_by(|e| { |
| 292 | let res = e.path(self).cmp(path); | 293 | let res = e.path(self).cmp(path); |
| 293 | if res.is_eq() { | 294 | if res.is_eq() { |
| 294 | stage_at_index = e.stage(); | 295 | stage_at_index = e.stage_raw(); |
| 295 | } | 296 | } |
| 296 | res | 297 | res |
| 297 | }) | 298 | }) |
| @@ -299,7 +300,7 @@ impl State { · 1 unmodified line | |||
| 299 | let idx = if stage_at_index == 0 || stage_at_index == 2 { | 300 | let idx = if stage_at_index == 0 || stage_at_index == 2 { |
| 300 | idx | 301 | idx |
| 301 | } else { | 302 | } else { |
| 302 | self.entry_index_by_idx_and_stage(path, idx, 2, stage_at_index.cmp(&2))? | 303 | self.entry_index_by_idx_and_stage(path, idx, Stage::Ours as StageRaw, stage_at_index.cmp(&2))? |
| 303 | }; | 304 | }; |
| 304 | Some(&self.entries[idx]) | 305 | Some(&self.entries[idx]) |
| 305 | } | 306 | } |
| @@ -334,13 +335,13 @@ impl State { · 28 unmodified lines | |||
| 334 | + self.entries[low..].partition_point(|e| e.path(self).get(..prefix_len).map_or(false, |p| p <= prefix)); | 335 | + self.entries[low..].partition_point(|e| e.path(self).get(..prefix_len).map_or(false, |p| p <= prefix)); |
| 335 | 336 | ||
| 336 | let low_entry = &self.entries.get(low)?; | 337 | let low_entry = &self.entries.get(low)?; |
| 337 | if low_entry.stage() != 0 { | 338 | if low_entry.stage_raw() != 0 { |
| 338 | low = self | 339 | low = self |
| 339 | .walk_entry_stages(low_entry.path(self), low, Ordering::Less) | 340 | .walk_entry_stages(low_entry.path(self), low, Ordering::Less) |
| 340 | .unwrap_or(low); | 341 | .unwrap_or(low); |
| 341 | } | 342 | } |
| 342 | if let Some(high_entry) = self.entries.get(high) { | 343 | if let Some(high_entry) = self.entries.get(high) { |
| 343 | if high_entry.stage() != 0 { | 344 | if high_entry.stage_raw() != 0 { |
| 344 | high = self | 345 | high = self |
| 345 | .walk_entry_stages(high_entry.path(self), high, Ordering::Less) | 346 | .walk_entry_stages(high_entry.path(self), high, Ordering::Less) |
| 346 | .unwrap_or(high); | 347 | .unwrap_or(high); |
| @@ -374,7 +375,7 @@ impl State { · 27 unmodified lines | |||
| 374 | .binary_search_by(|e| { | 375 | .binary_search_by(|e| { |
| 375 | let res = e.path(self).cmp(path); | 376 | let res = e.path(self).cmp(path); |
| 376 | if res.is_eq() { | 377 | if res.is_eq() { |
| 377 | stage_at_index = e.stage(); | 378 | stage_at_index = e.stage_raw(); |
| 378 | } | 379 | } |
| 379 | res | 380 | res |
| 380 | }) | 381 | }) |
| @@ -62,6 +62,23 @@ bitflags! { · 61 unmodified lines | |||
| 62 | impl Flags { | 62 | impl Flags { |
| 63 | /// Return the stage as extracted from the bits of this instance. | 63 | /// Return the stage as extracted from the bits of this instance. |
| 64 | pub fn stage(&self) -> Stage { | 64 | pub fn stage(&self) -> Stage { |
| 65 | match self.stage_raw() { | ||
| 66 | 0 => Stage::Unconflicted, | ||
| 67 | 1 => Stage::Base, | ||
| 68 | 2 => Stage::Ours, | ||
| 69 | 3 => Stage::Theirs, | ||
| 70 | _ => unreachable!("BUG: Flags::STAGE_MASK is two bits, whose 4 possible values we have covered"), | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | /// Return an entry's stage as raw number between 0 and 4. | ||
| 75 | /// Possible values are: | ||
| 76 | /// | ||
| 77 | /// * 0 = no conflict, | ||
| 78 | /// * 1 = base, | ||
| 79 | /// * 2 = ours, | ||
| 80 | /// * 3 = theirs | ||
| 81 | pub fn stage_raw(&self) -> u32 { | ||
| 65 | (*self & Flags::STAGE_MASK).bits() >> 12 | 82 | (*self & Flags::STAGE_MASK).bits() >> 12 |
| 66 | } | 83 | } |
| 67 | 84 | ||
| @@ -1,9 +1,23 @@ | |||
| 1 | /// The stage of an entry, one of… | 1 | /// The stage of an entry. |
| 2 | #[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] | ||
| 3 | pub enum Stage { | ||
| 4 | /// This is the default, and most entries are in this stage. | ||
| 5 | #[default] | ||
| 6 | Unconflicted = 0, | ||
| 7 | /// The entry is the common base between 'our' change and 'their' change, for comparison. | ||
| 8 | Base = 1, | ||
| 9 | /// The entry represents our change. | ||
| 10 | Ours = 2, | ||
| 11 | /// The entry represents their change. | ||
| 12 | Theirs = 3, | ||
| 13 | } | ||
| 14 | |||
| 15 | // The stage of an entry, one of… | ||
| 2 | /// * 0 = no conflict, | 16 | /// * 0 = no conflict, |
| 3 | /// * 1 = base, | 17 | /// * 1 = base, |
| 4 | /// * 2 = ours, | 18 | /// * 2 = ours, |
| 5 | /// * 3 = theirs | 19 | /// * 3 = theirs |
| 6 | pub type Stage = u32; | 20 | pub type StageRaw = u32; |
| 7 | 21 | ||
| 8 | /// | 22 | /// |
| 9 | #[allow(clippy::empty_docs)] | 23 | #[allow(clippy::empty_docs)] |
| @@ -78,6 +92,17 @@ mod access { · 68 unmodified lines | |||
| 78 | pub fn stage(&self) -> entry::Stage { | 92 | pub fn stage(&self) -> entry::Stage { |
| 79 | self.flags.stage() | 93 | self.flags.stage() |
| 80 | } | 94 | } |
| 95 | |||
| 96 | /// Return an entry's stage as raw number between 0 and 4. | ||
| 97 | /// Possible values are: | ||
| 98 | /// | ||
| 99 | /// * 0 = no conflict, | ||
| 100 | /// * 1 = base, | ||
| 101 | /// * 2 = ours, | ||
| 102 | /// * 3 = theirs | ||
| 103 | pub fn stage_raw(&self) -> u32 { | ||
| 104 | self.flags.stage_raw() | ||
| 105 | } | ||
| 81 | } | 106 | } |
| 82 | } | 107 | } |
| 83 | 108 | ||
| @@ -6,6 +6,7 @@ use crate::{ · 5 unmodified lines | |||
| 6 | }; | 6 | }; |
| 7 | 7 | ||
| 8 | #[derive(Clone)] | 8 | #[derive(Clone)] |
| 9 | #[allow(dead_code)] | ||
| 9 | pub enum Token { | 10 | pub enum Token { |
| 10 | V1 { nanos_since_1970: u64 }, | 11 | V1 { nanos_since_1970: u64 }, |
| 11 | V2 { token: BString }, | 12 | V2 { token: BString }, |
| @@ -144,6 +144,7 @@ pub struct State { · 143 unmodified lines | |||
| 144 | } | 144 | } |
| 145 | 145 | ||
| 146 | mod impls { | 146 | mod impls { |
| 147 | use crate::entry::Stage; | ||
| 147 | use std::fmt::{Debug, Formatter}; | 148 | use std::fmt::{Debug, Formatter}; |
| 148 | 149 | ||
| 149 | use crate::State; | 150 | use crate::State; |
| @@ -155,11 +156,10 @@ mod impls { · 5 unmodified lines | |||
| 155 | f, | 156 | f, |
| 156 | "{} {}{:?} {} {}", | 157 | "{} {}{:?} {} {}", |
| 157 | match entry.flags.stage() { | 158 | match entry.flags.stage() { |
| 158 | 0 => " ", | 159 | Stage::Unconflicted => " ", |
| 159 | 1 => "BASE ", | 160 | Stage::Base => "BASE ", |
| 160 | 2 => "OURS ", | 161 | Stage::Ours => "OURS ", |
| 161 | 3 => "THEIRS ", | 162 | Stage::Theirs => "THEIRS ", |
| 162 | _ => "UNKNOWN", | ||
| 163 | }, | 163 | }, |
| 164 | if entry.flags.is_empty() { | 164 | if entry.flags.is_empty() { |
| 165 | "".to_string() | 165 | "".to_string() |
| @@ -1,5 +1,6 @@ | |||
| 1 | use crate::index::Fixture; | 1 | use crate::index::Fixture; |
| 2 | use bstr::{BString, ByteSlice}; | 2 | use bstr::{BString, ByteSlice}; |
| 3 | use gix_index::entry::Stage; | ||
| 3 | 4 | ||
| 4 | fn icase_fixture() -> gix_index::File { | 5 | fn icase_fixture() -> gix_index::File { |
| 5 | Fixture::Generated("v2_icase_name_clashes").open() | 6 | Fixture::Generated("v2_icase_name_clashes").open() |
| @@ -11,7 +12,7 @@ fn entry_by_path() { · 5 unmodified lines | |||
| 11 | for entry in file.entries() { | 12 | for entry in file.entries() { |
| 12 | let path = entry.path(&file); | 13 | let path = entry.path(&file); |
| 13 | assert_eq!(file.entry_by_path(path), Some(entry)); | 14 | assert_eq!(file.entry_by_path(path), Some(entry)); |
| 14 | assert_eq!(file.entry_by_path_and_stage(path, 0), Some(entry)); | 15 | assert_eq!(file.entry_by_path_and_stage(path, Stage::Unconflicted), Some(entry)); |
| 15 | } | 16 | } |
| 16 | } | 17 | } |
| 17 | 18 | ||
| @@ -140,27 +141,27 @@ fn entry_by_path_and_stage() { · 122 unmodified lines | |||
| 140 | for entry in file.entries() { | 141 | for entry in file.entries() { |
| 141 | let path = entry.path(&file); | 142 | let path = entry.path(&file); |
| 142 | assert_eq!( | 143 | assert_eq!( |
| 143 | file.entry_index_by_path_and_stage(path, 0) | 144 | file.entry_index_by_path_and_stage(path, Stage::Unconflicted) |
| 144 | .map(|idx| &file.entries()[idx]), | 145 | .map(|idx| &file.entries()[idx]), |
| 145 | Some(entry) | 146 | Some(entry) |
| 146 | ); | 147 | ); |
| 147 | assert_eq!(file.entry_by_path_and_stage(path, 0), Some(entry)); | 148 | assert_eq!(file.entry_by_path_and_stage(path, Stage::Unconflicted), Some(entry)); |
| 148 | } | 149 | } |
| 149 | } | 150 | } |
| 150 | 151 | ||
| 151 | #[test] | 152 | #[test] |
| 152 | fn entry_by_path_with_conflicting_file() { | 153 | fn entry_by_path_with_conflicting_file() { |
| 153 | let file = Fixture::Loose("conflicting-file").open(); | 154 | let file = Fixture::Loose("conflicting-file").open(); |
| 154 | for expected_stage in [1 /* common ancestor */, 2 /* ours */, 3 /* theirs */] { | 155 | for expected_stage in [Stage::Base, Stage::Ours, Stage::Theirs] { |
| 155 | assert!( | 156 | assert!( |
| 156 | file.entry_by_path_and_stage("file".into(), expected_stage).is_some(), | 157 | file.entry_by_path_and_stage("file".into(), expected_stage).is_some(), |
| 157 | "we have no stage 0 during a conflict, but all other ones. Missed {expected_stage}" | 158 | "we have no stage 0 during a conflict, but all other ones. Missed {expected_stage:?}" |
| 158 | ); | 159 | ); |
| 159 | } | 160 | } |
| 160 | 161 | ||
| 161 | assert_eq!( | 162 | assert_eq!( |
| 162 | file.entry_by_path("file".into()).expect("found").stage(), | 163 | file.entry_by_path("file".into()).expect("found").stage(), |
| 163 | 2, | 164 | Stage::Ours, |
| 164 | "we always find our stage while in a merge" | 165 | "we always find our stage while in a merge" |
| 165 | ); | 166 | ); |
| 166 | } | 167 | } |
| @@ -226,13 +227,13 @@ fn sort_entries() { · 59 unmodified lines | |||
| 226 | 227 | ||
| 227 | for (idx, entry) in file.entries()[..valid_entries].iter().enumerate() { | 228 | for (idx, entry) in file.entries()[..valid_entries].iter().enumerate() { |
| 228 | assert_eq!( | 229 | assert_eq!( |
| 229 | file.entry_index_by_path_and_stage_bounded(entry.path(&file), 0, valid_entries), | 230 | file.entry_index_by_path_and_stage_bounded(entry.path(&file), Stage::Unconflicted, valid_entries), |
| 230 | Some(idx), | 231 | Some(idx), |
| 231 | "we can still find entries in the correctly sorted region" | 232 | "we can still find entries in the correctly sorted region" |
| 232 | ); | 233 | ); |
| 233 | } | 234 | } |
| 234 | assert_eq!( | 235 | assert_eq!( |
| 235 | file.entry_by_path_and_stage(new_entry_path, 0), | 236 | file.entry_by_path_and_stage(new_entry_path, Stage::Unconflicted), |
| 236 | None, | 237 | None, |
| 237 | "new entry can't be found due to incorrect order" | 238 | "new entry can't be found due to incorrect order" |
| 238 | ); | 239 | ); |
| @@ -241,7 +242,7 @@ fn sort_entries() { · 2 unmodified lines | |||
| 241 | assert!(file.verify_entries().is_ok(), "sorting of entries restores invariants"); | 242 | assert!(file.verify_entries().is_ok(), "sorting of entries restores invariants"); |
| 242 | 243 | ||
| 243 | assert_eq!( | 244 | assert_eq!( |
| 244 | file.entry_by_path_and_stage(new_entry_path, 0) | 245 | file.entry_by_path_and_stage(new_entry_path, Stage::Unconflicted) |
| 245 | .expect("can be found") | 246 | .expect("can be found") |
| 246 | .path(&file), | 247 | .path(&file), |
| 247 | new_entry_path, | 248 | new_entry_path, |
| @@ -157,11 +157,11 @@ where · 156 unmodified lines | |||
| 157 | let mut idx = 0; | 157 | let mut idx = 0; |
| 158 | while let Some(entry) = chunk_entries.get(idx) { | 158 | while let Some(entry) = chunk_entries.get(idx) { |
| 159 | let absolute_entry_index = entry_offset + idx; | 159 | let absolute_entry_index = entry_offset + idx; |
| 160 | if idx == 0 && entry.stage() != 0 { | 160 | if idx == 0 && entry.stage_raw() != 0 { |
| 161 | let offset = entry_offset.checked_sub(1).and_then(|prev_idx| { | 161 | let offset = entry_offset.checked_sub(1).and_then(|prev_idx| { |
| 162 | let prev_entry = &all_entries[prev_idx]; | 162 | let prev_entry = &all_entries[prev_idx]; |
| 163 | let entry_path = entry.path_in(state.path_backing); | 163 | let entry_path = entry.path_in(state.path_backing); |
| 164 | if prev_entry.stage() == 0 || prev_entry.path_in(state.path_backing) != entry_path { | 164 | if prev_entry.stage_raw() == 0 || prev_entry.path_in(state.path_backing) != entry_path { |
| 165 | // prev_entry (in previous chunk) does not belong to our conflict | 165 | // prev_entry (in previous chunk) does not belong to our conflict |
| 166 | return None; | 166 | return None; |
| 167 | } | 167 | } |
| @@ -286,7 +286,7 @@ impl<'index> State<'_, 'index> { · 118 unmodified lines | |||
| 286 | self.skipped_by_pathspec.fetch_add(1, Ordering::Relaxed); | 286 | self.skipped_by_pathspec.fetch_add(1, Ordering::Relaxed); |
| 287 | return None; | 287 | return None; |
| 288 | } | 288 | } |
| 289 | let status = if entry.stage() != 0 { | 289 | let status = if entry.stage_raw() != 0 { |
| 290 | Ok( | 290 | Ok( |
| 291 | Conflict::try_from_entry(entries, self.path_backing, entry_index, path).map(|(conflict, offset)| { | 291 | Conflict::try_from_entry(entries, self.path_backing, entry_index, path).map(|(conflict, offset)| { |
| 292 | *outer_entry_index += offset; // let out loop skip over entries related to the conflict | 292 | *outer_entry_index += offset; // let out loop skip over entries related to the conflict |
| @@ -604,7 +604,7 @@ impl Conflict { · 311 unmodified lines | |||
| 604 | let mut count = 0_usize; | 604 | let mut count = 0_usize; |
| 605 | for stage in (start_index..(start_index + 3).min(entries.len())).filter_map(|idx| { | 605 | for stage in (start_index..(start_index + 3).min(entries.len())).filter_map(|idx| { |
| 606 | let entry = &entries[idx]; | 606 | let entry = &entries[idx]; |
| 607 | let stage = entry.stage(); | 607 | let stage = entry.stage_raw(); |
| 608 | (stage > 0 && entry.path_in(path_backing) == entry_path).then_some(stage) | 608 | (stage > 0 && entry.path_in(path_backing) == entry_path).then_some(stage) |
| 609 | }) { | 609 | }) { |
| 610 | // This could be `1 << (stage - 1)` but let's be specific. | 610 | // This could be `1 << (stage - 1)` but let's be specific. |
| @@ -20,6 +20,7 @@ impl<'a> Platform<'a> { · 19 unmodified lines | |||
| 20 | /// # Panics | 20 | /// # Panics |
| 21 | /// | 21 | /// |
| 22 | /// If the cache was configured without exclude patterns. | 22 | /// If the cache was configured without exclude patterns. |
| 23 | #[doc(alias = "is_path_ignored", alias = "git2")] | ||
| 23 | pub fn is_excluded(&self) -> bool { | 24 | pub fn is_excluded(&self) -> bool { |
| 24 | self.matching_exclude_pattern() | 25 | self.matching_exclude_pattern() |
| 25 | .map_or(false, |m| !m.pattern.is_negative()) | 26 | .map_or(false, |m| !m.pattern.is_negative()) |
| @@ -131,7 +131,7 @@ impl State { · 130 unmodified lines | |||
| 131 | 131 | ||
| 132 | // Stage 0 means there is no merge going on, stage 2 means it's 'our' side of the merge, but then | 132 | // Stage 0 means there is no merge going on, stage 2 means it's 'our' side of the merge, but then |
| 133 | // there won't be a stage 0. | 133 | // there won't be a stage 0. |
| 134 | if entry.mode == gix_index::entry::Mode::FILE && (entry.stage() == 0 || entry.stage() == 2) { | 134 | if entry.mode == gix_index::entry::Mode::FILE && (entry.stage_raw() == 0 || entry.stage_raw() == 2) { |
| 135 | let basename = path.rfind_byte(b'/').map_or(path, |pos| path[pos + 1..].as_bstr()); | 135 | let basename = path.rfind_byte(b'/').map_or(path, |pos| path[pos + 1..].as_bstr()); |
| 136 | let ignore_source = names.iter().find_map(|t| { | 136 | let ignore_source = names.iter().find_map(|t| { |
| 137 | match case { | 137 | match case { |
| @@ -39,6 +39,7 @@ impl<'repo> AttributeStack<'repo> { · 38 unmodified lines | |||
| 39 | /// path is created as directory. If it's not known it is assumed to be a file. | 39 | /// path is created as directory. If it's not known it is assumed to be a file. |
| 40 | /// | 40 | /// |
| 41 | /// Provide access to cached information for that `relative` path via the returned platform. | 41 | /// Provide access to cached information for that `relative` path via the returned platform. |
| 42 | #[doc(alias = "is_path_ignored", alias = "git2")] | ||
| 42 | pub fn at_path( | 43 | pub fn at_path( |
| 43 | &mut self, | 44 | &mut self, |
| 44 | relative: impl AsRef<std::path::Path>, | 45 | relative: impl AsRef<std::path::Path>, |
| @@ -106,6 +106,7 @@ impl Repository { · 105 unmodified lines | |||
| 106 | /// When only excludes are desired, this is the most efficient way to obtain them. Otherwise use | 106 | /// When only excludes are desired, this is the most efficient way to obtain them. Otherwise use |
| 107 | /// [`Repository::attributes()`] for accessing both attributes and excludes. | 107 | /// [`Repository::attributes()`] for accessing both attributes and excludes. |
| 108 | // TODO: test | 108 | // TODO: test |
| 109 | #[doc(alias = "is_path_ignored", alias = "git2")] | ||
| 109 | #[cfg(feature = "excludes")] | 110 | #[cfg(feature = "excludes")] |
| 110 | pub fn excludes( | 111 | pub fn excludes( |
| 111 | &self, | 112 | &self, |
| @@ -40,6 +40,11 @@ impl crate::Repository { · 39 unmodified lines | |||
| 40 | /// Return a shared worktree index which is updated automatically if the in-memory snapshot has become stale as the underlying file | 40 | /// Return a shared worktree index which is updated automatically if the in-memory snapshot has become stale as the underlying file |
| 41 | /// on disk has changed. | 41 | /// on disk has changed. |
| 42 | /// | 42 | /// |
| 43 | /// ### Notes | ||
| 44 | /// | ||
| 45 | /// * This will fail if the file doesn't exist, like in a newly initialized repository. If that is the case, use | ||
| 46 | /// [index_or_empty()](Self::index_or_empty) or [try_index()](Self::try_index) instead. | ||
| 47 | /// | ||
| 43 | /// The index file is shared across all clones of this repository. | 48 | /// The index file is shared across all clones of this repository. |
| 44 | pub fn index(&self) -> Result<worktree::Index, worktree::open_index::Error> { | 49 | pub fn index(&self) -> Result<worktree::Index, worktree::open_index::Error> { |
| 45 | self.try_index().and_then(|opt| match opt { | 50 | self.try_index().and_then(|opt| match opt { |
| @@ -1,6 +1,7 @@ | |||
| 1 | use std::collections::HashSet; | 1 | use std::collections::HashSet; |
| 2 | 2 | ||
| 3 | use gix_hash::ObjectId; | 3 | use gix_hash::ObjectId; |
| 4 | use gix_index::entry::Stage; | ||
| 4 | use gix_revision::spec::parse::{ | 5 | use gix_revision::spec::parse::{ |
| 5 | delegate, | 6 | delegate, |
| 6 | delegate::{PeelTo, Traversal}, | 7 | delegate::{PeelTo, Traversal}, |
| @@ -305,9 +306,18 @@ impl<'repo> delegate::Navigate for Delegate<'repo> { · 298 unmodified lines | |||
| 305 | } | 306 | } |
| 306 | 307 | ||
| 307 | fn index_lookup(&mut self, path: &BStr, stage: u8) -> Option<()> { | 308 | fn index_lookup(&mut self, path: &BStr, stage: u8) -> Option<()> { |
| 309 | let stage = match stage { | ||
| 310 | 0 => Stage::Unconflicted, | ||
| 311 | 1 => Stage::Base, | ||
| 312 | 2 => Stage::Ours, | ||
| 313 | 3 => Stage::Theirs, | ||
| 314 | _ => unreachable!( | ||
| 315 | "BUG: driver will not pass invalid stages (and it uses integer to avoid gix-index as dependency)" | ||
| 316 | ), | ||
| 317 | }; | ||
| 308 | self.unset_disambiguate_call(); | 318 | self.unset_disambiguate_call(); |
| 309 | match self.repo.index() { | 319 | match self.repo.index() { |
| 310 | Ok(index) => match index.entry_by_path_and_stage(path, stage.into()) { | 320 | Ok(index) => match index.entry_by_path_and_stage(path, stage) { |
| 311 | Some(entry) => { | 321 | Some(entry) => { |
| 312 | self.objs[self.idx] | 322 | self.objs[self.idx] |
| 313 | .get_or_insert_with(HashSet::default) | 323 | .get_or_insert_with(HashSet::default) |
| @@ -323,21 +333,17 @@ impl<'repo> delegate::Navigate for Delegate<'repo> { · 9 unmodified lines | |||
| 323 | Some(()) | 333 | Some(()) |
| 324 | } | 334 | } |
| 325 | None => { | 335 | None => { |
| 326 | let stage_hint = [0, 1, 2] | 336 | let stage_hint = [Stage::Unconflicted, Stage::Base, Stage::Ours] |
| 327 | .iter() | 337 | .iter() |
| 328 | .filter(|our_stage| **our_stage != stage) | 338 | .filter(|our_stage| **our_stage != stage) |
| 329 | .find_map(|stage| { | 339 | .find_map(|stage| index.entry_index_by_path_and_stage(path, *stage).map(|_| *stage)); |
| 330 | index | ||
| 331 | .entry_index_by_path_and_stage(path, (*stage).into()) | ||
| 332 | .map(|_| (*stage).into()) | ||
| 333 | }); | ||
| 334 | let exists = self | 340 | let exists = self |
| 335 | .repo | 341 | .repo |
| 336 | .work_dir() | 342 | .work_dir() |
| 337 | .map_or(false, |root| root.join(gix_path::from_bstr(path)).exists()); | 343 | .map_or(false, |root| root.join(gix_path::from_bstr(path)).exists()); |
| 338 | self.err.push(Error::IndexLookup { | 344 | self.err.push(Error::IndexLookup { |
| 339 | desired_path: path.into(), | 345 | desired_path: path.into(), |
| 340 | desired_stage: stage.into(), | 346 | desired_stage: stage, |
| 341 | exists, | 347 | exists, |
| 342 | stage_hint, | 348 | stage_hint, |
| 343 | }); | 349 | }); |
| @@ -98,7 +98,7 @@ pub enum Error { · 97 unmodified lines | |||
| 98 | desired: usize, | 98 | desired: usize, |
| 99 | available: usize, | 99 | available: usize, |
| 100 | }, | 100 | }, |
| 101 | #[error("Path {desired_path:?} did not exist in index at stage {desired_stage}{}{}", stage_hint.map(|actual|format!(". It does exist at stage {actual}")).unwrap_or_default(), exists.then(|| ". It exists on disk").unwrap_or(". It does not exist on disk"))] | 101 | #[error("Path {desired_path:?} did not exist in index at stage {}{}{}", *desired_stage as u8, stage_hint.map(|actual|format!(". It does exist at stage {}", actual as u8)).unwrap_or_default(), exists.then(|| ". It exists on disk").unwrap_or(". It does not exist on disk"))] |
| 102 | IndexLookup { | 102 | IndexLookup { |
| 103 | desired_path: BString, | 103 | desired_path: BString, |
| 104 | desired_stage: gix_index::entry::Stage, | 104 | desired_stage: gix_index::entry::Stage, |
| @@ -199,10 +199,7 @@ mod submodule_status { · 198 unmodified lines | |||
| 199 | let local_repo = repo.to_thread_local(); | 199 | let local_repo = repo.to_thread_local(); |
| 200 | let submodule_paths = match local_repo.submodules()? { | 200 | let submodule_paths = match local_repo.submodules()? { |
| 201 | Some(sm) => { | 201 | Some(sm) => { |
| 202 | let mut v: Vec<_> = sm | 202 | let mut v: Vec<_> = sm.filter_map(|sm| sm.path().ok().map(Cow::into_owned)).collect(); |
| 203 | .filter(|sm| sm.is_active().unwrap_or_default()) | ||
| 204 | .filter_map(|sm| sm.path().ok().map(Cow::into_owned)) | ||
| 205 | .collect(); | ||
| 206 | v.sort(); | 203 | v.sort(); |
| 207 | v | 204 | v |
| 208 | } | 205 | } |
| @@ -271,7 +268,8 @@ mod submodule_status { · 62 unmodified lines | |||
| 271 | /// | 268 | /// |
| 272 | /// ### Submodules | 269 | /// ### Submodules |
| 273 | /// | 270 | /// |
| 274 | /// Note that submodules can be set to 'inactive' which automatically excludes them from the status operation. | 271 | /// Note that submodules can be set to 'inactive', which will not exclude them from the status operation, similar to |
| 272 | /// how `git status` includes them. | ||
| 275 | /// | 273 | /// |
| 276 | /// ### Index Changes | 274 | /// ### Index Changes |
| 277 | /// | 275 | /// |
| @@ -613,7 +611,10 @@ pub mod iter { · 335 unmodified lines | |||
| 613 | /// | 611 | /// |
| 614 | /// * `patterns` | 612 | /// * `patterns` |
| 615 | /// - Optional patterns to use to limit the paths to look at. If empty, all paths are considered. | 613 | /// - Optional patterns to use to limit the paths to look at. If empty, all paths are considered. |
| 616 | pub fn into_index_worktree_iter(self, patterns: Vec<BString>) -> Result<index_worktree::Iter, Error> { | 614 | pub fn into_index_worktree_iter( |
| 615 | self, | ||
| 616 | patterns: impl IntoIterator<Item = BString>, | ||
| 617 | ) -> Result<index_worktree::Iter, Error> { | ||
| 617 | let index = match self.index { | 618 | let index = match self.index { |
| 618 | None => IndexPersistedOrInMemory::Persisted(self.repo.index_or_empty()?), | 619 | None => IndexPersistedOrInMemory::Persisted(self.repo.index_or_empty()?), |
| 619 | Some(index) => index, | 620 | Some(index) => index, |
| @@ -634,6 +635,7 @@ pub mod iter { · 14 unmodified lines | |||
| 634 | { | 635 | { |
| 635 | let (tx, rx) = std::sync::mpsc::channel(); | 636 | let (tx, rx) = std::sync::mpsc::channel(); |
| 636 | let mut collect = Collect { tx }; | 637 | let mut collect = Collect { tx }; |
| 638 | let patterns: Vec<_> = patterns.into_iter().collect(); | ||
| 637 | let join = std::thread::Builder::new() | 639 | let join = std::thread::Builder::new() |
| 638 | .name("gix::status::index_worktree::iter::producer".into()) | 640 | .name("gix::status::index_worktree::iter::producer".into()) |
| 639 | .spawn({ | 641 | .spawn({ |