diff options
author | Joseph Hunkeler <jhunkeler@users.noreply.github.com> | 2024-07-06 09:49:51 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-06 09:49:51 -0400 |
commit | 8ce824ac4b2f526331093a7150e643700efd4d20 (patch) | |
tree | 99e27dee24e82d78941ded4c510e1bac57c3f015 /src/github.c | |
parent | abe87056faa6ed02aff3bbf77c1fd78b713a0864 (diff) | |
download | stasis-8ce824ac4b2f526331093a7150e643700efd4d20.tar.gz |
Add github.c and github.h (#9)
* Add github.c and github.h
* Implements get_github_release_notes()
* Remove unused variables
* Fix circular dependency on tplfunc_frame
* Remove predeclaration of tplfunc_frame
* tpl_register_func accepts pointer to void instead
* tpl_register_func sets maximum number of arguments
* Frame is generated within tpl_register_func
* Improve template function error handling and return/output management
* Remove redundant extern statement
* Include github.h and template_func_proto.h in core.h
* Expose get_github_release_notes_tplfunc_entrypoint function to template engine
* Add template_func_proto.c and template_func_proto.h
* Replace free() with guard variant
* Fix test_template::test_tpl_register_func
* Fix tests
* Fix tests
* cmd should be at least PATH_MAX in size.
* Magic number caused failure to install conda with a long installation path
* Implement get_github_release_notes_auto function that bases release note data off test contexts
* Disable overwriting releases by default
* Add automatic release note generation function call to release_notes.md.in
* Fix test_tpl_register_func()
* Add enough space for tar command plus a path
* Fix circular include
* Github functions do not require access to core.h anyway
* Add comments to union
* Update README to mention template function availability
* Add EnvCtl structure
* Add runtime checks to avoid running all the way to the end only to be met with a configuration error.
* Rename GITHUB to GH
* Development docs pre-rough-draft
Diffstat (limited to 'src/github.c')
-rw-r--r-- | src/github.c | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/src/github.c b/src/github.c new file mode 100644 index 0000000..36e2e7c --- /dev/null +++ b/src/github.c @@ -0,0 +1,133 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "core.h" + +struct GHContent { + char *data; + size_t len; +}; + +static size_t writer(void *contents, size_t size, size_t nmemb, void *result) { + const size_t newlen = size * nmemb; + struct GHContent *content = (struct GHContent *) result; + + char *ptr = realloc(content->data, content->len + newlen + 1); + if (!ptr) { + perror("realloc failed"); + return 0; + } + + content->data = ptr; + memcpy(&(content->data[content->len]), contents, newlen); + content->len += newlen; + content->data[content->len] = 0; + + return newlen; +} + +static char *unescape_lf(char *value) { + char *seq = strstr(value, "\\n"); + while (seq != NULL) { + size_t cur_len = strlen(seq); + memmove(seq, seq + 1, strlen(seq) - 1); + *seq = '\n'; + if (strlen(seq) && cur_len) { + seq[cur_len - 1] = 0; + } + seq = strstr(value, "\\n"); + } + return value; +} + +int get_github_release_notes(const char *api_token, const char *repo, const char *tag, const char *target_commitish, char **output) { + const char *field_body = "\"body\":\""; + const char *field_message = "\"message\":\""; + const char *endpoint_header_auth_fmt = "Authorization: Bearer %s"; + const char *endpoint_header_api_version = "X-GitHub-Api-Version: " STASIS_GITHUB_API_VERSION; + const char *endpoint_post_fields_fmt = "{\"tag_name\":\"%s\", \"target_commitish\":\"%s\"}"; + const char *endpoint_url_fmt = "https://api.github.com/repos/%s/releases/generate-notes"; + char endpoint_header_auth[PATH_MAX] = {0}; + char endpoint_post_fields[PATH_MAX] = {0}; + char endpoint_url[PATH_MAX] = {0}; + struct curl_slist *list = NULL; + struct GHContent content; + + CURL *curl = curl_easy_init(); + if (!curl) { + return -1; + } + + // Render the header data + sprintf(endpoint_header_auth, endpoint_header_auth_fmt, api_token); + sprintf(endpoint_post_fields, endpoint_post_fields_fmt, tag, target_commitish); + sprintf(endpoint_url, endpoint_url_fmt, repo); + + // Begin curl configuration + curl_easy_setopt(curl, CURLOPT_URL, endpoint_url); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, endpoint_post_fields); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writer); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &content); + + // Append headers to the request + list = curl_slist_append(list, "Accept: application/vnd.github+json"); + list = curl_slist_append(list, endpoint_header_auth); + list = curl_slist_append(list, endpoint_header_api_version); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); + + // Set the user-agent (github requires one) + char user_agent[20] = {0}; + sprintf(user_agent, "stasis/%s", VERSION); + curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent); + + // Execute curl request + memset(&content, 0, sizeof(content)); + CURLcode res; + res = curl_easy_perform(curl); + + // Clean up + curl_slist_free_all(list); + curl_easy_cleanup(curl); + + if(res != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(res)); + return -1; + } + + // Replace all "\\n" literals with new line characters + char *line = unescape_lf(content.data); + if (line) { + char *data_offset = NULL; + if ((data_offset = strstr(line, field_body))) { + // Skip past the body field + data_offset += strlen(field_body); + // Remove quotation mark (and trailing comma if it exists) + int trim = 2; + char last_char = data_offset[strlen(data_offset) - trim]; + if (last_char == ',') { + trim++; + } + data_offset[strlen(data_offset) - trim] = 0; + // Extract release notes + *output = strdup(data_offset); + } else if ((data_offset = strstr(line, field_message))) { + // Skip past the message field + data_offset += strlen(field_message); + *(strchr(data_offset, '"')) = 0; + fprintf(stderr, "GitHub API Error: '%s'\n", data_offset); + fprintf(stderr, "URL: %s\n", endpoint_url); + fprintf(stderr, "POST: %s\n", endpoint_post_fields); + guard_free(content.data); + return -1; + } + } else { + fprintf(stderr, "Unknown error\n"); + guard_free(content.data); + return -1; + } + + guard_free(content.data); + return 0; +}
\ No newline at end of file |