Skip to content

Commit f094f71

Browse files
committed
feat: gix status with basic index-worktree comparison
1 parent f9d14d8 commit f094f71

File tree

6 files changed

+179
-1
lines changed

6 files changed

+179
-1
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gitoxide-core/Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,11 @@ serde = ["gix/serde", "dep:serde_json", "dep:serde", "bytesize/serde"]
4444

4545
[dependencies]
4646
# deselect everything else (like "performance") as this should be controllable by the parent application.
47-
gix = { version = "^0.53.1", path = "../gix", default-features = false, features = ["blob-diff", "revision", "mailmap", "excludes", "attributes", "worktree-mutation", "credentials", "interrupt"] }
47+
gix = { version = "^0.53.1", path = "../gix", default-features = false, features = ["blob-diff", "revision", "mailmap", "excludes", "attributes", "worktree-mutation", "credentials", "interrupt", "status"] }
4848
gix-pack-for-configuration-only = { package = "gix-pack", version = "^0.42.0", path = "../gix-pack", default-features = false, features = ["pack-cache-lru-dynamic", "pack-cache-lru-static", "generate", "streaming-input"] }
4949
gix-transport-configuration-only = { package = "gix-transport", version = "^0.36.0", path = "../gix-transport", default-features = false }
5050
gix-archive-for-configuration-only = { package = "gix-archive", version = "^0.4.0", path = "../gix-archive", optional = true, features = ["tar", "tar_gz"] }
51+
gix-status = { version = "0.1.0", path = "../gix-status" }
5152
serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"] }
5253
anyhow = "1.0.42"
5354
thiserror = "1.0.34"

gitoxide-core/src/repository/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ pub mod mailmap;
4040
pub mod odb;
4141
pub mod remote;
4242
pub mod revision;
43+
pub mod status;
4344
pub mod submodule;
4445
pub mod tree;
4546
pub mod verify;
+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
use crate::OutputFormat;
2+
use anyhow::{bail, Context};
3+
use gix::bstr::{BStr, BString};
4+
use gix::index::Entry;
5+
use gix::prelude::FindExt;
6+
use gix::Progress;
7+
use gix_status::index_as_worktree::content::FastEq;
8+
use gix_status::index_as_worktree::Change;
9+
10+
pub enum Submodules {
11+
/// display all information about submodules, including ref changes, modifications and untracked files.
12+
All,
13+
/// Compare only the configuration of the superprojects commit with the actually checked out `HEAD` commit.
14+
RefChange,
15+
/// See if there are worktree modifications compared to the index, but do not check for untracked files.
16+
Modifications,
17+
}
18+
19+
pub struct Options {
20+
pub format: OutputFormat,
21+
pub submodules: Submodules,
22+
pub thread_limit: Option<usize>,
23+
}
24+
25+
pub fn show(
26+
repo: gix::Repository,
27+
pathspecs: Vec<BString>,
28+
out: impl std::io::Write,
29+
mut err: impl std::io::Write,
30+
mut progress: impl gix::NestedProgress,
31+
Options {
32+
format,
33+
// TODO: implement this
34+
submodules: _,
35+
thread_limit,
36+
}: Options,
37+
) -> anyhow::Result<()> {
38+
if format != OutputFormat::Human {
39+
bail!("Only human format is supported right now");
40+
}
41+
let mut index = repo.index()?;
42+
let index = gix::threading::make_mut(&mut index);
43+
let pathspec = repo.pathspec(
44+
pathspecs,
45+
true,
46+
index,
47+
gix::worktree::stack::state::attributes::Source::WorktreeThenIdMapping,
48+
)?;
49+
let mut progress = progress.add_child("traverse index");
50+
let start = std::time::Instant::now();
51+
gix_status::index_as_worktree(
52+
index,
53+
repo.work_dir()
54+
.context("This operation cannot be run on a bare repository")?,
55+
&mut Printer(out),
56+
FastEq,
57+
{
58+
let odb = repo.objects.clone().into_arc()?;
59+
move |id, buf| odb.find_blob(id, buf)
60+
},
61+
&mut progress,
62+
pathspec.detach()?,
63+
gix_status::index_as_worktree::Options {
64+
fs: repo.filesystem_options()?,
65+
thread_limit,
66+
stat: repo.stat_options()?,
67+
},
68+
)?;
69+
70+
writeln!(err, "\nhead -> index and untracked files aren't implemented yet")?;
71+
progress.show_throughput(start);
72+
Ok(())
73+
}
74+
75+
struct Printer<W>(W);
76+
77+
impl<'index, W> gix_status::index_as_worktree::VisitEntry<'index> for Printer<W>
78+
where
79+
W: std::io::Write,
80+
{
81+
type ContentChange = ();
82+
83+
fn visit_entry(
84+
&mut self,
85+
entry: &'index Entry,
86+
rela_path: &'index BStr,
87+
change: Option<Change<Self::ContentChange>>,
88+
conflict: bool,
89+
) {
90+
self.visit_inner(entry, rela_path, change, conflict).ok();
91+
}
92+
}
93+
94+
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(())
109+
}
110+
}
111+
112+
fn change_to_char(change: &Change<()>) -> Option<char> {
113+
// 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+
})
120+
}

