cargo_smart_release/changelog/
init.rs1use 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}