Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions mailmap/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ impl Mailmap {
let file = Pin::new(file.into_boxed_str());
let mut entries = Vec::with_capacity(file.lines().count());
for (idx, line) in file.lines().enumerate() {
if let Some(entry) = parse_line(&line, idx + 1) {
if let Some(entry) = parse_line(line, idx + 1) {
entries.push(entry.to_raw_entry());
}
}
Expand Down Expand Up @@ -152,11 +152,7 @@ fn read_email<'a>(line: &mut &'a str) -> Option<&'a str> {
}

fn read_name<'a>(line: &mut &'a str) -> Option<&'a str> {
let end = if let Some(end) = line.find('<') {
end
} else {
return None;
};
let end = line.find('<')?;
let ret = &line[..end].trim();
*line = &line[end..];
if ret.is_empty() {
Expand Down
83 changes: 32 additions & 51 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,7 @@ impl AuthorMap {
///
/// If the author is not already included in the map, they are added.
fn add(&mut self, author: Author, commit: Oid) {
self.map
.entry(author)
.or_insert_with(HashSet::new)
.insert(commit);
self.map.entry(author).or_default().insert(commit);
}

/// Iterate over each author and the number of commits that they (co-)authored.
Expand All @@ -72,10 +69,7 @@ impl AuthorMap {
/// Merge in the authorship data from another instance.
fn extend(&mut self, other: Self) {
for (author, set) in other.map {
self.map
.entry(author)
.or_insert_with(HashSet::new)
.extend(set);
self.map.entry(author).or_default().extend(set);
}
}

Expand All @@ -86,7 +80,7 @@ impl AuthorMap {
let mut new = AuthorMap::new();
new.map.reserve(self.map.len());
for (author, set) in self.map.iter() {
if let Some(other_set) = other.map.get(&author) {
if let Some(other_set) = other.map.get(author) {
let diff: HashSet<_> = set.difference(other_set).cloned().collect();
if !diff.is_empty() {
new.map.insert(author.clone(), diff);
Expand Down Expand Up @@ -175,13 +169,13 @@ fn update_repo(url: &str) -> Result<PathBuf, Box<dyn std::error::Error>> {
"--dissociate",
"--reference",
&tmp,
&url,
url,
&path_s,
])?;
std::fs::remove_dir_all(&tmp)?;
}
} else {
git(&["clone", "--bare", &url, &path_s])?;
git(&["clone", "--bare", url, &path_s])?;
}
Ok(path)
}
Expand Down Expand Up @@ -232,7 +226,7 @@ impl cmp::PartialEq for VersionTag {

impl cmp::PartialOrd for VersionTag {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(&other))
Some(self.cmp(other))
}
}

Expand All @@ -252,21 +246,21 @@ fn get_versions(repo: &Repository) -> Result<Vec<VersionTag>, Box<dyn std::error
let tags = repo
.tag_names(None)?
.into_iter()
.filter_map(|v| v)
.flatten()
.map(|v| v.to_owned())
.collect::<Vec<_>>();
let mut versions = tags
.iter()
.filter_map(|tag| {
Version::parse(&tag)
Version::parse(tag)
.or_else(|_| Version::parse(&format!("{}.0", tag)))
.ok()
.map(|v| VersionTag {
name: format!("Rust {}", v),
version: v,
raw_tag: tag.clone(),
commit: repo
.revparse_single(&tag)
.revparse_single(tag)
.unwrap()
.peel_to_commit()
.unwrap()
Expand Down Expand Up @@ -369,16 +363,15 @@ fn parse_bors_reviewer(
repo: &Repository,
commit: &Commit,
) -> Result<Option<Vec<Author>>, ErrorContext> {
let is_old_bors = commit.author().name_bytes() == b"bors" && commit.committer().name_bytes() == b"bors";
let is_old_bors =
commit.author().name_bytes() == b"bors" && commit.committer().name_bytes() == b"bors";
// This username was used for merges for a ~week from January 7 to January 12 2026 on the
// rust-lang/rust repository.
let is_new_bors = commit.author().name_bytes() == b"rust-bors[bot]";
let is_bors = is_old_bors || is_new_bors;

if !is_bors {
if commit.committer().name_bytes() != b"GitHub" || !is_rollup_commit(commit) {
return Ok(None);
}
if !is_bors && (commit.committer().name_bytes() != b"GitHub" || !is_rollup_commit(commit)) {
return Ok(None);
}

// Skip non-merge commits
Expand All @@ -388,15 +381,15 @@ fn parse_bors_reviewer(

let to_author = |list: &str| -> Result<Vec<Author>, ErrorContext> {
list.trim_end_matches('.')
.split(|c| c == ',' || c == '+')
.split([',', '+'])
.map(|r| r.trim_start_matches('@'))
.map(|r| r.trim_end_matches('`'))
.map(|r| r.trim())
.filter(|r| !r.is_empty())
.filter(|r| *r != "<try>")
.inspect(|r| {
if !r.chars().all(|c| {
c.is_alphabetic() || c.is_digit(10) || c == '-' || c == '_' || c == '='
c.is_alphabetic() || c.is_ascii_digit() || c == '-' || c == '_' || c == '='
}) {
eprintln!(
"warning: to_author for {} contained non-alphabetic characters: {:?}",
Expand Down Expand Up @@ -439,7 +432,7 @@ fn parse_bors_reviewer(
to_author(&line[start..end])?
} else if let Some(line) = message.lines().find(|l| l.starts_with("Reviewed-by: ")) {
let line = &line["Reviewed-by: ".len()..];
to_author(&line)?
to_author(line)?
} else {
// old bors didn't include r=
if message != "automated merge\n" {
Expand Down Expand Up @@ -499,7 +492,7 @@ fn build_author_map_(
])?;
}

if from == "" {
if from.is_empty() {
let to = repo.revparse_single(to)?.peel_to_commit()?.id();
walker.push(to)?;
} else {
Expand All @@ -522,7 +515,7 @@ fn build_author_map_(
// rollup, which isn't fair.
commit_authors.push(Author::from_sig(commit.author()));
}
match parse_bors_reviewer(&reviewers, &repo, &commit) {
match parse_bors_reviewer(reviewers, repo, &commit) {
Ok(Some(reviewers)) => commit_authors.extend(reviewers),
Ok(None) => {}
Err(ErrorContext(msg, e)) => {
Expand All @@ -547,23 +540,16 @@ fn build_author_map_(
/// Returns an error if the latest commit cannot be retrieved or if it does not
/// contain a `.mailmap` file to read.
fn mailmap_from_repo(repo: &git2::Repository) -> Result<Mailmap, Box<dyn std::error::Error>> {
let tree = repo.revparse_single("HEAD")?
.peel_to_commit()?
.tree()?;
let tree = repo.revparse_single("HEAD")?.peel_to_commit()?.tree()?;
let file = tree.get_name(".mailmap");
let file = match file {
None => {
eprintln!("No mailmap found");
return Mailmap::from_string("".to_string());
},
Some(f) => f
}
Some(f) => f,
};
let file = String::from_utf8(
file.to_object(&repo)?
.peel_to_blob()?
.content()
.into(),
)?;
let file = String::from_utf8(file.to_object(repo)?.peel_to_blob()?.content().into())?;
Mailmap::from_string(file)
}

Expand All @@ -583,21 +569,16 @@ fn up_to_release(
Box::new(e),
)
})?;
let modules = get_submodules(&repo, &to_commit)?;
let modules = get_submodules(repo, &to_commit)?;

let mut author_map = build_author_map(&repo, &reviewers, &mailmap, "", &to.raw_tag)
let mut author_map = build_author_map(repo, reviewers, mailmap, "", &to.raw_tag)
.map_err(|e| ErrorContext(format!("Up to {}", to), e))?;

for module in &modules {
if let Ok(path) = update_repo(&module.repository) {
let subrepo = Repository::open(&path)?;
let submap = build_author_map(
&subrepo,
&reviewers,
&mailmap,
"",
&module.commit.to_string(),
)?;
let submap =
build_author_map(&subrepo, reviewers, mailmap, "", &module.commit.to_string())?;
author_map.extend(submap);
}
}
Expand Down Expand Up @@ -670,11 +651,11 @@ fn generate_thanks() -> Result<BTreeMap<VersionTag, AuthorMap>, Box<dyn std::err

cache.insert(
version,
up_to_release(&repo, &reviewers, &mailmap, &version)?,
up_to_release(&repo, &reviewers, &mailmap, version)?,
);
let previous = match cache.remove(&previous) {
Some(v) => v,
None => up_to_release(&repo, &reviewers, &mailmap, &previous)?,
None => up_to_release(&repo, &reviewers, &mailmap, previous)?,
};
let current = cache.get(&version).unwrap();

Expand Down Expand Up @@ -725,7 +706,7 @@ fn get_submodules(
repo: &Repository,
at: &Commit,
) -> Result<Vec<Submodule>, Box<dyn std::error::Error>> {
let submodule_cfg = modules_file(&repo, &at)?;
let submodule_cfg = modules_file(repo, at)?;
let submodule_cfg = Config::parse(&submodule_cfg)?;
let mut path_to_url = HashMap::new();
let entries = submodule_cfg.entries(None)?;
Expand All @@ -742,7 +723,7 @@ fn get_submodules(
let tree = at.tree()?;
for (path, url) in &path_to_url {
let path = Path::new(&path);
let entry = tree.get_path(&path);
let entry = tree.get_path(path);
// the submodule may not actually exist
let entry = match entry {
Ok(e) => e,
Expand Down Expand Up @@ -786,9 +767,9 @@ fn get_submodules(
fn modules_file(repo: &Repository, at: &Commit) -> Result<String, Box<dyn std::error::Error>> {
if let Some(modules) = at.tree()?.get_name(".gitmodules") {
Ok(String::from_utf8(
modules.to_object(&repo)?.peel_to_blob()?.content().into(),
modules.to_object(repo)?.peel_to_blob()?.content().into(),
)?)
} else {
return Ok(String::new());
Ok(String::new())
}
}
42 changes: 24 additions & 18 deletions src/site.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ fn create_dir<P: AsRef<Path>>(p: P) -> Result<(), std::io::Error> {
match fs::create_dir_all(p) {
Ok(()) => {}
Err(ref e) if e.kind() == std::io::ErrorKind::AlreadyExists => {}
Err(e) => return Err(e.into()),
Err(e) => return Err(e),
};
Ok(())
}
Expand All @@ -64,7 +64,7 @@ fn copy_public() -> Result<(), Box<dyn std::error::Error>> {
Path::new("output").join(entry.path().strip_prefix("public/")?),
)?;
} else if entry.file_type().is_dir() {
create_dir(&Path::new("output").join(entry.path().strip_prefix("public/")?))?;
create_dir(Path::new("output").join(entry.path().strip_prefix("public/")?))?;
}
}
Ok(())
Expand Down Expand Up @@ -168,7 +168,7 @@ fn author_map_to_scores(map: &AuthorMap) -> Vec<Entry> {

let mut last_rank = 1;
let mut ranked_at_current = 0;
let mut last_commits = usize::max_value();
let mut last_commits = usize::MAX;
for entry in &mut scores {
if entry.commits < last_commits {
last_commits = entry.commits;
Expand All @@ -189,21 +189,27 @@ fn author_map_to_scores(map: &AuthorMap) -> Vec<Entry> {
fn deduplicate_scores(entries: Vec<Entry>) -> Vec<Entry> {
let mut entry_map: HashMap<String, Vec<Entry>> = HashMap::with_capacity(entries.len());
for entry in entries {
entry_map.entry(entry.email.clone()).or_default().push(entry);
entry_map
.entry(entry.email.clone())
.or_default()
.push(entry);
}

entry_map.into_values().map(|mut entry| {
// If there are multiple entries with the same maximum commit count, ensure that
// the ordering is stable, by sorting based on the whole entry.
entry.sort();
let canonical_entry = entry.iter().max_by_key(|entry| entry.commits).unwrap();
Entry {
rank: 0,
author: canonical_entry.author.clone(),
email: canonical_entry.email.clone(),
commits: entry.iter().map(|e| e.commits).sum(),
}
}).collect()
entry_map
.into_values()
.map(|mut entry| {
// If there are multiple entries with the same maximum commit count, ensure that
// the ordering is stable, by sorting based on the whole entry.
entry.sort();
let canonical_entry = entry.iter().max_by_key(|entry| entry.commits).unwrap();
Entry {
rank: 0,
author: canonical_entry.author.clone(),
email: canonical_entry.email.clone(),
commits: entry.iter().map(|e| e.commits).sum(),
}
})
.collect()
}

fn releases(
Expand All @@ -220,7 +226,7 @@ fn releases(
in_progress: bool,
}
let hb = hb()?;
let scores = author_map_to_scores(&all_time);
let scores = author_map_to_scores(all_time);

let res = hb.render(
"stats",
Expand All @@ -238,7 +244,7 @@ fn releases(
fs::write("output/rust/all-time/index.html", res)?;

for (version, map) in by_version {
let scores = author_map_to_scores(&map);
let scores = author_map_to_scores(map);
let res = hb.render(
"stats",
&Release {
Expand Down