…with it being added only if there is no other section present yet. That way changelogs can be configured intuitively.
Unified · Split
| @@ -7,7 +7,101 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 · 6 unmodified lines | |||
| 7 | 7 | ||
| 8 | ## Unreleased | 8 | ## Unreleased |
| 9 | 9 | ||
| 10 | ## v0.6.0 | 10 | ### Commit Statistics |
| 11 | |||
| 12 | <csr-read-only-do-not-edit/> | ||
| 13 | |||
| 14 | - 271 commits contributed to the release over the course of 18 calendar days. | ||
| 15 | - 57 commits where understood as [conventional](https://www.conventionalcommits.org). | ||
| 16 | - 9 unique issues were worked on | ||
| 17 | |||
| 18 | ### Thanks Clippy | ||
| 19 | |||
| 20 | <csr-read-only-do-not-edit/> | ||
| 21 | |||
| 22 | [Clippy](https://github.com/rust-lang/rust-clippy) helped 19 times to make code idiomatic. | ||
| 23 | |||
| 24 | ## v0.8.4 (2021-09-10) | ||
| 25 | |||
| 26 | ### Commit Statistics | ||
| 27 | |||
| 28 | <csr-read-only-do-not-edit/> | ||
| 29 | |||
| 30 | - 47 commits contributed to the release over the course of 3 calendar days. | ||
| 31 | - 0 commits where understood as [conventional](https://www.conventionalcommits.org). | ||
| 32 | - 1 unique issue was worked on | ||
| 33 | |||
| 34 | ### Thanks Clippy | ||
| 35 | |||
| 36 | <csr-read-only-do-not-edit/> | ||
| 37 | |||
| 38 | [Clippy](https://github.com/rust-lang/rust-clippy) helped 3 times to make code idiomatic. | ||
| 39 | |||
| 40 | ## v0.8.3 (2021-09-07) | ||
| 41 | |||
| 42 | ### Commit Statistics | ||
| 43 | |||
| 44 | <csr-read-only-do-not-edit/> | ||
| 45 | |||
| 46 | - 367 commits contributed to the release over the course of 20 calendar days. | ||
| 47 | - 1 commit where understood as [conventional](https://www.conventionalcommits.org). | ||
| 48 | - 2 unique issues were worked on | ||
| 49 | |||
| 50 | ### Thanks Clippy | ||
| 51 | |||
| 52 | <csr-read-only-do-not-edit/> | ||
| 53 | |||
| 54 | [Clippy](https://github.com/rust-lang/rust-clippy) helped 8 times to make code idiomatic. | ||
| 55 | |||
| 56 | ## v0.8.2 (2021-08-17) | ||
| 57 | |||
| 58 | ### Commit Statistics | ||
| 59 | |||
| 60 | <csr-read-only-do-not-edit/> | ||
| 61 | |||
| 62 | - 80 commits contributed to the release. | ||
| 63 | - 0 commits where understood as [conventional](https://www.conventionalcommits.org). | ||
| 64 | - 1 unique issue was worked on | ||
| 65 | |||
| 66 | ### Thanks Clippy | ||
| 67 | |||
| 68 | <csr-read-only-do-not-edit/> | ||
| 69 | |||
| 70 | [Clippy](https://github.com/rust-lang/rust-clippy) helped 3 times to make code idiomatic. | ||
| 71 | |||
| 72 | ## v0.8.1 (2021-08-15) | ||
| 73 | |||
| 74 | ### Commit Statistics | ||
| 75 | |||
| 76 | <csr-read-only-do-not-edit/> | ||
| 77 | |||
| 78 | - 1470 commits contributed to the release over the course of 98 calendar days. | ||
| 79 | - 0 commits where understood as [conventional](https://www.conventionalcommits.org). | ||
| 80 | - 3 unique issues were worked on | ||
| 81 | |||
| 82 | ### Thanks Clippy | ||
| 83 | |||
| 84 | <csr-read-only-do-not-edit/> | ||
| 85 | |||
| 86 | [Clippy](https://github.com/rust-lang/rust-clippy) helped 46 times to make code idiomatic. | ||
| 87 | |||
| 88 | ## v0.7.0 (2021-05-09) | ||
| 89 | |||
| 90 | ### Commit Statistics | ||
| 91 | |||
| 92 | <csr-read-only-do-not-edit/> | ||
| 93 | |||
| 94 | - 807 commits contributed to the release over the course of 143 calendar days. | ||
| 95 | - 1 commit where understood as [conventional](https://www.conventionalcommits.org). | ||
| 96 | - 6 unique issues were worked on | ||
| 97 | |||
| 98 | ### Thanks Clippy | ||
| 99 | |||
| 100 | <csr-read-only-do-not-edit/> | ||
| 101 | |||
| 102 | [Clippy](https://github.com/rust-lang/rust-clippy) helped 30 times to make code idiomatic. | ||
| 103 | |||
| 104 | ## v0.6.0 (2020-12-16) | ||
| 11 | 105 | ||
| 12 | Maintenance release without any new features. | 106 | Maintenance release without any new features. |
| 13 | 107 | ||
| @@ -19,16 +113,24 @@ This was eventually fixed with new minor releases across the ecosystem. · 5 unmodified lines | |||
| 19 | Finally, yet another breaking change due to the introduction of the `git-hash` | 113 | Finally, yet another breaking change due to the introduction of the `git-hash` |
| 20 | crate to break a dependency cycle between `git-object` and `git-features` caused | 114 | crate to break a dependency cycle between `git-object` and `git-features` caused |
| 21 | yet another maintenance release. | 115 | yet another maintenance release. |
| 22 | 116 | ## v0.5.0 (2020-12-15) | |
| 23 | ## v0.5.0 | ||
| 24 | 117 | ||
| 25 | Maintenance release without any new features. | 118 | Maintenance release without any new features. |
| 119 | ## v0.4.3 (2020-09-21) | ||
| 120 | |||
| 121 | ### Commit Statistics | ||
| 122 | |||
| 123 | <csr-read-only-do-not-edit/> | ||
| 26 | 124 | ||
| 27 | ## v0.4.1 | 125 | - 18 commits contributed to the release over the course of 2 calendar days. |
| 126 | - 1 commit where understood as [conventional](https://www.conventionalcommits.org). | ||
| 127 | - 1 unique issue was worked on | ||
| 128 | |||
| 129 | ## v0.4.1 (2020-09-18) | ||
| 28 | 130 | ||
| 29 | * fix installation via `cargo install` | 131 | * fix installation via `cargo install` |
| 30 | 132 | ||
| 31 | ## v0.4.0 | 133 | ## v0.4.0 (2020-09-12) |
| 32 | 134 | ||
| 33 | * add `remote-ref-list` and `pack-receive` subcommands to **gixp** | 135 | * add `remote-ref-list` and `pack-receive` subcommands to **gixp** |
| 34 | 136 | ||
| @@ -36,13 +138,12 @@ Maintenance release without any new features. · 1 unmodified line | |||
| 36 | 138 | ||
| 37 | * rename plumbing sub-command from `index-from-pack` to `pack-index-from-data` | 139 | * rename plumbing sub-command from `index-from-pack` to `pack-index-from-data` |
| 38 | 140 | ||
| 39 | ## v0.3.0 | 141 | ## v0.3.0 (2020-08-12) |
| 40 | 142 | ||
| 41 | * add `pack-explode` and `pack-index-from-data` sub-commands | 143 | * add `pack-explode` and `pack-index-from-data` sub-commands |
| 42 | * massive speed improvements for `pack-verify` | 144 | * massive speed improvements for `pack-verify` |
| 43 | 145 | ||
| 44 | Many small and possibly breaking changes are not mentioned here. | 146 | Many small and possibly breaking changes are not mentioned here. |
| 45 | 147 | ## v0.1.0 (2020-07-12) | |
| 46 | ## v0.1.0 | ||
| 47 | 148 | ||
| 48 | * Initial release with `pack-verify` | 149 | * Initial release with `pack-verify` |
| @@ -38,10 +38,7 @@ impl ChangeLog { · 37 unmodified lines | |||
| 38 | Section::Release { ref name, .. } => match find_target_section(name, sections, first_release_pos) { | 38 | Section::Release { ref name, .. } => match find_target_section(name, sections, first_release_pos) { |
| 39 | Insertion::MergeWith(pos) => merge_section(&mut sections[pos], section_to_merge), | 39 | Insertion::MergeWith(pos) => merge_section(&mut sections[pos], section_to_merge), |
| 40 | Insertion::At(pos) => { | 40 | Insertion::At(pos) => { |
| 41 | if let Section::Release { | 41 | if let Section::Release { heading_level, .. } = &mut section_to_merge { |
| 42 | ref mut heading_level, .. | ||
| 43 | } = section_to_merge | ||
| 44 | { | ||
| 45 | *heading_level = first_release_indentation; | 42 | *heading_level = first_release_indentation; |
| 46 | } | 43 | } |
| 47 | sections.insert(pos, section_to_merge); | 44 | sections.insert(pos, section_to_merge); |
| @@ -73,6 +70,11 @@ fn merge_section(dest: &mut Section, src: Section) { · 25 unmodified lines | |||
| 73 | }, | 70 | }, |
| 74 | ) => { | 71 | ) => { |
| 75 | assert!(rhs_unknown.is_empty(), "shouldn't ever generate 'unknown' portions"); | 72 | assert!(rhs_unknown.is_empty(), "shouldn't ever generate 'unknown' portions"); |
| 73 | let mode = if lhs_segments.is_empty() { | ||
| 74 | ReplaceMode::ReplaceAllOrAppend | ||
| 75 | } else { | ||
| 76 | ReplaceMode::ReplaceAllOrAppendIfPresentInLhs | ||
| 77 | }; | ||
| 76 | for rhs_segment in rhs_segments { | 78 | for rhs_segment in rhs_segments { |
| 77 | match rhs_segment { | 79 | match rhs_segment { |
| 78 | section::Segment::User { .. } => unreachable!("BUG: User segments are never auto-generated"), | 80 | section::Segment::User { .. } => unreachable!("BUG: User segments are never auto-generated"), |
| @@ -80,14 +82,20 @@ fn merge_section(dest: &mut Section, src: Section) { · 1 unmodified line | |||
| 80 | unreachable!("BUG: Clippy is set if generated, or not present") | 82 | unreachable!("BUG: Clippy is set if generated, or not present") |
| 81 | } | 83 | } |
| 82 | clippy @ section::Segment::Clippy(_) => { | 84 | clippy @ section::Segment::Clippy(_) => { |
| 83 | replace_all_or_append(lhs_segments, |s| matches!(s, section::Segment::Clippy(_)), clippy) | 85 | merge_segment(lhs_segments, |s| matches!(s, section::Segment::Clippy(_)), clippy, mode) |
| 84 | } | ||
| 85 | stats @ section::Segment::Statistics(_) => { | ||
| 86 | replace_all_or_append(lhs_segments, |s| matches!(s, section::Segment::Statistics(_)), stats) | ||
| 87 | } | ||
| 88 | details @ section::Segment::Details(_) => { | ||
| 89 | replace_all_or_append(lhs_segments, |s| matches!(s, section::Segment::Details(_)), details) | ||
| 90 | } | 86 | } |
| 87 | stats @ section::Segment::Statistics(_) => merge_segment( | ||
| 88 | lhs_segments, | ||
| 89 | |s| matches!(s, section::Segment::Statistics(_)), | ||
| 90 | stats, | ||
| 91 | mode, | ||
| 92 | ), | ||
| 93 | details @ section::Segment::Details(_) => merge_segment( | ||
| 94 | lhs_segments, | ||
| 95 | |s| matches!(s, section::Segment::Details(_)), | ||
| 96 | details, | ||
| 97 | mode, | ||
| 98 | ), | ||
| 91 | } | 99 | } |
| 92 | } | 100 | } |
| 93 | *lhs_date = rhs_date; | 101 | *lhs_date = rhs_date; |
| @@ -95,13 +103,24 @@ fn merge_section(dest: &mut Section, src: Section) { · 1 unmodified line | |||
| 95 | } | 103 | } |
| 96 | } | 104 | } |
| 97 | 105 | ||
| 98 | fn replace_all_or_append(dest: &mut Vec<Segment>, mut filter: impl FnMut(§ion::Segment) -> bool, insert: Segment) { | 106 | #[derive(Clone, Copy)] |
| 107 | enum ReplaceMode { | ||
| 108 | ReplaceAllOrAppend, | ||
| 109 | ReplaceAllOrAppendIfPresentInLhs, | ||
| 110 | } | ||
| 111 | |||
| 112 | fn merge_segment( | ||
| 113 | dest: &mut Vec<Segment>, | ||
| 114 | mut filter: impl FnMut(§ion::Segment) -> bool, | ||
| 115 | insert: Segment, | ||
| 116 | mode: ReplaceMode, | ||
| 117 | ) { | ||
| 99 | let mut found_one = false; | 118 | let mut found_one = false; |
| 100 | for dest_segment in dest.iter_mut().filter(|s| filter(s)) { | 119 | for dest_segment in dest.iter_mut().filter(|s| filter(s)) { |
| 101 | *dest_segment = insert.clone(); | 120 | *dest_segment = insert.clone(); |
| 102 | found_one = true; | 121 | found_one = true; |
| 103 | } | 122 | } |
| 104 | if !found_one { | 123 | if !found_one && matches!(mode, ReplaceMode::ReplaceAllOrAppend) { |
| 105 | dest.push(insert); | 124 | dest.push(insert); |
| 106 | } | 125 | } |
| 107 | } | 126 | } |
| @@ -139,6 +139,130 @@ fn only_last_release_without_unreleased_section() { · 138 unmodified lines | |||
| 139 | ); | 139 | ); |
| 140 | } | 140 | } |
| 141 | 141 | ||
| 142 | #[test] | ||
| 143 | fn into_previously_generated_with_removed_statistical_segments() { | ||
| 144 | let parsed = ChangeLog { | ||
| 145 | sections: vec![ | ||
| 146 | Section::Verbatim { | ||
| 147 | text: "preamble".into(), | ||
| 148 | generated: false, | ||
| 149 | }, | ||
| 150 | Section::Release { | ||
| 151 | date: None, | ||
| 152 | name: changelog::Version::Unreleased, | ||
| 153 | heading_level: 3, | ||
| 154 | segments: vec![section::Segment::Clippy(section::Data::Parsed)], // only clippy still available | ||
| 155 | unknown: Default::default(), | ||
| 156 | }, | ||
| 157 | Section::Release { | ||
| 158 | heading_level: 3, | ||
| 159 | date: Some( | ||
| 160 | time::Date::from_calendar_date(2021, time::Month::September, 15) | ||
| 161 | .unwrap() | ||
| 162 | .midnight() | ||
| 163 | .assume_utc(), | ||
| 164 | ), | ||
| 165 | name: changelog::Version::Semantic("1.0.0".parse().unwrap()), | ||
| 166 | segments: vec![], // all segments removed | ||
| 167 | unknown: "".into(), | ||
| 168 | }, | ||
| 169 | Section::Release { | ||
| 170 | date: date_m_d(time::Month::June, 1).into(), | ||
| 171 | name: changelog::Version::Semantic("0.8.0".parse().unwrap()), | ||
| 172 | unknown: "".into(), | ||
| 173 | heading_level: 3, | ||
| 174 | segments: vec![ | ||
| 175 | section::Segment::Details(section::Data::Parsed), | ||
| 176 | section::Segment::Statistics(section::Data::Parsed), | ||
| 177 | ], | ||
| 178 | }, | ||
| 179 | ], | ||
| 180 | }; | ||
| 181 | let clippy = section::Segment::Clippy(section::Data::Generated(section::ThanksClippy { count: 42 })); | ||
| 182 | let statistics = section::Segment::Statistics(section::Data::Generated(section::CommitStatistics { | ||
| 183 | count: 1, | ||
| 184 | duration: None, | ||
| 185 | conventional_count: 2, | ||
| 186 | unique_issues_count: 3, | ||
| 187 | })); | ||
| 188 | let details = section::Segment::Details(section::Data::Generated(section::Details { | ||
| 189 | commits_by_category: Default::default(), | ||
| 190 | })); | ||
| 191 | let segments = vec![statistics.clone(), clippy.clone(), details.clone()]; | ||
| 192 | let generated = ChangeLog { | ||
| 193 | sections: vec![ | ||
| 194 | Section::Verbatim { | ||
| 195 | text: "custom header".into(), | ||
| 196 | generated: true, | ||
| 197 | }, | ||
| 198 | Section::Release { | ||
| 199 | date: None, | ||
| 200 | name: changelog::Version::Unreleased, | ||
| 201 | heading_level: 3, | ||
| 202 | segments: segments.clone(), | ||
| 203 | unknown: Default::default(), | ||
| 204 | }, | ||
| 205 | Section::Release { | ||
| 206 | heading_level: 3, | ||
| 207 | date: Some( | ||
| 208 | time::Date::from_calendar_date(2021, time::Month::September, 15) | ||
| 209 | .unwrap() | ||
| 210 | .midnight() | ||
| 211 | .assume_utc(), | ||
| 212 | ), | ||
| 213 | name: changelog::Version::Semantic("1.0.0".parse().unwrap()), | ||
| 214 | segments: segments.clone(), | ||
| 215 | unknown: "".into(), | ||
| 216 | }, | ||
| 217 | Section::Release { | ||
| 218 | date: date_m_d(time::Month::June, 1).into(), | ||
| 219 | name: changelog::Version::Semantic("0.8.0".parse().unwrap()), | ||
| 220 | unknown: "".into(), | ||
| 221 | heading_level: 3, | ||
| 222 | segments: segments.clone(), | ||
| 223 | }, | ||
| 224 | ], | ||
| 225 | }; | ||
| 226 | let merged = parsed.merge_generated(generated); | ||
| 227 | assert_eq!( | ||
| 228 | merged, | ||
| 229 | ChangeLog { | ||
| 230 | sections: vec![ | ||
| 231 | Section::Verbatim { | ||
| 232 | text: "preamble".into(), | ||
| 233 | generated: false, | ||
| 234 | }, | ||
| 235 | Section::Release { | ||
| 236 | date: None, | ||
| 237 | name: changelog::Version::Unreleased, | ||
| 238 | heading_level: 3, | ||
| 239 | segments: vec![clippy], | ||
| 240 | unknown: Default::default(), | ||
| 241 | }, | ||
| 242 | Section::Release { | ||
| 243 | heading_level: 3, | ||
| 244 | date: Some( | ||
| 245 | time::Date::from_calendar_date(2021, time::Month::September, 15) | ||
| 246 | .unwrap() | ||
| 247 | .midnight() | ||
| 248 | .assume_utc(), | ||
| 249 | ), | ||
| 250 | name: changelog::Version::Semantic("1.0.0".parse().unwrap()), | ||
| 251 | segments: segments.clone(), | ||
| 252 | unknown: "".into(), | ||
| 253 | }, | ||
| 254 | Section::Release { | ||
| 255 | date: date_m_d(time::Month::June, 1).into(), | ||
| 256 | name: changelog::Version::Semantic("0.8.0".parse().unwrap()), | ||
| 257 | unknown: "".into(), | ||
| 258 | heading_level: 3, | ||
| 259 | segments: vec![details, statistics], | ||
| 260 | }, | ||
| 261 | ], | ||
| 262 | } | ||
| 263 | ) | ||
| 264 | } | ||
| 265 | |||
| 142 | fn date_m_d(month: time::Month, day: u8) -> OffsetDateTime { | 266 | fn date_m_d(month: time::Month, day: u8) -> OffsetDateTime { |
| 143 | time::Date::from_calendar_date(2021, month, day) // generated, correct date | 267 | time::Date::from_calendar_date(2021, month, day) // generated, correct date |
| 144 | .unwrap() | 268 | .unwrap() |