src/plumbing/main.rs

+27
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,33 @@ pub fn main() -> Result<()> {
133133
})?;
134134

135135
match cmd {
136+
Subcommands::Status(crate::plumbing::options::status::Platform { submodules, pathspec }) => prepare_and_run(
137+
"status",
138+
trace,
139+
auto_verbose,
140+
progress,
141+
progress_keep_open,
142+
None,
143+
move |progress, out, err| {
144+
use crate::plumbing::options::status::Submodules;
145+
core::repository::status::show(
146+
repository(Mode::Lenient)?,
147+
pathspec,
148+
out,
149+
err,
150+
progress,
151+
core::repository::status::Options {
152+
format,
153+
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!
154+
submodules: match submodules {
155+
Submodules::All => core::repository::status::Submodules::All,
156+
Submodules::RefChange => core::repository::status::Submodules::RefChange,
157+
Submodules::Modifications => core::repository::status::Submodules::Modifications,
158+
},
159+
},
160+
)
161+
},
162+
),
136163
Subcommands::Submodule(platform) => match platform
137164
.cmds
138165
.unwrap_or(crate::plumbing::options::submodule::Subcommands::List)

src/plumbing/options/mod.rs

+28
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ pub enum Subcommands {
127127
Submodule(submodule::Platform),
128128
/// Show which git configuration values are used or planned.
129129
ConfigTree,
130+
Status(status::Platform),
130131
Config(config::Platform),
131132
#[cfg(feature = "gitoxide-core-tools-corpus")]
132133
Corpus(corpus::Platform),
@@ -183,6 +184,33 @@ pub mod archive {
183184
}
184185
}
185186

187+
pub mod status {
188+
use gitoxide::shared::CheckPathSpec;
189+
use gix::bstr::BString;
190+
191+
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
192+
pub enum Submodules {
193+
/// display all information about submodules, including ref changes, modifications and untracked files.
194+
#[default]
195+
All,
196+
/// Compare only the configuration of the superprojects commit with the actually checked out `HEAD` commit.
197+
RefChange,
198+
/// See if there are worktree modifications compared to the index, but do not check for untracked files.
199+
Modifications,
200+
}
201+
202+
#[derive(Debug, clap::Parser)]
203+
#[command(about = "compute repository status similar to `git status`")]
204+
pub struct Platform {
205+
/// Define how to display submodule status.
206+
#[clap(long, default_value = "all")]
207+
pub submodules: Submodules,
208+
/// The git path specifications to list attributes for, or unset to read from stdin one per line.
209+
#[clap(value_parser = CheckPathSpec)]
210+
pub pathspec: Vec<BString>,
211+
}
212+
}
213+
186214
#[cfg(feature = "gitoxide-core-tools-corpus")]
187215
pub mod corpus {
188216
use std::path::PathBuf;

0 commit comments

Comments
 (0)