Skip to content

Commit

Permalink
Merge branch 'reset'
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Oct 5, 2023
2 parents 3939a45 + 67a0220 commit b842691
Show file tree
Hide file tree
Showing 65 changed files with 1,966 additions and 588 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

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

4 changes: 3 additions & 1 deletion crate-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,9 @@ Make it the best-performing implementation and the most convenient one.

### gix-status
* [x] differences between index and worktree to turn index into worktree
* [ ] differences between tree and index to turn tree into index
- [ ] rename tracking
* [ ] differences between index and index to learn what changed
- [ ] rename tracking
* [ ] untracked files
* [ ] fast answer to 'is it dirty'.
*
Expand Down
7 changes: 4 additions & 3 deletions gitoxide-core/src/repository/index/entries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,9 +404,10 @@ pub(crate) mod function {
out,
"{} {}{:?} {} {}{}{}",
match entry.flags.stage() {
0 => "BASE ",
1 => "OURS ",
2 => "THEIRS ",
0 => " ",
1 => "BASE ",
2 => "OURS ",
3 => "THEIRS ",
_ => "UNKNOWN",
},
if entry.flags.is_empty() {
Expand Down
177 changes: 141 additions & 36 deletions gitoxide-core/src/repository/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ 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;
use gix_status::index_as_worktree::traits::FastEq;
use gix_status::index_as_worktree::{Change, Conflict, EntryStatus};

pub enum Submodules {
/// display all information about submodules, including ref changes, modifications and untracked files.
Expand All @@ -20,6 +20,8 @@ pub struct Options {
pub format: OutputFormat,
pub submodules: Submodules,
pub thread_limit: Option<usize>,
pub statistics: bool,
pub allow_write: bool,
}

pub fn show(
Expand All @@ -33,12 +35,14 @@ pub fn show(
// TODO: implement this
submodules: _,
thread_limit,
allow_write,
statistics,
}: Options,
) -> anyhow::Result<()> {
if format != OutputFormat::Human {
bail!("Only human format is supported right now");
}
let mut index = repo.index()?;
let mut index = repo.index_or_empty()?;
let index = gix::threading::make_mut(&mut index);
let pathspec = repo.pathspec(
pathspecs,
Expand All @@ -48,73 +52,174 @@ pub fn show(
)?;
let mut progress = progress.add_child("traverse index");
let start = std::time::Instant::now();
gix_status::index_as_worktree(
let options = gix_status::index_as_worktree::Options {
fs: repo.filesystem_options()?,
thread_limit,
stat: repo.stat_options()?,
attributes: match repo
.attributes_only(
index,
gix::worktree::stack::state::attributes::Source::WorktreeThenIdMapping,
)?
.detach()
.state_mut()
{
gix::worktree::stack::State::AttributesStack(attrs) => std::mem::take(attrs),
// TODO: this should be nicer by creating attributes directly, but it's a private API
_ => unreachable!("state must be attributes stack only"),
},
};
let mut printer = Printer {
out,
changes: Vec::new(),
};
let outcome = gix_status::index_as_worktree(
index,
repo.work_dir()
.context("This operation cannot be run on a bare repository")?,
&mut Printer(out),
&mut printer,
FastEq,
Submodule,
{
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()?,
},
repo.filter_pipeline(Some(gix::hash::ObjectId::empty_tree(repo.object_hash())))?
.0
.into_parts()
.0,
&gix::interrupt::IS_INTERRUPTED,
options,
)?;

if outcome.entries_to_update != 0 && allow_write {
{
let entries = index.entries_mut();
for (entry_index, change) in printer.changes {
let entry = &mut entries[entry_index];
match change {
ApplyChange::SetSizeToZero => {
entry.stat.size = 0;
}
ApplyChange::NewStat(new_stat) => {
entry.stat = new_stat;
}
}
}
}
index.write(gix::index::write::Options {
extensions: Default::default(),
skip_hash: false, // TODO: make this based on configuration
})?;
}

if statistics {
writeln!(err, "{outcome:#?}").ok();
}

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

struct Printer<W>(W);
#[derive(Clone)]
struct Submodule;

impl gix_status::index_as_worktree::traits::SubmoduleStatus for Submodule {
type Output = ();
type Error = std::convert::Infallible;

fn status(&mut self, _entry: &Entry, _rela_path: &BStr) -> Result<Option<Self::Output>, Self::Error> {
Ok(None)
}
}

struct Printer<W> {
out: W,
changes: Vec<(usize, ApplyChange)>,
}

enum ApplyChange {
SetSizeToZero,
NewStat(gix::index::entry::Stat),
}

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

fn visit_entry(
&mut self,
entry: &'index Entry,
_entries: &'index [Entry],
_entry: &'index Entry,
entry_index: usize,
rela_path: &'index BStr,
change: Option<Change<Self::ContentChange>>,
conflict: bool,
status: EntryStatus<Self::ContentChange>,
) {
self.visit_inner(entry, rela_path, change, conflict).ok();
self.visit_inner(entry_index, rela_path, status).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 visit_inner(&mut self, entry_index: usize, rela_path: &BStr, status: EntryStatus<()>) -> std::io::Result<()> {
let char_storage;
let status = match status {
EntryStatus::Conflict(conflict) => as_str(conflict),
EntryStatus::Change(change) => {
if matches!(
change,
Change::Modification {
set_entry_stat_size_zero: true,
..
}
) {
self.changes.push((entry_index, ApplyChange::SetSizeToZero))
}
char_storage = change_to_char(&change);
std::str::from_utf8(std::slice::from_ref(&char_storage)).expect("valid ASCII")
}
EntryStatus::NeedsUpdate(stat) => {
self.changes.push((entry_index, ApplyChange::NewStat(stat)));
return Ok(());
}
EntryStatus::IntentToAdd => "A",
};

writeln!(&mut self.out, "{status: >3} {rela_path}")
}
}

fn as_str(c: Conflict) -> &'static str {
match c {
Conflict::BothDeleted => "DD",
Conflict::AddedByUs => "AU",
Conflict::DeletedByThem => "UD",
Conflict::AddedByThem => "UA",
Conflict::DeletedByUs => "DU",
Conflict::BothAdded => "AA",
Conflict::BothModified => "UU",
}
}

fn change_to_char(change: &Change<()>) -> Option<char> {
fn change_to_char(change: &Change<()>) -> u8 {
// 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,
})
match change {
Change::Removed => b'D',
Change::Type => b'T',
Change::SubmoduleModification(_) => b'M',
Change::Modification {
executable_bit_changed, ..
} => {
if *executable_bit_changed {
b'X'
} else {
b'M'
}
}
}
}
18 changes: 6 additions & 12 deletions gix-command/tests/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,32 +82,26 @@ mod spawn {

#[test]
fn sh_shell_specific_script_code_with_single_extra_arg() -> crate::Result {
let out = gix_command::prepare("echo")
let out = gix_command::prepare("printf")
.with_shell()
.arg("1")
.spawn()?
.wait_with_output()?;
assert!(out.status.success());
#[cfg(not(windows))]
assert_eq!(out.stdout.as_bstr(), "1\n");
#[cfg(windows)]
assert_eq!(out.stdout.as_bstr(), "1\r\n");
assert_eq!(out.stdout.as_bstr(), "1");
Ok(())
}

#[test]
fn sh_shell_specific_script_code_with_multiple_extra_args() -> crate::Result {
let out = gix_command::prepare("echo")
let out = gix_command::prepare("printf")
.with_shell()
.arg("1")
.arg("2")
.arg("%s")
.arg("arg")
.spawn()?
.wait_with_output()?;
assert!(out.status.success());
#[cfg(not(windows))]
assert_eq!(out.stdout.as_bstr(), "1 2\n");
#[cfg(windows)]
assert_eq!(out.stdout.as_bstr(), "1 2\r\n");
assert_eq!(out.stdout.as_bstr(), "arg");
Ok(())
}
}
Expand Down
28 changes: 21 additions & 7 deletions gix-features/src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ pub fn hasher(kind: gix_hash::Kind) -> Sha1 {
#[cfg(all(feature = "progress", any(feature = "rustsha1", feature = "fast-sha1")))]
pub fn bytes_of_file(
path: &std::path::Path,
num_bytes_from_start: usize,
num_bytes_from_start: u64,
kind: gix_hash::Kind,
progress: &mut dyn crate::progress::Progress,
should_interrupt: &std::sync::atomic::AtomicBool,
Expand All @@ -110,28 +110,42 @@ pub fn bytes_of_file(
)
}

/// Similar to [`bytes_of_file`], but operates on an already open file.
/// Similar to [`bytes_of_file`], but operates on a stream of bytes.
#[cfg(all(feature = "progress", any(feature = "rustsha1", feature = "fast-sha1")))]
pub fn bytes(
read: &mut dyn std::io::Read,
num_bytes_from_start: usize,
num_bytes_from_start: u64,
kind: gix_hash::Kind,
progress: &mut dyn crate::progress::Progress,
should_interrupt: &std::sync::atomic::AtomicBool,
) -> std::io::Result<gix_hash::ObjectId> {
let mut hasher = hasher(kind);
bytes_with_hasher(read, num_bytes_from_start, hasher(kind), progress, should_interrupt)
}

/// Similar to [`bytes()`], but takes a `hasher` instead of a hash kind.
#[cfg(all(feature = "progress", any(feature = "rustsha1", feature = "fast-sha1")))]
pub fn bytes_with_hasher(
read: &mut dyn std::io::Read,
num_bytes_from_start: u64,
mut hasher: Sha1,
progress: &mut dyn crate::progress::Progress,
should_interrupt: &std::sync::atomic::AtomicBool,
) -> std::io::Result<gix_hash::ObjectId> {
let start = std::time::Instant::now();
// init progress before the possibility for failure, as convenience in case people want to recover
progress.init(Some(num_bytes_from_start), crate::progress::bytes());
progress.init(
Some(num_bytes_from_start as prodash::progress::Step),
crate::progress::bytes(),
);

const BUF_SIZE: usize = u16::MAX as usize;
let mut buf = [0u8; BUF_SIZE];
let mut bytes_left = num_bytes_from_start;

while bytes_left > 0 {
let out = &mut buf[..BUF_SIZE.min(bytes_left)];
let out = &mut buf[..BUF_SIZE.min(bytes_left as usize)];
read.read_exact(out)?;
bytes_left -= out.len();
bytes_left -= out.len() as u64;
progress.inc_by(out.len());
hasher.update(out);
if should_interrupt.load(std::sync::atomic::Ordering::SeqCst) {
Expand Down

0 comments on commit b842691

Please sign in to comment.