Release Notes Manager
Loading...
Searching...
No Matches
Main.cpp File Reference

This file contains the main functions related to generating release notes from commit messages or pull requests. More...

#include <iostream>
#include <fstream>
#include <string>
#include <cstring>
#include <regex>
#include <curl/curl.h>
#include <json.hpp>
#include "Config.h"
#include "Enums.h"
#include "Utils.h"
#include "Format.h"

Functions

void addPullRequestInfoInNotes (json pullRequestInfo, string &pullRequestsReleaseNotes, ReleaseNoteModes releaseNotesMode, int commitTypeIndex)
 Formats given pull request information (title, body) into a nice looking markdown format, then adds them in the given current release notes, based on the release notes mode.
 
string getPullRequestInfo (string pullRequestUrl, string githubToken)
 Retrieves pull request info from the GitHub API using libcurl.
 
string getCommitsNotesFromPullRequests (int commitTypeIndex, string releaseStartRef, string releaseEndRef, string githubToken, ReleaseNoteModes releaseNotesMode)
 Retrieves release notes from each commit's pull request between the start reference and the end reference based on the given conventional commit type, release notes mode, and using the given GitHub token.
 
string getCommitsNotesFromCommitMessages (int commitTypeIndex, string releaseStartRef, string releaseEndRef)
 Retrieves release notes from each commit's message between the start reference and the end reference based on the given conventional commit type.
 
void generateReleaseNotes (ReleaseNoteSources releaseNoteSource, string releaseStartRef, string releaseEndRef, string githubToken, ReleaseNoteModes releaseNoteMode)
 Generates release notes using commit messages between the start reference and the end reference using the given release notes source and if the source is pull requests then generates them based on the release note mode and using the given GitHub token.
 
void generatePullRequestChangeNote (string pullRequestNumber, string githubToken)
 Generates a single change note with it's conventional commit type category for a single pull request using the GitHub API (Not using commit messages at all)
 
int main (int argc, char *argv[])
 

Variables

Config config
 

Detailed Description

This file contains the main functions related to generating release notes from commit messages or pull requests.

Author
Ahmed Khaled

Function Documentation

◆ addPullRequestInfoInNotes()

void addPullRequestInfoInNotes ( json pullRequestInfo,
string & pullRequestsReleaseNotes,
ReleaseNoteModes releaseNotesMode,
int commitTypeIndex )

Formats given pull request information (title, body) into a nice looking markdown format, then adds them in the given current release notes, based on the release notes mode.

