Skip to content

improvements for onefetch #368

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Mar 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
91065cd
feat: `CommitRefIter::parent_ids()` (#364)
Byron Mar 29, 2022
c51a925
feat: `CommitRefIter::(author|committer)()`, better usability (#364)
Byron Mar 29, 2022
59d75fc
cleaner API for detaching objects, now for commits as well (#364)
Byron Mar 29, 2022
13799e2
feat: Time::new(seconds_since_epoch, offset) (#364)
Byron Mar 29, 2022
705adfd
feat: SignatureRef is now Copy (#364)
Byron Mar 30, 2022
e5c0200
adjust to changes in git-actor (#364)
Byron Mar 30, 2022
3079e11
thanks clippy
Byron Mar 30, 2022
6129607
More speedy access to author/committer (#364)
Byron Mar 30, 2022
b94471a
Full error handling for CommitRefIter (#364)
Byron Mar 30, 2022
a7dbed1
consolidate naming of directories, use same convention as git2 (#364)
Byron Mar 30, 2022
df24f16
Fix lifetime declarations to allow ancestors().all() chaining (#364)
Byron Mar 30, 2022
a8b6589
change!: Easier access to local and remote branches (#364)
Byron Mar 30, 2022
70a259c
feat: `Time::seconds()` shortcut (#364)
Byron Mar 30, 2022
af2d399
feat: FullName(Ref)::strip_prefix() (#364)
Byron Mar 30, 2022
2066a80
thanks clippy
Byron Mar 30, 2022
e7e4ba2
feat: `Fullname(Ref)::category()` and `Category` (#364)
Byron Mar 30, 2022
b1b9871
refactor (#364)
Byron Mar 30, 2022
5f75953
rename!: `Id::prefix` -> `Id::shorten()` (#364)
Byron Mar 30, 2022
29822c6
fix docs (#364)
Byron Mar 30, 2022
f0d8a49
feat: `Repository::head_id()` (#364)
Byron Mar 30, 2022
0057804
feat: `Repository::head_commit()` (#364)
Byron Mar 30, 2022
8f1b42b
feat: `CommitRef::time()` (#364)
Byron Mar 30, 2022
11644bd
fix install_dir(); refactor (#364)
Byron Mar 30, 2022
5052a4e
also inform about average and max commit sizes (#364)
Byron Mar 31, 2022
600eb69
More aggressive mailmap substitution for better results (#364)
Byron Mar 31, 2022
a39bf71
feat: support for trimming of whitespace around name and email (#364)
Byron Mar 31, 2022
beb478f
simplify estimate-hours by just looking at the author signature (#364)
Byron Mar 31, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cargo-smart-release/src/command/release/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ pub(in crate::command::release_impl) fn create_version_tag<'repo>(
tag_name,
target,
git_repository::objs::Kind::Commit,
Some(&crate::git::author()?.to_ref()),
Some(crate::git::author()?.to_ref()),
message,
constraint,
)?;
Expand Down
2 changes: 1 addition & 1 deletion cargo-smart-release/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ pub fn is_pre_release_version(semver: &Version) -> bool {

pub fn is_top_level_package(manifest_path: &Utf8Path, repo: &git::Repository) -> bool {
manifest_path
.strip_prefix(repo.work_tree().as_ref().expect("repo with working tree"))
.strip_prefix(repo.work_dir().as_ref().expect("repo with working tree"))
.map_or(false, |p| p.components().count() == 1)
}

Expand Down
2 changes: 1 addition & 1 deletion experiments/traversal/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ where
for commit in commits {
let tree_id = db
.try_find(commit, &mut buf)?
.and_then(|o| o.try_into_commit_iter().and_then(|mut c| c.tree_id()))
.and_then(|o| o.try_into_commit_iter().and_then(|mut c| c.tree_id().ok()))
.expect("commit as starting point");

let mut count = Count { entries: 0, seen };
Expand Down
2 changes: 1 addition & 1 deletion git-actor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ local-time-support = ["git-features/time"]
git-features = { version = "^0.19.1", path = "../git-features", optional = true }
quick-error = "2.0.0"
btoi = "0.4.2"
bstr = { version = "0.2.13", default-features = false, features = ["std"]}
bstr = { version = "0.2.13", default-features = false, features = ["std", "unicode"]}
nom = { version = "7", default-features = false, features = ["std"]}
itoa = "1.0.1"
serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]}
Expand Down
2 changes: 1 addition & 1 deletion git-actor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub struct Signature {
/// A immutable signature is created by an actor at a certain time.
///
/// Note that this is not a cryptographical signature.
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy, Default)]
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
pub struct SignatureRef<'a> {
/// The actor's name.
Expand Down
10 changes: 10 additions & 0 deletions git-actor/src/signature/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod _ref {
use crate::{signature::decode, Signature, SignatureRef};
use bstr::ByteSlice;

impl<'a> SignatureRef<'a> {
/// Deserialize a signature from the given `data`.
Expand All @@ -18,6 +19,15 @@ mod _ref {
time: self.time,
}
}

/// Trim whitespace surrounding the name and email and return a new signature.
pub fn trim(&self) -> SignatureRef<'a> {
SignatureRef {
name: self.name.trim().as_bstr(),
email: self.email.trim().as_bstr(),
time: self.time,
}
}
}
}

Expand Down
14 changes: 14 additions & 0 deletions git-actor/src/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ impl Default for Time {
}

impl Time {
/// Create a new instance from seconds and offset.
pub fn new(seconds_since_unix_epoch: u32, offset_in_seconds: i32) -> Self {
Time {
seconds_since_unix_epoch,
offset_in_seconds,
sign: offset_in_seconds.into(),
}
}

/// Return the passed seconds since epoch since this signature was made.
pub fn seconds(&self) -> u32 {
self.seconds_since_unix_epoch
}

/// Serialize this instance to `out` in a format suitable for use in header fields of serialized git commits or tags.
pub fn write_to(&self, mut out: impl io::Write) -> io::Result<()> {
let mut itoa = itoa::Buffer::new();
Expand Down
10 changes: 9 additions & 1 deletion git-actor/tests/signature/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,20 @@ mod write_to {
use bstr::ByteSlice;
use git_actor::Signature;

#[test]
fn trim() {
let sig = git_actor::SignatureRef::from_bytes::<()>(b" \t hello there \t < \t email \t > 1 -0030").unwrap();
let sig = sig.trim();
assert_eq!(sig.name, "hello there");
assert_eq!(sig.email, "email");
}

#[test]
fn round_trip() -> Result<(), Box<dyn std::error::Error>> {
for input in &[
&b"Sebastian Thiel <[email protected]> 1 -0030"[..],
".. ☺️Sebastian 王知明 Thiel🙌 .. <[email protected]> 1528473343 +0230".as_bytes(),
".. whitespace \t is explicitly allowed - unicode aware trimming must be done elsewhere <[email protected]> 1528473343 +0230"
".. whitespace \t is explicitly allowed - unicode aware trimming must be done elsewhere <[email protected]> 1528473343 +0230"
.as_bytes(),
] {
let signature: Signature = git_actor::SignatureRef::from_bytes::<()>(input)?.into();
Expand Down
39 changes: 30 additions & 9 deletions git-mailmap/src/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,15 @@ pub struct ResolvedSignature<'a> {
}

impl<'a> ResolvedSignature<'a> {
fn try_new(new_email: Option<&'a BString>, new_name: Option<&'a BString>) -> Option<Self> {
fn try_new(
new_email: Option<&'a BString>,
matched_email: &'a BStr,
current_email: &'_ BStr,
new_name: Option<&'a BString>,
) -> Option<Self> {
let new_email = new_email
.map(|n| n.as_bstr())
.or_else(|| (matched_email != current_email).then(|| matched_email));
match (new_email, new_name) {
(None, None) => None,
(new_email, new_name) => Some(ResolvedSignature {
Expand Down Expand Up @@ -244,8 +252,12 @@ impl Snapshot {
/// Try to resolve `signature` by its contained email and name and provide resolved/mapped names as reference.
/// Return `None` if no such mapping was found.
///
/// Note that opposed to what git seems to do, we also normalize the case of email addresses to match the one
/// given in the mailmap. That is, if `[email protected]` is the current email, it will be matched and replaced with
/// `[email protected]`. This leads to better mapping results and saves entries in the mailmap.
///
/// This is the fastest possible lookup as there is no allocation.
pub fn try_resolve_ref<'a>(&'a self, signature: &git_actor::SignatureRef<'_>) -> Option<ResolvedSignature<'a>> {
pub fn try_resolve_ref<'a>(&'a self, signature: git_actor::SignatureRef<'_>) -> Option<ResolvedSignature<'a>> {
let email: EncodedStringRef<'_> = signature.email.into();
let pos = self
.entries_by_old_email
Expand All @@ -257,18 +269,28 @@ impl Snapshot {

match entry.entries_by_old_name.binary_search_by(|e| e.old_name.cmp_ref(name)) {
Ok(pos) => {
let entry = &entry.entries_by_old_name[pos];
ResolvedSignature::try_new(entry.new_email.as_ref(), entry.new_name.as_ref())
let name_entry = &entry.entries_by_old_name[pos];
ResolvedSignature::try_new(
name_entry.new_email.as_ref(),
entry.old_email.as_bstr(),
signature.email,
name_entry.new_name.as_ref(),
)
}
Err(_) => ResolvedSignature::try_new(entry.new_email.as_ref(), entry.new_name.as_ref()),
Err(_) => ResolvedSignature::try_new(
entry.new_email.as_ref(),
entry.old_email.as_bstr(),
signature.email,
entry.new_name.as_ref(),
),
}
}

/// Try to resolve `signature` by its contained email and name and provide resolved/mapped names as owned signature,
/// with the mapped name and/or email replaced accordingly.
///
/// Return `None` if no such mapping was found.
pub fn try_resolve(&self, signature: &git_actor::SignatureRef<'_>) -> Option<git_actor::Signature> {
pub fn try_resolve(&self, signature: git_actor::SignatureRef<'_>) -> Option<git_actor::Signature> {
let new = self.try_resolve_ref(signature)?;
enriched_signature(signature, new)
}
Expand All @@ -277,16 +299,15 @@ impl Snapshot {
/// of `signature` if no mapping was found.
///
/// Note that this method will always allocate.
pub fn resolve(&self, signature: &git_actor::SignatureRef<'_>) -> git_actor::Signature {
pub fn resolve(&self, signature: git_actor::SignatureRef<'_>) -> git_actor::Signature {
self.try_resolve(signature).unwrap_or_else(|| signature.to_owned())
}
}

fn enriched_signature(
SignatureRef { name, email, time }: &SignatureRef<'_>,
SignatureRef { name, email, time }: SignatureRef<'_>,
new: ResolvedSignature<'_>,
) -> Option<git_actor::Signature> {
let time = *time;
match (new.email, new.name) {
(Some(new_email), Some(new_name)) => git_actor::Signature {
email: new_email.to_owned(),
Expand Down
40 changes: 20 additions & 20 deletions git-mailmap/tests/snapshot/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,49 +5,49 @@ use git_testtools::fixture_bytes;
fn try_resolve() {
let snapshot = Snapshot::from_bytes(&fixture_bytes("typical.txt"));
assert_eq!(
snapshot.try_resolve(&signature("Foo", "[email protected]").to_ref()),
Some(signature("Joe R. Developer", "Joe@example.com")),
"resolved signatures contain all original fields, but normalizes only what's in the mapping, lookup is case-insensitive"
snapshot.try_resolve(signature("Foo", "[email protected]").to_ref()),
Some(signature("Joe R. Developer", "joe@example.com")),
"resolved signatures contain all original fields, and normalize the email as well to match the one that it was looked up with"
);
assert_eq!(
snapshot.try_resolve(&signature("Joe", "[email protected]").to_ref()),
snapshot.try_resolve(signature("Joe", "[email protected]").to_ref()),
Some(signature("Joe R. Developer", "[email protected]")),
"name and email can be mapped specifically"
);

assert_eq!(
snapshot.try_resolve(&signature("Jane", "jane@laptop.(none)").to_ref()),
snapshot.try_resolve(signature("Jane", "jane@laptop.(none)").to_ref()),
Some(signature("Jane Doe", "[email protected]")),
"fix name and email by email"
);
assert_eq!(
snapshot.try_resolve(&signature("Jane", "jane@desktop.(none)").to_ref()),
snapshot.try_resolve(signature("Jane", "jane@desktop.(none)").to_ref()),
Some(signature("Jane Doe", "[email protected]")),
"fix name and email by other email"
);

assert_eq!(
snapshot.try_resolve(&signature("janE", "[email protected]").to_ref()),
snapshot.try_resolve(signature("janE", "[email protected]").to_ref()),
Some(signature("Jane Doe", "[email protected]")),
"name and email can be mapped specifically, case insensitive matching of name"
);

let sig = signature("Jane", "[email protected]");
assert_eq!(snapshot.try_resolve(&sig.to_ref()), None, "unmatched email");
assert_eq!(snapshot.try_resolve(sig.to_ref()), None, "unmatched email");

assert_eq!(
snapshot.resolve(&sig.to_ref()),
snapshot.resolve(sig.to_ref()),
sig,
"resolution always works here, returning a copy of the original"
);

let sig = signature("Jean", "[email protected]");
assert_eq!(
snapshot.try_resolve(&sig.to_ref()),
snapshot.try_resolve(sig.to_ref()),
None,
"matched email, unmatched name"
);
assert_eq!(snapshot.resolve(&sig.to_ref()), sig);
assert_eq!(snapshot.resolve(sig.to_ref()), sig);

assert_eq!(snapshot.entries().len(), 5);
}
Expand All @@ -56,7 +56,7 @@ fn try_resolve() {
fn non_name_and_name_mappings_will_not_clash() {
let entries = vec![
// add mapping from email
git_mailmap::Entry::change_name_and_email_by_email("new-name", "new-email", "old-email"),
git_mailmap::Entry::change_name_by_email("new-name", "old-email"),
// add mapping from name and email
git_mailmap::Entry::change_name_and_email_by_name_and_email(
"other-new-name",
Expand All @@ -69,12 +69,12 @@ fn non_name_and_name_mappings_will_not_clash() {
let snapshot = Snapshot::new(entries);

assert_eq!(
snapshot.try_resolve(&signature("replace-by-email", "old-email").to_ref()),
Some(signature("new-name", "new-email")),
"it can match by email only"
snapshot.try_resolve(signature("replace-by-email", "Old-Email").to_ref()),
Some(signature("new-name", "old-email")),
"it can match by email only, and the email is normalized"
);
assert_eq!(
snapshot.try_resolve(&signature("old-name", "old-email").to_ref()),
snapshot.try_resolve(signature("old-name", "Old-Email").to_ref()),
Some(signature("other-new-name", "other-new-email")),
"it can match by email and name as well"
);
Expand All @@ -87,25 +87,25 @@ fn non_name_and_name_mappings_will_not_clash() {
fn overwrite_entries() {
let snapshot = Snapshot::from_bytes(&fixture_bytes("overwrite.txt"));
assert_eq!(
snapshot.try_resolve(&signature("does not matter", "old-a-email").to_ref()),
snapshot.try_resolve(signature("does not matter", "old-a-email").to_ref()),
Some(signature("A-overwritten", "old-a-email")),
"email only by email"
);

assert_eq!(
snapshot.try_resolve(&signature("to be replaced", "old-b-EMAIL").to_ref()),
snapshot.try_resolve(signature("to be replaced", "old-b-EMAIL").to_ref()),
Some(signature("B-overwritten", "new-b-email-overwritten")),
"name and email by email"
);

assert_eq!(
snapshot.try_resolve(&signature("old-c", "old-C-email").to_ref()),
snapshot.try_resolve(signature("old-c", "old-C-email").to_ref()),
Some(signature("C-overwritten", "new-c-email-overwritten")),
"name and email by name and email"
);

assert_eq!(
snapshot.try_resolve(&signature("unchanged", "old-d-email").to_ref()),
snapshot.try_resolve(signature("unchanged", "old-d-email").to_ref()),
Some(signature("unchanged", "new-d-email-overwritten")),
"email by email"
);
Expand Down
19 changes: 19 additions & 0 deletions git-object/src/commit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,29 @@ impl<'a> CommitRef<'a> {
crate::commit::ExtraHeaders::new(self.extra_headers.iter().map(|(k, v)| (*k, v.as_ref())))
}

/// Return the author, with whitespace trimmed.
///
/// This is different from the `author` field which may contain whitespace.
pub fn author(&self) -> git_actor::SignatureRef<'a> {
self.author.trim()
}

/// Return the committer, with whitespace trimmed.
///
/// This is different from the `committer` field which may contain whitespace.
pub fn committer(&self) -> git_actor::SignatureRef<'a> {
self.committer.trim()
}

/// Returns a partially parsed message from which more information can be derived.
pub fn message(&self) -> MessageRef<'a> {
MessageRef::from_bytes(self.message)
}

/// Returns the time at which this commit was created.
pub fn time(&self) -> git_actor::Time {
self.committer.time
}
}

impl Commit {
Expand Down
Loading