Skip to content

Commit eea1294

Browse files
committed
feat!: replace conflict marker with detailed decoding of stages.
We also adjust the returned data structure to allow the input to be immutable, which delegates entry updates to the caller. This also paves the way for rename tracking, which requires free access to entries for searching renames among the added and removed items, and/or copies among the added ones.
1 parent b55a8d5 commit eea1294

File tree

7 files changed

+482
-186
lines changed

7 files changed

+482
-186
lines changed

gix-status/src/index_as_worktree/function.rs

Lines changed: 158 additions & 73 deletions
Large diffs are not rendered by default.

gix-status/src/index_as_worktree/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Changes between an index and a worktree.
22
///
33
mod types;
4-
pub use types::{Change, Error, Options, Outcome, VisitEntry};
4+
pub use types::{Change, Conflict, EntryStatus, Error, Options, Outcome, VisitEntry};
55

66
mod recorder;
77
pub use recorder::{Record, Recorder};
Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use bstr::BStr;
22
use gix_index as index;
33

4-
use crate::index_as_worktree::{Change, VisitEntry};
4+
use crate::index_as_worktree::{EntryStatus, VisitEntry};
55

66
/// A record of a change.
77
///
@@ -14,10 +14,8 @@ pub struct Record<'index, T, U> {
1414
pub entry_index: usize,
1515
/// The path to the entry.
1616
pub relative_path: &'index BStr,
17-
/// The change itself, or `None` if there is only a conflict.
18-
pub change: Option<Change<T, U>>,
19-
/// information about the conflict itself
20-
pub conflict: bool,
17+
/// The status information itself.
18+
pub status: EntryStatus<T, U>,
2119
}
2220

2321
/// Convenience implementation of [`VisitEntry`] that collects all non-trivial changes into a `Vec`.
@@ -33,20 +31,17 @@ impl<'index, T: Send, U: Send> VisitEntry<'index> for Recorder<'index, T, U> {
3331

3432
fn visit_entry(
3533
&mut self,
34+
_entries: &'index [index::Entry],
3635
entry: &'index index::Entry,
3736
entry_index: usize,
38-
rela_path: &'index BStr,
39-
change: Option<Change<Self::ContentChange, Self::SubmoduleStatus>>,
40-
conflict: bool,
37+
relative_path: &'index BStr,
38+
status: EntryStatus<Self::ContentChange, Self::SubmoduleStatus>,
4139
) {
42-
if conflict || change.is_some() {
43-
self.records.push(Record {
44-
entry,
45-
entry_index,
46-
relative_path: rela_path,
47-
change,
48-
conflict,
49-
})
50-
}
40+
self.records.push(Record {
41+
entry,
42+
entry_index,
43+
relative_path,
44+
status,
45+
})
5146
}
5247
}

