Skip to content

Commit 2dc373f

Browse files
committed
feat: provide Entry::worktree_summary()
That way it's possible to more easily and straight-forwardly understand the status of an entry, comparing index to worktree.
1 parent 917634f commit 2dc373f

File tree

3 files changed

+151
-4
lines changed

3 files changed

+151
-4
lines changed

gix-status/src/index_as_worktree_with_renames/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Changes between the index and the worktree along with optional rename tracking.
22
mod types;
3-
pub use types::{Context, DirwalkContext, Entry, Error, Options, Outcome, RewriteSource, Sorting, VisitEntry};
3+
pub use types::{Context, DirwalkContext, Entry, Error, Options, Outcome, RewriteSource, Sorting, Summary, VisitEntry};
44

55
mod recorder;
66
pub use recorder::Recorder;

gix-status/src/index_as_worktree_with_renames/types.rs

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::index_as_worktree::EntryStatus;
1+
use crate::index_as_worktree::{Change, EntryStatus};
22
use bstr::{BStr, ByteSlice};
33
use std::sync::atomic::AtomicBool;
44

@@ -141,6 +141,58 @@ pub enum Entry<'index, ContentChange, SubmoduleStatus> {
141141
},
142142
}
143143

144+
/// An easy to grasp summary of the changes of the worktree compared to the index.
145+
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
146+
pub enum Summary {
147+
/// An entry exists in the index but doesn't in the worktree.
148+
Removed,
149+
/// A file exists in the worktree but doesn't have a corresponding entry in the index.
150+
///
151+
/// In a `git status`, this would be an untracked file.
152+
Added,
153+
/// A file or submodule was modified, compared to the state recorded in the index.
154+
/// On Unix, the change of executable bit also counts as modification.
155+
///
156+
/// If the modification is a submodule, it could also stem from various other factors, like
157+
/// having modified or untracked files, or changes in the index.
158+
Modified,
159+
/// The type of the entry in the worktree changed compared to the index.
160+
///
161+
/// This can happen if a file in the worktree now is a directory, or a symlink, for example.
162+
TypeChange,
163+
/// A match between an entry in the index and a differently named file in the worktree was detected,
164+
/// considering the index the source of a rename operation, and the worktree file the destination.
165+
///
166+
/// Note that the renamed file may also have been modified, but is considered similar enough.
167+
///
168+
/// To obtain this state, rewrite-tracking must have been enabled, as otherwise the source would be
169+
/// considered `Removed` and the destination would be considered `Added`.
170+
Renamed,
171+
/// A match between an entry in the index and a differently named file in the worktree was detected,
172+
/// considering the index the source of the copy of a worktree file.
173+
///
174+
/// Note that the copied file may also have been modified, but is considered similar enough.
175+
///
176+
/// To obtain this state, rewrite-and-copy-tracking must have been enabled, as otherwise the source would be
177+
/// considered `Removed` and the destination would be considered `Added`.
178+
Copied,
179+
/// An index entry with a corresponding worktree file that corresponds to an untracked worktree
180+
/// file marked with `git add --intent-to-add`.
181+
///
182+
/// This means it's not available in the object database yet even though now an entry exists
183+
/// that represents the worktree file.
184+
/// The entry represents the promise of adding a new file, no matter the actual stat or content.
185+
/// Effectively this means nothing changed.
186+
/// This also means the file is still present, and that no detailed change checks were performed.
187+
IntentToAdd,
188+
/// Describes a conflicting entry in the index, which also means that
189+
/// no further comparison to the worktree file was performed.
190+
///
191+
/// As this variant only describes the state of the index, the corresponding worktree file may
192+
/// or may not exist.
193+
Conflict,
194+
}
195+
144196
/// Access
145197
impl<ContentChange, SubmoduleStatus> RewriteSource<'_, ContentChange, SubmoduleStatus> {
146198
/// The repository-relative path of this source.
@@ -156,6 +208,47 @@ impl<ContentChange, SubmoduleStatus> RewriteSource<'_, ContentChange, SubmoduleS
156208

