use crate::declare_detector;
use crate::diagnostic::{Action, ChangelogAction, Diagnostic};
use crate::{FixerError, FixerPreferences};
use debian_changelog::iter_changes_by_author;
use debian_workspace::Workspace;
use std::collections::HashMap;
use std::path::PathBuf;
pub fn detect(
ws: &dyn Workspace,
_preferences: &FixerPreferences,
) -> Result<Vec<Diagnostic>, FixerError> {
let changelog_rel = PathBuf::from("debian/changelog");
let changelog = match ws.parsed_changelog() {
Ok(c) => c,
Err(debian_workspace::Error::NotFound) => return Ok(Vec::new()),
Err(e) => return Err(e.into()),
};
let Some(first_entry) = changelog.iter().next() else {
return Ok(Vec::new());
};
if first_entry.is_unreleased() != Some(true) {
return Ok(Vec::new());
}
let first_package = first_entry.package();
let Some(first_version) = first_entry.version() else {
return Ok(Vec::new());
};
let first_version_str = first_version.to_string();
let mut counts: HashMap<(Option<String>, String), usize> = HashMap::new();
let mut diagnostics = Vec::new();
for change in iter_changes_by_author(&changelog) {
if change.package() != first_package
|| change.version().map(|v| v.to_string()) != Some(first_version_str.clone())
{
continue;
}
for bullet in change.split_into_bullets() {
let author = bullet.author().map(|s| s.to_string());
let text = bullet.lines().join("\n");
let key = (author.clone(), text.clone());
let count = counts.entry(key).or_insert(0);
if *count > 0 {
let action = Action::Changelog(ChangelogAction::RemoveBullet {
file: changelog_rel.clone(),
version: first_version_str.clone(),
author,
text,
occurrence: *count,
});
diagnostics.push(Diagnostic::untagged(
"Duplicate line in changelog.",
"Remove duplicate line from changelog.",
vec![action],
));
}
*count += 1;
}
}
Ok(diagnostics)
}
declare_detector! {
name: "changelog-has-duplicate-line",
tags: [],
triggers: [debian_workspace::Trigger::Changelog(
debian_workspace::ChangelogAspect::Body,
)],
detect: |ws, prefs| detect(ws, prefs),
}
#[cfg(test)]
mod tests {
use super::*;
use crate::detector::Detector;
use crate::{FixerPreferences, Version};
use std::fs;
use std::path::Path;
use tempfile::TempDir;
fn run_apply(base: &Path) -> Result<crate::FixerResult, FixerError> {
let version: Version = "1.0".parse().unwrap();
let adapter = DetectorImpl;
{
let ws = debian_workspace::fs_workspace::FsWorkspace::new(
base,
Some("test".into()),
Some(version.clone()),
);
adapter.apply(&ws, &FixerPreferences::default())
}
}
#[test]
fn test_simple_duplicate() {
let tmp = TempDir::new().unwrap();
let debian = tmp.path().join("debian");
fs::create_dir(&debian).unwrap();
let path = debian.join("changelog");
fs::write(
&path,
"blah (5.42+dfsg1-2) UNRELEASED; urgency=medium\n\n * New upstream release.\n * Fix day-of-week for changelog entry 4.23-1.\n * New upstream release.\n\n -- Jelmer Vernooij <jelmer@debian.org> Mon, 30 Dec 2019 15:25:35 +0000\n\nblah (5.42+dfsg1-1) unstable; urgency=medium\n\n * Initial Release.\n * Initial Release.\n\n -- Somebody <somebody@example.com> Fri, 25 Jan 2019 00:15:07 +0100\n",
)
.unwrap();
let result = run_apply(tmp.path()).unwrap();
assert_eq!(result.description, "Remove duplicate line from changelog.");
assert_eq!(
fs::read_to_string(&path).unwrap(),
"blah (5.42+dfsg1-2) UNRELEASED; urgency=medium\n\n * New upstream release.\n * Fix day-of-week for changelog entry 4.23-1.\n\n -- Jelmer Vernooij <jelmer@debian.org> Mon, 30 Dec 2019 15:25:35 +0000\n\nblah (5.42+dfsg1-1) unstable; urgency=medium\n\n * Initial Release.\n * Initial Release.\n\n -- Somebody <somebody@example.com> Fri, 25 Jan 2019 00:15:07 +0100\n",
);
}
#[test]
fn test_already_released() {
let tmp = TempDir::new().unwrap();
let debian = tmp.path().join("debian");
fs::create_dir(&debian).unwrap();
let path = debian.join("changelog");
let content = "blah (5.42+dfsg1-2) unstable; urgency=medium\n\n * New upstream release.\n * Fix day-of-week for changelog entry 4.23-1.\n * New upstream release.\n\n -- Jelmer Vernooij <jelmer@debian.org> Mon, 30 Dec 2019 15:25:35 +0000\n";
fs::write(&path, content).unwrap();
assert!(matches!(run_apply(tmp.path()), Err(FixerError::NoChanges)));
assert_eq!(fs::read_to_string(&path).unwrap(), content);
}
#[test]
fn test_no_duplicates() {
let tmp = TempDir::new().unwrap();
let debian = tmp.path().join("debian");
fs::create_dir(&debian).unwrap();
fs::write(
debian.join("changelog"),
"blah (5.42+dfsg1-2) UNRELEASED; urgency=medium\n\n * New upstream release.\n * Fix day-of-week for changelog entry 4.23-1.\n\n -- Jelmer Vernooij <jelmer@debian.org> Mon, 30 Dec 2019 15:25:35 +0000\n",
)
.unwrap();
assert!(matches!(run_apply(tmp.path()), Err(FixerError::NoChanges)));
}
#[test]
fn test_no_changelog() {
let tmp = TempDir::new().unwrap();
assert!(matches!(run_apply(tmp.path()), Err(FixerError::NoChanges)));
}
}