1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
|
pub mod pull_requests;
pub mod release_notes;
use app::constants::{APP_USER_AGENT, FLIGHTCORE_REPO_NAME, SECTION_ORDER};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use ts_rs::TS;
#[derive(Serialize, Deserialize, Debug, Clone, TS)]
#[ts(export)]
pub struct Tag {
name: String,
}
/// Wrapper type needed for frontend
#[derive(Serialize, Deserialize, Debug, Clone, TS)]
#[ts(export)]
pub struct TagWrapper {
label: String,
value: Tag,
}
#[derive(Debug, Deserialize)]
struct CommitInfo {
sha: String,
commit: Commit,
}
#[derive(Debug, Deserialize)]
struct Commit {
message: String,
}
#[derive(Debug, Deserialize)]
struct Comparison {
commits: Vec<CommitInfo>,
}
/// Get a list of tags on the FlightCore repo
#[tauri::command]
pub fn get_list_of_tags() -> Result<Vec<TagWrapper>, String> {
// Set the repository name.
// Create a `reqwest` client with a user agent.
let client = reqwest::blocking::Client::builder()
.user_agent(APP_USER_AGENT)
.build()
.unwrap();
// Fetch the list of tags for the repository as a `Vec<Tag>`.
let tags_url = format!("https://api.github.com/repos/{}/tags", FLIGHTCORE_REPO_NAME);
let tags: Vec<Tag> = client.get(tags_url).send().unwrap().json().unwrap();
// Map each `Tag` element to a `TagWrapper` element with the desired label and `Tag` value.
let tag_wrappers: Vec<TagWrapper> = tags
.into_iter()
.map(|tag| TagWrapper {
label: tag.name.clone(),
value: tag,
})
.collect();
Ok(tag_wrappers)
}
/// Use GitHub API to compare two tags of the same repo against each other and get the resulting changes
#[tauri::command]
pub fn compare_tags(first_tag: Tag, second_tag: Tag) -> Result<String, String> {
// Fetch the list of commits between the two tags.
// Create a `reqwest` client with a user agent.
let client = reqwest::blocking::Client::builder()
.user_agent(APP_USER_AGENT)
.build()
.unwrap();
let repo = "R2NorthstarTools/FlightCore";
let mut full_patch_notes = "".to_string();
let mut patch_notes: Vec<String> = [].to_vec();
println!("{}", repo);
// let repo = "R2Northstar/NorthstarLauncher";
let comparison_url = format!(
"https://api.github.com/repos/{}/compare/{}...{}",
repo, first_tag.name, second_tag.name
);
let comparison: Comparison = client.get(&comparison_url).send().unwrap().json().unwrap();
let commits = comparison.commits;
// Display the list of commits.
println!(
"Commits between {} and {}:",
first_tag.name, second_tag.name
);
// Iterate over all commits in the diff
for commit in commits {
println!(
" * {} : {}",
commit.sha,
commit.commit.message.split('\n').next().unwrap()
);
patch_notes.push(
commit
.commit
.message
.split('\n')
.next()
.unwrap()
.to_string(),
);
}
full_patch_notes += &generate_flightcore_release_notes(patch_notes);
Ok(full_patch_notes.to_string())
}
/// Generate release notes in the format used for FlightCore
fn generate_flightcore_release_notes(commits: Vec<String>) -> String {
let grouped_commits = group_commits_by_type(commits);
let mut release_notes = String::new();
// Go over commit types and generate notes
for commit_type in SECTION_ORDER {
if let Some(commit_list) = grouped_commits.get(commit_type) {
if !commit_list.is_empty() {
let section_title = match commit_type {
"feat" => "**Features:**",
"fix" => "**Bug Fixes:**",
"docs" => "**Documentation:**",
"style" => "**Styles:**",
"refactor" => "**Code Refactoring:**",
"build" => "**Build:**",
"test" => "**Tests:**",
"chore" => "**Chores:**",
_ => "**Other:**",
};
release_notes.push_str(&format!("{}\n", section_title));
for commit_message in commit_list {
release_notes.push_str(&format!("- {}\n", commit_message));
}
release_notes.push('\n');
}
}
}
release_notes
}
/// Group semantic commit messages by type
/// Commmit messages that are not formatted accordingly are marked as "other"
fn group_commits_by_type(commits: Vec<String>) -> HashMap<String, Vec<String>> {
let mut grouped_commits: HashMap<String, Vec<String>> = HashMap::new();
let mut other_commits: Vec<String> = vec![];
for commit in commits {
let commit_parts: Vec<&str> = commit.splitn(2, ':').collect();
if commit_parts.len() == 2 {
let commit_type = commit_parts[0].to_lowercase();
let commit_description = commit_parts[1].trim().to_string();
// Check if known commit type
if SECTION_ORDER.contains(&commit_type.as_str()) {
let commit_list = grouped_commits.entry(commit_type.to_string()).or_default();
commit_list.push(commit_description);
} else {
// otherwise add to list of "other"
other_commits.push(commit.to_string());
}
} else {
other_commits.push(commit.to_string());
}
}
grouped_commits.insert("other".to_string(), other_commits);
grouped_commits
}
|