157209
/// Access
158210
impl<ContentChange, SubmoduleStatus> Entry<'_, ContentChange, SubmoduleStatus> {
211+
/// Return a summary of the entry as digest of its status, or `None` if this entry is
212+
/// created from the directory walk and is *not untracked*, or if it is merely to communicate
213+
/// a needed update to the index entry.
214+
pub fn summary(&self) -> Option<Summary> {
215+
Some(match self {
216+
Entry::Modification {
217+
status: EntryStatus::Conflict(_),
218+
..
219+
} => Summary::Conflict,
220+
Entry::Modification {
221+
status: EntryStatus::IntentToAdd,
222+
..
223+
} => Summary::IntentToAdd,
224+
Entry::Modification {
225+
status: EntryStatus::NeedsUpdate(_),
226+
..
227+
} => return None,
228+
Entry::Modification {
229+
status: EntryStatus::Change(change),
230+
..
231+
} => match change {
232+
Change::SubmoduleModification(_) | Change::Modification { .. } => Summary::Modified,
233+
Change::Type => Summary::TypeChange,
234+
Change::Removed => Summary::Removed,
235+
},
236+
Entry::DirectoryContents { entry, .. } => {
237+
if matches!(entry.status, gix_dir::entry::Status::Untracked) {
238+
Summary::Added
239+
} else {
240+
return None;
241+
}
242+
}
243+
Entry::Rewrite { copy, .. } => {
244+
if *copy {
245+
Summary::Copied
246+
} else {
247+
Summary::Renamed
248+
}
249+
}
250+
})
251+
}
159252
/// The repository-relative path at which the source of a rewrite is located.
160253
///
161254
/// If this isn't a rewrite, the path is the location of the entry itself.

gix-status/tests/status/index_as_worktree_with_renames.rs

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ use gix_diff::rewrites::CopySource;
55
use gix_status::index_as_worktree::traits::FastEq;
66
use gix_status::index_as_worktree::{Change, EntryStatus};
77
use gix_status::index_as_worktree_with_renames;
8-
use gix_status::index_as_worktree_with_renames::{Context, DirwalkContext, Entry, Options, Outcome, Recorder, Sorting};
8+
use gix_status::index_as_worktree_with_renames::{
9+
Context, DirwalkContext, Entry, Options, Outcome, Recorder, Sorting, Summary,
10+
};
911
use pretty_assertions::assert_eq;
1012

1113
#[test]
@@ -267,10 +269,31 @@ fn fixture_filtered_detailed(
267269
)
268270
.unwrap();
269271

270-
assert_eq!(records_to_expectations(&recorder.records), expected);
272+
let actual = records_to_expectations(&recorder.records);
273+
assert_eq!(actual, expected);
274+
assert_summary(&recorder.records, expected);
271275
cleanup(outcome)
272276
}
273277

278+
fn assert_summary(entries: &[Entry<(), ()>], expected: &[Expectation]) {
279+
let entries: Vec<_> = entries
280+
.iter()
281+
.filter(|r| {
282+
!matches!(
283+
r,
284+
Entry::Modification {
285+
status: EntryStatus::NeedsUpdate(..),
286+
..
287+
}
288+
)
289+
})
290+
.collect();
291+
assert_eq!(entries.len(), expected.len());
292+
for (entry, expected) in entries.iter().zip(expected) {
293+
assert_eq!(entry.summary(), expected.summary());
294+
}
295+
}
296+
274297
fn records_to_expectations<'a>(recs: &'a [Entry<'_, (), ()>]) -> Vec<Expectation<'a>> {
275298
recs.iter()
276299
.filter(|r| {
@@ -328,3 +351,34 @@ enum Expectation<'a> {
328351
copy: bool,
329352
},
330353
}
354+
355+
impl Expectation<'_> {
356+
pub fn summary(&self) -> Option<Summary> {
357+
Some(match self {
358+
Expectation::Modification { status, .. } => match status {
359+
EntryStatus::Conflict(_) => Summary::Conflict,
360+
EntryStatus::Change(change) => match change {
361+
Change::Removed => Summary::Removed,
362+
Change::Type => Summary::TypeChange,
363+
Change::Modification { .. } | Change::SubmoduleModification(_) => Summary::Modified,
364+
},
365+
EntryStatus::NeedsUpdate(_) => return None,
366+
EntryStatus::IntentToAdd => Summary::IntentToAdd,
367+
},
368+
Expectation::DirwalkEntry { status, .. } => {
369+
if matches!(status, gix_dir::entry::Status::Untracked) {
370+
Summary::Added
371+
} else {
372+
return None;
373+
}
374+
}
375+
Expectation::Rewrite { copy, .. } => {
376+
if *copy {
377+
Summary::Copied
378+
} else {
379+
Summary::Renamed
380+
}
381+
}
382+
})
383+
}
384+
}

0 commit comments

Comments
 (0)