| @@ -5,7 +5,7 @@ use gix::index::Entry; · 4 unmodified lines |
| 5 | 5 | use gix::prelude::FindExt; |
| 6 | 6 | use gix::Progress; |
| 7 | 7 | use gix_status::index_as_worktree::traits::FastEq; |
| 8 | | −use gix_status::index_as_worktree::Change; |
| 8 | +use gix_status::index_as_worktree::{Change, Conflict, EntryStatus}; |
| 9 | 9 | |
| 10 | 10 | pub enum Submodules { |
| 11 | 11 | /// display all information about submodules, including ref changes, modifications and untracked files. |
| @@ -48,23 +48,42 @@ pub fn show( · 36 unmodified lines |
| 48 | 48 | )?; |
| 49 | 49 | let mut progress = progress.add_child("traverse index"); |
| 50 | 50 | let start = std::time::Instant::now(); |
| 51 | + let options = gix_status::index_as_worktree::Options { |
| 52 | + fs: repo.filesystem_options()?, |
| 53 | + thread_limit, |
| 54 | + stat: repo.stat_options()?, |
| 55 | + attributes: match repo |
| 56 | + .attributes_only( |
| 57 | + index, |
| 58 | + gix::worktree::stack::state::attributes::Source::WorktreeThenIdMapping, |
| 59 | + )? |
| 60 | + .detach() |
| 61 | + .state_mut() |
| 62 | + { |
| 63 | + gix::worktree::stack::State::AttributesStack(attrs) => std::mem::take(attrs), |
| 64 | + // TODO: this should be nicer by creating attributes directly, but it's a private API |
| 65 | + _ => unreachable!("state must be attributes stack only"), |
| 66 | + }, |
| 67 | + }; |
| 51 | 68 | gix_status::index_as_worktree( |
| 52 | 69 | index, |
| 53 | 70 | repo.work_dir() |
| 54 | 71 | .context("This operation cannot be run on a bare repository")?, |
| 55 | 72 | &mut Printer(out), |
| 56 | 73 | FastEq, |
| 74 | + Submodule, |
| 57 | 75 | { |
| 58 | 76 | let odb = repo.objects.clone().into_arc()?; |
| 59 | 77 | move |id, buf| odb.find_blob(id, buf) |
| 60 | 78 | }, |
| 61 | 79 | &mut progress, |
| 62 | 80 | pathspec.detach()?, |
| 63 | | − gix_status::index_as_worktree::Options { |
| 64 | | − fs: repo.filesystem_options()?, |
| 65 | | − thread_limit, |
| 66 | | − stat: repo.stat_options()?, |
| 67 | | − }, |
| 81 | + repo.filter_pipeline(Some(gix::hash::ObjectId::empty_tree(repo.object_hash())))? |
| 82 | + .0 |
| 83 | + .into_parts() |
| 84 | + .0, |
| 85 | + &gix::interrupt::IS_INTERRUPTED, |
| 86 | + options, |
| 68 | 87 | )?; |
| 69 | 88 | |
| 70 | 89 | writeln!(err, "\nhead -> index and untracked files aren't implemented yet")?; |
| @@ -72,6 +91,18 @@ pub fn show( · 1 unmodified line |
| 72 | 91 | Ok(()) |
| 73 | 92 | } |
| 74 | 93 | |
| 94 | +#[derive(Clone)] |
| 95 | +struct Submodule; |
| 96 | + |
| 97 | +impl gix_status::index_as_worktree::traits::SubmoduleStatus for Submodule { |
| 98 | + type Output = (); |
| 99 | + type Error = std::convert::Infallible; |
| 100 | + |
| 101 | + fn status(&mut self, _entry: &Entry, _rela_path: &BStr) -> Result<Option<Self::Output>, Self::Error> { |
| 102 | + Ok(None) |
| 103 | + } |
| 104 | +} |
| 105 | + |
| 75 | 106 | struct Printer<W>(W); |
| 76 | 107 | |
| 77 | 108 | impl<'index, W> gix_status::index_as_worktree::VisitEntry<'index> for Printer<W> |
| @@ -79,42 +110,54 @@ where · 1 unmodified line |
| 79 | 110 | W: std::io::Write, |
| 80 | 111 | { |
| 81 | 112 | type ContentChange = (); |
| 113 | + type SubmoduleStatus = (); |
| 82 | 114 | |
| 83 | 115 | fn visit_entry( |
| 84 | 116 | &mut self, |
| 85 | | − entry: &'index Entry, |
| 117 | + _entries: &'index [Entry], |
| 118 | + _entry: &'index Entry, |
| 119 | + _entry_index: usize, |
| 86 | 120 | rela_path: &'index BStr, |
| 87 | | − change: Option<Change<Self::ContentChange>>, |
| 88 | | − conflict: bool, |
| 121 | + status: EntryStatus<Self::ContentChange>, |
| 89 | 122 | ) { |
| 90 | | − self.visit_inner(entry, rela_path, change, conflict).ok(); |
| 123 | + self.visit_inner(rela_path, status).ok(); |
| 91 | 124 | } |
| 92 | 125 | } |
| 93 | 126 | |
| 94 | 127 | impl<W: std::io::Write> Printer<W> { |
| 95 | | − fn visit_inner( |
| 96 | | − &mut self, |
| 97 | | − _entry: &Entry, |
| 98 | | − rela_path: &BStr, |
| 99 | | − change: Option<Change<()>>, |
| 100 | | − conflict: bool, |
| 101 | | − ) -> anyhow::Result<()> { |
| 102 | | − if let Some(change) = conflict |
| 103 | | − .then_some('U') |
| 104 | | − .or_else(|| change.as_ref().and_then(change_to_char)) |
| 105 | | − { |
| 106 | | − writeln!(&mut self.0, "{change} {rela_path}")?; |
| 107 | | − } |
| 108 | | − Ok(()) |
| 128 | + fn visit_inner(&mut self, rela_path: &BStr, status: EntryStatus<()>) -> std::io::Result<()> { |
| 129 | + let char_storage; |
| 130 | + let status = match status { |
| 131 | + EntryStatus::Conflict(conflict) => as_str(conflict), |
| 132 | + EntryStatus::Change(change) => { |
| 133 | + char_storage = change_to_char(&change); |
| 134 | + std::str::from_utf8(std::slice::from_ref(&char_storage)).expect("valid ASCII") |
| 135 | + } |
| 136 | + EntryStatus::NeedsUpdate(_) => return Ok(()), |
| 137 | + EntryStatus::IntentToAdd => "A", |
| 138 | + }; |
| 139 | + |
| 140 | + writeln!(&mut self.0, "{status: >3} {rela_path}") |
| 109 | 141 | } |
| 110 | 142 | } |
| 111 | 143 | |
| 112 | | −fn change_to_char(change: &Change<()>) -> Option<char> { |
| 144 | +fn as_str(c: Conflict) -> &'static str { |
| 145 | + match c { |
| 146 | + Conflict::BothDeleted => "DD", |
| 147 | + Conflict::AddedByUs => "AU", |
| 148 | + Conflict::DeletedByThem => "UD", |
| 149 | + Conflict::AddedByThem => "UA", |
| 150 | + Conflict::DeletedByUs => "DU", |
| 151 | + Conflict::BothAdded => "AA", |
| 152 | + Conflict::BothModified => "UU", |
| 153 | + } |
| 154 | +} |
| 155 | + |
| 156 | +fn change_to_char(change: &Change<()>) -> u8 { |
| 113 | 157 | // Known status letters: https://github.com/git/git/blob/6807fcfedab84bc8cd0fbf721bc13c4e68cda9ae/diff.h#L613 |
| 114 | | − Some(match change { |
| 115 | | − Change::Removed => 'D', |
| 116 | | − Change::Type => 'T', |
| 117 | | − Change::Modification { .. } => 'M', |
| 118 | | − Change::IntentToAdd => return None, |
| 119 | | − }) |
| 158 | + match change { |
| 159 | + Change::Removed => b'D', |
| 160 | + Change::Type => b'T', |
| 161 | + Change::SubmoduleModification(_) | Change::Modification { .. } => b'M', |
| 162 | + } |
| 120 | 163 | } |