Skip to content

Commit

Permalink
feat: gix status with basic index-worktree comparison
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Sep 23, 2023
1 parent f9d14d8 commit f094f71
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 1 deletion.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion gitoxide-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ serde = ["gix/serde", "dep:serde_json", "dep:serde", "bytesize/serde"]

[dependencies]
# deselect everything else (like "performance") as this should be controllable by the parent application.
gix = { version = "^0.53.1", path = "../gix", default-features = false, features = ["blob-diff", "revision", "mailmap", "excludes", "attributes", "worktree-mutation", "credentials", "interrupt"] }
gix = { version = "^0.53.1", path = "../gix", default-features = false, features = ["blob-diff", "revision", "mailmap", "excludes", "attributes", "worktree-mutation", "credentials", "interrupt", "status"] }
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"] }
gix-transport-configuration-only = { package = "gix-transport", version = "^0.36.0", path = "../gix-transport", default-features = false }
gix-archive-for-configuration-only = { package = "gix-archive", version = "^0.4.0", path = "../gix-archive", optional = true, features = ["tar", "tar_gz"] }
gix-status = { version = "0.1.0", path = "../gix-status" }
serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"] }
anyhow = "1.0.42"
thiserror = "1.0.34"
Expand Down
1 change: 1 addition & 0 deletions gitoxide-core/src/repository/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub mod mailmap;
pub mod odb;
pub mod remote;
pub mod revision;
pub mod status;
pub mod submodule;
pub mod tree;
pub mod verify;
120 changes: 120 additions & 0 deletions gitoxide-core/src/repository/status.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use crate::OutputFormat;
use anyhow::{bail, Context};
use gix::bstr::{BStr, BString};
use gix::index::Entry;
use gix::prelude::FindExt;
use gix::Progress;
use gix_status::index_as_worktree::content::FastEq;
use gix_status::index_as_worktree::Change;

pub enum Submodules {
/// display all information about submodules, including ref changes, modifications and untracked files.
All,
/// Compare only the configuration of the superprojects commit with the actually checked out `HEAD` commit.
RefChange,
/// See if there are worktree modifications compared to the index, but do not check for untracked files.
Modifications,
}

pub struct Options {
pub format: OutputFormat,
pub submodules: Submodules,
pub thread_limit: Option<usize>,
}

pub fn show(
repo: gix::Repository,
pathspecs: Vec<BString>,
out: impl std::io::Write,
mut err: impl std::io::Write,
mut progress: impl gix::NestedProgress,
Options {
format,
// TODO: implement this
submodules: _,
thread_limit,
}: Options,
) -> anyhow::Result<()> {
if format != OutputFormat::Human {
bail!("Only human format is supported right now");
}
let mut index = repo.index()?;
let index = gix::threading::make_mut(&mut index);
let pathspec = repo.pathspec(
pathspecs,
true,
index,
gix::worktree::stack::state::attributes::Source::WorktreeThenIdMapping,
)?;
let mut progress = progress.add_child("traverse index");
let start = std::time::Instant::now();
gix_status::index_as_worktree(
index,
repo.work_dir()
.context("This operation cannot be run on a bare repository")?,
&mut Printer(out),
FastEq,
{
let odb = repo.objects.clone().into_arc()?;
move |id, buf| odb.find_blob(id, buf)
},
&mut progress,
pathspec.detach()?,
gix_status::index_as_worktree::Options {
fs: repo.filesystem_options()?,
thread_limit,
stat: repo.stat_options()?,
},
)?;

writeln!(err, "\nhead -> index and untracked files aren't implemented yet")?;
progress.show_throughput(start);
Ok(())
}

struct Printer<W>(W);

impl<'index, W> gix_status::index_as_worktree::VisitEntry<'index> for Printer<W>
where
W: std::io::Write,
{
type ContentChange = ();

fn visit_entry(
&mut self,
entry: &'index Entry,
rela_path: &'index BStr,
change: Option<Change<Self::ContentChange>>,
conflict: bool,
) {
self.visit_inner(entry, rela_path, change, conflict).ok();
}
}

impl<W: std::io::Write> Printer<W> {
fn visit_inner(
&mut self,
_entry: &Entry,
rela_path: &BStr,
change: Option<Change<()>>,
conflict: bool,
) -> anyhow::Result<()> {
if let Some(change) = conflict
.then_some('U')
.or_else(|| change.as_ref().and_then(change_to_char))
{
writeln!(&mut self.0, "{change} {rela_path}")?;
}
Ok(())
}
}

fn change_to_char(change: &Change<()>) -> Option<char> {
// Known status letters: https://github.com/git/git/blob/6807fcfedab84bc8cd0fbf721bc13c4e68cda9ae/diff.h#L613
Some(match change {
Change::Removed => 'D',
Change::Type => 'T',
Change::Modification { .. } => 'M',
Change::IntentToAdd => return None,
})
}
27 changes: 27 additions & 0 deletions src/plumbing/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,33 @@ pub fn main() -> Result<()> {
})?;

match cmd {
Subcommands::Status(crate::plumbing::options::status::Platform { submodules, pathspec }) => prepare_and_run(
"status",
trace,
auto_verbose,
progress,
progress_keep_open,
None,
move |progress, out, err| {
use crate::plumbing::options::status::Submodules;
core::repository::status::show(
repository(Mode::Lenient)?,
pathspec,
out,
err,
progress,
core::repository::status::Options {
format,
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!
submodules: match submodules {
Submodules::All => core::repository::status::Submodules::All,
Submodules::RefChange => core::repository::status::Submodules::RefChange,
Submodules::Modifications => core::repository::status::Submodules::Modifications,
},
},
)
},
),
Subcommands::Submodule(platform) => match platform
.cmds
.unwrap_or(crate::plumbing::options::submodule::Subcommands::List)
Expand Down
28 changes: 28 additions & 0 deletions src/plumbing/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ pub enum Subcommands {
Submodule(submodule::Platform),
/// Show which git configuration values are used or planned.
ConfigTree,
Status(status::Platform),
Config(config::Platform),
#[cfg(feature = "gitoxide-core-tools-corpus")]
Corpus(corpus::Platform),
Expand Down Expand Up @@ -183,6 +184,33 @@ pub mod archive {
}
}

pub mod status {
use gitoxide::shared::CheckPathSpec;
use gix::bstr::BString;

#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
pub enum Submodules {
/// display all information about submodules, including ref changes, modifications and untracked files.
#[default]
All,
/// Compare only the configuration of the superprojects commit with the actually checked out `HEAD` commit.
RefChange,
/// See if there are worktree modifications compared to the index, but do not check for untracked files.
Modifications,
}

#[derive(Debug, clap::Parser)]
#[command(about = "compute repository status similar to `git status`")]
pub struct Platform {
/// Define how to display submodule status.
#[clap(long, default_value = "all")]
pub submodules: Submodules,
/// The git path specifications to list attributes for, or unset to read from stdin one per line.
#[clap(value_parser = CheckPathSpec)]
pub pathspec: Vec<BString>,
}
}

#[cfg(feature = "gitoxide-core-tools-corpus")]
pub mod corpus {
use std::path::PathBuf;
Expand Down

0 comments on commit f094f71

Please sign in to comment.