Skip to content

Diff between worktree and index #805

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 32 commits into from
Apr 17, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c49f12d
allow access to index timestamp
pascalkuthe Apr 2, 2023
38e228c
add function to read blob from worktree
pascalkuthe Apr 2, 2023
efcbf0d
accept paths in scripted_fixture_writable
pascalkuthe Apr 2, 2023
0a8e50f
feat: diff between worktree and index
pascalkuthe Apr 2, 2023
31ddda2
refactor
Byron Apr 3, 2023
16eab81
refactor gix_worktree::read module
pascalkuthe Apr 4, 2023
1c43c75
change index/worktree diff to a visitor based API
pascalkuthe Apr 4, 2023
870bdb2
centralize index entry Stat creation/comparison
pascalkuthe Apr 12, 2023
0f747f3
streamline status API
pascalkuthe Apr 12, 2023
d7f250d
parallel status check
pascalkuthe Apr 7, 2023
f2a9b3f
update index::entry::stat tests
pascalkuthe Apr 12, 2023
9cb76e9
Cleanup entry::mode API
pascalkuthe Apr 12, 2023
bf8a7a4
Improve Mode::change_to_match_fs documentation
pascalkuthe Apr 12, 2023
c5f3fc8
use existing concurrency primitive in_parallel
pascalkuthe Apr 13, 2023
4736b60
clarify Stat::is_racy documentation
pascalkuthe Apr 13, 2023
1e19760
improve documentation of gix_index::entry::Stat::matches
pascalkuthe Apr 13, 2023
8df154b
clean up status::Diff implementations
pascalkuthe Apr 13, 2023
8b2bcdc
improve terminology and documentation
pascalkuthe Apr 13, 2023
0582ec5
ensure stable sort order for deterministic tests
pascalkuthe Apr 13, 2023
55d8902
remove unused config option
pascalkuthe Apr 13, 2023
c5adbe1
add test for racy git detection
pascalkuthe Apr 13, 2023
23ee47f
Merge branch 'main' into dev
Byron Apr 15, 2023
6acc5f1
make it compile
Byron Apr 15, 2023
35cb6b4
feat: Allow `USE_NSEC` and `USE_STDEV` compile time flags to configur…
Byron Apr 15, 2023
691758a
refactor
Byron Apr 15, 2023
9af47c3
feat: add `Index::entries_mut_and_pathbacking()`.
Byron Apr 15, 2023
cae539b
adjust to changes in `gix-index`
Byron Apr 15, 2023
27471e7
Add a test for --intend-to-add and clarify what this flag means.
Byron Apr 15, 2023
055611c
Add a test to assure we can detect conflicts
Byron Apr 15, 2023
f8cc33c
create new `gix-fs` crate to consolidate all filesystem utilities
Byron Apr 16, 2023
8920229
Minor adjustments to the worktree structure.
Byron Apr 16, 2023
cdef398
Merge branch 'main' into dev
Byron Apr 17, 2023
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 Cargo.lock

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

70 changes: 70 additions & 0 deletions gix-features/src/parallel/in_parallel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,76 @@ pub fn build_thread() -> std::thread::Builder {
std::thread::Builder::new()
}

/// Read items from `input` and `consume` them in multiple threads,
/// whose output output is collected by a `reducer`. Its task is to
/// aggregate these outputs into the final result returned by this function with the benefit of not having to be thread-safe.
///
/// * if `thread_limit` is `Some`, the given amount of threads will be used. If `None`, all logical cores will be used.
/// * `new_thread_state(thread_number) -> State` produces thread-local state once per thread to be based to `consume`
/// * `consume(Item, &mut State) -> Output` produces an output given an input obtained by `input` along with mutable state initially
/// created by `new_thread_state(…)`.
/// * For `reducer`, see the [`Reduce`] trait
pub fn in_parallel_chunks<'a, I, S, O, R>(
input: &'a mut [I],
chunk_size: usize,
thread_limit: Option<usize>,
new_thread_state: impl Fn(usize) -> S + Send + Clone,
consume: impl Fn(&'a mut I, &mut S) -> Option<O> + Send + Clone,
mut reducer: R,
) -> Result<<R as Reduce>::Output, <R as Reduce>::Error>
where
R: Reduce<Input = O>,
I: Send,
O: Send,
{
let num_threads = num_threads(thread_limit);
std::thread::scope(move |s| {
let receive_result = {
let (send_input, receive_input) = crossbeam_channel::bounded::<&mut [I]>(num_threads);
let (send_result, receive_result) = crossbeam_channel::bounded::<O>(num_threads);
for thread_id in 0..num_threads {
std::thread::Builder::new()
.name(format!("gitoxide.in_parallel.produce.{thread_id}"))
.spawn_scoped(s, {
let send_result = send_result.clone();
let receive_input = receive_input.clone();
let new_thread_state = new_thread_state.clone();
let consume = consume.clone();
move || {
let mut state = new_thread_state(thread_id);
for chunk in receive_input {
for item in chunk {
if let Some(output) = consume(item, &mut state) {
if send_result.send(output).is_err() {
break;
}
}
}
}
}
})
.expect("valid name");
}
std::thread::Builder::new()
.name("gitoxide.in_parallel.feed".into())
.spawn_scoped(s, move || {
for chunk in input.chunks_mut(chunk_size) {
if send_input.send(chunk).is_err() {
break;
}
}
})
.expect("valid name");
receive_result
};

for item in receive_result {
drop(reducer.feed(item)?);
}
reducer.finalize()
})
}

