diff --git a/.builds/alpine.yml b/.builds/alpine.yml new file mode 100644 index 00000000..d67fa09b --- /dev/null +++ b/.builds/alpine.yml @@ -0,0 +1,34 @@ +image: alpine/edge +packages: + - libcurl + - gcc + - autoconf + - automake + - libtool + - make + - pkgconf + - musl-dev + - curl-dev + - flex + - bison + - xz + - gzip + - bzip2 + - libbsd-dev + - kyua + - atf-dev +sources: + - https://git.sr.ht/~herrhotzenplotz/gcli +tasks: + - build: | + cd gcli + ./autogen.sh + { + CFLAGS='-std=c99 -pedantic -Wall -Wextra -Werror' \ + CPPFLAGS='-D_XOPEN_SOURCE=600' \ + ./configure --disable-silent-rules || (cat config.log && exit 42) + } + make -j + - check: | + cd gcli + make -j distcheck diff --git a/.builds/debian-stable.yml b/.builds/debian-stable.yml new file mode 100644 index 00000000..e3baf612 --- /dev/null +++ b/.builds/debian-stable.yml @@ -0,0 +1,29 @@ +image: debian/stable +packages: + - build-essential + - libcurl4-openssl-dev + - pkgconf + - autotools-dev + - bison + - flex + - make + - autoconf + - automake + - libtool + - libbsd-dev + - libatf-dev kyua +sources: + - https://git.sr.ht/~herrhotzenplotz/gcli +tasks: + - build: | + cd gcli + ./autogen.sh + { + CFLAGS='-std=c99 -pedantic -Wall -Wextra -Werror' \ + CPPFLAGS='-D_XOPEN_SOURCE=600' \ + ./configure --disable-silent-rules || (cat config.log && exit 42) + } + make + - check: | + cd gcli + make distcheck diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml new file mode 100644 index 00000000..e85d65cf --- /dev/null +++ b/.builds/freebsd.yml @@ -0,0 +1,33 @@ +image: freebsd/14.x +packages: + - atf + - autoconf + - automake + - ca_root_nss + - curl + - kyua + - libedit + - libssh2 + - libtool + - libunistring + - m4 + - pkg + - pkgconf + - readline +sources: + - https://git.sr.ht/~herrhotzenplotz/gcli +tasks: + - build: | + cd gcli + ./autogen.sh + { + CFLAGS='-std=c99 -pedantic -Wall -Wextra -Wno-misleading-indentation -Werror' \ + CPPFLAGS='-D_XOPEN_SOURCE=600' \ + LEX=flex YACC=byacc \ + ./configure --disable-silent-rules || (cat config.log && exit 42) + } + make -j 4 + + - check: | + cd gcli + make -j 4 distcheck diff --git a/.gitignore b/.gitignore index f42e8c9c..f1a8221e 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,10 @@ /configure /configure~ /depcomp -/gcli-0.9.9.tar.gz +# build artifacts +/gcli-*.tar.bz2 +/gcli-*.tar.gz +/gcli-*.tar.xz /install-sh /libtool /ltmain.sh @@ -47,3 +50,4 @@ /m4/ltversion.m4 /m4/lt~obsolete.m4 /libgcli.pc +/build diff --git a/.hgignore b/.hgignore deleted file mode 100644 index ba3045c3..00000000 --- a/.hgignore +++ /dev/null @@ -1,26 +0,0 @@ -syntax: glob -build/* -Makefile.in -aclocal.m4 -ar-lib -autom4te.cache/ -compile -config.guess -config.h.in -config.sub -configure -depcomp -install-sh -ltmain.sh -m4/libtool.m4 -m4/ltoptions.m4 -m4/ltsugar.m4 -m4/ltversion.m4 -m4/lt~obsolete.m4 -missing -test-driver -ylwrap -*~ -docs/tutorial/*.html -TAGS -build-*/ diff --git a/Changelog.md b/Changelog.md index 9c8f1b46..1f1cb8f2 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,85 @@ This changelog does not follow semantic versioning. +## 2.2.0 (2024-Feb-05) + +### Added + +- Preliminary (and thus experimental) support for Bugzilla has been + added. For this a new yet undocumented `attachments` subcommand + has been introduced. + Currently if no account has been specified it will default to the + FreeBSD Bugzilla - this may however change in the future. + +- A search feature has been added to the issues subcommand. You can + now optionally provide trailing text to the issues subcommand + which will be used as a search term: + + ```console + $ gcli issues -A herrhotzenplotz Segfault + ``` + + This will search for tickets authored by herrhotzenplotz containing + "Segfault". + +- Added partial support for auto-merge. When creating a pull request + on Gitlab and Github you can set an automerge flag. Whenever this + automerge flag is set a pull request will be merged once all the + pipelines/checks on the pull request pass. + + This feature is not fully documented yet as there are bugs in it, + especially on Gitlab there are flaws. Please consider this feature + unstable and experimental. + +### Fixed + +- Fixed a segmentation fault when getting a 404 on Gitlab. This bug + occured on Debian Linux when querying pipelines at the KiCad project. + The returned 404 contained unparsable data which then lead to the + error message to be improperly initialised. + Reported by: Simon Richter + +- Fixed missing URL-encode calls in Gitlab Pipelines causing 404 errors + when using subprojects on Gitlab. You're now not forced anymore + to manually urlencode slashes as %2F in the repos. + Reported by: Simon Richter + +- Fixed the patch generator for Gitlab Merge Requests to produce + patches that can be applied with `git am`. + Previously the patches were invalid when new files were created + or deleted. + +- Fixed Segmentation fault when the editor was opened and closed + without changing the file. Several subcommands have been updated + to also account for empty user messages. + +- Fixed incorrect colour when creating labels. In any forge the + provided colour code was converted incorrectly and always producing + the wrong colour. + +- Fixed a segmentation fault when listing Github gists + +- Fixed possible JSON escape bug when creating a Github Gist + +- Fixed gcli reporting incorrect libcurl version in the User-Agent + header when performing HTTP requests. + +- Fixed possible segmentation fault when no token was configured in + gcli configuration file. + +### Changed + +- Internally a lot of code was using string views. Maintaining this + was a bit cumbersome and required frequent reallocations. + A lot of these uses have been refactored to use plain C-Strings + now. This also involved changing some code to use the new + `gcli_jsongen` set of routines. + Due to these changes there may be regressions that are only visible + during use. If you encounter such regressions where previously + working commands suddenly fail due to malformed requests please + report immediately. + +### Removed ## 2.1.0 (2023-Dec-08) diff --git a/HACKING.md b/HACKING.md index db0e1d79..114c4f91 100644 --- a/HACKING.md +++ b/HACKING.md @@ -134,15 +134,18 @@ Please use the BSD Style conventions for formatting your code. This means: This allows to search for the implementation of a function through a simple `grep -rn '^foo' .`. -- typedef structs separately from their definitions - - typedef struct foo foo; +- Use struct tags for structs, do not typedef them struct foo { int bar; char const *baz; }; + static void + foodoo(struct foo const *const bar) + { + } + - Indent with tabs, align with spaces `ยป` denotes a TAB character, `.` indicates a whitespace: diff --git a/Makefile.am b/Makefile.am index 41b488bc..9af5865f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -61,6 +61,7 @@ dist_man_MANS = \ docs/gcli.5 gcli_SOURCES = \ + include/gcli/cmd/attachments.h src/cmd/attachments.c \ include/gcli/cmd/ci.h src/cmd/ci.c \ include/gcli/cmd/cmdconfig.h src/cmd/cmdconfig.c \ include/gcli/cmd/cmd.h src/cmd/cmd.c \ @@ -119,7 +120,9 @@ TEMPLATES = \ templates/gitlab/status.t \ templates/gitlab/snippets.t \ templates/gitea/milestones.t \ - templates/gitea/status.t + templates/gitea/status.t \ + templates/bugzilla/api.t \ + templates/bugzilla/bugs.t headerdir = $(prefix) nobase_header_HEADERS = include/gcli/gcli.h include/gcli/comments.h \ @@ -136,6 +139,8 @@ libgcli_la_SOURCES = \ include/gcli/ctx.h src/ctx.c \ include/gcli/gcli.h src/gcli.c \ include/gcli/date_time.h src/date_time.c \ + src/attachments.c include/gcli/attachments.h \ + src/base64.c include/gcli/base64.h \ src/comments.c include/gcli/comments.h \ src/curl.c include/gcli/curl.h \ src/forges.c include/gcli/forges.h \ @@ -145,6 +150,7 @@ libgcli_la_SOURCES = \ src/json_util.c include/gcli/json_util.h \ src/labels.c include/gcli/labels.h \ src/milestones.c include/gcli/milestones.h \ + src/nvlist.c include/gcli/nvlist.h \ src/pulls.c include/gcli/pulls.h \ src/releases.c include/gcli/releases.h \ src/repos.c include/gcli/repos.h \ @@ -189,6 +195,11 @@ libgcli_la_SOURCES = \ src/gitea/sshkeys.c include/gcli/gitea/sshkeys.h \ src/gitea/status.c include/gcli/gitea/status.h \ src/gitea/milestones.c include/gcli/gitea/milestones.h \ + src/bugzilla/api.c include/gcli/bugzilla/api.h \ + src/bugzilla/attachments.c include/gcli/bugzilla/attachments.h \ + src/bugzilla/bugs.c include/gcli/bugzilla/bugs.h \ + src/bugzilla/bugs-parser.c include/gcli/bugzilla/bugs-parser.h \ + src/bugzilla/config.c include/gcli/bugzilla/config.h \ $(TEMPLATES) libgcli_la_CPPFLAGS = \ @@ -218,6 +229,8 @@ check_PROGRAMS = \ tests/github-parse-tests$(EXEEXT) \ tests/gitlab-parse-tests$(EXEEXT) \ tests/gitea-parse-tests$(EXEEXT) \ + tests/bugzilla-parse-tests$(EXEEXT) \ + tests/base64-tests$(EXEEXT) \ tests/url-encode$(EXEEXT) \ tests/pretty_print_test$(EXEEXT) \ tests/test-jsongen$(EXEEXT) @@ -254,6 +267,18 @@ tests_gitea_parse_tests_LDADD = \ libgcli.la libpdjson.la libsn.la \ $(LIBATFC_LDFLAGS) +tests_bugzilla_parse_tests_SOURCES = \ + tests/bugzilla-parse-tests.c +tests_bugzilla_parse_tests_LDADD = \ + libgcli.la libpdjson.la libsn.la \ + $(LIBATFC_LDFLAGS) + +tests_base64_tests_SOURCES = \ + tests/base64-tests.c +tests_base64_tests_LDADD = \ + libgcli.la libpdjson.la libsn.la \ + $(LIBATFC_LDFLAGS) + tests_url_encode_SOURCES = \ tests/url-encode.c tests_url_encode_LDADD = \ @@ -291,6 +316,9 @@ EXTRA_DIST += tests/samples/github_simple_comment.json \ tests/samples/gitlab_simple_repo.json \ tests/samples/gitlab_simple_snippet.json \ tests/samples/github_simple_check.json \ - tests/samples/gitea_simple_notification.json + tests/samples/gitea_simple_notification.json \ + tests/samples/bugzilla_attachments.json \ + tests/samples/bugzilla_comments.json \ + tests/samples/bugzilla_simple_bug.json endif diff --git a/README.md b/README.md index 59d555df..bb8773a5 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ # GCLI -Simple and portable CLI tool for interacting with GitHub, GitLab and -Gitea from the command line. +Portable CLI tool for interacting with Git(Hub|Lab|Tea) from the command line. -![](docs/screenshot-04.png) +![](docs/screenshot.png) ## Why? @@ -12,9 +11,8 @@ unified tool for various git forges such as GitHub and GitLab because every forge does things differently yet all build on Git and purposefully break with its philosophy. -Also, the official tool from Github is written in Go, which does -manual [DNS -resolution](https://github.com/golang/go/blob/master/src/net/dnsclient_unix.go#L49) +Also, the official tool from Github is written in Go, which does manual +[DNS resolution](https://github.com/golang/go/blob/master/src/net/dnsclient_unix.go#L49) which is a massive security vulnerability for people using Tor as it leaks your IP to the DNS server. This program builds upon libcurl, which obeys the operating system's DNS resolution mechanisms and thus diff --git a/configure.ac b/configure.ac index 4beb05b4..c683c784 100644 --- a/configure.ac +++ b/configure.ac @@ -3,14 +3,14 @@ AC_PREREQ([2.69]) AC_INIT([gcli], - [2.1.0], + [2.2.0], [~herrhotzenplotz/gcli-discuss@lists.sr.ht], [gcli], [https://herrhotzenplotz.de/gcli]) AM_INIT_AUTOMAKE([1.0 foreign subdir-objects dist-bzip2 dist-xz -Wall]) dnl Release Date. -PACKAGE_DATE="2023-Dec-08" +PACKAGE_DATE="2024-Feb-05" AC_SUBST([PACKAGE_DATE]) dnl Silent by default. diff --git a/docs/.gitignore b/docs/.gitignore index c7a552e4..01bb910c 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -13,3 +13,4 @@ /gcli-snippets.1 /gcli-status.1 /gcli.1 +/gcli.5 diff --git a/docs/gcli-issues.1.in b/docs/gcli-issues.1.in index a0d2f158..d28658ab 100644 --- a/docs/gcli-issues.1.in +++ b/docs/gcli-issues.1.in @@ -11,7 +11,9 @@ .Op Fl s .Op Fl A Ar author .Op Fl L Ar label +.Op Fl M Ar milestone .Op Fl o Ar owner Fl r Ar repo +.Op Ar "search-query" .Nm .Fl i Ar issue .Op Fl o Ar owner Fl r Ar repo @@ -23,7 +25,7 @@ .Sh DESCRIPTION Use .Nm -to list, create, edit or delete issues in repositories in various +to search, list, create, edit or delete issues in repositories in various .Xr git 1 forges such as GitHub, GitLab and Gitea. Without any action specified, .Nm @@ -49,6 +51,8 @@ option. Only list issues authored by the given user. .It Fl L , Fl -label Ar label Filter issues by the given label. This option may only be specfied once. +.It Fl M , Fl -milestone Ar milestone +Filter issues by the given milestone. This option may only be specfied once. .It Fl n , -count Ar n Fetch at least .Ar n @@ -119,6 +123,8 @@ action that prints the list of comments associated with the issue. .It Cm title Ar new-title Change the title of the issue to .Ar new-title . +.It Cm attachments +List bug attachments. This action is only available on Bugzilla. .El .Sh EXAMPLES Print a list of issues in the current project: @@ -126,9 +132,16 @@ Print a list of issues in the current project: $ gcli issues .Ed .Pp +Search for issues containing +.Dq crash +in contour-terminal/contour on GitHub including closed issues: +.Bd -literal -offset indent +$ gcli -t github issues -o contour-terminal -r contour -a crash +.Ed +.Pp Report a new issue in the current project: .Bd -literal -offset indent -$ gcli create issues "summary here" +$ gcli issues create "summary here" .Ed .Pp Print both a summary and comments of issue 1 in herrhotzenplotz/gcli: diff --git a/docs/gcli-pulls.1.in b/docs/gcli-pulls.1.in index 7129c503..7b6c44bb 100644 --- a/docs/gcli-pulls.1.in +++ b/docs/gcli-pulls.1.in @@ -67,7 +67,11 @@ option above - the same reasoning applies to this option. List all PRs, including closed and merged ones. Cannot be combined with actions. This does not affect the .Fl n -option. +option. Note that this flag has a different meaning in the +.Cm create +subcommand. See +.Sx SUBCOMMANDS +for more information. .It Fl n , -count Ar n Fetch at least .Ar n @@ -105,6 +109,8 @@ the commits that are to be merged into the target repository. You may omit this flag and gcli will try to infer this information. .It Fl y , -yes Do not ask for confirmation before creating the PR. Assume yes. +.It Fl a , -automerge +Enable the automerge feature when creating the PR. .It Ar "PR Title..." The title of the Pull Request or Merge Request. .El diff --git a/docs/gcli.1.in b/docs/gcli.1.in index 66acf37a..b7a2fc9b 100644 --- a/docs/gcli.1.in +++ b/docs/gcli.1.in @@ -115,8 +115,9 @@ Forcefully override the forge type. Set to .Sq github , .Sq gitlab +.Sq gitea , or -.Sq gitea +.Sq bugzilla to connect to the corresponding services. .El .Pp diff --git a/docs/screenshot-04.png b/docs/screenshot.png similarity index 100% rename from docs/screenshot-04.png rename to docs/screenshot.png diff --git a/docs/website/deploy.sh b/docs/website/deploy.sh index ae754bad..1e104b80 100755 --- a/docs/website/deploy.sh +++ b/docs/website/deploy.sh @@ -24,7 +24,7 @@ cp -vp \ ${DISTDIR}/tutorial cp -vp \ - ../screenshot-04.png \ + ../screenshot.png \ ${DISTDIR}/assets/screenshot.png tar -c -f - -C ${DISTDIR} \. | xz > website_dist.tar.xz diff --git a/docs/website/index.html b/docs/website/index.html index 40b2641a..70289881 100644 --- a/docs/website/index.html +++ b/docs/website/index.html @@ -30,14 +30,13 @@ } img#screenshot { - margin: 50px 20%; + margin: 50px 0px; width: 60%; max-width: 800px; } @media only screen and (max-width: 1024px) { img#screenshot { - margin: 50px 0px; width: 100%; } } diff --git a/include/gcli/attachments.h b/include/gcli/attachments.h new file mode 100644 index 00000000..1b93f0aa --- /dev/null +++ b/include/gcli/attachments.h @@ -0,0 +1,66 @@ +/* + * Copyright 2023 Nico Sonack + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GCLI_ATTACHMENTS_H +#define GCLI_ATTACHMENTS_H + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include + +struct gcli_attachment { + gcli_id id; + bool is_obsolete; + char *created_at; + char *author; + char *file_name; + char *summary; + char *content_type; + + char *data_base64; +}; + +struct gcli_attachment_list { + struct gcli_attachment *attachments; + size_t attachments_size; +}; + +void gcli_attachments_free(struct gcli_attachment_list *list); +void gcli_attachment_free(struct gcli_attachment *attachment); +int gcli_attachment_get_content(struct gcli_ctx *const ctx, gcli_id const id, + FILE *out); + +#endif /* GCLI_ATTACHMENTS_H */ diff --git a/include/gcli/base64.h b/include/gcli/base64.h new file mode 100644 index 00000000..160e1e67 --- /dev/null +++ b/include/gcli/base64.h @@ -0,0 +1,43 @@ +/* + * Copyright 2023 Nico Sonack + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GCLI_BASE64_H +#define GCLI_BASE64_H + +#include + +#include + +int gcli_decode_base64(struct gcli_ctx *ctx, char const *input, char *buffer, + size_t buffer_size); + +int gcli_base64_decode_print(struct gcli_ctx *ctx, FILE *out, + char const *const input); + +#endif /* GCLI_BASE64_H */ diff --git a/include/gcli/bugzilla/api.h b/include/gcli/bugzilla/api.h new file mode 100644 index 00000000..79dc6082 --- /dev/null +++ b/include/gcli/bugzilla/api.h @@ -0,0 +1,41 @@ +/* + * Copyright 2023 Nico Sonack + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GCLI_BUGZILLA_API_H +#define GCLI_BUGZILLA_API_H + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +char const *bugzilla_api_error_string(struct gcli_ctx *ctx, struct gcli_fetch_buffer *it); + +#endif /* GCLI_BUGZILLA_API_H */ diff --git a/include/gcli/bugzilla/attachments.h b/include/gcli/bugzilla/attachments.h new file mode 100644 index 00000000..383d5fc3 --- /dev/null +++ b/include/gcli/bugzilla/attachments.h @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Nico Sonack + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GCLI_BUGZILLA_ATTACHMENTS_H +#define GCLI_BUGZILLA_ATTACHMENTS_H + +#include + +int bugzilla_attachment_get_content(struct gcli_ctx *ctx, gcli_id attachment_id, + FILE *output); + +#endif /* GCLI_BUGZILLA_ATTACHMENTS_H */ diff --git a/include/gcli/bugzilla/bugs-parser.h b/include/gcli/bugzilla/bugs-parser.h new file mode 100644 index 00000000..1da4bbef --- /dev/null +++ b/include/gcli/bugzilla/bugs-parser.h @@ -0,0 +1,68 @@ +/* + * Copyright 2023 Nico Sonack + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GCLI_BUGZILLA_BUGS_PARSER_H +#define GCLI_BUGZILLA_BUGS_PARSER_H + +#include + +#include +#include +#include + +#include + +int parse_bugzilla_bug_comments_dictionary_skip_first(struct gcli_ctx *const ctx, + struct json_stream *stream, + struct gcli_comment_list *out); + +int parse_bugzilla_comments_array_skip_first(struct gcli_ctx *ctx, + struct json_stream *stream, + struct gcli_comment_list *out); + +int parse_bugzilla_bug_comments_dictionary_only_first(struct gcli_ctx *const ctx, + struct json_stream *stream, + char **out); + +int parse_bugzilla_comments_array_only_first(struct gcli_ctx *ctx, + struct json_stream *stream, + char **out); + +int parse_bugzilla_assignee(struct gcli_ctx *ctx, struct json_stream *stream, + struct gcli_issue *out); + +int parse_bugzilla_bug_attachments_dict(struct gcli_ctx *ctx, + struct json_stream *stream, + struct gcli_attachment_list *out); + +int parse_bugzilla_attachment_content_only_first(struct gcli_ctx *ctx, + struct json_stream *stream, + struct gcli_attachment *out); + +#endif /* GCLI_BUGZILLA_BUGS_PARSER_H */ diff --git a/include/gcli/bugzilla/bugs.h b/include/gcli/bugzilla/bugs.h new file mode 100644 index 00000000..c11440ec --- /dev/null +++ b/include/gcli/bugzilla/bugs.h @@ -0,0 +1,62 @@ +/* + * Copyright 2023 Nico Sonack + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GCLI_BUGZILLA_BUGS_H +#define GCLI_BUGZILLA_BUGS_H + +#include + +#include +#include +#include + +#include + +int bugzilla_get_bugs(struct gcli_ctx *ctx, char const *product, + char const *component, + struct gcli_issue_fetch_details const *details, int const max, + struct gcli_issue_list *out); + +int bugzilla_get_bug(struct gcli_ctx *ctx, char const *product, + char const *component, gcli_id bug_id, struct gcli_issue *out); + +int bugzilla_bug_get_comments(struct gcli_ctx *const ctx, + char const *const product, + char const *const component, gcli_id const bug_id, + struct gcli_comment_list *out); + +int bugzilla_bug_get_attachments(struct gcli_ctx *ctx, char const *const product, + char const *const component, + gcli_id const bug_id, + struct gcli_attachment_list *const out); + +int bugzilla_bug_submit(struct gcli_ctx *ctx, struct gcli_submit_issue_options opts, + struct gcli_fetch_buffer *out); + +#endif /* GCLI_BUGZILLA_BUGS_H */ diff --git a/include/gcli/bugzilla/config.h b/include/gcli/bugzilla/config.h new file mode 100644 index 00000000..ba507d08 --- /dev/null +++ b/include/gcli/bugzilla/config.h @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Nico Sonack + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GCLI_BUGZILLA_CONFIG_H +#define GCLI_BUGZILLA_CONFIG_H + +#include + +char *bugzilla_make_authheader(struct gcli_ctx *ctx, char const *token); + +#endif /* GCLI_BUGZILLA_CONFIG_H */ diff --git a/include/gcli/cmd/attachments.h b/include/gcli/cmd/attachments.h new file mode 100644 index 00000000..34be2850 --- /dev/null +++ b/include/gcli/cmd/attachments.h @@ -0,0 +1,35 @@ +/* + * Copyright 2023 Nico Sonack + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GCLI_CMD_ATTACHMENTS_H +#define GCLI_CMD_ATTACHMENTS_H + +int subcommand_attachments(int argc, char *argv[]); + +#endif /* GCLI_CMD_ATTACHMENTS_H */ diff --git a/include/gcli/cmd/ci.h b/include/gcli/cmd/ci.h index e7767c98..fb0d1aca 100644 --- a/include/gcli/cmd/ci.h +++ b/include/gcli/cmd/ci.h @@ -36,8 +36,8 @@ #include -void github_print_checks(github_check_list const *checks); -void github_print_checks(github_check_list const *const list); +void github_print_checks(struct github_check_list const *checks); +void github_print_checks(struct github_check_list const *const list); int github_checks(char const *const owner, char const *const repo, char const *const ref, int const max); int subcommand_ci(int argc, char *argv[]); diff --git a/include/gcli/cmd/cmd.h b/include/gcli/cmd/cmd.h index 367fc544..1767bb11 100644 --- a/include/gcli/cmd/cmd.h +++ b/include/gcli/cmd/cmd.h @@ -38,7 +38,7 @@ #include -extern gcli_ctx *g_clictx; +extern struct gcli_ctx *g_clictx; static inline char * shift(int *argc, char ***argv) @@ -51,6 +51,7 @@ shift(int *argc, char ***argv) } void version(void); +void longversion(void); void copyright(void); void check_owner_and_repo(const char **owner, const char **repo); diff --git a/include/gcli/cmd/cmdconfig.h b/include/gcli/cmd/cmdconfig.h index 5c00e029..26aebee2 100644 --- a/include/gcli/cmd/cmdconfig.h +++ b/include/gcli/cmd/cmdconfig.h @@ -49,24 +49,24 @@ struct gcli_config_entry { TAILQ_HEAD(gcli_config_entries, gcli_config_entry); -int gcli_config_parse_args(gcli_ctx *ctx, int *argc, char ***argv); -int gcli_config_init_ctx(gcli_ctx *ctx); -void gcli_config_get_upstream_parts(gcli_ctx *ctx, sn_sv *owner, sn_sv *repo); -char *gcli_config_get_apibase(gcli_ctx *); -sn_sv gcli_config_find_by_key(gcli_ctx *ctx, char const *section_name, +int gcli_config_parse_args(struct gcli_ctx *ctx, int *argc, char ***argv); +int gcli_config_init_ctx(struct gcli_ctx *ctx); +void gcli_config_get_upstream_parts(struct gcli_ctx *ctx, sn_sv *owner, sn_sv *repo); +char *gcli_config_get_apibase(struct gcli_ctx *); +sn_sv gcli_config_find_by_key(struct gcli_ctx *ctx, char const *section_name, char const *key); -char *gcli_config_get_editor(gcli_ctx *ctx); -char *gcli_config_get_token(gcli_ctx *ctx); -char *gcli_config_get_account_name(gcli_ctx *ctx); -sn_sv gcli_config_get_upstream(gcli_ctx *ctx); -sn_sv gcli_config_get_base(gcli_ctx *ctx); -gcli_forge_type gcli_config_get_forge_type(gcli_ctx *ctx); -sn_sv gcli_config_get_override_default_account(gcli_ctx *ctx); -bool gcli_config_pr_inhibit_delete_source_branch(gcli_ctx *ctx); -void gcli_config_get_repo(gcli_ctx *ctx, char const **, char const **); -int gcli_config_have_colours(gcli_ctx *ctx); +char *gcli_config_get_editor(struct gcli_ctx *ctx); +char *gcli_config_get_token(struct gcli_ctx *ctx); +char *gcli_config_get_account_name(struct gcli_ctx *ctx); +sn_sv gcli_config_get_upstream(struct gcli_ctx *ctx); +sn_sv gcli_config_get_base(struct gcli_ctx *ctx); +gcli_forge_type gcli_config_get_forge_type(struct gcli_ctx *ctx); +sn_sv gcli_config_get_override_default_account(struct gcli_ctx *ctx); +bool gcli_config_pr_inhibit_delete_source_branch(struct gcli_ctx *ctx); +void gcli_config_get_repo(struct gcli_ctx *ctx, char const **, char const **); +int gcli_config_have_colours(struct gcli_ctx *ctx); struct gcli_config_entries const *gcli_config_get_section_entries( - gcli_ctx *ctx, char const *section_name); + struct gcli_ctx *ctx, char const *section_name); #endif /* GCLI_CMD_CMDCONFIG_H */ diff --git a/include/gcli/cmd/comment.h b/include/gcli/cmd/comment.h index ce7a429b..9be3d2a1 100644 --- a/include/gcli/cmd/comment.h +++ b/include/gcli/cmd/comment.h @@ -39,7 +39,7 @@ int gcli_issue_comments(char const *owner, char const *repo, int issue); int gcli_pull_comments(char const *owner, char const *repo, int pull); -void gcli_print_comment_list(gcli_comment_list const *list); +void gcli_print_comment_list(struct gcli_comment_list const *list); int subcommand_comment(int argc, char *argv[]); #endif /* GCLI_CMD_COMMENT_H */ diff --git a/include/gcli/cmd/config.h b/include/gcli/cmd/config.h index 7e0ce117..2dcedbcf 100644 --- a/include/gcli/cmd/config.h +++ b/include/gcli/cmd/config.h @@ -37,7 +37,7 @@ #include #include -void gcli_sshkeys_print_keys(gcli_sshkey_list const *list); +void gcli_sshkeys_print_keys(struct gcli_sshkey_list const *list); int subcommand_config(int argc, char *argv[]); diff --git a/include/gcli/cmd/editor.h b/include/gcli/cmd/editor.h index 27062856..dc8e0cd6 100644 --- a/include/gcli/cmd/editor.h +++ b/include/gcli/cmd/editor.h @@ -36,11 +36,9 @@ #include -#include - -sn_sv gcli_editor_get_user_message( - gcli_ctx *ctx, - void (*initializer)(gcli_ctx *, FILE *, void *), +char *gcli_editor_get_user_message( + struct gcli_ctx *ctx, + void (*initializer)(struct gcli_ctx *, FILE *, void *), void *user_data); #endif /* GCLI_CMD_EDITOR_H */ diff --git a/include/gcli/cmd/forks.h b/include/gcli/cmd/forks.h index 9c40d781..21727e97 100644 --- a/include/gcli/cmd/forks.h +++ b/include/gcli/cmd/forks.h @@ -39,7 +39,7 @@ int subcommand_forks(int argc, char *argv[]); -void gcli_print_forks(enum gcli_output_flags flags, gcli_fork_list const *list, +void gcli_print_forks(enum gcli_output_flags flags, struct gcli_fork_list const *list, int max); #endif /* GCLI_CMD_FORKS_H */ diff --git a/include/gcli/cmd/gists.h b/include/gcli/cmd/gists.h index 61ad96b3..bf7c78eb 100644 --- a/include/gcli/cmd/gists.h +++ b/include/gcli/cmd/gists.h @@ -38,7 +38,7 @@ int subcommand_gists(int argc, char *argv[]); -void gcli_print_gists(enum gcli_output_flags flags, gcli_gist_list const *list, +void gcli_print_gists(enum gcli_output_flags flags, struct gcli_gist_list const *list, int max); #endif /* GCLI_CMD_GISTS_H */ diff --git a/include/gcli/cmd/gitconfig.h b/include/gcli/cmd/gitconfig.h index 084feaa0..efe13055 100644 --- a/include/gcli/cmd/gitconfig.h +++ b/include/gcli/cmd/gitconfig.h @@ -36,8 +36,6 @@ #include -typedef struct gcli_gitremote gcli_gitremote; - struct gcli_gitremote { sn_sv name; sn_sv owner; @@ -50,7 +48,7 @@ sn_sv gcli_gitconfig_get_current_branch(void); void gcli_gitconfig_add_fork_remote(char const *org, char const *repo); -int gcli_gitconfig_get_forgetype(gcli_ctx *ctx, char const *remote_name); +int gcli_gitconfig_get_forgetype(struct gcli_ctx *ctx, char const *remote_name); int gcli_gitconfig_repo_by_remote(char const *remote_name, char const **owner, diff --git a/include/gcli/cmd/issues.h b/include/gcli/cmd/issues.h index f45e9690..866dc8c9 100644 --- a/include/gcli/cmd/issues.h +++ b/include/gcli/cmd/issues.h @@ -37,11 +37,11 @@ #include void gcli_print_issues(enum gcli_output_flags const flags, - gcli_issue_list const *const list, int const max); + struct gcli_issue_list const *const list, int const max); -void gcli_issue_print_summary(gcli_issue const *const it); +void gcli_issue_print_summary(struct gcli_issue const *const it); -void gcli_issue_print_op(gcli_issue const *const it); +void gcli_issue_print_op(struct gcli_issue const *const it); int subcommand_issues(int argc, char *argv[]); diff --git a/include/gcli/cmd/labels.h b/include/gcli/cmd/labels.h index 83217fac..eca84f76 100644 --- a/include/gcli/cmd/labels.h +++ b/include/gcli/cmd/labels.h @@ -36,7 +36,7 @@ #include -void gcli_labels_print(gcli_label_list const *list, int max); +void gcli_labels_print(struct gcli_label_list const *list, int max); int subcommand_labels(int argc, char *argv[]); diff --git a/include/gcli/cmd/milestones.h b/include/gcli/cmd/milestones.h index 1ebd03f5..75a65428 100644 --- a/include/gcli/cmd/milestones.h +++ b/include/gcli/cmd/milestones.h @@ -36,10 +36,10 @@ #include -void gcli_print_milestones(gcli_ctx *ctx, gcli_milestone_list const *it, +void gcli_print_milestones(struct gcli_ctx *ctx, struct gcli_milestone_list const *it, int max); -void gcli_print_milestone(gcli_ctx *ctx, gcli_milestone const *it); +void gcli_print_milestone(struct gcli_ctx *ctx, struct gcli_milestone const *it); int subcommand_milestones(int argc, char *argv[]); diff --git a/include/gcli/cmd/pipelines.h b/include/gcli/cmd/pipelines.h index 4d7e2d65..cc613b43 100644 --- a/include/gcli/cmd/pipelines.h +++ b/include/gcli/cmd/pipelines.h @@ -34,16 +34,16 @@ #include -void gitlab_print_pipelines(gitlab_pipeline_list const *const list); +void gitlab_print_pipelines(struct gitlab_pipeline_list const *const list); int gitlab_pipelines(char const *owner, char const *repo, int const count); int gitlab_mr_pipelines(char const *owner, char const *repo, int const mr_id); int gitlab_pipeline_jobs(char const *owner, char const *repo, long pipeline, int count); -void gitlab_print_jobs(gitlab_job_list const *const list); +void gitlab_print_jobs(struct gitlab_job_list const *const list); -void gitlab_print_job_status(gitlab_job const *const job); +void gitlab_print_job_status(struct gitlab_job const *const job); int gitlab_job_status(char const *owner, char const *repo, long const jid); int subcommand_pipelines(int argc, char *argv[]); diff --git a/include/gcli/cmd/pulls.h b/include/gcli/cmd/pulls.h index 0099ca33..6fdd469a 100644 --- a/include/gcli/cmd/pulls.h +++ b/include/gcli/cmd/pulls.h @@ -36,19 +36,19 @@ #include -void gcli_print_pulls(enum gcli_output_flags flags, gcli_pull_list const *list, +void gcli_print_pulls(enum gcli_output_flags flags, struct gcli_pull_list const *list, int max); int gcli_print_pull_diff(FILE *stream, char const *owner, char const *reponame, int pr_number); -void gcli_print_pull(gcli_pull const *pull); +void gcli_print_pull(struct gcli_pull const *pull); -void gcli_pull_print_op(gcli_pull const *pull); +void gcli_pull_print_op(struct gcli_pull const *pull); int gcli_pull_checks(char const *owner, char const *repo, int pr_number); -void gcli_print_commits(gcli_commit_list const *const list); +void gcli_print_commits(struct gcli_commit_list const *const list); int subcommand_pulls(int argc, char *argv[]); diff --git a/include/gcli/cmd/releases.h b/include/gcli/cmd/releases.h index 6b1c7414..b82662f0 100644 --- a/include/gcli/cmd/releases.h +++ b/include/gcli/cmd/releases.h @@ -37,7 +37,7 @@ #include void gcli_releases_print(enum gcli_output_flags flags, - gcli_release_list const *list, int max); + struct gcli_release_list const *list, int max); int subcommand_releases(int argc, char *argv[]); diff --git a/include/gcli/cmd/repos.h b/include/gcli/cmd/repos.h index c7ab4653..26d8893a 100644 --- a/include/gcli/cmd/repos.h +++ b/include/gcli/cmd/repos.h @@ -37,10 +37,10 @@ #include #include -void gcli_print_repos(enum gcli_output_flags flags, gcli_repo_list const *repos, +void gcli_print_repos(enum gcli_output_flags flags, struct gcli_repo_list const *repos, int max); -void gcli_repo_print(gcli_repo const *it); +void gcli_repo_print(struct gcli_repo const *it); int subcommand_repos(int argc, char *argv[]); diff --git a/include/gcli/cmd/snippets.h b/include/gcli/cmd/snippets.h index a790867b..b34e56d4 100644 --- a/include/gcli/cmd/snippets.h +++ b/include/gcli/cmd/snippets.h @@ -39,7 +39,7 @@ #include void gcli_snippets_print(enum gcli_output_flags const flags, - gcli_gitlab_snippet_list const *const list, + struct gcli_gitlab_snippet_list const *const list, int const max); int subcommand_snippets(int argc, char *argv[]); diff --git a/include/gcli/cmd/status.h b/include/gcli/cmd/status.h index 73654d9a..96b97268 100644 --- a/include/gcli/cmd/status.h +++ b/include/gcli/cmd/status.h @@ -38,7 +38,7 @@ #include int gcli_status(int count); -void gcli_print_notifications(gcli_notification_list const *); +void gcli_print_notifications(struct gcli_notification_list const *); int subcommand_status(int argc, char *argv[]); #endif /* GCLI_CMD_STATUS_H */ diff --git a/include/gcli/cmd/table.h b/include/gcli/cmd/table.h index d0d5d2fd..2e36155d 100644 --- a/include/gcli/cmd/table.h +++ b/include/gcli/cmd/table.h @@ -41,7 +41,6 @@ #include -typedef struct gcli_tblcoldef gcli_tblcoldef; typedef void *gcli_tbl; typedef void *gcli_dict; @@ -81,7 +80,7 @@ struct gcli_tblcoldef { }; /* Init a table printer */ -gcli_tbl gcli_tbl_begin(gcli_tblcoldef const *cols, +gcli_tbl gcli_tbl_begin(struct gcli_tblcoldef const *cols, size_t cols_size); /* Print the table contents and free all the resources allocated in diff --git a/include/gcli/comments.h b/include/gcli/comments.h index 8ec2181a..f82b6363 100644 --- a/include/gcli/comments.h +++ b/include/gcli/comments.h @@ -41,39 +41,37 @@ #include -typedef struct gcli_comment gcli_comment; -typedef struct gcli_comment_list gcli_comment_list; -typedef struct gcli_submit_comment_opts gcli_submit_comment_opts; - struct gcli_comment { char *author; /* Login name of the comment author */ char *date; /* Creation date of the comment */ - int id; /* id of the comment */ + gcli_id id; /* id of the comment */ char *body; /* Raw text of the comment */ }; struct gcli_comment_list { - gcli_comment *comments; /* List of comments */ - size_t comments_size; /* Size of the list */ + struct gcli_comment *comments; /* List of comments */ + size_t comments_size; /* Size of the list */ }; struct gcli_submit_comment_opts { enum comment_target_type { ISSUE_COMMENT, PR_COMMENT } target_type; char const *owner, *repo; - int target_id; - sn_sv message; + gcli_id target_id; + char const *message; }; -void gcli_comments_free(gcli_comment_list *list); +void gcli_comments_free(struct gcli_comment_list *list); -void gcli_comment_free(gcli_comment *const it); +void gcli_comment_free(struct gcli_comment *const it); -int gcli_get_issue_comments(gcli_ctx *ctx, char const *owner, char const *repo, - int issue, gcli_comment_list *out); +int gcli_get_issue_comments(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue, + struct gcli_comment_list *out); -int gcli_get_pull_comments(gcli_ctx *ctx, char const *owner, char const *repo, - int issue, gcli_comment_list *out); +int gcli_get_pull_comments(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue, + struct gcli_comment_list *out); -int gcli_comment_submit(gcli_ctx *ctx, gcli_submit_comment_opts opts); +int gcli_comment_submit(struct gcli_ctx *ctx, struct gcli_submit_comment_opts opts); #endif /* COMMENTS_H */ diff --git a/include/gcli/ctx.h b/include/gcli/ctx.h index edc89cb7..4388c293 100644 --- a/include/gcli/ctx.h +++ b/include/gcli/ctx.h @@ -38,6 +38,7 @@ * data */ struct gcli_ctx { CURL *curl; + char *curl_useragent; void *usrdata; char *last_error; @@ -55,5 +56,6 @@ int gcli_error(struct gcli_ctx *ctx, char const *const fmt, ...); char *gcli_get_apibase(struct gcli_ctx *ctx); char *gcli_get_authheader(struct gcli_ctx *ctx); +char *gcli_get_token(struct gcli_ctx *ctx); #endif /* GCLI_CTX_H */ diff --git a/include/gcli/curl.h b/include/gcli/curl.h index 4920b5d0..08ceab52 100644 --- a/include/gcli/curl.h +++ b/include/gcli/curl.h @@ -43,15 +43,13 @@ #include #include -typedef int (*parsefn)(gcli_ctx *, json_stream *stream, void *list, - size_t *listsize); +typedef int (*parsefn)(struct gcli_ctx *, struct json_stream *stream, + void *list, size_t *listsize); typedef void (*filterfn)(void *list, size_t *listsize, void const *userdata); -typedef struct gcli_fetch_buffer gcli_fetch_buffer; -typedef struct gcli_fetch_list_ctx gcli_fetch_list_ctx; struct gcli_fetch_buffer { - char *data; - size_t length; + char *data; + size_t length; }; struct gcli_fetch_list_ctx { @@ -64,27 +62,30 @@ struct gcli_fetch_list_ctx { void const *userdata; }; -int gcli_fetch(gcli_ctx *ctx, char const *url, char **pagination_next, - gcli_fetch_buffer *out); +int gcli_fetch(struct gcli_ctx *ctx, char const *url, char **pagination_next, + struct gcli_fetch_buffer *out); -int gcli_curl(gcli_ctx *ctx, FILE *stream, char const *url, +int gcli_curl(struct gcli_ctx *ctx, FILE *stream, char const *url, char const *content_type); -int gcli_fetch_with_method(gcli_ctx *ctx, char const *method, +int gcli_fetch_with_method(struct gcli_ctx *ctx, char const *method, char const *url, char const *data, - char **pagination_next, gcli_fetch_buffer *out); + char **pagination_next, + struct gcli_fetch_buffer *out); -int gcli_post_upload(gcli_ctx *ctx, char const *url, char const *content_type, - void *buffer, size_t buffer_size, gcli_fetch_buffer *out); +int gcli_post_upload(struct gcli_ctx *ctx, char const *url, + char const *content_type, void *buffer, size_t buffer_size, + struct gcli_fetch_buffer *out); -int gcli_curl_gitea_upload_attachment(gcli_ctx *ctx, char const *url, +int gcli_curl_gitea_upload_attachment(struct gcli_ctx *ctx, char const *url, char const *filename, - gcli_fetch_buffer *out); + struct gcli_fetch_buffer *out); -int gcli_curl_test_success(gcli_ctx *ctx, char const *url); +int gcli_curl_test_success(struct gcli_ctx *ctx, char const *url); char *gcli_urlencode(char const *); sn_sv gcli_urlencode_sv(sn_sv const); -char *gcli_urldecode(gcli_ctx *ctx, char const *input); -int gcli_fetch_list(gcli_ctx *ctx, char *url, gcli_fetch_list_ctx *fctx); +char *gcli_urldecode(struct gcli_ctx *ctx, char const *input); +int gcli_fetch_list(struct gcli_ctx *ctx, char *url, + struct gcli_fetch_list_ctx *fctx); #endif /* CURL_H */ diff --git a/include/gcli/date_time.h b/include/gcli/date_time.h index e8e790cb..4800dd33 100644 --- a/include/gcli/date_time.h +++ b/include/gcli/date_time.h @@ -41,7 +41,7 @@ enum { DATEFMT_GITLAB, }; -int gcli_normalize_date(gcli_ctx *ctx, int fmt, char const *const input, +int gcli_normalize_date(struct gcli_ctx *ctx, int fmt, char const *const input, char *output, size_t const output_size); #endif /* GCLI_DATE_TIME_H */ diff --git a/include/gcli/forges.h b/include/gcli/forges.h index 41538567..a704bb6d 100644 --- a/include/gcli/forges.h +++ b/include/gcli/forges.h @@ -46,12 +46,10 @@ #include #include -typedef struct gcli_forge_descriptor gcli_forge_descriptor; - /* Hopefully temporary hack */ typedef int (*gcli_get_pull_checks_cb)( - gcli_ctx *, char const *, char const *, gcli_id, - gcli_pull_checks_list *); + struct gcli_ctx *, char const *, char const *, gcli_id, + struct gcli_pull_checks_list *); /** * Struct of function pointers to perform actions in the given @@ -60,68 +58,68 @@ struct gcli_forge_descriptor { /** * Submit a comment to a pull/mr or issue */ int (*perform_submit_comment)( - gcli_ctx *ctx, - gcli_submit_comment_opts opts, - gcli_fetch_buffer *out); + struct gcli_ctx *ctx, + struct gcli_submit_comment_opts opts, + struct gcli_fetch_buffer *out); /** * List comments on the given issue */ int (*get_issue_comments)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue, - gcli_comment_list *out); + struct gcli_comment_list *out); /** * List comments on the given PR */ int (*get_pull_comments)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr, - gcli_comment_list *out); + struct gcli_comment_list *out); /** * List forks of the given repo */ int (*get_forks)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, int max, - gcli_fork_list *out); + struct gcli_fork_list *out); /** * Fork the given repo into the owner _in */ int (*fork_create)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, char const *_in); /** * Get a list of issues on the given repo */ - int (*get_issues)( - gcli_ctx *ctx, + int (*search_issues)( + struct gcli_ctx *ctx, char const *owner, char const *repo, - gcli_issue_fetch_details const *details, + struct gcli_issue_fetch_details const *details, int max, - gcli_issue_list *out); + struct gcli_issue_list *out); /** * Get a summary of an issue */ int (*get_issue_summary)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue_number, - gcli_issue *out); + struct gcli_issue *out); /** * Close the given issue */ int (*issue_close)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue_number); @@ -129,7 +127,7 @@ struct gcli_forge_descriptor { /** * Reopen the given issue */ int (*issue_reopen)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue_number); @@ -137,7 +135,7 @@ struct gcli_forge_descriptor { /** * Assign an issue to a user */ int (*issue_assign)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue_number, @@ -146,7 +144,7 @@ struct gcli_forge_descriptor { /** * Add labels to issues */ int (*issue_add_labels)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue, @@ -156,7 +154,7 @@ struct gcli_forge_descriptor { /** * Removes labels from issues */ int (*issue_remove_labels)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue, @@ -166,16 +164,44 @@ struct gcli_forge_descriptor { /** * Submit an issue */ int (*perform_submit_issue)( - gcli_ctx *ctx, - gcli_submit_issue_options opts, - gcli_fetch_buffer *out); + struct gcli_ctx *ctx, + struct gcli_submit_issue_options opts, + struct gcli_fetch_buffer *out); /** * Change the title of an issue */ int (*issue_set_title)( - gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue, + struct gcli_ctx *ctx, + char const *owner, + char const *repo, + gcli_id issue, char const *new_title); + /** + * Get attachments of an issue */ + int (*get_issue_attachments)( + struct gcli_ctx *ctx, + char const *owner, + char const *repo, + gcli_id issue, + struct gcli_attachment_list *out); + + /** + * Dump the contents of the attachment to the given file */ + int (*attachment_get_content)( + struct gcli_ctx *ctx, + gcli_id id, + FILE *out); + + /* Issue quirk bitmask */ + enum { + GCLI_ISSUE_QUIRKS_LOCKED = 0x1, + GCLI_ISSUE_QUIRKS_COMMENTS = 0x2, + GCLI_ISSUE_QUIRKS_PROD_COMP = 0x4, + GCLI_ISSUE_QUIRKS_URL = 0x8, + GCLI_ISSUE_QUIRKS_ATTACHMENTS = 0x10, + } const issue_quirks; + /** * Bitmask of exceptions/fields that the forge doesn't support */ enum { @@ -188,31 +214,31 @@ struct gcli_forge_descriptor { /** * Get list of milestones */ int (*get_milestones)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, int max, - gcli_milestone_list *out); + struct gcli_milestone_list *out); /** * Get a single milestone */ int (*get_milestone)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone, - gcli_milestone *out); + struct gcli_milestone *out); /** * create a milestone */ int (*create_milestone)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, struct gcli_milestone_create_args const *args); /** * delete a milestone */ int (*delete_milestone)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone); @@ -220,7 +246,7 @@ struct gcli_forge_descriptor { /** * delete a milestone */ int (*milestone_set_duedate)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone, @@ -229,15 +255,15 @@ struct gcli_forge_descriptor { /** * Get list of issues attached to this milestone */ int (*get_milestone_issues)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone, - gcli_issue_list *out); + struct gcli_issue_list *out); /** Assign an issue to a milestone */ int (*issue_set_milestone)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue, @@ -246,7 +272,7 @@ struct gcli_forge_descriptor { /** * Clear the milestones of an issue */ int (*issue_clear_milestone)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue); @@ -254,17 +280,17 @@ struct gcli_forge_descriptor { /** * Get a list of PRs/MRs on the given repo */ int (*get_pulls)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *reponame, - gcli_pull_fetch_details const *details, + struct gcli_pull_fetch_details const *details, int max, - gcli_pull_list *out); + struct gcli_pull_list *out); /** * Fetch the PR diff into the file */ int (*pull_get_diff)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, FILE *stream, char const *owner, char const *reponame, @@ -273,7 +299,7 @@ struct gcli_forge_descriptor { /** * Fetch the PR patch series into the file */ int (*pull_get_patch)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, FILE *stream, char const *owner, char const *repo, @@ -283,13 +309,13 @@ struct gcli_forge_descriptor { * Return a list of checks associated with the given pull. * * The type of the returned list depends on the forge type. See - * the definition of gcli_pull_checks_list. */ + * the definition of struct gcli_pull_checks_list. */ gcli_get_pull_checks_cb get_pull_checks; /** * Merge the given PR/MR */ int (*pull_merge)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *reponame, gcli_id pr_number, @@ -298,7 +324,7 @@ struct gcli_forge_descriptor { /** * Reopen the given PR/MR */ int (*pull_reopen)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *reponame, gcli_id pr_number); @@ -306,7 +332,7 @@ struct gcli_forge_descriptor { /** * Close the given PR/MR */ int (*pull_close)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *reponame, gcli_id pr_number); @@ -314,17 +340,17 @@ struct gcli_forge_descriptor { /** * Submit PR/MR */ int (*perform_submit_pull)( - gcli_ctx *ctx, - gcli_submit_pull_options opts); + struct gcli_ctx *ctx, + struct gcli_submit_pull_options opts); /** * Get a list of commits in the given PR/MR */ int (*get_pull_commits)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number, - gcli_commit_list *out); + struct gcli_commit_list *out); /** Bitmask of unsupported fields in the pull summary for this * forge */ @@ -335,21 +361,22 @@ struct gcli_forge_descriptor { GCLI_PRS_QUIRK_MERGED = 0x08, GCLI_PRS_QUIRK_DRAFT = 0x10, GCLI_PRS_QUIRK_COVERAGE = 0x20, + GCLI_PRS_QUIRK_AUTOMERGE = 0x40, } pull_summary_quirks; /** * Get a summary of the given PR/MR */ int (*get_pull)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number, - gcli_pull *out); + struct gcli_pull *out); /** * Add labels to Pull Requests */ int (*pull_add_labels)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr, @@ -359,7 +386,7 @@ struct gcli_forge_descriptor { /** * Removes labels from Pull Requests */ int (*pull_remove_labels)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr, @@ -369,7 +396,7 @@ struct gcli_forge_descriptor { /** * Assign a PR to a milestone */ int (*pull_set_milestone)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pull, @@ -378,7 +405,7 @@ struct gcli_forge_descriptor { /** * Clear a milestone on a PR */ int (*pull_clear_milestone)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pull); @@ -386,7 +413,7 @@ struct gcli_forge_descriptor { /** * Request review of a given pull request by a user */ int (*pull_add_reviewer)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pull, @@ -395,7 +422,7 @@ struct gcli_forge_descriptor { /** * Change the title of a pull request */ int (*pull_set_title)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pull, @@ -404,22 +431,22 @@ struct gcli_forge_descriptor { /** * Get a list of releases in the given repo */ int (*get_releases)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, int max, - gcli_release_list *out); + struct gcli_release_list *out); /** * Create a new release */ int (*create_release)( - gcli_ctx *ctx, - gcli_new_release const *release); + struct gcli_ctx *ctx, + struct gcli_new_release const *release); /** * Delete the release */ int (*delete_release)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, char const *id); @@ -427,26 +454,26 @@ struct gcli_forge_descriptor { /** * Get a list of labels that are valid in the given repository */ int (*get_labels)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, int max, - gcli_label_list *out); + struct gcli_label_list *out); /** * Create the given label * * The ID will be filled in for you */ int (*create_label)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, - gcli_label *label); + struct gcli_label *label); /** * Delete the given label */ int (*delete_label)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, char const *label); @@ -454,29 +481,29 @@ struct gcli_forge_descriptor { /** * Get a list of repos of the given owner */ int (*get_repos)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, int max, - gcli_repo_list *out); + struct gcli_repo_list *out); /** * Create the given repo */ int (*repo_create)( - gcli_ctx *ctx, - gcli_repo_create_options const *options, - gcli_repo *out); + struct gcli_ctx *ctx, + struct gcli_repo_create_options const *options, + struct gcli_repo *out); /** * Delete the given repo */ int (*repo_delete)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo); /** * Change the visibility level of a repository */ int (*repo_set_visibility)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_repo_visibility vis); @@ -484,43 +511,43 @@ struct gcli_forge_descriptor { /** * Status summary for the account */ int (*get_notifications)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, int max, - gcli_notification_list *notifications); + struct gcli_notification_list *notifications); /** * Mark notification with the given id as read * * Returns 0 on success or negative code on failure. */ int (*notification_mark_as_read)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *id); /** * Get an the http authentication header for use by curl */ - char *(*make_authheader)(gcli_ctx *ctx, char const *token); + char *(*make_authheader)(struct gcli_ctx *ctx, char const *token); /** * Get list of SSH keys */ - int (*get_sshkeys)(gcli_ctx *ctx, gcli_sshkey_list *); + int (*get_sshkeys)(struct gcli_ctx *ctx, struct gcli_sshkey_list *); /** * Add an SSH public key */ int (*add_sshkey)( - gcli_ctx *ctx, + struct gcli_ctx *ctx, char const *title, char const *public_key_path, - gcli_sshkey *out); + struct gcli_sshkey *out); /** * Delete an SSH public key by its ID */ - int (*delete_sshkey)(gcli_ctx *ctx, gcli_id id); + int (*delete_sshkey)(struct gcli_ctx *ctx, gcli_id id); /** * Get the error string from the API */ char const *(*get_api_error_string)( - gcli_ctx *ctx, - gcli_fetch_buffer *); + struct gcli_ctx *ctx, + struct gcli_fetch_buffer *); /** * A key in the user json object sent by the API that represents @@ -528,6 +555,23 @@ struct gcli_forge_descriptor { char const *user_object_key; }; -gcli_forge_descriptor const *gcli_forge(gcli_ctx *ctx); +struct gcli_forge_descriptor const *gcli_forge(struct gcli_ctx *ctx); + +/** A macro used for calling one of the dispatch points above. + * + * It check whether the given function pointer is null. If it is it will return + * an error message otherwise the function is called with the specified + * arguments. */ +#define gcli_null_check_call(routine, ctx, ...) \ + do { \ + struct gcli_forge_descriptor const *const forge = gcli_forge(ctx); \ + \ + if (forge->routine) { \ + return forge->routine(ctx, __VA_ARGS__); \ + } else { \ + return gcli_error(ctx, #routine " is not available on this forge"); \ + } \ + } while (0) + #endif /* FORGES_H */ diff --git a/include/gcli/forks.h b/include/gcli/forks.h index 902a137b..695452a3 100644 --- a/include/gcli/forks.h +++ b/include/gcli/forks.h @@ -37,31 +37,29 @@ #include #include -typedef struct gcli_fork gcli_fork; -typedef struct gcli_fork_list gcli_fork_list; - struct gcli_fork { - sn_sv full_name; - sn_sv owner; - sn_sv date; + char *full_name; + char *owner; + char *date; int forks; }; struct gcli_fork_list { - gcli_fork *forks; + struct gcli_fork *forks; size_t forks_size; }; -int gcli_get_forks(gcli_ctx *ctx, char const *owner, char const *reponame, - int max, gcli_fork_list *out); +int gcli_get_forks(struct gcli_ctx *ctx, char const *owner, + char const *reponame, int max, struct gcli_fork_list *out); -int gcli_fork_create(gcli_ctx *ctx, char const *owner, char const *repo, +int gcli_fork_create(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *in); void gcli_fork_delete(char const *owner, char const *repo); -void gcli_forks_free(gcli_fork_list *list); +void gcli_forks_free(struct gcli_fork_list *list); + +void gcli_fork_free(struct gcli_fork *fork); -void gcli_fork_free(gcli_fork *fork); #endif /* FORK_H */ diff --git a/include/gcli/gcli.h b/include/gcli/gcli.h index 3d682fd4..68868f8e 100644 --- a/include/gcli/gcli.h +++ b/include/gcli/gcli.h @@ -47,6 +47,7 @@ typedef enum gcli_forge_type { GCLI_FORGE_GITHUB, GCLI_FORGE_GITLAB, GCLI_FORGE_GITEA, + GCLI_FORGE_BUGZILLA, } gcli_forge_type; typedef uint64_t gcli_id; @@ -57,17 +58,17 @@ typedef uint64_t gcli_id; #include #endif /* IN_LIBGCLI */ -typedef struct gcli_ctx gcli_ctx; +struct gcli_ctx; -char const *gcli_init(gcli_ctx **, - gcli_forge_type (*get_forge_type)(gcli_ctx *), - char *(*get_authheader)(gcli_ctx *), - char *(*get_apibase)(gcli_ctx *)); +char const *gcli_init(struct gcli_ctx **, + gcli_forge_type (*get_forge_type)(struct gcli_ctx *), + char *(*get_authheader)(struct gcli_ctx *), + char *(*get_apibase)(struct gcli_ctx *)); void *gcli_get_userdata(struct gcli_ctx const *); void gcli_set_userdata(struct gcli_ctx *, void *usrdata); void gcli_set_progress_func(struct gcli_ctx *, void (*pfunc)(bool done)); -void gcli_destroy(gcli_ctx **ctx); -char const *gcli_get_error(gcli_ctx *ctx); +void gcli_destroy(struct gcli_ctx **ctx); +char const *gcli_get_error(struct gcli_ctx *ctx); #endif /* GCLI_H */ diff --git a/include/gcli/gitea/comments.h b/include/gcli/gitea/comments.h index cf72f7c8..40520ae0 100644 --- a/include/gcli/gitea/comments.h +++ b/include/gcli/gitea/comments.h @@ -37,10 +37,11 @@ #include #include -int gitea_get_comments(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id issue, gcli_comment_list *out); +int gitea_get_comments(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id issue, struct gcli_comment_list *out); -int gitea_perform_submit_comment(gcli_ctx *ctx, gcli_submit_comment_opts opts, - gcli_fetch_buffer *out); +int gitea_perform_submit_comment(struct gcli_ctx *ctx, + struct gcli_submit_comment_opts opts, + struct gcli_fetch_buffer *out); #endif /* GITEA_COMMENTS_H */ diff --git a/include/gcli/gitea/config.h b/include/gcli/gitea/config.h index fc509606..eae5a6c5 100644 --- a/include/gcli/gitea/config.h +++ b/include/gcli/gitea/config.h @@ -38,6 +38,6 @@ #include -char *gitea_make_authheader(gcli_ctx *ctx, char const *token); +char *gitea_make_authheader(struct gcli_ctx *ctx, char const *token); #endif /* GITEA_CONFIG_H */ diff --git a/include/gcli/gitea/forks.h b/include/gcli/gitea/forks.h index 4e952975..f840a44d 100644 --- a/include/gcli/gitea/forks.h +++ b/include/gcli/gitea/forks.h @@ -36,10 +36,10 @@ #include -int gitea_get_forks(gcli_ctx *ctx, char const *owner, char const *repo, - int max, gcli_fork_list *out); +int gitea_get_forks(struct gcli_ctx *ctx, char const *owner, char const *repo, + int max, struct gcli_fork_list *out); -int gitea_fork_create(gcli_ctx *ctx, char const *owner, char const *repo, +int gitea_fork_create(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *_in); #endif /* GITEA_FORKS_H */ diff --git a/include/gcli/gitea/issues.h b/include/gcli/gitea/issues.h index d50078a5..12742c49 100644 --- a/include/gcli/gitea/issues.h +++ b/include/gcli/gitea/issues.h @@ -36,40 +36,41 @@ #include -int gitea_get_issues(gcli_ctx *ctx, char const *owner, char const *reponame, - gcli_issue_fetch_details const *details, int max, - gcli_issue_list *out); +int gitea_issues_search(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_issue_fetch_details const *details, int max, + struct gcli_issue_list *out); -int gitea_get_issue_summary(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id issue_number, gcli_issue *out); +int gitea_get_issue_summary(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue_number, + struct gcli_issue *out); -int gitea_submit_issue(gcli_ctx *ctx, gcli_submit_issue_options opts, - gcli_fetch_buffer *out); +int gitea_submit_issue(struct gcli_ctx *ctx, struct gcli_submit_issue_options opts, + struct gcli_fetch_buffer *out); -int gitea_issue_close(gcli_ctx *ctx, char const *owner, char const *repo, +int gitea_issue_close(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue_number); -int gitea_issue_reopen(gcli_ctx *ctx, char const *owner, char const *repo, +int gitea_issue_reopen(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue_number); -int gitea_issue_assign(gcli_ctx *ctx, char const *owner, char const *repo, +int gitea_issue_assign(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue_number, char const *assignee); -int gitea_issue_add_labels(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id issue_number, char const *const labels[], - size_t labels_size); +int gitea_issue_add_labels(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue_number, + char const *const labels[], size_t labels_size); -int gitea_issue_remove_labels(gcli_ctx *ctx, char const *owner, +int gitea_issue_remove_labels(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue, char const *const labels[], size_t labels_size); -int gitea_issue_set_milestone(gcli_ctx *ctx, char const *owner, +int gitea_issue_set_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue, gcli_id milestone); -int gitea_issue_clear_milestone(gcli_ctx *ctx, char const *owner, +int gitea_issue_clear_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue); -int gitea_issue_set_title(gcli_ctx *ctx, char const *const owner, +int gitea_issue_set_title(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const issue, char const *const new_title); diff --git a/include/gcli/gitea/labels.h b/include/gcli/gitea/labels.h index 930662ab..abb7f130 100644 --- a/include/gcli/gitea/labels.h +++ b/include/gcli/gitea/labels.h @@ -36,13 +36,13 @@ #include -int gitea_get_labels(gcli_ctx *ctx, char const *owner, char const *reponame, - int max, gcli_label_list *out); +int gitea_get_labels(struct gcli_ctx *ctx, char const *owner, char const *repo, + int max, struct gcli_label_list *out); -int gitea_create_label(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_label *label); +int gitea_create_label(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_label *label); -int gitea_delete_label(gcli_ctx *ctx, char const *owner, char const *repo, +int gitea_delete_label(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *label); #endif /* GITEA_LABELS_H */ diff --git a/include/gcli/gitea/milestones.h b/include/gcli/gitea/milestones.h index 6a26456a..54531036 100644 --- a/include/gcli/gitea/milestones.h +++ b/include/gcli/gitea/milestones.h @@ -32,26 +32,26 @@ #include -int gitea_get_milestones(gcli_ctx *ctx, char const *const owner, +int gitea_get_milestones(struct gcli_ctx *ctx, char const *const owner, char const *const repo, int max, - gcli_milestone_list *out); + struct gcli_milestone_list *out); -int gitea_get_milestone(gcli_ctx *ctx, char const *const owner, +int gitea_get_milestone(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id milestone, - gcli_milestone *out); + struct gcli_milestone *out); -int gitea_create_milestone(gcli_ctx *ctx, +int gitea_create_milestone(struct gcli_ctx *ctx, struct gcli_milestone_create_args const *args); -int gitea_delete_milestone(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id milestone); +int gitea_delete_milestone(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id milestone); -int gitea_milestone_set_duedate(gcli_ctx *ctx, char const *owner, +int gitea_milestone_set_duedate(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone, char const *date); -int gitea_milestone_get_issues(gcli_ctx *ctx, char const *owner, +int gitea_milestone_get_issues(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone, - gcli_issue_list *out); + struct gcli_issue_list *out); #endif /* GCLI_GITEA_MILESTONES_H */ diff --git a/include/gcli/gitea/pulls.h b/include/gcli/gitea/pulls.h index cb942f7a..401575b5 100644 --- a/include/gcli/gitea/pulls.h +++ b/include/gcli/gitea/pulls.h @@ -37,46 +37,50 @@ #include #include -int gitea_get_pulls(gcli_ctx *ctx, char const *owner, char const *reponame, - gcli_pull_fetch_details const *details, int max, - gcli_pull_list *out); +int gitea_get_pulls(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_pull_fetch_details const *details, int max, + struct gcli_pull_list *out); -int gitea_get_pull(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pr_number, gcli_pull *out); +int gitea_get_pull(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id pr_number, struct gcli_pull *out); -int gitea_get_pull_commits(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pr_number, gcli_commit_list *out); +int gitea_get_pull_commits(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id pr_number, + struct gcli_commit_list *out); -int gitea_pull_submit(gcli_ctx *ctx, gcli_submit_pull_options opts); +int gitea_pull_submit(struct gcli_ctx *ctx, struct gcli_submit_pull_options opts); -int gitea_pull_merge(gcli_ctx *ctx, char const *owner, char const *reponame, +int gitea_pull_merge(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number, enum gcli_merge_flags flags); -int gitea_pull_close(gcli_ctx *ctx, char const *owner, char const *repo, +int gitea_pull_close(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number); -int gitea_pull_reopen(gcli_ctx *ctx, char const *owner, char const *repo, +int gitea_pull_reopen(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number); -int gitea_pull_get_diff(gcli_ctx *ctx, FILE *stream, char const *owner, +int gitea_pull_get_diff(struct gcli_ctx *ctx, FILE *stream, char const *owner, char const *repo, gcli_id pr_number); -int gitea_pull_get_patch(gcli_ctx *ctx, FILE *stream, char const *owner, +int gitea_pull_get_patch(struct gcli_ctx *ctx, FILE *stream, char const *owner, char const *repo, gcli_id pr_number); -int gitea_pull_get_checks(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pr_number, gcli_pull_checks_list *out); +int gitea_pull_get_checks(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id pr_number, + struct gcli_pull_checks_list *out); -int gitea_pull_set_milestone(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pr_number, gcli_id milestone_id); +int gitea_pull_set_milestone(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id pr_number, + gcli_id milestone_id); -int gitea_pull_clear_milestone(gcli_ctx *ctx, char const *owner, +int gitea_pull_clear_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number); -int gitea_pull_add_reviewer(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pr_number, char const *username); +int gitea_pull_add_reviewer(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id pr_number, + char const *username); -int gitea_pull_set_title(gcli_ctx *ctx, char const *const owner, +int gitea_pull_set_title(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id pull, char const *const title); diff --git a/include/gcli/gitea/releases.h b/include/gcli/gitea/releases.h index 1a10ec1d..8087d6d5 100644 --- a/include/gcli/gitea/releases.h +++ b/include/gcli/gitea/releases.h @@ -36,12 +36,13 @@ #include -int gitea_get_releases(gcli_ctx *ctx, char const *owner, char const *repo, - int max, gcli_release_list *list); +int gitea_get_releases(struct gcli_ctx *ctx, char const *owner, char const *repo, + int max, struct gcli_release_list *list); -int gitea_create_release(gcli_ctx *ctx, gcli_new_release const *release); +int gitea_create_release(struct gcli_ctx *ctx, + struct gcli_new_release const *release); -int gitea_delete_release(gcli_ctx *ctx, char const *owner, char const *repo, - char const *id); +int gitea_delete_release(struct gcli_ctx *ctx, char const *owner, + char const *repo, char const *id); #endif /* GITEA_RELEASES_H */ diff --git a/include/gcli/gitea/repos.h b/include/gcli/gitea/repos.h index 5dcc4faa..281b6508 100644 --- a/include/gcli/gitea/repos.h +++ b/include/gcli/gitea/repos.h @@ -36,17 +36,19 @@ #include -int gitea_get_repos(gcli_ctx *ctx, char const *owner, int max, - gcli_repo_list *out); +int gitea_get_repos(struct gcli_ctx *ctx, char const *owner, int max, + struct gcli_repo_list *out); -int gitea_get_own_repos(gcli_ctx *ctx, int max, gcli_repo_list *out); +int gitea_get_own_repos(struct gcli_ctx *ctx, int max, + struct gcli_repo_list *out); -int gitea_repo_create(gcli_ctx *ctx, gcli_repo_create_options const *options, - gcli_repo *out); +int gitea_repo_create(struct gcli_ctx *ctx, + struct gcli_repo_create_options const *options, + struct gcli_repo *out); -int gitea_repo_delete(gcli_ctx *ctx, char const *owner, char const *repo); +int gitea_repo_delete(struct gcli_ctx *ctx, char const *owner, char const *repo); -int gitea_repo_set_visibility(gcli_ctx *ctx, char const *const owner, +int gitea_repo_set_visibility(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_repo_visibility vis); diff --git a/include/gcli/gitea/sshkeys.h b/include/gcli/gitea/sshkeys.h index 46c8bc0f..7afabd66 100644 --- a/include/gcli/gitea/sshkeys.h +++ b/include/gcli/gitea/sshkeys.h @@ -32,11 +32,11 @@ #include -int gitea_get_sshkeys(gcli_ctx *ctx, gcli_sshkey_list *out); +int gitea_get_sshkeys(struct gcli_ctx *ctx, struct gcli_sshkey_list *out); -int gitea_add_sshkey(gcli_ctx *ctx, char const *title, - char const *public_key_data, gcli_sshkey *out); +int gitea_add_sshkey(struct gcli_ctx *ctx, char const *title, + char const *public_key_data, struct gcli_sshkey *out); -int gitea_delete_sshkey(gcli_ctx *ctx, gcli_id id); +int gitea_delete_sshkey(struct gcli_ctx *ctx, gcli_id id); #endif /* GCLI_GITEA_SSHKEYS_H */ diff --git a/include/gcli/gitea/status.h b/include/gcli/gitea/status.h index 88be8602..6e1ddc4c 100644 --- a/include/gcli/gitea/status.h +++ b/include/gcli/gitea/status.h @@ -32,9 +32,9 @@ #include -int gitea_get_notifications(gcli_ctx *ctx, int max, - gcli_notification_list *out); +int gitea_get_notifications(struct gcli_ctx *ctx, int max, + struct gcli_notification_list *out); -int gitea_notification_mark_as_read(gcli_ctx *ctx, char const *id); +int gitea_notification_mark_as_read(struct gcli_ctx *ctx, char const *id); #endif /* GITEA_STATUS_H */ diff --git a/include/gcli/github/api.h b/include/gcli/github/api.h index d27269ba..84737e83 100644 --- a/include/gcli/github/api.h +++ b/include/gcli/github/api.h @@ -36,6 +36,6 @@ #include -char const *github_api_error_string(gcli_ctx *ctx, gcli_fetch_buffer *it); +char const *github_api_error_string(struct gcli_ctx *ctx, struct gcli_fetch_buffer *it); #endif /* GITHUB_API_H */ diff --git a/include/gcli/github/checks.h b/include/gcli/github/checks.h index b80a0a62..4fb66de8 100644 --- a/include/gcli/github/checks.h +++ b/include/gcli/github/checks.h @@ -38,9 +38,6 @@ #include -typedef struct gcli_github_check gcli_github_check; -typedef struct github_check_list github_check_list; - struct gcli_github_check { char *name; char *status; @@ -51,16 +48,16 @@ struct gcli_github_check { }; struct github_check_list { - gcli_github_check *checks; + struct gcli_github_check *checks; size_t checks_size; }; -int github_get_checks(gcli_ctx *ctx, char const *owner, char const *repo, +int github_get_checks(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *ref, int max, - github_check_list *checks); + struct github_check_list *checks); -void github_free_checks(github_check_list *checks); +void github_free_checks(struct github_check_list *checks); -void gcli_github_check_free(gcli_github_check *check); +void gcli_github_check_free(struct gcli_github_check *check); #endif /* GITHUB_CHECKS_H */ diff --git a/include/gcli/github/comments.h b/include/gcli/github/comments.h index 6bd6e05b..14a59aef 100644 --- a/include/gcli/github/comments.h +++ b/include/gcli/github/comments.h @@ -37,10 +37,11 @@ #include #include -int github_perform_submit_comment(gcli_ctx *ctx, gcli_submit_comment_opts opts, - gcli_fetch_buffer *out); +int github_perform_submit_comment(struct gcli_ctx *ctx, + struct gcli_submit_comment_opts opts, + struct gcli_fetch_buffer *out); -int github_get_comments(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id issue, gcli_comment_list *out); +int github_get_comments(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id issue, struct gcli_comment_list *out); #endif /* GITHUB_COMMENTS_H */ diff --git a/include/gcli/github/config.h b/include/gcli/github/config.h index 33a848b7..9e5b3fd4 100644 --- a/include/gcli/github/config.h +++ b/include/gcli/github/config.h @@ -38,6 +38,6 @@ #include -char *github_make_authheader(gcli_ctx *ctx, char const *token); +char *github_make_authheader(struct gcli_ctx *ctx, char const *token); #endif /* GITHUB_CONFIG_H */ diff --git a/include/gcli/github/forks.h b/include/gcli/github/forks.h index 4e02c582..c05a5805 100644 --- a/include/gcli/github/forks.h +++ b/include/gcli/github/forks.h @@ -36,10 +36,10 @@ #include -int github_get_forks(gcli_ctx *ctx, char const *owner, char const *repo, - int max, gcli_fork_list *out); +int github_get_forks(struct gcli_ctx *ctx, char const *owner, char const *repo, + int max, struct gcli_fork_list *out); -int github_fork_create(gcli_ctx *ctx, char const *owner, char const *repo, +int github_fork_create(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *_in); #endif /* GITHUB_FORKS_H */ diff --git a/include/gcli/github/gists.h b/include/gcli/github/gists.h index ddcc99c5..f76dc58a 100644 --- a/include/gcli/github/gists.h +++ b/include/gcli/github/gists.h @@ -39,32 +39,27 @@ #include #include -typedef struct gcli_gist gcli_gist; -typedef struct gcli_gist_list gcli_gist_list; -typedef struct gcli_gist_file gcli_gist_file; -typedef struct gcli_new_gist gcli_new_gist; - struct gcli_gist_file { - sn_sv filename; - sn_sv language; - sn_sv url; - sn_sv type; + char *filename; + char *language; + char *url; + char *type; size_t size; }; struct gcli_gist_list { - gcli_gist *gists; + struct gcli_gist *gists; size_t gists_size; }; struct gcli_gist { - sn_sv id; - sn_sv owner; - sn_sv url; - sn_sv date; - sn_sv git_pull_url; - sn_sv description; - gcli_gist_file *files; + char *id; + char *owner; + char *url; + char *date; + char *git_pull_url; + char *description; + struct gcli_gist_file *files; size_t files_size; }; @@ -74,17 +69,18 @@ struct gcli_new_gist { char const *gist_description; }; -int gcli_get_gists(gcli_ctx *ctx, char const *user, int max, - gcli_gist_list *list); +int gcli_get_gists(struct gcli_ctx *ctx, char const *user, int max, + struct gcli_gist_list *list); -int gcli_get_gist(gcli_ctx *ctx, char const *gist_id, gcli_gist *out); +int gcli_get_gist(struct gcli_ctx *ctx, char const *gist_id, + struct gcli_gist *out); -int gcli_create_gist(gcli_ctx *ctx, gcli_new_gist); +int gcli_create_gist(struct gcli_ctx *ctx, struct gcli_new_gist); -int gcli_delete_gist(gcli_ctx *ctx, char const *gist_id); +int gcli_delete_gist(struct gcli_ctx *ctx, char const *gist_id); -void gcli_gists_free(gcli_gist_list *list); -void gcli_gist_free(gcli_gist *g); +void gcli_gists_free(struct gcli_gist_list *list); +void gcli_gist_free(struct gcli_gist *g); /** * NOTE(Nico): Because of idiots designing a web API, we get a list of @@ -92,7 +88,8 @@ void gcli_gist_free(gcli_gist *g); * file names. The objects describing the files obviously contain the * file name again. Whatever...here's a hack. Blame GitHub. */ -int parse_github_gist_files_idiot_hack(gcli_ctx *ctx, json_stream *stream, - gcli_gist *gist); +int parse_github_gist_files_idiot_hack(struct gcli_ctx *ctx, + struct json_stream *stream, + struct gcli_gist *gist); #endif /* GCLI_GITHUB_GISTS_H */ diff --git a/include/gcli/github/issues.h b/include/gcli/github/issues.h index 6c4de0e4..54e5e456 100644 --- a/include/gcli/github/issues.h +++ b/include/gcli/github/issues.h @@ -37,43 +37,47 @@ #include #include -int github_fetch_issues(gcli_ctx *ctx, char *url, int max, - gcli_issue_list *out); +int github_fetch_issues(struct gcli_ctx *ctx, char *url, int max, + struct gcli_issue_list *out); -int github_get_issues(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_issue_fetch_details const *details, int max, - gcli_issue_list *out); +int github_issues_search(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_issue_fetch_details const *details, int max, + struct gcli_issue_list *out); -int github_get_issue_summary(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id issue_number, gcli_issue *out); +int github_get_issue_summary(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue_number, + struct gcli_issue *out); -int github_issue_close(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id issue_number); +int github_issue_close(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue_number); -int github_issue_reopen(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id issue_number); +int github_issue_reopen(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue_number); -int github_perform_submit_issue(gcli_ctx *ctx, gcli_submit_issue_options opts, - gcli_fetch_buffer *out); +int github_perform_submit_issue(struct gcli_ctx *ctx, + struct gcli_submit_issue_options opts, + struct gcli_fetch_buffer *out); -int github_issue_assign(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id issue_number, char const *assignee); +int github_issue_assign(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue_number, + char const *assignee); -int github_issue_add_labels(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id issue, char const *const labels[], - size_t labels_size); +int github_issue_add_labels(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue, + char const *const labels[], size_t labels_size); -int github_issue_remove_labels(gcli_ctx *ctx, char const *owner, +int github_issue_remove_labels(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue, char const *const labels[], size_t labels_size); -int github_issue_set_milestone(gcli_ctx *ctx, char const *owner, - char const *repo, gcli_id issue, gcli_id milestone); +int github_issue_set_milestone(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue, + gcli_id milestone); -int github_issue_clear_milestone(gcli_ctx *ctx, char const *owner, +int github_issue_clear_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue); -int github_issue_set_title(gcli_ctx *ctx, char const *const owner, +int github_issue_set_title(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const issue, char const *const new_title); diff --git a/include/gcli/github/labels.h b/include/gcli/github/labels.h index 1f318a77..6ab47d37 100644 --- a/include/gcli/github/labels.h +++ b/include/gcli/github/labels.h @@ -36,13 +36,13 @@ #include -int github_get_labels(gcli_ctx *ctx, char const *owner, char const *reponame, - int max, gcli_label_list *out); +int github_get_labels(struct gcli_ctx *ctx, char const *owner, char const *repo, + int max, struct gcli_label_list *out); -int github_create_label(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_label *label); +int github_create_label(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_label *label); -int github_delete_label(gcli_ctx *ctx, char const *owner, char const *repo, +int github_delete_label(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *label); #endif /* GITHUB_LABELS_H */ diff --git a/include/gcli/github/milestones.h b/include/gcli/github/milestones.h index ad3310b0..dc2964b5 100644 --- a/include/gcli/github/milestones.h +++ b/include/gcli/github/milestones.h @@ -32,23 +32,25 @@ #include -int github_get_milestones(gcli_ctx *ctx, char const *owner, char const *repo, - int max, gcli_milestone_list *out); +int github_get_milestones(struct gcli_ctx *ctx, char const *owner, + char const *repo, int max, + struct gcli_milestone_list *out); -int github_get_milestone(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id milestone, gcli_milestone *out); +int github_get_milestone(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id milestone, + struct gcli_milestone *out); -int github_create_milestone(gcli_ctx *ctx, +int github_create_milestone(struct gcli_ctx *ctx, struct gcli_milestone_create_args const *args); -int github_delete_milestone(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id milestone); +int github_delete_milestone(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id milestone); -int github_milestone_get_issues(gcli_ctx *ctx, char const *owner, +int github_milestone_get_issues(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone, - gcli_issue_list *out); + struct gcli_issue_list *out); -int github_milestone_set_duedate(gcli_ctx *ctx, char const *owner, +int github_milestone_set_duedate(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone, char const *date); diff --git a/include/gcli/github/pulls.h b/include/gcli/github/pulls.h index 30092036..30f07abc 100644 --- a/include/gcli/github/pulls.h +++ b/include/gcli/github/pulls.h @@ -37,42 +37,46 @@ #include #include -int github_get_pulls(gcli_ctx *ctx, char const *owner, char const *reponame, - gcli_pull_fetch_details const *details, int max, - gcli_pull_list *out); +int github_get_pulls(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_pull_fetch_details const *details, int max, + struct gcli_pull_list *out); -int github_pull_get_diff(gcli_ctx *ctx, FILE *stream, char const *owner, +int github_pull_get_diff(struct gcli_ctx *ctx, FILE *stream, char const *owner, char const *reponame, gcli_id pr_number); -int github_pull_get_patch(gcli_ctx *ctx, FILE *stream, char const *owner, +int github_pull_get_patch(struct gcli_ctx *ctx, FILE *stream, char const *owner, char const *reponame, gcli_id pr_number); -int github_pull_get_checks(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pr_number, gcli_pull_checks_list *out); +int github_pull_get_checks(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id pr_number, + struct gcli_pull_checks_list *out); -int github_pull_merge(gcli_ctx *ctx, char const *owner, char const *reponame, - gcli_id pr_number, enum gcli_merge_flags flags); +int github_pull_merge(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id pr_number, + enum gcli_merge_flags flags); -int github_pull_reopen(gcli_ctx *ctx, char const *owner, char const *reponame, - gcli_id pr_number); +int github_pull_reopen(struct gcli_ctx *ctx, char const *owner, + char const *reponame, gcli_id pr_number); -int github_pull_close(gcli_ctx *ctx, char const *owner, char const *reponame, +int github_pull_close(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number); -int github_perform_submit_pull(gcli_ctx *ctx, gcli_submit_pull_options opts); +int github_perform_submit_pull(struct gcli_ctx *ctx, struct gcli_submit_pull_options opts); -int github_get_pull_commits(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pr_number, gcli_commit_list *out); +int github_get_pull_commits(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id pr_number, + struct gcli_commit_list *out); -int github_get_pull(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pr_number, gcli_pull *out); +int github_get_pull(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id pr_number, struct gcli_pull *out); sn_sv github_pull_try_derive_head(void); -int github_pull_add_reviewer(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pr_number, char const *username); +int github_pull_add_reviewer(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id pr_number, + char const *username); -int github_pull_set_title(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pull, char const *new_title); +int github_pull_set_title(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id pull, char const *new_title); #endif /* GITHUB_PULLS_H */ diff --git a/include/gcli/github/releases.h b/include/gcli/github/releases.h index 526dee02..16a322a7 100644 --- a/include/gcli/github/releases.h +++ b/include/gcli/github/releases.h @@ -36,12 +36,14 @@ #include -int github_get_releases(gcli_ctx *ctx, char const *owner, char const *repo, - int max, gcli_release_list *list); +int github_get_releases(struct gcli_ctx *ctx, char const *owner, + char const *repo, int max, + struct gcli_release_list *list); -int github_create_release(gcli_ctx *ctx, gcli_new_release const *release); +int github_create_release(struct gcli_ctx *ctx, + struct gcli_new_release const *release); -int github_delete_release(gcli_ctx *ctx, char const *owner, char const *repo, - char const *id); +int github_delete_release(struct gcli_ctx *ctx, char const *owner, + char const *repo, char const *id); #endif /* GITHUB_RELEASES_H */ diff --git a/include/gcli/github/repos.h b/include/gcli/github/repos.h index 2b893c53..77ca147b 100644 --- a/include/gcli/github/repos.h +++ b/include/gcli/github/repos.h @@ -36,17 +36,20 @@ #include -int github_get_repos(gcli_ctx *ctx, char const *owner, int max, - gcli_repo_list *out); +int github_get_repos(struct gcli_ctx *ctx, char const *owner, int max, + struct gcli_repo_list *out); -int github_get_own_repos(gcli_ctx *ctx, int max, gcli_repo_list *out); +int github_get_own_repos(struct gcli_ctx *ctx, int max, + struct gcli_repo_list *out); -int github_repo_delete(gcli_ctx *ctx, char const *owner, char const *repo); +int github_repo_delete(struct gcli_ctx *ctx, char const *owner, + char const *repo); -int github_repo_create(gcli_ctx *ctx, gcli_repo_create_options const *options, - gcli_repo *out); +int github_repo_create(struct gcli_ctx *ctx, + struct gcli_repo_create_options const *options, + struct gcli_repo *out); -int github_repo_set_visibility(gcli_ctx *ctx, char const *const owner, +int github_repo_set_visibility(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_repo_visibility vis); diff --git a/include/gcli/github/sshkeys.h b/include/gcli/github/sshkeys.h index e1730a60..0cae5fdf 100644 --- a/include/gcli/github/sshkeys.h +++ b/include/gcli/github/sshkeys.h @@ -32,11 +32,11 @@ #include -int github_get_sshkeys(gcli_ctx *ctx, gcli_sshkey_list *out); +int github_get_sshkeys(struct gcli_ctx *ctx, struct gcli_sshkey_list *out); -int github_add_sshkey(gcli_ctx *ctx, char const *const title, - char const *const pubkey, gcli_sshkey *out); +int github_add_sshkey(struct gcli_ctx *ctx, char const *const title, + char const *const pubkey, struct gcli_sshkey *out); -int github_delete_sshkey(gcli_ctx *ctx, gcli_id id); +int github_delete_sshkey(struct gcli_ctx *ctx, gcli_id id); #endif /* GCLI_GITHUB_SSHKEYS_H */ diff --git a/include/gcli/github/status.h b/include/gcli/github/status.h index bd474bf8..ec60dc9b 100644 --- a/include/gcli/github/status.h +++ b/include/gcli/github/status.h @@ -36,7 +36,8 @@ #include -int github_get_notifications(gcli_ctx *ctx, int max, gcli_notification_list *out); -int github_notification_mark_as_read(gcli_ctx *ctx, char const *id); +int github_get_notifications(struct gcli_ctx *ctx, int max, + struct gcli_notification_list *out); +int github_notification_mark_as_read(struct gcli_ctx *ctx, char const *id); #endif /* GITHUB_STATUS_H */ diff --git a/include/gcli/gitlab/api.h b/include/gcli/gitlab/api.h index 8d98b90f..be6cfdeb 100644 --- a/include/gcli/gitlab/api.h +++ b/include/gcli/gitlab/api.h @@ -36,7 +36,7 @@ #include -char const *gitlab_api_error_string(gcli_ctx *ctx, gcli_fetch_buffer *buf); -int gitlab_user_id(gcli_ctx *ctx, char const *user_name); +char const *gitlab_api_error_string(struct gcli_ctx *ctx, struct gcli_fetch_buffer *buf); +int gitlab_user_id(struct gcli_ctx *ctx, char const *user_name); #endif /* GITLAB_API_H */ diff --git a/include/gcli/gitlab/comments.h b/include/gcli/gitlab/comments.h index 53318116..46646435 100644 --- a/include/gcli/gitlab/comments.h +++ b/include/gcli/gitlab/comments.h @@ -37,14 +37,16 @@ #include #include -int gitlab_perform_submit_comment(gcli_ctx *ctx, gcli_submit_comment_opts opts, - gcli_fetch_buffer *out); +int gitlab_perform_submit_comment(struct gcli_ctx *ctx, + struct gcli_submit_comment_opts opts, + struct gcli_fetch_buffer *out); -int gitlab_get_issue_comments(gcli_ctx *ctx, char const *owner, +int gitlab_get_issue_comments(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue, - gcli_comment_list *out); + struct gcli_comment_list *out); -int gitlab_get_mr_comments(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id issue, gcli_comment_list *out); +int gitlab_get_mr_comments(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue, + struct gcli_comment_list *out); #endif /* GITLAB_COMMENTS_H */ diff --git a/include/gcli/gitlab/config.h b/include/gcli/gitlab/config.h index 981dac26..47ca3b47 100644 --- a/include/gcli/gitlab/config.h +++ b/include/gcli/gitlab/config.h @@ -38,6 +38,6 @@ #include -char *gitlab_make_authheader(gcli_ctx *ctx, char const *token); +char *gitlab_make_authheader(struct gcli_ctx *ctx, char const *token); #endif /* GITLAB_CONFIG_H */ diff --git a/include/gcli/gitlab/forks.h b/include/gcli/gitlab/forks.h index 5bc49269..4a03a619 100644 --- a/include/gcli/gitlab/forks.h +++ b/include/gcli/gitlab/forks.h @@ -36,10 +36,10 @@ #include -int gitlab_get_forks(gcli_ctx *ctx, char const *owner, char const *repo, - int max, gcli_fork_list *out); +int gitlab_get_forks(struct gcli_ctx *ctx, char const *owner, char const *repo, + int max, struct gcli_fork_list *out); -int gitlab_fork_create(gcli_ctx *ctx, char const *owner, char const *repo, +int gitlab_fork_create(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *_in); #endif /* GITLAB_FORKS_H */ diff --git a/include/gcli/gitlab/issues.h b/include/gcli/gitlab/issues.h index 7f4264da..bdb1dade 100644 --- a/include/gcli/gitlab/issues.h +++ b/include/gcli/gitlab/issues.h @@ -37,43 +37,48 @@ #include #include -int gitlab_fetch_issues(gcli_ctx *ctx, char *url, int max, - gcli_issue_list *out); +int gitlab_fetch_issues(struct gcli_ctx *ctx, char *url, int max, + struct gcli_issue_list *out); -int gitlab_get_issues(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_issue_fetch_details const *details, int max, - gcli_issue_list *out); +int gitlab_issues_search(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_issue_fetch_details const *details, + int max, struct gcli_issue_list *out); -int gitlab_get_issue_summary(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id issue_number, gcli_issue *out); +int gitlab_get_issue_summary(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue_number, + struct gcli_issue *out); -int gitlab_issue_close(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id issue_number); +int gitlab_issue_close(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue_number); -int gitlab_issue_reopen(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id issue_number); +int gitlab_issue_reopen(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue_number); -int gitlab_issue_assign(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id issue_number, char const *assignee); +int gitlab_issue_assign(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue_number, + char const *assignee); -int gitlab_perform_submit_issue(gcli_ctx *ctx, gcli_submit_issue_options opts, - gcli_fetch_buffer *out); +int gitlab_perform_submit_issue(struct gcli_ctx *ctx, + struct gcli_submit_issue_options opts, + struct gcli_fetch_buffer *out); -int gitlab_issue_add_labels(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id issue, char const *const labels[], - size_t labels_size); +int gitlab_issue_add_labels(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue, + char const *const labels[], size_t labels_size); -int gitlab_issue_remove_labels(gcli_ctx *ctx, char const *owner, +int gitlab_issue_remove_labels(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue, char const *const labels[], size_t labels_size); -int gitlab_issue_set_milestone(gcli_ctx *ctx, char const *owner, - char const *repo, gcli_id issue, gcli_id milestone); +int gitlab_issue_set_milestone(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue, + gcli_id milestone); -int gitlab_issue_clear_milestone(gcli_ctx *ctx, char const *owner, +int gitlab_issue_clear_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue); -int gitlab_issue_set_title(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id issue, char const *new_title); +int gitlab_issue_set_title(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue, + char const *new_title); #endif /* GITLAB_ISSUES_H */ diff --git a/include/gcli/gitlab/labels.h b/include/gcli/gitlab/labels.h index e0323327..34bbde8d 100644 --- a/include/gcli/gitlab/labels.h +++ b/include/gcli/gitlab/labels.h @@ -36,13 +36,13 @@ #include -int gitlab_get_labels(gcli_ctx *ctx, char const *owner, char const *reponame, - int max, gcli_label_list *out); +int gitlab_get_labels(struct gcli_ctx *ctx, char const *owner, char const *repo, + int max, struct gcli_label_list *out); -int gitlab_create_label(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_label *label); +int gitlab_create_label(struct gcli_ctx *ctx, char const *owner, + char const *repo, struct gcli_label *label); -int gitlab_delete_label(gcli_ctx *ctx, char const *owner, char const *repo, - char const *label); +int gitlab_delete_label(struct gcli_ctx *ctx, char const *owner, + char const *repo, char const *label); #endif /* GITLAB_LABELS_H */ diff --git a/include/gcli/gitlab/merge_requests.h b/include/gcli/gitlab/merge_requests.h index 507c3542..889f3ac7 100644 --- a/include/gcli/gitlab/merge_requests.h +++ b/include/gcli/gitlab/merge_requests.h @@ -36,7 +36,6 @@ #include -typedef struct gitlab_reviewer_id_list gitlab_reviewer_id_list; struct gitlab_reviewer_id_list { gcli_id *reviewers; size_t reviewers_size; @@ -44,7 +43,6 @@ struct gitlab_reviewer_id_list { /* Structs used for internal patch generator. Gitlab does not provide * an endpoint for doing this properly. */ -typedef struct gitlab_diff gitlab_diff; struct gitlab_diff { char *diff; char *old_path; @@ -56,62 +54,64 @@ struct gitlab_diff { bool deleted_file; }; -typedef struct gitlab_diff_list gitlab_diff_list; struct gitlab_diff_list { - gitlab_diff *diffs; + struct gitlab_diff *diffs; size_t diffs_size; }; -int gitlab_fetch_mrs(gcli_ctx *ctx, char *url, int max, - gcli_pull_list *list); +int gitlab_fetch_mrs(struct gcli_ctx *ctx, char *url, int max, + struct gcli_pull_list *list); -int gitlab_get_mrs(gcli_ctx *ctx, char const *owner, +int gitlab_get_mrs(struct gcli_ctx *ctx, char const *owner, char const *reponame, - gcli_pull_fetch_details const *details, + struct gcli_pull_fetch_details const *details, int max, - gcli_pull_list *out); + struct gcli_pull_list *out); -int gitlab_mr_get_diff(gcli_ctx *ctx, FILE *stream, char const *owner, +int gitlab_mr_get_diff(struct gcli_ctx *ctx, FILE *stream, char const *owner, char const *reponame, gcli_id mr_number); -int gitlab_mr_get_patch(gcli_ctx *ctx, FILE *stream, char const *owner, +int gitlab_mr_get_patch(struct gcli_ctx *ctx, FILE *stream, char const *owner, char const *reponame, gcli_id mr_number); -int gitlab_mr_merge(gcli_ctx *ctx, char const *owner, char const *reponame, +int gitlab_mr_merge(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id mr_number, enum gcli_merge_flags flags); -int gitlab_mr_close(gcli_ctx *ctx, char const *owner, char const *reponame, +int gitlab_mr_close(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id mr_number); -int gitlab_mr_reopen(gcli_ctx *ctx, char const *owner, char const *reponame, +int gitlab_mr_reopen(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id mr_number); -int gitlab_get_pull(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id mr_number, gcli_pull *out); +int gitlab_get_pull(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id mr_number, struct gcli_pull *out); -int gitlab_get_pull_commits(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id mr_number, gcli_commit_list *out); +int gitlab_get_pull_commits(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id mr_number, + struct gcli_commit_list *out); -int gitlab_perform_submit_mr(gcli_ctx *ctx, gcli_submit_pull_options opts); +int gitlab_perform_submit_mr(struct gcli_ctx *ctx, struct gcli_submit_pull_options opts); -int gitlab_mr_add_labels(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id mr_number, char const *const labels[], - size_t labels_size); +int gitlab_mr_add_labels(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id mr_number, + char const *const labels[], size_t labels_size); -int gitlab_mr_remove_labels(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id mr_number, char const *const labels[], - size_t labels_size); +int gitlab_mr_remove_labels(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id mr_number, + char const *const labels[], size_t labels_size); -int gitlab_mr_set_milestone(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id mr_number, gcli_id milestone_id); +int gitlab_mr_set_milestone(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id mr_number, + gcli_id milestone_id); -int gitlab_mr_clear_milestone(gcli_ctx *ctx, char const *owner, +int gitlab_mr_clear_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id mr_number); -int gitlab_mr_add_reviewer(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id mr_number, char const *username); +int gitlab_mr_add_reviewer(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id mr_number, + char const *username); -int gitlab_mr_set_title(gcli_ctx *ctx, char const *const owner, +int gitlab_mr_set_title(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const id, char const *const new_title); diff --git a/include/gcli/gitlab/milestones.h b/include/gcli/gitlab/milestones.h index 81d6dc93..44225e14 100644 --- a/include/gcli/gitlab/milestones.h +++ b/include/gcli/gitlab/milestones.h @@ -36,23 +36,25 @@ #include -int gitlab_get_milestones(gcli_ctx *ctx, char const *owner, char const *repo, - int max, gcli_milestone_list *const out); +int gitlab_get_milestones(struct gcli_ctx *ctx, char const *owner, + char const *repo, int max, + struct gcli_milestone_list *const out); -int gitlab_create_milestone(gcli_ctx *ctx, +int gitlab_create_milestone(struct gcli_ctx *ctx, struct gcli_milestone_create_args const *args); -int gitlab_delete_milestone(gcli_ctx *ctx, char const *owner, +int gitlab_delete_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone); -int gitlab_get_milestone(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id milestone, gcli_milestone *out); +int gitlab_get_milestone(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id milestone, + struct gcli_milestone *out); -int gitlab_milestone_get_issues(gcli_ctx *ctx, char const *owner, +int gitlab_milestone_get_issues(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone, - gcli_issue_list *out); + struct gcli_issue_list *out); -int gitlab_milestone_set_duedate(gcli_ctx *ctx, char const *owner, +int gitlab_milestone_set_duedate(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone, char const *date); diff --git a/include/gcli/gitlab/pipelines.h b/include/gcli/gitlab/pipelines.h index 05fe6cef..e89e5373 100644 --- a/include/gcli/gitlab/pipelines.h +++ b/include/gcli/gitlab/pipelines.h @@ -36,11 +36,6 @@ #include -typedef struct gitlab_pipeline gitlab_pipeline; -typedef struct gitlab_pipeline_list gitlab_pipeline_list; -typedef struct gitlab_job gitlab_job; -typedef struct gitlab_job_list gitlab_job_list; - struct gitlab_pipeline { gcli_id id; char *status; @@ -52,7 +47,7 @@ struct gitlab_pipeline { }; struct gitlab_pipeline_list { - gitlab_pipeline *pipelines; + struct gitlab_pipeline *pipelines; size_t pipelines_size; }; @@ -72,39 +67,42 @@ struct gitlab_job { }; struct gitlab_job_list { - gitlab_job *jobs; + struct gitlab_job *jobs; size_t jobs_size; }; -int gitlab_get_pipelines(gcli_ctx *ctx, char const *owner, char const *repo, - int max, gitlab_pipeline_list *out); +int gitlab_get_pipelines(struct gcli_ctx *ctx, char const *owner, + char const *repo, int max, + struct gitlab_pipeline_list *out); -void gitlab_pipeline_free(gitlab_pipeline *pipeline); -void gitlab_pipelines_free(gitlab_pipeline_list *list); +void gitlab_pipeline_free(struct gitlab_pipeline *pipeline); +void gitlab_pipelines_free(struct gitlab_pipeline_list *list); -int gitlab_get_pipeline_jobs(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pipeline, int count, gitlab_job_list *out); +int gitlab_get_pipeline_jobs(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id pipeline, int count, + struct gitlab_job_list *out); -void gitlab_free_jobs(gitlab_job_list *jobs); -void gitlab_free_job(gitlab_job *job); +void gitlab_free_jobs(struct gitlab_job_list *jobs); +void gitlab_free_job(struct gitlab_job *job); -int gitlab_job_get_log(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id job_id, FILE *stream); +int gitlab_job_get_log(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id job_id, FILE *stream); -int gitlab_job_cancel(gcli_ctx *ctx, char const *owner, char const *repo, +int gitlab_job_cancel(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id job_id); -int gitlab_job_retry(gcli_ctx *ctx, char const *owner, char const *repo, +int gitlab_job_retry(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id job_id); -int gitlab_job_download_artifacts(gcli_ctx *ctx, char const *owner, +int gitlab_job_download_artifacts(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id jid, char const *outfile); -int gitlab_get_mr_pipelines(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id mr_id, gitlab_pipeline_list *list); +int gitlab_get_mr_pipelines(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id mr_id, + struct gitlab_pipeline_list *list); -int gitlab_get_job(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const jid, gitlab_job *const out); +int gitlab_get_job(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const jid, struct gitlab_job *const out); #endif /* GITLAB_PIPELINES_H */ diff --git a/include/gcli/gitlab/releases.h b/include/gcli/gitlab/releases.h index e586c7ec..bde9e872 100644 --- a/include/gcli/gitlab/releases.h +++ b/include/gcli/gitlab/releases.h @@ -36,14 +36,17 @@ #include -int gitlab_get_releases(gcli_ctx *ctx, char const *owner, char const *repo, - int max, gcli_release_list *list); +int gitlab_get_releases(struct gcli_ctx *ctx, char const *owner, + char const *repo, int max, + struct gcli_release_list *list); -int gitlab_create_release(gcli_ctx *ctx, gcli_new_release const *release); +int gitlab_create_release(struct gcli_ctx *ctx, + struct gcli_new_release const *release); -int gitlab_delete_release(gcli_ctx *ctx, char const *owner, char const *repo, - char const *id); +int gitlab_delete_release(struct gcli_ctx *ctx, char const *owner, + char const *repo, char const *id); -void gitlab_fixup_release_assets(gcli_ctx *ctx, gcli_release *const release); +void gitlab_fixup_release_assets(struct gcli_ctx *ctx, + struct gcli_release *const release); #endif /* GITLAB_RELEASES_H */ diff --git a/include/gcli/gitlab/repos.h b/include/gcli/gitlab/repos.h index c1e0664a..785193ae 100644 --- a/include/gcli/gitlab/repos.h +++ b/include/gcli/gitlab/repos.h @@ -36,20 +36,23 @@ #include -int gitlab_get_repo(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_repo *out); +int gitlab_get_repo(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_repo *out); -int gitlab_get_repos(gcli_ctx *ctx, char const *owner, int max, - gcli_repo_list *out); +int gitlab_get_repos(struct gcli_ctx *ctx, char const *owner, int max, + struct gcli_repo_list *out); -int gitlab_get_own_repos(gcli_ctx *ctx, int max, gcli_repo_list *out); +int gitlab_get_own_repos(struct gcli_ctx *ctx, int max, + struct gcli_repo_list *out); -int gitlab_repo_delete(gcli_ctx *ctx, char const *owner, char const *repo); +int gitlab_repo_delete(struct gcli_ctx *ctx, char const *owner, + char const *repo); -int gitlab_repo_create(gcli_ctx *ctx, gcli_repo_create_options const *options, - gcli_repo *out); +int gitlab_repo_create(struct gcli_ctx *ctx, + struct gcli_repo_create_options const *options, + struct gcli_repo *out); -int gitlab_repo_set_visibility(gcli_ctx *ctx, char const *const owner, +int gitlab_repo_set_visibility(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_repo_visibility vis); diff --git a/include/gcli/gitlab/snippets.h b/include/gcli/gitlab/snippets.h index fdedae4a..cc5a058c 100644 --- a/include/gcli/gitlab/snippets.h +++ b/include/gcli/gitlab/snippets.h @@ -36,9 +36,6 @@ #include -typedef struct gcli_gitlab_snippet gcli_gitlab_snippet; -typedef struct gcli_gitlab_snippet_list gcli_gitlab_snippet_list; - struct gcli_gitlab_snippet { int id; char *title; @@ -50,17 +47,19 @@ struct gcli_gitlab_snippet { }; struct gcli_gitlab_snippet_list { - gcli_gitlab_snippet *snippets; + struct gcli_gitlab_snippet *snippets; size_t snippets_size; }; -void gcli_snippets_free(gcli_gitlab_snippet_list *list); +void gcli_snippets_free(struct gcli_gitlab_snippet_list *list); + +int gcli_snippets_get(struct gcli_ctx *ctx, int max, + struct gcli_gitlab_snippet_list *out); -int gcli_snippets_get(gcli_ctx *ctx, int max, gcli_gitlab_snippet_list *out); +int gcli_snippet_delete(struct gcli_ctx *ctx, char const *snippet_id); -int gcli_snippet_delete(gcli_ctx *ctx, char const *snippet_id); +int gcli_snippet_get(struct gcli_ctx *ctx, char const *snippet_id, FILE *stream); -int gcli_snippet_get(gcli_ctx *ctx, char const *snippet_id, FILE *stream); +void gcli_gitlab_snippet_free(struct gcli_gitlab_snippet *snippet); -void gcli_gitlab_snippet_free(gcli_gitlab_snippet *snippet); #endif /* GITLAB_SNIPPETS_H */ diff --git a/include/gcli/gitlab/sshkeys.h b/include/gcli/gitlab/sshkeys.h index 482fa645..61ac3639 100644 --- a/include/gcli/gitlab/sshkeys.h +++ b/include/gcli/gitlab/sshkeys.h @@ -36,11 +36,11 @@ #include -int gitlab_get_sshkeys(gcli_ctx *ctx, gcli_sshkey_list *list); +int gitlab_get_sshkeys(struct gcli_ctx *ctx, struct gcli_sshkey_list *list); -int gitlab_add_sshkey(gcli_ctx *ctx, char const *const title, - char const *const pubkey, gcli_sshkey *const out); +int gitlab_add_sshkey(struct gcli_ctx *ctx, char const *const title, + char const *const pubkey, struct gcli_sshkey *const out); -int gitlab_delete_sshkey(gcli_ctx *ctx, gcli_id id); +int gitlab_delete_sshkey(struct gcli_ctx *ctx, gcli_id id); #endif /* GCLI_GITLAB_SSHKEYS_H */ diff --git a/include/gcli/gitlab/status.h b/include/gcli/gitlab/status.h index 83667ed6..af350951 100644 --- a/include/gcli/gitlab/status.h +++ b/include/gcli/gitlab/status.h @@ -36,7 +36,8 @@ #include -int gitlab_get_notifications(gcli_ctx *ctx, int max, gcli_notification_list *out); -int gitlab_notification_mark_as_read(gcli_ctx *ctx, char const *id); +int gitlab_get_notifications(struct gcli_ctx *ctx, int max, + struct gcli_notification_list *out); +int gitlab_notification_mark_as_read(struct gcli_ctx *ctx, char const *id); #endif /* GITLAB_STATUS_H */ diff --git a/include/gcli/issues.h b/include/gcli/issues.h index 44d0bb84..b223279e 100644 --- a/include/gcli/issues.h +++ b/include/gcli/issues.h @@ -34,43 +34,47 @@ #include #endif -#include #include +#include + +#include #include #include -typedef struct gcli_issue gcli_issue; -typedef struct gcli_submit_issue_options gcli_submit_issue_options; -typedef struct gcli_issue_list gcli_issue_list; -typedef struct gcli_issue_fetch_details gcli_issue_fetch_details; +#include struct gcli_issue { gcli_id number; - sn_sv title; - sn_sv created_at; - sn_sv author; - sn_sv state; + char *title; + char *product; /* only on Bugzilla */ + char *component; /* only on Bugzilla */ + char *url; /* only on Bugzilla */ + char *created_at; + char *author; + char *state; int comments; bool locked; - sn_sv body; - sn_sv *labels; + char *body; + char **labels; size_t labels_size; - sn_sv *assignees; + char **assignees; size_t assignees_size; /* workaround for GitHub where PRs are also issues */ int is_pr; - sn_sv milestone; + char *milestone; }; struct gcli_submit_issue_options { char const *owner; char const *repo; - sn_sv title; - sn_sv body; + char *title; + char *body; + + struct gcli_nvlist extra; }; struct gcli_issue_list { - gcli_issue *issues; + struct gcli_issue *issues; size_t issues_size; }; @@ -79,45 +83,51 @@ struct gcli_issue_fetch_details { char const *author; /* filter issues by this author*/ char const *label; /* filter by the given label */ char const *milestone; /* filter by the given milestone */ + char const *search_term; /* a search term or NULL if unspecified */ }; -int gcli_get_issues(gcli_ctx *ctx, char const *owner, char const *reponame, - gcli_issue_fetch_details const *details, int max, - gcli_issue_list *out); +int gcli_issues_search(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_issue_fetch_details const *details, int max, + struct gcli_issue_list *out); -void gcli_issues_free(gcli_issue_list *); +void gcli_issues_free(struct gcli_issue_list *); -int gcli_get_issue(gcli_ctx *ctx, char const *owner, char const *reponame, - gcli_id issue_number, gcli_issue *out); +int gcli_get_issue(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id issue_number, struct gcli_issue *out); -void gcli_issue_free(gcli_issue *it); +void gcli_issue_free(struct gcli_issue *it); -int gcli_issue_close(gcli_ctx *ctx, char const *owner, char const *repo, +int gcli_issue_close(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue_number); -int gcli_issue_reopen(gcli_ctx *ctx, char const *owner, char const *repo, +int gcli_issue_reopen(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue_number); -int gcli_issue_submit(gcli_ctx *ctx, gcli_submit_issue_options); +int gcli_issue_submit(struct gcli_ctx *ctx, struct gcli_submit_issue_options); -int gcli_issue_assign(gcli_ctx *ctx, char const *owner, char const *repo, +int gcli_issue_assign(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue_number, char const *assignee); -int gcli_issue_add_labels(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id issue_number, char const *const labels[], - size_t labels_size); +int gcli_issue_add_labels(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue_number, + char const *const labels[], size_t labels_size); -int gcli_issue_remove_labels(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id issue_number, char const *const labels[], - size_t labels_size); +int gcli_issue_remove_labels(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue_number, + char const *const labels[], size_t labels_size); -int gcli_issue_set_milestone(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id issue, int milestone); +int gcli_issue_set_milestone(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue, int milestone); -int gcli_issue_clear_milestone(gcli_ctx *cxt, char const *owner, +int gcli_issue_clear_milestone(struct gcli_ctx *cxt, char const *owner, char const *repo, gcli_id issue); -int gcli_issue_set_title(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id issue, char const *new_title); +int gcli_issue_set_title(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue, + char const *new_title); + +int gcli_issue_get_attachments(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue, + struct gcli_attachment_list *attachments); #endif /* ISSUES_H */ diff --git a/include/gcli/json_gen.h b/include/gcli/json_gen.h index c5286116..c6166dfd 100644 --- a/include/gcli/json_gen.h +++ b/include/gcli/json_gen.h @@ -34,6 +34,8 @@ #include #endif +#include + #include #include @@ -42,7 +44,6 @@ enum { GCLI_JSONGEN_OBJECT = 2, }; -typedef struct gcli_jsongen gcli_jsongen; struct gcli_jsongen { char *buffer; size_t buffer_size; @@ -56,17 +57,19 @@ struct gcli_jsongen { bool first_elem; /* first element in object/array */ }; -int gcli_jsongen_init(gcli_jsongen *gen); -void gcli_jsongen_free(gcli_jsongen *gen); -char *gcli_jsongen_to_string(gcli_jsongen *gen); +int gcli_jsongen_init(struct gcli_jsongen *gen); +void gcli_jsongen_free(struct gcli_jsongen *gen); +char *gcli_jsongen_to_string(struct gcli_jsongen *gen); -int gcli_jsongen_begin_object(gcli_jsongen *gen); -int gcli_jsongen_end_object(gcli_jsongen *gen); -int gcli_jsongen_begin_array(gcli_jsongen *gen); -int gcli_jsongen_end_array(gcli_jsongen *gen); -int gcli_jsongen_objmember(gcli_jsongen *gen, char const *key); -int gcli_jsongen_number(gcli_jsongen *gen, long long num); -int gcli_jsongen_string(gcli_jsongen *gen, char const *value); -int gcli_jsongen_null(gcli_jsongen *gen); +int gcli_jsongen_begin_object(struct gcli_jsongen *gen); +int gcli_jsongen_end_object(struct gcli_jsongen *gen); +int gcli_jsongen_begin_array(struct gcli_jsongen *gen); +int gcli_jsongen_end_array(struct gcli_jsongen *gen); +int gcli_jsongen_objmember(struct gcli_jsongen *gen, char const *key); +int gcli_jsongen_number(struct gcli_jsongen *gen, long long num); +int gcli_jsongen_id(struct gcli_jsongen *gen, gcli_id const id); +int gcli_jsongen_string(struct gcli_jsongen *gen, char const *value); +int gcli_jsongen_bool(struct gcli_jsongen *gen, bool value); +int gcli_jsongen_null(struct gcli_jsongen *gen); #endif /* GCLI_JSON_GEN_H */ diff --git a/include/gcli/json_util.h b/include/gcli/json_util.h index 632d8d62..0adf824e 100644 --- a/include/gcli/json_util.h +++ b/include/gcli/json_util.h @@ -49,54 +49,34 @@ #define get_double(ctx, input, out) get_double_(ctx, input, out, __func__) #define get_parse_int(ctx, input, out) get_parse_int_(ctx, input, out, __func__) #define get_bool(ctx, input, out) get_bool_(ctx, input, out, __func__) +#define get_bool_relaxed(ctx, input, out) get_bool_relaxed_(ctx, input, out, __func__) #define get_string(ctx, input, out) get_string_(ctx, input, out, __func__) #define get_sv(ctx, input, out) get_sv_(ctx, input, out, __func__) #define get_user(ctx, input, out) get_user_(ctx, input, out, __func__) #define get_label(ctx, input, out) get_label_(ctx, input, out, __func__) #define get_is_string(ctx, input, out) ((void)ctx, (*out = json_next(input) == JSON_STRING), 1) -#define get_int_to_sv(ctx, input, out) get_int_to_sv_(ctx, input, out, __func__) #define get_int_to_string(ctx, input, out) get_int_to_string_(ctx, input, out, __func__) -int get_int_(gcli_ctx *ctx, json_stream *input, int *out, char const *function); -int get_id_(gcli_ctx *ctx, json_stream *input, gcli_id *out, char const *function); -int get_long_(gcli_ctx *ctx, json_stream *input, long *out, char const *function); -int get_size_t_(gcli_ctx *ctx, json_stream *input, size_t *out, char const *function); -int get_double_(gcli_ctx *ctx, json_stream *input, double *out, char const *function); -int get_parse_int_(gcli_ctx *ctx, json_stream *input, long *out, char const *function); -int get_bool_(gcli_ctx *ctx, json_stream *input, bool *out, char const *function); -int get_string_(gcli_ctx *ctx, json_stream *input, char **out, char const *function); -int get_sv_(gcli_ctx *ctx, json_stream *input, sn_sv *out, char const *function); -int get_user_(gcli_ctx *ctx, json_stream *input, char **out, char const *function); -int get_label_(gcli_ctx *ctx, json_stream *input, char const **out, char const *function); -int get_github_style_colour(gcli_ctx *ctx, json_stream *input, uint32_t *out); -int get_gitlab_style_colour(gcli_ctx *ctx, json_stream *input, uint32_t *out); -int get_github_is_pr(gcli_ctx *ctx, json_stream *input, int *out); -int get_gitlab_can_be_merged(gcli_ctx *ctx, json_stream *input, bool *out); -int get_gitea_visibility(gcli_ctx *ctx, json_stream *input, sn_sv *out); -int get_int_to_sv_(gcli_ctx *ctx, json_stream *input, sn_sv *out, - char const *function); +int get_int_(struct gcli_ctx *ctx, json_stream *input, int *out, char const *function); +int get_id_(struct gcli_ctx *ctx, json_stream *input, gcli_id *out, char const *function); +int get_long_(struct gcli_ctx *ctx, json_stream *input, long *out, char const *function); +int get_size_t_(struct gcli_ctx *ctx, json_stream *input, size_t *out, char const *function); +int get_double_(struct gcli_ctx *ctx, json_stream *input, double *out, char const *function); +int get_parse_int_(struct gcli_ctx *ctx, json_stream *input, long *out, char const *function); +int get_bool_(struct gcli_ctx *ctx, json_stream *input, bool *out, char const *function); +int get_bool_relaxed_(struct gcli_ctx *ctx, json_stream *input, bool *out, char const *function); +int get_string_(struct gcli_ctx *ctx, json_stream *input, char **out, char const *function); +int get_sv_(struct gcli_ctx *ctx, json_stream *input, sn_sv *out, char const *function); +int get_user_(struct gcli_ctx *ctx, json_stream *input, char **out, char const *function); +int get_label_(struct gcli_ctx *ctx, json_stream *input, char const **out, char const *function); +int get_github_style_colour(struct gcli_ctx *ctx, json_stream *input, uint32_t *out); +int get_gitlab_style_colour(struct gcli_ctx *ctx, json_stream *input, uint32_t *out); +int get_github_is_pr(struct gcli_ctx *ctx, json_stream *input, int *out); +int get_gitlab_can_be_merged(struct gcli_ctx *ctx, json_stream *input, bool *out); +int get_gitea_visibility(struct gcli_ctx *ctx, json_stream *input, char **out); sn_sv gcli_json_escape(sn_sv); #define gcli_json_escape_cstr(x) (gcli_json_escape(SV((char *)(x))).data) -int gcli_json_advance(gcli_ctx *ctx, json_stream *input, char const *fmt, ...); - -static inline int -get_user_sv(gcli_ctx *ctx, json_stream *input, sn_sv *out) -{ - char *user_str; - int rc = get_user(ctx, input, &user_str); - if (rc < 0) - return rc; - - *out = SV(user_str); - - return 0; -} - -static inline int -parse_user(gcli_ctx *ctx, json_stream *input, sn_sv *out) -{ - return get_user_sv(ctx, input, out); -} +int gcli_json_advance(struct gcli_ctx *ctx, json_stream *input, char const *fmt, ...); static inline char const * gcli_json_bool(bool it) @@ -105,7 +85,7 @@ gcli_json_bool(bool it) } static inline int -get_int_to_string_(gcli_ctx *ctx, json_stream *input, char **out, +get_int_to_string_(struct gcli_ctx *ctx, json_stream *input, char **out, char const *const fn) { int rc; @@ -136,10 +116,4 @@ get_int_to_string_(gcli_ctx *ctx, json_stream *input, char **out, } \ } while (0) -static inline int -parse_sv(gcli_ctx *ctx, json_stream *stream, sn_sv *out) -{ - return get_sv(ctx, stream, out); -} - #endif /* JSON_UTIL_H */ diff --git a/include/gcli/labels.h b/include/gcli/labels.h index 8b7a3935..39c7161b 100644 --- a/include/gcli/labels.h +++ b/include/gcli/labels.h @@ -40,9 +40,6 @@ #include -typedef struct gcli_label gcli_label; -typedef struct gcli_label_list gcli_label_list; - struct gcli_label { gcli_id id; char *name; @@ -51,21 +48,21 @@ struct gcli_label { }; struct gcli_label_list { - gcli_label *labels; + struct gcli_label *labels; size_t labels_size; }; -int gcli_get_labels(gcli_ctx *ctx, char const *owner, char const *reponame, - int max, gcli_label_list *out); +int gcli_get_labels(struct gcli_ctx *ctx, char const *owner, char const *repo, + int max, struct gcli_label_list *out); -void gcli_free_label(gcli_label *label); +void gcli_free_label(struct gcli_label *label); -void gcli_free_labels(gcli_label_list *labels); +void gcli_free_labels(struct gcli_label_list *labels); -int gcli_create_label(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_label *label); +int gcli_create_label(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_label *label); -int gcli_delete_label(gcli_ctx *ctx, char const *owner, char const *repo, +int gcli_delete_label(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *label); #endif /* LABELS_H */ diff --git a/include/gcli/milestones.h b/include/gcli/milestones.h index c7345ecc..1da0fb4f 100644 --- a/include/gcli/milestones.h +++ b/include/gcli/milestones.h @@ -40,9 +40,6 @@ #include #include -typedef struct gcli_milestone gcli_milestone; -typedef struct gcli_milestone_list gcli_milestone_list; - struct gcli_milestone { gcli_id id; char *title; @@ -61,7 +58,7 @@ struct gcli_milestone { }; struct gcli_milestone_list { - gcli_milestone *milestones; + struct gcli_milestone *milestones; size_t milestones_size; }; @@ -75,26 +72,28 @@ struct gcli_milestone_create_args { char const *repo; }; -int gcli_get_milestones(gcli_ctx *ctx, char const *owner, char const *repo, - int max, gcli_milestone_list *out); +int gcli_get_milestones(struct gcli_ctx *ctx, char const *owner, + char const *repo, int max, + struct gcli_milestone_list *out); -int gcli_get_milestone(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id milestone, gcli_milestone *out); +int gcli_get_milestone(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id milestone, + struct gcli_milestone *out); -int gcli_create_milestone(gcli_ctx *ctx, +int gcli_create_milestone(struct gcli_ctx *ctx, struct gcli_milestone_create_args const *args); -int gcli_delete_milestone(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id milestone); +int gcli_delete_milestone(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id milestone); -void gcli_free_milestone(gcli_milestone *it); -void gcli_free_milestones(gcli_milestone_list *it); +void gcli_free_milestone(struct gcli_milestone *it); +void gcli_free_milestones(struct gcli_milestone_list *it); -int gcli_milestone_get_issues(gcli_ctx *ctx, char const *owner, +int gcli_milestone_get_issues(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone, - gcli_issue_list *out); + struct gcli_issue_list *out); -int gcli_milestone_set_duedate(gcli_ctx *ctx, char const *owner, +int gcli_milestone_set_duedate(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone, char const *date); diff --git a/include/gcli/nvlist.h b/include/gcli/nvlist.h new file mode 100644 index 00000000..ea2a59b1 --- /dev/null +++ b/include/gcli/nvlist.h @@ -0,0 +1,55 @@ +/* + * Copyright 2023 Nico Sonack + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GCLI_NVLIST_H +#define GCLI_NVLIST_H + +#ifdef HAVE_CONFIG_H +#include +#endif /* HAVE_CONFIG_H */ + +#include + +struct gcli_nvpair { + TAILQ_ENTRY(gcli_nvpair) next; + + char *key; + char *value; +}; + +TAILQ_HEAD(gcli_nvlist, gcli_nvpair); + +int gcli_nvlist_init(struct gcli_nvlist *list); +int gcli_nvlist_free(struct gcli_nvlist *list); +int gcli_nvlist_append(struct gcli_nvlist *list, char *key, char *value); +char const *gcli_nvlist_find(struct gcli_nvlist const *list, char const *key); +char const *gcli_nvlist_find_or(struct gcli_nvlist const *list, + char const *key, char const *alternative); + +#endif /* GCLI_NVLIST_H */ diff --git a/include/gcli/pgen.h b/include/gcli/pgen.h index a4c021f7..5c5cd19f 100644 --- a/include/gcli/pgen.h +++ b/include/gcli/pgen.h @@ -5,6 +5,7 @@ #include #endif +#include #include /* PGen command line options */ @@ -30,6 +31,7 @@ struct objparser { enum { OBJPARSER_ENTRIES, OBJPARSER_SELECT } kind; char *name; char *returntype; + bool is_struct; struct objentry *entries; struct { char *fieldtype; @@ -39,6 +41,7 @@ struct objparser { struct arrayparser { char *name; + bool is_struct; char *returntype; char *parser; }; diff --git a/include/gcli/pulls.h b/include/gcli/pulls.h index b0b1d7eb..d9724561 100644 --- a/include/gcli/pulls.h +++ b/include/gcli/pulls.h @@ -40,16 +40,8 @@ #include #include -typedef struct gcli_pull gcli_pull; -typedef struct gcli_pull_fetch_details gcli_pull_fetch_details; -typedef struct gcli_submit_pull_options gcli_submit_pull_options; -typedef struct gcli_commit gcli_commit; -typedef struct gcli_commit_list gcli_commit_list; -typedef struct gcli_pull_list gcli_pull_list; -typedef struct gcli_pull_checks_list gcli_pull_checks_list; - struct gcli_pull_list { - gcli_pull *pulls; + struct gcli_pull *pulls; size_t pulls_size; }; @@ -67,6 +59,7 @@ struct gcli_pull { char *milestone; gcli_id id; gcli_id number; + char *node_id; /* Github: GraphQL compat */ int comments; int additions; int deletions; @@ -75,7 +68,7 @@ struct gcli_pull { int head_pipeline_id; /* GitLab specific */ char *coverage; /* Gitlab Specific */ - sn_sv *labels; + char **labels; size_t labels_size; char **reviewers; /**< User names */ @@ -84,6 +77,7 @@ struct gcli_pull { bool merged; bool mergeable; bool draft; + bool automerge; }; struct gcli_commit { @@ -91,7 +85,7 @@ struct gcli_commit { }; struct gcli_commit_list { - gcli_commit *commits; + struct gcli_commit *commits; size_t commits_size; }; @@ -99,13 +93,14 @@ struct gcli_commit_list { struct gcli_submit_pull_options { char const *owner; char const *repo; - sn_sv from; - sn_sv to; - sn_sv title; - sn_sv body; - char const **labels; + char const *from; + char const *to; + char const *title; + char *body; + char **labels; size_t labels_size; int draft; + bool automerge; /** Automatically merge the PR when a pipeline passes */ }; struct gcli_pull_fetch_details { @@ -120,7 +115,7 @@ struct gcli_pull_fetch_details { * NOTE: KEEP THIS ORDER! WE DEPEND ON THE ABI HERE. * * For github the type of checks is gitlab_check* - * For gitlab the type of checks is gitlab_pipeline* + * For gitlab the type of checks is struct gitlab_pipeline* * * You can cast this type to the list type of either one of them. */ struct gcli_pull_checks_list { @@ -129,67 +124,71 @@ struct gcli_pull_checks_list { int forge_type; }; -int gcli_get_pulls(gcli_ctx *ctx, char const *owner, char const *reponame, - gcli_pull_fetch_details const *details, int max, - gcli_pull_list *out); +int gcli_get_pulls(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_pull_fetch_details const *details, int max, + struct gcli_pull_list *out); -void gcli_pull_free(gcli_pull *it); +void gcli_pull_free(struct gcli_pull *it); -void gcli_pulls_free(gcli_pull_list *list); +void gcli_pulls_free(struct gcli_pull_list *list); -int gcli_pull_get_diff(gcli_ctx *ctx, FILE *fout, char const *owner, +int gcli_pull_get_diff(struct gcli_ctx *ctx, FILE *fout, char const *owner, char const *repo, gcli_id pr_number); -int gcli_pull_get_checks(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pr_number, gcli_pull_checks_list *out); +int gcli_pull_get_checks(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id pr_number, + struct gcli_pull_checks_list *out); -void gcli_pull_checks_free(gcli_pull_checks_list *list); +void gcli_pull_checks_free(struct gcli_pull_checks_list *list); -int gcli_pull_get_commits(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pr_number, gcli_commit_list *out); +int gcli_pull_get_commits(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id pr_number, + struct gcli_commit_list *out); -void gcli_commits_free(gcli_commit_list *list); +void gcli_commits_free(struct gcli_commit_list *list); -int gcli_get_pull(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pr_number, gcli_pull *out); +int gcli_get_pull(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id pr_number, struct gcli_pull *out); -int gcli_pull_submit(gcli_ctx *ctx, gcli_submit_pull_options); +int gcli_pull_submit(struct gcli_ctx *ctx, struct gcli_submit_pull_options); enum gcli_merge_flags { GCLI_PULL_MERGE_SQUASH = 0x1, /* squash commits when merging */ GCLI_PULL_MERGE_DELETEHEAD = 0x2, /* delete the source branch after merging */ }; -int gcli_pull_merge(gcli_ctx *ctx, char const *owner, char const *reponame, +int gcli_pull_merge(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number, enum gcli_merge_flags flags); -int gcli_pull_close(gcli_ctx *ctx, char const *owner, char const *reponame, +int gcli_pull_close(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number); -int gcli_pull_reopen(gcli_ctx *ctx, char const *owner, char const *reponame, +int gcli_pull_reopen(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number); -int gcli_pull_add_labels(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pr_number, char const *const labels[], - size_t labels_size); +int gcli_pull_add_labels(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id pr_number, + char const *const labels[], size_t labels_size); -int gcli_pull_remove_labels(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pr_number, char const *const labels[], - size_t labels_size); +int gcli_pull_remove_labels(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id pr_number, + char const *const labels[], size_t labels_size); -int gcli_pull_set_milestone(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pr_number, int milestone_id); +int gcli_pull_set_milestone(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id pr_number, + int milestone_id); -int gcli_pull_clear_milestone(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pr_number); +int gcli_pull_clear_milestone(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id pr_number); -int gcli_pull_add_reviewer(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pr_number, char const *username); +int gcli_pull_add_reviewer(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id pr_number, + char const *username); -int gcli_pull_get_patch(gcli_ctx *ctx, FILE *out, char const *owner, +int gcli_pull_get_patch(struct gcli_ctx *ctx, FILE *out, char const *owner, char const *repo, gcli_id pr_number); -int gcli_pull_set_title(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pull, char const *new_title); +int gcli_pull_set_title(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id pull, char const *new_title); #endif /* PULLS_H */ diff --git a/include/gcli/releases.h b/include/gcli/releases.h index d9b13624..5ac509a3 100644 --- a/include/gcli/releases.h +++ b/include/gcli/releases.h @@ -37,32 +37,26 @@ #include #include -typedef struct gcli_release gcli_release; -typedef struct gcli_release_list gcli_release_list; -typedef struct gcli_new_release gcli_new_release; -typedef struct gcli_release_asset gcli_release_asset; -typedef struct gcli_release_asset_upload gcli_release_asset_upload; - struct gcli_release_asset { char *name; char *url; }; struct gcli_release { - sn_sv id; /* Probably shouldn't be called id */ - gcli_release_asset *assets; - size_t assets_size; - sn_sv name; - sn_sv body; - sn_sv author; - sn_sv date; - sn_sv upload_url; - bool draft; - bool prerelease; + char *id; /* Probably shouldn't be called id */ + struct gcli_release_asset *assets; + size_t assets_size; + char *name; + char *body; + char *author; + char *date; + char *upload_url; + bool draft; + bool prerelease; }; struct gcli_release_list { - gcli_release *releases; + struct gcli_release *releases; size_t releases_size; }; @@ -78,27 +72,27 @@ struct gcli_new_release { char const *repo; char const *tag; char const *name; - sn_sv body; + char *body; char const *commitish; bool draft; bool prerelease; - gcli_release_asset_upload assets[GCLI_RELEASE_MAX_ASSETS]; + struct gcli_release_asset_upload assets[GCLI_RELEASE_MAX_ASSETS]; size_t assets_size; }; -int gcli_get_releases(gcli_ctx *ctx, char const *owner, char const *repo, - int max, gcli_release_list *list); +int gcli_get_releases(struct gcli_ctx *ctx, char const *owner, char const *repo, + int max, struct gcli_release_list *list); -void gcli_free_releases(gcli_release_list *); +void gcli_free_releases(struct gcli_release_list *); -int gcli_create_release(gcli_ctx *ctx, gcli_new_release const *); +int gcli_create_release(struct gcli_ctx *ctx, struct gcli_new_release const *); -int gcli_release_push_asset(gcli_ctx *, gcli_new_release *, - gcli_release_asset_upload); +int gcli_release_push_asset(struct gcli_ctx *, struct gcli_new_release *, + struct gcli_release_asset_upload); -int gcli_delete_release(gcli_ctx *ctx, char const *owner, char const *repo, - char const *id); +int gcli_delete_release(struct gcli_ctx *ctx, char const *owner, + char const *repo, char const *id); -void gcli_release_free(gcli_release *release); +void gcli_release_free(struct gcli_release *release); #endif /* RELEASES_H */ diff --git a/include/gcli/repos.h b/include/gcli/repos.h index c196d0b6..1cefe1e6 100644 --- a/include/gcli/repos.h +++ b/include/gcli/repos.h @@ -37,28 +37,24 @@ #include #include -typedef struct gcli_repo gcli_repo; -typedef struct gcli_repo_list gcli_repo_list; -typedef struct gcli_repo_create_options gcli_repo_create_options; - struct gcli_repo { gcli_id id; - sn_sv full_name; - sn_sv name; - sn_sv owner; - sn_sv date; - sn_sv visibility; + char *full_name; + char *name; + char *owner; + char *date; + char *visibility; bool is_fork; }; struct gcli_repo_list { - gcli_repo *repos; + struct gcli_repo *repos; size_t repos_size; }; struct gcli_repo_create_options { - sn_sv name; - sn_sv description; + char *name; + char *description; bool private; }; @@ -67,18 +63,19 @@ typedef enum { GCLI_REPO_VISIBILITY_PUBLIC, } gcli_repo_visibility; -int gcli_get_repos(gcli_ctx *ctx, char const *owner, int max, - gcli_repo_list *list); +int gcli_get_repos(struct gcli_ctx *ctx, char const *owner, int max, + struct gcli_repo_list *list); -void gcli_repos_free(gcli_repo_list *list); -void gcli_repo_free(gcli_repo *it); +void gcli_repos_free(struct gcli_repo_list *list); +void gcli_repo_free(struct gcli_repo *it); -int gcli_repo_delete(gcli_ctx *ctx, char const *owner, char const *repo); +int gcli_repo_delete(struct gcli_ctx *ctx, char const *owner, char const *repo); -int gcli_repo_create(gcli_ctx *ctx, gcli_repo_create_options const *, - gcli_repo *out); +int gcli_repo_create(struct gcli_ctx *ctx, + struct gcli_repo_create_options const *, + struct gcli_repo *out); -int gcli_repo_set_visibility(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_repo_visibility visibility); +int gcli_repo_set_visibility(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_repo_visibility visibility); #endif /* REPOS_H */ diff --git a/include/gcli/sshkeys.h b/include/gcli/sshkeys.h index fb91d788..911b3524 100644 --- a/include/gcli/sshkeys.h +++ b/include/gcli/sshkeys.h @@ -50,13 +50,10 @@ struct gcli_sshkey_list { size_t keys_size; }; -typedef struct gcli_sshkey gcli_sshkey; -typedef struct gcli_sshkey_list gcli_sshkey_list; - -int gcli_sshkeys_get_keys(gcli_ctx *ctx, gcli_sshkey_list *out); -int gcli_sshkeys_add_key(gcli_ctx *ctx, char const *title, - char const *public_key_path, gcli_sshkey *out); -int gcli_sshkeys_delete_key(gcli_ctx *ctx, gcli_id id); -void gcli_sshkeys_free_keys(gcli_sshkey_list *list); +int gcli_sshkeys_get_keys(struct gcli_ctx *ctx, struct gcli_sshkey_list *out); +int gcli_sshkeys_add_key(struct gcli_ctx *ctx, char const *title, + char const *public_key_path, struct gcli_sshkey *out); +int gcli_sshkeys_delete_key(struct gcli_ctx *ctx, gcli_id id); +void gcli_sshkeys_free_keys(struct gcli_sshkey_list *list); #endif /* GCLI_SSHKEYS_H */ diff --git a/include/gcli/status.h b/include/gcli/status.h index c2c4bbe0..68ec2575 100644 --- a/include/gcli/status.h +++ b/include/gcli/status.h @@ -38,9 +38,6 @@ #include -typedef struct gcli_notification gcli_notification; -typedef struct gcli_notification_list gcli_notification_list; - struct gcli_notification { char *id; char *title; @@ -51,13 +48,14 @@ struct gcli_notification { }; struct gcli_notification_list { - gcli_notification *notifications; + struct gcli_notification *notifications; size_t notifications_size; }; -int gcli_get_notifications(gcli_ctx *ctx, int count, gcli_notification_list *out); -int gcli_notification_mark_as_read(gcli_ctx *ctx, char const *id); -void gcli_free_notification(gcli_notification *); -void gcli_free_notifications(gcli_notification_list *); +int gcli_get_notifications(struct gcli_ctx *ctx, int count, + struct gcli_notification_list *out); +int gcli_notification_mark_as_read(struct gcli_ctx *ctx, char const *id); +void gcli_free_notification(struct gcli_notification *); +void gcli_free_notifications(struct gcli_notification_list *); #endif /* STATUS_H */ diff --git a/src/attachments.c b/src/attachments.c new file mode 100644 index 00000000..8c32279e --- /dev/null +++ b/src/attachments.c @@ -0,0 +1,68 @@ +/* + * Copyright 2023 Nico Sonack + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include + +void +gcli_attachments_free(struct gcli_attachment_list *list) +{ + for (size_t i = 0; i < list->attachments_size; ++i) { + gcli_attachment_free(&list->attachments[i]); + } + + free(list->attachments); + list->attachments = NULL; + list->attachments_size = 0; +} + +void +gcli_attachment_free(struct gcli_attachment *it) +{ + free(it->created_at); + free(it->author); + free(it->file_name); + free(it->summary); + free(it->content_type); + free(it->data_base64); +} + +int +gcli_attachment_get_content(struct gcli_ctx *const ctx, gcli_id const id, FILE *out) +{ + struct gcli_forge_descriptor const *const forge = gcli_forge(ctx); + + /* FIXME: this is not entirely correct. Add a separate quirks category. */ + if (forge->issue_quirks & GCLI_ISSUE_QUIRKS_ATTACHMENTS) + return gcli_error(ctx, "forge does not support attachements"); + else + return gcli_forge(ctx)->attachment_get_content(ctx, id, out); +} diff --git a/src/base64.c b/src/base64.c new file mode 100644 index 00000000..c99bdceb --- /dev/null +++ b/src/base64.c @@ -0,0 +1,152 @@ +/* + * Copyright 2023 Nico Sonack + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include +#include + +/* The code below is taken from my IRC chat bot and was originally written by + * raym aka. Aritra Sarkar in 2022. */ +int +gcli_decode_base64(struct gcli_ctx *ctx, char const *input, char *buffer, + size_t buffer_size) +{ + char const digits[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxy" + "z0123456789+/"; + + char digit = 0; + unsigned long octets = 0; + size_t bits_rem = 0; + size_t length = 0; + + memset(buffer, 0, buffer_size); + + while ((digit = *input++)) { + + if (digit == '=') { + if (bits_rem % 8 == 0) + /* Unnecessary padding char */ + return gcli_error(ctx, "invalid base64 input"); + + do { + if (digit != '=') + return gcli_error(ctx, "invalid base64 input"); + + octets >>= 2; + bits_rem -= 2; + + digit = *input++; + + } while (bits_rem % 8 != 0); + + if (digit) + return gcli_error(ctx, "invalid base64 input"); + + size_t byte_count = 0; + while (bits_rem > 0) { + unsigned char octet = octets & 0xff; + + if (octet == '\0') + return gcli_error(ctx, "null-character encountered during base64 decode"); + + buffer[length + (bits_rem / 8) - 1] = (char) octet; + octets >>= 8; + bits_rem -= 8; + byte_count++; + } + + length += byte_count; + + return 0; + } + + size_t sextet = 0; + + /* Lookup index for a digit. We shall perform a linear search + * for the index of the digit. Since there are only 64 digits, + * this should be done in a jiffy. */ + for ( ; sextet < 64; sextet++) + if (digits[sextet] == digit) + break; + + if (sextet == 64) + /* Oops! We couldn't lookup the index of `digit` */ + return gcli_error(ctx, "invalid base64 input"); + + octets = (octets << 6) | sextet; + bits_rem += 6; + + /* 4 sextets (24 bits) of base64 input yields 3 bytes */ + + if (bits_rem == 24) { + while (bits_rem > 0) { + unsigned char octet = octets & 0xff; + + if (octet == '\0') + return gcli_error(ctx, "null-character encountered during base64 decode"); + + buffer[length + (bits_rem / 8) - 1] = (char) octet; + octets >>= 8; + bits_rem -= 8; + } + + length += 3; + } + } + + if (bits_rem > 0) + return gcli_error(ctx, "invalid base64 input"); + + return 0; +} + +int +gcli_base64_decode_print(struct gcli_ctx *ctx, FILE *out, char const *const input) +{ + int rc = 0; + char *buffer = NULL; + size_t buffer_size = 0, input_size = 0; + + input_size = strlen(input); + /* account for BASE64 inflation */ + buffer_size = (input_size / 4) * 3; + buffer = calloc(1, buffer_size); + + rc = gcli_decode_base64(ctx, input, buffer, buffer_size); + if (rc < 0) + return rc; + + fwrite(buffer, buffer_size, 1, out); + + free(buffer); + buffer = NULL; + + return 0; +} diff --git a/src/bugzilla/api.c b/src/bugzilla/api.c new file mode 100644 index 00000000..c1593e57 --- /dev/null +++ b/src/bugzilla/api.c @@ -0,0 +1,52 @@ +/* + * Copyright 2023 Nico Sonack + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include + +#include + +char const * +bugzilla_api_error_string(struct gcli_ctx *ctx, struct gcli_fetch_buffer *const buf) +{ + struct json_stream stream = {0}; + int rc; + char *msg; + + json_open_buffer(&stream, buf->data, buf->length); + rc = parse_bugzilla_get_error(ctx, &stream, &msg); + json_close(&stream); + + if (rc < 0) + return strdup("no message: failed to parser error response"); + else + return msg; +} diff --git a/src/bugzilla/attachments.c b/src/bugzilla/attachments.c new file mode 100644 index 00000000..b1227dc4 --- /dev/null +++ b/src/bugzilla/attachments.c @@ -0,0 +1,71 @@ +/* + * Copyright 2023 Nico Sonack + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include +#include + +#include + +int +bugzilla_attachment_get_content(struct gcli_ctx *ctx, gcli_id attachment_id, + FILE *output) +{ + int rc = 0; + char *url; + struct gcli_fetch_buffer buffer = {0}; + struct json_stream stream = {0}; + struct gcli_attachment attachment = {0}; + + url = sn_asprintf("%s/rest/bug/attachment/%"PRIid, + gcli_get_apibase(ctx), attachment_id); + + rc = gcli_fetch(ctx, url, NULL, &buffer); + if (rc < 0) + goto error_fetch; + + json_open_buffer(&stream, buffer.data, buffer.length); + rc = parse_bugzilla_attachment_content(ctx, &stream, &attachment); + if (rc < 0) + goto error_parse; + + rc = gcli_base64_decode_print(ctx, output, attachment.data_base64); + + gcli_attachment_free(&attachment); + +error_parse: + json_close(&stream); + free(buffer.data); + +error_fetch: + free(url); + + return rc; +} diff --git a/src/bugzilla/bugs-parser.c b/src/bugzilla/bugs-parser.c new file mode 100644 index 00000000..4391202e --- /dev/null +++ b/src/bugzilla/bugs-parser.c @@ -0,0 +1,190 @@ +/* + * Copyright 2023 Nico Sonack + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + /* Parser helpers for Bugzilla */ + +#include + +#include +#include + +#include + +int +parse_bugzilla_comments_array_skip_first(struct gcli_ctx *ctx, + struct json_stream *stream, + struct gcli_comment_list *out) +{ + int rc = 0; + + if (json_next(stream) != JSON_ARRAY) + return gcli_error(ctx, "expected array for comments array"); + + SKIP_OBJECT_VALUE(stream); + + while (json_peek(stream) != JSON_ARRAY_END) { + out->comments = realloc(out->comments, sizeof(*out->comments) * (out->comments_size + 1)); + memset(&out->comments[out->comments_size], 0, sizeof(out->comments[out->comments_size])); + rc = parse_bugzilla_comment(ctx, stream, &out->comments[out->comments_size++]); + if (rc < 0) + return rc; + } + + if (json_next(stream) != JSON_ARRAY_END) + return gcli_error(ctx, "unexpected element in array while parsing"); + + return 0; +} + +int +parse_bugzilla_comments_array_only_first(struct gcli_ctx *ctx, + struct json_stream *stream, char **out) +{ + int rc = 0; + + if (json_next(stream) != JSON_ARRAY) + return gcli_error(ctx, "expected array for comments array"); + + rc = parse_bugzilla_comment_text(ctx, stream, out); + if (rc < 0) + return rc; + + while (json_peek(stream) != JSON_ARRAY_END) { + SKIP_OBJECT_VALUE(stream); + } + + if (json_next(stream) != JSON_ARRAY_END) + return gcli_error(ctx, "unexpected element in array while parsing"); + + return 0; +} + +int +parse_bugzilla_bug_comments_dictionary_skip_first(struct gcli_ctx *const ctx, + struct json_stream *stream, + struct gcli_comment_list *out) +{ + enum json_type next = JSON_NULL; + int rc = 0; + + if ((next = json_next(stream)) != JSON_OBJECT) + return gcli_error(ctx, "expected bugzilla comments dictionary"); + + while ((next = json_next(stream)) == JSON_STRING) { + rc = parse_bugzilla_comments_internal_skip_first(ctx, stream, out); + if (rc < 0) + return rc; + } + + if (next != JSON_OBJECT_END) + return gcli_error(ctx, "unclosed bugzilla comments dictionary"); + + return rc; +} + +int +parse_bugzilla_bug_comments_dictionary_only_first(struct gcli_ctx *const ctx, + struct json_stream *stream, + char **out) +{ + enum json_type next = JSON_NULL; + int rc = 0; + + if ((next = json_next(stream)) != JSON_OBJECT) + return gcli_error(ctx, "expected bugzilla comments dictionary"); + + while ((next = json_next(stream)) == JSON_STRING) { + rc = parse_bugzilla_comments_internal_only_first(ctx, stream, out); + if (rc < 0) + return rc; + } + + if (next != JSON_OBJECT_END) + return gcli_error(ctx, "unclosed bugzilla comments dictionary"); + + return rc; +} + +int +parse_bugzilla_assignee(struct gcli_ctx *ctx, struct json_stream *stream, + struct gcli_issue *out) +{ + out->assignees = calloc(1, sizeof (*out->assignees)); + out->assignees_size = 1; + + return get_string(ctx, stream, out->assignees); +} + +int +parse_bugzilla_bug_attachments_dict(struct gcli_ctx *ctx, + struct json_stream *stream, + struct gcli_attachment_list *out) +{ + enum json_type next = JSON_NULL; + int rc = 0; + + if ((next = json_next(stream)) != JSON_OBJECT) + return gcli_error(ctx, "expected bugzilla attachments dictionary"); + + while ((next = json_next(stream)) == JSON_STRING) { + rc = parse_bugzilla_bug_attachments_internal(ctx, stream, + &out->attachments, + &out->attachments_size); + if (rc < 0) + return rc; + } + + if (next != JSON_OBJECT_END) + return gcli_error(ctx, "unclosed bugzilla attachments dictionary"); + + return rc; +} + +int +parse_bugzilla_attachment_content_only_first(struct gcli_ctx *ctx, + struct json_stream *stream, + struct gcli_attachment *out) +{ + enum json_type next = JSON_NULL; + int rc = 0; + + if ((next = json_next(stream)) != JSON_OBJECT) + return gcli_error(ctx, "expected bugzilla attachments dictionary"); + + while ((next = json_next(stream)) == JSON_STRING) { + rc = parse_bugzilla_bug_attachment(ctx, stream, out); + if (rc < 0) + return rc; + } + + if (next != JSON_OBJECT_END) + return gcli_error(ctx, "unclosed bugzilla attachments dictionary"); + + return rc; +} diff --git a/src/bugzilla/bugs.c b/src/bugzilla/bugs.c new file mode 100644 index 00000000..3820727a --- /dev/null +++ b/src/bugzilla/bugs.c @@ -0,0 +1,359 @@ +/* + * Copyright 2023 Nico Sonack + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include + +#include + +#include + +#include +#include +#include + +#include + +int +bugzilla_get_bugs(struct gcli_ctx *ctx, char const *product, char const *component, + struct gcli_issue_fetch_details const *details, int const max, + struct gcli_issue_list *out) +{ + char *url, *e_product = NULL, *e_component = NULL, *e_author = NULL, + *e_query = NULL; + struct gcli_fetch_buffer buffer = {0}; + int rc = 0; + + if (product) { + char *tmp = gcli_urlencode(product); + e_product = sn_asprintf("&product=%s", tmp); + free(tmp); + } + + if (component) { + char *tmp = gcli_urlencode(component); + e_component = sn_asprintf("&component=%s", tmp); + free(tmp); + } + + if (details->author) { + char *tmp = gcli_urlencode(details->author); + e_author = sn_asprintf("&creator=%s", tmp); + free(tmp); + } + + if (details->search_term) { + char *tmp = gcli_urlencode(details->search_term); + e_query = sn_asprintf("&quicksearch=%s", tmp); + free(tmp); + } + + /* TODO: handle the max = -1 case */ + /* Note(Nico): Most of the options here are not very well + * documented. Specifically the order= parameter I have figured out by + * reading the code and trying things until it worked. */ + url = sn_asprintf("%s/rest/bug?order=bug_id%%20DESC%%2C&limit=%d%s%s%s%s%s", + gcli_get_apibase(ctx), max, + details->all ? "&status=All" : "&status=Open&status=New", + e_product ? e_product : "", + e_component ? e_component : "", + e_author ? e_author : "", + e_query ? e_query : ""); + + free(e_query); + free(e_product); + free(e_component); + free(e_author); + + rc = gcli_fetch(ctx, url, NULL, &buffer); + if (rc == 0) { + struct json_stream stream = {0}; + + json_open_buffer(&stream, buffer.data, buffer.length); + rc = parse_bugzilla_bugs(ctx, &stream, out); + + json_close(&stream); + } + + free(buffer.data); + free(url); + + return rc; +} + +int +bugzilla_bug_get_comments(struct gcli_ctx *const ctx, char const *const product, + char const *const component, gcli_id const bug_id, + struct gcli_comment_list *out) +{ + int rc = 0; + struct gcli_fetch_buffer buffer = {0}; + struct json_stream stream = {0}; + char *url = NULL; + + (void) product; + (void) component; + + url = sn_asprintf("%s/rest/bug/%"PRIid"/comment?include_fields=_all", + gcli_get_apibase(ctx), bug_id); + + rc = gcli_fetch(ctx, url, NULL, &buffer); + if (rc < 0) + goto error_fetch; + + json_open_buffer(&stream, buffer.data, buffer.length); + rc = parse_bugzilla_comments(ctx, &stream, out); + json_close(&stream); + + free(buffer.data); + +error_fetch: + free(url); + + return rc; +} + +static int +bugzilla_bug_get_op(struct gcli_ctx *ctx, gcli_id const bug_id, char **out) +{ + int rc = 0; + struct gcli_fetch_buffer buffer = {0}; + struct json_stream stream = {0}; + char *url = NULL; + + url = sn_asprintf("%s/rest/bug/%"PRIid"/comment?include_fields=_all", + gcli_get_apibase(ctx), bug_id); + + rc = gcli_fetch(ctx, url, NULL, &buffer); + if (rc < 0) + goto error_fetch; + + json_open_buffer(&stream, buffer.data, buffer.length); + rc = parse_bugzilla_bug_op(ctx, &stream, out); + json_close(&stream); + + free(buffer.data); + +error_fetch: + free(url); + + return rc; +} + +int +bugzilla_get_bug(struct gcli_ctx *ctx, char const *product, + char const *component, gcli_id bug_id, struct gcli_issue *out) +{ + int rc = 0; + char *url; + struct gcli_fetch_buffer buffer = {0}; + struct gcli_issue_list list = {0}; + struct json_stream stream = {0}; + + /* XXX should we warn if product or component is set? */ + (void) product; + (void) component; + + url = sn_asprintf("%s/rest/bug?limit=1&id=%"PRIid, gcli_get_apibase(ctx), bug_id); + rc = gcli_fetch(ctx, url, NULL, &buffer); + + if (rc < 0) + goto error_fetch; + + json_open_buffer(&stream, buffer.data, buffer.length); + rc = parse_bugzilla_bugs(ctx, &stream, &list); + + if (rc < 0) + goto error_parse; + + if (list.issues_size == 0) { + rc = gcli_error(ctx, "no bug with id %"PRIid, bug_id); + goto error_no_such_bug; + } + + if (list.issues_size > 0) { + assert(list.issues_size == 1); + memcpy(out, &list.issues[0], sizeof(*out)); + } + + /* don't use gcli_issues_free because it frees data behind pointers we + * just copied */ + free(list.issues); + + /* The OP is in the comments. Fetch it separately. */ + rc = bugzilla_bug_get_op(ctx, bug_id, &out->body); + +error_no_such_bug: +error_parse: + json_close(&stream); + free(buffer.data); + +error_fetch: + free(url); + + return rc; +} + +int +bugzilla_bug_get_attachments(struct gcli_ctx *ctx, char const *const product, + char const *const component, gcli_id const bug_id, + struct gcli_attachment_list *const out) +{ + int rc = 0; + char *url = NULL; + struct gcli_fetch_buffer buffer = {0}; + struct json_stream stream = {0}; + + (void) product; + (void) component; + + url = sn_asprintf("%s/rest/bug/%"PRIid"/attachment", + gcli_get_apibase(ctx), bug_id); + + rc = gcli_fetch(ctx, url, NULL, &buffer); + if (rc < 0) + goto error_fetch; + + json_open_buffer(&stream, buffer.data, buffer.length); + rc = parse_bugzilla_bug_attachments(ctx, &stream, out); + json_close(&stream); + + free(buffer.data); + +error_fetch: + free(url); + + return rc; +} + +static void +add_extra_options(struct gcli_nvlist const *list, struct gcli_jsongen *gen) +{ + static struct extra_opt { + char const *json_name; + char const *cli_name; + char const *default_value; + } extra_opts[] = { + { .json_name = "op_sys", + .cli_name = "os", + .default_value = "All" }, + { .json_name = "rep_platform", + .cli_name = "hardware", + .default_value = "All" }, + { .json_name = "version", + .cli_name = "version", + .default_value = "unspecified" }, + }; + static size_t extra_opts_size = ARRAY_SIZE(extra_opts); + + for (size_t i = 0; i < extra_opts_size; ++i) { + struct extra_opt const *o = &extra_opts[i]; + char const *const val = gcli_nvlist_find_or( + list, o->json_name, o->default_value); + + gcli_jsongen_objmember(gen, o->json_name); + gcli_jsongen_string(gen, val); + } +} + +int +bugzilla_bug_submit(struct gcli_ctx *ctx, struct gcli_submit_issue_options opts, + struct gcli_fetch_buffer *out) +{ + char *payload = NULL, *url = NULL; + char *token; /* bugzilla wants the api token as a parameter in the url or the json payload */ + char const *product = opts.owner, *component = opts.repo, + *summary = opts.title, *description = opts.body; + struct gcli_jsongen gen = {0}; + int rc = 0; + + /* prepare data for payload generation */ + if (product == NULL) + return gcli_error(ctx, "product must not be empty"); + + if (component == NULL) + return gcli_error(ctx, "component must not be empty"); + + token = gcli_get_token(ctx); + if (!token) + return gcli_error(ctx, "creating bugs on bugzilla requires a token"); + + /* generate payload */ + rc = gcli_jsongen_init(&gen); + if (rc < 0) { + gcli_error(ctx, "failed to init json generator"); + goto err_jsongen_init; + } + + /* + * { + * "product" : "TestProduct", + * "component" : "TestComponent", + * "summary" : "'This is a test bug - please disregard", + * "description": ..., + * } */ + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "product"); + gcli_jsongen_string(&gen, product); + + gcli_jsongen_objmember(&gen, "component"); + gcli_jsongen_string(&gen, component); + + gcli_jsongen_objmember(&gen, "summary"); + gcli_jsongen_string(&gen, summary); + + if (description) { + gcli_jsongen_objmember(&gen, "description"); + gcli_jsongen_string(&gen, description); + } + + gcli_jsongen_objmember(&gen, "api_key"); + gcli_jsongen_string(&gen, token); + + add_extra_options(&opts.extra, &gen); + } + gcli_jsongen_end_object(&gen); + + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); + + /* generate url and perform request */ + url = sn_asprintf("%s/rest/bug", gcli_get_apibase(ctx)); + rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, out); + + free(url); + free(payload); + +err_jsongen_init: + free(token); + + return rc; +} diff --git a/src/bugzilla/config.c b/src/bugzilla/config.c new file mode 100644 index 00000000..d3b4c30c --- /dev/null +++ b/src/bugzilla/config.c @@ -0,0 +1,39 @@ +/* + * Copyright 2023 Nico Sonack + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +char * +bugzilla_make_authheader(struct gcli_ctx *ctx, char const *const token) +{ + (void) ctx; + (void) token; + return NULL; +} diff --git a/src/cmd/api.c b/src/cmd/api.c index 1f995bf3..55953448 100644 --- a/src/cmd/api.c +++ b/src/cmd/api.c @@ -64,10 +64,10 @@ fetch_all(char *_url) url = _url; do { - gcli_fetch_buffer buffer = {0}; + struct gcli_fetch_buffer buffer = {0}; if (gcli_fetch(g_clictx, url, &next_url, &buffer) < 0) - errx(1, "error: failed to fetch data: %s", + errx(1, "gcli: error: failed to fetch data: %s", gcli_get_error(g_clictx)); fwrite(buffer.data, buffer.length, 1, stdout); @@ -109,9 +109,9 @@ subcommand_api(int argc, char *argv[]) path = shift(&argc, &argv); } else { if (!argc) - errx(1, "error: missing path"); + errx(1, "gcli: error: missing path"); else - errx(1, "error: too many arguments"); + errx(1, "gcli: error: too many arguments"); } if (path[0] == '/') @@ -122,7 +122,7 @@ subcommand_api(int argc, char *argv[]) if (do_all) fetch_all(url); else if (gcli_curl(g_clictx, stdout, url, "application/json") < 0) - errx(1, "error: failed to fetch data: %s", + errx(1, "gcli: error: failed to fetch data: %s", gcli_get_error(g_clictx)); free(url); diff --git a/src/cmd/attachments.c b/src/cmd/attachments.c new file mode 100644 index 00000000..7d5b5d1b --- /dev/null +++ b/src/cmd/attachments.c @@ -0,0 +1,188 @@ +/* + * Copyright 2023 Nico Sonack + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include + +#include +#include +#include + +static void +usage(void) +{ + fprintf(stderr, "usage: gcli [options] attachments -i actions...\n"); + fprintf(stderr, "OPTIONS:\n"); + fprintf(stderr, " -i id Execute the given actions for the specified attachment id.\n"); + fprintf(stderr, "ACTIONS:\n"); + fprintf(stderr, " get [-o path] Fetch and dump the contents of the " + "attachments to the given path or stdout\n"); + fprintf(stderr, "\n"); + version(); +} + +static int +action_attachment_get(int *argc, char ***argv, gcli_id const id) +{ + int ch, rc = 0; + bool oflag_seen = false; + FILE *outfile = NULL; + struct option options[] = { + { .name = "output", .has_arg = required_argument, .flag = NULL, .val = 'o' }, + {0}, + }; + + while ((ch = getopt_long(*argc, *argv, "+o:", options, NULL)) != -1) { + switch (ch) { + case 'o': { + outfile = fopen(optarg, "w"); + if (!outfile) { + fprintf(stderr, "gcli: failed to open ยป%sยซ: %s\n", + optarg, strerror(errno)); + return EXIT_FAILURE; + } + oflag_seen = true; + } break; + default: { + usage(); + return EXIT_FAILURE; + } break; + } + } + + *argc -= optind; + *argv += optind; + optind = 0; /* reset */ + + /* -o wasn't specified */ + if (outfile == NULL) + outfile = stdout; + + rc = gcli_attachment_get_content(g_clictx, id, outfile); + if (rc < 0) { + fprintf(stderr, "gcli: failed to get attachment: %s\n", + gcli_get_error(g_clictx)); + return EXIT_FAILURE; + } + + if (oflag_seen) + fclose(outfile); + + outfile = NULL; + + return EXIT_SUCCESS; +} + +static struct action { + char const *const name; + int (*fn)(int *argc, char ***argv, gcli_id const id); +} const actions[] = { + { .name = "get", .fn = action_attachment_get }, +}; + +static size_t const actions_size = ARRAY_SIZE(actions); + +static struct action const * +find_action(char const *const name) +{ + for (size_t i = 0; i < actions_size; ++i) { + if (strcmp(name, actions[i].name) == 0) + return &actions[i]; + } + return NULL; +} + +int +subcommand_attachments(int argc, char *argv[]) +{ + int ch; + gcli_id iflag; + bool iflag_seen = false; + + struct option options[] = { + { .name = "id", .has_arg = required_argument, .flag = NULL, .val = 'i' }, + {0}, + }; + + while ((ch = getopt_long(argc, argv, "+i:", options, NULL)) != -1) { + switch (ch) { + case 'i': { + char *endptr; + + iflag_seen = true; + iflag = strtoull(optarg, &endptr, 10); + + if (optarg + strlen(optarg) != endptr) { + fprintf(stderr, "gcli: bad attachment id ยป%sยซ\n", optarg); + return EXIT_FAILURE; + } + } break; + default: + usage(); + return EXIT_FAILURE; + } + } + + argc -= optind; + argv += optind; + + optind = 0; /* reset */ + + if (!iflag_seen) { + fprintf(stderr, "gcli: missing -i flag\n"); + usage(); + return EXIT_FAILURE; + } + + if (argc == 0) { + fprintf(stderr, "gcli: missing actions\n"); + usage(); + return EXIT_FAILURE; + } + + while (argc) { + int rc; + char const *const action_name = *argv; + struct action const *const action = find_action(action_name); + + if (action == NULL) { + fprintf(stderr, "gcli: %s: no such action\n", action_name); + usage(); + return EXIT_FAILURE; + } + + rc = action->fn(&argc, &argv, iflag); + if (rc) + return rc; + } + + return 0; +} diff --git a/src/cmd/ci.c b/src/cmd/ci.c index 6e681874..6516647e 100644 --- a/src/cmd/ci.c +++ b/src/cmd/ci.c @@ -59,10 +59,10 @@ usage(void) } void -github_print_checks(github_check_list const *const list) +github_print_checks(struct github_check_list const *const list) { gcli_tbl table; - gcli_tblcoldef cols[] = { + struct gcli_tblcoldef cols[] = { { .name = "ID", .type = GCLI_TBLCOLTYPE_ID, .flags = GCLI_TBLCOL_JUSTIFYR }, { .name = "STATUS", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, { .name = "CONCLUSION", .type = GCLI_TBLCOLTYPE_STRING, .flags = GCLI_TBLCOL_STATECOLOURED }, @@ -78,7 +78,7 @@ github_print_checks(github_check_list const *const list) table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!table) - errx(1, "error: could not init table"); + errx(1, "gcli: error: could not init table"); for (size_t i = 0; i < list->checks_size; ++i) { gcli_tbl_add_row(table, list->checks[i].id, list->checks[i].status, @@ -93,7 +93,7 @@ int github_checks(char const *const owner, char const *const repo, char const *const ref, int const max) { - github_check_list list = {0}; + struct github_check_list list = {0}; int rc = 0; rc = github_get_checks(g_clictx, owner, repo, ref, max, &list); @@ -149,13 +149,13 @@ subcommand_ci(int argc, char *argv[]) /* Check that we have exactly one left argument and print proper * error messages */ if (argc < 1) { - fprintf(stderr, "error: missing ref\n"); + fprintf(stderr, "gcli: error: missing ref\n"); usage(); return EXIT_FAILURE; } if (argc > 1) { - fprintf(stderr, "error: stray arguments\n"); + fprintf(stderr, "gcli: error: stray arguments\n"); usage(); return EXIT_FAILURE; } @@ -168,11 +168,11 @@ subcommand_ci(int argc, char *argv[]) /* Make sure we are actually talking about a github remote because * we might be incorrectly inferring it */ if (gcli_config_get_forge_type(g_clictx) != GCLI_FORGE_GITHUB) - errx(1, "error: The ci subcommand only works for GitHub. " + errx(1, "gcli: error: The ci subcommand only works for GitHub. " "Use gcli -t github ... to force a GitHub remote."); if (github_checks(owner, repo, ref, count) < 0) - errx(1, "error: failed to get github checks: %s", + errx(1, "gcli: error: failed to get github checks: %s", gcli_get_error(g_clictx)); diff --git a/src/cmd/cmd.c b/src/cmd/cmd.c index dd491c66..56e7ed90 100644 --- a/src/cmd/cmd.c +++ b/src/cmd/cmd.c @@ -51,8 +51,14 @@ copyright(void) void version(void) { + fprintf(stderr, PACKAGE_STRING" ("HOSTOS")\n"); +} + +void +longversion(void) +{ + version(); fprintf(stderr, - PACKAGE_STRING" ("HOSTOS")\n" "Using %s\n" "Using vendored pdjson library\n" "\n" @@ -64,9 +70,13 @@ version(void) void check_owner_and_repo(const char **owner, const char **repo) { + /* HACK */ + if (gcli_config_get_forge_type(g_clictx) == GCLI_FORGE_BUGZILLA) + return; + /* If no remote was specified, try to autodetect */ if ((*owner == NULL) != (*repo == NULL)) - errx(1, "error: missing either explicit owner or repo"); + errx(1, "gcli: error: missing either explicit owner or repo"); if (*owner == NULL) gcli_config_get_repo(g_clictx, owner, repo); @@ -132,8 +142,8 @@ delete_repo(bool always_yes, const char *owner, const char *repo) } if (!delete) - errx(1, "Operation aborted"); + errx(1, "gcli: Operation aborted"); if (gcli_repo_delete(g_clictx, owner, repo) < 0) - errx(1, "error: failed to delete repo"); + errx(1, "gcli: error: failed to delete repo"); } diff --git a/src/cmd/cmdconfig.c b/src/cmd/cmdconfig.c index 159c1463..e412755e 100644 --- a/src/cmd/cmdconfig.c +++ b/src/cmd/cmdconfig.c @@ -97,7 +97,7 @@ ctx_config(struct gcli_ctx *ctx) } static bool -should_init_dotgcli(gcli_ctx *ctx) +should_init_dotgcli(struct gcli_ctx *ctx) { struct gcli_dotgcli *dgcli = ctx_dotgcli(ctx); @@ -115,7 +115,7 @@ find_dotgcli(void) curr_dir_path = getcwd(NULL, 128); if (!curr_dir_path) - err(1, "getcwd"); + err(1, "gcli: getcwd"); /* Here we are trying to traverse upwards through the directory * tree, searching for a directory called .git. @@ -123,7 +123,7 @@ find_dotgcli(void) do { curr_dir = opendir(curr_dir_path); if (!curr_dir) - err(1, "opendir"); + err(1, "gcli: opendir"); while ((ent = readdir(curr_dir))) { if (strcmp(".", ent->d_name) == 0 || strcmp("..", ent->d_name) == 0) @@ -154,7 +154,7 @@ find_dotgcli(void) curr_dir_path = realpath(tmp, NULL); if (!curr_dir_path) - err(1, "realpath at %s", tmp); + err(1, "gcli: realpath at %s", tmp); free(tmp); @@ -178,7 +178,7 @@ find_dotgcli(void) } static void -init_local_config(gcli_ctx *ctx) +init_local_config(struct gcli_ctx *ctx) { if (!should_init_dotgcli(ctx)) { return; @@ -198,7 +198,7 @@ init_local_config(gcli_ctx *ctx) int len = sn_mmap_file(path, &dgcli->mmap_pointer); if (len < 0) - err(1, "Unable to open config file"); + err(1, "gcli: unable to open config file"); dgcli->buffer = sn_sv_from_parts(dgcli->mmap_pointer, len); dgcli->buffer = sn_sv_trim_front(dgcli->buffer); @@ -210,7 +210,7 @@ init_local_config(gcli_ctx *ctx) line = sn_sv_trim(line); if (line.length == 0) - errx(1, "%s:%d: Unexpected end of line", + errx(1, "gcli: %s:%d: Unexpected end of line", path, curr_line); // Comments @@ -225,7 +225,7 @@ init_local_config(gcli_ctx *ctx) key = sn_sv_trim(key); if (key.length == 0) - errx(1, "%s:%d: empty key", path, curr_line); + errx(1, "gcli: %s:%d: empty key", path, curr_line); line.data += 1; line.length -= 1; @@ -291,7 +291,7 @@ parse_section_entry(struct config_parser *input, sn_sv key = sn_sv_chop_until(&input->buffer, '='); if (key.length == 0) - errx(1, "%s:%d: empty key", input->filename, input->line); + errx(1, "gcli: %s:%d: empty key", input->filename, input->line); input->buffer.data += 1; input->buffer.length -= 1; @@ -307,7 +307,7 @@ parse_section_title(struct config_parser *input) { size_t len = 0; if (input->buffer.length == 0) - errx(1, "%s:%d: unexpected end of input in section title", + errx(1, "gcli: %s:%d: unexpected end of input in section title", input->filename, input->line); @@ -321,10 +321,10 @@ parse_section_title(struct config_parser *input) skip_ws_and_comments(input); if (input->buffer.length == 0) - errx(1, "%s:%d: unexpected end of input", input->filename, input->line); + errx(1, "gcli: %s:%d: unexpected end of input", input->filename, input->line); if (input->buffer.data[0] != '{') - errx(1, "%s:%d: expected '{'", input->filename, input->line); + errx(1, "gcli: %s:%d: expected '{'", input->filename, input->line); input->buffer.length -= 1; input->buffer.data += 1; @@ -354,7 +354,7 @@ parse_config_section(struct gcli_config *cfg, } if (input->buffer.length == 0) - errx(1, "%s:%d: missing '}' before end of file", + errx(1, "gcli: %s:%d: missing '}' before end of file", input->filename, input->line); input->buffer.length -= 1; @@ -378,7 +378,7 @@ parse_config_file(struct gcli_config *cfg, * return 0. Otherwise return -1. */ static struct gcli_config * -ensure_config(gcli_ctx *ctx) +ensure_config(struct gcli_ctx *ctx) { struct gcli_config *cfg = ctx_config(ctx); char *file_path = NULL; @@ -405,13 +405,13 @@ ensure_config(gcli_ctx *ctx) } if (access(file_path, R_OK) < 0) { - warn("Cannot access config file at %s", file_path); + warn("gcli: cannot access config file at %s", file_path); return cfg; } int len = sn_mmap_file(file_path, &cfg->mmap_pointer); if (len < 0) - err(1, "Unable to open config file"); + err(1, "gcli: unable to open config file"); cfg->buffer = sn_sv_from_parts(cfg->mmap_pointer, len); cfg->buffer = sn_sv_trim_front(cfg->buffer); @@ -490,7 +490,7 @@ gcli_config_init_ctx(struct gcli_ctx *ctx) } int -gcli_config_parse_args(gcli_ctx *ctx, int *argc, char ***argv) +gcli_config_parse_args(struct gcli_ctx *ctx, int *argc, char ***argv) { /* These are the very first options passed to the gcli command * itself. It is the first ever getopt call we do to parse any @@ -562,8 +562,10 @@ gcli_config_parse_args(gcli_ctx *ctx, int *argc, char ***argv) cfg->override_forgetype = GCLI_FORGE_GITLAB; } else if (strcmp(optarg, "gitea") == 0) { cfg->override_forgetype = GCLI_FORGE_GITEA; + } else if (strcmp(optarg, "bugzilla") == 0) { + cfg->override_forgetype = GCLI_FORGE_BUGZILLA; } else { - fprintf(stderr, "error: unknown forge type '%s'. " + fprintf(stderr, "gcli: error: unknown forge type '%s'. " "Have either github, gitlab or gitea.\n", optarg); return EXIT_FAILURE; } @@ -604,7 +606,7 @@ find_section(struct gcli_config *cfg, char const *name) } struct gcli_config_entries const * -gcli_config_get_section_entries(gcli_ctx *ctx, char const *section_name) +gcli_config_get_section_entries(struct gcli_ctx *ctx, char const *section_name) { struct gcli_config_section const *s; struct gcli_config *cfg; @@ -619,7 +621,8 @@ gcli_config_get_section_entries(gcli_ctx *ctx, char const *section_name) } sn_sv -gcli_config_find_by_key(gcli_ctx *ctx, char const *section_name, char const *key) +gcli_config_find_by_key(struct gcli_ctx *ctx, char const *section_name, + char const *key) { struct gcli_config_entry *entry; struct gcli_config *cfg = ensure_config(ctx); @@ -628,7 +631,7 @@ gcli_config_find_by_key(gcli_ctx *ctx, char const *section_name, char const *key find_section(cfg, section_name); if (!section) { - warnx("no config section with name '%s'", section_name); + warnx("gcli: no config section with name '%s'", section_name); return SV_NULL; } @@ -641,7 +644,7 @@ gcli_config_find_by_key(gcli_ctx *ctx, char const *section_name, char const *key } static sn_sv -gcli_local_config_find_by_key(gcli_ctx *ctx, char const *const key) +gcli_local_config_find_by_key(struct gcli_ctx *ctx, char const *const key) { struct gcli_dotgcli *lcfg = ctx_dotgcli(ctx); struct gcli_config_entry *entry; @@ -655,7 +658,7 @@ gcli_local_config_find_by_key(gcli_ctx *ctx, char const *const key) } char * -gcli_config_get_editor(gcli_ctx *ctx) +gcli_config_get_editor(struct gcli_ctx *ctx) { ensure_config(ctx); @@ -664,12 +667,13 @@ gcli_config_get_editor(gcli_ctx *ctx) static char const *const default_account_entry_names[] = { - [GCLI_FORGE_GITHUB] = "github-default-account", - [GCLI_FORGE_GITLAB] = "gitlab-default-account", - [GCLI_FORGE_GITEA] = "gitea-default-account", }; + [GCLI_FORGE_GITHUB] = "github-default-account", + [GCLI_FORGE_GITLAB] = "gitlab-default-account", + [GCLI_FORGE_GITEA] = "gitea-default-account", + [GCLI_FORGE_BUGZILLA] = "bugzilla-default-account",}; static char * -get_default_account(gcli_ctx *ctx, gcli_forge_type ftype) +get_default_account(struct gcli_ctx *ctx, gcli_forge_type ftype) { char const *const defaultname = default_account_entry_names[ftype]; sn_sv act = gcli_config_find_by_key(ctx, "defaults", defaultname); @@ -681,7 +685,7 @@ get_default_account(gcli_ctx *ctx, gcli_forge_type ftype) } static char * -gcli_config_get_account(gcli_ctx *ctx) +gcli_config_get_account(struct gcli_ctx *ctx) { struct gcli_config *cfg = ctx_config(ctx); gcli_forge_type ftype = gcli_config_get_forge_type(ctx); @@ -697,13 +701,14 @@ gcli_config_get_account(gcli_ctx *ctx) } static char const *const default_urls[] = { - [GCLI_FORGE_GITHUB] = "https://api.github.com", - [GCLI_FORGE_GITLAB] = "https://gitlab.com/api/v4", - [GCLI_FORGE_GITEA] = "https://codeberg.org/api/v1", + [GCLI_FORGE_GITHUB] = "https://api.github.com", + [GCLI_FORGE_GITLAB] = "https://gitlab.com/api/v4", + [GCLI_FORGE_GITEA] = "https://codeberg.org/api/v1", + [GCLI_FORGE_BUGZILLA] = "https://bugs.freebsd.org/bugzilla", }; char * -gcli_config_get_apibase(gcli_ctx *ctx) +gcli_config_get_apibase(struct gcli_ctx *ctx) { char *acct = gcli_config_get_account(ctx); char *url = NULL; @@ -724,7 +729,7 @@ gcli_config_get_apibase(gcli_ctx *ctx) } char * -gcli_config_get_account_name(gcli_ctx *ctx) +gcli_config_get_account_name(struct gcli_ctx *ctx) { char *account = gcli_config_get_account(ctx); sn_sv actname = gcli_config_find_by_key( @@ -736,7 +741,7 @@ gcli_config_get_account_name(gcli_ctx *ctx) } static char * -get_account_token(gcli_ctx *ctx) +get_account_token(struct gcli_ctx *ctx) { char *account; sn_sv token; @@ -753,7 +758,7 @@ get_account_token(gcli_ctx *ctx) } char * -gcli_config_get_token(gcli_ctx *ctx) +gcli_config_get_token(struct gcli_ctx *ctx) { ensure_config(ctx); @@ -761,7 +766,7 @@ gcli_config_get_token(gcli_ctx *ctx) } sn_sv -gcli_config_get_upstream(gcli_ctx *ctx) +gcli_config_get_upstream(struct gcli_ctx *ctx) { init_local_config(ctx); @@ -769,7 +774,7 @@ gcli_config_get_upstream(gcli_ctx *ctx) } bool -gcli_config_pr_inhibit_delete_source_branch(gcli_ctx *ctx) +gcli_config_pr_inhibit_delete_source_branch(struct gcli_ctx *ctx) { sn_sv val; @@ -781,7 +786,7 @@ gcli_config_pr_inhibit_delete_source_branch(gcli_ctx *ctx) } void -gcli_config_get_upstream_parts(gcli_ctx *ctx, sn_sv *const owner, +gcli_config_get_upstream_parts(struct gcli_ctx *ctx, sn_sv *const owner, sn_sv *const repo) { ensure_config(ctx); @@ -791,7 +796,7 @@ gcli_config_get_upstream_parts(gcli_ctx *ctx, sn_sv *const owner, /* Sanity check: did we actually reach the '/'? */ if (*upstream.data != '/') - errx(1, ".gcli has invalid upstream format. expected owner/repo"); + errx(1, "gcli: .gcli has invalid upstream format. expected owner/repo"); upstream.data += 1; upstream.length -= 1; @@ -799,7 +804,7 @@ gcli_config_get_upstream_parts(gcli_ctx *ctx, sn_sv *const owner, } sn_sv -gcli_config_get_base(gcli_ctx *ctx) +gcli_config_get_base(struct gcli_ctx *ctx) { init_local_config(ctx); @@ -807,7 +812,7 @@ gcli_config_get_base(gcli_ctx *ctx) } sn_sv -gcli_config_get_override_default_account(gcli_ctx *ctx) +gcli_config_get_override_default_account(struct gcli_ctx *ctx) { struct gcli_config *cfg; @@ -821,7 +826,7 @@ gcli_config_get_override_default_account(gcli_ctx *ctx) } static gcli_forge_type -gcli_config_get_forge_type_internal(gcli_ctx *ctx) +gcli_config_get_forge_type_internal(struct gcli_ctx *ctx) { struct gcli_config *cfg = ctx_config(ctx); @@ -839,7 +844,7 @@ gcli_config_get_forge_type_internal(gcli_ctx *ctx) entry = gcli_config_find_by_key(ctx, section, "forge-type"); if (sn_sv_null(entry)) errx(1, - "error: given default override account not found or " + "gcli: error: given default override account not found or " "missing forge-type"); } else { entry = gcli_local_config_find_by_key(ctx, "forge-type"); @@ -852,21 +857,23 @@ gcli_config_get_forge_type_internal(gcli_ctx *ctx) return GCLI_FORGE_GITLAB; else if (sn_sv_eq_to(entry, "gitea")) return GCLI_FORGE_GITEA; + else if (sn_sv_eq_to(entry, "bugzilla")) + return GCLI_FORGE_BUGZILLA; else - errx(1, "Unknown forge type "SV_FMT, SV_ARGS(entry)); + errx(1, "gcli: unknown forge type "SV_FMT, SV_ARGS(entry)); } /* As a last resort, try to infer from the git remote */ int const type = gcli_gitconfig_get_forgetype(ctx, cfg->override_remote); if (type < 0) - errx(1, "error: cannot infer forge type. " + errx(1, "gcli: error: cannot infer forge type. " "use -t to overrride manually."); return type; } gcli_forge_type -gcli_config_get_forge_type(gcli_ctx *ctx) +gcli_config_get_forge_type(struct gcli_ctx *ctx) { gcli_forge_type const result = gcli_config_get_forge_type_internal(ctx); @@ -876,12 +883,13 @@ gcli_config_get_forge_type(gcli_ctx *ctx) static char const *const ftype_name[] = { [GCLI_FORGE_GITHUB] = "GitHub", [GCLI_FORGE_GITLAB] = "Gitlab", - [GCLI_FORGE_GITEA] = "Gitea", - }; + [GCLI_FORGE_GITEA] = "Gitea", + [GCLI_FORGE_BUGZILLA] = "Bugzilla", + }; if (!have_printed_forge_type) { have_printed_forge_type = 1; - fprintf(stderr, "info: forge type is %s\n", ftype_name[result]); + fprintf(stderr, "gcli: info: forge type is %s\n", ftype_name[result]); } } @@ -889,7 +897,7 @@ gcli_config_get_forge_type(gcli_ctx *ctx) } void -gcli_config_get_repo(gcli_ctx *ctx, char const **const owner, +gcli_config_get_repo(struct gcli_ctx *ctx, char const **const owner, char const **const repo) { sn_sv upstream = {0}; @@ -903,7 +911,7 @@ gcli_config_get_repo(gcli_ctx *ctx, char const **const owner, if (forge >= 0) { if ((int)(gcli_config_get_forge_type(ctx)) != forge) - errx(1, "error: forge types are inconsistent"); + errx(1, "gcli: error: forge types are inconsistent"); } return; @@ -925,7 +933,7 @@ gcli_config_get_repo(gcli_ctx *ctx, char const **const owner, } int -gcli_config_have_colours(gcli_ctx *ctx) +gcli_config_have_colours(struct gcli_ctx *ctx) { static int tested_tty = 0; struct gcli_config *cfg; diff --git a/src/cmd/colour.c b/src/cmd/colour.c index ca84d628..71f00e93 100644 --- a/src/cmd/colour.c +++ b/src/cmd/colour.c @@ -152,19 +152,23 @@ gcli_state_colour_str(char const *it) static const struct { char const *name; int code; } state_colour_table[] = { - { .name = "open", .code = GCLI_COLOR_GREEN }, - { .name = "active", .code = GCLI_COLOR_GREEN }, - { .name = "success", .code = GCLI_COLOR_GREEN }, - { .name = "APPROVED", .code = GCLI_COLOR_GREEN }, - { .name = "merged", .code = GCLI_COLOR_MAGENTA }, - { .name = "closed", .code = GCLI_COLOR_RED }, - { .name = "failed", .code = GCLI_COLOR_RED }, - { .name = "canceled", .code = GCLI_COLOR_RED }, /* orthography has left the channel */ - { .name = "failure", .code = GCLI_COLOR_RED }, - { .name = "running", .code = GCLI_COLOR_BLUE }, - { .name = "created", .code = GCLI_COLOR_BLUE }, - { .name = "COMMENTED", .code = GCLI_COLOR_BLUE }, - { .name = "pending", .code = GCLI_COLOR_CYAN }, + { .name = "open", .code = GCLI_COLOR_GREEN }, + { .name = "Open", .code = GCLI_COLOR_GREEN }, + { .name = "active", .code = GCLI_COLOR_GREEN }, + { .name = "success", .code = GCLI_COLOR_GREEN }, + { .name = "APPROVED", .code = GCLI_COLOR_GREEN }, + { .name = "merged", .code = GCLI_COLOR_MAGENTA }, + { .name = "closed", .code = GCLI_COLOR_RED }, + { .name = "Closed", .code = GCLI_COLOR_RED }, + { .name = "failed", .code = GCLI_COLOR_RED }, + { .name = "canceled", .code = GCLI_COLOR_RED }, /* orthography has left the channel */ + { .name = "failure", .code = GCLI_COLOR_RED }, + { .name = "running", .code = GCLI_COLOR_BLUE }, + { .name = "created", .code = GCLI_COLOR_BLUE }, + { .name = "New", .code = GCLI_COLOR_BLUE }, + { .name = "COMMENTED", .code = GCLI_COLOR_BLUE }, + { .name = "pending", .code = GCLI_COLOR_CYAN }, + { .name = "In Progress", .code = GCLI_COLOR_CYAN }, }; char const * diff --git a/src/cmd/comment.c b/src/cmd/comment.c index 244d3c46..be72e765 100644 --- a/src/cmd/comment.c +++ b/src/cmd/comment.c @@ -38,6 +38,7 @@ #include #include +#include #ifdef HAVE_GETOPT_H #include #endif @@ -57,9 +58,9 @@ usage(void) } static void -comment_init(gcli_ctx *ctx, FILE *f, void *_data) +comment_init(struct gcli_ctx *ctx, FILE *f, void *_data) { - gcli_submit_comment_opts *info = _data; + struct gcli_submit_comment_opts *info = _data; const char *target_type = NULL; switch (info->target_type) { @@ -75,6 +76,10 @@ comment_init(gcli_ctx *ctx, FILE *f, void *_data) case GCLI_FORGE_GITLAB: target_type = "Merge Request"; break; + case GCLI_FORGE_BUGZILLA: + /* FIXME think about this one */ + assert(0 && "unreachable"); + break; } } break; } @@ -84,27 +89,32 @@ comment_init(gcli_ctx *ctx, FILE *f, void *_data) "! Enter your comment above, save and exit.\n" "! All lines with a leading '!' are discarded and will not\n" "! appear in your comment.\n" - "! COMMENT IN : %s/%s %s #%d\n", + "! COMMENT IN : %s/%s %s #%"PRIid"\n", info->owner, info->repo, target_type, info->target_id); } -static sn_sv -gcli_comment_get_message(gcli_submit_comment_opts *info) +static char * +gcli_comment_get_message(struct gcli_submit_comment_opts *info) { return gcli_editor_get_user_message(g_clictx, comment_init, info); } static int -comment_submit(gcli_submit_comment_opts opts, int always_yes) +comment_submit(struct gcli_submit_comment_opts opts, int always_yes) { - sn_sv const message = gcli_comment_get_message(&opts); - opts.message = gcli_json_escape(message); int rc = 0; + char *message; + + message = gcli_comment_get_message(&opts); + opts.message = message; + + if (message == NULL) + errx(1, "gcli: empty message. aborting."); fprintf( stdout, - "You will be commenting the following in %s/%s #%d:\n"SV_FMT"\n", - opts.owner, opts.repo, opts.target_id, SV_ARGS(message)); + "You will be commenting the following in %s/%s #%"PRIid":\n%s\n", + opts.owner, opts.repo, opts.target_id, opts.message); if (!always_yes) { if (!sn_yesno("Is this okay?")) @@ -113,8 +123,8 @@ comment_submit(gcli_submit_comment_opts opts, int always_yes) rc = gcli_comment_submit(g_clictx, opts); - free(message.data); - free(opts.message.data); + free(message); + opts.message = NULL; return rc; } @@ -122,7 +132,7 @@ comment_submit(gcli_submit_comment_opts opts, int always_yes) int gcli_issue_comments(char const *owner, char const *repo, int const issue) { - gcli_comment_list list = {0}; + struct gcli_comment_list list = {0}; int rc = 0; rc = gcli_get_issue_comments(g_clictx, owner, repo, issue, &list); @@ -138,7 +148,7 @@ gcli_issue_comments(char const *owner, char const *repo, int const issue) int gcli_pull_comments(char const *owner, char const *repo, int const pull) { - gcli_comment_list list = {0}; + struct gcli_comment_list list = {0}; int rc = 0; rc = gcli_get_pull_comments(g_clictx, owner, repo, pull, &list); @@ -152,7 +162,7 @@ gcli_pull_comments(char const *owner, char const *repo, int const pull) } void -gcli_print_comment_list(gcli_comment_list const *const list) +gcli_print_comment_list(struct gcli_comment_list const *const list) { for (size_t i = 0; i < list->comments_size; ++i) { printf("AUTHOR : %s%s%s\n" @@ -213,7 +223,7 @@ subcommand_comment(int argc, char *argv[]) char *endptr; target_id = strtoul(optarg, &endptr, 10); if (endptr != optarg + strlen(optarg)) - err(1, "error: Cannot parse issue/PR number"); + err(1, "gcli: error: Cannot parse issue/PR number"); } break; case 'y': always_yes = true; @@ -230,12 +240,12 @@ subcommand_comment(int argc, char *argv[]) check_owner_and_repo(&owner, &repo); if (target_id < 0) { - fprintf(stderr, "error: missing issue/PR number (use -i/-p)\n"); + fprintf(stderr, "gcli: error: missing issue/PR number (use -i/-p)\n"); usage(); return EXIT_FAILURE; } - rc = comment_submit((gcli_submit_comment_opts) { + rc = comment_submit((struct gcli_submit_comment_opts) { .owner = owner, .repo = repo, .target_type = target_type, @@ -243,7 +253,7 @@ subcommand_comment(int argc, char *argv[]) always_yes); if (rc < 0) - errx(1, "error: failed to submit comment: %s", gcli_get_error(g_clictx)); + errx(1, "gcli: error: failed to submit comment: %s", gcli_get_error(g_clictx)); return EXIT_SUCCESS; } diff --git a/src/cmd/config.c b/src/cmd/config.c index f4bcd852..cd2d49c2 100644 --- a/src/cmd/config.c +++ b/src/cmd/config.c @@ -59,10 +59,10 @@ usage(void) } void -gcli_sshkeys_print_keys(gcli_sshkey_list const *list) +gcli_sshkeys_print_keys(struct gcli_sshkey_list const *list) { gcli_tbl *tbl; - gcli_tblcoldef cols[] = { + struct gcli_tblcoldef cols[] = { { .name = "ID", .type = GCLI_TBLCOLTYPE_ID, .flags = 0 }, { .name = "CREATED", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, { .name = "TITLE", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, @@ -86,10 +86,10 @@ gcli_sshkeys_print_keys(gcli_sshkey_list const *list) static int list_sshkeys(void) { - gcli_sshkey_list list = {0}; + struct gcli_sshkey_list list = {0}; if (gcli_sshkeys_get_keys(g_clictx, &list) < 0) { - fprintf(stderr, "error: could not get list of SSH keys\n"); + fprintf(stderr, "gcli: error: could not get list of SSH keys\n"); return EXIT_FAILURE; } @@ -120,7 +120,7 @@ add_sshkey(int argc, char *argv[]) keypath = optarg; if (access(keypath, R_OK) < 0) { - fprintf(stderr, "error: cannot access %s: %s\n", + fprintf(stderr, "gcli: error: cannot access %s: %s\n", keypath, strerror(errno)); return EXIT_FAILURE; } @@ -133,13 +133,13 @@ add_sshkey(int argc, char *argv[]) } if (title == NULL) { - fprintf(stderr, "error: missing title\n"); + fprintf(stderr, "gcli: error: missing title\n"); usage(); return EXIT_FAILURE; } if (keypath == NULL) { - fprintf(stderr, "error: missing public key path\n"); + fprintf(stderr, "gcli: error: missing public key path\n"); usage(); return EXIT_FAILURE; } @@ -160,7 +160,7 @@ delete_sshkey(int argc, char *argv[]) --argc; ++argv; if (argc != 1) { - fprintf(stderr, "error: incorrect number of arguments\n"); + fprintf(stderr, "gcli: error: incorrect number of arguments\n"); usage(); return EXIT_FAILURE; } @@ -169,7 +169,7 @@ delete_sshkey(int argc, char *argv[]) id = strtol(argv[0], &endptr, 10); if (endptr != argv[0] + strlen(argv[0])) { - fprintf(stderr, "error: could not parse ID of SSH key to delete\n"); + fprintf(stderr, "gcli: error: could not parse ID of SSH key to delete\n"); return EXIT_FAILURE; } @@ -194,7 +194,7 @@ subcommand_ssh(int argc, char *argv[]) else if (strcmp(cmdname, "delete") == 0) return delete_sshkey(argc, argv); - fprintf(stderr, "error: unrecognised subcommand ยป%sยซ.\n", cmdname); + fprintf(stderr, "gcli: error: unrecognised subcommand ยป%sยซ.\n", cmdname); usage(); return EXIT_FAILURE; } @@ -229,7 +229,7 @@ subcommand_config(int argc, char *argv[]) /* Check if the user gave us at least one option for this * subcommand */ if (argc == 0) { - fprintf(stderr, "error: missing subcommand for config\n"); + fprintf(stderr, "gcli: error: missing subcommand for config\n"); usage(); return EXIT_FAILURE; } @@ -239,7 +239,7 @@ subcommand_config(int argc, char *argv[]) return subcommands[i].fn(argc, argv); } - fprintf(stderr, "error: unrecognised config subcommand ยป%sยซ\n", + fprintf(stderr, "gcli: error: unrecognised config subcommand ยป%sยซ\n", argv[0]); usage(); diff --git a/src/cmd/editor.c b/src/cmd/editor.c index 244359e7..34771169 100644 --- a/src/cmd/editor.c +++ b/src/cmd/editor.c @@ -41,17 +41,19 @@ static sn_sv sv_append(sn_sv this, sn_sv const that) { - this.data = realloc(this.data, this.length + that.length); + /* Allocate one byte more as we're going to manually zero-terminate the result + * down in get_message */ + this.data = realloc(this.data, this.length + that.length + 1); memcpy(this.data + this.length, that.data, that.length); this.length += that.length; return this; } -sn_sv +char * gcli_editor_get_user_message( - gcli_ctx *ctx, - void (*file_initializer)(gcli_ctx *, FILE *, void *), + struct gcli_ctx *ctx, + void (*file_initializer)(struct gcli_ctx *, FILE *, void *), void *user_data) { char *editor = getenv("EDITOR"); @@ -64,8 +66,8 @@ gcli_editor_get_user_message( "file or set the EDITOR environment variable."); } - char filename[31] = "/tmp/gcli_message.XXXXXXX\0"; - int fd = mkstemp(filename); + char filename[31] = "/tmp/gcli_message.XXXXXXX\0"; + int fd = mkstemp(filename); FILE *file = fdopen(fd, "w"); file_initializer(ctx, file, user_data); @@ -119,5 +121,10 @@ gcli_editor_get_user_message( munmap(file_content, len); unlink(filename); - return result; + /* When the input is empty, the data pointer is going to be NULL. + * Do not access it in this case. */ + if (result.length) + result.data[result.length] = '\0'; + + return result.data; } diff --git a/src/cmd/forks.c b/src/cmd/forks.c index 64d5d596..a3cc6d58 100644 --- a/src/cmd/forks.c +++ b/src/cmd/forks.c @@ -63,11 +63,11 @@ usage(void) void gcli_print_forks(enum gcli_output_flags const flags, - gcli_fork_list const *const list, int const max) + struct gcli_fork_list const *const list, int const max) { size_t n; gcli_tbl table; - gcli_tblcoldef cols[] = { + struct gcli_tblcoldef cols[] = { { .name = "OWNER", .type = GCLI_TBLCOLTYPE_SV, .flags = GCLI_TBLCOL_BOLD }, { .name = "DATE", .type = GCLI_TBLCOLTYPE_SV, .flags = 0 }, { .name = "FORKS", .type = GCLI_TBLCOLTYPE_INT, .flags = GCLI_TBLCOL_JUSTIFYR }, @@ -87,7 +87,7 @@ gcli_print_forks(enum gcli_output_flags const flags, table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!table) - errx(1, "error: could not initialize table"); + errx(1, "gcli: error: could not initialize table"); if (flags & OUTPUT_SORTED) { for (size_t i = 0; i < n; ++i) { @@ -164,7 +164,7 @@ subcommand_forks_create(int argc, char *argv[]) check_owner_and_repo(&owner, &repo); if (gcli_fork_create(g_clictx, owner, repo, in) < 0) - errx(1, "error: failed to fork repository: %s", gcli_get_error(g_clictx)); + errx(1, "gcli: error: failed to fork repository: %s", gcli_get_error(g_clictx)); if (!always_yes) { if (!sn_yesno("Do you want to add a remote for the fork?")) @@ -173,7 +173,7 @@ subcommand_forks_create(int argc, char *argv[]) if (!in) { if ((in = gcli_config_get_account_name(g_clictx)) == NULL) { - errx(1, "error: could not fetch account: %s", + errx(1, "gcli: error: could not fetch account: %s", gcli_get_error(g_clictx)); } } @@ -186,7 +186,7 @@ subcommand_forks_create(int argc, char *argv[]) int subcommand_forks(int argc, char *argv[]) { - gcli_fork_list forks = {0}; + struct gcli_fork_list forks = {0}; char const *owner = NULL, *repo = NULL; int ch = 0; int count = 30; @@ -239,10 +239,10 @@ subcommand_forks(int argc, char *argv[]) count = strtol(optarg, &endptr, 10); if (endptr != (optarg + strlen(optarg))) - err(1, "forks: unable to parse forks count argument"); + err(1, "gcli: error: unable to parse forks count argument"); if (count == 0) - errx(1, "error: forks count must not be zero"); + errx(1, "gcli: error: forks count must not be zero"); } break; case 's': flags |= OUTPUT_SORTED; @@ -261,7 +261,7 @@ subcommand_forks(int argc, char *argv[]) if (argc == 0) { if (gcli_get_forks(g_clictx, owner, repo, count, &forks) < 0) - errx(1, "error: could not get forks: %s", gcli_get_error(g_clictx)); + errx(1, "gcli: error: could not get forks: %s", gcli_get_error(g_clictx)); gcli_print_forks(flags, &forks, count); gcli_forks_free(&forks); @@ -275,7 +275,7 @@ subcommand_forks(int argc, char *argv[]) if (strcmp(action, "delete") == 0) { delete_repo(always_yes, owner, repo); } else { - fprintf(stderr, "error: forks: unknown action '%s'\n", action); + fprintf(stderr, "gcli: error: forks: unknown action '%s'\n", action); } } diff --git a/src/cmd/gcli.c b/src/cmd/gcli.c index e542df28..deee76f0 100644 --- a/src/cmd/gcli.c +++ b/src/cmd/gcli.c @@ -38,6 +38,7 @@ #include #include +#include #include #include #include @@ -63,7 +64,7 @@ subcommand_version(int argc, char *argv[]) (void) argc; (void) argv; - version(); + longversion(); copyright(); return EXIT_SUCCESS; @@ -116,6 +117,9 @@ static struct subcommand { { .cmd_name = "status", .fn = subcommand_status, .docstring = "General user status and notifications" }, + { .cmd_name = "attachments", + .fn = subcommand_attachments, + .docstring = "Bugzilla Attachments management" }, { .cmd_name = "api", .fn = subcommand_api, .docstring = "Fetch plain JSON info from an API (for debugging purposes)" }, @@ -138,6 +142,7 @@ usage(void) fprintf(stderr, " - github (default: github.com)\n"); fprintf(stderr, " - gitlab (default: gitlab.com)\n"); fprintf(stderr, " - gitea (default: codeberg.org)\n"); + fprintf(stderr, " - bugzilla (default: bugs.freebsd.org)\n"); fprintf(stderr, " -c Force colour and text formatting.\n"); fprintf(stderr, " -q Be quiet. (Not implemented yet)\n\n"); fprintf(stderr, " -v Be verbose.\n\n"); @@ -154,7 +159,7 @@ usage(void) } /** The CMD global gcli context */ -gcli_ctx *g_clictx = NULL; +struct gcli_ctx *g_clictx = NULL; static void gcli_progress_func(bool const done) @@ -216,7 +221,7 @@ is_unique_match(size_t const idx, char const *const name, size_t const name_len) break; /* we found a duplicate prefix. */ } - fprintf(stderr, "error: %s: subcommand is ambiguous. could be one of:\n", name); + fprintf(stderr, "gcli: error: %s: subcommand is ambiguous. could be one of:\n", name); /* List until either the end or until we don't match any more prefixes */ for (size_t i = idx; i < subcommands_size; ++i) { if (strncmp(name, subcommands[i].cmd_name, name_len)) @@ -256,7 +261,7 @@ find_subcommand(char const *const name, int *error) } /* no match */ - fprintf(stderr, "error: %s: no such subcommand\n", name); + fprintf(stderr, "gcli: error: %s: no such subcommand\n", name); if (error) *error = LOOKUP_NOSUCHCMD; @@ -273,7 +278,7 @@ add_subcommand_alias(char const *alias_name, char const *alias_for) old_sc = find_subcommand(alias_for, NULL); if (old_sc == NULL) { - fprintf(stderr, "note: this error occured while defining the alias ยป%sยซ\n", + fprintf(stderr, "gcli: note: this error occured while defining the alias ยป%sยซ\n", alias_name); exit(EXIT_FAILURE); } @@ -333,10 +338,10 @@ main(int argc, char *argv[]) errmsg = gcli_init(&g_clictx, gcli_config_get_forge_type, gcli_config_get_token, gcli_config_get_apibase); if (errmsg) - errx(1, "error: %s", errmsg); + errx(1, "gcli: error: %s", errmsg); if (gcli_config_init_ctx(g_clictx) < 0) - errx(1, "error: failed to init context: %s", gcli_get_error(g_clictx)); + errx(1, "gcli: error: failed to init context: %s", gcli_get_error(g_clictx)); gcli_set_progress_func(g_clictx, gcli_progress_func); @@ -357,7 +362,7 @@ main(int argc, char *argv[]) /* Make sure we have a subcommand */ if (argc == 0) { - fprintf(stderr, "error: missing subcommand\n"); + fprintf(stderr, "gcli: error: missing subcommand\n"); usage(); return EXIT_FAILURE; } diff --git a/src/cmd/gists.c b/src/cmd/gists.c index ac1c5df5..f5dfe33a 100644 --- a/src/cmd/gists.c +++ b/src/cmd/gists.c @@ -89,31 +89,31 @@ language_fmt(char const *it) } static void -print_gist_file(gcli_gist_file const *const file) +print_gist_file(struct gcli_gist_file const *const file) { printf(" โ€ข %-15.15s %-8.8s %-s\n", - language_fmt(file->language.data), + language_fmt(file->language), human_readable_size(file->size), - file->filename.data); + file->filename); } static void -print_gist(enum gcli_output_flags const flags, gcli_gist const *const gist) +print_gist(enum gcli_output_flags const flags, struct gcli_gist const *const gist) { (void) flags; - printf(" ID : %s"SV_FMT"%s\n" - "OWNER : %s"SV_FMT"%s\n" - "DESCR : "SV_FMT"\n" - " DATE : "SV_FMT"\n" - " URL : "SV_FMT"\n" - " PULL : "SV_FMT"\n", - gcli_setcolour(GCLI_COLOR_YELLOW), SV_ARGS(gist->id), gcli_resetcolour(), - gcli_setbold(), SV_ARGS(gist->owner), gcli_resetbold(), - SV_ARGS(gist->description), - SV_ARGS(gist->date), - SV_ARGS(gist->url), - SV_ARGS(gist->git_pull_url)); + printf(" ID : %s%s%s\n" + "OWNER : %s%s%s\n" + "DESCR : %s\n" + " DATE : %s\n" + " URL : %s\n" + " PULL : %s\n", + gcli_setcolour(GCLI_COLOR_YELLOW), gist->id, gcli_resetcolour(), + gcli_setbold(), gist->owner, gcli_resetbold(), + gist->description, + gist->date, + gist->url, + gist->git_pull_url); printf("FILES : %-15.15s %-8.8s %-s\n", "LANGUAGE", "SIZE", "FILENAME"); @@ -125,7 +125,7 @@ print_gist(enum gcli_output_flags const flags, gcli_gist const *const gist) static void gcli_print_gists_long(enum gcli_output_flags const flags, - gcli_gist_list const *const list, int const max) + struct gcli_gist_list const *const list, int const max) { size_t n; @@ -145,16 +145,16 @@ gcli_print_gists_long(enum gcli_output_flags const flags, static void gcli_print_gists_short(enum gcli_output_flags const flags, - gcli_gist_list const *const list, int const max) + struct gcli_gist_list const *const list, int const max) { size_t n; gcli_tbl table; - gcli_tblcoldef cols[] = { - { .name = "ID", .type = GCLI_TBLCOLTYPE_SV, .flags = GCLI_TBLCOL_COLOUREXPL }, - { .name = "OWNER", .type = GCLI_TBLCOLTYPE_SV, .flags = GCLI_TBLCOL_BOLD }, - { .name = "DATE", .type = GCLI_TBLCOLTYPE_SV, .flags = 0 }, - { .name = "FILES", .type = GCLI_TBLCOLTYPE_INT, .flags = GCLI_TBLCOL_JUSTIFYR }, - { .name = "DESCRIPTION", .type = GCLI_TBLCOLTYPE_SV, .flags = 0 }, + struct gcli_tblcoldef cols[] = { + { .name = "ID", .type = GCLI_TBLCOLTYPE_STRING, .flags = GCLI_TBLCOL_COLOUREXPL }, + { .name = "OWNER", .type = GCLI_TBLCOLTYPE_STRING, .flags = GCLI_TBLCOL_BOLD }, + { .name = "DATE", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, + { .name = "FILES", .type = GCLI_TBLCOLTYPE_INT, .flags = GCLI_TBLCOL_JUSTIFYR }, + { .name = "DESCRIPTION", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, }; if (max < 0 || (size_t)(max) > list->gists_size) @@ -164,7 +164,7 @@ gcli_print_gists_short(enum gcli_output_flags const flags, table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!table) - errx(1, "error: could not init table"); + errx(1, "gcli: error: could not init table"); if (flags & OUTPUT_SORTED) { for (size_t i = 0; i < n; ++i) { @@ -191,7 +191,7 @@ gcli_print_gists_short(enum gcli_output_flags const flags, void gcli_print_gists(enum gcli_output_flags const flags, - gcli_gist_list const *const list, int const max) + struct gcli_gist_list const *const list, int const max) { if (list->gists_size == 0) { puts("No Gists"); @@ -211,28 +211,28 @@ subcommand_gist_get(int argc, char *argv[]) char const *gist_id = shift(&argc, &argv); char const *file_name = shift(&argc, &argv); - gcli_gist gist = {0}; - gcli_gist_file *file = NULL; + struct gcli_gist gist = {0}; + struct gcli_gist_file *file = NULL; if (gcli_get_gist(g_clictx, gist_id, &gist) < 0) - errx(1, "error: failed to get gist: %s", gcli_get_error(g_clictx)); + errx(1, "gcli: error: failed to get gist: %s", gcli_get_error(g_clictx)); for (size_t f = 0; f < gist.files_size; ++f) { - if (sn_sv_eq_to(gist.files[f].filename, file_name)) { + if (strcmp(gist.files[f].filename, file_name) == 0) { file = &gist.files[f]; break; } } if (!file) - errx(1, "error: gists get: %s: no such file in gist with id %s", + errx(1, "gcli: error: %s: no such file in gist with id %s", file_name, gist_id); if (isatty(STDOUT_FILENO) && (file->size >= 4 * 1024 * 1024)) - errx(1, "error: File is bigger than 4 MiB, refusing to print to stdout."); + errx(1, "gcli: error: File is bigger than 4 MiB, refusing to print to stdout."); - if (gcli_curl(g_clictx, stdout, file->url.data, file->type.data) < 0) - errx(1, "error: failed to fetch gist: %s", gcli_get_error(g_clictx)); + if (gcli_curl(g_clictx, stdout, file->url, file->type) < 0) + errx(1, "gcli: error: failed to fetch gist: %s", gcli_get_error(g_clictx)); gcli_gist_free(&gist); @@ -242,9 +242,9 @@ subcommand_gist_get(int argc, char *argv[]) static int subcommand_gist_create(int argc, char *argv[]) { - int ch; - gcli_new_gist opts = {0}; - char const *file = NULL; + char const *file = NULL; + int ch; + struct gcli_new_gist opts = {0}; struct option const options[] = { { .name = "file", @@ -277,7 +277,7 @@ subcommand_gist_create(int argc, char *argv[]) argv += optind; if (argc != 1) { - fprintf(stderr, "error: gists create: missing file name for gist\n"); + fprintf(stderr, "gcli: error: missing file name for gist\n"); usage(); return EXIT_FAILURE; } @@ -286,7 +286,7 @@ subcommand_gist_create(int argc, char *argv[]) if (file) { if ((opts.file = fopen(file, "r")) == NULL) - err(1, "error: gists create: cannot open file"); + err(1, "gcli: error: cannot open file"); } else { opts.file = stdin; } @@ -295,7 +295,7 @@ subcommand_gist_create(int argc, char *argv[]) opts.gist_description = "gcli paste"; if (gcli_create_gist(g_clictx, opts) < 0) - errx(1, "error: failed to create gist: %s", gcli_get_error(g_clictx)); + errx(1, "gcli: error: failed to create gist: %s", gcli_get_error(g_clictx)); return EXIT_SUCCESS; } @@ -303,9 +303,9 @@ subcommand_gist_create(int argc, char *argv[]) static int subcommand_gist_delete(int argc, char *argv[]) { - int ch; - bool always_yes = false; - char const *gist_id = NULL; + bool always_yes = false; + char const *gist_id = NULL; + int ch; struct option const options[] = { { .name = "yes", @@ -333,7 +333,7 @@ subcommand_gist_delete(int argc, char *argv[]) gist_id = shift(&argc, &argv); if (!always_yes && !sn_yesno("Are you sure you want to delete this gist?")) - errx(1, "Aborted by user"); + errx(1, "gcli: Aborted by user"); gcli_delete_gist(g_clictx, gist_id); @@ -352,15 +352,15 @@ static struct { int subcommand_gists(int argc, char *argv[]) { - int ch; - char const *user = NULL; - gcli_gist_list gists = {0}; - int count = 30; - enum gcli_output_flags flags = 0; + char const *user = NULL; + enum gcli_output_flags flags = 0; + int ch; + int count = 30; + struct gcli_gist_list gists = {0}; /* Make sure we are looking at a GitHub forge */ if (gcli_config_get_forge_type(g_clictx) != GCLI_FORGE_GITHUB) { - errx(1, "error: The gists subcommand only works for Github " + errx(1, "gcli: error: The gists subcommand only works for Github " "forges. Please use either -a or -t to force using a " "Github account."); } @@ -421,7 +421,7 @@ subcommand_gists(int argc, char *argv[]) argv += optind; if (gcli_get_gists(g_clictx, user, count, &gists) < 0) - errx(1, "error: failed to get gists: %s", gcli_get_error(g_clictx)); + errx(1, "gcli: error: failed to get gists: %s", gcli_get_error(g_clictx)); gcli_print_gists(flags, &gists, count); gcli_gists_free(&gists); diff --git a/src/cmd/gitconfig.c b/src/cmd/gitconfig.c index d83fb747..f851f0f4 100644 --- a/src/cmd/gitconfig.c +++ b/src/cmd/gitconfig.c @@ -44,7 +44,7 @@ #include #define MAX_REMOTES 64 -static gcli_gitremote remotes[MAX_REMOTES]; +static struct gcli_gitremote remotes[MAX_REMOTES]; static size_t remotes_size; /* Resolve a worktree .git if needed */ @@ -56,7 +56,7 @@ resolve_worktree_gitdir_if_needed(char *dotgit) char *newdir = NULL; if (stat(dotgit, &sb) < 0) - err(1, "stat"); + err(1, "gcli: stat"); /* Real .git directory */ if (S_ISDIR(sb.st_mode)) @@ -64,7 +64,7 @@ resolve_worktree_gitdir_if_needed(char *dotgit) f = fopen(dotgit, "r"); if (!f) - err(1, "fopen"); + err(1, "gcli: fopen"); while (!ferror(f) && !feof(f)) { char *key, *value; @@ -95,7 +95,7 @@ resolve_worktree_gitdir_if_needed(char *dotgit) } if (newdir == NULL) - errx(1, "error: .git is a file but does not contain a gitdir pointer"); + errx(1, "gcli: error: .git is a file but does not contain a gitdir pointer"); fclose(f); @@ -118,7 +118,7 @@ find_file_in_dotgit(char const *fname) curr_dir_path = getcwd(NULL, 128); if (!curr_dir_path) - err(1, "getcwd"); + err(1, "gcli: getcwd"); /* Here we are trying to traverse upwards through the directory * tree, searching for a directory called .git. @@ -126,7 +126,7 @@ find_file_in_dotgit(char const *fname) do { curr_dir = opendir(curr_dir_path); if (!curr_dir) - err(1, "opendir"); + err(1, "gcli: opendir"); /* Read entries of the directory */ while ((ent = readdir(curr_dir))) { @@ -162,7 +162,7 @@ find_file_in_dotgit(char const *fname) curr_dir_path = realpath(tmp, NULL); if (!curr_dir_path) - err(1, "error: realpath at %s", tmp); + err(1, "gcli: error: realpath at %s", tmp); free(tmp); @@ -189,7 +189,7 @@ find_file_in_dotgit(char const *fname) /* Now search for the file in the found .git directory */ curr_dir = opendir(dotgit); if (!curr_dir) - err(1, "opendir"); + err(1, "gcli: opendir"); while ((ent = readdir(curr_dir))) { /* skip over . and .. directory entries */ @@ -215,7 +215,7 @@ find_file_in_dotgit(char const *fname) } } - errx(1, "error: .git without a config file"); + errx(1, "gcli: error: .git without a config file"); return NULL; } @@ -240,7 +240,7 @@ gcli_gitconfig_get_current_branch(void) int len = sn_mmap_file(HEAD, &mmap_pointer); if (len < 0) - err(1, "mmap"); + err(1, "gcli: mmap"); buffer = sn_sv_from_parts(mmap_pointer, len); @@ -256,7 +256,7 @@ gcli_gitconfig_get_current_branch(void) } static void -http_extractor(gcli_gitremote *const remote, char const *prefix) +http_extractor(struct gcli_gitremote *const remote, char const *prefix) { size_t prefix_size = strlen(prefix); sn_sv pair = remote->url; @@ -289,7 +289,7 @@ http_extractor(gcli_gitremote *const remote, char const *prefix) } static void -ssh_extractor(gcli_gitremote *const remote, char const *prefix) +ssh_extractor(struct gcli_gitremote *const remote, char const *prefix) { size_t prefix_size = strlen(prefix); @@ -320,7 +320,7 @@ ssh_extractor(gcli_gitremote *const remote, char const *prefix) struct forge_ex_def { char const *prefix; - void (*extractor)(gcli_gitremote *const, char const *); + void (*extractor)(struct gcli_gitremote *const, char const *); } url_extractors[] = { { .prefix = "git@", .extractor = ssh_extractor }, { .prefix = "ssh://", .extractor = ssh_extractor }, @@ -350,9 +350,9 @@ gitconfig_parse_remote(sn_sv section_title, sn_sv entry) while ((entry = sn_sv_trim_front(entry)).length > 0) { if (sn_sv_has_prefix(entry, "url")) { if (remotes_size == MAX_REMOTES) - errx(1, "error: too many remotes"); + errx(1, "gcli: error: too many remotes"); - gcli_gitremote *const remote = &remotes[remotes_size++]; + struct gcli_gitremote *const remote = &remotes[remotes_size++]; remote->name = remote_name; @@ -406,7 +406,7 @@ gcli_gitconfig_read_gitconfig(void) /* TODO: Git Config files support comments */ if (*buffer.data != '[') - errx(1, "error: invalid git config"); + errx(1, "gcli: error: invalid git config"); sn_sv section_title = sn_sv_chop_until(&buffer, ']'); section_title.length -= 1; @@ -432,7 +432,7 @@ gcli_gitconfig_add_fork_remote(char const *org, char const *repo) FILE *remote_list = popen("git remote", "r"); if (!remote_list) - err(1, "popen"); + err(1, "gcli: popen"); /* TODO: Output informational messages */ /* Rename possibly existing origin remote to point at the @@ -450,9 +450,9 @@ gcli_gitconfig_add_fork_remote(char const *org, char const *repo) waitpid(pid, &status, 0); if (!(WIFEXITED(status) && (WEXITSTATUS(status) == 0))) - errx(1, "git child process failed"); + errx(1, "gcli: git child process failed"); } else { - err(1, "fork"); + err(1, "gcli: fork"); } break; @@ -475,9 +475,9 @@ gcli_gitconfig_add_fork_remote(char const *org, char const *repo) waitpid(pid, &status, 0); if (!(WIFEXITED(status) && (WEXITSTATUS(status) == 0))) - errx(1, "git child process failed"); + errx(1, "gcli: git child process failed"); } else { - err(1, "fork"); + err(1, "gcli: fork"); } } } @@ -486,7 +486,7 @@ gcli_gitconfig_add_fork_remote(char const *org, char const *repo) * Return the gcli_forge_type for the given remote or -1 if * unknown */ int -gcli_gitconfig_get_forgetype(gcli_ctx *ctx, char const *const remote_name) +gcli_gitconfig_get_forgetype(struct gcli_ctx *ctx, char const *const remote_name) { (void) ctx; gcli_gitconfig_read_gitconfig(); @@ -523,11 +523,11 @@ gcli_gitconfig_repo_by_remote( } } - errx(1, "error: no such remote: %s", remote_name); + errx(1, "gcli: error: no such remote: %s", remote_name); } if (!remotes_size) - errx(1, "error: no remotes to auto-detect forge"); + errx(1, "gcli: error: no remotes to auto-detect forge"); *owner = sn_sv_to_cstr(remotes[0].owner); *repo = sn_sv_to_cstr(remotes[0].repo); diff --git a/src/cmd/issues.c b/src/cmd/issues.c index b4d4c0f2..83203bfc 100644 --- a/src/cmd/issues.c +++ b/src/cmd/issues.c @@ -36,8 +36,10 @@ #include #include +#include #include +#include #include #ifdef HAVE_GETOPT_H @@ -49,7 +51,7 @@ usage(void) { fprintf(stderr, "usage: gcli issues create [-o owner -r repo] [-y] title...\n"); fprintf(stderr, " gcli issues [-o owner -r repo] [-a] [-n number] [-A author] [-L label]\n"); - fprintf(stderr, " [-M milestone] [-s]\n"); + fprintf(stderr, " [-M milestone] [-s] [search query...]\n"); fprintf(stderr, " gcli issues [-o owner -r repo] -i issue actions...\n"); fprintf(stderr, "OPTIONS:\n"); fprintf(stderr, " -o owner The repository owner\n"); @@ -84,15 +86,15 @@ usage(void) void gcli_print_issues(enum gcli_output_flags const flags, - gcli_issue_list const *const list, int const max) + struct gcli_issue_list const *const list, int const max) { int n, pruned = 0; gcli_tbl table; - gcli_tblcoldef cols[] = { - { .name = "NUMBER", .type = GCLI_TBLCOLTYPE_ID, .flags = GCLI_TBLCOL_JUSTIFYR }, - { .name = "NOTES", .type = GCLI_TBLCOLTYPE_INT, .flags = GCLI_TBLCOL_JUSTIFYR }, - { .name = "STATE", .type = GCLI_TBLCOLTYPE_SV, .flags = GCLI_TBLCOL_STATECOLOURED }, - { .name = "TITLE", .type = GCLI_TBLCOLTYPE_SV, .flags = 0 }, + struct gcli_tblcoldef cols[] = { + { .name = "NUMBER", .type = GCLI_TBLCOLTYPE_ID, .flags = GCLI_TBLCOL_JUSTIFYR }, + { .name = "NOTES", .type = GCLI_TBLCOLTYPE_INT, .flags = GCLI_TBLCOL_JUSTIFYR }, + { .name = "STATE", .type = GCLI_TBLCOLTYPE_STRING, .flags = GCLI_TBLCOL_STATECOLOURED }, + { .name = "TITLE", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, }; if (list->issues_size == 0) { @@ -102,7 +104,7 @@ gcli_print_issues(enum gcli_output_flags const flags, table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!table) - errx(1, "could not init table printer"); + errx(1, "gcli: could not init table printer"); /* Determine the correct number of items to print */ if (max < 0 || (size_t)(max) > list->issues_size) @@ -146,34 +148,52 @@ gcli_print_issues(enum gcli_output_flags const flags, } void -gcli_issue_print_summary(gcli_issue const *const it) +gcli_issue_print_summary(struct gcli_issue const *const it) { gcli_dict dict; + uint32_t const quirks = gcli_forge(g_clictx)->issue_quirks; dict = gcli_dict_begin(); gcli_dict_add(dict, "NUMBER", 0, 0, "%"PRIid, it->number); - gcli_dict_add(dict, "TITLE", 0, 0, SV_FMT, SV_ARGS(it->title)); - gcli_dict_add(dict, "CREATED", 0, 0, SV_FMT, SV_ARGS(it->created_at)); + gcli_dict_add(dict, "TITLE", 0, 0, "%s", it->title); + + gcli_dict_add(dict, "CREATED", 0, 0, "%s", it->created_at); + + if ((quirks & GCLI_ISSUE_QUIRKS_PROD_COMP) == 0) { + gcli_dict_add(dict, "PRODUCT", 0, 0, "%s", it->product); + gcli_dict_add(dict, "COMPONENT", 0, 0, "%s", it->component); + } + gcli_dict_add(dict, "AUTHOR", GCLI_TBLCOL_BOLD, 0, - SV_FMT, SV_ARGS(it->author)); + "%s", it->author); gcli_dict_add(dict, "STATE", GCLI_TBLCOL_STATECOLOURED, 0, - SV_FMT, SV_ARGS(it->state)); - gcli_dict_add(dict, "COMMENTS", 0, 0, "%d", it->comments); - gcli_dict_add(dict, "LOCKED", 0, 0, "%s", sn_bool_yesno(it->locked)); + "%s", it->state); + + if ((quirks & GCLI_ISSUE_QUIRKS_URL) == 0 && !sn_strempty(it->url)) + gcli_dict_add(dict, "URL", 0, 0, "%s", it->url); - if (it->milestone.length) - gcli_dict_add(dict, "MILESTONE", 0, 0, SV_FMT, SV_ARGS(it->milestone)); + if ((quirks & GCLI_ISSUE_QUIRKS_COMMENTS) == 0) + gcli_dict_add(dict, "COMMENTS", 0, 0, "%d", it->comments); + + if ((quirks & GCLI_ISSUE_QUIRKS_LOCKED) == 0) + gcli_dict_add(dict, "LOCKED", 0, 0, "%s", sn_bool_yesno(it->locked)); + + if (!sn_strempty(it->milestone)) + gcli_dict_add(dict, "MILESTONE", 0, 0, "%s", it->milestone); if (it->labels_size) { - gcli_dict_add_sv_list(dict, "LABELS", it->labels, it->labels_size); + gcli_dict_add_string_list(dict, "LABELS", + (char const *const *)it->labels, + it->labels_size); } else { gcli_dict_add(dict, "LABELS", 0, 0, "none"); } if (it->assignees_size) { - gcli_dict_add_sv_list(dict, "ASSIGNEES", - it->assignees, it->assignees_size); + gcli_dict_add_string_list(dict, "ASSIGNEES", + (char const *const *)it->assignees, + it->assignees_size); } else { gcli_dict_add(dict, "ASSIGNEES", 0, 0, "none"); } @@ -184,33 +204,33 @@ gcli_issue_print_summary(gcli_issue const *const it) } void -gcli_issue_print_op(gcli_issue const *const it) +gcli_issue_print_op(struct gcli_issue const *const it) { - if (it->body.length && it->body.data) - pretty_print(it->body.data, 4, 80, stdout); + if (it->body) + pretty_print(it->body, 4, 80, stdout); } static void -issue_init_user_file(gcli_ctx *ctx, FILE *stream, void *_opts) +issue_init_user_file(struct gcli_ctx *ctx, FILE *stream, void *_opts) { (void) ctx; - gcli_submit_issue_options *opts = _opts; + struct gcli_submit_issue_options *opts = _opts; fprintf( stream, - "! ISSUE TITLE : "SV_FMT"\n" + "! ISSUE TITLE : %s\n" "! Enter issue description above.\n" "! All lines starting with '!' will be discarded.\n", - SV_ARGS(opts->title)); + opts->title); } -static sn_sv -gcli_issue_get_user_message(gcli_submit_issue_options *opts) +static char * +gcli_issue_get_user_message(struct gcli_submit_issue_options *opts) { return gcli_editor_get_user_message(g_clictx, issue_init_user_file, opts); } static int -create_issue(gcli_submit_issue_options opts, int always_yes) +create_issue(struct gcli_submit_issue_options opts, int always_yes) { int rc; @@ -218,36 +238,62 @@ create_issue(gcli_submit_issue_options opts, int always_yes) printf("The following issue will be created:\n" "\n" - "TITLE : "SV_FMT"\n" + "TITLE : %s\n" "OWNER : %s\n" "REPO : %s\n" - "MESSAGE :\n"SV_FMT"\n", - SV_ARGS(opts.title), - opts.owner, opts.repo, - SV_ARGS(opts.body)); + "MESSAGE :\n%s\n", + opts.title, opts.owner, opts.repo, + opts.body ? opts.body : "No message"); putchar('\n'); if (!always_yes) { if (!sn_yesno("Do you want to continue?")) - errx(1, "Submission aborted."); + errx(1, "gcli: Submission aborted."); } rc = gcli_issue_submit(g_clictx, opts); - free(opts.body.data); - free(opts.body.data); + free(opts.body); + free(opts.body); return rc; } +static int +parse_submit_issue_option(struct gcli_submit_issue_options *opts) +{ + char *hd = strdup(optarg); + char *key = hd; + char *value = NULL; + + hd = strchr(hd, '='); + if (hd == NULL || *hd != '=') { + fprintf(stderr, "gcli: -O expects a key-value-pair as key=value\n"); + return -1; + } + + *hd++ = '\0'; + value = strdup(hd); /* make key and value separate allocations */ + + gcli_nvlist_append(&opts->extra, key, value); + + return 0; +} + static int subcommand_issue_create(int argc, char *argv[]) { int ch; - gcli_submit_issue_options opts = {0}; + struct gcli_submit_issue_options opts = {0}; int always_yes = 0; + if (gcli_nvlist_init(&opts.extra) < 0) { + fprintf(stderr, "gcli: failed to init nvlist: %s\n", + strerror(errno)); + return EXIT_FAILURE; + } + const struct option options[] = { { .name = "owner", .has_arg = required_argument, @@ -264,7 +310,7 @@ subcommand_issue_create(int argc, char *argv[]) {0}, }; - while ((ch = getopt_long(argc, argv, "o:r:", options, NULL)) != -1) { + while ((ch = getopt_long(argc, argv, "o:r:O:", options, NULL)) != -1) { switch (ch) { case 'o': opts.owner = optarg; @@ -275,6 +321,11 @@ subcommand_issue_create(int argc, char *argv[]) case 'y': always_yes = 1; break; + case 'O': { + int rc = parse_submit_issue_option(&opts); + if (rc < 0) + return EXIT_FAILURE; + } break; default: usage(); return EXIT_FAILURE; @@ -287,15 +338,18 @@ subcommand_issue_create(int argc, char *argv[]) check_owner_and_repo(&opts.owner, &opts.repo); if (argc != 1) { - fprintf(stderr, "error: Expected one argument for issue title\n"); + fprintf(stderr, "gcli: error: Expected one argument for issue title\n"); usage(); return EXIT_FAILURE; } - opts.title = SV(argv[0]); + opts.title = argv[0]; if (create_issue(opts, always_yes) < 0) - errx(1, "error: failed to submit issue: %s", gcli_get_error(g_clictx)); + errx(1, "gcli: error: failed to submit issue: %s", + gcli_get_error(g_clictx)); + + gcli_nvlist_free(&opts.extra); return EXIT_SUCCESS; } @@ -308,12 +362,12 @@ static inline int handle_issues_actions(int argc, char *argv[], int subcommand_issues(int argc, char *argv[]) { - gcli_issue_list list = {0}; + struct gcli_issue_list list = {0}; char const *owner = NULL; char const *repo = NULL; char *endptr = NULL; int ch = 0, issue_id = -1, n = 30; - gcli_issue_fetch_details details = {0}; + struct gcli_issue_fetch_details details = {0}; enum gcli_output_flags flags = 0; /* detect whether we wanna create an issue */ @@ -376,21 +430,21 @@ subcommand_issues(int argc, char *argv[]) case 'i': { issue_id = strtol(optarg, &endptr, 10); if (endptr != (optarg + strlen(optarg))) - err(1, "error: cannot parse issue number"); + err(1, "gcli: error: cannot parse issue number"); if (issue_id < 0) - errx(1, "error: issue number is out of range"); + errx(1, "gcli: error: issue number is out of range"); } break; case 'n': { n = strtol(optarg, &endptr, 10); if (endptr != (optarg + strlen(optarg))) - err(1, "error: cannot parse issue count"); + err(1, "gcli: error: cannot parse issue count"); if (n < -1) - errx(1, "error: issue count is out of range"); + errx(1, "gcli: error: issue count is out of range"); if (n == 0) - errx(1, "error: issue count must not be zero"); + errx(1, "gcli: error: issue count must not be zero"); } break; case 'a': details.all = true; @@ -417,13 +471,16 @@ subcommand_issues(int argc, char *argv[]) argc -= optind; argv += optind; - check_owner_and_repo(&owner, &repo); /* No issue number was given, so list all open issues */ if (issue_id < 0) { - if (gcli_get_issues(g_clictx, owner, repo, &details, n, &list) < 0) - errx(1, "error: could not get issues: %s", gcli_get_error(g_clictx)); + /* Prepare search term if specified */ + if (argc) + details.search_term = sn_join_with((char const *const *)argv, argc, " "); + + if (gcli_issues_search(g_clictx, owner, repo, &details, n, &list) < 0) + errx(1, "gcli: error: could not get issues: %s", gcli_get_error(g_clictx)); gcli_print_issues(flags, &list, n); @@ -433,7 +490,8 @@ subcommand_issues(int argc, char *argv[]) /* require -a to not be set */ if (details.all) { - fprintf(stderr, "error: -a cannot be combined with operations on an issue\n"); + fprintf(stderr, "gcli: error: -a cannot be combined with operations on " + "an issue\n"); usage(); return EXIT_FAILURE; } @@ -445,13 +503,13 @@ subcommand_issues(int argc, char *argv[]) static inline void ensure_issue(char const *const owner, char const *const repo, int const issue_id, - int *const have_fetched_issue, gcli_issue *const issue) + int *const have_fetched_issue, struct gcli_issue *const issue) { if (*have_fetched_issue) return; if (gcli_get_issue(g_clictx, owner, repo, issue_id, issue) < 0) - errx(1, "error: failed to retrieve issue data: %s", + errx(1, "gcli: error: failed to retrieve issue data: %s", gcli_get_error(g_clictx)); *have_fetched_issue = 1; @@ -470,7 +528,7 @@ handle_issue_labels_action(int *argc, char ***argv, int rc = 0; if (argc == 0) { - fprintf(stderr, "error: expected label operations\n"); + fprintf(stderr, "gcli: error: expected label operations\n"); usage(); exit(EXIT_FAILURE); } @@ -484,7 +542,7 @@ handle_issue_labels_action(int *argc, char ***argv, add_labels, add_labels_size); if (rc < 0) { - errx(1, "error: failed to add labels: %s", + errx(1, "gcli: error: failed to add labels: %s", gcli_get_error(g_clictx)); } } @@ -494,7 +552,7 @@ handle_issue_labels_action(int *argc, char ***argv, remove_labels, remove_labels_size); if (rc < 0) { - errx(1, "error: failed to remove labels: %s", + errx(1, "gcli: error: failed to remove labels: %s", gcli_get_error(g_clictx)); } } @@ -517,7 +575,7 @@ handle_issue_milestone_action(int *argc, char ***argv, * * Check that the user provided a milestone id */ if (!argc) { - fprintf(stderr, "error: missing milestone id\n"); + fprintf(stderr, "gcli: error: missing milestone id\n"); usage(); exit(EXIT_FAILURE); } @@ -530,7 +588,7 @@ handle_issue_milestone_action(int *argc, char ***argv, if (strcmp(milestone_str, "-d") == 0) { rc = gcli_issue_clear_milestone(g_clictx, owner, repo, issue_id); if (rc < 0) { - errx(1, "error: could not clear milestone of issue #%d: %s", + errx(1, "gcli: error: could not clear milestone of issue #%d: %s", issue_id, gcli_get_error(g_clictx)); } return; @@ -541,17 +599,41 @@ handle_issue_milestone_action(int *argc, char ***argv, /* Check successful for parse */ if (endptr != milestone_str + strlen(milestone_str)) { - fprintf(stderr, "error: could not parse milestone id\n"); + fprintf(stderr, "gcli: error: could not parse milestone id\n"); usage(); exit(EXIT_FAILURE); } /* Pass it to the dispatch */ if (gcli_issue_set_milestone(g_clictx, owner, repo, issue_id, milestone) < 0) - errx(1, "error: could not assign milestone: %s", + errx(1, "gcli: error: could not assign milestone: %s", gcli_get_error(g_clictx)); } +static void +gcli_print_attachments(struct gcli_attachment_list const *const list) +{ + gcli_tbl tbl; + struct gcli_tblcoldef columns[] = { + { .name = "ID", .type = GCLI_TBLCOLTYPE_ID, .flags = GCLI_TBLCOL_JUSTIFYR }, + { .name = "AUTHOR", .type = GCLI_TBLCOLTYPE_STRING, .flags = GCLI_TBLCOL_BOLD }, + { .name = "CREATED", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, + { .name = "CONTENT", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, + { .name = "OBSOLETE", .type = GCLI_TBLCOLTYPE_BOOL, .flags = 0 }, + { .name = "FILENAME", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, + }; + + tbl = gcli_tbl_begin(columns, ARRAY_SIZE(columns)); + + for (size_t i = 0; i < list->attachments_size; ++i) { + struct gcli_attachment const *const it = &list->attachments[i]; + gcli_tbl_add_row(tbl, it->id, it->author, it->created_at, + it->content_type, it->is_obsolete, it->file_name); + } + + gcli_tbl_end(tbl); +} + static inline int handle_issues_actions(int argc, char *argv[], char const *const owner, @@ -559,11 +641,11 @@ handle_issues_actions(int argc, char *argv[], int const issue_id) { int have_fetched_issue = 0; - gcli_issue issue = {0}; + struct gcli_issue issue = {0}; /* Check if the user missed out on supplying actions */ if (argc == 0) { - fprintf(stderr, "error: no actions supplied\n"); + fprintf(stderr, "gcli: error: no actions supplied\n"); usage(); exit(EXIT_FAILURE); } @@ -585,7 +667,7 @@ handle_issues_actions(int argc, char *argv[], strcmp("notes", operation) == 0) { /* Doesn't require fetching the issue data */ if (gcli_issue_comments(owner, repo, issue_id) < 0) - errx(1, "error: failed to fetch issue comments: %s", + errx(1, "gcli: error: failed to fetch issue comments: %s", gcli_get_error(g_clictx)); } else if (strcmp("op", operation) == 0) { @@ -603,20 +685,20 @@ handle_issues_actions(int argc, char *argv[], } else if (strcmp("close", operation) == 0) { if (gcli_issue_close(g_clictx, owner, repo, issue_id) < 0) - errx(1, "error: failed to close issue: %s", + errx(1, "gcli: error: failed to close issue: %s", gcli_get_error(g_clictx)); } else if (strcmp("reopen", operation) == 0) { if (gcli_issue_reopen(g_clictx, owner, repo, issue_id) < 0) - errx(1, "error: failed to reopen issue: %s", + errx(1, "gcli: error: failed to reopen issue: %s", gcli_get_error(g_clictx)); } else if (strcmp("assign", operation) == 0) { char const *assignee = shift(&argc, &argv); if (gcli_issue_assign(g_clictx, owner, repo, issue_id, assignee) < 0) - errx(1, "error: failed to assign issue: %s", + errx(1, "gcli: error: failed to assign issue: %s", gcli_get_error(g_clictx)); } else if (strcmp("labels", operation) == 0) { @@ -636,12 +718,25 @@ handle_issues_actions(int argc, char *argv[], new_title); if (rc < 0) { - errx(1, "error: failed to set new issue title: %s", + errx(1, "gcli: error: failed to set new issue title: %s", gcli_get_error(g_clictx)); } + } else if (strcmp("attachments", operation) == 0) { + + struct gcli_attachment_list list = {0}; + int rc = gcli_issue_get_attachments(g_clictx, owner, repo, issue_id, + &list); + if (rc < 0) { + errx(1, "gcli: error: failed to fetch attachments: %s", + gcli_get_error(g_clictx)); + } + + gcli_print_attachments(&list); + gcli_attachments_free(&list); + } else { - fprintf(stderr, "error: unknown operation %s\n", operation); + fprintf(stderr, "gcli: error: unknown operation %s\n", operation); usage(); return EXIT_FAILURE; } diff --git a/src/cmd/labels.c b/src/cmd/labels.c index 047fb3d1..0827715f 100644 --- a/src/cmd/labels.c +++ b/src/cmd/labels.c @@ -62,11 +62,11 @@ usage(void) } void -gcli_labels_print(gcli_label_list const *const list, int const max) +gcli_labels_print(struct gcli_label_list const *const list, int const max) { size_t n; gcli_tbl table; - gcli_tblcoldef cols[] = { + struct gcli_tblcoldef cols[] = { { .name = "ID", .type = GCLI_TBLCOLTYPE_ID, .flags = GCLI_TBLCOL_JUSTIFYR }, { .name = "", .type = GCLI_TBLCOLTYPE_STRING, .flags = GCLI_TBLCOL_256COLOUR|GCLI_TBLCOL_TIGHT }, { .name = "NAME", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, @@ -82,7 +82,7 @@ gcli_labels_print(gcli_label_list const *const list, int const max) /* Fill table */ table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!table) - errx(1, "error: could not init table"); + errx(1, "gcli: error: could not init table"); for (size_t i = 0; i < n; ++i) { gcli_tbl_add_row(table, @@ -99,7 +99,7 @@ gcli_labels_print(gcli_label_list const *const list, int const max) static int subcommand_labels_delete(int argc, char *argv[]) { - int ch, rc; + int ch, rc; char const *owner = NULL, *repo = NULL; const struct option options[] = { {.name = "repo", .has_arg = required_argument, .val = 'r'}, @@ -128,14 +128,14 @@ subcommand_labels_delete(int argc, char *argv[]) check_owner_and_repo(&owner, &repo); if (argc != 1) { - fprintf(stderr, "error: missing label to delete\n"); + fprintf(stderr, "gcli: error: missing label to delete\n"); usage(); return EXIT_FAILURE; } rc = gcli_delete_label(g_clictx, owner, repo, argv[0]); if (rc < 0) { - fprintf(stderr, "error: couldn't delete label\n"); + fprintf(stderr, "gcli: error: couldn't delete label\n"); return EXIT_FAILURE; } @@ -145,8 +145,8 @@ subcommand_labels_delete(int argc, char *argv[]) static int subcommand_labels_create(int argc, char *argv[]) { - gcli_label label = {0}; - gcli_label_list labels = { .labels = &label, .labels_size = 1 }; + struct gcli_label label = {0}; + struct gcli_label_list labels = { .labels = &label, .labels_size = 1 }; char const *owner = NULL, *repo = NULL; int ch; @@ -169,9 +169,12 @@ subcommand_labels_create(int argc, char *argv[]) break; case 'c': { char *endptr = NULL; + if (strlen(optarg) != 6) + err(1, "gcli: error: colour must be a six-digit hexadecimal colour code"); + label.colour = strtol(optarg, &endptr, 16); if (endptr != (optarg + strlen(optarg))) - err(1, "labels: cannot parse colour"); + err(1, "gcli: error: cannot parse colour"); } break; case 'd': { label.description = optarg; @@ -192,19 +195,21 @@ subcommand_labels_create(int argc, char *argv[]) check_owner_and_repo(&owner, &repo); if (!label.name) { - fprintf(stderr, "error: missing name for label\n"); + fprintf(stderr, "gcli: error: missing name for label\n"); usage(); return EXIT_FAILURE; } if (!label.description) { - fprintf(stderr, "error: missing description for label\n"); + fprintf(stderr, "gcli: error: missing description for label\n"); usage(); return EXIT_FAILURE; } - if (gcli_create_label(g_clictx, owner, repo, &label) < 0) - errx(1, "error: failed to create label: %s", gcli_get_error(g_clictx)); + if (gcli_create_label(g_clictx, owner, repo, &label) < 0) { + errx(1, "gcli: error: failed to create label: %s", + gcli_get_error(g_clictx)); + } /* only if we are not quieted */ if (!sn_quiet()) @@ -229,7 +234,7 @@ subcommand_labels(int argc, char *argv[]) int count = 30; int ch; char const *owner = NULL, *repo = NULL; - gcli_label_list labels = {0}; + struct gcli_label_list labels = {0}; const struct option options[] = { {.name = "repo", .has_arg = required_argument, .flag = NULL, .val = 'r'}, @@ -259,10 +264,10 @@ subcommand_labels(int argc, char *argv[]) count = strtol(optarg, &endptr, 10); if (endptr != (optarg + strlen(optarg))) - errx(1, "labels: cannot parse label count"); + errx(1, "gcli: error: cannot parse label count"); if (count == 0) - errx(1, "error: number of labels must not be zero"); + errx(1, "gcli: error: number of labels must not be zero"); } break; case '?': default: @@ -276,7 +281,7 @@ subcommand_labels(int argc, char *argv[]) /* sanity check: we must have parsed everything by now */ if (argc > 0) { - fprintf(stderr, "error: stray arguments\n"); + fprintf(stderr, "gcli: error: stray arguments\n"); usage(); return EXIT_FAILURE; } @@ -284,7 +289,7 @@ subcommand_labels(int argc, char *argv[]) check_owner_and_repo(&owner, &repo); if (gcli_get_labels(g_clictx, owner, repo, count, &labels) < 0) - errx(1, "error: could not fetch list of labels: %s", + errx(1, "gcli: error: could not fetch list of labels: %s", gcli_get_error(g_clictx)); gcli_labels_print(&labels, count); diff --git a/src/cmd/milestones.c b/src/cmd/milestones.c index ce2832b8..43d2a167 100644 --- a/src/cmd/milestones.c +++ b/src/cmd/milestones.c @@ -64,11 +64,11 @@ usage(void) } void -gcli_print_milestones(gcli_milestone_list const *const list, int max) +gcli_print_milestones(struct gcli_milestone_list const *const list, int max) { size_t n; gcli_tbl tbl; - gcli_tblcoldef cols[] = { + struct gcli_tblcoldef cols[] = { { .name = "ID", .type = GCLI_TBLCOLTYPE_ID, .flags = GCLI_TBLCOL_JUSTIFYR }, { .name = "STATE", .type = GCLI_TBLCOLTYPE_STRING, .flags = GCLI_TBLCOL_STATECOLOURED }, { .name = "CREATED", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, @@ -82,7 +82,7 @@ gcli_print_milestones(gcli_milestone_list const *const list, int max) tbl = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!tbl) - errx(1, "error: could not init table printer"); + errx(1, "gcli: error: could not init table printer"); if (max < 0 || (size_t)(max) > list->milestones_size) n = list->milestones_size; @@ -101,7 +101,7 @@ gcli_print_milestones(gcli_milestone_list const *const list, int max) } void -gcli_print_milestone(gcli_milestone const *const milestone) +gcli_print_milestone(struct gcli_milestone const *const milestone) { gcli_dict dict; uint32_t const quirks = gcli_forge(g_clictx)->milestone_quirks; @@ -188,18 +188,18 @@ subcommand_milestone_create(int argc, char *argv[]) /* sanity check argumets */ if (argc) - errx(1, "error: stray arguments"); + errx(1, "gcli: error: stray arguments"); /* make sure both are set or deduce them */ check_owner_and_repo(&args.owner, &args.repo); /* enforce the user to at least provide a title */ if (!args.title) - errx(1, "error: missing milestone title"); + errx(1, "gcli: error: missing milestone title"); /* actually create the milestone */ if (gcli_create_milestone(g_clictx, &args) < 0) - errx(1, "error: could not create milestone: %s", + errx(1, "gcli: error: could not create milestone: %s", gcli_get_error(g_clictx)); return 0; @@ -251,16 +251,16 @@ subcommand_milestones(int argc, char *argv[]) char *endptr; max = strtol(optarg, &endptr, 10); if (endptr != optarg + strlen(optarg)) - errx(1, "error: cannot parse milestone count"); + errx(1, "gcli: error: cannot parse milestone count"); } break; case 'i': { char *endptr; milestone_id = strtol(optarg, &endptr, 10); if (endptr != optarg + strlen(optarg)) - errx(1, "error: cannot parse milestone id"); + errx(1, "gcli: error: cannot parse milestone id"); if (milestone_id < 0) - errx(1, "error: milestone id must not be negative"); + errx(1, "gcli: error: milestone id must not be negative"); } break; default: { usage(); @@ -275,12 +275,13 @@ subcommand_milestones(int argc, char *argv[]) check_owner_and_repo(&owner, &repo); if (milestone_id < 0) { - gcli_milestone_list list = {0}; + struct gcli_milestone_list list = {0}; rc = gcli_get_milestones(g_clictx, owner, repo, max, &list); - if (rc < 0) - errx(1, "error: cannot get list of milestones: %s", + if (rc < 0) { + errx(1, "gcli: error: cannot get list of milestones: %s", gcli_get_error(g_clictx)); + } gcli_print_milestones(&list, max); @@ -297,7 +298,7 @@ ensure_milestone(char const *const owner, char const *const repo, int const milestone_id, int *const fetched_milestone, - gcli_milestone *const milestone) + struct gcli_milestone *const milestone) { int rc; @@ -306,7 +307,7 @@ ensure_milestone(char const *const owner, rc = gcli_get_milestone(g_clictx, owner, repo, milestone_id, milestone); if (rc < 0) - errx(1, "error: could not get milestone %d: %s", milestone_id, + errx(1, "gcli: error: could not get milestone %d: %s", milestone_id, gcli_get_error(g_clictx)); *fetched_milestone = 1; @@ -318,12 +319,12 @@ handle_milestone_actions(int argc, char *argv[], char const *const repo, int const milestone_id) { - gcli_milestone milestone = {0}; + struct gcli_milestone milestone = {0}; int fetched_milestone = 0; /* Check if the user missed out on supplying actions */ if (argc == 0) { - fprintf(stderr, "error: no actions supplied\n"); + fprintf(stderr, "gcli: error: no actions supplied\n"); usage(); exit(EXIT_FAILURE); } @@ -338,7 +339,7 @@ handle_milestone_actions(int argc, char *argv[], if (strcmp(action, "all") == 0) { int rc = 0; - gcli_issue_list issues = {0}; + struct gcli_issue_list issues = {0}; ensure_milestone(owner, repo, milestone_id, &fetched_milestone, &milestone); @@ -347,9 +348,10 @@ handle_milestone_actions(int argc, char *argv[], rc = gcli_milestone_get_issues(g_clictx, owner, repo, milestone_id, &issues); - if (rc < 0) - errx(1, "error: failed to fetch issues: %s", + if (rc < 0) { + errx(1, "gcli: error: failed to fetch issues: %s", gcli_get_error(g_clictx)); + } printf("\nISSUES:\n"); gcli_print_issues(0, &issues, -1); @@ -357,14 +359,15 @@ handle_milestone_actions(int argc, char *argv[], } else if (strcmp(action, "issues") == 0) { int rc = 0; - gcli_issue_list issues = {0}; + struct gcli_issue_list issues = {0}; /* Fetch list of issues associated with milestone */ rc = gcli_milestone_get_issues(g_clictx, owner, repo, milestone_id, &issues); - if (rc < 0) - errx(1, "error: failed to fetch issues: %s", + if (rc < 0) { + errx(1, "gcli: error: failed to fetch issues: %s", gcli_get_error(g_clictx)); + } /* Print them as a table */ gcli_print_issues(0, &issues, -1); @@ -383,9 +386,10 @@ handle_milestone_actions(int argc, char *argv[], } else if (strcmp(action, "delete") == 0) { /* Delete the milestone */ - if (gcli_delete_milestone(g_clictx, owner, repo, milestone_id) < 0) - errx(1, "error: could not delete milestone: %s", + if (gcli_delete_milestone(g_clictx, owner, repo, milestone_id) < 0) { + errx(1, "gcli: error: could not delete milestone: %s", gcli_get_error(g_clictx)); + } } else if (strcmp(action, "set-duedate") == 0) { @@ -394,22 +398,23 @@ handle_milestone_actions(int argc, char *argv[], /* grab the the date that the user provided */ if (!argc) - errx(1, "error: missing date for set-duedate"); + errx(1, "gcli: error: missing date for set-duedate"); new_date = shift(&argc, &argv); /* Do it! */ rc = gcli_milestone_set_duedate(g_clictx, owner, repo, milestone_id, new_date); - if (rc < 0) - errx(1, "error: could not update milestone due date: %s", + if (rc < 0) { + errx(1, "gcli: error: could not update milestone due date: %s", gcli_get_error(g_clictx));; + } } else { /* We don't know of the action - maybe a syntax error or * trailing garbage. Error out in this case. */ - fprintf(stderr, "error: unknown action %s\n", action); + fprintf(stderr, "gcli: error: unknown action %s\n", action); usage(); return EXIT_FAILURE; } diff --git a/src/cmd/pipelines.c b/src/cmd/pipelines.c index 92e2188a..ca6473f1 100644 --- a/src/cmd/pipelines.c +++ b/src/cmd/pipelines.c @@ -72,7 +72,7 @@ usage(void) int gitlab_mr_pipelines(char const *owner, char const *repo, int const mr_id) { - gitlab_pipeline_list list = {0}; + struct gitlab_pipeline_list list = {0}; int rc = 0; rc = gitlab_get_mr_pipelines(g_clictx, owner, repo, mr_id, &list); @@ -85,10 +85,10 @@ gitlab_mr_pipelines(char const *owner, char const *repo, int const mr_id) } void -gitlab_print_pipelines(gitlab_pipeline_list const *const list) +gitlab_print_pipelines(struct gitlab_pipeline_list const *const list) { gcli_tbl table; - gcli_tblcoldef cols[] = { + struct gcli_tblcoldef cols[] = { { .name = "ID", .type = GCLI_TBLCOLTYPE_ID, .flags = GCLI_TBLCOL_JUSTIFYR }, { .name = "STATUS", .type = GCLI_TBLCOLTYPE_STRING, .flags = GCLI_TBLCOL_STATECOLOURED }, { .name = "CREATED", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, @@ -103,7 +103,7 @@ gitlab_print_pipelines(gitlab_pipeline_list const *const list) table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!table) - errx(1, "error: could not init table"); + errx(1, "gcli: error: could not init table"); for (size_t i = 0; i < list->pipelines_size; ++i) { gcli_tbl_add_row(table, @@ -120,7 +120,7 @@ gitlab_print_pipelines(gitlab_pipeline_list const *const list) int gitlab_pipelines(char const *owner, char const *repo, int const count) { - gitlab_pipeline_list pipelines = {0}; + struct gitlab_pipeline_list pipelines = {0}; int rc = 0; rc = gitlab_get_pipelines(g_clictx, owner, repo, count, &pipelines); @@ -134,10 +134,10 @@ gitlab_pipelines(char const *owner, char const *repo, int const count) } void -gitlab_print_jobs(gitlab_job_list const *const list) +gitlab_print_jobs(struct gitlab_job_list const *const list) { gcli_tbl table; - gcli_tblcoldef cols[] = { + struct gcli_tblcoldef cols[] = { { .name = "ID", .type = GCLI_TBLCOLTYPE_ID, .flags = GCLI_TBLCOL_JUSTIFYR }, { .name = "NAME", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, { .name = "STATUS", .type = GCLI_TBLCOLTYPE_STRING, .flags = GCLI_TBLCOL_STATECOLOURED }, @@ -154,7 +154,7 @@ gitlab_print_jobs(gitlab_job_list const *const list) table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!table) - errx(1, "error: could not initialize table"); + errx(1, "gcli: error: could not initialize table"); for (size_t i = 0; i < list->jobs_size; ++i) { gcli_tbl_add_row(table, @@ -174,7 +174,7 @@ int gitlab_pipeline_jobs(char const *owner, char const *repo, long const id, int const count) { - gitlab_job_list jobs = {0}; + struct gitlab_job_list jobs = {0}; int rc = 0; rc = gitlab_get_pipeline_jobs(g_clictx, owner, repo, id, count, &jobs); @@ -188,7 +188,7 @@ gitlab_pipeline_jobs(char const *owner, char const *repo, } void -gitlab_print_job_status(gitlab_job const *const job) +gitlab_print_job_status(struct gitlab_job const *const job) { gcli_dict printer; @@ -213,7 +213,7 @@ gitlab_print_job_status(gitlab_job const *const job) int gitlab_job_status(char const *owner, char const *repo, long const jid) { - gitlab_job job = {0}; + struct gitlab_job job = {0}; int rc = 0; rc = gitlab_get_job(g_clictx, owner, repo, jid, &job); @@ -276,24 +276,24 @@ subcommand_pipelines(int argc, char *argv[]) char *endptr = NULL; count = strtol(optarg, &endptr, 10); if (endptr != (optarg + strlen(optarg))) - err(1, "ci: cannot parse argument to -n"); + err(1, "gcli: error: cannot parse argument to -n"); } break; case 'p': { char *endptr = NULL; pid = strtol(optarg, &endptr, 10); if (endptr != (optarg + strlen(optarg))) - err(1, "ci: cannot parse argument to -p"); + err(1, "gcli: error: cannot parse argument to -p"); if (pid < 0) { - errx(1, "error: pipeline id must be a positive number"); + errx(1, "gcli: error: pipeline id must be a positive number"); } } break; case 'j': { char *endptr = NULL; jid = strtol(optarg, &endptr, 10); if (endptr != (optarg + strlen(optarg))) - err(1, "ci: cannot parse argument to -j"); + err(1, "gcli: error: cannot parse argument to -j"); if (jid < 0) { - errx(1, "error: job id must be a positive number"); + errx(1, "gcli: error: job id must be a positive number"); } } break; case '?': @@ -307,7 +307,7 @@ subcommand_pipelines(int argc, char *argv[]) argv += optind; if (pid > 0 && jid > 0) { - fprintf(stderr, "error: -p and -j are mutually exclusive\n"); + fprintf(stderr, "gcli: error: -p and -j are mutually exclusive\n"); usage(); return EXIT_FAILURE; } @@ -317,7 +317,7 @@ subcommand_pipelines(int argc, char *argv[]) /* Make sure we are actually talking about a gitlab remote because * we might be incorrectly inferring it */ if (gcli_config_get_forge_type(g_clictx) != GCLI_FORGE_GITLAB) - errx(1, "error: The pipelines subcommand only works for GitLab. " + errx(1, "gcli: error: The pipelines subcommand only works for GitLab. " "Use gcli -t gitlab ... to force a GitLab remote."); /* If the user specified a pipeline id, print the jobs of that @@ -325,14 +325,15 @@ subcommand_pipelines(int argc, char *argv[]) if (pid >= 0) { /* Make sure we are interpreting things correctly */ if (argc != 0) { - fprintf(stderr, "error: stray arguments\n"); + fprintf(stderr, "gcli: error: stray arguments\n"); usage(); return EXIT_FAILURE; } - if (gitlab_pipeline_jobs(owner, repo, pid, count) < 0) - errx(1, "error: failed to get pipeline jobs: %s", + if (gitlab_pipeline_jobs(owner, repo, pid, count) < 0) { + errx(1, "gcli: error: failed to get pipeline jobs: %s", gcli_get_error(g_clictx)); + } return EXIT_SUCCESS; } @@ -341,14 +342,15 @@ subcommand_pipelines(int argc, char *argv[]) if (jid < 0) { /* Make sure we are interpreting things correctly */ if (argc != 0) { - fprintf(stderr, "error: stray arguments\n"); + fprintf(stderr, "gcli: error: stray arguments\n"); usage(); return EXIT_FAILURE; } - if (gitlab_pipelines(owner, repo, count) < 0) - errx(1, "error: failed to get pipelines: %s", + if (gitlab_pipelines(owner, repo, count) < 0) { + errx(1, "gcli: error: failed to get pipelines: %s", gcli_get_error(g_clictx)); + } return EXIT_SUCCESS; } @@ -368,7 +370,7 @@ subcommand_pipelines(int argc, char *argv[]) /* Check if the user missed out on supplying actions */ if (argc == 0) { - fprintf(stderr, "error: no actions supplied\n"); + fprintf(stderr, "gcli: error: no actions supplied\n"); usage(); exit(EXIT_FAILURE); } @@ -383,14 +385,15 @@ subcommand_pipelines(int argc, char *argv[]) char const *outfile = "artifacts.zip"; if (argc && strcmp(argv[0], "-o") == 0) { if (argc < 2) - errx(1, "error: -o is missing the output filename"); + errx(1, "gcli: error: -o is missing the output filename"); outfile = argv[1]; argc -= 2; argv += 2; } - if (gitlab_job_download_artifacts(g_clictx, owner, repo, jid, outfile) < 0) - errx(1, "error: failed to download file: %s", + if (gitlab_job_download_artifacts(g_clictx, owner, repo, jid, outfile) < 0) { + errx(1, "gcli: error: failed to download file: %s", gcli_get_error(g_clictx)); + } goto next_action; } @@ -398,12 +401,12 @@ subcommand_pipelines(int argc, char *argv[]) for (size_t i = 0; i < ARRAY_SIZE(job_actions); ++i) { if (strcmp(action, job_actions[i].name) == 0) { if (job_actions[i].fn(owner, repo, jid) < 0) - errx(1, "error: failed to perform action '%s'", action); + errx(1, "gcli: error: failed to perform action '%s'", action); goto next_action; } } - fprintf(stderr, "error: unknown action '%s'\n", action); + fprintf(stderr, "gcli: error: unknown action '%s'\n", action); usage(); return EXIT_FAILURE; } diff --git a/src/cmd/pulls.c b/src/cmd/pulls.c index bb1022f9..7344f479 100644 --- a/src/cmd/pulls.c +++ b/src/cmd/pulls.c @@ -56,14 +56,15 @@ static void usage(void) { fprintf(stderr, "usage: gcli pulls create [-o owner -r repo] [-f from]\n"); - fprintf(stderr, " [-t to] [-d] [-l label] pull-request-title\n"); + fprintf(stderr, " [-t to] [-d] [-a] [-l label] pull-request-title\n"); fprintf(stderr, " gcli pulls [-o owner -r repo] [-a] [-A author] [-n number]\n"); fprintf(stderr, " [-L label] [-M milestone] [-s]\n"); fprintf(stderr, " gcli pulls [-o owner -r repo] -i pull-id actions...\n"); fprintf(stderr, "OPTIONS:\n"); fprintf(stderr, " -o owner The repository owner\n"); fprintf(stderr, " -r repo The repository name\n"); - fprintf(stderr, " -a Fetch everything including closed and merged PRs\n"); + fprintf(stderr, " -a When listing PRs, show everything including closed and merged PRs.\n"); + fprintf(stderr, " When creating a PR enable automerge.\n"); fprintf(stderr, " -A author Filter pull requests by the given author\n"); fprintf(stderr, " -L label Filter pull requests by the given label\n"); fprintf(stderr, " -M milestone Filter pull requests by the given milestone\n"); @@ -103,11 +104,11 @@ usage(void) void gcli_print_pulls(enum gcli_output_flags const flags, - gcli_pull_list const *const list, int const max) + struct gcli_pull_list const *const list, int const max) { int n; gcli_tbl table; - gcli_tblcoldef cols[] = { + struct gcli_tblcoldef cols[] = { { .name = "NUMBER", .type = GCLI_TBLCOLTYPE_ID, .flags = GCLI_TBLCOL_JUSTIFYR }, { .name = "STATE", .type = GCLI_TBLCOLTYPE_STRING, .flags = GCLI_TBLCOL_STATECOLOURED }, { .name = "MERGED", .type = GCLI_TBLCOLTYPE_BOOL, .flags = 0 }, @@ -130,7 +131,7 @@ gcli_print_pulls(enum gcli_output_flags const flags, /* Fill the table */ table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!table) - errx(1, "error: cannot init table"); + errx(1, "gcli: error: cannot init table"); if (flags & OUTPUT_SORTED) { for (int i = 0; i < n; ++i) { @@ -172,10 +173,10 @@ gcli_pull_print_patch(FILE *stream, char const *owner, char const *reponame, } void -gcli_pull_print(gcli_pull const *const it) +gcli_pull_print(struct gcli_pull const *const it) { gcli_dict dict; - gcli_forge_descriptor const *const forge = gcli_forge(g_clictx); + struct gcli_forge_descriptor const *const forge = gcli_forge(g_clictx); int const quirks = forge->pull_summary_quirks; dict = gcli_dict_begin(); @@ -208,6 +209,9 @@ gcli_pull_print(gcli_pull const *const it) if ((quirks & GCLI_PRS_QUIRK_CHANGES) == 0) gcli_dict_add(dict, "CHANGED", 0, 0, "%d", it->changed_files); + if ((quirks & GCLI_PRS_QUIRK_AUTOMERGE) == 0) + gcli_dict_add_string(dict, "AUTOMERGE", 0, 0, sn_bool_yesno(it->automerge)); + if ((quirks & GCLI_PRS_QUIRK_MERGED) == 0) gcli_dict_add_string(dict, "MERGED", 0, 0, sn_bool_yesno(it->merged)); @@ -219,7 +223,9 @@ gcli_pull_print(gcli_pull const *const it) gcli_dict_add_string(dict, "COVERAGE", 0, 0, it->coverage); if (it->labels_size) { - gcli_dict_add_sv_list(dict, "LABELS", it->labels, it->labels_size); + gcli_dict_add_string_list(dict, "LABELS", + (char const *const *)it->labels, + it->labels_size); } else { gcli_dict_add_string(dict, "LABELS", 0, 0, "none"); } @@ -237,21 +243,21 @@ gcli_pull_print(gcli_pull const *const it) } void -gcli_pull_print_op(gcli_pull const *const pull) +gcli_pull_print_op(struct gcli_pull const *const pull) { if (pull->body) pretty_print(pull->body, 4, 80, stdout); } static void -gcli_print_checks_list(gcli_pull_checks_list const *const list) +gcli_print_checks_list(struct gcli_pull_checks_list const *const list) { switch (list->forge_type) { case GCLI_FORGE_GITHUB: - github_print_checks((github_check_list const *)(list)); + github_print_checks((struct github_check_list const *)(list)); break; case GCLI_FORGE_GITLAB: - gitlab_print_pipelines((gitlab_pipeline_list const*)(list)); + gitlab_print_pipelines((struct gitlab_pipeline_list const*)(list)); break; default: assert(0 && "unreachable"); @@ -261,7 +267,7 @@ gcli_print_checks_list(gcli_pull_checks_list const *const list) int gcli_pull_checks(char const *owner, char const *repo, int pr_number) { - gcli_pull_checks_list list = {0}; + struct gcli_pull_checks_list list = {0}; gcli_forge_type t = gcli_config_get_forge_type(g_clictx); list.forge_type = t; @@ -304,10 +310,10 @@ cut_newline(char const *const _it) } void -gcli_print_commits(gcli_commit_list const *const list) +gcli_print_commits(struct gcli_commit_list const *const list) { gcli_tbl table; - gcli_tblcoldef cols[] = { + struct gcli_tblcoldef cols[] = { { .name = "SHA", .type = GCLI_TBLCOLTYPE_STRING, .flags = GCLI_TBLCOL_COLOUREXPL }, { .name = "AUTHOR", .type = GCLI_TBLCOLTYPE_STRING, .flags = GCLI_TBLCOL_BOLD }, { .name = "EMAIL", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, @@ -322,7 +328,7 @@ gcli_print_commits(gcli_commit_list const *const list) table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!table) - errx(1, "error: could not initialize table"); + errx(1, "gcli: error: could not initialize table"); for (size_t i = 0; i < list->commits_size; ++i) { char *message = cut_newline(list->commits[i].message); @@ -341,7 +347,7 @@ int gcli_pull_commits(char const *owner, char const *repo, int const pr_number) { - gcli_commit_list commits = {0}; + struct gcli_commit_list commits = {0}; int rc = 0; rc = gcli_pull_get_commits(g_clictx, owner, repo, pr_number, &commits); @@ -355,53 +361,51 @@ gcli_pull_commits(char const *owner, char const *repo, } static void -pull_init_user_file(gcli_ctx *ctx, FILE *stream, void *_opts) +pull_init_user_file(struct gcli_ctx *ctx, FILE *stream, void *_opts) { - gcli_submit_pull_options *opts = _opts; + struct gcli_submit_pull_options *opts = _opts; (void) ctx; fprintf( stream, - "! PR TITLE : "SV_FMT"\n" + "! PR TITLE : %s\n" "! Enter PR comments above.\n" "! All lines starting with '!' will be discarded.\n", - SV_ARGS(opts->title)); + opts->title); } -static sn_sv -gcli_pull_get_user_message(gcli_submit_pull_options *opts) +static char * +gcli_pull_get_user_message(struct gcli_submit_pull_options *opts) { return gcli_editor_get_user_message(g_clictx, pull_init_user_file, opts); } static int -create_pull(gcli_submit_pull_options opts, int always_yes) +create_pull(struct gcli_submit_pull_options opts, int always_yes) { opts.body = gcli_pull_get_user_message(&opts); fprintf(stdout, "The following PR will be created:\n" "\n" - "TITLE : "SV_FMT"\n" - "BASE : "SV_FMT"\n" - "HEAD : "SV_FMT"\n" + "TITLE : %s\n" + "BASE : %s\n" + "HEAD : %s\n" "IN : %s/%s\n" - "MESSAGE :\n"SV_FMT"\n", - SV_ARGS(opts.title),SV_ARGS(opts.to), - SV_ARGS(opts.from), - opts.owner, opts.repo, - SV_ARGS(opts.body)); + "MESSAGE :\n%s\n", + opts.title, opts.to, opts.from, + opts.owner, opts.repo, opts.body ? opts.body : "No message."); fputc('\n', stdout); if (!always_yes) if (!sn_yesno("Do you want to continue?")) - errx(1, "PR aborted."); + errx(1, "gcli: PR aborted."); return gcli_pull_submit(g_clictx, opts); } -static sn_sv +static char const * pr_try_derive_head(void) { char const *account; @@ -409,19 +413,20 @@ pr_try_derive_head(void) if ((account = gcli_config_get_account_name(g_clictx)) == NULL) { errx(1, - "error: Cannot derive PR head. Please specify --from or set the\n" - " account in the users gcli config file.\n" - "note: %s", + "gcli: error: Cannot derive PR head. Please specify --from or set the" + " account in the users gcli config file.\n" + "gcli: note: %s", gcli_get_error(g_clictx)); } - if (!(branch = gcli_gitconfig_get_current_branch()).length) + if (!(branch = gcli_gitconfig_get_current_branch()).length) { errx(1, - "error: Cannot derive PR head. Please specify --from or, if you\n" - " are in ยปdetached HEADยซ state, checkout the branch you \n" - " want to pull request.\n"); + "gcli: error: Cannot derive PR head. Please specify --from or, if you" + " are in ยปdetached HEADยซ state, checkout the branch you" + " want to pull request."); + } - return sn_sv_fmt("%s:"SV_FMT, account, SV_ARGS(branch)); + return sn_asprintf("%s:"SV_FMT, account, SV_ARGS(branch)); } static int @@ -429,7 +434,7 @@ subcommand_pull_create(int argc, char *argv[]) { /* we'll use getopt_long here to parse the arguments */ int ch; - gcli_submit_pull_options opts = {0}; + struct gcli_submit_pull_options opts = {0}; int always_yes = 0; const struct option options[] = { @@ -457,16 +462,20 @@ subcommand_pull_create(int argc, char *argv[]) .has_arg = required_argument, .flag = NULL, .val = 'l' }, + { .name = "automerge", + .has_arg = required_argument, + .flag = NULL, + .val = 'a' }, {0}, }; - while ((ch = getopt_long(argc, argv, "yf:t:do:r:l:", options, NULL)) != -1) { + while ((ch = getopt_long(argc, argv, "ayf:t:do:r:l:", options, NULL)) != -1) { switch (ch) { case 'f': - opts.from = SV(optarg); + opts.from = optarg; break; case 't': - opts.to = SV(optarg); + opts.to = optarg; break; case 'd': opts.draft = 1; @@ -485,6 +494,9 @@ subcommand_pull_create(int argc, char *argv[]) case 'y': always_yes = 1; break; + case 'a': + opts.automerge = true; + break; default: usage(); return EXIT_FAILURE; @@ -494,29 +506,31 @@ subcommand_pull_create(int argc, char *argv[]) argc -= optind; argv += optind; - if (!opts.from.length) + if (!opts.from) opts.from = pr_try_derive_head(); - if (!opts.to.length) { - opts.to = gcli_config_get_base(g_clictx); - if (opts.to.length == 0) + if (!opts.to) { + sn_sv base = gcli_config_get_base(g_clictx); + if (base.length == 0) errx(1, - "error: PR base is missing. Please either specify " + "gcli: error: PR base is missing. Please either specify " "--to branch-name or set pr.base in .gcli."); + + opts.to = sn_sv_to_cstr(base); } check_owner_and_repo(&opts.owner, &opts.repo); if (argc != 1) { - fprintf(stderr, "error: Missing title to PR\n"); + fprintf(stderr, "gcli: error: Missing title to PR\n"); usage(); return EXIT_FAILURE; } - opts.title = SV(argv[0]); + opts.title = argv[0]; if (create_pull(opts, always_yes) < 0) - errx(1, "error: failed to submit pull request: %s", + errx(1, "gcli: error: failed to submit pull request: %s", gcli_get_error(g_clictx)); free(opts.labels); @@ -536,11 +550,11 @@ subcommand_pulls(int argc, char *argv[]) char *endptr = NULL; char const *owner = NULL; char const *repo = NULL; - gcli_pull_list pulls = {0}; + struct gcli_pull_list pulls = {0}; int ch = 0; int pr = -1; int n = 30; /* how many prs to fetch at least */ - gcli_pull_fetch_details details = {0}; + struct gcli_pull_fetch_details details = {0}; enum gcli_output_flags flags = 0; /* detect whether we wanna create a PR */ @@ -601,21 +615,21 @@ subcommand_pulls(int argc, char *argv[]) case 'i': { pr = strtoul(optarg, &endptr, 10); if (endptr != (optarg + strlen(optarg))) - err(1, "error: cannot parse pr number ยป%sยซ", optarg); + err(1, "gcli: error: cannot parse pr number ยป%sยซ", optarg); if (pr <= 0) - errx(1, "error: pr number is out of range"); + errx(1, "gcli: error: pr number is out of range"); } break; case 'n': { n = strtoul(optarg, &endptr, 10); if (endptr != (optarg + strlen(optarg))) - err(1, "error: cannot parse pr count ยป%sยซ", optarg); + err(1, "gcli: error: cannot parse pr count ยป%sยซ", optarg); if (n < -1) - errx(1, "error: pr count is out of range"); + errx(1, "gcli: error: pr count is out of range"); if (n == 0) - errx(1, "error: pr count must not be zero"); + errx(1, "gcli: error: pr count must not be zero"); } break; case 'a': { details.all = true; @@ -648,7 +662,7 @@ subcommand_pulls(int argc, char *argv[]) * open PRs and exit */ if (pr < 0) { if (gcli_get_pulls(g_clictx, owner, repo, &details, n, &pulls) < 0) - errx(1, "error: could not fetch pull requests: %s", + errx(1, "gcli: error: could not fetch pull requests: %s", gcli_get_error(g_clictx)); gcli_print_pulls(flags, &pulls, n); @@ -659,7 +673,7 @@ subcommand_pulls(int argc, char *argv[]) /* If a PR number was given, require -a to be unset */ if (details.all || details.author) { - fprintf(stderr, "error: -a and -A cannot be combined with operations on a PR\n"); + fprintf(stderr, "gcli: error: -a and -A cannot be combined with operations on a PR\n"); usage(); return EXIT_FAILURE; } @@ -678,7 +692,7 @@ struct action_ctx { * we'll fetch the summary only if a command requires it. Then * we'll proceed to actually handling it. */ int fetched_pull; - gcli_pull pull; + struct gcli_pull pull; }; /** Helper routine for fetching a PR if required */ @@ -689,7 +703,7 @@ action_ctx_ensure_pull(struct action_ctx *ctx) return; if (gcli_get_pull(g_clictx, ctx->owner, ctx->repo, ctx->pr, &ctx->pull) < 0) - errx(1, "error: failed to fetch pull request data: %s", + errx(1, "gcli: error: failed to fetch pull request data: %s", gcli_get_error(g_clictx)); ctx->fetched_pull = 1; @@ -711,13 +725,13 @@ action_all(struct action_ctx *ctx) /* Commits */ puts("\nCOMMITS"); if (gcli_pull_commits(ctx->owner, ctx->repo, ctx->pr) < 0) - errx(1, "error: failed to fetch pull request checks: %s", + errx(1, "gcli: error: failed to fetch pull request checks: %s", gcli_get_error(g_clictx)); /* Checks */ puts("\nCHECKS"); if (gcli_pull_checks(ctx->owner, ctx->repo, ctx->pr) < 0) - errx(1, "error: failed to fetch pull request checks: %s", + errx(1, "gcli: error: failed to fetch pull request checks: %s", gcli_get_error(g_clictx)); } @@ -751,34 +765,38 @@ action_commits(struct action_ctx *const ctx) static void action_diff(struct action_ctx *const ctx) { - if (gcli_pull_print_diff(stdout, ctx->owner, ctx->repo, ctx->pr) < 0) - errx(1, "error: failed to fetch diff: %s", + if (gcli_pull_print_diff(stdout, ctx->owner, ctx->repo, ctx->pr) < 0) { + errx(1, "gcli: error: failed to fetch diff: %s", gcli_get_error(g_clictx)); + } } static void action_patch(struct action_ctx *const ctx) { - if (gcli_pull_print_patch(stdout, ctx->owner, ctx->repo, ctx->pr) < 0) - errx(1, "error: failed to fetch patch: %s", + if (gcli_pull_print_patch(stdout, ctx->owner, ctx->repo, ctx->pr) < 0) { + errx(1, "gcli: error: failed to fetch patch: %s", gcli_get_error(g_clictx)); + } } /* aliased to notes */ static void action_comments(struct action_ctx *const ctx) { - if (gcli_pull_comments(ctx->owner, ctx->repo, ctx->pr) < 0) - errx(1, "error: failed to fetch pull comments: %s", + if (gcli_pull_comments(ctx->owner, ctx->repo, ctx->pr) < 0) { + errx(1, "gcli: error: failed to fetch pull comments: %s", gcli_get_error(g_clictx)); + } } static void action_ci(struct action_ctx *const ctx) { - if (gcli_pull_checks(ctx->owner, ctx->repo, ctx->pr) < 0) - errx(1, "error: failed to fetch pull request checks: %s", + if (gcli_pull_checks(ctx->owner, ctx->repo, ctx->pr) < 0) { + errx(1, "gcli: error: failed to fetch pull request checks: %s", gcli_get_error(g_clictx)); + } } static void @@ -808,25 +826,28 @@ action_merge(struct action_ctx *const ctx) } } - if (gcli_pull_merge(g_clictx, ctx->owner, ctx->repo, ctx->pr, flags) < 0) - errx(1, "error: failed to merge pull request: %s", + if (gcli_pull_merge(g_clictx, ctx->owner, ctx->repo, ctx->pr, flags) < 0) { + errx(1, "gcli: error: failed to merge pull request: %s", gcli_get_error(g_clictx)); + } } static void action_close(struct action_ctx *const ctx) { - if (gcli_pull_close(g_clictx, ctx->owner, ctx->repo, ctx->pr) < 0) - errx(1, "error: failed to close pull request: %s", + if (gcli_pull_close(g_clictx, ctx->owner, ctx->repo, ctx->pr) < 0) { + errx(1, "gcli: error: failed to close pull request: %s", gcli_get_error(g_clictx)); + } } static void action_reopen(struct action_ctx *const ctx) { - if (gcli_pull_reopen(g_clictx, ctx->owner, ctx->repo, ctx->pr) < 0) - errx(1, "error: failed to reopen pull request: %s", + if (gcli_pull_reopen(g_clictx, ctx->owner, ctx->repo, ctx->pr) < 0) { + errx(1, "gcli: error: failed to reopen pull request: %s", gcli_get_error(g_clictx)); + } } static void @@ -839,7 +860,7 @@ action_labels(struct action_ctx *const ctx) int rc = 0; if (ctx->argc == 0) { - fprintf(stderr, "error: expected label action\n"); + fprintf(stderr, "gcli: error: expected label action\n"); usage(); exit(EXIT_FAILURE); } @@ -861,16 +882,20 @@ action_labels(struct action_ctx *const ctx) if (add_labels_size) { rc = gcli_pull_add_labels(g_clictx, ctx->owner, ctx->repo, ctx->pr, add_labels, add_labels_size); - if (rc < 0) - errx(1, "error: failed to add labels: %s", gcli_get_error(g_clictx)); + if (rc < 0) { + errx(1, "gcli: error: failed to add labels: %s", + gcli_get_error(g_clictx)); + } } if (remove_labels_size) { rc = gcli_pull_remove_labels(g_clictx, ctx->owner, ctx->repo, ctx->pr, remove_labels, remove_labels_size); - if (rc < 0) - errx(1, "error: failed to remove labels: %s", gcli_get_error(g_clictx)); + if (rc < 0) { + errx(1, "gcli: error: failed to remove labels: %s", + gcli_get_error(g_clictx)); + } } free(add_labels); @@ -893,9 +918,10 @@ action_milestone(struct action_ctx *const ctx) ctx->argv += 1; if (strcmp(arg, "-d") == 0) { - if (gcli_pull_clear_milestone(g_clictx, ctx->owner, ctx->repo, ctx->pr) < 0) - errx(1, "error: failed to clear milestone: %s", + if (gcli_pull_clear_milestone(g_clictx, ctx->owner, ctx->repo, ctx->pr) < 0) { + errx(1, "gcli: error: failed to clear milestone: %s", gcli_get_error(g_clictx)); + } } else { int milestone_id = 0; @@ -904,15 +930,16 @@ action_milestone(struct action_ctx *const ctx) milestone_id = strtoul(arg, &endptr, 10); if (endptr != arg + strlen(arg)) { - fprintf(stderr, "error: cannot parse milestone id ยป%sยซ\n", arg); + fprintf(stderr, "gcli: error: cannot parse milestone id ยป%sยซ\n", arg); exit(EXIT_FAILURE); } rc = gcli_pull_set_milestone(g_clictx, ctx->owner, ctx->repo, ctx->pr, milestone_id); - if (rc < 0) - errx(1, "error: failed to set milestone: %s", + if (rc < 0) { + errx(1, "gcli: error: failed to set milestone: %s", gcli_get_error(g_clictx)); + } } } @@ -922,15 +949,17 @@ action_request_review(struct action_ctx *const ctx) int rc; if (ctx->argc < 2) { - fprintf(stderr, "error: missing user name for reviewer\n"); + fprintf(stderr, "gcli: error: missing user name for reviewer\n"); usage(); exit(EXIT_FAILURE); } rc = gcli_pull_add_reviewer(g_clictx, ctx->owner, ctx->repo, ctx->pr, ctx->argv[1]); - if (rc < 0) - errx(1, "error: failed to request review: %s", gcli_get_error(g_clictx)); + if (rc < 0) { + errx(1, "gcli: error: failed to request review: %s", + gcli_get_error(g_clictx)); + } ctx->argc -= 1; ctx->argv += 1; @@ -942,7 +971,7 @@ action_title(struct action_ctx *const ctx) int rc = 0; if (ctx->argc < 2) { - fprintf(stderr, "error: missing title\n"); + fprintf(stderr, "gcli: error: missing title\n"); usage(); exit(EXIT_FAILURE); } @@ -950,7 +979,7 @@ action_title(struct action_ctx *const ctx) rc = gcli_pull_set_title(g_clictx, ctx->owner, ctx->repo, ctx->pr, ctx->argv[1]); if (rc < 0) { - errx(1, "error: failed to update review title: %s", + errx(1, "gcli: error: failed to update review title: %s", gcli_get_error(g_clictx)); } @@ -1010,7 +1039,7 @@ handle_pull_actions(int argc, char *argv[], char const *owner, char const *repo, /* Check if the user missed out on supplying actions */ if (argc == 0) { - fprintf(stderr, "error: no actions supplied\n"); + fprintf(stderr, "gcli: error: no actions supplied\n"); usage(); exit(EXIT_FAILURE); } @@ -1029,7 +1058,7 @@ handle_pull_actions(int argc, char *argv[], char const *owner, char const *repo, /* At this point we found an unknown action / stray * options on the command line. Error out in this case. */ - fprintf(stderr, "error: unknown action %s\n", action); + fprintf(stderr, "gcli: error: unknown action %s\n", action); usage(); return EXIT_FAILURE; diff --git a/src/cmd/releases.c b/src/cmd/releases.c index 10f067eb..6572c1ab 100644 --- a/src/cmd/releases.c +++ b/src/cmd/releases.c @@ -69,7 +69,7 @@ usage(void) static void gcli_print_release(enum gcli_output_flags const flags, - gcli_release const *const it) + struct gcli_release const *const it) { gcli_dict dict; @@ -77,10 +77,10 @@ gcli_print_release(enum gcli_output_flags const flags, dict = gcli_dict_begin(); - gcli_dict_add(dict, "ID", 0, 0, SV_FMT, SV_ARGS(it->id)); - gcli_dict_add(dict, "NAME", 0, 0, SV_FMT, SV_ARGS(it->name)); - gcli_dict_add(dict, "AUTHOR", 0, 0, SV_FMT, SV_ARGS(it->author)); - gcli_dict_add(dict, "DATE", 0, 0, SV_FMT, SV_ARGS(it->date)); + gcli_dict_add(dict, "ID", 0, 0, "%s", it->id); + gcli_dict_add(dict, "NAME", 0, 0, "%s", it->name); + gcli_dict_add(dict, "AUTHOR", 0, 0, "%s", it->author); + gcli_dict_add(dict, "DATE", 0, 0, "%s", it->date); gcli_dict_add_string(dict, "DRAFT", 0, 0, sn_bool_yesno(it->draft)); gcli_dict_add_string(dict, "PRERELEASE", 0, 0, sn_bool_yesno(it->prerelease)); gcli_dict_add_string(dict, "ASSETS", 0, 0, ""); @@ -94,9 +94,9 @@ gcli_print_release(enum gcli_output_flags const flags, gcli_dict_end(dict); /* body */ - if (it->body.length) { + if (it->body) { putchar('\n'); - pretty_print(it->body.data, 13, 80, stdout); + pretty_print(it->body, 13, 80, stdout); } putchar('\n'); @@ -104,7 +104,7 @@ gcli_print_release(enum gcli_output_flags const flags, static void gcli_releases_print_long(enum gcli_output_flags const flags, - gcli_release_list const *const list, int const max) + struct gcli_release_list const *const list, int const max) { int n; @@ -125,16 +125,17 @@ gcli_releases_print_long(enum gcli_output_flags const flags, static void gcli_releases_print_short(enum gcli_output_flags const flags, - gcli_release_list const *const list, int const max) + struct gcli_release_list const *const list, + int const max) { size_t n; gcli_tbl table; - gcli_tblcoldef cols[] = { - { .name = "ID", .type = GCLI_TBLCOLTYPE_SV, .flags = 0 }, - { .name = "DATE", .type = GCLI_TBLCOLTYPE_SV, .flags = 0 }, - { .name = "DRAFT", .type = GCLI_TBLCOLTYPE_BOOL, .flags = 0 }, - { .name = "PRERELEASE", .type = GCLI_TBLCOLTYPE_BOOL, .flags = 0 }, - { .name = "NAME", .type = GCLI_TBLCOLTYPE_SV, .flags = 0 }, + struct gcli_tblcoldef cols[] = { + { .name = "ID", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, + { .name = "DATE", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, + { .name = "DRAFT", .type = GCLI_TBLCOLTYPE_BOOL, .flags = 0 }, + { .name = "PRERELEASE", .type = GCLI_TBLCOLTYPE_BOOL, .flags = 0 }, + { .name = "NAME", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, }; if (max < 0 || (size_t)(max) > list->releases_size) @@ -144,7 +145,7 @@ gcli_releases_print_short(enum gcli_output_flags const flags, table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!table) - errx(1, "error: could not init table"); + errx(1, "gcli: error: could not init table"); if (flags & OUTPUT_SORTED) { for (size_t i = 0; i < n; ++i) { @@ -171,7 +172,7 @@ gcli_releases_print_short(enum gcli_output_flags const flags, void gcli_releases_print(enum gcli_output_flags const flags, - gcli_release_list const *const list, int const max) + struct gcli_release_list const *const list, int const max) { if (list->releases_size == 0) { puts("No releases"); @@ -185,9 +186,9 @@ gcli_releases_print(enum gcli_output_flags const flags, } static void -releasemsg_init(gcli_ctx *ctx, FILE *f, void *_data) +releasemsg_init(struct gcli_ctx *ctx, FILE *f, void *_data) { - gcli_new_release const *info = _data; + struct gcli_new_release const *info = _data; (void) ctx; @@ -202,8 +203,8 @@ releasemsg_init(gcli_ctx *ctx, FILE *f, void *_data) info->owner, info->repo, info->tag, info->name); } -static sn_sv -get_release_message(gcli_new_release const *info) +static char * +get_release_message(struct gcli_new_release const *info) { return gcli_editor_get_user_message(g_clictx, releasemsg_init, (void *)info); @@ -212,9 +213,9 @@ get_release_message(gcli_new_release const *info) static int subcommand_releases_create(int argc, char *argv[]) { - gcli_new_release release = {0}; - int ch; - bool always_yes = false; + struct gcli_new_release release = {0}; + int ch; + bool always_yes = false; struct option const options[] = { { .name = "yes", @@ -281,13 +282,15 @@ subcommand_releases_create(int argc, char *argv[]) release.owner = optarg; break; case 'a': { - gcli_release_asset_upload asset = { + struct gcli_release_asset_upload asset = { .path = optarg, .name = optarg, .label = "unused", }; - if (gcli_release_push_asset(g_clictx, &release, asset) < 0) - errx(1, "failed to add asset: %s", gcli_get_error(g_clictx)); + if (gcli_release_push_asset(g_clictx, &release, asset) < 0) { + errx(1, "gcli: error: failed to add asset: %s", + gcli_get_error(g_clictx)); + } } break; case 'y': { always_yes = true; @@ -305,19 +308,23 @@ subcommand_releases_create(int argc, char *argv[]) /* make sure we have a tag for the release */ if (!release.tag) { - fprintf(stderr, "error: releases create: missing tag name\n"); + fprintf(stderr, "gcli: error: releases create: missing tag name\n"); usage(); return EXIT_FAILURE; } release.body = get_release_message(&release); + if (release.body == NULL) + errx(1, "gcli: empty message. aborting."); if (!always_yes) if (!sn_yesno("Do you want to create this release?")) - errx(1, "Aborted by user"); + errx(1, "gcli: Aborted by user"); - if (gcli_create_release(g_clictx, &release) < 0) - errx(1, "failed to create release: %s", gcli_get_error(g_clictx)); + if (gcli_create_release(g_clictx, &release) < 0) { + errx(1, "gcli: error: failed to create release: %s", + gcli_get_error(g_clictx)); + } return EXIT_SUCCESS; } @@ -370,17 +377,19 @@ subcommand_releases_delete(int argc, char *argv[]) /* make sure the user supplied the release id */ if (argc != 1) { - fprintf(stderr, "error: releases delete: missing release id\n"); + fprintf(stderr, "gcli: error: releases delete: missing release id\n"); usage(); return EXIT_FAILURE; } if (!always_yes) if (!sn_yesno("Are you sure you want to delete this release?")) - errx(1, "Aborted by user"); + errx(1, "gcli: Aborted by user"); - if (gcli_delete_release(g_clictx, owner, repo, argv[0]) < 0) - errx(1, "failed to delete the release: %s", gcli_get_error(g_clictx)); + if (gcli_delete_release(g_clictx, owner, repo, argv[0]) < 0) { + errx(1, "gcli: error: failed to delete the release: %s", + gcli_get_error(g_clictx)); + } return EXIT_SUCCESS; } @@ -396,12 +405,12 @@ static struct { int subcommand_releases(int argc, char *argv[]) { - int ch; - int count = 30; - char const *owner = NULL; - char const *repo = NULL; - gcli_release_list releases = {0}; - enum gcli_output_flags flags = 0; + int ch; + int count = 30; + char const *owner = NULL; + char const *repo = NULL; + struct gcli_release_list releases = {0}; + enum gcli_output_flags flags = 0; if (argc > 1) { for (size_t i = 0; i < ARRAY_SIZE(releases_subcommands); ++i) { @@ -448,10 +457,10 @@ subcommand_releases(int argc, char *argv[]) char *endptr = NULL; count = strtol(optarg, &endptr, 10); if (endptr != (optarg + strlen(optarg))) - err(1, "releases: cannot parse release count"); + err(1, "gcli: error: cannot parse release count"); if (count == 0) - errx(1, "error: number of releases must not be zero"); + errx(1, "gcli: error: number of releases must not be zero"); } break; case 's': @@ -472,15 +481,17 @@ subcommand_releases(int argc, char *argv[]) /* sanity check */ if (argc > 0) { - fprintf(stderr, "error: stray arguments\n"); + fprintf(stderr, "gcli: error: stray arguments\n"); usage(); return EXIT_FAILURE; } check_owner_and_repo(&owner, &repo); - if (gcli_get_releases(g_clictx, owner, repo, count, &releases) < 0) - errx(1, "error: could not get releases: %s", gcli_get_error(g_clictx)); + if (gcli_get_releases(g_clictx, owner, repo, count, &releases) < 0) { + errx(1, "gcli: error: could not get releases: %s", + gcli_get_error(g_clictx)); + } gcli_releases_print(flags, &releases, count); diff --git a/src/cmd/repos.c b/src/cmd/repos.c index 4ff42858..c2b47106 100644 --- a/src/cmd/repos.c +++ b/src/cmd/repos.c @@ -67,15 +67,15 @@ usage(void) void gcli_print_repos(enum gcli_output_flags const flags, - gcli_repo_list const *const list, int const max) + struct gcli_repo_list const *const list, int const max) { size_t n; gcli_tbl table; - gcli_tblcoldef cols[] = { - { .name = "FORK", .type = GCLI_TBLCOLTYPE_BOOL, .flags = 0 }, - { .name = "VISBLTY", .type = GCLI_TBLCOLTYPE_SV, .flags = 0 }, - { .name = "DATE", .type = GCLI_TBLCOLTYPE_SV, .flags = 0 }, - { .name = "FULLNAME", .type = GCLI_TBLCOLTYPE_SV, .flags = 0 }, + struct gcli_tblcoldef cols[] = { + { .name = "FORK", .type = GCLI_TBLCOLTYPE_BOOL, .flags = 0 }, + { .name = "VISBLTY", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, + { .name = "DATE", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, + { .name = "FULLNAME", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, }; if (list->repos_size == 0) { @@ -92,7 +92,7 @@ gcli_print_repos(enum gcli_output_flags const flags, /* init table */ table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!table) - errx(1, "error: could not init table"); + errx(1, "gcli: error: could not init table"); /* put data into table */ if (flags & OUTPUT_SORTED) { @@ -116,17 +116,17 @@ gcli_print_repos(enum gcli_output_flags const flags, } void -gcli_repo_print(gcli_repo const *it) +gcli_repo_print(struct gcli_repo const *it) { gcli_dict dict; dict = gcli_dict_begin(); gcli_dict_add(dict, "ID", 0, 0, "%"PRIid, it->id); - gcli_dict_add(dict, "FULL NAME", 0, 0, SV_FMT, SV_ARGS(it->full_name)); - gcli_dict_add(dict, "NAME", 0, 0, SV_FMT, SV_ARGS(it->name)); - gcli_dict_add(dict, "OWNER", 0, 0, SV_FMT, SV_ARGS(it->owner)); - gcli_dict_add(dict, "DATE", 0, 0, SV_FMT, SV_ARGS(it->date)); - gcli_dict_add(dict, "VISIBILITY", 0, 0, SV_FMT, SV_ARGS(it->visibility)); + gcli_dict_add(dict, "FULL NAME", 0, 0, "%s", it->full_name); + gcli_dict_add(dict, "NAME", 0, 0, "%s", it->name); + gcli_dict_add(dict, "OWNER", 0, 0, "%s", it->owner); + gcli_dict_add(dict, "DATE", 0, 0, "%s", it->date); + gcli_dict_add(dict, "VISIBILITY", 0, 0, "%s", it->visibility); gcli_dict_add(dict, "IS FORK", 0, 0, "%s", sn_bool_yesno(it->is_fork)); gcli_dict_end(dict); @@ -136,8 +136,8 @@ static int subcommand_repos_create(int argc, char *argv[]) { int ch; - gcli_repo_create_options create_options = {0}; - gcli_repo repo = {0}; + struct gcli_repo_create_options create_options = {0}; + struct gcli_repo repo = {0}; const struct option options[] = { { .name = "repo", @@ -158,10 +158,10 @@ subcommand_repos_create(int argc, char *argv[]) while ((ch = getopt_long(argc, argv, "r:d:p", options, NULL)) != -1) { switch (ch) { case 'r': - create_options.name = SV(optarg); + create_options.name = optarg; break; case 'd': - create_options.description = SV(optarg); + create_options.description = optarg; break; case 'p': create_options.private = true; @@ -176,16 +176,18 @@ subcommand_repos_create(int argc, char *argv[]) argc -= optind; argv += optind; - if (sn_sv_null(create_options.name)) { + if (!create_options.name) { fprintf(stderr, - "name cannot be empty. please set a repository " + "gcli: name cannot be empty. please set a repository " "name with -r/--name\n"); usage(); return EXIT_FAILURE; } - if (gcli_repo_create(g_clictx, &create_options, &repo) < 0) - errx(1, "error: failed to create repository: %s", gcli_get_error(g_clictx)); + if (gcli_repo_create(g_clictx, &create_options, &repo) < 0) { + errx(1, "gcli: error: failed to create repository: %s", + gcli_get_error(g_clictx)); + } gcli_repo_print(&repo); gcli_repo_free(&repo); @@ -248,7 +250,7 @@ action_set_visibility(char const *const owner, char const *const repo, int rc; if (*argc < 2) { - fprintf(stderr, "error: missing visibility level\n"); + fprintf(stderr, "gcli: error: missing visibility level\n"); return 1; } @@ -258,12 +260,12 @@ action_set_visibility(char const *const owner, char const *const repo, visblty = parse_visibility(visblty_str); if (visblty < 0) { - fprintf(stderr, "error: bad visibility level ยป%sยซ\n", visblty_str); + fprintf(stderr, "gcli: error: bad visibility level ยป%sยซ\n", visblty_str); return 1; } if ((rc = gcli_repo_set_visibility(g_clictx, owner, repo, visblty)) < 0) { - fprintf(stderr, "error: failed to set visibility: %s\n", + fprintf(stderr, "gcli: error: failed to set visibility: %s\n", gcli_get_error(g_clictx)); return 1; } @@ -299,7 +301,7 @@ subcommand_repos(int argc, char *argv[]) int ch, n = 30; char const *owner = NULL; char const *repo = NULL; - gcli_repo_list repos = {0}; + struct gcli_repo_list repos = {0}; enum gcli_output_flags flags = 0; /* detect whether we wanna create a repo */ @@ -343,10 +345,10 @@ subcommand_repos(int argc, char *argv[]) char *endptr = NULL; n = strtol(optarg, &endptr, 10); if (endptr != (optarg + strlen(optarg))) - err(1, "repos: cannot parse repo count"); + err(1, "gcli: error: cannot parse repo count"); if (n == 0) - errx(1, "error: number of repos must not be zero"); + errx(1, "gcli: error: number of repos must not be zero"); } break; case '?': default: @@ -364,7 +366,7 @@ subcommand_repos(int argc, char *argv[]) int rc = 0; if (repo) { - fprintf(stderr, "error: repos: no actions specified\n"); + fprintf(stderr, "gcli: error: no actions specified\n"); usage(); return EXIT_FAILURE; } @@ -376,14 +378,17 @@ subcommand_repos(int argc, char *argv[]) * gcli_get_repos. This is bad since that causes segfaults down the * line. (https://github.com/herrhotzenplotz/gcli/issues/118) */ if (!owner) { - fprintf(stderr, "error: no account specified or no default account" - " configured. use -o to provide an explicit account name.\n"); + fprintf(stderr, "gcli: error: no account specified or no default" + " account configured. use -o to provide an explicit" + " account name.\n"); return EXIT_FAILURE; } rc = gcli_get_repos(g_clictx, owner, n, &repos); - if (rc < 0) - errx(1, "error: failed to fetch repos: %s", gcli_get_error(g_clictx)); + if (rc < 0) { + errx(1, "gcli: error: failed to fetch repos: %s", + gcli_get_error(g_clictx)); + } gcli_print_repos(flags, &repos, n); gcli_repos_free(&repos); @@ -395,7 +400,7 @@ subcommand_repos(int argc, char *argv[]) int rc = 0; if (!action) { - fprintf(stderr, "error: unrecognised action ยป%sยซ\n", argv[0]); + fprintf(stderr, "gcli: error: unrecognised action ยป%sยซ\n", argv[0]); return EXIT_FAILURE; } diff --git a/src/cmd/snippets.c b/src/cmd/snippets.c index b0ff0cce..5ee8045d 100644 --- a/src/cmd/snippets.c +++ b/src/cmd/snippets.c @@ -58,7 +58,7 @@ usage(void) static void gcli_print_snippet(enum gcli_output_flags const flags, - gcli_gitlab_snippet const *const it) + struct gcli_gitlab_snippet const *const it) { gcli_dict dict; @@ -79,7 +79,7 @@ gcli_print_snippet(enum gcli_output_flags const flags, static void gcli_print_snippets_long(enum gcli_output_flags const flags, - gcli_gitlab_snippet_list const *const list, int const max) + struct gcli_gitlab_snippet_list const *const list, int const max) { int n; @@ -100,12 +100,12 @@ gcli_print_snippets_long(enum gcli_output_flags const flags, static void gcli_print_snippets_short(enum gcli_output_flags const flags, - gcli_gitlab_snippet_list const *const list, + struct gcli_gitlab_snippet_list const *const list, int const max) { int n; gcli_tbl table; - gcli_tblcoldef cols[] = { + struct gcli_tblcoldef cols[] = { { .name = "ID", .type = GCLI_TBLCOLTYPE_ID, .flags = GCLI_TBLCOL_JUSTIFYR }, { .name = "DATE", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, { .name = "VISIBILITY", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, @@ -122,7 +122,7 @@ gcli_print_snippets_short(enum gcli_output_flags const flags, /* Fill table */ table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!table) - errx(1, "error: could not init table"); + errx(1, "gcli: error: could not init table"); if (flags & OUTPUT_SORTED) { for (int i = 0; i < n; ++i) @@ -147,7 +147,7 @@ gcli_print_snippets_short(enum gcli_output_flags const flags, void gcli_snippets_print(enum gcli_output_flags const flags, - gcli_gitlab_snippet_list const *const list, int const max) + struct gcli_gitlab_snippet_list const *const list, int const max) { if (list->snippets_size == 0) { puts("No Snippets"); @@ -167,7 +167,7 @@ subcommand_snippet_get(int argc, char *argv[]) argv += 1; if (!argc) { - fprintf(stderr, "error: get snippets: expected ID of snippet to fetch\n"); + fprintf(stderr, "gcli: error: expected ID of snippet to fetch\n"); usage(); return EXIT_FAILURE; } @@ -175,13 +175,13 @@ subcommand_snippet_get(int argc, char *argv[]) char *snippet_id = shift(&argc, &argv); if (argc) { - fprintf(stderr, "error: stray arguments\n"); + fprintf(stderr, "gcli: error: stray arguments\n"); usage(); return EXIT_FAILURE; } if (gcli_snippet_get(g_clictx, snippet_id, stdout) < 0) - errx(1, "error: failed to fetch snippet contents: %s", + errx(1, "gcli: error: failed to fetch snippet contents: %s", gcli_get_error(g_clictx)); return EXIT_SUCCESS; @@ -194,7 +194,7 @@ subcommand_snippet_delete(int argc, char *argv[]) argv += 1; if (!argc) { - fprintf(stderr, "error: delete snippets: expected ID of snippet to delete\n"); + fprintf(stderr, "gcli: error: expected ID of snippet to delete\n"); usage(); return EXIT_FAILURE; } @@ -202,13 +202,13 @@ subcommand_snippet_delete(int argc, char *argv[]) char *snippet_id = shift(&argc, &argv); if (argc) { - fprintf(stderr, "error: delete snippet: trailing options\n"); + fprintf(stderr, "gcli: error: trailing options\n"); usage(); return EXIT_FAILURE; } if (gcli_snippet_delete(g_clictx, snippet_id) < 0) - errx(1, "error: failed to delete snippet: %s", + errx(1, "gcli: error: failed to delete snippet: %s", gcli_get_error(g_clictx)); return EXIT_SUCCESS; @@ -226,7 +226,7 @@ int subcommand_snippets(int argc, char *argv[]) { int ch; - gcli_gitlab_snippet_list list = {0}; + struct gcli_gitlab_snippet_list list = {0}; int count = 30; enum gcli_output_flags flags = 0; @@ -261,10 +261,10 @@ subcommand_snippets(int argc, char *argv[]) count = strtol(optarg, &endptr, 10); if (endptr != (optarg + strlen(optarg))) - err(1, "snippets: cannot parse snippets count"); + err(1, "gcli: error: cannot parse snippets count"); if (count == 0) - errx(1, "error: snippets count must not be zero"); + errx(1, "gcli: error: snippets count must not be zero"); } break; case 's': flags |= OUTPUT_SORTED; @@ -283,7 +283,7 @@ subcommand_snippets(int argc, char *argv[]) argv += optind; if (gcli_snippets_get(g_clictx, count, &list) < 0) - errx(1, "error: failed to fetch snippets: %s", + errx(1, "gcli: error: failed to fetch snippets: %s", gcli_get_error(g_clictx)); gcli_snippets_print(flags, &list, count); diff --git a/src/cmd/status.c b/src/cmd/status.c index 268965ce..ea787121 100644 --- a/src/cmd/status.c +++ b/src/cmd/status.c @@ -54,7 +54,7 @@ usage(void) int gcli_status(int const count) { - gcli_notification_list list = {0}; + struct gcli_notification_list list = {0}; int rc = 0; rc = gcli_get_notifications(g_clictx, count, &list); @@ -68,7 +68,7 @@ gcli_status(int const count) } void -gcli_print_notifications(gcli_notification_list const *const list) +gcli_print_notifications(struct gcli_notification_list const *const list) { for (size_t i = 0; i < list->notifications_size; ++i) { printf("%s - %s - %s - %s", @@ -112,7 +112,7 @@ subcommand_status(int argc, char *argv[]) case 'n': { count = strtol(optarg, &endptr, 10); if (endptr != optarg + strlen(optarg)) - err(1, "status: cannot parse parameter to -n"); + err(1, "gcli: error: cannot parse parameter to -n"); } break; case 'm': { mark = 1; @@ -130,22 +130,22 @@ subcommand_status(int argc, char *argv[]) gcli_status(count); } else { if (count != 30) - warnx("ignoring -n/--count argument"); + warnx("gcli: ignoring -n/--count argument"); if (argc > 1) { - fprintf(stderr, "error: too many arguments for marking notifications\n"); + fprintf(stderr, "gcli: error: too many arguments for marking notifications\n"); usage(); return EXIT_FAILURE; } if (argc < 1) { - fprintf(stderr, "error: missing notification id to mark as read\n"); + fprintf(stderr, "gcli: error: missing notification id to mark as read\n"); usage(); return EXIT_FAILURE; } if (gcli_notification_mark_as_read(g_clictx, argv[0]) < 0) - errx(1, "error: failed to mark the notification as read: %s", + errx(1, "gcli: error: failed to mark the notification as read: %s", gcli_get_error(g_clictx)); } diff --git a/src/cmd/table.c b/src/cmd/table.c index 789d2700..fa97de8a 100644 --- a/src/cmd/table.c +++ b/src/cmd/table.c @@ -45,9 +45,9 @@ struct gcli_tblrow; /* Internal state of a table printer. We return a handle to it in * gcli_table_init. */ struct gcli_tbl { - gcli_tblcoldef const *cols; /* user provided column definitons */ - int *col_widths; /* minimum width of the columns */ - size_t cols_size; /* size of above arrays */ + struct gcli_tblcoldef const *cols; /* user provided column definitons */ + int *col_widths; /* minimum width of the columns */ + size_t cols_size; /* size of above arrays */ struct gcli_tblrow *rows; /* list of rows */ size_t rows_size; /* number of rows */ @@ -76,7 +76,7 @@ table_pushrow(struct gcli_tbl *const table, struct gcli_tblrow row) /** Initialize the internal state structure of the table printer. */ gcli_tbl -gcli_tbl_begin(gcli_tblcoldef const *const cols, size_t const cols_size) +gcli_tbl_begin(struct gcli_tblcoldef const *const cols, size_t const cols_size) { struct gcli_tbl *tbl; @@ -355,7 +355,7 @@ struct gcli_dict { size_t max_key_len; - gcli_ctx *ctx; + struct gcli_ctx *ctx; }; /* Create a new long list printer and return a handle to it */ diff --git a/src/comments.c b/src/comments.c index 45cd8827..9677dbaf 100644 --- a/src/comments.c +++ b/src/comments.c @@ -37,7 +37,7 @@ #include void -gcli_comment_free(gcli_comment *const it) +gcli_comment_free(struct gcli_comment *const it) { free(it->author); free(it->date); @@ -45,7 +45,7 @@ gcli_comment_free(gcli_comment *const it) } void -gcli_comments_free(gcli_comment_list *const list) +gcli_comments_free(struct gcli_comment_list *const list) { for (size_t i = 0; i < list->comments_size; ++i) gcli_comment_free(&list->comments[i]); @@ -56,21 +56,21 @@ gcli_comments_free(gcli_comment_list *const list) } int -gcli_get_issue_comments(gcli_ctx *ctx, char const *owner, char const *repo, - int const issue, gcli_comment_list *out) +gcli_get_issue_comments(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const issue, struct gcli_comment_list *out) { - return gcli_forge(ctx)->get_issue_comments(ctx, owner, repo, issue, out); + gcli_null_check_call(get_issue_comments, ctx, owner, repo, issue, out); } int -gcli_get_pull_comments(gcli_ctx *ctx, char const *owner, char const *repo, - int const pull, gcli_comment_list *out) +gcli_get_pull_comments(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const pull, struct gcli_comment_list *out) { - return gcli_forge(ctx)->get_pull_comments(ctx, owner, repo, pull, out); + gcli_null_check_call(get_pull_comments, ctx, owner, repo, pull, out); } int -gcli_comment_submit(gcli_ctx *ctx, gcli_submit_comment_opts opts) +gcli_comment_submit(struct gcli_ctx *ctx, struct gcli_submit_comment_opts opts) { - return gcli_forge(ctx)->perform_submit_comment(ctx, opts, NULL); + gcli_null_check_call(perform_submit_comment, ctx, opts, NULL); } diff --git a/src/ctx.c b/src/ctx.c index 0ff61590..f5ea5d8c 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -87,16 +87,23 @@ gcli_get_apibase(struct gcli_ctx *ctx) return ctx->apibase; } +char * +gcli_get_token(struct gcli_ctx *ctx) +{ + return ctx->get_token(ctx); +} + char * gcli_get_authheader(struct gcli_ctx *ctx) { char *hdr = NULL; - char *token = ctx->get_token(ctx); + char *token = gcli_get_token(ctx); - if (token) { + if (token && gcli_forge(ctx)->make_authheader) { hdr = gcli_forge(ctx)->make_authheader(ctx, token); - free(token); } + free(token); + return hdr; } diff --git a/src/curl.c b/src/curl.c index 06a4e9bf..0842f7f8 100644 --- a/src/curl.c +++ b/src/curl.c @@ -60,18 +60,23 @@ gcli_curl_isalnum(char const c) /* XXX move to gcli_ctx destructor */ void -gcli_curl_ctx_destroy(gcli_ctx *ctx) +gcli_curl_ctx_destroy(struct gcli_ctx *ctx) { if (ctx->curl) curl_easy_cleanup(ctx->curl); + ctx->curl = NULL; + + free(ctx->curl_useragent); + ctx->curl_useragent = NULL; } /* Ensures a clean cURL handle. Call this whenever you wanna use the * ctx->curl */ static int -gcli_curl_ensure(gcli_ctx *ctx) +gcli_curl_ensure(struct gcli_ctx *ctx) { + if (ctx->curl) { curl_easy_reset(ctx->curl); } else { @@ -80,14 +85,21 @@ gcli_curl_ensure(gcli_ctx *ctx) return gcli_error(ctx, "failed to initialise curl context"); } + if (!ctx->curl_useragent) { + curl_version_info_data const *ver; + + ver = curl_version_info(CURLVERSION_NOW); + ctx->curl_useragent = sn_asprintf("curl/%s", ver->version); + } + return 0; } /* Check the given curl code for an OK result. If not, print an * appropriate error message and exit */ static int -gcli_curl_check_api_error(gcli_ctx *ctx, CURLcode code, char const *url, - gcli_fetch_buffer *const result) +gcli_curl_check_api_error(struct gcli_ctx *ctx, CURLcode code, char const *url, + struct gcli_fetch_buffer *const result) { long status_code = 0; @@ -108,7 +120,7 @@ gcli_curl_check_api_error(gcli_ctx *ctx, CURLcode code, char const *url, return 0; } -/* Callback for writing data into the gcli_fetch_buffer passed by +/* Callback for writing data into the struct gcli_fetch_buffer passed by * calling routines */ static size_t fetch_write_callback(char *in, size_t size, size_t nmemb, void *data) @@ -116,7 +128,7 @@ fetch_write_callback(char *in, size_t size, size_t nmemb, void *data) /* the user may have passed null indicating that we do not care * about the result body of the request. */ if (data) { - gcli_fetch_buffer *out = data; + struct gcli_fetch_buffer *out = data; out->data = realloc(out->data, out->length + size * nmemb); memcpy(&(out->data[out->length]), in, size * nmemb); @@ -129,10 +141,10 @@ fetch_write_callback(char *in, size_t size, size_t nmemb, void *data) /* Plain HTTP get request. * * pagination_next returns the next url to query for paged results. - * Results are placed into the gcli_fetch_buffer. */ + * Results are placed into the struct gcli_fetch_buffer. */ int -gcli_fetch(gcli_ctx *ctx, char const *url, char **const pagination_next, - gcli_fetch_buffer *out) +gcli_fetch(struct gcli_ctx *ctx, char const *url, char **const pagination_next, + struct gcli_fetch_buffer *out) { return gcli_fetch_with_method(ctx, "GET", url, NULL, pagination_next, out); } @@ -141,7 +153,7 @@ static int gcli_report_progress(void *_ctx, double dltotal, double dlnow, double ultotal, double ulnow) { - gcli_ctx *ctx = _ctx; + struct gcli_ctx *ctx = _ctx; (void) dltotal; (void) dlnow; @@ -156,10 +168,10 @@ gcli_report_progress(void *_ctx, double dltotal, double dlnow, /* Check the given url for a successful query */ int -gcli_curl_test_success(gcli_ctx *ctx, char const *url) +gcli_curl_test_success(struct gcli_ctx *ctx, char const *url) { CURLcode ret; - gcli_fetch_buffer buffer = {0}; + struct gcli_fetch_buffer buffer = {0}; long status_code; bool is_success = true; int rc = 0; @@ -171,7 +183,7 @@ gcli_curl_test_success(gcli_ctx *ctx, char const *url) curl_easy_setopt(ctx->curl, CURLOPT_BUFFERSIZE, 102400L); curl_easy_setopt(ctx->curl, CURLOPT_NOPROGRESS, 1L); curl_easy_setopt(ctx->curl, CURLOPT_MAXREDIRS, 50L); - curl_easy_setopt(ctx->curl, CURLOPT_USERAGENT, "curl/7.78.0"); + curl_easy_setopt(ctx->curl, CURLOPT_USERAGENT, ctx->curl_useragent); #if defined(CURL_HTTP_VERSION_2TLS) curl_easy_setopt( ctx->curl, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2TLS); @@ -213,11 +225,12 @@ gcli_curl_test_success(gcli_ctx *ctx, char const *url) * * content_type may be NULL. */ int -gcli_curl(gcli_ctx *ctx, FILE *stream, char const *url, char const *content_type) +gcli_curl(struct gcli_ctx *ctx, FILE *stream, char const *url, + char const *content_type) { CURLcode ret; struct curl_slist *headers; - gcli_fetch_buffer buffer = {0}; + struct gcli_fetch_buffer buffer = {0}; char *auth_header = NULL; int rc = 0; @@ -230,7 +243,8 @@ gcli_curl(gcli_ctx *ctx, FILE *stream, char const *url, char const *content_type headers = curl_slist_append(headers, content_type); auth_header = gcli_get_authheader(ctx); - headers = curl_slist_append(headers, auth_header); + if (auth_header) + headers = curl_slist_append(headers, auth_header); curl_easy_setopt(ctx->curl, CURLOPT_URL, url); curl_easy_setopt(ctx->curl, CURLOPT_BUFFERSIZE, 102400L); @@ -238,7 +252,7 @@ gcli_curl(gcli_ctx *ctx, FILE *stream, char const *url, char const *content_type curl_easy_setopt(ctx->curl, CURLOPT_MAXREDIRS, 50L); curl_easy_setopt(ctx->curl, CURLOPT_FTP_SKIP_PASV_IP, 1L); curl_easy_setopt(ctx->curl, CURLOPT_HTTPHEADER, headers); - curl_easy_setopt(ctx->curl, CURLOPT_USERAGENT, "curl/7.78.0"); + curl_easy_setopt(ctx->curl, CURLOPT_USERAGENT, ctx->curl_useragent); #if defined(CURL_HTTP_VERSION_2TLS) curl_easy_setopt( ctx->curl, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2TLS); @@ -350,17 +364,17 @@ parse_link_header(char *_header) * will be set to NULL. */ int gcli_fetch_with_method( - gcli_ctx *ctx, - char const *method, /* HTTP method. e.g. POST, GET, DELETE etc. */ - char const *url, /* Endpoint */ - char const *data, /* Form data */ - char **const pagination_next, /* Next URL for pagination */ - gcli_fetch_buffer *const out) /* output buffer */ + struct gcli_ctx *ctx, + char const *method, /* HTTP method. e.g. POST, GET, DELETE etc. */ + char const *url, /* Endpoint */ + char const *data, /* Form data */ + char **const pagination_next, /* Next URL for pagination */ + struct gcli_fetch_buffer *const out) /* output buffer */ { CURLcode ret; struct curl_slist *headers; - gcli_fetch_buffer tmp = {0}; /* used for error codes when out is NULL */ - gcli_fetch_buffer *buf = NULL; + struct gcli_fetch_buffer tmp = {0}; /* used for error codes when out is NULL */ + struct gcli_fetch_buffer *buf = NULL; char *link_header = NULL; int rc = 0; @@ -386,7 +400,7 @@ gcli_fetch_with_method( * user is not interested in the result we use a temporary buffer * for proper error reporting. */ if (out) { - *out = (gcli_fetch_buffer) {0}; + *out = (struct gcli_fetch_buffer) {0}; buf = out; } else { buf = &tmp; @@ -398,7 +412,7 @@ gcli_fetch_with_method( curl_easy_setopt(ctx->curl, CURLOPT_POSTFIELDS, data); curl_easy_setopt(ctx->curl, CURLOPT_HTTPHEADER, headers); - curl_easy_setopt(ctx->curl, CURLOPT_USERAGENT, "curl/7.79.1"); + curl_easy_setopt(ctx->curl, CURLOPT_USERAGENT, ctx->curl_useragent); curl_easy_setopt(ctx->curl, CURLOPT_CUSTOMREQUEST, method); curl_easy_setopt(ctx->curl, CURLOPT_TCP_KEEPALIVE, 1L); curl_easy_setopt(ctx->curl, CURLOPT_WRITEDATA, buf); @@ -453,9 +467,9 @@ gcli_fetch_with_method( * content_type may not be NULL. */ int -gcli_post_upload(gcli_ctx *ctx, char const *url, char const *content_type, +gcli_post_upload(struct gcli_ctx *ctx, char const *url, char const *content_type, void *buffer, size_t const buffer_size, - gcli_fetch_buffer *const out) + struct gcli_fetch_buffer *const out) { CURLcode ret; struct curl_slist *headers; @@ -478,7 +492,10 @@ gcli_post_upload(gcli_ctx *ctx, char const *url, char const *content_type, headers = curl_slist_append( headers, "Accept: application/vnd.github.v3+json"); - headers = curl_slist_append(headers, auth_header); + + if (auth_header) + headers = curl_slist_append(headers, auth_header); + headers = curl_slist_append(headers, contenttype_header); headers = curl_slist_append(headers, contentsize_header); @@ -488,7 +505,7 @@ gcli_post_upload(gcli_ctx *ctx, char const *url, char const *content_type, curl_easy_setopt(ctx->curl, CURLOPT_POSTFIELDSIZE, (long)buffer_size); curl_easy_setopt(ctx->curl, CURLOPT_HTTPHEADER, headers); - curl_easy_setopt(ctx->curl, CURLOPT_USERAGENT, "curl/7.79.1"); + curl_easy_setopt(ctx->curl, CURLOPT_USERAGENT, ctx->curl_useragent); curl_easy_setopt(ctx->curl, CURLOPT_WRITEDATA, out); curl_easy_setopt(ctx->curl, CURLOPT_WRITEFUNCTION, fetch_write_callback); @@ -521,9 +538,9 @@ gcli_post_upload(gcli_ctx *ctx, char const *url, char const *content_type, * code. */ int -gcli_curl_gitea_upload_attachment(gcli_ctx *ctx, char const *url, +gcli_curl_gitea_upload_attachment(struct gcli_ctx *ctx, char const *url, char const *filename, - gcli_fetch_buffer *const out) + struct gcli_fetch_buffer *const out) { CURLcode ret; curl_mime *mime; @@ -544,7 +561,9 @@ gcli_curl_gitea_upload_attachment(gcli_ctx *ctx, char const *url, headers = curl_slist_append( headers, "Accept: application/json"); - headers = curl_slist_append(headers, auth_header); + + if (auth_header) + headers = curl_slist_append(headers, auth_header); /* The docs say we should be using this mime thing. */ mime = curl_mime_init(ctx->curl); @@ -624,7 +643,7 @@ gcli_urlencode(char const *input) } char * -gcli_urldecode(gcli_ctx *ctx, char const *input) +gcli_urldecode(struct gcli_ctx *ctx, char const *input) { char *curlresult, *result; @@ -656,13 +675,13 @@ gcli_urldecode(gcli_ctx *ctx, char const *input) * * If max is -1 then everything will be fetched. */ int -gcli_fetch_list(gcli_ctx *ctx, char *url, gcli_fetch_list_ctx *fl) +gcli_fetch_list(struct gcli_ctx *ctx, char *url, struct gcli_fetch_list_ctx *fl) { char *next_url = NULL; int rc; do { - gcli_fetch_buffer buffer = {0}; + struct gcli_fetch_buffer buffer = {0}; rc = gcli_fetch(ctx, url, &next_url, &buffer); if (rc == 0) { diff --git a/src/date_time.c b/src/date_time.c index 713a2bf9..c0266261 100644 --- a/src/date_time.c +++ b/src/date_time.c @@ -35,7 +35,7 @@ #include int -gcli_normalize_date(gcli_ctx *ctx, int fmt, char const *const input, +gcli_normalize_date(struct gcli_ctx *ctx, int fmt, char const *const input, char *output, size_t const output_size) { struct tm tm_buf = {0}; diff --git a/src/forges.c b/src/forges.c index 7461740a..6835a52d 100644 --- a/src/forges.c +++ b/src/forges.c @@ -1,5 +1,5 @@ /* - * Copyright 2021, 2022 Nico Sonack + * Copyright 2021, 2022, 2023 Nico Sonack * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -70,7 +70,12 @@ #include #include -static gcli_forge_descriptor const +#include +#include +#include +#include + +static struct gcli_forge_descriptor const github_forge_descriptor = { /* Comments */ @@ -84,7 +89,7 @@ github_forge_descriptor = /* Issues */ .get_issue_summary = github_get_issue_summary, - .get_issues = github_get_issues, + .search_issues = github_issues_search, .issue_add_labels = github_issue_add_labels, .issue_assign = github_issue_assign, .issue_clear_milestone = github_issue_clear_milestone, @@ -94,6 +99,9 @@ github_forge_descriptor = .issue_set_milestone = github_issue_set_milestone, .issue_set_title = github_issue_set_title, .perform_submit_issue = github_perform_submit_issue, + .issue_quirks = GCLI_ISSUE_QUIRKS_PROD_COMP + | GCLI_ISSUE_QUIRKS_URL + | GCLI_ISSUE_QUIRKS_ATTACHMENTS, /* Milestones */ .create_milestone = github_create_milestone, @@ -159,10 +167,11 @@ github_forge_descriptor = .milestone_quirks = GCLI_MILESTONE_QUIRKS_EXPIRED | GCLI_MILESTONE_QUIRKS_DUEDATE | GCLI_MILESTONE_QUIRKS_PULLS, - .pull_summary_quirks = GCLI_PRS_QUIRK_COVERAGE, + .pull_summary_quirks = GCLI_PRS_QUIRK_COVERAGE + | GCLI_PRS_QUIRK_AUTOMERGE, }; -static gcli_forge_descriptor const +static struct gcli_forge_descriptor const gitlab_forge_descriptor = { /* Comments */ @@ -176,7 +185,7 @@ gitlab_forge_descriptor = /* Issues */ .get_issue_summary = gitlab_get_issue_summary, - .get_issues = gitlab_get_issues, + .search_issues = gitlab_issues_search, .issue_add_labels = gitlab_issue_add_labels, .issue_assign = gitlab_issue_assign, .issue_clear_milestone = gitlab_issue_clear_milestone, @@ -186,6 +195,9 @@ gitlab_forge_descriptor = .issue_set_milestone = gitlab_issue_set_milestone, .issue_set_title = gitlab_issue_set_title, .perform_submit_issue = gitlab_perform_submit_issue, + .issue_quirks = GCLI_ISSUE_QUIRKS_PROD_COMP + | GCLI_ISSUE_QUIRKS_URL + | GCLI_ISSUE_QUIRKS_ATTACHMENTS, /* Milestones */ .create_milestone = gitlab_create_milestone, @@ -251,7 +263,7 @@ gitlab_forge_descriptor = | GCLI_PRS_QUIRK_MERGED, }; -static gcli_forge_descriptor const +static struct gcli_forge_descriptor const gitea_forge_descriptor = { /* Comments */ @@ -265,7 +277,7 @@ gitea_forge_descriptor = /* Issues */ .get_issue_summary = gitea_get_issue_summary, - .get_issues = gitea_get_issues, + .search_issues = gitea_issues_search, .issue_add_labels = gitea_issue_add_labels, .issue_assign = gitea_issue_assign, .issue_clear_milestone = gitea_issue_clear_milestone, @@ -275,6 +287,9 @@ gitea_forge_descriptor = .issue_set_milestone = gitea_issue_set_milestone, .issue_set_title = gitea_issue_set_title, .perform_submit_issue = gitea_submit_issue, + .issue_quirks = GCLI_ISSUE_QUIRKS_PROD_COMP + | GCLI_ISSUE_QUIRKS_URL + | GCLI_ISSUE_QUIRKS_ATTACHMENTS, /* Milestones */ .create_milestone = gitea_create_milestone, @@ -337,13 +352,34 @@ gitea_forge_descriptor = | GCLI_MILESTONE_QUIRKS_PULLS, .pull_summary_quirks = GCLI_PRS_QUIRK_COMMITS | GCLI_PRS_QUIRK_ADDDEL + | GCLI_PRS_QUIRK_AUTOMERGE | GCLI_PRS_QUIRK_DRAFT | GCLI_PRS_QUIRK_CHANGES | GCLI_PRS_QUIRK_COVERAGE, }; -gcli_forge_descriptor const * -gcli_forge(gcli_ctx *ctx) +static struct gcli_forge_descriptor const +bugzilla_forge_descriptor = +{ + /* Issues */ + .search_issues = bugzilla_get_bugs, + .get_issue_summary = bugzilla_get_bug, + .get_issue_comments = bugzilla_bug_get_comments, + .get_issue_attachments = bugzilla_bug_get_attachments, + .perform_submit_issue = bugzilla_bug_submit, + .issue_quirks = GCLI_ISSUE_QUIRKS_COMMENTS + | GCLI_ISSUE_QUIRKS_LOCKED, + + .attachment_get_content = bugzilla_attachment_get_content, + + /* Internal stuff */ + .make_authheader = bugzilla_make_authheader, + .get_api_error_string = bugzilla_api_error_string, + .user_object_key = "---dummy---", +}; + +struct gcli_forge_descriptor const * +gcli_forge(struct gcli_ctx *ctx) { switch (ctx->get_forge_type(ctx)) { case GCLI_FORGE_GITHUB: @@ -352,6 +388,8 @@ gcli_forge(gcli_ctx *ctx) return &gitlab_forge_descriptor; case GCLI_FORGE_GITEA: return &gitea_forge_descriptor; + case GCLI_FORGE_BUGZILLA: + return &bugzilla_forge_descriptor; default: errx(1, "error: cannot determine forge type. try forcing an account " diff --git a/src/forks.c b/src/forks.c index 3508b2d9..b8ee77a1 100644 --- a/src/forks.c +++ b/src/forks.c @@ -34,29 +34,29 @@ #include int -gcli_get_forks(gcli_ctx *ctx, char const *owner, char const *repo, - int const max, gcli_fork_list *const out) +gcli_get_forks(struct gcli_ctx *ctx, char const *owner, char const *repo, + int const max, struct gcli_fork_list *const out) { - return gcli_forge(ctx)->get_forks(ctx, owner, repo, max, out); + gcli_null_check_call(get_forks, ctx, owner, repo, max, out); } int -gcli_fork_create(gcli_ctx *ctx, char const *owner, char const *repo, +gcli_fork_create(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *_in) { - return gcli_forge(ctx)->fork_create(ctx, owner, repo, _in); + gcli_null_check_call(fork_create, ctx, owner, repo, _in); } void -gcli_fork_free(gcli_fork *fork) +gcli_fork_free(struct gcli_fork *fork) { - free(fork->full_name.data); - free(fork->owner.data); - free(fork->date.data); + free(fork->full_name); + free(fork->owner); + free(fork->date); } void -gcli_forks_free(gcli_fork_list *const list) +gcli_forks_free(struct gcli_fork_list *const list) { for (size_t i = 0; i < list->forks_size; ++i) { gcli_fork_free(&list->forks[i]); diff --git a/src/gcli.c b/src/gcli.c index b3846876..c956c057 100644 --- a/src/gcli.c +++ b/src/gcli.c @@ -34,8 +34,10 @@ #include char const * -gcli_init(gcli_ctx **ctx, gcli_forge_type (*get_forge_type)(gcli_ctx *), - char *(*get_token)(gcli_ctx *), char *(*get_apibase)(gcli_ctx *)) +gcli_init(struct gcli_ctx **ctx, + gcli_forge_type (*get_forge_type)(struct gcli_ctx *), + char *(*get_token)(struct gcli_ctx *), + char *(*get_apibase)(struct gcli_ctx *)) { *ctx = calloc(sizeof (struct gcli_ctx), 1); if (!(*ctx)) @@ -51,7 +53,7 @@ gcli_init(gcli_ctx **ctx, gcli_forge_type (*get_forge_type)(gcli_ctx *), } void -gcli_destroy(gcli_ctx **ctx) +gcli_destroy(struct gcli_ctx **ctx) { if (ctx && *ctx) { free((*ctx)->apibase); @@ -64,7 +66,7 @@ gcli_destroy(gcli_ctx **ctx) } char const * -gcli_get_error(gcli_ctx *ctx) +gcli_get_error(struct gcli_ctx *ctx) { if (ctx->last_error) return ctx->last_error; diff --git a/src/gitea/comments.c b/src/gitea/comments.c index 7be342e3..1b9b6653 100644 --- a/src/gitea/comments.c +++ b/src/gitea/comments.c @@ -31,15 +31,15 @@ #include int -gitea_get_comments(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const issue, gcli_comment_list *const out) +gitea_get_comments(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const issue, struct gcli_comment_list *const out) { return github_get_comments(ctx, owner, repo, issue, out); } int -gitea_perform_submit_comment(gcli_ctx *ctx, gcli_submit_comment_opts opts, - gcli_fetch_buffer *const out) +gitea_perform_submit_comment(struct gcli_ctx *ctx, struct gcli_submit_comment_opts opts, + struct gcli_fetch_buffer *const out) { return github_perform_submit_comment(ctx, opts, out); } diff --git a/src/gitea/config.c b/src/gitea/config.c index 5b22ec09..158e2600 100644 --- a/src/gitea/config.c +++ b/src/gitea/config.c @@ -32,7 +32,7 @@ #include char * -gitea_make_authheader(gcli_ctx *ctx, char const *token) +gitea_make_authheader(struct gcli_ctx *ctx, char const *token) { (void) ctx; return sn_asprintf("Authorization: token %s", token); diff --git a/src/gitea/forks.c b/src/gitea/forks.c index 8c9b5017..1552a005 100644 --- a/src/gitea/forks.c +++ b/src/gitea/forks.c @@ -33,14 +33,14 @@ #include int -gitea_get_forks(gcli_ctx *ctx, char const *owner, char const *repo, - int const max, gcli_fork_list *const out) +gitea_get_forks(struct gcli_ctx *ctx, char const *owner, char const *repo, + int const max, struct gcli_fork_list *const out) { return github_get_forks(ctx, owner, repo, max, out); } int -gitea_fork_create(gcli_ctx *ctx, char const *owner, char const *repo, +gitea_fork_create(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *_in) { return github_fork_create(ctx, owner, repo, _in); diff --git a/src/gitea/issues.c b/src/gitea/issues.c index 010418df..a73c08e4 100644 --- a/src/gitea/issues.c +++ b/src/gitea/issues.c @@ -32,102 +32,167 @@ #include #include #include +#include #include #include #include int -gitea_get_issues(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_issue_fetch_details const *details, int const max, - gcli_issue_list *const out) +gitea_issues_search(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_issue_fetch_details const *details, + int const max, struct gcli_issue_list *const out) { - return github_get_issues(ctx, owner, repo, details, max, out); + char *url = NULL, *e_owner = NULL, *e_repo = NULL, *e_author = NULL, + *e_label = NULL, *e_milestone = NULL, *e_query = NULL; + + if (details->milestone) { + char *tmp = gcli_urlencode(details->milestone); + e_milestone = sn_asprintf("&milestones=%s", tmp); + free(tmp); + } + + if (details->author) { + char *tmp = gcli_urlencode(details->author); + e_author = sn_asprintf("&created_by=%s", tmp); + free(tmp); + } + + if (details->label) { + char *tmp = gcli_urlencode(details->label); + e_label = sn_asprintf("&labels=%s", tmp); + free(tmp); + } + + if (details->search_term) { + char *tmp = gcli_urlencode(details->search_term); + e_query = sn_asprintf("&q=%s", tmp); + free(tmp); + } + + e_owner = gcli_urlencode(owner); + e_repo = gcli_urlencode(repo); + + url = sn_asprintf("%s/repos/%s/%s/issues?state=%s%s%s%s%s", + gcli_get_apibase(ctx), + e_owner, e_repo, + details->all ? "all" : "open", + e_author ? e_author : "", + e_label ? e_label : "", + e_milestone ? e_milestone : "", + e_query ? e_query : ""); + + free(e_query); + free(e_milestone); + free(e_author); + free(e_label); + free(e_owner); + free(e_repo); + + return github_fetch_issues(ctx, url, max, out); } int -gitea_get_issue_summary(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const issue_number, gcli_issue *const out) +gitea_get_issue_summary(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id const issue_number, + struct gcli_issue *const out) { return github_get_issue_summary(ctx, owner, repo, issue_number, out); } int -gitea_submit_issue(gcli_ctx *ctx, gcli_submit_issue_options opts, - gcli_fetch_buffer *const out) +gitea_submit_issue(struct gcli_ctx *ctx, struct gcli_submit_issue_options opts, + struct gcli_fetch_buffer *const out) { return github_perform_submit_issue(ctx,opts, out); } /* Gitea has closed, Github has close ... go figure */ static int -gitea_issue_patch_state(gcli_ctx *ctx, char const *owner, char const *repo, +gitea_issue_patch_state(struct gcli_ctx *ctx, char const *owner, char const *repo, int const issue_number, char const *const state) { - char *url = NULL; - char *data = NULL; - char *e_owner = NULL; - char *e_repo = NULL; + char *url = NULL, *payload = NULL, *e_owner = NULL, *e_repo = NULL; + struct gcli_jsongen gen = {0}; int rc = 0; + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "state"); + gcli_jsongen_string(&gen, state); + } + gcli_jsongen_end_object(&gen); + + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); + e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/repos/%s/%s/issues/%d", gcli_get_apibase(ctx), e_owner, e_repo, issue_number); - data = sn_asprintf("{ \"state\": \"%s\"}", state); - - rc = gcli_fetch_with_method(ctx, "PATCH", url, data, NULL, NULL); - free(data); - free(url); free(e_owner); free(e_repo); + rc = gcli_fetch_with_method(ctx, "PATCH", url, payload, NULL, NULL); + + free(payload); + free(url); + return rc; } int -gitea_issue_close(gcli_ctx *ctx, char const *owner, char const *repo, +gitea_issue_close(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const issue_number) { return gitea_issue_patch_state(ctx, owner, repo, issue_number, "closed"); } int -gitea_issue_reopen(gcli_ctx *ctx, char const *owner, char const *repo, +gitea_issue_reopen(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const issue_number) { return gitea_issue_patch_state(ctx, owner, repo, issue_number, "open"); } int -gitea_issue_assign(gcli_ctx *ctx, char const *owner, char const *repo, +gitea_issue_assign(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const issue_number, char const *const assignee) { - sn_sv escaped_assignee = SV_NULL; - char *post_fields = NULL; - char *url = NULL; - char *e_owner = NULL; - char *e_repo = NULL; + char *url = NULL, *e_owner = NULL, *e_repo = NULL, *payload = NULL; + struct gcli_jsongen gen = {0}; int rc = 0; - escaped_assignee = gcli_json_escape(SV((char *)assignee)); - post_fields = sn_asprintf("{ \"assignees\": [\""SV_FMT"\"] }", - SV_ARGS(escaped_assignee)); + /* Generate payload */ + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "assignees"); + gcli_jsongen_begin_array(&gen); + gcli_jsongen_string(&gen, assignee); + gcli_jsongen_end_array(&gen); + } + gcli_jsongen_end_object(&gen); + + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); + /* Generate URL */ e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/repos/%s/%s/issues/%"PRIid, gcli_get_apibase(ctx), e_owner, e_repo, issue_number); - rc = gcli_fetch_with_method(ctx, "PATCH", url, post_fields, NULL, NULL); - - free(escaped_assignee.data); - free(post_fields); free(e_owner); free(e_repo); + + rc = gcli_fetch_with_method(ctx, "PATCH", url, payload, NULL, NULL); + + free(payload); free(url); return rc; @@ -136,7 +201,7 @@ gitea_issue_assign(gcli_ctx *ctx, char const *owner, char const *repo, /* Return the stringified id of the given label */ static char * get_id_of_label(char const *label_name, - gcli_label_list const *const list) + struct gcli_label_list const *const list) { for (size_t i = 0; i < list->labels_size; ++i) if (strcmp(list->labels[i].name, label_name) == 0) @@ -154,10 +219,10 @@ free_id_list(char *list[], size_t const list_size) } static char ** -label_names_to_ids(gcli_ctx *ctx, char const *owner, char const *repo, +label_names_to_ids(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *const names[], size_t const names_size) { - gcli_label_list list = {0}; + struct gcli_label_list list = {0}; char **ids = NULL; size_t ids_size = 0; @@ -184,13 +249,12 @@ label_names_to_ids(gcli_ctx *ctx, char const *owner, char const *repo, } int -gitea_issue_add_labels(gcli_ctx *ctx, char const *owner, char const *repo, +gitea_issue_add_labels(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const issue, char const *const labels[], size_t const labels_size) { - char *list = NULL; - char *data = NULL; - char *url = NULL; + char *payload = NULL, *url = NULL, *e_owner = NULL, *e_repo = NULL; + struct gcli_jsongen gen = {0}; int rc = 0; /* First, convert to ids */ @@ -199,30 +263,46 @@ gitea_issue_add_labels(gcli_ctx *ctx, char const *owner, char const *repo, return -1; /* Construct json payload */ + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "labels"); + gcli_jsongen_begin_array(&gen); + for (size_t i = 0; i < labels_size; ++i) { + gcli_jsongen_string(&gen, ids[i]); + } + gcli_jsongen_end_array(&gen); + } + gcli_jsongen_end_object(&gen); - /* Note: http://www.c-faq.com/ansi/constmismatch.html */ - list = sn_join_with((char const **)ids, labels_size, ","); - data = sn_asprintf("{ \"labels\": [%s] }", list); + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); + free_id_list(ids, labels_size); - url = sn_asprintf("%s/repos/%s/%s/issues/%"PRIid"/labels", gcli_get_apibase(ctx), - owner, repo, issue); + e_owner = gcli_urlencode(owner); + e_repo = gcli_urlencode(repo); - rc = gcli_fetch_with_method(ctx, "POST", url, data, NULL, NULL); + url = sn_asprintf("%s/repos/%s/%s/issues/%"PRIid"/labels", + gcli_get_apibase(ctx), e_owner, e_repo, issue); - free(list); - free(data); + free(e_owner); + free(e_repo); + + rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, NULL); + + free(payload); free(url); - free_id_list(ids, labels_size); return rc; } int -gitea_issue_remove_labels(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const issue, char const *const labels[], - size_t const labels_size) +gitea_issue_remove_labels(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id const issue, + char const *const labels[], size_t const labels_size) { int rc = 0; + char *e_owner, *e_repo; /* Unfortunately the gitea api does not give us an endpoint to * delete labels from an issue in bulk. So, just iterate over the * given labels and delete them one after another. */ @@ -230,11 +310,15 @@ gitea_issue_remove_labels(gcli_ctx *ctx, char const *owner, char const *repo, if (!ids) return -1; + e_owner = gcli_urlencode(owner); + e_repo = gcli_urlencode(repo); + for (size_t i = 0; i < labels_size; ++i) { char *url = NULL; url = sn_asprintf("%s/repos/%s/%s/issues/%"PRIid"/labels/%s", - gcli_get_apibase(ctx), owner, repo, issue, ids[i]); + gcli_get_apibase(ctx), e_owner, e_repo, issue, + ids[i]); rc = gcli_fetch_with_method(ctx, "DELETE", url, NULL, NULL, NULL); free(url); @@ -243,13 +327,16 @@ gitea_issue_remove_labels(gcli_ctx *ctx, char const *owner, char const *repo, break; } + free(e_owner); + free(e_repo); + free_id_list(ids, labels_size); return rc; } int -gitea_issue_set_milestone(gcli_ctx *ctx, char const *const owner, +gitea_issue_set_milestone(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const issue, gcli_id const milestone) { @@ -257,14 +344,14 @@ gitea_issue_set_milestone(gcli_ctx *ctx, char const *const owner, } int -gitea_issue_clear_milestone(gcli_ctx *ctx, char const *owner, +gitea_issue_clear_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue) { return github_issue_set_milestone(ctx, owner, repo, issue, 0); } int -gitea_issue_set_title(gcli_ctx *ctx, char const *const owner, +gitea_issue_set_title(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const issue, char const *const new_title) { diff --git a/src/gitea/labels.c b/src/gitea/labels.c index b5d5ccdc..09f40e5e 100644 --- a/src/gitea/labels.c +++ b/src/gitea/labels.c @@ -36,25 +36,25 @@ #include int -gitea_get_labels(gcli_ctx *ctx, char const *owner, char const *reponame, - int max, gcli_label_list *const list) +gitea_get_labels(struct gcli_ctx *ctx, char const *owner, char const *reponame, + int max, struct gcli_label_list *const list) { return github_get_labels(ctx, owner, reponame, max, list); } int -gitea_create_label(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_label *const label) +gitea_create_label(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_label *const label) { return github_create_label(ctx, owner, repo, label); } int -gitea_delete_label(gcli_ctx *ctx, char const *owner, char const *repo, +gitea_delete_label(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *label) { char *url = NULL; - gcli_label_list list = {0}; + struct gcli_label_list list = {0}; int id = -1; int rc = 0; diff --git a/src/gitea/milestones.c b/src/gitea/milestones.c index 2e33dd72..9adda519 100644 --- a/src/gitea/milestones.c +++ b/src/gitea/milestones.c @@ -39,14 +39,14 @@ #include int -gitea_get_milestones(gcli_ctx *ctx, char const *const owner, +gitea_get_milestones(struct gcli_ctx *ctx, char const *const owner, char const *const repo, int const max, - gcli_milestone_list *const out) + struct gcli_milestone_list *const out) { char *url; char *e_owner, *e_repo; - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &out->milestones, .sizep = &out->milestones_size, .max = max, @@ -66,12 +66,12 @@ gitea_get_milestones(gcli_ctx *ctx, char const *const owner, } int -gitea_get_milestone(gcli_ctx *ctx, char const *const owner, +gitea_get_milestone(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone, - gcli_milestone *const out) + struct gcli_milestone *const out) { char *url, *e_owner, *e_repo; - gcli_fetch_buffer buffer = {0}; + struct gcli_fetch_buffer buffer = {0}; int rc = 0; e_owner = gcli_urlencode(owner); @@ -86,7 +86,7 @@ gitea_get_milestone(gcli_ctx *ctx, char const *const owner, rc = gcli_fetch(ctx, url, NULL, &buffer); if (rc == 0) { - json_stream stream = {0}; + struct json_stream stream = {0}; json_open_buffer(&stream, buffer.data, buffer.length); parse_gitea_milestone(ctx, &stream, out); @@ -100,16 +100,16 @@ gitea_get_milestone(gcli_ctx *ctx, char const *const owner, } int -gitea_create_milestone(gcli_ctx *ctx, +gitea_create_milestone(struct gcli_ctx *ctx, struct gcli_milestone_create_args const *args) { return github_create_milestone(ctx, args); } int -gitea_milestone_get_issues(gcli_ctx *ctx, char const *const owner, +gitea_milestone_get_issues(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone, - gcli_issue_list *const out) + struct gcli_issue_list *const out) { char *url, *e_owner, *e_repo; @@ -126,14 +126,14 @@ gitea_milestone_get_issues(gcli_ctx *ctx, char const *const owner, } int -gitea_delete_milestone(gcli_ctx *ctx, char const *const owner, +gitea_delete_milestone(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone) { return github_delete_milestone(ctx, owner, repo, milestone); } int -gitea_milestone_set_duedate(gcli_ctx *ctx, char const *const owner, +gitea_milestone_set_duedate(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone, char const *const date) { diff --git a/src/gitea/pulls.c b/src/gitea/pulls.c index c4233d41..66db5932 100644 --- a/src/gitea/pulls.c +++ b/src/gitea/pulls.c @@ -31,30 +31,33 @@ #include #include +#include + int -gitea_get_pulls(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_pull_fetch_details const *const details, int const max, - gcli_pull_list *const out) +gitea_get_pulls(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_pull_fetch_details const *const details, int const max, + struct gcli_pull_list *const out) { return github_get_pulls(ctx, owner, repo, details, max, out); } int -gitea_get_pull(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const pr_number, gcli_pull *const out) +gitea_get_pull(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const pr_number, struct gcli_pull *const out) { return github_get_pull(ctx, owner, repo, pr_number, out); } int -gitea_get_pull_commits(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const pr_number, gcli_commit_list *const out) +gitea_get_pull_commits(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id const pr_number, + struct gcli_commit_list *const out) { return github_get_pull_commits(ctx, owner, repo, pr_number, out); } int -gitea_pull_submit(gcli_ctx *ctx, gcli_submit_pull_options opts) +gitea_pull_submit(struct gcli_ctx *ctx, struct gcli_submit_pull_options opts) { warnx("In case the following process errors out, see: " "https://github.com/go-gitea/gitea/issues/20175"); @@ -62,38 +65,52 @@ gitea_pull_submit(gcli_ctx *ctx, gcli_submit_pull_options opts) } int -gitea_pull_merge(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const pr_number, enum gcli_merge_flags const flags) +gitea_pull_merge(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const pr, enum gcli_merge_flags const flags) { - int rc = 0; - char *url = NULL; - char *e_owner = NULL; - char *e_repo = NULL; - char *data = NULL; - bool const squash = flags & GCLI_PULL_MERGE_SQUASH; bool const delete_branch = flags & GCLI_PULL_MERGE_DELETEHEAD; + bool const squash = flags & GCLI_PULL_MERGE_SQUASH; + char *url = NULL, *e_owner = NULL, *e_repo = NULL, *payload = NULL; + struct gcli_jsongen gen = {0}; + int rc = 0; + + /* Generate payload */ + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "Do"); + gcli_jsongen_string(&gen, squash ? "squash" : "merge"); + gcli_jsongen_objmember(&gen, "delete_branch_after_merge"); + gcli_jsongen_bool(&gen, delete_branch); + } + gcli_jsongen_end_object(&gen); + + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); + + /* Generate URL */ e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); - url = sn_asprintf("%s/repos/%s/%s/pulls/%"PRIid"/merge", - gcli_get_apibase(ctx), e_owner, e_repo, pr_number); - data = sn_asprintf("{ \"Do\": \"%s\", \"delete_branch_after_merge\": %s }", - squash ? "squash" : "merge", - delete_branch ? "true" : "false"); - rc = gcli_fetch_with_method(ctx, "POST", url, data, NULL, NULL); + url = sn_asprintf("%s/repos/%s/%s/pulls/%"PRIid"/merge", + gcli_get_apibase(ctx), e_owner, e_repo, pr); - free(url); free(e_owner); free(e_repo); - free(data); + + rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, NULL); + + free(url); + free(payload); return rc; } static int -gitea_pulls_patch_state(gcli_ctx *ctx, char const *owner, char const *repo, - int const pr_number, char const *state) +gitea_pulls_patch_state(struct gcli_ctx *ctx, char const *owner, + char const *repo, int const pr_number, + char const *state) { char *url = NULL; char *data = NULL; @@ -122,21 +139,21 @@ gitea_pulls_patch_state(gcli_ctx *ctx, char const *owner, char const *repo, } int -gitea_pull_close(gcli_ctx *ctx, char const *owner, char const *repo, +gitea_pull_close(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const pr_number) { return gitea_pulls_patch_state(ctx, owner, repo, pr_number, "closed"); } int -gitea_pull_reopen(gcli_ctx *ctx, char const *owner, char const *repo, +gitea_pull_reopen(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const pr_number) { return gitea_pulls_patch_state(ctx, owner, repo, pr_number, "open"); } int -gitea_pull_get_patch(gcli_ctx *ctx, FILE *const stream, char const *owner, +gitea_pull_get_patch(struct gcli_ctx *ctx, FILE *const stream, char const *owner, char const *repo, gcli_id const pr_number) { char *url = NULL; @@ -161,7 +178,7 @@ gitea_pull_get_patch(gcli_ctx *ctx, FILE *const stream, char const *owner, return rc; } int -gitea_pull_get_diff(gcli_ctx *ctx, FILE *const stream, char const *owner, +gitea_pull_get_diff(struct gcli_ctx *ctx, FILE *const stream, char const *owner, char const *repo, gcli_id const pr_number) { char *url = NULL; @@ -187,8 +204,8 @@ gitea_pull_get_diff(gcli_ctx *ctx, FILE *const stream, char const *owner, } int -gitea_pull_get_checks(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const pr_number, gcli_pull_checks_list *out) +gitea_pull_get_checks(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const pr_number, struct gcli_pull_checks_list *out) { (void) ctx; (void) owner; @@ -200,16 +217,17 @@ gitea_pull_get_checks(gcli_ctx *ctx, char const *owner, char const *repo, } int -gitea_pull_set_milestone(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pr_number, gcli_id milestone_id) +gitea_pull_set_milestone(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id pr_number, + gcli_id milestone_id) { return github_issue_set_milestone(ctx, owner, repo, pr_number, milestone_id); } int -gitea_pull_clear_milestone(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pr_number) +gitea_pull_clear_milestone(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id pr_number) { /* NOTE: The github routine for clearing issues sets the milestone * to null (not the integer zero). However this does not work in @@ -219,14 +237,15 @@ gitea_pull_clear_milestone(gcli_ctx *ctx, char const *owner, char const *repo, } int -gitea_pull_add_reviewer(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pr_number, char const *username) +gitea_pull_add_reviewer(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id pr_number, + char const *username) { return github_pull_add_reviewer(ctx, owner, repo, pr_number, username); } int -gitea_pull_set_title(gcli_ctx *ctx, char const *const owner, +gitea_pull_set_title(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id pull, char const *const title) { diff --git a/src/gitea/releases.c b/src/gitea/releases.c index 6d78f79a..7d3c337a 100644 --- a/src/gitea/releases.c +++ b/src/gitea/releases.c @@ -31,34 +31,35 @@ #include #include +#include #include #include int -gitea_get_releases(gcli_ctx *ctx, char const *owner, char const *repo, - int const max, gcli_release_list *const list) +gitea_get_releases(struct gcli_ctx *ctx, char const *owner, char const *repo, + int const max, struct gcli_release_list *const list) { return github_get_releases(ctx, owner, repo, max, list); } static void -gitea_parse_release(gcli_ctx *ctx, gcli_fetch_buffer const *const buffer, - gcli_release *const out) +gitea_parse_release(struct gcli_ctx *ctx, struct gcli_fetch_buffer const *const buffer, + struct gcli_release *const out) { - json_stream stream = {0}; + struct json_stream stream = {0}; json_open_buffer(&stream, buffer->data, buffer->length); parse_github_release(ctx, &stream, out); json_close(&stream); } static int -gitea_upload_release_asset(gcli_ctx *ctx, char *const url, - gcli_release_asset_upload const asset) +gitea_upload_release_asset(struct gcli_ctx *ctx, char *const url, + struct gcli_release_asset_upload const asset) { char *e_assetname = NULL; char *request = NULL; - gcli_fetch_buffer buffer = {0}; + struct gcli_fetch_buffer buffer = {0}; int rc = 0; e_assetname = gcli_urlencode(asset.name); @@ -74,63 +75,63 @@ gitea_upload_release_asset(gcli_ctx *ctx, char *const url, } int -gitea_create_release(gcli_ctx *ctx, gcli_new_release const *release) +gitea_create_release(struct gcli_ctx *ctx, struct gcli_new_release const *release) { - char *commitish_json = NULL; - char *e_owner = NULL; - char *e_repo = NULL; - char *name_json = NULL; - char *post_data = NULL; - char *upload_url = NULL; - char *url = NULL; - gcli_fetch_buffer buffer = {0}; - gcli_release response = {0}; - sn_sv escaped_body = {0}; + char *e_owner = NULL, *e_repo = NULL, *payload = NULL, *upload_url = NULL, *url = NULL; + struct gcli_fetch_buffer buffer = {0}; + struct gcli_jsongen gen = {0}; + struct gcli_release response = {0}; int rc = 0; + /* Payload */ + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "tag_name"); + gcli_jsongen_string(&gen, release->tag); + + gcli_jsongen_objmember(&gen, "draft"); + gcli_jsongen_bool(&gen, release->draft); + + gcli_jsongen_objmember(&gen, "prerelease"); + gcli_jsongen_bool(&gen, release->prerelease); + + if (release->body) { + gcli_jsongen_objmember(&gen, "body"); + gcli_jsongen_string(&gen, release->body); + } + + if (release->commitish) { + gcli_jsongen_objmember(&gen, "target_commitish"); + gcli_jsongen_string(&gen, release->commitish); + } + + if (release->name) { + gcli_jsongen_objmember(&gen, "name"); + gcli_jsongen_string(&gen, release->name); + } + } + gcli_jsongen_end_object(&gen); + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); + + /* Generate URL */ e_owner = gcli_urlencode(release->owner); - e_repo = gcli_urlencode(release->repo); + e_repo = gcli_urlencode(release->repo); /* https://docs.github.com/en/rest/reference/repos#create-a-release */ - url = sn_asprintf( - "%s/repos/%s/%s/releases", - gcli_get_apibase(ctx), e_owner, e_repo); - - escaped_body = gcli_json_escape(release->body); - - if (release->commitish) - commitish_json = sn_asprintf( - ",\"target_commitish\": \"%s\"", - release->commitish); - - if (release->name) - name_json = sn_asprintf(",\"name\": \"%s\"", release->name); - - post_data = sn_asprintf( - "{" - " \"tag_name\": \"%s\"," - " \"draft\": %s," - " \"prerelease\": %s," - " \"body\": \""SV_FMT"\"" - " %s" - " %s" - "}", - release->tag, - gcli_json_bool(release->draft), - gcli_json_bool(release->prerelease), - SV_ARGS(escaped_body), - commitish_json ? commitish_json : "", - name_json ? name_json : ""); - - rc = gcli_fetch_with_method(ctx, "POST", url, post_data, NULL, &buffer); + url = sn_asprintf("%s/repos/%s/%s/releases", gcli_get_apibase(ctx), + e_owner, e_repo); + + rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, &buffer); if (rc < 0) goto out; gitea_parse_release(ctx, &buffer, &response); - upload_url = sn_asprintf("%s/repos/%s/%s/releases/"SV_FMT"/assets", + upload_url = sn_asprintf("%s/repos/%s/%s/releases/%s/assets", gcli_get_apibase(ctx), e_owner, e_repo, - SV_ARGS(response.id)); + response.id); for (size_t i = 0; i < release->assets_size; ++i) { printf("INFO : Uploading asset %s...\n", release->assets[i].path); @@ -140,22 +141,20 @@ gitea_create_release(gcli_ctx *ctx, gcli_new_release const *release) break; } + gcli_release_free(&response); out: + free(e_owner); + free(e_repo); free(upload_url); free(buffer.data); free(url); - free(post_data); - free(escaped_body.data); - free(e_owner); - free(e_repo); - free(name_json); - free(commitish_json); + free(payload); return rc; } int -gitea_delete_release(gcli_ctx *ctx, char const *owner, char const *repo, +gitea_delete_release(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *id) { return github_delete_release(ctx, owner, repo, id); diff --git a/src/gitea/repos.c b/src/gitea/repos.c index f2afef9d..8b8f6548 100644 --- a/src/gitea/repos.c +++ b/src/gitea/repos.c @@ -35,27 +35,28 @@ #include int -gitea_get_repos(gcli_ctx *ctx, char const *owner, int const max, - gcli_repo_list *const list) +gitea_get_repos(struct gcli_ctx *ctx, char const *owner, int const max, + struct gcli_repo_list *const list) { return github_get_repos(ctx, owner, max, list); } int -gitea_get_own_repos(gcli_ctx *ctx, int const max, gcli_repo_list *const list) +gitea_get_own_repos(struct gcli_ctx *ctx, int const max, + struct gcli_repo_list *const list) { return github_get_own_repos(ctx, max, list); } int -gitea_repo_create(gcli_ctx *ctx, gcli_repo_create_options const *options, - gcli_repo *const out) +gitea_repo_create(struct gcli_ctx *ctx, struct gcli_repo_create_options const *options, + struct gcli_repo *const out) { return github_repo_create(ctx, options, out); } int -gitea_repo_delete(gcli_ctx *ctx, char const *owner, char const *repo) +gitea_repo_delete(struct gcli_ctx *ctx, char const *owner, char const *repo) { return github_repo_delete(ctx, owner, repo); } @@ -63,7 +64,7 @@ gitea_repo_delete(gcli_ctx *ctx, char const *owner, char const *repo) /* Unlike Github and Gitlab, Gitea only supports private or non-private * (thus public) repositories. Separate implementation required. */ int -gitea_repo_set_visibility(gcli_ctx *ctx, char const *const owner, +gitea_repo_set_visibility(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_repo_visibility vis) { char *url; diff --git a/src/gitea/sshkeys.c b/src/gitea/sshkeys.c index 53715b46..9430ea79 100644 --- a/src/gitea/sshkeys.c +++ b/src/gitea/sshkeys.c @@ -37,20 +37,20 @@ #include int -gitea_get_sshkeys(gcli_ctx *ctx, gcli_sshkey_list *list) +gitea_get_sshkeys(struct gcli_ctx *ctx, struct gcli_sshkey_list *list) { return gitlab_get_sshkeys(ctx, list); } int -gitea_add_sshkey(gcli_ctx *ctx, char const *const title, - char const *const pubkey, gcli_sshkey *const out) +gitea_add_sshkey(struct gcli_ctx *ctx, char const *const title, + char const *const pubkey, struct gcli_sshkey *const out) { return gitlab_add_sshkey(ctx, title, pubkey, out); } int -gitea_delete_sshkey(gcli_ctx *ctx, gcli_id const id) +gitea_delete_sshkey(struct gcli_ctx *ctx, gcli_id const id) { return gitlab_delete_sshkey(ctx, id); } diff --git a/src/gitea/status.c b/src/gitea/status.c index dd54263d..fc5b878e 100644 --- a/src/gitea/status.c +++ b/src/gitea/status.c @@ -36,12 +36,12 @@ #include int -gitea_get_notifications(gcli_ctx *ctx, int const max, - gcli_notification_list *const out) +gitea_get_notifications(struct gcli_ctx *ctx, int const max, + struct gcli_notification_list *const out) { char *url = NULL; - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &out->notifications, .sizep = &out->notifications_size, .parse = (parsefn)(parse_gitea_notifications), @@ -53,7 +53,7 @@ gitea_get_notifications(gcli_ctx *ctx, int const max, } int -gitea_notification_mark_as_read(gcli_ctx *ctx, char const *id) +gitea_notification_mark_as_read(struct gcli_ctx *ctx, char const *id) { return github_notification_mark_as_read(ctx, id); } diff --git a/src/github/api.c b/src/github/api.c index 251e3d0d..74b8ab9d 100644 --- a/src/github/api.c +++ b/src/github/api.c @@ -35,9 +35,9 @@ #include char const * -github_api_error_string(gcli_ctx *ctx, gcli_fetch_buffer *const buf) +github_api_error_string(struct gcli_ctx *ctx, struct gcli_fetch_buffer *const buf) { - json_stream stream = {0}; + struct json_stream stream = {0}; int rc; char *msg; diff --git a/src/github/checks.c b/src/github/checks.c index d3378069..c0694fa4 100644 --- a/src/github/checks.c +++ b/src/github/checks.c @@ -41,10 +41,10 @@ #include int -github_get_checks(gcli_ctx *ctx, char const *owner, char const *repo, - char const *ref, int const max, github_check_list *const out) +github_get_checks(struct gcli_ctx *ctx, char const *owner, char const *repo, + char const *ref, int const max, struct github_check_list *const out) { - gcli_fetch_buffer buffer = {0}; + struct gcli_fetch_buffer buffer = {0}; char *url = NULL, *next_url = NULL; int rc = 0; @@ -78,7 +78,7 @@ github_get_checks(gcli_ctx *ctx, char const *owner, char const *repo, } void -gcli_github_check_free(gcli_github_check *check) +gcli_github_check_free(struct gcli_github_check *check) { free(check->name); free(check->status); @@ -88,7 +88,7 @@ gcli_github_check_free(gcli_github_check *check) } void -github_free_checks(github_check_list *const list) +github_free_checks(struct github_check_list *const list) { for (size_t i = 0; i < list->checks_size; ++i) { gcli_github_check_free(&list->checks[i]); diff --git a/src/github/comments.c b/src/github/comments.c index 3c986f44..27acb460 100644 --- a/src/github/comments.c +++ b/src/github/comments.c @@ -29,6 +29,7 @@ #include #include +#include #include #include @@ -36,39 +37,48 @@ #include int -github_perform_submit_comment(gcli_ctx *ctx, gcli_submit_comment_opts opts, - gcli_fetch_buffer *out) +github_perform_submit_comment(struct gcli_ctx *ctx, + struct gcli_submit_comment_opts opts, + struct gcli_fetch_buffer *out) { int rc = 0; + struct gcli_jsongen gen = {0}; char *e_owner = gcli_urlencode(opts.owner); char *e_repo = gcli_urlencode(opts.repo); + char *payload = NULL, *url = NULL; - char *post_fields = sn_asprintf( - "{ \"body\": \""SV_FMT"\" }", - SV_ARGS(opts.message)); - char *url = sn_asprintf( - "%s/repos/%s/%s/issues/%d/comments", - gcli_get_apibase(ctx), - e_owner, e_repo, opts.target_id); + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "body"); + gcli_jsongen_string(&gen, opts.message); + } + gcli_jsongen_end_object(&gen); - rc = gcli_fetch_with_method(ctx, "POST", url, post_fields, NULL, out); + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); - free(post_fields); + url = sn_asprintf("%s/repos/%s/%s/issues/%"PRIid"/comments", + gcli_get_apibase(ctx), e_owner, e_repo, opts.target_id); + + rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, out); + + free(payload); + free(url); free(e_owner); free(e_repo); - free(url); return rc; } int -github_get_comments(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const issue, gcli_comment_list *const out) +github_get_comments(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const issue, struct gcli_comment_list *const out) { char *e_owner = NULL; char *e_repo = NULL; char *url = NULL; - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &out->comments, .sizep = &out->comments_size, .parse = (parsefn)parse_github_comments, diff --git a/src/github/config.c b/src/github/config.c index edcc0f68..a90387af 100644 --- a/src/github/config.c +++ b/src/github/config.c @@ -31,7 +31,7 @@ #include char * -github_make_authheader(gcli_ctx *ctx, char const *token) +github_make_authheader(struct gcli_ctx *ctx, char const *token) { (void) ctx; return sn_asprintf("Authorization: token %s", token); diff --git a/src/github/forks.c b/src/github/forks.c index 68373f49..4e270c8b 100644 --- a/src/github/forks.c +++ b/src/github/forks.c @@ -37,21 +37,21 @@ #include int -github_get_forks(gcli_ctx *ctx, char const *owner, char const *repo, - int const max, gcli_fork_list *const list) +github_get_forks(struct gcli_ctx *ctx, char const *owner, char const *repo, + int const max, struct gcli_fork_list *const list) { char *url = NULL; char *e_owner = NULL; char *e_repo = NULL; - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &list->forks, .sizep = &list->forks_size, .max = max, .parse = (parsefn)(parse_github_forks), }; - *list = (gcli_fork_list) {0}; + *list = (struct gcli_fork_list) {0}; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); @@ -68,7 +68,7 @@ github_get_forks(gcli_ctx *ctx, char const *owner, char const *repo, } int -github_fork_create(gcli_ctx *ctx, char const *owner, char const *repo, +github_fork_create(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *_in) { char *url = NULL; diff --git a/src/github/gists.c b/src/github/gists.c index 177a8673..b44ada99 100644 --- a/src/github/gists.c +++ b/src/github/gists.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -41,8 +42,8 @@ /* /!\ Before changing this, see comment in gists.h /!\ */ int -parse_github_gist_files_idiot_hack(gcli_ctx *ctx, json_stream *stream, - gcli_gist *const gist) +parse_github_gist_files_idiot_hack(struct gcli_ctx *ctx, json_stream *stream, + struct gcli_gist *const gist) { (void) ctx; @@ -56,7 +57,7 @@ parse_github_gist_files_idiot_hack(gcli_ctx *ctx, json_stream *stream, while ((next = json_next(stream)) == JSON_STRING) { gist->files = realloc(gist->files, sizeof(*gist->files) * (gist->files_size + 1)); - gcli_gist_file *it = &gist->files[gist->files_size++]; + struct gcli_gist_file *it = &gist->files[gist->files_size++]; if (parse_github_gist_file(ctx, stream, it) < 0) return -1; } @@ -68,11 +69,11 @@ parse_github_gist_files_idiot_hack(gcli_ctx *ctx, json_stream *stream, } int -gcli_get_gists(gcli_ctx *ctx, char const *user, int const max, - gcli_gist_list *const list) +gcli_get_gists(struct gcli_ctx *ctx, char const *user, int const max, + struct gcli_gist_list *const list) { char *url = NULL; - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &list->gists, .sizep = &list->gists_size, .parse = (parsefn)(parse_github_gists), @@ -88,10 +89,10 @@ gcli_get_gists(gcli_ctx *ctx, char const *user, int const max, } int -gcli_get_gist(gcli_ctx *ctx, char const *gist_id, gcli_gist *out) +gcli_get_gist(struct gcli_ctx *ctx, char const *gist_id, struct gcli_gist *out) { char *url = NULL; - gcli_fetch_buffer buffer = {0}; + struct gcli_fetch_buffer buffer = {0}; int rc = 0; url = sn_asprintf("%s/gists/%s", gcli_get_apibase(ctx), gist_id); @@ -115,36 +116,49 @@ gcli_get_gist(gcli_ctx *ctx, char const *gist_id, gcli_gist *out) } #define READ_SZ 4096 -static size_t -read_file(FILE *f, char **out) +static char * +read_file(FILE *f) { size_t size = 0; - - *out = NULL; + char *out = NULL; while (!feof(f) && !ferror(f)) { - *out = realloc(*out, size + READ_SZ); - size_t bytes_read = fread(*out + size, 1, READ_SZ, f); + out = realloc(out, size + READ_SZ); + size_t bytes_read = fread(out + size, 1, READ_SZ, f); if (bytes_read == 0) break; + size += bytes_read; } - return size; + if (out) { + out = realloc(out, size + 1); + out[size] = '\0'; + } + + if (ferror(f)) { + free(out); + out = NULL; + } + + return out; } int -gcli_create_gist(gcli_ctx *ctx, gcli_new_gist opts) +gcli_create_gist(struct gcli_ctx *ctx, struct gcli_new_gist opts) { - char *url = NULL; + char *content = NULL; char *post_data = NULL; - gcli_fetch_buffer fetch_buffer = {0}; - sn_sv read_buffer = {0}; - sn_sv content = {0}; + char *url = NULL; int rc = 0; + struct gcli_fetch_buffer fetch_buffer = {0}; + struct gcli_jsongen gen = {0}; - read_buffer.length = read_file(opts.file, &read_buffer.data); - content = gcli_json_escape(read_buffer); + /* Read in the file content. this may come from stdin this we don't know + * the size in advance. */ + content = read_file(opts.file); + if (content == NULL) + return gcli_error(ctx, "failed to read from input file"); /* This API is documented very badly. In fact, I dug up how you're * supposed to do this from @@ -163,19 +177,40 @@ gcli_create_gist(gcli_ctx *ctx, gcli_new_gist opts) * } * } */ + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "description"); + gcli_jsongen_string(&gen, opts.gist_description); + + gcli_jsongen_objmember(&gen, "public"); + gcli_jsongen_bool(&gen, true); + + gcli_jsongen_objmember(&gen, "files"); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, opts.file_name); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "content"); + gcli_jsongen_string(&gen, content); + } + gcli_jsongen_end_object(&gen); + } + gcli_jsongen_end_object(&gen); + } + gcli_jsongen_end_object(&gen); + + post_data = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); - /* TODO: Escape gist_description and file_name */ + /* Generate URL */ url = sn_asprintf("%s/gists", gcli_get_apibase(ctx)); - post_data = sn_asprintf( - "{\"description\":\"%s\",\"public\":true,\"files\":" - "{\"%s\": {\"content\":\""SV_FMT"\"}}}", - opts.gist_description, - opts.file_name, - SV_ARGS(content)); + /* Perferm fetch */ rc = gcli_fetch_with_method(ctx, "POST", url, post_data, NULL, &fetch_buffer); - free(read_buffer.data); + free(content); free(fetch_buffer.data); free(url); free(post_data); @@ -184,10 +219,10 @@ gcli_create_gist(gcli_ctx *ctx, gcli_new_gist opts) } int -gcli_delete_gist(gcli_ctx *ctx, char const *gist_id) +gcli_delete_gist(struct gcli_ctx *ctx, char const *gist_id) { char *url = NULL; - gcli_fetch_buffer buffer = {0}; + struct gcli_fetch_buffer buffer = {0}; int rc = 0; url = sn_asprintf("%s/gists/%s", gcli_get_apibase(ctx), gist_id); @@ -201,20 +236,20 @@ gcli_delete_gist(gcli_ctx *ctx, char const *gist_id) } void -gcli_gist_free(gcli_gist *g) +gcli_gist_free(struct gcli_gist *g) { - free(g->id.data); - free(g->owner.data); - free(g->url.data); - free(g->date.data); - free(g->git_pull_url.data); - free(g->description.data); + free(g->id); + free(g->owner); + free(g->url); + free(g->date); + free(g->git_pull_url); + free(g->description); for (size_t j = 0; j < g->files_size; ++j) { - free(g->files[j].filename.data); - free(g->files[j].language.data); - free(g->files[j].url.data); - free(g->files[j].type.data); + free(g->files[j].filename); + free(g->files[j].language); + free(g->files[j].url); + free(g->files[j].type); } free(g->files); @@ -223,7 +258,7 @@ gcli_gist_free(gcli_gist *g) } void -gcli_gists_free(gcli_gist_list *const list) +gcli_gists_free(struct gcli_gist_list *const list) { for (size_t i = 0; i < list->gists_size; ++i) gcli_gist_free(&list->gists[i]); diff --git a/src/github/issues.c b/src/github/issues.c index f122001b..f1185aa4 100644 --- a/src/github/issues.c +++ b/src/github/issues.c @@ -47,14 +47,14 @@ * request issues. This function nukes them from the list, readjusts * the allocation size and fixes the reported list size. */ static void -github_hack_fixup_issues_that_are_actually_pulls(gcli_issue **list, size_t *size, +github_hack_fixup_issues_that_are_actually_pulls(struct gcli_issue **list, size_t *size, void *_data) { (void) _data; for (size_t i = *size; i > 0; --i) { if ((*list)[i-1].is_pr) { - gcli_issue *l = *list; + struct gcli_issue *l = *list; /* len = 7, i = 5, to move = 7 - 5 = 2 * 0 1 2 3 4 5 6 * | x | x | x | x | X | x | x | */ @@ -67,10 +67,10 @@ github_hack_fixup_issues_that_are_actually_pulls(gcli_issue **list, size_t *size } int -github_fetch_issues(gcli_ctx *ctx, char *url, int const max, - gcli_issue_list *const out) +github_fetch_issues(struct gcli_ctx *ctx, char *url, int const max, + struct gcli_issue_list *const out) { - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &out->issues, .sizep = &out->issues_size, .parse = (parsefn)(parse_github_issues), @@ -82,11 +82,11 @@ github_fetch_issues(gcli_ctx *ctx, char *url, int const max, } static int -get_milestone_id(gcli_ctx *ctx, char const *owner, char const *repo, +get_milestone_id(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *milestone_name, gcli_id *out) { int rc = 0; - gcli_milestone_list list = {0}; + struct gcli_milestone_list list = {0}; rc = github_get_milestones(ctx, owner, repo, -1, &list); if (rc < 0) @@ -108,8 +108,8 @@ get_milestone_id(gcli_ctx *ctx, char const *owner, char const *repo, } static int -parse_github_milestone(gcli_ctx *ctx, char const *owner, char const *repo, - char const *milestone, gcli_id *out) +parse_github_milestone(struct gcli_ctx *ctx, char const *owner, + char const *repo, char const *milestone, gcli_id *out) { char *endptr = NULL; size_t const m_len = strlen(milestone); @@ -123,10 +123,65 @@ parse_github_milestone(gcli_ctx *ctx, char const *owner, char const *repo, return get_milestone_id(ctx, owner, repo, milestone, out); } -int -github_get_issues(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_issue_fetch_details const *details, int const max, - gcli_issue_list *const out) +/* Search issues with a search term */ +static int +search_issues(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_issue_fetch_details const *details, int const max, + struct gcli_issue_list *const out) +{ + char *url = NULL, *query_string = NULL, *e_query_string = NULL, + *milestone = NULL, *author = NULL, *label = NULL; + int rc = 0; + struct gcli_fetch_buffer buffer = {0}; + struct json_stream stream = {0}; + + (void) max; + + if (details->milestone) + milestone = sn_asprintf("milestone:%s", details->milestone); + + if (details->author) + author = sn_asprintf("author:%s", details->author); + + if (details->label) + label = sn_asprintf("label:%s", details->label); + + query_string = sn_asprintf("repo:%s/%s is:issue%s %s %s %s %s", owner, repo, + details->all ? "" : " is:open", + milestone ? milestone : "", author ? author : "", + label ? label : "", details->search_term); + e_query_string = gcli_urlencode(query_string); + + url = sn_asprintf("%s/search/issues?q=%s", gcli_get_apibase(ctx), + e_query_string); + + free(milestone); + free(author); + free(label); + free(query_string); + free(e_query_string); + + rc = gcli_fetch(ctx, url, NULL, &buffer); + if (rc < 0) + goto error_fetch; + + json_open_buffer(&stream, buffer.data, buffer.length); + rc = parse_github_issue_search_result(ctx, &stream, out); + + json_close(&stream); + free(buffer.data); + +error_fetch: + free(url); + + return rc; +} + +/* Optimised routine for issues without a search term */ +static int +get_issues(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_issue_fetch_details const *details, int const max, + struct gcli_issue_list *const out) { char *url = NULL; char *e_owner = NULL; @@ -180,14 +235,27 @@ github_get_issues(gcli_ctx *ctx, char const *owner, char const *repo, } int -github_get_issue_summary(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const issue_number, gcli_issue *const out) +github_issues_search(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_issue_fetch_details const *details, + int const max, struct gcli_issue_list *const out) +{ + + if (details->search_term) + return search_issues(ctx, owner, repo, details, max, out); + else + return get_issues(ctx, owner, repo, details, max, out); +} + +int +github_get_issue_summary(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id const issue_number, + struct gcli_issue *const out) { char *url = NULL; char *e_owner = NULL; char *e_repo = NULL; - gcli_fetch_buffer buffer = {0}; - json_stream parser = {0}; + struct gcli_fetch_buffer buffer = {0}; + struct json_stream parser = {0}; int rc = 0; e_owner = gcli_urlencode(owner); @@ -214,29 +282,24 @@ github_get_issue_summary(gcli_ctx *ctx, char const *owner, char const *repo, return rc; } -int -github_issue_close(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const issue_number) +static int +github_issue_patch_state(struct gcli_ctx *ctx, char const *const owner, + char const *const repo, gcli_id const issue, + char const *const state) { - char *url = NULL; - char *data = NULL; - char *e_owner = NULL; - char *e_repo = NULL; + char *url = NULL, *payload = NULL, *e_owner = NULL, *e_repo = NULL; int rc = 0; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); - url = sn_asprintf( - "%s/repos/%s/%s/issues/%"PRIid, - gcli_get_apibase(ctx), - e_owner, e_repo, - issue_number); - data = sn_asprintf("{ \"state\": \"close\"}"); + url = sn_asprintf("%s/repos/%s/%s/issues/%"PRIid, gcli_get_apibase(ctx), + e_owner, e_repo, issue); + payload = sn_asprintf("{ \"state\": \"%s\"}", state); - rc = gcli_fetch_with_method(ctx, "PATCH", url, data, NULL, NULL); + rc = gcli_fetch_with_method(ctx, "PATCH", url, payload, NULL, NULL); - free(data); + free(payload); free(url); free(e_owner); free(e_repo); @@ -245,101 +308,107 @@ github_issue_close(gcli_ctx *ctx, char const *owner, char const *repo, } int -github_issue_reopen(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const issue_number) +github_issue_close(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const issue) { - char *url = NULL; - char *data = NULL; - char *e_owner = NULL; - char *e_repo = NULL; - int rc = 0; - - e_owner = gcli_urlencode(owner); - e_repo = gcli_urlencode(repo); - - url = sn_asprintf( - "%s/repos/%s/%s/issues/%"PRIid, - gcli_get_apibase(ctx), - e_owner, e_repo, - issue_number); - data = sn_asprintf("{ \"state\": \"open\"}"); - - rc = gcli_fetch_with_method(ctx, "PATCH", url, data, NULL, NULL); - - free(data); - free(url); - free(e_owner); - free(e_repo); + return github_issue_patch_state(ctx, owner, repo, issue, "closed"); +} - return rc; +int +github_issue_reopen(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const issue) +{ + return github_issue_patch_state(ctx, owner, repo, issue, "open"); } int -github_perform_submit_issue(gcli_ctx *ctx, gcli_submit_issue_options opts, - gcli_fetch_buffer *out) +github_perform_submit_issue(struct gcli_ctx *ctx, struct gcli_submit_issue_options opts, + struct gcli_fetch_buffer *out) { - char *e_owner = gcli_urlencode(opts.owner); - char *e_repo = gcli_urlencode(opts.repo); - sn_sv e_title = gcli_json_escape(opts.title); - sn_sv e_body = gcli_json_escape(opts.body); + char *e_owner = NULL, *e_repo = NULL, *payload = NULL, *url = NULL; + struct gcli_jsongen gen = {0}; int rc = 0; - char *post_fields = sn_asprintf( - "{ \"title\": \""SV_FMT"\", \"body\": \""SV_FMT"\" }", - SV_ARGS(e_title), SV_ARGS(e_body)); + /* Generate Payload */ + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "title"); + gcli_jsongen_string(&gen, opts.title); + + /* Body can be omitted and is NULL in that case */ + if (opts.body) { + gcli_jsongen_objmember(&gen, "body"); + gcli_jsongen_string(&gen, opts.body); + } + } + gcli_jsongen_begin_object(&gen); - char *url = sn_asprintf("%s/repos/%s/%s/issues", - gcli_get_apibase(ctx), e_owner, e_repo); + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); - rc = gcli_fetch_with_method(ctx, "POST", url, post_fields, NULL, out); + /* Generate URL */ + e_owner = gcli_urlencode(opts.owner); + e_repo = gcli_urlencode(opts.repo); + + url = sn_asprintf("%s/repos/%s/%s/issues", gcli_get_apibase(ctx), e_owner, + e_repo); free(e_owner); free(e_repo); - free(e_title.data); - free(e_body.data); - free(post_fields); + + rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, out); + + free(payload); free(url); return rc; } int -github_issue_assign(gcli_ctx *ctx, char const *owner, char const *repo, +github_issue_assign(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const issue_number, char const *assignee) { - sn_sv escaped_assignee = SV_NULL; - char *post_fields = NULL; - char *url = NULL; - char *e_owner = NULL; - char *e_repo = NULL; + struct gcli_jsongen gen = {0}; + char *url = NULL, *payload = NULL, *e_owner = NULL, *e_repo = NULL; int rc = 0; - escaped_assignee = gcli_json_escape(SV((char *)assignee)); - post_fields = sn_asprintf("{ \"assignees\": [\""SV_FMT"\"] }", - SV_ARGS(escaped_assignee)); + /* Generate Payload */ + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "assignees"); + gcli_jsongen_begin_array(&gen); + gcli_jsongen_string(&gen, assignee); + gcli_jsongen_end_object(&gen); + } + gcli_jsongen_end_object(&gen); + + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); + /* Generate URL */ e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); - url = sn_asprintf( - "%s/repos/%s/%s/issues/%"PRIid"/assignees", - gcli_get_apibase(ctx), e_owner, e_repo, issue_number); - - rc = gcli_fetch_with_method(ctx, "POST", url, post_fields, NULL, NULL); + url = sn_asprintf("%s/repos/%s/%s/issues/%"PRIid"/assignees", + gcli_get_apibase(ctx), e_owner, e_repo, issue_number); - free(escaped_assignee.data); - free(post_fields); free(e_owner); free(e_repo); + + rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, NULL); + free(url); + free(payload); return rc; } int -github_issue_add_labels(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const issue, char const *const labels[], - size_t const labels_size) +github_issue_add_labels(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id const issue, + char const *const labels[], size_t const labels_size) { char *url = NULL; char *data = NULL; @@ -364,9 +433,9 @@ github_issue_add_labels(gcli_ctx *ctx, char const *owner, char const *repo, } int -github_issue_remove_labels(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const issue, char const *const labels[], - size_t const labels_size) +github_issue_remove_labels(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id const issue, + char const *const labels[], size_t const labels_size) { char *url = NULL; char *e_label = NULL; @@ -391,7 +460,7 @@ github_issue_remove_labels(gcli_ctx *ctx, char const *owner, char const *repo, } int -github_issue_set_milestone(gcli_ctx *ctx, char const *const owner, +github_issue_set_milestone(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const issue, gcli_id const milestone) { @@ -418,10 +487,11 @@ github_issue_set_milestone(gcli_ctx *ctx, char const *const owner, } int -github_issue_clear_milestone(gcli_ctx *ctx, char const *const owner, +github_issue_clear_milestone(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const issue) { - char *url, *e_owner, *e_repo, *body; + char *url, *e_owner, *e_repo; + char const *payload; int rc; e_owner = gcli_urlencode(owner); @@ -431,11 +501,10 @@ github_issue_clear_milestone(gcli_ctx *ctx, char const *const owner, gcli_get_apibase(ctx), e_owner, e_repo, issue); - body = sn_asprintf("{ \"milestone\": null }"); + payload = "{ \"milestone\": null }"; - rc = gcli_fetch_with_method(ctx, "PATCH", url, body, NULL, NULL); + rc = gcli_fetch_with_method(ctx, "PATCH", url, payload, NULL, NULL); - free(body); free(url); free(e_repo); free(e_owner); @@ -444,12 +513,12 @@ github_issue_clear_milestone(gcli_ctx *ctx, char const *const owner, } int -github_issue_set_title(gcli_ctx *ctx, char const *const owner, +github_issue_set_title(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const issue, char const *const new_title) { char *url, *e_owner, *e_repo, *payload; - gcli_jsongen gen = {0}; + struct gcli_jsongen gen = {0}; int rc; /* Generate url */ diff --git a/src/github/labels.c b/src/github/labels.c index 46a344a3..d467c495 100644 --- a/src/github/labels.c +++ b/src/github/labels.c @@ -28,6 +28,7 @@ */ #include +#include #include #include @@ -35,18 +36,18 @@ #include int -github_get_labels(gcli_ctx *ctx, char const *owner, char const *reponame, - int const max, gcli_label_list *const out) +github_get_labels(struct gcli_ctx *ctx, char const *owner, char const *reponame, + int const max, struct gcli_label_list *const out) { char *url = NULL; - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &out->labels, .sizep= &out->labels_size, .parse = (parsefn)(parse_github_labels), .max = max, }; - *out = (gcli_label_list) {0}; + *out = (struct gcli_label_list) {0}; url = sn_asprintf( "%s/repos/%s/%s/labels", @@ -56,45 +57,50 @@ github_get_labels(gcli_ctx *ctx, char const *owner, char const *reponame, } int -github_create_label(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_label *const label) +github_create_label(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_label *const label) { - char *url = NULL; - char *data = NULL; - char *e_owner = NULL; - char *e_repo = NULL; - char *colour = NULL; - sn_sv label_name = SV_NULL; - sn_sv label_descr = SV_NULL; - sn_sv label_colour = SV_NULL; - gcli_fetch_buffer buffer = {0}; - struct json_stream stream = {0}; + char *url = NULL, *payload = NULL, *e_owner = NULL, *e_repo = NULL, + *colour = NULL; + struct gcli_fetch_buffer buffer = {0}; + struct gcli_jsongen gen = {0}; int rc = 0; + struct json_stream stream = {0}; - e_owner = gcli_urlencode(owner); - e_repo = gcli_urlencode(repo); + /* Generate payload */ + colour = sn_asprintf("%06X", label->colour & 0xFFFFFF); - colour = sn_asprintf("%06X", label->colour >> 8); + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "name"); + gcli_jsongen_string(&gen, label->name); - label_name = gcli_json_escape(SV(label->name)); - label_descr = gcli_json_escape(SV(label->description)); - label_colour = gcli_json_escape(SV(colour)); + gcli_jsongen_objmember(&gen, "description"); + gcli_jsongen_string(&gen, label->description); - /* /repos/{owner}/{repo}/labels */ - url = sn_asprintf("%s/repos/%s/%s/labels", - gcli_get_apibase(ctx), e_owner, e_repo); + gcli_jsongen_objmember(&gen, "color"); + gcli_jsongen_string(&gen, colour); + } + gcli_jsongen_end_object(&gen); + payload = gcli_jsongen_to_string(&gen); - data = sn_asprintf("{ " - " \"name\": \""SV_FMT"\", " - " \"description\": \""SV_FMT"\", " - " \"color\": \""SV_FMT"\"" - "}", - SV_ARGS(label_name), - SV_ARGS(label_descr), - SV_ARGS(label_colour)); + gcli_jsongen_free(&gen); + free(colour); - rc = gcli_fetch_with_method(ctx, "POST", url, data, NULL, &buffer); + /* Generate URL */ + e_owner = gcli_urlencode(owner); + e_repo = gcli_urlencode(repo); + + /* /repos/{owner}/{repo}/labels */ + url = sn_asprintf("%s/repos/%s/%s/labels", gcli_get_apibase(ctx), e_owner, + e_repo); + + free(e_owner); + free(e_repo); + + rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, &buffer); if (rc == 0) { json_open_buffer(&stream, buffer.data, buffer.length); @@ -103,20 +109,14 @@ github_create_label(gcli_ctx *ctx, char const *owner, char const *repo, } free(url); - free(data); - free(e_owner); - free(e_repo); - free(colour); - free(label_name.data); - free(label_descr.data); - free(label_colour.data); + free(payload); free(buffer.data); return rc; } int -github_delete_label(gcli_ctx *ctx, char const *owner, char const *repo, +github_delete_label(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *label) { char *url = NULL; diff --git a/src/github/milestones.c b/src/github/milestones.c index d3e7d02e..c10ad097 100644 --- a/src/github/milestones.c +++ b/src/github/milestones.c @@ -43,12 +43,12 @@ #include int -github_get_milestones(gcli_ctx *ctx, char const *const owner, +github_get_milestones(struct gcli_ctx *ctx, char const *const owner, char const *const repo, int const max, - gcli_milestone_list *const out) + struct gcli_milestone_list *const out) { char *url, *e_owner, *e_repo; - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &out->milestones, .sizep = &out->milestones_size, .parse = (parsefn)parse_github_milestones, @@ -69,12 +69,12 @@ github_get_milestones(gcli_ctx *ctx, char const *const owner, } int -github_get_milestone(gcli_ctx *ctx, char const *const owner, +github_get_milestone(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone, - gcli_milestone *const out) + struct gcli_milestone *const out) { char *url, *e_owner, *e_repo; - gcli_fetch_buffer buffer = {0}; + struct gcli_fetch_buffer buffer = {0}; int rc = 0; e_owner = gcli_urlencode(owner); @@ -88,7 +88,7 @@ github_get_milestone(gcli_ctx *ctx, char const *const owner, rc = gcli_fetch(ctx, url, NULL, &buffer); if (rc == 0) { - json_stream stream = {0}; + struct json_stream stream = {0}; json_open_buffer(&stream, buffer.data, buffer.length); parse_github_milestone(ctx, &stream, out); @@ -102,9 +102,9 @@ github_get_milestone(gcli_ctx *ctx, char const *const owner, } int -github_milestone_get_issues(gcli_ctx *ctx, char const *const owner, +github_milestone_get_issues(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone, - gcli_issue_list *const out) + struct gcli_issue_list *const out) { char *url, *e_owner, *e_repo; @@ -122,7 +122,7 @@ github_milestone_get_issues(gcli_ctx *ctx, char const *const owner, } int -github_create_milestone(gcli_ctx *ctx, +github_create_milestone(struct gcli_ctx *ctx, struct gcli_milestone_create_args const *args) { char *url, *e_owner, *e_repo; @@ -162,7 +162,7 @@ github_create_milestone(gcli_ctx *ctx, } int -github_delete_milestone(gcli_ctx *ctx, char const *const owner, +github_delete_milestone(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone) { char *url, *e_owner, *e_repo; @@ -186,7 +186,7 @@ github_delete_milestone(gcli_ctx *ctx, char const *const owner, } int -github_milestone_set_duedate(gcli_ctx *ctx, char const *const owner, +github_milestone_set_duedate(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone, char const *const date) { diff --git a/src/github/pulls.c b/src/github/pulls.c index f2e8b0a8..61ae2342 100644 --- a/src/github/pulls.c +++ b/src/github/pulls.c @@ -53,22 +53,22 @@ * allocation. */ static bool -pull_has_label(gcli_pull const *p, char const *const label) +pull_has_label(struct gcli_pull const *p, char const *const label) { for (size_t i = 0; i < p->labels_size; ++i) { - if (sn_sv_eq_to(p->labels[i], label)) + if (strcmp(p->labels[i], label) == 0) return true; } return false; } static void -github_pulls_filter(gcli_pull **listp, size_t *sizep, - gcli_pull_fetch_details const *details) +github_pulls_filter(struct gcli_pull **listp, size_t *sizep, + struct gcli_pull_fetch_details const *details) { for (size_t i = *sizep; i > 0; --i) { - gcli_pull *pulls = *listp; - gcli_pull *pull = &pulls[i-1]; + struct gcli_pull *pulls = *listp; + struct gcli_pull *pull = &pulls[i-1]; bool should_remove = false; if (details->author && strcmp(details->author, pull->author)) @@ -91,11 +91,11 @@ github_pulls_filter(gcli_pull **listp, size_t *sizep, } static int -github_fetch_pulls(gcli_ctx *ctx, char *url, - gcli_pull_fetch_details const *details, int max, - gcli_pull_list *const list) +github_fetch_pulls(struct gcli_ctx *ctx, char *url, + struct gcli_pull_fetch_details const *details, int max, + struct gcli_pull_list *const list) { - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &list->pulls, .sizep = &list->pulls_size, .parse = (parsefn)(parse_github_pulls), @@ -108,9 +108,9 @@ github_fetch_pulls(gcli_ctx *ctx, char *url, } int -github_get_pulls(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_pull_fetch_details const *const details, - int const max, gcli_pull_list *const list) +github_get_pulls(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_pull_fetch_details const *const details, + int const max, struct gcli_pull_list *const list) { char *url = NULL; char *e_owner = NULL; @@ -131,7 +131,7 @@ github_get_pulls(gcli_ctx *ctx, char const *owner, char const *repo, } int -github_pull_get_patch(gcli_ctx *ctx, FILE *stream, char const *owner, +github_pull_get_patch(struct gcli_ctx *ctx, FILE *stream, char const *owner, char const *repo, gcli_id const pr_number) { char *url = NULL; @@ -156,7 +156,7 @@ github_pull_get_patch(gcli_ctx *ctx, FILE *stream, char const *owner, } int -github_pull_get_diff(gcli_ctx *ctx, FILE *stream, char const *owner, +github_pull_get_diff(struct gcli_ctx *ctx, FILE *stream, char const *owner, char const *repo, gcli_id const pr_number) { char *url = NULL; @@ -183,10 +183,10 @@ github_pull_get_diff(gcli_ctx *ctx, FILE *stream, char const *owner, /* TODO: figure out a way to get rid of the 3 consecutive urlencode * calls */ static int -github_pull_delete_head_branch(gcli_ctx *ctx, char const *owner, +github_pull_delete_head_branch(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const pr_number) { - gcli_pull pull = {0}; + struct gcli_pull pull = {0}; char *url, *e_owner, *e_repo; char const *head_branch; int rc = 0; @@ -213,7 +213,7 @@ github_pull_delete_head_branch(gcli_ctx *ctx, char const *owner, } int -github_pull_merge(gcli_ctx *ctx, char const *owner, char const *repo, +github_pull_merge(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const pr_number, enum gcli_merge_flags const flags) { char *url = NULL; @@ -245,119 +245,175 @@ github_pull_merge(gcli_ctx *ctx, char const *owner, char const *repo, return rc; } -int -github_pull_close(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const pr_number) +static int +github_pull_patch_state(struct gcli_ctx *const ctx, char const *const owner, + char const *const repo, gcli_id const pr, + char const *const new_state) { - char *url = NULL; - char *e_owner = NULL; - char *e_repo = NULL; - char *data = NULL; + char *url = NULL, *e_owner = NULL, *e_repo = NULL, *payload = NULL; + struct gcli_jsongen gen = {0}; int rc = 0; + /* Generate payload */ + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "state"); + gcli_jsongen_string(&gen, new_state); + } + gcli_jsongen_end_object(&gen); + + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); + + /* Generate URL */ e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); - url = sn_asprintf( - "%s/repos/%s/%s/pulls/%"PRIid, - gcli_get_apibase(ctx), - e_owner, e_repo, pr_number); - data = sn_asprintf("{ \"state\": \"closed\"}"); - - rc = gcli_fetch_with_method(ctx, "PATCH", url, data, NULL, NULL); + url = sn_asprintf("%s/repos/%s/%s/pulls/%"PRIid, gcli_get_apibase(ctx), + e_owner, e_repo, pr); - free(url); free(e_repo); free(e_owner); - free(data); + + rc = gcli_fetch_with_method(ctx, "PATCH", url, payload, NULL, NULL); + + free(url); + free(payload); return rc; } int -github_pull_reopen(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const pr_number) +github_pull_close(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const pr) { - char *url = NULL; - char *data = NULL; - char *e_owner = NULL; - char *e_repo = NULL; - int rc = 0; + return github_pull_patch_state(ctx, owner, repo, pr, "closed"); +} - e_owner = gcli_urlencode(owner); - e_repo = gcli_urlencode(repo); +int +github_pull_reopen(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const pr) +{ + return github_pull_patch_state(ctx, owner, repo, pr, "open"); +} - url = sn_asprintf( - "%s/repos/%s/%s/pulls/%"PRIid, - gcli_get_apibase(ctx), - e_owner, e_repo, pr_number); - data = sn_asprintf("{ \"state\": \"open\"}"); +static int +github_pull_set_automerge(struct gcli_ctx *const ctx, char const *const node_id) +{ + char *url, *query, *payload; + int rc; + char const *const fmt = + "mutation updateAutomergeState {\n" + " enablePullRequestAutoMerge(input: {\n" + " pullRequestId: \"%s\",\n" + " mergeMethod: MERGE\n" + " }) {\n" + " clientMutationId\n" + " }\n" + "}\n"; + + struct gcli_jsongen gen = {0}; - rc = gcli_fetch_with_method(ctx, "PATCH", url, data, NULL, NULL); + query = sn_asprintf(fmt, node_id); + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "query"); + gcli_jsongen_string(&gen, query); + } + gcli_jsongen_end_object(&gen); + + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); + free(query); + + url = sn_asprintf("%s/graphql", gcli_get_apibase(ctx)); + + rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, NULL); + + free(payload); free(url); - free(data); - free(e_owner); - free(e_repo); return rc; } int -github_perform_submit_pull(gcli_ctx *ctx, gcli_submit_pull_options opts) +github_perform_submit_pull(struct gcli_ctx *ctx, struct gcli_submit_pull_options opts) { - sn_sv e_head, e_base, e_title, e_body; - gcli_fetch_buffer fetch_buffer = {0}; - struct json_stream json = {0}; - gcli_pull pull = {0}; + char *url = NULL, *payload = NULL, *e_owner = NULL, *e_repo = NULL; + struct gcli_fetch_buffer fetch_buffer = {0}; + struct gcli_jsongen gen = {0}; int rc = 0; - e_head = gcli_json_escape(opts.from); - e_base = gcli_json_escape(opts.to); - e_title = gcli_json_escape(opts.title); - e_body = gcli_json_escape(opts.body); - - char *post_fields = sn_asprintf( - "{\"head\":\""SV_FMT"\",\"base\":\""SV_FMT"\", " - "\"title\": \""SV_FMT"\", \"body\": \""SV_FMT"\" }", - SV_ARGS(e_head), - SV_ARGS(e_base), - SV_ARGS(e_title), - SV_ARGS(e_body)); - char *url = sn_asprintf( - "%s/repos/%s/%s/pulls", - gcli_get_apibase(ctx), - opts.owner, opts.repo); + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "head"); + gcli_jsongen_string(&gen, opts.from); + + gcli_jsongen_objmember(&gen, "base"); + gcli_jsongen_string(&gen, opts.to); + + gcli_jsongen_objmember(&gen, "title"); + gcli_jsongen_string(&gen, opts.title); + + /* Body is optional and will be NULL if unset */ + if (opts.body) { + gcli_jsongen_objmember(&gen, "body"); + gcli_jsongen_string(&gen, opts.body); + } + } + gcli_jsongen_end_object(&gen); + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); - rc = gcli_fetch_with_method(ctx, "POST", url, post_fields, NULL, - &fetch_buffer); + e_owner = gcli_urlencode(opts.owner); + e_repo = gcli_urlencode(opts.repo); + + url = sn_asprintf("%s/repos/%s/%s/pulls", gcli_get_apibase(ctx), e_owner, + e_repo); + + free(e_owner); + free(e_repo); + + rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, &fetch_buffer); /* Add labels if requested. GitHub doesn't allow us to do this all * with one request. */ - if (rc == 0 && opts.labels_size) { + if (rc == 0 && (opts.labels_size || opts.automerge)) { + struct json_stream json = {0}; + struct gcli_pull pull = {0}; + json_open_buffer(&json, fetch_buffer.data, fetch_buffer.length); parse_github_pull(ctx, &json, &pull); - github_issue_add_labels(ctx, opts.owner, opts.repo, pull.id, - opts.labels, opts.labels_size); + if (opts.labels_size) { + rc = github_issue_add_labels(ctx, opts.owner, opts.repo, pull.id, + (char const *const *)opts.labels, + opts.labels_size); + } + + if (rc == 0 && opts.automerge) { + /* pull.id is the global pull request ID */ + rc = github_pull_set_automerge(ctx, pull.node_id); + } gcli_pull_free(&pull); json_close(&json); } + free(fetch_buffer.data); - free(e_head.data); - free(e_base.data); - free(e_title.data); - free(e_body.data); - free(post_fields); + free(payload); free(url); return rc; } static void -filter_commit_short_sha(gcli_commit **listp, size_t *sizep, void *_data) +filter_commit_short_sha(struct gcli_commit **listp, size_t *sizep, void *_data) { (void) _data; @@ -366,14 +422,15 @@ filter_commit_short_sha(gcli_commit **listp, size_t *sizep, void *_data) } int -github_get_pull_commits(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const pr_number, gcli_commit_list *const out) +github_get_pull_commits(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id const pr, + struct gcli_commit_list *const out) { char *url = NULL; char *e_owner = NULL; char *e_repo = NULL; - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &out->commits, .sizep = &out->commits_size, .max = -1, @@ -384,10 +441,8 @@ github_get_pull_commits(gcli_ctx *ctx, char const *owner, char const *repo, e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); - url = sn_asprintf( - "%s/repos/%s/%s/pulls/%"PRIid"/commits", - gcli_get_apibase(ctx), - e_owner, e_repo, pr_number); + url = sn_asprintf("%s/repos/%s/%s/pulls/%"PRIid"/commits", + gcli_get_apibase(ctx), e_owner, e_repo, pr); free(e_owner); free(e_repo); @@ -396,28 +451,25 @@ github_get_pull_commits(gcli_ctx *ctx, char const *owner, char const *repo, } int -github_get_pull(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const pr_number, gcli_pull *const out) +github_get_pull(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const pr, struct gcli_pull *const out) { int rc = 0; - gcli_fetch_buffer json_buffer = {0}; - char *url = NULL; - char *e_owner = NULL; - char *e_repo = NULL; + struct gcli_fetch_buffer json_buffer = {0}; + char *url = NULL, *e_owner = NULL, *e_repo = NULL; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); - url = sn_asprintf( - "%s/repos/%s/%s/pulls/%"PRIid, - gcli_get_apibase(ctx), - e_owner, e_repo, pr_number); + url = sn_asprintf("%s/repos/%s/%s/pulls/%"PRIid, gcli_get_apibase(ctx), + e_owner, e_repo, pr); + free(e_owner); free(e_repo); rc = gcli_fetch(ctx, url, NULL, &json_buffer); if (rc == 0) { - json_stream stream = {0}; + struct json_stream stream = {0}; json_open_buffer(&stream, json_buffer.data, json_buffer.length); parse_github_pull(ctx, &stream, out); @@ -431,8 +483,8 @@ github_get_pull(gcli_ctx *ctx, char const *owner, char const *repo, } int -github_pull_get_checks(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const pr_number, gcli_pull_checks_list *out) +github_pull_get_checks(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const pr_number, struct gcli_pull_checks_list *out) { char refname[64] = {0}; @@ -441,16 +493,17 @@ github_pull_get_checks(gcli_ctx *ctx, char const *owner, char const *repo, snprintf(refname, sizeof refname, "refs%%2Fpull%%2F%"PRIid"%%2Fhead", pr_number); return github_get_checks(ctx, owner, repo, refname, -1, - (github_check_list *)out); + (struct github_check_list *)out); } int -github_pull_add_reviewer(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pr_number, char const *username) +github_pull_add_reviewer(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id pr_number, + char const *username) { int rc = 0; char *url, *payload, *e_owner, *e_repo; - gcli_jsongen gen = {0}; + struct gcli_jsongen gen = {0}; /* URL-encode repo and owner */ e_owner = gcli_urlencode(owner); @@ -487,12 +540,12 @@ github_pull_add_reviewer(gcli_ctx *ctx, char const *owner, char const *repo, } int -github_pull_set_title(gcli_ctx *ctx, char const *owner, char const *repo, +github_pull_set_title(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pull, char const *new_title) { char *url, *e_owner, *e_repo, *payload; int rc; - gcli_jsongen gen = {0}; + struct gcli_jsongen gen = {0}; /* Generate the url */ e_owner = gcli_urlencode(owner); diff --git a/src/github/releases.c b/src/github/releases.c index 46df9cd2..12d2695b 100644 --- a/src/github/releases.c +++ b/src/github/releases.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -38,21 +39,21 @@ #include int -github_get_releases(gcli_ctx *ctx, char const *owner, char const *repo, - int const max, gcli_release_list *const list) +github_get_releases(struct gcli_ctx *ctx, char const *owner, char const *repo, + int const max, struct gcli_release_list *const list) { char *url = NULL; char *e_owner = NULL; char *e_repo = NULL; - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &list->releases, .sizep = &list->releases_size, .max = max, .parse = (parsefn)(parse_github_releases), }; - *list = (gcli_release_list) {0}; + *list = (struct gcli_release_list) {0}; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); @@ -69,8 +70,8 @@ github_get_releases(gcli_ctx *ctx, char const *owner, char const *repo, } static void -github_parse_single_release(gcli_ctx *ctx, gcli_fetch_buffer buffer, - gcli_release *const out) +github_parse_single_release(struct gcli_ctx *ctx, struct gcli_fetch_buffer buffer, + struct gcli_release *const out) { struct json_stream stream = {0}; @@ -81,25 +82,26 @@ github_parse_single_release(gcli_ctx *ctx, gcli_fetch_buffer buffer, } static int -github_get_upload_url(gcli_ctx *ctx, gcli_release *const it, char **out) +github_get_upload_url(struct gcli_ctx *ctx, struct gcli_release *const it, + char **out) { - char *delim = strchr(it->upload_url.data, '{'); + char *delim = strchr(it->upload_url, '{'); if (delim == NULL) return gcli_error(ctx, "GitHub API returned an invalid upload url"); - size_t len = delim - it->upload_url.data; - *out = sn_strndup(it->upload_url.data, len); + size_t len = delim - it->upload_url; + *out = sn_strndup(it->upload_url, len); return 0; } static int -github_upload_release_asset(gcli_ctx *ctx, char const *url, - gcli_release_asset_upload const asset) +github_upload_release_asset(struct gcli_ctx *ctx, char const *url, + struct gcli_release_asset_upload const asset) { char *req = NULL; sn_sv file_content = {0}; - gcli_fetch_buffer buffer = {0}; + struct gcli_fetch_buffer buffer = {0}; int rc = 0; file_content.length = sn_mmap_file(asset.path, (void **)&file_content.data); @@ -124,59 +126,58 @@ github_upload_release_asset(gcli_ctx *ctx, char const *url, } int -github_create_release(gcli_ctx *ctx, gcli_new_release const *release) +github_create_release(struct gcli_ctx *ctx, struct gcli_new_release const *release) { - char *url = NULL; - char *e_owner = NULL; - char *e_repo = NULL; - char *upload_url = NULL; - char *post_data = NULL; - char *name_json = NULL; - char *commitish_json = NULL; - sn_sv escaped_body = {0}; - gcli_fetch_buffer buffer = {0}; - gcli_release response = {0}; + char *url = NULL, *e_owner = NULL, *e_repo = NULL, *upload_url = NULL, + *payload = NULL; + struct gcli_fetch_buffer buffer = {0}; + struct gcli_jsongen gen = {0}; + struct gcli_release response = {0}; int rc = 0; - assert(release); + /* Payload */ + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "tag_name"); + gcli_jsongen_string(&gen, release->tag); + + gcli_jsongen_objmember(&gen, "draft"); + gcli_jsongen_bool(&gen, release->draft); + + gcli_jsongen_objmember(&gen, "prerelease"); + gcli_jsongen_bool(&gen, release->prerelease); + + if (release->body) { + gcli_jsongen_objmember(&gen, "body"); + gcli_jsongen_string(&gen, release->body); + } + + if (release->commitish) { + gcli_jsongen_objmember(&gen, "target_commitish"); + gcli_jsongen_string(&gen, release->commitish); + } + + if (release->name) { + gcli_jsongen_objmember(&gen, "name"); + gcli_jsongen_string(&gen, release->name); + } + } + gcli_jsongen_end_object(&gen); + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); e_owner = gcli_urlencode(release->owner); e_repo = gcli_urlencode(release->repo); /* https://docs.github.com/en/rest/reference/repos#create-a-release */ - url = sn_asprintf( - "%s/repos/%s/%s/releases", - gcli_get_apibase(ctx), e_owner, e_repo); - - escaped_body = gcli_json_escape(release->body); - - if (release->commitish) - commitish_json = sn_asprintf( - ",\"target_commitish\": \"%s\"", - release->commitish); - - if (release->name) - name_json = sn_asprintf( - ",\"name\": \"%s\"", - release->name); - - post_data = sn_asprintf( - "{" - " \"tag_name\": \"%s\"," - " \"draft\": %s," - " \"prerelease\": %s," - " \"body\": \""SV_FMT"\"" - " %s" - " %s" - "}", - release->tag, - gcli_json_bool(release->draft), - gcli_json_bool(release->prerelease), - SV_ARGS(escaped_body), - commitish_json ? commitish_json : "", - name_json ? name_json : ""); - - rc = gcli_fetch_with_method(ctx, "POST", url, post_data, NULL, &buffer); + url = sn_asprintf("%s/repos/%s/%s/releases", gcli_get_apibase(ctx), + e_owner, e_repo); + + free(e_owner); + free(e_repo); + + rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, &buffer); if (rc < 0) goto out; @@ -195,21 +196,17 @@ github_create_release(gcli_ctx *ctx, gcli_new_release const *release) } out: + gcli_release_free(&response); free(upload_url); free(buffer.data); free(url); - free(post_data); - free(escaped_body.data); - free(e_owner); - free(e_repo); - free(name_json); - free(commitish_json); + free(payload); return rc; } int -github_delete_release(gcli_ctx *ctx, char const *owner, char const *repo, +github_delete_release(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *id) { char *url = NULL; diff --git a/src/github/repos.c b/src/github/repos.c index 83a848a5..32d5de3e 100644 --- a/src/github/repos.c +++ b/src/github/repos.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -39,14 +40,14 @@ #include int -github_get_repos(gcli_ctx *ctx, char const *owner, int const max, - gcli_repo_list *const list) +github_get_repos(struct gcli_ctx *ctx, char const *owner, int const max, + struct gcli_repo_list *const list) { char *url = NULL; char *e_owner = NULL; int rc = 0; - gcli_fetch_list_ctx lf = { + struct gcli_fetch_list_ctx lf = { .listp = &list->repos, .sizep = &list->repos_size, .max = max, @@ -88,10 +89,11 @@ github_get_repos(gcli_ctx *ctx, char const *owner, int const max, } int -github_get_own_repos(gcli_ctx *ctx, int const max, gcli_repo_list *const list) +github_get_own_repos(struct gcli_ctx *ctx, int const max, + struct gcli_repo_list *const list) { char *url = NULL; - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &list->repos, .sizep = &list->repos_size, .max = max, @@ -104,7 +106,7 @@ github_get_own_repos(gcli_ctx *ctx, int const max, gcli_repo_list *const list) } int -github_repo_delete(gcli_ctx *ctx, char const *owner, char const *repo) +github_repo_delete(struct gcli_ctx *ctx, char const *owner, char const *repo) { char *url = NULL; char *e_owner = NULL; @@ -128,31 +130,38 @@ github_repo_delete(gcli_ctx *ctx, char const *owner, char const *repo) } int -github_repo_create(gcli_ctx *ctx, gcli_repo_create_options const *options, - gcli_repo *const out) +github_repo_create(struct gcli_ctx *ctx, struct gcli_repo_create_options const *options, + struct gcli_repo *const out) { - char *url, *data; - gcli_fetch_buffer buffer = {0}; + char *url, *payload; + struct gcli_fetch_buffer buffer = {0}; + struct gcli_jsongen gen = {0}; struct json_stream stream = {0}; - sn_sv e_name, e_description; int rc = 0; /* Request preparation */ url = sn_asprintf("%s/user/repos", gcli_get_apibase(ctx)); - /* JSON-escape repo name and description */ - e_name = gcli_json_escape(options->name); - e_description = gcli_json_escape(options->description); - /* Construct payload */ - data = sn_asprintf("{\"name\": \""SV_FMT"\"," - " \"description\": \""SV_FMT"\"," - " \"private\": %s }", - SV_ARGS(e_name), SV_ARGS(e_description), - gcli_json_bool(options->private)); + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "name"); + gcli_jsongen_string(&gen, options->name); + + gcli_jsongen_objmember(&gen, "description"); + gcli_jsongen_string(&gen, options->description); + + gcli_jsongen_objmember(&gen, "private"); + gcli_jsongen_bool(&gen, options->private); + } + gcli_jsongen_end_object(&gen); + + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); /* Fetch and parse result */ - rc = gcli_fetch_with_method(ctx, "POST", url, data, NULL, + rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, out ? &buffer : NULL); if (rc == 0 && out) { @@ -163,16 +172,14 @@ github_repo_create(gcli_ctx *ctx, gcli_repo_create_options const *options, /* Cleanup */ free(buffer.data); - free(e_name.data); - free(e_description.data); - free(data); + free(payload); free(url); return rc; } int -github_repo_set_visibility(gcli_ctx *ctx, char const *const owner, +github_repo_set_visibility(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_repo_visibility vis) { char *url; diff --git a/src/github/sshkeys.c b/src/github/sshkeys.c index 33bbc481..212ca1e1 100644 --- a/src/github/sshkeys.c +++ b/src/github/sshkeys.c @@ -34,20 +34,20 @@ #include int -github_get_sshkeys(gcli_ctx *ctx, gcli_sshkey_list *out) +github_get_sshkeys(struct gcli_ctx *ctx, struct gcli_sshkey_list *out) { return gitlab_get_sshkeys(ctx, out); } int -github_add_sshkey(gcli_ctx *ctx, char const *const title, - char const *const pubkey, gcli_sshkey *out) +github_add_sshkey(struct gcli_ctx *ctx, char const *const title, + char const *const pubkey, struct gcli_sshkey *out) { return gitlab_add_sshkey(ctx, title, pubkey, out); } int -github_delete_sshkey(gcli_ctx *ctx, gcli_id const id) +github_delete_sshkey(struct gcli_ctx *ctx, gcli_id const id) { return gitlab_delete_sshkey(ctx, id); } diff --git a/src/github/status.c b/src/github/status.c index 8e0d4757..14ea8814 100644 --- a/src/github/status.c +++ b/src/github/status.c @@ -37,12 +37,12 @@ #include int -github_get_notifications(gcli_ctx *ctx, int const max, - gcli_notification_list *const out) +github_get_notifications(struct gcli_ctx *ctx, int const max, + struct gcli_notification_list *const out) { char *url = NULL; - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &out->notifications, .sizep = &out->notifications_size, .parse = (parsefn)(parse_github_notifications), @@ -54,7 +54,7 @@ github_get_notifications(gcli_ctx *ctx, int const max, } int -github_notification_mark_as_read(gcli_ctx *ctx, char const *id) +github_notification_mark_as_read(struct gcli_ctx *ctx, char const *id) { char *url = NULL; int rc = 0; diff --git a/src/gitlab/api.c b/src/gitlab/api.c index 95aebe7f..ea87a745 100644 --- a/src/gitlab/api.c +++ b/src/gitlab/api.c @@ -35,26 +35,34 @@ #include char const * -gitlab_api_error_string(gcli_ctx *ctx, gcli_fetch_buffer *const buf) +gitlab_api_error_string(struct gcli_ctx *ctx, struct gcli_fetch_buffer *const buf) { - char *msg; + char *msg = NULL; int rc; - json_stream stream = {0}; + struct json_stream stream = {0}; json_open_buffer(&stream, buf->data, buf->length); rc = parse_gitlab_get_error(ctx, &stream, &msg); json_close(&stream); - if (rc < 0) - return strdup("no error message: failed to parse error response"); - else + if (rc < 0 || msg == NULL) { + if (sn_verbose()) { + return sn_asprintf("Could not parse Gitlab error response. " + "The response was:\n\n%.*s\n", + (int)buf->length, buf->data); + } else { + return strdup("no error message: failed to parse error response. " + "Please run the gcli query with verbose mode again."); + } + } else { return msg; + } } int -gitlab_user_id(gcli_ctx *ctx, char const *user_name) +gitlab_user_id(struct gcli_ctx *ctx, char const *user_name) { - gcli_fetch_buffer buffer = {0}; + struct gcli_fetch_buffer buffer = {0}; struct json_stream stream = {0}; char *url = NULL; char *e_username; diff --git a/src/gitlab/comments.c b/src/gitlab/comments.c index 85d39658..009b684b 100644 --- a/src/gitlab/comments.c +++ b/src/gitlab/comments.c @@ -29,17 +29,18 @@ #include #include +#include #include #include int -gitlab_perform_submit_comment(gcli_ctx *ctx, gcli_submit_comment_opts opts, - gcli_fetch_buffer *const out) +gitlab_perform_submit_comment(struct gcli_ctx *ctx, struct gcli_submit_comment_opts opts, + struct gcli_fetch_buffer *const out) { + char *url = NULL, *payload = NULL, *e_owner = NULL, *e_repo = NULL; char const *type = NULL; - char *e_owner = NULL; - char *e_repo = NULL; + struct gcli_jsongen gen = {0}; int rc = 0; e_owner = gcli_urlencode(opts.owner); @@ -54,32 +55,39 @@ gitlab_perform_submit_comment(gcli_ctx *ctx, gcli_submit_comment_opts opts, break; } - char *post_fields = sn_asprintf( - "{ \"body\": \""SV_FMT"\" }", - SV_ARGS(opts.message)); - char *url = sn_asprintf( - "%s/projects/%s%%2F%s/%s/%d/notes", - gcli_get_apibase(ctx), - e_owner, e_repo, type, opts.target_id); + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "body"); + gcli_jsongen_string(&gen, opts.message); + } + gcli_jsongen_end_object(&gen); + + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); - rc = gcli_fetch_with_method(ctx, "POST", url, post_fields, NULL, out); + url = sn_asprintf("%s/project/%s%%2F%s/%s/%"PRIid"/notes", + gcli_get_apibase(ctx), e_owner, e_repo, type, + opts.target_id); - free(post_fields); + rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, out); + + free(payload); + free(url); free(e_owner); free(e_repo); - free(url); return rc; } int -gitlab_get_mr_comments(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const mr, gcli_comment_list *const out) +gitlab_get_mr_comments(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const mr, struct gcli_comment_list *const out) { char *e_owner = gcli_urlencode(owner); char *e_repo = gcli_urlencode(repo); - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &out->comments, .sizep = &out->comments_size, .parse = (parsefn)parse_gitlab_comments, @@ -98,13 +106,14 @@ gitlab_get_mr_comments(gcli_ctx *ctx, char const *owner, char const *repo, } int -gitlab_get_issue_comments(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const issue, gcli_comment_list *const out) +gitlab_get_issue_comments(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id const issue, + struct gcli_comment_list *const out) { char *e_owner = gcli_urlencode(owner); char *e_repo = gcli_urlencode(repo); - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &out->comments, .sizep = &out->comments_size, .parse = (parsefn)parse_gitlab_comments, diff --git a/src/gitlab/config.c b/src/gitlab/config.c index dc911f16..0161b034 100644 --- a/src/gitlab/config.c +++ b/src/gitlab/config.c @@ -32,7 +32,7 @@ #include char * -gitlab_make_authheader(gcli_ctx *ctx, char const *token) +gitlab_make_authheader(struct gcli_ctx *ctx, char const *token) { (void) ctx; return sn_asprintf("PRIVATE-TOKEN: %s", token); diff --git a/src/gitlab/forks.c b/src/gitlab/forks.c index a0a63d1e..53477b9d 100644 --- a/src/gitlab/forks.c +++ b/src/gitlab/forks.c @@ -37,14 +37,14 @@ #include int -gitlab_get_forks(gcli_ctx *ctx, char const *owner, char const *repo, - int const max, gcli_fork_list *const list) +gitlab_get_forks(struct gcli_ctx *ctx, char const *owner, char const *repo, + int const max, struct gcli_fork_list *const list) { char *url = NULL; char *e_owner = NULL; char *e_repo = NULL; - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &list->forks, .sizep = &list->forks_size, .parse = (parsefn)parse_gitlab_forks, @@ -54,7 +54,7 @@ gitlab_get_forks(gcli_ctx *ctx, char const *owner, char const *repo, e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); - *list = (gcli_fork_list) {0}; + *list = (struct gcli_fork_list) {0}; url = sn_asprintf("%s/projects/%s%%2F%s/forks", gcli_get_apibase(ctx), e_owner, e_repo); @@ -66,7 +66,7 @@ gitlab_get_forks(gcli_ctx *ctx, char const *owner, char const *repo, } int -gitlab_fork_create(gcli_ctx *ctx, char const *owner, char const *repo, +gitlab_fork_create(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *_in) { char *url = NULL; diff --git a/src/gitlab/issues.c b/src/gitlab/issues.c index 386cfed0..fe6ea433 100644 --- a/src/gitlab/issues.c +++ b/src/gitlab/issues.c @@ -40,10 +40,10 @@ /** Given the url fetch issues */ int -gitlab_fetch_issues(gcli_ctx *ctx, char *url, int const max, - gcli_issue_list *const out) +gitlab_fetch_issues(struct gcli_ctx *ctx, char *url, int const max, + struct gcli_issue_list *const out) { - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &out->issues, .sizep = &out->issues_size, .max = max, @@ -54,9 +54,9 @@ gitlab_fetch_issues(gcli_ctx *ctx, char *url, int const max, } int -gitlab_get_issues(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_issue_fetch_details const *details, int const max, - gcli_issue_list *const out) +gitlab_issues_search(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_issue_fetch_details const *details, + int const max, struct gcli_issue_list *const out) { char *url = NULL; char *e_owner = NULL; @@ -64,6 +64,7 @@ gitlab_get_issues(gcli_ctx *ctx, char const *owner, char const *repo, char *e_author = NULL; char *e_labels = NULL; char *e_milestone = NULL; + char *e_search = NULL; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); @@ -94,11 +95,20 @@ gitlab_get_issues(gcli_ctx *ctx, char const *owner, char const *repo, free(tmp); } - url = sn_asprintf("%s/projects/%s%%2F%s/issues%s%s%s%s", + if (details->search_term) { + char *tmp = gcli_urlencode(details->search_term); + int const should_do_qmark = details->all && !details->author && + !details->label && !details->milestone; + e_search = sn_asprintf("%csearch=%s", should_do_qmark ? '?': '&', tmp); + free(tmp); + } + + url = sn_asprintf("%s/projects/%s%%2F%s/issues%s%s%s%s%s", gcli_get_apibase(ctx), e_owner, e_repo, details->all ? "" : "?state=opened", e_author ? e_author : "", e_labels ? e_labels : "", - e_milestone ? e_milestone : ""); + e_milestone ? e_milestone : "", + e_search ? e_search : ""); free(e_milestone); free(e_author); @@ -110,14 +120,15 @@ gitlab_get_issues(gcli_ctx *ctx, char const *owner, char const *repo, } int -gitlab_get_issue_summary(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const issue_number, gcli_issue *const out) +gitlab_get_issue_summary(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id const issue_number, + struct gcli_issue *const out) { char *url = NULL; char *e_owner = NULL; char *e_repo = NULL; - gcli_fetch_buffer buffer = {0}; - json_stream parser = {0}; + struct gcli_fetch_buffer buffer = {0}; + struct json_stream parser = {0}; int rc = 0; e_owner = gcli_urlencode(owner); @@ -142,171 +153,212 @@ gitlab_get_issue_summary(gcli_ctx *ctx, char const *owner, char const *repo, return rc; } -int -gitlab_issue_close(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const issue_number) +static int +gitlab_issue_patch_state(struct gcli_ctx *const ctx, char const *const owner, + char const *const repo, gcli_id const issue, + char const *const new_state) { - char *url = NULL; - char *data = NULL; - char *e_owner = NULL; - char *e_repo = NULL; + char *url = NULL, *payload = NULL, *e_owner = NULL, *e_repo = NULL; + struct gcli_jsongen gen = {0}; int rc = 0; + /* Generate payload */ + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "state_event"); + gcli_jsongen_string(&gen, new_state); + } + gcli_jsongen_end_object(&gen); + + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); + + /* Generate URL */ e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); - url = sn_asprintf("%s/projects/%s%%2F%s/issues/%"PRIid, gcli_get_apibase(ctx), - e_owner, e_repo, issue_number); - data = sn_asprintf("{ \"state_event\": \"close\"}"); + url = sn_asprintf("%s/projects/%s%%2F%s/issues/%"PRIid, + gcli_get_apibase(ctx), e_owner, e_repo, issue); - rc = gcli_fetch_with_method(ctx, "PUT", url, data, NULL, NULL); - - free(data); - free(url); free(e_owner); free(e_repo); + rc = gcli_fetch_with_method(ctx, "PUT", url, payload, NULL, NULL); + + free(payload); + free(url); + return rc; } int -gitlab_issue_reopen(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const issue_number) +gitlab_issue_close(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const issue) { - char *url = NULL; - char *data = NULL; - char *e_owner = NULL; - char *e_repo = NULL; - int rc = 0; - - e_owner = gcli_urlencode(owner); - e_repo = gcli_urlencode(repo); - - url = sn_asprintf("%s/projects/%s%%2F%s/issues/%"PRIid, gcli_get_apibase(ctx), - e_owner, e_repo, issue_number); - data = sn_asprintf("{ \"state_event\": \"reopen\"}"); - - rc = gcli_fetch_with_method(ctx, "PUT", url, data, NULL, NULL); - - free(data); - free(url); - free(e_owner); - free(e_repo); + return gitlab_issue_patch_state(ctx, owner, repo, issue, "close"); +} - return rc; +int +gitlab_issue_reopen(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const issue) +{ + return gitlab_issue_patch_state(ctx, owner, repo, issue, "reopen"); } int -gitlab_perform_submit_issue(gcli_ctx *ctx, gcli_submit_issue_options opts, - gcli_fetch_buffer *const out) +gitlab_perform_submit_issue(struct gcli_ctx *ctx, struct gcli_submit_issue_options opts, + struct gcli_fetch_buffer *const out) { - char *e_owner = gcli_urlencode(opts.owner); - char *e_repo = gcli_urlencode(opts.repo); - sn_sv e_title = gcli_json_escape(opts.title); - sn_sv e_body = gcli_json_escape(opts.body); + char *e_owner = NULL, *e_repo = NULL, *url = NULL, *payload = NULL; + struct gcli_jsongen gen = {0}; int rc = 0; - char *post_fields = sn_asprintf( - "{ \"title\": \""SV_FMT"\", \"description\": \""SV_FMT"\" }", - SV_ARGS(e_title), SV_ARGS(e_body)); - char *url = sn_asprintf("%s/projects/%s%%2F%s/issues", gcli_get_apibase(ctx), - e_owner, e_repo); + e_owner = gcli_urlencode(opts.owner); + e_repo = gcli_urlencode(opts.repo); + + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "title"); + gcli_jsongen_string(&gen, opts.title); + + /* The body may be NULL if empty. In this case we can omit the + * body / description as it is not required by the API */ + if (opts.body) { + gcli_jsongen_objmember(&gen, "description"); + gcli_jsongen_string(&gen, opts.body); + } + } + gcli_jsongen_end_object(&gen); + + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); - rc = gcli_fetch_with_method(ctx, "POST", url, post_fields, NULL, out); + url = sn_asprintf("%s/projects/%s%%2F%s/issues", gcli_get_apibase(ctx), + e_owner, e_repo); free(e_owner); free(e_repo); - free(e_title.data); - free(e_body.data); - free(post_fields); + + rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, out); + + free(payload); free(url); return rc; } int -gitlab_issue_assign(gcli_ctx *ctx, char const *owner, char const *repo, +gitlab_issue_assign(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const issue_number, char const *assignee) { + char *url = NULL, *payload = NULL, *e_owner = NULL, *e_repo = NULL; + struct gcli_jsongen gen = {0}; int assignee_uid = -1; - char *url = NULL; - char *post_data = NULL; - char *e_owner = NULL; - char *e_repo = NULL; int rc = 0; assignee_uid = gitlab_user_id(ctx, assignee); if (assignee_uid < 0) return assignee_uid; + /* Generate payload */ + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "assignee_ids"); + gcli_jsongen_begin_array(&gen); + gcli_jsongen_number(&gen, assignee_uid); + gcli_jsongen_end_array(&gen); + } + gcli_jsongen_end_object(&gen); + + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); + + /* Generate URL */ e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/projects/%s%%2F%s/issues/%"PRIid, gcli_get_apibase(ctx), e_owner, e_repo, issue_number); - post_data = sn_asprintf("{ \"assignee_ids\": [ %d ] }", assignee_uid); - rc = gcli_fetch_with_method(ctx, "PUT", url, post_data, NULL, NULL); - free(e_owner); free(e_repo); + + rc = gcli_fetch_with_method(ctx, "PUT", url, payload, NULL, NULL); + free(url); - free(post_data); + free(payload); return rc; } -int -gitlab_issue_add_labels(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const issue, char const *const labels[], - size_t const labels_size) +static int +gitlab_issues_update_labels(struct gcli_ctx *const ctx, char const *const owner, + char const *const repo, gcli_id const issue, + char const *const labels[], size_t const labels_size, + char const *const what) { - char *url = NULL; - char *data = NULL; - char *list = NULL; + char *url = NULL, *payload = NULL, *label_list = NULL, *e_owner = NULL, + *e_repo = NULL; + struct gcli_jsongen gen = {0}; int rc = 0; - url = sn_asprintf("%s/projects/%s%%2F%s/issues/%"PRIid, gcli_get_apibase(ctx), - owner, repo, issue); + /* Generate payload. For some reason Gitlab expects us to put a + * comma-separated list of issues into a JSON string. Figures...*/ + label_list = sn_join_with(labels, labels_size, ","); - list = sn_join_with(labels, labels_size, ","); - data = sn_asprintf("{ \"add_labels\": \"%s\"}", list); + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, what); + gcli_jsongen_string(&gen, label_list); + } + gcli_jsongen_end_object(&gen); - rc = gcli_fetch_with_method(ctx, "PUT", url, data, NULL, NULL); + free(label_list); - free(url); - free(data); - free(list); - - return rc; -} + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); -int -gitlab_issue_remove_labels(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const issue, char const *const labels[], - size_t const labels_size) -{ - char *url = NULL; - char *data = NULL; - char *list = NULL; - int rc = 0; + /* Generate URL */ + e_owner = gcli_urlencode(owner); + e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/projects/%s%%2F%s/issues/%"PRIid, gcli_get_apibase(ctx), - owner, repo, issue); + e_owner, e_repo, issue); - list = sn_join_with(labels, labels_size, ","); - data = sn_asprintf("{ \"remove_labels\": \"%s\"}", list); + free(e_owner); + free(e_repo); - rc = gcli_fetch_with_method(ctx, "PUT", url, data, NULL, NULL); + rc = gcli_fetch_with_method(ctx, "PUT", url, payload, NULL, NULL); free(url); - free(data); - free(list); + free(payload); return rc; } int -gitlab_issue_set_milestone(gcli_ctx *ctx, char const *const owner, +gitlab_issue_add_labels(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id const issue, + char const *const labels[], size_t const labels_size) +{ + return gitlab_issues_update_labels(ctx, owner, repo, issue, labels, + labels_size, "add_labels"); +} + +int +gitlab_issue_remove_labels(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id const issue, + char const *const labels[], size_t const labels_size) +{ + return gitlab_issues_update_labels(ctx, owner, repo, issue, labels, + labels_size, "remove_labels"); +} + +int +gitlab_issue_set_milestone(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const issue, gcli_id const milestone) { @@ -328,10 +380,11 @@ gitlab_issue_set_milestone(gcli_ctx *ctx, char const *const owner, } int -gitlab_issue_clear_milestone(gcli_ctx *ctx, char const *const owner, +gitlab_issue_clear_milestone(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const issue) { - char *url, *e_owner, *e_repo, *payload; + char *url, *e_owner, *e_repo; + char const *payload; int rc; /* The Gitlab API says: @@ -350,11 +403,10 @@ gitlab_issue_clear_milestone(gcli_ctx *ctx, char const *const owner, e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/projects/%s%%2F%s/issues/%"PRIid, gcli_get_apibase(ctx), e_owner, e_repo, issue); - payload = sn_asprintf("{ \"milestone_id\": null }"); + payload = "{ \"milestone_id\": null }"; rc = gcli_fetch_with_method(ctx, "PUT", url, payload, NULL, NULL); - free(payload); free(url); free(e_repo); free(e_owner); @@ -363,11 +415,12 @@ gitlab_issue_clear_milestone(gcli_ctx *ctx, char const *const owner, } int -gitlab_issue_set_title(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id issue, char const *const new_title) +gitlab_issue_set_title(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue, + char const *const new_title) { char *url, *e_owner, *e_repo, *payload; - gcli_jsongen gen = {0}; + struct gcli_jsongen gen = {0}; int rc; /* Generate url */ diff --git a/src/gitlab/labels.c b/src/gitlab/labels.c index 67421e6e..f1c8351f 100644 --- a/src/gitlab/labels.c +++ b/src/gitlab/labels.c @@ -29,6 +29,7 @@ #include #include +#include #include #include @@ -36,18 +37,18 @@ #include int -gitlab_get_labels(gcli_ctx *ctx, char const *owner, char const *repo, - int const max, gcli_label_list *const out) +gitlab_get_labels(struct gcli_ctx *ctx, char const *owner, char const *repo, + int const max, struct gcli_label_list *const out) { char *url = NULL; - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &out->labels, .sizep = &out->labels_size, .max = max, .parse = (parsefn)(parse_gitlab_labels), }; - *out = (gcli_label_list) {0}; + *out = (struct gcli_label_list) {0}; url = sn_asprintf("%s/projects/%s%%2F%s/labels", gcli_get_apibase(ctx), owner, repo); @@ -56,33 +57,49 @@ gitlab_get_labels(gcli_ctx *ctx, char const *owner, char const *repo, } int -gitlab_create_label(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_label *const label) +gitlab_create_label(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_label *const label) { - char *url = NULL; - char *data = NULL; - char *colour_string = NULL; - sn_sv lname_escaped = SV_NULL; - sn_sv ldesc_escaped = SV_NULL; - gcli_fetch_buffer buffer = {0}; - struct json_stream stream = {0}; + char *url = NULL, *payload = NULL, *colour_string = NULL, *e_owner = NULL, + *e_repo = NULL; + struct gcli_fetch_buffer buffer = {0}; + struct gcli_jsongen gen = {0}; int rc = 0; + struct json_stream stream = {0}; + + /* Generate payload */ + colour_string = sn_asprintf("#%06X", label->colour & 0xFFFFFF); + + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "name"); + gcli_jsongen_string(&gen, label->name); + + gcli_jsongen_objmember(&gen, "color"); + gcli_jsongen_string(&gen, colour_string); + + gcli_jsongen_objmember(&gen, "description"); + gcli_jsongen_string(&gen, label->description); + } + gcli_jsongen_end_object(&gen); + + payload = gcli_jsongen_to_string(&gen); + + gcli_jsongen_free(&gen); + free(colour_string); + + /* Generate URL */ + e_owner = gcli_urlencode(owner); + e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/projects/%s%%2F%s/labels", gcli_get_apibase(ctx), - owner, repo); + e_owner, e_repo); - lname_escaped = gcli_json_escape(SV(label->name)); - ldesc_escaped = gcli_json_escape(SV(label->description)); - colour_string = sn_asprintf("%06X", (label->colour>>8)&0xFFFFFF); - data = sn_asprintf( - "{\"name\": \""SV_FMT"\"," - "\"color\":\"#%s\"," - "\"description\":\""SV_FMT"\"}", - SV_ARGS(lname_escaped), - colour_string, - SV_ARGS(ldesc_escaped)); + free(e_owner); + free(e_repo); - rc = gcli_fetch_with_method(ctx, "POST", url, data, NULL, &buffer); + rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, &buffer); if (rc == 0) { json_open_buffer(&stream, buffer.data, buffer.length); @@ -91,10 +108,7 @@ gitlab_create_label(gcli_ctx *ctx, char const *owner, char const *repo, json_close(&stream); } - free(lname_escaped.data); - free(ldesc_escaped.data); - free(colour_string); - free(data); + free(payload); free(url); free(buffer.data); @@ -102,7 +116,7 @@ gitlab_create_label(gcli_ctx *ctx, char const *owner, char const *repo, } int -gitlab_delete_label(gcli_ctx *ctx, char const *owner, char const *repo, +gitlab_delete_label(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *label) { char *url = NULL; diff --git a/src/gitlab/merge_requests.c b/src/gitlab/merge_requests.c index 65bfe744..d95928be 100644 --- a/src/gitlab/merge_requests.c +++ b/src/gitlab/merge_requests.c @@ -39,10 +39,12 @@ #include +#include /* for nanosleep */ + /* Workaround because gitlab doesn't give us an explicit field for * this. */ static void -gitlab_mrs_fixup(gcli_pull_list *const list) +gitlab_mrs_fixup(struct gcli_pull_list *const list) { for (size_t i = 0; i < list->pulls_size; ++i) { list->pulls[i].merged = !strcmp(list->pulls[i].state, "merged"); @@ -50,12 +52,12 @@ gitlab_mrs_fixup(gcli_pull_list *const list) } int -gitlab_fetch_mrs(gcli_ctx *ctx, char *url, int const max, - gcli_pull_list *const list) +gitlab_fetch_mrs(struct gcli_ctx *ctx, char *url, int const max, + struct gcli_pull_list *const list) { int rc = 0; - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &list->pulls, .sizep = &list->pulls_size, .max = max, @@ -72,9 +74,9 @@ gitlab_fetch_mrs(gcli_ctx *ctx, char *url, int const max, } int -gitlab_get_mrs(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_pull_fetch_details const *const details, int const max, - gcli_pull_list *const list) +gitlab_get_mrs(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_pull_fetch_details const *const details, int const max, + struct gcli_pull_list *const list) { char *url = NULL; char *e_owner = NULL; @@ -125,7 +127,7 @@ gitlab_get_mrs(gcli_ctx *ctx, char const *owner, char const *repo, } static void -gitlab_free_diff(gitlab_diff *diff) +gitlab_free_diff(struct gitlab_diff *diff) { free(diff->diff); free(diff->old_path); @@ -137,7 +139,7 @@ gitlab_free_diff(gitlab_diff *diff) } static void -gitlab_free_diffs(gitlab_diff_list *list) +gitlab_free_diffs(struct gitlab_diff_list *list) { for (size_t i = 0; i < list->diffs_size; ++i) { gitlab_free_diff(&list->diffs[i]); @@ -148,17 +150,40 @@ gitlab_free_diffs(gitlab_diff_list *list) list->diffs_size = 0; } +static void +gitlab_make_commit_diff(struct gcli_commit const *const commit, + struct gitlab_diff const *const diff, + char const *const prev_commit_sha, FILE *const out) +{ + fprintf(out, "diff --git a/%s b/%s\n", diff->old_path, diff->new_path); + if (diff->new_file) { + fprintf(out, "new file mode %s\n", diff->b_mode); + fprintf(out, "index 0000000..%s\n", commit->sha); + } else { + fprintf(out, "index %s..%s %s\n", prev_commit_sha, commit->sha, + diff->b_mode); + } + + fprintf(out, "--- %s%s\n", + diff->new_file ? "" : "a/", + diff->new_file ? "/dev/null" : diff->old_path); + fprintf(out, "+++ %s%s\n", + diff->deleted_file ? "" : "b/", + diff->deleted_file ? "/dev/null" : diff->new_path); + fputs(diff->diff, out); +} + static int -gitlab_make_commit_patch(gcli_ctx *ctx, FILE *stream, +gitlab_make_commit_patch(struct gcli_ctx *ctx, FILE *stream, char const *const e_owner, char const *const e_repo, char const *const prev_commit_sha, - gcli_commit const *const commit) + struct gcli_commit const *const commit) { char *url; int rc; - gitlab_diff_list list = {0}; + struct gitlab_diff_list list = {0}; - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &list.diffs, .sizep = &list.diffs_size, .max = -1, @@ -179,20 +204,11 @@ gitlab_make_commit_patch(gcli_ctx *ctx, FILE *stream, fprintf(stream, "Subject: %s\n\n", commit->message); for (size_t i = 0; i < list.diffs_size; ++i) { - gitlab_diff const *d = &list.diffs[i]; - fprintf(stream, - "diff --git a/%s b/%s\n" - "index %s..%s %s\n" - "--- a/%s\n" - "+++ b/%s\n" - "%s", - d->old_path, d->new_path, - prev_commit_sha, commit->sha, d->b_mode, - d->old_path, d->new_path, - d->diff); + gitlab_make_commit_diff(commit, &list.diffs[i], + prev_commit_sha, stream); } - fprintf(stream, "--\n2.42.2\n\n"); + fprintf(stream, "--\n2.42.2\n\n\n"); gitlab_free_diffs(&list); @@ -202,13 +218,13 @@ gitlab_make_commit_patch(gcli_ctx *ctx, FILE *stream, } int -gitlab_mr_get_patch(gcli_ctx *ctx, FILE *stream, char const *owner, +gitlab_mr_get_patch(struct gcli_ctx *ctx, FILE *stream, char const *owner, char const *reponame, gcli_id mr_number) { int rc = 0; char *e_owner, *e_repo; - gcli_pull pull = {0}; - gcli_commit_list commits = {0}; + struct gcli_pull pull = {0}; + struct gcli_commit_list commits = {0}; char const *prev_commit_sha; char *base_sha_short; @@ -225,14 +241,14 @@ gitlab_mr_get_patch(gcli_ctx *ctx, FILE *stream, char const *owner, base_sha_short = sn_strndup(pull.base_sha, 8); prev_commit_sha = base_sha_short; - for (size_t i = 0; i < commits.commits_size; ++i) { + for (size_t i = commits.commits_size; i > 0; --i) { rc = gitlab_make_commit_patch(ctx, stream, e_owner, e_repo, prev_commit_sha, - &commits.commits[i]); + &commits.commits[i - 1]); if (rc < 0) goto err_make_commit_patch; - prev_commit_sha = commits.commits[i].sha; + prev_commit_sha = commits.commits[i - 1].sha; } err_make_commit_patch: @@ -248,7 +264,7 @@ gitlab_mr_get_patch(gcli_ctx *ctx, FILE *stream, char const *owner, } int -gitlab_mr_get_diff(gcli_ctx *ctx, FILE *stream, char const *owner, +gitlab_mr_get_diff(struct gcli_ctx *ctx, FILE *stream, char const *owner, char const *reponame, gcli_id mr_number) { (void) stream; @@ -260,10 +276,34 @@ gitlab_mr_get_diff(gcli_ctx *ctx, FILE *stream, char const *owner, } int -gitlab_mr_merge(gcli_ctx *ctx, char const *owner, char const *repo, +gitlab_mr_set_automerge(struct gcli_ctx *const ctx, char const *const owner, + char const *const repo, gcli_id const mr_number) +{ + char *url, *e_owner, *e_repo; + int rc; + + e_owner = gcli_urlencode(owner); + e_repo = gcli_urlencode(repo); + + url = sn_asprintf("%s/projects/%s%%2F%s/merge_requests/%"PRIid"/merge" + "?merge_when_pipeline_succeeds=true", + gcli_get_apibase(ctx), e_owner, e_repo, mr_number); + + free(e_owner); + free(e_repo); + + rc = gcli_fetch_with_method(ctx, "PUT", url, NULL, NULL, NULL); + + free(url); + + return rc; +} + +int +gitlab_mr_merge(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const mr_number, enum gcli_merge_flags const flags) { - gcli_fetch_buffer buffer = {0}; + struct gcli_fetch_buffer buffer = {0}; char *url = NULL; char *e_owner = NULL; char *e_repo = NULL; @@ -295,10 +335,10 @@ gitlab_mr_merge(gcli_ctx *ctx, char const *owner, char const *repo, } int -gitlab_get_pull(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const pr_number, gcli_pull *const out) +gitlab_get_pull(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const pr_number, struct gcli_pull *const out) { - gcli_fetch_buffer json_buffer = {0}; + struct gcli_fetch_buffer json_buffer = {0}; char *url = NULL; char *e_owner = NULL; char *e_repo = NULL; @@ -316,7 +356,7 @@ gitlab_get_pull(gcli_ctx *ctx, char const *owner, char const *repo, rc = gcli_fetch(ctx, url, NULL, &json_buffer); if (rc == 0) { - json_stream stream = {0}; + struct json_stream stream = {0}; json_open_buffer(&stream, json_buffer.data, json_buffer.length); parse_gitlab_mr(ctx, &stream, out); json_close(&stream); @@ -329,14 +369,14 @@ gitlab_get_pull(gcli_ctx *ctx, char const *owner, char const *repo, } int -gitlab_get_pull_commits(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const pr_number, gcli_commit_list *const out) +gitlab_get_pull_commits(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const pr_number, struct gcli_commit_list *const out) { char *url = NULL; char *e_owner = NULL; char *e_repo = NULL; - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &out->commits, .sizep = &out->commits_size, .max = -1, @@ -356,221 +396,321 @@ gitlab_get_pull_commits(gcli_ctx *ctx, char const *owner, char const *repo, return gcli_fetch_list(ctx, url, &fl); } -int -gitlab_mr_close(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const pr_number) +static int +gitlab_mr_patch_state(struct gcli_ctx *const ctx, char const *const owner, + char const *const repo, gcli_id const mr, + char const *const new_state) { - char *url = NULL; - char *data = NULL; - char *e_owner = NULL; - char *e_repo = NULL; + char *url = NULL, *payload = NULL, *e_owner = NULL, *e_repo = NULL; + struct gcli_jsongen gen = {0}; int rc = 0; + /* Generate payload */ + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "state_event"); + gcli_jsongen_string(&gen, new_state); + } + gcli_jsongen_end_object(&gen); + + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); + + /* Generate URL */ e_owner = gcli_urlencode(owner); - e_repo = gcli_urlencode(repo); + e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/projects/%s%%2F%s/merge_requests/%"PRIid, - gcli_get_apibase(ctx), e_owner, e_repo, pr_number); - data = sn_asprintf("{ \"state_event\": \"close\"}"); - - rc = gcli_fetch_with_method(ctx, "PUT", url, data, NULL, NULL); + gcli_get_apibase(ctx), e_owner, e_repo, mr); - free(url); free(e_owner); free(e_repo); - free(data); + + rc = gcli_fetch_with_method(ctx, "PUT", url, payload, NULL, NULL); + + free(url); + free(payload); return rc; } int -gitlab_mr_reopen(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const pr_number) +gitlab_mr_close(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const mr) { - char *url = NULL; - char *data = NULL; - char *e_owner = NULL; - char *e_repo = NULL; - int rc = 0; + return gitlab_mr_patch_state(ctx, owner, repo, mr, "close"); +} - e_owner = gcli_urlencode(owner); - e_repo = gcli_urlencode(repo); +int +gitlab_mr_reopen(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const mr) +{ + return gitlab_mr_patch_state(ctx, owner, repo, mr, "reopen"); +} + +/* This routine is a workaround for a Gitlab bug: + * + * https://gitlab.com/gitlab-org/gitlab/-/issues/353984 + * + * This is a race condition because something in the creation of a merge request + * is being handled asynchronously. See the above link for more details. + * + * TL;DR: We need to wait until the ยปmerge_statusยซ field of the MR is set to + * ยปcan_be_mergedยซ. This is indicated by the mergable field becoming true. */ +static int +gitlab_mr_wait_until_mergeable(struct gcli_ctx *ctx, char const *const e_owner, + char const *const e_repo, gcli_id const mr_id) +{ + char *url; + int rc = 0; + struct timespec const ts = { .tv_sec = 1, .tv_nsec = 0 }; url = sn_asprintf("%s/projects/%s%%2F%s/merge_requests/%"PRIid, - gcli_get_apibase(ctx), e_owner, e_repo, pr_number); - data = sn_asprintf("{ \"state_event\": \"reopen\"}"); + gcli_get_apibase(ctx), e_owner, e_repo, mr_id); - rc = gcli_fetch_with_method(ctx, "PUT", url, data, NULL, NULL); + for (;;) { + bool is_mergeable; + struct gcli_fetch_buffer buffer = {0}; + struct json_stream stream = {0}; + struct gcli_pull pull = {0}; + + rc = gcli_fetch(ctx, url, NULL, &buffer); + if (rc < 0) + break; + + json_open_buffer(&stream, buffer.data, buffer.length); + rc = parse_gitlab_mr(ctx, &stream, &pull); + json_close(&stream); + + /* FIXME: this doesn't quite cut it when the PR has no commits in it. + * In that case this will turn into an infinite loop. */ + is_mergeable = pull.mergeable; + + gcli_pull_free(&pull); + free(buffer.data); + + if (is_mergeable) + break; + + /* sort of a hack: wait for a second until the next request goes out */ + nanosleep(&ts, NULL); + } - free(e_owner); - free(e_repo); free(url); - free(data); return rc; } int -gitlab_perform_submit_mr(gcli_ctx *ctx, gcli_submit_pull_options opts) +gitlab_perform_submit_mr(struct gcli_ctx *ctx, struct gcli_submit_pull_options opts) { /* Note: this doesn't really allow merging into repos with * different names. We need to figure out a way to make this * better for both github and gitlab. */ - gcli_repo target = {0}; - sn_sv target_branch = {0}; - sn_sv source_owner = {0}; - sn_sv source_branch = {0}; - char *labels = NULL; + char *source_branch = NULL, *source_owner = NULL, *payload = NULL, + *e_owner = NULL, *e_repo = NULL, *url = NULL; + char const *target_branch = NULL; int rc = 0; - - /* json escaped variants */ - sn_sv e_source_branch, e_target_branch, e_title, e_body; + struct gcli_fetch_buffer buffer = {0}; + struct gcli_jsongen gen = {0}; + struct gcli_repo target = {0}; target_branch = opts.to; - source_branch = opts.from; - source_owner = sn_sv_chop_until(&source_branch, ':'); - if (source_branch.length == 0) + source_owner = strdup(opts.from); + source_branch = strchr(source_owner, ':'); + if (source_branch == NULL) return gcli_error(ctx, "bad merge request source: expected 'owner:branch'"); - source_branch.length -= 1; - source_branch.data += 1; + *source_branch++ = '\0'; /* Figure out the project id */ rc = gitlab_get_repo(ctx, opts.owner, opts.repo, &target); if (rc < 0) return rc; - /* escape things in the post payload */ - e_source_branch = gcli_json_escape(source_branch); - e_target_branch = gcli_json_escape(target_branch); - e_title = gcli_json_escape(opts.title); - e_body = gcli_json_escape(opts.body); + /* generate payload */ + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "source_branch"); + gcli_jsongen_string(&gen, source_branch); + + gcli_jsongen_objmember(&gen, "target_branch"); + gcli_jsongen_string(&gen, target_branch); + + gcli_jsongen_objmember(&gen, "title"); + gcli_jsongen_string(&gen, opts.title); + + /* description is optional and will be NULL if unset */ + if (opts.body) { + gcli_jsongen_objmember(&gen, "description"); + gcli_jsongen_string(&gen, opts.body); + } - /* Prepare the label list if needed */ - if (opts.labels_size) { - char *joined_items = NULL; + gcli_jsongen_objmember(&gen, "target_project_id"); + gcli_jsongen_number(&gen, target.id); - /* Join by "," */ - joined_items = sn_join_with( - opts.labels, opts.labels_size, "\",\""); + if (opts.labels_size) { + gcli_jsongen_objmember(&gen, "labels"); - /* Construct something we can shove into the payload below */ - labels = sn_asprintf(", \"labels\": [\"%s\"]", joined_items); - free(joined_items); + gcli_jsongen_begin_array(&gen); + for (size_t i = 0; i < opts.labels_size; ++i) + gcli_jsongen_string(&gen, opts.labels[i]); + gcli_jsongen_end_array(&gen); + } } + gcli_jsongen_end_object(&gen); + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); + gcli_repo_free(&target); + + /* generate url */ + e_owner = gcli_urlencode(source_owner); + e_repo = gcli_urlencode(opts.repo); - /* prepare payload */ - char *post_fields = sn_asprintf( - "{\"source_branch\":\""SV_FMT"\",\"target_branch\":\""SV_FMT"\", " - "\"title\": \""SV_FMT"\", \"description\": \""SV_FMT"\", " - "\"target_project_id\": %"PRIid" %s }", - SV_ARGS(e_source_branch), - SV_ARGS(e_target_branch), - SV_ARGS(e_title), - SV_ARGS(e_body), - target.id, - labels ? labels : ""); - - /* construct url. The thing below works as the string view is - * malloced and also NUL-terminated */ - char *e_owner = gcli_urlencode_sv(source_owner).data; - char *e_repo = gcli_urlencode(opts.repo); - - char *url = sn_asprintf("%s/projects/%s%%2F%s/merge_requests", - gcli_get_apibase(ctx), - e_owner, e_repo); + url = sn_asprintf("%s/projects/%s%%2F%s/merge_requests", gcli_get_apibase(ctx), + e_owner, e_repo); /* perform request */ - rc = gcli_fetch_with_method(ctx, "POST", url, post_fields, NULL, NULL); + rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, &buffer); + + /* if that succeeded and the user wants automerge, parse the result and + * set the automerge flag */ + if (rc == 0 && opts.automerge) { + struct json_stream stream = {0}; + struct gcli_pull pull = {0}; + + json_open_buffer(&stream, buffer.data, buffer.length); + rc = parse_gitlab_mr(ctx, &stream, &pull); + json_close(&stream); + + if (rc < 0) + goto out; + + rc = gitlab_mr_wait_until_mergeable(ctx, e_owner, e_repo, pull.number); + if (rc < 0) + goto out; + + rc = gitlab_mr_set_automerge(ctx, opts.owner, opts.repo, pull.number); + + out: + gcli_pull_free(&pull); + } /* cleanup */ - free(e_source_branch.data); - free(e_target_branch.data); - free(e_title.data); - free(e_body.data); free(e_owner); free(e_repo); - free(labels); - free(post_fields); + free(buffer.data); + free(source_owner); + free(payload); free(url); return rc; } -int -gitlab_mr_add_labels(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const mr, char const *const labels[], - size_t const labels_size) +static int +gitlab_mr_update_labels(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id const mr, + char const *const labels[], size_t const labels_size, + char const *const update_action) { - char *url = NULL; - char *data = NULL; - char *list = NULL; - int rc = 0; - - url = sn_asprintf("%s/projects/%s%%2F%s/merge_requests/%"PRIid, - gcli_get_apibase(ctx), owner, repo, mr); + char *url = NULL, *payload = NULL, *list = NULL, *e_owner = NULL, + *e_repo = NULL; + struct gcli_jsongen gen = {0}; + int rc = 0; + /* Generate payload */ list = sn_join_with(labels, labels_size, ","); - data = sn_asprintf("{ \"add_labels\": \"%s\"}", list); - - rc = gcli_fetch_with_method(ctx, "PUT", url, data, NULL, NULL); + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, update_action); + gcli_jsongen_string(&gen, list); + } + gcli_jsongen_end_object(&gen); + payload = gcli_jsongen_to_string(&gen); - free(url); - free(data); + gcli_jsongen_free(&gen); free(list); - return rc; -} - -int -gitlab_mr_remove_labels(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const mr, char const *const labels[], - size_t const labels_size) -{ - char *url = NULL; - char *data = NULL; - char *list = NULL; - int rc = 0; + /* Generate URL */ + e_owner = gcli_urlencode(owner); + e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/projects/%s%%2F%s/merge_requests/%"PRIid, - gcli_get_apibase(ctx), owner, repo, mr); + gcli_get_apibase(ctx), e_owner, e_repo, mr); - list = sn_join_with(labels, labels_size, ","); - data = sn_asprintf("{ \"remove_labels\": \"%s\"}", list); + free(e_owner); + free(e_repo); - rc = gcli_fetch_with_method(ctx, "PUT", url, data, NULL, NULL); + rc = gcli_fetch_with_method(ctx, "PUT", url, payload, NULL, NULL); free(url); - free(data); - free(list); + free(payload); return rc; } int -gitlab_mr_set_milestone(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id mr, gcli_id milestone_id) +gitlab_mr_add_labels(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const mr, char const *const labels[], + size_t const labels_size) +{ + return gitlab_mr_update_labels(ctx, owner, repo, mr, labels, labels_size, + "add_labels"); +} + +int +gitlab_mr_remove_labels(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id const mr, + char const *const labels[], size_t const labels_size) { - char *url = NULL; - char *data = NULL; + return gitlab_mr_update_labels(ctx, owner, repo, mr, labels, labels_size, + "remove_labels"); +} + +int +gitlab_mr_set_milestone(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id mr, gcli_id milestone_id) +{ + char *url = NULL, *payload = NULL, *e_owner = NULL, *e_repo = NULL; + struct gcli_jsongen gen = {0}; int rc = 0; + /* Generate Payload */ + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "milestone_id"); + gcli_jsongen_id(&gen, milestone_id); + } + gcli_jsongen_end_object(&gen); + + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); + + /* Generate URL */ + e_owner = gcli_urlencode(owner); + e_repo = gcli_urlencode(repo); + url = sn_asprintf("%s/projects/%s%%2F%s/merge_requests/%"PRIid, - gcli_get_apibase(ctx), owner, repo, mr); + gcli_get_apibase(ctx), e_owner, e_repo, mr); - data = sn_asprintf("{ \"milestone_id\": \"%"PRIid"\"}", milestone_id); + free(e_owner); + free(e_repo); - rc = gcli_fetch_with_method(ctx, "PUT", url, data, NULL, NULL); + rc = gcli_fetch_with_method(ctx, "PUT", url, payload, NULL, NULL); free(url); - free(data); + free(payload); return rc; } int -gitlab_mr_clear_milestone(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const mr) +gitlab_mr_clear_milestone(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id const mr) { /* GitLab's REST API docs state: * @@ -583,19 +723,20 @@ gitlab_mr_clear_milestone(gcli_ctx *ctx, char const *owner, char const *repo, /* Helper function to fetch the list of user ids that are reviewers * of a merge requests. */ static int -gitlab_mr_get_reviewers(gcli_ctx *ctx, char const *e_owner, char const *e_repo, - gcli_id const mr, gitlab_reviewer_id_list *const out) +gitlab_mr_get_reviewers(struct gcli_ctx *ctx, char const *e_owner, + char const *e_repo, gcli_id const mr, + struct gitlab_reviewer_id_list *const out) { char *url; int rc; - gcli_fetch_buffer json_buffer = {0}; + struct gcli_fetch_buffer json_buffer = {0}; url = sn_asprintf("%s/projects/%s%%2F%s/merge_requests/%"PRIid, gcli_get_apibase(ctx), e_owner, e_repo, mr); rc = gcli_fetch(ctx, url, NULL, &json_buffer); if (rc == 0) { - json_stream stream = {0}; + struct json_stream stream = {0}; json_open_buffer(&stream, json_buffer.data, json_buffer.length); parse_gitlab_reviewer_ids(ctx, &stream, out); json_close(&stream); @@ -608,7 +749,7 @@ gitlab_mr_get_reviewers(gcli_ctx *ctx, char const *e_owner, char const *e_repo, } static void -gitlab_reviewer_list_free(gitlab_reviewer_id_list *const list) +gitlab_reviewer_list_free(struct gitlab_reviewer_id_list *const list) { free(list->reviewers); list->reviewers = NULL; @@ -616,13 +757,13 @@ gitlab_reviewer_list_free(gitlab_reviewer_id_list *const list) } int -gitlab_mr_add_reviewer(gcli_ctx *ctx, char const *owner, char const *repo, +gitlab_mr_add_reviewer(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id mr_number, char const *username) { char *url, *e_owner, *e_repo, *payload; int uid, rc = 0; - gitlab_reviewer_id_list list = {0}; - gcli_jsongen gen = {0}; + struct gitlab_reviewer_id_list list = {0}; + struct gcli_jsongen gen = {0}; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); @@ -679,12 +820,12 @@ gitlab_mr_add_reviewer(gcli_ctx *ctx, char const *owner, char const *repo, } int -gitlab_mr_set_title(gcli_ctx *ctx, char const *const owner, +gitlab_mr_set_title(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const id, char const *const new_title) { char *url, *e_owner, *e_repo, *payload; - gcli_jsongen gen = {0}; + struct gcli_jsongen gen = {0}; int rc = 0; /* Generate url diff --git a/src/gitlab/milestones.c b/src/gitlab/milestones.c index 8de8e819..b9f1a461 100644 --- a/src/gitlab/milestones.c +++ b/src/gitlab/milestones.c @@ -43,13 +43,13 @@ #include int -gitlab_get_milestones(gcli_ctx *ctx, char const *owner, char const *repo, - int max, gcli_milestone_list *const out) +gitlab_get_milestones(struct gcli_ctx *ctx, char const *owner, char const *repo, + int max, struct gcli_milestone_list *const out) { char *url; char *e_owner, *e_repo; - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &out->milestones, .sizep = &out->milestones_size, .max = max, @@ -69,12 +69,12 @@ gitlab_get_milestones(gcli_ctx *ctx, char const *owner, char const *repo, } int -gitlab_get_milestone(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const milestone, gcli_milestone *const out) +gitlab_get_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const milestone, struct gcli_milestone *const out) { char *url, *e_owner, *e_repo; - gcli_fetch_buffer buffer = {0}; - json_stream stream = {0}; + struct gcli_fetch_buffer buffer = {0}; + struct json_stream stream = {0}; int rc = 0; e_owner = gcli_urlencode(owner); @@ -99,9 +99,9 @@ gitlab_get_milestone(gcli_ctx *ctx, char const *owner, char const *repo, } int -gitlab_milestone_get_issues(gcli_ctx *ctx, char const *const owner, +gitlab_milestone_get_issues(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone, - gcli_issue_list *const out) + struct gcli_issue_list *const out) { char *url, *e_owner, *e_repo; @@ -119,7 +119,7 @@ gitlab_milestone_get_issues(gcli_ctx *ctx, char const *const owner, } int -gitlab_create_milestone(gcli_ctx *ctx, +gitlab_create_milestone(struct gcli_ctx *ctx, struct gcli_milestone_create_args const *args) { char *url, *e_owner, *e_repo, *e_title, *json_body, *description = NULL; @@ -159,7 +159,7 @@ gitlab_create_milestone(gcli_ctx *ctx, } int -gitlab_delete_milestone(gcli_ctx *ctx, char const *const owner, +gitlab_delete_milestone(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone) { char *url, *e_owner, *e_repo; @@ -181,7 +181,7 @@ gitlab_delete_milestone(gcli_ctx *ctx, char const *const owner, } int -gitlab_milestone_set_duedate(gcli_ctx *ctx, char const *const owner, +gitlab_milestone_set_duedate(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone, char const *const date) { diff --git a/src/gitlab/pipelines.c b/src/gitlab/pipelines.c index a29df0ac..cc050d3c 100644 --- a/src/gitlab/pipelines.c +++ b/src/gitlab/pipelines.c @@ -44,10 +44,10 @@ #include static int -fetch_pipelines(gcli_ctx *ctx, char *url, int const max, - gitlab_pipeline_list *const list) +fetch_pipelines(struct gcli_ctx *ctx, char *url, int const max, + struct gitlab_pipeline_list *const list) { - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &list->pipelines, .sizep = &list->pipelines_size, .max = max, @@ -58,32 +58,41 @@ fetch_pipelines(gcli_ctx *ctx, char *url, int const max, } int -gitlab_get_pipelines(gcli_ctx *ctx, char const *owner, char const *repo, - int const max, gitlab_pipeline_list *const list) +gitlab_get_pipelines(struct gcli_ctx *ctx, char const *owner, char const *repo, + int const max, struct gitlab_pipeline_list *const list) { char *url = NULL; + char *e_owner = gcli_urlencode(owner); + char *e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/projects/%s%%2F%s/pipelines", gcli_get_apibase(ctx), - owner, repo); + e_owner, e_repo); + free(e_owner); + free(e_repo); return fetch_pipelines(ctx, url, max, list); } int -gitlab_get_mr_pipelines(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const mr_id, gitlab_pipeline_list *const list) +gitlab_get_mr_pipelines(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const mr_id, struct gitlab_pipeline_list *const list) { char *url = NULL; + char *e_owner = gcli_urlencode(owner); + char *e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/projects/%s%%2F%s/merge_requests/%"PRIid"/pipelines", - gcli_get_apibase(ctx), owner, repo, mr_id); + gcli_get_apibase(ctx), e_owner, e_repo, mr_id); + + free(e_owner); + free(e_repo); /* fetch everything */ return fetch_pipelines(ctx, url, -1, list); } void -gitlab_pipeline_free(gitlab_pipeline *pipeline) +gitlab_pipeline_free(struct gitlab_pipeline *pipeline) { free(pipeline->status); free(pipeline->created_at); @@ -94,7 +103,7 @@ gitlab_pipeline_free(gitlab_pipeline *pipeline) } void -gitlab_pipelines_free(gitlab_pipeline_list *const list) +gitlab_pipelines_free(struct gitlab_pipeline_list *const list) { for (size_t i = 0; i < list->pipelines_size; ++i) { gitlab_pipeline_free(&list->pipelines[i]); @@ -106,26 +115,32 @@ gitlab_pipelines_free(gitlab_pipeline_list *const list) } int -gitlab_get_pipeline_jobs(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const pipeline, int const max, - gitlab_job_list *const out) +gitlab_get_pipeline_jobs(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id const pipeline, + int const max, struct gitlab_job_list *const out) { - char *url = NULL; - gcli_fetch_list_ctx fl = { + char *url = NULL, *e_owner = NULL, *e_repo = NULL; + struct gcli_fetch_list_ctx fl = { .listp = &out->jobs, .sizep = &out->jobs_size, .max = max, .parse = (parsefn)(parse_gitlab_jobs), }; + e_owner = gcli_urlencode(owner); + e_repo = gcli_urlencode(repo); + url = sn_asprintf("%s/projects/%s%%2F%s/pipelines/%"PRIid"/jobs", - gcli_get_apibase(ctx), owner, repo, pipeline); + gcli_get_apibase(ctx), e_owner, e_repo, pipeline); + + free(e_owner); + free(e_repo); return gcli_fetch_list(ctx, url, &fl); } void -gitlab_free_job(gitlab_job *const job) +gitlab_free_job(struct gitlab_job *const job) { free(job->status); free(job->stage); @@ -139,7 +154,7 @@ gitlab_free_job(gitlab_job *const job) } void -gitlab_free_jobs(gitlab_job_list *list) +gitlab_free_jobs(struct gitlab_job_list *list) { for (size_t i = 0; i < list->jobs_size; ++i) gitlab_free_job(&list->jobs[i]); @@ -151,14 +166,20 @@ gitlab_free_jobs(gitlab_job_list *list) } int -gitlab_job_get_log(gcli_ctx *ctx, char const *owner, char const *repo, +gitlab_job_get_log(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const job_id, FILE *stream) { - char *url = NULL; + char *url = NULL, *e_owner = NULL, *e_repo = NULL; int rc = 0; + e_owner = gcli_urlencode(owner); + e_repo = gcli_urlencode(repo); + url = sn_asprintf("%s/projects/%s%%2F%s/jobs/%"PRIid"/trace", - gcli_get_apibase(ctx), owner, repo, job_id); + gcli_get_apibase(ctx), e_owner, e_repo, job_id); + + free(e_owner); + free(e_repo); rc = gcli_curl(ctx, stream, url, NULL); @@ -168,15 +189,21 @@ gitlab_job_get_log(gcli_ctx *ctx, char const *owner, char const *repo, } int -gitlab_get_job(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const jid, gitlab_job *const out) +gitlab_get_job(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const jid, struct gitlab_job *const out) { - gcli_fetch_buffer buffer = {0}; - char *url = NULL; + struct gcli_fetch_buffer buffer = {0}; + char *url = NULL, *e_owner = NULL, *e_repo = NULL; int rc = 0; + e_owner = gcli_urlencode(owner); + e_repo = gcli_urlencode(repo); + url = sn_asprintf("%s/projects/%s%%2F%s/jobs/%"PRIid, gcli_get_apibase(ctx), - owner, repo, jid); + e_owner, e_repo, jid); + + free(e_owner); + free(e_repo); rc = gcli_fetch(ctx, url, NULL, &buffer); if (rc == 0) { @@ -195,14 +222,21 @@ gitlab_get_job(gcli_ctx *ctx, char const *owner, char const *repo, } int -gitlab_job_cancel(gcli_ctx *ctx, char const *owner, char const *repo, +gitlab_job_cancel(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const jid) { - char *url = NULL; + char *url = NULL, *e_owner = NULL, *e_repo = NULL; int rc = 0; + e_owner = gcli_urlencode(owner); + e_repo = gcli_urlencode(repo); + url = sn_asprintf("%s/projects/%s%%2F%s/jobs/%"PRIid"/cancel", - gcli_get_apibase(ctx), owner, repo, jid); + gcli_get_apibase(ctx), e_owner, e_repo, jid); + + free(e_owner); + free(e_repo); + rc = gcli_fetch_with_method(ctx, "POST", url, NULL, NULL, NULL); free(url); @@ -211,14 +245,21 @@ gitlab_job_cancel(gcli_ctx *ctx, char const *owner, char const *repo, } int -gitlab_job_retry(gcli_ctx *ctx, char const *owner, char const *repo, +gitlab_job_retry(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const jid) { int rc = 0; - char *url = NULL; + char *url = NULL, *e_owner = NULL, *e_repo = NULL; + + e_owner = gcli_urlencode(owner); + e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/projects/%s%%2F%s/jobs/%"PRIid"/retry", gcli_get_apibase(ctx), - owner, repo, jid); + e_owner, e_repo, jid); + + free(e_owner); + free(e_repo); + rc = gcli_fetch_with_method(ctx, "POST", url, NULL, NULL, NULL); free(url); @@ -227,7 +268,7 @@ gitlab_job_retry(gcli_ctx *ctx, char const *owner, char const *repo, } int -gitlab_job_download_artifacts(gcli_ctx *ctx, char const *owner, +gitlab_job_download_artifacts(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const jid, char const *const outfile) { @@ -246,12 +287,13 @@ gitlab_job_download_artifacts(gcli_ctx *ctx, char const *owner, url = sn_asprintf("%s/projects/%s%%2F%s/jobs/%"PRIid"/artifacts", gcli_get_apibase(ctx), e_owner, e_repo, jid); + free(e_owner); + free(e_repo); + rc = gcli_curl(ctx, f, url, "application/zip"); fclose(f); free(url); - free(e_owner); - free(e_repo); return rc; } diff --git a/src/gitlab/releases.c b/src/gitlab/releases.c index 5f251e32..993e33cc 100644 --- a/src/gitlab/releases.c +++ b/src/gitlab/releases.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -39,21 +40,21 @@ #include static void -fixup_asset_name(gcli_ctx *ctx, gcli_release_asset *const asset) +fixup_asset_name(struct gcli_ctx *ctx, struct gcli_release_asset *const asset) { if (!asset->name) asset->name = gcli_urldecode(ctx, strrchr(asset->url, '/') + 1); } void -gitlab_fixup_release_assets(gcli_ctx *ctx, gcli_release *const release) +gitlab_fixup_release_assets(struct gcli_ctx *ctx, struct gcli_release *const release) { for (size_t i = 0; i < release->assets_size; ++i) fixup_asset_name(ctx, &release->assets[i]); } static void -fixup_release_asset_names(gcli_ctx *ctx, gcli_release_list *list) +fixup_release_asset_names(struct gcli_ctx *ctx, struct gcli_release_list *list) { /* Iterate over releases */ for (size_t i = 0; i < list->releases_size; ++i) @@ -61,22 +62,22 @@ fixup_release_asset_names(gcli_ctx *ctx, gcli_release_list *list) } int -gitlab_get_releases(gcli_ctx *ctx, char const *owner, char const *repo, - int const max, gcli_release_list *const list) +gitlab_get_releases(struct gcli_ctx *ctx, char const *owner, char const *repo, + int const max, struct gcli_release_list *const list) { char *url = NULL; char *e_owner = NULL; char *e_repo = NULL; int rc = 0; - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &list->releases, .sizep = &list->releases_size, .max = max, .parse = (parsefn)(parse_gitlab_releases), }; - *list = (gcli_release_list) {0}; + *list = (struct gcli_release_list) {0}; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); @@ -96,37 +97,12 @@ gitlab_get_releases(gcli_ctx *ctx, char const *owner, char const *repo, } int -gitlab_create_release(gcli_ctx *ctx, gcli_new_release const *release) +gitlab_create_release(struct gcli_ctx *ctx, struct gcli_new_release const *release) { - char *url = NULL; - char *upload_url = NULL; - char *post_data = NULL; - char *name_json = NULL; - char *e_owner = NULL; - char *e_repo = NULL; - char *commitish_json = NULL; - sn_sv escaped_body = {0}; + char *e_owner = NULL, *e_repo = NULL, *url = NULL, *payload = NULL; + struct gcli_jsongen gen = {0}; int rc = 0; - e_owner = gcli_urlencode(release->owner); - e_repo = gcli_urlencode(release->repo); - - /* https://docs.github.com/en/rest/reference/repos#create-a-release */ - url = sn_asprintf("%s/projects/%s%%2F%s/releases", gcli_get_apibase(ctx), - e_owner, e_repo); - - escaped_body = gcli_json_escape(release->body); - - if (release->commitish) - commitish_json = sn_asprintf( - ",\"ref\": \"%s\"", - release->commitish); - - if (release->name) - name_json = sn_asprintf( - ",\"name\": \"%s\"", - release->name); - /* Warnings because unsupported on gitlab */ if (release->prerelease) warnx("prereleases are not supported on GitLab, option ignored"); @@ -134,37 +110,57 @@ gitlab_create_release(gcli_ctx *ctx, gcli_new_release const *release) if (release->draft) warnx("draft releases are not supported on GitLab, option ignored"); - post_data = sn_asprintf( - "{" - " \"tag_name\": \"%s\"," - " \"description\": \""SV_FMT"\"" - " %s" - " %s" - "}", - release->tag, - SV_ARGS(escaped_body), - commitish_json ? commitish_json : "", - name_json ? name_json : ""); - - rc = gcli_fetch_with_method(ctx, "POST", url, post_data, NULL, NULL); - if (release->assets_size) warnx("GitLab release asset uploads are not yet supported"); - free(upload_url); - free(url); - free(post_data); - free(escaped_body.data); - free(name_json); - free(commitish_json); + /* Payload generation */ + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "tag_name"); + gcli_jsongen_string(&gen, release->tag); + + if (release->body) { + gcli_jsongen_objmember(&gen, "description"); + gcli_jsongen_string(&gen, release->body); + } + + if (release->commitish) { + gcli_jsongen_objmember(&gen, "ref"); + gcli_jsongen_string(&gen, release->commitish); + } + + if (release->name) { + gcli_jsongen_objmember(&gen, "name"); + gcli_jsongen_string(&gen, release->name); + } + } + gcli_jsongen_end_object(&gen); + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); + + /* Generate URL */ + e_owner = gcli_urlencode(release->owner); + e_repo = gcli_urlencode(release->repo); + + /* https://docs.github.com/en/rest/reference/repos#create-a-release */ + url = sn_asprintf("%s/projects/%s%%2F%s/releases", gcli_get_apibase(ctx), + e_owner, e_repo); + free(e_owner); free(e_repo); + rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, NULL); + + free(url); + free(payload); + return rc; } int -gitlab_delete_release(gcli_ctx *ctx, char const *owner, char const *repo, char const *id) +gitlab_delete_release(struct gcli_ctx *ctx, char const *owner, + char const *repo, char const *id) { char *url = NULL; char *e_owner = NULL; diff --git a/src/gitlab/repos.c b/src/gitlab/repos.c index 9f012da6..2e13dba0 100644 --- a/src/gitlab/repos.c +++ b/src/gitlab/repos.c @@ -29,6 +29,7 @@ #include #include +#include #include #include @@ -39,13 +40,13 @@ #include int -gitlab_get_repo(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_repo *const out) +gitlab_get_repo(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_repo *const out) { /* GET /projects/:id */ char *url = NULL; - gcli_fetch_buffer buffer = {0}; - json_stream stream = {0}; + struct gcli_fetch_buffer buffer = {0}; + struct json_stream stream = {0}; char *e_owner = {0}; char *e_repo = {0}; int rc; @@ -73,31 +74,27 @@ gitlab_get_repo(gcli_ctx *ctx, char const *owner, char const *repo, } static void -gitlab_repos_fixup_missing_visibility(gcli_repo_list *const list) +gitlab_repos_fixup_missing_visibility(struct gcli_repo_list *const list) { - static char const public[] = "public"; - static size_t const public_len = sizeof(public) - 1; + static char const *const public = "public"; /* Gitlab does not return a visibility field in the repo object on * unauthenticated API requests. We fix up the missing field here * assuming that the repository must be public. */ for (size_t i = 0; i < list->repos_size; ++i) { - if (sn_sv_null(list->repos[i].visibility)) - list->repos[i].visibility = (sn_sv) { - .data = strdup(public), - .length = public_len, - }; + if (!list->repos[i].visibility) + list->repos[i].visibility = strdup(public); } } int -gitlab_get_repos(gcli_ctx *ctx, char const *owner, int const max, - gcli_repo_list *const list) +gitlab_get_repos(struct gcli_ctx *ctx, char const *owner, int const max, + struct gcli_repo_list *const list) { char *url = NULL; char *e_owner = NULL; int rc = 0; - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &list->repos, .sizep = &list->repos_size, .parse = (parsefn)(parse_gitlab_repos), @@ -117,7 +114,7 @@ gitlab_get_repos(gcli_ctx *ctx, char const *owner, int const max, } int -gitlab_repo_delete(gcli_ctx *ctx, char const *owner, char const *repo) +gitlab_repo_delete(struct gcli_ctx *ctx, char const *owner, char const *repo) { char *url = NULL; char *e_owner = NULL; @@ -140,26 +137,37 @@ gitlab_repo_delete(gcli_ctx *ctx, char const *owner, char const *repo) } int -gitlab_repo_create(gcli_ctx *ctx, gcli_repo_create_options const *options, - gcli_repo *out) +gitlab_repo_create(struct gcli_ctx *ctx, struct gcli_repo_create_options const *options, + struct gcli_repo *out) { - char *url, *data; - gcli_fetch_buffer buffer = {0}; - json_stream stream = {0}; + char *url, *payload; + struct gcli_fetch_buffer buffer = {0}; + struct gcli_jsongen gen = {0}; int rc; + struct json_stream stream = {0}; /* Request preparation */ url = sn_asprintf("%s/projects", gcli_get_apibase(ctx)); - /* TODO: escape the repo name and the description */ - data = sn_asprintf("{\"name\": \""SV_FMT"\"," - " \"description\": \""SV_FMT"\"," - " \"visibility\": \"%s\" }", - SV_ARGS(options->name), - SV_ARGS(options->description), - options->private ? "private" : "public"); + + gcli_jsongen_init(&gen); + gcli_jsongen_begin_object(&gen); + { + gcli_jsongen_objmember(&gen, "name"); + gcli_jsongen_string(&gen, options->name); + + gcli_jsongen_objmember(&gen, "description"); + gcli_jsongen_string(&gen, options->description); + + gcli_jsongen_objmember(&gen, "visibility"); + gcli_jsongen_string(&gen, options->private ? "private" : "public"); + } + gcli_jsongen_end_object(&gen); + + payload = gcli_jsongen_to_string(&gen); + gcli_jsongen_free(&gen); /* Fetch and parse result */ - rc = gcli_fetch_with_method(ctx, "POST", url, data, NULL, out ? &buffer : NULL); + rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, out ? &buffer : NULL); if (rc == 0 && out) { json_open_buffer(&stream, buffer.data, buffer.length); parse_gitlab_repo(ctx, &stream, out); @@ -168,14 +176,14 @@ gitlab_repo_create(gcli_ctx *ctx, gcli_repo_create_options const *options, } free(buffer.data); - free(data); + free(payload); free(url); return rc; } int -gitlab_repo_set_visibility(gcli_ctx *ctx, char const *const owner, +gitlab_repo_set_visibility(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_repo_visibility vis) { char *url; diff --git a/src/gitlab/snippets.c b/src/gitlab/snippets.c index c2703ab1..0765a447 100644 --- a/src/gitlab/snippets.c +++ b/src/gitlab/snippets.c @@ -41,7 +41,7 @@ #include void -gcli_gitlab_snippet_free(gcli_gitlab_snippet *snippet) +gcli_gitlab_snippet_free(struct gcli_gitlab_snippet *snippet) { free(snippet->title); free(snippet->filename); @@ -52,7 +52,7 @@ gcli_gitlab_snippet_free(gcli_gitlab_snippet *snippet) } void -gcli_snippets_free(gcli_gitlab_snippet_list *const list) +gcli_snippets_free(struct gcli_gitlab_snippet_list *const list) { for (size_t i = 0; i < list->snippets_size; ++i) { gcli_gitlab_snippet_free(&list->snippets[i]); @@ -65,25 +65,26 @@ gcli_snippets_free(gcli_gitlab_snippet_list *const list) } int -gcli_snippets_get(gcli_ctx *ctx, int const max, gcli_gitlab_snippet_list *const out) +gcli_snippets_get(struct gcli_ctx *ctx, int const max, + struct gcli_gitlab_snippet_list *const out) { char *url = NULL; - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &out->snippets, .sizep = &out->snippets_size, .max = max, .parse = (parsefn)(parse_gitlab_snippets), }; - *out = (gcli_gitlab_snippet_list) {0}; + *out = (struct gcli_gitlab_snippet_list) {0}; url = sn_asprintf("%s/snippets", gcli_get_apibase(ctx)); return gcli_fetch_list(ctx, url, &fl); } int -gcli_snippet_delete(gcli_ctx *ctx, char const *snippet_id) +gcli_snippet_delete(struct gcli_ctx *ctx, char const *snippet_id) { int rc = 0; char *url; @@ -97,7 +98,7 @@ gcli_snippet_delete(gcli_ctx *ctx, char const *snippet_id) } int -gcli_snippet_get(gcli_ctx *ctx, char const *snippet_id, FILE *stream) +gcli_snippet_get(struct gcli_ctx *ctx, char const *snippet_id, FILE *stream) { int rc = 0; char *url = sn_asprintf("%s/snippets/%s/raw", diff --git a/src/gitlab/sshkeys.c b/src/gitlab/sshkeys.c index dd7792c6..b323f07c 100644 --- a/src/gitlab/sshkeys.c +++ b/src/gitlab/sshkeys.c @@ -41,29 +41,29 @@ #include int -gitlab_get_sshkeys(gcli_ctx *ctx, gcli_sshkey_list *list) +gitlab_get_sshkeys(struct gcli_ctx *ctx, struct gcli_sshkey_list *list) { char *url; - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &list->keys, .sizep = &list->keys_size, .max = -1, .parse = (parsefn)(parse_gitlab_sshkeys), }; - *list = (gcli_sshkey_list) {0}; + *list = (struct gcli_sshkey_list) {0}; url = sn_asprintf("%s/user/keys", gcli_get_apibase(ctx)); return gcli_fetch_list(ctx, url, &fl); } int -gitlab_add_sshkey(gcli_ctx *ctx, char const *const title, - char const *const pubkey, gcli_sshkey *const out) +gitlab_add_sshkey(struct gcli_ctx *ctx, char const *const title, + char const *const pubkey, struct gcli_sshkey *const out) { char *url, *payload; char *e_title, *e_key; - gcli_fetch_buffer buf = {0}; + struct gcli_fetch_buffer buf = {0}; int rc = 0; url = sn_asprintf("%s/user/keys", gcli_get_apibase(ctx)); @@ -79,11 +79,11 @@ gitlab_add_sshkey(gcli_ctx *ctx, char const *const title, rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, &buf); if (rc == 0 && out) { - json_stream str; + struct json_stream stream = {0}; - json_open_buffer(&str, buf.data, buf.length); - parse_gitlab_sshkey(ctx, &str, out); - json_close(&str); + json_open_buffer(&stream, buf.data, buf.length); + parse_gitlab_sshkey(ctx, &stream, out); + json_close(&stream); } free(buf.data); @@ -92,7 +92,7 @@ gitlab_add_sshkey(gcli_ctx *ctx, char const *const title, } int -gitlab_delete_sshkey(gcli_ctx *ctx, gcli_id id) +gitlab_delete_sshkey(struct gcli_ctx *ctx, gcli_id id) { char *url; int rc = 0; diff --git a/src/gitlab/status.c b/src/gitlab/status.c index ee53bc74..004fc114 100644 --- a/src/gitlab/status.c +++ b/src/gitlab/status.c @@ -38,12 +38,12 @@ #include int -gitlab_get_notifications(gcli_ctx *ctx, int const max, - gcli_notification_list *const out) +gitlab_get_notifications(struct gcli_ctx *ctx, int const max, + struct gcli_notification_list *const out) { char *url = NULL; - gcli_fetch_list_ctx fl = { + struct gcli_fetch_list_ctx fl = { .listp = &out->notifications, .sizep = &out->notifications_size, .parse = (parsefn)(parse_gitlab_todos), @@ -56,7 +56,7 @@ gitlab_get_notifications(gcli_ctx *ctx, int const max, } int -gitlab_notification_mark_as_read(gcli_ctx *ctx, char const *id) +gitlab_notification_mark_as_read(struct gcli_ctx *ctx, char const *id) { char *url = NULL; int rc = 0; diff --git a/src/issues.c b/src/issues.c index 08114ed5..d367fb00 100644 --- a/src/issues.c +++ b/src/issues.c @@ -34,31 +34,34 @@ #include void -gcli_issue_free(gcli_issue *const it) +gcli_issue_free(struct gcli_issue *const it) { - free(it->title.data); - free(it->created_at.data); - free(it->author.data); - free(it->state.data); - free(it->body.data); + free(it->product); + free(it->component); + free(it->created_at); + free(it->author); + free(it->state); + free(it->body); + free(it->url); + free(it->title); for (size_t i = 0; i < it->labels_size; ++i) - free(it->labels[i].data); + free(it->labels[i]); free(it->labels); it->labels = NULL; for (size_t i = 0; i < it->assignees_size; ++i) - free(it->assignees[i].data); + free(it->assignees[i]); free(it->assignees); it->assignees = NULL; - free(it->milestone.data); + free(it->milestone); } void -gcli_issues_free(gcli_issue_list *const list) +gcli_issues_free(struct gcli_issue_list *const list) { for (size_t i = 0; i < list->issues_size; ++i) gcli_issue_free(&list->issues[i]); @@ -70,91 +73,107 @@ gcli_issues_free(gcli_issue_list *const list) } int -gcli_get_issues(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_issue_fetch_details const *details, int const max, - gcli_issue_list *const out) +gcli_issues_search(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_issue_fetch_details const *details, int const max, + struct gcli_issue_list *const out) { - return gcli_forge(ctx)->get_issues(ctx, owner, repo, details, max, out); + gcli_null_check_call(search_issues, ctx, owner, repo, details, max, out); } int -gcli_get_issue(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const issue_number, gcli_issue *const out) +gcli_get_issue(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const issue_number, struct gcli_issue *const out) { - return gcli_forge(ctx)->get_issue_summary( - ctx, owner, repo, issue_number, out); + gcli_null_check_call(get_issue_summary, ctx, owner, repo, issue_number, + out); } int -gcli_issue_close(gcli_ctx *ctx, char const *owner, char const *repo, +gcli_issue_close(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const issue_number) { - return gcli_forge(ctx)->issue_close(ctx, owner, repo, issue_number); + gcli_null_check_call(issue_close, ctx, owner, repo, issue_number); } int -gcli_issue_reopen(gcli_ctx *ctx, char const *owner, char const *repo, +gcli_issue_reopen(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const issue_number) { - return gcli_forge(ctx)->issue_reopen(ctx, owner, repo, issue_number); + gcli_null_check_call(issue_reopen, ctx, owner, repo, issue_number); } int -gcli_issue_submit(gcli_ctx *ctx, gcli_submit_issue_options opts) +gcli_issue_submit(struct gcli_ctx *ctx, struct gcli_submit_issue_options opts) { - gcli_fetch_buffer json_buffer = {0}; - int rc = 0; - - rc = gcli_forge(ctx)->perform_submit_issue(ctx, opts, &json_buffer); - free(json_buffer.data); - - return rc; + gcli_null_check_call(perform_submit_issue, ctx, opts, NULL); } int -gcli_issue_assign(gcli_ctx *ctx, char const *owner, char const *repo, +gcli_issue_assign(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const issue_number, char const *assignee) { - return gcli_forge(ctx)->issue_assign(ctx, owner, repo, issue_number, assignee); + gcli_null_check_call(issue_assign, ctx, owner, repo, issue_number, + assignee); } int -gcli_issue_add_labels(gcli_ctx *ctx, char const *owner, char const *repo, +gcli_issue_add_labels(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const issue, char const *const labels[], size_t const labels_size) { - return gcli_forge(ctx)->issue_add_labels(ctx,owner, repo, issue, labels, - labels_size); + gcli_null_check_call(issue_add_labels, ctx, owner, repo, issue, labels, + labels_size); } int -gcli_issue_remove_labels(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const issue, char const *const labels[], - size_t const labels_size) +gcli_issue_remove_labels(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id const issue, + char const *const labels[], size_t const labels_size) { - return gcli_forge(ctx)->issue_remove_labels( - ctx, owner, repo, issue, labels, labels_size); + gcli_null_check_call(issue_remove_labels, ctx, owner, repo, issue, + labels, labels_size); } int -gcli_issue_set_milestone(gcli_ctx *ctx, char const *const owner, +gcli_issue_set_milestone(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const issue, int const milestone) { - return gcli_forge(ctx)->issue_set_milestone( - ctx, owner, repo, issue, milestone); + gcli_null_check_call(issue_set_milestone, ctx, owner, repo, issue, + milestone); } int -gcli_issue_clear_milestone(gcli_ctx *ctx, char const *const owner, +gcli_issue_clear_milestone(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const issue) { - return gcli_forge(ctx)->issue_clear_milestone(ctx, owner, repo, issue); + gcli_null_check_call(issue_clear_milestone, ctx, owner, repo, issue); } int -gcli_issue_set_title(gcli_ctx *ctx, char const *owner, char const *repo, +gcli_issue_set_title(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue, char const *new_title) { - return gcli_forge(ctx)->issue_set_title(ctx, owner, repo, issue, new_title); + gcli_null_check_call(issue_set_title, ctx, owner, repo, issue, + new_title); +} + +int +gcli_issue_get_attachments(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id issue, + struct gcli_attachment_list *out) +{ + struct gcli_forge_descriptor const *const forge = + gcli_forge(ctx); + + bool const avail = + (forge->issue_quirks & GCLI_ISSUE_QUIRKS_ATTACHMENTS) && + (forge->get_issue_attachments != NULL); + + if (avail) { + return gcli_error(ctx, "attachments are not available on this forge"); + } else { + return gcli_forge(ctx)->get_issue_attachments(ctx, owner, repo, + issue, out); + } } diff --git a/src/json_gen.c b/src/json_gen.c index 9c3b260f..51ee1ae2 100644 --- a/src/json_gen.c +++ b/src/json_gen.c @@ -39,14 +39,14 @@ #include static void -grow_buffer(gcli_jsongen *gen) +grow_buffer(struct gcli_jsongen *gen) { gen->buffer_capacity *= 2; gen->buffer = realloc(gen->buffer, gen->buffer_capacity); } int -gcli_jsongen_init(gcli_jsongen *gen) +gcli_jsongen_init(struct gcli_jsongen *gen) { /* This will allocate a 32 byte buffer. We can optimise * this for better allocation speed by analysing some statistics @@ -63,7 +63,7 @@ gcli_jsongen_init(gcli_jsongen *gen) } void -gcli_jsongen_free(gcli_jsongen *gen) +gcli_jsongen_free(struct gcli_jsongen *gen) { free(gen->buffer); gen->buffer = NULL; @@ -74,7 +74,7 @@ gcli_jsongen_free(gcli_jsongen *gen) } char * -gcli_jsongen_to_string(gcli_jsongen *gen) +gcli_jsongen_to_string(struct gcli_jsongen *gen) { char *buf = calloc(gen->buffer_size + 1, 1); @@ -82,14 +82,14 @@ gcli_jsongen_to_string(gcli_jsongen *gen) } static void -fit(gcli_jsongen *gen, size_t const n_chars) +fit(struct gcli_jsongen *gen, size_t const n_chars) { while (gen->buffer_capacity - gen->buffer_size < n_chars) grow_buffer(gen); } static int -push_scope(gcli_jsongen *gen, int const scope) +push_scope(struct gcli_jsongen *gen, int const scope) { if (gen->scopes_size >= (sizeof(gen->scopes) / sizeof(*gen->scopes))) return -1; @@ -100,7 +100,7 @@ push_scope(gcli_jsongen *gen, int const scope) } static int -pop_scope(gcli_jsongen *gen) +pop_scope(struct gcli_jsongen *gen) { if (gen->scopes_size == 0) return -1; @@ -109,13 +109,13 @@ pop_scope(gcli_jsongen *gen) } static bool -is_array_or_object_scope(gcli_jsongen *gen) +is_array_or_object_scope(struct gcli_jsongen *gen) { return !!gen->scopes_size; } static void -append_str(gcli_jsongen *gen, char const *str) +append_str(struct gcli_jsongen *gen, char const *str) { size_t const len = strlen(str); fit(gen, len); @@ -124,7 +124,7 @@ append_str(gcli_jsongen *gen, char const *str) } static void -put_comma_if_needed(gcli_jsongen *gen) +put_comma_if_needed(struct gcli_jsongen *gen) { if (!gen->await_object_value && !gen->first_elem && is_array_or_object_scope(gen)) append_str(gen, ", "); @@ -133,7 +133,7 @@ put_comma_if_needed(gcli_jsongen *gen) } static bool -is_object_scope(gcli_jsongen *gen) +is_object_scope(struct gcli_jsongen *gen) { if (gen->scopes_size == 0) return false; @@ -142,7 +142,7 @@ is_object_scope(gcli_jsongen *gen) } int -gcli_jsongen_begin_object(gcli_jsongen *gen) +gcli_jsongen_begin_object(struct gcli_jsongen *gen) { /* Cannot put a json object into a json object key */ if (is_object_scope(gen) && !gen->await_object_value) @@ -161,7 +161,7 @@ gcli_jsongen_begin_object(gcli_jsongen *gen) } int -gcli_jsongen_end_object(gcli_jsongen *gen) +gcli_jsongen_end_object(struct gcli_jsongen *gen) { if (pop_scope(gen) != GCLI_JSONGEN_OBJECT) return -1; @@ -175,7 +175,7 @@ gcli_jsongen_end_object(gcli_jsongen *gen) } int -gcli_jsongen_begin_array(gcli_jsongen *gen) +gcli_jsongen_begin_array(struct gcli_jsongen *gen) { /* Cannot put a json array into a json object key */ if (is_object_scope(gen) && !gen->await_object_value) @@ -194,7 +194,7 @@ gcli_jsongen_begin_array(gcli_jsongen *gen) } int -gcli_jsongen_end_array(gcli_jsongen *gen) +gcli_jsongen_end_array(struct gcli_jsongen *gen) { if (pop_scope(gen) != GCLI_JSONGEN_ARRAY) return -1; @@ -208,7 +208,7 @@ gcli_jsongen_end_array(gcli_jsongen *gen) } static void -append_vstrf(gcli_jsongen *gen, char const *const fmt, va_list vp) +append_vstrf(struct gcli_jsongen *gen, char const *const fmt, va_list vp) { va_list vp_copy; size_t len; @@ -223,7 +223,7 @@ append_vstrf(gcli_jsongen *gen, char const *const fmt, va_list vp) } static void -append_strf(gcli_jsongen *gen, char const *const fmt, ...) +append_strf(struct gcli_jsongen *gen, char const *const fmt, ...) { va_list ap; @@ -233,7 +233,7 @@ append_strf(gcli_jsongen *gen, char const *const fmt, ...) } int -gcli_jsongen_objmember(gcli_jsongen *gen, char const *const key) +gcli_jsongen_objmember(struct gcli_jsongen *gen, char const *const key) { if (!is_object_scope(gen)) return -1; @@ -252,7 +252,7 @@ gcli_jsongen_objmember(gcli_jsongen *gen, char const *const key) } int -gcli_jsongen_number(gcli_jsongen *gen, long long const number) +gcli_jsongen_number(struct gcli_jsongen *gen, long long const number) { put_comma_if_needed(gen); append_strf(gen, "%lld", number); @@ -264,7 +264,32 @@ gcli_jsongen_number(gcli_jsongen *gen, long long const number) } int -gcli_jsongen_string(gcli_jsongen *gen, char const *value) +gcli_jsongen_id(struct gcli_jsongen *gen, gcli_id const id) +{ + put_comma_if_needed(gen); + append_strf(gen, "%"PRIid, id); + + gen->await_object_value = false; + gen->first_elem = false; + + return 0; +} + +int +gcli_jsongen_bool(struct gcli_jsongen *gen, bool const value) +{ + put_comma_if_needed(gen); + + append_strf(gen, "%s", value ? "true" : "false"); + + gen->await_object_value = false; + gen->first_elem = false; + + return 0; +} + +int +gcli_jsongen_string(struct gcli_jsongen *gen, char const *value) { put_comma_if_needed(gen); char *const e_value = gcli_json_escape_cstr(value); @@ -280,7 +305,7 @@ gcli_jsongen_string(gcli_jsongen *gen, char const *value) } int -gcli_jsongen_null(gcli_jsongen *gen) +gcli_jsongen_null(struct gcli_jsongen *gen) { put_comma_if_needed(gen); append_str(gen, "null"); diff --git a/src/json_util.c b/src/json_util.c index 8c3581ac..87836d50 100644 --- a/src/json_util.c +++ b/src/json_util.c @@ -37,7 +37,7 @@ #include int -get_int_(gcli_ctx *ctx, json_stream *const input, int *out, char const *where) +get_int_(struct gcli_ctx *ctx, json_stream *const input, int *out, char const *where) { if (json_next(input) != JSON_NUMBER) return gcli_error(ctx, "unexpected non-integer field in %s", where); @@ -48,7 +48,7 @@ get_int_(gcli_ctx *ctx, json_stream *const input, int *out, char const *where) } int -get_id_(gcli_ctx *ctx, json_stream *const input, gcli_id *out, char const *where) +get_id_(struct gcli_ctx *ctx, json_stream *const input, gcli_id *out, char const *where) { if (json_next(input) != JSON_NUMBER) return gcli_error(ctx, "unexpected non-integer ID field in %s", where); @@ -59,7 +59,7 @@ get_id_(gcli_ctx *ctx, json_stream *const input, gcli_id *out, char const *where } int -get_long_(gcli_ctx *ctx, json_stream *const input, long *out, char const *where) +get_long_(struct gcli_ctx *ctx, json_stream *const input, long *out, char const *where) { if (json_next(input) != JSON_NUMBER) return gcli_error(ctx, "unexpected non-integer field in %s", where); @@ -70,7 +70,7 @@ get_long_(gcli_ctx *ctx, json_stream *const input, long *out, char const *where) } int -get_size_t_(gcli_ctx *ctx, json_stream *const input, size_t *out, char const *where) +get_size_t_(struct gcli_ctx *ctx, json_stream *const input, size_t *out, char const *where) { if (json_next(input) != JSON_NUMBER) return gcli_error(ctx, "unexpected non-integer field in %s", where); @@ -81,7 +81,7 @@ get_size_t_(gcli_ctx *ctx, json_stream *const input, size_t *out, char const *wh } int -get_double_(gcli_ctx *ctx, json_stream *const input, double *out, char const *where) +get_double_(struct gcli_ctx *ctx, json_stream *const input, double *out, char const *where) { enum json_type type = json_next(input); @@ -100,7 +100,7 @@ get_double_(gcli_ctx *ctx, json_stream *const input, double *out, char const *wh } int -get_string_(gcli_ctx *ctx, json_stream *const input, char **out, +get_string_(struct gcli_ctx *ctx, json_stream *const input, char **out, char const *where) { enum json_type const type = json_next(input); @@ -124,7 +124,7 @@ get_string_(gcli_ctx *ctx, json_stream *const input, char **out, } int -get_bool_(gcli_ctx *ctx,json_stream *const input, bool *out, char const *where) +get_bool_(struct gcli_ctx *ctx,json_stream *const input, bool *out, char const *where) { enum json_type value_type = json_next(input); if (value_type == JSON_TRUE) { @@ -139,7 +139,25 @@ get_bool_(gcli_ctx *ctx,json_stream *const input, bool *out, char const *where) } int -get_user_(gcli_ctx *ctx, json_stream *const input, char **out, +get_bool_relaxed_(struct gcli_ctx *ctx,json_stream *const input, bool *out, char const *where) +{ + enum json_type value_type = json_next(input); + if (value_type == JSON_TRUE) { + *out = true; + return 0; + } else if (value_type == JSON_FALSE || value_type == JSON_NULL) { // HACK + *out = false; + return 0; + } else if (value_type == JSON_NUMBER) { + *out = json_get_number(input) != 0.0; + return 0; + } + + return gcli_error(ctx, "unexpected non-boolean value in %s", where); +} + +int +get_user_(struct gcli_ctx *ctx, json_stream *const input, char **out, char const *where) { if (json_next(input) != JSON_OBJECT) @@ -207,7 +225,7 @@ gcli_json_escape(sn_sv const it) } int -get_sv_(gcli_ctx *ctx, json_stream *const input, sn_sv *out, char const *where) +get_sv_(struct gcli_ctx *ctx, json_stream *const input, sn_sv *out, char const *where) { enum json_type type = json_next(input); if (type == JSON_NULL) { @@ -227,7 +245,7 @@ get_sv_(gcli_ctx *ctx, json_stream *const input, sn_sv *out, char const *where) } int -get_label_(gcli_ctx *ctx, json_stream *const input, char const **out, +get_label_(struct gcli_ctx *ctx, json_stream *const input, char const **out, char const *where) { if (json_next(input) != JSON_OBJECT) @@ -253,7 +271,7 @@ get_label_(gcli_ctx *ctx, json_stream *const input, char const **out, } int -gcli_json_advance(gcli_ctx *ctx, json_stream *const stream, char const *fmt, ...) +gcli_json_advance(struct gcli_ctx *ctx, json_stream *const stream, char const *fmt, ...) { va_list ap; va_start(ap, fmt); @@ -299,7 +317,7 @@ gcli_json_advance(gcli_ctx *ctx, json_stream *const stream, char const *fmt, ... } int -get_parse_int_(gcli_ctx *ctx, json_stream *const input, long *out, +get_parse_int_(struct gcli_ctx *ctx, json_stream *const input, long *out, char const *function) { char *endptr = NULL; @@ -318,7 +336,7 @@ get_parse_int_(gcli_ctx *ctx, json_stream *const input, long *out, } int -get_github_style_colour(gcli_ctx *ctx, json_stream *const input, uint32_t *out) +get_github_style_colour(struct gcli_ctx *ctx, json_stream *const input, uint32_t *out) { char *colour_str; char *endptr = NULL; @@ -340,7 +358,7 @@ get_github_style_colour(gcli_ctx *ctx, json_stream *const input, uint32_t *out) } int -get_gitlab_style_colour(gcli_ctx *ctx, json_stream *const input, uint32_t *out) +get_gitlab_style_colour(struct gcli_ctx *ctx, json_stream *const input, uint32_t *out) { char *colour; char *endptr = NULL; @@ -363,22 +381,20 @@ get_gitlab_style_colour(gcli_ctx *ctx, json_stream *const input, uint32_t *out) } int -get_gitea_visibility(gcli_ctx *ctx, json_stream *const input, sn_sv *out) +get_gitea_visibility(struct gcli_ctx *ctx, json_stream *const input, char **out) { - char *v = NULL; bool is_private; int rc = get_bool(ctx, input, &is_private); if (rc < 0) return rc; - v = strdup(is_private ? "private" : "public"); - *out = SV(v); + *out = strdup(is_private ? "private" : "public"); return 0; } int -get_gitlab_can_be_merged(gcli_ctx *ctx, json_stream *const input, bool *out) +get_gitlab_can_be_merged(struct gcli_ctx *ctx, json_stream *const input, bool *out) { sn_sv tmp; int rc = 0; @@ -394,7 +410,7 @@ get_gitlab_can_be_merged(gcli_ctx *ctx, json_stream *const input, bool *out) } int -get_github_is_pr(gcli_ctx *ctx, json_stream *input, int *out) +get_github_is_pr(struct gcli_ctx *ctx, json_stream *input, int *out) { enum json_type next = json_peek(input); @@ -411,7 +427,7 @@ get_github_is_pr(gcli_ctx *ctx, json_stream *input, int *out) } int -get_int_to_sv_(gcli_ctx *ctx, json_stream *input, sn_sv *out, +get_int_to_sv_(struct gcli_ctx *ctx, json_stream *input, sn_sv *out, char const *function) { int rc, val; diff --git a/src/labels.c b/src/labels.c index 18375c7a..6f7b27aa 100644 --- a/src/labels.c +++ b/src/labels.c @@ -31,21 +31,21 @@ #include int -gcli_get_labels(gcli_ctx *ctx, char const *owner, char const *reponame, - int const max, gcli_label_list *const out) +gcli_get_labels(struct gcli_ctx *ctx, char const *owner, char const *reponame, + int const max, struct gcli_label_list *const out) { - return gcli_forge(ctx)->get_labels(ctx, owner, reponame, max, out); + gcli_null_check_call(get_labels, ctx, owner, reponame, max, out); } void -gcli_free_label(gcli_label *const label) +gcli_free_label(struct gcli_label *const label) { free(label->name); free(label->description); } void -gcli_free_labels(gcli_label_list *const list) +gcli_free_labels(struct gcli_label_list *const list) { for (size_t i = 0; i < list->labels_size; ++i) gcli_free_label(&list->labels[i]); @@ -56,15 +56,15 @@ gcli_free_labels(gcli_label_list *const list) } int -gcli_create_label(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_label *const label) +gcli_create_label(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_label *const label) { - return gcli_forge(ctx)->create_label(ctx, owner, repo, label); + gcli_null_check_call(create_label, ctx, owner, repo, label); } int -gcli_delete_label(gcli_ctx *ctx, char const *owner, char const *repo, +gcli_delete_label(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *const label) { - return gcli_forge(ctx)->delete_label(ctx, owner, repo, label); + gcli_null_check_call(delete_label, ctx, owner, repo, label); } diff --git a/src/milestones.c b/src/milestones.c index 4af2bf2f..fbac6126 100644 --- a/src/milestones.c +++ b/src/milestones.c @@ -31,36 +31,36 @@ #include int -gcli_get_milestones(gcli_ctx *ctx, char const *const owner, +gcli_get_milestones(struct gcli_ctx *ctx, char const *const owner, char const *const repo, int const max, - gcli_milestone_list *const out) + struct gcli_milestone_list *const out) { - return gcli_forge(ctx)->get_milestones(ctx, owner, repo, max, out); + gcli_null_check_call(get_milestones, ctx, owner, repo, max, out); } int -gcli_get_milestone(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const milestone, gcli_milestone *const out) +gcli_get_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const milestone, struct gcli_milestone *const out) { - return gcli_forge(ctx)->get_milestone(ctx, owner, repo, milestone, out); + gcli_null_check_call(get_milestone, ctx, owner, repo, milestone, out); } int -gcli_create_milestone(gcli_ctx *ctx, +gcli_create_milestone(struct gcli_ctx *ctx, struct gcli_milestone_create_args const *args) { - return gcli_forge(ctx)->create_milestone(ctx, args); + gcli_null_check_call(create_milestone, ctx, args); } int -gcli_delete_milestone(gcli_ctx *ctx, char const *const owner, +gcli_delete_milestone(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone) { - return gcli_forge(ctx)->delete_milestone(ctx, owner, repo, milestone); + gcli_null_check_call(delete_milestone, ctx, owner, repo, milestone); } void -gcli_free_milestone(gcli_milestone *const it) +gcli_free_milestone(struct gcli_milestone *const it) { free(it->title); it->title = NULL; @@ -78,7 +78,7 @@ gcli_free_milestone(gcli_milestone *const it) } void -gcli_free_milestones(gcli_milestone_list *const it) +gcli_free_milestones(struct gcli_milestone_list *const it) { for (size_t i = 0; i < it->milestones_size; ++i) gcli_free_milestone(&it->milestones[i]); @@ -89,19 +89,19 @@ gcli_free_milestones(gcli_milestone_list *const it) } int -gcli_milestone_get_issues(gcli_ctx *ctx, char const *const owner, +gcli_milestone_get_issues(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone, - gcli_issue_list *const out) + struct gcli_issue_list *const out) { - return gcli_forge(ctx)->get_milestone_issues( - ctx, owner, repo, milestone, out); + gcli_null_check_call(get_milestone_issues, ctx, owner, repo, milestone, + out); } int -gcli_milestone_set_duedate(gcli_ctx *ctx, char const *const owner, +gcli_milestone_set_duedate(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone, char const *const date) { - return gcli_forge(ctx)->milestone_set_duedate( - ctx, owner, repo, milestone, date); + gcli_null_check_call(milestone_set_duedate, ctx, owner, repo, + milestone, date); } diff --git a/src/nvlist.c b/src/nvlist.c new file mode 100644 index 00000000..dc573004 --- /dev/null +++ b/src/nvlist.c @@ -0,0 +1,105 @@ +/* + * Copyright 2023 Nico Sonack + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include + +#include +#include + +int +gcli_nvlist_init(struct gcli_nvlist *list) +{ + TAILQ_INIT(list); + + return 0; +} + +int +gcli_nvlist_free(struct gcli_nvlist *list) +{ + struct gcli_nvpair *p1, *p2; + + p1 = TAILQ_FIRST(list); + while (p1 != NULL) { + p2 = TAILQ_NEXT(p1, next); + + free(p1->key); + free(p1->value); + free(p1); + + p1 = p2; + } + + TAILQ_INIT(list); + + return 0; +} + +int +gcli_nvlist_append(struct gcli_nvlist *list, char *key, char *value) +{ + /* TODO: handle the case where a pair with an already existing + * key is inserted. */ + + struct gcli_nvpair *pair = calloc(1, sizeof(*pair)); + if (pair == NULL) + return -1; + + pair->key = key; + pair->value = value; + + TAILQ_INSERT_TAIL(list, pair, next); + + return 0; +} + +char const * +gcli_nvlist_find(struct gcli_nvlist const *list, char const *key) +{ + struct gcli_nvpair const *pair; + + TAILQ_FOREACH(pair, list,next) { + if (strcmp(pair->key, key) == 0) + return pair->value; + } + return NULL; +} + +char const * +gcli_nvlist_find_or(struct gcli_nvlist const *list, char const *const key, + char const *const alternative) +{ + char const *const result = gcli_nvlist_find(list, key); + if (result) + return result; + else + return alternative; +} diff --git a/src/pgen/dump_c.c b/src/pgen/dump_c.c index c555484b..6de3ec61 100644 --- a/src/pgen/dump_c.c +++ b/src/pgen/dump_c.c @@ -37,9 +37,9 @@ pregen_array_parser(struct objparser *p, struct objentry *it) { fprintf(outfile, "static int\n" - "parse_%s_%s_array(gcli_ctx *ctx, struct json_stream *stream, " - "%s *out)\n", - p->name, it->name, p->returntype); + "parse_%s_%s_array(struct gcli_ctx *ctx, struct json_stream *stream, " + "%s%s *out)\n", + p->name, it->name, p->is_struct ? "struct " : "", p->returntype); fprintf(outfile, "{\n"); fprintf(outfile, "\tint rc = 0;\n"); fprintf(outfile, "\tif (json_peek(stream) == JSON_NULL) {\n"); @@ -139,8 +139,8 @@ objparser_dump_c(struct objparser *p) fprintf(outfile, "int\n" - "parse_%s(gcli_ctx *ctx, struct json_stream *stream, %s *out)\n", - p->name, p->returntype); + "parse_%s(struct gcli_ctx *ctx, struct json_stream *stream, %s%s *out)\n", + p->name, p->is_struct ? "struct " : "", p->returntype); fprintf(outfile, "{\n"); fprintf(outfile, "\tenum json_type key_type;\n"); fprintf(outfile, "\tconst char *key;\n\n"); @@ -166,9 +166,9 @@ arrayparser_dump_c(struct arrayparser *p) { fprintf(outfile, "int\n" - "parse_%s(gcli_ctx *ctx, struct json_stream *stream, %s **out, " + "parse_%s(struct gcli_ctx *ctx, struct json_stream *stream, %s%s **out, " "size_t *out_size)\n", - p->name, p->returntype); + p->name, p->is_struct ? "struct " : "", p->returntype); fprintf(outfile, "{\n"); fprintf(outfile, "\tif (json_peek(stream) == JSON_NULL) {\n"); fprintf(outfile, "\t\tjson_next(stream);\n"); @@ -183,7 +183,7 @@ arrayparser_dump_c(struct arrayparser *p) fprintf(outfile, "\twhile (json_peek(stream) != JSON_ARRAY_END) {\n"); fprintf(outfile, "\t\tint rc;\n"); - fprintf(outfile, "\t\t%s *it;\n", p->returntype); + fprintf(outfile, "\t\t%s%s *it;\n", p->is_struct ? "struct " : "", p->returntype); fprintf(outfile, "\t\t*out = realloc(*out, sizeof(**out) * (*out_size + 1));\n"); fprintf(outfile, "\t\tit = &(*out)[(*out_size)++];\n"); fprintf(outfile, "\t\tmemset(it, 0, sizeof(*it));\n"); diff --git a/src/pgen/dump_h.c b/src/pgen/dump_h.c index 0ce194e0..3c85843f 100644 --- a/src/pgen/dump_h.c +++ b/src/pgen/dump_h.c @@ -73,8 +73,8 @@ header_dump_h(void) void objparser_dump_h(struct objparser *p) { - fprintf(outfile, "int parse_%s(gcli_ctx *ctx, struct json_stream *, %s *);\n", - p->name, p->returntype); + fprintf(outfile, "int parse_%s(struct gcli_ctx *ctx, struct json_stream *, %s%s *);\n", + p->name, p->is_struct ? "struct " : "", p->returntype); } void @@ -96,7 +96,7 @@ footer_dump_h(void) void arrayparser_dump_h(struct arrayparser *p) { - fprintf(outfile, "int parse_%s(gcli_ctx *ctx, struct json_stream *, " - "%s **out, size_t *out_size);\n", - p->name, p->returntype); + fprintf(outfile, "int parse_%s(struct gcli_ctx *ctx, struct json_stream *, " + "%s%s **out, size_t *out_size);\n", + p->name, p->is_struct ? "struct " : "", p->returntype); } diff --git a/src/pgen/lexer.l b/src/pgen/lexer.l index ed0234d9..c4f145c7 100644 --- a/src/pgen/lexer.l +++ b/src/pgen/lexer.l @@ -61,6 +61,7 @@ use { yycol += yyleng; return USE; } array { yycol += yyleng; return ARRAY; } of { yycol += yyleng; return OF; } select { yycol += yyleng; return SELECT; } +struct { yycol += yyleng; return STRUCT; } => { yycol += yyleng; return FATARROW; } "(" { yycol += yyleng; return OPAREN; } ")" { yycol += yyleng; return CPAREN; } diff --git a/src/pgen/parser.y b/src/pgen/parser.y index dff6b13f..d600408c 100644 --- a/src/pgen/parser.y +++ b/src/pgen/parser.y @@ -48,7 +48,7 @@ static void footer_dump(void); %} %token PARSER IS OBJECT WITH AS USE FATARROW INCLUDE -%token OPAREN CPAREN SEMICOLON ARRAY OF COMMA SELECT +%token OPAREN CPAREN SEMICOLON ARRAY OF COMMA SELECT STRUCT %union { struct strlit strlit; @@ -91,9 +91,18 @@ objparser: PARSER IDENT IS OBJECT OF IDENT WITH OPAREN obj_entries CPAREN { $$.kind = OBJPARSER_ENTRIES; $$.name = $2.text; + $$.is_struct = false; $$.returntype = $6.text; $$.entries = $9; } + | PARSER IDENT IS OBJECT OF STRUCT IDENT WITH OPAREN obj_entries CPAREN + { + $$.kind = OBJPARSER_ENTRIES; + $$.name = $2.text; + $$.is_struct = true; + $$.returntype = $7.text; + $$.entries = $10; + } | PARSER IDENT IS OBJECT OF IDENT SELECT STRLIT AS IDENT { $$.kind = OBJPARSER_SELECT; @@ -107,9 +116,17 @@ objparser: PARSER IDENT IS OBJECT OF IDENT WITH OPAREN obj_entries CPAREN arrayparser: PARSER IDENT IS ARRAY OF IDENT USE IDENT { $$.name = $2.text; + $$.is_struct = false; $$.returntype = $6.text; $$.parser = $8.text; } + | PARSER IDENT IS ARRAY OF STRUCT IDENT USE IDENT + { + $$.name = $2.text; + $$.is_struct = true; + $$.returntype = $7.text; + $$.parser = $9.text; + } ; obj_entries: obj_entries COMMA obj_entry diff --git a/src/pulls.c b/src/pulls.c index a77f1b6e..373c2e3d 100644 --- a/src/pulls.c +++ b/src/pulls.c @@ -38,7 +38,7 @@ #include void -gcli_pulls_free(gcli_pull_list *const it) +gcli_pulls_free(struct gcli_pull_list *const it) { for (size_t i = 0; i < it->pulls_size; ++i) gcli_pull_free(&it->pulls[i]); @@ -50,29 +50,30 @@ gcli_pulls_free(gcli_pull_list *const it) } int -gcli_get_pulls(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_pull_fetch_details const *const details, int const max, - gcli_pull_list *const out) +gcli_get_pulls(struct gcli_ctx *ctx, char const *owner, char const *repo, + struct gcli_pull_fetch_details const *const details, int const max, + struct gcli_pull_list *const out) { - return gcli_forge(ctx)->get_pulls(ctx, owner, repo, details, max, out); + gcli_null_check_call(get_pulls, ctx, owner, repo, details, max, out); } int -gcli_pull_get_diff(gcli_ctx *ctx, FILE *stream, char const *owner, +gcli_pull_get_diff(struct gcli_ctx *ctx, FILE *stream, char const *owner, char const *reponame, gcli_id const pr_number) { - return gcli_forge(ctx)->pull_get_diff(ctx, stream, owner, reponame, pr_number); + gcli_null_check_call(pull_get_diff, ctx, stream, owner, reponame, + pr_number); } int -gcli_pull_get_commits(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const pr_number, gcli_commit_list *const out) +gcli_pull_get_commits(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const pr_number, struct gcli_commit_list *const out) { - return gcli_forge(ctx)->get_pull_commits(ctx, owner, repo, pr_number, out); + gcli_null_check_call(get_pull_commits, ctx, owner, repo, pr_number, out); } void -gcli_commits_free(gcli_commit_list *list) +gcli_commits_free(struct gcli_commit_list *list) { for (size_t i = 0; i < list->commits_size; ++i) { free(list->commits[i].sha); @@ -90,7 +91,7 @@ gcli_commits_free(gcli_commit_list *list) } void -gcli_pull_free(gcli_pull *const it) +gcli_pull_free(struct gcli_pull *const it) { free(it->author); free(it->state); @@ -104,36 +105,37 @@ gcli_pull_free(gcli_pull *const it) free(it->base_sha); free(it->milestone); free(it->coverage); + free(it->node_id); for (size_t i = 0; i < it->labels_size; ++i) - free(it->labels[i].data); + free(it->labels[i]); free(it->labels); } int -gcli_get_pull(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const pr_number, gcli_pull *const out) +gcli_get_pull(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const pr_number, struct gcli_pull *const out) { - return gcli_forge(ctx)->get_pull(ctx, owner, repo, pr_number, out); + gcli_null_check_call(get_pull, ctx, owner, repo, pr_number, out); } int -gcli_pull_get_checks(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const pr_number, gcli_pull_checks_list *out) +gcli_pull_get_checks(struct gcli_ctx *ctx, char const *owner, char const *repo, + gcli_id const pr_number, struct gcli_pull_checks_list *out) { - return gcli_forge(ctx)->get_pull_checks(ctx, owner, repo, pr_number, out); + gcli_null_check_call(get_pull_checks, ctx, owner, repo, pr_number, out); } void -gcli_pull_checks_free(gcli_pull_checks_list *list) +gcli_pull_checks_free(struct gcli_pull_checks_list *list) { switch (list->forge_type) { case GCLI_FORGE_GITHUB: - github_free_checks((github_check_list *)list); + github_free_checks((struct github_check_list *)list); break; case GCLI_FORGE_GITLAB: - gitlab_pipelines_free((gitlab_pipeline_list *)list); + gitlab_pipelines_free((struct gitlab_pipeline_list *)list); break; default: assert(0 && "unreachable"); @@ -141,84 +143,92 @@ gcli_pull_checks_free(gcli_pull_checks_list *list) } int -gcli_pull_submit(gcli_ctx *ctx, gcli_submit_pull_options opts) +gcli_pull_submit(struct gcli_ctx *ctx, struct gcli_submit_pull_options opts) { - return gcli_forge(ctx)->perform_submit_pull(ctx, opts); + if (opts.automerge) { + int const q = gcli_forge(ctx)->pull_summary_quirks; + if (q & GCLI_PRS_QUIRK_AUTOMERGE) + return gcli_error(ctx, "forge does not support auto-merge"); + } + + gcli_null_check_call(perform_submit_pull, ctx, opts); } int -gcli_pull_merge(gcli_ctx *ctx, char const *owner, char const *reponame, +gcli_pull_merge(struct gcli_ctx *ctx, char const *owner, char const *reponame, gcli_id const pr_number, enum gcli_merge_flags flags) { - return gcli_forge(ctx)->pull_merge(ctx, owner, reponame, pr_number, flags); + gcli_null_check_call(pull_merge, ctx, owner, reponame, pr_number, + flags); } int -gcli_pull_close(gcli_ctx *ctx, char const *owner, char const *reponame, +gcli_pull_close(struct gcli_ctx *ctx, char const *owner, char const *reponame, gcli_id const pr_number) { - return gcli_forge(ctx)->pull_close(ctx, owner, reponame, pr_number); + gcli_null_check_call(pull_close, ctx, owner, reponame, pr_number); } int -gcli_pull_reopen(gcli_ctx *ctx, char const *owner, char const *reponame, +gcli_pull_reopen(struct gcli_ctx *ctx, char const *owner, char const *reponame, gcli_id const pr_number) { - return gcli_forge(ctx)->pull_reopen(ctx, owner, reponame, pr_number); + gcli_null_check_call(pull_reopen, ctx, owner, reponame, pr_number); } int -gcli_pull_add_labels(gcli_ctx *ctx, char const *owner, char const *repo, +gcli_pull_add_labels(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const pr_number, char const *const labels[], size_t const labels_size) { - return gcli_forge(ctx)->pull_add_labels( - ctx, owner, repo, pr_number, labels, labels_size); + gcli_null_check_call(pull_add_labels, ctx, owner, repo, pr_number, + labels, labels_size); } int -gcli_pull_remove_labels(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const pr_number, char const *const labels[], - size_t const labels_size) +gcli_pull_remove_labels(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id const pr_number, + char const *const labels[], size_t const labels_size) { - return gcli_forge(ctx)->pull_remove_labels( - ctx, owner, repo, pr_number, labels, labels_size); + gcli_null_check_call(pull_remove_labels, ctx, owner, repo, pr_number, + labels, labels_size); } int -gcli_pull_set_milestone(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const pr_number, int milestone_id) +gcli_pull_set_milestone(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id const pr_number, + int milestone_id) { - return gcli_forge(ctx)->pull_set_milestone( - ctx, owner, repo, pr_number, milestone_id); + gcli_null_check_call(pull_set_milestone, ctx, owner, repo, pr_number, + milestone_id); } int -gcli_pull_clear_milestone(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id const pr_number) +gcli_pull_clear_milestone(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id const pr_number) { - return gcli_forge(ctx)->pull_clear_milestone(ctx, owner, repo, pr_number); + gcli_null_check_call(pull_clear_milestone, ctx, owner, repo, pr_number); } int -gcli_pull_add_reviewer(gcli_ctx *ctx, char const *owner, char const *repo, - gcli_id pr_number, char const *username) +gcli_pull_add_reviewer(struct gcli_ctx *ctx, char const *owner, + char const *repo, gcli_id pr_number, char const *username) { - return gcli_forge(ctx)->pull_add_reviewer( - ctx, owner, repo, pr_number, username); + gcli_null_check_call(pull_add_reviewer, ctx, owner, repo, pr_number, + username); } int -gcli_pull_get_patch(gcli_ctx *ctx, FILE *out, char const *owner, char const *repo, - gcli_id pull_id) +gcli_pull_get_patch(struct gcli_ctx *ctx, FILE *out, char const *owner, + char const *repo, gcli_id pull_id) { - return gcli_forge(ctx)->pull_get_patch(ctx, out, owner, repo, pull_id); + gcli_null_check_call(pull_get_patch, ctx, out, owner, repo, pull_id); } int -gcli_pull_set_title(gcli_ctx *ctx, char const *const owner, +gcli_pull_set_title(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const pull, char const *new_title) { - return gcli_forge(ctx)->pull_set_title(ctx, owner, repo, pull, new_title); + gcli_null_check_call(pull_set_title, ctx, owner, repo, pull, new_title); } diff --git a/src/releases.c b/src/releases.c index 5beddfa6..b6130f6b 100644 --- a/src/releases.c +++ b/src/releases.c @@ -35,21 +35,21 @@ #include int -gcli_get_releases(gcli_ctx *ctx, char const *owner, char const *repo, - int const max, gcli_release_list *const list) +gcli_get_releases(struct gcli_ctx *ctx, char const *owner, char const *repo, + int const max, struct gcli_release_list *const list) { - return gcli_forge(ctx)->get_releases(ctx, owner, repo, max, list); + gcli_null_check_call(get_releases, ctx, owner, repo, max, list); } void -gcli_release_free(gcli_release *release) +gcli_release_free(struct gcli_release *release) { - free(release->id.data); - free(release->name.data); - free(release->body.data); - free(release->author.data); - free(release->date.data); - free(release->upload_url.data); + free(release->id); + free(release->name); + free(release->body); + free(release->author); + free(release->date); + free(release->upload_url); for (size_t i = 0; i < release->assets_size; ++i) { free(release->assets[i].name); @@ -60,7 +60,7 @@ gcli_release_free(gcli_release *release) } void -gcli_free_releases(gcli_release_list *const list) +gcli_free_releases(struct gcli_release_list *const list) { for (size_t i = 0; i < list->releases_size; ++i) { gcli_release_free(&list->releases[i]); @@ -73,14 +73,14 @@ gcli_free_releases(gcli_release_list *const list) } int -gcli_create_release(gcli_ctx *ctx, gcli_new_release const *release) +gcli_create_release(struct gcli_ctx *ctx, struct gcli_new_release const *release) { - return gcli_forge(ctx)->create_release(ctx, release); + gcli_null_check_call(create_release, ctx, release); } int -gcli_release_push_asset(gcli_ctx *ctx, gcli_new_release *const release, - gcli_release_asset_upload const asset) +gcli_release_push_asset(struct gcli_ctx *ctx, struct gcli_new_release *const release, + struct gcli_release_asset_upload const asset) { if (release->assets_size == GCLI_RELEASE_MAX_ASSETS) return gcli_error(ctx, "too many assets"); @@ -91,8 +91,8 @@ gcli_release_push_asset(gcli_ctx *ctx, gcli_new_release *const release, } int -gcli_delete_release(gcli_ctx *ctx, char const *const owner, +gcli_delete_release(struct gcli_ctx *ctx, char const *const owner, char const *const repo, char const *const id) { - return gcli_forge(ctx)->delete_release(ctx, owner, repo, id); + gcli_null_check_call(delete_release, ctx, owner, repo, id); } diff --git a/src/repos.c b/src/repos.c index f90179bc..14a5c501 100644 --- a/src/repos.c +++ b/src/repos.c @@ -35,25 +35,25 @@ #include int -gcli_get_repos(gcli_ctx *ctx, char const *owner, int const max, - gcli_repo_list *const out) +gcli_get_repos(struct gcli_ctx *ctx, char const *owner, int const max, + struct gcli_repo_list *const out) { - return gcli_forge(ctx)->get_repos(ctx, owner, max, out); + gcli_null_check_call(get_repos, ctx, owner, max, out); } void -gcli_repo_free(gcli_repo *it) +gcli_repo_free(struct gcli_repo *it) { - free(it->full_name.data); - free(it->name.data); - free(it->owner.data); - free(it->date.data); - free(it->visibility.data); + free(it->full_name); + free(it->name); + free(it->owner); + free(it->date); + free(it->visibility); memset(it, 0, sizeof(*it)); } void -gcli_repos_free(gcli_repo_list *const list) +gcli_repos_free(struct gcli_repo_list *const list) { for (size_t i = 0; i < list->repos_size; ++i) { gcli_repo_free(&list->repos[i]); @@ -66,21 +66,21 @@ gcli_repos_free(gcli_repo_list *const list) } int -gcli_repo_delete(gcli_ctx *ctx, char const *owner, char const *repo) +gcli_repo_delete(struct gcli_ctx *ctx, char const *owner, char const *repo) { - return gcli_forge(ctx)->repo_delete(ctx, owner, repo); + gcli_null_check_call(repo_delete, ctx, owner, repo); } int -gcli_repo_create(gcli_ctx *ctx, gcli_repo_create_options const *options, - gcli_repo *out) +gcli_repo_create(struct gcli_ctx *ctx, struct gcli_repo_create_options const *options, + struct gcli_repo *out) { - return gcli_forge(ctx)->repo_create(ctx, options, out); + gcli_null_check_call(repo_create, ctx, options, out); } int -gcli_repo_set_visibility(gcli_ctx *ctx, char const *const owner, +gcli_repo_set_visibility(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_repo_visibility vis) { - return gcli_forge(ctx)->repo_set_visibility(ctx, owner, repo, vis); + gcli_null_check_call(repo_set_visibility, ctx, owner, repo, vis); } diff --git a/src/sshkeys.c b/src/sshkeys.c index 5ff34707..5f079d47 100644 --- a/src/sshkeys.c +++ b/src/sshkeys.c @@ -36,13 +36,13 @@ #include int -gcli_sshkeys_get_keys(gcli_ctx *ctx, gcli_sshkey_list *out) +gcli_sshkeys_get_keys(struct gcli_ctx *ctx, struct gcli_sshkey_list *out) { - return gcli_forge(ctx)->get_sshkeys(ctx, out); + gcli_null_check_call(get_sshkeys, ctx, out); } void -gcli_sshkeys_free_keys(gcli_sshkey_list *list) +gcli_sshkeys_free_keys(struct gcli_sshkey_list *list) { for (size_t i = 0; i < list->keys_size; ++i) { free(list->keys[i].title); @@ -57,24 +57,29 @@ gcli_sshkeys_free_keys(gcli_sshkey_list *list) } int -gcli_sshkeys_add_key(gcli_ctx *ctx, char const *title, - char const *public_key_path, gcli_sshkey *out) +gcli_sshkeys_add_key(struct gcli_ctx *ctx, char const *title, + char const *public_key_path, struct gcli_sshkey *out) { int rc; char *buffer; + struct gcli_forge_descriptor const *const forge = gcli_forge(ctx); + + if (forge->add_sshkey == NULL) { + return gcli_error(ctx, "ssh_add_key is not supported by this forge"); + } rc = sn_read_file(public_key_path, &buffer); if (rc < 0) return rc; - rc = gcli_forge(ctx)->add_sshkey(ctx, title, buffer, out); + rc = forge->add_sshkey(ctx, title, buffer, out); free(buffer); return rc; } int -gcli_sshkeys_delete_key(gcli_ctx *ctx, gcli_id const id) +gcli_sshkeys_delete_key(struct gcli_ctx *ctx, gcli_id const id) { - return gcli_forge(ctx)->delete_sshkey(ctx, id); + gcli_null_check_call(delete_sshkey, ctx, id); } diff --git a/src/status.c b/src/status.c index aaf441c4..5a3ed392 100644 --- a/src/status.c +++ b/src/status.c @@ -31,14 +31,14 @@ #include int -gcli_get_notifications(gcli_ctx *ctx, int const max, - gcli_notification_list *const out) +gcli_get_notifications(struct gcli_ctx *ctx, int const max, + struct gcli_notification_list *const out) { - return gcli_forge(ctx)->get_notifications(ctx, max, out); + gcli_null_check_call(get_notifications, ctx, max, out); } void -gcli_free_notification(gcli_notification *const notification) +gcli_free_notification(struct gcli_notification *const notification) { free(notification->id); free(notification->title); @@ -49,7 +49,7 @@ gcli_free_notification(gcli_notification *const notification) } void -gcli_free_notifications(gcli_notification_list *list) +gcli_free_notifications(struct gcli_notification_list *list) { for (size_t i = 0; i < list->notifications_size; ++i) { gcli_free_notification(&list->notifications[i]); @@ -61,7 +61,7 @@ gcli_free_notifications(gcli_notification_list *list) } int -gcli_notification_mark_as_read(gcli_ctx *ctx, char const *id) +gcli_notification_mark_as_read(struct gcli_ctx *ctx, char const *id) { - return gcli_forge(ctx)->notification_mark_as_read(ctx, id); + gcli_null_check_call(notification_mark_as_read, ctx, id); } diff --git a/templates/bugzilla/api.t b/templates/bugzilla/api.t new file mode 100644 index 00000000..1b862aad --- /dev/null +++ b/templates/bugzilla/api.t @@ -0,0 +1 @@ +parser bugzilla_get_error is object of char* select "message" as string; diff --git a/templates/bugzilla/bugs.t b/templates/bugzilla/bugs.t new file mode 100644 index 00000000..02790287 --- /dev/null +++ b/templates/bugzilla/bugs.t @@ -0,0 +1,79 @@ +include "gcli/comments.h"; +include "gcli/issues.h"; +include "gcli/bugzilla/bugs.h"; +include "gcli/bugzilla/bugs-parser.h"; + +parser bugzilla_bug_creator is +object of struct gcli_issue with + ("real_name" => author as string); + +parser bugzilla_assigned_to_detail is +object of struct gcli_issue with + ("name" => use parse_bugzilla_assignee); + +parser bugzilla_bug_item is +object of struct gcli_issue with + ("id" => number as id, + "summary" => title as string, + "creation_time" => created_at as string, + "creator_detail" => use parse_bugzilla_bug_creator, + "status" => state as string, + "product" => product as string, + "component" => component as string, + "status" => state as string, + "product" => product as string, + "component" => component as string, + "assigned_to_detail" => use parse_bugzilla_assigned_to_detail, + "url" => url as string); + +parser bugzilla_bugs is +object of struct gcli_issue_list with + ("bugs" => issues as array of gcli_issue use parse_bugzilla_bug_item); + +parser bugzilla_comment is +object of struct gcli_comment with + ("id" => id as id, + "text" => body as string, + "creation_time" => date as string, + "creator" => author as string); + +parser bugzilla_comments_internal_skip_first is +object of struct gcli_comment_list with + ("comments" => use parse_bugzilla_comments_array_skip_first); + +parser bugzilla_comments is +object of struct gcli_comment_list with + ("bugs" => use parse_bugzilla_bug_comments_dictionary_skip_first); + +parser bugzilla_comment_text is +object of char* select "text" as string; + +parser bugzilla_comments_internal_only_first is +object of char* with + ("comments" => use parse_bugzilla_comments_array_only_first); + +parser bugzilla_bug_op is +object of char* with + ("bugs" => use parse_bugzilla_bug_comments_dictionary_only_first); + +parser bugzilla_bug_attachments is +object of struct gcli_attachment_list with + ("bugs" => use parse_bugzilla_bug_attachments_dict); + +parser bugzilla_bug_attachment is +object of struct gcli_attachment with + ("id" => id as id, + "summary" => summary as string, + "file_name" => file_name as string, + "creation_time" => created_at as string, + "creator" => author as string, + "content_type" => content_type as string, + "is_obsolete" => is_obsolete as bool_relaxed, + "data" => data_base64 as string); + +parser bugzilla_bug_attachments_internal is +array of struct gcli_attachment use parse_bugzilla_bug_attachment; + +parser bugzilla_attachment_content is +object of struct gcli_attachment with + ("attachments" => use parse_bugzilla_attachment_content_only_first); diff --git a/templates/gitea/milestones.t b/templates/gitea/milestones.t index 85b70c5e..d363ca99 100644 --- a/templates/gitea/milestones.t +++ b/templates/gitea/milestones.t @@ -1,7 +1,7 @@ include "gcli/milestones.h"; parser gitea_milestone is -object of gcli_milestone with +object of struct gcli_milestone with ("id" => id as id, "title" => title as string, "created_at" => created_at as string, @@ -13,4 +13,4 @@ object of gcli_milestone with "closed_issues" => closed_issues as int); parser gitea_milestones is -array of gcli_milestone use parse_gitea_milestone; +array of struct gcli_milestone use parse_gitea_milestone; diff --git a/templates/gitea/status.t b/templates/gitea/status.t index 9196d240..50236b3b 100644 --- a/templates/gitea/status.t +++ b/templates/gitea/status.t @@ -1,20 +1,20 @@ include "gcli/status.h"; parser gitea_notification_repository is -object of gcli_notification with +object of struct gcli_notification with ("full_name" => repository as string); parser gitea_notification_status is -object of gcli_notification with +object of struct gcli_notification with ("title" => title as string, "type" => type as string); parser gitea_notification is -object of gcli_notification with +object of struct gcli_notification with ("id" => id as int_to_string, "repository" => use parse_gitea_notification_repository, "subject" => use parse_gitea_notification_status, "updated_at" => date as string); parser gitea_notifications is -array of gcli_notification use parse_gitea_notification; +array of struct gcli_notification use parse_gitea_notification; diff --git a/templates/github/checks.t b/templates/github/checks.t index cd164e25..81429e50 100644 --- a/templates/github/checks.t +++ b/templates/github/checks.t @@ -1,7 +1,7 @@ include "gcli/github/checks.h"; parser github_check is -object of gcli_github_check with +object of struct gcli_github_check with ("name" => name as string, "status" => status as string, "conclusion" => conclusion as string, @@ -10,6 +10,6 @@ object of gcli_github_check with "id" => id as id); parser github_checks is -object of github_check_list with +object of struct github_check_list with ("check_runs" => checks as array of gcli_github_check - use parse_github_check); \ No newline at end of file + use parse_github_check); diff --git a/templates/github/comments.t b/templates/github/comments.t index dcd09f15..e06f87b4 100644 --- a/templates/github/comments.t +++ b/templates/github/comments.t @@ -1,11 +1,11 @@ include "gcli/github/comments.h"; parser github_comment is -object of gcli_comment with - ("id" => id as int, +object of struct gcli_comment with + ("id" => id as id, "created_at" => date as string, "body" => body as string, "user" => author as user); -parser github_comments is array of gcli_comment - use parse_github_comment; \ No newline at end of file +parser github_comments is array of struct gcli_comment + use parse_github_comment; diff --git a/templates/github/forks.t b/templates/github/forks.t index acf96c0e..55e2a961 100644 --- a/templates/github/forks.t +++ b/templates/github/forks.t @@ -1,11 +1,11 @@ include "gcli/forks.h"; parser github_fork is -object of gcli_fork with - ("full_name" => full_name as sv, - "owner" => owner as user_sv, - "created_at" => date as sv, +object of struct gcli_fork with + ("full_name" => full_name as string, + "owner" => owner as user, + "created_at" => date as string, "forks_count" => forks as int); -parser github_forks is array of gcli_fork - use parse_github_fork; \ No newline at end of file +parser github_forks is array of struct gcli_fork + use parse_github_fork; diff --git a/templates/github/gists.t b/templates/github/gists.t index be625efe..dd771fbc 100644 --- a/templates/github/gists.t +++ b/templates/github/gists.t @@ -2,22 +2,22 @@ include "gcli/json_util.h"; include "gcli/github/gists.h"; parser github_gist_file is -object of gcli_gist_file with - ("filename" => filename as sv, - "language" => language as sv, - "raw_url" => url as sv, +object of struct gcli_gist_file with + ("filename" => filename as string, + "language" => language as string, + "raw_url" => url as string, "size" => size as size_t, - "type" => type as sv); + "type" => type as string); parser github_gist is -object of gcli_gist with - ("owner" => owner as user_sv, - "html_url" => url as sv, - "id" => id as sv, - "created_at" => date as sv, - "git_pull_url" => git_pull_url as sv, - "description" => description as sv, +object of struct gcli_gist with + ("owner" => owner as user, + "html_url" => url as string, + "id" => id as string, + "created_at" => date as string, + "git_pull_url" => git_pull_url as string, + "description" => description as string, "files" => use parse_github_gist_files_idiot_hack); -parser github_gists is array of gcli_gist - use parse_github_gist; \ No newline at end of file +parser github_gists is array of struct gcli_gist + use parse_github_gist; diff --git a/templates/github/issues.t b/templates/github/issues.t index faee7f79..05de5321 100644 --- a/templates/github/issues.t +++ b/templates/github/issues.t @@ -4,23 +4,27 @@ include "gcli/labels.h"; include "templates/github/labels.h"; parser github_issue_milestone is -object of gcli_issue with - ("title" => milestone as sv); +object of struct gcli_issue with + ("title" => milestone as string); parser github_issue is -object of gcli_issue with - ("title" => title as sv, - "state" => state as sv, - "body" => body as sv, - "created_at" => created_at as sv, +object of struct gcli_issue with + ("title" => title as string, + "state" => state as string, + "body" => body as string, + "created_at" => created_at as string, "number" => number as id, "comments" => comments as int, - "user" => author as user_sv, + "user" => author as user, "locked" => locked as bool, "labels" => labels as array of github_label use parse_github_label_text, - "assignees" => assignees as array of sn_sv use parse_user, + "assignees" => assignees as array of char* use get_user, "pull_request" => is_pr as github_is_pr, "milestone" => use parse_github_issue_milestone); -parser github_issues is array of gcli_issue use parse_github_issue; +parser github_issues is array of struct gcli_issue use parse_github_issue; + +parser github_issue_search_result is +object of struct gcli_issue_list with + ("items" => issues as array of gcli_issue use parse_github_issue); diff --git a/templates/github/labels.t b/templates/github/labels.t index 9aaf5d51..20fd63ae 100644 --- a/templates/github/labels.t +++ b/templates/github/labels.t @@ -1,13 +1,13 @@ include "gcli/github/labels.h"; -parser github_label_text is object of sn_sv select "name" as sv; +parser github_label_text is object of char* select "name" as string; parser github_label is -object of gcli_label with +object of struct gcli_label with ("id" => id as id, "name" => name as string, "description" => description as string, "color" => colour as github_style_colour); -parser github_labels is array of gcli_label +parser github_labels is array of struct gcli_label use parse_github_label; diff --git a/templates/github/milestones.t b/templates/github/milestones.t index e6cca23a..6aea61f1 100644 --- a/templates/github/milestones.t +++ b/templates/github/milestones.t @@ -1,7 +1,7 @@ include "gcli/milestones.h"; parser github_milestone is -object of gcli_milestone with +object of struct gcli_milestone with ("number" => id as id, "title" => title as string, "created_at" => created_at as string, @@ -12,4 +12,4 @@ object of gcli_milestone with "closed_issues" => closed_issues as int); parser github_milestones is -array of gcli_milestone use parse_github_milestone; +array of struct gcli_milestone use parse_github_milestone; diff --git a/templates/github/pulls.t b/templates/github/pulls.t index a8437b5a..8f2e467b 100644 --- a/templates/github/pulls.t +++ b/templates/github/pulls.t @@ -2,48 +2,49 @@ include "gcli/pulls.h"; include "templates/github/labels.h"; parser github_commit_author_field is -object of gcli_commit with +object of struct gcli_commit with ("name" => author as string, "email" => email as string, "date" => date as string); parser github_commit_commit_field is -object of gcli_commit with +object of struct gcli_commit with ("message" => message as string, "author" => use parse_github_commit_author_field); parser github_commit is -object of gcli_commit with +object of struct gcli_commit with ("sha" => long_sha as string, "commit" => use parse_github_commit_commit_field); -parser github_commits is array of gcli_commit +parser github_commits is array of struct gcli_commit use parse_github_commit; parser github_pull_head is -object of gcli_pull with +object of struct gcli_pull with ("sha" => head_sha as string, "label" => head_label as string); parser github_branch_label is -object of gcli_pull with +object of struct gcli_pull with ("label" => base_label as string); parser github_pull_milestone is -object of gcli_pull with +object of struct gcli_pull with ("title" => milestone as string); parser github_reviewer is object of char* select "login" as string; parser github_pull is -object of gcli_pull with +object of struct gcli_pull with ("title" => title as string, "state" => state as string, "body" => body as string, "created_at" => created_at as string, "number" => number as id, "id" => id as id, + "node_id" => node_id as string, "commits" => commits as int, "labels" => labels as array of github_label use parse_github_label_text, @@ -62,5 +63,5 @@ object of gcli_pull with parser github_pr_merge_message is object of char* select "message" as string; -parser github_pulls is array of gcli_pull +parser github_pulls is array of struct gcli_pull use parse_github_pull; diff --git a/templates/github/releases.t b/templates/github/releases.t index d2748590..4c176aaf 100644 --- a/templates/github/releases.t +++ b/templates/github/releases.t @@ -1,22 +1,22 @@ include "gcli/releases.h"; parser github_release_asset is -object of gcli_release_asset with +object of struct gcli_release_asset with ("browser_download_url" => url as string, "name" => name as string); parser github_release is -object of gcli_release with - ("name" => name as sv, - "body" => body as sv, - "id" => id as int_to_sv, - "author" => author as user_sv, - "created_at" => date as sv, +object of struct gcli_release with + ("name" => name as string, + "body" => body as string, + "id" => id as int_to_string, + "author" => author as user, + "created_at" => date as string, "draft" => draft as bool, "prerelease" => prerelease as bool, "assets" => assets as array of gcli_release_asset use parse_github_release_asset, - "upload_url" => upload_url as sv); + "upload_url" => upload_url as string); -parser github_releases is array of gcli_release +parser github_releases is array of struct gcli_release use parse_github_release; diff --git a/templates/github/repos.t b/templates/github/repos.t index cd08aba4..533bd43d 100644 --- a/templates/github/repos.t +++ b/templates/github/repos.t @@ -2,15 +2,15 @@ include "gcli/github/repos.h"; include "gcli/gitea/repos.h"; parser github_repo is -object of gcli_repo with +object of struct gcli_repo with ("id" => id as id, - "full_name" => full_name as sv, - "name" => name as sv, - "owner" => owner as user_sv, - "created_at" => date as sv, - "visibility" => visibility as sv, + "full_name" => full_name as string, + "name" => name as string, + "owner" => owner as user, + "created_at" => date as string, + "visibility" => visibility as string, "private" => visibility as gitea_visibility, "fork" => is_fork as bool); -parser github_repos is array of gcli_repo +parser github_repos is array of struct gcli_repo use parse_github_repo; diff --git a/templates/github/status.t b/templates/github/status.t index f6faddab..2d176b37 100644 --- a/templates/github/status.t +++ b/templates/github/status.t @@ -1,21 +1,21 @@ include "gcli/github/status.h"; parser github_notification_subject is -object of gcli_notification with +object of struct gcli_notification with ("title" => title as string, "type" => type as string); parser github_notification_repository is -object of gcli_notification with +object of struct gcli_notification with ("full_name" => repository as string); parser github_notification is -object of gcli_notification with +object of struct gcli_notification with ("updated_at" => date as string, "id" => id as string, "reason" => reason as string, "subject" => use parse_github_notification_subject, "repository" => use parse_github_notification_repository); -parser github_notifications is array of gcli_notification - use parse_github_notification; \ No newline at end of file +parser github_notifications is array of struct gcli_notification + use parse_github_notification; diff --git a/templates/gitlab/comments.t b/templates/gitlab/comments.t index 3221a4b6..6ee0f3a9 100644 --- a/templates/gitlab/comments.t +++ b/templates/gitlab/comments.t @@ -1,10 +1,11 @@ include "gcli/gitlab/comments.h"; parser gitlab_comment is -object of gcli_comment with +object of struct gcli_comment with ("created_at" => date as string, "body" => body as string, - "author" => author as user); + "author" => author as user, + "id" => id as id); parser gitlab_comments is -array of gcli_comment use parse_gitlab_comment; +array of struct gcli_comment use parse_gitlab_comment; diff --git a/templates/gitlab/forks.t b/templates/gitlab/forks.t index 7e673546..d63ab511 100644 --- a/templates/gitlab/forks.t +++ b/templates/gitlab/forks.t @@ -1,15 +1,15 @@ include "gcli/gitlab/forks.h"; parser gitlab_fork_namespace is -object of gcli_fork with - ("full_path" => owner as sv); +object of struct gcli_fork with + ("full_path" => owner as string); parser gitlab_fork is -object of gcli_fork with - ("path_with_namespace" => full_name as sv, +object of struct gcli_fork with + ("path_with_namespace" => full_name as string, "namespace" => use parse_gitlab_fork_namespace, - "created_at" => date as sv, + "created_at" => date as string, "forks_count" => forks as int); parser gitlab_forks is -array of gcli_fork use parse_gitlab_fork; +array of struct gcli_fork use parse_gitlab_fork; diff --git a/templates/gitlab/issues.t b/templates/gitlab/issues.t index 3c089348..46a90eac 100644 --- a/templates/gitlab/issues.t +++ b/templates/gitlab/issues.t @@ -1,24 +1,24 @@ include "gcli/gitlab/issues.h"; -parser gitlab_user is object of sn_sv select "username" as sv; +parser gitlab_user is object of char* select "username" as string; parser gitlab_issue_milestone is -object of gcli_issue with - ("title" => milestone as sv); +object of struct gcli_issue with + ("title" => milestone as string); parser gitlab_issue is -object of gcli_issue with - ("title" => title as sv, - "state" => state as sv, - "description" => body as sv, - "created_at" => created_at as sv, +object of struct gcli_issue with + ("title" => title as string, + "state" => state as string, + "description" => body as string, + "created_at" => created_at as string, "iid" => number as id, "user_notes_count" => comments as int, - "author" => author as user_sv, + "author" => author as user, "discussion_locked" => locked as bool, - "labels" => labels as array of sn_sv use parse_sv, + "labels" => labels as array of char* use get_string, "assignees" => assignees as array of gitlab_user use parse_gitlab_user, "milestone" => use parse_gitlab_issue_milestone); -parser gitlab_issues is array of gcli_issue use parse_gitlab_issue; +parser gitlab_issues is array of struct gcli_issue use parse_gitlab_issue; diff --git a/templates/gitlab/labels.t b/templates/gitlab/labels.t index c2a3ed84..40bf7b7b 100644 --- a/templates/gitlab/labels.t +++ b/templates/gitlab/labels.t @@ -1,10 +1,10 @@ include "gcli/gitlab/labels.h"; parser gitlab_label is -object of gcli_label with +object of struct gcli_label with ("name" => name as string, "description" => description as string, "color" => colour as gitlab_style_colour, "id" => id as id); -parser gitlab_labels is array of gcli_label use parse_gitlab_label; +parser gitlab_labels is array of struct gcli_label use parse_gitlab_label; diff --git a/templates/gitlab/merge_requests.t b/templates/gitlab/merge_requests.t index a8c4b09e..3b5eeb6c 100644 --- a/templates/gitlab/merge_requests.t +++ b/templates/gitlab/merge_requests.t @@ -1,45 +1,46 @@ include "gcli/gitlab/merge_requests.h"; parser gitlab_mr_milestone is -object of gcli_pull with +object of struct gcli_pull with ("title" => milestone as string); parser gitlab_mr_head_pipeline is -object of gcli_pull with +object of struct gcli_pull with ("id" => head_pipeline_id as int, "coverage" => coverage as string); parser gitlab_reviewer is object of char* select "username" as string; parser gitlab_diff_refs is -object of gcli_pull with +object of struct gcli_pull with ("base_sha" => base_sha as string, "head_sha" => head_sha as string); parser gitlab_mr is -object of gcli_pull with - ("title" => title as string, - "state" => state as string, - "description" => body as string, - "created_at" => created_at as string, - "iid" => number as id, - "id" => id as id, - "labels" => labels as array of sn_sv use parse_sv, - "user_notes_count" => comments as int, - "merge_status" => mergeable as gitlab_can_be_merged, - "draft" => draft as bool, - "author" => author as user, - "source_branch" => head_label as string, - "target_branch" => base_label as string, - "milestone" => use parse_gitlab_mr_milestone, - "head_pipeline" => use parse_gitlab_mr_head_pipeline, - "reviewers" => reviewers as array of char* use parse_gitlab_reviewer, - "diff_refs" => use parse_gitlab_diff_refs); +object of struct gcli_pull with + ("title" => title as string, + "state" => state as string, + "description" => body as string, + "created_at" => created_at as string, + "iid" => number as id, + "id" => id as id, + "labels" => labels as array of char* use get_string, + "user_notes_count" => comments as int, + "merge_status" => mergeable as gitlab_can_be_merged, + "draft" => draft as bool, + "author" => author as user, + "source_branch" => head_label as string, + "target_branch" => base_label as string, + "milestone" => use parse_gitlab_mr_milestone, + "head_pipeline" => use parse_gitlab_mr_head_pipeline, + "reviewers" => reviewers as array of char* use parse_gitlab_reviewer, + "diff_refs" => use parse_gitlab_diff_refs, + "merge_when_pipeline_succeeds" => automerge as bool); -parser gitlab_mrs is array of gcli_pull use parse_gitlab_mr; +parser gitlab_mrs is array of struct gcli_pull use parse_gitlab_mr; parser gitlab_commit is -object of gcli_commit with +object of struct gcli_commit with ("short_id" => sha as string, "id" => long_sha as string, "title" => message as string, @@ -47,16 +48,16 @@ object of gcli_commit with "author_name" => author as string, "author_email" => email as string); -parser gitlab_commits is array of gcli_commit use parse_gitlab_commit; +parser gitlab_commits is array of struct gcli_commit use parse_gitlab_commit; parser gitlab_reviewer_id is object of gcli_id select "id" as id; parser gitlab_reviewer_ids is -object of gitlab_reviewer_id_list with +object of struct gitlab_reviewer_id_list with ("reviewers" => reviewers as array of gcli_id use parse_gitlab_reviewer_id); parser gitlab_diff is -object of gitlab_diff with +object of struct gitlab_diff with ("diff" => diff as string, "new_path" => new_path as string, "old_path" => old_path as string, @@ -67,4 +68,4 @@ object of gitlab_diff with "deleted_file" => deleted_file as bool); parser gitlab_diffs is -array of gitlab_diff use parse_gitlab_diff; +array of struct gitlab_diff use parse_gitlab_diff; diff --git a/templates/gitlab/milestones.t b/templates/gitlab/milestones.t index dd9f8b26..108a4eef 100644 --- a/templates/gitlab/milestones.t +++ b/templates/gitlab/milestones.t @@ -1,7 +1,7 @@ include "gcli/milestones.h"; parser gitlab_milestone is -object of gcli_milestone with +object of struct gcli_milestone with ("title" => title as string, "id" => id as id, "state" => state as string, @@ -11,4 +11,4 @@ object of gcli_milestone with "due_date" => due_date as string, "expired" => expired as bool); -parser gitlab_milestones is array of gcli_milestone use parse_gitlab_milestone; +parser gitlab_milestones is array of struct gcli_milestone use parse_gitlab_milestone; diff --git a/templates/gitlab/pipelines.t b/templates/gitlab/pipelines.t index 569796cb..2e18ffe4 100644 --- a/templates/gitlab/pipelines.t +++ b/templates/gitlab/pipelines.t @@ -1,7 +1,7 @@ include "gcli/gitlab/pipelines.h"; parser gitlab_pipeline is -object of gitlab_pipeline with +object of struct gitlab_pipeline with ("status" => status as string, "created_at" => created_at as string, "updated_at" => updated_at as string, @@ -11,15 +11,15 @@ object of gitlab_pipeline with "id" => id as id); parser gitlab_pipelines is -array of gitlab_pipeline use parse_gitlab_pipeline; +array of struct gitlab_pipeline use parse_gitlab_pipeline; parser gitlab_job_runner is -object of gitlab_job with +object of struct gitlab_job with ("name" => runner_name as string, "description" => runner_description as string); parser gitlab_job is -object of gitlab_job with +object of struct gitlab_job with ("status" => status as string, "stage" => stage as string, "name" => name as string, @@ -33,4 +33,4 @@ object of gitlab_job with "coverage" => coverage as double); parser gitlab_jobs is -array of gitlab_job use parse_gitlab_job; +array of struct gitlab_job use parse_gitlab_job; diff --git a/templates/gitlab/releases.t b/templates/gitlab/releases.t index 1c4b83d9..ad67b732 100644 --- a/templates/gitlab/releases.t +++ b/templates/gitlab/releases.t @@ -1,23 +1,23 @@ include "gcli/gitlab/releases.h"; parser gitlab_release_asset is -object of gcli_release_asset with +object of struct gcli_release_asset with ("url" => url as string); parser gitlab_release_assets is -object of gcli_release with +object of struct gcli_release with ("sources" => assets as array of gcli_release_asset use parse_gitlab_release_asset); parser gitlab_release is -object of gcli_release with - ("name" => name as sv, - "tag_name" => id as sv, - "description" => body as sv, +object of struct gcli_release with + ("name" => name as string, + "tag_name" => id as string, + "description" => body as string, "assets" => use parse_gitlab_release_assets, - "author" => author as user_sv, - "created_at" => date as sv, + "author" => author as user, + "created_at" => date as string, "upcoming_release" => prerelease as bool); parser gitlab_releases is -array of gcli_release use parse_gitlab_release; \ No newline at end of file +array of struct gcli_release use parse_gitlab_release; diff --git a/templates/gitlab/repos.t b/templates/gitlab/repos.t index 5c2c9913..01429ee8 100644 --- a/templates/gitlab/repos.t +++ b/templates/gitlab/repos.t @@ -1,14 +1,14 @@ include "gcli/gitlab/repos.h"; parser gitlab_repo is -object of gcli_repo with - ("path_with_namespace" => full_name as sv, - "name" => name as sv, - "owner" => owner as user_sv, - "created_at" => date as sv, - "visibility" => visibility as sv, +object of struct gcli_repo with + ("path_with_namespace" => full_name as string, + "name" => name as string, + "owner" => owner as user, + "created_at" => date as string, + "visibility" => visibility as string, "fork" => is_fork as bool, "id" => id as id); parser gitlab_repos is -array of gcli_repo use parse_gitlab_repo; +array of struct gcli_repo use parse_gitlab_repo; diff --git a/templates/gitlab/snippets.t b/templates/gitlab/snippets.t index ed250d2d..e9025195 100644 --- a/templates/gitlab/snippets.t +++ b/templates/gitlab/snippets.t @@ -2,7 +2,7 @@ include "gcli/json_util.h"; include "gcli/gitlab/snippets.h"; parser gitlab_snippet is -object of gcli_gitlab_snippet with +object of struct gcli_gitlab_snippet with ("title" => title as string, "id" => id as int, "raw_url" => raw_url as string, @@ -12,4 +12,4 @@ object of gcli_gitlab_snippet with "visibility" => visibility as string); parser gitlab_snippets is -array of gcli_gitlab_snippet use parse_gitlab_snippet; +array of struct gcli_gitlab_snippet use parse_gitlab_snippet; diff --git a/templates/gitlab/sshkeys.t b/templates/gitlab/sshkeys.t index d9c5d8a0..b0be2074 100644 --- a/templates/gitlab/sshkeys.t +++ b/templates/gitlab/sshkeys.t @@ -1,10 +1,10 @@ include "gcli/sshkeys.h"; parser gitlab_sshkey is -object of gcli_sshkey with +object of struct gcli_sshkey with ("title" => title as string, "id" => id as id, "key" => key as string, "created_at" => created_at as string); -parser gitlab_sshkeys is array of gcli_sshkey use parse_gitlab_sshkey; +parser gitlab_sshkeys is array of struct gcli_sshkey use parse_gitlab_sshkey; diff --git a/templates/gitlab/status.t b/templates/gitlab/status.t index 932396d0..3a27bbe4 100644 --- a/templates/gitlab/status.t +++ b/templates/gitlab/status.t @@ -1,11 +1,11 @@ include "gcli/gitlab/status.h"; parser gitlab_project is -object of gcli_notification with +object of struct gcli_notification with ("path_with_namespace" => repository as string); parser gitlab_todo is -object of gcli_notification with +object of struct gcli_notification with ("updated_at" => date as string, "action_name" => reason as string, "id" => id as int_to_string, @@ -14,4 +14,4 @@ object of gcli_notification with "project" => use parse_gitlab_project); parser gitlab_todos is -array of gcli_notification use parse_gitlab_todo; +array of struct gcli_notification use parse_gitlab_todo; diff --git a/tests/Kyuafile.in b/tests/Kyuafile.in index 483eab8b..94ab5e05 100644 --- a/tests/Kyuafile.in +++ b/tests/Kyuafile.in @@ -8,17 +8,22 @@ atf_test_program{ atf_test_program{ name = 'github-parse-tests', - required_files = '@TESTSRCDIR@/samples/github_simple_issue.json @TESTSRCDIR@/samples/github_simple_pull.json @TESTSRCDIR@/samples/github_simple_label.json' } atf_test_program{ name = 'gitlab-parse-tests', - required_files = '@TESTSRCDIR@/samples/gitlab_simple_merge_request.json' } atf_test_program{ name = 'gitea-parse-tests', - required_files = '@TESTSRCDIR@/samples/gitea_simple_notification.json' +} + +atf_test_program{ + name = 'bugzilla-parse-tests', +} + +atf_test_program{ + name = 'base64-tests' } atf_test_program{ diff --git a/tests/base64-tests.c b/tests/base64-tests.c new file mode 100644 index 00000000..a7af3901 --- /dev/null +++ b/tests/base64-tests.c @@ -0,0 +1,21 @@ +#include + +#include + +ATF_TC_WITHOUT_HEAD(simple_decode); +ATF_TC_BODY(simple_decode, tc) +{ + char const input[] = "aGVsbG8gd29ybGQ="; + char output[sizeof("hello world")] = {0}; + + int rc = gcli_decode_base64(NULL, input, output, sizeof(output)); + ATF_REQUIRE(rc == 0); + ATF_CHECK_STREQ(output, "hello world"); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, simple_decode); + + return atf_no_error(); +} diff --git a/tests/bugzilla-parse-tests.c b/tests/bugzilla-parse-tests.c new file mode 100644 index 00000000..96e3115a --- /dev/null +++ b/tests/bugzilla-parse-tests.c @@ -0,0 +1,179 @@ +/* + * Copyright 2023 Nico Sonack + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include +#include +#include + +#include + +#include + +#include "gcli_tests.h" + +static gcli_forge_type +get_bugzilla_forge_type(struct gcli_ctx *ctx) +{ + (void) ctx; + return GCLI_FORGE_BUGZILLA; +} + +static struct gcli_ctx * +test_context(void) +{ + struct gcli_ctx *ctx; + ATF_REQUIRE(gcli_init(&ctx, get_bugzilla_forge_type, NULL, NULL) == NULL); + return ctx; +} + +static FILE * +open_sample(char const *const name) +{ + FILE *r; + char p[4096] = {0}; + + snprintf(p, sizeof p, "%s/samples/%s", TESTSRCDIR, name); + + r = fopen(p, "r"); + return r; +} + +ATF_TC_WITHOUT_HEAD(simple_bugzilla_issue); +ATF_TC_BODY(simple_bugzilla_issue, tc) +{ + struct gcli_issue_list list = {0}; + struct gcli_issue const *issue; + FILE *f; + struct json_stream stream; + struct gcli_ctx *ctx = test_context(); + + ATF_REQUIRE(f = open_sample("bugzilla_simple_bug.json")); + json_open_stream(&stream, f); + + ATF_REQUIRE(parse_bugzilla_bugs(ctx, &stream, &list) == 0); + + ATF_REQUIRE_EQ(list.issues_size, 1); + + issue = &list.issues[0]; + + ATF_CHECK_EQ(issue->number, 1); + ATF_CHECK_STREQ(issue->title, "[aha] [scsi] Toshiba MK156FB scsi drive does not work with 2.0 kernel"); + ATF_CHECK_STREQ(issue->created_at, "1994-09-14T09:10:01Z"); + ATF_CHECK_STREQ(issue->author, "Dave Evans"); + ATF_CHECK_STREQ(issue->state, "Closed"); + ATF_CHECK_STREQ(issue->product, "Base System"); + ATF_CHECK_STREQ(issue->component, "kern"); + + json_close(&stream); + gcli_destroy(&ctx); +} + +ATF_TC_WITHOUT_HEAD(bugzilla_comments); +ATF_TC_BODY(bugzilla_comments, tc) +{ + FILE *f; + struct gcli_comment const *cmt = NULL; + struct gcli_comment_list list = {0}; + struct gcli_ctx *ctx = test_context(); + struct json_stream stream; + + ATF_REQUIRE(f = open_sample("bugzilla_comments.json")); + json_open_stream(&stream, f); + + ATF_REQUIRE(parse_bugzilla_comments(ctx, &stream, &list) == 0); + json_close(&stream); + fclose(f); + f = NULL; + + ATF_REQUIRE_EQ(list.comments_size, 1); + + cmt = &list.comments[0]; + ATF_CHECK_EQ(cmt->id, 1285943); + ATF_CHECK_STREQ(cmt->author, "zlei@FreeBSD.org"); + ATF_CHECK_STREQ(cmt->date, "2023-11-27T17:20:15Z"); + ATF_CHECK(cmt->body != NULL); + + gcli_comments_free(&list); + gcli_destroy(&ctx); +} + +ATF_TC_WITHOUT_HEAD(bugzilla_attachments); +ATF_TC_BODY(bugzilla_attachments, tc) +{ + FILE *f = NULL; + struct gcli_attachment const *it; + struct gcli_attachment_list list = {0}; + struct gcli_ctx *ctx = test_context(); + struct json_stream stream = {0}; + + ATF_REQUIRE(f = open_sample("bugzilla_attachments.json")); + json_open_stream(&stream, f); + + ATF_REQUIRE(parse_bugzilla_bug_attachments(ctx, &stream, &list) == 0); + + ATF_CHECK(list.attachments_size == 2); + + it = list.attachments; + ATF_CHECK_EQ(it->id, 246131); + ATF_CHECK_EQ(it->is_obsolete, true); + ATF_CHECK_STREQ(it->author, "nsonack@outlook.com"); + ATF_CHECK_STREQ(it->content_type, "text/plain"); + ATF_CHECK_STREQ(it->created_at, "2023-11-04T20:19:11Z"); + ATF_CHECK_STREQ(it->file_name, "0001-devel-open62541-Update-to-version-1.3.8.patch"); + ATF_CHECK_STREQ(it->summary, "Patch for updating the port"); + + it++; + ATF_CHECK_EQ(it->id, 246910); + ATF_CHECK_EQ(it->is_obsolete, false); + ATF_CHECK_STREQ(it->author, "nsonack@outlook.com"); + ATF_CHECK_STREQ(it->content_type, "text/plain"); + ATF_CHECK_STREQ(it->created_at, "2023-12-08T17:10:06Z"); + ATF_CHECK_STREQ(it->file_name, "0001-devel-open62541-Update-to-version-1.3.8.patch"); + ATF_CHECK_STREQ(it->summary, "Patch v2 (now for version 1.3.9)"); + + gcli_attachments_free(&list); + + json_close(&stream); + fclose(f); + f = NULL; + + gcli_destroy(&ctx); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, simple_bugzilla_issue); + ATF_TP_ADD_TC(tp, bugzilla_comments); + ATF_TP_ADD_TC(tp, bugzilla_attachments); + + return atf_no_error(); +} diff --git a/tests/gitea-parse-tests.c b/tests/gitea-parse-tests.c index f0a2badc..a68497fd 100644 --- a/tests/gitea-parse-tests.c +++ b/tests/gitea-parse-tests.c @@ -12,16 +12,16 @@ #include static gcli_forge_type -get_gitea_forge_type(gcli_ctx *ctx) +get_gitea_forge_type(struct gcli_ctx *ctx) { (void) ctx; return GCLI_FORGE_GITEA; } -static gcli_ctx * +static struct gcli_ctx * test_context(void) { - gcli_ctx *ctx; + struct gcli_ctx *ctx; ATF_REQUIRE(gcli_init(&ctx, get_gitea_forge_type, NULL, NULL) == NULL); return ctx; } @@ -42,10 +42,10 @@ open_sample(char const *const name) ATF_TC_WITHOUT_HEAD(gitea_simple_notification); ATF_TC_BODY(gitea_simple_notification, tc) { - gcli_notification notification = {0}; + struct gcli_notification notification = {0}; FILE *sample; - json_stream stream = {0}; - gcli_ctx *ctx; + struct json_stream stream = {0}; + struct gcli_ctx *ctx; ctx = test_context(); sample = open_sample("gitea_simple_notification.json"); diff --git a/tests/github-parse-tests.c b/tests/github-parse-tests.c index ff3ba52a..a39756b2 100644 --- a/tests/github-parse-tests.c +++ b/tests/github-parse-tests.c @@ -45,16 +45,16 @@ #include "gcli_tests.h" static gcli_forge_type -get_github_forge_type(gcli_ctx *ctx) +get_github_forge_type(struct gcli_ctx *ctx) { (void) ctx; return GCLI_FORGE_GITHUB; } -static gcli_ctx * +static struct gcli_ctx * test_context(void) { - gcli_ctx *ctx; + struct gcli_ctx *ctx; ATF_REQUIRE(gcli_init(&ctx, get_github_forge_type, NULL, NULL) == NULL); return ctx; } @@ -74,10 +74,10 @@ open_sample(char const *const name) ATF_TC_WITHOUT_HEAD(simple_github_issue); ATF_TC_BODY(simple_github_issue, tc) { - gcli_issue issue = {0}; + struct gcli_issue issue = {0}; FILE *f; - json_stream stream; - gcli_ctx *ctx = test_context(); + struct json_stream stream; + struct gcli_ctx *ctx = test_context(); ATF_REQUIRE(f = open_sample("github_simple_issue.json")); json_open_stream(&stream, f); @@ -85,18 +85,18 @@ ATF_TC_BODY(simple_github_issue, tc) ATF_REQUIRE(parse_github_issue(ctx, &stream, &issue) == 0); ATF_CHECK(issue.number = 115); - ATF_CHECK_SV_EQTO(issue.title, "consider removing FILE *out from printing functions"); - ATF_CHECK_SV_EQTO(issue.created_at, "2022-03-22T16:06:10Z"); - ATF_CHECK_SV_EQTO(issue.author, "herrhotzenplotz"); - ATF_CHECK_SV_EQTO(issue.state, "closed"); + ATF_CHECK_STREQ(issue.title, "consider removing FILE *out from printing functions"); + ATF_CHECK_STREQ(issue.created_at, "2022-03-22T16:06:10Z"); + ATF_CHECK_STREQ(issue.author, "herrhotzenplotz"); + ATF_CHECK_STREQ(issue.state, "closed"); ATF_CHECK(issue.comments == 0); ATF_CHECK(issue.locked == false); - ATF_CHECK_SV_EQTO(issue.body, - "We use these functions with ghcli only anyways. In " - "the GUI stuff we use the datastructures returned by " - "the api directly. And If we output, it is stdout " - "everywhere.\n"); + ATF_CHECK_STREQ(issue.body, + "We use these functions with ghcli only anyways. In " + "the GUI stuff we use the datastructures returned by " + "the api directly. And If we output, it is stdout " + "everywhere.\n"); ATF_CHECK(issue.labels_size == 0); ATF_CHECK(issue.labels == NULL); @@ -105,7 +105,7 @@ ATF_TC_BODY(simple_github_issue, tc) ATF_CHECK(issue.assignees == NULL); ATF_CHECK(issue.is_pr == 0); - ATF_CHECK(sn_sv_null(issue.milestone)); + ATF_CHECK(!issue.milestone); json_close(&stream); gcli_issue_free(&issue); @@ -115,10 +115,10 @@ ATF_TC_BODY(simple_github_issue, tc) ATF_TC_WITHOUT_HEAD(simple_github_pull); ATF_TC_BODY(simple_github_pull, tc) { - gcli_pull pull = {0}; + struct gcli_pull pull = {0}; FILE *f; - json_stream stream; - gcli_ctx *ctx = test_context(); + struct json_stream stream; + struct gcli_ctx *ctx = test_context(); ATF_REQUIRE(f = open_sample("github_simple_pull.json")); json_open_stream(&stream, f); @@ -155,10 +155,10 @@ ATF_TC_BODY(simple_github_pull, tc) ATF_TC_WITHOUT_HEAD(simple_github_label); ATF_TC_BODY(simple_github_label, tc) { - gcli_label label = {0}; + struct gcli_label label = {0}; FILE *f; - json_stream stream; - gcli_ctx *ctx = test_context(); + struct json_stream stream; + struct gcli_ctx *ctx = test_context(); ATF_REQUIRE(f = open_sample("github_simple_label.json")); json_open_stream(&stream, f); @@ -178,10 +178,10 @@ ATF_TC_BODY(simple_github_label, tc) ATF_TC_WITHOUT_HEAD(simple_github_milestone); ATF_TC_BODY(simple_github_milestone, tc) { - gcli_milestone milestone = {0}; + struct gcli_milestone milestone = {0}; FILE *f; - json_stream stream; - gcli_ctx *ctx = test_context(); + struct json_stream stream; + struct gcli_ctx *ctx = test_context(); ATF_REQUIRE(f = open_sample("github_simple_milestone.json")); json_open_stream(&stream, f); @@ -207,24 +207,24 @@ ATF_TC_BODY(simple_github_milestone, tc) ATF_TC_WITHOUT_HEAD(simple_github_release); ATF_TC_BODY(simple_github_release, tc) { - gcli_release release = {0}; + struct gcli_release release = {0}; FILE *f; - json_stream stream; - gcli_ctx *ctx = test_context(); + struct json_stream stream; + struct gcli_ctx *ctx = test_context(); ATF_REQUIRE(f = open_sample("github_simple_release.json")); json_open_stream(&stream, f); ATF_REQUIRE(parse_github_release(ctx, &stream, &release) == 0); - ATF_CHECK_SV_EQTO(release.id, "116031718"); + ATF_CHECK_STREQ(release.id, "116031718"); ATF_CHECK(release.assets_size == 0); ATF_CHECK(release.assets == NULL); - ATF_CHECK_SV_EQTO(release.name, "1.2.0"); - ATF_CHECK_SV_EQTO(release.body, "# Version 1.2.0\n\nThis is version 1.2.0 of gcli.\n\n## Notes\n\nPlease test and report bugs.\n\nYou can download autotoolized tarballs at: https://herrhotzenplotz.de/gcli/releases/gcli-1.2.0/\n\n## Bug Fixes\n\n- Fix compile error when providing --with-libcurl without any arguments\n- Fix memory leaks in string processing functions\n- Fix missing nul termination in read-file function\n- Fix segmentation fault when clearing the milestone of a PR on Gitea\n- Fix missing documentation for milestone action in issues and pulls\n- Set the 'merged' flag properly when showing Gitlab merge requests\n\n## New features\n\n- Add a config subcommand for managing ssh keys (see gcli-config(1))\n- Show number of comments/notes in list of issues and PRs\n- Add support for milestone management in pull requests\n"); - ATF_CHECK_SV_EQTO(release.author, "herrhotzenplotz"); - ATF_CHECK_SV_EQTO(release.date, "2023-08-11T07:42:37Z"); - ATF_CHECK_SV_EQTO(release.upload_url, "https://uploads.github.com/repos/herrhotzenplotz/gcli/releases/116031718/assets{?name,label}"); + ATF_CHECK_STREQ(release.name, "1.2.0"); + ATF_CHECK_STREQ(release.body, "# Version 1.2.0\n\nThis is version 1.2.0 of gcli.\n\n## Notes\n\nPlease test and report bugs.\n\nYou can download autotoolized tarballs at: https://herrhotzenplotz.de/gcli/releases/gcli-1.2.0/\n\n## Bug Fixes\n\n- Fix compile error when providing --with-libcurl without any arguments\n- Fix memory leaks in string processing functions\n- Fix missing nul termination in read-file function\n- Fix segmentation fault when clearing the milestone of a PR on Gitea\n- Fix missing documentation for milestone action in issues and pulls\n- Set the 'merged' flag properly when showing Gitlab merge requests\n\n## New features\n\n- Add a config subcommand for managing ssh keys (see gcli-config(1))\n- Show number of comments/notes in list of issues and PRs\n- Add support for milestone management in pull requests\n"); + ATF_CHECK_STREQ(release.author, "herrhotzenplotz"); + ATF_CHECK_STREQ(release.date, "2023-08-11T07:42:37Z"); + ATF_CHECK_STREQ(release.upload_url, "https://uploads.github.com/repos/herrhotzenplotz/gcli/releases/116031718/assets{?name,label}"); ATF_CHECK(release.draft == false); ATF_CHECK(release.prerelease == false); @@ -236,10 +236,10 @@ ATF_TC_BODY(simple_github_release, tc) ATF_TC_WITHOUT_HEAD(simple_github_repo); ATF_TC_BODY(simple_github_repo, tc) { - gcli_repo repo = {0}; + struct gcli_repo repo = {0}; FILE *f; - json_stream stream; - gcli_ctx *ctx = test_context(); + struct json_stream stream; + struct gcli_ctx *ctx = test_context(); ATF_REQUIRE(f = open_sample("github_simple_repo.json")); json_open_stream(&stream, f); @@ -247,11 +247,11 @@ ATF_TC_BODY(simple_github_repo, tc) ATF_REQUIRE(parse_github_repo(ctx, &stream, &repo) == 0); ATF_CHECK(repo.id == 415015197); - ATF_CHECK_SV_EQTO(repo.full_name, "herrhotzenplotz/gcli"); - ATF_CHECK_SV_EQTO(repo.name, "gcli"); - ATF_CHECK_SV_EQTO(repo.owner, "herrhotzenplotz"); - ATF_CHECK_SV_EQTO(repo.date, "2021-10-08T14:20:15Z"); - ATF_CHECK_SV_EQTO(repo.visibility, "public"); + ATF_CHECK_STREQ(repo.full_name, "herrhotzenplotz/gcli"); + ATF_CHECK_STREQ(repo.name, "gcli"); + ATF_CHECK_STREQ(repo.owner, "herrhotzenplotz"); + ATF_CHECK_STREQ(repo.date, "2021-10-08T14:20:15Z"); + ATF_CHECK_STREQ(repo.visibility, "public"); ATF_CHECK(repo.is_fork == false); json_close(&stream); @@ -262,19 +262,19 @@ ATF_TC_BODY(simple_github_repo, tc) ATF_TC_WITHOUT_HEAD(simple_github_fork); ATF_TC_BODY(simple_github_fork, tc) { - gcli_fork fork = {0}; + struct gcli_fork fork = {0}; FILE *f; - json_stream stream; - gcli_ctx *ctx = test_context(); + struct json_stream stream; + struct gcli_ctx *ctx = test_context(); ATF_REQUIRE(f = open_sample("github_simple_fork.json")); json_open_stream(&stream, f); ATF_REQUIRE(parse_github_fork(ctx, &stream, &fork) == 0); - ATF_CHECK_SV_EQTO(fork.full_name, "gjnoonan/quick-lint-js"); - ATF_CHECK_SV_EQTO(fork.owner, "gjnoonan"); - ATF_CHECK_SV_EQTO(fork.date, "2023-05-11T05:37:41Z"); + ATF_CHECK_STREQ(fork.full_name, "gjnoonan/quick-lint-js"); + ATF_CHECK_STREQ(fork.owner, "gjnoonan"); + ATF_CHECK_STREQ(fork.date, "2023-05-11T05:37:41Z"); ATF_CHECK(fork.forks == 0); json_close(&stream); @@ -285,10 +285,10 @@ ATF_TC_BODY(simple_github_fork, tc) ATF_TC_WITHOUT_HEAD(simple_github_comment); ATF_TC_BODY(simple_github_comment, tc) { - gcli_comment comment = {0}; + struct gcli_comment comment = {0}; FILE *f; - json_stream stream; - gcli_ctx *ctx = test_context(); + struct json_stream stream; + struct gcli_ctx *ctx = test_context(); ATF_REQUIRE(f = open_sample("github_simple_comment.json")); json_open_stream(&stream, f); @@ -308,10 +308,10 @@ ATF_TC_BODY(simple_github_comment, tc) ATF_TC_WITHOUT_HEAD(simple_github_check); ATF_TC_BODY(simple_github_check, tc) { - gcli_github_check check = {0}; + struct gcli_github_check check = {0}; FILE *f; - json_stream stream; - gcli_ctx *ctx = test_context(); + struct json_stream stream; + struct gcli_ctx *ctx = test_context(); ATF_REQUIRE(f = open_sample("github_simple_check.json")); json_open_stream(&stream, f); diff --git a/tests/gitlab-parse-tests.c b/tests/gitlab-parse-tests.c index be9cf383..a8153482 100644 --- a/tests/gitlab-parse-tests.c +++ b/tests/gitlab-parse-tests.c @@ -16,16 +16,16 @@ #include "gcli_tests.h" static gcli_forge_type -get_gitlab_forge_type(gcli_ctx *ctx) +get_gitlab_forge_type(struct gcli_ctx *ctx) { (void) ctx; return GCLI_FORGE_GITLAB; } -static gcli_ctx * +static struct gcli_ctx * test_context(void) { - gcli_ctx *ctx; + struct gcli_ctx *ctx; ATF_REQUIRE(gcli_init(&ctx, get_gitlab_forge_type, NULL, NULL) == NULL); return ctx; } @@ -46,10 +46,10 @@ open_sample(char const *const name) ATF_TC_WITHOUT_HEAD(gitlab_simple_merge_request); ATF_TC_BODY(gitlab_simple_merge_request, tc) { - json_stream stream = {0}; - gcli_ctx *ctx = test_context(); + struct json_stream stream = {0}; + struct gcli_ctx *ctx = test_context(); FILE *f = open_sample("gitlab_simple_merge_request.json"); - gcli_pull pull = {0}; + struct gcli_pull pull = {0}; json_open_stream(&stream, f); ATF_REQUIRE(parse_gitlab_mr(ctx, &stream, &pull) == 0); @@ -86,28 +86,28 @@ ATF_TC_BODY(gitlab_simple_merge_request, tc) ATF_TC_WITHOUT_HEAD(gitlab_simple_issue); ATF_TC_BODY(gitlab_simple_issue, tc) { - json_stream stream = {0}; - gcli_ctx *ctx = test_context(); + struct json_stream stream = {0}; + struct gcli_ctx *ctx = test_context(); FILE *f = open_sample("gitlab_simple_issue.json"); - gcli_issue issue = {0}; + struct gcli_issue issue = {0}; json_open_stream(&stream, f); ATF_REQUIRE(parse_gitlab_issue(ctx, &stream, &issue) == 0); ATF_CHECK(issue.number == 193); - ATF_CHECK_SV_EQTO(issue.title, "Make notifications API use a list struct containing both the ptr and size"); - ATF_CHECK_SV_EQTO(issue.created_at, "2023-08-13T18:43:05.766Z"); - ATF_CHECK_SV_EQTO(issue.author, "herrhotzenplotz"); - ATF_CHECK_SV_EQTO(issue.state, "closed"); + ATF_CHECK_STREQ(issue.title, "Make notifications API use a list struct containing both the ptr and size"); + ATF_CHECK_STREQ(issue.created_at, "2023-08-13T18:43:05.766Z"); + ATF_CHECK_STREQ(issue.author, "herrhotzenplotz"); + ATF_CHECK_STREQ(issue.state, "closed"); ATF_CHECK(issue.comments == 2); ATF_CHECK(issue.locked == false); - ATF_CHECK_SV_EQTO(issue.body, "That would make some of the code much cleaner"); + ATF_CHECK_STREQ(issue.body, "That would make some of the code much cleaner"); ATF_CHECK(issue.labels_size == 1); - ATF_CHECK_SV_EQTO(issue.labels[0], "good-first-issue"); + ATF_CHECK_STREQ(issue.labels[0], "good-first-issue"); ATF_CHECK(issue.assignees == NULL); ATF_CHECK(issue.assignees_size == 0); ATF_CHECK(issue.is_pr == 0); - ATF_CHECK(sn_sv_null(issue.milestone)); + ATF_CHECK(issue.milestone == NULL); json_close(&stream); gcli_issue_free(&issue); @@ -117,10 +117,10 @@ ATF_TC_BODY(gitlab_simple_issue, tc) ATF_TC_WITHOUT_HEAD(gitlab_simple_label); ATF_TC_BODY(gitlab_simple_label, tc) { - json_stream stream = {0}; - gcli_ctx *ctx = test_context(); + struct json_stream stream = {0}; + struct gcli_ctx *ctx = test_context(); FILE *f = open_sample("gitlab_simple_label.json"); - gcli_label label = {0}; + struct gcli_label label = {0}; json_open_stream(&stream, f); ATF_REQUIRE(parse_gitlab_label(ctx, &stream, &label) == 0); @@ -138,10 +138,10 @@ ATF_TC_BODY(gitlab_simple_label, tc) ATF_TC_WITHOUT_HEAD(gitlab_simple_release); ATF_TC_BODY(gitlab_simple_release, tc) { - json_stream stream = {0}; - gcli_ctx *ctx = test_context(); + struct json_stream stream = {0}; + struct gcli_ctx *ctx = test_context(); FILE *f = open_sample("gitlab_simple_release.json"); - gcli_release release = {0}; + struct gcli_release release = {0}; json_open_stream(&stream, f); ATF_REQUIRE(parse_gitlab_release(ctx, &stream, &release) == 0); @@ -152,7 +152,7 @@ ATF_TC_BODY(gitlab_simple_release, tc) gitlab_fixup_release_assets(ctx, &release); /* NOTE(Nico): on gitlab this is the tag name */ - ATF_CHECK_SV_EQTO(release.id, "1.2.0"); + ATF_CHECK_STREQ(release.id, "1.2.0"); ATF_CHECK(release.assets_size == 4); { ATF_CHECK_STREQ(release.assets[0].name, "gcli-1.2.0.zip"); @@ -175,11 +175,11 @@ ATF_TC_BODY(gitlab_simple_release, tc) "https://gitlab.com/herrhotzenplotz/gcli/-/archive/1.2.0/gcli-1.2.0.tar"); } - ATF_CHECK_SV_EQTO(release.name, "1.2.0"); - ATF_CHECK_SV_EQTO(release.body, "# Version 1.2.0\n\nThis is version 1.2.0 of gcli.\n\n## Notes\n\nPlease test and report bugs.\n\nYou can download autotoolized tarballs at: https://herrhotzenplotz.de/gcli/releases/gcli-1.2.0/\n\n## Bug Fixes\n\n- Fix compile error when providing --with-libcurl without any arguments\n- Fix memory leaks in string processing functions\n- Fix missing nul termination in read-file function\n- Fix segmentation fault when clearing the milestone of a PR on Gitea\n- Fix missing documentation for milestone action in issues and pulls\n- Set the 'merged' flag properly when showing Gitlab merge requests\n\n## New features\n\n- Add a config subcommand for managing ssh keys (see gcli-config(1))\n- Show number of comments/notes in list of issues and PRs\n- Add support for milestone management in pull requests\n"); - ATF_CHECK_SV_EQTO(release.author, "herrhotzenplotz"); - ATF_CHECK_SV_EQTO(release.date, "2023-08-11T07:56:06.371Z"); - ATF_CHECK(sn_sv_null(release.upload_url)); + ATF_CHECK_STREQ(release.name, "1.2.0"); + ATF_CHECK_STREQ(release.body, "# Version 1.2.0\n\nThis is version 1.2.0 of gcli.\n\n## Notes\n\nPlease test and report bugs.\n\nYou can download autotoolized tarballs at: https://herrhotzenplotz.de/gcli/releases/gcli-1.2.0/\n\n## Bug Fixes\n\n- Fix compile error when providing --with-libcurl without any arguments\n- Fix memory leaks in string processing functions\n- Fix missing nul termination in read-file function\n- Fix segmentation fault when clearing the milestone of a PR on Gitea\n- Fix missing documentation for milestone action in issues and pulls\n- Set the 'merged' flag properly when showing Gitlab merge requests\n\n## New features\n\n- Add a config subcommand for managing ssh keys (see gcli-config(1))\n- Show number of comments/notes in list of issues and PRs\n- Add support for milestone management in pull requests\n"); + ATF_CHECK_STREQ(release.author, "herrhotzenplotz"); + ATF_CHECK_STREQ(release.date, "2023-08-11T07:56:06.371Z"); + ATF_CHECK(release.upload_url == NULL); ATF_CHECK(release.draft == false); ATF_CHECK(release.prerelease == false); @@ -191,17 +191,17 @@ ATF_TC_BODY(gitlab_simple_release, tc) ATF_TC_WITHOUT_HEAD(gitlab_simple_fork); ATF_TC_BODY(gitlab_simple_fork, tc) { - json_stream stream = {0}; - gcli_ctx *ctx = test_context(); + struct json_stream stream = {0}; + struct gcli_ctx *ctx = test_context(); FILE *f = open_sample("gitlab_simple_fork.json"); - gcli_fork fork = {0}; + struct gcli_fork fork = {0}; json_open_stream(&stream, f); ATF_REQUIRE(parse_gitlab_fork(ctx, &stream, &fork) == 0); - ATF_CHECK_SV_EQTO(fork.full_name, "gjnoonan/gcli"); - ATF_CHECK_SV_EQTO(fork.owner, "gjnoonan"); - ATF_CHECK_SV_EQTO(fork.date, "2022-10-02T13:54:20.517Z"); + ATF_CHECK_STREQ(fork.full_name, "gjnoonan/gcli"); + ATF_CHECK_STREQ(fork.owner, "gjnoonan"); + ATF_CHECK_STREQ(fork.date, "2022-10-02T13:54:20.517Z"); ATF_CHECK(fork.forks == 0); json_close(&stream); @@ -212,10 +212,10 @@ ATF_TC_BODY(gitlab_simple_fork, tc) ATF_TC_WITHOUT_HEAD(gitlab_simple_milestone); ATF_TC_BODY(gitlab_simple_milestone, tc) { - json_stream stream = {0}; - gcli_ctx *ctx = test_context(); + struct json_stream stream = {0}; + struct gcli_ctx *ctx = test_context(); FILE *f = open_sample("gitlab_simple_milestone.json"); - gcli_milestone milestone = {0}; + struct gcli_milestone milestone = {0}; json_open_stream(&stream, f); ATF_REQUIRE(parse_gitlab_milestone(ctx, &stream, &milestone) == 0); @@ -241,10 +241,10 @@ ATF_TC_BODY(gitlab_simple_milestone, tc) ATF_TC_WITHOUT_HEAD(gitlab_simple_pipeline); ATF_TC_BODY(gitlab_simple_pipeline, tc) { - json_stream stream = {0}; - gcli_ctx *ctx = test_context(); + struct json_stream stream = {0}; + struct gcli_ctx *ctx = test_context(); FILE *f = open_sample("gitlab_simple_pipeline.json"); - gitlab_pipeline pipeline = {0}; + struct gitlab_pipeline pipeline = {0}; json_open_stream(&stream, f); ATF_REQUIRE(parse_gitlab_pipeline(ctx, &stream, &pipeline) == 0); @@ -265,20 +265,20 @@ ATF_TC_BODY(gitlab_simple_pipeline, tc) ATF_TC_WITHOUT_HEAD(gitlab_simple_repo); ATF_TC_BODY(gitlab_simple_repo, tc) { - json_stream stream = {0}; - gcli_ctx *ctx = test_context(); + struct json_stream stream = {0}; + struct gcli_ctx *ctx = test_context(); FILE *f = open_sample("gitlab_simple_repo.json"); - gcli_repo repo = {0}; + struct gcli_repo repo = {0}; json_open_stream(&stream, f); ATF_REQUIRE(parse_gitlab_repo(ctx, &stream, &repo) == 0); ATF_CHECK(repo.id == 34707535); - ATF_CHECK_SV_EQTO(repo.full_name, "herrhotzenplotz/gcli"); - ATF_CHECK_SV_EQTO(repo.name, "gcli"); - ATF_CHECK_SV_EQTO(repo.owner, "herrhotzenplotz"); - ATF_CHECK_SV_EQTO(repo.date, "2022-03-22T16:57:59.891Z"); - ATF_CHECK_SV_EQTO(repo.visibility, "public"); + ATF_CHECK_STREQ(repo.full_name, "herrhotzenplotz/gcli"); + ATF_CHECK_STREQ(repo.name, "gcli"); + ATF_CHECK_STREQ(repo.owner, "herrhotzenplotz"); + ATF_CHECK_STREQ(repo.date, "2022-03-22T16:57:59.891Z"); + ATF_CHECK_STREQ(repo.visibility, "public"); ATF_CHECK(repo.is_fork == false); json_close(&stream); @@ -289,10 +289,10 @@ ATF_TC_BODY(gitlab_simple_repo, tc) ATF_TC_WITHOUT_HEAD(gitlab_simple_snippet); ATF_TC_BODY(gitlab_simple_snippet, tc) { - json_stream stream = {0}; - gcli_ctx *ctx = test_context(); + struct json_stream stream = {0}; + struct gcli_ctx *ctx = test_context(); FILE *f = open_sample("gitlab_simple_snippet.json"); - gcli_gitlab_snippet snippet = {0}; + struct gcli_gitlab_snippet snippet = {0}; json_open_stream(&stream, f); ATF_REQUIRE(parse_gitlab_snippet(ctx, &stream, &snippet) == 0); diff --git a/tests/samples/bugzilla_attachments.json b/tests/samples/bugzilla_attachments.json new file mode 100644 index 00000000..f8981c09 --- /dev/null +++ b/tests/samples/bugzilla_attachments.json @@ -0,0 +1,59 @@ +{ + "attachments": {}, + "bugs": { + "274920": [ + { + "bug_id": 274920, + "creation_time": "2023-11-04T20:19:11Z", + "creator": "nsonack@outlook.com", + "size": 2290, + "data": "RnJvbSA0ZWFiYjEzNTU4YTY5YmQwYWJlYmExMmQ0YzBmNjA5YmY3NTFjZTMyIE1vbiBTZXAgMTcgMDA6MDA6MDAgMjAwMQpGcm9tOiBOaWNvIFNvbmFjayA8bnNvbmFja0BoZXJyaG90emVucGxvdHouZGU+CkRhdGU6IEZyaSwgMjcgT2N0IDIwMjMgMTY6MTY6MjEgKzAwMDAKU3ViamVjdDogW1BBVENIXSBkZXZlbC9vcGVuNjI1NDE6IFVwZGF0ZSB0byB2ZXJzaW9uIDEuMy44CgpSZW1vdmVzIHRoZSBwYXRjaCBhcyB0aGUgdXBzdHJlYW0gYnVnIHJlcG9ydCBoYXMgYmVlbiByZXNvbHZlZC4KU2VlIGh0dHBzOi8vZ2l0aHViLmNvbS9vcGVuNjI1NDEvb3BlbjYyNTQxL2lzc3Vlcy82MDEwCgpTaWduZWQtb2ZmLWJ5OiBOaWNvIFNvbmFjayA8bnNvbmFja0BoZXJyaG90emVucGxvdHouZGU+Ci0tLQogZGV2ZWwvb3BlbjYyNTQxL01ha2VmaWxlICAgICAgICAgICAgICAgICAgIHwgIDIgKy0KIGRldmVsL29wZW42MjU0MS9kaXN0aW5mbyAgICAgICAgICAgICAgICAgICB8ICA2ICsrKy0tLQogZGV2ZWwvb3BlbjYyNTQxL2ZpbGVzL3BhdGNoLUNNYWtlTGlzdHMudHh0IHwgMTEgLS0tLS0tLS0tLS0KIDMgZmlsZXMgY2hhbmdlZCwgNCBpbnNlcnRpb25zKCspLCAxNSBkZWxldGlvbnMoLSkKIGRlbGV0ZSBtb2RlIDEwMDY0NCBkZXZlbC9vcGVuNjI1NDEvZmlsZXMvcGF0Y2gtQ01ha2VMaXN0cy50eHQKCmRpZmYgLS1naXQgYS9kZXZlbC9vcGVuNjI1NDEvTWFrZWZpbGUgYi9kZXZlbC9vcGVuNjI1NDEvTWFrZWZpbGUKaW5kZXggYTY5MDg0ZWY5NzQzLi5mOTkzMWJhNTE3ZjUgMTAwNjQ0Ci0tLSBhL2RldmVsL29wZW42MjU0MS9NYWtlZmlsZQorKysgYi9kZXZlbC9vcGVuNjI1NDEvTWFrZWZpbGUKQEAgLTEsNiArMSw2IEBACiBQT1JUTkFNRT0Jb3BlbjYyNTQxCiBESVNUVkVSU0lPTlBSRUZJWD0JdgotRElTVFZFUlNJT049CTEuMy43CitESVNUVkVSU0lPTj0JMS4zLjgKIENBVEVHT1JJRVM9CWRldmVsCiAKIE1BSU5UQUlORVI9CW5zb25hY2tAb3V0bG9vay5jb20KZGlmZiAtLWdpdCBhL2RldmVsL29wZW42MjU0MS9kaXN0aW5mbyBiL2RldmVsL29wZW42MjU0MS9kaXN0aW5mbwppbmRleCAyMTQzNzIyMWU2YjEuLjQ5YzQ4YzVhNjE5MCAxMDA2NDQKLS0tIGEvZGV2ZWwvb3BlbjYyNTQxL2Rpc3RpbmZvCisrKyBiL2RldmVsL29wZW42MjU0MS9kaXN0aW5mbwpAQCAtMSwzICsxLDMgQEAKLVRJTUVTVEFNUCA9IDE2OTQ0MzYwNjAKLVNIQTI1NiAob3BlbjYyNTQxLW9wZW42MjU0MS12MS4zLjdfR0gwLnRhci5neikgPSBkM2Y4NGYxZTI2MzJjMTVhMzg5MmRjNmM4OWYwY2Q2YjQxMzdlOTkwYjhhZWY4ZmUyNDVjZDhlNzVmYmI1Mzg4Ci1TSVpFIChvcGVuNjI1NDEtb3BlbjYyNTQxLXYxLjMuN19HSDAudGFyLmd6KSA9IDM4NzEwNTcKK1RJTUVTVEFNUCA9IDE2OTg0MjI4MTYKK1NIQTI1NiAob3BlbjYyNTQxLW9wZW42MjU0MS12MS4zLjhfR0gwLnRhci5neikgPSBiNjk0M2I1NjQ3ODdjNDk1M2I3N2NhOGQ3Zjk4N2M0Yjg5NmIzZjNlOTFmNDVkOWYxM2U5MDU2YjYxNDhiYzFkCitTSVpFIChvcGVuNjI1NDEtb3BlbjYyNTQxLXYxLjMuOF9HSDAudGFyLmd6KSA9IDM4NzQxODUKZGlmZiAtLWdpdCBhL2RldmVsL29wZW42MjU0MS9maWxlcy9wYXRjaC1DTWFrZUxpc3RzLnR4dCBiL2RldmVsL29wZW42MjU0MS9maWxlcy9wYXRjaC1DTWFrZUxpc3RzLnR4dApkZWxldGVkIGZpbGUgbW9kZSAxMDA2NDQKaW5kZXggMzBhNmVmZmM3ZjcxLi4wMDAwMDAwMDAwMDAKLS0tIGEvZGV2ZWwvb3BlbjYyNTQxL2ZpbGVzL3BhdGNoLUNNYWtlTGlzdHMudHh0CisrKyAvZGV2L251bGwKQEAgLTEsMTEgKzAsMCBAQAotLS0tIENNYWtlTGlzdHMudHh0Lm9yaWcJMjAyMy0wOS0xMSAxMjo0NzowOCBVVEMKLSsrKyBDTWFrZUxpc3RzLnR4dAotQEAgLTQzLDcgKzQzLDcgQEAgc2V0KENNQUtFX0FSQ0hJVkVfT1VUUFVUX0RJUkVDVE9SWSAke0NNQUtFX0JJTkFSWV9ESVJ9Ci0gIyBvdmVyd3JpdHRlbiB3aXRoIG1vcmUgZGV0YWlsZWQgaW5mb3JtYXRpb24gaWYgZ2l0IGlzIGF2YWlsYWJsZS4KLSBzZXQoT1BFTjYyNTQxX1ZFUl9NQUpPUiAxKQotIHNldChPUEVONjI1NDFfVkVSX01JTk9SIDMpCi0tc2V0KE9QRU42MjU0MV9WRVJfUEFUQ0ggNikKLStzZXQoT1BFTjYyNTQxX1ZFUl9QQVRDSCA3KQotIHNldChPUEVONjI1NDFfVkVSX0xBQkVMICItdW5kZWZpbmVkIikgIyBsaWtlICItcmMxIiBvciAiLWc0NTM4YWJjZCIgb3IgIi1nNDUzOGFiY2QtZGlydHkiCi0gc2V0KE9QRU42MjU0MV9WRVJfQ09NTUlUICJ1bmtub3duLWNvbW1pdCIpCi0gCi0tIAoyLjQyLjAKCg==", + "file_name": "0001-devel-open62541-Update-to-version-1.3.8.patch", + "content_type": "text/plain", + "flags": [ + { + "status": "+", + "setter": "nsonack@outlook.com", + "id": 76972, + "name": "maintainer-approval", + "type_id": 1, + "creation_date": "2023-11-04T20:19:11Z", + "modification_date": "2023-11-04T20:19:11Z" + } + ], + "is_private": 0, + "id": 246131, + "last_change_time": "2023-12-08T17:10:06Z", + "is_patch": 1, + "summary": "Patch for updating the port", + "is_obsolete": 1 + }, + { + "id": 246910, + "is_private": 0, + "is_obsolete": 0, + "summary": "Patch v2 (now for version 1.3.9)", + "last_change_time": "2023-12-08T17:10:06Z", + "is_patch": 1, + "creation_time": "2023-12-08T17:10:06Z", + "bug_id": 274920, + "flags": [ + { + "creation_date": "2023-12-08T17:10:06Z", + "modification_date": "2023-12-08T17:10:06Z", + "type_id": 1, + "name": "maintainer-approval", + "id": 77649, + "setter": "nsonack@outlook.com", + "status": "+" + } + ], + "content_type": "text/plain", + "data": "RnJvbSA5MTE2MmYyY2Y2NGI4NjlmOTEwYzBmMjRmMzIyZWU3Y2Q1ZDZmZDdlIE1vbiBTZXAgMTcgMDA6MDA6MDAgMjAwMQpGcm9tOiBOaWNvIFNvbmFjayA8bnNvbmFja0BoZXJyaG90emVucGxvdHouZGU+CkRhdGU6IEZyaSwgMjcgT2N0IDIwMjMgMTY6MTY6MjEgKzAwMDAKU3ViamVjdDogW1BBVENIXSBkZXZlbC9vcGVuNjI1NDE6IFVwZGF0ZSB0byB2ZXJzaW9uIDEuMy44CgpSZW1vdmVzIHRoZSBwYXRjaCBhcyB0aGUgdXBzdHJlYW0gYnVnIHJlcG9ydCBoYXMgYmVlbiByZXNvbHZlZC4KU2VlIGh0dHBzOi8vZ2l0aHViLmNvbS9vcGVuNjI1NDEvb3BlbjYyNTQxL2lzc3Vlcy82MDEwCgpTaWduZWQtb2ZmLWJ5OiBOaWNvIFNvbmFjayA8bnNvbmFja0BoZXJyaG90emVucGxvdHouZGU+Ci0tLQogZGV2ZWwvb3BlbjYyNTQxL01ha2VmaWxlICAgICAgICAgICAgICAgICAgIHwgIDYgKysrLS0tCiBkZXZlbC9vcGVuNjI1NDEvZGlzdGluZm8gICAgICAgICAgICAgICAgICAgfCAgNiArKystLS0KIGRldmVsL29wZW42MjU0MS9maWxlcy9wYXRjaC1DTWFrZUxpc3RzLnR4dCB8IDExIC0tLS0tLS0tLS0tCiAzIGZpbGVzIGNoYW5nZWQsIDYgaW5zZXJ0aW9ucygrKSwgMTcgZGVsZXRpb25zKC0pCiBkZWxldGUgbW9kZSAxMDA2NDQgZGV2ZWwvb3BlbjYyNTQxL2ZpbGVzL3BhdGNoLUNNYWtlTGlzdHMudHh0CgpkaWZmIC0tZ2l0IGEvZGV2ZWwvb3BlbjYyNTQxL01ha2VmaWxlIGIvZGV2ZWwvb3BlbjYyNTQxL01ha2VmaWxlCmluZGV4IGE2OTA4NGVmOTc0My4uMWE5Y2E1ZTJlZWI4IDEwMDY0NAotLS0gYS9kZXZlbC9vcGVuNjI1NDEvTWFrZWZpbGUKKysrIGIvZGV2ZWwvb3BlbjYyNTQxL01ha2VmaWxlCkBAIC0xLDYgKzEsNiBAQAogUE9SVE5BTUU9CW9wZW42MjU0MQogRElTVFZFUlNJT05QUkVGSVg9CXYKLURJU1RWRVJTSU9OPQkxLjMuNworRElTVFZFUlNJT049CTEuMy45CiBDQVRFR09SSUVTPQlkZXZlbAogCiBNQUlOVEFJTkVSPQluc29uYWNrQG91dGxvb2suY29tCkBAIC0xNiw5ICsxNiw5IEBAIFVTRV9MRENPTkZJRz0JeWVzCiAKIFNIRUJBTkdfR0xPQj0JKi5weQogCi1DTUFLRV9PTj0JQlVJTERfU0hBUkVEX0xJQlMKK0NNQUtFX09OPQlCVUlMRF9TSEFSRURfTElCUyBcCisJCUNNQUtFX0RJU0FCTEVfRklORF9QQUNLQUdFX0dpdAogQ01BS0VfT0ZGPQlVQV9GT1JDRV9XRVJST1IKLUJJTkFSWV9BTElBUz0JcHl0aG9uMz0ke1BZVEhPTl9DTUR9CiBQTElTVF9TVUI9CURJU1RWRVJTSU9OPSR7RElTVFZFUlNJT059CiAKIE9QVElPTlNfREVGSU5FPQkJT1NTTApkaWZmIC0tZ2l0IGEvZGV2ZWwvb3BlbjYyNTQxL2Rpc3RpbmZvIGIvZGV2ZWwvb3BlbjYyNTQxL2Rpc3RpbmZvCmluZGV4IDIxNDM3MjIxZTZiMS4uNDE4Njk4MTU4MTFmIDEwMDY0NAotLS0gYS9kZXZlbC9vcGVuNjI1NDEvZGlzdGluZm8KKysrIGIvZGV2ZWwvb3BlbjYyNTQxL2Rpc3RpbmZvCkBAIC0xLDMgKzEsMyBAQAotVElNRVNUQU1QID0gMTY5NDQzNjA2MAotU0hBMjU2IChvcGVuNjI1NDEtb3BlbjYyNTQxLXYxLjMuN19HSDAudGFyLmd6KSA9IGQzZjg0ZjFlMjYzMmMxNWEzODkyZGM2Yzg5ZjBjZDZiNDEzN2U5OTBiOGFlZjhmZTI0NWNkOGU3NWZiYjUzODgKLVNJWkUgKG9wZW42MjU0MS1vcGVuNjI1NDEtdjEuMy43X0dIMC50YXIuZ3opID0gMzg3MTA1NworVElNRVNUQU1QID0gMTcwMjA1NTE4MgorU0hBMjU2IChvcGVuNjI1NDEtb3BlbjYyNTQxLXYxLjMuOV9HSDAudGFyLmd6KSA9IDcxNzY0ZDRhMDYwY2ZhMDdlYWU3YWFhYmQxNzZkYTM4YjE1NWVmMDFjNjMxMDM1MTMzMzk2OTlmZDgwMjZlMmYKK1NJWkUgKG9wZW42MjU0MS1vcGVuNjI1NDEtdjEuMy45X0dIMC50YXIuZ3opID0gMzg3NDcwMQpkaWZmIC0tZ2l0IGEvZGV2ZWwvb3BlbjYyNTQxL2ZpbGVzL3BhdGNoLUNNYWtlTGlzdHMudHh0IGIvZGV2ZWwvb3BlbjYyNTQxL2ZpbGVzL3BhdGNoLUNNYWtlTGlzdHMudHh0CmRlbGV0ZWQgZmlsZSBtb2RlIDEwMDY0NAppbmRleCAzMGE2ZWZmYzdmNzEuLjAwMDAwMDAwMDAwMAotLS0gYS9kZXZlbC9vcGVuNjI1NDEvZmlsZXMvcGF0Y2gtQ01ha2VMaXN0cy50eHQKKysrIC9kZXYvbnVsbApAQCAtMSwxMSArMCwwIEBACi0tLS0gQ01ha2VMaXN0cy50eHQub3JpZwkyMDIzLTA5LTExIDEyOjQ3OjA4IFVUQwotKysrIENNYWtlTGlzdHMudHh0Ci1AQCAtNDMsNyArNDMsNyBAQCBzZXQoQ01BS0VfQVJDSElWRV9PVVRQVVRfRElSRUNUT1JZICR7Q01BS0VfQklOQVJZX0RJUn0KLSAjIG92ZXJ3cml0dGVuIHdpdGggbW9yZSBkZXRhaWxlZCBpbmZvcm1hdGlvbiBpZiBnaXQgaXMgYXZhaWxhYmxlLgotIHNldChPUEVONjI1NDFfVkVSX01BSk9SIDEpCi0gc2V0KE9QRU42MjU0MV9WRVJfTUlOT1IgMykKLS1zZXQoT1BFTjYyNTQxX1ZFUl9QQVRDSCA2KQotK3NldChPUEVONjI1NDFfVkVSX1BBVENIIDcpCi0gc2V0KE9QRU42MjU0MV9WRVJfTEFCRUwgIi11bmRlZmluZWQiKSAjIGxpa2UgIi1yYzEiIG9yICItZzQ1MzhhYmNkIiBvciAiLWc0NTM4YWJjZC1kaXJ0eSIKLSBzZXQoT1BFTjYyNTQxX1ZFUl9DT01NSVQgInVua25vd24tY29tbWl0IikKLSAKLS0gCjIuNDIuMAoK", + "file_name": "0001-devel-open62541-Update-to-version-1.3.8.patch", + "size": 2577, + "creator": "nsonack@outlook.com" + } + ] + } +} diff --git a/tests/samples/bugzilla_comments.json b/tests/samples/bugzilla_comments.json new file mode 100644 index 00000000..31abfa20 --- /dev/null +++ b/tests/samples/bugzilla_comments.json @@ -0,0 +1 @@ +{"comments":{},"bugs":{"275381":{"comments":[{"is_private":false,"time":"2023-11-27T17:14:39Z","text":"This is originally reported by khng@ on Telegram bsd dev group. Post it here to make it public.\n\nSteps to repeat:\n\nBoot with Ethernet interface disabled, then try to enable it.\n\n```\n> set hint.hn.0.disabled=\"1\"\n> boot\n...\n# devctl enable hn0\n```\n\n\nPart of core text dump:\n\nfreebsd dumped core - see /var/crash/vmcore.0\n\nMon Nov 20 04:17:24 UTC 2023\n\nFreeBSD freebsd 14.0-RELEASE FreeBSD 14.0-RELEASE #0 releng/14.0-n265380-f9716eee8ab4: Fri Nov 10 05:57:23 UTC 2023 root@releng1.nyi.freebsd.org:/usr/obj/usr/src/amd64.amd64/sys/GENERIC amd64\n\npanic: page fault\n\nGNU gdb (GDB) 13.2 [GDB v13.2 for FreeBSD]\nCopyright (C) 2023 Free Software Foundation, Inc.\nLicense GPLv3+: GNU GPL version 3 or later \nThis is free software: you are free to change and redistribute it.\nThere is NO WARRANTY, to the extent permitted by law.\nType \"show copying\" and \"show warranty\" for details.\nThis GDB was configured as \"x86_64-portbld-freebsd14.0\".\nType \"show configuration\" for configuration details.\nFor bug reporting instructions, please see:\n.\nFind the GDB manual and other documentation resources online at:\n .\n\nFor help, type \"help\".\nType \"apropos word\" to search for commands related to \"word\"...\nReading symbols from /boot/kernel/kernel...\nReading symbols from /usr/lib/debug//boot/kernel/kernel.debug...\n\nUnread portion of the kernel message buffer:\n\n\nFatal trap 12: page fault while in kernel mode\ncpuid = 1; apic id = 01\nfault virtual address\t= 0x28\nfault code\t\t= supervisor read data, page not present\ninstruction pointer\t= 0x20:0xffffffff80c5e0c8\nstack pointer\t = 0x28:0xfffffe0053f4b900\nframe pointer\t = 0x28:0xfffffe0053f4b940\ncode segment\t\t= base 0x0, limit 0xfffff, type 0x1b\n\t\t\t= DPL 0, pres 1, long 1, def32 0, gran 1\nprocessor eflags\t= interrupt enabled, resume, IOPL = 0\ncurrent process\t\t= 650 (devctl)\nrdi: fffff80006eb6800 rsi: fffff80001027500 rdx: 0000000000000001\nrcx: 0000000000000001 r8: 0000000000000000 r9: 8080808080808080\nrax: 0000000000000000 rbx: fffffe0054963c80 rbp: fffffe0053f4b940\nr10: ffffffff811e1f39 r11: 8b9091ff93939e00 r12: fffff80007fca000\nr13: fffff80007305c20 r14: ffffffff811e1f39 r15: 0000000000000000\ntrap number\t\t= 12\npanic: page fault\ncpuid = 1\ntime = 1700453806\nKDB: stack backtrace:\n#0 0xffffffff80b9002d at kdb_backtrace+0x5d\n#1 0xffffffff80b43132 at vpanic+0x132\n#2 0xffffffff80b42ff3 at panic+0x43\n#3 0xffffffff8100c85c at trap_fatal+0x40c\n#4 0xffffffff8100c8af at trap_pfault+0x4f\n#5 0xffffffff80fe3828 at calltrap+0x8\n#6 0xffffffff80c5ceb5 at if_attach_internal+0x55\n#7 0xffffffff80c6824c at ether_ifattach+0x2c\n#8 0xffffffff80f779c6 at hn_attach+0x21d6\n#9 0xffffffff80b7fa1e at device_attach+0x3be\n#10 0xffffffff80b84dcf at devctl2_ioctl+0x56f\n#11 0xffffffff809d10dc at devfs_ioctl+0xcc\n#12 0xffffffff80c3b9b4 at vn_ioctl+0xd4\n#13 0xffffffff809d177e at devfs_ioctl_f+0x1e\n#14 0xffffffff80bb1535 at kern_ioctl+0x255\n#15 0xffffffff80bb1273 at sys_ioctl+0x123\n#16 0xffffffff8100d119 at amd64_syscall+0x109\n#17 0xffffffff80fe413b at fast_syscall_common+0xf8\nUptime: 15s\nDumping 212 out of 470 MB:..8%..16%..23%..31%..46%..53%..61%..76%..83%..91%\n\n__curthread () at /usr/src/sys/amd64/include/pcpu_aux.h:57\n57\t/usr/src/sys/amd64/include/pcpu_aux.h: No such file or directory.\n(kgdb) #0 __curthread () at /usr/src/sys/amd64/include/pcpu_aux.h:57\n#1 doadump (textdump=)\n at /usr/src/sys/kern/kern_shutdown.c:405\n#2 0xffffffff80b42cc7 in kern_reboot (howto=260)\n at /usr/src/sys/kern/kern_shutdown.c:526\n#3 0xffffffff80b4319f in vpanic (fmt=0xffffffff81136b3b \"%s\", \n ap=ap@entry=0xfffffe0053f4b750) at /usr/src/sys/kern/kern_shutdown.c:970\n#4 0xffffffff80b42ff3 in panic (fmt=)\n at /usr/src/sys/kern/kern_shutdown.c:894\n#5 0xffffffff8100c85c in trap_fatal (frame=0xfffffe0053f4b840, eva=40)\n at /usr/src/sys/amd64/amd64/trap.c:952\n#6 0xffffffff8100c8af in trap_pfault (frame=0xfffffe0053f4b840, \n usermode=false, signo=, ucode=)\n at /usr/src/sys/amd64/amd64/trap.c:760\n#7 \n#8 0xffffffff80c5e0c8 in if_addgroup (ifp=ifp@entry=0xfffff80007fca000, \n groupname=0xffffffff811e1f39 \"all\") at /usr/src/sys/net/if.c:1477\n#9 0xffffffff80c5ceb5 in if_attach_internal (\n ifp=ifp@entry=0xfffff80007fca000, vmove=false)\n at /usr/src/sys/net/if.c:842\n#10 0xffffffff80c5ce59 in if_attach (ifp=0xfffff80006eb6800, \n ifp@entry=0xfffff80007fca000) at /usr/src/sys/net/if.c:772\n#11 0xffffffff80c6824c in ether_ifattach (ifp=0xfffff80006eb6800, \n ifp@entry=0xfffff80007fca000, lla=0xfffff80001027500 \"\", \n lla@entry=0xfffffe0053f4ba80 \"\") at /usr/src/sys/net/if_ethersubr.c:1001\n#12 0xffffffff80f779c6 in hn_attach (dev=0xfffff8000291ce00)\n at /usr/src/sys/dev/hyperv/netvsc/if_hn.c:2436\n#13 0xffffffff80b7fa1e in DEVICE_ATTACH (dev=0xfffff8000291ce00)\n at ./device_if.h:195\n#14 device_attach (dev=dev@entry=0xfffff8000291ce00)\n at /usr/src/sys/kern/subr_bus.c:2535\n#15 0xffffffff80b84dcf in devctl2_ioctl (cdev=, \n cmd=2157462531, data=, fflag=, \n td=0xfffffe0054963c80) at /usr/src/sys/kern/subr_bus.c:5433\n#16 0xffffffff809d10dc in devfs_ioctl (ap=0xfffffe0053f4bc40)\n at /usr/src/sys/fs/devfs/devfs_vnops.c:933\n#17 0xffffffff80c3b9b4 in vn_ioctl (fp=0xfffff8000704ce10, \n com=18446735277633467648, data=0xfffff8000779ee00, \n active_cred=0xfffff8000702cb00, td=0x0)\n at /usr/src/sys/kern/vfs_vnops.c:1701\n#18 0xffffffff809d177e in devfs_ioctl_f (fp=0xfffff80006eb6800, \n com=18446735277633467648, data=0x1, cred=0x1, td=0x0)\n at /usr/src/sys/fs/devfs/devfs_vnops.c:864\n#19 0xffffffff80bb1535 in fo_ioctl (fp=0xfffff8000704ce10, com=2157462531, \n data=0x1, active_cred=0x1, td=0xfffffe0054963c80)\n at /usr/src/sys/sys/file.h:366\n#20 kern_ioctl (td=td@entry=0xfffffe0054963c80, fd=, \n com=com@entry=2157462531, \n data=0x1 , \n data@entry=0xfffff8000779ee00 \"hn0\")\n at /usr/src/sys/kern/sys_generic.c:805\n#21 0xffffffff80bb1273 in sys_ioctl (td=0xfffffe0054963c80, \n uap=0xfffffe0054964080) at /usr/src/sys/kern/sys_generic.c:713\n#22 0xffffffff8100d119 in syscallenter (td=0xfffffe0054963c80)\n at /usr/src/sys/amd64/amd64/../../kern/subr_syscall.c:187\n#23 amd64_syscall (td=0xfffffe0054963c80, traced=0)\n at /usr/src/sys/amd64/amd64/trap.c:1197\n#24 \n#25 0x000032e7074bce0a in ?? ()\nBacktrace stopped: Cannot access memory at address 0x32e7069aff48\n(kgdb)","creation_time":"2023-11-27T17:14:39Z","bug_id":275381,"count":0,"id":1285941,"creator":"zlei@FreeBSD.org","attachment_id":null},{"text":"Other ethernet interface drivers are also affected, tested with re(4) and cxgbe(4).\n\nProposed fix: https://reviews.freebsd.org/D42678","time":"2023-11-27T17:20:15Z","bug_id":275381,"creation_time":"2023-11-27T17:20:15Z","is_private":false,"attachment_id":null,"creator":"zlei@FreeBSD.org","id":1285943,"count":1}]}}} \ No newline at end of file diff --git a/tests/samples/bugzilla_simple_bug.json b/tests/samples/bugzilla_simple_bug.json new file mode 100644 index 00000000..f451d762 --- /dev/null +++ b/tests/samples/bugzilla_simple_bug.json @@ -0,0 +1,53 @@ +{ + "bugs": [ + { + "platform": "Any", + "target_milestone": "---", + "op_sys": "Any", + "summary": "[aha] [scsi] Toshiba MK156FB scsi drive does not work with 2.0 kernel", + "blocks": [], + "last_change_time": "2016-08-11T10:54:59Z", + "priority": "Normal", + "resolution": "FIXED", + "is_cc_accessible": true, + "deadline": null, + "classification": "Unclassified", + "is_creator_accessible": true, + "dupe_of": null, + "id": 1, + "see_also": [], + "status": "Closed", + "groups": [], + "is_open": false, + "whiteboard": "", + "assigned_to": "core@FreeBSD.org", + "assigned_to_detail": { + "name": "core@FreeBSD.org", + "real_name": "FreeBSD Core Team", + "id": 3, + "email": "core@FreeBSD.org" + }, + "keywords": [], + "alias": [], + "cc_detail": [], + "url": "", + "cc": [], + "version": "Unspecified", + "is_confirmed": true, + "component": "kern", + "creator_detail": { + "name": "root@hclb.demon.co.uk", + "real_name": "Dave Evans", + "email": "root@hclb.demon.co.uk", + "id": 22 + }, + "depends_on": [], + "creation_time": "1994-09-14T09:10:01Z", + "severity": "Affects Only Me", + "flags": [], + "qa_contact": "", + "creator": "root@hclb.demon.co.uk", + "product": "Base System" + } + ] +} diff --git a/tests/test-jsongen.c b/tests/test-jsongen.c index fca41a1e..4526f17d 100644 --- a/tests/test-jsongen.c +++ b/tests/test-jsongen.c @@ -36,7 +36,7 @@ ATF_TC_WITHOUT_HEAD(empty_object); ATF_TC_BODY(empty_object, tc) { - gcli_jsongen gen = {0}; + struct gcli_jsongen gen = {0}; ATF_REQUIRE(gcli_jsongen_init(&gen) == 0); ATF_REQUIRE(gcli_jsongen_begin_object(&gen) == 0); @@ -52,7 +52,7 @@ ATF_TC_BODY(empty_object, tc) ATF_TC_WITHOUT_HEAD(array_with_two_empty_objects); ATF_TC_BODY(array_with_two_empty_objects, tc) { - gcli_jsongen gen = {0}; + struct gcli_jsongen gen = {0}; ATF_REQUIRE(gcli_jsongen_init(&gen) == 0); ATF_REQUIRE(gcli_jsongen_begin_array(&gen) == 0); @@ -72,7 +72,7 @@ ATF_TC_BODY(array_with_two_empty_objects, tc) ATF_TC_WITHOUT_HEAD(empty_array); ATF_TC_BODY(empty_array, tc) { - gcli_jsongen gen = {0}; + struct gcli_jsongen gen = {0}; ATF_REQUIRE(gcli_jsongen_init(&gen) == 0); ATF_REQUIRE(gcli_jsongen_begin_array(&gen) == 0); @@ -88,7 +88,7 @@ ATF_TC_BODY(empty_array, tc) ATF_TC_WITHOUT_HEAD(object_with_number); ATF_TC_BODY(object_with_number, tc) { - gcli_jsongen gen = {0}; + struct gcli_jsongen gen = {0}; ATF_REQUIRE(gcli_jsongen_init(&gen) == 0); ATF_REQUIRE(gcli_jsongen_begin_object(&gen) == 0); @@ -106,7 +106,7 @@ ATF_TC_BODY(object_with_number, tc) ATF_TC_WITHOUT_HEAD(object_nested); ATF_TC_BODY(object_nested, tc) { - gcli_jsongen gen = {0}; + struct gcli_jsongen gen = {0}; ATF_REQUIRE(gcli_jsongen_init(&gen) == 0); ATF_REQUIRE(gcli_jsongen_begin_object(&gen) == 0); @@ -131,7 +131,7 @@ ATF_TC_BODY(object_nested, tc) ATF_TC_WITHOUT_HEAD(object_with_strings); ATF_TC_BODY(object_with_strings, tc) { - gcli_jsongen gen = {0}; + struct gcli_jsongen gen = {0}; ATF_REQUIRE(gcli_jsongen_init(&gen) == 0); ATF_REQUIRE(gcli_jsongen_begin_object(&gen) == 0); @@ -149,7 +149,7 @@ ATF_TC_BODY(object_with_strings, tc) ATF_TC_WITHOUT_HEAD(object_with_mixed_values); ATF_TC_BODY(object_with_mixed_values, tc) { - gcli_jsongen gen = {0}; + struct gcli_jsongen gen = {0}; ATF_REQUIRE(gcli_jsongen_init(&gen) == 0); ATF_REQUIRE(gcli_jsongen_begin_object(&gen) == 0); @@ -174,7 +174,7 @@ ATF_TC_BODY(object_with_mixed_values, tc) ATF_TC_WITHOUT_HEAD(object_with_two_keys_and_values_that_are_string); ATF_TC_BODY(object_with_two_keys_and_values_that_are_string, tc) { - gcli_jsongen gen = {0}; + struct gcli_jsongen gen = {0}; ATF_REQUIRE(gcli_jsongen_init(&gen) == 0); ATF_REQUIRE(gcli_jsongen_begin_object(&gen) == 0); diff --git a/thirdparty/sn/sn.c b/thirdparty/sn/sn.c index d504bc69..8eb9785f 100644 --- a/thirdparty/sn/sn.c +++ b/thirdparty/sn/sn.c @@ -49,191 +49,191 @@ static int verbosity = VERBOSITY_NORMAL; void sn_setverbosity(int level) { - verbosity = level; + verbosity = level; } int sn_getverbosity(void) { - return verbosity; + return verbosity; } void errx(int code, const char *fmt, ...) { - va_list ap; + va_list ap; - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); - fputc('\n', stderr); - exit(code); + fputc('\n', stderr); + exit(code); } void err(int code, const char *fmt, ...) { - va_list ap; + va_list ap; - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); - fprintf(stderr, ": %s\n", strerror(errno)); - exit(code); + fprintf(stderr, ": %s\n", strerror(errno)); + exit(code); } void warnx(const char *fmt, ...) { - if (!sn_verbose()) - return; + if (!sn_verbose()) + return; - fputs("warning: ", stderr); - va_list ap; + fputs("warning: ", stderr); + va_list ap; - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); - fputc('\n', stderr); + fputc('\n', stderr); } void warn(const char *fmt, ...) { - if (!sn_verbose()) - return; + if (!sn_verbose()) + return; - fputs("warning: ", stderr); - va_list ap; + fputs("warning: ", stderr); + va_list ap; - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); - fprintf(stderr, ": %s\n", strerror(errno)); + fprintf(stderr, ": %s\n", strerror(errno)); } char * sn_strndup(const char *it, size_t len) { - size_t actual = 0; - const char *tmp = NULL; - char *result = NULL; + char *result = NULL; + char const *tmp = NULL; + size_t actual = 0; - if (!len) - return NULL; + if (!len) + return NULL; - tmp = it; + tmp = it; - while (tmp[actual++] && actual < len); + while (tmp[actual++] && actual < len); - result = calloc(1, actual + 1); - memcpy(result, it, actual); - return result; + result = calloc(1, actual + 1); + memcpy(result, it, actual); + return result; } char * sn_asprintf(const char *fmt, ...) { - char tmp = 0, *result = NULL; - size_t actual = 0; - va_list vp; + char tmp = 0, *result = NULL; + size_t actual = 0; + va_list vp; - va_start(vp, fmt); - actual = vsnprintf(&tmp, 1, fmt, vp); - va_end(vp); + va_start(vp, fmt); + actual = vsnprintf(&tmp, 1, fmt, vp); + va_end(vp); - result = calloc(1, actual + 1); - if (!result) - err(1, "calloc"); + result = calloc(1, actual + 1); + if (!result) + err(1, "calloc"); - va_start(vp, fmt); - vsnprintf(result, actual + 1, fmt, vp); - va_end(vp); + va_start(vp, fmt); + vsnprintf(result, actual + 1, fmt, vp); + va_end(vp); - return result; + return result; } static int word_length(const char *x) { - int l = 0; + int l = 0; - while (*x && !isspace(*x++)) - l++; - return l; + while (*x && !isspace(*x++)) + l++; + return l; } void pretty_print(const char *input, int indent, int maxlinelen, FILE *out) { - const char *it = input; + const char *it = input; - if (!it) - return; + if (!it) + return; - while (*it) { - int linelength = indent; - fprintf(out, "%*.*s", indent, indent, ""); + while (*it) { + int linelength = indent; + fprintf(out, "%*.*s", indent, indent, ""); - do { - int w = word_length(it) + 1; + do { + int w = word_length(it) + 1; - if (it[w - 1] == '\n') { - fprintf(out, "%.*s", w - 1, it); - it += w; - break; - } else if (it[w - 1] == '\0') { - w -= 1; - } + if (it[w - 1] == '\n') { + fprintf(out, "%.*s", w - 1, it); + it += w; + break; + } else if (it[w - 1] == '\0') { + w -= 1; + } - fprintf(out, "%.*s", w, it); - it += w; - linelength += w; + fprintf(out, "%.*s", w, it); + it += w; + linelength += w; + } while (*it && (linelength < maxlinelen)); - } while (*it && (linelength < maxlinelen)); - fputc('\n', out); - } + fputc('\n', out); + } } int sn_mmap_file(const char *path, void **buffer) { - struct stat stat_buf = {0}; - int fd = 0; + struct stat stat_buf = {0}; + int fd = 0; - /* Precautiously nullify the buffer, because it is better to have - * it point to null if the pointer variable passed here is - * allocated on the stack and not initialized. This will make - * debugging easier. */ - *buffer = NULL; + /* Precautiously nullify the buffer, because it is better to have + * it point to null if the pointer variable passed here is + * allocated on the stack and not initialized. This will make + * debugging easier. */ + *buffer = NULL; - if (access(path, R_OK) < 0) - err(1, "access"); + if (access(path, R_OK) < 0) + err(1, "access"); - if (stat(path, &stat_buf) < 0) - err(1, "stat"); + if (stat(path, &stat_buf) < 0) + err(1, "stat"); - /* we should not pass a size of 0 to mmap, as this will trigger an - * EINVAL. Thus we can also avoid calling open on the file and - * save a few resources. I discovered this error the hard way in a - * Haiku VM. */ - if (stat_buf.st_size == 0) - return 0; + /* we should not pass a size of 0 to mmap, as this will trigger an + * EINVAL. Thus we can also avoid calling open on the file and + * save a few resources. I discovered this error the hard way in a + * Haiku VM. */ + if (stat_buf.st_size == 0) + return 0; - if ((fd = open(path, O_RDONLY)) < 0) - err(1, "open"); + if ((fd = open(path, O_RDONLY)) < 0) + err(1, "open"); - *buffer = mmap(NULL, stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); - if (*buffer == MAP_FAILED) - err(1, "mmap"); + *buffer = mmap(NULL, stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (*buffer == MAP_FAILED) + err(1, "mmap"); - return stat_buf.st_size; + return stat_buf.st_size; } int @@ -274,213 +274,213 @@ sn_read_file(char const *path, char **buffer) sn_sv sn_sv_trim_front(sn_sv it) { - if (it.length == 0) - return it; + if (it.length == 0) + return it; - // TODO: not utf-8 aware - while (it.length > 0) { - if (!isspace(*it.data)) - break; + // TODO: not utf-8 aware + while (it.length > 0) { + if (!isspace(*it.data)) + break; - it.data++; - it.length--; - } + it.data++; + it.length--; + } - return it; + return it; } static sn_sv sn_sv_trim_end(sn_sv it) { - while (it.length > 0 && isspace(it.data[it.length - 1])) - it.length--; + while (it.length > 0 && isspace(it.data[it.length - 1])) + it.length--; - return it; + return it; } sn_sv sn_sv_trim(sn_sv it) { - return sn_sv_trim_front(sn_sv_trim_end(it)); + return sn_sv_trim_front(sn_sv_trim_end(it)); } sn_sv sn_sv_chop_until(sn_sv *it, char c) { - sn_sv result = *it; + sn_sv result = *it; - result.length = 0; + result.length = 0; - while (it->length > 0) { + while (it->length > 0) { - if (*it->data == c) - break; + if (*it->data == c) + break; - it->data++; - it->length--; - result.length++; - } + it->data++; + it->length--; + result.length++; + } - return result; + return result; } bool sn_sv_has_prefix(sn_sv it, const char *prefix) { - size_t len = strlen(prefix); + size_t len = strlen(prefix); - if (it.length < len) - return false; + if (it.length < len) + return false; - return strncmp(it.data, prefix, len) == 0; + return strncmp(it.data, prefix, len) == 0; } bool sn_sv_eq(sn_sv this, sn_sv that) { - if (this.length != that.length) - return false; + if (this.length != that.length) + return false; - return strncmp(this.data, that.data, this.length) == 0; + return strncmp(this.data, that.data, this.length) == 0; } bool sn_sv_eq_to(const sn_sv this, const char *that) { - size_t len = strlen(that); - if (len != this.length) - return false; + size_t len = strlen(that); + if (len != this.length) + return false; - return strncmp(this.data, that, len) == 0; + return strncmp(this.data, that, len) == 0; } char * sn_strip_suffix(char *it, const char *suffix) { - int it_len = strlen(it); - int su_len = strlen(suffix); + int it_len = strlen(it); + int su_len = strlen(suffix); - if (su_len > it_len) - return it; + if (su_len > it_len) + return it; - int off = it_len - su_len; + int off = it_len - su_len; - if (strncmp(it + off, suffix, su_len) == 0) - it[off] = '\0'; + if (strncmp(it + off, suffix, su_len) == 0) + it[off] = '\0'; - return it; + return it; } sn_sv sn_sv_fmt(const char *fmt, ...) { - char tmp = 0; - va_list vp; - sn_sv result = {0}; + char tmp = 0; + va_list vp; + sn_sv result = {0}; - va_start(vp, fmt); + va_start(vp, fmt); - result.length = vsnprintf(&tmp, 1, fmt, vp); - va_end(vp); - result.data = calloc(1, result.length + 1); + result.length = vsnprintf(&tmp, 1, fmt, vp); + va_end(vp); + result.data = calloc(1, result.length + 1); - va_start(vp, fmt); - vsnprintf(result.data, result.length + 1, fmt, vp); - va_end(vp); + va_start(vp, fmt); + vsnprintf(result.data, result.length + 1, fmt, vp); + va_end(vp); - return result; + return result; } char * sn_sv_to_cstr(sn_sv it) { - return sn_strndup(it.data, it.length); + return sn_strndup(it.data, it.length); } bool sn_yesno(const char *fmt, ...) { - char tmp = 0; - va_list vp; - sn_sv message = {0}; - bool result = false; + char tmp = 0; + va_list vp; + sn_sv message = {0}; + bool result = false; - va_start(vp, fmt); + va_start(vp, fmt); - message.length = vsnprintf(&tmp, 1, fmt, vp); - va_end(vp); - message.data = calloc(1, message.length + 1); + message.length = vsnprintf(&tmp, 1, fmt, vp); + va_end(vp); + message.data = calloc(1, message.length + 1); - va_start(vp, fmt); - vsnprintf(message.data, message.length + 1, fmt, vp); - va_end(vp); + va_start(vp, fmt); + vsnprintf(message.data, message.length + 1, fmt, vp); + va_end(vp); - do { - printf(SV_FMT" [yN] ", SV_ARGS(message)); + do { + printf(SV_FMT" [yN] ", SV_ARGS(message)); - char c = getchar(); + char c = getchar(); - if (c == 'y' || c == 'Y') { - result = true; - break; - } else if (c == '\n' || c == 'n' || c == 'N') { - break; - } + if (c == 'y' || c == 'Y') { + result = true; + break; + } else if (c == '\n' || c == 'n' || c == 'N') { + break; + } - getchar(); // consume newline character + getchar(); // consume newline character - } while (!feof(stdin)); + } while (!feof(stdin)); - free(message.data); - return result; + free(message.data); + return result; } sn_sv sn_sv_strip_suffix(sn_sv input, const char *suffix) { - sn_sv expected_suffix = SV((char *)suffix); + sn_sv expected_suffix = SV((char *)suffix); - if (input.length < expected_suffix.length) - return input; + if (input.length < expected_suffix.length) + return input; - sn_sv actual_suffix = sn_sv_from_parts( - input.data + input.length - expected_suffix.length, - expected_suffix.length); + sn_sv actual_suffix = sn_sv_from_parts( + input.data + input.length - expected_suffix.length, + expected_suffix.length); - if (sn_sv_eq(expected_suffix, actual_suffix)) - input.length -= expected_suffix.length; + if (sn_sv_eq(expected_suffix, actual_suffix)) + input.length -= expected_suffix.length; - return input; + return input; } char * sn_join_with(char const *const items[], size_t const items_size, char const *sep) { - char *buffer = NULL; - size_t buffer_size = 0; - size_t bufoff = 0; - size_t sep_size = 0; + char *buffer = NULL; + size_t buffer_size = 0; + size_t bufoff = 0; + size_t sep_size = 0; - sep_size = strlen(sep); + sep_size = strlen(sep); - /* this works because of the null terminator at the end */ - for (size_t i = 0; i < items_size; ++i) { - buffer_size += strlen(items[i]) + sep_size; - } + /* this works because of the null terminator at the end */ + for (size_t i = 0; i < items_size; ++i) { + buffer_size += strlen(items[i]) + sep_size; + } - buffer = calloc(1, buffer_size); - if (!buffer) - return NULL; + buffer = calloc(1, buffer_size); + if (!buffer) + return NULL; - for (size_t i = 0; i < items_size; ++i) { - size_t len = strlen(items[i]); + for (size_t i = 0; i < items_size; ++i) { + size_t len = strlen(items[i]); - memcpy(buffer + bufoff, items[i], len); - if (i != items_size - 1) - memcpy(&buffer[bufoff + len], sep, sep_size); + memcpy(buffer + bufoff, items[i], len); + if (i != items_size - 1) + memcpy(&buffer[bufoff + len], sep, sep_size); - bufoff += len + sep_size; - } + bufoff += len + sep_size; + } - return buffer; + return buffer; } diff --git a/thirdparty/sn/sn.h b/thirdparty/sn/sn.h index d30b0ba1..1b501c5c 100644 --- a/thirdparty/sn/sn.h +++ b/thirdparty/sn/sn.h @@ -46,9 +46,9 @@ #endif enum { - VERBOSITY_NORMAL = 0, - VERBOSITY_QUIET = 1, - VERBOSITY_VERBOSE = 2, + VERBOSITY_NORMAL = 0, + VERBOSITY_QUIET = 1, + VERBOSITY_VERBOSE = 2, }; /* mostly concerning warn(x) */ @@ -58,19 +58,19 @@ int sn_getverbosity(void); static inline int sn_verbose(void) { - return sn_getverbosity() == VERBOSITY_VERBOSE; + return sn_getverbosity() == VERBOSITY_VERBOSE; } static inline int sn_normal(void) { - return sn_getverbosity() == VERBOSITY_NORMAL; + return sn_getverbosity() == VERBOSITY_NORMAL; } static inline int sn_quiet(void) { - return sn_getverbosity() == VERBOSITY_QUIET; + return sn_getverbosity() == VERBOSITY_QUIET; } /* error functions */ @@ -109,15 +109,15 @@ char *sn_strip_suffix(char *it, const char *suffix); void pretty_print(const char *input, int indent, int maxlinelen, FILE *out); /* io file mapping */ -int sn_mmap_file(const char *path, void **buffer); -int sn_read_file(char const *path, char **buffer); +int sn_mmap_file(const char *path, void **buffer); +int sn_read_file(char const *path, char **buffer); /* stringview */ typedef struct sn_sv sn_sv; struct sn_sv { - char *data; - size_t length; + char *data; + size_t length; }; #define SV(x) (sn_sv) { .data = x, .length = strlen(x) } @@ -128,23 +128,32 @@ struct sn_sv { static inline sn_sv sn_sv_from_parts(char *buf, size_t len) { - return (sn_sv) { .data = buf, .length = len }; + return (sn_sv) { .data = buf, .length = len }; } -sn_sv sn_sv_trim_front(sn_sv); -sn_sv sn_sv_trim(sn_sv); -sn_sv sn_sv_chop_until(sn_sv *, char); -bool sn_sv_has_prefix(sn_sv, const char *); -bool sn_sv_eq(const sn_sv, const sn_sv); -bool sn_sv_eq_to(const sn_sv, const char *); -sn_sv sn_sv_fmt(const char *fmt, ...) PRINTF_FORMAT(1, 2); -char *sn_sv_to_cstr(sn_sv); -sn_sv sn_sv_strip_suffix(sn_sv, const char *suffix); +sn_sv sn_sv_trim_front(sn_sv); +sn_sv sn_sv_trim(sn_sv); +sn_sv sn_sv_chop_until(sn_sv *, char); +bool sn_sv_has_prefix(sn_sv, const char *); +bool sn_sv_eq(const sn_sv, const sn_sv); +bool sn_sv_eq_to(const sn_sv, const char *); +sn_sv sn_sv_fmt(const char *fmt, ...) PRINTF_FORMAT(1, 2); +char *sn_sv_to_cstr(sn_sv); +sn_sv sn_sv_strip_suffix(sn_sv, const char *suffix); static inline bool sn_sv_null(sn_sv it) { - return it.data == NULL && it.length == 0; + return it.data == NULL && it.length == 0; +} + +static inline bool +sn_strempty(char const *const str) +{ + if (str == NULL) + return true; + + return *str == '\0'; } /* interactive user functions */ @@ -153,7 +162,7 @@ bool sn_yesno(const char *fmt, ...) PRINTF_FORMAT(1, 2); static inline const char * sn_bool_yesno(bool x) { - return x ? "yes" : "no"; + return x ? "yes" : "no"; } char *sn_join_with(char const *const items[], size_t const items_size, char const *sep);