cargo_smart_release/changelog/
init.rs

1use anyhow::Context;
2use cargo_metadata::{
3    camino::{Utf8Path, Utf8PathBuf},
4    Package,
5};
6
7use crate::{
8    changelog::{section::segment, Section},
9    commit, ChangeLog,
10};
11
12#[derive(Clone, Copy)]
13pub enum State {
14    Created,
15    Modified,
16    Unchanged,
17}
18
19impl State {
20    pub fn is_modified(&self) -> bool {
21        !matches!(self, State::Unchanged)
22    }
23    pub fn as_str(&self) -> &'static str {
24        match self {
25            State::Created => "created",
26            State::Modified => "modified",
27            State::Unchanged => "unchanged",
28        }
29    }
30}
31
32pub struct Outcome {
33    pub log: ChangeLog,
34    pub state: State,
35    pub lock: gix::lock::File,
36    pub previous_content: Option<String>,
37}
38
39impl ChangeLog {
40    pub fn for_package_with_write_lock<'a>(
41        package: &'a Package,
42        history: &commit::History,
43        ctx: &'a crate::Context,
44        selection: segment::Selection,
45    ) -> anyhow::Result<Outcome> {
46        let mut generated = ChangeLog::from_history_segments(
47            package,
48            &crate::git::history::crate_ref_segments(
49                package,
50                ctx,
51                history,
52                crate::git::history::SegmentScope::EntireHistory,
53            )?,
54            &ctx.repo,
55            selection,
56        );
57        generated.sections.insert(
58            0,
59            Section::Verbatim {
60                text: include_str!("header.md").to_owned(),
61                generated: true,
62            },
63        );
64        let changelog_path = path_from_manifest(&package.manifest_path);
65        let lock =
66            gix::lock::File::acquire_to_update_resource(&changelog_path, gix::lock::acquire::Fail::Immediately, None)?;
67        let (log, state, previous_content) = if let Ok(markdown) = std::fs::read_to_string(changelog_path) {
68            let existing_log = ChangeLog::from_markdown(&markdown);
69            let copy_of_existing = existing_log.clone();
70            let merged = existing_log
71                .merge_generated(generated)
72                .with_context(|| format!("Changelog generation for crate {:?} failed", package.name))?;
73            let changed = merged != copy_of_existing;
74            (
75                merged,
76                if changed { State::Modified } else { State::Unchanged },
77                Some(markdown),
78            )
79        } else {
80            (generated, State::Created, None)
81        };
82        Ok(Outcome {
83            log,
84            state,
85            lock,
86            previous_content,
87        })
88    }
89
90    pub fn for_crate_by_name_with_write_lock<'a>(
91        package: &'a Package,
92        history: &commit::History,
93        ctx: &'a crate::Context,
94        selection: segment::Selection,
95    ) -> anyhow::Result<(Outcome, &'a Package)> {
96        let out = Self::for_package_with_write_lock(package, history, ctx, selection)?;
97        Ok((out, package))
98    }
99
100    pub fn from_history_segments(
101        package: &Package,
102        segments: &[commit::history::Segment<'_>],
103        repo: &gix::Repository,
104        selection: segment::Selection,
105    ) -> Self {
106        ChangeLog {
107            sections: {
108                let mut s = segments.windows(2).fold(Vec::new(), |mut acc, segments| {
109                    acc.push(Section::from_history_segment(
110                        package,
111                        &segments[0],
112                        repo,
113                        selection,
114                        (&segments[1]).into(),
115                    ));
116                    acc
117                });
118                if let Some(segment) = segments.last() {
119                    s.push(Section::from_history_segment(package, segment, repo, selection, None))
120                }
121                s
122            },
123        }
124    }
125}
126
127fn path_from_manifest(path: &Utf8Path) -> Utf8PathBuf {
128    path.parent().expect("parent for Cargo.toml").join("CHANGELOG.md")
129}