gix-status/src/index_as_worktree/types.rs

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,11 @@ pub struct Outcome {
5353
pub entries_skipped_by_entry_flags: usize,
5454
/// The amount of times we queried symlink-metadata for a file on disk.
5555
pub symlink_metadata_calls: usize,
56-
/// The amount of entries whose stats have been updated as its modification couldn't be determined without an expensive calculation.
56+
/// The amount of entries whose stats would need to be updated as its modification couldn't be determined without
57+
/// an expensive calculation.
5758
///
5859
/// With these updates, this calculation will be avoided next time the status runs.
60+
/// Note that the stat updates are delegated to the caller.
5961
pub entries_updated: usize,
6062
/// The amount of entries that were considered racy-clean - they will need thorough checking to see if they are truly clean,
6163
/// i.e. didn't change.
@@ -79,7 +81,7 @@ impl Outcome {
7981
}
8082

8183
/// How an index entry needs to be changed to obtain the destination worktree state, i.e. `entry.apply(this_change) == worktree-entry`.
82-
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
84+
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
8385
pub enum Change<T = (), U = ()> {
8486
/// This corresponding file does not exist in the worktree anymore.
8587
Removed,
@@ -95,6 +97,11 @@ pub enum Change<T = (), U = ()> {
9597
/// If there is no content change and only the executable bit
9698
/// changed then this is `None`.
9799
content_change: Option<T>,
100+
/// If true, the caller is expected to set [entry.stat.size = 0](gix_index::entry::Stat::size) to assure this
101+
/// otherwise racily clean entry can still be detected as dirty next time this is called, but this time without
102+
/// reading it from disk to hash it. It's a performance optimization and not doing so won't change the correctness
103+
/// of the operation.
104+
set_entry_stat_size_zero: bool,
98105
},
99106
/// A submodule is initialized and checked out, and there was modification to either:
100107
///
@@ -105,28 +112,73 @@ pub enum Change<T = (), U = ()> {
105112
/// The exact nature of the modification is handled by the caller which may retain information per submodule or
106113
/// re-compute details as needed when seeing this variant.
107114
SubmoduleModification(U),
108-
/// An index entry that correspond to an untracked worktree file marked with `git add --intent-to-add`.
115+
}
116+
117+
/// Information about an entry.
118+
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
119+
pub enum EntryStatus<T = (), U = ()> {
120+
/// The entry is in a conflicting state, and we didn't collect any more information about it.
121+
Conflict(Conflict),
122+
/// There is no conflict and a change was discovered.
123+
Change(Change<T, U>),
124+
/// 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
125+
/// given stat.
126+
NeedsUpdate(
127+
/// The new stats which represent what's currently in the working tree. If these replace the current stats in the entry,
128+
/// next time this operation runs we can determine the actual state much faster.
129+
gix_index::entry::Stat,
130+
),
131+
/// An index entry that corresponds to an untracked worktree file marked with `git add --intent-to-add`.
109132
///
110-
/// This means it's not available in the object database yet
111-
/// even though now an entry exists that represents the worktree file.
133+
/// This means it's not available in the object database yet even though now an entry exists that represents the worktree file.
134+
/// The entry represents the promise of adding a new file, no matter the actual stat or content.
135+
/// Effectively this means nothing changed.
136+
/// This also means the file is still present, and that no detailed change checks were performed.
112137
IntentToAdd,
113138
}
114139

115-
/// Observe changes by comparing an index entry to the worktree or another index.
140+
impl<T, U> From<Change<T, U>> for EntryStatus<T, U> {
141+
fn from(value: Change<T, U>) -> Self {
142+
EntryStatus::Change(value)
143+
}
144+
}
145+
146+
/// Describes a conflicting entry as comparison between 'our' version and 'their' version of it.
147+
///
148+
/// If one side isn't specified, it is assumed to have modified the entry. In general, there would be no conflict
149+
/// if both parties ended up in the same state.
150+
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
151+
pub enum Conflict {
152+
/// Both deleted a different version of the entry.
153+
BothDeleted,
154+
/// We added, they modified, ending up in different states.
155+
AddedByUs,
156+
/// They deleted the entry, we modified it.
157+
DeletedByThem,
158+
/// They added the entry, we modified it, ending up in different states.
159+
AddedByThem,
160+
/// We deleted the entry, they modified it, ending up in different states.
161+
DeletedByUs,
162+
/// Both added the entry in different states.
163+
BothAdded,
164+
/// Both modified the entry, ending up in different states.
165+
BothModified,
166+
}
167+
168+
/// Observe the status of an entry by comparing an index entry to the worktree.
116169
pub trait VisitEntry<'index> {
117170
/// Data generated by comparing an entry with a file.
118171
type ContentChange;
119172
/// Data obtained when checking the submodule status.
120173
type SubmoduleStatus;
121-
/// Observe the `change` of `entry` at the repository-relative `rela_path` at `entry_index`
122-
/// (relative to the complete list of all index entries), indicating whether or not it has a `conflict`.
123-
/// If `change` is `None`, there is no change.
174+
/// Observe the `status` of `entry` at the repository-relative `rela_path` at `entry_index`
175+
/// (for accessing `entry` and surrounding in the complete list of `entries`).
124176
fn visit_entry(
125177
&mut self,
178+
entries: &'index [gix_index::Entry],
126179
entry: &'index gix_index::Entry,
127180
entry_index: usize,
128181
rela_path: &'index BStr,
129-
change: Option<Change<Self::ContentChange, Self::SubmoduleStatus>>,
130-
conflict: bool,
182+
status: EntryStatus<Self::ContentChange, Self::SubmoduleStatus>,
131183
);
132184
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/bin/bash
2+
set -eu -o pipefail
3+
4+
(git init both-deleted && cd both-deleted
5+
echo test > file
6+
git add file && git commit -m file &&
7+
git branch alt && git mv file added-by-them
8+
git commit -m "file renamed in added-by-them" && git checkout alt
9+
git mv file added-by-us
10+
git commit -m "file renamed in added-by-us"
11+
git reset --hard alt
12+
git merge main || :
13+
)
14+
15+
(git init deleted-by-us && cd deleted-by-us
16+
git init
17+
>file && git add file && git commit -m "initial"
18+
echo change >> file && git commit -am "modify"
19+
git checkout -b side HEAD^
20+
git rm file
21+
git commit -m delete
22+
git merge main || :
23+
)
24+
25+
(git init deleted-by-them && cd deleted-by-them
26+
echo "This is some content." > file
27+
git add file
28+
git commit -m "Initial commit"
29+
git checkout -b conflict
30+
git rm file
31+
git commit -m "Delete file in feature branch"
32+
git checkout main
33+
echo "Modified by main branch." >> file
34+
git add file
35+
git commit -m "Modified file in main branch"
36+
git merge conflict || :
37+
)
38+
39+
(git init both-modified && cd both-modified
40+
git init
41+
> file && git add file && git commit -m "init"
42+
43+
git checkout -b conflict
44+
echo conflicting >> file && git commit -am "alt-change"
45+
46+
git checkout main
47+
echo other >> file && git commit -am "change"
48+
49+
git merge conflict || :
50+
)
51+
52+
(git init both-added && cd both-added
53+
git init
54+
set -x
55+
echo init >> deleted-by-them && git add . && git commit -m "init"
56+
57+
git checkout -b second_branch
58+
git rm deleted-by-them
59+
git commit -m "deleted-by-them deleted on second_branch"
60+
echo second > both-added && git add . && git commit -m second
61+
62+
git checkout main
63+
echo on_second > deleted-by-them && git commit -am "on second"
64+
echo main > both-added && git add . && git commit -m main
65+
66+
git merge second_branch || :
67+
)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:855ef3e8f1cf518daab18a572ad9dc4a7c532a213d1b9f8a78f32f5346b518a2
3+
size 19036

0 commit comments

Comments
 (0)