/// Read items from `input` and `consume` them in multiple threads,
/// whose output output is collected by a `reducer`. Its task is to
/// aggregate these outputs into the final result returned by this function with the benefit of not having to be thread-safe.
Expand Down
51 changes: 49 additions & 2 deletions gix-features/src/parallel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@
#[cfg(feature = "parallel")]
mod in_parallel;
#[cfg(feature = "parallel")]
pub use in_parallel::{build_thread, in_parallel, in_parallel_with_slice, join, threads};
pub use in_parallel::{build_thread, in_parallel, in_parallel_chunks, in_parallel_with_slice, join, threads};

mod serial;
#[cfg(not(feature = "parallel"))]
pub use serial::{build_thread, in_parallel, in_parallel_with_slice, join, threads};
pub use serial::{build_thread, in_parallel, in_parallel_chunks, in_parallel_with_slice, join, threads};

mod in_order;
pub use in_order::{InOrderIter, SequenceId};
Expand Down Expand Up @@ -128,6 +128,53 @@ pub fn num_threads(thread_limit: Option<usize>) -> usize {
.unwrap_or(logical_cores)
}

/// Run [`in_parallel()`] only if the given `condition()` returns true when eagerly evaluated.
///
/// For parameters, see the documentation of [`in_parallel()`]
#[cfg(feature = "parallel")]
pub fn in_parallel_chunks_if<'a, I, S, O, R>(
condition: impl FnOnce() -> bool,
input: &'a mut [I],
chunk_size: usize,
thread_limit: Option<usize>,
new_thread_state: impl Fn(usize) -> S + Send + Clone,
consume: impl Fn(&'a mut I, &mut S) -> Option<O> + Send + Clone,
reducer: R,
) -> Result<<R as Reduce>::Output, <R as Reduce>::Error>
where
R: Reduce<Input = O>,
I: Send,
O: Send,
{
if num_threads(thread_limit) > 1 && condition() {
in_parallel_chunks(input, chunk_size, thread_limit, new_thread_state, consume, reducer)
} else {
serial::in_parallel_chunks(input, chunk_size, thread_limit, new_thread_state, consume, reducer)
}
}

/// Run [`in_parallel()`] only if the given `condition()` returns true when eagerly evaluated.
///
/// For parameters, see the documentation of [`in_parallel()`]
///
/// Note that the non-parallel version is equivalent to [`in_parallel()`].
#[cfg(not(feature = "parallel"))]
pub fn in_parallel_chunks_if<'a, I, S, O, R>(
_condition: impl FnOnce() -> bool,
input: &'a mut [I],
chunk_size: usize,
thread_limit: Option<usize>,
new_thread_state: impl Fn(usize) -> S + Send + Clone,
consume: impl Fn(&'a mut I, &mut S) -> Option<O> + Send + Clone,
reducer: R,
) -> Result<<R as Reduce>::Output, <R as Reduce>::Error>
where
R: Reduce<Input = O>,
I: Send,
O: Send,
{
serial::in_parallel_chunks(input, chunk_size, thread_limit, new_thread_state, consume, reducer)
}
/// Run [`in_parallel()`] only if the given `condition()` returns true when eagerly evaluated.
///
/// For parameters, see the documentation of [`in_parallel()`]
Expand Down
32 changes: 32 additions & 0 deletions gix-features/src/parallel/serial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,35 @@ where
}
reducer.finalize()
}

/// Read items from `input` and `consume` them in a single thread, producing an output to be collected by a `reducer`,
/// whose task is to aggregate these outputs into the final result returned by this function.
///
/// * `new_thread_state(thread_number) -> State` produces thread-local state once per thread to be based to `consume`
/// * `consume(Item, &mut State) -> Output` produces an output given an input along with mutable state.
/// * For `reducer`, see the [`Reduce`] trait
/// * if `thread_limit` has no effect as everything is run on the main thread, but is present to keep the signature
/// similar to the parallel version.
///
/// **This serial version performing all calculations on the current thread.**
pub fn in_parallel_chunks<'a, I, S, O, R>(
input: &'a mut [I],
_chunk_size: usize,
_thread_limit: Option<usize>,
new_thread_state: impl Fn(usize) -> S + Send + Clone,
consume: impl Fn(&'a mut I, &mut S) -> Option<O> + Send + Clone,
mut reducer: R,
) -> Result<<R as Reduce>::Output, <R as Reduce>::Error>
where
R: Reduce<Input = O>,
I: Send,
O: Send,
{
let mut state = new_thread_state(0);
for item in input {
if let Some(res) = consume(item, &mut state) {
drop(reducer.feed(res)?);
}
}
reducer.finalize()
}
6 changes: 6 additions & 0 deletions gix-hash/src/object_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ impl ObjectId {
}
}

