Skip to content

Commit 46e5919

Browse files
committed
feat: gix status auto-writes changed indices.
This prevents expensive operations to re-occour.
1 parent b5b50f8 commit 46e5919

File tree

3 files changed

+59
-7
lines changed

3 files changed

+59
-7
lines changed

Diff for: gitoxide-core/src/repository/status.rs

+54-7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub struct Options {
2121
pub submodules: Submodules,
2222
pub thread_limit: Option<usize>,
2323
pub statistics: bool,
24+
pub allow_write: bool,
2425
}
2526

2627
pub fn show(
@@ -34,6 +35,7 @@ pub fn show(
3435
// TODO: implement this
3536
submodules: _,
3637
thread_limit,
38+
allow_write,
3739
statistics,
3840
}: Options,
3941
) -> anyhow::Result<()> {
@@ -67,11 +69,15 @@ pub fn show(
6769
_ => unreachable!("state must be attributes stack only"),
6870
},
6971
};
72+
let mut printer = Printer {
73+
out,
74+
changes: Vec::new(),
75+
};
7076
let outcome = gix_status::index_as_worktree(
7177
index,
7278
repo.work_dir()
7379
.context("This operation cannot be run on a bare repository")?,
74-
&mut Printer(out),
80+
&mut printer,
7581
FastEq,
7682
Submodule,
7783
{
@@ -88,6 +94,27 @@ pub fn show(
8894
options,
8995
)?;
9096

97+
if outcome.entries_to_update != 0 && allow_write {
98+
{
99+
let entries = index.entries_mut();
100+
for (entry_index, change) in printer.changes {
101+
let entry = &mut entries[entry_index];
102+
match change {
103+
ApplyChange::SetSizeToZero => {
104+
entry.stat.size = 0;
105+
}
106+
ApplyChange::NewStat(new_stat) => {
107+
entry.stat = new_stat;
108+
}
109+
}
110+
}
111+
}
112+
index.write(gix::index::write::Options {
113+
extensions: Default::default(),
114+
skip_hash: false, // TODO: make this based on configuration
115+
})?;
116+
}
117+
91118
if statistics {
92119
writeln!(err, "{outcome:#?}").ok();
93120
}
@@ -109,7 +136,15 @@ impl gix_status::index_as_worktree::traits::SubmoduleStatus for Submodule {
109136
}
110137
}
111138

112-
struct Printer<W>(W);
139+
struct Printer<W> {
140+
out: W,
141+
changes: Vec<(usize, ApplyChange)>,
142+
}
143+
144+
enum ApplyChange {
145+
SetSizeToZero,
146+
NewStat(gix::index::entry::Stat),
147+
}
113148

114149
impl<'index, W> gix_status::index_as_worktree::VisitEntry<'index> for Printer<W>
115150
where
@@ -122,28 +157,40 @@ where
122157
&mut self,
123158
_entries: &'index [Entry],
124159
_entry: &'index Entry,
125-
_entry_index: usize,
160+
entry_index: usize,
126161
rela_path: &'index BStr,
127162
status: EntryStatus<Self::ContentChange>,
128163
) {
129-
self.visit_inner(rela_path, status).ok();
164+
self.visit_inner(entry_index, rela_path, status).ok();
130165
}
131166
}
132167

133168
impl<W: std::io::Write> Printer<W> {
134-
fn visit_inner(&mut self, rela_path: &BStr, status: EntryStatus<()>) -> std::io::Result<()> {
169+
fn visit_inner(&mut self, entry_index: usize, rela_path: &BStr, status: EntryStatus<()>) -> std::io::Result<()> {
135170
let char_storage;
136171
let status = match status {
137172
EntryStatus::Conflict(conflict) => as_str(conflict),
138173
EntryStatus::Change(change) => {
174+
if matches!(
175+
change,
176+
Change::Modification {
177+
set_entry_stat_size_zero: true,
178+
..
179+
}
180+
) {
181+
self.changes.push((entry_index, ApplyChange::SetSizeToZero))
182+
}
139183
char_storage = change_to_char(&change);
140184
std::str::from_utf8(std::slice::from_ref(&char_storage)).expect("valid ASCII")
141185
}
142-
EntryStatus::NeedsUpdate(_) => return Ok(()),
186+
EntryStatus::NeedsUpdate(stat) => {
187+
self.changes.push((entry_index, ApplyChange::NewStat(stat)));
188+
return Ok(());
189+
}
143190
EntryStatus::IntentToAdd => "A",
144191
};
145192

146-
writeln!(&mut self.0, "{status: >3} {rela_path}")
193+
writeln!(&mut self.out, "{status: >3} {rela_path}")
147194
}
148195
}
149196

Diff for: src/plumbing/main.rs

+2
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ pub fn main() -> Result<()> {
136136
Subcommands::Status(crate::plumbing::options::status::Platform {
137137
statistics,
138138
submodules,
139+
no_write,
139140
pathspec,
140141
}) => prepare_and_run(
141142
"status",
@@ -156,6 +157,7 @@ pub fn main() -> Result<()> {
156157
format,
157158
statistics,
158159
thread_limit: thread_limit.or(cfg!(target_os = "macos").then_some(3)), // TODO: make this a configurable when in `gix`, this seems to be optimal on MacOS, linux scales though!
160+
allow_write: !no_write,
159161
submodules: match submodules {
160162
Submodules::All => core::repository::status::Submodules::All,
161163
Submodules::RefChange => core::repository::status::Submodules::RefChange,

Diff for: src/plumbing/options/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ pub mod status {
208208
/// Print additional statistics to help understanding performance.
209209
#[clap(long, short = 's')]
210210
pub statistics: bool,
211+
/// Don't write back a changed index, which forces this operation to always be idempotent.
212+
#[clap(long)]
213+
pub no_write: bool,
211214
/// The git path specifications to list attributes for, or unset to read from stdin one per line.
212215
#[clap(value_parser = CheckPathSpec)]
213216
pub pathspec: Vec<BString>,

0 commit comments

Comments
 (0)