Parameters
pullRequestInfoJSON object containing raw pull request information
pullRequestsReleaseNotesExisting release notes generated from pull requests
releaseNotesModeThe release notes mode that will decide if the pull request body will be included or not
commitTypeIndexIndex of the commit type in the commit types 2d array that this pull request belongs to
150 {
151 if (!pullRequestInfo["title"].is_null()) {
152 string title = pullRequestInfo["title"];
153
154 CommitTypeMatchResults matchResult = checkCommitTypeMatch(title, commitTypeIndex);
155
156 if(releaseNotesMode == ReleaseNoteModes::Full)
157 pullRequestsReleaseNotes += convertConventionalCommitTitleToReleaseNoteTitle(title, matchResult,
159 else
160 pullRequestsReleaseNotes += convertConventionalCommitTitleToReleaseNoteTitle(title, matchResult,
162 }
163
164 if (releaseNotesMode == ReleaseNoteModes::Full && !pullRequestInfo["body"].is_null()) {
165 string body = pullRequestInfo["body"];
166
167 // Capitalizing the first letter of the body
168 body[0] = toupper(body[0]);
169 body = formatPullRequestBody(body);
170
171 pullRequestsReleaseNotes += indentAllLinesInString(body) + "\n";
172 }
173
174 pullRequestsReleaseNotes += "\n";
175}
CommitTypeMatchResults
Enumeration for the result of matching a commit type (e.g., fix: or fix(GUI):) against any commit typ...
Definition Enums.h:42
string indentAllLinesInString(string s)
Indents (puts 4 spaces) before all lines in a string.
Definition Format.cpp:24
string convertConventionalCommitTitleToReleaseNoteTitle(string conventionalCommitTitle, CommitTypeMatchResults matchResult, string markdownPrefix)
Converts the given conventional commit title to a better markdown title that could be used in the rel...
Definition Format.cpp:139
string formatPullRequestBody(string pullRequestBody)
Makes the formatting of the retrieved PR body look like the PR on GitHub.
Definition Format.cpp:123
Config config
Definition Main.cpp:40
CommitTypeMatchResults checkCommitTypeMatch(string commitMessage, int commitTypeIndex)
Checks how the given commit message matches the expected conventional commit type.
Definition Utils.cpp:96
string markdownFullModeReleaseNotePrefix
Definition Config.h:50
string markdownReleaseNotePrefix
Definition Config.h:49

◆ generatePullRequestChangeNote()

void generatePullRequestChangeNote ( string pullRequestNumber,
string githubToken )

Generates a single change note with it's conventional commit type category for a single pull request using the GitHub API (Not using commit messages at all)

Parameters
pullRequestNumberThe number of the pull request to generate change note for (e.g., 13, 144, 3722, etc.)
githubTokenThe GitHub token used to make authenticated requests to the GitHub API
389 {
391
392 string jsonResponse = getPullRequestInfo(config.repoPullRequestsApiUrl + pullRequestNumber, githubToken);
393 json pullRequestInfo = json::parse(jsonResponse);
394
395 string pullRequestChangeNote = "";
396 for (int commitTypeIndex = 0; commitTypeIndex < config.commitTypesCount; commitTypeIndex++)
397 {
398 if (checkCommitTypeMatch(pullRequestInfo["title"], commitTypeIndex) != CommitTypeMatchResults::NoMatch) {
399 pullRequestChangeNote += "\n" + config.commitTypes[commitTypeIndex][(int)CommitTypeInfo::MarkdownTitle] + "\n";
400 addPullRequestInfoInNotes(pullRequestInfo, pullRequestChangeNote, ReleaseNoteModes::Full, commitTypeIndex);
401 break;
402 }
403 }
404
405 writeGeneratedNotesInFiles(pullRequestChangeNote, githubToken);
406
407 cout << "Pull request change note generated successfully, check " + config.markdownOutputFileName + " and " + config.htmlOutputFileName + " in the current directory" << endl;
408}
void addPullRequestInfoInNotes(json pullRequestInfo, string &pullRequestsReleaseNotes, ReleaseNoteModes releaseNotesMode, int commitTypeIndex)
Formats given pull request information (title, body) into a nice looking markdown format,...
Definition Main.cpp:149
string getPullRequestInfo(string pullRequestUrl, string githubToken)
Retrieves pull request info from the GitHub API using libcurl.
Definition Main.cpp:183
void writeGeneratedNotesInFiles(string markdownGeneratedNotes, string githubToken)
Writes the generated markdown notes in the markdown file and converts these markdown notes to HTML an...
Definition Utils.cpp:174
string repoPullRequestsApiUrl
Definition Config.h:25
int commitTypesCount
Definition Config.h:26
string markdownOutputFileName
Definition Config.h:18
string commitTypes[50][2]
2d array storing conventional commit types and their corresponding markdown titles The first dimensio...
Definition Config.h:31
string generatingReleaseNotesMessage
Definition Config.h:71
string htmlOutputFileName
Definition Config.h:19

◆ generateReleaseNotes()

void generateReleaseNotes ( ReleaseNoteSources releaseNoteSource,
string releaseStartRef,
string releaseEndRef,
string githubToken,
ReleaseNoteModes releaseNoteMode )

Generates release notes using commit messages between the start reference and the end reference using the given release notes source and if the source is pull requests then generates them based on the release note mode and using the given GitHub token.

Parameters
releaseNoteSourceThe source to generate release notes from (commit messages or pull requests)
releaseStartRefThe git reference (commit SHA or tag name) that references the commit directly before the commit that starts the commit messages of the release, for example, the tag name of the previous release
releaseEndRefThe git reference (commit SHA or tag name) that references the end of the commit messages of the release
releaseNoteModeThe release notes mode when the source is pull requests
githubTokenThe GitHub token used to make authenticated requests to the GitHub API when source is pull requests
362 {
364
365 string commandToRetrieveCommitsMessages;
366 string currentCommitMessage;
367 string markdownReleaseNotes = "";
368 for (int i = 0; i < config.commitTypesCount; i++)
369 {
370 if (releaseNoteSource == ReleaseNoteSources::CommitMessages) {
371 markdownReleaseNotes += getCommitsNotesFromCommitMessages(i, releaseStartRef, releaseEndRef);
372 }
373 else if (releaseNoteSource == ReleaseNoteSources::PullRequests) {
374 markdownReleaseNotes += getCommitsNotesFromPullRequests(i, releaseStartRef, releaseEndRef, githubToken, releaseNoteMode);
375 }
376 }
377
378 writeGeneratedNotesInFiles(markdownReleaseNotes, githubToken);
379
380 cout << "Release notes generated successfully, check " + config.markdownOutputFileName + " and " + config.htmlOutputFileName + " in the current directory" << endl;
381}
string getCommitsNotesFromPullRequests(int commitTypeIndex, string releaseStartRef, string releaseEndRef, string githubToken, ReleaseNoteModes releaseNotesMode)
Retrieves release notes from each commit's pull request between the start reference and the end refer...
Definition Main.cpp:248
string getCommitsNotesFromCommitMessages(int commitTypeIndex, string releaseStartRef, string releaseEndRef)
Retrieves release notes from each commit's message between the start reference and the end reference ...
Definition Main.cpp:311

◆ getCommitsNotesFromCommitMessages()

string getCommitsNotesFromCommitMessages ( int commitTypeIndex,
string releaseStartRef,
string releaseEndRef )

Retrieves release notes from each commit's message between the start reference and the end reference based on the given conventional commit type.

Parameters
commitTypeIndexIndex of the commit type in the commit types 2d array, to only generate release notes from the given commit type (fix, feat, etc.)
releaseStartRefThe git reference (commit SHA or tag name) that references the commit directly before the commit that starts the commit messages of the release, for example, the tag name of the previous release
releaseEndRefThe git reference (commit SHA or tag name) that references the end of the commit messages of the release
Returns
The generated release notes
311 {
312 string commandToRetrieveCommitsMessages = "git log " + releaseStartRef + ".." + releaseEndRef +
313 " --oneline --format=\"%s\" --grep=\"^" + config.commitTypes[commitTypeIndex][(int)CommitTypeInfo::ConventionalName] + "[:(]\"";
314
315 FILE* pipe = popen(commandToRetrieveCommitsMessages.c_str(), "r");
316 if (!pipe) {
317 throw runtime_error(config.gitLogError);
318 }
319
320 char buffer[150];
321 string commitMessage, releaseNotesFromCommitMessages, subCategoryText;
322
323 // Add the title of this commit type section in the release notes
324 releaseNotesFromCommitMessages += "\n" + config.commitTypes[commitTypeIndex][(int)CommitTypeInfo::MarkdownTitle] + "\n";
325
326 bool commitTypeContainsReleaseNotes = 0;
327
328 // Reading commit messages line-by-line from the output of the git log command
329 while (fgets(buffer, sizeof(buffer), pipe) != NULL) {
330 commitTypeContainsReleaseNotes = 1;
331 commitMessage = buffer;
332
333 CommitTypeMatchResults matchResult = checkCommitTypeMatch(commitMessage, commitTypeIndex);
334
335 if (matchResult != CommitTypeMatchResults::NoMatch) {
336 releaseNotesFromCommitMessages += convertConventionalCommitTitleToReleaseNoteTitle(commitMessage, matchResult,
338 }
339 }
340
341 // Remove the title of this commit type section if it doesn't contain any release notes
342 if (!commitTypeContainsReleaseNotes) {
343 releaseNotesFromCommitMessages = "";
344 }
345
346 pclose(pipe);
347 return releaseNotesFromCommitMessages;
348}
string gitLogError
Definition Config.h:67

◆ getCommitsNotesFromPullRequests()

string getCommitsNotesFromPullRequests ( int commitTypeIndex,
string releaseStartRef,
string releaseEndRef,
string githubToken,
ReleaseNoteModes releaseNotesMode )

Retrieves release notes from each commit's pull request between the start reference and the end reference based on the given conventional commit type, release notes mode, and using the given GitHub token.

Parameters
commitTypeIndexIndex of the commit type in the commit types 2d array, to only generate release notes from the given commit type (fix, feat, etc.)
releaseStartRefThe git reference (commit SHA or tag name) that references the commit directly before the commit that starts the commit messages of the release, for example, the tag name of the previous release
releaseEndRefThe git reference (commit SHA or tag name) that references the end of the commit messages of the release
releaseNotesModeThe release notes mode
githubTokenThe GitHub token used to make authenticated requests to the GitHub API
Returns
The generated release notes
249 {
250 string commandToRetrieveCommitsMessages = "git log " + releaseStartRef + ".." + releaseEndRef +
251 " --oneline --format=\"%s\" --grep=\"^" + config.commitTypes[commitTypeIndex][(int)CommitTypeInfo::ConventionalName] + "[:(]\"" +
252 " --grep=\"#[0-9]\" --all-match";
253
254 FILE* pipe = popen(commandToRetrieveCommitsMessages.c_str(), "r");
255 if (!pipe) {
256 throw runtime_error(config.gitLogError);
257 }
258
259 char buffer[150];
260 string commitMessage, commitPullRequestNumber, releaseNotesFromPullRequests = "";
261
262 // Add the title of this commit type section in the release notes
263 releaseNotesFromPullRequests += "\n" + config.commitTypes[commitTypeIndex][(int)CommitTypeInfo::MarkdownTitle] + "\n";
264
265 bool commitTypeContainsReleaseNotes = 0;
266
267 // Reading commit messages line-by-line from the output of the git log command
268 while (fgets(buffer, sizeof(buffer), pipe) != NULL) {
269 commitTypeContainsReleaseNotes = 1;
270 commitMessage = buffer;
271
272 CommitTypeMatchResults matchResult = checkCommitTypeMatch(commitMessage, commitTypeIndex);
273
274 if (matchResult != CommitTypeMatchResults::NoMatch) {
275 // Regular expression to match # followed by one or more digits
276 regex prRegex(R"(#(\d+))");
277 smatch match;
278
279 // Validating that a hashtag exists
280 if (regex_search(commitMessage, match, prRegex))
281 {
282 // Extracting the PR number associated with the commit from the first capture group
283 commitPullRequestNumber = match.str(1);
284
285 string jsonResponse = getPullRequestInfo(config.repoPullRequestsApiUrl + commitPullRequestNumber, githubToken);
286 json pullRequestInfo = json::parse(jsonResponse);
287
288 addPullRequestInfoInNotes(pullRequestInfo, releaseNotesFromPullRequests, releaseNotesMode, commitTypeIndex);
289 }
290 }
291 }
292
293 // Remove the title of this commit type section if it doesn't contain any release notes
294 if (!commitTypeContainsReleaseNotes) {
295 releaseNotesFromPullRequests = "";
296 }
297
298 pclose(pipe);
299 return releaseNotesFromPullRequests;
300}

◆ getPullRequestInfo()

string getPullRequestInfo ( string pullRequestUrl,
string githubToken )

Retrieves pull request info from the GitHub API using libcurl.

Parameters
pullRequestUrlThe GitHub API URL of the pull request
githubTokenThe GitHub token used to make authenticated requests to the GitHub API
Returns
The pull request info in JSON
183 {
184 // Initializing libcurl
185 CURL* curl = curl_easy_init();
186 CURLcode resultCode;
187 string jsonResponse;
188 struct curl_slist* headers = NULL;
189 headers = curl_slist_append(headers, ("Authorization: token " + githubToken).c_str());
190
191 if (curl) {
192 curl_easy_setopt(curl, CURLOPT_URL, pullRequestUrl.c_str());
193 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, handleApiCallBack);
194 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &jsonResponse);
195 curl_easy_setopt(curl, CURLOPT_USERAGENT, "Ahmed-Khaled-dev");
196 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
197
198 resultCode = curl_easy_perform(curl);
199
200 if (resultCode == CURLE_OK) {
201 // Get the HTTP response code
202 long httpCode;
203 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
204
205 // All info obtained from https://docs.github.com/en/rest/using-the-rest-api/troubleshooting-the-rest-api?apiVersion=2022-11-28
206 // and https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#get-a-pull-request
207 if (httpCode == 200) {
208 return jsonResponse;
209 }
210 else if (httpCode == 503 || httpCode == 500 || httpCode == 422 || httpCode == 406) {
211 throw runtime_error("GitHub API request could not be processed to retrieve pull request " + pullRequestUrl
212 + " Additional information : " + jsonResponse);
213 }
214 else if (httpCode == 404) {
215 throw runtime_error("Pull request " + pullRequestUrl + " not found "
216 + "or you are accessing a private repository and the GitHub token used doesn't have permissions to access pull requests info. "
217 + "Additional information : " + jsonResponse);
218 }
219 else {
220 handleGithubApiErrorCodes(httpCode, jsonResponse);
221 }
222 }
223 else {
224 throw runtime_error(config.githubApiUnableToMakeRequestError);
225 }
226
227 curl_easy_cleanup(curl);
228 curl_slist_free_all(headers);
229 }
230 else {
231 throw runtime_error(config.githubApiLibcurlError);
232 }
233
234 return jsonResponse;
235}
void handleGithubApiErrorCodes(long errorCode, string apiResponse)
Throws runtime exceptions with appropriate messages that describe the given GitHub API error code All...
Definition Utils.cpp:78
size_t handleApiCallBack(char *data, size_t size, size_t numOfBytes, string *buffer)
Callback function that is required to handle API response in libcurl.
Definition Utils.cpp:66
string githubApiUnableToMakeRequestError
Definition Config.h:65
string githubApiLibcurlError
Definition Config.h:66

◆ main()

int main ( int argc,
char * argv[] )
42 {
43
44 // Reading values from the external configuration file
45 const string releaseNotesConfigFileName = "release_notes_config.json";
46 try {
47 config.load(releaseNotesConfigFileName);
48 }
49 catch (const exception& e) {
50 cerr << e.what() << endl;
51 return 1;
52 }
53
54 if (argc <= 1) {
56 return 1;
57 }
58
59 try {
60 if (strcmp(argv[1], config.singlePullRequestSourceCliInputName.c_str()) == 0) {
61 if (argc <= 2) {
63 return 1;
64 }
65 else if (argc <= 3) {
67 return 1;
68 }
69 else if (argc <= 4) {
71 return 1;
72 }
73
74 config.repoCommitsUrl = config.githubUrl + argv[4] + "/commit/";
75 config.repoIssuesUrl = config.githubUrl + argv[4] + "/issues/";
77
78 generatePullRequestChangeNote(argv[2], argv[3]);
79 }
80 else {
81 if (argc <= 2) {
83 return 1;
84 }
85 else if (argc <= 3) {
87 return 1;
88 }
89 else if (argc <= 4) {
91 return 1;
92 }
93
94 if (strcmp(argv[1], config.commitMessagesSourceCliInputName.c_str()) == 0
95 || strcmp(argv[1], config.commitMessagesSourceGithubActionsInputName.c_str()) == 0) {
97 }
98 else if (strcmp(argv[1], config.pullRequestsSourceCliInputName.c_str()) == 0
99 || strcmp(argv[1], config.pullRequestsSourceGithubActionsInputName.c_str()) == 0) {
100 if (argc <= 5) {
102 return 1;
103 }
104 else if(argc <= 6) {
106 return 1;
107 }
108
109 config.repoCommitsUrl = config.githubUrl + argv[6] + "/commit/";
110 config.repoIssuesUrl = config.githubUrl + argv[6] + "/issues/";
112
113 if (strcmp(argv[5], config.fullModeCliInputName.c_str()) == 0
114 || strcmp(argv[5], config.fullModeGithubActionsInputName.c_str()) == 0) {
116 }
117 else if (strcmp(argv[5], config.shortModeCliInputName.c_str()) == 0
118 || strcmp(argv[5], config.shortModeGithubActionsInputName.c_str()) == 0) {
120 }
121 else {
123 return 1;
124 }
125 }
126 else {
128 return 1;
129 }
130 }
131 }
132 catch (const exception& e) {
134 cerr << e.what() << endl;
135 return 1;
136 }
137
138 return 0;
139}
@ NoReleaseStartReference
@ NoReleaseNotesSource
@ NoReleaseEndReference
@ IncorrectReleaseNotesMode
@ IncorrectReleaseNotesSource
void generatePullRequestChangeNote(string pullRequestNumber, string githubToken)
Generates a single change note with it's conventional commit type category for a single pull request ...
Definition Main.cpp:389
void generateReleaseNotes(ReleaseNoteSources releaseNoteSource, string releaseStartRef, string releaseEndRef, string githubToken, ReleaseNoteModes releaseNoteMode=ReleaseNoteModes::Short)
Generates release notes using commit messages between the start reference and the end reference using...
Definition Main.cpp:361
void printInputError(InputErrors inputError)
Prints error messages when user runs the script with incorrect parameters/input.
Definition Utils.cpp:27
string githubUrl
Definition Config.h:20
string pullRequestsSourceCliInputName
Definition Config.h:40
string pullRequestsSourceGithubActionsInputName
Definition Config.h:41
void load(const string &configFileName)
Definition Config.cpp:17
string fullModeCliInputName
Definition Config.h:44
string githubReposApiUrl
Definition Config.h:21
string singlePullRequestSourceCliInputName
Definition Config.h:46
string shortModeCliInputName
Definition Config.h:42
string repoCommitsUrl
Definition Config.h:24
string shortModeGithubActionsInputName
Definition Config.h:43
string fullModeGithubActionsInputName
Definition Config.h:45
string commitMessagesSourceGithubActionsInputName
Definition Config.h:39
string commitMessagesSourceCliInputName
Definition Config.h:38
string repoIssuesUrl
Definition Config.h:23
string failedToGenerateReleaseNotesMessage
Definition Config.h:72

Variable Documentation

◆ config

Config config