Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gitoxide-core/src/repository/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ fn print_index_entry_status(
) -> std::io::Result<()> {
let char_storage;
let status = match status {
EntryStatus::Conflict(conflict) => as_str(conflict),
EntryStatus::Conflict { summary, entries: _ } => as_str(summary),
EntryStatus::Change(change) => {
char_storage = change_to_char(&change);
std::str::from_utf8(std::slice::from_ref(&char_storage)).expect("valid ASCII")
Expand Down
2 changes: 1 addition & 1 deletion gix-index/src/entry/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ bitflags! {
/// In-memory flags.
///
/// Notably, not all of these will be persisted but can be used to aid all kinds of operations.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
pub struct Flags: u32 {
// TODO: could we use the pathlen ourselves to save 8 bytes? And how to handle longer paths than that? 0 as sentinel maybe?
/// The mask to obtain the length of the path associated with this entry, up to 4095 characters without extension.
Expand Down
44 changes: 31 additions & 13 deletions gix-status/src/index_as_worktree/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use gix_features::parallel::{in_parallel_if, Reduce};
use gix_filter::pipeline::convert::ToGitOutcome;
use gix_object::FindExt;

use crate::index_as_worktree::types::ConflictIndexEntry;
use crate::{
index_as_worktree::{
traits,
Expand Down Expand Up @@ -165,7 +166,7 @@ where
return None;
}
Conflict::try_from_entry(all_entries, state.path_backing, absolute_entry_index, entry_path)
.map(|(_conflict, offset)| offset)
.map(|(_conflict, offset, _entries)| offset)
});
if let Some(entries_to_skip_as_conflict_originates_in_previous_chunk) = offset {
// skip current entry as it's done, along with following conflict entries
Expand Down Expand Up @@ -287,10 +288,22 @@ impl<'index> State<'_, 'index> {
}
let status = if entry.stage_raw() != 0 {
Ok(
Conflict::try_from_entry(entries, self.path_backing, entry_index, path).map(|(conflict, offset)| {
*outer_entry_index += offset; // let out loop skip over entries related to the conflict
EntryStatus::Conflict(conflict)
}),
Conflict::try_from_entry(entries, self.path_backing, entry_index, path).map(
|(conflict, offset, entries)| {
*outer_entry_index += offset; // let out loop skip over entries related to the conflict
EntryStatus::Conflict {
summary: conflict,
entries: Box::new({
let mut a: [Option<ConflictIndexEntry>; 3] = Default::default();
let src = entries.into_iter().map(|e| e.map(ConflictIndexEntry::from));
for (a, b) in a.iter_mut().zip(src) {
*a = b;
}
a
}),
}
},
),
)
} else {
self.compute_status(entry, path, diff, submodule, objects)
Expand Down Expand Up @@ -622,20 +635,23 @@ impl Conflict {
/// Also return the amount of extra-entries that were part of the conflict declaration (not counting the entry at `start_index`)
///
/// If for some reason entry at `start_index` isn't in conflicting state, `None` is returned.
pub fn try_from_entry(
entries: &[gix_index::Entry],
///
/// Return `(Self, num_consumed_entries, three_possibly_entries)`.
pub fn try_from_entry<'entry>(
entries: &'entry [gix_index::Entry],
path_backing: &gix_index::PathStorageRef,
start_index: usize,
entry_path: &BStr,
) -> Option<(Self, usize)> {
) -> Option<(Self, usize, [Option<&'entry gix_index::Entry>; 3])> {
use Conflict::*;
let mut mask = None::<u8>;
let mut seen: [Option<&gix_index::Entry>; 3] = Default::default();

let mut count = 0_usize;
for stage in (start_index..(start_index + 3).min(entries.len())).filter_map(|idx| {
let mut num_consumed_entries = 0_usize;
for (stage, entry) in (start_index..(start_index + 3).min(entries.len())).filter_map(|idx| {
let entry = &entries[idx];
let stage = entry.stage_raw();
(stage > 0 && entry.path_in(path_backing) == entry_path).then_some(stage)
(stage > 0 && entry.path_in(path_backing) == entry_path).then_some((stage, entry))
}) {
// This could be `1 << (stage - 1)` but let's be specific.
*mask.get_or_insert(0) |= match stage {
Expand All @@ -644,7 +660,8 @@ impl Conflict {
3 => 0b100,
_ => 0,
};
count += 1;
num_consumed_entries = stage as usize - 1;
seen[num_consumed_entries] = Some(entry);
}

mask.map(|mask| {
Expand All @@ -659,7 +676,8 @@ impl Conflict {
0b111 => BothModified,
_ => unreachable!("BUG: bitshifts and typical entry layout doesn't allow for more"),
},
count - 1,
num_consumed_entries,
seen,
)
})
}
Expand Down
2 changes: 1 addition & 1 deletion gix-status/src/index_as_worktree/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Changes between an index and a worktree.
///
mod types;
pub use types::{Change, Conflict, Context, EntryStatus, Error, Options, Outcome, VisitEntry};
pub use types::{Change, Conflict, ConflictIndexEntry, Context, EntryStatus, Error, Options, Outcome, VisitEntry};

mod recorder;
pub use recorder::{Record, Recorder};
Expand Down
44 changes: 41 additions & 3 deletions gix-status/src/index_as_worktree/types.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::sync::atomic::AtomicBool;

use bstr::{BStr, BString};
use gix_index::entry;

/// The error returned by [index_as_worktree()`](crate::index_as_worktree()).
#[derive(Debug, thiserror::Error)]
Expand Down Expand Up @@ -144,11 +145,48 @@ pub enum Change<T = (), U = ()> {
SubmoduleModification(U),
}

/// Like [`gix_index::Entry`], but without disk-metadata.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct ConflictIndexEntry {
/// The object id for this entry's ODB representation (assuming it's up-to-date with it).
pub id: gix_hash::ObjectId,
/// Additional flags for use in algorithms and for efficiently storing stage information, primarily
/// to obtain the [stage](entry::Flags::stage()).
pub flags: entry::Flags,
/// The kind of item this entry represents - it's not all blobs in the index anymore.
pub mode: entry::Mode,
}

impl From<&gix_index::Entry> for ConflictIndexEntry {
fn from(
gix_index::Entry {
stat: _,
id,
flags,
mode,
..
}: &gix_index::Entry,
) -> Self {
ConflictIndexEntry {
id: *id,
flags: *flags,
mode: *mode,
}
}
}

/// Information about an entry.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum EntryStatus<T = (), U = ()> {
/// The entry is in a conflicting state, and we didn't collect any more information about it.
Conflict(Conflict),
/// The entry is in a conflicting state, and we provide all related entries along with a summary.
Conflict {
/// An analysis on the conflict itself based on the observed index entries.
summary: Conflict,
/// The entries from stage 1 to stage 3, where stage 1 is at index 0 and stage 3 at index 2.
/// Note that when there are conflicts, there is no stage 0.
/// Further, all entries are looking at the same path.
entries: Box<[Option<ConflictIndexEntry>; 3]>,
},
/// There is no conflict and a change was discovered.
Change(Change<T, U>),
/// The entry didn't change, but its state caused extra work that can be avoided next time if its stats would be updated to the
Expand Down
2 changes: 1 addition & 1 deletion gix-status/src/index_as_worktree_with_renames/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ pub(super) mod function {
fn kind(&self) -> ChangeKind {
match self {
ModificationOrDirwalkEntry::Modification(m) => match &m.status {
EntryStatus::Conflict(_) | EntryStatus::IntentToAdd | EntryStatus::NeedsUpdate(_) => {
EntryStatus::Conflict { .. } | EntryStatus::IntentToAdd | EntryStatus::NeedsUpdate(_) => {
ChangeKind::Modification
}
EntryStatus::Change(c) => match c {
Expand Down
8 changes: 4 additions & 4 deletions gix-status/src/index_as_worktree_with_renames/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub enum Sorting {
}

/// Provide additional information collected during the runtime of [`index_as_worktree_with_renames()`](crate::index_as_worktree_with_renames()).
#[derive(Clone, Debug, Default, PartialEq)]
#[derive(Clone, Debug, Default)]
pub struct Outcome {
/// The outcome of the modification check of tracked files.
pub tracked_file_modification: crate::index_as_worktree::Outcome,
Expand All @@ -49,7 +49,7 @@ pub struct Outcome {
}

/// Either an index entry for renames or another directory entry in case of copies.
#[derive(Clone, PartialEq, Debug)]
#[derive(Clone, Debug)]
pub enum RewriteSource<'index, ContentChange, SubmoduleStatus> {
/// The source originates in the index and is detected as missing in the working tree.
/// This can also happen for copies.
Expand Down Expand Up @@ -86,7 +86,7 @@ pub enum RewriteSource<'index, ContentChange, SubmoduleStatus> {
}

/// An 'entry' in the sense of a merge of modified tracked files and results from a directory walk.
#[derive(Clone, PartialEq, Debug)]
#[derive(Clone, Debug)]
pub enum Entry<'index, ContentChange, SubmoduleStatus> {
/// A tracked file was modified, and index-specific information is passed.
Modification {
Expand Down Expand Up @@ -218,7 +218,7 @@ impl<ContentChange, SubmoduleStatus> Entry<'_, ContentChange, SubmoduleStatus> {
pub fn summary(&self) -> Option<Summary> {
Some(match self {
Entry::Modification {
status: EntryStatus::Conflict(_),
status: EntryStatus::Conflict { .. },
..
} => Summary::Conflict,
Entry::Modification {
Expand Down
Loading
Loading