Skip to content

gix-status improvements #1146

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crate-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ The top-level crate that acts as hub to all functionality provided by the `gix-*
* [x] support for `GIT_CEILING_DIRECTORIES` environment variable
* [ ] handle other non-discovery modes and provide control over environment variable usage required in applications
* [x] rev-parse
- [ ] handle relative paths as relative to working directory
* [x] rev-walk
* [x] include tips
* [ ] exclude commits
Expand Down
78 changes: 71 additions & 7 deletions gitoxide-core/src/repository/revision/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,32 @@ pub struct Options {
pub explain: bool,
pub cat_file: bool,
pub tree_mode: TreeMode,
pub blob_format: BlobFormat,
}

pub enum TreeMode {
Raw,
Pretty,
}

#[derive(Copy, Clone)]
pub enum BlobFormat {
Git,
Worktree,
Diff,
DiffOrGit,
}

pub(crate) mod function {
use std::ffi::OsString;

use anyhow::Context;
use anyhow::{anyhow, Context};
use gix::diff::blob::ResourceKind;
use gix::filter::plumbing::driver::apply::Delay;
use gix::revision::Spec;

use super::Options;
use crate::repository::revision::resolve::BlobFormat;
use crate::{
repository::{revision, revision::resolve::TreeMode},
OutputFormat,
Expand All @@ -33,9 +45,26 @@ pub(crate) mod function {
explain,
cat_file,
tree_mode,
blob_format,
}: Options,
) -> anyhow::Result<()> {
repo.object_cache_size_if_unset(1024 * 1024);
let mut cache = (!matches!(blob_format, BlobFormat::Git))
.then(|| {
repo.diff_resource_cache(
match blob_format {
BlobFormat::Git => {
unreachable!("checked before")
}
BlobFormat::Worktree | BlobFormat::Diff => {
gix::diff::blob::pipeline::Mode::ToWorktreeAndBinaryToText
}
BlobFormat::DiffOrGit => gix::diff::blob::pipeline::Mode::ToGitUnlessBinaryToTextIsPresent,
},
Default::default(),
)
})
.transpose()?;

match format {
OutputFormat::Human => {
Expand All @@ -46,7 +75,7 @@ pub(crate) mod function {
let spec = gix::path::os_str_into_bstr(&spec)?;
let spec = repo.rev_parse(spec)?;
if cat_file {
return display_object(spec, tree_mode, out);
return display_object(&repo, spec, tree_mode, cache.as_mut().map(|c| (blob_format, c)), out);
}
writeln!(out, "{spec}", spec = spec.detach())?;
}
Expand All @@ -73,16 +102,51 @@ pub(crate) mod function {
Ok(())
}

fn display_object(spec: Spec<'_>, tree_mode: TreeMode, mut out: impl std::io::Write) -> anyhow::Result<()> {
fn display_object(
repo: &gix::Repository,
spec: Spec<'_>,
tree_mode: TreeMode,
cache: Option<(BlobFormat, &mut gix::diff::blob::Platform)>,
mut out: impl std::io::Write,
) -> anyhow::Result<()> {
let id = spec.single().context("rev-spec must resolve to a single object")?;
let object = id.object()?;
match object.kind {
let header = id.header()?;
match header.kind() {
gix::object::Kind::Tree if matches!(tree_mode, TreeMode::Pretty) => {
for entry in object.into_tree().iter() {
for entry in id.object()?.into_tree().iter() {
writeln!(out, "{}", entry?)?;
}
}
_ => out.write_all(&object.data)?,
gix::object::Kind::Blob if cache.is_some() && spec.path_and_mode().is_some() => {
let (path, mode) = spec.path_and_mode().expect("is present");
let is_dir = Some(mode.is_tree());
match cache.expect("is some") {
(BlobFormat::Git, _) => unreachable!("no need for a cache when querying object db"),
(BlobFormat::Worktree, cache) => {
let platform = cache.attr_stack.at_entry(path, is_dir, &repo.objects)?;
let object = id.object()?;
let mut converted = cache.filter.worktree_filter.convert_to_worktree(
&object.data,
path,
&mut |_path, attrs| {
let _ = platform.matching_attributes(attrs);
},
Delay::Forbid,
)?;
std::io::copy(&mut converted, &mut out)?;
}
(BlobFormat::Diff | BlobFormat::DiffOrGit, cache) => {
cache.set_resource(id.detach(), mode.kind(), path, ResourceKind::OldOrSource, &repo.objects)?;
let resource = cache.resource(ResourceKind::OldOrSource).expect("just set");
let data = resource
.data
.as_slice()
.ok_or_else(|| anyhow!("Binary data at {} cannot be diffed", path))?;
out.write_all(data)?;
}
}
}
_ => out.write_all(&id.object()?.data)?,
}
Ok(())
}
Expand Down
20 changes: 17 additions & 3 deletions gix-diff/src/blob/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,11 @@ impl Pipeline {
.try_header(id)
.map_err(gix_object::find::existing_object::Error::Find)?
.ok_or_else(|| gix_object::find::existing_object::Error::NotFound { oid: id.to_owned() })?;
if is_binary.is_none() && self.options.large_file_threshold_bytes > 0 {
is_binary = Some(header.size > self.options.large_file_threshold_bytes);
if is_binary.is_none()
&& self.options.large_file_threshold_bytes > 0
&& header.size > self.options.large_file_threshold_bytes
{
is_binary = Some(true);
};
let data = if is_binary == Some(true) {
Data::Binary { size: header.size }
Expand Down Expand Up @@ -425,7 +428,18 @@ impl Pipeline {
})?;
match cmd_and_file {
Some((cmd, mut tmp_file)) => {
tmp_file.write_all(out).map_err(|err| {
match res {
ToWorktreeOutcome::Unchanged(buf) | ToWorktreeOutcome::Buffer(buf) => {
tmp_file.write_all(buf)
}
ToWorktreeOutcome::Process(MaybeDelayed::Immediate(mut stream)) => {
std::io::copy(&mut stream, &mut tmp_file).map(|_| ())
}
ToWorktreeOutcome::Process(MaybeDelayed::Delayed(_)) => {
unreachable!("we prohibit this")
}
}
.map_err(|err| {
convert_to_diffable::Error::CreateTempfile {
source: err,
rela_path: rela_path.to_owned(),
Expand Down
55 changes: 55 additions & 0 deletions gix-diff/tests/blob/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,61 @@ pub(crate) mod convert_to_diffable {
Ok(())
}

#[test]
fn binary_below_large_file_threshold() -> crate::Result {
let tmp = gix_testtools::tempfile::TempDir::new()?;
let mut filter = gix_diff::blob::Pipeline::new(
WorktreeRoots {
old_root: None,
new_root: Some(tmp.path().to_owned()),
},
gix_filter::Pipeline::default(),
vec![],
gix_diff::blob::pipeline::Options {
large_file_threshold_bytes: 5,
..default_options()
},
);

let does_not_matter = gix_hash::Kind::Sha1.null();
let mut buf = Vec::new();
let a_name = "a";
let large_content = "a\0b";
std::fs::write(tmp.path().join(a_name), large_content.as_bytes())?;
let out = filter.convert_to_diffable(
&does_not_matter,
EntryKind::BlobExecutable,
a_name.into(),
ResourceKind::NewOrDestination,
&mut |_, _| {},
&gix_object::find::Never,
pipeline::Mode::default(),
&mut buf,
)?;
assert!(out.driver_index.is_none(), "there was no driver");
assert_eq!(out.data, Some(pipeline::Data::Binary { size: 3 }), "detected in buffer");
assert_eq!(buf.len(), 0, "it should avoid querying that data in the first place");

let mut db = ObjectDb::default();
let id = db.insert(large_content);
let out = filter.convert_to_diffable(
&id,
EntryKind::Blob,
a_name.into(),
ResourceKind::OldOrSource,
&mut |_, _| {},
&db,
pipeline::Mode::default(),
&mut buf,
)?;

assert!(out.driver_index.is_none(), "there was no driver");
assert_eq!(out.data, Some(pipeline::Data::Binary { size: 3 }));
assert_eq!(buf.len(), 0, "it should avoid querying that data in the first place");

Ok(())
}

#[test]
fn above_large_file_threshold() -> crate::Result {
let tmp = gix_testtools::tempfile::TempDir::new()?;
Expand Down
6 changes: 3 additions & 3 deletions gix-index/src/entry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ mod write;

use bitflags::bitflags;

// TODO: we essentially treat this as an enum withj the only exception being
// that `FILE_EXECUTABLE.contains(FILE)` works might want to turn this into an
// enum proper
// TODO: we essentially treat this as an enum with the only exception being
// that `FILE_EXECUTABLE.contains(FILE)` works might want to turn this into an
// enum proper
bitflags! {
/// The kind of file of an entry.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
Expand Down
6 changes: 6 additions & 0 deletions gix-index/src/entry/mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ impl Mode {
*self == Self::DIR | Self::SYMLINK
}

/// Convert this instance to a tree's entry mode, or return `None` if for some
/// and unexpected reason the bitflags don't resemble any known entry-mode.
pub fn to_tree_entry_mode(&self) -> Option<gix_object::tree::EntryMode> {
gix_object::tree::EntryMode::try_from(self.bits()).ok()
}

/// Compares this mode to the file system version ([`std::fs::symlink_metadata`])
/// and returns the change needed to update this mode to match the file.
///
Expand Down
1 change: 1 addition & 0 deletions gix/src/ext/rev_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ impl RevSpecExt for gix_revision::Spec {
fn attach(self, repo: &crate::Repository) -> crate::revision::Spec<'_> {
crate::revision::Spec {
inner: self,
path: None,
first_ref: None,
second_ref: None,
repo,
Expand Down
3 changes: 3 additions & 0 deletions gix/src/revision/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub use gix_revision as plumbing;

///
pub mod walk;
use crate::bstr::BString;
pub use walk::iter::Walk;

///
Expand All @@ -22,6 +23,8 @@ pub mod spec;
#[cfg(feature = "revision")]
pub struct Spec<'repo> {
pub(crate) inner: gix_revision::Spec,
/// The path we encountered in the revspec, like `@:<path>` or `@..@~1:<path>`.
pub(crate) path: Option<(BString, gix_object::tree::EntryMode)>,
/// The first name of a reference as seen while parsing a `RevSpec`, for completeness.
pub(crate) first_ref: Option<gix_ref::Reference>,
/// The second name of a reference as seen while parsing a `RevSpec`, for completeness.
Expand Down
9 changes: 9 additions & 0 deletions gix/src/revision/spec/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::bstr::BStr;
use crate::{ext::ReferenceExt, revision::Spec, Id, Reference};

///
Expand Down Expand Up @@ -37,6 +38,7 @@ impl<'repo> Spec<'repo> {
pub fn from_id(id: Id<'repo>) -> Self {
Spec {
inner: gix_revision::Spec::Include(id.inner),
path: None,
repo: id.repo,
first_ref: None,
second_ref: None,
Expand All @@ -62,6 +64,13 @@ impl<'repo> Spec<'repo> {
)
}

/// Return the path encountered in specs like `@:<path>` or `:<path>`, along with the kind of object it represents.
///
/// Note that there can only be one as paths always terminates further revspec parsing.
pub fn path_and_mode(&self) -> Option<(&BStr, gix_object::tree::EntryMode)> {
self.path.as_ref().map(|(p, mode)| (p.as_ref(), *mode))
}

/// Return the name of the first reference we encountered while resolving the rev-spec, or `None` if a short hash
/// was used. For example, `@` might yield `Some(HEAD)`, but `abcd` yields `None`.
pub fn first_reference(&self) -> Option<&gix_ref::Reference> {
Expand Down
2 changes: 2 additions & 0 deletions gix/src/revision/spec/parse/delegate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ impl<'repo> Delegate<'repo> {
Delegate {
refs: Default::default(),
objs: Default::default(),
paths: Default::default(),
ambiguous_objects: Default::default(),
idx: 0,
kind: None,
Expand Down Expand Up @@ -100,6 +101,7 @@ impl<'repo> Delegate<'repo> {

let range = zero_or_one_objects_or_ambiguity_err(self.objs, self.prefix, self.err, self.repo)?;
Ok(crate::revision::Spec {
path: self.paths[0].take().or(self.paths[1].take()),
first_ref: self.refs[0].take(),
second_ref: self.refs[1].take(),
inner: kind_to_spec(self.kind, range)?,
Expand Down
20 changes: 17 additions & 3 deletions gix/src/revision/spec/parse/delegate/navigate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ impl<'repo> delegate::Navigate for Delegate<'repo> {
let lookup_path = |obj: &ObjectId| {
let tree_id = peel(repo, obj, gix_object::Kind::Tree)?;
if path.is_empty() {
return Ok(tree_id);
return Ok((tree_id, gix_object::tree::EntryKind::Tree.into()));
}
let mut tree = repo.find_object(tree_id)?.into_tree();
let entry =
Expand All @@ -131,11 +131,17 @@ impl<'repo> delegate::Navigate for Delegate<'repo> {
object: obj.attach(repo).shorten_or_id(),
tree: tree_id.attach(repo).shorten_or_id(),
})?;
Ok(entry.object_id())
Ok((entry.object_id(), entry.mode()))
};
for obj in objs.iter() {
match lookup_path(obj) {
Ok(replace) => replacements.push((*obj, replace)),
Ok((replace, mode)) => {
if !path.is_empty() {
// Technically this is letting the last one win, but so be it.
self.paths[self.idx] = Some((path.to_owned(), mode));
}
replacements.push((*obj, replace))
}
Err(err) => errors.push((*obj, err)),
}
}
Expand Down Expand Up @@ -306,6 +312,14 @@ impl<'repo> delegate::Navigate for Delegate<'repo> {
self.objs[self.idx]
.get_or_insert_with(HashSet::default)
.insert(entry.id);

self.paths[self.idx] = Some((
path.to_owned(),
entry
.mode
.to_tree_entry_mode()
.unwrap_or(gix_object::tree::EntryKind::Blob.into()),
));
Some(())
}
None => {
Expand Down
4 changes: 4 additions & 0 deletions gix/src/revision/spec/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use gix_revision::spec::parse;
use crate::{bstr::BStr, revision::Spec, Repository};

mod types;
use crate::bstr::BString;
pub use types::{Error, ObjectKindHint, Options, RefsHint};

///
Expand Down Expand Up @@ -45,6 +46,9 @@ impl<'repo> Spec<'repo> {
struct Delegate<'repo> {
refs: [Option<gix_ref::Reference>; 2],
objs: [Option<HashSet<ObjectId>>; 2],
/// Path specified like `@:<path>` or `:<path>` for later use when looking up specs.
/// Note that it terminates spec parsing, so it's either `0` or `1`, never both.
paths: [Option<(BString, gix_object::tree::EntryMode)>; 2],
/// The originally encountered ambiguous objects for potential later use in errors.
ambiguous_objects: [Option<HashSet<ObjectId>>; 2],
idx: usize,
Expand Down
Loading