/// Returns true if this hash is equal to an empty blob
#[inline]
pub fn is_empty_blob(&self) -> bool {
self == &Self::empty_blob(self.kind())
}

/// Returns an Digest representing a hash with whose memory is zeroed.
#[inline]
pub const fn null(kind: crate::Kind) -> ObjectId {
Expand Down
15 changes: 15 additions & 0 deletions gix-index/src/access/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::cmp::Ordering;

use bstr::{BStr, ByteSlice, ByteVec};
use filetime::FileTime;

use crate::{entry, extension, Entry, PathStorage, State, Version};

Expand All @@ -15,6 +16,20 @@ impl State {
self.version
}

/// Returns time at which the state was created, indicating its freshness compared to other files on disk.
pub fn timestamp(&self) -> FileTime {
self.timestamp
}

/// Updates the timestamp of this state, indicating its freshness compared
/// to other files on disk. Be careful about using this as setting a
/// timestamp without correctly updating the index **will cause (file
/// system) race conditions** see racy-git.txt (in the git documentation)
/// for more details.
pub fn set_timestamp(&mut self, timestamp: FileTime) {
self.timestamp = timestamp
}

/// Return the kind of hashes used in this instance.
pub fn object_hash(&self) -> gix_hash::Kind {
self.object_hash
Expand Down
4 changes: 2 additions & 2 deletions gix-index/src/decode/entries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,11 @@ fn load_one<'a>(
Some((
Entry {
stat: entry::Stat {
ctime: entry::Time {
ctime: entry::stat::Time {
secs: ctime_secs,
nsecs: ctime_nsecs,
},
mtime: entry::Time {
mtime: entry::stat::Time {
secs: mtime_secs,
nsecs: mtime_nsecs,
},
Expand Down
4 changes: 2 additions & 2 deletions gix-index/src/decode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,11 +302,11 @@ pub(crate) fn stat(data: &[u8]) -> Option<(entry::Stat, &[u8])> {
let (size, data) = read_u32(data)?;
Some((
entry::Stat {
mtime: entry::Time {
mtime: entry::stat::Time {
secs: ctime_secs,
nsecs: ctime_nsecs,
},
ctime: entry::Time {
ctime: entry::stat::Time {
secs: mtime_secs,
nsecs: mtime_nsecs,
},
Expand Down
61 changes: 29 additions & 32 deletions gix-index/src/entry/mod.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,48 @@
/// The stage of an entry, one of 0 = base, 1 = ours, 2 = theirs
pub type Stage = u32;

mod mode;
pub use mode::Mode;
///
pub mod mode;

mod flags;
pub(crate) use flags::at_rest;
pub use flags::Flags;

///
pub mod stat;
mod write;

/// The time component in a [`Stat`] struct.
#[derive(Debug, Default, PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
pub struct Time {
/// The amount of seconds elapsed since EPOCH
pub secs: u32,
/// The amount of nanoseconds elapsed in the current second, ranging from 0 to 999.999.999 .
pub nsecs: u32,
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
bitflags! {
/// The kind of file of an entry.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Mode: u32 {
/// directory (only used for sparse checkouts), equivalent to a tree, which is _excluded_ from the index via
/// cone-mode.
const DIR = 0o040000;
/// regular file
const FILE = 0o100644;
/// regular file, executable
const FILE_EXECUTABLE = 0o100755;
/// Symbolic link
const SYMLINK = 0o120000;
/// A git commit for submodules
const COMMIT = 0o160000;
}
}

/// An entry's filesystem stat information.
#[derive(Debug, Default, PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
pub struct Stat {
/// Modification time
pub mtime: Time,
pub mtime: stat::Time,
/// Creation time
pub ctime: Time,
pub ctime: stat::Time,
/// Device number
pub dev: u32,
/// Inode number
Expand Down Expand Up @@ -64,29 +79,11 @@ mod access {
}

mod _impls {
use std::{cmp::Ordering, ops::Add, time::SystemTime};
use std::cmp::Ordering;

use bstr::BStr;

use crate::{entry::Time, Entry, State};

impl From<SystemTime> for Time {
fn from(s: SystemTime) -> Self {
let d = s
.duration_since(std::time::UNIX_EPOCH)
.expect("system time is not before unix epoch!");
Time {
secs: d.as_secs() as u32,
nsecs: d.subsec_nanos(),
}
}
}

impl From<Time> for SystemTime {
fn from(s: Time) -> Self {
std::time::UNIX_EPOCH.add(std::time::Duration::new(s.secs.into(), s.nsecs))
}
}
use crate::{Entry, State};

impl Entry {
/// Compare one entry to another by their path, by comparing only their common path portion byte by byte, then resorting to
Expand Down
Loading