From 6ac7dbe689b5599e72a05b4e23c1943cd2bba145 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 19 Sep 2022 17:38:02 +0800 Subject: [PATCH 01/48] refactor (#470) --- git-repository/src/object/tree.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/git-repository/src/object/tree.rs b/git-repository/src/object/tree.rs index 90e8942a868..fb2c89ecf1a 100644 --- a/git-repository/src/object/tree.rs +++ b/git-repository/src/object/tree.rs @@ -37,7 +37,6 @@ impl<'repo> Tree<'repo> { I: IntoIterator, P: PartialEq, { - // let mut out = None; let mut path = path.into_iter().peekable(); while let Some(component) = path.next() { match TreeRefIter::from_bytes(&self.data) @@ -49,9 +48,9 @@ impl<'repo> Tree<'repo> { return Ok(Some(entry.into())); } else { let next_id = entry.oid.to_owned(); - let handle = self.repo; + let repo = self.repo; drop(self); - self = match handle.find_object(next_id)?.try_into_tree() { + self = match repo.find_object(next_id)?.try_into_tree() { Ok(tree) => tree, Err(_) => return Ok(None), }; From a938fe491e98577230b2aefd536b600e74050225 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 19 Sep 2022 17:44:43 +0800 Subject: [PATCH 02/48] refactor (#470) --- git-repository/src/object/tree.rs | 176 --------------------- git-repository/src/object/tree/iter.rs | 48 ++++++ git-repository/src/object/tree/mod.rs | 76 +++++++++ git-repository/src/object/tree/traverse.rs | 57 +++++++ 4 files changed, 181 insertions(+), 176 deletions(-) delete mode 100644 git-repository/src/object/tree.rs create mode 100644 git-repository/src/object/tree/iter.rs create mode 100644 git-repository/src/object/tree/mod.rs create mode 100644 git-repository/src/object/tree/traverse.rs diff --git a/git-repository/src/object/tree.rs b/git-repository/src/object/tree.rs deleted file mode 100644 index fb2c89ecf1a..00000000000 --- a/git-repository/src/object/tree.rs +++ /dev/null @@ -1,176 +0,0 @@ -use git_hash::ObjectId; -use git_object::{bstr::BStr, TreeRefIter}; -use git_odb::FindExt; - -use crate::{object::find, Id, Tree}; - -/// Initialization -impl<'repo> Tree<'repo> { - /// Obtain a tree instance by handing in all components that it is made up of. - pub fn from_data(id: impl Into, data: Vec, repo: &'repo crate::Repository) -> Self { - Tree { - id: id.into(), - data, - repo, - } - } -} - -/// Access -impl<'repo> Tree<'repo> { - /// Return this tree's identifier. - pub fn id(&self) -> Id<'repo> { - Id::from_id(self.id, self.repo) - } - - // TODO: move implementation to git-object, tests. - /// Follow a sequence of `path` components starting from this instance, and look them up one by one until the last component - /// is looked up and its tree entry is returned. - /// - /// # Performance Notes - /// - /// Searching tree entries is currently done in sequence, which allows to the search to be allocation free. It would be possible - /// to re-use a vector and use a binary search instead, which might be able to improve performance over all. - /// However, a benchmark should be created first to have some data and see which trade-off to choose here. - pub fn lookup_path(mut self, path: I) -> Result, find::existing::Error> - where - I: IntoIterator, - P: PartialEq, - { - let mut path = path.into_iter().peekable(); - while let Some(component) = path.next() { - match TreeRefIter::from_bytes(&self.data) - .filter_map(Result::ok) - .find(|entry| component.eq(entry.filename)) - { - Some(entry) => { - if path.peek().is_none() { - return Ok(Some(entry.into())); - } else { - let next_id = entry.oid.to_owned(); - let repo = self.repo; - drop(self); - self = match repo.find_object(next_id)?.try_into_tree() { - Ok(tree) => tree, - Err(_) => return Ok(None), - }; - } - } - None => return Ok(None), - } - } - Ok(None) - } - - /// Obtain a platform for initiating a variety of traversals. - pub fn traverse(&self) -> Traversal<'_, 'repo> { - Traversal { - root: self, - breadthfirst: BreadthFirstTraversalPresets { root: self }, - } - } -} - -/// An intermediate object to start traversing the parent tree from. -pub struct Traversal<'a, 'repo> { - root: &'a Tree<'repo>, - /// TODO: EXPLAIN - pub breadthfirst: BreadthFirstTraversalPresets<'a, 'repo>, -} - -/// TODO: explain THIS! -#[derive(Copy, Clone)] -pub struct BreadthFirstTraversalPresets<'a, 'repo> { - root: &'a Tree<'repo>, -} - -impl<'a, 'repo> BreadthFirstTraversalPresets<'a, 'repo> { - /// Returns all entries and their file paths, recursively, as reachable from this tree. - pub fn files(&self) -> Result, git_traverse::tree::breadthfirst::Error> { - let mut recorder = git_traverse::tree::Recorder::default(); - Traversal { - root: self.root, - breadthfirst: *self, - } - .breadthfirst(&mut recorder)?; - Ok(recorder.records) - } -} - -impl<'a, 'repo> Traversal<'a, 'repo> { - /// Start a breadth-first traversal with a delegate, note that it's not sorted. - /// TODO: more docs or links to git-traverse - pub fn breadthfirst(&self, delegate: &mut V) -> Result<(), git_traverse::tree::breadthfirst::Error> - where - V: git_traverse::tree::Visit, - { - let root = git_object::TreeRefIter::from_bytes(&self.root.data); - let state = git_traverse::tree::breadthfirst::State::default(); - git_traverse::tree::breadthfirst( - root, - state, - |oid, buf| self.root.repo.objects.find_tree_iter(oid, buf).ok(), - delegate, - ) - } -} - -pub use iter::EntryRef; - -/// -mod iter { - use super::Tree; - use crate::Repository; - - /// An entry within a tree - pub struct EntryRef<'repo, 'a> { - /// The actual entry ref we are wrapping. - pub inner: git_object::tree::EntryRef<'a>, - - repo: &'repo Repository, - } - - impl<'repo, 'a> EntryRef<'repo, 'a> { - /// The kind of object to which [`id()`][Self::id()] is pointing. - pub fn mode(&self) -> git_object::tree::EntryMode { - self.inner.mode - } - - /// The name of the file in the parent tree. - pub fn filename(&self) -> &git_object::bstr::BStr { - self.inner.filename - } - - /// Return the entries id, connected to the underlying repository. - pub fn id(&self) -> crate::Id<'repo> { - crate::Id::from_id(self.inner.oid, self.repo) - } - } - - impl<'repo, 'a> std::fmt::Display for EntryRef<'repo, 'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{:06o} {:>6} {}\t{}", - self.mode() as u32, - self.mode().as_str(), - self.id().shorten_or_id(), - self.filename() - ) - } - } - - impl<'repo> Tree<'repo> { - /// Return an iterator over tree entries. - pub fn iter(&self) -> impl Iterator, git_object::decode::Error>> { - let repo = self.repo; - git_object::TreeRefIter::from_bytes(&self.data).map(move |e| e.map(|entry| EntryRef { inner: entry, repo })) - } - } -} - -impl<'r> std::fmt::Debug for Tree<'r> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Tree({})", self.id) - } -} diff --git a/git-repository/src/object/tree/iter.rs b/git-repository/src/object/tree/iter.rs new file mode 100644 index 00000000000..495e1912a6d --- /dev/null +++ b/git-repository/src/object/tree/iter.rs @@ -0,0 +1,48 @@ +use super::Tree; +use crate::Repository; + +/// An entry within a tree +pub struct EntryRef<'repo, 'a> { + /// The actual entry ref we are wrapping. + pub inner: git_object::tree::EntryRef<'a>, + + repo: &'repo Repository, +} + +impl<'repo, 'a> EntryRef<'repo, 'a> { + /// The kind of object to which [`id()`][Self::id()] is pointing. + pub fn mode(&self) -> git_object::tree::EntryMode { + self.inner.mode + } + + /// The name of the file in the parent tree. + pub fn filename(&self) -> &git_object::bstr::BStr { + self.inner.filename + } + + /// Return the entries id, connected to the underlying repository. + pub fn id(&self) -> crate::Id<'repo> { + crate::Id::from_id(self.inner.oid, self.repo) + } +} + +impl<'repo, 'a> std::fmt::Display for EntryRef<'repo, 'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{:06o} {:>6} {}\t{}", + self.mode() as u32, + self.mode().as_str(), + self.id().shorten_or_id(), + self.filename() + ) + } +} + +impl<'repo> Tree<'repo> { + /// Return an iterator over tree entries. + pub fn iter(&self) -> impl Iterator, git_object::decode::Error>> { + let repo = self.repo; + git_object::TreeRefIter::from_bytes(&self.data).map(move |e| e.map(|entry| EntryRef { inner: entry, repo })) + } +} diff --git a/git-repository/src/object/tree/mod.rs b/git-repository/src/object/tree/mod.rs new file mode 100644 index 00000000000..dac6147a891 --- /dev/null +++ b/git-repository/src/object/tree/mod.rs @@ -0,0 +1,76 @@ +use git_hash::ObjectId; +use git_object::{bstr::BStr, TreeRefIter}; + +use crate::{object::find, Id, Tree}; + +/// Initialization +impl<'repo> Tree<'repo> { + /// Obtain a tree instance by handing in all components that it is made up of. + pub fn from_data(id: impl Into, data: Vec, repo: &'repo crate::Repository) -> Self { + Tree { + id: id.into(), + data, + repo, + } + } +} + +/// Access +impl<'repo> Tree<'repo> { + /// Return this tree's identifier. + pub fn id(&self) -> Id<'repo> { + Id::from_id(self.id, self.repo) + } + + // TODO: move implementation to git-object, tests. + /// Follow a sequence of `path` components starting from this instance, and look them up one by one until the last component + /// is looked up and its tree entry is returned. + /// + /// # Performance Notes + /// + /// Searching tree entries is currently done in sequence, which allows to the search to be allocation free. It would be possible + /// to re-use a vector and use a binary search instead, which might be able to improve performance over all. + /// However, a benchmark should be created first to have some data and see which trade-off to choose here. + pub fn lookup_path(mut self, path: I) -> Result, find::existing::Error> + where + I: IntoIterator, + P: PartialEq, + { + let mut path = path.into_iter().peekable(); + while let Some(component) = path.next() { + match TreeRefIter::from_bytes(&self.data) + .filter_map(Result::ok) + .find(|entry| component.eq(entry.filename)) + { + Some(entry) => { + if path.peek().is_none() { + return Ok(Some(entry.into())); + } else { + let next_id = entry.oid.to_owned(); + let repo = self.repo; + drop(self); + self = match repo.find_object(next_id)?.try_into_tree() { + Ok(tree) => tree, + Err(_) => return Ok(None), + }; + } + } + None => return Ok(None), + } + } + Ok(None) + } +} + +/// +pub mod traverse; + +/// +mod iter; +pub use iter::EntryRef; + +impl<'r> std::fmt::Debug for Tree<'r> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Tree({})", self.id) + } +} diff --git a/git-repository/src/object/tree/traverse.rs b/git-repository/src/object/tree/traverse.rs new file mode 100644 index 00000000000..7ae51d53ea1 --- /dev/null +++ b/git-repository/src/object/tree/traverse.rs @@ -0,0 +1,57 @@ +use crate::Tree; +use git_odb::FindExt; + +/// Traversal +impl<'repo> Tree<'repo> { + /// Obtain a platform for initiating a variety of traversals. + pub fn traverse(&self) -> Platform<'_, 'repo> { + Platform { + root: self, + breadthfirst: BreadthFirstPresets { root: self }, + } + } +} + +/// An intermediate object to start traversing the parent tree from. +pub struct Platform<'a, 'repo> { + root: &'a Tree<'repo>, + /// TODO: EXPLAIN + pub breadthfirst: BreadthFirstPresets<'a, 'repo>, +} + +/// TODO: explain THIS! +#[derive(Copy, Clone)] +pub struct BreadthFirstPresets<'a, 'repo> { + root: &'a Tree<'repo>, +} + +impl<'a, 'repo> BreadthFirstPresets<'a, 'repo> { + /// Returns all entries and their file paths, recursively, as reachable from this tree. + pub fn files(&self) -> Result, git_traverse::tree::breadthfirst::Error> { + let mut recorder = git_traverse::tree::Recorder::default(); + Platform { + root: self.root, + breadthfirst: *self, + } + .breadthfirst(&mut recorder)?; + Ok(recorder.records) + } +} + +impl<'a, 'repo> Platform<'a, 'repo> { + /// Start a breadth-first traversal with a delegate, note that it's not sorted. + /// TODO: more docs or links to git-traverse + pub fn breadthfirst(&self, delegate: &mut V) -> Result<(), git_traverse::tree::breadthfirst::Error> + where + V: git_traverse::tree::Visit, + { + let root = git_object::TreeRefIter::from_bytes(&self.root.data); + let state = git_traverse::tree::breadthfirst::State::default(); + git_traverse::tree::breadthfirst( + root, + state, + |oid, buf| self.root.repo.objects.find_tree_iter(oid, buf).ok(), + delegate, + ) + } +} From c857b9b6a113bd60fa3c9aeaf3edb81164ae772a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 19 Sep 2022 17:56:58 +0800 Subject: [PATCH 03/48] diff platform for basic diff configuration (#470) --- git-repository/src/object/tree/mod.rs | 25 +++++++++++++++++++++- git-repository/src/object/tree/traverse.rs | 4 ++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/git-repository/src/object/tree/mod.rs b/git-repository/src/object/tree/mod.rs index dac6147a891..997d5d5ee53 100644 --- a/git-repository/src/object/tree/mod.rs +++ b/git-repository/src/object/tree/mod.rs @@ -22,7 +22,7 @@ impl<'repo> Tree<'repo> { Id::from_id(self.id, self.repo) } - // TODO: move implementation to git-object, tests. + // TODO: tests. /// Follow a sequence of `path` components starting from this instance, and look them up one by one until the last component /// is looked up and its tree entry is returned. /// @@ -62,6 +62,29 @@ impl<'repo> Tree<'repo> { } } +#[allow(missing_docs)] +/// +pub mod diff { + use crate::Tree; + + impl<'repo> Tree<'repo> { + pub fn changes<'other_repo, 'a>(&'a self) -> Platform<'a, 'repo> { + Platform { lhs: self } + } + } + + #[allow(dead_code)] + pub struct Platform<'a, 'repo> { + lhs: &'a Tree<'repo>, + } + + impl<'a, 'repo> Platform<'a, 'repo> { + pub fn to_obtain_tree(&self, _other: &Tree<'_>) { + todo!() + } + } +} + /// pub mod traverse; diff --git a/git-repository/src/object/tree/traverse.rs b/git-repository/src/object/tree/traverse.rs index 7ae51d53ea1..b95d4d46da8 100644 --- a/git-repository/src/object/tree/traverse.rs +++ b/git-repository/src/object/tree/traverse.rs @@ -15,11 +15,11 @@ impl<'repo> Tree<'repo> { /// An intermediate object to start traversing the parent tree from. pub struct Platform<'a, 'repo> { root: &'a Tree<'repo>, - /// TODO: EXPLAIN + #[allow(missing_docs)] // TODO pub breadthfirst: BreadthFirstPresets<'a, 'repo>, } -/// TODO: explain THIS! +#[allow(missing_docs)] // TODO #[derive(Copy, Clone)] pub struct BreadthFirstPresets<'a, 'repo> { root: &'a Tree<'repo>, From e51f3cda68606dafd908ec268099fc455493ebaf Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 19 Sep 2022 18:44:29 +0800 Subject: [PATCH 04/48] break through API surface and sketch delegate calling user-provided function (#470) --- git-repository/src/object/tree/mod.rs | 114 ++++++++++++++++++++++++-- 1 file changed, 109 insertions(+), 5 deletions(-) diff --git a/git-repository/src/object/tree/mod.rs b/git-repository/src/object/tree/mod.rs index 997d5d5ee53..c844596b6cb 100644 --- a/git-repository/src/object/tree/mod.rs +++ b/git-repository/src/object/tree/mod.rs @@ -65,22 +65,126 @@ impl<'repo> Tree<'repo> { #[allow(missing_docs)] /// pub mod diff { - use crate::Tree; + use crate::bstr::BStr; + use crate::ext::ObjectIdExt; + use crate::{Id, Repository, Tree}; + use git_object::TreeRefIter; + use git_odb::FindExt; + /// Represents any possible change in order to turn one tree into another. + #[derive(Debug, Clone, Copy)] + pub enum Event<'repo, 'other_repo> { + /// An entry was added, like the addition of a file or directory. + Addition { + /// The mode of the added entry. + entry_mode: git_object::tree::EntryMode, + /// The object id of the added entry. + id: Id<'other_repo>, + }, + /// An entry was deleted, like the deletion of a file or directory. + Deletion { + /// The mode of the deleted entry. + entry_mode: git_object::tree::EntryMode, + /// The object id of the deleted entry. + id: Id<'repo>, + }, + /// An entry was modified, e.g. changing the contents of a file adjusts its object id and turning + /// a file into a symbolic link adjusts its mode. + Modification { + /// The mode of the entry before the modification. + previous_entry_mode: git_object::tree::EntryMode, + /// The object id of the entry before the modification. + previous_id: Id<'repo>, + + /// The mode of the entry after the modification. + entry_mode: git_object::tree::EntryMode, + /// The object id after the modification. + id: Id<'other_repo>, + }, + } + + /// Diffing impl<'repo> Tree<'repo> { + /// Return a platform to see the changes needed to create other trees, for instance. pub fn changes<'other_repo, 'a>(&'a self) -> Platform<'a, 'repo> { - Platform { lhs: self } + Platform { + state: Default::default(), + lhs: self, + } } } - #[allow(dead_code)] pub struct Platform<'a, 'repo> { + state: git_diff::tree::State, lhs: &'a Tree<'repo>, } + /// Add the item to compare to. impl<'a, 'repo> Platform<'a, 'repo> { - pub fn to_obtain_tree(&self, _other: &Tree<'_>) { - todo!() + pub fn to_obtain_tree<'other_repo>( + &mut self, + other: &Tree<'other_repo>, + for_each: impl FnMut(Event<'repo, 'other_repo>) -> git_diff::tree::visit::Action, + ) -> Result<(), git_diff::tree::changes::Error> { + let repo = self.lhs.repo; + let mut delegate = Delegate { + repo: self.lhs.repo, + other_repo: other.repo, + visit: for_each, + }; + git_diff::tree::Changes::from(TreeRefIter::from_bytes(&self.lhs.data)).needed_to_obtain( + TreeRefIter::from_bytes(&other.data), + &mut self.state, + |oid, buf| repo.objects.find_tree_iter(oid, buf), + &mut delegate, + ) + } + } + + struct Delegate<'repo, 'other_repo, VisitFn> { + repo: &'repo Repository, + other_repo: &'other_repo Repository, + visit: VisitFn, + } + + impl<'repo, 'other_repo, VisitFn> git_diff::tree::Visit for Delegate<'repo, 'other_repo, VisitFn> + where + VisitFn: FnMut(Event<'repo, 'other_repo>) -> git_diff::tree::visit::Action, + { + fn pop_front_tracked_path_and_set_current(&mut self) {} + + fn push_back_tracked_path_component(&mut self, _component: &BStr) { + {} + } + + fn push_path_component(&mut self, _component: &BStr) {} + + fn pop_path_component(&mut self) {} + + fn visit(&mut self, change: git_diff::tree::visit::Change) -> git_diff::tree::visit::Action { + use git_diff::tree::visit::Change::*; + let event = match change { + Addition { entry_mode, oid } => Event::Addition { + entry_mode, + id: oid.attach(self.other_repo), + }, + Deletion { entry_mode, oid } => Event::Deletion { + entry_mode, + id: oid.attach(self.repo), + }, + Modification { + previous_entry_mode, + previous_oid, + entry_mode, + oid, + } => Event::Modification { + previous_entry_mode, + entry_mode, + previous_id: previous_oid.attach(self.repo), + id: oid.attach(self.other_repo), + }, + }; + (self.visit)(event) } } } From 5be96b3c5c276bc0176820da4a5f554c1a1623f3 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 19 Sep 2022 19:10:44 +0800 Subject: [PATCH 05/48] allow user callbacks to have any error (#470) --- git-repository/src/object/tree/mod.rs | 43 ++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/git-repository/src/object/tree/mod.rs b/git-repository/src/object/tree/mod.rs index c844596b6cb..eb90c578d79 100644 --- a/git-repository/src/object/tree/mod.rs +++ b/git-repository/src/object/tree/mod.rs @@ -71,6 +71,16 @@ pub mod diff { use git_object::TreeRefIter; use git_odb::FindExt; + /// The error return by methods on the [diff platform][super::Platform]. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + Diff(#[from] git_diff::tree::changes::Error), + #[error("The user-provided callback failed")] + ForEach(#[source] Box), + } + /// Represents any possible change in order to turn one tree into another. #[derive(Debug, Clone, Copy)] pub enum Event<'repo, 'other_repo> { @@ -121,35 +131,46 @@ pub mod diff { /// Add the item to compare to. impl<'a, 'repo> Platform<'a, 'repo> { - pub fn to_obtain_tree<'other_repo>( + /// Call `for_each` repeatedly with all changes that are needed to convert the source of the diff to the tree to `other`. + pub fn for_each_to_obtain_tree<'other_repo, E>( &mut self, other: &Tree<'other_repo>, - for_each: impl FnMut(Event<'repo, 'other_repo>) -> git_diff::tree::visit::Action, - ) -> Result<(), git_diff::tree::changes::Error> { + for_each: impl FnMut(Event<'repo, 'other_repo>) -> Result, + ) -> Result<(), Error> + where + E: std::error::Error + Sync + Send + 'static, + { let repo = self.lhs.repo; let mut delegate = Delegate { repo: self.lhs.repo, other_repo: other.repo, visit: for_each, + err: None, }; git_diff::tree::Changes::from(TreeRefIter::from_bytes(&self.lhs.data)).needed_to_obtain( TreeRefIter::from_bytes(&other.data), &mut self.state, |oid, buf| repo.objects.find_tree_iter(oid, buf), &mut delegate, - ) + )?; + match delegate.err { + Some(err) => Err(Error::ForEach(Box::new(err))), + None => Ok(()), + } } } - struct Delegate<'repo, 'other_repo, VisitFn> { + struct Delegate<'repo, 'other_repo, VisitFn, E> { repo: &'repo Repository, other_repo: &'other_repo Repository, visit: VisitFn, + err: Option, } - impl<'repo, 'other_repo, VisitFn> git_diff::tree::Visit for Delegate<'repo, 'other_repo, VisitFn> + impl<'repo, 'other_repo, VisitFn, E> git_diff::tree::Visit for Delegate<'repo, 'other_repo, VisitFn, E> where - VisitFn: FnMut(Event<'repo, 'other_repo>) -> git_diff::tree::visit::Action, + VisitFn: FnMut(Event<'repo, 'other_repo>) -> Result, + E: std::error::Error + Sync + Send + 'static, { fn pop_front_tracked_path_and_set_current(&mut self) {} @@ -184,7 +205,13 @@ pub mod diff { id: oid.attach(self.other_repo), }, }; - (self.visit)(event) + match (self.visit)(event) { + Ok(action) => action, + Err(err) => { + self.err = Some(err); + git_diff::tree::visit::Action::Cancel + } + } } } } From a9056fdd3f22347737b4d3c80a793d1d26f4218b Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 19 Sep 2022 19:23:03 +0800 Subject: [PATCH 06/48] first test for simple file modification detection (#470) --- git-repository/tests/object/commit.rs | 36 ++++++++++++++++++++++ git-repository/tests/object/mod.rs | 40 ++---------------------- git-repository/tests/object/tree.rs | 44 +++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 38 deletions(-) create mode 100644 git-repository/tests/object/commit.rs create mode 100644 git-repository/tests/object/tree.rs diff --git a/git-repository/tests/object/commit.rs b/git-repository/tests/object/commit.rs new file mode 100644 index 00000000000..1ebea60f0eb --- /dev/null +++ b/git-repository/tests/object/commit.rs @@ -0,0 +1,36 @@ +use std::cmp::Ordering; + +use git_testtools::hex_to_id; + +use crate::basic_repo; + +#[test] +fn short_id() -> crate::Result { + let repo = basic_repo()?; + let commit = repo.head_commit()?; + assert_eq!(commit.short_id()?.cmp_oid(&commit.id), Ordering::Equal); + Ok(()) +} + +#[test] +fn tree() -> crate::Result { + let repo = basic_repo()?; + let commit = repo.head_commit()?; + + assert_eq!(commit.tree()?.id, commit.tree_id().expect("id present")); + assert_eq!( + commit.tree_id().ok().map(|id| id.detach()), + Some(hex_to_id("21d3ba9a26b790a4858d67754ae05d04dfce4d0c")) + ); + Ok(()) +} + +#[test] +fn decode() -> crate::Result { + let repo = basic_repo()?; + let commit = repo.head_commit()?; + assert_eq!(commit.decode()?.message, commit.message_raw()?); + assert_eq!(commit.decode()?.message(), commit.message()?); + assert_eq!(commit.decode()?.message, "c2\n"); + Ok(()) +} diff --git a/git-repository/tests/object/mod.rs b/git-repository/tests/object/mod.rs index b9315b7453d..fbfcc579c9e 100644 --- a/git-repository/tests/object/mod.rs +++ b/git-repository/tests/object/mod.rs @@ -1,41 +1,5 @@ -mod commit { - use std::cmp::Ordering; - - use git_testtools::hex_to_id; - - use crate::basic_repo; - - #[test] - fn short_id() -> crate::Result { - let repo = basic_repo()?; - let commit = repo.head_commit()?; - assert_eq!(commit.short_id()?.cmp_oid(&commit.id), Ordering::Equal); - Ok(()) - } - - #[test] - fn tree() -> crate::Result { - let repo = basic_repo()?; - let commit = repo.head_commit()?; - - assert_eq!(commit.tree()?.id, commit.tree_id().expect("id present")); - assert_eq!( - commit.tree_id().ok().map(|id| id.detach()), - Some(hex_to_id("21d3ba9a26b790a4858d67754ae05d04dfce4d0c")) - ); - Ok(()) - } - - #[test] - fn decode() -> crate::Result { - let repo = basic_repo()?; - let commit = repo.head_commit()?; - assert_eq!(commit.decode()?.message, commit.message_raw()?); - assert_eq!(commit.decode()?.message(), commit.message()?); - assert_eq!(commit.decode()?.message, "c2\n"); - Ok(()) - } -} +mod commit; +mod tree; #[test] fn object_ref_size_in_memory() { diff --git a/git-repository/tests/object/tree.rs b/git-repository/tests/object/tree.rs new file mode 100644 index 00000000000..71a59862f16 --- /dev/null +++ b/git-repository/tests/object/tree.rs @@ -0,0 +1,44 @@ +mod diff { + use crate::remote; + use git_object::bstr::ByteSlice; + use git_object::tree::EntryMode; + use git_repository as git; + use git_repository::object::tree::diff::Event; + use std::convert::Infallible; + + #[test] + fn changes_against_tree_modified() { + let repo = remote::repo("base"); + let from = tree_named(&repo, "g"); + let to = tree_named(&repo, "h"); + from.changes() + .for_each_to_obtain_tree(&to, |event| -> Result<_, Infallible> { + match event { + Event::Modification { + previous_entry_mode, + previous_id, + entry_mode, + id, + } => { + assert_eq!(previous_entry_mode, EntryMode::Blob); + assert_eq!(entry_mode, EntryMode::Blob); + assert_eq!(previous_id.object().unwrap().data.as_bstr(), "g\n"); + assert_eq!(id.object().unwrap().data.as_bstr(), "h\n"); + Ok(git::diff::tree::visit::Action::Continue) + } + Event::Deletion { .. } | Event::Addition { .. } => unreachable!("only modification is expected"), + } + }) + .unwrap(); + } + + fn tree_named<'repo>(repo: &'repo git::Repository, rev_spec: &str) -> git::Tree<'repo> { + repo.rev_parse_single(rev_spec) + .unwrap() + .object() + .unwrap() + .peel_to_kind(git::object::Kind::Tree) + .unwrap() + .into_tree() + } +} From 7fd9b0eed4a18c2a9e9ae44bc8a8f72769995889 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 19 Sep 2022 19:32:28 +0800 Subject: [PATCH 07/48] Provisions for tracking the location of a change. (#470) --- git-repository/src/object/tree/mod.rs | 25 +++++++++++++++++++++---- git-repository/tests/object/tree.rs | 5 +++-- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/git-repository/src/object/tree/mod.rs b/git-repository/src/object/tree/mod.rs index eb90c578d79..e49b48914bc 100644 --- a/git-repository/src/object/tree/mod.rs +++ b/git-repository/src/object/tree/mod.rs @@ -65,7 +65,7 @@ impl<'repo> Tree<'repo> { #[allow(missing_docs)] /// pub mod diff { - use crate::bstr::BStr; + use crate::bstr::{BStr, BString}; use crate::ext::ObjectIdExt; use crate::{Id, Repository, Tree}; use git_object::TreeRefIter; @@ -83,6 +83,17 @@ pub mod diff { /// Represents any possible change in order to turn one tree into another. #[derive(Debug, Clone, Copy)] + pub struct Change<'a, 'repo, 'other_repo> { + /// The location of the file or directory described by `event`, if tracking was enabled. + /// + /// Otherwise this value is always an empty path. + pub location: &'a BStr, + /// The diff event itself to provide information about what would need to change. + pub event: Event<'repo, 'other_repo>, + } + + /// An event emitted when finding differences between two trees. + #[derive(Debug, Clone, Copy)] pub enum Event<'repo, 'other_repo> { /// An entry was added, like the addition of a file or directory. Addition { @@ -135,7 +146,7 @@ pub mod diff { pub fn for_each_to_obtain_tree<'other_repo, E>( &mut self, other: &Tree<'other_repo>, - for_each: impl FnMut(Event<'repo, 'other_repo>) -> Result, + for_each: impl FnMut(Change<'_, 'repo, 'other_repo>) -> Result, ) -> Result<(), Error> where E: std::error::Error + Sync + Send + 'static, @@ -144,6 +155,7 @@ pub mod diff { let mut delegate = Delegate { repo: self.lhs.repo, other_repo: other.repo, + location: BString::default(), visit: for_each, err: None, }; @@ -163,13 +175,15 @@ pub mod diff { struct Delegate<'repo, 'other_repo, VisitFn, E> { repo: &'repo Repository, other_repo: &'other_repo Repository, + location: BString, visit: VisitFn, err: Option, } impl<'repo, 'other_repo, VisitFn, E> git_diff::tree::Visit for Delegate<'repo, 'other_repo, VisitFn, E> where - VisitFn: FnMut(Event<'repo, 'other_repo>) -> Result, + VisitFn: + for<'delegate> FnMut(Change<'delegate, 'repo, 'other_repo>) -> Result, E: std::error::Error + Sync + Send + 'static, { fn pop_front_tracked_path_and_set_current(&mut self) {} @@ -205,7 +219,10 @@ pub mod diff { id: oid.attach(self.other_repo), }, }; - match (self.visit)(event) { + match (self.visit)(Change { + event, + location: self.location.as_ref(), + }) { Ok(action) => action, Err(err) => { self.err = Some(err); diff --git a/git-repository/tests/object/tree.rs b/git-repository/tests/object/tree.rs index 71a59862f16..8faba411c0a 100644 --- a/git-repository/tests/object/tree.rs +++ b/git-repository/tests/object/tree.rs @@ -12,8 +12,9 @@ mod diff { let from = tree_named(&repo, "g"); let to = tree_named(&repo, "h"); from.changes() - .for_each_to_obtain_tree(&to, |event| -> Result<_, Infallible> { - match event { + .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { + assert_eq!(change.location, "", "without configuration the location field is empty"); + match change.event { Event::Modification { previous_entry_mode, previous_id, From 88c4a57b8e84db74d9bc2a1d626bc5c51a069fad Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 19 Sep 2022 19:49:20 +0800 Subject: [PATCH 08/48] Support for file-name tracking (#470) --- git-repository/src/object/tree/mod.rs | 32 +++++++++++++++++++++++++-- git-repository/tests/object/tree.rs | 8 +++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/git-repository/src/object/tree/mod.rs b/git-repository/src/object/tree/mod.rs index e49b48914bc..158f69765b6 100644 --- a/git-repository/src/object/tree/mod.rs +++ b/git-repository/src/object/tree/mod.rs @@ -65,7 +65,7 @@ impl<'repo> Tree<'repo> { #[allow(missing_docs)] /// pub mod diff { - use crate::bstr::{BStr, BString}; + use crate::bstr::{BStr, BString, ByteVec}; use crate::ext::ObjectIdExt; use crate::{Id, Repository, Tree}; use git_object::TreeRefIter; @@ -131,13 +131,31 @@ pub mod diff { Platform { state: Default::default(), lhs: self, + tracking: None, } } } + /// The diffing platform returned by [`Tree::changes()`]. + #[derive(Clone)] pub struct Platform<'a, 'repo> { state: git_diff::tree::State, lhs: &'a Tree<'repo>, + tracking: Option, + } + + #[derive(Clone, Copy)] + enum Tracking { + FileName, + } + + /// Configuration + impl<'a, 'repo> Platform<'a, 'repo> { + /// Keep track of file-names, which makes the [`location`][Change::location] field usable with the filename of the changed item. + pub fn track_filename(&mut self) -> &mut Self { + self.tracking = Some(Tracking::FileName); + self + } } /// Add the item to compare to. @@ -155,6 +173,7 @@ pub mod diff { let mut delegate = Delegate { repo: self.lhs.repo, other_repo: other.repo, + tracking: self.tracking, location: BString::default(), visit: for_each, err: None, @@ -175,6 +194,7 @@ pub mod diff { struct Delegate<'repo, 'other_repo, VisitFn, E> { repo: &'repo Repository, other_repo: &'other_repo Repository, + tracking: Option, location: BString, visit: VisitFn, err: Option, @@ -192,7 +212,15 @@ pub mod diff { {} } - fn push_path_component(&mut self, _component: &BStr) {} + fn push_path_component(&mut self, component: &BStr) { + match self.tracking { + Some(Tracking::FileName) => { + self.location.clear(); + self.location.push_str(component); + } + None => {} + } + } fn pop_path_component(&mut self) {} diff --git a/git-repository/tests/object/tree.rs b/git-repository/tests/object/tree.rs index 8faba411c0a..2e7c1676ee8 100644 --- a/git-repository/tests/object/tree.rs +++ b/git-repository/tests/object/tree.rs @@ -31,6 +31,14 @@ mod diff { } }) .unwrap(); + + from.changes() + .track_filename() + .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { + assert_eq!(change.location, "file"); + Ok(git::diff::tree::visit::Action::Continue) + }) + .unwrap(); } fn tree_named<'repo>(repo: &'repo git::Repository, rev_spec: &str) -> git::Tree<'repo> { From d04807bc9a70ddb6139446356df5c1bdb902a497 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 19 Sep 2022 19:58:36 +0800 Subject: [PATCH 09/48] improved usability of the `Action` enum (#470) --- git-diff/src/tree/visit.rs | 6 ++++++ git-repository/src/object/tree/mod.rs | 23 +++++++++++++++++++---- git-repository/tests/object/tree.rs | 4 ++-- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/git-diff/src/tree/visit.rs b/git-diff/src/tree/visit.rs index b66e47b37ce..a95f488be31 100644 --- a/git-diff/src/tree/visit.rs +++ b/git-diff/src/tree/visit.rs @@ -42,6 +42,12 @@ pub enum Action { Cancel, } +impl Default for Action { + fn default() -> Self { + Action::Continue + } +} + impl Action { /// Returns true if this action means to stop the traversal. pub fn cancelled(&self) -> bool { diff --git a/git-repository/src/object/tree/mod.rs b/git-repository/src/object/tree/mod.rs index 158f69765b6..deb7e80f3d9 100644 --- a/git-repository/src/object/tree/mod.rs +++ b/git-repository/src/object/tree/mod.rs @@ -81,6 +81,21 @@ pub mod diff { ForEach(#[source] Box), } + /// Returned by the `for_each` function to control flow. + #[derive(Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)] + pub enum Action { + /// Continue the traversal of changes. + Continue, + /// Stop the traversal of changes and stop calling this function. + Cancel, + } + + impl Default for Action { + fn default() -> Self { + Action::Continue + } + } + /// Represents any possible change in order to turn one tree into another. #[derive(Debug, Clone, Copy)] pub struct Change<'a, 'repo, 'other_repo> { @@ -164,7 +179,7 @@ pub mod diff { pub fn for_each_to_obtain_tree<'other_repo, E>( &mut self, other: &Tree<'other_repo>, - for_each: impl FnMut(Change<'_, 'repo, 'other_repo>) -> Result, + for_each: impl FnMut(Change<'_, 'repo, 'other_repo>) -> Result, ) -> Result<(), Error> where E: std::error::Error + Sync + Send + 'static, @@ -202,8 +217,7 @@ pub mod diff { impl<'repo, 'other_repo, VisitFn, E> git_diff::tree::Visit for Delegate<'repo, 'other_repo, VisitFn, E> where - VisitFn: - for<'delegate> FnMut(Change<'delegate, 'repo, 'other_repo>) -> Result, + VisitFn: for<'delegate> FnMut(Change<'delegate, 'repo, 'other_repo>) -> Result, E: std::error::Error + Sync + Send + 'static, { fn pop_front_tracked_path_and_set_current(&mut self) {} @@ -251,7 +265,8 @@ pub mod diff { event, location: self.location.as_ref(), }) { - Ok(action) => action, + Ok(Action::Cancel) => git_diff::tree::visit::Action::Cancel, + Ok(Action::Continue) => git_diff::tree::visit::Action::Continue, Err(err) => { self.err = Some(err); git_diff::tree::visit::Action::Cancel diff --git a/git-repository/tests/object/tree.rs b/git-repository/tests/object/tree.rs index 2e7c1676ee8..989f3ea79d1 100644 --- a/git-repository/tests/object/tree.rs +++ b/git-repository/tests/object/tree.rs @@ -25,7 +25,7 @@ mod diff { assert_eq!(entry_mode, EntryMode::Blob); assert_eq!(previous_id.object().unwrap().data.as_bstr(), "g\n"); assert_eq!(id.object().unwrap().data.as_bstr(), "h\n"); - Ok(git::diff::tree::visit::Action::Continue) + Ok(Default::default()) } Event::Deletion { .. } | Event::Addition { .. } => unreachable!("only modification is expected"), } @@ -36,7 +36,7 @@ mod diff { .track_filename() .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { assert_eq!(change.location, "file"); - Ok(git::diff::tree::visit::Action::Continue) + Ok(git::object::tree::diff::Action::Continue) }) .unwrap(); } From 9b7aaa00ed7750e0f6a5898212d78ffa98456749 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 19 Sep 2022 19:59:45 +0800 Subject: [PATCH 10/48] fix docs (#470) --- git-repository/src/object/tree/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git-repository/src/object/tree/mod.rs b/git-repository/src/object/tree/mod.rs index deb7e80f3d9..963bca537f9 100644 --- a/git-repository/src/object/tree/mod.rs +++ b/git-repository/src/object/tree/mod.rs @@ -71,7 +71,7 @@ pub mod diff { use git_object::TreeRefIter; use git_odb::FindExt; - /// The error return by methods on the [diff platform][super::Platform]. + /// The error return by methods on the [diff platform][Platform]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { @@ -142,7 +142,7 @@ pub mod diff { /// Diffing impl<'repo> Tree<'repo> { /// Return a platform to see the changes needed to create other trees, for instance. - pub fn changes<'other_repo, 'a>(&'a self) -> Platform<'a, 'repo> { + pub fn changes<'a>(&'a self) -> Platform<'a, 'repo> { Platform { state: Default::default(), lhs: self, From 90b9c906bc3779d62b0317ea318c07693fda0d3c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 19 Sep 2022 20:01:05 +0800 Subject: [PATCH 11/48] refactor (#470) --- git-repository/src/object/tree/diff.rs | 209 ++++++++++++++++++++++++ git-repository/src/object/tree/mod.rs | 213 +------------------------ 2 files changed, 210 insertions(+), 212 deletions(-) create mode 100644 git-repository/src/object/tree/diff.rs diff --git a/git-repository/src/object/tree/diff.rs b/git-repository/src/object/tree/diff.rs new file mode 100644 index 00000000000..c316db4dc3d --- /dev/null +++ b/git-repository/src/object/tree/diff.rs @@ -0,0 +1,209 @@ +use crate::bstr::{BStr, BString, ByteVec}; +use crate::ext::ObjectIdExt; +use crate::{Id, Repository, Tree}; +use git_object::TreeRefIter; +use git_odb::FindExt; + +/// The error return by methods on the [diff platform][Platform]. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error(transparent)] + Diff(#[from] git_diff::tree::changes::Error), + #[error("The user-provided callback failed")] + ForEach(#[source] Box), +} + +/// Returned by the `for_each` function to control flow. +#[derive(Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)] +pub enum Action { + /// Continue the traversal of changes. + Continue, + /// Stop the traversal of changes and stop calling this function. + Cancel, +} + +impl Default for Action { + fn default() -> Self { + Action::Continue + } +} + +/// Represents any possible change in order to turn one tree into another. +#[derive(Debug, Clone, Copy)] +pub struct Change<'a, 'repo, 'other_repo> { + /// The location of the file or directory described by `event`, if tracking was enabled. + /// + /// Otherwise this value is always an empty path. + pub location: &'a BStr, + /// The diff event itself to provide information about what would need to change. + pub event: Event<'repo, 'other_repo>, +} + +/// An event emitted when finding differences between two trees. +#[derive(Debug, Clone, Copy)] +pub enum Event<'repo, 'other_repo> { + /// An entry was added, like the addition of a file or directory. + Addition { + /// The mode of the added entry. + entry_mode: git_object::tree::EntryMode, + /// The object id of the added entry. + id: Id<'other_repo>, + }, + /// An entry was deleted, like the deletion of a file or directory. + Deletion { + /// The mode of the deleted entry. + entry_mode: git_object::tree::EntryMode, + /// The object id of the deleted entry. + id: Id<'repo>, + }, + /// An entry was modified, e.g. changing the contents of a file adjusts its object id and turning + /// a file into a symbolic link adjusts its mode. + Modification { + /// The mode of the entry before the modification. + previous_entry_mode: git_object::tree::EntryMode, + /// The object id of the entry before the modification. + previous_id: Id<'repo>, + + /// The mode of the entry after the modification. + entry_mode: git_object::tree::EntryMode, + /// The object id after the modification. + id: Id<'other_repo>, + }, +} + +/// Diffing +impl<'repo> Tree<'repo> { + /// Return a platform to see the changes needed to create other trees, for instance. + pub fn changes<'a>(&'a self) -> Platform<'a, 'repo> { + Platform { + state: Default::default(), + lhs: self, + tracking: None, + } + } +} + +/// The diffing platform returned by [`Tree::changes()`]. +#[derive(Clone)] +pub struct Platform<'a, 'repo> { + state: git_diff::tree::State, + lhs: &'a Tree<'repo>, + tracking: Option, +} + +#[derive(Clone, Copy)] +enum Tracking { + FileName, +} + +/// Configuration +impl<'a, 'repo> Platform<'a, 'repo> { + /// Keep track of file-names, which makes the [`location`][Change::location] field usable with the filename of the changed item. + pub fn track_filename(&mut self) -> &mut Self { + self.tracking = Some(Tracking::FileName); + self + } +} + +/// Add the item to compare to. +impl<'a, 'repo> Platform<'a, 'repo> { + /// Call `for_each` repeatedly with all changes that are needed to convert the source of the diff to the tree to `other`. + pub fn for_each_to_obtain_tree<'other_repo, E>( + &mut self, + other: &Tree<'other_repo>, + for_each: impl FnMut(Change<'_, 'repo, 'other_repo>) -> Result, + ) -> Result<(), Error> + where + E: std::error::Error + Sync + Send + 'static, + { + let repo = self.lhs.repo; + let mut delegate = Delegate { + repo: self.lhs.repo, + other_repo: other.repo, + tracking: self.tracking, + location: BString::default(), + visit: for_each, + err: None, + }; + git_diff::tree::Changes::from(TreeRefIter::from_bytes(&self.lhs.data)).needed_to_obtain( + TreeRefIter::from_bytes(&other.data), + &mut self.state, + |oid, buf| repo.objects.find_tree_iter(oid, buf), + &mut delegate, + )?; + match delegate.err { + Some(err) => Err(Error::ForEach(Box::new(err))), + None => Ok(()), + } + } +} + +struct Delegate<'repo, 'other_repo, VisitFn, E> { + repo: &'repo Repository, + other_repo: &'other_repo Repository, + tracking: Option, + location: BString, + visit: VisitFn, + err: Option, +} + +impl<'repo, 'other_repo, VisitFn, E> git_diff::tree::Visit for Delegate<'repo, 'other_repo, VisitFn, E> +where + VisitFn: for<'delegate> FnMut(Change<'delegate, 'repo, 'other_repo>) -> Result, + E: std::error::Error + Sync + Send + 'static, +{ + fn pop_front_tracked_path_and_set_current(&mut self) {} + + fn push_back_tracked_path_component(&mut self, _component: &BStr) { + {} + } + + fn push_path_component(&mut self, component: &BStr) { + match self.tracking { + Some(Tracking::FileName) => { + self.location.clear(); + self.location.push_str(component); + } + None => {} + } + } + + fn pop_path_component(&mut self) {} + + fn visit(&mut self, change: git_diff::tree::visit::Change) -> git_diff::tree::visit::Action { + use git_diff::tree::visit::Change::*; + let event = match change { + Addition { entry_mode, oid } => Event::Addition { + entry_mode, + id: oid.attach(self.other_repo), + }, + Deletion { entry_mode, oid } => Event::Deletion { + entry_mode, + id: oid.attach(self.repo), + }, + Modification { + previous_entry_mode, + previous_oid, + entry_mode, + oid, + } => Event::Modification { + previous_entry_mode, + entry_mode, + previous_id: previous_oid.attach(self.repo), + id: oid.attach(self.other_repo), + }, + }; + match (self.visit)(Change { + event, + location: self.location.as_ref(), + }) { + Ok(Action::Cancel) => git_diff::tree::visit::Action::Cancel, + Ok(Action::Continue) => git_diff::tree::visit::Action::Continue, + Err(err) => { + self.err = Some(err); + git_diff::tree::visit::Action::Cancel + } + } + } +} diff --git a/git-repository/src/object/tree/mod.rs b/git-repository/src/object/tree/mod.rs index 963bca537f9..d8a2bad728e 100644 --- a/git-repository/src/object/tree/mod.rs +++ b/git-repository/src/object/tree/mod.rs @@ -62,219 +62,8 @@ impl<'repo> Tree<'repo> { } } -#[allow(missing_docs)] /// -pub mod diff { - use crate::bstr::{BStr, BString, ByteVec}; - use crate::ext::ObjectIdExt; - use crate::{Id, Repository, Tree}; - use git_object::TreeRefIter; - use git_odb::FindExt; - - /// The error return by methods on the [diff platform][Platform]. - #[derive(Debug, thiserror::Error)] - #[allow(missing_docs)] - pub enum Error { - #[error(transparent)] - Diff(#[from] git_diff::tree::changes::Error), - #[error("The user-provided callback failed")] - ForEach(#[source] Box), - } - - /// Returned by the `for_each` function to control flow. - #[derive(Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)] - pub enum Action { - /// Continue the traversal of changes. - Continue, - /// Stop the traversal of changes and stop calling this function. - Cancel, - } - - impl Default for Action { - fn default() -> Self { - Action::Continue - } - } - - /// Represents any possible change in order to turn one tree into another. - #[derive(Debug, Clone, Copy)] - pub struct Change<'a, 'repo, 'other_repo> { - /// The location of the file or directory described by `event`, if tracking was enabled. - /// - /// Otherwise this value is always an empty path. - pub location: &'a BStr, - /// The diff event itself to provide information about what would need to change. - pub event: Event<'repo, 'other_repo>, - } - - /// An event emitted when finding differences between two trees. - #[derive(Debug, Clone, Copy)] - pub enum Event<'repo, 'other_repo> { - /// An entry was added, like the addition of a file or directory. - Addition { - /// The mode of the added entry. - entry_mode: git_object::tree::EntryMode, - /// The object id of the added entry. - id: Id<'other_repo>, - }, - /// An entry was deleted, like the deletion of a file or directory. - Deletion { - /// The mode of the deleted entry. - entry_mode: git_object::tree::EntryMode, - /// The object id of the deleted entry. - id: Id<'repo>, - }, - /// An entry was modified, e.g. changing the contents of a file adjusts its object id and turning - /// a file into a symbolic link adjusts its mode. - Modification { - /// The mode of the entry before the modification. - previous_entry_mode: git_object::tree::EntryMode, - /// The object id of the entry before the modification. - previous_id: Id<'repo>, - - /// The mode of the entry after the modification. - entry_mode: git_object::tree::EntryMode, - /// The object id after the modification. - id: Id<'other_repo>, - }, - } - - /// Diffing - impl<'repo> Tree<'repo> { - /// Return a platform to see the changes needed to create other trees, for instance. - pub fn changes<'a>(&'a self) -> Platform<'a, 'repo> { - Platform { - state: Default::default(), - lhs: self, - tracking: None, - } - } - } - - /// The diffing platform returned by [`Tree::changes()`]. - #[derive(Clone)] - pub struct Platform<'a, 'repo> { - state: git_diff::tree::State, - lhs: &'a Tree<'repo>, - tracking: Option, - } - - #[derive(Clone, Copy)] - enum Tracking { - FileName, - } - - /// Configuration - impl<'a, 'repo> Platform<'a, 'repo> { - /// Keep track of file-names, which makes the [`location`][Change::location] field usable with the filename of the changed item. - pub fn track_filename(&mut self) -> &mut Self { - self.tracking = Some(Tracking::FileName); - self - } - } - - /// Add the item to compare to. - impl<'a, 'repo> Platform<'a, 'repo> { - /// Call `for_each` repeatedly with all changes that are needed to convert the source of the diff to the tree to `other`. - pub fn for_each_to_obtain_tree<'other_repo, E>( - &mut self, - other: &Tree<'other_repo>, - for_each: impl FnMut(Change<'_, 'repo, 'other_repo>) -> Result, - ) -> Result<(), Error> - where - E: std::error::Error + Sync + Send + 'static, - { - let repo = self.lhs.repo; - let mut delegate = Delegate { - repo: self.lhs.repo, - other_repo: other.repo, - tracking: self.tracking, - location: BString::default(), - visit: for_each, - err: None, - }; - git_diff::tree::Changes::from(TreeRefIter::from_bytes(&self.lhs.data)).needed_to_obtain( - TreeRefIter::from_bytes(&other.data), - &mut self.state, - |oid, buf| repo.objects.find_tree_iter(oid, buf), - &mut delegate, - )?; - match delegate.err { - Some(err) => Err(Error::ForEach(Box::new(err))), - None => Ok(()), - } - } - } - - struct Delegate<'repo, 'other_repo, VisitFn, E> { - repo: &'repo Repository, - other_repo: &'other_repo Repository, - tracking: Option, - location: BString, - visit: VisitFn, - err: Option, - } - - impl<'repo, 'other_repo, VisitFn, E> git_diff::tree::Visit for Delegate<'repo, 'other_repo, VisitFn, E> - where - VisitFn: for<'delegate> FnMut(Change<'delegate, 'repo, 'other_repo>) -> Result, - E: std::error::Error + Sync + Send + 'static, - { - fn pop_front_tracked_path_and_set_current(&mut self) {} - - fn push_back_tracked_path_component(&mut self, _component: &BStr) { - {} - } - - fn push_path_component(&mut self, component: &BStr) { - match self.tracking { - Some(Tracking::FileName) => { - self.location.clear(); - self.location.push_str(component); - } - None => {} - } - } - - fn pop_path_component(&mut self) {} - - fn visit(&mut self, change: git_diff::tree::visit::Change) -> git_diff::tree::visit::Action { - use git_diff::tree::visit::Change::*; - let event = match change { - Addition { entry_mode, oid } => Event::Addition { - entry_mode, - id: oid.attach(self.other_repo), - }, - Deletion { entry_mode, oid } => Event::Deletion { - entry_mode, - id: oid.attach(self.repo), - }, - Modification { - previous_entry_mode, - previous_oid, - entry_mode, - oid, - } => Event::Modification { - previous_entry_mode, - entry_mode, - previous_id: previous_oid.attach(self.repo), - id: oid.attach(self.other_repo), - }, - }; - match (self.visit)(Change { - event, - location: self.location.as_ref(), - }) { - Ok(Action::Cancel) => git_diff::tree::visit::Action::Cancel, - Ok(Action::Continue) => git_diff::tree::visit::Action::Continue, - Err(err) => { - self.err = Some(err); - git_diff::tree::visit::Action::Cancel - } - } - } - } -} +pub mod diff; /// pub mod traverse; From 9d01fb41c8b367b8bd73061fc5f0f7dc4d33f7d1 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 19 Sep 2022 20:10:44 +0800 Subject: [PATCH 12/48] refactor (#470) --- git-repository/src/object/tree/diff.rs | 75 ++++++++++++++------------ git-repository/tests/object/tree.rs | 2 +- 2 files changed, 41 insertions(+), 36 deletions(-) diff --git a/git-repository/src/object/tree/diff.rs b/git-repository/src/object/tree/diff.rs index c316db4dc3d..2171ec6521d 100644 --- a/git-repository/src/object/tree/diff.rs +++ b/git-repository/src/object/tree/diff.rs @@ -1,6 +1,6 @@ use crate::bstr::{BStr, BString, ByteVec}; use crate::ext::ObjectIdExt; -use crate::{Id, Repository, Tree}; +use crate::{Repository, Tree}; use git_object::TreeRefIter; use git_odb::FindExt; @@ -37,39 +37,44 @@ pub struct Change<'a, 'repo, 'other_repo> { /// Otherwise this value is always an empty path. pub location: &'a BStr, /// The diff event itself to provide information about what would need to change. - pub event: Event<'repo, 'other_repo>, + pub event: change::Event<'repo, 'other_repo>, } -/// An event emitted when finding differences between two trees. -#[derive(Debug, Clone, Copy)] -pub enum Event<'repo, 'other_repo> { - /// An entry was added, like the addition of a file or directory. - Addition { - /// The mode of the added entry. - entry_mode: git_object::tree::EntryMode, - /// The object id of the added entry. - id: Id<'other_repo>, - }, - /// An entry was deleted, like the deletion of a file or directory. - Deletion { - /// The mode of the deleted entry. - entry_mode: git_object::tree::EntryMode, - /// The object id of the deleted entry. - id: Id<'repo>, - }, - /// An entry was modified, e.g. changing the contents of a file adjusts its object id and turning - /// a file into a symbolic link adjusts its mode. - Modification { - /// The mode of the entry before the modification. - previous_entry_mode: git_object::tree::EntryMode, - /// The object id of the entry before the modification. - previous_id: Id<'repo>, - - /// The mode of the entry after the modification. - entry_mode: git_object::tree::EntryMode, - /// The object id after the modification. - id: Id<'other_repo>, - }, +/// +pub mod change { + use crate::Id; + + /// An event emitted when finding differences between two trees. + #[derive(Debug, Clone, Copy)] + pub enum Event<'repo, 'other_repo> { + /// An entry was added, like the addition of a file or directory. + Addition { + /// The mode of the added entry. + entry_mode: git_object::tree::EntryMode, + /// The object id of the added entry. + id: Id<'other_repo>, + }, + /// An entry was deleted, like the deletion of a file or directory. + Deletion { + /// The mode of the deleted entry. + entry_mode: git_object::tree::EntryMode, + /// The object id of the deleted entry. + id: Id<'repo>, + }, + /// An entry was modified, e.g. changing the contents of a file adjusts its object id and turning + /// a file into a symbolic link adjusts its mode. + Modification { + /// The mode of the entry before the modification. + previous_entry_mode: git_object::tree::EntryMode, + /// The object id of the entry before the modification. + previous_id: Id<'repo>, + + /// The mode of the entry after the modification. + entry_mode: git_object::tree::EntryMode, + /// The object id after the modification. + id: Id<'other_repo>, + }, + } } /// Diffing @@ -174,11 +179,11 @@ where fn visit(&mut self, change: git_diff::tree::visit::Change) -> git_diff::tree::visit::Action { use git_diff::tree::visit::Change::*; let event = match change { - Addition { entry_mode, oid } => Event::Addition { + Addition { entry_mode, oid } => change::Event::Addition { entry_mode, id: oid.attach(self.other_repo), }, - Deletion { entry_mode, oid } => Event::Deletion { + Deletion { entry_mode, oid } => change::Event::Deletion { entry_mode, id: oid.attach(self.repo), }, @@ -187,7 +192,7 @@ where previous_oid, entry_mode, oid, - } => Event::Modification { + } => change::Event::Modification { previous_entry_mode, entry_mode, previous_id: previous_oid.attach(self.repo), diff --git a/git-repository/tests/object/tree.rs b/git-repository/tests/object/tree.rs index 989f3ea79d1..1a0300ec41d 100644 --- a/git-repository/tests/object/tree.rs +++ b/git-repository/tests/object/tree.rs @@ -3,7 +3,7 @@ mod diff { use git_object::bstr::ByteSlice; use git_object::tree::EntryMode; use git_repository as git; - use git_repository::object::tree::diff::Event; + use git_repository::object::tree::diff::change::Event; use std::convert::Infallible; #[test] From ae3866065c9c3c6d01709f8dde1cea1ae1345779 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 19 Sep 2022 20:35:54 +0800 Subject: [PATCH 13/48] fix: rev-spec parsing can now handle the empty tree as full hex hash. (#470) Even though the empty-tree object can be found when searched via `Repository::find_object()`, previously it was not locatable when used during rev-spec parsing. --- .../revision/spec/parse/delegate/revision.rs | 11 +++++++- .../generated-archives/make_diff_repo.tar.xz | 3 +++ .../make_rev_spec_parse_repos.tar.xz | 4 +-- .../tests/fixtures/make_diff_repo.sh | 18 +++++++++++++ .../fixtures/make_rev_spec_parse_repos.sh | 1 + git-repository/tests/object/tree.rs | 27 +++++++++++++------ .../tests/revision/spec/from_bytes/mod.rs | 9 +++++++ 7 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 git-repository/tests/fixtures/generated-archives/make_diff_repo.tar.xz create mode 100644 git-repository/tests/fixtures/make_diff_repo.sh diff --git a/git-repository/src/revision/spec/parse/delegate/revision.rs b/git-repository/src/revision/spec/parse/delegate/revision.rs index 851686b3ddb..ead9887e144 100644 --- a/git-repository/src/revision/spec/parse/delegate/revision.rs +++ b/git-repository/src/revision/spec/parse/delegate/revision.rs @@ -40,7 +40,16 @@ impl<'repo> delegate::Revision for Delegate<'repo> { self.last_call_was_disambiguate_prefix[self.idx] = true; let mut candidates = Some(HashSet::default()); self.prefix[self.idx] = Some(prefix); - match self.repo.objects.lookup_prefix(prefix, candidates.as_mut()) { + + let empty_tree_id = git_hash::ObjectId::empty_tree(prefix.as_oid().kind()); + let res = if prefix.as_oid() == empty_tree_id { + candidates.as_mut().expect("set").insert(empty_tree_id); + Ok(Some(Err(()))) + } else { + self.repo.objects.lookup_prefix(prefix, candidates.as_mut()) + }; + + match res { Err(err) => { self.err.push(object::find::existing::Error::Find(err).into()); None diff --git a/git-repository/tests/fixtures/generated-archives/make_diff_repo.tar.xz b/git-repository/tests/fixtures/generated-archives/make_diff_repo.tar.xz new file mode 100644 index 00000000000..9f9fe388495 --- /dev/null +++ b/git-repository/tests/fixtures/generated-archives/make_diff_repo.tar.xz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2123aa9e3fd85166d35c02c59ce8f0e8aaa3631c3dd4e43468f98d26cddf049c +size 11128 diff --git a/git-repository/tests/fixtures/generated-archives/make_rev_spec_parse_repos.tar.xz b/git-repository/tests/fixtures/generated-archives/make_rev_spec_parse_repos.tar.xz index e2d3029e8d8..699d49f7e79 100644 --- a/git-repository/tests/fixtures/generated-archives/make_rev_spec_parse_repos.tar.xz +++ b/git-repository/tests/fixtures/generated-archives/make_rev_spec_parse_repos.tar.xz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4de4199add061d1ef825b392162898fcf4da67f69c5d588012fe4d71465c768f -size 28372 +oid sha256:0edf5366df74aaeb3c58ad070f9f9a522d0fae049f0905b7372ff28d0b3628bf +size 28716 diff --git a/git-repository/tests/fixtures/make_diff_repo.sh b/git-repository/tests/fixtures/make_diff_repo.sh new file mode 100644 index 00000000000..431c02138a1 --- /dev/null +++ b/git-repository/tests/fixtures/make_diff_repo.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -eu -o pipefail + +git init -q + +git checkout -b main +mkdir dir +touch a b dir/c +git add . +git commit -q -m c1 + +echo a >> a +echo b >> b +echo dir/c >> dir/c +git commit -q -am c2 + +echo a1 >> a +git commit -q -am c3 diff --git a/git-repository/tests/fixtures/make_rev_spec_parse_repos.sh b/git-repository/tests/fixtures/make_rev_spec_parse_repos.sh index a0bad26d051..1d02c182d08 100644 --- a/git-repository/tests/fixtures/make_rev_spec_parse_repos.sh +++ b/git-repository/tests/fixtures/make_rev_spec_parse_repos.sh @@ -389,6 +389,7 @@ git init complex_graph baseline "@^{tree}" baseline "@:" + baseline "4b825dc642cb6eb9a060e54bf8d69288fbee4904" ) git init new diff --git a/git-repository/tests/object/tree.rs b/git-repository/tests/object/tree.rs index 1a0300ec41d..d1203506668 100644 --- a/git-repository/tests/object/tree.rs +++ b/git-repository/tests/object/tree.rs @@ -1,5 +1,5 @@ mod diff { - use crate::remote; + use crate::named_repo; use git_object::bstr::ByteSlice; use git_object::tree::EntryMode; use git_repository as git; @@ -8,9 +8,9 @@ mod diff { #[test] fn changes_against_tree_modified() { - let repo = remote::repo("base"); - let from = tree_named(&repo, "g"); - let to = tree_named(&repo, "h"); + let repo = named_repo("make_diff_repo.sh").unwrap(); + let from = tree_named(&repo, "@^{/c3}~1"); + let to = tree_named(&repo, ":/c3"); from.changes() .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { assert_eq!(change.location, "", "without configuration the location field is empty"); @@ -23,22 +23,33 @@ mod diff { } => { assert_eq!(previous_entry_mode, EntryMode::Blob); assert_eq!(entry_mode, EntryMode::Blob); - assert_eq!(previous_id.object().unwrap().data.as_bstr(), "g\n"); - assert_eq!(id.object().unwrap().data.as_bstr(), "h\n"); + assert_eq!(previous_id.object().unwrap().data.as_bstr(), "a\n"); + assert_eq!(id.object().unwrap().data.as_bstr(), "a\na1\n"); Ok(Default::default()) } Event::Deletion { .. } | Event::Addition { .. } => unreachable!("only modification is expected"), } }) .unwrap(); + } + #[test] + fn changes_against_tree_with_filename_tracking() { + let repo = named_repo("make_diff_repo.sh").unwrap(); + let from = tree_named( + &repo, + &git::hash::ObjectId::empty_tree(git::hash::Kind::Sha1).to_string(), + ); + let to = tree_named(&repo, ":/c1"); + let mut expected = vec!["a", "b", "c"]; from.changes() .track_filename() .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { - assert_eq!(change.location, "file"); - Ok(git::object::tree::diff::Action::Continue) + expected.retain(|name| name != change.location); + Ok(Default::default()) }) .unwrap(); + assert_eq!(expected, Vec::<&str>::new(), "all paths should have been seen") } fn tree_named<'repo>(repo: &'repo git::Repository, rev_spec: &str) -> git::Tree<'repo> { diff --git a/git-repository/tests/revision/spec/from_bytes/mod.rs b/git-repository/tests/revision/spec/from_bytes/mod.rs index a26b257ea10..b341badfa9f 100644 --- a/git-repository/tests/revision/spec/from_bytes/mod.rs +++ b/git-repository/tests/revision/spec/from_bytes/mod.rs @@ -123,3 +123,12 @@ fn access_blob_through_tree() { "Could not find path \"missing\" in tree 0000000000c of parent object 0000000000c" ); } + +#[test] +fn empty_tree_as_full_name() { + let repo = repo("complex_graph").unwrap(); + assert_eq!( + parse_spec("4b825dc642cb6eb9a060e54bf8d69288fbee4904", &repo).unwrap(), + Spec::from_id(hex_to_id("4b825dc642cb6eb9a060e54bf8d69288fbee4904").attach(&repo)) + ); +} From 64bbb3da42f740206514baf2fa504371fd6f06c4 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 19 Sep 2022 20:45:11 +0800 Subject: [PATCH 14/48] Support for Path tracking (#470) --- git-repository/src/object/tree/diff.rs | 56 +++++++++++++++++++++++--- git-repository/tests/object/tree.rs | 12 +++++- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/git-repository/src/object/tree/diff.rs b/git-repository/src/object/tree/diff.rs index 2171ec6521d..6059b07dc7d 100644 --- a/git-repository/src/object/tree/diff.rs +++ b/git-repository/src/object/tree/diff.rs @@ -1,8 +1,9 @@ -use crate::bstr::{BStr, BString, ByteVec}; +use crate::bstr::{BStr, BString, ByteSlice, ByteVec}; use crate::ext::ObjectIdExt; use crate::{Repository, Tree}; use git_object::TreeRefIter; use git_odb::FindExt; +use std::collections::VecDeque; /// The error return by methods on the [diff platform][Platform]. #[derive(Debug, thiserror::Error)] @@ -100,6 +101,7 @@ pub struct Platform<'a, 'repo> { #[derive(Clone, Copy)] enum Tracking { FileName, + Path, } /// Configuration @@ -109,6 +111,14 @@ impl<'a, 'repo> Platform<'a, 'repo> { self.tracking = Some(Tracking::FileName); self } + + /// Keep track of the entire path of a change, relative to the repository. + /// + /// This makes the [`location`][Change::location] field usable. + pub fn track_path(&mut self) -> &mut Self { + self.tracking = Some(Tracking::Path); + self + } } /// Add the item to compare to. @@ -128,6 +138,7 @@ impl<'a, 'repo> Platform<'a, 'repo> { other_repo: other.repo, tracking: self.tracking, location: BString::default(), + path_deque: Default::default(), visit: for_each, err: None, }; @@ -149,19 +160,47 @@ struct Delegate<'repo, 'other_repo, VisitFn, E> { other_repo: &'other_repo Repository, tracking: Option, location: BString, + path_deque: VecDeque, visit: VisitFn, err: Option, } +impl Delegate<'_, '_, A, B> { + fn pop_element(&mut self) { + if let Some(pos) = self.location.rfind_byte(b'/') { + self.location.resize(pos, 0); + } else { + self.location.clear(); + } + } + + fn push_element(&mut self, name: &BStr) { + if !self.location.is_empty() { + self.location.push(b'/'); + } + self.location.push_str(name); + } +} + impl<'repo, 'other_repo, VisitFn, E> git_diff::tree::Visit for Delegate<'repo, 'other_repo, VisitFn, E> where VisitFn: for<'delegate> FnMut(Change<'delegate, 'repo, 'other_repo>) -> Result, E: std::error::Error + Sync + Send + 'static, { - fn pop_front_tracked_path_and_set_current(&mut self) {} + fn pop_front_tracked_path_and_set_current(&mut self) { + if let Some(Tracking::Path) = self.tracking { + self.location = self + .path_deque + .pop_front() + .expect("every call is matched with push_tracked_path_component"); + } + } - fn push_back_tracked_path_component(&mut self, _component: &BStr) { - {} + fn push_back_tracked_path_component(&mut self, component: &BStr) { + if let Some(Tracking::Path) = self.tracking { + self.push_element(component); + self.path_deque.push_back(self.location.clone()); + } } fn push_path_component(&mut self, component: &BStr) { @@ -170,11 +209,18 @@ where self.location.clear(); self.location.push_str(component); } + Some(Tracking::Path) => { + self.push_element(component); + } None => {} } } - fn pop_path_component(&mut self) {} + fn pop_path_component(&mut self) { + if let Some(Tracking::Path) = self.tracking { + self.pop_element(); + } + } fn visit(&mut self, change: git_diff::tree::visit::Change) -> git_diff::tree::visit::Action { use git_diff::tree::visit::Change::*; diff --git a/git-repository/tests/object/tree.rs b/git-repository/tests/object/tree.rs index d1203506668..094753ed032 100644 --- a/git-repository/tests/object/tree.rs +++ b/git-repository/tests/object/tree.rs @@ -49,7 +49,17 @@ mod diff { Ok(Default::default()) }) .unwrap(); - assert_eq!(expected, Vec::<&str>::new(), "all paths should have been seen") + assert_eq!(expected, Vec::<&str>::new(), "all paths should have been seen"); + + let mut expected = vec!["a", "b", "dir/c"]; + from.changes() + .track_path() + .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { + expected.retain(|name| name != change.location); + Ok(Default::default()) + }) + .unwrap(); + assert_eq!(expected, Vec::<&str>::new(), "all paths should have been seen"); } fn tree_named<'repo>(repo: &'repo git::Repository, rev_spec: &str) -> git::Tree<'repo> { From 06704683cdde64c0ed9b38df5e4e8ce29dbce524 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 19 Sep 2022 21:11:41 +0800 Subject: [PATCH 15/48] performance note (#470) --- git-repository/src/object/tree/diff.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/git-repository/src/object/tree/diff.rs b/git-repository/src/object/tree/diff.rs index 6059b07dc7d..db7a7ef6291 100644 --- a/git-repository/src/object/tree/diff.rs +++ b/git-repository/src/object/tree/diff.rs @@ -81,6 +81,10 @@ pub mod change { /// Diffing impl<'repo> Tree<'repo> { /// Return a platform to see the changes needed to create other trees, for instance. + /// + /// # Performance + /// + /// It's highly recommended to set an object cache to avoid extracting the same object multiple times. pub fn changes<'a>(&'a self) -> Platform<'a, 'repo> { Platform { state: Default::default(), From 963055b45643fb48460671959b10dc12658bb5d4 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 19 Sep 2022 21:33:44 +0800 Subject: [PATCH 16/48] Slightly improved docs for traversal docs. (#470) --- git-repository/src/object/tree/traverse.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/git-repository/src/object/tree/traverse.rs b/git-repository/src/object/tree/traverse.rs index b95d4d46da8..98a9a3f168c 100644 --- a/git-repository/src/object/tree/traverse.rs +++ b/git-repository/src/object/tree/traverse.rs @@ -15,11 +15,11 @@ impl<'repo> Tree<'repo> { /// An intermediate object to start traversing the parent tree from. pub struct Platform<'a, 'repo> { root: &'a Tree<'repo>, - #[allow(missing_docs)] // TODO + /// Provides easy access to presets for common breadth-first traversal. pub breadthfirst: BreadthFirstPresets<'a, 'repo>, } -#[allow(missing_docs)] // TODO +/// Presets for common choices in breadth-first traversal. #[derive(Copy, Clone)] pub struct BreadthFirstPresets<'a, 'repo> { root: &'a Tree<'repo>, @@ -39,8 +39,11 @@ impl<'a, 'repo> BreadthFirstPresets<'a, 'repo> { } impl<'a, 'repo> Platform<'a, 'repo> { - /// Start a breadth-first traversal with a delegate, note that it's not sorted. - /// TODO: more docs or links to git-traverse + /// Start a breadth-first traversal using `delegate`, for which a [`Recorder`][git_traverse::tree::Recorder] can be used to get started. + /// + /// # Note + /// + /// Results are not sorted. pub fn breadthfirst(&self, delegate: &mut V) -> Result<(), git_traverse::tree::breadthfirst::Error> where V: git_traverse::tree::Visit, From e164856ab8d80c13b082a283b4c73b6fb31968f6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 19 Sep 2022 21:55:48 +0800 Subject: [PATCH 17/48] feat: forward line-diffing capabilities curtesy of the `similar` crate. (#470) This is a first and maybe the last step towards providing diffing functionality, and it seems like the right choice to keep this in similar and contribute there as needed. All algorithms are well described and thus shouldn't be git-specific per-se, and `similar` is the best the community has to offer. --- Cargo.lock | 4 ++++ git-diff/Cargo.toml | 1 + git-diff/src/lib.rs | 3 +++ git-diff/src/lines.rs | 26 ++++++++++++++++++++++++++ 4 files changed, 34 insertions(+) create mode 100644 git-diff/src/lines.rs diff --git a/Cargo.lock b/Cargo.lock index a89a532b3a8..51e4d975db7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1262,6 +1262,7 @@ dependencies = [ "git-odb", "git-testtools", "git-traverse", + "similar", "thiserror", ] @@ -2899,6 +2900,9 @@ name = "similar" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62ac7f900db32bf3fd12e0117dd3dc4da74bc52ebaac97f39668446d89694803" +dependencies = [ + "bstr 0.2.17", +] [[package]] name = "slab" diff --git a/git-diff/Cargo.toml b/git-diff/Cargo.toml index 23853efe91f..a42c308e58c 100644 --- a/git-diff/Cargo.toml +++ b/git-diff/Cargo.toml @@ -15,6 +15,7 @@ doctest = false git-hash = { version = "^0.9.9", path = "../git-hash" } git-object = { version = "^0.20.3", path = "../git-object" } thiserror = "1.0.32" +similar = { version = "2.2.0", features = ["bytes"] } [dev-dependencies] git-odb = { path = "../git-odb" } diff --git a/git-diff/src/lib.rs b/git-diff/src/lib.rs index e279b442a1b..e1f1d292be3 100644 --- a/git-diff/src/lib.rs +++ b/git-diff/src/lib.rs @@ -4,3 +4,6 @@ /// pub mod tree; + +/// +pub mod lines; diff --git a/git-diff/src/lines.rs b/git-diff/src/lines.rs new file mode 100644 index 00000000000..ebb58ddb62d --- /dev/null +++ b/git-diff/src/lines.rs @@ -0,0 +1,26 @@ +use git_object::bstr::BStr; +use similar::TextDiff; + +/// The crate powering file diffs. +pub use similar; +pub use similar::Algorithm; + +/// Provide an iterator over the changes needed to turn `old` into `new` with `algorithm`. +/// +/// See [the `similar` crate documentation][similar::TextDiffConfig::diff_lines()] for information on how to use the iterator. +pub fn with<'old, 'new, 'bufs>( + old: &'old BStr, + new: &'new BStr, + algorithm: Algorithm, +) -> TextDiff<'old, 'new, 'bufs, [u8]> { + similar::TextDiffConfig::default() + .algorithm(algorithm) + .diff_lines(old.as_ref(), new.as_ref()) +} + +/// Provide an iterator over the changes needed to turn `old` into `new` with Myers algorithm, the default for `git`. +/// +/// See [the `similar` crate documentation][similar::TextDiffConfig::diff_lines()] for information on how to use the iterator. +pub fn myers<'old, 'new, 'bufs>(old: &'old BStr, new: &'new BStr) -> TextDiff<'old, 'new, 'bufs, [u8]> { + with(old, new, Algorithm::Myers) +} From 8c2e5c60f9f5f8d0859ecd84c17af20e88812512 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 19 Sep 2022 22:21:50 +0800 Subject: [PATCH 18/48] feat: Once a change is obtained, it's easy to obtain changes line by line. (#470) --- git-repository/src/object/tree/diff.rs | 72 ++++++++++++++++++++------ git-repository/tests/object/tree.rs | 15 +++++- 2 files changed, 69 insertions(+), 18 deletions(-) diff --git a/git-repository/src/object/tree/diff.rs b/git-repository/src/object/tree/diff.rs index db7a7ef6291..a3f440ddc74 100644 --- a/git-repository/src/object/tree/diff.rs +++ b/git-repository/src/object/tree/diff.rs @@ -32,35 +32,37 @@ impl Default for Action { /// Represents any possible change in order to turn one tree into another. #[derive(Debug, Clone, Copy)] -pub struct Change<'a, 'repo, 'other_repo> { +pub struct Change<'a, 'old, 'new> { /// The location of the file or directory described by `event`, if tracking was enabled. /// /// Otherwise this value is always an empty path. pub location: &'a BStr, /// The diff event itself to provide information about what would need to change. - pub event: change::Event<'repo, 'other_repo>, + pub event: change::Event<'old, 'new>, } /// pub mod change { + use crate::bstr::ByteSlice; use crate::Id; + use git_object::tree::EntryMode; /// An event emitted when finding differences between two trees. #[derive(Debug, Clone, Copy)] - pub enum Event<'repo, 'other_repo> { + pub enum Event<'old, 'new> { /// An entry was added, like the addition of a file or directory. Addition { /// The mode of the added entry. entry_mode: git_object::tree::EntryMode, /// The object id of the added entry. - id: Id<'other_repo>, + id: Id<'new>, }, /// An entry was deleted, like the deletion of a file or directory. Deletion { /// The mode of the deleted entry. entry_mode: git_object::tree::EntryMode, /// The object id of the deleted entry. - id: Id<'repo>, + id: Id<'old>, }, /// An entry was modified, e.g. changing the contents of a file adjusts its object id and turning /// a file into a symbolic link adjusts its mode. @@ -68,14 +70,52 @@ pub mod change { /// The mode of the entry before the modification. previous_entry_mode: git_object::tree::EntryMode, /// The object id of the entry before the modification. - previous_id: Id<'repo>, + previous_id: Id<'old>, /// The mode of the entry after the modification. entry_mode: git_object::tree::EntryMode, /// The object id after the modification. - id: Id<'other_repo>, + id: Id<'new>, }, } + + /// A platform to keep temporary information to perform line diffs. + pub struct DiffPlatform<'old, 'new> { + old: crate::Object<'old>, + new: crate::Object<'new>, + } + + impl<'old, 'new> Event<'old, 'new> { + /// Produce a platform for performing a line-diff, or `None` if this is not a [`Modification`][Event::Modification] + /// or one of the entries to compare is not a blob. + pub fn diff(&self) -> Option, crate::object::find::existing::Error>> { + match self { + Event::Modification { + previous_entry_mode: EntryMode::BlobExecutable | EntryMode::Blob, + previous_id, + entry_mode: EntryMode::BlobExecutable | EntryMode::Blob, + id, + } => match previous_id.object().and_then(|old| id.object().map(|new| (old, new))) { + Ok((old, new)) => Some(Ok(DiffPlatform { old, new })), + Err(err) => Some(Err(err)), + }, + _ => None, + } + } + } + + impl<'old, 'new> DiffPlatform<'old, 'new> { + /// Create a platform for performing various tasks to diff text. + /// + /// It can be used to traverse [all line changes](git_diff::lines::similar::TextDiff::iter_all_changes()) for example. + // TODO: How should this integrate with configurable algorithms? Maybe users should get it themselves and pass it here? + pub fn text<'bufs>( + &self, + algorithm: git_diff::lines::Algorithm, + ) -> git_diff::lines::similar::TextDiff<'_, '_, 'bufs, [u8]> { + git_diff::lines::with(self.old.data.as_bstr(), self.new.data.as_bstr(), algorithm) + } + } } /// Diffing @@ -126,12 +166,12 @@ impl<'a, 'repo> Platform<'a, 'repo> { } /// Add the item to compare to. -impl<'a, 'repo> Platform<'a, 'repo> { +impl<'a, 'old> Platform<'a, 'old> { /// Call `for_each` repeatedly with all changes that are needed to convert the source of the diff to the tree to `other`. - pub fn for_each_to_obtain_tree<'other_repo, E>( + pub fn for_each_to_obtain_tree<'new, E>( &mut self, - other: &Tree<'other_repo>, - for_each: impl FnMut(Change<'_, 'repo, 'other_repo>) -> Result, + other: &Tree<'new>, + for_each: impl FnMut(Change<'_, 'old, 'new>) -> Result, ) -> Result<(), Error> where E: std::error::Error + Sync + Send + 'static, @@ -159,9 +199,9 @@ impl<'a, 'repo> Platform<'a, 'repo> { } } -struct Delegate<'repo, 'other_repo, VisitFn, E> { - repo: &'repo Repository, - other_repo: &'other_repo Repository, +struct Delegate<'old, 'new, VisitFn, E> { + repo: &'old Repository, + other_repo: &'new Repository, tracking: Option, location: BString, path_deque: VecDeque, @@ -186,9 +226,9 @@ impl Delegate<'_, '_, A, B> { } } -impl<'repo, 'other_repo, VisitFn, E> git_diff::tree::Visit for Delegate<'repo, 'other_repo, VisitFn, E> +impl<'old, 'new, VisitFn, E> git_diff::tree::Visit for Delegate<'old, 'new, VisitFn, E> where - VisitFn: for<'delegate> FnMut(Change<'delegate, 'repo, 'other_repo>) -> Result, + VisitFn: for<'delegate> FnMut(Change<'delegate, 'old, 'new>) -> Result, E: std::error::Error + Sync + Send + 'static, { fn pop_front_tracked_path_and_set_current(&mut self) { diff --git a/git-repository/tests/object/tree.rs b/git-repository/tests/object/tree.rs index 094753ed032..f621460707a 100644 --- a/git-repository/tests/object/tree.rs +++ b/git-repository/tests/object/tree.rs @@ -25,13 +25,24 @@ mod diff { assert_eq!(entry_mode, EntryMode::Blob); assert_eq!(previous_id.object().unwrap().data.as_bstr(), "a\n"); assert_eq!(id.object().unwrap().data.as_bstr(), "a\na1\n"); - Ok(Default::default()) } Event::Deletion { .. } | Event::Addition { .. } => unreachable!("only modification is expected"), - } + }; + + let count = change + .event + .diff() + .expect("changed file") + .expect("objects available") + .text(git::diff::lines::Algorithm::Myers) + .iter_all_changes() + .count(); + assert_eq!(count, 2); + Ok(Default::default()) }) .unwrap(); } + #[test] fn changes_against_tree_with_filename_tracking() { let repo = named_repo("make_diff_repo.sh").unwrap(); From 6b2af578fbff43999266c2af324070b013d1699f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 19 Sep 2022 22:50:40 +0800 Subject: [PATCH 19/48] feat: Make it possible to access the current commits buffer during commit ancestor iteration. (#470) This is useful to avoid additional lookups of the same object for reading additional data from it. Currently one needs an object cache to avoid duplciate object extraction work, with such a cache being slower than accessing the same buffer again. --- git-traverse/src/commit.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/git-traverse/src/commit.rs b/git-traverse/src/commit.rs index 560c3fd2009..796595937aa 100644 --- a/git-traverse/src/commit.rs +++ b/git-traverse/src/commit.rs @@ -46,6 +46,7 @@ impl Default for Sorting { /// pub mod ancestors { + use std::borrow::Borrow; use std::{borrow::BorrowMut, collections::VecDeque, iter::FromIterator}; use git_hash::{oid, ObjectId}; @@ -85,6 +86,7 @@ pub mod ancestors { } } + /// Builder impl Ancestors { /// Change our commit parent handling mode to the given one. pub fn parents(mut self, mode: Parents) -> Self { @@ -93,6 +95,7 @@ pub mod ancestors { } } + /// Builder impl Ancestors where Find: for<'a> FnMut(&oid, &'a mut Vec) -> Result, E>, @@ -119,6 +122,7 @@ pub mod ancestors { } } + /// Initialization impl Ancestors bool, StateMut> where Find: for<'a> FnMut(&oid, &'a mut Vec) -> Result, E>, @@ -140,6 +144,7 @@ pub mod ancestors { } } + /// Initialization impl Ancestors where Find: for<'a> FnMut(&oid, &'a mut Vec) -> Result, E>, @@ -186,6 +191,16 @@ pub mod ancestors { } } } + /// Access + impl Ancestors + where + StateMut: Borrow, + { + /// Return an iterator for accessing more of the current commits data. + pub fn commit_iter(&self) -> CommitRefIter<'_> { + CommitRefIter::from_bytes(&self.state.borrow().buf) + } + } impl Iterator for Ancestors where @@ -208,6 +223,7 @@ pub mod ancestors { } } + /// Utilities impl Ancestors where Find: for<'a> FnMut(&oid, &'a mut Vec) -> Result, E>, @@ -272,6 +288,7 @@ pub mod ancestors { } } + /// Utilities impl Ancestors where Find: for<'a> FnMut(&oid, &'a mut Vec) -> Result, E>, From 1027be960852618915014f9ba6e6632bd4999b0e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 19 Sep 2022 22:53:25 +0800 Subject: [PATCH 20/48] feat: `interrupt::Iter` now allows accessing the inner iterator without consumption. (#470) This is useful if these provide additional out-of-band information. --- git-repository/src/interrupt.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/git-repository/src/interrupt.rs b/git-repository/src/interrupt.rs index 39fd259f7fa..070a95ea714 100644 --- a/git-repository/src/interrupt.rs +++ b/git-repository/src/interrupt.rs @@ -82,6 +82,11 @@ where pub fn into_inner(self) -> I { self.inner.inner } + + /// Return the inner iterator as reference + pub fn inner(&self) -> &I { + &self.inner.inner + } } impl Iterator for Iter From 28c4cae70aab2bd5b479961fcc6ee91ff80f651b Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 19 Sep 2022 22:55:00 +0800 Subject: [PATCH 21/48] feat: `ein tool hours --stat` to collect additional statistics per author. (#470) Note that these are expensive and unconditionally use threads to speed up these computations. --- gitoxide-core/src/hours.rs | 136 ++++++++++++++++++------------------- src/porcelain/main.rs | 2 + src/porcelain/options.rs | 3 + 3 files changed, 73 insertions(+), 68 deletions(-) diff --git a/gitoxide-core/src/hours.rs b/gitoxide-core/src/hours.rs index cdc33f2ba7f..35035ffbfd7 100644 --- a/gitoxide-core/src/hours.rs +++ b/gitoxide-core/src/hours.rs @@ -18,6 +18,8 @@ pub struct Context { pub ignore_bots: bool, /// Show personally identifiable information before the summary. Includes names and email addresses. pub show_pii: bool, + /// Collect additional information like tree changes and changed lines. + pub stats: bool, /// Omit unifying identities by name and email which can lead to the same author appear multiple times /// due to using different names or email addresses. pub omit_unify_identities: bool, @@ -38,6 +40,7 @@ pub fn estimate( Context { show_pii, ignore_bots, + stats: _, omit_unify_identities, mut out, }: Context, @@ -52,80 +55,77 @@ where let (all_commits, is_shallow) = { let mut progress = progress.add_child("Traverse commit graph"); - let string_heap = &mut string_heap; - std::thread::scope( - move |scope| -> anyhow::Result<(Vec>, bool)> { - let start = Instant::now(); - progress.init(None, progress::count("commits")); - let (tx, rx) = std::sync::mpsc::channel::>(); - let mailmap = repo.open_mailmap(); + std::thread::scope(|scope| -> anyhow::Result<(Vec>, bool)> { + let start = Instant::now(); + progress.init(None, progress::count("commits")); + let (tx, rx) = std::sync::mpsc::channel::>(); + let mailmap = repo.open_mailmap(); - let handle = scope.spawn(move || -> anyhow::Result>> { - let mut out = Vec::new(); - for commit_data in rx { - if let Some(author) = objs::CommitRefIter::from_bytes(&commit_data) - .author() - .map(|author| mailmap.resolve_cow(author.trim())) - .ok() - { - let mut string_ref = |s: &[u8]| -> &'static BStr { - match string_heap.get(s) { - Some(n) => n.as_bstr(), - None => { - let sv: Vec = s.to_owned().into(); - string_heap.insert(Box::leak(sv.into_boxed_slice())); - (*string_heap.get(s).expect("present")).as_ref() - } + let handle = scope.spawn(move || -> anyhow::Result>> { + let mut out = Vec::new(); + for commit_data in rx { + if let Some(author) = objs::CommitRefIter::from_bytes(&commit_data) + .author() + .map(|author| mailmap.resolve_cow(author.trim())) + .ok() + { + let mut string_ref = |s: &[u8]| -> &'static BStr { + match string_heap.get(s) { + Some(n) => n.as_bstr(), + None => { + let sv: Vec = s.to_owned().into(); + string_heap.insert(Box::leak(sv.into_boxed_slice())); + (*string_heap.get(s).expect("present")).as_ref() } - }; - let name = string_ref(author.name.as_ref()); - let email = string_ref(&author.email.as_ref()); + } + }; + let name = string_ref(author.name.as_ref()); + let email = string_ref(&author.email.as_ref()); - out.push(actor::SignatureRef { - name, - email, - time: author.time, - }); - } + out.push(actor::SignatureRef { + name, + email, + time: author.time, + }); } - out.shrink_to_fit(); - out.sort_by(|a, b| { - a.email.cmp(&b.email).then( - a.time - .seconds_since_unix_epoch - .cmp(&b.time.seconds_since_unix_epoch) - .reverse(), - ) - }); - Ok(out) + } + out.shrink_to_fit(); + out.sort_by(|a, b| { + a.email.cmp(&b.email).then( + a.time + .seconds_since_unix_epoch + .cmp(&b.time.seconds_since_unix_epoch) + .reverse(), + ) }); + Ok(out) + }); - let commit_iter = interrupt::Iter::new( - commit_id.ancestors(|oid, buf| { - progress.inc(); - repo.objects.find(oid, buf).map(|o| { - tx.send(o.data.to_owned()).ok(); - objs::CommitRefIter::from_bytes(o.data) - }) - }), - || anyhow!("Cancelled by user"), - ); - let mut is_shallow = false; - for c in commit_iter { - match c? { - Ok(c) => c, - Err(git::traverse::commit::ancestors::Error::FindExisting { .. }) => { - is_shallow = true; - break; - } - Err(err) => return Err(err.into()), - }; - } - drop(tx); - progress.show_throughput(start); - Ok((handle.join().expect("no panic")?, is_shallow)) - }, - )? + let commit_iter = interrupt::Iter::new( + commit_id.ancestors(|oid, buf| { + progress.inc(); + repo.objects.find(oid, buf).map(|o| { + tx.send(o.data.to_owned()).ok(); + objs::CommitRefIter::from_bytes(o.data) + }) + }), + || anyhow!("Cancelled by user"), + ); + let mut is_shallow = false; + for c in commit_iter { + match c? { + Ok(c) => c, + Err(git::traverse::commit::ancestors::Error::FindExisting { .. }) => { + is_shallow = true; + break; + } + Err(err) => return Err(err.into()), + }; + } + drop(tx); + progress.show_throughput(start); + Ok((handle.join().expect("no panic")?, is_shallow)) + })? }; if all_commits.is_empty() { diff --git a/src/porcelain/main.rs b/src/porcelain/main.rs index e0656617e30..ef1e2d575e5 100644 --- a/src/porcelain/main.rs +++ b/src/porcelain/main.rs @@ -40,6 +40,7 @@ pub fn main() -> Result<()> { working_dir, rev_spec, no_bots, + stats, show_pii, omit_unify_identities, }) => { @@ -58,6 +59,7 @@ pub fn main() -> Result<()> { hours::Context { show_pii, ignore_bots: no_bots, + stats, omit_unify_identities, out, }, diff --git a/src/porcelain/options.rs b/src/porcelain/options.rs index 5ee5774321f..454d6eed4f1 100644 --- a/src/porcelain/options.rs +++ b/src/porcelain/options.rs @@ -101,6 +101,9 @@ pub struct EstimateHours { /// Ignore github bots which match the `[bot]` search string. #[clap(short = 'b', long)] pub no_bots: bool, + /// Collect additional information like tree changes and changed lines. + #[clap(short = 's', long)] + pub stats: bool, /// Show personally identifiable information before the summary. Includes names and email addresses. #[clap(short = 'p', long)] pub show_pii: bool, From 5878ad17bc0c0d9d99b36f3ff9416cf9a47a4086 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 07:03:49 +0800 Subject: [PATCH 22/48] a little more complexity for diff tests (#470) --- .../tests/fixtures/generated-archives/make_diff_repo.tar.xz | 4 ++-- git-repository/tests/fixtures/make_diff_repo.sh | 3 ++- git-repository/tests/object/tree.rs | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/git-repository/tests/fixtures/generated-archives/make_diff_repo.tar.xz b/git-repository/tests/fixtures/generated-archives/make_diff_repo.tar.xz index 9f9fe388495..4a7f55cb2e0 100644 --- a/git-repository/tests/fixtures/generated-archives/make_diff_repo.tar.xz +++ b/git-repository/tests/fixtures/generated-archives/make_diff_repo.tar.xz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2123aa9e3fd85166d35c02c59ce8f0e8aaa3631c3dd4e43468f98d26cddf049c -size 11128 +oid sha256:a057f8e02b4fa2743d74c84a68ace9ce2c3a051527afa8d9a5cb671b48e36868 +size 11272 diff --git a/git-repository/tests/fixtures/make_diff_repo.sh b/git-repository/tests/fixtures/make_diff_repo.sh index 431c02138a1..4b0a0fb7e49 100644 --- a/git-repository/tests/fixtures/make_diff_repo.sh +++ b/git-repository/tests/fixtures/make_diff_repo.sh @@ -5,13 +5,14 @@ git init -q git checkout -b main mkdir dir -touch a b dir/c +touch a b dir/c d git add . git commit -q -m c1 echo a >> a echo b >> b echo dir/c >> dir/c +echo d >> d git commit -q -am c2 echo a1 >> a diff --git a/git-repository/tests/object/tree.rs b/git-repository/tests/object/tree.rs index f621460707a..e360d98ac60 100644 --- a/git-repository/tests/object/tree.rs +++ b/git-repository/tests/object/tree.rs @@ -52,7 +52,7 @@ mod diff { ); let to = tree_named(&repo, ":/c1"); - let mut expected = vec!["a", "b", "c"]; + let mut expected = vec!["a", "b", "c", "d"]; from.changes() .track_filename() .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { @@ -62,7 +62,7 @@ mod diff { .unwrap(); assert_eq!(expected, Vec::<&str>::new(), "all paths should have been seen"); - let mut expected = vec!["a", "b", "dir/c"]; + let mut expected = vec!["a", "b", "dir/c", "d"]; from.changes() .track_path() .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { From 523418f69030faa0add6472b14333e9aafc69f56 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 08:02:51 +0800 Subject: [PATCH 23/48] feat: add support for `wasi` (#470) This allows path conversions there to be just as efficient as on unix. This was adopted from [a PR in the hexlix-editor](https://github.com/helix-editor/helix/pull/3890/files#diff-504515b66023120e75a921cd56a932aed76c0ff62593fbb69d92e0ef65089501R47). --- git-path/src/convert.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/git-path/src/convert.rs b/git-path/src/convert.rs index 68b9bf219a5..2ddbcbbd607 100644 --- a/git-path/src/convert.rs +++ b/git-path/src/convert.rs @@ -41,6 +41,11 @@ pub fn try_into_bstr<'a>(path: impl Into>) -> Result use std::os::unix::ffi::OsStringExt; path.into_os_string().into_vec().into() }; + #[cfg(target_os = "wasi")] + let p: BString = { + use std::os::wasi::ffi::OsStringExt; + path.into_os_string().into_vec().into() + }; #[cfg(not(unix))] let p: BString = path.into_os_string().into_string().map_err(|_| Utf8Error)?.into(); p @@ -51,6 +56,11 @@ pub fn try_into_bstr<'a>(path: impl Into>) -> Result use std::os::unix::ffi::OsStrExt; path.as_os_str().as_bytes().into() }; + #[cfg(target_os = "wasi")] + let p: &BStr = { + use std::os::wasi::ffi::OsStrExt; + path.as_os_str().as_bytes().into() + }; #[cfg(not(unix))] let p: &BStr = path.to_str().ok_or(Utf8Error)?.as_bytes().into(); p @@ -75,6 +85,11 @@ pub fn try_from_byte_slice(input: &[u8]) -> Result<&Path, Utf8Error> { use std::os::unix::ffi::OsStrExt; OsStr::from_bytes(input).as_ref() }; + #[cfg(target_os = "wasi")] + let p = { + use std::os::wasi::ffi::OsStrExt; + OsStr::from_bytes(input).as_ref() + }; #[cfg(not(unix))] let p = Path::new(std::str::from_utf8(input).map_err(|_| Utf8Error)?); Ok(p) @@ -102,6 +117,11 @@ pub fn try_from_bstring(input: impl Into) -> Result use std::os::unix::ffi::OsStringExt; std::ffi::OsString::from_vec(input.into()).into() }; + #[cfg(target_os = "wasi")] + let p = { + use std::os::wasi::ffi::OsStringExt; + std::ffi::OsString::from_vec(input.into()).into() + }; #[cfg(not(unix))] let p = { use bstr::ByteVec; From 15a18e47f4a767a2cc31a30296844c207c7a8732 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 08:04:53 +0800 Subject: [PATCH 24/48] simplify looking up entries by path (#470) --- git-repository/src/object/tree/mod.rs | 18 ++++++++++++++++++ .../revision/spec/parse/delegate/navigate.rs | 18 +++++++----------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/git-repository/src/object/tree/mod.rs b/git-repository/src/object/tree/mod.rs index d8a2bad728e..cb4a6f9ecb8 100644 --- a/git-repository/src/object/tree/mod.rs +++ b/git-repository/src/object/tree/mod.rs @@ -60,6 +60,24 @@ impl<'repo> Tree<'repo> { } Ok(None) } + + /// Like [`lookup_path()`][Self::lookup_path()], but takes a `Path` directly via `relative_path`, a path relative to this tree. + /// + /// # Note + /// + /// If any path component contains illformed UTF-8 and thus can't be converted to bytes on platforms which can't do so natively, + /// the returned component will be empty which makes the lookup fail. + pub fn lookup_path_by_path( + self, + relative_path: impl AsRef, + ) -> Result, find::existing::Error> { + self.lookup_path( + relative_path + .as_ref() + .components() + .map(|c| git_path::os_str_into_bstr(c.as_os_str()).unwrap_or("".into()).as_ref()), + ) + } } /// diff --git a/git-repository/src/revision/spec/parse/delegate/navigate.rs b/git-repository/src/revision/spec/parse/delegate/navigate.rs index 3bcca5ebe50..35433a6e739 100644 --- a/git-repository/src/revision/spec/parse/delegate/navigate.rs +++ b/git-repository/src/revision/spec/parse/delegate/navigate.rs @@ -124,17 +124,13 @@ impl<'repo> delegate::Navigate for Delegate<'repo> { return Ok(tree_id); } let tree = repo.find_object(tree_id)?.into_tree(); - let entry = tree - .lookup_path(git_path::from_bstr(path).components().map(|c| { - git_path::os_str_into_bstr(c.as_os_str()) - .expect("no illformed UTF-8") - .as_ref() - }))? - .ok_or_else(|| Error::PathNotFound { - path: path.into(), - object: obj.attach(repo).shorten_or_id(), - tree: tree_id.attach(repo).shorten_or_id(), - })?; + let entry = + tree.lookup_path_by_path(git_path::from_bstr(path))? + .ok_or_else(|| Error::PathNotFound { + path: path.into(), + object: obj.attach(repo).shorten_or_id(), + tree: tree_id.attach(repo).shorten_or_id(), + })?; Ok(entry.oid) }; for obj in objs.iter() { From 79c22557ce0aea1ee8f3a58192c2c76087ccd3d8 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 08:05:38 +0800 Subject: [PATCH 25/48] rename!: `Tree::lookup_path()` -> `Tree::lookup_entry()`. (#470) --- git-repository/src/object/tree/mod.rs | 6 +++--- git-repository/src/repository/snapshots.rs | 2 +- git-repository/src/revision/spec/parse/delegate/navigate.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/git-repository/src/object/tree/mod.rs b/git-repository/src/object/tree/mod.rs index cb4a6f9ecb8..7418dcba971 100644 --- a/git-repository/src/object/tree/mod.rs +++ b/git-repository/src/object/tree/mod.rs @@ -31,7 +31,7 @@ impl<'repo> Tree<'repo> { /// Searching tree entries is currently done in sequence, which allows to the search to be allocation free. It would be possible /// to re-use a vector and use a binary search instead, which might be able to improve performance over all. /// However, a benchmark should be created first to have some data and see which trade-off to choose here. - pub fn lookup_path(mut self, path: I) -> Result, find::existing::Error> + pub fn lookup_entry(mut self, path: I) -> Result, find::existing::Error> where I: IntoIterator, P: PartialEq, @@ -67,11 +67,11 @@ impl<'repo> Tree<'repo> { /// /// If any path component contains illformed UTF-8 and thus can't be converted to bytes on platforms which can't do so natively, /// the returned component will be empty which makes the lookup fail. - pub fn lookup_path_by_path( + pub fn lookup_entry_by_path( self, relative_path: impl AsRef, ) -> Result, find::existing::Error> { - self.lookup_path( + self.lookup_entry( relative_path .as_ref() .components() diff --git a/git-repository/src/repository/snapshots.rs b/git-repository/src/repository/snapshots.rs index 527228828a9..6795a9a9425 100644 --- a/git-repository/src/repository/snapshots.rs +++ b/git-repository/src/repository/snapshots.rs @@ -42,7 +42,7 @@ impl crate::Repository { self.head().ok().and_then(|mut head| { let commit = head.peel_to_commit_in_place().ok()?; let tree = commit.tree().ok()?; - tree.lookup_path(Some(".mailmap")).ok()?.map(|e| e.oid) + tree.lookup_entry(Some(".mailmap")).ok()?.map(|e| e.oid) }) }); } diff --git a/git-repository/src/revision/spec/parse/delegate/navigate.rs b/git-repository/src/revision/spec/parse/delegate/navigate.rs index 35433a6e739..2605a40dd58 100644 --- a/git-repository/src/revision/spec/parse/delegate/navigate.rs +++ b/git-repository/src/revision/spec/parse/delegate/navigate.rs @@ -125,7 +125,7 @@ impl<'repo> delegate::Navigate for Delegate<'repo> { } let tree = repo.find_object(tree_id)?.into_tree(); let entry = - tree.lookup_path_by_path(git_path::from_bstr(path))? + tree.lookup_entry_by_path(git_path::from_bstr(path))? .ok_or_else(|| Error::PathNotFound { path: path.into(), object: obj.attach(repo).shorten_or_id(), From 5ec714fa687100d77b061516194bc9b96a03c9c0 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 08:06:29 +0800 Subject: [PATCH 26/48] adapt to changes in `git-repository` (#470) --- cargo-smart-release/src/git/history.rs | 4 ++-- cargo-smart-release/src/git/mod.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cargo-smart-release/src/git/history.rs b/cargo-smart-release/src/git/history.rs index aa4a6577278..658103a1945 100644 --- a/cargo-smart-release/src/git/history.rs +++ b/cargo-smart-release/src/git/history.rs @@ -237,10 +237,10 @@ fn add_item_if_package_changed<'a>( let mut repo = ctx.repo.clone(); repo.object_cache_size(1024 * 1024); let current = git::Tree::from_data(item.id, data_by_tree_id[&item.tree_id].to_owned(), &ctx.repo) - .lookup_path(components.iter().copied())?; + .lookup_entry(components.iter().copied())?; let parent = match item.parent_tree_id { Some(tree_id) => git::Tree::from_data(tree_id, data_by_tree_id[&tree_id].to_owned(), &ctx.repo) - .lookup_path(components.iter().copied())?, + .lookup_entry(components.iter().copied())?, None => None, }; match (current, parent) { diff --git a/cargo-smart-release/src/git/mod.rs b/cargo-smart-release/src/git/mod.rs index 0f35572d888..96c3b433926 100644 --- a/cargo-smart-release/src/git/mod.rs +++ b/cargo-smart-release/src/git/mod.rs @@ -47,14 +47,14 @@ pub fn change_since_last_release(package: &Package, ctx: &crate::Context) -> any .object()? .peel_to_kind(object::Kind::Tree)? .into_tree() - .lookup_path(components.clone())? + .lookup_entry(components.clone())? .expect("path must exist in current commit") .oid; let released_dir_id = released_target .object()? .peel_to_kind(object::Kind::Tree)? .into_tree() - .lookup_path(components)? + .lookup_entry(components)? .expect("path must exist as it was supposedly released there") .oid; From b28509724c8877d6488bf5dc72308e8b18928124 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 09:03:41 +0800 Subject: [PATCH 27/48] Add note on why we consume the tree for looking up an entry. (#470) The disadvantage of doing that is that we copy the name, which allocates, instead of just returning it by reference. --- git-repository/src/object/tree/iter.rs | 7 ++++++- git-repository/src/object/tree/mod.rs | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/git-repository/src/object/tree/iter.rs b/git-repository/src/object/tree/iter.rs index 495e1912a6d..a7acc10a467 100644 --- a/git-repository/src/object/tree/iter.rs +++ b/git-repository/src/object/tree/iter.rs @@ -6,7 +6,7 @@ pub struct EntryRef<'repo, 'a> { /// The actual entry ref we are wrapping. pub inner: git_object::tree::EntryRef<'a>, - repo: &'repo Repository, + pub(crate) repo: &'repo Repository, } impl<'repo, 'a> EntryRef<'repo, 'a> { @@ -24,6 +24,11 @@ impl<'repo, 'a> EntryRef<'repo, 'a> { pub fn id(&self) -> crate::Id<'repo> { crate::Id::from_id(self.inner.oid, self.repo) } + + /// Return the entries id, without repository connection. + pub fn oid(&self) -> git_hash::ObjectId { + self.inner.oid.to_owned() + } } impl<'repo, 'a> std::fmt::Display for EntryRef<'repo, 'a> { diff --git a/git-repository/src/object/tree/mod.rs b/git-repository/src/object/tree/mod.rs index 7418dcba971..35eb8df2e61 100644 --- a/git-repository/src/object/tree/mod.rs +++ b/git-repository/src/object/tree/mod.rs @@ -31,6 +31,12 @@ impl<'repo> Tree<'repo> { /// Searching tree entries is currently done in sequence, which allows to the search to be allocation free. It would be possible /// to re-use a vector and use a binary search instead, which might be able to improve performance over all. /// However, a benchmark should be created first to have some data and see which trade-off to choose here. + /// + /// # Why is this consunming? + /// + /// The borrow checker shows pathological behaviour in loops that mutate a buffer, but also want to return from it. + /// Workarounds include keeping an index and doing a separate access to the memory, which seems hard to do here without + /// re-parsing the entries. pub fn lookup_entry(mut self, path: I) -> Result, find::existing::Error> where I: IntoIterator, From 77ff8ae5fa9bbdb7c5e1c577845334f966294426 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 09:05:26 +0800 Subject: [PATCH 28/48] thanks clippy --- git-repository/src/object/tree/mod.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/git-repository/src/object/tree/mod.rs b/git-repository/src/object/tree/mod.rs index 35eb8df2e61..c048a692e1e 100644 --- a/git-repository/src/object/tree/mod.rs +++ b/git-repository/src/object/tree/mod.rs @@ -77,12 +77,11 @@ impl<'repo> Tree<'repo> { self, relative_path: impl AsRef, ) -> Result, find::existing::Error> { - self.lookup_entry( - relative_path + self.lookup_entry(relative_path.as_ref().components().map(|c| { + git_path::os_str_into_bstr(c.as_os_str()) + .unwrap_or_else(|_| "".into()) .as_ref() - .components() - .map(|c| git_path::os_str_into_bstr(c.as_os_str()).unwrap_or("".into()).as_ref()), - ) + })) } } From 593f57b486d03b4d689cda6c0800e9f349cc4ad5 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 09:07:20 +0800 Subject: [PATCH 29/48] fix docs (#470) --- git-repository/src/object/tree/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-repository/src/object/tree/mod.rs b/git-repository/src/object/tree/mod.rs index c048a692e1e..75b4ea4c6d4 100644 --- a/git-repository/src/object/tree/mod.rs +++ b/git-repository/src/object/tree/mod.rs @@ -67,7 +67,7 @@ impl<'repo> Tree<'repo> { Ok(None) } - /// Like [`lookup_path()`][Self::lookup_path()], but takes a `Path` directly via `relative_path`, a path relative to this tree. + /// Like [`lookup_entry()`][Self::lookup_entry()], but takes a `Path` directly via `relative_path`, a path relative to this tree. /// /// # Note /// From 0ac4a2c514aeb94d8e90ce28ae7a0e0350c21ab2 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 09:45:05 +0800 Subject: [PATCH 30/48] upgrade to prodash 20.1 for `Progress::counter()` feature (#470) --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- git-features/Cargo.toml | 2 +- src/porcelain/options.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 51e4d975db7..9fb5ea40687 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2534,9 +2534,9 @@ dependencies = [ [[package]] name = "prodash" -version = "20.0.0" +version = "20.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65ae8c4c227abf4616b5819a60cf72b34c5c2541581f916c174ec2b2c1566da0" +checksum = "477ce81c3e71b6005714157c54797ff4d84b5aa21d21e160fb9f1eeed936c931" dependencies = [ "async-io", "atty", diff --git a/Cargo.toml b/Cargo.toml index fc82ce0ad02..df224c94507 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,7 +86,7 @@ git-repository = { version = "^0.24.0", path = "git-repository", default-feature git-transport-for-configuration-only = { package = "git-transport", optional = true, version = "^0.20.0", path = "git-transport" } clap = { version = "3.2.5", features = ["derive", "cargo"] } -prodash = { version = "20.0.0", optional = true, default-features = false } +prodash = { version = "20.1.0", optional = true, default-features = false } atty = { version = "0.2.14", optional = true, default-features = false } env_logger = { version = "0.9.0", default-features = false } crosstermion = { version = "0.10.1", optional = true, default-features = false } diff --git a/git-features/Cargo.toml b/git-features/Cargo.toml index b91ea4d538c..d221cd3d815 100644 --- a/git-features/Cargo.toml +++ b/git-features/Cargo.toml @@ -115,7 +115,7 @@ crc32fast = { version = "1.2.1", optional = true } sha1 = { version = "0.10.0", optional = true } # progress -prodash = { version = "20.0.0", optional = true, default-features = false, features = ["unit-bytes", "unit-human"] } +prodash = { version = "20.1.0", optional = true, default-features = false, features = ["unit-bytes", "unit-human"] } # pipe bytes = { version = "1.0.0", optional = true } diff --git a/src/porcelain/options.rs b/src/porcelain/options.rs index 454d6eed4f1..8d7e42012b0 100644 --- a/src/porcelain/options.rs +++ b/src/porcelain/options.rs @@ -101,7 +101,7 @@ pub struct EstimateHours { /// Ignore github bots which match the `[bot]` search string. #[clap(short = 'b', long)] pub no_bots: bool, - /// Collect additional information like tree changes and changed lines. + /// Collect additional information about file modifications, additions and deletions. #[clap(short = 's', long)] pub stats: bool, /// Show personally identifiable information before the summary. Includes names and email addresses. From 0871a96b9cc84d7a496d39393e081999c0a3fe17 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 11:05:24 +0800 Subject: [PATCH 31/48] feat: `Object::peel_to_tree()` as convenience method. (#470) It's very common to try to work with trees, so let's make that easier. --- git-repository/src/object/peel.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/git-repository/src/object/peel.rs b/git-repository/src/object/peel.rs index c09ecbf668d..0654049e658 100644 --- a/git-repository/src/object/peel.rs +++ b/git-repository/src/object/peel.rs @@ -2,7 +2,7 @@ use crate::{ object, object::{peel, Kind}, - Object, + Object, Tree, }; /// @@ -67,6 +67,11 @@ impl<'repo> Object<'repo> { } } + /// Peel this object into a tree and return it, if this is possible. + pub fn peel_to_tree(self) -> Result, peel::to_kind::Error> { + Ok(self.peel_to_kind(git_object::Kind::Tree)?.into_tree()) + } + // TODO: tests /// Follow all tag object targets until a commit, tree or blob is reached. /// From 0947c703f9cecc31ceba101565e6ecafb00adb08 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 11:40:43 +0800 Subject: [PATCH 32/48] First attempt to get progress information from stat worker. (#470) But for some reason, the counter stays at 0 despite sharing the counter stat. --- Cargo.lock | 59 ++++++++++++++- Cargo.toml | 2 +- git-features/Cargo.toml | 2 +- gitoxide-core/Cargo.toml | 5 +- gitoxide-core/src/hours.rs | 146 ++++++++++++++++++++++++++++++++----- 5 files changed, 192 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9fb5ea40687..dbc3d933482 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -957,6 +957,19 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "pin-project", + "spin", +] + [[package]] name = "form_urlencoded" version = "1.1.0" @@ -1096,8 +1109,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1849,6 +1864,7 @@ dependencies = [ "blocking", "bytesize", "document-features", + "flume", "fs-err", "futures-io", "futures-lite", @@ -1859,6 +1875,7 @@ dependencies = [ "git-url", "itertools", "jwalk", + "num_cpus", "serde", "serde_json", "tempfile", @@ -2224,6 +2241,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom", +] + [[package]] name = "nix" version = "0.21.0" @@ -2427,6 +2453,26 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -2534,9 +2580,9 @@ dependencies = [ [[package]] name = "prodash" -version = "20.1.0" +version = "20.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "477ce81c3e71b6005714157c54797ff4d84b5aa21d21e160fb9f1eeed936c931" +checksum = "762467059887e40727b7cea07161956c9bd0fb6df0ca1225538effdb9f77c80a" dependencies = [ "async-io", "atty", @@ -2944,6 +2990,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" +dependencies = [ + "lock_api", +] + [[package]] name = "static_assertions" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index df224c94507..24d70220467 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,7 +86,7 @@ git-repository = { version = "^0.24.0", path = "git-repository", default-feature git-transport-for-configuration-only = { package = "git-transport", optional = true, version = "^0.20.0", path = "git-transport" } clap = { version = "3.2.5", features = ["derive", "cargo"] } -prodash = { version = "20.1.0", optional = true, default-features = false } +prodash = { version = "20.1.1", optional = true, default-features = false } atty = { version = "0.2.14", optional = true, default-features = false } env_logger = { version = "0.9.0", default-features = false } crosstermion = { version = "0.10.1", optional = true, default-features = false } diff --git a/git-features/Cargo.toml b/git-features/Cargo.toml index d221cd3d815..f0374f453b1 100644 --- a/git-features/Cargo.toml +++ b/git-features/Cargo.toml @@ -115,7 +115,7 @@ crc32fast = { version = "1.2.1", optional = true } sha1 = { version = "0.10.0", optional = true } # progress -prodash = { version = "20.1.0", optional = true, default-features = false, features = ["unit-bytes", "unit-human"] } +prodash = { version = "20.1.1", optional = true, default-features = false, features = ["unit-bytes", "unit-human"] } # pipe bytes = { version = "1.0.0", optional = true } diff --git a/gitoxide-core/Cargo.toml b/gitoxide-core/Cargo.toml index 61819fc8aef..60dc7cb8c01 100644 --- a/gitoxide-core/Cargo.toml +++ b/gitoxide-core/Cargo.toml @@ -18,7 +18,7 @@ default = [] ## Discover all git repositories within a directory. Particularly useful with [skim](https://github.com/lotabout/skim). organize = ["git-url", "jwalk"] ## Derive the amount of time invested into a git repository akin to [git-hours](https://github.com/kimmobrunfeldt/git-hours). -estimate-hours = ["itertools", "fs-err"] +estimate-hours = ["itertools", "fs-err", "num_cpus", "flume"] #! ### Mutually Exclusive Networking #! If both are set, _blocking-client_ will take precedence, allowing `--all-features` to be used. @@ -59,8 +59,11 @@ blocking = { version = "1.0.2", optional = true } git-url = { version = "^0.8.0", path = "../git-url", optional = true } jwalk = { version = "0.6.0", optional = true } +# for 'hours' itertools = { version = "0.10.1", optional = true } fs-err = { version = "2.6.0", optional = true } +num_cpus = { version = "1.13.1", optional = true } +flume = { version = "0.10.14", optional = true } document-features = { version = "0.2.0", optional = true } diff --git a/gitoxide-core/src/hours.rs b/gitoxide-core/src/hours.rs index 35035ffbfd7..d466f7f97bb 100644 --- a/gitoxide-core/src/hours.rs +++ b/gitoxide-core/src/hours.rs @@ -1,4 +1,6 @@ use std::collections::BTreeSet; +use std::convert::Infallible; +use std::sync::atomic::Ordering; use std::{ collections::{hash_map::Entry, HashMap}, io, @@ -9,7 +11,7 @@ use std::{ use anyhow::{anyhow, bail}; use git_repository as git; use git_repository::bstr::BStr; -use git_repository::{actor, bstr::ByteSlice, interrupt, objs, prelude::*, progress, Progress}; +use git_repository::{actor, bstr::ByteSlice, interrupt, prelude::*, progress, Progress}; use itertools::Itertools; /// Additional configuration for the hours estimation functionality. @@ -40,7 +42,7 @@ pub fn estimate( Context { show_pii, ignore_bots, - stats: _, + stats, omit_unify_identities, mut out, }: Context, @@ -53,18 +55,25 @@ where let commit_id = repo.rev_parse_single(rev_spec)?.detach(); let mut string_heap = BTreeSet::<&'static [u8]>::new(); - let (all_commits, is_shallow) = { - let mut progress = progress.add_child("Traverse commit graph"); + let (commit_authors, is_shallow) = { + let stat_progress = stats.then(|| progress.add_child("extract stats")).map(|mut p| { + p.init(None, progress::count("commits")); + p + }); + let stat_counter = stat_progress.as_ref().and_then(|p| p.counter()); + + let mut progress = progress.add_child("traverse commit graph"); + progress.init(None, progress::count("commits")); + std::thread::scope(|scope| -> anyhow::Result<(Vec>, bool)> { let start = Instant::now(); - progress.init(None, progress::count("commits")); let (tx, rx) = std::sync::mpsc::channel::>(); let mailmap = repo.open_mailmap(); - let handle = scope.spawn(move || -> anyhow::Result>> { + let commit_thread = scope.spawn(move || -> anyhow::Result>> { let mut out = Vec::new(); for commit_data in rx { - if let Some(author) = objs::CommitRefIter::from_bytes(&commit_data) + if let Some(author) = git::objs::CommitRefIter::from_bytes(&commit_data) .author() .map(|author| mailmap.resolve_cow(author.trim())) .ok() @@ -101,12 +110,89 @@ where Ok(out) }); + let (tx_tree_id, stat_threads) = stats + .then(|| { + let num_threads = num_cpus::get().saturating_sub(1 /*main thread*/).max(1); + let (tx, rx) = flume::unbounded::<(u32, Option, git::hash::ObjectId)>(); + let stat_workers = (0..num_threads) + .map(|_| { + scope.spawn({ + let counter = stat_counter.clone(); + let mut repo = repo.clone(); + repo.object_cache_size_if_unset(4 * 1024 * 1024); + let rx = rx.clone(); + move || -> Result<_, git::object::tree::diff::Error> { + let mut out = Vec::new(); + for (commit_idx, parent_commit, commit) in rx { + if let Some(c) = counter.as_ref() { + c.fetch_add(1, Ordering::SeqCst); + } + let mut stat = Stats::default(); + let from = match parent_commit { + Some(id) => { + match repo.find_object(id).ok().and_then(|c| c.peel_to_tree().ok()) { + Some(tree) => tree, + None => continue, + } + } + None => repo + .find_object(git::hash::ObjectId::empty_tree(repo.object_hash())) + .expect("always present") + .into_tree(), + }; + let to = match repo.find_object(commit).ok().and_then(|c| c.peel_to_tree().ok()) + { + Some(c) => c, + None => continue, + }; + from.changes().for_each_to_obtain_tree(&to, |change| { + use git::object::tree::diff::change::Event::*; + match change.event { + Addition { entry_mode, .. } => { + if entry_mode.is_no_tree() { + stat.added += 1 + } + } + Deletion { entry_mode, .. } => { + if entry_mode.is_no_tree() { + stat.removed += 1 + } + } + Modification { entry_mode, .. } => { + if entry_mode.is_no_tree() { + stat.modified += 1; + } + } + } + Ok::<_, Infallible>(Default::default()) + })?; + out.push((commit_idx, stat)); + } + Ok(out) + } + }) + }) + .collect::>(); + (Some(tx), stat_workers) + }) + .unwrap_or_else(Default::default); + + let mut commit_idx = 0_u32; let commit_iter = interrupt::Iter::new( commit_id.ancestors(|oid, buf| { progress.inc(); repo.objects.find(oid, buf).map(|o| { tx.send(o.data.to_owned()).ok(); - objs::CommitRefIter::from_bytes(o.data) + if let Some((tx_tree, first_parent, commit)) = tx_tree_id.as_ref().and_then(|tx| { + git::objs::CommitRefIter::from_bytes(o.data) + .parent_ids() + .next() + .map(|first_parent| (tx, Some(first_parent), oid.to_owned())) + }) { + tx_tree.send((commit_idx, first_parent, commit)).ok(); + } + commit_idx += 1; + git::objs::CommitRefIter::from_bytes(o.data) }) }), || anyhow!("Cancelled by user"), @@ -123,23 +209,38 @@ where }; } drop(tx); + drop(tx_tree_id); progress.show_throughput(start); - Ok((handle.join().expect("no panic")?, is_shallow)) + + let _stats_by_commit_idx = match stat_progress { + Some(mut progress) => { + progress.init(Some(commit_idx as usize), progress::count("commits")); + let mut stats = Vec::new(); + for handle in stat_threads { + stats.extend(handle.join().expect("no panic")?); + } + progress.show_throughput(start); + stats + } + None => Vec::new(), + }; + + Ok((commit_thread.join().expect("no panic")?, is_shallow)) })? }; - if all_commits.is_empty() { + if commit_authors.is_empty() { bail!("No commits to process"); } let start = Instant::now(); - let mut current_email = &all_commits[0].email; + let mut current_email = &commit_authors[0].email; let mut slice_start = 0; let mut results_by_hours = Vec::new(); let mut ignored_bot_commits = 0_u32; - for (idx, elm) in all_commits.iter().enumerate() { + for (idx, elm) in commit_authors.iter().enumerate() { if elm.email != *current_email { - let estimate = estimate_hours(&all_commits[slice_start..idx]); + let estimate = estimate_hours(&commit_authors[slice_start..idx]); slice_start = idx; current_email = &elm.email; if ignore_bots && estimate.name.contains_str(b"[bot]") { @@ -149,7 +250,7 @@ where results_by_hours.push(estimate); } } - if let Some(commits) = all_commits.get(slice_start..) { + if let Some(commits) = commit_authors.get(slice_start..) { results_by_hours.push(estimate_hours(commits)); } @@ -167,9 +268,9 @@ where let elapsed = start.elapsed(); progress.done(format!( "Extracted and organized data from {} commits in {:?} ({:0.0} commits/s)", - all_commits.len(), + commit_authors.len(), elapsed, - all_commits.len() as f32 / elapsed.as_secs_f32() + commit_authors.len() as f32 / elapsed.as_secs_f32() )); let num_unique_authors = results_by_hours.len(); @@ -207,7 +308,7 @@ where } assert_eq!( total_commits, - all_commits.len() as u32 - ignored_bot_commits, + commit_authors.len() as u32 - ignored_bot_commits, "need to get all commits" ); Ok(()) @@ -328,3 +429,14 @@ struct WorkByEmail { hours: f32, num_commits: u32, } + +/// Statistics for a particular commit. +#[derive(Debug, Default)] +struct Stats { + /// amount of added files + added: usize, + /// amount of removed files + removed: usize, + /// amount of modified files + modified: usize, +} From 67ec2c7f9a4a6cefdf7148f5c7e48a79f201c4d2 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 13:35:29 +0800 Subject: [PATCH 33/48] working progress printing (#470) Finally, issues due to bugs in prodash. --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- git-features/Cargo.toml | 2 +- gitoxide-core/src/hours.rs | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dbc3d933482..42d390e324f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2580,9 +2580,9 @@ dependencies = [ [[package]] name = "prodash" -version = "20.1.1" +version = "20.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "762467059887e40727b7cea07161956c9bd0fb6df0ca1225538effdb9f77c80a" +checksum = "cd4e8b029f29b4eb8f95315957fb7ac8a8fd1924405fadf885b0e208fe34ba39" dependencies = [ "async-io", "atty", diff --git a/Cargo.toml b/Cargo.toml index 24d70220467..92b3982a632 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,7 +86,7 @@ git-repository = { version = "^0.24.0", path = "git-repository", default-feature git-transport-for-configuration-only = { package = "git-transport", optional = true, version = "^0.20.0", path = "git-transport" } clap = { version = "3.2.5", features = ["derive", "cargo"] } -prodash = { version = "20.1.1", optional = true, default-features = false } +prodash = { version = "20.2.0", optional = true, default-features = false } atty = { version = "0.2.14", optional = true, default-features = false } env_logger = { version = "0.9.0", default-features = false } crosstermion = { version = "0.10.1", optional = true, default-features = false } diff --git a/git-features/Cargo.toml b/git-features/Cargo.toml index f0374f453b1..3f55553ba1a 100644 --- a/git-features/Cargo.toml +++ b/git-features/Cargo.toml @@ -115,7 +115,7 @@ crc32fast = { version = "1.2.1", optional = true } sha1 = { version = "0.10.0", optional = true } # progress -prodash = { version = "20.1.1", optional = true, default-features = false, features = ["unit-bytes", "unit-human"] } +prodash = { version = "20.2.0", optional = true, default-features = false, features = ["unit-bytes", "unit-human"] } # pipe bytes = { version = "1.0.0", optional = true } diff --git a/gitoxide-core/src/hours.rs b/gitoxide-core/src/hours.rs index d466f7f97bb..33b08edda5c 100644 --- a/gitoxide-core/src/hours.rs +++ b/gitoxide-core/src/hours.rs @@ -211,10 +211,11 @@ where drop(tx); drop(tx_tree_id); progress.show_throughput(start); + drop(progress); let _stats_by_commit_idx = match stat_progress { Some(mut progress) => { - progress.init(Some(commit_idx as usize), progress::count("commits")); + progress.set_max(Some(commit_idx as usize)); let mut stats = Vec::new(); for handle in stat_threads { stats.extend(handle.join().expect("no panic")?); From b8f2f8bac8af5ea5edd774860ee06117a54a723f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 15:04:40 +0800 Subject: [PATCH 34/48] feat: `ein tool hours -s` shows statistics about files added/removed/modified. (#470) --- gitoxide-core/src/hours.rs | 149 +++++++++++++++++++++++++------------ 1 file changed, 102 insertions(+), 47 deletions(-) diff --git a/gitoxide-core/src/hours.rs b/gitoxide-core/src/hours.rs index 33b08edda5c..9a9e15ae8ee 100644 --- a/gitoxide-core/src/hours.rs +++ b/gitoxide-core/src/hours.rs @@ -55,7 +55,7 @@ where let commit_id = repo.rev_parse_single(rev_spec)?.detach(); let mut string_heap = BTreeSet::<&'static [u8]>::new(); - let (commit_authors, is_shallow) = { + let (commit_authors, stats, is_shallow) = { let stat_progress = stats.then(|| progress.add_child("extract stats")).map(|mut p| { p.init(None, progress::count("commits")); p @@ -65,14 +65,14 @@ where let mut progress = progress.add_child("traverse commit graph"); progress.init(None, progress::count("commits")); - std::thread::scope(|scope| -> anyhow::Result<(Vec>, bool)> { + std::thread::scope(|scope| -> anyhow::Result<_> { let start = Instant::now(); - let (tx, rx) = std::sync::mpsc::channel::>(); + let (tx, rx) = std::sync::mpsc::channel::<(u32, Vec)>(); let mailmap = repo.open_mailmap(); - let commit_thread = scope.spawn(move || -> anyhow::Result>> { + let commit_thread = scope.spawn(move || -> anyhow::Result> { let mut out = Vec::new(); - for commit_data in rx { + for (commit_idx, commit_data) in rx { if let Some(author) = git::objs::CommitRefIter::from_bytes(&commit_data) .author() .map(|author| mailmap.resolve_cow(author.trim())) @@ -91,19 +91,22 @@ where let name = string_ref(author.name.as_ref()); let email = string_ref(&author.email.as_ref()); - out.push(actor::SignatureRef { - name, - email, - time: author.time, - }); + out.push(( + commit_idx, + actor::SignatureRef { + name, + email, + time: author.time, + }, + )); } } out.shrink_to_fit(); out.sort_by(|a, b| { - a.email.cmp(&b.email).then( - a.time + a.1.email.cmp(&b.1.email).then( + a.1.time .seconds_since_unix_epoch - .cmp(&b.time.seconds_since_unix_epoch) + .cmp(&b.1.time.seconds_since_unix_epoch) .reverse(), ) }); @@ -181,18 +184,18 @@ where let commit_iter = interrupt::Iter::new( commit_id.ancestors(|oid, buf| { progress.inc(); - repo.objects.find(oid, buf).map(|o| { - tx.send(o.data.to_owned()).ok(); + repo.objects.find(oid, buf).map(|obj| { + tx.send((commit_idx, obj.data.to_owned())).ok(); if let Some((tx_tree, first_parent, commit)) = tx_tree_id.as_ref().and_then(|tx| { - git::objs::CommitRefIter::from_bytes(o.data) + git::objs::CommitRefIter::from_bytes(obj.data) .parent_ids() .next() .map(|first_parent| (tx, Some(first_parent), oid.to_owned())) }) { tx_tree.send((commit_idx, first_parent, commit)).ok(); } - commit_idx += 1; - git::objs::CommitRefIter::from_bytes(o.data) + commit_idx = commit_idx.checked_add(1).expect("less then 4 billion commits"); + git::objs::CommitRefIter::from_bytes(obj.data) }) }), || anyhow!("Cancelled by user"), @@ -213,20 +216,25 @@ where progress.show_throughput(start); drop(progress); - let _stats_by_commit_idx = match stat_progress { + let stats_by_commit_idx = match stat_progress { Some(mut progress) => { progress.set_max(Some(commit_idx as usize)); let mut stats = Vec::new(); for handle in stat_threads { stats.extend(handle.join().expect("no panic")?); } + stats.sort_by_key(|t| t.0); progress.show_throughput(start); stats } None => Vec::new(), }; - Ok((commit_thread.join().expect("no panic")?, is_shallow)) + Ok(( + commit_thread.join().expect("no panic")?, + stats_by_commit_idx, + is_shallow, + )) })? }; @@ -235,13 +243,13 @@ where } let start = Instant::now(); - let mut current_email = &commit_authors[0].email; + let mut current_email = &commit_authors[0].1.email; let mut slice_start = 0; let mut results_by_hours = Vec::new(); let mut ignored_bot_commits = 0_u32; - for (idx, elm) in commit_authors.iter().enumerate() { + for (idx, (_, elm)) in commit_authors.iter().enumerate() { if elm.email != *current_email { - let estimate = estimate_hours(&commit_authors[slice_start..idx]); + let estimate = estimate_hours(&commit_authors[slice_start..idx], &stats); slice_start = idx; current_email = &elm.email; if ignore_bots && estimate.name.contains_str(b"[bot]") { @@ -252,7 +260,7 @@ where } } if let Some(commits) = commit_authors.get(slice_start..) { - results_by_hours.push(estimate_hours(commits)); + results_by_hours.push(estimate_hours(commits, &stats)); } let num_authors = results_by_hours.len(); @@ -275,15 +283,16 @@ where )); let num_unique_authors = results_by_hours.len(); - let (total_hours, total_commits) = results_by_hours + let (total_hours, total_commits, total_stats) = results_by_hours .iter() - .map(|e| (e.hours, e.num_commits)) - .reduce(|a, b| (a.0 + b.0, a.1 + b.1)) + .map(|e| (e.hours, e.num_commits, e.stats)) + .reduce(|a, b| (a.0 + b.0, a.1 + b.1, a.2.clone().added(&b.2))) .expect("at least one commit at this point"); if show_pii { results_by_hours.sort_by(|a, b| a.hours.partial_cmp(&b.hours).unwrap_or(std::cmp::Ordering::Equal)); + let show_stats = !stats.is_empty(); for entry in results_by_hours.iter() { - entry.write_to(total_hours, &mut out)?; + entry.write_to(total_hours, show_stats, &mut out)?; writeln!(out)?; } } @@ -296,6 +305,13 @@ where is_shallow.then(|| " (shallow)").unwrap_or_default(), num_authors )?; + if !stats.is_empty() { + writeln!( + out, + "total files added/removed/modified: {}/{}/{}", + total_stats.added, total_stats.removed, total_stats.modified + )?; + } if !omit_unify_identities { writeln!( out, @@ -318,30 +334,42 @@ where const MINUTES_PER_HOUR: f32 = 60.0; const HOURS_PER_WORKDAY: f32 = 8.0; -fn estimate_hours(commits: &[actor::SignatureRef<'static>]) -> WorkByEmail { +fn estimate_hours(commits: &[(u32, actor::SignatureRef<'static>)], stats: &[(u32, Stats)]) -> WorkByEmail { assert!(!commits.is_empty()); const MAX_COMMIT_DIFFERENCE_IN_MINUTES: f32 = 2.0 * MINUTES_PER_HOUR; const FIRST_COMMIT_ADDITION_IN_MINUTES: f32 = 2.0 * MINUTES_PER_HOUR; - let hours = FIRST_COMMIT_ADDITION_IN_MINUTES / 60.0 - + commits.iter().rev().tuple_windows().fold( - 0_f32, - |hours, (cur, next): (&actor::SignatureRef<'_>, &actor::SignatureRef<'_>)| { - let change_in_minutes = - (next.time.seconds_since_unix_epoch - cur.time.seconds_since_unix_epoch) as f32 / MINUTES_PER_HOUR; - if change_in_minutes < MAX_COMMIT_DIFFERENCE_IN_MINUTES { - hours + change_in_minutes as f32 / MINUTES_PER_HOUR - } else { - hours + (FIRST_COMMIT_ADDITION_IN_MINUTES / MINUTES_PER_HOUR) - } - }, - ); - let author = &commits[0]; + let hours_for_commits = commits.iter().map(|t| &t.1).rev().tuple_windows().fold( + 0_f32, + |hours, (cur, next): (&actor::SignatureRef<'_>, &actor::SignatureRef<'_>)| { + let change_in_minutes = (next + .time + .seconds_since_unix_epoch + .saturating_sub(cur.time.seconds_since_unix_epoch)) as f32 + / MINUTES_PER_HOUR; + if change_in_minutes < MAX_COMMIT_DIFFERENCE_IN_MINUTES { + hours + change_in_minutes as f32 / MINUTES_PER_HOUR + } else { + hours + (FIRST_COMMIT_ADDITION_IN_MINUTES / MINUTES_PER_HOUR) + } + }, + ); + + let author = &commits[0].1; WorkByEmail { name: author.name, email: author.email, - hours, + hours: FIRST_COMMIT_ADDITION_IN_MINUTES / 60.0 + hours_for_commits, num_commits: commits.len() as u32, + stats: commits.iter().map(|t| &t.0).fold(Stats::default(), |mut acc, id| { + match stats.binary_search_by(|t| t.0.cmp(id)) { + Ok(idx) => { + acc.add(&stats[idx].1); + acc + } + Err(_) => acc, + } + }), } } @@ -378,6 +406,7 @@ struct WorkByPerson { email: Vec<&'static BStr>, hours: f32, num_commits: u32, + stats: Stats, } impl<'a> WorkByPerson { @@ -390,6 +419,7 @@ impl<'a> WorkByPerson { } self.num_commits += other.num_commits; self.hours += other.hours; + self.stats.add(&other.stats); } } @@ -400,12 +430,13 @@ impl<'a> From<&'a WorkByEmail> for WorkByPerson { email: vec![w.email], hours: w.hours, num_commits: w.num_commits, + stats: w.stats, } } } impl WorkByPerson { - fn write_to(&self, total_hours: f32, mut out: impl std::io::Write) -> std::io::Result<()> { + fn write_to(&self, total_hours: f32, show_stats: bool, mut out: impl std::io::Write) -> std::io::Result<()> { writeln!( out, "{} <{}>", @@ -419,7 +450,15 @@ impl WorkByPerson { self.hours, self.hours / HOURS_PER_WORKDAY, (self.hours / total_hours) * 100.0 - ) + )?; + if show_stats { + writeln!( + out, + "total files added/removed/modified: {}/{}/{}", + self.stats.added, self.stats.removed, self.stats.modified + )?; + } + Ok(()) } } @@ -429,10 +468,11 @@ struct WorkByEmail { email: &'static BStr, hours: f32, num_commits: u32, + stats: Stats, } /// Statistics for a particular commit. -#[derive(Debug, Default)] +#[derive(Debug, Default, Copy, Clone)] struct Stats { /// amount of added files added: usize, @@ -441,3 +481,18 @@ struct Stats { /// amount of modified files modified: usize, } + +impl Stats { + fn add(&mut self, other: &Stats) -> &mut Self { + self.added += other.added; + self.removed += other.removed; + self.modified += other.modified; + self + } + + fn added(&self, other: &Stats) -> Self { + let mut a = *self; + a.add(other); + a + } +} From ffd4f0f7edcf403573044120caf789cddb38b868 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 15:11:40 +0800 Subject: [PATCH 35/48] avoid binary search if there is nothing to find (#470) Even though failing is cheap, it's not free and can be done a million times. --- gitoxide-core/src/hours.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/gitoxide-core/src/hours.rs b/gitoxide-core/src/hours.rs index 9a9e15ae8ee..98dec4ce4ff 100644 --- a/gitoxide-core/src/hours.rs +++ b/gitoxide-core/src/hours.rs @@ -361,15 +361,19 @@ fn estimate_hours(commits: &[(u32, actor::SignatureRef<'static>)], stats: &[(u32 email: author.email, hours: FIRST_COMMIT_ADDITION_IN_MINUTES / 60.0 + hours_for_commits, num_commits: commits.len() as u32, - stats: commits.iter().map(|t| &t.0).fold(Stats::default(), |mut acc, id| { - match stats.binary_search_by(|t| t.0.cmp(id)) { - Ok(idx) => { - acc.add(&stats[idx].1); - acc - } - Err(_) => acc, - } - }), + stats: (!stats.is_empty()) + .then(|| { + commits.iter().map(|t| &t.0).fold(Stats::default(), |mut acc, id| { + match stats.binary_search_by(|t| t.0.cmp(id)) { + Ok(idx) => { + acc.add(&stats[idx].1); + acc + } + Err(_) => acc, + } + }) + }) + .unwrap_or_default(), } } From 3c7c9a735f5771ef787cbc86b46cbafc9226f4d6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 15:37:06 +0800 Subject: [PATCH 36/48] change: `ein tool hours -s` was split into `-f|--file-stats` and `-l|line-stats`. (#470) That way more information is generated at increasingly high costs. --- gitoxide-core/src/hours.rs | 105 +++++++++++++++++++++++++++---------- src/porcelain/main.rs | 6 ++- src/porcelain/options.rs | 11 ++-- 3 files changed, 89 insertions(+), 33 deletions(-) diff --git a/gitoxide-core/src/hours.rs b/gitoxide-core/src/hours.rs index 98dec4ce4ff..35e1e4ab41f 100644 --- a/gitoxide-core/src/hours.rs +++ b/gitoxide-core/src/hours.rs @@ -20,8 +20,10 @@ pub struct Context { pub ignore_bots: bool, /// Show personally identifiable information before the summary. Includes names and email addresses. pub show_pii: bool, - /// Collect additional information like tree changes and changed lines. - pub stats: bool, + /// Collect how many files have been added, removed and modified (without rename tracking). + pub file_stats: bool, + /// Collect how many lines in files have been added, removed and modified (without rename tracking). + pub line_stats: bool, /// Omit unifying identities by name and email which can lead to the same author appear multiple times /// due to using different names or email addresses. pub omit_unify_identities: bool, @@ -42,7 +44,8 @@ pub fn estimate( Context { show_pii, ignore_bots, - stats, + file_stats, + line_stats, omit_unify_identities, mut out, }: Context, @@ -56,7 +59,7 @@ where let mut string_heap = BTreeSet::<&'static [u8]>::new(); let (commit_authors, stats, is_shallow) = { - let stat_progress = stats.then(|| progress.add_child("extract stats")).map(|mut p| { + let stat_progress = file_stats.then(|| progress.add_child("extract stats")).map(|mut p| { p.init(None, progress::count("commits")); p }); @@ -113,7 +116,7 @@ where Ok(out) }); - let (tx_tree_id, stat_threads) = stats + let (tx_tree_id, stat_threads) = (file_stats || line_stats) .then(|| { let num_threads = num_cpus::get().saturating_sub(1 /*main thread*/).max(1); let (tx, rx) = flume::unbounded::<(u32, Option, git::hash::ObjectId)>(); @@ -130,7 +133,7 @@ where if let Some(c) = counter.as_ref() { c.fetch_add(1, Ordering::SeqCst); } - let mut stat = Stats::default(); + let mut stat = FileStats::default(); let from = match parent_commit { Some(id) => { match repo.find_object(id).ok().and_then(|c| c.peel_to_tree().ok()) { @@ -283,16 +286,15 @@ where )); let num_unique_authors = results_by_hours.len(); - let (total_hours, total_commits, total_stats) = results_by_hours + let (total_hours, total_commits, total_files, total_lines) = results_by_hours .iter() - .map(|e| (e.hours, e.num_commits, e.stats)) - .reduce(|a, b| (a.0 + b.0, a.1 + b.1, a.2.clone().added(&b.2))) + .map(|e| (e.hours, e.num_commits, e.files, e.lines)) + .reduce(|a, b| (a.0 + b.0, a.1 + b.1, a.2.clone().added(&b.2), a.3.clone().added(&b.3))) .expect("at least one commit at this point"); if show_pii { results_by_hours.sort_by(|a, b| a.hours.partial_cmp(&b.hours).unwrap_or(std::cmp::Ordering::Equal)); - let show_stats = !stats.is_empty(); for entry in results_by_hours.iter() { - entry.write_to(total_hours, show_stats, &mut out)?; + entry.write_to(total_hours, file_stats, line_stats, &mut out)?; writeln!(out)?; } } @@ -305,11 +307,18 @@ where is_shallow.then(|| " (shallow)").unwrap_or_default(), num_authors )?; - if !stats.is_empty() { + if file_stats { writeln!( out, "total files added/removed/modified: {}/{}/{}", - total_stats.added, total_stats.removed, total_stats.modified + total_files.added, total_files.removed, total_files.modified + )?; + } + if line_stats { + writeln!( + out, + "total lines added/removed: {}/{}", + total_lines.added, total_lines.removed )?; } if !omit_unify_identities { @@ -334,7 +343,7 @@ where const MINUTES_PER_HOUR: f32 = 60.0; const HOURS_PER_WORKDAY: f32 = 8.0; -fn estimate_hours(commits: &[(u32, actor::SignatureRef<'static>)], stats: &[(u32, Stats)]) -> WorkByEmail { +fn estimate_hours(commits: &[(u32, actor::SignatureRef<'static>)], stats: &[(u32, FileStats)]) -> WorkByEmail { assert!(!commits.is_empty()); const MAX_COMMIT_DIFFERENCE_IN_MINUTES: f32 = 2.0 * MINUTES_PER_HOUR; const FIRST_COMMIT_ADDITION_IN_MINUTES: f32 = 2.0 * MINUTES_PER_HOUR; @@ -361,9 +370,9 @@ fn estimate_hours(commits: &[(u32, actor::SignatureRef<'static>)], stats: &[(u32 email: author.email, hours: FIRST_COMMIT_ADDITION_IN_MINUTES / 60.0 + hours_for_commits, num_commits: commits.len() as u32, - stats: (!stats.is_empty()) + files: (!stats.is_empty()) .then(|| { - commits.iter().map(|t| &t.0).fold(Stats::default(), |mut acc, id| { + commits.iter().map(|t| &t.0).fold(FileStats::default(), |mut acc, id| { match stats.binary_search_by(|t| t.0.cmp(id)) { Ok(idx) => { acc.add(&stats[idx].1); @@ -374,6 +383,7 @@ fn estimate_hours(commits: &[(u32, actor::SignatureRef<'static>)], stats: &[(u32 }) }) .unwrap_or_default(), + lines: Default::default(), } } @@ -410,7 +420,8 @@ struct WorkByPerson { email: Vec<&'static BStr>, hours: f32, num_commits: u32, - stats: Stats, + files: FileStats, + lines: LineStats, } impl<'a> WorkByPerson { @@ -423,7 +434,7 @@ impl<'a> WorkByPerson { } self.num_commits += other.num_commits; self.hours += other.hours; - self.stats.add(&other.stats); + self.files.add(&other.files); } } @@ -434,13 +445,20 @@ impl<'a> From<&'a WorkByEmail> for WorkByPerson { email: vec![w.email], hours: w.hours, num_commits: w.num_commits, - stats: w.stats, + files: w.files, + lines: w.lines, } } } impl WorkByPerson { - fn write_to(&self, total_hours: f32, show_stats: bool, mut out: impl std::io::Write) -> std::io::Result<()> { + fn write_to( + &self, + total_hours: f32, + show_files: bool, + show_lines: bool, + mut out: impl std::io::Write, + ) -> std::io::Result<()> { writeln!( out, "{} <{}>", @@ -455,11 +473,18 @@ impl WorkByPerson { self.hours / HOURS_PER_WORKDAY, (self.hours / total_hours) * 100.0 )?; - if show_stats { + if show_files { writeln!( out, "total files added/removed/modified: {}/{}/{}", - self.stats.added, self.stats.removed, self.stats.modified + self.files.added, self.files.removed, self.files.modified + )?; + } + if show_lines { + writeln!( + out, + "total lines added/removed: {}/{}", + self.lines.added, self.lines.removed )?; } Ok(()) @@ -472,12 +497,13 @@ struct WorkByEmail { email: &'static BStr, hours: f32, num_commits: u32, - stats: Stats, + files: FileStats, + lines: LineStats, } -/// Statistics for a particular commit. +/// File statistics for a particular commit. #[derive(Debug, Default, Copy, Clone)] -struct Stats { +struct FileStats { /// amount of added files added: usize, /// amount of removed files @@ -486,15 +512,38 @@ struct Stats { modified: usize, } -impl Stats { - fn add(&mut self, other: &Stats) -> &mut Self { +/// Line statistics for a particular commit. +#[derive(Debug, Default, Copy, Clone)] +struct LineStats { + /// amount of added lines + added: usize, + /// amount of removed lines + removed: usize, +} + +impl FileStats { + fn add(&mut self, other: &FileStats) -> &mut Self { self.added += other.added; self.removed += other.removed; self.modified += other.modified; self } - fn added(&self, other: &Stats) -> Self { + fn added(&self, other: &FileStats) -> Self { + let mut a = *self; + a.add(other); + a + } +} + +impl LineStats { + fn add(&mut self, other: &LineStats) -> &mut Self { + self.added += other.added; + self.removed += other.removed; + self + } + + fn added(&self, other: &LineStats) -> Self { let mut a = *self; a.add(other); a diff --git a/src/porcelain/main.rs b/src/porcelain/main.rs index ef1e2d575e5..710dca6434e 100644 --- a/src/porcelain/main.rs +++ b/src/porcelain/main.rs @@ -40,7 +40,8 @@ pub fn main() -> Result<()> { working_dir, rev_spec, no_bots, - stats, + file_stats, + line_stats, show_pii, omit_unify_identities, }) => { @@ -59,7 +60,8 @@ pub fn main() -> Result<()> { hours::Context { show_pii, ignore_bots: no_bots, - stats, + file_stats, + line_stats, omit_unify_identities, out, }, diff --git a/src/porcelain/options.rs b/src/porcelain/options.rs index 8d7e42012b0..5c60f71e5df 100644 --- a/src/porcelain/options.rs +++ b/src/porcelain/options.rs @@ -101,9 +101,14 @@ pub struct EstimateHours { /// Ignore github bots which match the `[bot]` search string. #[clap(short = 'b', long)] pub no_bots: bool, - /// Collect additional information about file modifications, additions and deletions. - #[clap(short = 's', long)] - pub stats: bool, + /// Collect additional information about file modifications, additions and deletions (without rename tracking). + #[clap(short = 'f', long)] + pub file_stats: bool, + /// Collect additional information about lines added and deleted (without rename tracking). + /// + /// Note that this implies the work to be done for file-stats, so it should be set as well. + #[clap(short = 'l', long)] + pub line_stats: bool, /// Show personally identifiable information before the summary. Includes names and email addresses. #[clap(short = 'p', long)] pub show_pii: bool, From 0614318c67927f84c07640234d266b8d24bc74d3 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 15:47:16 +0800 Subject: [PATCH 37/48] show discovered file chnages in real-time (#470) --- gitoxide-core/src/hours.rs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/gitoxide-core/src/hours.rs b/gitoxide-core/src/hours.rs index 35e1e4ab41f..5227bcf2ec0 100644 --- a/gitoxide-core/src/hours.rs +++ b/gitoxide-core/src/hours.rs @@ -59,12 +59,21 @@ where let mut string_heap = BTreeSet::<&'static [u8]>::new(); let (commit_authors, stats, is_shallow) = { - let stat_progress = file_stats.then(|| progress.add_child("extract stats")).map(|mut p| { + let needs_stats = file_stats || line_stats; + let stat_progress = needs_stats.then(|| progress.add_child("extract stats")).map(|mut p| { p.init(None, progress::count("commits")); p }); let stat_counter = stat_progress.as_ref().and_then(|p| p.counter()); + let change_progress = needs_stats + .then(|| progress.add_child("analyzing changes")) + .map(|mut p| { + p.init(None, progress::count("files")); + p + }); + let change_counter = change_progress.as_ref().and_then(|p| p.counter()); + let mut progress = progress.add_child("traverse commit graph"); progress.init(None, progress::count("commits")); @@ -116,21 +125,22 @@ where Ok(out) }); - let (tx_tree_id, stat_threads) = (file_stats || line_stats) + let (tx_tree_id, stat_threads) = needs_stats .then(|| { let num_threads = num_cpus::get().saturating_sub(1 /*main thread*/).max(1); let (tx, rx) = flume::unbounded::<(u32, Option, git::hash::ObjectId)>(); let stat_workers = (0..num_threads) .map(|_| { scope.spawn({ - let counter = stat_counter.clone(); + let commit_counter = stat_counter.clone(); + let change_counter = change_counter.clone(); let mut repo = repo.clone(); repo.object_cache_size_if_unset(4 * 1024 * 1024); let rx = rx.clone(); move || -> Result<_, git::object::tree::diff::Error> { let mut out = Vec::new(); for (commit_idx, parent_commit, commit) in rx { - if let Some(c) = counter.as_ref() { + if let Some(c) = commit_counter.as_ref() { c.fetch_add(1, Ordering::SeqCst); } let mut stat = FileStats::default(); @@ -153,6 +163,9 @@ where }; from.changes().for_each_to_obtain_tree(&to, |change| { use git::object::tree::diff::change::Event::*; + if let Some(c) = change_counter.as_ref() { + c.fetch_add(1, Ordering::SeqCst); + } match change.event { Addition { entry_mode, .. } => { if entry_mode.is_no_tree() { @@ -232,6 +245,9 @@ where } None => Vec::new(), }; + if let Some(mut progress) = change_progress { + progress.show_throughput(start); + } Ok(( commit_thread.join().expect("no panic")?, From 84eb777e52808c7c9019a7a0b004ed4d81a8be7c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 15:57:53 +0800 Subject: [PATCH 38/48] collect changes lines as well (#470) --- gitoxide-core/src/hours.rs | 75 +++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 22 deletions(-) diff --git a/gitoxide-core/src/hours.rs b/gitoxide-core/src/hours.rs index 5227bcf2ec0..42359f513e2 100644 --- a/gitoxide-core/src/hours.rs +++ b/gitoxide-core/src/hours.rs @@ -143,7 +143,8 @@ where if let Some(c) = commit_counter.as_ref() { c.fetch_add(1, Ordering::SeqCst); } - let mut stat = FileStats::default(); + let mut files = FileStats::default(); + let mut lines = LineStats::default(); let from = match parent_commit { Some(id) => { match repo.find_object(id).ok().and_then(|c| c.peel_to_tree().ok()) { @@ -167,25 +168,46 @@ where c.fetch_add(1, Ordering::SeqCst); } match change.event { - Addition { entry_mode, .. } => { + Addition { entry_mode, id } => { if entry_mode.is_no_tree() { - stat.added += 1 + files.added += 1 + } + if let Ok(blob) = id.object() { + lines.added = blob.data.lines_with_terminator().count(); } } - Deletion { entry_mode, .. } => { + Deletion { entry_mode, id } => { if entry_mode.is_no_tree() { - stat.removed += 1 + files.removed += 1 + } + if let Ok(blob) = id.object() { + lines.removed = blob.data.lines_with_terminator().count(); } } Modification { entry_mode, .. } => { if entry_mode.is_no_tree() { - stat.modified += 1; + files.modified += 1; + } + if line_stats { + if let Some(Ok(diff)) = change.event.diff() { + use git::diff::lines::similar::ChangeTag::*; + for change in diff + .text(git::diff::lines::Algorithm::Myers) + .iter_all_changes() + { + match change.tag() { + Delete => lines.removed += 1, + Insert => lines.added += 1, + Equal => {} + } + } + } } } } Ok::<_, Infallible>(Default::default()) })?; - out.push((commit_idx, stat)); + out.push((commit_idx, files, lines)); } Ok(out) } @@ -359,7 +381,10 @@ where const MINUTES_PER_HOUR: f32 = 60.0; const HOURS_PER_WORKDAY: f32 = 8.0; -fn estimate_hours(commits: &[(u32, actor::SignatureRef<'static>)], stats: &[(u32, FileStats)]) -> WorkByEmail { +fn estimate_hours( + commits: &[(u32, actor::SignatureRef<'static>)], + stats: &[(u32, FileStats, LineStats)], +) -> WorkByEmail { assert!(!commits.is_empty()); const MAX_COMMIT_DIFFERENCE_IN_MINUTES: f32 = 2.0 * MINUTES_PER_HOUR; const FIRST_COMMIT_ADDITION_IN_MINUTES: f32 = 2.0 * MINUTES_PER_HOUR; @@ -381,25 +406,31 @@ fn estimate_hours(commits: &[(u32, actor::SignatureRef<'static>)], stats: &[(u32 ); let author = &commits[0].1; + let (files, lines) = (!stats.is_empty()) + .then(|| { + commits + .iter() + .map(|t| &t.0) + .fold((FileStats::default(), LineStats::default()), |mut acc, id| match stats + .binary_search_by(|t| t.0.cmp(id)) + { + Ok(idx) => { + let t = &stats[idx]; + acc.0.add(&t.1); + acc.1.add(&t.2); + acc + } + Err(_) => acc, + }) + }) + .unwrap_or_default(); WorkByEmail { name: author.name, email: author.email, hours: FIRST_COMMIT_ADDITION_IN_MINUTES / 60.0 + hours_for_commits, num_commits: commits.len() as u32, - files: (!stats.is_empty()) - .then(|| { - commits.iter().map(|t| &t.0).fold(FileStats::default(), |mut acc, id| { - match stats.binary_search_by(|t| t.0.cmp(id)) { - Ok(idx) => { - acc.add(&stats[idx].1); - acc - } - Err(_) => acc, - } - }) - }) - .unwrap_or_default(), - lines: Default::default(), + files, + lines, } } From cb533241255922993bb1275428a97e6b4c3f6fdc Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 16:08:21 +0800 Subject: [PATCH 39/48] make stat extractions interruptable (#470) --- gitoxide-core/src/hours.rs | 52 ++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/gitoxide-core/src/hours.rs b/gitoxide-core/src/hours.rs index 42359f513e2..ad26ec04093 100644 --- a/gitoxide-core/src/hours.rs +++ b/gitoxide-core/src/hours.rs @@ -66,14 +66,18 @@ where }); let stat_counter = stat_progress.as_ref().and_then(|p| p.counter()); - let change_progress = needs_stats - .then(|| progress.add_child("analyzing changes")) - .map(|mut p| { - p.init(None, progress::count("files")); - p - }); + let change_progress = needs_stats.then(|| progress.add_child("find changes")).map(|mut p| { + p.init(None, progress::count("modified files")); + p + }); let change_counter = change_progress.as_ref().and_then(|p| p.counter()); + let lines_progress = line_stats.then(|| progress.add_child("find changes")).map(|mut p| { + p.init(None, progress::count("diff lines")); + p + }); + let lines_counter = lines_progress.as_ref().and_then(|p| p.counter()); + let mut progress = progress.add_child("traverse commit graph"); progress.init(None, progress::count("commits")); @@ -134,6 +138,7 @@ where scope.spawn({ let commit_counter = stat_counter.clone(); let change_counter = change_counter.clone(); + let lines_counter = lines_counter.clone(); let mut repo = repo.clone(); repo.object_cache_size_if_unset(4 * 1024 * 1024); let rx = rx.clone(); @@ -143,6 +148,9 @@ where if let Some(c) = commit_counter.as_ref() { c.fetch_add(1, Ordering::SeqCst); } + if git::interrupt::is_triggered() { + return Ok(out); + } let mut files = FileStats::default(); let mut lines = LineStats::default(); let from = match parent_commit { @@ -173,7 +181,11 @@ where files.added += 1 } if let Ok(blob) = id.object() { - lines.added = blob.data.lines_with_terminator().count(); + let nl = blob.data.lines_with_terminator().count(); + lines.added += nl; + if let Some(c) = lines_counter.as_ref() { + c.fetch_add(nl, Ordering::SeqCst); + } } } Deletion { entry_mode, id } => { @@ -181,7 +193,11 @@ where files.removed += 1 } if let Ok(blob) = id.object() { - lines.removed = blob.data.lines_with_terminator().count(); + let nl = blob.data.lines_with_terminator().count(); + lines.removed += nl; + if let Some(c) = lines_counter.as_ref() { + c.fetch_add(nl, Ordering::SeqCst); + } } } Modification { entry_mode, .. } => { @@ -191,16 +207,26 @@ where if line_stats { if let Some(Ok(diff)) = change.event.diff() { use git::diff::lines::similar::ChangeTag::*; + let mut nl = 0; for change in diff .text(git::diff::lines::Algorithm::Myers) .iter_all_changes() { match change.tag() { - Delete => lines.removed += 1, - Insert => lines.added += 1, + Delete => { + lines.removed += 1; + nl += 1; + } + Insert => { + lines.added += 1; + nl += 1 + } Equal => {} } } + if let Some(c) = lines_counter.as_ref() { + c.fetch_add(nl, Ordering::SeqCst); + } } } } @@ -260,6 +286,9 @@ where let mut stats = Vec::new(); for handle in stat_threads { stats.extend(handle.join().expect("no panic")?); + if git::interrupt::is_triggered() { + bail!("Cancelled by user"); + } } stats.sort_by_key(|t| t.0); progress.show_throughput(start); @@ -270,6 +299,9 @@ where if let Some(mut progress) = change_progress { progress.show_throughput(start); } + if let Some(mut progress) = lines_progress { + progress.show_throughput(start); + } Ok(( commit_thread.join().expect("no panic")?, From e3e3211e8ae3470556a12666c1c99fdd5221e887 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 16:21:32 +0800 Subject: [PATCH 40/48] refactor (#470) --- gitoxide-core/src/hours.rs | 42 +++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/gitoxide-core/src/hours.rs b/gitoxide-core/src/hours.rs index ad26ec04093..469242a2028 100644 --- a/gitoxide-core/src/hours.rs +++ b/gitoxide-core/src/hours.rs @@ -204,29 +204,29 @@ where if entry_mode.is_no_tree() { files.modified += 1; } - if line_stats { - if let Some(Ok(diff)) = change.event.diff() { - use git::diff::lines::similar::ChangeTag::*; - let mut nl = 0; - for change in diff - .text(git::diff::lines::Algorithm::Myers) - .iter_all_changes() - { - match change.tag() { - Delete => { - lines.removed += 1; - nl += 1; - } - Insert => { - lines.added += 1; - nl += 1 - } - Equal => {} + if let Some(Ok(diff)) = + line_stats.then(|| change.event.diff()).flatten() + { + use git::diff::lines::similar::ChangeTag::*; + let mut nl = 0; + for change in diff + .text(git::diff::lines::Algorithm::Myers) + .iter_all_changes() + { + match change.tag() { + Delete => { + lines.removed += 1; + nl += 1; } + Insert => { + lines.added += 1; + nl += 1 + } + Equal => {} } - if let Some(c) = lines_counter.as_ref() { - c.fetch_add(nl, Ordering::SeqCst); - } + } + if let Some(c) = lines_counter.as_ref() { + c.fetch_add(nl, Ordering::SeqCst); } } } From e27b65abfceb90cadd1902381414d9052ec6d8d0 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 16:42:15 +0800 Subject: [PATCH 41/48] replace `flume` with `crossbeam-channel` as it's already in the dependency graph (#470) --- Cargo.lock | 55 +------------------------------------- gitoxide-core/Cargo.toml | 4 +-- gitoxide-core/src/hours.rs | 3 ++- 3 files changed, 5 insertions(+), 57 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 42d390e324f..4ab7db8f050 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -957,19 +957,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "flume" -version = "0.10.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" -dependencies = [ - "futures-core", - "futures-sink", - "nanorand", - "pin-project", - "spin", -] - [[package]] name = "form_urlencoded" version = "1.1.0" @@ -1109,10 +1096,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi", - "wasm-bindgen", ] [[package]] @@ -1863,8 +1848,8 @@ dependencies = [ "async-trait", "blocking", "bytesize", + "crossbeam-channel", "document-features", - "flume", "fs-err", "futures-io", "futures-lite", @@ -2241,15 +2226,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "nanorand" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" -dependencies = [ - "getrandom", -] - [[package]] name = "nix" version = "0.21.0" @@ -2453,26 +2429,6 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" -[[package]] -name = "pin-project" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "pin-project-lite" version = "0.2.9" @@ -2990,15 +2946,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "spin" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" -dependencies = [ - "lock_api", -] - [[package]] name = "static_assertions" version = "1.1.0" diff --git a/gitoxide-core/Cargo.toml b/gitoxide-core/Cargo.toml index 60dc7cb8c01..046f72a76b8 100644 --- a/gitoxide-core/Cargo.toml +++ b/gitoxide-core/Cargo.toml @@ -18,7 +18,7 @@ default = [] ## Discover all git repositories within a directory. Particularly useful with [skim](https://github.com/lotabout/skim). organize = ["git-url", "jwalk"] ## Derive the amount of time invested into a git repository akin to [git-hours](https://github.com/kimmobrunfeldt/git-hours). -estimate-hours = ["itertools", "fs-err", "num_cpus", "flume"] +estimate-hours = ["itertools", "fs-err", "num_cpus", "crossbeam-channel"] #! ### Mutually Exclusive Networking #! If both are set, _blocking-client_ will take precedence, allowing `--all-features` to be used. @@ -63,7 +63,7 @@ jwalk = { version = "0.6.0", optional = true } itertools = { version = "0.10.1", optional = true } fs-err = { version = "2.6.0", optional = true } num_cpus = { version = "1.13.1", optional = true } -flume = { version = "0.10.14", optional = true } +crossbeam-channel = { version = "0.5.6", optional = true } document-features = { version = "0.2.0", optional = true } diff --git a/gitoxide-core/src/hours.rs b/gitoxide-core/src/hours.rs index 469242a2028..c678f548a26 100644 --- a/gitoxide-core/src/hours.rs +++ b/gitoxide-core/src/hours.rs @@ -132,7 +132,8 @@ where let (tx_tree_id, stat_threads) = needs_stats .then(|| { let num_threads = num_cpus::get().saturating_sub(1 /*main thread*/).max(1); - let (tx, rx) = flume::unbounded::<(u32, Option, git::hash::ObjectId)>(); + let (tx, rx) = + crossbeam_channel::unbounded::<(u32, Option, git::hash::ObjectId)>(); let stat_workers = (0..num_threads) .map(|_| { scope.spawn({ From 2a3c5b000493a5a4cf62a5d5e2a509bc12a9fedd Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 16:53:05 +0800 Subject: [PATCH 42/48] only diff files that are probably not a binary file (as per extension). (#470) We assume unguessable ones are a plain text file, like one without extension. --- Cargo.lock | 17 +++++++++++++ gitoxide-core/Cargo.toml | 3 ++- gitoxide-core/src/hours.rs | 50 ++++++++++++++++++++++---------------- 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ab7db8f050..3351098f8dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1860,6 +1860,7 @@ dependencies = [ "git-url", "itertools", "jwalk", + "mime_guess", "num_cpus", "serde", "serde_json", @@ -2199,6 +2200,22 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" diff --git a/gitoxide-core/Cargo.toml b/gitoxide-core/Cargo.toml index 046f72a76b8..44f9c90ba8c 100644 --- a/gitoxide-core/Cargo.toml +++ b/gitoxide-core/Cargo.toml @@ -18,7 +18,7 @@ default = [] ## Discover all git repositories within a directory. Particularly useful with [skim](https://github.com/lotabout/skim). organize = ["git-url", "jwalk"] ## Derive the amount of time invested into a git repository akin to [git-hours](https://github.com/kimmobrunfeldt/git-hours). -estimate-hours = ["itertools", "fs-err", "num_cpus", "crossbeam-channel"] +estimate-hours = ["itertools", "fs-err", "num_cpus", "crossbeam-channel", "mime_guess"] #! ### Mutually Exclusive Networking #! If both are set, _blocking-client_ will take precedence, allowing `--all-features` to be used. @@ -64,6 +64,7 @@ itertools = { version = "0.10.1", optional = true } fs-err = { version = "2.6.0", optional = true } num_cpus = { version = "1.13.1", optional = true } crossbeam-channel = { version = "0.5.6", optional = true } +mime_guess = { version = "2.0.4", optional = true } document-features = { version = "0.2.0", optional = true } diff --git a/gitoxide-core/src/hours.rs b/gitoxide-core/src/hours.rs index c678f548a26..c11b6217689 100644 --- a/gitoxide-core/src/hours.rs +++ b/gitoxide-core/src/hours.rs @@ -171,7 +171,7 @@ where Some(c) => c, None => continue, }; - from.changes().for_each_to_obtain_tree(&to, |change| { + from.changes().track_filename().for_each_to_obtain_tree(&to, |change| { use git::object::tree::diff::change::Event::*; if let Some(c) = change_counter.as_ref() { c.fetch_add(1, Ordering::SeqCst); @@ -205,29 +205,37 @@ where if entry_mode.is_no_tree() { files.modified += 1; } - if let Some(Ok(diff)) = - line_stats.then(|| change.event.diff()).flatten() - { - use git::diff::lines::similar::ChangeTag::*; - let mut nl = 0; - for change in diff - .text(git::diff::lines::Algorithm::Myers) - .iter_all_changes() + if line_stats { + let is_text_file = mime_guess::from_path( + git::path::from_bstr(change.location).as_ref(), + ) + .first_or_text_plain() + .type_() + == mime_guess::mime::TEXT; + if let Some(Ok(diff)) = + is_text_file.then(|| change.event.diff()).flatten() { - match change.tag() { - Delete => { - lines.removed += 1; - nl += 1; + use git::diff::lines::similar::ChangeTag::*; + let mut nl = 0; + for change in diff + .text(git::diff::lines::Algorithm::Myers) + .iter_all_changes() + { + match change.tag() { + Delete => { + lines.removed += 1; + nl += 1; + } + Insert => { + lines.added += 1; + nl += 1 + } + Equal => {} } - Insert => { - lines.added += 1; - nl += 1 - } - Equal => {} } - } - if let Some(c) = lines_counter.as_ref() { - c.fetch_add(nl, Ordering::SeqCst); + if let Some(c) = lines_counter.as_ref() { + c.fetch_add(nl, Ordering::SeqCst); + } } } } From bf63a139292ffe265b1d7bcbbd0c0980dc042e1e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 17:09:19 +0800 Subject: [PATCH 43/48] percentages for files and lines added (#470) --- gitoxide-core/src/hours.rs | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/gitoxide-core/src/hours.rs b/gitoxide-core/src/hours.rs index c11b6217689..81f65042f4b 100644 --- a/gitoxide-core/src/hours.rs +++ b/gitoxide-core/src/hours.rs @@ -373,7 +373,12 @@ where if show_pii { results_by_hours.sort_by(|a, b| a.hours.partial_cmp(&b.hours).unwrap_or(std::cmp::Ordering::Equal)); for entry in results_by_hours.iter() { - entry.write_to(total_hours, file_stats, line_stats, &mut out)?; + entry.write_to( + total_hours, + file_stats.then(|| total_files), + line_stats.then(|| total_lines), + &mut out, + )?; writeln!(out)?; } } @@ -543,8 +548,8 @@ impl WorkByPerson { fn write_to( &self, total_hours: f32, - show_files: bool, - show_lines: bool, + total_files: Option, + total_lines: Option, mut out: impl std::io::Write, ) -> std::io::Result<()> { writeln!( @@ -561,18 +566,23 @@ impl WorkByPerson { self.hours / HOURS_PER_WORKDAY, (self.hours / total_hours) * 100.0 )?; - if show_files { + if let Some(total) = total_files { writeln!( out, - "total files added/removed/modified: {}/{}/{}", - self.files.added, self.files.removed, self.files.modified + "total files added/removed/modified: {}/{}/{} ({:.02}%)", + self.files.added, + self.files.removed, + self.files.modified, + (self.files.sum() / total.sum()) * 100.0 )?; } - if show_lines { + if let Some(total) = total_lines { writeln!( out, - "total lines added/removed: {}/{}", - self.lines.added, self.lines.removed + "total lines added/removed: {}/{} ({:.02}%)", + self.lines.added, + self.lines.removed, + (self.lines.sum() / total.sum()) * 100.0 )?; } Ok(()) @@ -622,6 +632,10 @@ impl FileStats { a.add(other); a } + + fn sum(&self) -> f32 { + (self.added + self.removed + self.modified) as f32 + } } impl LineStats { @@ -636,4 +650,8 @@ impl LineStats { a.add(other); a } + + fn sum(&self) -> f32 { + (self.added + self.removed) as f32 + } } From 47e23703fd158ff3693bde84dd1bec31d1cc9d52 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 17:13:30 +0800 Subject: [PATCH 44/48] Some tests to pin the new statistical capabilities (#470) --- tests/journey/ein.sh | 18 ++++++++++++++++++ .../porcelain/estimate-hours/all-stats-success | 13 +++++++++++++ .../estimate-hours/file-stats-success | 6 ++++++ .../estimate-hours/line-stats-success | 6 ++++++ 4 files changed, 43 insertions(+) create mode 100644 tests/snapshots/porcelain/estimate-hours/all-stats-success create mode 100644 tests/snapshots/porcelain/estimate-hours/file-stats-success create mode 100644 tests/snapshots/porcelain/estimate-hours/line-stats-success diff --git a/tests/journey/ein.sh b/tests/journey/ein.sh index a0d2f7d8e49..88daa9d0852 100644 --- a/tests/journey/ein.sh +++ b/tests/journey/ein.sh @@ -55,6 +55,24 @@ title "Porcelain ${kind}" expect_run_sh $SUCCESSFULLY "$exe t estimate-hours --omit-unify-identities 2>/dev/null" } ) + (with "the --file-stats argument" + it "succeeds and shows file statistics" && { + WITH_SNAPSHOT="$snapshot/file-stats-success" \ + expect_run_sh $SUCCESSFULLY "$exe tool estimate-hours --file-stats 2>/dev/null" + } + ) + (with "the --line-stats argument" + it "succeeds and shows line statistics" && { + WITH_SNAPSHOT="$snapshot/line-stats-success" \ + expect_run_sh $SUCCESSFULLY "$exe tool estimate-hours --line-stats 2>/dev/null" + } + ) + (with "all --stats arguments and pii" + it "succeeds and shows all statistics" && { + WITH_SNAPSHOT="$snapshot/all-stats-success" \ + expect_run_sh $SUCCESSFULLY "$exe tool estimate-hours -pfl 2>/dev/null" + } + ) (with "a branch name that doesn't exist" it "fails and shows a decent enough error message" && { WITH_SNAPSHOT="$snapshot/invalid-branch-name-failure" \ diff --git a/tests/snapshots/porcelain/estimate-hours/all-stats-success b/tests/snapshots/porcelain/estimate-hours/all-stats-success new file mode 100644 index 00000000000..1c30a868651 --- /dev/null +++ b/tests/snapshots/porcelain/estimate-hours/all-stats-success @@ -0,0 +1,13 @@ +Sebastian Thiel +3 commits found +total time spent: 2.00h (0.25 8h days, 100.00%) +total files added/removed/modified: 1/0/1 (100.00%) +total lines added/removed: 1/0 (100.00%) + +total hours: 2.00 +total 8h days: 0.25 +total commits = 3 +total authors: 1 +total files added/removed/modified: 1/0/1 +total lines added/removed: 1/0 +total unique authors: 1 (0.00% duplication) \ No newline at end of file diff --git a/tests/snapshots/porcelain/estimate-hours/file-stats-success b/tests/snapshots/porcelain/estimate-hours/file-stats-success new file mode 100644 index 00000000000..cfdb9d1ffef --- /dev/null +++ b/tests/snapshots/porcelain/estimate-hours/file-stats-success @@ -0,0 +1,6 @@ +total hours: 2.00 +total 8h days: 0.25 +total commits = 3 +total authors: 1 +total files added/removed/modified: 1/0/1 +total unique authors: 1 (0.00% duplication) \ No newline at end of file diff --git a/tests/snapshots/porcelain/estimate-hours/line-stats-success b/tests/snapshots/porcelain/estimate-hours/line-stats-success new file mode 100644 index 00000000000..0d1db43a6fa --- /dev/null +++ b/tests/snapshots/porcelain/estimate-hours/line-stats-success @@ -0,0 +1,6 @@ +total hours: 2.00 +total 8h days: 0.25 +total commits = 3 +total authors: 1 +total lines added/removed: 1/0 +total unique authors: 1 (0.00% duplication) \ No newline at end of file From caa7a1bdef74d7d3166a7e38127a59f5ab3cfbdd Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Sep 2022 17:22:25 +0800 Subject: [PATCH 45/48] update changelogs prior to release (#470) --- CHANGELOG.md | 98 ++++++++++++--- git-actor/CHANGELOG.md | 79 ++++++++---- git-attributes/CHANGELOG.md | 52 ++++++-- git-chunk/CHANGELOG.md | 39 +++++- git-command/CHANGELOG.md | 57 ++++++++- git-commitgraph/CHANGELOG.md | 72 ++++++++--- git-config-value/CHANGELOG.md | 45 ++++++- git-config/CHANGELOG.md | 93 ++++++++++---- git-credentials/CHANGELOG.md | 204 +++++++++++++++++++++++++++++- git-date/CHANGELOG.md | 75 +++++++++-- git-diff/CHANGELOG.md | 45 ++++++- git-discover/CHANGELOG.md | 41 +++++- git-features/CHANGELOG.md | 39 +++++- git-glob/CHANGELOG.md | 43 ++++++- git-hash/CHANGELOG.md | 34 ++++- git-index/CHANGELOG.md | 80 ++++++++++-- git-mailmap/CHANGELOG.md | 60 +++++++-- git-object/CHANGELOG.md | 97 ++++++++++----- git-odb/CHANGELOG.md | 115 +++++++++++------ git-pack/CHANGELOG.md | 93 +++++++++----- git-packetline/CHANGELOG.md | 94 ++++++++++---- git-path/CHANGELOG.md | 63 ++++++++-- git-prompt/CHANGELOG.md | 71 ++++++++++- git-protocol/CHANGELOG.md | 126 ++++++++++++++----- git-quote/CHANGELOG.md | 37 +++++- git-ref/CHANGELOG.md | 102 ++++++++++----- git-refspec/CHANGELOG.md | 120 +++++++++++++++++- git-repository/CHANGELOG.md | 226 ++++++++++++++++++++++++++++++++-- git-revision/CHANGELOG.md | 62 ++++++++-- git-sec/CHANGELOG.md | 81 ++++++++++-- git-tempfile/CHANGELOG.md | 74 ++++++++--- git-transport/CHANGELOG.md | 99 ++++++++++----- git-traverse/CHANGELOG.md | 44 ++++++- git-url/CHANGELOG.md | 84 +++++++++++-- git-validate/CHANGELOG.md | 57 +++++++-- git-worktree/CHANGELOG.md | 55 +++++++-- gitoxide-core/CHANGELOG.md | 161 ++++++++++++++++++++---- tests/tools/CHANGELOG.md | 66 ++++++++-- 38 files changed, 2603 insertions(+), 480 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1062a004600..9974fa8c6ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,67 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed + + - `ein tool hours -s` was split into `-f|--file-stats` and `-l|line-stats`. + That way more information is generated at increasingly high costs. + +### New Features + + - `ein tool hours --stat` to collect additional statistics per author. + Note that these are expensive and unconditionally use threads to speed + up these computations. + - `ein tool hours -b` ignores bots. + For now it only considers bots with names containing `[bot]`. + +### Commit Statistics + + + + - 26 commits contributed to the release over the course of 27 calendar days. + - 27 days passed between releases. + - 3 commits were understood as [conventional](https://www.conventionalcommits.org). + - 2 unique issues were worked on: [#450](https://github.com/Byron/gitoxide/issues/450), [#470](https://github.com/Byron/gitoxide/issues/470) + +### Commit Details + + + +
view details + + * **[#450](https://github.com/Byron/gitoxide/issues/450)** + - refactor ([`11851f3`](https://github.com/Byron/gitoxide/commit/11851f334f642e7bd69bcbfc7ad4f1990fc326ba)) + - option to print server information about the connection ([`4720666`](https://github.com/Byron/gitoxide/commit/4720666c8bfdaa3acc5c832b44755d4b4f86e16e)) + - show fixes as well ([`2237495`](https://github.com/Byron/gitoxide/commit/2237495d82624b39bf75c6430549424a5e36b8bb)) + - Correct printing of tag information (even though it doesn't look great) ([`f4d8198`](https://github.com/Byron/gitoxide/commit/f4d8198992b4c45f64d81e20f40a1cad69883162)) + - wire up the `ref-map` sub-command. ([`94c2b78`](https://github.com/Byron/gitoxide/commit/94c2b785f892f85503b8927c7fa98ae99d677be7)) + - Select `gix` commands will now load the git installation configuration ([`23d2dec`](https://github.com/Byron/gitoxide/commit/23d2dec375305c39d472c4f8ff764274dd033f6b)) + - refactor ([`7abc0a3`](https://github.com/Byron/gitoxide/commit/7abc0a39205b9f374c90c4750fe6cc9b3749d7b9)) + - Add sketch of `gix credential` ([`642e21f`](https://github.com/Byron/gitoxide/commit/642e21fc58d8d4b68cba3067c88d44c019ec4ace)) + * **[#470](https://github.com/Byron/gitoxide/issues/470)** + - `ein tool hours -s` was split into `-f|--file-stats` and `-l|line-stats`. ([`3c7c9a7`](https://github.com/Byron/gitoxide/commit/3c7c9a735f5771ef787cbc86b46cbafc9226f4d6)) + - upgrade to prodash 20.1 for `Progress::counter()` feature ([`0ac4a2c`](https://github.com/Byron/gitoxide/commit/0ac4a2c514aeb94d8e90ce28ae7a0e0350c21ab2)) + - `ein tool hours --stat` to collect additional statistics per author. ([`28c4cae`](https://github.com/Byron/gitoxide/commit/28c4cae70aab2bd5b479961fcc6ee91ff80f651b)) + * **Uncategorized** + - use rev-specs instead of ref-names ([`cf7182e`](https://github.com/Byron/gitoxide/commit/cf7182e3390c03df97c10cd101440f7aa8874904)) + - `ein tool hours -b` ignores bots. ([`5d0332f`](https://github.com/Byron/gitoxide/commit/5d0332f51c63c5456a28c8f3f466ad805b2e0626)) + - Merge branch 'index-from-tree' ([`172f73c`](https://github.com/Byron/gitoxide/commit/172f73cf26878d153d51790fa01853fa4ba6beb7)) + - make fmt ([`535e967`](https://github.com/Byron/gitoxide/commit/535e967666c6da657ff1b7eff7c64ab27cafb182)) + - Merge branch 'main' into filter-refs-by-spec ([`9aa1d3d`](https://github.com/Byron/gitoxide/commit/9aa1d3dc46d4b1c76af257f573aff3aeef2d3fa8)) + - Merge branch 'main' into filter-refs-by-spec ([`1f6e5ab`](https://github.com/Byron/gitoxide/commit/1f6e5ab15f5fd8d23719b13e6aea59cd231ac0fe)) + - Merge branch 'git_date_parse' ([`75591fb`](https://github.com/Byron/gitoxide/commit/75591fb108ce440ba2f920bebf99158b407e3046)) + - Merge branch 'main' into filter-refs-by-spec ([`51dc828`](https://github.com/Byron/gitoxide/commit/51dc8282fb77b519ff7d2c94c6bd73af306cfe8b)) + - Merge branch 'macos-exfat' ([`f256f8f`](https://github.com/Byron/gitoxide/commit/f256f8fb7603f83d44acda07386f277c65ac652c)) + - Merge branch 'main' into filter-refs-by-spec ([`56ba481`](https://github.com/Byron/gitoxide/commit/56ba481f4c48f74f10397feb1b6dc3d7dd3704fb)) + - A basic implementation of rev-list without anything fancy ([`791dd66`](https://github.com/Byron/gitoxide/commit/791dd666430fe0586c7db75b352487a72d3789e7)) + - Merge branch 'main' into filter-refs-by-spec ([`a36c05d`](https://github.com/Byron/gitoxide/commit/a36c05d281269f3f8b297e7adc463bfb3c306663)) + - Merge branch 'main' into filter-refs-by-spec ([`cef0b51`](https://github.com/Byron/gitoxide/commit/cef0b51ade2a3301fa09ede7a425aa1fe3527e78)) + - Merge branch 'main' into filter-refs-by-spec ([`dbfa328`](https://github.com/Byron/gitoxide/commit/dbfa3282cf876596b250b2040c1ec0b761741796)) + - Merge branch 'main' into filter-refs-by-spec ([`cfa1440`](https://github.com/Byron/gitoxide/commit/cfa144031dbcac2707ab0cec012bc35e78f9c475)) +
+ ## 0.15.0 (2022-08-24) @@ -33,9 +94,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 14 commits contributed to the release over the course of 3 calendar days. + - 15 commits contributed to the release over the course of 3 calendar days. - 6 days passed between releases. - - 5 commits where understood as [conventional](https://www.conventionalcommits.org). + - 5 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#450](https://github.com/Byron/gitoxide/issues/450) ### Thanks Clippy @@ -51,6 +112,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details * **[#450](https://github.com/Byron/gitoxide/issues/450)** + - Support for -c CLI config overrides in `gix config`. ([`19c1746`](https://github.com/Byron/gitoxide/commit/19c1746cefca9bc76537637ec99634eb4cf66a92)) - remove `gix free remote ref-list` in favor of `gix remote refs` ([`dda9957`](https://github.com/Byron/gitoxide/commit/dda995790c260131048484a11e66185b9c841311)) - Support for `-c/--config` in `gix` ([`45a30f0`](https://github.com/Byron/gitoxide/commit/45a30f0f31a99cda5cf105408e9c3905f43071f2)) - refactor ([`e0be6e9`](https://github.com/Byron/gitoxide/commit/e0be6e9558add3255de63f3785306daace2707a6)) @@ -95,7 +157,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 15 commits contributed to the release over the course of 26 calendar days. - 26 days passed between releases. - - 5 commits where understood as [conventional](https://www.conventionalcommits.org). + - 5 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#427](https://github.com/Byron/gitoxide/issues/427) ### Commit Details @@ -137,7 +199,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 38 commits contributed to the release over the course of 101 calendar days. - 108 days passed between releases. - - 2 commits where understood as [conventional](https://www.conventionalcommits.org). + - 2 commits were understood as [conventional](https://www.conventionalcommits.org). - 3 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#331](https://github.com/Byron/gitoxide/issues/331), [#427](https://github.com/Byron/gitoxide/issues/427) ### Thanks Clippy @@ -209,7 +271,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 5 commits contributed to the release over the course of 1 calendar day. - 2 days passed between releases. - - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 1 commit was understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#298](https://github.com/Byron/gitoxide/issues/298) ### Commit Details @@ -269,7 +331,7 @@ which usually are `Clone` too as they are passed by immutable reference (which i - 61 commits contributed to the release over the course of 126 calendar days. - 165 days passed between releases. - - 10 commits where understood as [conventional](https://www.conventionalcommits.org). + - 10 commits were understood as [conventional](https://www.conventionalcommits.org). - 12 unique issues were worked on: [#215](https://github.com/Byron/gitoxide/issues/215), [#263](https://github.com/Byron/gitoxide/issues/263), [#266](https://github.com/Byron/gitoxide/issues/266), [#279](https://github.com/Byron/gitoxide/issues/279), [#287](https://github.com/Byron/gitoxide/issues/287), [#289](https://github.com/Byron/gitoxide/issues/289), [#293](https://github.com/Byron/gitoxide/issues/293), [#298](https://github.com/Byron/gitoxide/issues/298), [#301](https://github.com/Byron/gitoxide/issues/301), [#329](https://github.com/Byron/gitoxide/issues/329), [#366](https://github.com/Byron/gitoxide/issues/366), [#67](https://github.com/Byron/gitoxide/issues/67) ### Commit Details @@ -368,7 +430,7 @@ to the `clap-derive` crate. - 1 commit contributed to the release over the course of 1 calendar day. - 4 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#222](https://github.com/Byron/gitoxide/issues/222) ### Commit Details @@ -411,7 +473,7 @@ A first usable version of `git-repository` to make using `gitoxide` from your ap - 4 commits contributed to the release over the course of 26 calendar days. - 35 days passed between releases. - - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 1 commit was understood as [conventional](https://www.conventionalcommits.org). - 2 unique issues were worked on: [#200](https://github.com/Byron/gitoxide/issues/200), [#67](https://github.com/Byron/gitoxide/issues/67) ## v0.8.4 (2021-09-10) @@ -426,7 +488,7 @@ This is a maintenance release. - 1 commit contributed to the release over the course of 8 calendar days. - 20 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ## v0.8.2 (2021-08-17) @@ -437,7 +499,7 @@ This is a maintenance release. - 1 commit contributed to the release over the course of 1 calendar day. - 1 day passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ## v0.8.1 (2021-08-15) @@ -448,7 +510,7 @@ This is a maintenance release. - 43 commits contributed to the release over the course of 95 calendar days. - 98 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#83](https://github.com/Byron/gitoxide/issues/83) ### Thanks Clippy @@ -465,7 +527,7 @@ This is a maintenance release. - 33 commits contributed to the release over the course of 128 calendar days. - 143 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Thanks Clippy @@ -497,7 +559,7 @@ Maintenance release without any new features. - 3 commits contributed to the release over the course of 65 calendar days. - 84 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -520,7 +582,7 @@ Maintenance release without any new features. - 1 commit contributed to the release over the course of 1 calendar day. - 3 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ## v0.4.1 (2020-09-18) @@ -533,7 +595,7 @@ Maintenance release without any new features. - 2 commits contributed to the release over the course of 6 calendar days. - 6 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -561,7 +623,7 @@ Maintenance release without any new features. - 14 commits contributed to the release over the course of 29 calendar days. - 30 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -600,7 +662,7 @@ Many small and possibly breaking changes are not mentioned here. - 46 commits contributed to the release over the course of 30 calendar days. - 31 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -667,7 +729,7 @@ Many small and possibly breaking changes are not mentioned here. - 53 commits contributed to the release over the course of 765 calendar days. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details diff --git a/git-actor/CHANGELOG.md b/git-actor/CHANGELOG.md index 935a834deaa..67c1a4f7e10 100644 --- a/git-actor/CHANGELOG.md +++ b/git-actor/CHANGELOG.md @@ -5,6 +5,34 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed (BREAKING) + + - upgrade `bstr` to `1.0.1` + +### Commit Statistics + + + + - 3 commits contributed to the release over the course of 15 calendar days. + - 27 days passed between releases. + - 1 commit was understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#450](https://github.com/Byron/gitoxide/issues/450) + +### Commit Details + + + +
view details + + * **[#450](https://github.com/Byron/gitoxide/issues/450)** + - upgrade `bstr` to `1.0.1` ([`99905ba`](https://github.com/Byron/gitoxide/commit/99905bacace8aed42b16d43f0f04cae996cb971c)) + * **Uncategorized** + - Merge branch 'index-from-tree' ([`172f73c`](https://github.com/Byron/gitoxide/commit/172f73cf26878d153d51790fa01853fa4ba6beb7)) + - Release git-features v0.22.4, git-url v0.8.0, safety bump 4 crates ([`1d4600a`](https://github.com/Byron/gitoxide/commit/1d4600ae51475c2e225f96c16c41e2c4a2b3f2aa)) +
+ ## 0.11.4 (2022-08-24) A maintenance release without user facing changes. @@ -13,8 +41,8 @@ A maintenance release without user facing changes. - - 2 commits contributed to the release. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 3 commits contributed to the release. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -24,6 +52,7 @@ A maintenance release without user facing changes.
view details * **Uncategorized** + - Release git-date v0.1.0, git-actor v0.11.4, git-revision v0.4.3, git-repository v0.22.1, cargo-smart-release v0.11.0, git-commitgraph v0.8.2, gitoxide-core v0.17.0, gitoxide v0.15.0 ([`1fb931a`](https://github.com/Byron/gitoxide/commit/1fb931a7ea59f1cf895a6c1392fd8615b723c743)) - update changelogs prior to release ([`23cb58f`](https://github.com/Byron/gitoxide/commit/23cb58f02043e0e5027136fd6e8e724c03a2efbe)) - adjust to new version of git-date ([`b3fe26b`](https://github.com/Byron/gitoxide/commit/b3fe26bf03db7e1babb5ffbc89d71bf9614e3df3))
@@ -47,7 +76,7 @@ A maintenance release without user facing changes. - 5 commits contributed to the release over the course of 3 calendar days. - 4 days passed between releases. - - 3 commits where understood as [conventional](https://www.conventionalcommits.org). + - 3 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -74,7 +103,7 @@ A maintenance release with a dependency update. - 3 commits contributed to the release. - 1 day passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -103,7 +132,7 @@ A maintenance release with a dependency update. - 6 commits contributed to the release over the course of 18 calendar days. - 26 days passed between releases. - - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 1 commit was understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#427](https://github.com/Byron/gitoxide/issues/427) ### Commit Details @@ -141,7 +170,7 @@ A maintenance release with a dependency update. - 3 commits contributed to the release. - 39 days passed between releases. - - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 1 commit was understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) ### Commit Details @@ -167,7 +196,7 @@ A maintenance release without user-facing changes. - 4 commits contributed to the release over the course of 5 calendar days. - 25 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#427](https://github.com/Byron/gitoxide/issues/427) ### Commit Details @@ -194,7 +223,7 @@ A maintenance release without user-facing changes. - 3 commits contributed to the release over the course of 36 calendar days. - 45 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#301](https://github.com/Byron/gitoxide/issues/301) ### Commit Details @@ -237,7 +266,7 @@ A maintenance release without user-facing changes. - 16 commits contributed to the release over the course of 55 calendar days. - 55 days passed between releases. - - 6 commits where understood as [conventional](https://www.conventionalcommits.org). + - 6 commits were understood as [conventional](https://www.conventionalcommits.org). - 4 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#329](https://github.com/Byron/gitoxide/issues/329), [#364](https://github.com/Byron/gitoxide/issues/364), [#366](https://github.com/Byron/gitoxide/issues/366) ### Commit Details @@ -281,7 +310,7 @@ A maintenance release without user-facing changes. - 3 commits contributed to the release over the course of 12 calendar days. - 13 days passed between releases. - - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 1 commit was understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#329](https://github.com/Byron/gitoxide/issues/329) ### Commit Details @@ -307,7 +336,7 @@ A maintenance release thanks to upgraded dependencies. - 7 commits contributed to the release over the course of 35 calendar days. - 55 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -338,7 +367,7 @@ Maintenance release due, which isn't really required but one now has to be caref - 5 commits contributed to the release over the course of 25 calendar days. - 40 days passed between releases. - - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 1 commit was understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#250](https://github.com/Byron/gitoxide/issues/250) ### Commit Details @@ -366,7 +395,7 @@ A maintenance release due to properly dealing with previously breaking changes i - 2 commits contributed to the release. - 3 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#222](https://github.com/Byron/gitoxide/issues/222) ### Commit Details @@ -391,7 +420,7 @@ This release contains no functional changes. - 9 commits contributed to the release over the course of 5 calendar days. - 36 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#198](https://github.com/Byron/gitoxide/issues/198) ### Commit Details @@ -421,7 +450,7 @@ This release contains no functional changes. - 3 commits contributed to the release. - 1 day passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -444,7 +473,7 @@ This release contains no functional changes. - 6 commits contributed to the release over the course of 6 calendar days. - 10 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -470,7 +499,7 @@ This release contains no functional changes. - 4 commits contributed to the release over the course of 1 calendar day. - 1 day passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -494,7 +523,7 @@ This release contains no functional changes. - 6 commits contributed to the release over the course of 6 calendar days. - 8 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -520,7 +549,7 @@ This release contains no functional changes. - 2 commits contributed to the release over the course of 1 calendar day. - 3 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -542,7 +571,7 @@ This release contains no functional changes. - 2 commits contributed to the release. - 1 day passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -563,7 +592,7 @@ This release contains no functional changes. - 2 commits contributed to the release. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -584,7 +613,7 @@ This release contains no functional changes. - 2 commits contributed to the release. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -606,7 +635,7 @@ This release contains no functional changes. - 14 commits contributed to the release over the course of 45 calendar days. - 46 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -639,7 +668,7 @@ This release contains no functional changes. - 2 commits contributed to the release. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -660,7 +689,7 @@ This release contains no functional changes. - 4 commits contributed to the release. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Thanks Clippy diff --git a/git-attributes/CHANGELOG.md b/git-attributes/CHANGELOG.md index 6f60a20b674..097c5b93bce 100644 --- a/git-attributes/CHANGELOG.md +++ b/git-attributes/CHANGELOG.md @@ -5,6 +5,41 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed (BREAKING) + + - upgrade `bstr` to `1.0.1` + +### Commit Statistics + + + + - 10 commits contributed to the release over the course of 21 calendar days. + - 24 days passed between releases. + - 1 commit was understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#450](https://github.com/Byron/gitoxide/issues/450) + +### Commit Details + + + +
view details + + * **[#450](https://github.com/Byron/gitoxide/issues/450)** + - upgrade `bstr` to `1.0.1` ([`99905ba`](https://github.com/Byron/gitoxide/commit/99905bacace8aed42b16d43f0f04cae996cb971c)) + * **Uncategorized** + - Merge branch 'index-from-tree' ([`172f73c`](https://github.com/Byron/gitoxide/commit/172f73cf26878d153d51790fa01853fa4ba6beb7)) + - Merge branch 'main' into filter-refs-by-spec ([`9aa1d3d`](https://github.com/Byron/gitoxide/commit/9aa1d3dc46d4b1c76af257f573aff3aeef2d3fa8)) + - Release git-features v0.22.4, git-url v0.8.0, safety bump 4 crates ([`1d4600a`](https://github.com/Byron/gitoxide/commit/1d4600ae51475c2e225f96c16c41e2c4a2b3f2aa)) + - Merge branch 'main' into filter-refs-by-spec ([`1f6e5ab`](https://github.com/Byron/gitoxide/commit/1f6e5ab15f5fd8d23719b13e6aea59cd231ac0fe)) + - Merge branch 'git_date_parse' ([`75591fb`](https://github.com/Byron/gitoxide/commit/75591fb108ce440ba2f920bebf99158b407e3046)) + - Merge branch 'main' into filter-refs-by-spec ([`51dc828`](https://github.com/Byron/gitoxide/commit/51dc8282fb77b519ff7d2c94c6bd73af306cfe8b)) + - Merge branch 'main' into filter-refs-by-spec ([`56ba481`](https://github.com/Byron/gitoxide/commit/56ba481f4c48f74f10397feb1b6dc3d7dd3704fb)) + - Merge branch 'main' into filter-refs-by-spec ([`a36c05d`](https://github.com/Byron/gitoxide/commit/a36c05d281269f3f8b297e7adc463bfb3c306663)) + - Release git-path v0.4.2, git-config-value v0.7.0 ([`c48fb31`](https://github.com/Byron/gitoxide/commit/c48fb3107d29f9a06868b0c6de40567063a656d1)) +
+ ## 0.3.3 (2022-08-27) Maintenance release without user-facing changes. @@ -13,9 +48,9 @@ Maintenance release without user-facing changes. - - 2 commits contributed to the release. + - 3 commits contributed to the release. - 3 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -25,6 +60,7 @@ Maintenance release without user-facing changes.
view details * **Uncategorized** + - Release git-attributes v0.3.3, git-ref v0.15.3, git-index v0.4.3, git-worktree v0.4.3, git-testtools v0.8.0 ([`baad4ce`](https://github.com/Byron/gitoxide/commit/baad4ce51fe0e8c0c1de1b08148d8303878ca37b)) - prepare changelogs prior to release of git-testtools ([`7668e38`](https://github.com/Byron/gitoxide/commit/7668e38fab8891ed7e73fae3a6f5a8772e0f0d0b)) - Release git-features v0.22.3, git-revision v0.4.4 ([`c2660e2`](https://github.com/Byron/gitoxide/commit/c2660e2503323531ba02519eaa51124ee22fec51))
@@ -50,7 +86,7 @@ Maintenance release without user-facing changes. - 7 commits contributed to the release over the course of 3 calendar days. - 6 days passed between releases. - - 4 commits where understood as [conventional](https://www.conventionalcommits.org). + - 4 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -79,7 +115,7 @@ A maintenance release without user facing changes. - 9 commits contributed to the release over the course of 24 calendar days. - 26 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#427](https://github.com/Byron/gitoxide/issues/427) ### Commit Details @@ -111,7 +147,7 @@ This is a maintenance release with no functional changes. - 20 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -153,7 +189,7 @@ A maintenance release without user-facing changes. - 3 commits contributed to the release over the course of 16 calendar days. - 25 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -180,7 +216,7 @@ A maintenance release without user-facing changes. - 79 commits contributed to the release over the course of 61 calendar days. - 62 days passed between releases. - - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 1 commit was understood as [conventional](https://www.conventionalcommits.org). - 2 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#366](https://github.com/Byron/gitoxide/issues/366) ### Thanks Clippy @@ -288,7 +324,7 @@ Initial release with no content. - 3 commits contributed to the release. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#301](https://github.com/Byron/gitoxide/issues/301) ### Commit Details diff --git a/git-chunk/CHANGELOG.md b/git-chunk/CHANGELOG.md index eef8763d730..7471d529b84 100644 --- a/git-chunk/CHANGELOG.md +++ b/git-chunk/CHANGELOG.md @@ -5,8 +5,36 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +Maintenance release without observable changes. + +### Commit Statistics + + + + - 3 commits contributed to the release over the course of 14 calendar days. + - 27 days passed between releases. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#450](https://github.com/Byron/gitoxide/issues/450) + +### Commit Details + + + +
view details + + * **[#450](https://github.com/Byron/gitoxide/issues/450)** + - replace `quick-error` with `thiserror` ([`bc45906`](https://github.com/Byron/gitoxide/commit/bc45906ea38adb82a7179cb6b92f7bc34b7e0371)) + * **Uncategorized** + - Merge branch 'index-from-tree' ([`172f73c`](https://github.com/Byron/gitoxide/commit/172f73cf26878d153d51790fa01853fa4ba6beb7)) + - Merge branch 'git_date_relative' ([`83a3832`](https://github.com/Byron/gitoxide/commit/83a38329c59e9ebc057221da832fd8320bbeddb1)) +
+ ## 0.3.1 (2022-08-24) + + ### Chore - uniformize deny attributes @@ -15,9 +43,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 4 commits contributed to the release over the course of 211 calendar days. + - 5 commits contributed to the release over the course of 211 calendar days. - 212 days passed between releases. - - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 1 commit was understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -27,6 +55,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details * **Uncategorized** + - Release git-date v0.0.5, git-hash v0.9.8, git-features v0.22.2, git-actor v0.11.3, git-glob v0.3.2, git-quote v0.2.1, git-attributes v0.3.2, git-tempfile v2.0.4, git-lock v2.1.1, git-validate v0.5.5, git-object v0.20.2, git-ref v0.15.2, git-sec v0.3.1, git-config v0.7.0, git-credentials v0.4.0, git-diff v0.17.2, git-discover v0.4.1, git-bitmap v0.1.2, git-index v0.4.2, git-mailmap v0.3.2, git-chunk v0.3.1, git-traverse v0.16.2, git-pack v0.21.2, git-odb v0.31.2, git-packetline v0.12.7, git-url v0.7.2, git-transport v0.19.2, git-protocol v0.19.0, git-revision v0.4.2, git-refspec v0.1.0, git-worktree v0.4.2, git-repository v0.22.0, safety bump 4 crates ([`4974eca`](https://github.com/Byron/gitoxide/commit/4974eca96d525d1ee4f8cad79bb713af7a18bf9d)) - Merge branch 'example-new-repo' ([`946dd3a`](https://github.com/Byron/gitoxide/commit/946dd3a80522ef437e09528a93aa1433f01b0ee8)) - uniformize deny attributes ([`f7f136d`](https://github.com/Byron/gitoxide/commit/f7f136dbe4f86e7dee1d54835c420ec07c96cd78)) - Merge branch 'AP2008-implement-worktree' ([`f32c669`](https://github.com/Byron/gitoxide/commit/f32c669bc519d59a1f1d90d61cc48a422c86aede)) @@ -61,7 +90,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 19 commits contributed to the release over the course of 33 calendar days. - 34 days passed between releases. - - 6 commits where understood as [conventional](https://www.conventionalcommits.org). + - 6 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#279](https://github.com/Byron/gitoxide/issues/279) ### Commit Details @@ -112,7 +141,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 6 commits contributed to the release. - - 2 commits where understood as [conventional](https://www.conventionalcommits.org). + - 2 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#279](https://github.com/Byron/gitoxide/issues/279) ### Commit Details @@ -140,7 +169,7 @@ Initial release with enough functionality to handle multi-pack indices and commi - 8 commits contributed to the release. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#279](https://github.com/Byron/gitoxide/issues/279) ### Thanks Clippy diff --git a/git-command/CHANGELOG.md b/git-command/CHANGELOG.md index bebed83198f..0184e72372e 100644 --- a/git-command/CHANGELOG.md +++ b/git-command/CHANGELOG.md @@ -5,6 +5,57 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +The first usable release. + +### Commit Statistics + + + + - 22 commits contributed to the release over the course of 26 calendar days. + - 26 days passed between releases. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#450](https://github.com/Byron/gitoxide/issues/450) + +### Thanks Clippy + + + +[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. + +### Commit Details + + + +
view details + + * **[#450](https://github.com/Byron/gitoxide/issues/450)** + - Allow programs to communicate errors by default ([`5a2168e`](https://github.com/Byron/gitoxide/commit/5a2168e62f664d463fc8849efecccf7e90b382cd)) + - fix docs ([`f86364c`](https://github.com/Byron/gitoxide/commit/f86364c4e2d9efd04027978679232946494a4734)) + - fix CI ([`6565b97`](https://github.com/Byron/gitoxide/commit/6565b97d7d293ae881590960bf3e29f46fdb2cd1)) + - remove `allow prompt` builder method as typical prompt implementations don't need it ([`0236d75`](https://github.com/Byron/gitoxide/commit/0236d753805003d5a09505fab7da0b5b47392c45)) + - A builder method to allow prompts specifically ([`3be1fc7`](https://github.com/Byron/gitoxide/commit/3be1fc7d97f87893cecbe5d880576ab690bb205f)) + - Only actually use the shell if it appears to be required ([`830ee07`](https://github.com/Byron/gitoxide/commit/830ee07d943725e55a40a546b3a1b7ecefb75c4b)) + - support for multiple arguments with shell-script support ([`d8e8b54`](https://github.com/Byron/gitoxide/commit/d8e8b541bd776a267aca6dbfb8e7e793e264885b)) + - Squelch errors by default ([`1cb2e96`](https://github.com/Byron/gitoxide/commit/1cb2e967416b0fa5c6d32a0ad0b015b41f81e92c)) + - Add a way to transform a `Prepare` into a `Command` for even more flexibility ([`eeedd2c`](https://github.com/Byron/gitoxide/commit/eeedd2cab3c201109aa5bd986eb38c1f31d5fd20)) + - set version to 0.1 to avoid surprises like happened with `git-date` ([`1322f72`](https://github.com/Byron/gitoxide/commit/1322f72fd2bd310c1c3c859ee4b49f47cdfaf100)) + - add remaining docs ([`6a39e62`](https://github.com/Byron/gitoxide/commit/6a39e62bb4aebf9c48daddf007c95b2117b4454d)) + - basic support for 'sh' based execution ([`8c61b0b`](https://github.com/Byron/gitoxide/commit/8c61b0bded71dff223e24ae68f8cf7fc50195ce9)) + - First sketch of git-command API ([`cd4a608`](https://github.com/Byron/gitoxide/commit/cd4a608f0b8ef3adeb7a7f1979f653b63e77ad4d)) + * **Uncategorized** + - Merge branch 'index-from-tree' ([`172f73c`](https://github.com/Byron/gitoxide/commit/172f73cf26878d153d51790fa01853fa4ba6beb7)) + - make fmt ([`535e967`](https://github.com/Byron/gitoxide/commit/535e967666c6da657ff1b7eff7c64ab27cafb182)) + - Merge branch 'main' into filter-refs-by-spec ([`9aa1d3d`](https://github.com/Byron/gitoxide/commit/9aa1d3dc46d4b1c76af257f573aff3aeef2d3fa8)) + - Merge branch 'main' into filter-refs-by-spec ([`1f6e5ab`](https://github.com/Byron/gitoxide/commit/1f6e5ab15f5fd8d23719b13e6aea59cd231ac0fe)) + - Merge branch 'main' into filter-refs-by-spec ([`51dc828`](https://github.com/Byron/gitoxide/commit/51dc8282fb77b519ff7d2c94c6bd73af306cfe8b)) + - Merge branch 'main' into filter-refs-by-spec ([`56ba481`](https://github.com/Byron/gitoxide/commit/56ba481f4c48f74f10397feb1b6dc3d7dd3704fb)) + - Merge branch 'main' into filter-refs-by-spec ([`a36c05d`](https://github.com/Byron/gitoxide/commit/a36c05d281269f3f8b297e7adc463bfb3c306663)) + - Merge branch 'main' into filter-refs-by-spec ([`cef0b51`](https://github.com/Byron/gitoxide/commit/cef0b51ade2a3301fa09ede7a425aa1fe3527e78)) + - thanks clippy ([`0dc1da5`](https://github.com/Byron/gitoxide/commit/0dc1da5e636b2eecc26fcfa0ecd814af3b78ed29)) +
+ ## 0.0.0 (2022-08-25) Initial release to reserve the name. @@ -13,8 +64,8 @@ Initial release to reserve the name. - - 2 commits contributed to the release. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 3 commits contributed to the release. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#450](https://github.com/Byron/gitoxide/issues/450) ### Commit Details @@ -26,5 +77,7 @@ Initial release to reserve the name. * **[#450](https://github.com/Byron/gitoxide/issues/450)** - prepare changelog prior to release ([`579e8f1`](https://github.com/Byron/gitoxide/commit/579e8f138963a057d87837301b097fd804424447)) - first frame of `git-command` crate ([`436632a`](https://github.com/Byron/gitoxide/commit/436632a3822d3671c073cdbbbaf8e569de62bb09)) + * **Uncategorized** + - Release git-command v0.0.0 ([`6c27e94`](https://github.com/Byron/gitoxide/commit/6c27e94c8ed6fb6155704a04d876ab6129b3b413))
diff --git a/git-commitgraph/CHANGELOG.md b/git-commitgraph/CHANGELOG.md index 78bbd6af9db..9e989fc24b3 100644 --- a/git-commitgraph/CHANGELOG.md +++ b/git-commitgraph/CHANGELOG.md @@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed (BREAKING) + + - upgrade `bstr` to `1.0.1` + +### Commit Statistics + + + + - 6 commits contributed to the release over the course of 24 calendar days. + - 27 days passed between releases. + - 1 commit was understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#450](https://github.com/Byron/gitoxide/issues/450) + +### Commit Details + + + +
view details + + * **[#450](https://github.com/Byron/gitoxide/issues/450)** + - upgrade `bstr` to `1.0.1` ([`99905ba`](https://github.com/Byron/gitoxide/commit/99905bacace8aed42b16d43f0f04cae996cb971c)) + * **Uncategorized** + - Merge branch 'index-from-tree' ([`172f73c`](https://github.com/Byron/gitoxide/commit/172f73cf26878d153d51790fa01853fa4ba6beb7)) + - Release git-features v0.22.4, git-url v0.8.0, safety bump 4 crates ([`1d4600a`](https://github.com/Byron/gitoxide/commit/1d4600ae51475c2e225f96c16c41e2c4a2b3f2aa)) + - Merge branch 'git_date_parse' ([`75591fb`](https://github.com/Byron/gitoxide/commit/75591fb108ce440ba2f920bebf99158b407e3046)) + - Release git-hash v0.9.9 ([`da0716f`](https://github.com/Byron/gitoxide/commit/da0716f8c27b4f29cfff0e5ce7fcb3d7240f4aeb)) + - Release git-features v0.22.3, git-revision v0.4.4 ([`c2660e2`](https://github.com/Byron/gitoxide/commit/c2660e2503323531ba02519eaa51124ee22fec51)) +
+ ## 0.8.2 (2022-08-24) @@ -22,9 +53,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 6 commits contributed to the release over the course of 3 calendar days. + - 7 commits contributed to the release over the course of 3 calendar days. - 6 days passed between releases. - - 3 commits where understood as [conventional](https://www.conventionalcommits.org). + - 3 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -34,6 +65,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details * **Uncategorized** + - Release git-date v0.1.0, git-actor v0.11.4, git-revision v0.4.3, git-repository v0.22.1, cargo-smart-release v0.11.0, git-commitgraph v0.8.2, gitoxide-core v0.17.0, gitoxide v0.15.0 ([`1fb931a`](https://github.com/Byron/gitoxide/commit/1fb931a7ea59f1cf895a6c1392fd8615b723c743)) - update changelogs prior to release ([`23cb58f`](https://github.com/Byron/gitoxide/commit/23cb58f02043e0e5027136fd6e8e724c03a2efbe)) - Release git-date v0.0.5, git-hash v0.9.8, git-features v0.22.2, git-actor v0.11.3, git-glob v0.3.2, git-quote v0.2.1, git-attributes v0.3.2, git-tempfile v2.0.4, git-lock v2.1.1, git-validate v0.5.5, git-object v0.20.2, git-ref v0.15.2, git-sec v0.3.1, git-config v0.7.0, git-credentials v0.4.0, git-diff v0.17.2, git-discover v0.4.1, git-bitmap v0.1.2, git-index v0.4.2, git-mailmap v0.3.2, git-chunk v0.3.1, git-traverse v0.16.2, git-pack v0.21.2, git-odb v0.31.2, git-packetline v0.12.7, git-url v0.7.2, git-transport v0.19.2, git-protocol v0.19.0, git-revision v0.4.2, git-refspec v0.1.0, git-worktree v0.4.2, git-repository v0.22.0, safety bump 4 crates ([`4974eca`](https://github.com/Byron/gitoxide/commit/4974eca96d525d1ee4f8cad79bb713af7a18bf9d)) - Merge branch 'example-new-repo' ([`946dd3a`](https://github.com/Byron/gitoxide/commit/946dd3a80522ef437e09528a93aa1433f01b0ee8)) @@ -52,7 +84,7 @@ A maintenance release without user-facing changes. - 4 commits contributed to the release over the course of 26 calendar days. - 26 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -78,7 +110,7 @@ A maintenance release without user-facing changes. - 11 commits contributed to the release over the course of 99 calendar days. - 110 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#384](https://github.com/Byron/gitoxide/issues/384) ### Commit Details @@ -122,7 +154,7 @@ A maintenance release, triggered by putting too many adjustments into a single c - 31 commits contributed to the release over the course of 125 calendar days. - 165 days passed between releases. - - 2 commits where understood as [conventional](https://www.conventionalcommits.org). + - 2 commits were understood as [conventional](https://www.conventionalcommits.org). - 3 unique issues were worked on: [#279](https://github.com/Byron/gitoxide/issues/279), [#293](https://github.com/Byron/gitoxide/issues/293), [#329](https://github.com/Byron/gitoxide/issues/329) ### Thanks Clippy @@ -184,7 +216,7 @@ A maintenance release due to properly dealing with previously breaking changes i - 3 commits contributed to the release. - 3 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#222](https://github.com/Byron/gitoxide/issues/222) ### Commit Details @@ -210,7 +242,7 @@ This is a maintenance release without functional changes. - 4 commits contributed to the release over the course of 1 calendar day. - 38 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#198](https://github.com/Byron/gitoxide/issues/198) ### Commit Details @@ -235,7 +267,7 @@ This is a maintenance release without functional changes. - 2 commits contributed to the release over the course of 1 calendar day. - 8 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -257,7 +289,7 @@ This is a maintenance release without functional changes. - 4 commits contributed to the release over the course of 10 calendar days. - 12 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -281,7 +313,7 @@ This is a maintenance release without functional changes. - 3 commits contributed to the release over the course of 1 calendar day. - 2 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -311,7 +343,7 @@ This is a maintenance release without functional changes. - 27 commits contributed to the release over the course of 119 calendar days. - 128 days passed between releases. - - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 1 commit was understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Thanks Clippy @@ -364,7 +396,7 @@ This is a maintenance release without functional changes. - 10 commits contributed to the release over the course of 1 calendar day. - 56 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#63](https://github.com/Byron/gitoxide/issues/63) ### Commit Details @@ -395,7 +427,7 @@ This is a maintenance release without functional changes. - 3 commits contributed to the release over the course of 18 calendar days. - 32 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -418,7 +450,7 @@ This is a maintenance release without functional changes. - 5 commits contributed to the release over the course of 23 calendar days. - 24 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -442,7 +474,7 @@ This is a maintenance release without functional changes. - 2 commits contributed to the release. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -464,7 +496,7 @@ This is a maintenance release without functional changes. - 20 commits contributed to the release over the course of 64 calendar days. - 74 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Thanks Clippy @@ -509,7 +541,7 @@ This is a maintenance release without functional changes. - 2 commits contributed to the release. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -530,7 +562,7 @@ This is a maintenance release without functional changes. - 3 commits contributed to the release. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -553,7 +585,7 @@ This is a maintenance release without functional changes. - 27 commits contributed to the release over the course of 15 calendar days. - 42 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -599,7 +631,7 @@ This is a maintenance release without functional changes. - 1 commit contributed to the release. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details diff --git a/git-config-value/CHANGELOG.md b/git-config-value/CHANGELOG.md index f075712780c..746f730f2fe 100644 --- a/git-config-value/CHANGELOG.md +++ b/git-config-value/CHANGELOG.md @@ -5,6 +5,46 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### New Features + + - `Boolean::try_from(OsString)` + This makes getting booleans from the environment easier. + +### Changed (BREAKING) + + - upgrade `bstr` to `1.0.1` + +### Commit Statistics + + + + - 10 commits contributed to the release over the course of 21 calendar days. + - 21 days passed between releases. + - 2 commits were understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#450](https://github.com/Byron/gitoxide/issues/450) + +### Commit Details + + + +
view details + + * **[#450](https://github.com/Byron/gitoxide/issues/450)** + - upgrade `bstr` to `1.0.1` ([`99905ba`](https://github.com/Byron/gitoxide/commit/99905bacace8aed42b16d43f0f04cae996cb971c)) + - `Boolean::try_from(OsString)` ([`5f675d3`](https://github.com/Byron/gitoxide/commit/5f675d387e52a75ff7bd17a38516ce9778ea6b7e)) + - fix windows tests ([`0f11a6d`](https://github.com/Byron/gitoxide/commit/0f11a6dea937903d40833037d063bb82e224d66d)) + * **Uncategorized** + - Merge branch 'index-from-tree' ([`172f73c`](https://github.com/Byron/gitoxide/commit/172f73cf26878d153d51790fa01853fa4ba6beb7)) + - make fmt ([`535e967`](https://github.com/Byron/gitoxide/commit/535e967666c6da657ff1b7eff7c64ab27cafb182)) + - Merge branch 'main' into filter-refs-by-spec ([`9aa1d3d`](https://github.com/Byron/gitoxide/commit/9aa1d3dc46d4b1c76af257f573aff3aeef2d3fa8)) + - Merge branch 'main' into filter-refs-by-spec ([`1f6e5ab`](https://github.com/Byron/gitoxide/commit/1f6e5ab15f5fd8d23719b13e6aea59cd231ac0fe)) + - Merge branch 'main' into filter-refs-by-spec ([`51dc828`](https://github.com/Byron/gitoxide/commit/51dc8282fb77b519ff7d2c94c6bd73af306cfe8b)) + - Merge branch 'main' into filter-refs-by-spec ([`56ba481`](https://github.com/Byron/gitoxide/commit/56ba481f4c48f74f10397feb1b6dc3d7dd3704fb)) + - Merge branch 'main' into filter-refs-by-spec ([`a36c05d`](https://github.com/Byron/gitoxide/commit/a36c05d281269f3f8b297e7adc463bfb3c306663)) +
+ ## v0.7.0 (2022-08-29) ### Changed @@ -15,8 +55,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 6 commits contributed to the release. - - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 7 commits contributed to the release. + - 1 commit was understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#450](https://github.com/Byron/gitoxide/issues/450) ### Commit Details @@ -32,6 +72,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - copy all value code from git-config to the dedicated crate ([`edb1162`](https://github.com/Byron/gitoxide/commit/edb1162e284e343e2c575980854b8292de9c968f)) - add new git-config-value crate ([`f87edf2`](https://github.com/Byron/gitoxide/commit/f87edf26c1cb795142fbe95e12c0dfc1166e4233)) * **Uncategorized** + - Release git-config-value v0.7.0 ([`21c0ab9`](https://github.com/Byron/gitoxide/commit/21c0ab9c60ee317f574633081354351b0c7e5d0e)) - Release git-path v0.4.2, git-config-value v0.7.0 ([`c48fb31`](https://github.com/Byron/gitoxide/commit/c48fb3107d29f9a06868b0c6de40567063a656d1))
diff --git a/git-config/CHANGELOG.md b/git-config/CHANGELOG.md index 2c707b2ecc8..2a2d1b24b44 100644 --- a/git-config/CHANGELOG.md +++ b/git-config/CHANGELOG.md @@ -5,6 +5,52 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed + + - `git-config` now uses `git-config-value`. + +### Changed (BREAKING) + + - Add `Kind::GitInstallation` for a way to obtain special git-installation configuration paths. + Note that these are lazily cached as they call the `git` binary. + - upgrade `bstr` to `1.0.1` + +### Commit Statistics + + + + - 15 commits contributed to the release over the course of 21 calendar days. + - 22 days passed between releases. + - 3 commits were understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#450](https://github.com/Byron/gitoxide/issues/450) + +### Commit Details + + + +
view details + + * **[#450](https://github.com/Byron/gitoxide/issues/450)** + - Add `Kind::GitInstallation` for a way to obtain special git-installation configuration paths. ([`27fb1ce`](https://github.com/Byron/gitoxide/commit/27fb1ce27d2985eb1ee8bee5fffaf759902571fb)) + - upgrade `bstr` to `1.0.1` ([`99905ba`](https://github.com/Byron/gitoxide/commit/99905bacace8aed42b16d43f0f04cae996cb971c)) + - `git-config` now uses `git-config-value`. ([`5ad2965`](https://github.com/Byron/gitoxide/commit/5ad296577d837b0699b4718fa2be3d0978c4e342)) + - port tests over as well ([`9b28df2`](https://github.com/Byron/gitoxide/commit/9b28df22b858b6f1c9ca9b07a5a1c0cc300b50f0)) + * **Uncategorized** + - make fmt ([`429cccc`](https://github.com/Byron/gitoxide/commit/429cccc5831c25a7205a12dc7a0443ac48616e2c)) + - Merge branch 'index-from-tree' ([`172f73c`](https://github.com/Byron/gitoxide/commit/172f73cf26878d153d51790fa01853fa4ba6beb7)) + - make fmt ([`535e967`](https://github.com/Byron/gitoxide/commit/535e967666c6da657ff1b7eff7c64ab27cafb182)) + - Merge branch 'main' into filter-refs-by-spec ([`9aa1d3d`](https://github.com/Byron/gitoxide/commit/9aa1d3dc46d4b1c76af257f573aff3aeef2d3fa8)) + - Release git-features v0.22.4, git-url v0.8.0, safety bump 4 crates ([`1d4600a`](https://github.com/Byron/gitoxide/commit/1d4600ae51475c2e225f96c16c41e2c4a2b3f2aa)) + - Merge branch 'main' into filter-refs-by-spec ([`1f6e5ab`](https://github.com/Byron/gitoxide/commit/1f6e5ab15f5fd8d23719b13e6aea59cd231ac0fe)) + - Merge branch 'git_date_parse' ([`75591fb`](https://github.com/Byron/gitoxide/commit/75591fb108ce440ba2f920bebf99158b407e3046)) + - Merge branch 'main' into filter-refs-by-spec ([`51dc828`](https://github.com/Byron/gitoxide/commit/51dc8282fb77b519ff7d2c94c6bd73af306cfe8b)) + - Merge branch 'main' into filter-refs-by-spec ([`56ba481`](https://github.com/Byron/gitoxide/commit/56ba481f4c48f74f10397feb1b6dc3d7dd3704fb)) + - Merge branch 'main' into filter-refs-by-spec ([`a36c05d`](https://github.com/Byron/gitoxide/commit/a36c05d281269f3f8b297e7adc463bfb3c306663)) + - Release git-path v0.4.2, git-config-value v0.7.0 ([`c48fb31`](https://github.com/Byron/gitoxide/commit/c48fb3107d29f9a06868b0c6de40567063a656d1)) +
+ ## 0.7.1 (2022-08-28) Maintenance release without user-facing changes. @@ -13,9 +59,9 @@ Maintenance release without user-facing changes. - - 3 commits contributed to the release over the course of 1 calendar day. + - 4 commits contributed to the release over the course of 1 calendar day. - 4 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#XXX](https://github.com/Byron/gitoxide/issues/XXX) ### Commit Details @@ -27,6 +73,7 @@ Maintenance release without user-facing changes. * **[#XXX](https://github.com/Byron/gitoxide/issues/XXX)** - prepare changelogs prior to release ([`8c0bca3`](https://github.com/Byron/gitoxide/commit/8c0bca37ff9fbaadbe55561fb2b0d649980c95b1)) * **Uncategorized** + - Release git-object v0.20.3, git-ref v0.15.4, git-config v0.7.1, git-diff v0.18.0, git-traverse v0.16.3, git-pack v0.22.0, git-odb v0.32.0, git-url v0.7.3, git-transport v0.19.3, git-protocol v0.19.1, git-refspec v0.1.1, git-repository v0.23.0, safety bump 6 crates ([`85a3bed`](https://github.com/Byron/gitoxide/commit/85a3bedd68d2e5f36592a2f691c977dc55298279)) - Release git-attributes v0.3.3, git-ref v0.15.3, git-index v0.4.3, git-worktree v0.4.3, git-testtools v0.8.0 ([`baad4ce`](https://github.com/Byron/gitoxide/commit/baad4ce51fe0e8c0c1de1b08148d8303878ca37b)) - Release git-features v0.22.3, git-revision v0.4.4 ([`c2660e2`](https://github.com/Byron/gitoxide/commit/c2660e2503323531ba02519eaa51124ee22fec51))
@@ -82,7 +129,7 @@ Maintenance release without user-facing changes. - 20 commits contributed to the release over the course of 3 calendar days. - 6 days passed between releases. - - 11 commits where understood as [conventional](https://www.conventionalcommits.org). + - 11 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#450](https://github.com/Byron/gitoxide/issues/450) ### Thanks Clippy @@ -131,7 +178,7 @@ A maintenance release without user facing changes. - 10 commits contributed to the release over the course of 26 calendar days. - 26 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#482](https://github.com/Byron/gitoxide/issues/482) ### Thanks Clippy @@ -386,7 +433,7 @@ A maintenance release without user facing changes. - 316 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - - 93 commits where understood as [conventional](https://www.conventionalcommits.org). + - 93 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) ### Thanks Clippy @@ -734,7 +781,7 @@ A maintenance release without user facing changes. - 41 commits contributed to the release over the course of 22 calendar days. - 22 days passed between releases. - - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 1 commit was understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#436](https://github.com/Byron/gitoxide/issues/436) ### Commit Details @@ -802,7 +849,7 @@ A maintenance release without user facing changes. - 24 commits contributed to the release over the course of 2 calendar days. - 3 days passed between releases. - - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 1 commit was understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Thanks Clippy @@ -910,7 +957,7 @@ A maintenance release without user facing changes. - 86 commits contributed to the release over the course of 40 calendar days. - 43 days passed between releases. - - 14 commits where understood as [conventional](https://www.conventionalcommits.org). + - 14 commits were understood as [conventional](https://www.conventionalcommits.org). - 5 unique issues were worked on: [#298](https://github.com/Byron/gitoxide/issues/298), [#301](https://github.com/Byron/gitoxide/issues/301), [#331](https://github.com/Byron/gitoxide/issues/331), [#386](https://github.com/Byron/gitoxide/issues/386), [#404](https://github.com/Byron/gitoxide/issues/404) ### Thanks Clippy @@ -1032,7 +1079,7 @@ A maintenance release without user facing changes. - 5 commits contributed to the release over the course of 2 calendar days. - 3 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#298](https://github.com/Byron/gitoxide/issues/298) ### Thanks Clippy @@ -1080,7 +1127,7 @@ A maintenance release without user facing changes. - 44 commits contributed to the release over the course of 60 calendar days. - 60 days passed between releases. - - 2 commits where understood as [conventional](https://www.conventionalcommits.org). + - 2 commits were understood as [conventional](https://www.conventionalcommits.org). - 2 unique issues were worked on: [#298](https://github.com/Byron/gitoxide/issues/298), [#331](https://github.com/Byron/gitoxide/issues/331) ### Thanks Clippy @@ -1163,7 +1210,7 @@ A maintenance release without user facing changes. - 7 commits contributed to the release over the course of 7 calendar days. - 7 days passed between releases. - - 2 commits where understood as [conventional](https://www.conventionalcommits.org). + - 2 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#319](https://github.com/Byron/gitoxide/issues/319) ### Commit Details @@ -1195,7 +1242,7 @@ A maintenance release without user facing changes. - 7 commits contributed to the release over the course of 51 calendar days. - 55 days passed between releases. - - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 1 commit was understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#266](https://github.com/Byron/gitoxide/issues/266) ### Thanks Clippy @@ -1231,7 +1278,7 @@ A maintenance release. - 7 commits contributed to the release over the course of 11 calendar days. - 12 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Thanks Clippy @@ -1266,7 +1313,7 @@ A maintenance release triggered by changes to git-pack and changelog rewrites. - 19 commits contributed to the release over the course of 25 calendar days. - 31 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 2 unique issues were worked on: [#241](https://github.com/Byron/gitoxide/issues/241), [#254](https://github.com/Byron/gitoxide/issues/254) ### Commit Details @@ -1309,7 +1356,7 @@ This is a maintenance release without functional changes. - 13 commits contributed to the release over the course of 3 calendar days. - 38 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 2 unique issues were worked on: [#198](https://github.com/Byron/gitoxide/issues/198), [#213](https://github.com/Byron/gitoxide/issues/213) ### Commit Details @@ -1344,7 +1391,7 @@ This is a maintenance release without functional changes. - 6 commits contributed to the release over the course of 7 calendar days. - 8 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -1372,7 +1419,7 @@ This is a maintenance release without functional changes. - 7 commits contributed to the release over the course of 10 calendar days. - 12 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -1399,7 +1446,7 @@ This is a maintenance release without functional changes. - 12 commits contributed to the release. - 2 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -1431,7 +1478,7 @@ This is a maintenance release without functional changes. - 3 commits contributed to the release over the course of 6 calendar days. - 8 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -1475,7 +1522,7 @@ This is a maintenance release without functional changes. - 16 commits contributed to the release over the course of 86 calendar days. - 89 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Thanks Clippy @@ -1520,7 +1567,7 @@ lenfrom_envopen - 13 commits contributed to the release over the course of 56 calendar days. - 58 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Thanks Clippy @@ -1575,7 +1622,7 @@ lenfrom_envopen - 125 commits contributed to the release over the course of 157 calendar days. - 158 days passed between releases. - - 3 commits where understood as [conventional](https://www.conventionalcommits.org). + - 3 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -1719,7 +1766,7 @@ lenfrom_envopen - 1 commit contributed to the release. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details diff --git a/git-credentials/CHANGELOG.md b/git-credentials/CHANGELOG.md index a4bce7d2738..bf26837cd75 100644 --- a/git-credentials/CHANGELOG.md +++ b/git-credentials/CHANGELOG.md @@ -5,8 +5,199 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed + + - use `git-config-value` crate + +### New Features + + - `protocol::Context::to_bstring()`, and use it in `example/git-credential-lite` + - an example implementing a custom credential helper program + - `helper::main` to easily create credential helper implementations + - add `helper::Action::get_for_url(…)` + - `helper::invoke(helper, action, context)` function that allows for more flexible helper invocation + +### Other + + - :main::Action::as_str()` + +### Changed (BREAKING) + + - upgrade `bstr` to `1.0.1` + - rename `git()` to `builtin()` + - rename `Program::Custom*` variants to `Program::External*` + It's more true to what it is. + - differentiate between top-level functions and those which are invoked + That way it's easier to use as it can assure an account was actually + provided. + - invoke::Outcome can now represent partial identities + That way these can be assembled by multiple helpers called in a row. + - move `helper::(Next)Action` into `helper::invoke::` module + These are only relevant for invoke, hence the change. + - rename `helper::NextAction` variants to `store` and `erase` + - rename `helper::Action` variants to 'Get', 'Store', 'Erase' + It's more obvious what it does and is more typical for what credentials + helpers do. + - Use `helper::Context` in `helper::Action::Fill()` + That way additional information, like from configuration, can be passed + as well. + - move `helper::invoke()` related types into `helper::invoke` module. + Also allow to pass arbitrary bytes (more or less) as context by not + forcing it all into a string. Values can now be everything, which + helps with passing paths or other values. + - use `thiserror` instead of `quickerror` + - hide `helper::action()` in favor of single path via `helper()` + +### Other (BREAKING) + + - `helper::Kind` to `program::Kind` + - `helper::(encode|decode)_message(…)` to `::message::(encode|decode)(…)` + +### Commit Statistics + + + + - 113 commits contributed to the release over the course of 27 calendar days. + - 27 days passed between releases. + - 21 commits were understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#450](https://github.com/Byron/gitoxide/issues/450) + +### Thanks Clippy + + + +[Clippy](https://github.com/rust-lang/rust-clippy) helped 5 times to make code idiomatic. + +### Commit Details + + + +
view details + + * **[#450](https://github.com/Byron/gitoxide/issues/450)** + - upgrade `bstr` to `1.0.1` ([`99905ba`](https://github.com/Byron/gitoxide/commit/99905bacace8aed42b16d43f0f04cae996cb971c)) + - :main::Action::as_str()` ([`d95029e`](https://github.com/Byron/gitoxide/commit/d95029eac0e9179a7cd730d1d60a08696584bfd1)) + - `protocol::Context::to_bstring()`, and use it in `example/git-credential-lite` ([`15f1afc`](https://github.com/Byron/gitoxide/commit/15f1afccb7ed0ebaf217cbbdd58e6ae651a31e42)) + - assure that protocol::Context::to_url() never shows passwords ([`e9f4d40`](https://github.com/Byron/gitoxide/commit/e9f4d40b6f04414c04f153f74f13d2e1fe89e43a)) + - Make sure the helper::Cascade never sends the URL to helper programs ([`9059de8`](https://github.com/Byron/gitoxide/commit/9059de825d310c2c28f28d4009b09115acccc2bf)) + - fix docs ([`9a5ec7b`](https://github.com/Byron/gitoxide/commit/9a5ec7bd8b23bbef2c21de07638681160a7bbdee)) + - move `program::Cascade` to `helper::Cascade` which is what it is ([`6149a14`](https://github.com/Byron/gitoxide/commit/6149a14af1742bcfc30bfbe65656b411c6f771c9)) + - An example on how to invoke the git credential helper driver ([`be0f834`](https://github.com/Byron/gitoxide/commit/be0f83411371e445beceabfcc6e0458eedccf31a)) + - Context has to serialize url or else the builtin credential helper may not work. ([`87ae404`](https://github.com/Byron/gitoxide/commit/87ae40493cc0dbe11e5de5fd21e2391caa7161db)) + - credential context won't send url and quit fields to helpers ([`337a53b`](https://github.com/Byron/gitoxide/commit/337a53b945da26e253c9ba1c72b623d6a06d2e7c)) + - Cascade supports `use_http_path` and respects it when setting up the context ([`959c0bd`](https://github.com/Byron/gitoxide/commit/959c0bdfb6a634f590969f2c26d13ff8c05a4bb8)) + - make it less easy to start a cascade with platform_defaults() ([`4b5d63f`](https://github.com/Byron/gitoxide/commit/4b5d63f7e0ea6bc43f54c95dd30f823ead9fa1a2)) + - make clearer what platform builtins actually are ([`9788e30`](https://github.com/Byron/gitoxide/commit/9788e3070edc5c1d84099a2fc5fa9262604170e7)) + - credential-cascade now passes on prompt options ([`baad8a0`](https://github.com/Byron/gitoxide/commit/baad8a077ffd556cb29da93fb0081b245f4663ff)) + - refactor ([`c8f1b41`](https://github.com/Byron/gitoxide/commit/c8f1b41408f2ace5b01292ef95189b9e66ab4d8e)) + - always compile prompting support in ([`bd0ea68`](https://github.com/Byron/gitoxide/commit/bd0ea68225a73fb83c9fc1b8594fc6ad288a77a9)) + - set version of git-prompt to 0.1 and turn prompting on ([`7657693`](https://github.com/Byron/gitoxide/commit/7657693b8e23dfb69d6da4376bcd1b8e4e264f7e)) + - fix warnings ([`e011242`](https://github.com/Byron/gitoxide/commit/e011242c0c9f6779632f5d33dc7b185495f3868e)) + - more helpful prompt error messages when asking for credentials ([`b0c6863`](https://github.com/Byron/gitoxide/commit/b0c6863e6b91ded34ed1860ed449f797d28be81e)) + - use `git-config-value` crate ([`43656d5`](https://github.com/Byron/gitoxide/commit/43656d5ce84834c847cf8650d4c486c634f209b6)) + - proper prompt generation ([`63ee38d`](https://github.com/Byron/gitoxide/commit/63ee38dab45fd9d07532f6c01afc2d8dd1c1e904)) + - remove rustyline in favor of `git-prompt` ([`b3e5e59`](https://github.com/Byron/gitoxide/commit/b3e5e59cafaab0d4866c52722cd2a67aa313b395)) + - add interactive example for prompt, but… ([`a3fadea`](https://github.com/Byron/gitoxide/commit/a3fadea7759a20fe409762e48d0f1bb9c07f39ba)) + - blindly implement prompting if it is allowed ([`c78f4b8`](https://github.com/Byron/gitoxide/commit/c78f4b80d1554fdae49d8d5e7d1cfef6c1bf3b05)) + - frame to support prompting (as compile-time feature) ([`afaae28`](https://github.com/Byron/gitoxide/commit/afaae2880a77c30f845ccf2b3c2b7dc5210665f8)) + - another test ([`569b7bc`](https://github.com/Byron/gitoxide/commit/569b7bc3d8d8acfe8ad16fe1bc0480e3dbd263d2)) + - remove unnecessary `Helper` trait ([`19b84f0`](https://github.com/Byron/gitoxide/commit/19b84f0636f6a8d28e938c3a56b3e2cf0a3b4711)) + - use fixtures in all tests ([`24da911`](https://github.com/Byron/gitoxide/commit/24da911f2fcbc0073fcdab1a217686ac3e3b1c79)) + - fix tests on linux ([`89db8ee`](https://github.com/Byron/gitoxide/commit/89db8ee938f05f8f9066f34325619f434a5ea00f)) + - more tests ([`57e9060`](https://github.com/Byron/gitoxide/commit/57e906094683860b43f5b7ff71e0189bd2fd0a91)) + - refactor ([`561bb35`](https://github.com/Byron/gitoxide/commit/561bb356850715c2f4377dd36d1daff69126f543)) + - another test ([`52d2e54`](https://github.com/Byron/gitoxide/commit/52d2e547b18aa5a00d9d1ada9c88bd84e951e1ed)) + - fix CI ([`d526c6d`](https://github.com/Byron/gitoxide/commit/d526c6d111bfa05dfa20aca8426d78217ae41558)) + - improve path normalization; a new ignored test ([`ece5a3f`](https://github.com/Byron/gitoxide/commit/ece5a3f16bfbf84eddce42c64c32736ad98b5356)) + - parse 'quit' according to spec ([`5e260da`](https://github.com/Byron/gitoxide/commit/5e260dab2edd40092501ab52684f6370104a4eb1)) + - Allow disabling stderr on credential programs ([`4abec50`](https://github.com/Byron/gitoxide/commit/4abec50dc620e965fc03dda4c801753365839691)) + - refactor ([`cdfcea4`](https://github.com/Byron/gitoxide/commit/cdfcea4eb92097927d4c90639fc211e427b6415c)) + - url-preprocessing for scripts ([`c00cc35`](https://github.com/Byron/gitoxide/commit/c00cc35493cec8f0b2673248caf1f0d83590dd54)) + - breaking credential helpers don't stop everything ([`0cdbde7`](https://github.com/Byron/gitoxide/commit/0cdbde78a200ff8585fb217bab3daf81ff46dd6e)) + - refactor; try harder not to spill secrets in errors ([`525fa97`](https://github.com/Byron/gitoxide/commit/525fa9748b966d515fbdeaa48abd34798e97b78e)) + - first step towards our own `git credential` -like implementation ([`1d1622a`](https://github.com/Byron/gitoxide/commit/1d1622a0dd66ce181d0fa07fc440f85ad0212791)) + - refactor ([`ce16f51`](https://github.com/Byron/gitoxide/commit/ce16f513dc0a482583cdff168dcdbe2cdd379ad7)) + - Platform specific defaults for the program cascade ([`b66258f`](https://github.com/Byron/gitoxide/commit/b66258f3827e8ca4c7da4a5bca7768888a09e6d5)) + - refactor ([`85f8cd9`](https://github.com/Byron/gitoxide/commit/85f8cd9b9ef9e93c6495495a83b1ec96437672a5)) + - refactor ([`23fb302`](https://github.com/Byron/gitoxide/commit/23fb3025112d2f627896383fb0f74f7e91139116)) + - A sketch of how a custom 'git credential' could look like ([`4767a14`](https://github.com/Byron/gitoxide/commit/4767a14d2390edacf46d5436a07685b7d7b79cab)) + - make 'quit' handler request representable and raise it to an error ([`39b6514`](https://github.com/Byron/gitoxide/commit/39b6514928304807b3d43bd60be716a7f42169c7)) + - rename `git()` to `builtin()` ([`783a1a7`](https://github.com/Byron/gitoxide/commit/783a1a7dfd64a64fa765fa3d3ef41b1e954413ee)) + - fix docs ([`f86364c`](https://github.com/Byron/gitoxide/commit/f86364c4e2d9efd04027978679232946494a4734)) + - rename `Program::Custom*` variants to `Program::External*` ([`bfa2545`](https://github.com/Byron/gitoxide/commit/bfa2545883daf8c4d9e97d2fc91c9328d73ab0eb)) + - refactor ([`52e958d`](https://github.com/Byron/gitoxide/commit/52e958d62cdf49c35ed56cb26699b155ee0e7732)) + - fix build ([`99958c6`](https://github.com/Byron/gitoxide/commit/99958c6f87a09b99f21b88e42095a1326d6b8a82)) + - differentiate between top-level functions and those which are invoked ([`811985a`](https://github.com/Byron/gitoxide/commit/811985aba024385465104ed826a9989961555201)) + - invoke::Outcome can now represent partial identities ([`49b9bd5`](https://github.com/Byron/gitoxide/commit/49b9bd501f33f1e10ce0180e814b84e293bd3898)) + - make clear what `helper()` does by renaming it to `git` ([`2edb58b`](https://github.com/Byron/gitoxide/commit/2edb58b6c7395b67c8a7f7c9f6162e6e7c290aac)) + - Make clear in the error type that the helper program couldn't be started ([`c09d223`](https://github.com/Byron/gitoxide/commit/c09d2234cb7e89a2b6ae54e7c8497e86b38621f0)) + - improved error when identity could not be obtained ([`08c1287`](https://github.com/Byron/gitoxide/commit/08c12876d763a4ade3d4013ce1be66d9594e4ff1)) + - support for `quit` field in context ([`5a50528`](https://github.com/Byron/gitoxide/commit/5a50528a6f2b1a547fdc9a656e5ea2ca07396ecf)) + - refactor ([`7487b5a`](https://github.com/Byron/gitoxide/commit/7487b5a4142679ef423c5bd996e40e473c5dfc27)) + - support for non-consuming operation of `Program` ([`bcfe5ca`](https://github.com/Byron/gitoxide/commit/bcfe5ca22636114bb232d1208ab7c9d78d1fe1de)) + - disable test that seems to fail on linux ([`419ca56`](https://github.com/Byron/gitoxide/commit/419ca56f7a97cdb0c0e18a4a6f8fda6320692518)) + - More tests for custom helper scripts ([`b396032`](https://github.com/Byron/gitoxide/commit/b3960320d1ef86b42fe8d42c8d7f7abfe66e1710)) + - Support for script invocations ([`377cf14`](https://github.com/Byron/gitoxide/commit/377cf142996279394af38179ad5b51c419642f90)) + - use git_path::is_absolute() ([`2ba836f`](https://github.com/Byron/gitoxide/commit/2ba836f3e9e5231e8bc42d57d8ff939d85acfe16)) + - fix tests on windows ([`f4bc860`](https://github.com/Byron/gitoxide/commit/f4bc86011d4aafb5bdeafadd43adb0022ff9b449)) + - also fill in the git credential command prefix ([`b2f4fe8`](https://github.com/Byron/gitoxide/commit/b2f4fe8f96785222edc3c0472ccef3acf1f069f8)) + - initial version of parsing of custom helper definitions ([`2b2cd00`](https://github.com/Byron/gitoxide/commit/2b2cd0001babdc16e940fa7242c6d723fc9f789b)) + - `helper::Kind` to `program::Kind` ([`b8c54f0`](https://github.com/Byron/gitoxide/commit/b8c54f03fdb6060caf9c04557c0530c090e7a975)) + - sketch additional credentials programs ([`46e3045`](https://github.com/Byron/gitoxide/commit/46e3045e04e5197560d8c786642b8f1924a577f9)) + - first test for launching the git credential helper ([`4d7b1dd`](https://github.com/Byron/gitoxide/commit/4d7b1ddec6ef747665edcfddbba68ed12e3970c2)) + - an example implementing a custom credential helper program ([`b1d528a`](https://github.com/Byron/gitoxide/commit/b1d528ae60001ae51dd89b29c26ea505eacbef45)) + - add docs ([`a360594`](https://github.com/Byron/gitoxide/commit/a360594fac3102cd48aac0039efbe71693c5fa25)) + - `helper::main` to easily create credential helper implementations ([`eaff67c`](https://github.com/Byron/gitoxide/commit/eaff67c14366f149ccca346acb46af12531a24e6)) + - move `helper::(Next)Action` into `helper::invoke::` module ([`4b7d0b6`](https://github.com/Byron/gitoxide/commit/4b7d0b6d2c43cac9823885bc69510cc4bb6a3f00)) + - sketch for helper::invoke (get) test ([`c48eb39`](https://github.com/Byron/gitoxide/commit/c48eb390a2f95954f542992806d4e8667ee97981)) + - refactor ([`cb9d32a`](https://github.com/Byron/gitoxide/commit/cb9d32a3611463f983afea3b3ea875c33092207b)) + - rename `helper::NextAction` variants to `store` and `erase` ([`ddd5398`](https://github.com/Byron/gitoxide/commit/ddd53988a6d5da17fc65451a059bed1bfa2eb454)) + - fix docs ([`d9b4ba5`](https://github.com/Byron/gitoxide/commit/d9b4ba5a00c1c9f9c199ac218da76cb716896b75)) + - add `helper::Action::get_for_url(…)` ([`a253d30`](https://github.com/Byron/gitoxide/commit/a253d30093122e37b5560ff86a7888f8062c7014)) + - rename `helper::Action` variants to 'Get', 'Store', 'Erase' ([`2073b58`](https://github.com/Byron/gitoxide/commit/2073b583dc2bd83b800584edda6592bb71a01538)) + - Use `helper::Context` in `helper::Action::Fill()` ([`9c6f024`](https://github.com/Byron/gitoxide/commit/9c6f024f838d866645937a67cd67dffb8be17259)) + - remaining decode tests ([`0e76efe`](https://github.com/Byron/gitoxide/commit/0e76efe035a48f9d042096342ac79804f1eeebdc)) + - test context value validation ([`20dde9e`](https://github.com/Byron/gitoxide/commit/20dde9eb93ecfb56e72bc5d59caacf31328a55e4)) + - basic round-tripping of fully fleshed-out context ([`280e4a3`](https://github.com/Byron/gitoxide/commit/280e4a3f69699e11428decc6858711b35ae8249e)) + - flesh out `helper::Context` as it will soon be used. ([`0cb1ed4`](https://github.com/Byron/gitoxide/commit/0cb1ed4600c614169118b2a94fed83699989a6be)) + - move `helper::invoke()` related types into `helper::invoke` module. ([`71f6519`](https://github.com/Byron/gitoxide/commit/71f651930e6fd53e3c3f9e82dfd95828e4981d92)) + - refactor ([`03bf747`](https://github.com/Byron/gitoxide/commit/03bf747292af7792bc175c4f06939b1e02f7234c)) + - express `helper()` in terms of `helper::invoke()` ([`f2a2c5e`](https://github.com/Byron/gitoxide/commit/f2a2c5ebb7d7428460fe22e9b84dec242a992302)) + - `helper::invoke(helper, action, context)` function that allows for more flexible helper invocation ([`64bc2ec`](https://github.com/Byron/gitoxide/commit/64bc2ec666dacba486bd1de2fbd95f97f2efc7a5)) + - refactor ([`af27d20`](https://github.com/Byron/gitoxide/commit/af27d20909d14f2737fbad5edd9a6c9d86c93e24)) + - prepare for more additional implementations of helpers ([`486ef98`](https://github.com/Byron/gitoxide/commit/486ef98b792cc57412a4a90d2cf28586a06d7041)) + - refactor ([`167b521`](https://github.com/Byron/gitoxide/commit/167b5215326ff2f39e89f2130ff575f4ef6c02d6)) + - fix docs ([`db46b60`](https://github.com/Byron/gitoxide/commit/db46b60d8f9b4341cf215da6e2cd74bf554fe4b8)) + - re-add `Result` type ([`de92fce`](https://github.com/Byron/gitoxide/commit/de92fce44496b050e5697aab6d6d1ea98a5954dc)) + - use `thiserror` instead of `quickerror` ([`4c1a1a2`](https://github.com/Byron/gitoxide/commit/4c1a1a28558c4f8d084b8046afd5d87a11fd25b7)) + - hide `helper::action()` in favor of single path via `helper()` ([`081934c`](https://github.com/Byron/gitoxide/commit/081934ca4452e550cf2663026905bce67253af81)) + - `helper::(encode|decode)_message(…)` to `::message::(encode|decode)(…)` ([`4c39521`](https://github.com/Byron/gitoxide/commit/4c39521a47419bb4b0f0edbe51aa509fb4e2a7f1)) + - refactor ([`a395308`](https://github.com/Byron/gitoxide/commit/a395308fdc01b5449a851b1dcb6c3e97a205a5d0)) + * **Uncategorized** + - Merge branch 'index-from-tree' ([`172f73c`](https://github.com/Byron/gitoxide/commit/172f73cf26878d153d51790fa01853fa4ba6beb7)) + - make fmt ([`535e967`](https://github.com/Byron/gitoxide/commit/535e967666c6da657ff1b7eff7c64ab27cafb182)) + - Merge branch 'main' into filter-refs-by-spec ([`9aa1d3d`](https://github.com/Byron/gitoxide/commit/9aa1d3dc46d4b1c76af257f573aff3aeef2d3fa8)) + - Merge branch 'main' into filter-refs-by-spec ([`1f6e5ab`](https://github.com/Byron/gitoxide/commit/1f6e5ab15f5fd8d23719b13e6aea59cd231ac0fe)) + - Merge branch 'main' into filter-refs-by-spec ([`51dc828`](https://github.com/Byron/gitoxide/commit/51dc8282fb77b519ff7d2c94c6bd73af306cfe8b)) + - Merge branch 'main' into filter-refs-by-spec ([`56ba481`](https://github.com/Byron/gitoxide/commit/56ba481f4c48f74f10397feb1b6dc3d7dd3704fb)) + - Merge branch 'main' into filter-refs-by-spec ([`a36c05d`](https://github.com/Byron/gitoxide/commit/a36c05d281269f3f8b297e7adc463bfb3c306663)) + - Release git-path v0.4.2, git-config-value v0.7.0 ([`c48fb31`](https://github.com/Byron/gitoxide/commit/c48fb3107d29f9a06868b0c6de40567063a656d1)) + - Merge branch 'main' into filter-refs-by-spec ([`cef0b51`](https://github.com/Byron/gitoxide/commit/cef0b51ade2a3301fa09ede7a425aa1fe3527e78)) + - thanks clippy ([`c1399d1`](https://github.com/Byron/gitoxide/commit/c1399d155889e6142eafd65b9bbd2ed005f580dd)) + - thanks clippy ([`e8e80f5`](https://github.com/Byron/gitoxide/commit/e8e80f5b176ebca4ee81727a551d83383a0b38f8)) + - Merge branch 'main' into filter-refs-by-spec ([`dbfa328`](https://github.com/Byron/gitoxide/commit/dbfa3282cf876596b250b2040c1ec0b761741796)) + - thanks clippy ([`9b8a6d6`](https://github.com/Byron/gitoxide/commit/9b8a6d6afeab13968dea61619b1e742e93f60fab)) + - thanks clippy ([`8317b46`](https://github.com/Byron/gitoxide/commit/8317b4672c8cd38520ed90c42eaadd539ea4df66)) + - thanks clippy ([`01efe88`](https://github.com/Byron/gitoxide/commit/01efe88cff52e75ba0b76ecc27a41994ee908d2c)) +
+ ## 0.4.0 (2022-08-24) + + + ### Chore - uniformize deny attributes @@ -29,9 +220,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 16 commits contributed to the release over the course of 14 calendar days. + - 17 commits contributed to the release over the course of 14 calendar days. - 32 days passed between releases. - - 5 commits where understood as [conventional](https://www.conventionalcommits.org). + - 5 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#450](https://github.com/Byron/gitoxide/issues/450) ### Commit Details @@ -43,6 +234,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * **[#450](https://github.com/Byron/gitoxide/issues/450)** - adapt to changes in `git-url` and use `BString` to represent URLs. ([`12589cc`](https://github.com/Byron/gitoxide/commit/12589cc6f08e4d7aabae30bcdadaa0c2b4850229)) * **Uncategorized** + - Release git-date v0.0.5, git-hash v0.9.8, git-features v0.22.2, git-actor v0.11.3, git-glob v0.3.2, git-quote v0.2.1, git-attributes v0.3.2, git-tempfile v2.0.4, git-lock v2.1.1, git-validate v0.5.5, git-object v0.20.2, git-ref v0.15.2, git-sec v0.3.1, git-config v0.7.0, git-credentials v0.4.0, git-diff v0.17.2, git-discover v0.4.1, git-bitmap v0.1.2, git-index v0.4.2, git-mailmap v0.3.2, git-chunk v0.3.1, git-traverse v0.16.2, git-pack v0.21.2, git-odb v0.31.2, git-packetline v0.12.7, git-url v0.7.2, git-transport v0.19.2, git-protocol v0.19.0, git-revision v0.4.2, git-refspec v0.1.0, git-worktree v0.4.2, git-repository v0.22.0, safety bump 4 crates ([`4974eca`](https://github.com/Byron/gitoxide/commit/4974eca96d525d1ee4f8cad79bb713af7a18bf9d)) - Merge branch 'example-write-blob' ([`afedd7f`](https://github.com/Byron/gitoxide/commit/afedd7f86ec8ea18a8165f71698ecc886f5cf643)) - Merge pull request #494 from ultrasaurus/patch-1 ([`86fe22c`](https://github.com/Byron/gitoxide/commit/86fe22cb1aad5944a229bc2a5252b36ef1fd59ef)) - Merge branch 'main' into remote-ls-refs ([`95f2f4f`](https://github.com/Byron/gitoxide/commit/95f2f4f17f7eae174a64c7d9f6a1513d73b21bbb)) @@ -70,7 +262,7 @@ This is a maintenance release with no functional changes. - 5 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -97,7 +289,7 @@ A maintenance release without user-facing changes. - 2 commits contributed to the release. - 25 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -124,7 +316,7 @@ A maintenance release without user-facing changes. - 8 commits contributed to the release over the course of 33 calendar days. - 33 days passed between releases. - - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 1 commit was understood as [conventional](https://www.conventionalcommits.org). - 2 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#386](https://github.com/Byron/gitoxide/issues/386) ### Commit Details @@ -155,7 +347,7 @@ An empty crate without any content to reserve the name for the gitoxide project. - 2 commits contributed to the release. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#386](https://github.com/Byron/gitoxide/issues/386) ### Commit Details diff --git a/git-date/CHANGELOG.md b/git-date/CHANGELOG.md index a8d0b37133c..c5dfed4e416 100644 --- a/git-date/CHANGELOG.md +++ b/git-date/CHANGELOG.md @@ -5,6 +5,64 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed (BREAKING) + + - upgrade `bstr` to `1.0.1` + - parse now takes the current time `parse(…, Option