pax_global_header00006660000000000000000000000064146006227120014512gustar00rootroot0000000000000052 comment=95ef6a12f6b98849ecf6cd7d4b6f20f20835a2c7 gcli-2.3.0/000077500000000000000000000000001460062271200124325ustar00rootroot00000000000000gcli-2.3.0/.builds/000077500000000000000000000000001460062271200137725ustar00rootroot00000000000000gcli-2.3.0/.builds/alpine.yml000066400000000000000000000010731460062271200157660ustar00rootroot00000000000000image: 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 gcli-2.3.0/.builds/debian-stable.yml000066400000000000000000000010551460062271200172100ustar00rootroot00000000000000image: 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 gcli-2.3.0/.builds/freebsd.yml000066400000000000000000000011401460062271200161230ustar00rootroot00000000000000image: 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 gcli-2.3.0/.editorconfig000066400000000000000000000002301460062271200151020ustar00rootroot00000000000000root = true [{*.{c,h,sh},Makefile,*.mk}] end_of_line = lf insert_final_newline = true indent_style = tab tab_width = 4 trim_trailing_whitespace = true gcli-2.3.0/.gcli000066400000000000000000000001451460062271200133510ustar00rootroot00000000000000pr.base=trunk pr.upstream=herrhotzenplotz/gcli pr.inhibit-delete-source-branch=yes forge-type=gitlab gcli-2.3.0/.gitignore000066400000000000000000000012611460062271200144220ustar00rootroot00000000000000/gcli /templates/*/*.c /templates/*/*.t~ /templates/*/*.h /Makefile /Makefile.in /aclocal.m4 /autom4te.cache/ /compile /config.guess /config.h /config.h.in /config.h.in~ /config.log /config.status /config.sub /configure /configure~ /depcomp # build artifacts /gcli-*.tar.bz2 /gcli-*.tar.gz /gcli-*.tar.xz /install-sh /libtool /ltmain.sh /missing /**/.deps/ /**/.dirstamp /src/pgen/parser.h /stamp-h1 /ylwrap /**/*.o /**/*.lo /**/*.la /**/*.*~ /**/.libs/ /ar-lib /configure.ac~ /test-driver /tests/pgen-simple.log /tests/pgen-simple.trs /**/*.a /src/pgen/lexer.c /src/pgen/parser.c /pgen /m4/libtool.m4 /m4/ltoptions.m4 /m4/ltsugar.m4 /m4/ltversion.m4 /m4/lt~obsolete.m4 /libgcli.pc /build gcli-2.3.0/.gitlab-ci.yml000066400000000000000000000036051460062271200150720ustar00rootroot00000000000000stages: - testing - dist alpine-amd64: stage: testing tags: - linux image: alpine:3.17 script: - apk add libcurl gcc autoconf automake libtool make pkgconf musl-dev curl-dev flex bison xz gzip bzip2 libbsd-dev kyua atf-dev libedit-dev - ./autogen.sh - ./configure CFLAGS='-std=c99 -pedantic -Wall -Wextra -Werror' CPPFLAGS='-D_XOPEN_SOURCE=600' --disable-silent-rules --with-libedit || (cat config.log && exit 42) - make -j - make -j distcheck freebsd-arm64: stage: testing tags: - freebsd - arm64 script: - ./autogen.sh - ./configure LEX=flex YACC=byacc CFLAGS='-std=c99 -pedantic -Wall -Wextra -Wno-misleading-indentation -Werror' CPPFLAGS='-D_XOPEN_SOURCE=600' --disable-silent-rules || (cat config.log && exit 42) - make -j 4 - make -j 4 distcheck dist: stage: dist tags: - linux image: alpine:3.18 script: - apk add libcurl gcc autoconf automake libtool make pkgconf musl-dev curl-dev flex bison xz gzip bzip2 libbsd-dev kyua atf-dev cmark - ./autogen.sh - ./configure CFLAGS='-std=c99 -pedantic -Wall -Wextra -Werror' CPPFLAGS='-D_XOPEN_SOURCE=600' --disable-silent-rules || (cat config.log && exit 42) - make -j dist - cd docs/website && ./deploy.sh artifacts: name: "Dist Tarballs" paths: - gcli-*.tar.* - docs/website/website_dist.tar.xz debian-amd64: stage: testing tags: - linux image: debian:bullseye script: - apt-get update - apt-get install -y --no-install-recommends build-essential libcurl4-openssl-dev pkgconf autotools-dev bison flex make autoconf automake libtool libbsd-dev libatf-dev kyua libreadline-dev - ./autogen.sh - ./configure CFLAGS='-std=c99 -pedantic -Wall -Wextra -Werror -Wno-misleading-indentation' CPPFLAGS='-D_XOPEN_SOURCE=600' --disable-silent-rules --with-readline || (cat config.log && exit 42) - make -j - make -j distcheck gcli-2.3.0/.hotzrc000066400000000000000000000007751460062271200137550ustar00rootroot00000000000000# local kakrc for gcli # We build inside the build directory cd build set-option global ctagscmd "uctags -R --fields=+S" set-option global ctagspaths "../include ../src ../thirdparty" # Define the make cmd to be parallel but only use half of the available cpu cores evaluate-commands %sh{ N=$(nproc) T=$(($N - 1)) S=$(($T / 2)) echo "set-option global makecmd \"cpuset -l${S}-${T} make -j${T}\"" } hook global BufSetOption filetype=c %{ add-highlighter buffer/ show-whitespaces -spc " " smarttab } gcli-2.3.0/Changelog.md000066400000000000000000000162021460062271200146440ustar00rootroot00000000000000# Changelog This changelog does not follow semantic versioning. ## 2.3.0 (2024-Mar-25) ### Added - It is now possible to build gcli against libgcli as a DLL on cygwin. Submitted by: Daisuke Fujimura - The pulls subcommand now allows searching for pull requests with a given search term. The search terms can be appended to the regular pull subcommand for listing PRs: ```console $ gcli pulls -L bug segmentation fault ``` The above will search for pull requests containing »segmentation fault« and the label »bug«. - An interactive mode for creating both PRs and issues has been added. You can now interactively create pull requests and issues by omitting their title: ```console $ gcli issues create Owner [herrhotzenplotz]: Repository [gcli]: Title: foo The following issue will be created: TITLE : foo OWNER : herrhotzenplotz REPO : gcli MESSAGE : No message Do you want to continue? [yN] ``` ### Fixed - gcli was incorrectly using an environment variable *XDG_CONFIG_DIR*. This variable has now been fixed to be *XDG_CONFIG_HOME*. Submitted by: Jakub Wilk - Fixed a segmentation fault when listing forks - Fixed error when submitting a comment on Gitlab issues - The build on Haiku has been fixed. GCLI can now be compiled and used on this platform. ## 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) ### Added - Added a little spinner to indicate network activity - Added Windows 10 MSYS2 to list of confirmed-to-work platforms - Added a new action `set-visibility` to the repos subcommand that allows updating the visibility level of a repository. - Added a new action `request-review` to the pulls subcommand that allows requesting a review of a pull request from a given user. - One can now define custom aliases in the alias section of the config file. Aliases are very primitive as of now. This means they are just different names for subcommands. Aliases may reference other aliases. - Added a new `-M` flag to both the pulls and the issues subcommand to allow filtering by milestones. - Added a new `patch` action to the pulls subcommand. This allows you to print the entire patch series for a given pull request. Also added the missing implementations for this feature for Github and Gitea. - Added a new `title` action to both the issues and the pulls subcommand that allows updating their titles. ### Fixed - Fixed incorrect internal help message of the `repos` subcommand. - Worked around ICE with xlC 16 on ppc64le Debian Linux, gcli now compiles using xlC and works too. - Fixed various memory leaks. - Spelling fixes in manual pages (submitted by Jakub Wilk https://github.com/herrhotzenplotz/gcli/pull/121) - Wired up alread existing implementation for forking on Gitea to gcli command. - The `status` subcommand now works properly on Gitea. ### Changed - Subcommands can now be abbreviated by providing an unambiguous prefix that matches the subcommand. ### Removed ## 2.0.0 (2023-Sep-21) ### Added - This changelog has been added - gcli is now built as a shared or static library which the gcli tool links against This implied so many changes that the major version number was bumped. - Added a package-config file for libgcli - Added a `-L` flag to the `issues` and `pulls` subcommand to allow filtering by label - A work-in-progress tutorial has been added and is available at [the GCLI directory](https://herrhotzenplotz.de/gcli/tutorial) on my website. - Gitlab jobs now show coverage information ### Fixed - Parallel builds in autotools have been re-enabled - Improved error messages in various places - Bad roff syntax in manual pages has been fixed ### Changed - the `gcli pulls create` subcommand does not print the URL to the created release anymore. - The test suite is now using [atf-c](https://github.com/jmmv/atf) and [kyua](https://github.com/jmmv/kyua). These are dependencies if you want to run the tests. These tools are installed out of the box on most BSDs. - A newly introduced dependency is the `sys/queue.h` header. On GNU/Linux systems you might need to install it as part of libbsd. ### Removed - The reviews subcommand has been removed because it was generally useless This feature will be reimplemented as a WIP of [#189](https://gitlab.com/herrhotzenplotz/gcli/-/issues/189) gcli-2.3.0/HACKING.md000066400000000000000000000226271460062271200140310ustar00rootroot00000000000000# Hacking on GCLI This document gives you hints to get started working with on source code of [gcli](https://herrhotzenplotz.de/gcli/). Please note that this document only captures the state of the code at the some points in time and may be out of date. If you feel like this is the case please submit bug reports or, even better, provide patches. ## Building GCLI We use the GNU Autotools to build GCLI. Using the autotools has a few advantages: - Portability across many platforms, even many older ones - I (Nico) know Autotools fairly well - Cross-Compilation is easy (though not yet fully supported by gcli) - Maturity of the tooling due to many years of development - Very few dependencies ### General workflow Autotools generate a `configure` script from `configure.ac` and Makefiles using the `configure` script from `Makefile.am`. The `configure` script checks the build system for various features and edge cases and allows you to enable features etc. This is the place where you can set the C Compiler to be used, flags that should be passed to it and where libraries should be found. To generate the configure script you need to invoke autoreconf via the provided autogen script: $ ./autogen.sh I suggest out-of-tree builds (that is source code is separate from build output). For various different build configurations I create different build directories and one for general debug work: $ mkdir build build-sanitized build-32 $ cd build/ Note: In the following I assume LLVM Clang like compiler options. If your compiler uses different flags (like e.g. Oracle DeveloperStudio) please change the options appropriately. #### Full Debug build Then you can configure each build directory with appropriate options: $ ../configure \ CC=/usr/bin/cc \ CFLAGS='-std=iso9899:1999 -pedantic -Wall -Wextra -Wno-misleading-indentation -Werror -g -O0' \ LDFLAGS='-g' \ --disable-shared The above will give you a fully debuggable and build with strict C99 compiler errors. I very much suggest that you use those options while working on and debugging gcli. *Note*: The `--disable-shared` is required because if you build a shared version of libgcli, libtool will replace the gcli binary with a shell script that alters the dld search path to read the correct `libgcli.so`. Because of `build/gcli` now not being an ELF executable but a shell script debuggers can't load gcli properly. #### Sanitized Builds I sometimes enable the sanitizer features of the C Compiler to check for common bugs: $ ../configure CC=/usr/bin/cc \ CFLAGS='-std=iso9899:1999 -pedantic -Wall -Wextra -Wno-misleading-indentation -Werror -g -O0 -fsanitize=address,undefined' \ LDFLAGS=-g \ --disable-shared #### Cross-Compilation This is not yet fully supported by gcli. However, it is possible to e.g. build a 32bit version of gcli on a 64bit host OS. In this example I have a 32bit version of libcurl installed in /opt/sn. To build gcli against that version you can do something like the following: $ ../configure CC=/usr/bin/cc \ CFLAGS='-m32 -std=iso9899:1999 -pedantic -Wall -Wextra -Wno-misleading-indentation -Werror -g -O0' \ LDFLAGS='-g -L/opt/sn/lib32' \ --with-libcurl=/opt/sn Note: The RPATH will be set automatically by configure. You don't need to cram it into the LDFLAGS. ## Tests The test suite depends on [Kyua](https://github.com/jmmv/kyua) and [libatf-c](https://github.com/jmmv/atf). Before submitting patches please make sure that your changes pass the test suite: $ make -C build check If you change the build system also make sure that it passes a distcheck: $ make -C build distcheck # Code Style Please use the BSD Style conventions for formatting your code. This means: - Functions return type and name go on separate lines, no mixed code and declarations (except in for loops): void foo(int bar) { int x, y, z; x = bar; for (int i = 0; i < 10; ++i) z += i; return x; } This allows to search for the implementation of a function through a simple `grep -rn '^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: void foo(struct foo const *thefoo) { » if (thefoo) » » printf("%s: %d\n" » » .......thefoo->wat, » » .......thefoo->count); } - Try to have a max of 80 characters per line I know we're not using punchcards anymore, however it makes the code way more readable. - Use C99 Please don't use C11 or even more modern features. Reason being that I want gcli to be portable to older platforms where either no modern compilers are available or where we have to rely on old gcc versions and/or buggy vendor compilers. Notable forbidden features are `_Static_assert` and anonymous unions. If you use the compiler flags I mentioned above you should get notified by the compiler. There is a `.editorconfig` included in the source code that should automatically provide you with all needed options. [Editorconfig](https://editorconfig.org/#pre-installed) is a plugin that is available for almost all notable editors out there. I highly recommend you use it. # Adding support for new forges The starting point for adding forges is [include/gcli/forges.h](include/gcli/forges.h). This file contains the dispatch table for fetching data from various kinds of forges. A pointer to the current dispatch table can be retrieved through a call to `gcli_forge()`. You may have to adjust the routines called by it to allow for automagic detection as well as overrides on the command line for your new forge type. You should likely never call `gcli_forge()` directly when adding a new forge type as there are various frontend functions available that will do dispatching for the caller. ## Parsing JSON When you need to parse JSON Objects into C structs you likely want to generate that code. Please see the [templates/](templates/) directory for examples on how to do that. Currently the [PR Parser for Github](templates/github/pulls.t) can act as an example for all features available in the code generator. The code generator is fully documented in [pgen.org](docs/pgen.org). ## Generating JSON We not only need to parse JSON often, we also need to generate it on the fly when submitting data to forge APIs. For this the `gcli_jsongen_` family of functions exist. Since these have been introduced quite late in the project their use is not particularly wide-spread. However this may change in the future. To use these, take a look at the header [include/gcli/json_gen.h](include/gcli/json_gen.h) and also the use in [src/gitlab/merge_requests.c](src/gitlab/merge_requests.c). # User Frontend Features The gcli command line tool links against libgcli. Through a context structure it passes information like the forge type and user credentials into the library. All code for the command line frontend tool is found in the [src/cmd/](src/cmd/) directory. [src/cmd/gcli.c](src/cmd/gcli.c) is the entry point for the command line tool. In this file you can find the dispatch table for all subcommands of gcli. ## Subcommands Subcommand implementations are found in separate C files in the `src/cmd` subdirectory. When parsing command line options please use `getopt_long`. Do not forget to prefix your getopt string with a `+` as we do multiple calls to `getopt_long` so it needs to reset some internal state. ## Output formatting Output is usually formatted as a dictionary or a table. For these cases gcli provides a few convenience functions and data structures. The relevant header is [gcli/cmd/table.h](include/gcli/cmd/table.h). Do not use these functions in the library code. It's only supposed to be used from the command line tool. ### Tables You can print tables by defining a list of columns first: ```C gcli_tblcoldef cols[] = { { .... }, { .... }, }; ``` For a complete definition look at the header or uses of that interface in e.g. [src/cmd/issues.c](src/cmd/issues.c). You can then start adding rows to your table: ```C gcli_tbl table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); for (int i = 0; i < list.whatever_size; ++i) { gcli_tbl_add_row(table, ...); } ``` The variadic arguments you need to provide depends on the columns defined. Most relevant is the flags and type field. Make sure you get data type sizes correct. To dump the table to stdout use the following call: ```C gcli_tbl_end(table); ``` This will print the table and free all resources acquired by calls to the tbl routines. You may no reuse the handle returned by `gcli_tbl_begin()` after this call. Instead, call the begin routine again to obtain a new handle. ### Dictionaries The dictionary routines act almost the same way as tables except that you don't define the columns. Instead you obtain a handle through `gcli_dict_begin` and add entries to the dictionary by calling `gcli_dict_add` or one of the specialized functions for strings. `gcli_dict_add` is the most generic of them all and provides a printf-like format and variadic argument list. You can dump the dictionary and free resources through a call to `gcli_dict_end`. gcli-2.3.0/LICENSE000066400000000000000000000024231460062271200134400ustar00rootroot00000000000000Copyright 2021, 2022 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. gcli-2.3.0/Makefile.am000066400000000000000000000250751460062271200144770ustar00rootroot00000000000000ACLOCAL_AMFLAGS = -I m4 AM_YFLAGS = -d AM_CPPFLAGS = -I$(srcdir)/include AM_CPPFLAGS += -I$(srcdir)/thirdparty AM_CPPFLAGS += -I$(srcdir) AM_CPPFLAGS += -DYY_NO_INPUT AM_CPPFLAGS += -DYY_NO_UNPUT AM_CPPFLAGS += -D_XOPEN_SOURCE=600 AM_CFLAGS = -Wno-gnu-zero-variadic-macro-arguments noinst_PROGRAMS = pgen$(EXEEXT) pgen_SOURCES = \ include/gcli/pgen.h \ thirdparty/sn/sn.c \ src/pgen/dump_c.c \ src/pgen/dump_h.c \ src/pgen/dump_plain.c \ src/pgen/parser.y \ src/pgen/lexer.l lib_LTLIBRARIES = libgcli.la noinst_LTLIBRARIES = libpdjson.la # For testing puproses I'll reenable parallel builds. If it breaks again, uncomment. # .NOTPARALLEL: pgen$(EXEEXT) $(builddir)/src/pgen/parser.c $(builddir)/src/pgen/parser.h $(builddir)/src/pgen/lexer.c src/pgen/lexer.c: src/pgen/parser.h libgcli_la_DEPENDENCIES = pgen$(EXEEXT) libpdjson.la $(BUILT_SOURCES): pgen$(EXEEXT) SUFFIXES = .t .t.h: $(AM_V_GEN)./pgen -th -o $@ $< .t.c: $(AM_V_GEN)./pgen -tc -o $@ $< bin_PROGRAMS = gcli$(EXEEXT) gcli_LDADD = libgcli.la libgcli_la_LIBADD = libpdjson.la libgcli_la_LDFLAGS = -no-undefined dist_man_MANS = \ docs/gcli-api.1 \ docs/gcli-comment.1 \ docs/gcli-config.1 \ docs/gcli-forks.1 \ docs/gcli-gists.1 \ docs/gcli-issues.1 \ docs/gcli-labels.1 \ docs/gcli-milestones.1 \ docs/gcli-pipelines.1 \ docs/gcli-pulls.1 \ docs/gcli-releases.1 \ docs/gcli-repos.1 \ docs/gcli-snippets.1 \ docs/gcli-status.1 \ docs/gcli.1 \ 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 \ include/gcli/cmd/colour.h src/cmd/colour.c \ include/gcli/cmd/comment.h src/cmd/comment.c \ include/gcli/cmd/config.h src/cmd/config.c \ include/gcli/cmd/editor.h src/cmd/editor.c \ include/gcli/cmd/forks.h src/cmd/forks.c \ include/gcli/cmd/gists.h src/cmd/gists.c \ include/gcli/cmd/gitconfig.h src/cmd/gitconfig.c \ include/gcli/cmd/issues.h src/cmd/issues.c \ include/gcli/cmd/labels.h src/cmd/labels.c \ include/gcli/cmd/milestones.h src/cmd/milestones.c \ include/gcli/cmd/pipelines.h src/cmd/pipelines.c \ include/gcli/cmd/pulls.h src/cmd/pulls.c \ include/gcli/cmd/releases.h src/cmd/releases.c \ include/gcli/cmd/repos.h src/cmd/repos.c \ include/gcli/cmd/snippets.h src/cmd/snippets.c \ include/gcli/cmd/status.h src/cmd/status.c \ include/gcli/cmd/table.h src/cmd/table.c \ include/gcli/cmd/interactive.h src/cmd/interactive.c \ src/cmd/api.c \ src/cmd/gcli.c libpdjson_la_SOURCES = \ thirdparty/pdjson/pdjson.c \ thirdparty/pdjson/pdjson.h TEMPLATES = \ templates/github/api.t \ templates/github/checks.t \ templates/github/comments.t \ templates/github/forks.t \ templates/github/gists.t \ templates/github/issues.t \ templates/github/labels.t \ templates/github/pulls.t \ templates/github/releases.t \ templates/github/repos.t \ templates/github/status.t \ templates/github/milestones.t \ templates/gitlab/api.t \ templates/gitlab/comments.t \ templates/gitlab/forks.t \ templates/gitlab/issues.t \ templates/gitlab/labels.t \ templates/gitlab/merge_requests.t \ templates/gitlab/milestones.t \ templates/gitlab/pipelines.t \ templates/gitlab/releases.t \ templates/gitlab/repos.t \ templates/gitlab/sshkeys.t \ templates/gitlab/status.t \ templates/gitlab/snippets.t \ templates/gitea/milestones.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 \ include/gcli/curl.h include/gcli/forks.h \ include/gcli/issues.h include/gcli/labels.h \ include/gcli/milestones.h include/gcli/pulls.h \ include/gcli/repos.h include/gcli/gitlab/snippets.h \ include/gcli/status.h include/gcli/sshkeys.h pkgconfdir = $(libdir)/pkgconfig pkgconf_DATA = libgcli.pc 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 \ src/forks.c include/gcli/forks.h \ src/issues.c include/gcli/issues.h \ src/json_gen.c include/gcli/json_gen.h \ 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 \ src/gitlab/snippets.c include/gcli/gitlab/snippets.h \ src/status.c include/gcli/status.h \ src/sshkeys.c include/gcli/sshkeys.h \ src/gitlab/api.c include/gcli/gitlab/api.h \ src/gitlab/comments.c include/gcli/gitlab/comments.h \ src/gitlab/config.c include/gcli/gitlab/config.h \ src/gitlab/forks.c include/gcli/gitlab/forks.h \ src/gitlab/issues.c include/gcli/gitlab/issues.h \ src/gitlab/labels.c include/gcli/gitlab/labels.h \ src/gitlab/merge_requests.c include/gcli/gitlab/merge_requests.h \ src/gitlab/milestones.c include/gcli/gitlab/milestones.h \ src/gitlab/pipelines.c include/gcli/gitlab/pipelines.h \ src/gitlab/releases.c include/gcli/gitlab/releases.h \ src/gitlab/repos.c include/gcli/gitlab/repos.h \ src/gitlab/status.c include/gcli/gitlab/status.h \ src/gitlab/sshkeys.c include/gcli/gitlab/sshkeys.h \ src/github/releases.c include/gcli/github/releases.h \ src/github/config.c include/gcli/github/config.h \ src/github/api.c include/gcli/github/api.h \ src/github/repos.c include/gcli/github/repos.h \ src/github/forks.c include/gcli/github/forks.h \ src/github/pulls.c include/gcli/github/pulls.h \ src/github/comments.c include/gcli/github/comments.h \ src/github/status.c include/gcli/github/status.h \ src/github/labels.c include/gcli/github/labels.h \ src/github/milestones.c include/gcli/github/milestones.h \ src/github/issues.c include/gcli/github/issues.h \ src/github/checks.c include/gcli/github/checks.h \ src/github/gists.c include/gcli/github/gists.h \ src/github/sshkeys.c include/gcli/github/sshkeys.h \ src/gitea/issues.c include/gcli/gitea/issues.h \ src/gitea/labels.c include/gcli/gitea/labels.h \ src/gitea/forks.c include/gcli/gitea/forks.h \ src/gitea/comments.c include/gcli/gitea/comments.h \ src/gitea/config.c include/gcli/gitea/config.h \ src/gitea/pulls.c include/gcli/gitea/pulls.h \ src/gitea/releases.c include/gcli/gitea/releases.h \ src/gitea/repos.c include/gcli/gitea/repos.h \ 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) \ thirdparty/sn/sn.c thirdparty/sn/sn.h libgcli_la_CPPFLAGS = \ $(AM_CPPFLAGS) \ -DIN_LIBGCLI=1 BUILT_SOURCES = \ $(TEMPLATES:.t=.c) \ $(TEMPLATES:.t=.h) CLEANFILES = $(BUILT_SOURCES) EXTRA_DIST = \ LICENSE \ README.md \ HACKING.md \ autogen.sh \ m4/.gitkeep \ docs/pgen.org \ Changelog.md ########################################### # Tests if HAVE_TESTS check_PROGRAMS = \ tests/json-escape$(EXEEXT) \ 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) $(check_PROGRAMS): tests/gcli_tests.h EXTRA_DIST += tests/gcli_tests.h check: do_test do_test: $(check_PROGRAMS) tests/Kyuafile kyua test -k tests/Kyuafile tests_json_escape_SOURCES = \ tests/json-escape.c tests_json_escape_CFLAGS = \ $(AM_CFLAGS) \ $(LIBATFC_CFLAGS) tests_json_escape_LDADD = \ libgcli.la libpdjson.la \ $(LIBATFC_LIBS) tests_github_parse_tests_SOURCES = \ tests/github-parse-tests.c tests_github_parse_tests_CFLAGS = \ $(AM_CFLAGS) \ $(LIBATFC_CFLAGS) tests_github_parse_tests_LDADD = \ libgcli.la libpdjson.la \ $(LIBATFC_LIBS) tests_gitlab_parse_tests_SOURCES = \ tests/gitlab-parse-tests.c tests_gitlab_parse_tests_CFLAGS = \ $(AM_CFLAGS) \ $(LIBATFC_CFLAGS) tests_gitlab_parse_tests_LDADD = \ libgcli.la libpdjson.la \ $(LIBATFC_LIBS) tests_gitea_parse_tests_SOURCES = \ tests/gitea-parse-tests.c tests_gitea_parse_tests_CFLAGS = \ $(AM_CFLAGS) \ $(LIBATFC_CFLAGS) tests_gitea_parse_tests_LDADD = \ libgcli.la libpdjson.la \ $(LIBATFC_LIBS) tests_bugzilla_parse_tests_SOURCES = \ tests/bugzilla-parse-tests.c tests_bugzilla_parse_tests_CFLAGS = \ $(AM_CFLAGS) \ $(LIBATFC_CFLAGS) tests_bugzilla_parse_tests_LDADD = \ libgcli.la libpdjson.la \ $(LIBATFC_LIBS) tests_base64_tests_SOURCES = \ tests/base64-tests.c tests_base64_tests_CFLAGS = \ $(AM_CFLAGS) \ $(LIBATFC_CFLAGS) tests_base64_tests_LDADD = \ libgcli.la libpdjson.la \ $(LIBATFC_LIBS) tests_url_encode_SOURCES = \ tests/url-encode.c tests_url_encode_CFLAGS = \ $(AM_CFLAGS) \ $(LIBATFC_CFLAGS) tests_url_encode_LDADD = \ libgcli.la libpdjson.la \ $(LIBATFC_LIBS) tests_pretty_print_test_SOURCES = \ tests/pretty_print_test.c \ thirdparty/sn/sn.c thirdparty/sn/sn.h tests_pretty_print_test_CFLAGS = \ $(AM_CFLAGS) \ $(LIBATFC_CFLAGS) tests_pretty_print_test_LDADD = \ $(LIBATFC_LIBS) tests_test_jsongen_SOURCES = \ tests/test-jsongen.c tests_test_jsongen_CFLAGS = \ $(AM_CFLAGS) \ $(LIBATFC_CFLAGS) tests_test_jsongen_LDADD = \ libgcli.la \ $(LIBATFC_LIBS) EXTRA_DIST += tests/samples/github_simple_comment.json \ tests/samples/github_simple_fork.json \ tests/samples/github_simple_issue.json \ tests/samples/github_simple_issue.json \ tests/samples/github_simple_label.json \ tests/samples/github_simple_milestone.json \ tests/samples/github_simple_pull.json \ tests/samples/github_simple_release.json \ tests/samples/github_simple_repo.json \ tests/samples/gitlab_simple_issue.json \ tests/samples/gitlab_simple_fork.json \ tests/samples/gitlab_simple_label.json \ tests/samples/gitlab_simple_merge_request.json \ tests/samples/gitlab_simple_milestone.json \ tests/samples/gitlab_simple_pipeline.json \ tests/samples/gitlab_simple_release.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/bugzilla_attachments.json \ tests/samples/bugzilla_comments.json \ tests/samples/bugzilla_simple_bug.json endif gcli-2.3.0/README.md000066400000000000000000000077621460062271200137250ustar00rootroot00000000000000# GCLI Portable CLI tool for interacting with Git(Hub|Lab|Tea) from the command line. ![](docs/screenshot.png) ## Why? The official GitHub CLI tool only supports GitHub. I wanted a simple 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) 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 also works with Tor. ## Building ### Download Recent tarballs can be downloaded here: [https://herrhotzenplotz.de/gcli/releases/](https://herrhotzenplotz.de/gcli/releases/) Please note that the tarballs provided by Github and Gitlab will not work without you having autoconf and automake installed. So please consider using the link above. There are official packages available: - [FreeBSD](https://freshports.org/devel/gcli) - [Debian Testing](https://packages.debian.org/trixie/gcli) - [ArchLinux AUR](https://aur.archlinux.org/packages/gcli) - [NixPkgs Unstable](https://search.nixos.org/packages?channel=unstable&show=gcli&from=0&size=50&sort=relevance&type=packages&query=gcli) ### Dependencies Required dependencies: - libcurl - yacc (System V yacc, Berkeley Yacc or Bison should suffice) - lex (flex is preferred) - C99 Compiler and linker - make Optional dependencies: - pkg-config If you are building from Git you will also need: - m4 - autoconf - automake The test suite requires: - [Kyua](https://github.com/jmmv/kyua) - [ATF](https://github.com/jmmv/atf) ### Compile In order to perform a build, do: ```console $ ./configure [--prefix=/usr/local] $ make # make [DESTDIR=/] install ``` You may leave out `DESTDIR` and `--prefix=`. The above is the default value. The final installation destination is `$DESTDIR/$PREFIX/...`. If you are unsure, consult the builtin configure help by running `./configure --help`. Also, if you are building from Git you need to generate the configure script first: ```console $ ./autogen.sh ``` For more details also see [HACKING.md](HACKING.md). In case any of this does not work, please either report a bug, or submit a patch in case you managed to fix it. Tested Operating Systems so far: - FreeBSD 13.0-RELEASE amd64 and arm64 - Solaris 10 and 11, sparc64 - SunOS 5.11 i86pc (OmniOS) - Devuan GNU/Linux Chimaera x86_64 - Debian GNU/Linux ppc64, ppc64le - Gentoo Linux sparc64, ia64 - Fedora 34 x86_64 - Haiku x86_64 - Minix 3.4.0 (GENERIC) i386 - OpenBSD 7.0 GENERIC amd64 - Alpine Linux 3.16 x86_64 - Darwin 22.2.0 arm64 - Windows 10 (MSYS2 mingw32-w64) - NetBSD 9.3 amd64, sparc64 and VAX Tested Compilers so far: - LLVM Clang (various versions) - GCC (various versions) - Oracle DeveloperStudio 12.6 - IBM XL C/C++ V16.1.1 (Community Edition) ## Support Please refer to the manual pages that come with gcli. You may want to start at `gcli(1)`. For further questions refer to the issues on Github and Gitlab or ask on IRC at #gcli on [Libera.Chat](https://libera.chat/). Alternatively you may also use the mailing list at [https://lists.sr.ht/~herrhotzenplotz/gcli-discuss](https://lists.sr.ht/~herrhotzenplotz/gcli-discuss). ## Bugs and contributions Please report bugs, issues and questions to [~herrhotzenplotz/gcli-discuss@lists.sr.ht](mailto:~herrhotzenplotz/gcli-discuss@lists.sr.ht) or on [GitLab](https://gitlab.com/herrhotzenplotz/gcli). You can also submit patches using git-send-email or Mercurial patchbomb to [~herrhotzenplotz/gcli-devel@lists.sr.ht](mailto:~herrhotzenplotz/gcli-devl@lists.sr.ht). ## License BSD-2 CLAUSE (aka. FreeBSD License). Please see the LICENSE file attached. ## Credits This program makes heavy use of both [libcurl](https://curl.haxx.se/) and [pdjson](https://github.com/skeeto/pdjson). herrhotzenplotz aka. Nico Sonack October 2021 gcli-2.3.0/autogen.sh000077500000000000000000000000361460062271200144320ustar00rootroot00000000000000#!/bin/sh -xe autoreconf -iv gcli-2.3.0/configure.ac000066400000000000000000000165571460062271200147360ustar00rootroot00000000000000# -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ([2.69]) AC_INIT([gcli], [2.3.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="2024-May-25" AC_SUBST([PACKAGE_DATE]) dnl Silent by default. AM_SILENT_RULES([yes]) AC_CONFIG_SRCDIR([config.h.in]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_MACRO_DIRS([m4]) AM_PROG_AR LT_INIT([pic-only]) AC_SUBST([LIBTOOL_DEPS]) AC_PROG_CC([cc clang gcc cl]) AC_LANG([C]) AC_PROG_YACC AC_PROG_LEX(noyywrap) dnl Produce a better error message if we don't have pkg-config's macros dnl Inspired by the way dpkg solves the problem m4_ifndef([PKG_PROG_PKG_CONFIG], [m4_fatal([pkgconf macros missing. do you have pkgconf installed?])]) PKG_PROG_PKG_CONFIG dnl Use ccache if found CCACHE="" AC_CHECK_PROG([CCACHE], [ccache], [ccache]) if ! test -z "${CCACHE}"; then CC="${CCACHE} ${CC}" fi dnl #################################################################################### dnl LIBCURL dnl #################################################################################### dnl Go looking for libcurl OPT_LIBCURL=check AC_ARG_WITH([libcurl], [AS_HELP_STRING([--with-libcurl[[=DIR]]], [Give an alternate path to libcurl.])], OPT_LIBCURL=$withval ) AS_IF([test "x$OPT_LIBCURL" = "xno"], [AC_MSG_ERROR([--with-libcurl must not be disabled])]) AS_IF([test "x$OPT_LIBCURL" = "xcheck" || test "x$OPT_LIBCURL" = "xyes"], [PKG_CHECK_MODULES([LIBCURL], [libcurl],,[AC_MSG_ERROR([Could not find libcurl])]) CFLAGS="$LIBCURL_CFLAGS $CFLAGS" LDFLAGS="$LIBCURL_LIBS $LDFLAGS"], [CPPFLAGS="-I$OPT_LIBCURL/include $CPPFLAGS" LDFLAGS="-L$OPT_LIBCURL/lib $LDFLAGS"]) AC_CHECK_HEADER([curl/curl.h],,[AC_MSG_ERROR([Cannot find libcurl headers])]) dnl FIXME find a better way for this dnl AC_CHECK_LIB([curl],[curl_easy_init],,[AC_MSG_ERROR([-lcurl doesn not contain curl_easy_init])]) dnl #################################################################################### dnl LIBEDIT dnl #################################################################################### dnl Check for libedit OPT_LIBEDIT=check AC_ARG_WITH([libedit], [AS_HELP_STRING([--with-libedit[[=DIR]]], [Use libedit at the given prefix for interactive editing])], OPT_LIBEDIT=$withval, OPT_LIBEDIT=no ) HAVE_LIBEDIT=0 if test "x$OPT_LIBEDIT" = "xyes" || test "x$OPT_LIBEDIT" = "xcheck"; then HAVE_LIBEDIT=1 PKG_CHECK_MODULES([LIBEDIT], [libedit],,[AC_MSG_ERROR([Could not find libedit])]) CFLAGS="$LIBEDIT_CFLAGS $CFLAGS" LDFLAGS="$LIBEDIT_LIBS $LDFLAGS" elif ! test "x$OPT_LIBEDIT" = "xno"; then HAVE_LIBEDIT=1 CPPFLAGS="-I$OPT_LIBEDIT/include $CPPFLAGS" LDFLAGS="-L$OPT_LIBEDIT/lib $LDFLAGS" fi if test $HAVE_LIBEDIT -eq 1; then AC_CHECK_HEADER([histedit.h],,[AC_MSG_ERROR([Cannot find libedit headers])]) fi AC_DEFINE_UNQUOTED([HAVE_LIBEDIT], [$HAVE_LIBEDIT], [Define if we link against libedit]) dnl #################################################################################### dnl READLINE (second option) dnl #################################################################################### if ! test $HAVE_LIBEDIT -eq 1; then OPT_READLINE=check AC_ARG_WITH([readline], [AS_HELP_STRING([--with-readline[[=DIR]]], [Use readline at the given prefix for interactive editing. When libedit is available this option is a no-op.])], OPT_READLINE=$withval, OPT_READLINE=no ) HAVE_READLINE=0 if test "x$OPT_READLINE" = "xyes" || test "x$OPT_READLINE" = "xcheck"; then HAVE_READLINE=1 # FIXME: This is an ugly hack because the readline headers are # not C99 clean. Clang seems to not like them and causes # the configure script to fail. CFLAGS="${CFLAGS} -Wno-strict-prototypes" PKG_CHECK_MODULES([READLINE], [readline],,[AC_MSG_ERROR([Could not find readline])]) CFLAGS="$READLINE_CFLAGS $CFLAGS" LDFLAGS="$READLINE_LIBS $LDFLAGS" elif ! test "x$OPT_READLINE" = "xno"; then HAVE_READLINE=1 CPPFLAGS="-I$OPT_READLINE/include $CPPFLAGS" LDFLAGS="-L$OPT_READLINE/lib $LDFLAGS" fi if test $HAVE_READLINE -eq 1; then AC_CHECK_HEADER([readline/readline.h],,[AC_MSG_ERROR([Cannot find readline headers])]) fi AC_DEFINE_UNQUOTED([HAVE_READLINE], [$HAVE_READLINE], [Define if we link against readline]) fi dnl #################################################################################### dnl TEST SUITE STUFF dnl #################################################################################### dnl For the test suite we require libatf-c and Kyua OPT_LIBATFC=check AC_ARG_WITH([libatf-c], [AS_HELP_STRING([--with-libatf-c[[=DIR]]], [Give an alternate path to libatf-c.])], OPT_LIBATFC=$withval ) HAVE_ATFC=no if test "x$OPT_LIBATFC" = "xcheck" || test "x$OPT_LIBATFC" = "xyes" then PKG_CHECK_MODULES([LIBATFC], [atf-c], [HAVE_ATFC=yes],[HAVE_ATFC=no]) elif test "x$OPT_LIBATFC" = "xno" then HAVE_ATFC=no else LIBATFC_CFLAGS="-I$OPT_LIBATFC/include" LIBATFC_LIBS="-L$OPT_LIBATFC/lib" HAVE_ATFC=yes fi if test "x$HAVE_ATFC" = "xyes"; then AC_SUBST([LIBATFC_CFLAGS]) AC_SUBST([LIBATFC_LIBS]) _OLD_CFLAGS="$CFLAGS" _OLD_LDFLAGS="$LDFLAGS" CFLAGS="$CFLAGS $LIBATFC_CFLAGS" LDFLAGS="$LDFLAGS $LIBATFC_LIBS" AC_CHECK_HEADER([atf-c.h],,[AC_MSG_ERROR([Cannot find libatf-c headers])]) CFLAGS="$_OLD_CFLAGS" LDFLAGS="$_OLD_LDFLAGS" HAVE_ATFC=yes fi AC_CHECK_PROG([KYUA], [kyua], [kyua]) AC_CHECK_PROGS([REALPATH], [realpath grealpath], []) HAVE_TESTS=0 AS_IF([test -z "$KYUA" || test "x$HAVE_ATFC" = xno || test -z "$REALPATH"], [HAVE_TESTS=no], [HAVE_TESTS=yes]) AC_SUBST([HAVE_TESTS]) AM_CONDITIONAL([HAVE_TESTS], [test "x$HAVE_TESTS" = xyes]) dnl Go looking for headers that may define getopt_long AC_CHECK_HEADERS([getopt.h unistd.h]) dnl Other headers that we require AC_CHECK_HEADERS([string.h signal.h sys/wait.h unistd.h sys/mman.h sys/types.h sys/queue.h], , [AC_MSG_ERROR([Required header not found])]) dnl Check that the function is defined somewhere. AC_CHECK_FUNC([getopt_long]) dnl Host OS name in various files AC_DEFINE_UNQUOTED([HOSTOS], ["$host"], [Define to the triplet of the host operating system]) dnl Generate and substitute various files AC_CONFIG_FILES([Makefile libgcli.pc docs/gcli-api.1 docs/gcli-comment.1 docs/gcli-config.1 docs/gcli-forks.1 docs/gcli-gists.1 docs/gcli-issues.1 docs/gcli-labels.1 docs/gcli-milestones.1 docs/gcli-pipelines.1 docs/gcli-pulls.1 docs/gcli-releases.1 docs/gcli-repos.1 docs/gcli-snippets.1 docs/gcli-status.1 docs/gcli.1 docs/gcli.5]) dnl Technically only needed if tests are enabled but this doesn't dnl hurt. if test "x$HAVE_TESTS" = "xyes"; then TESTSRCDIR="$($REALPATH ${srcdir}/tests)" AC_SUBST([TESTSRCDIR]) AC_DEFINE_UNQUOTED([TESTSRCDIR], ["$TESTSRCDIR"], [Directory to the test sources]) fi AC_CONFIG_FILES([tests/Kyuafile]) AC_OUTPUT gcli-2.3.0/docs/000077500000000000000000000000001460062271200133625ustar00rootroot00000000000000gcli-2.3.0/docs/.gitignore000066400000000000000000000003471460062271200153560ustar00rootroot00000000000000/gcli-api.1 /gcli-comment.1 /gcli-config.1 /gcli-forks.1 /gcli-gists.1 /gcli-issues.1 /gcli-labels.1 /gcli-milestones.1 /gcli-pipelines.1 /gcli-pulls.1 /gcli-releases.1 /gcli-repos.1 /gcli-snippets.1 /gcli-status.1 /gcli.1 /gcli.5 gcli-2.3.0/docs/gcli-api.1.in000066400000000000000000000022121460062271200155330ustar00rootroot00000000000000.Dd @PACKAGE_DATE@ .Dt GCLI-API 1 .Os @PACKAGE_STRING@ .Sh NAME .Nm gcli api .Nd Perform API Calls to Git Forges and retrieve results .Sh SYNOPSIS .Nm .Op Fl a .Ar path .Sh DESCRIPTION The .Nm is used to debug API calls. It will autodetect the current forge and perform a request to its REST API. The response is printed to stdout. The API base URL is automatically prepended to the given .Ar path . .Sh OPTIONS .Bl -tag -width "-a, --all" .It Fl a , -all Fetch all pages of data (follow the pagination link). .El .Sh EXAMPLES Fetch all pages of data from the issues endpoint of the gcli project. Dump the data into /tmp/foo and be verbose. This will print the queries that are performed to stderr: .Bd -literal -offset indent $ gcli -v api -a /projects/herrhotzenplotz%2Fgcli/issues >/tmp/foo .Ed .Sh SEE ALSO .Xr git 1 , .Xr gcli 1 .Sh AUTHORS .An Nico Sonack aka. herrhotzenplotz Aq Mt nsonack@herrhotzenplotz.de and contributors. .Sh BUGS Please report bugs via E-Mail to .Mt @PACKAGE_BUGREPORT@ . .Pp Alternatively you can report them on any of the forges linked at .Lk @PACKAGE_URL@ . However, the preferred and quickest method is to use the mailing list. gcli-2.3.0/docs/gcli-comment.1.in000066400000000000000000000036731460062271200164400ustar00rootroot00000000000000.Dd @PACKAGE_DATE@ .Dt GCLI-COMMENT 1 .Os @PACKAGE_STRING@ .Dd $Mdocdate$ .Sh NAME .Nm gcli comment .Nd Comment on tickets in git forges .Sh SYNOPSIS .Nm .Op Fl y .Op Fl o Ar owner Fl r Ar repo .Op Fl i Ar issue | Fl p Ar PR .Sh DESCRIPTION .Nm can be used to add comments in the discussion under issues and pull requests on .Xr git 1 forges such as Github, Gitlab and Gitea. Note that PRs are treated as issues on GitHub and Gitea, making the .Fl i and .Fl p flags exchangeable without changing the overall effect of creating the comment. .Nm will open an editor, either specified in your environment through .Ev EDITOR or the one set in your global config file to enter the comment. See .Xr gcli 1 . .Sh OPTIONS .Bl -tag -width indent .It Fl o , -owner Ar owner Comment in the repository of the given owner. This option can only be used in combination with .Fl r . .It Fl r , -repo Ar repo Comment in the given repository. This option can only be used in combination with .Fl r . .It Fl y , -yes Do not ask for confirmation before submitting the comment. Assume yes. .It Fl i , -issue Ar issue Create the comment under issue .Ar #issue . .It Fl p , -pull Ar pr Create the comment under PR .Ar #pr . .El .Sh EXAMPLES Comment under PR #11 in the upstream repository: .Bd -literal -offset indent $ gcli comment -p 11 .Ed .Pp Comment under issue 1 in herrhotzenplotz/gcli: .Bd -literal -offset indent $ gcli comment -o herrhotzenplotz -r gcli -i 1 .Ed .Sh SEE ALSO .Xr git 1 , .Xr gcli 1 , .Xr gcli-issues 1 , .Xr gcli-pulls 1 .Sh AUTHORS .An Nico Sonack aka. herrhotzenplotz Aq Mt nsonack@herrhotzenplotz.de and contributors. .Sh BUGS There is no way to preview the markdown markup, however you can input markdown which will be rendered on the remote site. .Pp Please report bugs via E-Mail to .Mt @PACKAGE_BUGREPORT@ . .Pp Alternatively you can report them on any of the forges linked at .Lk @PACKAGE_URL@ . However, the preferred and quickest method is to use the mailing list. gcli-2.3.0/docs/gcli-config.1.in000066400000000000000000000032621460062271200162350ustar00rootroot00000000000000.Dd @PACKAGE_DATE@ .Dt GCLI-ISSUES 1 .Os @PACKAGE_STRING@ .Sh NAME .Nm gcli config .Nd Git Forge Configuration .Sh SYNOPSIS .Nm .Cm ssh .Nm .Cm ssh add .Fl t Ar title .Fl k Ar keypath .Nm .Cm ssh delete .Fl i Ar id .Sh DESCRIPTION .Nm is used to change the settings of the Git Forge Account. You can use it to e.g. add or delete SSH Public Keys used to push to forges. .Sh OPTIONS .Bl -tag -width xxxxxxxxxxxxxxxxx .It Fl t , -title Ar title Set the title of the SSH Key to be added. This is a short description of the key. .It Fl k , -key Pa keypath Path to the file containing the SSH public key. .It Fl i , -id Ar id ID of the public key to delete. .El . .Sh SUBCOMMANDS .Bl -tag -width xxxxxxxxxxx .It Cm ssh List SSH public keys for the current user. .It Cm ssh add Add an SSH public key for the current user. .It Cm ssh delete Delete an SSH public key for the current user. .El .Sh EXAMPLES Print a list of registered SSH public keys: .Bd -literal -offset indent $ gcli config ssh .Ed .Pp Register ~/.ssh/id_rsa.pub on the default forge: .Bd -literal -offset indent $ gcli config ssh add \\ -t "Key for $(hostname)" \\ -k ~/.ssh/id_rsa.pub .Ed .Pp .Sh SEE ALSO .Xr git 1 , .Xr gcli 1 .Sh AUTHORS .An Nico Sonack aka. herrhotzenplotz Aq Mt nsonack@herrhotzenplotz.de and contributors. .Sh BUGS When using this feature to manage SSH keys on Github be aware that you need the .Dq read:public_key scope enabled on your access token. You will receive HTTP 404 errors otherwise. .Pp Please report bugs via E-Mail to .Mt @PACKAGE_BUGREPORT@ . .Pp Alternatively you can report them on any of the forges linked at .Lk @PACKAGE_URL@ . However, the preferred and quickest method is to use the mailing list. gcli-2.3.0/docs/gcli-forks.1.in000066400000000000000000000051221460062271200161110ustar00rootroot00000000000000.Dd @PACKAGE_DATE@ .Dt GCLI-FORKS 1 .Os @PACKAGE_STRING@ .Sh NAME .Nm gcli forks .Nd Manage repository forks in git forges .Sh SYNOPSIS .Nm .Op Fl n Ar n .Op Fl y .Op Fl s .Op Fl o Ar owner Fl r Ar repo .Ar actions... .Nm .Cm create .Op Fl o Ar owner Fl r Ar repo .Fl i Ar target-owner .Sh DESCRIPTION Use .Nm to manage forks of other repositories in various .Xr git 1 forges such as GitHub, GitLab and Gitea. .Sh OPTIONS .Bl -tag -width indent .It Fl o , -owner Ar owner Operate on the repository of the given owner. This option can only be used in combination with .Fl r . .It Fl r , -repo Ar repo Operate on the given repository. This option can only be used in combination with .Fl o . .It Fl y , -yes Do not ask for confirmation. Assume yes. .It Fl s , -sorted Reverse the output such that most recent items appear at the bottom. .It Fl i , -into Ar target-owner When forking a repository, this is the organization or user the repository is forked into. .It Fl n , -count Ar n Fetch at least .Ar n forks. Setting .Ar n to -1 will fetch all forks. Default: 30. Note that on repositories with many forks fetching all forks can take a considerable amount of time and may result in rate limiting by the respective API. .El .Sh ACTIONS .Ar actions... may be one or more of the following: .Bl -tag -width indent .It Cm delete Delete the fork. You will be asked for confirmation unless you set .Fl y . .Pp The following flags can be specified: .Bl -tag -width indent .It Fl r , -repo See .Sx OPTIONS .It Fl o , -owner See .Sx OPTIONS .It Fl y , -yes Do not ask for confirmation before deleting the fork. See .Sx OPTIONS . .El .El .Sh EXAMPLES Clone vim/vim and fork it into your account: .Bd -literal -offset indent $ git clone git@github.com:vim/vim $ cd vim $ gcli forks create --into .Ed .Pp This will ask you if you want to add a remote to your fork. In case you accept the offer, the origin remote will be renamed to upstream and a new origin will be pointed at your newly created fork. You may also want to setup a .gcli file at the same time: .Bd -literal -offset indent $ printf -- "pr.upstream=vim/vim\\npr.base=trunk\\n" >> .gcli .Ed .Pp Delete your fork of the current repository without confirmation: .Bd -literal -offset indent $ gcli forks -y delete .Ed .Sh SEE ALSO .Xr git 1 , .Xr gcli 1 .Sh AUTHORS .An Nico Sonack aka. herrhotzenplotz Aq Mt nsonack@herrhotzenplotz.de and contributors. .Sh BUGS Please report bugs via E-Mail to .Mt @PACKAGE_BUGREPORT@ . .Pp Alternatively you can report them on any of the forges linked at .Lk @PACKAGE_URL@ . However, the preferred and quickest method is to use the mailing list. gcli-2.3.0/docs/gcli-gists.1.in000066400000000000000000000050511460062271200161170ustar00rootroot00000000000000.Dd @PACKAGE_DATE@ .Dt GCLI-GISTS 1 .Os @PACKAGE_STRING@ .Sh NAME .Nm gcli gists .Nd manage Github Gists .Sh SYNOPSIS .Nm .Op Fl s .Op Fl l .Op Fl n Ar n .Op Fl u Ar user .Nm .Cm create .Op Fl d Ar description .Op Fl f Pa path .Ar gist-file-name .Nm .Cm delete .Op Fl y .Ar gist-id .Nm .Cm get .Ar gist-id .Ar file-name .Sh DESCRIPTION Use .Nm to list, create, download or delete Github Gists. Without a subcommand specified, .Nm will list Gists of the given or autodetected user account. .Sh OPTIONS .Bl -tag -width indent .It Fl l , -long Print a long list instead of a short table. .It Fl s , -sorted Reverse the output such that most recent items appear at the bottom. .It Fl u , -user Ar owner List Gists of the given user. .It Fl n , -count Ar n Fetch at least .Ar n gists. Setting .Ar n to -1 will fetch all gists. Default: 30. Note that on users with many gists fetching all gists can take a considerable amount of time and may result in rate limiting by the GitHub API. .El .Sh SUBCOMMANDS .Bl -tag -width indent .It Cm create Paste a new Gist. The following flags can be specified: .Bl -tag -width indent .It Fl f , -file Pa file Read the content from the specified file instead of standard input. .It Fl d , -description Ar description The description of the Gist to be created. .El .It Cm delete Delete a Gist. The following options can be specified: .Bl -tag -width indent .It Fl y , -yes Do not ask for confirmation before deleting the Gist. Assume yes. .El .It Cm get Download a file from a Gist. There are no options to this subcommand. .El .Sh EXAMPLES List neutaaaaan's Gists: .Bd -literal -offset indent $ gcli gists -u neutaaaaan .Ed .Pp Paste a new gist named foobar and read from foobar.txt: .Bd -literal -offset indent $ gcli gists create foobar < foobar.txt .Ed .Pp Delete gist with id 3b546069d2856e6051bbe3c1080f1b5d: .Bd -literal -offset indent $ gcli gists delete 3b546069d2856e6051bbe3c1080f1b5d .Ed .Pp Print foobar.txt from Gist with id 3b546069d2856e6051bbe3c1080f1b5d into your pager: .Bd -literal -offset indent $ gcli gists get 3b546069d2856e6051bbe3c1080f1b5d foobar.txt | $PAGER .Ed .Pp .Sh SEE ALSO .Xr git 1 , .Xr gcli 1 .Sh AUTHORS .An Nico Sonack aka. herrhotzenplotz Aq Mt nsonack@herrhotzenplotz.de and contributors. .Sh BUGS This subcommand only works on GitHub. It is not implemented for GitLab, as GitLab snippets work differently. .Pp Please report bugs via E-Mail to .Mt @PACKAGE_BUGREPORT@ . .Pp Alternatively you can report them on any of the forges linked at .Lk @PACKAGE_URL@ . However, the preferred and quickest method is to use the mailing list. gcli-2.3.0/docs/gcli-issues.1.in000066400000000000000000000124341460062271200163040ustar00rootroot00000000000000.Dd @PACKAGE_DATE@ .Dt GCLI-ISSUES 1 .Os @PACKAGE_STRING@ .Sh NAME .Nm gcli issues .Nd Manage issues in various git forges .Sh SYNOPSIS .Nm .Op Fl n Ar n .Op Fl a .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 .Ar actions... .Nm .Cm create .Op Fl o Ar owner Fl r Ar repo .Op Fl y .Op Ar issue-title .Sh DESCRIPTION Use .Nm 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 will list issues in the given or autodetected repository. .Sh OPTIONS .Bl -tag -width indent .It Fl s , -sorted Reverse the output such that most recent items appear at the bottom. .It Fl o , -owner Ar owner List issues in the repository of the given owner. This option can only be used in combination with .Fl r . .It Fl r , -repo Ar repo List issues in the given repository. This option can only be used in combination with .Fl o . .It Fl a List issues disregarding their state. This will list closed issues as well. Cannot be combined with actions. This does not affect the .Fl n option. .It Fl A , Fl -author Ar user 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 specified once. .It Fl M , Fl -milestone Ar milestone Filter issues by the given milestone. This option may only be specified once. .It Fl n , -count Ar n Fetch at least .Ar n issues. Setting .Ar n to -1 will fetch all issues. Default: 30. Note that on large repositories fetching all issues can take a considerable amount of time and may result in rate limiting by the respective API. See .Sx CAVEATS . .It Fl i , -id Ar issue execute the given .Ar actions for the specified .Ar issue . .El . .Sh SUBCOMMANDS .Bl -tag -width indent .It Cm create Create a new issue in the given or autodetected repository. The editor will come up and ask you to enter an issue message. .Pp When the issue title is omitted gcli will interactively prompt you for all the details to create an issue. .Pp The following flags can be specified: .Bl -tag -width indent .It Fl i , -in Ar owner/repo Specify in which repository the issue is to be created. .It Fl y , -yes Do not ask for confirmation before creating the issue. Assume yes. .El .El .Sh ACTIONS .Ar actions... may be one or more of the following: .Bl -tag -width "labels [options]" .It Cm all Display both a summary and the original post of the issue. .It Cm comments Print a list of comments under the issue. .It Cm status Print a short summary of the issue. .It Cm op Print the original post of the issue. .It Cm close Close the issue. .It Cm reopen Reopen a closed issue. .It Cm assign Ar assignee Assign the issue to the given .Ar assignee (user name). .It Cm labels Op Ar options The following options can be specified more than once: .Bl -tag -width indent .It add Ar label Add the given label to the issue. .It remove Ar label Remove the given label from the issue. .El .It Cm milestone Ar id Assign the issue to a milestone with the given .Ar id . .It Cm milestone Fl d Clear associated milestone of the given issue. .It Cm notes Alias for the .Cm comments 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: .Bd -literal -offset indent $ 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; interactively asking for details: .Bd -literal -offset indent $ gcli issues create .Ed .Pp Report a new issue titled .Dq summary here in the current project: .Bd -literal -offset indent $ gcli issues create "summary here" .Ed .Pp Print both a summary and comments of issue 1 in herrhotzenplotz/gcli: .Bd -literal -offset indent $ gcli issues -o herrhotzenplotz -r gcli -i 1 status comments .Ed .Pp Add the labels .Sq foo and .Sq bar to the issue with id 420: .Bd -literal -offset indent $ gcli issues -i420 labels add foo add bar .Ed .Pp List issues with the label .Dq bug : .Bd -literal -offset indent $ gcli issues -L bug .Ed .Sh SEE ALSO .Xr git 1 , .Xr gcli 1 .Sh AUTHORS .An Nico Sonack aka. herrhotzenplotz Aq Mt nsonack@herrhotzenplotz.de and contributors. .Sh CAVEATS GitHub and Gitea treat Pull Requests as Issues. Due to the semantics of .Nm those issues that are actually PRs are dropped from the output. In this case a note will be printed indicating how many issues were dropped. You can suppress this warning using the .Fl q program option. .Sh BUGS GitHub only supports removing labels from issues one by one. If you still want to remove multiple issues with a single gcli call, you may do something like: . .Bd -literal -offset indent $ gcli issues -i42 \\ labels remove bug \\ labels remove foo .Ed .Pp Please report bugs via E-Mail to .Mt @PACKAGE_BUGREPORT@ . .Pp Alternatively you can report them on any of the forges linked at .Lk @PACKAGE_URL@ . However, the preferred and quickest method is to use the mailing list. gcli-2.3.0/docs/gcli-labels.1.in000066400000000000000000000051241460062271200162310ustar00rootroot00000000000000.Dd @PACKAGE_DATE@ .Dt GCLI-LABELS 1 .Os @PACKAGE_STRING@ .Sh NAME .Nm gcli labels .Nd Manage ticket labels in git forges .Sh SYNOPSIS .Nm .Op Fl o Ar owner Fl r Ar repo .Nm .Cm create .Op Fl o Ar owner Fl r Ar repo .Fl d Ar description .Fl n Ar name .Fl c Ar colour .Nm .Cm delete .Op Fl o Ar owner Fl r Ar repo .Ar name\ or\ id .Sh DESCRIPTION Use .Nm to list, create, edit or delete labels for Pull Requests/Merge Requests and issues in repositories in various git forges such as GitHub, GitLab and Gitea. Without any action specified, .Nm will list all defined labels in the given or autodetected repository. .Sh OPTIONS .Bl -tag -width indent .It Fl o , -owner Ar owner Work in the repository of the given owner. This option can only be used in combination with .Fl r . .It Fl r , -repo Ar repo Work in the given repository. This option can only be used in combination with .Fl o . .El .Pp .Sh SUBCOMMANDS .Bl -tag -width indent .It Cm create Create a new label in the given or autodetected repository. .Pp The following flags must be specified: .Bl -tag -width indent .It Fl n , -name Ar name Set the short name of the label to the given .Ar name . .It Fl d , -description Ar description Set the description of the label to the given .Ar text . Note that on GitHub this field may only consist of up to 150 characters. .It Fl c , -colour Ar code Set the colour of the label to the given .Ar code . .Ar code is expected to be a 6 digit hexadecimal RGB colour code. .El .It Cm delete Delete the specified label in the given or autodetected repository. .Pp There are no flags for this subcommand. .El .Sh EXAMPLES Print a list of all labels in the current project: .Bd -literal -offset indent $ gcli labels .Ed .Pp Create a new label called .Sq bug with a description .Sq Something is not working as expected and give it a red colour: .Bd -literal -offset indent $ gcli labels create \\ --name bug \\ --description "Something is not working as expected" \\ --colour FF0000 .Ed .Pp Delete the label .Sq foobar in herrhotzenplotz/gcli and use the configured account .Sq gitlab : .Bd -literal -offset indent $ gcli -a gitlab labels delete -o herrhotzenplotz -r gcli foobar .Ed .Sh SEE ALSO .Xr git 1 , .Xr gcli 1 .Sh AUTHORS .An Nico Sonack aka. herrhotzenplotz Aq Mt nsonack@herrhotzenplotz.de and contributors. .Sh BUGS The delete subcommand should ask for confirmation and have a flag to override this behaviour. .Pp Please report bugs via E-Mail to .Mt @PACKAGE_BUGREPORT@ . .Pp Alternatively you can report them on any of the forges linked at .Lk @PACKAGE_URL@ . However, the preferred and quickest method is to use the mailing list. gcli-2.3.0/docs/gcli-milestones.1.in000066400000000000000000000043651460062271200171570ustar00rootroot00000000000000.Dd @PACKAGE_DATE@ .Dt GCLI-MILESTONES 1 .Os @PACKAGE_STRING@ .Sh NAME .Nm gcli milestones .Nd List and manage milestones in various Git Forges .Sh SYNOPSIS .Nm .Op Fl o Ar owner Fl r Ar repo .Nm .Op Fl o Ar owner Fl r Ar repo .Op Fl i Ar id .Nm .Cm create .Op Fl o Ar owner Fl r Ar repo .Fl t Ar title .Op Fl d Ar description .Sh DESCRIPTION The .Nm command can be used to work with milestones on various .Xr git 1 forges. You can list, create, modify or delete milestons. .Sh OPTIONS .Bl -tag -width indent .It Fl i , -id Ar id Operate on a milestone with the given .Ar id . .It Fl o , -owner Ar owner Work with milestones on a repository of the given .Ar owner . .It Fl r , -repo Ar repo Work with milestones on the given .Ar repository . .It Fl t , -title Ar title Create a milestone with the given .Ar title . This option is mandatory when creating a milestone. .It Fl d , -description Ar description Create the milestone with the given .Ar description . .El .Sh ACTIONS When operating on a single milestone you may use one or more of the following actions: .Bl -tag -width indent .It Cm all Print both general status info and a list of issues related to the given milestone. .It Cm status Print general metadata and information about the milestone. .It Cm issues Print a list of issues attached to the milestone. .It Cm delete Delete this milestone. .El .Sh EXAMPLES Print a list of milestones for the current autodetected forge: .Bd -literal -offset indent $ gcli milestones .Ed .Pp Print details about the milestone with the ID 42: .Bd -literal -offset indent $ gcli milestones -i 42 status .Ed .Pp Create a new milestone with the title foobar: .Bd -literal -offset indent $ gcli milestones create -t foobar .Ed .Pp Delete milestone number 420 in vim/vim on Github: .Bd -literal -offset indent $ gcli -t github milestones -i 420 delete .Ed .Sh SEE ALSO .Xr git 1 , .Xr gcli 1 .Sh AUTHORS .An Nico Sonack aka. herrhotzenplotz Aq Mt nsonack@herrhotzenplotz.de and contributors. .Sh BUGS The delete subcommand deletes the milestone without asking for confirmation. .Pp Please report bugs via E-Mail to .Mt @PACKAGE_BUGREPORT@ . .Pp Alternatively you can report them on any of the forges linked at .Lk @PACKAGE_URL@ . However, the preferred and quickest method is to use the mailing list. gcli-2.3.0/docs/gcli-pipelines.1.in000066400000000000000000000046451460062271200167660ustar00rootroot00000000000000.Dd @PACKAGE_DATE@ .Dt GCLI-PIPELINES 1 .Os @PACKAGE_STRING@ .Sh NAME .Nm gcli pipelines .Nd Inspect and manage GitLab Pipelines .Sh SYNOPSIS .Nm .Op Fl n Ar n .Op Fl o Ar owner Fl r Ar repo .Nm .Fl p Ar pipeline-id .Op Fl o Ar owner Fl r Ar repo .Op Fl n Ar n .Nm .Fl j Ar job-id .Op Fl o Ar owner Fl r Ar repo .Ar actions... .Sh DESCRIPTION .Nm is used to display data about the Continuous Integration (CI) service of Gitlab. You can list pipelines of a given repository, list jobs in a given pipeline or perform actions such as restarting jobs or fetching their logs. .Sh OPTIONS .Bl -tag -width indent .It Fl o , -owner Ar owner Operate on the repository of the given owner. This option can only be used in combination with .Fl r . .It Fl r , -repo Ar repo Operate on the given repository. This option can only be used in combination with .Fl o . .It Fl n , -count Ar n Fetch at least .Ar n items. Setting .Ar n to -1 will fetch all items. Default: 30. Note that on large repositories fetching all items can take a considerable amount of time and may result in rate limiting by the GitLab API. .It Fl p , -pipeline Ar pipeline-id List jobs in the given pipeline. .It Fl j , -jobs Ar job execute the given .Ar actions for the specified .Ar job . .El .Sh ACTIONS .Ar actions... may be one or more of the following: .Bl -tag -width artifacts .It Cm log Dump the log of the job. .It Cm status Print a short summary of the job. .It Cm cancel Cancel the job. .It Cm retry Retry the job. .It Cm artifacts Op Fl o Ar outfile Download the artifacts archive as a zip to disk. The default output file is .Pa artifacts.zip but it can be overridden by using the .Fl o flag. .El .Sh EXAMPLES Print a list of the last 30 pipelines in the current project: .Bd -literal -offset indent $ gcli pipelines .Ed .Pp List all jobs of pipeline #3316: .Bd -literal -offset indent $ gcli pipelines -p3316 .Ed .Pp Dump the log of Job #423141 in herrhotzenplotz/gcli: .Bd -literal -offset indent $ gcli pipelines -o herrhotzenplotz -r gcli -j 423141 log .Ed .Sh SEE ALSO .Xr git 1 , .Xr gcli 1 .Sh AUTHORS .An Nico Sonack aka. herrhotzenplotz Aq Mt nsonack@herrhotzenplotz.de and contributors. .Sh BUGS We are missing a .Fl a flag. This is the current implied behaviour. .Pp Please report bugs via E-Mail to .Mt @PACKAGE_BUGREPORT@ . .Pp Alternatively you can report them on any of the forges linked at .Lk @PACKAGE_URL@ . However, the preferred and quickest method is to use the mailing list. gcli-2.3.0/docs/gcli-pulls.1.in000066400000000000000000000162011460062271200161240ustar00rootroot00000000000000.Dd @PACKAGE_DATE@ .Dt GCLI-PULLS 1 .Os @PACKAGE_STRING@ .Sh NAME .Nm gcli pulls .Nd Manage Pull Requests on Git Forges .Sh SYNOPSIS .Nm .Op Fl a .Op Fl A Ar author .Op Fl L Ar label .Op Fl M Ar milestone .Op Fl s .Op Fl n Ar n .Op Fl o Ar owner Fl r Ar repo .Op Ar search-terms... .Nm .Fl i Ar pr .Op Fl o Ar owner Fl r Ar repo .Ar actions... .Nm .Cm create .Op Fl o Ar owner Fl r Ar repo .Op Fl t Ar branch .Op Fl f Ar owner:branch .Op Fl y .Op Ar "PR title..." .Sh DESCRIPTION Use .Nm to list, create, edit or delete Pull Requests (PRs) in repositories on various .Xr git 1 forges such as GitLab, Gitea or GitHub. Without any action specified, .Nm will list open PRs in the given or autodetected repository. .Sh OPTIONS .Bl -tag -width indent .It Fl s , -sorted Reverse the output such that most recent items appear at the bottom. .It Fl o , -owner Ar owner List PRs in the repository of the given owner. This option can only be used in combination with .Fl r . .It Fl r , -repo Ar repo List PRs in the given repository. This option can only be used in combination with .Fl o . .It Fl A , -author Ar author Filter pull requests by the given author. .Pp Note that the implementation is somewhat limited on GitHub and Gitea since the respective API does not allow off-loading the filtering to its side. Due to this fact using this option may take an increased amount of time because .Nm needs to iterate all the fetched data and filter out the requested information. .It Fl L , -label Ar label Filter pull requests by the given label. See the notes about the .Fl A option above - the same reasoning applies to this option. .It Fl M , -milestone Ar milestone Filter pull requests by the given milestone. See the notes about the .Fl A option above - the same reasoning applies to this option. .It Fl a List all PRs, including closed and merged ones. Cannot be combined with actions. This does not affect the .Fl n 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 pull requests. Default: 30. If .Ar n is set to -1 this will fetch all pull requests. Note that on large repositories fetching all pull requests can take a considerable amount of time and may result in rate limiting by the respective API. .It Fl i , -id Ar PR execute the given .Ar actions on the specified .Ar PR . .El .Pp .Sh SUBCOMMANDS .Bl -tag -width create .It Cm create Create a new PR in the given or autodetected repository. The editor will come up and ask you to enter the PR message. .Pp When the title is omitted gcli will interactively prompt the various options listed below, including the title. .Pp The following flags can be specified: .Bl -tag -width indent .It Fl o , -owner Ar owner Specify the owner of the repository where the PR is to be created. .It Fl r , -repo Ar repository Specify the name of the repository where the PR is to be created. .It Fl t , -to Ar branch The target (base) branch of the PR. This is the branch the commits are to be merged into. You may omit this flag if you have set pr.base in your .gcli config file. .It Fl f , -from Ar owner:branch The source (head) branch of the PR. This is the branch that contains 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 .El .Sh ACTIONS .Ar actions... may be one or more of the following: .Bl -tag -width comments .It Cm all Get all the relevant information about a PR. The following actions are implied: .Cm status , .Cm op , .Cm commits and .Cm ci . .It Cm commits Print the list of commits associated with the Pull Requests. .It Cm comments Print a list of comments under the PR. .It Cm status Print metadata of the commit such as the ID, head and base branch etc. .It Cm op Print the original post of the Pull Request. .It Cm ci Print a list of checks that ran on the PR (GitLab Pipelines and GitHub CI). .It Cm diff Print a diff of the changes attached to the PR. This can be piped into .Xr patch 1 or .Xr git-apply 1 . .It Cm close Close the PR. .It Cm reopen Reopen a closed PR. .It Cm merge Op Ar options Merge the PR. The source branch is deleted by default unless you set the .Dq pr.inhibit-delete-source-branch option to yes in your .Pa .gcli file. You may supply the following options: .Bl -tag -width indent .It Fl -squash , s Squash the commits before merging. .It Fl -inhibit-delete , D Delete the source branch after merging. .El .It Cm milestone Ar milestone-id Assign the pull request to the given .Ar milestone-id . .It Cm milestone Fl d Clear a set milestone on the pull request. .It Cm notes Alias for the .Cm comments action that prints a list of comments associated with the PR. .It Cm labels Op Ar options The following options can be specified more than once: .Bl -tag -width indent .It add Ar label Add the given label to the pull request. .It remove Ar label Remove the given label from the pull request. .El .It Cm title Ar new-title Change the title of the pull request to .Ar new-title . .El .Sh EXAMPLES Print a list of open PRs in the current project: .Bd -literal -offset indent $ gcli pulls .Ed .Pp Create a new PR and let gcli interactively prompt you for details: .Bd -literal -offset indent $ gcli pr create .Ed .Pp Create a new PR in the current Project, the head is the currently checked out branch of git. See .Xr git-status 1 The base will be what pr.base in .gcli is set to. .Bd -literal -offset indent $ gcli pulls create "summary here" .Ed .Pp Print both a summary and comments of PR 11 in herrhotzenplotz/gcli: .Bd -literal -offset indent $ gcli pulls -o herrhotzenplotz -r gcli -i 11 all comments .Ed .Pp Merge PR 42 in the upstream repository: .Bd -literal -offset indent $ gcli pulls -i 42 merge .Ed .Pp Note that you could also pull the PR head and merge it manually into the base branch. Assuming trunk is the base branch: .Bd -literal -offset indent $ git fetch upstream pull/42/head:42-review $ git checkout 42-review $ $ git checkout trunk $ git merge --no-ff 42-review .Ed .Pp List pull requests that have the .Dq bug label: .Bd -literal -offset indent $ gcli pulls -L bug .Ed .Pp List pull requests that are associated with the milestone .Dq version420 : .Bd -literal -offset indent $ gcli pulls -M version420 .Ed .Pp Change the title of pull request #42 on Github to .Dq "This is the new title" : .Bd -literal -offset indent $ gcli -t github pulls -i 42 title "This is the new title" .Ed .Pp Same command as above, but with abbreviated pulls subcommand: .Bd -literal -offset indent $ gcli -t github pu -i 42 title "This is the new title" .Ed .Sh SEE ALSO .Xr git 1 , .Xr git-merge 1 , .Xr git-branch 1 , .Xr gcli 1 , .Xr patch 1 .Sh AUTHORS .An Nico Sonack aka. herrhotzenplotz Aq Mt nsonack@herrhotzenplotz.de and contributors. .Sh BUGS Please report bugs via E-Mail to .Mt @PACKAGE_BUGREPORT@ . .Pp Alternatively you can report them on any of the forges linked at .Lk @PACKAGE_URL@ . However, the preferred and quickest method is to use the mailing list. gcli-2.3.0/docs/gcli-releases.1.in000066400000000000000000000111501460062271200165660ustar00rootroot00000000000000.Dd @PACKAGE_DATE@ .Dt GCLI-RELEASES 1 .Os @PACKAGE_STRING@ .Sh NAME .Nm gcli releases .Nd manage releases on git forges .Sh SYNOPSIS .Nm .Op Fl sl .Op Fl n Ar n .Op Fl o Ar owner Fl r Ar repo .Nm .Cm create .Fl t Ar tagname .Op Fl n Ar name .Op Fl c Ar commitish .Op Fl a Pa asset .Op Fl o Ar owner Fl r Ar repo .Op Fl d .Op Fl p .Nm .Cm delete .Op Fl o Ar owner Fl r Ar repo .Op Fl y .Ar release-id .Sh DESCRIPTION Use .Nm to list, create or delete releases for repositories on .Xr git 1 forges such as GitLab, Gitea or GitHub. Without a subcommand specified, .Nm will list releases in the given or autodetected repository. If you are the owner of that repo, you will also see draft releases. You will not see those if you are not the owner of that particular repository. .Sh OPTIONS .Bl -tag -width indent .It Fl s , -sorted Reverse the output such that most recent items appear at the bottom. .It Fl l , -long Print a long list instead of a short table. .It Fl o , -owner Ar owner List releases in the repo of the given owner. This option can only be used in combination with .Fl r . .It Fl r , -repo Ar repo List releases in the given repo. This option can only be used in combination with .Fl o . .It Fl n , -count Ar n Fetch at least .Ar n releases. Setting .Ar n to -1 will fetch all releases. Default: 30. Note that on repositories with many releases fetching all releases can take a considerable amount of time and may result in rate limiting by the GitHub API. .El .Sh SUBCOMMANDS .Bl -tag -width indent .It Cm create Create a new release on the given or autodetected repository. The editor will come up and ask you to enter a message for the release. .Pp The following flags can be specified: .Pp .Bl -tag -width indent .It Fl t , -tag Ar tagname Specify a tag to be used or to be created for the release. This option is mandatory. See .Fl c for how to specify from what the tag should be created. .It Fl n , -name Ar name Name of the release. .It Fl c , -commitish Ar commitish When a new tag is to be created, this specifies what the tag is based on. It can be either a branch or a commit hash. Unused if the tag already exists. Otherwise this defaults to the default branch from .Xr git 1 . .It Fl o , -owner Ar owner Operate on the repository of the specified owner. This option can only be used in combination with .Fl r . Use this if you want to e.g. create the release in an organization and not your own account. .It Fl r , -repo Ar repo Create the release in the given repository. This option can only be used in combination with .Fl o . .It Fl a , -asset Pa asset Attach the given asset to the release. It will be uploaded to Github and be made available for download. You can specify this option multiple times to attach more than one asset to the release. .It Fl y , -yes Do not ask for confirmation before creating the release. Assume yes. .El .It Cm delete Delete a release. .Pp The following options can be specified: .Bl -tag -width indent .It Fl r , -repo Ar repo Delete the release in the given repository. This option can only be used in combination with .Fl o . .It Fl o , -owner Ar owner Delete the release in the repository of the given owner. This option can only be used in combination with .Fl r . Use this if you want to delete a release in a given organization and not your own account. .It Fl y , -yes Do not ask for confirmation before deleting the repository. Assume yes. .El .El .Sh EXAMPLES Delete release with ID 54656866 in herrhotzenplotz/gcli-playground without asking for confirmation: .Pp .Bd -literal -offset indent $ gcli releases delete --owner herrhotzenplotz \\ --repo gcli-playground --yes 54656866 .Ed .Pp Create a new release named Foobar in herrhotzenplotz/gcli-playground. Create a new tag called banana based on the commit with the hash 0fed3c9 and upload .Pa foobar.tar.xz , barfoo.tar.gz and .Pa CHANGELOG as assets to the release. .Pp .Bd -literal -offset indent $ gcli releases create --owner herrhotzenplotz \\ --repo gcli-playground --tag banana --name Foobar \\ --commitish 0fed3c9 --asset foobar.tar.xz \\ --asset barfoo.tar.gz --asset CHANGELOG .Ed .Sh SEE ALSO .Xr git 1 , .Xr gcli 1 .Sh AUTHORS .An Nico Sonack aka. herrhotzenplotz Aq Mt nsonack@herrhotzenplotz.de and contributors. .Sh BUGS Currently uploading release assets to GitLab doesn't work. Prereleases and draft releases are unsupported by GitLab. Using those flags in a GitLab forge type remote will produce warnings but still create the release. .Pp Please report bugs via E-Mail to .Mt @PACKAGE_BUGREPORT@ . .Pp Alternatively you can report them on any of the forges linked at .Lk @PACKAGE_URL@ . However, the preferred and quickest method is to use the mailing list. gcli-2.3.0/docs/gcli-repos.1.in000066400000000000000000000056731460062271200161300ustar00rootroot00000000000000.Dd @PACKAGE_DATE@ .Dt GCLI-REPOS 1 .Os @PACKAGE_STRING@ .Sh NAME .Nm gcli repos .Nd Manage remote repositories on various git forges .Sh SYNOPSIS .Nm .Op Fl s .Op Fl n Ar n .Op Fl o Ar owner .Nm .Cm create .Fl r Ar name .Op Fl d Ar description .Op Fl p .Nm .Op Fl o Ar owner Fl r Ar repo .Ar actions... .Sh DESCRIPTION .Nm can be used to list or manage your own or an organization's repositories on .Xr git 1 forges such as Github, Gitea and GitLab. With no actions given, .Nm will list repositories, either of the through .Fl o specified owner or, if omitted, your own. Otherwise the given actions are executed on the specified or autodetected repository. See .Sx ACTIONS . .Sh OPTIONS .Bl -tag -width indent .It Fl s , -sorted Reverse the output such that most recent items appear at the bottom. .It Fl o , -owner Ar owner Operate on the repository of the given owner. This option can only be used in combination with .Fl r . .It Fl r , -repo Ar repo Operate on the given repository. This option can only be used in combination with .Fl o unless you are creating a repository. .It Fl y , -yes Do not ask for confirmation. Assume yes. Applies only to the .Cm delete action. .It Fl n , -count Ar n Fetch at least .Ar n repositories. Setting .Ar n to -1 will fetch all repositories. Default: 30. Note that on owners with many repositories fetching all of them can take a considerable amount of time and may result in rate limiting by the GitHub/GitLab API. .It Fl d , -description Ar description Set the description of a repo to be created. .It Fl p , -private Create a private repo. .El .Sh ACTIONS .Ar actions... may be one or more of the following: .Bl -tag -width "set-visibility level" .It Cm delete Op Fl y Delete the repository. You will be asked for confirmation unless you set .Fl y . .It Cm set-visibility Ar level Change the visibility level of the repository. .Ar level may be one of: .Bl -tag -width "private" .It private Make the repository private. .It public Make the repository public. .El .El .Sh EXAMPLES List your own repos: .Bd -literal -offset indent $ gcli repos .Ed .Pp List neutaaaaan's repositories: .Bd -literal -offset indent $ gcli repos -o neutaaaaan .Ed .Pp Delete vim/vim without confirmation: .Bd -literal -offset indent $ gcli repos -o vim -r vim -y delete .Ed .Pp Create a repository called emacs with a description and make it public: .Bd -literal -offset indent $ gcli repos create -r emacs -d "welcome to the holy church of emacs." .Ed .Sh SEE ALSO .Xr git 1 , .Xr gcli 1 , .Xr emacs 1 .Sh AUTHORS .An Nico Sonack aka. herrhotzenplotz Aq Mt nsonack@herrhotzenplotz.de and contributors. .Sh BUGS Currently it is only possible to create repositories for authenticated users thus it is impossible to create a repository in another organization. .Pp Please report bugs via E-Mail to .Mt @PACKAGE_BUGREPORT@ . .Pp Alternatively you can report them on any of the forges linked at .Lk @PACKAGE_URL@ . However, the preferred and quickest method is to use the mailing list. gcli-2.3.0/docs/gcli-snippets.1.in000066400000000000000000000035571460062271200166440ustar00rootroot00000000000000.Dd @PACKAGE_DATE@ .Dt GCLI-SNIPPETS 1 .Os @PACKAGE_STRING@ .Sh NAME .Nm gcli snippets .Nd manage Gitlab snippets .Sh SYNOPSIS .Nm .Op Fl l .Op Fl s .Op Fl n Ar n .Nm .Cm delete .Ar snippet-id .Nm .Cm get .Ar snippet-id .Sh DESCRIPTION Use .Nm to list, create, download or delete GitLab snippets. Without a subcommand specified, .Nm will list all of your own snippets. .Sh OPTIONS .Bl -tag -width indent .It Fl l , -long Print a long list instead of a short table. .It Fl s , -sorted Reverse the output such that most recent items appear at the bottom. .It Fl n , -count Ar n Fetch at least .Ar n snippets. Setting .Ar n to -1 will fetch all snippets. Default: 30. Note that on users with many snippets fetching all snippets can take a considerable amount of time and may result in rate limiting by the GitLab API. .El .Sh SUBCOMMANDS .Bl -tag -width indent .It Cm delete Delete a snippet. .It Cm get Fetch the raw contents of the snippet. .El .Sh EXAMPLES List all of your snippets: .Bd -literal -offset indent $ gcli -t gitlab snippets .Ed .Pp Delete snippet with id 69420: .Bd -literal -offset indent $ gcli -t gitlab snippets delete 69420 .Ed .Pp Print snippet with id 69420 into your pager: .Bd -literal -offset indent $ gcli -t gitlab snippets get 69420 | $PAGER .Ed .Sh SEE ALSO .Xr git 1 , .Xr gcli 1 .Sh AUTHORS .An Nico Sonack aka. herrhotzenplotz Aq Mt nsonack@herrhotzenplotz.de and contributors. .Sh BUGS .Bl -dash .It This subcommand only works on Gitlab. It is not implemented for GitHub, as GitHub Gists work differently. .It Creating snippets is currently unimplemented. .It There is no .Fl y flag to ask the user whether he is sure about deleting a snippet. .El .Pp Please report bugs via E-Mail to .Mt @PACKAGE_BUGREPORT@ . .Pp Alternatively you can report them on any of the forges linked at .Lk @PACKAGE_URL@ . However, the preferred and quickest method is to use the mailing list. gcli-2.3.0/docs/gcli-status.1.in000066400000000000000000000017011460062271200163070ustar00rootroot00000000000000.Dd @PACKAGE_DATE@ .Dt GCLI-STATUS 1 .Os @PACKAGE_STRING@ .Sh NAME .Nm gcli status .Nd Print a list of notifications and/or TODOs .Sh SYNOPSIS .Nm .Op Fl n Ar number-of-items .Sh DESCRIPTION .Nm prints a list of TODOs and notifications on the given account. .Sh OPTIONS .Bl -tag -width indent .It Fl n Ar number-of-items Fetch at most .Ar number-of-items items and print them. If given a negative number, all notifications are fetched. The default is 30. .El .Sh EXAMPLES Print a TODO list for my-account: .Bd -literal -offset indent $ gcli -a my-account status .Ed .Pp .Sh SEE ALSO .Xr gcli 1 , .Xr gcli-issues 1 , .Xr gcli-pulls 1 .Sh AUTHORS .An Nico Sonack aka. herrhotzenplotz Aq Mt nsonack@herrhotzenplotz.de and contributors. .Sh BUGS Please report bugs via E-Mail to .Mt @PACKAGE_BUGREPORT@ . .Pp Alternatively you can report them on any of the forges linked at .Lk @PACKAGE_URL@ . However, the preferred and quickest method is to use the mailing list. gcli-2.3.0/docs/gcli.1.in000066400000000000000000000177641460062271200150060ustar00rootroot00000000000000.Dd @PACKAGE_DATE@ .Dt GCLI 1 .Os @PACKAGE_STRING@ .Sh NAME .Nm gcli .Nd interact with git forges without using a web-browser .Sh SYNOPSIS .Pp .Nm .Op overrides .Cm subcommand .Op options .Sh DESCRIPTION .Nm can be used to interact with .Xr git 1 forges like GitHub, GitLab and Gitea from the command line in order to make many tasks like managing issues and pull requests easier. .Pp Calls to .Nm usually consist of either only the subcommand to list requested data or the subcommand plus further subcommands or options to perform various tasks. Some commands may also take an item to operate on and accept multiple actions that will be performed on the item (e.g. PRs may be summarized, comments fetched and a diff printed all in one command). .Pp The default behaviour of .Nm can be overridden to accommodate more nuanced use cases. Manual overrides must be passed before subcommands and their options. .Sh SUBCOMMANDS Most of these subcommands are documented in dedicated man pages. .Bl -tag -width milestones .It Cm issues Issues in repositories. See .Xr gcli-issues 1 . .It Cm pulls Pull Requests on repositories. See .Xr gcli-pulls 1 . .It Cm labels Manage labels for issues and pull/merge requests on repositories. See .Xr gcli-labels 1 . .It Cm forks Forking repositories. See .Xr gcli-forks 1 . .It Cm gists Github Gists are like paste bins to where you can dump code snippets etc. See .Xr gcli-gists 1 . .It Cm snippets Support for Gitlab snippets. See .Xr gcli-snippets 1 . .It Cm repos Manage your own or other repositories. See .Xr gcli-repos 1 . .It Cm comment Submit comments under issues and PRs. See .Xr gcli-comment 1 . .It Cm status Print a list of TODOs and/or notifications. See .Xr gcli-status 1 . .It Cm pipelines Inspect and manage Gitlab Pipelines. See .Xr gcli-pipelines 1 . .It Cm releases Create and manage releases. See .Xr gcli-releases 1 . .It Cm milestones List and manage milestones. See .Xr gcli-milestones 1 . .It Cm config Change user settings for the forge. Allows you to e.g. upload or delete ssh keys. See .Xr gcli-config 1 . .It Cm api Perform direct queries to the API and dump the JSON response to stdout. This is primarily intended to assist debugging gcli. See .Xr gcli-api 1 . .It Cm version Print version and exit. .El .Sh OPTIONS .Nm overrides are: .Bl -tag -width indent .It Fl a , -account Ar override-account Manually override the default account. .Ar override-account must name a config section for an account in the global config file. See .Sx FILES . .It Fl r , -remote Ar override-remote Use .Ar override-remote as the remote when trying to infer repository data. .It Fl c , -colours Ignore .Ev NO_COLOR as well as whether the output is not tty and print ANSI escape sequences for changing text formatting. Default is to output colours unless stdout is not a tty. See .Xr isatty 3 . This is useful in combination with modern pagers such as .Xr less 1 . .It Fl q , -quiet Suppresses most output of .Nm . .It Fl v , -verbose Be very verbose. This means that warnings about missing config files and request steps are printed to stderr. .It Fl t , -type Ar forge-type Forcefully override the forge type. Set .Ar forge-type to .Sq github , .Sq gitlab .Sq gitea , or .Sq bugzilla to connect to the corresponding services. .El .Pp Common options across almost all of the subcommands are: .Bl -tag -width indent .It Fl s , -sorted Reverse the output such that most recent items appear at the bottom. .It Fl n , -count Ar n Fetch multiple items of data. The default is usually 30 items, but this parameter allows to fetch more than that. Setting .Ar n to -1 will result in all pages being queried and all items being read. However, be careful with that, since if there is a lot of data to be fetched, it may result in rate limiting by the Github API, aside from the fact that it may also take a considerable amount of time to process. .It Fl a , -all Fetch all data, including closed issues and closed/merged PRs. .It Fl y , -yes Do not ask for confirmation when performing destructive operations or performing submissions. Always assume yes. .It Fl o , -owner Ar owner Operate on the given owner (organization or user). Can only be used in combination with .Fl r . .It Fl r , -repo Ar repo Operate on the given repository. Can only be used in combination with .Fl o . .It Fl i Ar id Operate on the given numeric identifier. .El .Pp Other options specific to the context are documented in the respective man pages. .Sh ENVIRONMENT .Bl -tag -width XDG_CONFIG_HOME .It Ev EDITOR If the gcli config file does not name an editor, .Nm may use this editor. .It Ev XDG_CONFIG_HOME There should be a subdirectory called gcli in the directory this environment variable points to where .Nm will go looking for its configuration file. See .Sx FILES . .It Ev GCLI_ACCOUNT Specifies an account name that should be used instead of an inferred one. The value of .Ev GCLI_ACCOUNT can be overridden again by using .Fl a Ar account-name . This is helpful in cases where you have multiple accounts of the same forge-type configured and you don't want to use the default. .It Ev NO_COLOR If set to .Sq 1 , .Sq y or .Sq yes (capitalization ignored) this will suppress output of ANSI colour escape sequences. See .Sx OPTIONS (--colours). .El .Sh FILES .Bl -tag -width ${XDG_CONFIG_HOME}/gcli/config -compact .It Pa ${XDG_CONFIG_HOME}/gcli/config The user configuration file for gcli. It contains account definitions as well as sensible default values. See .Xr gcli 5 . .Pp .It Pa .gcli A repo-specific config file intended to be committed into the repo so that users don't have to manually specify all the options like .Fl -in , .Fl -from , .Fl -base etc. when creating pull requests. See .Xr gcli 5 for details about this file. .Pp .El .Sh EXAMPLES List recently opened issues in the current upstream repository: .Bd -literal -offset indent $ gcli issues .Ed .Pp Merge upstream PR #22: .Bd -literal -offset indent $ gcli pulls -p 22 merge .Ed .Pp Get a summary and comments of upstream PR #22: .Bd -literal -offset indent $ gcli pulls -p 22 summary comments .Ed .Pp Establish a connection to github and print the last 10 pull requests in contour-terminal/contour regardless of their state. .Bd -literal -offset indent $ gcli -t github pulls -o contour-terminal -r contour -a -n10 .Ed .Pp This can be useful if neither your config file nor the directory you're working from contain the relevant forge and repository information. .Sh SEE ALSO .Xr git 1 , .Xr gcli-issues 1 , .Xr gcli-pulls 1 , .Xr gcli-labels 1 , .Xr gcli-comment 1 , .Xr gcli-review 1 , .Xr gcli-forks 1 , .Xr gcli-repos 1 , .Xr gcli-gists 1 , .Xr gcli-releases 1 , .Xr gcli-comment 1 .Xr gcli-pipelines 1 .Xr gcli-config 1 .Sh HISTORY The idea for .Nm appeared during a long rant on IRC where the issue with the official tool written by GitHub became clear to be the manual dialing and DNS resolving by the Go runtime, circumventing almost the entirety of the IP and DNS services of the operating system and leaking sensitive information when using Tor. .Pp Implementation started in October 2021 with the goal of having a decent, sufficiently portable and secure version of a cli utility to interact with the GitHub world without using the inconvenient web interface. .Pp Later, support for GitLab and Gitea (Codeberg) were added. .Sh AUTHORS .An Nico Sonack aka. herrhotzenplotz Aq Mt nsonack@herrhotzenplotz.de and contributors. .Sh CAVEATS Not all features that are available from the web version are available in .Nm . However, it is a non-goal of the project to provide all this functionality. .Sh BUGS There is an undocumented .Cm ci subcommand available for GitHub CI services. The subcommand is undocumented as it is not well tested and likely subject to changes. .Pp Please report bugs via E-Mail to .Mt @PACKAGE_BUGREPORT@ . .Pp Alternatively you can report them on any of the forges linked at .Lk @PACKAGE_URL@ . However, the preferred and quickest method is to use the mailing list. .Pp You may also report an issue like so: .Bd -literal -offset indent $ gcli -a some-gitlab-account \\ issues create \\ -o herrhotzenplotz -r gcli \\ "BUG : ..." .Ed gcli-2.3.0/docs/gcli.5.in000066400000000000000000000115641460062271200150020ustar00rootroot00000000000000.Dd @PACKAGE_DATE@ .Dt GCLI 5 .Os @PACKAGE_STRING@ .Sh NAME .Nm gcli .Nd gcli configuration file formats .Sh DESCRIPTION .Nm gcli has two different configuration files. A user configuration file that contains default values for .Nm gcli and a repository-local configuration that contains sensible default values for a given repository. The latter is meant to be checked into the repository and provide these default values to other users as well. .Ss User Configuration File The user configuration file is located in .Pa ${XDG_CONFIG_HOME}/gcli/config . On most systems this equal to .Pa ${HOME}/.config/gcli/config . .Pp The user configuration file contains definitions for accounts as well as sensible default values for things like an editor. .Pp The file is structured in sections, each section has a name and consists of a collection of key-value pairs. E.g.: .Pp .Bd -literal -offset indent section-name { key1 = value 1 key2 = value 2 } .Ed .Pp There must be a section named .Dq defaults which may contain the following keys: .Bl -tag .It editor Path to a default editor. This might be overridden by the environment variable .Ev EDITOR . .It github-default-account Section name of a default GitHub account to use whenever the account is unspecified on the command line or in the environment. See .Ev GCLI_ACCOUNT in .Xr gcli 1 . .It gitlab-default-account Section name of a default GitLab account to use whenever the account is unspecified on the command line or in the environment. See .Ev GCLI_ACCOUNT in .Xr gcli 1 . .It gitea-default-account Section name of a default Gitea account to use whenever the account is unspecified on the command line or in the environment. See .Ev GCLI_ACCOUNT in .Xr gcli 1 . .El .Pp All other sections define accounts for forges. Each of these account definitions have the account name as their section name and may have one or more of the following keys defined: .Bl -tag -width forge-type .It forge-type The type of the forge. May be one of: .Bl -bullet -compact .It github .It gitlab .It gitea .El .It api-base (optional) Used to override the API base URL of the forge. This is useful for self-hosted instances. Depending on the .Dq forge-type the default values are: .Bl -column forge-type "default value" .It Em forge-type Ta Em "default value" .It github Ta Lk "https://api.github.com" .It gitlab Ta Lk "https://gitlab.com/api/v4" .It gitea Ta Lk "https://codeberg.org/api/v1" .El .It account (optional) The username used to authenticate at the API. .It token (optional) A generated application token to use with this account. TODO: Document for each forge how to generate these. .El .Ss Repository Local Configuration File For repository-local configuration you can use a special configuration file. It contains definitions for gcli that are specific to the repository. .Pp The Repository-local configuration file is located in the root directory of the repository and should be named .Pa .gcli . .Pp It contains a list of key-value pairs. Allowed keys are: .Bl -tag -width pr.upstream .It pr.base Name of a branch that the changes should be merged into by default. Usually this is one of .Em master , .Em main or .Em trunk . .It pr.upstream Name of the upstream repository to submit the pull request to by default. This is a pair of the format .Dq owner/repository . .It pr.inhibit-delete-source-branch If defined and set to .Dq yes this will prevent the pull request source branch to get deleted when merging a pull request by default. .It forge-type When hosting on multiple forges this can be set to a type that will be used as a default when other overrides are unspecified. For possible values see the equivalent definition in .Sx "User Configuration File" . .El .Sh EXAMPLES .Ss User Configuration File An example for the user configuration file consisting of both a Github and a Gitlab account: .Bd -literal defaults { editor=/path/to/ganoooo/emacs github-default-account=herrhotzenplotz-gh gitlab-default-account=herrhotzenplotz-gitlab } herrhotzenplotz-gh { account=herrhotzenplotz token=foobar apibase=https://api.github.com forge-type=github } herrhotzenplotz-gl { account=herrhotzenplotz token= apibase=https://gitlab.com/api/v4 forge-type=gitlab } .Ed .Pp Notice that this allows you to run gcli and force it to use a specific Gitlab account. E.g.: .Bd -literal $ gcli -a herrhotzenplotz-gl issues -a .Ed .Pp .Ss Repository-Local Configuration file The .Pa .gcli file for the gcli project itself looks like this: .Bd -literal pr.upstream=herrhotzenplotz/gcli pr.base=trunk pr.inhibit-delete-source-branch=yes .Ed .Sh SEE ALSO .Xr git 1 , .Xr gcli 1 .Sh AUTHORS .An Nico Sonack aka. herrhotzenplotz Aq Mt nsonack@herrhotzenplotz.de and contributors. .Sh BUGS Please report bugs via E-Mail to .Mt @PACKAGE_BUGREPORT@ . .Pp Alternatively you can report them on any of the forges linked at .Lk @PACKAGE_URL@ . However, the preferred and quickest method is to use the mailing list. gcli-2.3.0/docs/pgen.org000066400000000000000000000151421460062271200150270ustar00rootroot00000000000000# -*- org-confirm-babel-evaluate: nil; -*- #+TITLE: PGEN #+SUBTITLE: JSON Parser Generator for C #+AUTHOR: Nico Sonack #+EMAIL: nsonack@herrhotzenplotz.de #+OPTIONS: toc:nil * Motivation GCLI needs to traverse and parse lots of JSON data because of the nature of the web APIs it communicates with. Initially most of this JSON traversal code was written by hand. It contained lots of boilerplate that could be generated. A language was invented to describe these parsers and generate C Code and C headers from them. ** Example Suppose you have the following JSON object: #+begin_src javascript { "title": "foo", "things": [ { "id": 42, "name": "Alice" }, { "id": 69, "name": "Bob" } ] } #+end_src You want to parse this into a C struct like the following: #+begin_src C typedef struct thing { int id; const char *name; } thing; typedef struct thinglist { const char *title; struct thing *things; size_t things_size; } thinglist; #+end_src You can now use the following /PGEN/ code to describe the parser that reads in JSON and dumps out C structs: #+name: parser-def #+begin_src prog parser thing is object of thing with ("id" => id as int, "name" => name as string); parser thinglist is object of thinglist with ("title" => title as string, "things" => things as array of thing use parse_thing); #+end_src This will generate two functions with the following signatures: #+begin_src shell :exports results :results output ../pgen -th < id as int, "name" => name as string); parser thinglist is object of thinglist with ("title" => title as string, "things" => things as array of thing use parse_thing); EOF #+end_src #+RESULTS: : #ifndef : #define : : #include : void parse_thing(struct json_stream *, thing *); : void parse_thinglist(struct json_stream *, thinglist *); : : #endif /* */ Whenever you call into the generated parsers, make sure the pointers to your output variables point to zeroed memory. ** Simple Object Parser Use the following syntax to parse a simple object: #+name: jsonin #+begin_example javascript { "id": 42, "title": "test", "users": [ {"name":"user1"} ] } #+end_example #+begin_src C typedef struct user { char *name; } user; typedef struct thing { int id; char *title; user *users; size_t users_size; } thing; #+end_src #+name: parser_def_code #+begin_example prog parser user is object of user with ("name" => name as string); parser thing is object of thing with ("id" => id as int, "title" => title as string, "users" => users as array of user use parse_user); #+end_example Then you can parse the object like so: #+begin_src C :stdin so-testdata #include #include #include #include int main(int argc, char *argv[]) { struct json_stream input = {0}; thing the_thing = {0}; json_open_stream(&input, stdin); parse_thing(&input, &the_thing); json_close(&input); printf("id = %d\n", the_thing.id); printf("title = %s\n", the_thing.title); printf("users:\n"); for (size_t i = 0; i < the_thing.users_size; ++i) { printf(" - name = %s\n", the_thing.users[i].name); } return 0; } #+end_src #+RESULTS: ** Corner Cases Suppose you have the following JSON data: #+begin_src javascript { "foo": 420, "bar": { "id": 3.14, "deep": { "info": true } } } #+end_src Which you want to parse into the following struct: #+begin_src C typedef struct whatever { int foo; float id; int info; } whatever; #+end_src You will have to dig into the layers of objects but output the data into a flat struct. To do this, you can use the *continuation-style* parsers: #+begin_src prog parser whatever_deep is object of whatever with ("info" => info as bool); parser whatever_bar is object of whatever with ("deep" => use parse_whatever_deep, "id" => id as bool); parser whatever is object of whatever with ("foo" => foo as int, "bar" => use parse_whatever_bar); #+end_src As you can see, the parsers =whatever= and =whatever_bar= use other object parsers to continue parsing the into the same struct in a nested JSON object. The code above generates the following header: #+begin_src sh :exports results :results output ../pgen -th < info as bool); parser whatever_bar is object of whatever with ("deep" => use parse_whatever_deep, "id" => id as bool); parser whatever is object of whatever with ("foo" => foo as int, "bar" => use parse_whatever_bar); EOF #+end_src #+RESULTS: : #ifndef : #define : : #include : void parse_whatever_deep(struct json_stream *, whatever *); : void parse_whatever_bar(struct json_stream *, whatever *); : void parse_whatever(struct json_stream *, whatever *); : : #endif /* */ * Notes and experiments ** Github Checks #+name: jsondata #+begin_src sh :results output verbatim :exports both curl -4 -L "https://api.github.com/repos/quick-lint/quick-lint-js/commits/b4cc317fab45960888d708edb41c1ccbc4a4dd21/check-runs" \ | jq '.check_runs | .[].name' #+end_src #+RESULTS: jsondata #+begin_example "test npm package on Ubuntu with Yarn" "test npm package on Ubuntu with npm (--global)" "test npm package on Ubuntu with npm" "test npm package on macOS 12 with Yarn" "test npm package on macOS 12 with npm (--global)" "test npm package on macOS 12 with npm" "test npm package on macOS 11 with Yarn" "test npm package on macOS 11 with npm (--global)" "test npm package on macOS 11 with npm" "test npm package on Windows with Yarn" "test npm package on Windows with npm (--global)" "test npm package on Windows with npm" "npm package" "winget manifests" "test Chocolatey package" "MSIX installer" "test on Ubuntu 20.04 LTS Focal" "test on Ubuntu 18.04 LTS Bionic" "test on Fedora 36" "test on Fedora 35" "test on Debian 10 Buster" "test on Debian 11 Bullseye" "test on Arch Linux" "test on macOS 12" "test on macOS 11" "test on Windows" "Scoop package" "Chocolatey package" "test on Ubuntu 20.04 LTS Focal" "test on Ubuntu 18.04 LTS Bionic" #+end_example gcli-2.3.0/docs/screenshot.png000066400000000000000000005621641460062271200162630ustar00rootroot00000000000000PNG  IHDR 7c?eXIfII*  7(1 2iGIMP 2.10.342023:10:20 16:58:37iCCPICC profilex}=H@_SRfqP"ũVBЪ4iHR\ׂUg]\AIEJ_Rhq?{ܽfUѬ鶙I%\~U" cVR,cN_.Ʋ9Ղ8MAXeX֕= #}e4G" B:*FV OG\H.\(X@ $S^R$ 8Z >v |⟤7:Z.;\OdJ}Sk^o}>Y*}%^yw_woi%r XiTXtXML:com.adobe.xmp ybKGDZZZau pHYs+tIME :%6m~ IDATxunbanQQl@QEP%Uk[?vYX|ޯ*;w39ϜRĕ,.`ͺ࿿E/nB!BM r b{(J}mV v8XX 6?\H!B!Ŀ Eb ,o;T84 '$B!eZ6\,†e*%B!W vQB!B4M.e@nWB!BιxÆe#2,XU|P/~I²~įJ#B!QpQ|,JFkKa=6 L2VlB(FLd (wfΌ :4+%ba2l@*7xĕ*Ǯ\\ׯKB !ĿPP"MSyTP ӲvkY2. $4D7 :]'TZ,FrC,z}re_4-"p9y~gqlu$$35ԪRpZ4FJZ%&*׷3{-ڣi*gPx0LҡU]5?B?!<,nwbYfQ@зkS$eeǑPQ#дA5v=Ƥ0 0Lzjf-I*T,_9 ^:Y_ vaEec><݉p6)1d`k,|'bՆCbY%~T[x*#uHgv=oJ\l nZԫYʏl6^QwelvWJ,߬Oք!ğރxЩM]m׈3kgݓ9}6o;n溙&<1JJ3ntü}Эޤ ^q"e 2<>ƽ ET!Bp9m=nb.z5CH ''%0LQ!sd2zEUVof¬y?0o;3x}"TUc뮣{|\ۮv@  QC)Nd&EB?H|@/<~3K~Nms]}0|6 w3//bZg=B\ GO$]ӬKt, 6ɠe8&BBRBxu2(S6?RK6n?O1K,"ƒp턇b>?çek8" ˠt!up` prs5 ]3 i-2Q\n)EQkT+ɸW'oϖ}ɋ[̂ucl{eg+(E7 ^ O<؏`6m=/qi^ Tq{t+؉D>{;˄nƲ,^yg>xYjecyIHg?)B!~UUx*f}cCraqlBo/`۞c|%a7GSy_ҥ-MS0B!W+pzDmoD%@B!W))|x/o8͡+:a"$B!WU0\GuY=H6!- B!B t- ֽ{PBI,x&s6e2D!B)Q1%,f#4, O~>D{#4 >B!N4(JdT0+yXB!BEsZ1$B!W3,X®I !B!*|,X!+ѿeejoHja2 B! #LgU>d4DVoNϠ:I%!B!UQl HFugGYfw,"T.B!I޳F-4Cɘ|!B!$< w +Q;fR"B! Ȉ?8[\vQJB!BM2^qG%B!W?1 Z_X{|S @B!ci6axx8__B)q t!B!ğSXΛ$B!B\Um%dB!B\U6˃_}$fIb^uWsr<$fIs¹soB!BeW+SQ :Չ4R\uq1Gr[u/|ݟ_pE3c~~g+ɧK,Bg{we}wGҷeYB!oWUE* |(:؛|Z%,mlW`ŜE/+FNF&Ot4D5ݬq3ߧDs} GLgMQ|9g 1/G˨ hР<[g|aC,?|njHbMw%{fv'{d3!BsU| H55(^EidrhCϻsg l;Jvk򄗒} .Cu7sneF>_yejŕnǚ85iYd޴|>NЍzv-!f~zY98eCםAߛsCD`ߚu<k֮&牥 z!BG.X;w'UjE"h*e" ɷc̈́־v-׊>DY,?H ,[so .Em1^Ŝm^;:Qv"JRl8*ƕvukS 27+~f.7ѩ] ~jZdM`WXw8|dJ -6(vmjĐyP-OO3*j[to9JEyؒ΄Gx *ScH)B!b- #k"zgEX>bo.+ƾ9$\3}]N紩^H0KvT|ʐ/bHONbHmkǩ4 "$8~vgZ{\ C9ty5٩8@RlRgXCRǟAz>&4ܷdbg'3r4Y>Ѐ]Yر&a1ƤM?;v}/B! /59薵w]˔Ui}CUt9,nWL ð@Q-VϏ񰬂Ca` vM)ֺ Vapy+^R53{[ӡq%ׅ{Omc9GrMo#<t HG8} MM"˘{0sDZ@Q2 t4E,?>fK&B9(gNP!B&Ц-ÛDrP &(.tr|$}{s6{_@L/ӤJ87lfՍuqDDӹC=`ɜ.Ѹ=הN#).[İjK {Pa o[oڮ([ӱ=JnhlGR/FaAU(,_3Ժ6F"oLˑļ+J %YxkoZ1ߎ賛X iڥ;-ڟLL᝕ '~ kӘ1(vBҫ.V}˸OK,!B787 ci6axn<?ev%ro|d䛴A>*w{3 >r&]#2Q42 2XFx𓔣_ЅI!*̉]5:ЧkpCXh_G"43}.Nffh+6LOFn)8ml^OXAV6B i>BBЮ8~)N~S> B pR _5 rI+!B̩S([ޤ >pt[4M#$ɺ?4EEQ t)FDpR}򊢠 6_t,wȀbi6QaNTO'j_wCDZ;57w\t]51J!BCx;UPH?s93?JS+PY|NUobQl\ ˤ5߱:VF_}m!< D:1M EUPGtrطx1SלRu؊WX$[^ЍJçfç wqKé;'aظhv'^Dѣ\];?fH嵇>{i=L|>hڽ#VNcx@rJq3LONP$+g^Q潻pC*h,r1[BJg=2lqˬL+M)\,y/(nh*ܮc7-2h E[^"n(/d޴UV-ǠGҶZD^}j.BYQѕӫW[zuoLZr ;N`bkզ5UqxM<)NulA!TlX[pM*՘.[RB8UҡUy2RZfzU>ǑIbr&IYy *6nDa$dUoNK+ѝsSq;|>b&IYMȣj3-UtjX7"5DE')%|ji-?Lru/FI' E矃O/hR@NrsWUƛZУShк%Mg5MpIʶ26flI`ȘD&\=^z6*Ҽ]3yEIJE'BV>!U{cU4mSmՇP3jMEFqmZ22kcݿ$ΐ:^%5o$5ݍX]Qik4=$&gbE}(2IEWGӭF8^h,êD@vHH\ޚAmAFM~hgzU Txmc"4uOC.#Q JM IH ǯSas *Vx-mnTiߍwevѤ]}|$&go")EE:S3IYfв]mSHL$ߟMV[> OiҦ9QxW!_tgL2d4!_s&#=j='& qd_<0(coఇsmv -]j_29v:[Zpx\$]޸A-b8v:0Qqj*R0ͦ`jEX|17kbѻ|AT8\5MEU Vpep߶ ӊ:\gš5vhc֬`rc߶ єR![tTB"WnO\7IOngnꖍ 3o[vi@| +ӡ=8CR-m"*ӷd㰫T7z.))$:^2=#p' ud@߶ hWp9zVaKŞ[Yn躝Ko]Eàl8gˉKsD"*NlB"%OS;8];]^098g4'HNqҠ hjQԢ)CCK\ZӨ Usxj"4gwеZgS94B wZQUbS OLTyTBZ ۖ(]Bʯ–;syv#, Nru6ٳ8JWz (LY*:?:XGj|_\.52r <aaB֫<%HfPLB7ldnR$t4-ޱC< *}EX%Aq8U²B2& ڿWL`(C:Ɛ?G6?x IDAT 54~y.v)6,&?6 .n}kE |}-;:mbC/Ӯ\aCyr#0 }0}ٱ<Ř71Wi 3eྴ1-HԾl=;B@ԙH9' N LˁmWo~ 3o/:s'}IG^}ۻeRҳ[`sqipyu;l<;j<\zϜ1.gёrbgPNl'*cSmq{|)vE!eC/[Qӷx*u~k|/AJP\6VgZT:5lI'߼!Gt`2(jSzqhS\ѭ$&_A+ gO/ۈA*r(x}lT?6qUEIat;FqFJ`R4 ĭ#n*2yV|J4@XJ:nBR 94b1WٶaC4F@qSF%X Hp$9_b\w,EcS"~IZwnSǿk|z>-;N!#@BQM{;]Nޓ1%TƶN%]]Sewj-}ۡDsvC )m@{<(ͻ vmϸ=7Qؓ⿂5#=4-SشNj ]/=ʠWC*l_4a%5"bm@z˶_ݪF%r5F )IeݬIzJ"0rPC?ow^q2ۍ$j `eJVփ,V.OAMҷ{gհ. Xo0o~(ĩ\KL0um;Ɖ_3|X&eF' t!({s2Βhz¯88]v@Tll۱{oqlJ k|ҏ4˞G١b\, 'v F:3P}}m +MEy%[h}43NX&VßU]aTj؜JrѲC ʝ<¢%2g 4sYՏ`iTY;CʣÏv-aB ~wi~ U{^oy`#'I̲:srH3pJNfn?-UFEٶ+\09 != 3Mgl[?P D"уZpP2O'?!)iN"S0# }i$qb@rD>[li&$+#|q.C\ռ`36U5AVz`Cjz..3٫; +~cvEWGu:G߅eV$5e$Hho$;(}Hz Ȣi`ᮂWRWyzx9!`𓞞_Ue$ңq=%-Ma7uy?fĔťo7bAK撖*)ƭ#CxvTw `^_Hn0] ٻbʏ}ٹ +pggtCn"o-ZG4Y=ucm7?#?&{禭Pu#<԰`5 뷒|*J}[ѡG 2fl?ZHƫ[76}so`n`k\ '==eG;t' p{@Zn{OrtN !wJ|<~p9TL>F~(8lZ6ċju 2/`𰆽`jHN 4Y*Š^x+dYuU-p!tj P ]1t6춢n).?K!*v}:, P1t~sE0~,X/4L| hZA~)RE% kIM74lg,2 ˮ/譥iBԂBuo_)v}~Rp8|sAæ e-M2 :^sJXY0o]&S`XrV88ix.\n+9i.UVLݏWWۥC![ xH!B!DB!B]$JԢ*Q!g dt%CpI麪lQ_;~@p|@PEV5"JDP2:2)E_qZo''aQVPJF/B!{b#T Nnx㕇2n0-JhXhf3O-1<%%_k2+4-;KcKmƒl9eRu_fMBwWxyj5-CneS1f=16<۪]Fsk`~5yۍޜ7m|; /ʤZrn]/M- nb o .{E_c^~鬒/.Rh>~w t;e,`V-zkeмk /aU rmd<) ьwF31|azWs\f4hׅWnoNWL asp&B/ͣߘ]Nurw5)5>u4;S ʻ45M\v=Ύ=iTkV?9; L6,1LxKoߟڟ}c[aZx~<~yAǏ~[eYx~zt<:IJwq ,1,,ò0L ˲p2, W/\GP٪\ӼN\Ys~S{ {Io xnWex<~^5o~עm&yX`E.8ݕLʷ{9Fnͧ:󇲨iPwj/A 1*y'ߣc^21,K(C+Ъe= OC 41 S?M&~}|ͧcby*(niO$YPM|Y߯˷ y*?F7ps9, |sL}^?,bi8Ɔ_ b2o&n\4>D0s2ؽ$Tg r²b~W,;WCɸKNK/0x~ݔ|!.񯨈nr>w#iOk a:g\bصxrod[~'7p:Mu4412TV,q&ʴi]B!?lc РN45@v:hV/q$  iR"/Ier7؃-) ;0b[D^GqfNa aԘ8dkI>-E5`Gݽ'݄͘x?FϿY)ߴɓ}*W⻏gIz=ޞ#>cSX7tVZr|bFMPa;݇J^Sb #^ݝKa),ZibD&R g y{|Wn}31V:9ks ۚYrcjl)f 73^yc<rsr~uϵ,hӧ?cnoG`~N ZԈDsDqX3 xԻy)ٗע=8F-508}?;pggCWϯU./s m*X"&L_KdaCՍYF7ۈ{xxn4|>4oLɟ2,U>K ̌ɝyDԯʲwb1dEޛ=%C̡ݬ:]uy;nm,ʞ3ycZ1};yݕBx~L,˶Уa$K?BU;HOHѓ+tjZ0=bf#qn]}i Cz'O:ō7~ErlUϑ\e])z7L0M%s0|vdXf,_%(6{7{r//L ^ސYTޗf؈!С&o:xi[)zXjc=Ƞn6vH͌8#/9>#v=k' ؒCVxfޛd/z)_,{͙7zH_ۓ IgxZǔfѳA{Vϸ7 ?A I$#oi±U_{-lʂcx׺d=f3{L,tWB- `d&XЦfit#\ E%?_Ō!%h.}~tGӮ_CH5n-L UМbMlzJ.:*jӦبqkҽ[gڝʚbcEk L7Ӵ"lYIN&~?4姴zFJ&?k:A!tk]|[5 mFO*:s>[MxLֈ _nYTԜtϤqFy'0^^CVi~>B mL/a#u|TYH#}pC5HN̥ykAwP#8=u#h>Z}?˧Dͼ$ 22nf'""@jd3snhOή/74B &"< @Dxh{L7뙱p[vwߟϐƾ8]\[A^y#>,ᡁ.xFJ5x.B Az%8xp0\AG`ʬY^2Ss؝áynL&}`|nj+)ۚF"K|ڟ[Q OtP\6L rx|+_O|K3xm>J*^z~+k(պ/ '$w@Hx7SvZ N*+Y΄W:ZIoelF}%!gs765Ύyq0O|n/gR(Y]i7pE]\?|\$'Ec*˗PLB gg:p`FeQ>6KQP]NU VpLvHa À*5-KQ25*%V}T6P1?̬[1* Ǔn|=goOq/lQRWi?kYqq6̖m{IM5hحY'v1sSN+_{{#w0 \4m 9={_Z@zE*\16xinyf6tiO򲳨4| <:2p:3Og ymhڬAkv~~L%Z+ִuV>،ij.Q}~L{7`QR%<, ;WĖ+OMNjqSZ*dc[^f㬗+xxDp'{yۺel`RmjQ KQҩ^$zq耛\Uk^ ؏5i\bެ|=Kl r/c-:J*Q1u1%|vN(TY|8K=Jn'%EӴZ8{u |HSN/ cX1qӷ/Z}bmflJ'ЖAa>\AG]λsBbK 5#P&:#I*ڶg2fѷ?ǝMNKbC2ƍyG>Re悕aܫ)VF}e!3&d6l#93Or<,-cë*?9^H$E:(MPQDTAP@REtPz{s~4Qߗ>OݳggvvfvwƖXx /hѱx} |(nHK䇭'XzOO4*Œ ̊{Fs!;b8"[vc$$pN-̬{(_;7sH,`XLLni8ޘ\"7=%t=+^A*&]ܖ|j/"Wغ?sh.Dx '0@ê2ocd~yׯ2gRZƭUV_z2h8J:V)n\3J_ KpXZngenEbI7K#`[*[j#"E1D^v>8H~'jD3gs28l_;PPy@6X)0);?Rrw'?'YvG(Ъo$+, z`^K QZEZ^L|~=EP `dQGtT`+,{Q%Pk=h>. yxkQ㷒72W$;;.H(ҐWy/?άoIWk OP,c^17Cq2dPQ#k5aɜQؗV5#*%^s |I皟JQ$$"WΥPF0䤓dUGɘmy-(%V :* hԎ*A@tUhΝPx 681jq卑0DV0e"D): FAU%cP@h~D/. 6 W!{u VSE )2*,bcA.񙦒"1t)eQS!nDZz{%4*[I߾"{W?NO3wO(QШշJIW<AAPd6;cnkױUQd]YQHEPl\OrT0v(uC=AP ́rigO'p.:?Qiϲݶ/a|ޔW9j͖{ IDATߺ%n2 Ɔdi֧9"C-(liEMSЗe(;#Ӻ[OI0p\E*sl&ភ/E\:)RWHJX Tv+PD@XyEcX9s(!{a YZZ789O0\dQTn#Zb%;K./^sw?K|-ĝFzޣ'ҨH[N"9ΐ~FT`u:7:Nãa{foÎEزiZP\0J$tϒ魡E"klzQ ;#3WY`!~̈1S \ :&\޽+~G3q2>x><;2u2bs폏ٹ}+.DuL]dDLLlk`ƭM#v۝eWxn@ol4bNKKyq`/ h폒w5n)dдŠٱ1ս\Af͈Уש фvn>vw$bޮ".n:S3zF<K[Kս >9&ac׭[F'. GHKR@n:f|5W0kU=D|N'r]Afs 7=w@Y|4D RptуvBk/SO3x@kBLMHy9P)|8nrD]:WR̈GOgG 'j%1LjwPd-"Tj jԠs:ë{V3V:ԍϵ@x/&;1vW6s%ZORW^}[`knRˆkH@5wpuׯ=5۴&R}H^/u5x'i INpLZj&&W/?j5y}Ѵ9`y3 pqnx͛ӫK}ѭ[ZVRo=ۤt=H۸/Ӭeub~>@N˚sWѧCM];,bdoEv rS֨oI؂"4 u#m >}-s7ͥl3 \Ljz.,W,-;-1aBY2~|oAF`*Gڈr 'p_/M2{tgQ|;Y*D,2MueQ,2-B57$Bj5`rl:# *a] fΒLjuY|~HN mՇ @b&cG}tAO3mQR0Iͱ ˲J˒O om{S_s6ާKS8V!HOtJzI1wKVyEQ/=x|=U;“}^dٰȒf<3&\:>YE7s)v9%7-\ܳAɬݹ#"K6NIY"`-m`k$ 'p '6NZɋ\N5TFΌD#ɤk/oE݉1id~Kq#.ldI-{jr!APSs" P2\$Cd+;uoS S1Kdv5iERFWs1f9ɦB;Fp͍cg 9j7Q#^sbgԍ$=&>IC Gl6eݩ cz&Gن]98;ؓû=+c\6eTc.bp1ß)ϖ߱䪣~*8u {[(Dc̻OnfLCǐu;O.Yf̱LowD vf:ΫyK7>~ JKg}mT֏Co7c開T#kW#D~ïgXk-5c/6+VIam?)4~UXm JhOdxĩ/ ؟n-=Ij14sd>m6R^M%`| #/?ꡈ!X|AYjZ1R;Y9@FUN oNV~E`6 {a> E=_vr 'p?rK6s9qu0llr eLe2y<ͬ_s(й{<3dO`ބޏt~ޫ6uRsͤLg;Zj2 W~\~;~"ԳѠ"ڭrBQQ(Si -I27Q ߥ!ިС OvmV^^ee4`<)NEZeX2eLa\2 F^|M:1{Qr=mSQhY&./^.N$טԭ ٩ql?Oh0Ws>[KQgB¢y 04hBO'R:{cdGo<"k1{ҋffSy2%;uaN\+ƔOn.ΤH)9$dH6mǬ1=b9wy *o/T9}Jߒ>~^4-'ۙ,k]#8CC@FO~Va~?ɫEl~Oc%7$~úkQ#X(0?^4,ŘSYLC).\b QLDϖ|h;᭺1+2v|xEe0D?O`h/*nO+Ƴvd33$l?b/I0![ħ}& V<'+!Q1q@|3ifBZvⳎ䧜"K]~յ(*o'6뱫Tx2crs<{uG-1AeYKnpꅇ&w8>siӾ˹8 N8Ŀ ߽G-#$.]^SA/fD~ 4y?}BBAȑ3"{UG3@$&I9By`) MC)9/pUAe__IPΈƊ8+"= si<ɳxbr%^0mg}#>; @ƎU +*PP5gNēIAjOĐhŬFbu]dùBtB=137RP%veޟ y e#PjN'H um]w3̘.bՉ̩טJΙyI#eGuᢲs%T]\/v;}χKO!*x;[mX//4*ʇc"ТU1s '+8|9?ao/Vq1J F*y  ?jECo#IFc̙ddI VVo3=>`9EQJ*jU(*;M[~dx6m=G4dD,L^8ÑiW1\|C1{wd\\«u|vd0U$[\]b&" KL=ZC q1.xQI7LQ~&:܊m f7x b2 ^u"f[+N8h^LZ|1i `EBi/JR9Y9W8dy} t#%GAA BGZ%H8o/ߑTj<FyKQ.)dtJRaV(h1MJWWTj"$YKj"MqQ2.gَ)%I7둌6-AE/ Ix l`UO<,c~~|r5~|j-eBd6m є`F7n&@$!. >xyZ0l B6oO"ԤUȒkoƌS1T,պ) g _OBxd94<Ν|㟚 \s|aGGp0&І%2`EU~p}" nE*A殆^l{ƚŧÿ I~.)`CR%q"¯ qEoW㥱m*mDʲE&/>LE rb)iЪ1xB^KVLZ0թD<1MΕ 'p‰VSY=ggEo.JP.L֍^xeSVIZ[j];cus93"nAZJfǜ)rD&:UTÅ*\dlv\<7L"WAbPbRƓ H4Q| ϼҗ dPyWOIĹ+EEQ-ժ'q=ݟBݛcѯ7d~.<0zo Hɷ{D}oW6@DrΝ I(ش|^{tcj6.}a^gI2DEGUa\i08JE̴f|4?|mز<w>>MZatTjWb'tGNַ| ~SK\ufON64.{"TTμѽ߱5^XVEDtD Z_T&j+J'h]܊R9,a@ynw%D[~}hmQ<#X! y{#xѺH-) ;FK.mt4M;нC Lѭ."z/qzQ9<$Gxs1Wԙ#(FUN8ĿQErфDһG+zumBrHF3Qٱ.#;|_2싽_^}sGS|RDuwpQ S*n)R(&2ǩXM:@扺A@$',qAź*\"%g:2SI.FH^vWS,9ٻӨ0Sd?flp:9Da71r+"jW4zWܔ|bqpU' ZnR1I9Rj(K&W&i’C31;qC'I CF-LYbJ$ x{厛V{Mbۙ  \aJ\"2rQWwD V~x,Μm))/OXé\r6a8/ l闎3txAPc?c IEvv+$]'G펷T\ٹqvzб?g7@T. iiQVѾX'~O''3J-й7A8nQ[<0՛N^!zQ$?-)DjL|=l*ҵk[$.ZAx*3 IDAT^: )ě +ׯ%ShQ;߮;R)U]l|DOIVЀ?pg/'pӚ#ջѩs?] jjMqV18[LۧSu))a., 6IΈ(=-4Z+TCF4͚_N8cL ka]5N8WPkҐɃ`9s@p 'p 'U&tAgl'^QN8N8ԨJ *V9 S+>FN8N8sƇZtI;5v )_wCEZ4E:OjԨHs4(,:!lWgtԩdoPDѴAE|o)+ԯ]e=Dԩdq Ope?CCZ>ʾZN+U]>X:E?O.ЦOo? }cq9Y7%+yP)[ѵ1*eV &c{SdwѺ~[3aKV7;Y2|HOj{Ztfu0,P=B*38j4kN@*>wpۭ )ޮO[>fꪲx9#9Ŀ@'^1CiQ7g|/=? 3٫;VS&|sؓ!^:#v̅Y|6َ,VEUt,ز"cR$D\DBIy*Vَ Q,*R\R$̊ (-v pЏzj2(ʭ! E&r%]T-&3V!^:G;EL~ D2VZ zWb,$W*DL$%p W )i&ʄ"],;"ʀ eR;3RlrE%"~ndˎbfͣ]4tkQ r""֮al^m^yk x},c/vG[bnbE?K+QSح!.*l&36(ܒ&3968܏lfRTx(PHO(VrmMF d\ɷQ_HdH1\-u+qYٴ3Ŋ2~z%;%Q3_.LRry u#S,II62棏ek:ߛo5k괄-Bv`w<4E9E|ъ&OЀUOvEOYo-Po@'+zB%4W0-u(& fY pCeÒBDPI C:p_T82z<7|+SGy L_ns(쒛JRB >t g[bþ|PS{|Q> hن/zCVtiؗ_)HF=$X.O<ƽRODT*Wґi^flVT;<8. ߗ{vv]*9CJ>hɱKYI;h&fO@=ɺ0(/7N5ӌdXFY`5^ZT m;sMo_kR,f=V`$^Yƅ>@ԓY+Hn~Әev.=WmDNgކd'a!xً_DKf g[Cٰk)IYԸ5w'l& lVѮ3גҁøUA?ąrBK-f:kK9]:UAV@E/:u -SxcD3|TMoAPQjUNYӕ]YGƓu#j.Rk|=KY5ʯ|ƅo1$n5]Ϗ㟥f0sNGkD[ra4fބ4"i /s92&>y-'}7/& ~[ZTId%Wȷ٩޲k8LQTI]o _N=_ϱ\y#c)UΌvq9i1Bu["|6kM1z`/]M#x;ʼs57'-hQU3Iu#@'UPzRΎøv~G4B9^>LY5ly- 'dX 9BvhՖAV  5?Ɨcg`Acl8WUxvOQA|V4=UZZKH4/āuat et+,tOEE.߆Z.V]b=="kaմn1u/N͎$5-U,٨]ۙ\(Dct"ⵕ'(6k^/1rbV|?2f{~s0*UEvx+:jGsQ@ҼM8vS]=ЊLٓ~8g d|<#rt?M 8A7_ӮA ^G'<ٱ2fx{Ԣvê2b Ը8J`zךy8%nXMQT MaR@B#i~ҕBzyuƌY9K4!&pio||wj0^MB𼼟.&Ė9xsWZq@dD0* l3]}#2!apx`U 0az|vdr dPZ$l8oyݲ* >׏2iw|R~>g(J]K58'삊}8jمyLk|+ڙ tDDZ j[Q3xs4f/@δ^~l WxqG1r*E+6$ѥGW|\t߆єa֛qw =]):MCPCK *KYTvÈɺ|q4mE՟N2t(tڟ 9i#P.Z6&%jB|#9B~fDrnLp#X->nFo`Ig+ԥV R),%F nWT)o~3,""WٗFBF5GXR,w޷RzjÆ}ɯ'2ѨEv/e|5i)W5V[RM2j +0gLbhdރ\HWPdOYq @є2Jg~/L} Ɨd>;s}4#:>D弓Ll5FL܎WL{]ҵ hŅ|=a&o[SKy 'p N*ouę@0+NRQF/\;U"ϩ Ua%GKvIb7bT>(v;U+PN+sG/@! +Mbu,QQMCB9b 9 . T @Z& EeW+\ıDGx*vVK2S^;Ƣ7]·㥕U,V˱VJѴZiNaE3*eݳbȷY7{v3~RK-h;ٹ6B_gddoYeViX>rk py &/?ѠTԬY=ҾA94:O+zϬ_'ZC|k|b.voc U?ʝv"Q-*k͛&-P e}" ,SzCc8T(N+l`) AU)bņؿg?c#ݗJZ.Φm{v:5[TEk!ocΓ̙-o9\O`t܄u\+(IT2 Xrx:$ˡc1\/`JaǏRzQ9v&Mœȡ1BR˱c1nЅXwl+2/ưd,.$-$\EW2ݺ4y 2 =%`Ke8tiE}q),KDu">Gj~66m+IjQV߶ iu$cx2 ,KVt‰!D "ud˜~kK^Aj+'1{[6gj:݀ f/:NI#۠CX 6\OC.d0)Jc)v}ŮJ8Vk ]l+B~2 v@-AJ#3T) oݛа̜+V)~M&[E}?Qy>,t)J 6ECP9O =qt^=**6^]!Kw;* dnW#T"wD l緋 [.ȴiӞ.XR+5UK`0?e'fޜi\Jӈdݘ՘2 캐lODz{+T''W$V\HXDYѕx~uO6YQphLV삃ʸ$EZEϧ*4xW.'$0)tԘ_W-ືҫV;(V;2߸ӥ("jZAyIWڑU"RWnG ipDf (:%lv$+{F+,^,> >bib z7Ƞ4"(ugor{qONw#"vACetz7>M$AtIB͆K8ݟƍ[i1i',$4릡](xdNo5 nj]$'.^7js26jҾSJoNk@` w)](4VTΕ8j>naí:,Dvo>3wE޼U"$w;~fl /OPPu;p<"Q/rOͦB|l|!r.13nw7Ⱥ?H͘KBfYAnDT jFNȢG~U]1>8{H+sQXY끷@fĖxܑv=#X/ON`L=G3VVu=k,%둫@ĉ1SJ!fB2pΛIĶ5 gvPƏ[YMI0ٙ6'uPts5<=Aڞ¢xIu⩗Htqct؛YKM^>9CzICO+/|w%{fˆ]EAݯ-j(5#=fʪ m4."ea\+uL!l ]C.;-[E\8=~IbLxn^0JNZ=y야['EuJ>"{,1#y< _<_@Cn~<0ƽ- ȯENzV/孡xfIH dv==@dc?c3z[\&so4卯 zȍ+sfcj^/D+慅;Hݟ1jBC<״fڃAlb)pτ;yOט9×~]/؇I`xQ K7Bnu$P¯[WmIEec9L5ȲD;F2;:dCV{-Vr(t42h8XZCE-\sN7N[ʌ !dvfrrWǵ fFFYvBǸhӡC3QJZvFDhI۷g>W?'e"XET'7^(NOEѼIهR ЪIErVݓݷ:q!(?֣ ^]y4$u~t3ZN_m4ecz!Xb<7{xŷ<-UX̙G9^h%uI|i$ h%=uRWHFZ5#JaxVL9wBMFnlr%omBbRxҾM"I88_Kh"/EPxnhVֆzU:'ϘmH}8̴iK[ xyٷ5ZNeиy"Q>TeDFL&4+:naT"1~?zj CXQnVNb="ӻC[6PB^⛝-g*hޢQ N&zęI m~;FLPЭ]sݎ.e@G .\G*QnB''wȶ!hNʆyqlQJnRyÿuw9OKr~ 5ѴSX/ܰ IDATڄ+jAb[_yW1(.XH|z=`8j$sia}vp6*-e8MR+O ƛdl>w"G< E-8Eˡ2{$ZɂG8ol8ȁ3iKV߽BF 4)rYgJWM$cWz&G'3e~ BʼnsF$UDSpb/]qq'#e牗6b] ~N`v8&,q6-_ۛDW5GC'RNJY\,v #c`&V3 Ѐ#tz=NZHiWD1/1t<^j=-ˆoӝw3WȶUgn?yvr'V?>ѵ 1qLؗ̍9~ 'Qog}]}fn0ްg>PY"YrƢ\p%,4s=P |k*wbt(zL|sb,"y_--+[{+?#q~yfy yЀ%tBW5,O`"BHt䢬& ƳxjI|ޝׇc6o(xf ,ZZQy2 $8yp7ni0>@\ۿZJAc ^π۟Ms.;Mj/LmfN.e{_??{rP9!рprPvQ-I*R}#B/_=ע* 4Z h/7/F) vKۀ4wXɫRE촑oT03s(==1[HWuy5OnCֻahR5[\Cr@]eSeYFot%7 2*Jt %_9҃-_ÂCVQjލZ%*ŵj=:oS<սBJ/%BDԢEZGXOCż7;EAqs?4QZ[K1 c%V z6 \Ok2ykCS^J J5^J^u͎,ۈf6~)k=ҟKW&&W䷲T/@5^3Pd¿|Z͏-t H{$rgH q0G=چR#^; h@!ڮ!_c;h̆=0^eçSN\ *|o>qY%B|2q3,Z/fΞu(s2j*shy=s'a߾8]SEWvוBű)5U#}},76?<։Bǵ=3"_XȌz2N [pzt?ы_%K5μ'cڒʸO`b쿍^%/5ժy MCȳ?Lg?ldGy>wt7 ͢iJM f+wSl8ЀC[ô~a~;Y,`Ø'sʟߘFXx)tJDCHoJrsHNH%Z,R#:īY щEr`ID%/?|s/ɿ,b2V_UdIdѓ1ט1+)u|]?ݒϩ$#A\\TXMe7,00q9l9e$Y?^lea2`(L}xE0k^ޛ9T:Zvݿ ҕ-paʩĔSb"mvL*/_r֏۬6L*m 3qt?2O{bH.'&c<3WGKx;E4 Շh?İo„>#y^q/`~J XeeP9kSX)xaAU8׍.~~YQZ߯rK\zzBFѦdTfLGLRL66+&c55kUZc^B㥥yJ"%%?N,RT\SDLF V Rx}U9CtE&soZц֌X:+Rk卑b2VP㐨.g¢R^p`2"SNuk _*ʭkc <}|TRa*T`yE 0m>z)Z(vRRX)#wUy-*ȒB3Lmsz]]fQOkkgOa2bh<hmZ3&c.'j-7VR\55X]~j*irQIpHzV{TfCr09d򬪼\<N[~ Jhgӈm`H:(,em\ɭ$$Aǐqj/|ۡeGij^?|L)WޙCy7lzm(zЪo"b`ԔG1次^ή;>$U\l{|ΐ49Z7QvZ\6%݅M^z}=M Tv V} !;m}E%;ޔ9qW}I9 Ht<')];Pg<~!.ܼ',@ġdgŒd_%DĪMSxi߼4@u=;̯8v:.7=7`<6)'R= BA󞷱eק\g]APs]lݾU/absjz}7;v~HA*r.桤WZ{SSmo;o&Y=d̊HvIi9߿$ϻ0r^/)Kt KEq6o9eQpCHKܓo3sX"J{se߱Xv90C7AƆ xvT4w'9/BgֲwM_ĜMP `c_.,w]WG.[3| k"JCL.%|1Ytsjw~L7xp@'-7}D^uBe@$]֒>}9WX/g?zlRH֏0-⣧f(IGak߬~GƂl MGiGF&Y[| +)cZPw9!,\ /=:Sq$if,_>`{crWbuԞK4 c"awQT)¿i[>Z2jBrlХSS$[Ee9G@ gq*bJb#zC3[TdhAV e[nE&pe8#dYY4V쯀q-Jz'uL9C7IgfX]N;z "O@1=0 f3y]>ߓ˟ڻIlæu)ã1D# :˒I2#;6B["Zx`Ĝ|Z2>Őw৩/KMi{SԚӆ)8ͷ{r41{Ǣ Y( Y |뎖S{o^fKfݗ9zStd*Gf\gAK"V5}pn^'d%x]o̘!Ik4t$z3{vPͤiskWVh9bzkh-˯wVSᢶŵ-E\@Mn--{drmf=y*A@A'mLTȜiopm*N߂B@|p-_B##AA@|j&F7N ۜo-b֢ +ס T} Yx[3yEZQP'PxjT j3Iv.ZQgEDh+l~$9niœm)?]EL:E gP$wJ^NRm@o{jw&b"w %I@?.Zӗ{6ߊ|p;rˆHm[s0?W LhJcwgs%:í+8{p//P3ٴ?޿T7!{iGri3ۗs|V$Uc+c"\$5K%Uok*D2T׏Uq ThnˠtDH6nϣ3C{.D-qLvvf[-Ndq]S38ngC U8 tѝq>N91݉Z2v8Ƿ#9C"m8@:iz GNP~3g`4ki=G>f4Y֊2:+Aw!G]$ўb"g+]N D'pO?h="Ԩ:DF* dOW;֒{9=YM9͊ >.$|(,rh~*jo;8-/ >LeCg4 #\An=y!Zs +2Յ|vqa4 o`Jr@Pk %]nwp-?.ht.Q ʟ+&HuYF\ e  :O$'yKr}(ёtQW+%!(4J*?4?j#]oW#IؐtHRT::>YXRSB+>Fwb?~L]](-.ϾB *3UPpb@Q!^&% 5Ƹs2:Sn. be=8k-WDq'tni(? u$j% e}|[ycJ7/ZGMVZJH IDAT ;$2(UWQ(/ WG}GQWWgx u:{h:^RS@@tPjJQrM&;îŸq-(qkI`wH9wxFG&a%γ\(pKG9dE,\sݳpaޜߩK3hL@ي^Z3=CШ15+(1<EDy!RB9fLcRSzj.ٜ9Zne`\7Dr :(<ϢZ^9vɎ.@dy'P%`#i`& b&8VSvē6"K hw!΋wXvor3w="*|}YPPzKd>r[/b΄h:^ye>(#ܵ#v>K*Mz|NVLC'J38m='Y,(O h^A.؉BJ-3Ӱ  p8jy81'`+.b_Ugp]_ӂhԫ+g¢Qk"<[ѷ9e*'dl{Hծt {Nxlu#-?_th5BE=rTi@ze}MtN wa~tmpק\i+w%֨6lqɆ^ѳeLxm&NһN:IMn>[0{ ݉S xĶՁqh:nF9*f%mXJ.>+M Y;wBwס ƠI`xEmp4k"C'O#\Z?[h cno?^ǀ{u${%^̻1+?Aк8,>oM/~x^֤ٙ\MPqt =~ jMN[zU+$Nt<'rH,bƒ#Yj#kXQ]ncHs7š֘W(ՆU+zw9sFą`ȒHbAm!6ʛֳ̛sf̹ua\ASfdBn:(Ӷ2BڶX}u<=1^@Ywm^0j+w{ b" a;B+PY<:no_D "L{( 3L$/z$]dwy4ׇԍyj7%t} 1xuPj~#YMM bY|>F7t ZHq'}DIŝšO㢒-O'@֕|wֆ(Bp>[0#F #[X`":NϜ廝9adE:OӯJX=w7?Maylٻ8e=NUq|.͠Z%+=$^NoݸNvc u'9vZJJ))JC ;5U uXj(TטnVRl+lenq٫0[!;+9^SJŠ,Jo5,4P(3A X-\Ԓ_ j_BNfUWC^Pdb]It1_բP(H? h4/ 29iQVI RwnO򧥀JbC=BJPJ2OT1MҢ\q i ;Z;Nz }mWO9NfFG\gpLIbN+7f熹8~VTg 159u $()LMM量pb+ל@a@p(1qTxGȶ,Xő(hư>Y>-y(bM3* tSPþ=0;öE(8ܧ? f<{364PYZDS+ISTQ[? ;I/"Tsڴ kVfyܢcU1 *fQ1QJXq+Xi R%rY|Tɦt 2.awsL]PSt8%78dAqxH>xcD=SA)^~e# l|E@0$KE'V= 9ccW0J}(>sRD'VOf{h8{0.=mڵ >\K`hzڶoN\Z5.czINvIĄW͇`Ona+Si*#Q= Ѿ Y]LZ`SԠ%)G`s#Ŏ4McT|cchl':IHSMFds?o Pyf% qaxi?F‡(M9fDOX-{wgK7K$)Pf}LJYvu(H=KdI\n +_pԛa0u9DzpqqB6{<ƃ8?[e/$IhEv %WS)q#mN?ϞJhᇢig9ZFMt}p\:\ E&1\9\8}S"IM|&ǍUķhErb ~ NsKoa9+Aʲuԝvh4@7'OrԁZ/rY"" \cED+Kel\vi ԁ!K >9TwTs?J%DDP(TUT{კ7sRӹm${sF]]=hAӧ8]Drpb1jɻp / @ ||Z&63ᡸ*rY0u.0D$0f@k| QQHJZbNZ ']A4.0Wд3~.  JN7<֨P*utht+uy?4BzйM,m0;#FA,_Fgcx呉,?lXdŢopaRoH J"5}(_>2%%JN6ؾ`O0{Dh@`DмYtjZ{ȳ[7 ԊICr]wCx0t`Wj߾ABowE&`-EAE " ^B !;e"gDˁbF2+%/ٖƁH"%DGǞ ԭ/!W_I`Ơ `@gc\X &\$<$pޟ&?[ڱ4gD6kŌ7Ӹ{g-2nюVqq` ^>Ͽ1'j'Ҍ %2}'/Qd>> 5/0MO" 8Fq&&߰NJ[AF=1Gy/ǰxeSDy:ٿ}'s՛ys"p`ޛ;IĞA(&I7j^aث۷%J ;7me'G?SdDZDzŽ;ykvBI-l*[U">fDZ)Ƈf gT&lz˷Cq]jmаx^-UlJJlxxpL&oꇄʒMKBQ}x}P;BtNQ[oο]#F6dDz}1s'爃M"~t/o-MO9f.Mf$]o2 e(CFTUdܒP#Bl҂gn{sܲ-lyOxxu D\ώ8Z&i]1H8aګG7=3sPcMVߗLycc^Sͺ+D6*džS5U+oLOi<@t^,R[qY9a*8WgJص ,ݖFhrT<ź VuD4(NSu'^NHL )w^6?n1JGs)a쏘g\sL)+mu`+/͹_ 3 ?+DKtR"|m)KzQj^OLc^};p0a$׏3~22}P32ޢjՈr۸n,d-`Z̻.އ nSQ^(7_I՜2>u PO*ˉ>y9~yk/oN|7y%*ONsο~Drzd/nC5oF/G5lPa&L#45rj>Z j ;+W6ӡ ^y[ 4  UELG0bދ>kh!!VP2וUd4v\4Jd}6];2s_T $%7i׵#eīxFEv!* BRTA>xzTo hՁ0 n-$){׫HK$ak7jbj :uj/W vXI3Ҿ[5^;'Ú4uWmtDR/͙Y)Zoj#@{&}0VS2mo {3+7йWW"%$D ?-.m$k!˕\'jK[vlI+빑*υ\mGيZ P5#yWr⛏ヂVڀHrU y4r{n9dy3Pʴ+ЧF(:L@ZfԬGxpSfjˏZpd qrHԫ\ndսQW"Р"@#_Hd%X53lAApppc4YSEHM^6%oT<ȻJK>ƠW_֖o9e0Q9o'o1v.YǙ"D֌Ӑƺ1\֨µ0\̩CW8/ +*B"Bp:z}e(CP_e-ugƤN\ݵysPT KfecGNKm{*aRL32q{cRATR;. ÎԔ >‹Vd@EDAġNぬP"L?ffZLe;Z48kH5'c)xe =E޿D$P,DIK:qfsGs98]j[.g8s>%QV]6GaNjJGD$QαBkmNk'ZA4YAݎAd^7$n }Ǐgӡ|ʛlPb5EalE.΂ꥹ'NzZEpD*GtDQB!jmX(ϣDDQS.2C@q8\T*$!8v^\0+ 8K 3-!&?J苈wt%o:zQ!.Zol@OO,.^^v2tB?y,VL偻1\;|1!UX ِīܐѹE~~V@;9da[Tw 5޵Yn&^( 26 j9Ō^ŝV2Thrqx.) (I2w2 IQSlLd'=Ǝ#=:br̸ܔ1j4I$v'YQ\(Hs{*3Y$РEPJr'MF~d(`Eث]Vɧ e(COP@ rl Ş՘;5_%V1T Ӭm3ȽARʽF0 7Zk+*iy?i421ɱoܒ>t\:p8H:U?FtUؓZ6s"Đm(c IDATC?Ũs)iYϩH1YҹiL!}D>9ZԩQr>Ņ!Hb3;l ~OT"x gE'C^B0ڑv!XE_tkڊ!$N'UP|Tr .[Š<5z: V/Kdp\< fz6%*΢*#ւw>_ Qkw؏iCޒX)h$*N׷o,AC'3O_A/@J6:'X_)=( 9JBed͎Hn3g,L=]Ga!ӾX[EyL Yb{=Y3U7¾oqG'Us~y պ'MQRىEӴŇ]$F77%n904C`_o8^%%.٩-5K'OnK\pR#!h+ }:4ipo.k#?MdYTx*l~#})} 9sQQ"83coF؃`: _A2;OX[W3\H"oYzl,56Vk{{zDٓ'Q\ e(C2_DHچE&1.S(g}C[QR(>*.=ČٛȕxhtdP`Jlj y\.KKVQLNH,++jr 1+DN@vvGq|j1 H[(LƁg"۹ayǂJ!Fm͕5ْ`ERq|,2TA[ gZ)_{޻w6[ t>NCI|~z|fVr*gO]%SAD rȍ\! זܹ~EnTj\>|(h B,2ټ*D>ZNԂ˜ñTdY$7+gt+CvvYY\:s뷲ٴ5 EܼS +k py~V@hh!^ WNs3S1QLXX A2O\܍Kĥ:W渣r@o._fǹR(U> /78f!,,kg/f 81ST"K"R2 e(CP2<< 2* * $KY˜/A]t"#Nx?e1e(CP2 _= I$8en>ǯm^-K&'JVVUJ/](i>ZG^)ͪt U]$ x 7H)&"W,AIs'yK#Z6FutYtus%QDBN/ZANgRI| vAAHPP6Zv(~w@tXhuT(ЖM1far)8vىFA s[C#G瞤!C#Sdva3YD< ͉|nwRl;\N7.5(2& M%%BR-q`X"A39鴨8@)#FTh+:&1CZ٭o^Oҭ]4#i&(0tL7:Cte/ggv44p-y\YjnTtў*nw*MNTZ {*QL߉j+p#w zk!;L€()ǒ͠KhGjh߭:'Xq=Ѡ$D0rŘ`u:\Q˰]ܢ*y- 2_s@G^:]jA|c1>Ү: ?f11kbvab B*887 @ >/2#Gkmx2JT;co7:wؑ8WcJVvƿmCqsz2Wtj5?FǏԇ\]|(D[錜9{WERR^c+jN=KY/%g{YX)_ԑ4  :̏k_v=ZP"H Zƀ<5쁥4 }ܞ/ [e%=o/x:|O_?M5<>M9NJхrp#f-YW7su=ƘLhICI=>ȪS+% EѼ%_}>g?g#i.G_aÞZ00فբwfE#0$M0b`;4n,3x(oGmzRi|$CЋÆ5&YSdj[DУM ~9]&FdS~/IyU€x} ࣩiP"3RyaDN _\DfĠ~U 1*cGV JӶ ߲g( U:? 2_.HeBP6t"Ӎ|w^5>D>\P{e"aEDE&eE*ELCvc<"H"a}uf1r'@%-V;[&mCQYK0XĿ+(ddɵ#L+2lM',bL-X`Gr0fZ tzĘi!C0f)rHy~^cνN Z{ cj fQ) u3#cZɝCS%2mnƘata0cq{݌h4SI/ӓ%-Ȓ1ӄ1~9gfԒcǘQJywsmj "uv| YVo1PE%JO> @"A!Ӧ@n0+n;% KV»lmn'_3|x;bžhǺ5ifw{9ZJPw6?=,^Qg(]7(,E&;kHY&ſ-41`,rܡnQe@[0S,!Nn! Vk4x{QP鉧T{ _K]مڬd*t|j#^UZ>zԒn}"lb %)L8_No/ZXRA"3|l$_Ԯ@zvdY]8N;h4S:i1%OelZZƘnX3{c,oX7.Y&LJyRd)*`̱A+۫X?߷$nqxP$]xxy`PJy &z&FL♥ՙXqKy[7"2ޞh>~ 6 LF8i/cO,vxɼm=gD)O7\:<'Ӊ1r:K~+{Ӌ\L 6QAr;I6J^$Ėkf"享(2LG)0c/\M3F gdrl_8?`KtI.ن1ίZo{ Erava50fYqޞbT^zda̲a^tL38ΧAfSYGccV&+c{h";r;wNgٜ,=CY$ز.KtSժ#)G!CG X3K}lW ĕb3 }>CEI:|}=O_%\$B|LrKt T.f5 Èx$dGǟ^=G7vL=ل!Makہo@Př,)&wOS)O5 Ͽ1ǧVw2RH̬W?xC2iT G1}[g}SXdlrߺ5|AoIlLѭ8;*]a,4")0 Pű={*~:#p>'%v-ۉ`| P* 7j0+.3i0=/?`Β?7bdĕC{ysW_nšF:6ɣ̜tq; `껣ܥ>z{>?nƒutOT!S\㦓h<7'7-;!r1:Ո+1lAjҳV;e!|TdÄuhUMDt7S+([\>8A 9а=OEMVQ|W!Kj׭>wO0wWS0quR ԂVㅅD"ɪ~i9ǯReM<]/6]w1䕉{6t~;V‰k2~Vs Y_׉Ԉ@?=_m_sy}{!~- $?komrD8= Fl+Ete٧SCO_}1(@nwԺC|t*I`bʿ+/pXPzUq{1p^Ş'A`RֆTnY%o(V/LW/G>bsK.%x dևѨGفO2ydN[?k 8`8Ԛ@??N/M9"HDE"W)aOبoK{]$r'ӴiۚY_f+̞s<=Heޛŋmڗ.IhDXʹrZ#/Sޑ̮XNMH=W錪Sc~`cc)pGpmv߬¢M|W|p3}sYbE 񦤨k7pW"!$DG(yHN2ob1tk11?v:*-:;nRࡥ y5v5Ҡ-}4{ C^, ϿJk~UbG'UlP?m}ғz!kr*B[HTNScBHǶnbW1y}p16C܇rXM.m@Td+aKL׉<C \Y|Z6I3sm@|l:YWTeG|um1&[ek-%LW)h]OC%"*jV@5M `}rm+7Ar^ndz2DoZбV3%W/CRE~' qeaszXeP˿1 7C[v$:cgc0f2zLؘt.w^^hoߪ|(GmfL[LL]#Zvc`7Jԍ|x06vY Z˻x:!@Wډ9<ɇQq{(GX&6)LۋћuGe<S*_Y˜W/]>I>~ ;/`˺+"uK}NgSAZV_oHjUp?>F׳Q64 ksotFX]ŢyKY{0)NXĘ7Wp,BO>v>$1AvҊY˗JQ`pytbcv1sv]F&'(ȿs9nb8J GՆ\JG T́wXʇRQj@.kl|wu>O_PtQc5K3P}r"&-yJ޴PiNB^ao_Y~f=yF2k|xKCdYMM)N:GDAT`(׈wJOdHپȺj{yޘ{Zo GpjT]*}Pr*pȟCZo|uUaI*~3댯OK i֋x!ڃ/dbƎ@/|#6`x0}eƌ1dBfm/Qp"&-Ka &^O'wROc;tKyJAw8Wٺ3 L?ūս6 {5SR,v-[@ߡyiBp2t`cw1db,RG _}9/[Avw!0-dw7큔u!3O *M[S-H<|'G(}&<"ٿlaD?њGpE5`l! n**UԻ,vo-'o E'7WD _]e1t ֔ 2K$E+G%THS? Iv%ЙMDsxӶ XV@d|9{4dYMCϳ!>xg&j ̙֭<$ 7X;e7nylQ>fNxI}\HіrJ awW8GVO- qDKiD=4TCzk9o(*=)BؖOBjLd]motrO5$EEѨA͑RJy$ᖤ;Kp{dn_\"V<|<9QjZ+6*MvBInuWx{l) ׉-\AsB&;Dž9Ȭ;+ Hnv~oy"Q,*TL#b(A,FQᡒwB."ADCmvR.vpE4E19e #'٤$;޼[5ޤJ4nwUg8lڑ*RX>Ç/ArOQ@)A%$ҲAS^pSlJ#ݚl3w@XHVqG*HE=eBΧ8'V )&k{Lj)x'4Mi @ =Ryi;&6Vr·5FVCTl\N Y:|u9k] Ę8Ⲋ9ye֯bP5ygs3>]TEU}ȟ4Ā`(ylm;Nlִڅ fBC)K͓/@?6;1g/a +-b ]^cmZ :ee(*yYqr {ՂDP"RJSQDQDp *HCIH%  |^^G>9gN?3{9$ipw*r>&,'˙ʡ Fyg9ƛsg[je۩<|>ge@Ol %P/ h ̞=[y*՞с&0+ܚPMu47F@UݴxpzoP{R]Ifa 8o _\t4mۆz e&0w![2. ,Luؙ)O5qE)y\O,SNᖹYVxs*;ERŷ2ίȄE̜ч~yeB?4 D76ЖT>n‹  (i>~[.hd-;HOQddM(mzLQ@?Z!!X#og 1nKa1rF;&4%Om*ЮG#*krcꙈm{aLOM"$G$yv oz]“|Clj2v-k(tw67᳖UZZxrgK- 4ZN8xʼ9?$oԉ ,|YSJuta,hQo )>O<x3<6{竧+>?4ani֙:?#10G ٺNӬmڷ n Wj10s's`v{`qp= L0ѦSGw鉤2<vM'O⇝~:jQ16fCnO us0U8) XR^ hOq#ao=b5\D8}MȯDدCoa=.Vv~׾br|A!?r 21n v߂T’;8>ӵ=Gw2|Y-iнa4@JDgFΪQߎ9 2.JNjD?wRi­m0fSjEMA@TUM;e-EX= 9 bˊE>mU8 M YAÁF(ʤLUVGsqʈ(C+0 r]2h0gt[ɶ[[ "F7;͎(q.Zjd2SI-s\.u4x՜=onW?_=ɑ1ёGxZ!BeI)I) K#TʅL3򈌌'ƒ7WIzz. VyPU_LtX,) q,a 8/&/!_B#d+DDfLEs1F$%=|228OVQ);vSeS\PDBl"g"3(s\&ΞMx 2w>^%%+6-GTk%XHĉSIsx:npzKRlSK-\UATΜM!(#9->Ӳ9} ,Ҫ[חΧ7Q"LyA^jEa("2BcM1GNh TPKUPUU\I]|S6!\,?ۿ׷^@#BcLe"SK,bN(A2fNn gZ=/oG1 [FSLҊ4oDƉ˴#cS(I(zLM%.ûOz6.D%S*ה Fʙ\<:DrlƒSbA# TVQ["".R!8Ѐ*c8u!E @l%ma.'xȈK z{ b X]sgO6`?-ё;|rE c"ϲ\i1/aNO$J$;.cg.,rx:]Ą];<~r%":r**N}4>ګ |L2ryǓ~w?&:8H ;˚ {KsmlQIq@A2I iDg ҐId|>u:vS= ydUъnN'Q^(RdZtI9eDI̙ >^L$ԁFQKtF5z=zl"ˉ@I&@g㻯w婼ߏ .RJ$^H".D^ݩ+ISE,DD_KQr,o5QƐVO\dȬVUV"Ѽq @I̱ѭ@BZxy XĺL\/V}IlmZ3{`/) ΄cV!_CRDN 4ė9_j#=GNx n}X*JΡ(rt5 8VM`xK-+҈Ͷ!7=Ģzxq9 I׀-}h~m}U$JO[;)~u}ow]-~(?ۧ'*^n_cR}9gO,Re3ۚpr7WlHvSŐxs{atUj5oww1`uS‹ ?OBzWux\dG5 )mڍ'(>!{$=cYaoqtNK4qjb?%:C9v`wDz/7r uB :AN1UZ"OCi!E:~L&;x/d'[4AZ~졠ĆYA'hP 46fP .ח!hBScc֓JBo݆<3k剤T "C_OO(Un10iT bɶ7v -UX<.7%v\ zZ˱ 0UB7Ie.йmLnhrqz_}>VdiyF_-fNc^y.2 e.jmSh0 V&E [PJ~EGW$ @tRT`.?ubo 2_iY%]jLtRow23dMH^I"{g?* c; UxZKXl*A`09!ػ},cʨx U9p$ Z z j JRkx W$FnIn sUNoĀ]N[dD<Ҙp8h:0jx/ӕpIlNɃK;evIX5K+W/Y}ZDrLS8etz=:QdscBvʞqO}//‹"_rv@^i3 rJbSy=TRypdwŧ˓dZ+9֍HK2HGoC}{1uH["R hɹ*530 Djm#Pp=fy>ա3INeEG>t=bh NUWӠVwVvPlw#^QPVR?ƺy clYԍ !܃4.W>Xj冷A8^؝>[q&ܕ)yh#?Av|!pkdw IDAT<3idG_Ā)kp+`)otDZ^·O 3thf5h*,{ F^8}!vmU[XwM&_$]3E11i{,Y@9 >=VF]Jvډ{$2SX~&샇 f/[{+hb@UVD~!4 AT%"oc֧{KN‹<~_'lTrk4Kbي)qkhH'?~7ץԮ':RUUmJ"\E&Z-ZƿIj J|wLLP_'ۥw뎥k$TUUN홬͖r6]aPnEA$_t7૮;# M(<ة(ac]X d㮡CPٯ1~k}@Uk˾Amܕc[Р&m Hz!8&:eջC1 zb[) OyaV_6^hML%FO~ >΄ >5VcΎX=FcCpOX)ۆdڐ:*hb)lC;7TVs)oyフz;DY^-/2sW=B Yȋ:2m(-%m4*·h]KL(Id%<4mj⡇{ W]h=E}s!Ϙp]xbW[3~rb댟˿=5> oJe4,7/^@R@&'~<7^g%-qɸrܮQd@Ԛй**hiC Ohk\aHX)nr.r*qFPWh=[#jSV%$߹N؜>X6;s6y:4ǖ*/u rA5razLO/$.2~F=}'-L)@p@ -,i-UqvmƝFCA0Wt{Ij#Ԃ)#Vd2 ߿K|= ~x`'b܌@F}~6U3oVVyJ ^V=T꧸QNLnV Nttujj5"'NqPg$ @vKğ ],fuq(V>u iu`ΠkD6T .ly{2ڂ(݊I AqK3^"^T$:tptVanXAk.RQUMFR*Ra3Z\ODfJ:flȊ(8p;]TW+Ccl@AR ^$qfyOG@^B ^݊գ;ֹzKerֵ moa{ҫ\ CkF~q) ch'=1Gv.[hb!NC;QL|y8I+Si(t$Yʶ,+M>ZNJJuՆǷOfP:t*|PlCVTTD^3 ×@`U"{U.5P)*DOS].67ţiWt`TEDQUTIyBi-`6:|O 7?t;s / $2lD߅6X9Fn ʨ*-c{fQm ѱӑmV> N+mQxFڝGiɗ,|ɿfvۑUAc@RÃJW _dUB#ŭ BSX eSC*Kۇs0F'|>u|6O%/?p$+:98"D=,h0t;q*$c1Y[d ۃڂ"5G.rG5Ŏ,A!1:WY. %)CzccD%ͥgΒygʹ|uEhJ@pNT(r>DUAd0\^Z [5ƴ"V$yPdhѣ?]mTSR&hӶ17@5xz-xo G e4uxLڪy<=kiTחXŅ NFϸ{Wh>u4+v}1QgYVY TK`h)" QK`zk̊vnGonX<6μ5 hdKy,tkQ<=!949 '1S^P_ϟqw zD8jPA$0h(AQ<v "Z|zʝh4 uټ eȞ*K}MpI\V^5K2t2Kɣ"5ة.]^+|`-w!iwQJf("FW-@lv7>Pa'|y7YmF rM ]{u–KR9b{Y; LjsAdF."1E7j41Ev‰ o~˨mEŒ(DeDrKώG):\;iz\LW0cRnkmH"WJeEF3],+"*gee ծ|b92X=` |k|x7iqDU0Emyl4UQq>`%ϡpt燼S넯b盘'{غ| +v lLnwL x|.D?A¬J+ 4wOU'?XY ̉Td 6;6 ?:MftA;%<Dn!4iM'hڻ#vmB <(mڛe>~b9&IwgSuTOrP $ϡf-yNh\ˉdkYbylV.cfoҡYKFwUaЈ2Z;u#, VˬgfҺ_=}'s`XRGmD"Pw.e]E0gb&"Qh]{2xo'kxѱ~IT~㯐VoشS7XSma,?U0qcӿ> u=t[)wmV5}݁YSSVK?ҍaE˗å&>+"sm8y9' xǾÇ˂9cT#*U4 ⛞1sN&!=?U,(09]jކяt+v}nAQ]`\@`K{8;/Ϻ&mI ;͆}1"c:=::%*ޑ^x? #*- Ԅbb1I^a5Z0qQV>@,+8q2GGZiV{r}vQd*%ZBA<ZAA=/%" J]NDA:| f"Z\ .A8)SvQRICBuUr D~AOa#3@v|"'"tr%t>>pRUV̑yoo-C=ȒRJ( \1sxYrK&*grȵH9G%s!.d **!,* iE^,#Kn1 KډTKlL&Vfx#`-\\!z¹Y%:wo-4쪀dt+shEv3*Xb%>2R'JRl gPHVBlDG`2[]]Ĺ"lV/Yjj$&&ZAEICQvPQTop.(ҬEd+옫ˉqN[ALH#>/"PM]1Gf{,H Ddrm5$ɍ%/iv*#9 FPLBj݁n$䑔Y*DEzfT@S cSn"732Ue1r!>2~>C?aWEDEAf.xTiSH,xTϧeƳ=Yk`l&" nҒӈOIh}p8dQ(%g#I)JrrXJIN:G25dNLe:t G6 /!yw5Ӏvzt4녧< )kPGFWtU0y+޴ }o~%x6[nAM:vay_$@=ܵ]m˘[^x$toצujwa|xvQglU0믙 "A@.C%ط9 !R}}5 AA#35OΤLd/7lKM^Y, enB*9sGDz:Lzt<3郐N鏿iRT AfU낖@ZTII s"`E6m aSө_Az/4+c劉ԓ$NAAf -+?].&n ώ]p$$eLTax`pg#c?_6?~gq퉎BRF>2&b蠶d;O5L۴MY*(+7 r /.\n_&=e3ɇ^;#AWHLx[]@WwY:SI%Ч=4o>9hCn~usjLI/"eM3JIб=փF0g^ e޹ʗNLO3u_Y&^L)JU; Qs[:6U }deA~a%zc=.7aw_TI}iX-v Mt߇6AaJ_ u}RUZq7op4Sd04\ai7[ /'-j~pt7k8RfٴSչ ^{i-j`&}~r c|S>(^ puԕQ+?D@u O `0Mh5 Ztf]Ѡf2F&i1DxAAAE z-Rduɠ(ګ>+j"ZΠǠٟKsK:WxA(X|i]si]S3lWk4%j~: Z>KQs=oИ,m4ƶeKcIc^; :U2;F[^ci45爫?ĕm Wל#suDk&_T5;j+kEWZ:w gR1hZ%ϷU10ۋ0'!>|<6ӻ^ DgaoQ%u d$Т~+vG%Ƿ;۷G)5[h8' B" q*G+iڏm[ "׽g{k:E{3/"!L_E,͛ԑi)10=xԜ9yy+VZiлL{:G[ѷYmJfT0w"PQq1i ĖR]&Ey䵧xsT a3i"r>Oc_8;2EU!f?F@a*h5⯌'_X:q}" ̜j_u~OQrX]\`,#Cۯ!m7Ϣ_7y9%AY"7oA"/&*ck˦>|/z,A^xOߣjE@]U'S"hDD茩2j11ƌchkT{PUwuIv եbuVGbmR=t 9wL!E# ]m'*Jx'jR~h59"vEz!t1Mϱ*;zt7~qvr-]0\jߑܟ"9v|Ͱ t=(0ٸ宻p;Ĵ9i)o*?ڞYذWfw%=lK"Im ˅}8[!rP IDAT-®OX].;u P' "/C@<2{{{v8'og%L|/muGcgyyG>QG:I< WSv{3rcooˏϓ!L>"jqpV]2o-PU4(</~X{9p(Bֵ~N[GzdT\>eɟ U%"'`XS7vHΒ PIk&6[6a7'EE5ZÄiK`Ҡ֔Hd<8-o垎ujZ >ĸG_cyL^4zH<2N\Ńr6Nlw.q7SfWY {@W3eWy/w*eYь~8u4 O>BO^xa3xۮ ezW}ޛSסtg j6+Hۙ{* "+ ,|eF-;)ج{h)&褗YrKԽm7 Wa줗zƇ^x?kbmxn`.bUSi^ aݳP1ﻆҭK`w(19n:RP/#5)Z 5bA@ UȠRv9amrMU\!(Ɓk:bNH\FOCs1*S\UeD/dڼtx':kʏ_lِ&m|*GQA5}ty/[AeV2K_B/UG9c| [=c1떾j0=J0#na+yk&?JO o.\N>eg:MVNKMײ,(xϴxd Aք[".ln\'Yw霉׃BЈ^m @EX`u)9&:HzDUWԗAL߬D`:3Ol!Fp<حUXm"C|1Rrӎ#u{ioҨ$&qڰK %Xpܗ z+ZM5jeȂɜmݢxsgjLo3/=0ջ$b̔V[z莭l:ZPm!HQ($6#/ ˃W=詥 Rq<n/ 6݊]9A,_ 91DE+ISrpCqЉ+9zغa5c޴i3th=(/spj|rB3Y(wˇX{5 _(df|}$a$QQigB89zpvy0TT;ШTkZb:eC+!dhfHIRԉ2d='zZ: ۅ׫#ɸ$ՅUC572_sg.2w_t"k !4j&(KjpתqgѨYiml]YFD ji5bnpPҝ[Z1 QY,IB$ÃRjjˍǫ@) x$ dfc#jzUN7x!tKp"&>Ċعo9}_r>| 0z"F!qnRc$ iα}In[.k3) eUF/?M)21"K1kKԎ? =_yKP Vh\F14p!2rХm7kC)uy=%(fHRoLuheWMEѐ+_{pnP_柟ndcn,Jm^];r5Yz%xp!5ı0< UWyA<]{Lۏ܅e쳼52Tzh}'>KÛ m͙[~e ,L}L?KcV,?,ػ}%xE/׼ɓ?`dtkV3Kɜ]w.n7sXbVYf^SPrz<4C <|#nLeK m_9RÎ ̝,pKP\6#+|TSΨϰclj[KdǪD"Z&U 7 H+WgGGïxe>hEa|[LzeSlK**El<5 Ժ֕/C9[IR{zDnfL܄e-e>f~n\aO盎ēOz4a]vnG:Rm Ǧӻ_/z' ;N+q`R:CS7"hR,(\"LQP+M0(٫8a!0d:8eX Q*e*U qd; NsfA$=jl$O+r!S&I]2" q)tJ@9 hѩ  ^7ElߟOʠ.*ZGdKč}-GЩk R(oq7%";pCdÁ9QFm>(ܽznێcE ^sbbIY8X/Z{#==C[ٶi ˁw+FrfݵיE3` 48O^z !dcc OTvnͤeY$e RNvn=D9;^bZذ_m !.-.aXJ ؼ,?8ze 'GPO:AKAtJ]ZDaPC־ɫGbӒ^ 'vd߹:^E~&ILO'C[Ś$%smFsZ tL &sIhP'9SfJ t >XMl6DEڧӶYjþMȪ< Uqs$4Y^yxEVd$S{ gP-G5Wod;elID+]|`i;ܦ1{4'@%H&Ȣ[aDvZn=g=pn6/иJ0LEB)7 Ɉtz4'`.-:SrT=Qh:!293u 禮V~qZ7IйIئԚ⸵k,:OS}}oO}> j1Ȳܫzkf}ء5q||m,ϹJAz\gcUX,@KYÇ>|I; ?[bCHvrJdܫn|qEYʥ#?ǟEd/j·x$J_Ç>U;(̯ΟW94Շl^bOe(gR|!Jo+>:3sLgr3Wcی< o |U)pIV<2A>xs*1;6RXeiL]ƒ* +)o;IҝE/Lis2 TdLT_'S_$CY(̷&S_OBy%JK(̯~'G0eogYe%; U؅OK&^7Po ,yLz+NcْĊ!:Ň>|\%%(5Ͽ qg?iՖ%o̩ #oF$U(O>$N~ę}yjp;߱:e`ۅR}d#ؕL.mIP?J4: o"g"##IIIk2H m FvۘMlFͺL`JG^~k5_d+yqzQ6_,JX .CiVԨ9){C!!|P"7(-w="4l8F%  R mC ";wHD%77={WP|z%߽1VF@rqt%)ɖci/'Ҳ TS I7\nێ҈RO>*R0EJN5^NgDL .s^N]ץUÇDm=ɋy #?BAFJX!wxd$ v}̢'3\HZ?WF'0*TE5F#. AQP^xiBYA) e2)(%QJLJ z򲀟ZBsydQIDleA$"̀&+Lz" 3cRT j&x"* 2f¨Q&2" *& jzxR!5W^H%Af"CMTMug2cR+D0k#_REH?!Fgj ah3c:ˢZKd?afu @f ~S*5aDQ+EDQA\Jѡ&T"2FlDчS]%|bo3Uh  :܈JjWyA>B@<4g.}iIOj QDg":"0DA5VzS:뗽؍ HmN(d?ZI(DnK6č6'Ռ)eҥ<:y9Qru7Y*.-,ٌ:G@jד)c7&6VZ- 1 403dhl삀Hd JEh>|*K]_hE |^O kFPs 5gRaUrK2xbT(rhd_rN edFwBD.UһE/?"_<}Ƥ$asZh9{~8m=Jm|[YN\h7F$aqpyEDY߭?G{i>€s.x݅FٯH97 ;ӅfyQplO9g  efCcmߺE6$M,bwsoi>nǖͩ=ͬO> 1G:j2}O<~$wKEәu9_O@DDˢa҉(^}m4:4C'9;Gi|6sAL"fBwNVJqqfO]ĶhcxmȞmw7蔨Bcػ/4V3dedmQT`//e: Rm~c^Zsȳ~o? Wt|i&YQ&ndXĬ{SeSK9VᠨCp5 {)*s={>D;k2?a[2]nL>ͯ3_We/Lhs08C3e3'!k?\2 džMGLT:09xHF5[ <:- J{1VbQ\usv{>El8bPl8&>7[[r(M6L/Rn5/w#=ÌH$^f'8R)0g#4͈G?bYA}ci{3-=|1=LjC}M- /p/OT*?M ,6]sQfgõ: ,6?{~:=h A9Z!^0s(O#FӨY,"/qڼ/@TY1UT6罋Ç?iL5UJ5""hM]U&Eu2?V>}GTwGeg}Y6ڊT'D!:&{> cٴlFtݱM7zH1{Xv+;I2p}RsJluhM6놻8ek຤dr vuhP]Hhŏ|~][Qp6g??+ޡYwF]#r]32A6FzC"nO;Sq} !Ou }ܟ菲:C`B_էIx>yGMD'C;%?djq ͙sǢ(r(S>)zFFd'~wXֽCmyH·KhAz8?=Rѝ IDATȲ` >:w3#'h::ezdÇ?u2:^zlpaBOv c(Y8z55^kǏgh[&<Ӝ'p3̷]R6bQC9s ֡Eh:!T H))PP TԖ 45Dī\H sT WdZ*/e4۷n':nZwf;D63~Xjr;] vg$N])^Ώ?/ q871͘L擞z?p&ǢO" bm~{?VPT㦕,#TWuf)ǷJ392ip<uퟮfCM19d/ T_ $%ϏHJ5&j%7?2~(`jz$N;e,kphVp;!_%ӛ[lrOOD Y-#{% Q)/c!*eb;3ch@̓,@޽H n=8 ݍ0{ Du*i~Ǡa?unUm7v"[ԉzd${4{=߲z*uthP_JlR @\lYUd f쭐'#iJxa-gDc o*8āz+!+-s,3aO?X/V3{nl/]MAXUjh";s=ӃZF]CURDђbPa?/%;ORHoWsZn/#cDAp&Y`>>‡>Lqx`H z Zi͟w.Z?Z +F&ξ@|aF]Y@24V,#+;15DY]yx=7 A!k Z#No&C*zLNs+θ6fwS^`cזx|M6z(W[t.=1?P=r6K x֗ٸl.;vIbojUp! 9m%<ulN9.%W z$s3k+P njl~\8k:udbLU4;WTw,tl,GhQ$lN!悗mz4F tWRv6sENרiDPz28%,( \V Bds>y.Dd2bdVW &K"y%/u.R]ue /PWSkRg kf`˒WXI!.ӵ;e¡-Y&9.fx`x=G'XZ}dLnOkS*8jYl6(@ÆB۹{dl؍Jt 2H7n -zp, e6w9ˈR6£Ha*Y?,=/cפeG@ɤ9Ȩa{QnRd<\['pi|>˅zr@$v蒭$xA^`SoRH@tv@|O Ldް֜ߵErQŢknDDfو;c٨4PgfyB^~8 X:{mpϋo>۳ Ǹ^ᥪ$wB4Tm6YF)d6y' n^X9/x<|Tf=oiuE*6 OܽS?y3H$4$ 8i,+aC(-LA%]V^<3듘<ٲAaw@560gؕ0wxyt.tGF3wzM<H?e* &9B-DŽ|9w&qG]_ZKQN}[Xw:=@k6䘩ƝA~@IV؍)ƱnXѧzq뀶|F?#|{L:ՍH*?f?;ury{d9働/[&9/UPܑ NF7UiSgIOAmس~)&@~Fϑ82F&͸㓣19x bK/h(?⩷Љ`Nk#z1c7ܱ~;:Oq2 Iʴ9WT&'JlA)6xga.L'lN7,e)fUӿu{F<JTVAR:Y(LUR>Qtyyo +39) O$B̷Ʋf2uKPռ$<k{Rhʾ|mƫP@&@z\4"e7Cx|,- BѸ)8+1Bce4J=1~&tH<bq9r8M$RLBƮo0GvTFbhZG N; ZI8gQpxEPX>zw2⹝ WchBtfvZ KԔWp8tRP(ڭ-#ҥsk4U9l;mEդH >̀/M35rz%#MP|s|NDO64kBrDEpx|52D!5Pť#> x8(u)o`Wf 6mlw/JZdc kyN-zV>|r~JD˄3˱&C&?*Ҩ,;yWlϤ{ѹ} '{|_,-=qY8j4+}iʄj[3c:T:&:+wTF ?ڀPzU[V[|X>|;W.@xzDŽ)? zZǏdW^_˹2>s(Py0ϟ8 Un/}㯢B lށofa}Ç[%rϔr {Y>ȇ_瀦oÇO; WmtRXXOa2X߯l]SK.\ 164#2k00IaDxQ||ܟ]qmV}ݸN' YW\ǿuǐ`G=?))nՋ1\xuY]dKK7KFK7v2w_Fj6?Ld6)QCyn5$&f8+*.?hmQ3/毼^δ1\SXbX^Aat)-ntt1z {ƚ,3mnŇ>7j4Cg^_}@ΦL' Q =93DD>Z$6kNW,@jTdY^T<=0pUfD˖?oBiC\ߟ<-zQJ%.ڐHZ \=JߛCD%3s(q!umъNiX^lϱӲEe쟔_^& ŧg8Kpm>ZK:_JPj☐%7I~IjDI;Ç?U9""`'0v7fPkD=~76dٸt|-|L'`TP*Q Qr YvQr !`0 PW.NdL*=zjNJ&4 Iy~0/+\ ~H l v Dxgq Â`d B6KUq9%ϟ"(IIBj':A~Av B3k(>W68lA쁝:=ƴ\=&D+7of%%\"i,BD3ӾE,‰s5Ȁ1 P=:\^HT2gT F7HM doJdL< QyhS ƥ FQ@o'1ĻAeYdkRƄ6 6.,:WD%iQ`k$}cER{G&"2)z/)HJ~߇>Q)45'7 jb^7?#.#OtF*d c+v"bɫ(Eh %ypFNrTk8]OΕ9H)#jr ]KC?k=N4j5l˲R#{1"1*F!:$tcW%oϊ1 iCcX->%>_mb,8]|>s7=.B b㭐jKXß5>LNni-iqAT=ӓ)}/ꕉz=Qddѫ/йIfRGs2ck&9qW(/e'8R!cP< $9x'\*D/^X]k`G~s:C>.Aᖹu0E{U:ky&?'3s&OC5ܱ̖wȷ!tD^gcȣ-務ew{;`wcTU3mXŹJ[Dz,9#`n I`p<{6m<:EP,g]Q@n FOՎ6[/o@1͆Wi)COPZƚYzL}ҷ{*Aacp1E#Vcw8ū8s3}MA0.fɄ%,߿5Vt:d3{( &*9N8H{|5?I63qҚu;Z8p摹oKgi/ε+Z6wN#NK&6\ǫ#*g>J̴ǯ%&:UI FnwESU!"9Zߍcg2NdRܣ7cnCfZ?Cv"~tB\eZL@ضfگyd2-(W,[;{p[K:A%{jG!w҈*&6uj;ٹ=w[3so2K*iЬ90f쫴k23 X̅ :B]zw4̺3>όm2p l6J>|KDс YpZ{G 8 by ?Ru3zJК yi[0v|ޙ4fBSPP_FMk>3ؕl'VNV/NKSBQPmYI NS PDAh2s1ҭ cu/ղWU2Z:_gIJv$2}i3|); |n3A(4ӱO0 (b'2cpmb H zIݯDo ;VnFg^[u-2t g}ƘON?2C n9G.5( k2un;GTy IDAT1=pKW;& i4t~CE[!/ށRzO썘{^7 ާȕcm8M^8aCQ8`\Z>&,!G'p^8hz[{^}[+O̡S\ [}-@f:ޒo? 54iвS]r#zҠZVdEYQwRxl/Fveg|:[дCrGMs۩׮ |==mmML$ Vv.X-q<5Y;g$T0<üOH(`ĸ)) ~LA * w{Z'nC 仜it!<2NQ?vk\ E@~nеmơA]2vw)“聟֯#q|Rm$ԟF?"wcKb ms|:PYR^e)pNgΖ |N*]- ODƝ{2WG}I-aXsiWS\A"b q1(JDpSCvh=p^[X6|y.! "E98-ȊP:~ 3d"HT8\QF)Ag⧝;ylJ[ڏ&mY,`Bjꩧd?&)I4'+5>VIYf 3/d;6Dm8ZIińF eK^N8$ PR}/X t'vϧY@n$o‰s18<-lh QG]kG$wx=x/ b-8etHEAg_=?uuŜVeY uXUNvr/jx=iQ8h. Dd[쭁藷W\UK|erZ}^'&҈mP,> onޠI)iRIQDs)UFˆ_ppW$.Ċ٘%Apr\&ȑR> t` f-,ɣC|AFG.ca;: x۪ p98YBTnރxqL@XgaHz%ŧf0AeϚOYeB'(Y^@)*trEl$bq".ٍS9ѣU$QN|(/^Հ(WFD#])"#ةkʝ\85:}.Vd'Ȩ`p#I)@,=&3~t羥 ԄF  \ͻ8z@rg%>f: Trd)fHz EPW***j50q n X{NBV846s`JB?y|nhXD2'Ş8T)J2H9O5L%ygN" (^$@"k]T?%}Sތ~-m:E +c^7I2V,}r+Q19~r78KR%+^ʊJ\n'.dLEyND B/﹦ J8utH `Qy!Y,,cKࡻy<23/}4nܾb!~T:JP$ Vd󚟹S}n"BgV~*ߒɽ6pSXY@&>:]Fo%gqHPSn6x ondEV(//QZ)ϳt[쪤N^ ie∩XbݞKX8=\M\%G|./\!(U1֭Eb^% j{1,xKqUyALCDYY1rTiמU:% Dz9٬1N/x=E…UPd/7(qxQ)M'Cx 2.粵"k5@É Cy٠APnl1!8+I*N =(OAd/e />eeS5@zqy<(((LˋY;k>/sg @C%ًŠT8Dh9a} (,5G,_2Я}-Jl$UTJEEE̿c n=cÊgE?s42||2w5CPChd$wLKRZ[#);F1i75Q (Gv!l$VWFDR@өٞzr/8/Lم˒M2tBΎLvfb֖B2wLpGh,_GL!{I{c<[tV1řq=MF~؟N'Vt^ Pq՛'V48MY6_!(/Y̴!(*zLףBkݖLtU•;Ħh=1>7#,0"y{ciN19лi$:KϮ~r Ӳ|02:R1¥^)+B||7>lvgS9Mp a YɘW3_"Gfۯw\hbꢷ8y}ۅd`?:ov+lt;tg=Ϛ0>FODv0)^wnxf-7B:+IΑL!ce|=Gr*.][>}g{ߒnn>{y[Š:I2Y֯zz~e7AmzlL; zl;/ȢchouRR';q85X0gߍbW2kbE+ T!DvxD3O&ApTvlm'Vt̜5m?&40}/En'iokP];C.IBְKr|n7RocW^"{gOߕmOv~Y1 oޚO\u}8SgǯԱhNBkl^40Ȧy mW'0ihGh8",,?{ .I1ϰӗi,gOp3UZo&9e>oU%NG9Cy=M|=KFS_ta2lnɆpy.?[ϖ}vk1ljY<>'>BC4MA4\=(hi$ Q?/Z]Yo(V3D*=\Dmh.>hݞ2]Hނ ZH)B:V΄NrW9"+fA&$@+tlTzh-z_H |K|R!1 #*2ŜM+-{tQ~:rD^Xt%r!:&P?#"z*2ԺI|2(E.֝K:On7и^4gIwhiZ? )p#L$Ƈv.+fLZ$TCzaW@sdˈZubC#:yɏf;B`ubigS 9U082QMJΕ#z?cnΝˢƒjn@$Og(u])wDP+Z͙hg2U\L^F/$p+"NOQERLrnŕy({u ɑ$!er؉_PqN$XGS' GQ.AF|`+ƌg9 /sWb#V9_LNEC^fy7^k#('B>%/ZZ&\@eRO]yDDL(&Dc(Gᯧ8;?|%|D$/q2&k(S &H*h(󩤕'䳉#W&j '$G@p eN./40YDƆ-%-$0+tJ$j9r, 'z6 ©TJ] +qHJdSh|pOi1&,m4cL\(E*6]L1e &!Ҋ\UƱ\ (D #߄(Щ< |mԉ)tV-GT٪ fTr(!1Xj8ڨX\yͭl'1@f~ EҒbS S?*#VGQQNx(pJ)񱸳ɮJ8άBc4SNbW,I>ysd-m]ȥRPQQ1 Я' YikDyhw4ߚXGvmD_?3d5* `ո6h>=V;q%SLfu(>~SE.+ 1wwޟN߂/{h2vѻjt QQQQQ[;*2:1=U9okYh/`^\ZՇ?\NI%BߊF׋x˫] !PO ݻ |{55:T'Z@o~)P{HEEEEEEEEEE76E~mw'݅/\Tjh*ţwKAE6 ܝL馴um~\N7%N*/wBe^?\Vj_aӚ<׿xX6 nXBEEEEEEEEEp SY6@1xE+ʼ}\KPj_oT+ؼa'cm!EQٴGRg-.I dc3a8oOp]`+SF2x^ZȞJ`^ nYA7>U+2r&ٗ`+^@ x|j{#<ЂVWkў!XqRYΧ?BF:0*mq.4imHATdêxa^LķxMCbh0uتzl&"-6 "d'[ȗGV#UlZJ~{A $2ѩ]=Sk e]}Tu).pytvf(Sϥ :i%(/fֳrߠgY9XyƱ#~Qatj߀[sq,V-YR+|d sܪ/_52\Z=mM+agkDMjNʳ)-VѼ?j"4:ɩuokVy`=E]xa!;0eWj91ht=qwpY=GTbrq~~/tC,^@3dHk^>>Snx]NG4* r45iq ?oی,ބQ ۿL'0ͺU8ZH9Fذx5H7wh0Gxr5ZEEEEEEEEߤ`yydG=ȤOU uWl`w]JQABQv23l=S;?U|ÕtcE'Xp˯.Bi(FH3\C0zY,aVl B 3|6ʐ77"i14Sv&S';O`zpWݽ-c5 lx%gAl;jb¡Q@ľI7 'M"<@c~fG/P$RY̹<[#~ZĿn[z-s JDjɺsOƐ#_.f]pJY` onHnRrx0Uܤ8uJ婵h0B3r{*w gNfi:gB 1|hKs:Q&1_GW\UMDTTTTTTTTTf&:< {ɔ4H%kF2qZ?v!-Ȋ|%t#(&BBE䫲lU[/˿rIī'"KzrdCTDڴnR@#`5 ^YFbvgI&ӟNy&߉OxR'.'RU}Q׸f|] ZGR~|NJ8**$0 ne{9 k`T 3W\QUl£p{D EIR^pKD Q>nΟeoqGRzF4ddSu,o=6^X4F򋫐+$oas7" 쬢3xh|*9Waץb;|-qd.Q@vޥGrw/H}H(7߽᝞(76!y<|feW}|kfhͺew`+8y= Fm9y#S+?ƥ: ( .@_Bn{a1nRl%̦ ;tؚ&#jb1hQN.d]5lFH.A6bZ|'7=yN\^'P[DC3Bf+yydUV+FdG\jK eh|lDg|;]W %jMjJ_X(+½R~~􈲗;veˎ5R;D Md`UnL';#2HH?VHEI Ynw( R^RBvK=*********rEwF56icwEcn!({Fo&V@(Q T( }X;f)"ڛ|i۲<n8$ %>`l: ^D KY3SXDKFZߡ,>UG$Ѩ(pY IDATm@AQdE,˗:>nl 2h=ݻeAV3NS|TȜڷ>0p*~ eǦDjV CQuxgw݆S N;h2onpg&] ysoɼ(e'2:Uk(?֖܃w_z!62 nK "P7岒tq8TV:%WQW8(8CKMS%fK8[cP r)~FYE%ZA$OlܗlvVv;DHd _. (кg3~$ERG,acgɉhN ]e$7m9|R;6oĭ QŪY>Vߞnd%K]5&3):& QDJtywlXݍ>m1'D&:M_-و T{7`Pk ߀>.wdȀx"wϡ1~C?B0/2L*{H˨' Tځj˂`{dT7 ϧ4̩CI,!e/8;iciܻ l]Z:TTTTTTTTTg[Ek(Vޖ@[`;.+,%y䗸ӎ+vPDQzIJK+(*J/_H~!fTc7BEiޡ-Q7 Wj_<#RIC/jq@еQN~=gEUPQQQQQQQQI+(gmjF!{}ݏg"iE竈K(ݚEЙ0<1qjeYO~q5gΐC7~-%UPS>Q~9YߜE{C}O?m!Yӌw>lc!qTƴ5.Vr@k{y j8K!BQ Slw#hu5+ iX:CYyud$(.epVT"k TH,SUEZWTeUWYM -\7)С5:CPX@5ὒyKj);.qZ ]EEEEEEEEEE_E)TTTTTTTTTTTe _Tynwxnrߋ'~)8MEN}_9d?9n}sEXs{UH f!ti_2V[yKd_]Pl{? :H%>|/^3—--*)2&O`S-)t P|~o}6_*4xi|ˇ.[\W5,^:sp$B|`_2储lm]32k~{_eMdkʖՌOE/jH'o z} tqڲ96ܪ/_Vɣi@@T=|]KXGRGNjOO_gƐKP*=l&Z<ū-O{'_^faO( V9IueVpQ8I+ާ콄hڮ3rhW,f7<+q"n3x ݲYqQg( Ӆ"oNޡkbų!:"cC)M,lLuA~8kKK>\[s>Uϡ[7d #;dY=[W7lt<#kQ4{iYZFO.mmr4Qg.2k"1Y}xOߐA$p(5Zӵu G9s0Pd/mtqĊsxeY49鴃k؉( (ǙG`WX4b԰Fd|k94І|I4qt&n_{C *pS@ MڲˣdgF2E|HΞq:,g"$[3&뽒FLp~J!G#ϳl74eg㱕QLMҎmFF[;M vIiٵ'b0 h3 *r͙8;;/J*tȸ3u.h"1D4v,J\B-Bh&'lit"T Sâ<Q{ƒfuѷ[=YJe=|EɩdWQ`aO'x&bD&SӬ pX`0Iim?g?W݋sS˃>PC4$bhcH09)wбW_~vO3^Nj+W1h< B=?JfpuciӮ%J~aGz) a4h@PT ctgYs******ޏW5)a}/SQ AaqX~(CnwjF,

E2DM>8't{f=zi=3a ^M-W@vmu^u#~[/ZhXw58fPQQQQQ77{]{ObʺAHf^7Jph$]1i@BPBYf.$gŞ]fI!#^?cE4m*\A15<BA>z<^O`堠C:m*"db Rˡ3=J62Yt.{^gPS~P>Oρ/2uĬ Ds)\udx29샾Bϟ_Vr2$|/:©<:c `ohY$Id" j4@NJg"8w S?ًtM҉""8,d&u ZAA> :kG Mvk6T"%UeX"hh߷_ VDtԧ^/MlOAa z-A3f& D 4NN+&.$Mf N2~m4h(j@p9z _@@o)IUMlacQrLWMrGW0jX}t3:a}NAV* k&ئSTTTTTTf-.X( \aloIL'ݠT+(SV}*>9 QNN^J}Un(ʵx욕QPo&tS{֟"ZyhXO ?Aj׍"tO:qP25ked3pOǵTytk,=AY<}טj!b(~gY$O0{338c@AJX/yo(He'jT(9[Ss+[H'ygDu 2Ǔ\qhղ}L ʟҥB]^Ug^1A1 F#>N-^  [Rc$JvAƌZHQuhP% _cl݂PU2ò'jE2 {F$gV^Ziww7vA~8k4ΡЫy$G+KF7 a S͊U{sl2xp|z~>v;С^'r\^/o4mv,dYOiKdVn?OMڠpF h'T#}uíL$i׳=bN)AkԍGؿf=;2<$  \_&Dug2$ICp?QwVTZz !>(ui2)-h5~˟K⡇пe 5Die@Za޴7qUmgy*Tgzىw8Lw=W:D-!1 yU#&nghT_xť(-V6[{Hʼn|/q<8 A6B 0#yU hHx l{9غX_o˳bd}+ &?‚#h{wbP>)?؛z‰%̢"RAAC8 }XMWm˧6 /~qK.ᑒ \:wԀ7neW CHF Fea6f@ 96JBM>CņV-99Lē^O]ƨ}xS49 ӧ㌝:q@.04%'Xh!_Zy} s;!|Lt1zP$RRX&BH >zn}_}C[%$W#uu$ktPTXLK3ՂTR!~0c48rNE=:u>=4BNg*1  7 @[,kN#LQoIwK6T>7Aƪ=$tJS8nG[Cث;3i{vr<9 }).錽奔i%,6M*7j$+Tc# IDATKKbvDt䁡K'+$wҢQ# o%&1pIgPd'Dk'+0 KН_ϔRI&Ϳ֕JFQ :<$)t-{'q`Mm£Hۻ IqTz%Vg1wIYEpd0Ɍ$J/[X'L0V1׾Oͩdzy/9cgp%KP$ $Xx9×^LOOc|3cɇCHpӷ |~"Ź |  jZl~8/'u(j7Wė:0[H^+U@#NVoܝ'nI݋4kZPeو:)EF)D(@5xd_د #ӁKcU$LPU*T f %n0VZ. ,>*+ȵ_d[MNvSTƥ@ |+V2'n?J_'* E?UKdy\n 3aJTV8(T@׈O  P#_2RS   ,BAA?@BeeTxĎ  ?_" MV@Sg(Pњ"{v6S.ts|&v/>5ānT^"a<N؝=k_gNGŮ  @P*ey?y XIr>cI56s1ziϢ=/f7ҶS9 uTԋ?2r*j3ByfQ bfo&׈^wSqO{_:;>޷y0s>Oc05;3iԭ?>.$ (ڴT'm'W ۴w7]`3AALV5ѱGKHqY/-f]  @#y`DZ9u8F'1)u" _s qG7­P v'*2PRA%*n"6;| ލMz]$6kͭ(Ө}7t$n͆ gm(^up{зN(iq  U|j3zL֥(CGh.e" ڕT[͓3U1[9*PU3%RdF 8Ƽ Mq:YIBZԏґdQbł ׽])Ŋ5kڷ^_Ǹ`&5Tn վw L`U   L0kr_|4 g)&?$֯P}6|5?ƳtTN'oUH?t$ss4uD~ϐ% { \O#<؇g_$q{s_[\yn=9Ga}c5D^]ܞwf(TAAA V5Z-6_?P\ddVVf(YFH Cu2܂J\1hB'QNf몚5^gO ŕ߳XJYqN!:XGڥZO:QU˨ ;ӎ-*΂Av鐉j()ka(7N",h}*  @AAA@D     eM 2S#)k%91 pVqi.{0G'һq T/x+I^MTUgaM)` $YK ^I8 uQrU=nn='mMx=n2SR9x2$ݸ֒4h\Zf2,wOJٓ4z5ClTȫz\aDj)f4ʵ+i$$S?ľȮޜPECb:ԋ!-D6[iѴ6fs9z:sEKuI 6rG. F2S ?/%v:ض'*t>4lX`QZ&&Ö)NkV6MNRqUyIoAXb$v}{Y}̴iDI-q!>!anEvs& \jYU݌]GBzٳe/9.Ʉa o~|2&Տ mytѵIxqT9bGIAA:q;< ܖrlqkP7{cr<5<7{49'+OI}@NK %;a e~42!c.CiRש7p#GQ~8>+_&a[M “Y7]r $8z9|Z;_JQӖdyQ②x{" 1Ҹm$ Uvs$s?A()'.Ê7Q0I ROȰhՁO_ ((sX5f=<0_D;˥B'^HK07)a; D4n{/&r}b=jFO׆A3|HAAo6]<4f2TV KˇP{;9v-{gJѤCBVqm^ -;V)Sߡ0Kǻ߸G;ɨs~% p1M\8oa;o؈߻Pӿ _MH)r:.e0G 66q+Ә-&݈.6s؍4;H΁b3`6Xɧ8owuOxAφ9utmsg+  2>-b+Eu^| 3[-hT5u3{?%6fd e~Q#{'FG}e:I{OS~UZ ^/?{[QJ|bZߏ{y@XǢtr W/AVl^xfk>{ AAJjI鎵l`3fQX\~^*Z$ҮE"Mh%P\.ԉaTab( 6,c5UOh4n@ 7UmX}\DѾ6rs3F@ 45mp4R"\ܽ-',fz  6 ąӢDĎMjqyh$CJE:Q'1$qqr<2Wh̋V-q{X'T#:DQ7!nJfA j alc9E@zx$,[¤1c! ƫխxs,7n)UytЉDjdbh2WG}$X4c{7Aө]DFCe9Û(lNw" { {Kۅ [Wk^Qd]XwW5'NQ8y"BcPoaxuzWE3.us0k^ Pej5kޭ7yP% kL zm"UM 6x olBДw>OUeKUS,2ɓ1ȵ1h<73KH>f ܠIBXC*Zs6wQP =;T5ޠGv:h֯ *s4+F.!Ϡu#[4|")7Dcw=(9!y&[JZ"!=_安B["}lpٛDgAAn@TTеE=,x W+Qsy%3hPV,XFHesi\D M2C$؟)5X| \Ʊk%J?g4A#ʘLr%H;Bi`|s4{VڗљLI550^N(x"adm2t2H^v.Yʺ70<>s\NZCu@#q܈Po(\67wڃ_Ea_6n ]r QHHفuac 7͍6J.9@wzeYFd1e\5p>O4d* Ly_`7g"Tc҈Ck^?0}ky[EB[QyRp9Ѵ ~<3HF? FubV]~wf2L-Ws~#WWt⿌  KgkNI;ѢA Z%!"oiq8L#_VkKȨ\f#y=2 cj\I5ѻoKmIfW%[iҬ.w<L8a>[ #XVH($uľeb:7-;/{ٯ;G.r*ѬmAU(W!~c6Mh$+U%0"h52bice֧&jz|U?s/jHnmGȠz$?H$[+`dYCX:$ACb Y蝏QlݽA3{} 7. .}^r Pܜ?]@ߗ Pj*Dn[VS[WQIj=u9ɴiq[5Tlt=pkuvQ;@GP!|c[̞)'>[\: OMvZP'8VO࡛&}o;QF-gĄCoNďY>&Ѽ7~dQ q$a0g64rz<?hP^VW+L:VXLz^`'45`1yi4.mx!/@Tbcpo> W_{p>f-3Lw)f.CcQLńVMԸEp䙷ٱxb뗋xgs(j)/*bݢEV L{EK.lxnA3zq*|u Yvmjc!~S8O>c\J2ÌV i"DAUVנ*)/uHhQIVЪ'مxIzfU/ 0_~WjLDlYE~X4ed8~G!S,ZtcJAfP+Lr[|u@S5IV(ߪV_z8Qx*+ήB|4kk<ϊ" f#:HK09ȥ}lDURZXNIK 0LzHeXVVr $ڪ!=}AAD[CEfD"IK-eH fUQ ^ k?ÕT:Ȫ=Y-mŨ@U)DX rѯ gי?!P>E h6¯ҬY~AAȍР9OWnw3!6ecYz:I%]3Sɮu0!C0e dRz0%v,yR;·?aȔSsۃQ1A$A*HR+<'d̩?ndm.~*;2d?Jb1  @ntgKF=eI9XfUU#]Vܼ>J2u}vه1aIp ?׍c*nc܃4'kc0}㚝^£Gqrl>Y;6#qt _r AoHObHuG;Y9w%G}rO#K,v{R>W=H9}/ه1ҽa eZq041O[yp%ǐ;yrtK8S& ĄӶGmQU-_HD&/=g_zͫcZOy%>y76aUT"xA4 ӲsݷnC|}Sg!rtÖy3!AAHq8hФ.wҎIJrkuT-]+2:}iޢ_::9bЊdghgRu}^"mHGyCNu%c%?D-▇`} +E&iQ|接 IDATh^;caDj4|iI !ܔ=Q;| jO#/obILV ڵkJɓF%C*8s V-Fb/f3bL?򊝴iט`<:MXߌh#wK0~U5"JD`p+0pN~+J>|p"MzةAL\ UG5nmH,Aćj9O  ?^lH>ݹ{s4ɻsb$U5$Ҳż.RtɱGqρBW u*6n`gpgy}cIqAg3ptC.;Og֊X}f ye ȗxd%<3y)E*K;sǫh j a(8I^;M c\f9z?;vkKE/twQz<(?3'Of}cxs=>dWzNZT@V|=gj[8 {?W.f?O@- g{33UNk3+>A ;KxitwGA}_܈(ysAACF@vW;itZ$TT^I,AρDySdƳPϹrn$ 0%JH)-lR)*עU(Kv]$g=F&ǽͥ VN#jPSi7_K͢Bqym! Cˉ$Y0@vqzp"ѣCAzEz7وFWFEUo#p^HAAd#ctOWOz,?GK9KYh T;y5v%Y&!.r/Q 8 }bGZuN+,uRЁߩ1 ݩĤZM' ߐU'lIg˖|trl!*rO''*nA'akƘ|+90]׀o| Fdɞx}2]UjBxx${^Uaì|'_}7}! \x=<^$Gct#+5 kBl덕(n P^/:Uo;S<7@6 6fؾ$CbwݽqyU%nOՔ˿x<֞awf}{&kyݏ[6'X$t s8O<<C>^f+y<|Fs*xZ9aHe"N¹_C'>> @xGt#ݛ^]kck Gh(>  pR`pjt8nHyeXTer8SuD4xn2`4lB{y%xl6lʻd{DjrB"h$p;dgU # \%ef­U+@c|M8sIXbrT3~zB|(6AAD+B-מ95>AAD 3. ?@;  DUKπnu1Jand)v31a> ,эXs9'@c,z#z`DUT$IBĘV~{̒d'9TgtUhMW?55AADrM~܎CC3Km/xz9>:T cg{ p??45s._LNֵP?,YUA.  w܆<&<=귊 7ΞwSe枦סzvm~N69$^1:>8Ԕmk4FANq&> a4jk\k9b-yDĦ^ZU)ϒytI(#s Z{iR)y :yK$)[j(v24maJA̖=2NtVy H\*7  ;D&)/Q?tZKJ2f [Q|(w^`On^פE$IǨM[зGsZƘ{eԏv?T_G/e9u< Ue /%gd[{MMR$W*` *_tAP/=[xРTS$y\.[B4"rAAy;1,}=Π؜(seQ@\\cƫ1 oֲwNpAAMB I<<; b_gYЛsL&".\l}U]^YV\+U<ު@y՛*U9Z-h*a ϥʗuyvWUdPUY`*s&]ƤSѨt^"  w۫'7> s߂#Eqt|GoKH+' a4qۈNT=:p,r0^xe0Ǘ`k H9+ѼB$mŞ! M$ ^LnL'e!嬆JpJW"_z2mi NpI*N&< :LE7   p$w8 X{XYv:qUկ*qm2frl>=[pZ Nܞ+$<Ң*hyX|⚁&˦ydA:~uө55hWɗHj& m2٣Ì6^W둪FR nOU]/z1i6e^2ɄJboAAoI V5Z-6_?z0UQ(+wRb u]8Kxf: P+|97Q5W@ebX*\9O zʋ+1،wJӀwiXAFڪUU\U,*PU 5Q^Gunwc: +AAߟ?w $l&l~suiͿxh2bo/&~wYYW%HRU 5Weկe0[t++  MiEANmsIA,+  "   7.Q5fNx S=n̝arj+h6;dk(ݞz7t+-^CekDz%kɈģ="s0$l\Ԅ]Ofud1abgx3Ox UQAe=_|S4jׁQKXVNzn`sI4ԁj:©  {޳[Q-ѢQػ*ߙS{!"].AA6Dk/ JG[(i$!>3*[Y˵̜93{ٜ'.‘؁".-:1gR]++][$pgvۅ 4lߚnkxxva|01MZs[^W2hDz6YA;S-ĂGqܑڑv^ ][nxgfuIc֋2tuW%ϟ~<ڢɦB!B\kʀtuDfiDo.&$y=?*mnTE MW[\jB)*IhS:J~DVA,&&[/,3Q/apƼEJ}Fq[_xEN2YMTJpplϻrn΃*<{Ǣ[aLvɂ!Bq-ܶV %[~d]6حf4o%.hƙ(ZwC%ԎAE*ĄWx4nd%L:Lh ё4qҊB! vLIՄ^}> {msN_ !Bqͮiv3P>cJީ,t>1 7Oި}&Ꙭ*fC`P8.$Y^SNBჸ90'sIhVǃ*ˈ)wq`Jƕ`@w]u{oMK;B!?G73|l>>Зq[Qɂ'XbR=z L;|{'xAŤ,-p;;@~e>[`}VkOQR73ii_E@ZQZ˭Q&sKյ 04o4Pn#ZZI}7vI+B!L2Lf3!]d0}ytL&? & ]EWU+^w=.7& |^n݆{+.Bh š>/.@@t͊bƺ8>^L|q2{eNfws\Q԰[M(҆B!02RSD!B!D !B!2!B!2MB<Щ.kX~<ٻV-g> lF?lu۵.fUkSNL54إb}|[~ޟ48MDT`^i&M^o2f?pޛg9WVykg ˗a |2u(3V%gPD7t%0mjlسbcfo$ O!Bwm͕K`ƪ~I =]pgGnoqιJ5FNMHLdsi ײZBoGّ CPͩӰnes~w54FC ٱa=EZI>з7%\u/`FFe{q6ӹW'4CZ$<kƊ֒ٔWA-p_h~ZeCR:6u#B!$ae #o;6j<=<=5:a L]EgΊMJC`u>/yo] 8cGaT@aHk%VvΧZ.Wd 4)x-ԮUZaJKUIhG؁ hv 62n[,ؚ^鮌f/uͯǿC.7r)`ZSIG(5|{5)tAYA!-eߩ״׈B!ϭިC*)8JqWW8gA 0+oe̳CI>Lǹ":tioV~e<fSI7d`SEN `⤧1T3~<c>|o1ygOedXV@lנ`Z\KJuaL47wbS tE!$"HPgP-?iް]`@hj-!B @.kfI nDt:>G浅{ᕁ!f}/~wA&J!XqtV_?^mf lpzZ bR'J"EгM PIwE)_ekݜ)%ٳaۖQi[ƒ<`H罵XCT6~s[&bMZL%"EIQ %nG4;u%%䗡+ +ytlY侂<~mmՆ|}&xyd@yxX(aշljOghQ7-%E ~ OUB!b bM)YydB!WQ àzB! c0n3Zi=tCQ(;/? <(@ihڇ~۰*vbFY g?b#1tTUQ)9༸ B!/Pn]T38zVw ecdjzԫt45eŔOhڱ%kӽKSTGtDV&I$dO)qv=K@ϳ')ž'T>/}0,ͧQfB!R,gIZJMg IDAT5X˂/_WXҁL e+YWcOyniϾU{*@iQ>~%yފ + 4=w ,?(7$D:S 07jI/7[Ahy V}`0 r摞G@j>WyJ 14҉a`& Cwfx1C5a+ .By8c]S\fw-@ 4/iEg`V+v.1@7d1!B!~/π8vf-x)ޛ蚓3I1UC5HDDVƂTAtGO>Oz a 09M~kYy'C~ҥzDq2sKPPpU/f7ɴڜxH!B!ҝ P)q 㒟 (d(q-W\^^k&=xcu0;g.bI' f]dlc6aS 6jθ7S3La߆Md'^Q:VO #x-iB!5e@a+6yOan_RjPſuhkU4 75VV>=,!YlܓM{rK B_sޫ>+j]Tթ.j!^\_EcPQ"̋I|sBˉ˫QT SYlNTHPbd.QbYn4ME6D[t\B!B\S}}S.19+x5:XLSۚ,۞M{o^[3Zҙ}" 5\X: b p&U;cڏhX-$]'o?47 RsF< p:hU.W(s6y[+ ]QMTVnʃ%4s??-2qM5=/bpgr>uylhYF偙P'gH5=)ob%Y%(8/|g!g2[?zҼқ4p|a ITz!M!Bu FPe Kʏ. c) 6k>;)CD;ooGw&GlEqװޓ~7]rО:AnrK]nQkqwԠ`f=.M'#yx+2!6'[W]%'%2\D=~21qt0y6z,%:i{Jcy]5D}#U;6ei!V[0Tc+3^Oa>lY)㟝˟%rzХemܚMLb]D `ՂSg'ixU !BH>T4ͨ0((('=Mn8GMqM׻68\)Q_]#{6(۞JX)J΄ GB8y oFWoݞuħbJFX 9K@]BNq9iܼ"j%T UKv0a@#97D͍JwtJҏC8]_-ѣw34 bͦDn')idgS6`ҥU !Bkp(';;ɔ}ArY1nmA nO5ꆒr"hGwWD<|>P CO8|gpsۙSTq^m(߁!<àXK99r얎NF;ʫS_e9oˆ`]ۅVI=@`}R^MzՃwKG~q ܥ:B 7db7)֩ՌfjAU |x0XjMx~hF8 Myf4 T@IB!zn?bY<{OSр[cyj)&+{cq%|}qE= (>o@=dŏwh(>:|4Xu.mLϞƧD6%:|!6:(.ȑb@zl8;b꺳X*|.G&i>֬G7o`_,/Uj6U;&=h#.iǤaCFDXJ1q>=!R(&wla{kYl(B!L/nG$4oW"(.Z#a߾ǽt%j.1g](BNNǛs87FjѾN4u֠~4K#dW%ޛͩ;Dx9K|;g%&BB,TKʢ[v8F-u8%bji_'zuSnu֏/]snrhئvT:& G20L&2&q@]i7+hwsmKt ;NᬟbوҠ9s2=:wA"4]y|)TAjıy3Y|GNYn=؅B!ߛϏ"(d6咚iBh1|?wlvH괺½"#(B!o ##57"9\c"~'6֙LyoB!v_X2 =Ӂl'r- kxoUB!I/W,{-!p?4!B?O @ʤ;|<2z1C\auX0e[Q:`ZF֙|"vfh0>P׾bS8t(%r =`1oR~MOc"F6~GV euRq]"*B__~zL9 Uu,^şoR#R#.w%iVNB݄Z6 (~C|R >r.B-sEdx/>QJGVV1i..$\Y^ iB!aT 1!X"i&;}Y'ˣ+pΦ;W9XG.OEtxYZRK~f7T޽/qŲdd\q7t⎉3kĩL{c&_'a7FҧM<'ڛ~2agҹ*bę|at G3E$e!BK /fo 4lTc;jbDVmʲx\9'41 ^&>}1'\:&kĩ PV}4O_' cljn (vkڀ_b۔L4{Nklf;CJ"u:d/P"nu_5m/21EZB!gxYUUlv; >"ciJ\| 5cAnr2!QUX=fCS˜͖7أo~_4˶fo휃j80g(ioW/'2U]Ä'OBiN_9(=6A7ץQ&.T?#<307-'ς*'viIB!B\Rq]ng;mIAXOY.L`pHlɟ%26_7Kt~gSjkpCpvo൷̉m?G_s<׋5 xhQR3!aAX-Eu]8MBZDgOA]1[@yyk2SoeJG3KK>Xޙ<CAwS$>-)>wkChq .海q^W|R^{g!2lҊB!Fd@tzuj;W2uؽ^BnGvsNWyYmOUqtH<*)ZC1ҀIx ߬NCP/eƃ(dfOGlv:M҃[~CѩA5'i]}YZ = 2p~Ϥ} ,|vPz'dBdB!$aP= cfM#d.U#0 P3ubܒ#; L ІU3⣩Y# [ wi*טNb8km vWoּ rV~v!oxi[4Ǜ]JdNDXKZN+i-8 I|e$& AQ@MS(>}ã}c^;_Y!B!B{~M((*ٺ 1.elIjyTEőoOמV0`xŀ14riz{wBJO2,Wa`: v'ZS+~}׍L p[XQ4>Wyв lݖĢcLsrҨײ)kH<40TlBLC i!B!MKK#IY8E]oٶ` l0:mbɅݐFvxe|_~1,[Xuhhe׷[k4܊qG^W XWVIc=:uF-pGtOg(_Y+fX1U*բ1,|z85&U+J`C wTڵ&;yIڋB!@˪ba%VA?{/~̖L ?L6>yg0GX|z43"vwin?0Fqz]ҰDn!4:64=B9k9NpHow":ԏom%cB5&4䚈mVVN>¹s`S:kDQ=i^j&'^{9%J^f~DH޵7W~edU5C;S$B!5QRTefCBq\R3B!B02RSEB!!B!91P?aPon:ڷ^]$4ťU~^Q~WV6 F8߸OU1`D/ni=Mv⩱jg0]eIFmyppK Fs67'OFw&\dN{oªUj6S_[;ha-;Ӊã_V._ ÈahO|uﺶ?42'n n79eW䷬B~z=GwcӇ#3fN;PH[/}ඁxr d: ڽ 9œx:W\m1 z{T6Ȏ6"9~zqKÀ3^N}7NwЛ T޹iށO NKE%08Z> L? l1 zw'-F7Fuֳ97.[޴'1L>}c |c*]֤cFgk)>{ңI uHZ|EYVw;믐}t9'w|1G?zR#B\ΠE:gGu*:XGn d%:Fe!]+tT"/yd0ۈ IǂH7Q5~qS[kd1~i~T42ruo..%ߣR|e(&J a"d{؉ 0?aPTzkj۱(nҊQxf#3Kp i9Nb=aq8rk/r]oJ @8J]kFdÆ% .<ˠ{ݜ/VaEIaAD7QRꦰ&̺ 47!U 6_Tإݬ`e 5*։fR}^0"ӑ[F[b'*+QevBW~f|0?B~QXU0 bp;6kXɦ͹kX?80ϵJEL3^Ҋ4(јNM>-s[QoSFA@p+MRt0\s9pbpPT hkAq"6庩Dž<716EŅ"~J U,d'wN3b/( QWRG`EڨI@<v®Pg}GDD4"K| V ."iK>-da&ɛ嵑ףjͻug՚usnJM &uD, p󯐾v,R|7ӆQU1@50֯N=God|;,X 񝑠9b([6&u|>?3_Ǚ[;c I/zh %Z^v77O#\V΄jzK֛2Se҅|Z+~ZX>~݇Xmм&<<>!~Z0 QM&Tcˑ ʘL|*gb }|]D Y[#7B'c\V\bƪ*D5hogpnt޽>A\9sb\}7obG kȾH3U.㮷>ODziR]רvlܡZm?-ϦCf<> k7%BT'Q3-FƑZ6)NeɱŔeNrd\ z V Ym^|;8}VJ5v+~EhP,vVoIOٶ W@TΝ'N41z|9L^cTF4)\I/Yd4T / =Ϲsٽ`A}\b;KK,Ѕ?yŽhp3X<̘geYωSS]ITp;^:7ke9y^]Mw&E~ !o b?6cV'֘.ѳQ.Gc. qI&7}LeI.N!U&</.wb>6}|akđsvlc<}l#C=`;*Z*<Λb\Բ v| jbMQБx}1cDzj8#'ơUK߾1!cgb3J-z{9t&Zq}6uG3s1iU zs VmC` [k*X:Iv%5w6Roߓiꏦ2x[,_bg~G~FWKfSn΍#0 .~``WQO`\ǨgaNbuvPyЩs[Zy45pU%ÖrSTc0dhkt TyU&}ݽڑȧzsnr?.3?qlΉ!(GpRӒ1'6[_s {54zmw<0u?mvoEE_Đ'Sޜ7ƣF~foaCcҴXHPWzz<<وJsb Fu͸k0>2O ņlS;{dF<GyAB|44,cXl#FOdc>z䔛Kxb\BOK`]hԑ6U&MgjoĮl=S /=00; W182fkR.ѓL6nkMffDDɰ~DUUBQea’w^N}y6_^IYY1n CR|^_{Bϛ9O,b鋠蚁0woOޜ KI"*"&Q0Y0!fQPQ̢H l<ӡ?{{} =UH{t^|*&b;'Bӝm!/+u60 @LE -]T8OTTGuP =OfҕlBt!S]ǏO\0|6SZY[oF& 5{*WpW߂f7R[@M00λ ;vHI*3j.p]GDُVSWR<[ 4>z 0tpG(!BѫłI9VU]ן]|A%+ʶ"J$-:c5k ['٨YF=|0(&~^O٣y6ֲ,u;H O}]Ap,&j" $(F" 4d즪;u`)ڀBr2[zәIiA58pqRi tAٶ穌ՙcظtmgrn(7s՗٣ǣO!)J"U>> kehOa44Ψ ZٝŐ~Ʉj/  Ud ,-l&1.ҫK$޲J5,۔y/;V- v|zoM|J$I+N&:Ht^z;\7e"/>x7$N=Z *!(:!"{$aB{Sjр7*;s"t]g߶T=`` :j!Iu.C_S_`5b5Ddl^w iݔ3T ,:oa2cB~I9kNIU|p3iMB#ұ=xNk 6YW^̊i&v 0eN?ٌ.CbmB ~g2)SōbЬf,*&χzP_s#a2N "M ؒco u d ξrfLɹp&NP5l6>\N pH;z%ؖ Q 5:Xm}ʫ5@ښ`!"̏ŎnìCTUGW̿Q~1 %& U(-ҿ{B}d!q0 i 6J_!<\Uħ T9U^W&.ݘ8Vf?p +{Qz^{"6~ãops~09zm~\DdB ^OX9;j6ѳ ;T6ЌW tK|t"zs9v@MMhF4MEbPc7:*0 BQX vy'ߖ$h򅗀Ft+-*QPΣ2 ^Ah hhZnTU㗮a`rQBA?5RZ4>_Cω*\t2[ishrti㮈 Lffn\;AlGگ)u4N.K&Yk-gK$b1Dv} 7|Tۜ;m)4}z4=溴]Bt͜7bݢ瓛rDMzt:) A^ՃHL~*?ۅolbMkg73þˏnEpsm Qk |} ws;g45A'cZ;\AKnAu|f7LuW2kY97ps&M?cK^˘~yT_j@L~nlcVX""}D#Ijνd5dGU<4:wϹ[rP ˞ar=H{ȪY94z?V(Z¢?d0"[ǎVS״L8◗r՟Lܘ=. S uf7Xw9,AERom7_rkmZ__sJ/"mn"#O&I`UY>u\'UYm:MCJ#tVK?S}Ԃ\7ytT!=FSOiÇ˸V6̺!ohg>y펳;9.oAFR#g^[}3{錸o$?!s:ctc e">8=ǵq`;s'lf9lڳM[j{co\ "+HNar[TGkjeSg9@A.;-bJ.=#/SQ#ܛ%8Mmu%>m([j&~'ݱ/_IM&v]͡LDaN|AlZj5:%j}'hs 4z]Hٲh+ٛٗ dAo(bwN=a&ض C -U =1 Dkm%>BYF()m*a[ , +ؿ?}OEelȪ",*0AEA1^I+aؽ,XENǨ+fA 50Ame Z3zÚneۚL*}<%%Ը5tC"vmdkp#Hu~!U>n7q1{l5n/7ϾAf`-7 y >9po>?5b2ɢcWَYQTP~\ !Ɓqww.F|&/PURuWCL]RFo+g&-QЬ4)-ӇQͼrǮ X_BB9ln.|&LtIB|f |:v*=bF:%5!B]fͤ'&HF5h(*䀚ĉ[jɩhF|*=*m &BCe;S"B'Ds=et5c}ؾy|G1cY/ :%Ss)}G֢<.m*jpgM`~IV~=ʽUKS>߬Ft cO)Qyu~"~[[Ua dgUB1Y9dGB4Ll8\N~=sn֟P3p-||Ąnp 3f66T elRHXxMkl48]+hs7kO9ή4]xO.{ XHY?\ݬX͎,eޜ%lk6A"-`GY @~(EXHNh4T[>1@`5 ؃l߶Rg0Jj8Bס4TB=;[o^IaaPb!:&` SF]9N?-u b.l AqxI,{"~iJ:c9f1Vm0H$I$b⨮քA3aT6]ÓoFG` ,`>nCx걱:U9Pڢ$I$I"xNL W WC!<*DY &?B@D@$I$_K_|ꥲ?m syD^zYL=/(4&Uq;l^U.nţʍg1YHP4S]/cp{Oׁ'y~xHf#.† z!)尢Q:q Kq'=Ϝ$?;~|U/gάI<=2:+G:21Q/sp& w;ܸv˯U+B@70K`Y)v7jϯĝ/cHu)Ӯfܠ0v$Iu%/>{/h/qV}ɂ"cFHQ2BNh SƜ+G BߩWέ1|]>6?r~kg6Ώc8 oP%k)r.}H3\dk0W\z2 χ8&9B!ϥO']9ZϞΌtR~[u#0eŔ3yxkc0lI,ߺ;JkcKh '͇s'3[,pԹE|왇nl}0 8wȟO#8s8O|I$I9 zy'!2KNjWCŘ/lNP|pC7ϟzL$Zmل|XJ:&՚B:,& ]k:!u` XV P,_'zyusX,c6l6+SM i&l-%:.~ *XmVLf} Ռhb6cUjfblRA(Ʉ !M`9vSn)o_&VNH&bʠ\00P0 $`3PCً_cĦh#36րզ`Z(CaP ʉ0 Bݪ5Ռ@V$h`vxE:3M7 TS̚0H0v0^m40,AUb`5?a[L!PpL&6S{&_˿0 Th,fL 6+8pmb2i:(' 4@p{T :|$-Vp_nfkOgeBA a2a[d2MJBAHxyCNe\<#c1+l1zO !PU$!5@;eP?=n^>I IDATE9:jKnx"U/?=-of6:s81Ve+tA/4~ KrXSƣ<8-9wB^<_;;?)n:u{@PgsԺ]l.v&|U-*ۮIǯpQ; בБopٌLZd9ĹSyޛ"Gsr[,[OE=;9=&o%6&?Y@5wlck>g#\4n&W0%{L {?{1~Ca#8ow{,F +^׾LS!0  (B ! Yau3?1-\Ln3C Sn,Vyy&s/NE`s3{,y)J}>7LyM }:]=)H7/2yeӏ׹g 0!wj~Fx+C"7;2PJrM/wƗa%cR8:Ky3aYk,M'S2AGw78˳4n7?\p>Ur듌/Pq;o2(3_Sp|I]Jwr OQm7B}DLԣńKDPӜƩ}14'.gˆ[?W9t # cקL5A8"CNS<0a>H,g|_~q-& y4 we#;ҵs2No}쁬yqmkd!I$o@#4nC7k6Kyo]-CmK JU>;htM`@4޺ߐ 2s+i/q?:?G:V,v.w-\2pAZ~ퟨ)&ܭ$GF^~ztW <.U#h~!!u-^-if4gW F - D)Z!׳o#ζ+GGV !7sϔ;ZE iPuZã~DӋ Lɺdr90Ag=O5c0 VC Pe3ML07|7хżIl+vފsoQnFB /&>>aqvMŢi,:=ܵwlWշYK5!>z;j<;= WW8 .ߌq]c-ֈ4bmd3_2!w!?aQhuwĩ$&DhK">-UA߇ )%oS)PN{ДLb\$gEJxuﺏzI!%I7J0t|P{1OE`a%z<;Jh1+x8p' APooM ≌hj ЬCTT?Q]:` Ͼyb:n>/[`1L ױF vc'?DŽ[;?SM+8̛8ʧYߔKFR?vK)­""ii <G$fښlx}<](=ĈcgkVp[ii  Cg!,6kn h?!M=_M B !`H i=|fțOΝ|IK@`M7 4x"&ji+=i.!U諸EA Dqyơ^Ó ϹC*Pp'I=+׺/P] Itl;B0-y=~ -hن&kL-vzv3\X){Ly'Μ0R/$IWUSp >ǧfїO/I٫޻'Φb|hAVL}<|QO\F}3sT PRwb>G6j]n潮#U \E гGq9L;o7~j͵̿r:_(F43iRV|]R Qm3o.w>U |~%Q0뱫6?7JkY"wA v,&1'ݦ`̇rE#ZfϤ gTNM2, 2 ![339_Zf,aݞJ&4K {I:n8snLXt$SgL8-:n(6.r'i|Nj)gQ5+Sg1vL:]D'Z%L !s[|/BSf,iӸф]x gaNs`2=%II#.׿lNDU;-XČ"n[.fϢun3M{wq"p;ď4C8W6~~Q 1<8u)Q9w]G rPdϦPO!~ .iM/ pHKzJ Ka~%g D$`U=U/੗ᴈ2muҔ$I}& ÁiT̂|_.%u%ʮŢ1S@h2vWv_B[%ޭxotc*b_m,'_ 6`C9}2|o "k8]a[F硢}e-M-4^JJJ_HlNĚ[نBre籷؉hfgXS)J0RKuk`0H]Yu>6&KS]5?,]k0sJ0:/zή{(sjf{_*- hjPOy۱Եb|T~MasdDCcCY+ ʪE3PC~r2 -o`"4]dOˮ(W"|@yY;Y@5+hS l6>\>|S :UN7{vYBw$Iopnk5y鲗+lؓ$I$I/;WQ d$I$I$U= BW fVڋ$I$I$'zilaA$I$I*_$I$I$H$I$I$I$I$I$H$I$I$I$I$I$H$I$I$I$I$Id"I$I$I$I$I$Id"I$I$I$I$I$I?'Bjp04*mt fRP~$bQI9৮)DJ5AcA=.Q50'AdD Z(DmvSñk$I$I7uDK.eQ^2.fwy~D)k^x`W{qg.z1BίLxiB 3܎y)TqA:ikZf){^_?o^JC7uUМgBWyy2$I$I#z@'c`9eXn݉R{?OEA&Z:wN9| ҅RU ԮV3řf|K_RZƬYڹG^_^NKMbyx"S'CL$I$IQpTēoCp#i[-n+'̜{CCI9J>?° /Og܅6/DySZ&cGٔ7eI$I$Iߔ$ 1};R_AM[D g_ u>]`iaC VHH?TFC.ɧіʈ;q!8L%>F"7F!sM$I$Ifb⨮D$I$I$?ݒ$I$I$ed"I$I$I_&pXPj{04*+ħGa>TtU9tydc|JgjDp=Jwg/q&ξ>6DWSKxt`4}R#DY3&oZʺS 4%'R4\Du

0ᗙ%I#z@!gr}{,vz9^4{t&[ߠnTbP-ߨK{QyRB IDATn=Ƞ?Rciþ%51qF2lP_^]mG&c+Z 3rE2KA{oO5phf{1} 4"=ƶV>]5D&7 L$`[ q@&Ag-=W噳\sݭ@;Sʦy8ĄiC-pwE\% r8a?|OB:>xwY2 \VusG}0 "X:4 JvF֓ "n=ٽy%r:uN^CnNGуpդ3U$uBG`kI]TRLLy'x~ip$p^_;f+⻅֏[Uj0Y$ ]đﮢ2K8!y5\B 2pqd+mpqbs^pZ3btV Z'%mnډ[}Ua!U4=qa-?DY56K] Q^ĮXBߨx+OZ#%*^|"6t\x.]lXA 0oJyQ#΀\bYw >7eZ‡%>8s[cBуTT7`:Tֶe)𵶐SRGAY=Uun{U9'B w)ҥ)E@:* R"EHKUHGJNB IHe9 Mz}Zsrس>~lGWw1=Qx69nv|:" F<2K(`7 Ž8'/X(>xƂ?*[_@Ժe.Wi7N쬃+Gv}BB cwsc~|߸#^1* ""A|+c D4,7F,vϹ͜%JhDxF V8hj401=i {Dri VƁ(ޏU*W\dڼˤi~*'.]G3 _iCUBFĵ-3T2D@d?m, `2cڻIRPB*x Pju[wsb |!r xpsHno~v;",};d4Aj# jT%!:"Ġ(ʽ"/oK(_9m27\J*a\_/A^:%a (* {F vբ|HH&DFEXz\ȭ;T, d@Oyb97s;PYD ͠|d(ΚGϜpHՊkV\KL|^7 a#ӂj!#-<3ރ!nŦcAԨ;YAP73y+v7ˊzPGN^ATP)ʏT4aTS>Ưlܾō074 LE$$br wEV`,,$!9‰ڑJ, (T1DRM^$ K*;rXaioN*a T҃J`ceIɏ]~~OpVR!K͏H6oNϒ51ن&Wj$>n5q֍e<6ŎSl8&ر)xVI0;}IJ˷ sj.d՛vb댎dX.?|_hu}qvCq#2o{D˥J퉏x8Վxq\z>$fWm˟ GJTX&W^?eYz?'lV;cqG~+~K ;1*4YIy7SV /',1 KRޥk dg?LMqnѥBd7 Oư#Q vO`cxԫQ)a֏{,sWvr0G3Q!7MR\çVn*<#F˸%cx/ ‰=[fIcoD;6ƛXNt,*z"JFnnҸ{%&fJ̲Iej:ҙ ;| KP9d9YR;J 늷ɍ?eӨ]+SQjvߎgX Zd$؋'CoaR Ov8{lj-'7MDL`Y _Y ==ax;dI3$ C/1sAdԋKn+"MЗ}g4w1*jNھ4Ԟln6Mu֯ 7_ʅ{ K)kC҅b UՉ==mGswCFp+`N`惼ܵ'~z/6Bo7R&L X xw-Btܠ;l . uX5d!_M;LyXj]-1:o`sp8uf< \ wHIëi]H Ȩi܏Xf}i᫩Ц=N&EIrQa)ڝ,l:g&Ύb.߼.H &RH4R"傩Pč䘟D(槳i8?%]yT8#+q%m)z*W ];qWI2ȠPZ9^l"Y&uCIg 9IH.D鬡8?1=a{jK,٠"{\#׷ 맮 EG ̎/۰ 4 %6RwvE+H3 N9o19"sF<_C=v\(Isw 7H,OjU De-ڵDPF55:7f¥X,!4`ܥ;K(_sW%Rh8Mh\#$ O1Ez;_`'&GVk^wP Uybʭ4L GWZ;(K{6 P*:Sɕۙڋ%`" -BF*u;5|0YҼ ( Y8}!7ҬX<m9om$p\])hkc,W:3O[o'-||szcȖTg,Cf2f*|bkneMf$nO?`m7֯nGo3]~ d Yt-f ḋܼGP@ | ӿF9M!]ҥ:'NebSl2ig5Zgf_Zm>, X1?{W  dYP]mqwُRW` {CEtf~84:SÙ|Hf|n_Nff;R\aסY^W7*ATsT:icHsƇҬ~E"*`5wƞ+ZoK7Qʹ"Z6BP{Ѻ7 ~( !՚V!Ha/T-'9zBuNA(KiˋՂFcIʳ{!CR7PԫKWqEdԽ[VyIٔTśidPQjjk©犩dr4^퍈@x*Ȏt(T@DdgT+ϷK1Qz8 $[q~2SIPGE\LO;Z;i8񍊠: oL9 &+Y K֙SBOMIR!1:<+B07ҟtj]ܩU5kȳBj!T)-le޶LC5 LErs#LVQ>“hKϓDy(ҝl^F͟N-Hx%IbHV )b@L7U5P~ }0m]z~!t.4j&).LƕV ˣQ>i,kf*oeG|`'{oƢz`|"!èDAF`$B+WA\ !7S~39QZkD%*!;9]P7]2r.ل_jy`).vL[D|vn)1x 55S=+1%?1PPWJPK!XKR(BF>8(1@g{ƒy;Xs^1r! / Ш 8 ؔ '"܇Qߚ| 5F(BvdV 6>뽋/v\PH62hԕRϮ_2eT* rծ)sX1]hڱm t Ex/)yqfZz,ԫg1lǪ̗R[ p+ǂ[`QbHn^{n9'ډԘspܦOS};Bb4j_OH>5_;1g;̱7{ J3//G *ҶEg8K5!B=lFA.%7w#'8Ws)ѓ)'Nf26PsRŗn]Z9Mos|Faޜ=Uú94#wkI9OgZһ+J/vqO<<]سf-3&ѢGxùQ=UgdԢDhO(`ʸ[%̪5r$ ^XsI,zDR)dBE1%^Ec^_{2fѿg=N]&fw*3 ={ $9Jw^U"k IDATNEՁZ'ap(DDI'Z E"ފD<[(z {0)"Ak)LDT0dPݙ>ߠ&nrXh}ք{S_w)̕pۂsH>TpaXVlŶ%=1'S 9(av>]?Jg0| ŴStF|zT݈IoemBY٤]Cٯˢ K{ FlU )?a oɖ]r3XHϭ5'xVT ǧ XS8w=SVO೟Z/Wx;,Yl Wꑚw+E7~fߵt*WNX>/$jli1勏|/xg4)g^3c.wVɐt\+&,"Y%:ٟ=X@pqPS~Р}[)G 8xZM^o KI1kɵЬmkSvFcG -3揧mUa98<6G" O jD2v BY=_/@ 03n6l-jHAx(uK ;(Ԃ\ APB0 rK2EwEDkw4x* WVIv@q3{$^]ͿMyWn//tFDTZ55{;h"#F3W0% ZCq+eѯy},F, d`e̓i?9hx͉U!##6m/Oj(Gn1e@EGVùC>߿BN-7-oY\͏ET*92Cp6&oi|ӧUlX˫}&ɢ HD۾i [͸eު.?tV(d8צAw~^vUoGkĕ[hAt$T*"zTpJ6:$3DzwM?&D׶23ͫ$J(DWQlq}lYvow*6c˯x(@Av\4q82|d}?, ogU<:V3jz%)7W-bnc S ڮ6̋]?LӶQ\\fb#?rr.=LȂV),3[m VEGOc䛔j5x+pdٸ %nL+돯g*%sq9m/{R06v)sIjyZw=z2%S7zC>kzr ُ"7]ۑ4ߛ /;ֈ$_=ދؗwtĐV HSuU34!SŎEx(jJMZy(I9V#ճ99o0jyAA{͔垓^`I=93d ~>UUor'Tc $?7 vctB,&*.יI^  98ə"Z; . #۝|ڎ܎[5tVςstm?Π.+zu)oTmJ:.$Pen*qr^n.5PI˕ӭd:{xSh8cǞ-?DjV' ЪDޘC^:/Ll)o ,7f4/5}74/t:ȸ3یQlJq+/LNQ-֎Uzs.o}z\d>ًʹ*)fa|]C##ێt>Mi E*֏ӭNXJx}mդYmUHpVC!dfk&L@-¾_rQԩ5!!eWtO=:E79fm)X*@jkb *.JX~Ҟl=6h~ Yv<*W`z P*dYA+.;tNHFR("R;ob9(. IIIAx;nfEb6|&7g^|2>?*1wz1]}d\%3)Tjchn&h?~J/Sp5G Y3teKj<JK &b8D0'->3>ݵ?w1knk]:sghs1/ 0sǂVYCdڤ<QلwWb:Gwq א}$/_@>OQq ߙN!!]yp $s'֎4Q{G=2 v\DA ;.xw΂V(dƦpϓ{yKjd&uRx&Fx(D եIF0mXP(ߨ sǼg3)Ş)\OL݅{X+ t4@P&F+ ^Al\8ݝ#9N""g_gɢ 2ܫ )V(.\-@Dpt+ŽnkU>|La7̢I2bT$%yz] ğgOFwfJ Ya~9WD6 v c- MHE{N%=Kh!ص[bT"6\՚wdk,4t!7&q)\Zt#ւ}+m&r(Q*{A/j9t'OrE/oGp6E?ݎǰ 6E 7KĘ Ğ N@Vfy8iU(pʏjYBDtm`|j8Dž+)L I~lăTbL܊+]^~ I8i Da3e+UqR8ɸ9 + e@T*KGFwGGv0Joz!4+ԜݾW ,S,Z|&uTgr氀Sh>v3~B2JQ0X YF4C>nI-RF1Pk HR>@-FIaO\AXOSrx3f^ 8$U|+\*~vs1IaL_u|VUC$/YSq OgB6maƋiUh,PE{hKA'ը׹ ]q!Qԑztc4n+v7XA,BIMG  xjt*P|I7LvN0zO0t *^N0ꊢ<5oNN5qQ 8 )+ \UI|/=} YW5m yU"O<$@aZsع~zeސv;J}%]AT^V`u;_AJ)i bRh<epswC(;KF"`+z5hQu"i_*^šmgvCdܸ̥D+Q~̽;6j6MyI8;i\F]n$ɇ}CaDDz=.I$Ś߻;_ۋzdYF=ȒMWФ_"t?GѴ|cٞUޣBOڵuCJM+Yq`VnQ(ظ.ldl !k(1;tU#t`ԖR؈$83 Y!"Y Fmj ?%!sL[jJ Iy8{d=gPI8gD}8FD 1 {V!,{!Kd;x) $tJ7$1K,ɎgFJg ᎉJavB@Gc x^WT5cŴ8 vqZ4qzUzw䀨ȣHC`Leo/Lə*dYKú~%hRٔNvZ:F7*@" ̇ޞA /?*bww#( C^1;gΤkhi ]{Y+(FeV yF 6BlZpBG-y!?}~:,_pB]CTSj3YPb.RA(_ꝄԊHGx:Q6܌|⓬8e!["'F|Gڬ4څ?5=ylyz/OӵggK"9v+іҷMI]9ݦ|DIPh0R8j3Nڒ; &ȵMxI|udI<#B%] HR;.c>}ȑy|C x6uDxn]FѪ|}3~$:%{|?P! |hD܍MK:MfU5E йܺȉ_f6'7pZX~}pv] 9 O6QNPX{4r<~JxkYFW.^IY7% " (ٞgZz .eбCF _>_^-ux=}O""qWYt7`~c 1?'EQxQ@:>x*dA^UȖXsl:zq$!nq[d%ALM ׵3%(c4j2=ǮewWT.!ÉU(8H:tn;9Z3p*sU` PߕQH<|um™)6a3g *L;#Ǝ+k8̓Q?'(!c% ztomfJ!(8{2w; O5(*9+35<־퍑 >c%gWXXyFc'7$˅ J+b=ϵEg'1rjNGph:6ctȀ-^kz|`gX\ô}-ƶQ3 \Rje>0M:L }EYFdۯ5S5+I' J`.bl sE5ZH@;pW2a4gOFP`-~gv0`e?ϤI3?̎N/>Μw;/S]Wvҕ#yώ μȯB>u՟/լf>?X N^xx(Yu'= 6|c5@Xw×.f$9GT|R2t(]9s˶"*0Mj-$8{jk<|z2{uR?f$Fټs6]fa^G!vSXn.N壻h +st?kiV-'n\ZNNl6 ݙ~ӧLKٖ?Вlu֣zpx+Xwg\K!|i/Se񛫼4v-]lĿ6mQ?.jZ`8^y0el`ƻzuT y33$a`_vPGAKy=qk7}3Ct E JSlԖ9R|_T̚?^Rj,wU2=WbӜX<;BBE2w\6nŽ܇u e$t󷞍jǞdɇ? ě71hV?qPnG=޿A}G۽ē莟$r.IƱ|i*z#phu̾8=4G/WgxJQy~lјffǏShhry{O֯Dkozwqy2]x µo9p]8C\}THPa#'hc+3lSQc9Oy>;Yj'O]B'fn:2R0]Vؕ z1?}6!ȼHuk- yI%w#e[3傂Jje UXC + k-$#{ 2g燌~>︚@XU-fEɃO{蔡%i{CJ f~a34odZn|v7Bd d fe jZ'hT'~ A(uUAu#tZpAR.\Vc  /~|%i& e~P5=^OI$`(DIs"z0&&zlJq$-u2񠟂 auHgh`7!PYlhْ$.;nOEmqgJ2p-uQn7I*C-fRf^/AL]KMe5ALӄ^#PT!)#@ˉ"S\\'חHIu㭨&+щ1XO_%1ɅݤAU|> Hٶy IDATz<H2iNKk.dO-Ap,Z|u^4V+yPql8zdUO\CVҢ:H8SܘJbyl*v dZ()%LZ@E hCjBQRTZ 0vKD8ZVG\ˉ;SPAQVNQT!L'եՄHMG(oҢ!a[Ir%euDVrMM:Q Kk*INa˄Jf;i8~TIGV2Μc%zTWy6i1Z(徆Տ]&b eU&iY™eѡF;d:OT#㽋ɩv<ҍ$'fMVꆀh12%j $M_G$n; >O=UT !%*E.n8:/u$$bk2%R_Kam$wAdDMV0HXuPUdB9NAE47J}U U!4׉bTOU5UsmL.mpt&1Hq+<c*HNIJMU +XJ&@z#ɉBuoDAch> 5ӧ-х!{i Rmȱ0u#IV)lrh"5>b*N&$9$n;n5̋*I$$:hL୨"2D;N%hm$9 ~k%-yYTdRlMZ(EGkPf-(A3uUԄzn.KIM1hIK6S^V\ [q'ر5}TׇQUV/fxXA) %ِ"Z?%ՙpeKkʶ$FLSK'vzd5Niq*1%`U bYKJViC FDS8ťՄBht; zƴTV@N|"Oz d).#L'1F@}M=Z _Y5htRmhhs"H^Zd J!TTlD:jq I(G"zcj*"Gح$:h$jђ {=F0۬$9^BYKiY $lMnK,ċX'%9%|u*}qյx yox8\.=9 B?j9#UW7`NRF^o\ AB!}?sWA'V\ Jl6{8J`}YW~k椂 j(DAAc=    o   "AAA   "AAA   "AAA   DAAA   DAAA   H#af L%NqeTp8zF2MެRW>GeN+Vp(Du]d%2 NbzQHӄQS#UAAro}a. _-[\GAP^dg9tF]8r 㣗[񉻹G"^o{,`sqyCR3u zaO|-ƒȖf AAj}(ޜ>kאz}P TEAQUT=\7) 5TEUrYL0ҼE[BGn?ҝI!>/#c8z8"=CA =PX/AAAi2Dy~_TPvt{ C_jKm͌j¢P% |UPTm^f?R_ .7l/w#F`  ?ωU%h"FTK~ *viXxi,Ib݋ nze Io& i<0w ts2k“EA{?ʤna!O+F  r3C Ij-*I_5CBemxw1n z <R &BlFtHZ \%n}-wGAAk9ysY+\8M0T72fӂiQYY>#wf6 N IIvz QU=޲`kiIΠU ί5갚E"  4'*]kfՓ>I%ѪU$IȒ$Ȓ$ٞV>R503z&y;H _$!ܥ mJS_f^ FFRlp   ?jZ'PÄsEQ+ Ie$Tb h5 AWGMkIh42rcry42SQTIF#*"I ĽAA Eѣ؈e$&Ǐ?GO>,7 \$4  ?,.   "AA!XFU%N"B'm*)(#/;93NmMgͧ&&NVnįhtirbR%7Pi)58zVZv,D@[OAbq9NUWRWh:!URPC$r4xo$ej+)dЙtsY`}U4oYxWǡZcUB?5"ȩ*$e!ՔR5iifj\Y94sɨBKAIJzRMhjWӘtj@!Ekk揩زrIewspU0eT>CRi`T" EUx w*қ`WDMI(=T@YPH%רNB7[+Źj^=Y<-{HS3>ԁֶ/>ur֔7{02:-gRSxHSxiΚ3ۥS`<΋<9-%Gyv_4ǹi9N}*NuR.yn+_3UŐyv`T_-qC[[ɓPx̬19c~{Cxb ﯫ}wNϽĨ>N޺o26{Lu]*)-ᑅyiĊS~Gɑ6lm$."0ﶉ\[z6ase˙i1iV ,_"{.u׳EeKF{UIl|g7'X5, _┓\Ϩ+%O2=.k,/L >]83kpm>eܶ$6&2}4$~/N}ףA04Z5݆)QƐu1l 0s mj+a/=g [3qE&OlTFvU8CGnzt*>~Ա,4l$)h5J':E;|߾˅_E WEF\L,C#%Z'rC7$Idw;ĤS)؜W^Ɯw1nE7wHǂzXO嫵o3ywtp.oι 5e/0s^|p*q!?O=QCXb+g7_BS=#~3k^͍=!vs!A|n>/@/5|RüY?i0f%!Xo_f*{?kR%䛯nfv͌*^!-h59J˟Xh4| ?H:PDm֠A^m[d ՇځnE&HNAWuZ `HM s∥=};9rq)bY77H6RY\J >P*+8R׵*h7vc2i*U|CW5Z=1T1b0]4L$'v|Y0?HRN;Pcq Y*jdr2IHǏ-޾c^pNFhqڵ_N:0jI[%rgG:dc!e|< & 3Isbԝ($Ydۛ0GW6 菥 IKZF"9$['֊CE#W`l.YOte&+Y8k.z~jO^hY;cњU{w F^UMsS|-Egn'r07/йp18c#ܨGa2ϣv[r݋:-j-*f^#W9_I׼!`dܫr8GGSEHJЭU=x[?2m2w󖪘[8Bm97O~j$8oOUIo=wVCYzK&*-eYQRr[jck<>D#D Eqd%Q]6JAXU{I7^+cKxmαW`N6"S 8 4LR+Dd{*xLiE2M9DQK7rRKo vX}pYoAk>#udXuF3ӓIȾU!-%qټw{k$;; Lz2,T ڴN'jߝ$) = *:]>Q<g3 jT k̼\~;j`+> R<>gS`+=z=]d4TbA]R}d9a oxh٘ #"mp5K_XoOcqp sZ":tB)\C"goάU; xQtL6K1+[-dx|\G5)|AώȀF):$aiı} \0O;K>HRg~"PitjI P]zjxmZ"A @ r`T.eUsybe]yjxk"Jt2z`2h=t5É(LHoO9L|3JHF0<5.,;k~O)>) gu2 j㱹K`R,[ o,PxE;aPSǃzgn{->`Gu~;@-{H?0`m gZ~rFL  & A +x7Iqj@X,BaD h&>>39祍?%f^?ϒyq%5i=PU3WsK, .~@ Qo'? Q`#H4u0{2$iqUY3X% K~ +plrF&5ɂ&*fe+')j ru9M 8EM3Ujjfq'UvB0U6ard<!݀Ӥ+HhJO*4qRR'#H}@ӊTFd̍1PR&c1JVO`5dP^'Кu$cE Tyc-憴 Lt':U Z:u1JEu˂{I.|z6tdx dԒd7bЊOUt$6cat2JʀDMx!rlޓPH\AAAk,.   [D" TUKQ/?UyO߾70UdJ8@7 Ba/Vϥ)A'Ә-eY`40;9LDixFכmt=ylDRH zqRZ^LFx 9!lVx'MY2#9PU;%$Сm685b1Zri.0>ZhK)hcAj,v:u̥U ).kmdHiNkFG6tlS6x=%Reѵ]&.} * knK)شQ*NO62 j(pcd9tiL]a5r*ih*.S1a8V:ϥuvXZҲ]Z$GR8Wsrd;e57Ѫm.[:(oF3mҾů~ &Bi՘tGf A?7fMLKf4KlIlNtʥEO}hxF;:5K!7'fԖUV$\iitCۈKD99OmE^RƴLw9~͚gTkˋ ǰ{#JqΣޑEf=X*=US5|ჇPl.:w̥Exs`wҹC3ZFҾOϖ>Q; W/UνqIᳲ#oGK~wܚ-g޻m%bYGh2xN^˓}:;M]W\t 6yu} ^'R׌69ʷz6NcܰP]R)9|oxwMd|̎^TUGy|a_!"x΀d)+Sn_e&<ϵ1x5]ІA%q,Ƅ+agbێb<5/S91`Ɍdrwӵ} 6C1v_XҺEF3¼ܧ%+ n-:uٟE\ u .\[8rx[V}*&RL5n[]W”$a!}Q{~&<2n eɨ?[Jdu/%g-qήpaL.4p SxgG4,NGUS4i~(uyD<9Jx'k77wæNiKLREyJ#G>G %iS;KAHoے聍@c"sXw(roz%NC)DW}E<7)v9Nd܋/dDn.Uֳ9j#z|㛳*٦ 97<]o|\DT?ރt In~w}GM.qOG$gEU'{Uʎ?p3oO+2v8X2=@s>?]!,LM̸/,UOo!!voePhbBQM{%P0WK>"n(þ :PD%v)k\')[cs)*sLy;i)DLdhV!;EE+A> ?Vms >>(tbbˇܕ/f=ygxv{}n{S<+= Mܷ/Ovq:lc>Q?,Ai|H-Yy\'o3| *(9eXuQ1_lVt{g2٣a+?Wƛsgr{^N-!E沿x^<~3WjX>m5W0 Ŏq'OUr#I~gǫqK?y#9Pdmd]]} <{|S$n^رJɽn޻ r`ŸxB/cX4r]R}.k gkhi|amL%`fhw?4[?B%\>9ٍGVcҥ}}kl;~x㹯JY|X {qӳy2>0.)?n|uJDF օi_щwǩ6U<|:~cJ#2y>ֳd\-XCK/c5I\:d.t&woS=oi|h|~>M/n% a%>5.Vwgf;L^\7QͨHt2e[$Q_1u6=oD.vkMnd7;7˹ٺsf֢FXSܺ(I]qa>xi;ӗ%8JGxK_Mtj6f-kVq Z8j?|HfY'{6R,@m(J};=^}SөG'TtGoܯ?Cm(e/ c{YuMG.{p.PVO%¶#=O> |h˃\zhb(VƌB\ _lHƌ:yh]}X= \Y18jm%fMQ}蘓 |ZEivZ#GZ.473w;y}~4y-CtoR 2v-(\1cIj$H^AØ=m~ªmeьO!Ț8#?236*'O{@Ĥl#'SAhѧ=z'/lidS^N V|>4Ū$b&sjOzfruD3$#Ɖ奠v $Jz fayIhԡ`棿YW-eS.jїԘgʣRWyȡ5>-O̻C[chumrRsHo:w F,֟>*m<#ne<;u8.ICyA$ 'B~grMI[}ӧ1ۚ`AcdmgcV?i"]ޅ%3'4ƨ_bȐ!ݪA)|@DY/`•,Ҭ Sn|HiDA.E2 aJrּ݊##o}GQy`kMxG(il OCm1a5hs /䵇VQ(m|7L.b=84*:CA B=k^&1yT~;)1[ dCxjNX0$W$vTyIܖzrz)Jޠ Mbs8OM ]%ʥC91Ou\rхtWTu|q5{q~|Px7U?}2 Z2mny+bS55YS/:41@αu; 5& &uN-z)I6Lr30 5XM <%)ϿO,Gz_x=0xm؋/6[\W Ƿ_?ձnj/yO~p?L2=\qPj"pN+fMg{G2p慧fѳ<giK -&%^-gOaqySnXQQJY<1n.眪kx^LL4w~䁧$珚vL棂5wr6,ؾJMz=E7rbJBh8Mfl_a{1>[Ybxf%wNI-%Fgcc-@U,uoGhgđWN kxƻq#鍧j?Rz[Ya4vRc+O .W&g>Cؾ1>:::hS.)]yu Ÿ @jƴX_ +s>H8<ڻ()D&]̰*  PTT\\PP *F@PTVL NL =cqѽU陞95MU[0qy|lp?{qԝz ⼏T m. pqNW/^#?v_:Q>+OHc77QT|TMw̘.t,,Hs ,kC!n3?U鵒 u lC[9O6k}CXrCM M-).7T l60"PRfe;D^vJ,8Z;3#:'Ĺ|; ؎yF eS;1~s46GnzON[JYMW>+؁@y1 E(>’dاT0T7Ҷ}<7mmc0#aL0ԣI/ȰǾ&p[a֔SlU\o?3Br 6'OMoѦ!ԍW72á nw8r:fbjŌdC(Z:/b1/|zWɒ{VsqW/:i{l ̨3utoNspl ᜁ|"V{3Yod߹Y@va@~ u"IƳ狝GƼEj+4Zm'0t|Q[@}^3g|t^z&Fvaf$Bˑ־6Qn†E47!dg&HMEj  5{yrV칂ڦPk/#r3} `Ղa~!g|zJzxo\R{톎emqLavٝxVV>:+F$=0z_N΍Lxl^ G&7[ȉ/).cyAIYωjxȣ &*\n'u407щ.N80`R maϯ:n4=Qxnޛ3*1Pٱ 7rpPXߪlњIq$q#wq}S< ㌄?zNY_T%3,Vn?ͽpxȒISX3`9VHl \W&Ƣ̤ Kz1YCL 0,ОhK3[_6ASlSX^4bzR:1Շ1 Vڝq6Oqkq8"84Pe*jL57Hr[XmBNA)㢌l>ގ=P+4M8-@4z(5W䧦3۵!a{RԬqgBFvZGUbSuTTnʳpZ-UKn]V5&#eÉ+)O:tʩ]<k~`ABj0:FP뇬hd6T  u G+50BePAnX74( 4VoC)ZzZS:f}[,jg=7yo7VMs <Cˌ|rxI~\mعCow`G5x.JJlp82$JrL**(˧<>^<45Nf=& -\ʣcwwwﰪD.3xmݶ_Ηv3Hq[XdM%RPJEAe"m*9%T6DqeҶn9@O&ejuоm 9[N;ͳ89#Ȋ`:pD6bC>5?0~ٜCjU1Ld}BzVT7@E\\|u}a]ĒG&>S&XOi_nK⌓+`ؼi=xnIlxuu0^F6ڧcD?Xo6#yLxlNuJ{3sibǜ?,JȮD eؾU漽rRL<5m.2XkKTRScZ,fgС|'ĕu+0]C^; Gͷ5y\F(Wf%*nΎ/Pk5>0h&X'VdU]EnEdcΙdVU[iӯcKb`Xde$e0t̠0Fja@j&z<6}P˶l)p ûx xH5 Ov#͌'eVLlwRO;'n7|};Gظ12'k,e}:qޙm9`f++ĻeUDۮ8%"lR07k -=BkZ ][ڬf^)IDAT$/z% ?Vp:lMX<ٮ4^ϣ_']A+9|!*/qv߂` %V]O @^e''eggp^zsAy ͳLXnȓgY)n<>:i툱iN{[:Lڝ͉\V݅krG9 g_AJ\;k_07v 9;㹴7W$ZQH)xTׅS01{ׅkX6>5Ҹ]V5F2;!LۛxC%Kl84ޙ+M;䵗"Ҽa)dR782! M~_ ri5Ly*>Xh\{U0Tgsxj}5g fN 1DZϿwlK[~o ˂8owFbqXfgW[_ cuvVo5;HM(l43:*>4pa6]cF1&\v̘:Ez_ʋ$SǵwZàt%Xޯ0_';Ә>}DGٙ#\1gd1a"|rT,s73^Lz`%ȗL1w.Ø|6q !*M#:̩-9œz۰h^g^^Si88{pN)7X&i /McŹy,z ^4݋-X˗ΆB,@w$|S@r+ O,_z Wq|v2x\0_GnMqj*=hp`xvKm#AډpuC<<}>Y<vùsx{6|W7mǰsƀskzXz|g5:rCw{o;xM N?b^5iKr9^:6x9dy}8F@+r2m=5{xO8WI^2iWnu÷ubjw6[_;ta1WoQ4Gl+'n0dYLOʗoWKY&yHa: <ؤ O}]‚3dsQ&D{Y9ww'c5 gu,X^|9s8.FNz&qݒ\,.7f?c/穙pY,s2q\0$aL갱lZ&p='|yF.Nn̬4Ytσ&E~E| !#O R\2p&yHڏS3&BX2V*ʪׄd/L8Ʊ`cYl8+~jCtiuݯp|w;wg eڈ[XYb`$LnN]q3 QMMT4$=; +&e~5&q^* Kf`}ExScݼD#?'gA󉈈;8<Մ~m z=WÝʉY^< ֖uF07?t?"r 1!5,DQRT;J\d$Џ9DiAՕEDD+zX)>뷲p66*ǰçdºާ%4#q PFBAhHc- g_΄ܻsN ;-ËVK\y'ɦg#pϛDDDDD?v1 N$_۟m3v]=;EDDDDDUw?.n=Vo,; +|@*q汧*B!ysqj*,#Ao^߭ԁe>%]ܥ RB0&?"8}":d% $j+eZGDDDDD~mkw """""?six h\;22]َfX:^K?Pc@DDDDDD@DDDDDDDDDDDDDDDDDDD@DDDDDDDDDDDDDDDDDDD@DDDDDD@DDDDDDDDDDDD@DDDDDD@DDDDDDDDDDDDDDDDDDD@DDDDDD@DDDDDDDDDDDD@DDDDDD@DDDDDDDDDDDDDDDDDDD@DDDDDDDDDDDDDDDDDDD@DDDDDD@DDDDDDDDDDDD@DDDDDD@DDDDDDDDDDDDDDDDDDD@DDDDDDDDDDDDDDDDDDD@DDDDDD@DDDDDDDDDDDD@DDDDDD@DDDDDDDDDDDDDDDDDDD  !"""""r Pc@@DDDDDQc@4 ȱc1!"""""*""""""34 KMDDDDDDg`C DDDDDD‡] """""ǂa""""""ǎ)|(1 """"""DŽixEDDDDffs0aavkHIK7M4MLL0J(Mpw8Qn/W 4-7Z %70ZGjX 8u~J{IENDB`gcli-2.3.0/docs/website/000077500000000000000000000000001460062271200150245ustar00rootroot00000000000000gcli-2.3.0/docs/website/deploy.sh000077500000000000000000000010541460062271200166570ustar00rootroot00000000000000#!/bin/sh -xe # # This script builds the tutorial and then creates a tarball that # is pulled regularly from the server # cd $(dirname $0) # Build the tutorial ( cd tutorial ./gen.sh ) # Make a dist directory and copy over files DISTDIR=$(mktemp -d) mkdir -p ${DISTDIR}/tutorial mkdir -p ${DISTDIR}/assets cp -vp index.html ${DISTDIR}/ cp -vp \ tutorial/0*.html \ tutorial/index.html \ ${DISTDIR}/tutorial cp -vp \ ../screenshot.png \ ${DISTDIR}/assets/screenshot.png tar -c -f - -C ${DISTDIR} \. | xz > website_dist.tar.xz rm -fr ${DISTDIR} gcli-2.3.0/docs/website/index.html000066400000000000000000000100331460062271200170160ustar00rootroot00000000000000 GCLI - A Git Forge CLI

GCLI - A Git Forge CLI

GCLI is a tool that lets you interact with Git forges such as Gitlab, Gitea and GitHub with a consistent command line interface.
It allows you to create, inspect and interact with issues, pull- and merge requests, inspect CI and pipelines and much more.
Screenshot of gcli

General

There is not much so far on this page. However, you can go look at

GCLI is available in various distributions

If you want gcli to be available in your favourite operating system please submit them to the respective packaging tree.
Please also tell me such that I can link to them here.

Bug Reports and Development

Report bugs by sending an E-Mail to the mailing list. You can view the archives on the web on Sourcehut. We also accept bugs reported via our mirrors on GitLab and GitHub, however the mailing list is the preferred method.
I also happily accept patches and encourage sending to our development mailing list. Its archives can also be viewed on the web on Sourcehut. Alternatively you can send patches to our mirrors on Gitlab and Github too, however the mailing list is the preferred method.
For sending patches via E-Mail please refer to git-send-email. If you have never done that, I can recommend git-send-email.io
If you are using Mercurial, you can refer to Sourcehut's documentation for patchbomb.
For questions or general discussions about patches and bugs you can join the IRC channel #gcli on Libera.Chat.

The CSS on this page is stolen and edited from the even better motherfucking website .

gcli-2.3.0/docs/website/tutorial/000077500000000000000000000000001460062271200166675ustar00rootroot00000000000000gcli-2.3.0/docs/website/tutorial/01-Installation.html000066400000000000000000000070621460062271200224410ustar00rootroot00000000000000 GCLI Tutorial | Installation

Installing GCLI

Through package manager

If you're on FreeBSD you can just install gcli by running the following command:

# pkg install gcli

Compile the source code

Other operating systems currently require manual compilation and installation.

Windows NT Notes

It is entirely possible to build gcli on Windows using MSYS2. Please follow their instructions on how to set up a development environment.

Generic build instructions

For this purpose go to https://herrhotzenplotz.de/gcli/releases and choose the latest release. Then download one of the tarballs.

For version 1.1.0 this would be:

https://herrhotzenplotz.de/gcli/releases/gcli-1.1.0/gcli-1.1.0.tar.xz

Now that you have a link, you can download it, extract it, compile the code and install it:

$ mkdir ~/build
$ cd ~/build
$ curl -4LO https://herrhotzenplotz.de/gcli/releases/gcli-1.1.0/gcli-1.1.0.tar.xz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  342k  100  342k    0     0  2739k      0 --:--:-- --:--:-- --:--:-- 2736k
$ ls
gcli-1.1.0.tar.xz
$

Install the dependencies for building gcli:

e.g. on Debian systems:

# apt install libcurl4-openssl-dev pkgconf build-essential

or on MSYS2:

$ pacman -S libcurl-devel pkgconf

Extract the tarball:

$ tar xf gcli-1.1.0.tar.xz
$ cd gcli-1.1.0

Configure, build and install gcli:

$ ./configure
...
$ make
...
$ make install

Check that the shell finds gcli:

$ which gcli
/usr/local/bin/gcli
$
$ gcli version
gcli 1.1.0 (amd64-unknown-freebsd13.2)
Using libcurl/8.1.2 OpenSSL/1.1.1t zlib/1.2.13 libpsl/0.21.2 (+libidn2/2.3.4) libssh2/1.11.0 nghttp2/1.53.0
Using vendored pdjson library
Report bugs at https://gitlab.com/herrhotzenplotz/gcli/.
Copyright 2021, 2022, 2023 Nico Sonack <nsonack@herrhotzenplotz.de> and contributors.
$

Advanced Windows Environment Setup

In case you want to use the installed gcli from outside MSYS2 (e.g. in cmd.exe) you may wish to update the Path environment variable and add the C:\msys2\usr\bin directory to it. Make sure you have the library paths set up correctly.



gcli-2.3.0/docs/website/tutorial/01-Installation.md000066400000000000000000000044521460062271200220750ustar00rootroot00000000000000# Installing GCLI ## Through package manager If you're on FreeBSD you can just install gcli by running the following command: # pkg install gcli ## Compile the source code Other operating systems currently require manual compilation and installation. ### Windows NT Notes It is entirely possible to build gcli on Windows using [MSYS2](https://msys2.org). Please follow their instructions on how to set up a development environment. ### Generic build instructions For this purpose go to [https://herrhotzenplotz.de/gcli/releases](https://herrhotzenplotz.de/gcli/releases) and choose the latest release. Then download one of the tarballs. For version 1.1.0 this would be: https://herrhotzenplotz.de/gcli/releases/gcli-1.1.0/gcli-1.1.0.tar.xz Now that you have a link, you can download it, extract it, compile the code and install it: $ mkdir ~/build $ cd ~/build $ curl -4LO https://herrhotzenplotz.de/gcli/releases/gcli-1.1.0/gcli-1.1.0.tar.xz % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 342k 100 342k 0 0 2739k 0 --:--:-- --:--:-- --:--:-- 2736k $ ls gcli-1.1.0.tar.xz $ Install the dependencies for building gcli: e.g. on Debian systems: # apt install libcurl4-openssl-dev pkgconf build-essential or on MSYS2: $ pacman -S libcurl-devel pkgconf Extract the tarball: $ tar xf gcli-1.1.0.tar.xz $ cd gcli-1.1.0 Configure, build and install gcli: $ ./configure ... $ make ... $ make install Check that the shell finds gcli: $ which gcli /usr/local/bin/gcli $ $ gcli version gcli 1.1.0 (amd64-unknown-freebsd13.2) Using libcurl/8.1.2 OpenSSL/1.1.1t zlib/1.2.13 libpsl/0.21.2 (+libidn2/2.3.4) libssh2/1.11.0 nghttp2/1.53.0 Using vendored pdjson library Report bugs at https://gitlab.com/herrhotzenplotz/gcli/. Copyright 2021, 2022, 2023 Nico Sonack and contributors. $ ### Advanced Windows Environment Setup In case you want to use the installed gcli from outside MSYS2 (e.g. in cmd.exe) you may wish to update the `Path` environment variable and add the `C:\msys2\usr\bin` directory to it. Make sure you have the library paths set up correctly. gcli-2.3.0/docs/website/tutorial/02-First-Steps.html000066400000000000000000000107101460062271200221560ustar00rootroot00000000000000 GCLI Tutorial | First Steps

First steps

Listing issues

Let's start off by listing some issues - here for the curl project which is hosted on GitHub under curl/curl. To list issues for it one would run:

$ gcli -t github issues -o curl -r curl

You will see the list of the 30 most recent open issue tickets. The command above does the following:

  • invoke gcli
  • as a global option we switch it into Github-Mode
  • invoke the issues subcommand
  • operate on the repository owner curl (-o curl)
  • operate on the repository curl (-r curl)

Note that the -t github option goes before the issues subcommand because it is a global option for gcli that affects how all the following things like subcommands operate.

However, now I also want to see closed issues:

$ gcli -t github issues -o curl -r curl -a

The -a option will disregard the status of the issue.

Oh and the screen is a bit cluttered by all these tickets - let's only fetch the first 10 issues:

$ gcli -t github issues -o curl -r curl -n10

Examining issues

As of now we only produced lists of issues. However, we may also want to look at the details of an issue such as:

  • the original post
  • labels
  • comments
  • assignees of the issue (that is someone who is working on the bug)

Let's get a good summary of issue #11268 in the curl project:

$ gcli -t github issues -o curl -r curl -i 11268 all

As you can see most of the options are the same, however now we tell gcli with the -i 11268 option that we want to work with a single issue. Then we tell gcli what actions to perform on the issue. Another important action is comments. Guess what it does:

$ gcli -t github issues -o curl -r curl -i 11268 comments

I know a person that likes to post long verbose traces. Let's search for an issue authored by them on the OpenSSL GitHub page:

$ gcli -t github issues -o openssl -r openssl -A blastwave -a
NUMBER  STATE   TITLE
 20379  open    test "80-test_ssl_new.t" fails on Solaris 10 SPARCv9
 10547  open    Strict C90 CFLAGS results in sha.h:91 ISO C90 does not support long long
  8048  closed  OPENSSL_strnlen SIGSEGV in o_str.c line 76
$

The -A option lets you filter for specific authors.

Let's look at the issue state of #10547:

$ gcli -t github issues -o openssl -r openssl -i 10547 status
     NAME : 10547
    TITLE : Strict C90 CFLAGS results in sha.h:91 ISO C90 does not support long long
  CREATED : 2019-12-01T04:35:23Z
   AUTHOR : blastwave
    STATE : open
 COMMENTS : 9
   LOCKED : no
   LABELS : triaged: bug
ASSIGNEES : none
$

That's nine comments - let's read the original post and the comments in our favourite pager less:

$ gcli -t github issues -o openssl -r openssl -i 10547 op comments | less

As you can see gcli will accept multiple actions for an issue and executes them sequentially.



gcli-2.3.0/docs/website/tutorial/02-First-Steps.md000066400000000000000000000055161460062271200216220ustar00rootroot00000000000000# First steps ## Listing issues Let's start off by listing some issues - here for the curl project which is hosted on GitHub under curl/curl. To list issues for it one would run: $ gcli -t github issues -o curl -r curl You will see the list of the 30 most recent open issue tickets. The command above does the following: - invoke gcli - as a global option we switch it into Github-Mode - invoke the issues subcommand - operate on the repository owner curl (`-o curl`) - operate on the repository curl (`-r curl`) Note that the `-t github` option goes before the issues subcommand because it is a global option for gcli that affects how all the following things like subcommands operate. However, now I also want to see closed issues: $ gcli -t github issues -o curl -r curl -a The `-a` option will disregard the status of the issue. Oh and the screen is a bit cluttered by all these tickets - let's only fetch the first 10 issues: $ gcli -t github issues -o curl -r curl -n10 ## Examining issues As of now we only produced lists of issues. However, we may also want to look at the details of an issue such as: - the original post - labels - comments - assignees of the issue (that is someone who is working on the bug) Let's get a good summary of issue `#11268` in the curl project: $ gcli -t github issues -o curl -r curl -i 11268 all As you can see most of the options are the same, however now we tell gcli with the `-i 11268` option that we want to work with a single issue. Then we tell gcli what actions to perform on the issue. Another important action is `comments`. Guess what it does: $ gcli -t github issues -o curl -r curl -i 11268 comments I know a person that likes to post long verbose traces. Let's search for an issue authored by them on the OpenSSL GitHub page: $ gcli -t github issues -o openssl -r openssl -A blastwave -a NUMBER STATE TITLE 20379 open test "80-test_ssl_new.t" fails on Solaris 10 SPARCv9 10547 open Strict C90 CFLAGS results in sha.h:91 ISO C90 does not support long long 8048 closed OPENSSL_strnlen SIGSEGV in o_str.c line 76 $ The `-A` option lets you filter for specific authors. Let's look at the issue state of `#10547`: $ gcli -t github issues -o openssl -r openssl -i 10547 status NAME : 10547 TITLE : Strict C90 CFLAGS results in sha.h:91 ISO C90 does not support long long CREATED : 2019-12-01T04:35:23Z AUTHOR : blastwave STATE : open COMMENTS : 9 LOCKED : no LABELS : triaged: bug ASSIGNEES : none $ That's nine comments - let's read the original post and the comments in our favourite pager `less`: $ gcli -t github issues -o openssl -r openssl -i 10547 op comments | less As you can see gcli will accept multiple actions for an issue and executes them sequentially. gcli-2.3.0/docs/website/tutorial/03-Find-Documentation.html000066400000000000000000000072401460062271200234670ustar00rootroot00000000000000 GCLI Tutorial | How to find documentation

How to find documentation

When using gcli one may not always remember all the options and flags for every subcommand. gcli has lots of integrated help to guide you through its commands.

Subcommand help

You can list all available options for the issues subcommand by doing:

$ gcli issues --help

With your current knowledge you can also explore the gcli pulls subcommand.

General usage

Run the following command:

$ gcli --help
usage: gcli [options] subcommand

OPTIONS:
  -a account     Use the configured account instead of inferring it
  -r remote      Infer account from the given git remote
  -t type        Force the account type:
                    - github (default: github.com)
                    - gitlab (default: gitlab.com)
                    - gitea (default: codeberg.org)
  -c             Force colour and text formatting.
  -q             Be quiet. (Not implemented yet)

  -v             Be verbose.

SUBCOMMANDS:
  ci             Github CI status info
  comment        Comment under issues and PRs
  config         Configure forges
  forks          Create, delete and list repository forks
  gists          Create, fetch and list Github Gists
  issues         Manage issues
  labels         Manage issue and PR labels
  milestones     Milestone handling
  pipelines      Gitlab CI management
  pulls          Create, view and manage PRs
  releases       Manage releases of repositories
  repos          Remote Repository management
  snippets       Fetch and list Gitlab snippets
  status         General user status and notifications
  api            Fetch plain JSON info from an API (for debugging purposes)
  version        Print version

gcli 1.2.0 (amd64-unknown-freebsd13.2)
Using libcurl/8.1.2 OpenSSL/1.1.1t zlib/1.2.13 libpsl/0.21.2 (+libidn2/2.3.4) libssh2/1.11.0 nghttp2/1.53.0
Using vendored pdjson library
Report bugs at https://gitlab.com/herrhotzenplotz/gcli/.
Copyright 2021, 2022, 2023 Nico Sonack <nsonack@herrhotzenplotz.de> and contributors.

This gives you an overview over all the available subcommands. Each subcommand in turn allows you to get its usage by supplying the --help option to it.

Manual pages

Furthermore I recommend reading into the manual page gcli-issues(1) and gcli-pulls(1):

$ man gcli-issues
$ man gcli-pulls


gcli-2.3.0/docs/website/tutorial/03-Find-Documentation.md000066400000000000000000000046221460062271200231240ustar00rootroot00000000000000# How to find documentation When using gcli one may not always remember all the options and flags for every subcommand. gcli has lots of integrated help to guide you through its commands. ## Subcommand help You can list all available options for the issues subcommand by doing: $ gcli issues --help With your current knowledge you can also explore the `gcli pulls` subcommand. ## General usage Run the following command: $ gcli --help usage: gcli [options] subcommand OPTIONS: -a account Use the configured account instead of inferring it -r remote Infer account from the given git remote -t type Force the account type: - github (default: github.com) - gitlab (default: gitlab.com) - gitea (default: codeberg.org) -c Force colour and text formatting. -q Be quiet. (Not implemented yet) -v Be verbose. SUBCOMMANDS: ci Github CI status info comment Comment under issues and PRs config Configure forges forks Create, delete and list repository forks gists Create, fetch and list Github Gists issues Manage issues labels Manage issue and PR labels milestones Milestone handling pipelines Gitlab CI management pulls Create, view and manage PRs releases Manage releases of repositories repos Remote Repository management snippets Fetch and list Gitlab snippets status General user status and notifications api Fetch plain JSON info from an API (for debugging purposes) version Print version gcli 1.2.0 (amd64-unknown-freebsd13.2) Using libcurl/8.1.2 OpenSSL/1.1.1t zlib/1.2.13 libpsl/0.21.2 (+libidn2/2.3.4) libssh2/1.11.0 nghttp2/1.53.0 Using vendored pdjson library Report bugs at https://gitlab.com/herrhotzenplotz/gcli/. Copyright 2021, 2022, 2023 Nico Sonack and contributors. This gives you an overview over all the available subcommands. Each subcommand in turn allows you to get its usage by supplying the `--help` option to it. ## Manual pages Furthermore I recommend reading into the manual page `gcli-issues(1)` and `gcli-pulls(1)`: $ man gcli-issues $ man gcli-pulls gcli-2.3.0/docs/website/tutorial/04-Account-Setup.html000066400000000000000000000060311460062271200224700ustar00rootroot00000000000000 GCLI Tutorial | Setting up an account

Setting up gcli for use with an account

Creating issues on Github requires an account which we need to generate an authentication token for gcli.

Log into your GitHub account and click on your account icon in the top right corner. Then choose the Settings option. Scroll down and choose Developer settings on the bottom of the left column. Under Personal access tokens choose Tokens (classic).

Click on Generate new token (classic).

Set a useful name such as gcli in the Note field, set the expiration to No expiration and allow the following Scopes:

  • repo
  • workflow
  • admin:public_key
  • gist

Then create the token. It'll be printed in green. Do not share it!

Now we need to tell gcli about this new token. To do this, create a configuration file for gcli - on Windows you need to do this from the MSYS2 Shell:

$ mkdir -p ${HOME}/.config/gcli
$ vi ${HOME}/.config/gcli/config

Obviously, you can choose any other editor of your choice. Put the following into this file:

defaults {
    editor=vi
    github-default-account=my-github-account
}

my-github-account {
    token=<token-goes-here>
    account=<account-name>
    forge-type=github
}

Replace the <token-goes-here> with the previously generated token and the <account> with your account name.

If you now run

$ gcli -t github repos

you should get a list of your repos. If not, check again that you did all the steps above correctly.



gcli-2.3.0/docs/website/tutorial/04-Account-Setup.md000066400000000000000000000027231460062271200221300ustar00rootroot00000000000000# Setting up gcli for use with an account Creating issues on Github requires an account which we need to generate an authentication token for gcli. Log into your GitHub account and click on your account icon in the top right corner. Then choose the `Settings` option. Scroll down and choose `Developer settings` on the bottom of the left column. Under `Personal access tokens` choose `Tokens (classic)`. Click on `Generate new token (classic)`. Set a useful name such as `gcli` in the Note field, set the expiration to `No expiration` and allow the following `Scopes`: - `repo` - `workflow` - `admin:public_key` - `gist` Then create the token. It'll be printed in green. Do not share it! Now we need to tell gcli about this new token. To do this, create a configuration file for gcli - on Windows you need to do this from the MSYS2 Shell: $ mkdir -p ${HOME}/.config/gcli $ vi ${HOME}/.config/gcli/config Obviously, you can choose any other editor of your choice. Put the following into this file: defaults { editor=vi github-default-account=my-github-account } my-github-account { token= account= forge-type=github } Replace the `` with the previously generated token and the `` with your account name. If you now run $ gcli -t github repos you should get a list of your repos. If not, check again that you did all the steps above correctly. gcli-2.3.0/docs/website/tutorial/05-Creating-an-issue.html000066400000000000000000000064661460062271200232710ustar00rootroot00000000000000 GCLI Tutorial | Creating an issue

Creating an issue

Note: This assumes you have configured gcli with an account for Github already.

Preparation

For this case I have a playground repository that you may as well use for testing with gcli. It is available at herrhotzenplotz/ghcli-playground.

To see a list of issues, we can run:

$ gcli -t github issues -o herrhotzenplotz -r ghcli-playground -a
NUMBER  NOTES  STATE   TITLE
    13      0  open    yet another issue
    12      0  closed  wat
    11      0  closed  blaaaaaaaaaaaaaaaaaaaah
    10      0  closed  "this is the quoted" issue title? anyone?"
     9      0  closed  test
     8      0  closed  foobar
     7      0  closed  foobar
     5      0  closed  test2
     4      0  closed  test
$

Invoke gcli

Let's create a bug report where we complain about things not working:

$ gcli -t github issues create -o herrhotzenplotz -r ghcli-playground \
    "Bug: Doesn't work on my machine"

The message "Bug: doesn't work on my machine" is the title of the issue.

Original Post

You will see the default editor come up and instruct you to type in a message. This message is the "original post" or the body of the issue ticket that you're about to submit. You can use Markdown Syntax:

 I tried building this code on my machine but unfortunately it errors
 out with the following message:

 ```console
 $ make love
 make: don't know how to make love. Stop

 make: stopped in /tmp/wat
 $
 ```

 What am I doing wrong?

 ! ISSUE TITLE : Bug: Doesn't work on my machine
 ! Enter issue description above.
 ! All lines starting with '!' will be discarded.

Submit the issue

After you save and exit the editor gcli gives you a chance to check back and finally submit the issue. Type 'y' and hit enter.

You can check back if the issue was created and also view details about it as you learned earlier.



gcli-2.3.0/docs/website/tutorial/05-Creating-an-issue.md000066400000000000000000000036651460062271200227230ustar00rootroot00000000000000# Creating an issue **Note**: This assumes you have [configured gcli with an account](./04-Account-Setup.html) for Github already. ## Preparation For this case I have a playground repository that you may as well use for testing with gcli. It is available at `herrhotzenplotz/ghcli-playground`. To see a list of issues, we can run: $ gcli -t github issues -o herrhotzenplotz -r ghcli-playground -a NUMBER NOTES STATE TITLE 13 0 open yet another issue 12 0 closed wat 11 0 closed blaaaaaaaaaaaaaaaaaaaah 10 0 closed "this is the quoted" issue title? anyone?" 9 0 closed test 8 0 closed foobar 7 0 closed foobar 5 0 closed test2 4 0 closed test $ ## Invoke gcli Let's create a bug report where we complain about things not working: $ gcli -t github issues create -o herrhotzenplotz -r ghcli-playground \ "Bug: Doesn't work on my machine" The message "Bug: doesn't work on my machine" is the title of the issue. ## Original Post You will see the default editor come up and instruct you to type in a message. This message is the "original post" or the body of the issue ticket that you're about to submit. You can use Markdown Syntax: I tried building this code on my machine but unfortunately it errors out with the following message: ```console $ make love make: don't know how to make love. Stop make: stopped in /tmp/wat $ ``` What am I doing wrong? ! ISSUE TITLE : Bug: Doesn't work on my machine ! Enter issue description above. ! All lines starting with '!' will be discarded. ## Submit the issue After you save and exit the editor gcli gives you a chance to check back and finally submit the issue. Type 'y' and hit enter. You can check back if the issue was created and also view details about it as you learned earlier. gcli-2.3.0/docs/website/tutorial/06-Commenting.html000066400000000000000000000040271460062271200221030ustar00rootroot00000000000000 GCLI Tutorial | Interacting and commenting

Commenting

Discussions on Github and the like are done through comments. You can comment on issues and pull requests.

Reviewing a discussion

Say you were looking at an issue in curl/curl:

$ gcli issues -o curl -r curl -i 11461 comments

Create the comment

And now you wish to respond to this thread:

$ gcli comment -o curl -r curl -i 11461

This will now open the editor and lets you type in your message. After saving and exiting gcli will ask you to confirm. Type 'y' and hit enter:

$ gcli -t github comment -o curl -r curl -i 11461
You will be commenting the following in curl/curl #11461:

Is this okay? [yN] y
$

Commenting on pull requests

When you want to comment under a pull request use the -p flag instead of the -i flag to indicate the PR number.



gcli-2.3.0/docs/website/tutorial/06-Commenting.md000066400000000000000000000014451460062271200215400ustar00rootroot00000000000000# Commenting Discussions on Github and the like are done through comments. You can comment on issues and pull requests. ## Reviewing a discussion Say you were looking at an issue in curl/curl: $ gcli issues -o curl -r curl -i 11461 comments ## Create the comment And now you wish to respond to this thread: $ gcli comment -o curl -r curl -i 11461 This will now open the editor and lets you type in your message. After saving and exiting gcli will ask you to confirm. Type 'y' and hit enter: $ gcli -t github comment -o curl -r curl -i 11461 You will be commenting the following in curl/curl #11461: Is this okay? [yN] y $ ## Commenting on pull requests When you want to comment under a pull request use the `-p` flag instead of the `-i` flag to indicate the PR number. gcli-2.3.0/docs/website/tutorial/footer.html000066400000000000000000000000201460062271200210430ustar00rootroot00000000000000 gcli-2.3.0/docs/website/tutorial/gen.sh000077500000000000000000000045631460062271200200070ustar00rootroot00000000000000#!/bin/sh # Set the following options: # -e: Exit immediately if any command exits with a non-zero status (error). # -u: Treat unset variables as errors, causing the script to exit. set -eu # # Static Site generator for the tutorial. # # You will need cmark for this to work. # if ! command -v cmark >/dev/null 2>&1; then echo "cmark is required but it's not installed. Exiting." >&2 exit 1 fi header() { TITLE="${1}" PREVURL="${2-}" NEXTURL="${3-}" sed -e "s/{{TITLE_PLACEHOLDER}}/${TITLE}/g" \ -e "s/{{PREVURL}}/${PREVURL}/g" \ -e "s/{{NEXURL}}/${NEXTURL}/g" top.html } footer() { cat footer.html } pagination() { PREVDOC="$1" PREVTITLE="$2" NEXTDOC="$3" NEXTTITLE="$4" echo "" } genindex() { header "Index" cat <A GCLI Tutorial

This document is aimed at those who are new to gcli and want get started using it.

Table of contents

EOF echo "
    " awk -F\\t '{printf "
  1. %s
  2. \n", $1, $2}' < toc echo "
" footer } genpage() { PAGETITLE="$1" PAGEMDFILE="$2" PREVDOC="$3" PREVTITLE="$4" NEXTDOC="$5" NEXTTITLE="$6" header "${PAGETITLE}" "${PREVDOC}" "${NEXTDOC}" pagination "${PREVDOC}" "${PREVTITLE}" "${NEXTDOC}" "${NEXTTITLE}" echo "
" cmark -t html < "${PAGEMDFILE}" echo "
" echo "
" pagination "${PREVDOC}" "${PREVTITLE}" "${NEXTDOC}" "${NEXTTITLE}" footer } prevhtmldoc="" prevtitlename="" while IFS="$(printf '\t')" read -r htmldoc title; do mddoc="${htmldoc%.html}.md" read -r nexthtmldoc nexttitle < "${htmldoc}" # Update the previous document filename and title for the next iteration prevhtmldoc="$htmldoc" prevtitlename="$title" done < toc genindex > index.html gcli-2.3.0/docs/website/tutorial/index.html000066400000000000000000000024521460062271200206670ustar00rootroot00000000000000 GCLI Tutorial | Index

A GCLI Tutorial

This document is aimed at those who are new to gcli and want get started using it.

Table of contents

  1. Installation
  2. First Steps
  3. How to find documentation
  4. Setting up an account
  5. Creating an issue
  6. Interacting and commenting
gcli-2.3.0/docs/website/tutorial/old_index.md000066400000000000000000000004471460062271200211630ustar00rootroot00000000000000# GCLI Tutorial This document is aimed at those who are new to gcli and want get started using it. ## Table of contents 1. [Installing GCLI](./02-Installation.html) 1. [First steps](./03-First-Steps.html) #### Details about issues #### Further reading ### Creating issues ### First issue gcli-2.3.0/docs/website/tutorial/toc000066400000000000000000000003751460062271200174040ustar00rootroot0000000000000001-Installation.html Installation 02-First-Steps.html First Steps 03-Find-Documentation.html How to find documentation 04-Account-Setup.html Setting up an account 05-Creating-an-issue.html Creating an issue 06-Commenting.html Interacting and commenting gcli-2.3.0/docs/website/tutorial/top.html000066400000000000000000000014041460062271200203560ustar00rootroot00000000000000 GCLI Tutorial | {{TITLE_PLACEHOLDER}} gcli-2.3.0/include/000077500000000000000000000000001460062271200140555ustar00rootroot00000000000000gcli-2.3.0/include/gcli/000077500000000000000000000000001460062271200147735ustar00rootroot00000000000000gcli-2.3.0/include/gcli/attachments.h000066400000000000000000000041261460062271200174620ustar00rootroot00000000000000/* * 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 */ gcli-2.3.0/include/gcli/base64.h000066400000000000000000000033041460062271200162300ustar00rootroot00000000000000/* * 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 */ gcli-2.3.0/include/gcli/bugzilla/000077500000000000000000000000001460062271200166045ustar00rootroot00000000000000gcli-2.3.0/include/gcli/bugzilla/api.h000066400000000000000000000031361460062271200175310ustar00rootroot00000000000000/* * 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 */ gcli-2.3.0/include/gcli/bugzilla/attachments.h000066400000000000000000000031651460062271200212750ustar00rootroot00000000000000/* * 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 */ gcli-2.3.0/include/gcli/bugzilla/bugs-parser.h000066400000000000000000000060101460062271200212040ustar00rootroot00000000000000/* * 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 */ gcli-2.3.0/include/gcli/bugzilla/bugs.h000066400000000000000000000051741460062271200177240ustar00rootroot00000000000000/* * 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_issue *out); #endif /* GCLI_BUGZILLA_BUGS_H */ gcli-2.3.0/include/gcli/bugzilla/config.h000066400000000000000000000030441460062271200202230ustar00rootroot00000000000000/* * 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 */ gcli-2.3.0/include/gcli/cmd/000077500000000000000000000000001460062271200155365ustar00rootroot00000000000000gcli-2.3.0/include/gcli/cmd/attachments.h000066400000000000000000000027671460062271200202360ustar00rootroot00000000000000/* * 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 */ gcli-2.3.0/include/gcli/cmd/ci.h000066400000000000000000000034511460062271200163050ustar00rootroot00000000000000/* * 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_CI_H #define GCLI_CMD_CI_H #ifdef HAVE_CONFIG_H #include #endif #include 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[]); #endif /* GCLI_CMD_CI_H */ gcli-2.3.0/include/gcli/cmd/cmd.h000066400000000000000000000042131460062271200164520ustar00rootroot00000000000000/* * Copyright 2022 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_CMD_H #define GCLI_CMD_CMD_H #ifdef HAVE_CONFIG_H #include #endif #include #include extern struct gcli_ctx *g_clictx; static inline char * shift(int *argc, char ***argv) { if (*argc == 0) errx(1, "error: Not enough arguments"); (*argc)--; return *((*argv)++); } void version(void); void longversion(void); void copyright(void); void check_owner_and_repo(const char **owner, const char **repo); void parse_labels_options( int *argc, char ***argv, const char ***_add_labels, size_t *_add_labels_size, const char ***_remove_labels, size_t *_remove_labels_size); void delete_repo(bool always_yes, const char *owner, const char *repo); /* List of subcommand entry points */ int subcommand_api(int argc, char *argv[]); #endif /* GCLI_CMD_CMD_H */ gcli-2.3.0/include/gcli/cmd/cmdconfig.h000066400000000000000000000055231460062271200176450ustar00rootroot00000000000000/* * Copyright 2021, 2022 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_CMDCONFIG_H #define GCLI_CMD_CMDCONFIG_H #ifdef HAVE_CONFIG_H #include #endif #include #include #ifdef HAVE_SYS_QUEUE_H #include #endif /* HAVE_SYS_QUEUE_H */ struct gcli_config_entry { TAILQ_ENTRY(gcli_config_entry) next; sn_sv key; sn_sv value; }; TAILQ_HEAD(gcli_config_entries, gcli_config_entry); 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(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); int 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( struct gcli_ctx *ctx, char const *section_name); #endif /* GCLI_CMD_CMDCONFIG_H */ gcli-2.3.0/include/gcli/cmd/colour.h000066400000000000000000000040661460062271200172200ustar00rootroot00000000000000/* * Copyright 2022 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 COLOR_H #define COLOR_H #ifdef HAVE_CONFIG_H #include #endif #include #include #include #define GCLI_256COLOR_DONE 0x3F0FAF00 #define GCLI_256COLOR_OPEN 0x04FF0100 enum { GCLI_COLOR_BLACK, GCLI_COLOR_RED, GCLI_COLOR_GREEN, GCLI_COLOR_YELLOW, GCLI_COLOR_BLUE, GCLI_COLOR_MAGENTA, GCLI_COLOR_CYAN, GCLI_COLOR_WHITE, GCLI_COLOR_DEFAULT, }; char const *gcli_setcolour256(uint32_t colourcode); char const *gcli_resetcolour(void); char const *gcli_setcolour(int colour); char const *gcli_state_colour_sv(sn_sv const state); char const *gcli_state_colour_str(char const *it); char const *gcli_setbold(void); char const *gcli_resetbold(void); #endif /* COLOR_H */ gcli-2.3.0/include/gcli/cmd/comment.h000066400000000000000000000034371460062271200173600ustar00rootroot00000000000000/* * 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_COMMENT_H #define GCLI_CMD_COMMENT_H #ifdef HAVE_CONFIG_H #include #endif #include #include 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(struct gcli_comment_list const *list); int subcommand_comment(int argc, char *argv[]); #endif /* GCLI_CMD_COMMENT_H */ gcli-2.3.0/include/gcli/cmd/config.h000066400000000000000000000032121460062271200171520ustar00rootroot00000000000000/* * 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_CONFIG_H #define GCLI_CMD_CONFIG_H #ifdef HAVE_CONFIG_H #include #endif #include #include void gcli_sshkeys_print_keys(struct gcli_sshkey_list const *list); int subcommand_config(int argc, char *argv[]); #endif /* GCLI_CMD_CONFIG_H */ gcli-2.3.0/include/gcli/cmd/editor.h000066400000000000000000000032121460062271200171730ustar00rootroot00000000000000/* * Copyright 2021, 2022 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_EDITOR_H #define GCLI_CMD_EDITOR_H #ifdef HAVE_CONFIG_H #include #endif #include 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 */ gcli-2.3.0/include/gcli/cmd/forks.h000066400000000000000000000032701460062271200170350ustar00rootroot00000000000000/* * 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_FORKS_H #define GCLI_CMD_FORKS_H #ifdef HAVE_CONFIG_H #include #endif #include #include int subcommand_forks(int argc, char *argv[]); void gcli_print_forks(enum gcli_output_flags flags, struct gcli_fork_list const *list, int max); #endif /* GCLI_CMD_FORKS_H */ gcli-2.3.0/include/gcli/cmd/gists.h000066400000000000000000000032501460062271200170400ustar00rootroot00000000000000/* * 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_GISTS_H #define GCLI_CMD_GISTS_H #ifdef HAVE_CONFIG_H #include #endif #include int subcommand_gists(int argc, char *argv[]); void gcli_print_gists(enum gcli_output_flags flags, struct gcli_gist_list const *list, int max); #endif /* GCLI_CMD_GISTS_H */ gcli-2.3.0/include/gcli/cmd/gitconfig.h000066400000000000000000000040271460062271200176630ustar00rootroot00000000000000/* * Copyright 2021, 2022 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_GITCONFIG_H #define GCLI_CMD_GITCONFIG_H #ifdef HAVE_CONFIG_H #include #endif #include struct gcli_gitremote { sn_sv name; sn_sv owner; sn_sv repo; sn_sv url; int forge_type; }; 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(struct gcli_ctx *ctx, char const *remote_name); int gcli_gitconfig_repo_by_remote(struct gcli_ctx *ctx, char const *const remote_name, char const **const owner, char const **const repo, int *const forge); #endif /* GCLI_CMD_GITCONFIG_H */ gcli-2.3.0/include/gcli/cmd/interactive.h000066400000000000000000000031001460062271200202160ustar00rootroot00000000000000/* * Copyright 2024 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_INTERACTIVE_H #define GCLI_CMD_INTERACTIVE_H #ifdef HAVE_CONFIG_H #include #endif char *gcli_cmd_prompt(char const *const fmt, char const *const deflt, ...); #endif /* GCLI_CMD_INTERACTIVE_H */ gcli-2.3.0/include/gcli/cmd/issues.h000066400000000000000000000034741460062271200172320ustar00rootroot00000000000000/* * 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_ISSUES_H #define GCLI_CMD_ISSUES_H #ifdef HAVE_CONFIG_H #include #endif #include void gcli_print_issues(enum gcli_output_flags const flags, struct gcli_issue_list const *const list, int const max); void gcli_issue_print_summary(struct gcli_issue const *const it); void gcli_issue_print_op(struct gcli_issue const *const it); int subcommand_issues(int argc, char *argv[]); #endif /* GCLI_CMD_ISSUES_H */ gcli-2.3.0/include/gcli/cmd/labels.h000066400000000000000000000031641460062271200171550ustar00rootroot00000000000000/* * 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_LABELS_H #define GCLI_CMD_LABELS_H #ifdef HAVE_CONFIG_H #include #endif #include void gcli_labels_print(struct gcli_label_list const *list, int max); int subcommand_labels(int argc, char *argv[]); #endif /* GCLI_CMD_LABELS_H */ gcli-2.3.0/include/gcli/cmd/milestones.h000066400000000000000000000034221460062271200200720ustar00rootroot00000000000000/* * 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_MILESTONES_H #define GCLI_CMD_MILESTONES_H #ifdef HAVE_CONFIG_H #include #endif #include void gcli_print_milestones(struct gcli_ctx *ctx, struct gcli_milestone_list const *it, int max); void gcli_print_milestone(struct gcli_ctx *ctx, struct gcli_milestone const *it); int subcommand_milestones(int argc, char *argv[]); #endif /* GCLI_CMD_MILESTONES_H */ gcli-2.3.0/include/gcli/cmd/pipelines.h000066400000000000000000000041301460062271200176750ustar00rootroot00000000000000/* * 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_PIPELINES_H #define GCLI_CMD_PIPELINES_H #include #include 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(struct gitlab_job_list const *const list); 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[]); #endif /* GCLI_CMD_PIPELINES_H */ gcli-2.3.0/include/gcli/cmd/pulls.h000066400000000000000000000040301460062271200170430ustar00rootroot00000000000000/* * 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_PULLS_H #define GCLI_CMD_PULLS_H #ifdef HAVE_CONFIG_H #include #endif #include 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(struct 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(struct gcli_commit_list const *const list); int subcommand_pulls(int argc, char *argv[]); #endif /* GCLI_CMD_PULLS_H */ gcli-2.3.0/include/gcli/cmd/releases.h000066400000000000000000000032711460062271200175150ustar00rootroot00000000000000/* * 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_RELEASES_H #define GCLI_CMD_RELEASES_H #ifdef HAVE_CONFIG_H #include #endif #include void gcli_releases_print(enum gcli_output_flags flags, struct gcli_release_list const *list, int max); int subcommand_releases(int argc, char *argv[]); #endif /* GCLI_CMD_RELEASES_H */ gcli-2.3.0/include/gcli/cmd/repos.h000066400000000000000000000033541460062271200170440ustar00rootroot00000000000000/* * 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_REPOS_H #define GCLI_CMD_REPOS_H #ifdef HAVE_CONFIG_H #include #endif #include #include void gcli_print_repos(enum gcli_output_flags flags, struct gcli_repo_list const *repos, int max); void gcli_repo_print(struct gcli_repo const *it); int subcommand_repos(int argc, char *argv[]); #endif /* GCLI_CMD_REPOS_H */ gcli-2.3.0/include/gcli/cmd/snippets.h000066400000000000000000000034121460062271200175540ustar00rootroot00000000000000/* * 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_SNIPPETS_H #define GCLI_CMD_SNIPPETS_H #ifdef HAVE_CONFIG_H #include #endif #include #include void gcli_snippets_print(enum gcli_output_flags const flags, struct gcli_gitlab_snippet_list const *const list, int const max); int subcommand_snippets(int argc, char *argv[]); #endif /* GCLI_CMD_SNIPPETS_H */ gcli-2.3.0/include/gcli/cmd/status.h000066400000000000000000000032471460062271200172400ustar00rootroot00000000000000/* * 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_STATUS_H #define GCLI_CMD_STATUS_H #ifdef HAVE_CONFIG_H #include #endif #include #include int gcli_status(int count); void gcli_print_notifications(struct gcli_notification_list const *); int subcommand_status(int argc, char *argv[]); #endif /* GCLI_CMD_STATUS_H */ gcli-2.3.0/include/gcli/cmd/table.h000066400000000000000000000073201460062271200170000ustar00rootroot00000000000000/* * Copyright 2022 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_TABLE_H #define GCLI_CMD_TABLE_H #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include typedef void *gcli_tbl; typedef void *gcli_dict; /** Flags for table column definitions */ enum gcli_tblcol_flags { /* column is as string and colour is derived from its contents. */ GCLI_TBLCOL_STATECOLOURED = 1, /* Right-justify the column */ GCLI_TBLCOL_JUSTIFYR = 2, /* Make it bold */ GCLI_TBLCOL_BOLD = 4, /* Explicit colour - provide the colour to gcli_tbl_add_row first * and second the content of the cell. */ GCLI_TBLCOL_COLOUREXPL = 8, /* 256 colour handling. Just like the above */ GCLI_TBLCOL_256COLOUR = 16, /* Have a column spacing to the right of one instead of two spaces */ GCLI_TBLCOL_TIGHT = 32, }; enum gcli_tblcoltype { GCLI_TBLCOLTYPE_INT, /* integer */ GCLI_TBLCOLTYPE_LONG, /* signed long int */ GCLI_TBLCOLTYPE_ID, /* some ID type (uint64_t) */ GCLI_TBLCOLTYPE_STRING, /* C string */ GCLI_TBLCOLTYPE_DOUBLE, /* double precision float */ GCLI_TBLCOLTYPE_BOOL, /* yes/no */ }; /** A single table column */ struct gcli_tblcoldef { char const *name; /* name of the column, also displayed in first row */ int type; /* type of values in this column */ int flags; /* flags about this column */ }; /* Init a table printer */ 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 * the table */ void gcli_tbl_end(gcli_tbl table); /* Add a single to an initialized table */ int gcli_tbl_add_row(gcli_tbl table, ...); gcli_dict gcli_dict_begin(void); int gcli_dict_add(gcli_dict list, char const *key, int flags, uint32_t colour_args, char const *fmt, ...); int gcli_dict_add_string(gcli_dict list, char const *key, int flags, uint32_t colour_args, char const *str); int gcli_dict_add_sv_list(gcli_dict dict, char const *key, sn_sv const *list, size_t list_size); int gcli_dict_add_string_list(gcli_dict dict, char const *const key, char const *const *list, size_t const list_size); int gcli_dict_end(gcli_dict _list); #endif /* GCLI_CMD_TABLE_H */ gcli-2.3.0/include/gcli/comments.h000066400000000000000000000053341460062271200167760ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 COMMENTS_H #define COMMENTS_H #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include struct gcli_comment { char *author; /* Login name of the comment author */ char *date; /* Creation date of the comment */ gcli_id id; /* id of the comment */ char *body; /* Raw text of the comment */ }; struct gcli_comment_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; gcli_id target_id; char const *message; }; void gcli_comments_free(struct gcli_comment_list *list); void gcli_comment_free(struct gcli_comment *const it); 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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue, struct gcli_comment_list *out); int gcli_comment_submit(struct gcli_ctx *ctx, struct gcli_submit_comment_opts opts); #endif /* COMMENTS_H */ gcli-2.3.0/include/gcli/ctx.h000066400000000000000000000041121460062271200157400ustar00rootroot00000000000000/* * 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_CTX_H #define GCLI_CTX_H #include #include /* Strictly internal structure containing the gcli library context * data */ struct gcli_ctx { CURL *curl; char *curl_useragent; void *usrdata; char *last_error; char *apibase; /* generated by a call to get_apibase */ char *(*get_token)(struct gcli_ctx *); gcli_forge_type (*get_forge_type)(struct gcli_ctx *ctx); char *(*get_apibase)(struct gcli_ctx *); void (*report_progress)(bool done); }; /* Error routine */ 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 */ gcli-2.3.0/include/gcli/curl.h000066400000000000000000000064501460062271200161160ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 CURL_H #define CURL_H #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include 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); struct gcli_fetch_buffer { char *data; size_t length; }; struct gcli_fetch_list_ctx { void *listp; /* pointer to pointer of start of list */ size_t *sizep; /* pointer to list size */ int max; parsefn parse; /* json parse routine */ filterfn filter; /* optional filter */ void const *userdata; }; int gcli_fetch(struct gcli_ctx *ctx, char const *url, char **pagination_next, struct gcli_fetch_buffer *out); int gcli_curl(struct gcli_ctx *ctx, FILE *stream, char const *url, char const *content_type); int gcli_fetch_with_method(struct gcli_ctx *ctx, char const *method, char const *url, char const *data, char **pagination_next, struct 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(struct gcli_ctx *ctx, char const *url, char const *filename, struct gcli_fetch_buffer *out); 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(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 */ gcli-2.3.0/include/gcli/date_time.h000066400000000000000000000032711460062271200171020ustar00rootroot00000000000000/* * 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_DATE_TIME_H #define GCLI_DATE_TIME_H #ifdef HAVE_CONFIG_H #include #endif #include enum { DATEFMT_ISO8601, DATEFMT_GITLAB, }; 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 */ gcli-2.3.0/include/gcli/forges.h000066400000000000000000000327611460062271200164420ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 FORGES_H #define FORGES_H #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include /* Hopefully temporary hack */ typedef int (*gcli_get_pull_checks_cb)( struct gcli_ctx *, char const *, char const *, gcli_id, struct gcli_pull_checks_list *); /** * Struct of function pointers to perform actions in the given * forge. It is like a plugin system to dispatch. */ struct gcli_forge_descriptor { /** * Submit a comment to a pull/mr or issue */ int (*perform_submit_comment)( 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)( struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue, struct gcli_comment_list *out); /** * List comments on the given PR */ int (*get_pull_comments)( struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr, struct gcli_comment_list *out); /** * List forks of the given repo */ int (*get_forks)( struct gcli_ctx *ctx, char const *owner, char const *repo, int max, struct gcli_fork_list *out); /** * Fork the given repo into the owner _in */ int (*fork_create)( struct gcli_ctx *ctx, char const *owner, char const *repo, char const *_in); /** * Get a list of issues on the given repo */ int (*search_issues)( struct gcli_ctx *ctx, char const *owner, char const *repo, struct gcli_issue_fetch_details const *details, int max, struct gcli_issue_list *out); /** * Get a summary of an issue */ int (*get_issue_summary)( struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue_number, struct gcli_issue *out); /** * Close the given issue */ int (*issue_close)( struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue_number); /** * Reopen the given issue */ int (*issue_reopen)( struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue_number); /** * Assign an issue to a user */ int (*issue_assign)( struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue_number, char const *assignee); /** * Add labels to issues */ int (*issue_add_labels)( struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue, char const *const labels[], size_t labels_size); /** * Removes labels from issues */ int (*issue_remove_labels)( struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue, char const *const labels[], size_t labels_size); /** * Submit an issue */ int (*perform_submit_issue)( struct gcli_ctx *ctx, struct gcli_submit_issue_options *opts, struct gcli_issue *out); /** * Change the title of an issue */ int (*issue_set_title)( 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 { GCLI_MILESTONE_QUIRKS_EXPIRED = 0x1, GCLI_MILESTONE_QUIRKS_DUEDATE = 0x2, GCLI_MILESTONE_QUIRKS_PULLS = 0x4, GCLI_MILESTONE_QUIRKS_NISSUES = 0x8, } const milestone_quirks; /** * Get list of milestones */ int (*get_milestones)( struct gcli_ctx *ctx, char const *owner, char const *repo, int max, struct gcli_milestone_list *out); /** * Get a single milestone */ int (*get_milestone)( struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone, struct gcli_milestone *out); /** * create a milestone */ int (*create_milestone)( struct gcli_ctx *ctx, struct gcli_milestone_create_args const *args); /** * delete a milestone */ int (*delete_milestone)( struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone); /** * delete a milestone */ int (*milestone_set_duedate)( struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone, char const *date); /** * Get list of issues attached to this milestone */ int (*get_milestone_issues)( struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone, struct gcli_issue_list *out); /** Assign an issue to a milestone */ int (*issue_set_milestone)( struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue, gcli_id milestone); /** * Clear the milestones of an issue */ int (*issue_clear_milestone)( struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue); /** * Get a list of PRs/MRs on the given repo */ int (*search_pulls)( struct gcli_ctx *ctx, char const *owner, char const *reponame, struct gcli_pull_fetch_details const *details, int max, struct gcli_pull_list *out); /** * Fetch the PR diff into the file */ int (*pull_get_diff)( struct gcli_ctx *ctx, FILE *stream, char const *owner, char const *reponame, gcli_id pr_number); /** * Fetch the PR patch series into the file */ int (*pull_get_patch)( struct gcli_ctx *ctx, FILE *stream, char const *owner, char const *repo, gcli_id pull_id); /** * 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 struct gcli_pull_checks_list. */ gcli_get_pull_checks_cb get_pull_checks; /** * Merge the given PR/MR */ int (*pull_merge)( struct gcli_ctx *ctx, char const *owner, char const *reponame, gcli_id pr_number, enum gcli_merge_flags flags); /** * Reopen the given PR/MR */ int (*pull_reopen)( struct gcli_ctx *ctx, char const *owner, char const *reponame, gcli_id pr_number); /** * Close the given PR/MR */ int (*pull_close)( struct gcli_ctx *ctx, char const *owner, char const *reponame, gcli_id pr_number); /** * Submit PR/MR */ int (*perform_submit_pull)( struct gcli_ctx *ctx, struct gcli_submit_pull_options *opts); /** * Get a list of commits in the given PR/MR */ int (*get_pull_commits)( struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number, struct gcli_commit_list *out); /** Bitmask of unsupported fields in the pull summary for this * forge */ enum gcli_pull_summary_quirks { GCLI_PRS_QUIRK_ADDDEL = 0x01, GCLI_PRS_QUIRK_COMMITS = 0x02, GCLI_PRS_QUIRK_CHANGES = 0x04, 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)( struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number, struct gcli_pull *out); /** * Add labels to Pull Requests */ int (*pull_add_labels)( struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr, char const *const labels[], size_t labels_size); /** * Removes labels from Pull Requests */ int (*pull_remove_labels)( struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr, char const *const labels[], size_t labels_size); /** * Assign a PR to a milestone */ int (*pull_set_milestone)( struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pull, gcli_id milestone_id); /** * Clear a milestone on a PR */ int (*pull_clear_milestone)( struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pull); /** * Request review of a given pull request by a user */ int (*pull_add_reviewer)( struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pull, char const *username); /** * Change the title of a pull request */ int (*pull_set_title)( struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pull, char const *new_title); /** * Get a list of releases in the given repo */ int (*get_releases)( struct gcli_ctx *ctx, char const *owner, char const *repo, int max, struct gcli_release_list *out); /** * Create a new release */ int (*create_release)( struct gcli_ctx *ctx, struct gcli_new_release const *release); /** * Delete the release */ int (*delete_release)( struct gcli_ctx *ctx, char const *owner, char const *repo, char const *id); /** * Get a list of labels that are valid in the given repository */ int (*get_labels)( struct gcli_ctx *ctx, char const *owner, char const *repo, int max, struct gcli_label_list *out); /** * Create the given label * * The ID will be filled in for you */ int (*create_label)( struct gcli_ctx *ctx, char const *owner, char const *repo, struct gcli_label *label); /** * Delete the given label */ int (*delete_label)( struct gcli_ctx *ctx, char const *owner, char const *repo, char const *label); /** * Get a list of repos of the given owner */ int (*get_repos)( struct gcli_ctx *ctx, char const *owner, int max, struct gcli_repo_list *out); /** * Create the given repo */ int (*repo_create)( struct gcli_ctx *ctx, struct gcli_repo_create_options const *options, struct gcli_repo *out); /** * Delete the given repo */ int (*repo_delete)( struct gcli_ctx *ctx, char const *owner, char const *repo); /** * Change the visibility level of a repository */ int (*repo_set_visibility)( struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_repo_visibility vis); /** * Status summary for the account */ int (*get_notifications)( struct gcli_ctx *ctx, int max, 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)( struct gcli_ctx *ctx, char const *id); /** * Get an the http authentication header for use by curl */ char *(*make_authheader)(struct gcli_ctx *ctx, char const *token); /** * Get list of SSH keys */ int (*get_sshkeys)(struct gcli_ctx *ctx, struct gcli_sshkey_list *); /** * Add an SSH public key */ int (*add_sshkey)( struct gcli_ctx *ctx, char const *title, char const *public_key_path, struct gcli_sshkey *out); /** * Delete an SSH public key by its ID */ int (*delete_sshkey)(struct gcli_ctx *ctx, gcli_id id); /** * Get the error string from the API */ char const *(*get_api_error_string)( struct gcli_ctx *ctx, struct gcli_fetch_buffer *); /** * A key in the user json object sent by the API that represents * the user name */ char const *user_object_key; }; 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 */ gcli-2.3.0/include/gcli/forks.h000066400000000000000000000041061460062271200162710ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 FORK_H #define FORK_H #ifdef HAVE_CONFIG_H #include #endif #include #include struct gcli_fork { char *full_name; char *owner; char *date; int forks; }; struct gcli_fork_list { struct gcli_fork *forks; size_t forks_size; }; 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(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(struct gcli_fork_list *list); void gcli_fork_free(struct gcli_fork *fork); #endif /* FORK_H */ gcli-2.3.0/include/gcli/gcli.h000066400000000000000000000045601460062271200160670ustar00rootroot00000000000000/* * Copyright 2021, 2022 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_H #define GCLI_H #ifdef HAVE_CONFIG_H #include #endif #include #include #include enum gcli_output_flags { OUTPUT_SORTED = (1 << 0), OUTPUT_LONG = (1 << 1), }; 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; #define PRIid PRIu64 #ifdef IN_LIBGCLI #include #endif /* IN_LIBGCLI */ struct 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(struct gcli_ctx **ctx); char const *gcli_get_error(struct gcli_ctx *ctx); #endif /* GCLI_H */ gcli-2.3.0/include/gcli/gitea/000077500000000000000000000000001460062271200160645ustar00rootroot00000000000000gcli-2.3.0/include/gcli/gitea/comments.h000066400000000000000000000035551460062271200200720ustar00rootroot00000000000000/* * Copyright 2022 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 GITEA_COMMENTS_H #define GITEA_COMMENTS_H #ifdef HAVE_CONFIG_H #include #endif #include #include 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(struct gcli_ctx *ctx, struct gcli_submit_comment_opts opts, struct gcli_fetch_buffer *out); #endif /* GITEA_COMMENTS_H */ gcli-2.3.0/include/gcli/gitea/config.h000066400000000000000000000031161460062271200175030ustar00rootroot00000000000000/* * Copyright 2022 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 GITEA_CONFIG_H #define GITEA_CONFIG_H #ifdef HAVE_CONFIG_H #include #endif #include #include char *gitea_make_authheader(struct gcli_ctx *ctx, char const *token); #endif /* GITEA_CONFIG_H */ gcli-2.3.0/include/gcli/gitea/forks.h000066400000000000000000000033651460062271200173700ustar00rootroot00000000000000/* * Copyright 2022 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 GITEA_FORKS_H #define GITEA_FORKS_H #ifdef HAVE_CONFIG_H #include #endif #include 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(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *_in); #endif /* GITEA_FORKS_H */ gcli-2.3.0/include/gcli/gitea/issues.h000066400000000000000000000065351460062271200175610ustar00rootroot00000000000000/* * Copyright 2022 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 GITEA_ISSUES_H #define GITEA_ISSUES_H #ifdef HAVE_CONFIG_H #include #endif #include 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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue_number, struct gcli_issue *out); int gitea_submit_issue(struct gcli_ctx *ctx, struct gcli_submit_issue_options *opts, struct gcli_issue *out); int gitea_issue_close(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue_number); int gitea_issue_reopen(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue_number); 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(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(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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue, gcli_id milestone); int gitea_issue_clear_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue); 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); #endif /* GITEA_ISSUES_H */ gcli-2.3.0/include/gcli/gitea/labels.h000066400000000000000000000036051460062271200175030ustar00rootroot00000000000000/* * Copyright 2022 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 GITEA_LABELS_H #define GITEA_LABELS_H #ifdef HAVE_CONFIG_H #include #endif #include 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(struct gcli_ctx *ctx, char const *owner, char const *repo, struct gcli_label *label); int gitea_delete_label(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *label); #endif /* GITEA_LABELS_H */ gcli-2.3.0/include/gcli/gitea/milestones.h000066400000000000000000000047601460062271200204260ustar00rootroot00000000000000/* * 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_GITEA_MILESTONES_H #define GCLI_GITEA_MILESTONES_H #include int gitea_get_milestones(struct gcli_ctx *ctx, char const *const owner, char const *const repo, int max, struct gcli_milestone_list *out); int gitea_get_milestone(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id milestone, struct gcli_milestone *out); int gitea_create_milestone(struct gcli_ctx *ctx, struct gcli_milestone_create_args const *args); int gitea_delete_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone); 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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone, struct gcli_issue_list *out); #endif /* GCLI_GITEA_MILESTONES_H */ gcli-2.3.0/include/gcli/gitea/pulls.h000066400000000000000000000073161460062271200174030ustar00rootroot00000000000000/* * Copyright 2022 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 GITEA_PULLS_H #define GITEA_PULLS_H #ifdef HAVE_CONFIG_H #include #endif #include #include int gitea_search_pulls(struct gcli_ctx *ctx, char const *owner, char const *repo, struct gcli_pull_fetch_details const *details, int const max, struct gcli_pull_list *const 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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number, struct gcli_commit_list *out); int gitea_pull_submit(struct gcli_ctx *ctx, struct gcli_submit_pull_options *opts); 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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number); int gitea_pull_reopen(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number); 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(struct gcli_ctx *ctx, FILE *stream, char const *owner, char const *repo, gcli_id pr_number); 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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number, gcli_id milestone_id); int gitea_pull_clear_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number); 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(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id pull, char const *const title); #endif /* GITEA_PULLS_H */ gcli-2.3.0/include/gcli/gitea/releases.h000066400000000000000000000036021460062271200200410ustar00rootroot00000000000000/* * Copyright 2022 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 GITEA_RELEASES_H #define GITEA_RELEASES_H #ifdef HAVE_CONFIG_H #include #endif #include 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(struct gcli_ctx *ctx, struct gcli_new_release const *release); int gitea_delete_release(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *id); #endif /* GITEA_RELEASES_H */ gcli-2.3.0/include/gcli/gitea/repos.h000066400000000000000000000042071460062271200173700ustar00rootroot00000000000000/* * Copyright 2022 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 GITEA_REPOS_H #define GITEA_REPOS_H #ifdef HAVE_CONFIG_H #include #endif #include int gitea_get_repos(struct gcli_ctx *ctx, char const *owner, int max, struct gcli_repo_list *out); int gitea_get_own_repos(struct gcli_ctx *ctx, int max, struct gcli_repo_list *out); int gitea_repo_create(struct gcli_ctx *ctx, struct gcli_repo_create_options const *options, struct gcli_repo *out); int gitea_repo_delete(struct gcli_ctx *ctx, char const *owner, char const *repo); int gitea_repo_set_visibility(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_repo_visibility vis); #endif /* GITEA_REPOS_H */ gcli-2.3.0/include/gcli/gitea/sshkeys.h000066400000000000000000000033521460062271200177310ustar00rootroot00000000000000/* * 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_GITEA_SSHKEYS_H #define GCLI_GITEA_SSHKEYS_H #include int gitea_get_sshkeys(struct gcli_ctx *ctx, struct gcli_sshkey_list *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(struct gcli_ctx *ctx, gcli_id id); #endif /* GCLI_GITEA_SSHKEYS_H */ gcli-2.3.0/include/gcli/gitea/status.h000066400000000000000000000032151460062271200175610ustar00rootroot00000000000000/* * 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 GITEA_STATUS_H #define GITEA_STATUS_H #include int gitea_get_notifications(struct gcli_ctx *ctx, int max, struct gcli_notification_list *out); int gitea_notification_mark_as_read(struct gcli_ctx *ctx, char const *id); #endif /* GITEA_STATUS_H */ gcli-2.3.0/include/gcli/github/000077500000000000000000000000001460062271200162555ustar00rootroot00000000000000gcli-2.3.0/include/gcli/github/api.h000066400000000000000000000031151460062271200171770ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 GITHUB_API_H #define GITHUB_API_H #ifdef HAVE_CONFIG_H #include #endif #include char const *github_api_error_string(struct gcli_ctx *ctx, struct gcli_fetch_buffer *it); #endif /* GITHUB_API_H */ gcli-2.3.0/include/gcli/github/checks.h000066400000000000000000000040321460062271200176650ustar00rootroot00000000000000/* * Copyright 2022 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 GITHUB_CHECKS_H #define GITHUB_CHECKS_H #ifdef HAVE_CONFIG_H #include #endif #include #include struct gcli_github_check { char *name; char *status; char *conclusion; char *started_at; char *completed_at; gcli_id id; }; struct github_check_list { struct gcli_github_check *checks; size_t checks_size; }; int github_get_checks(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *ref, int max, struct github_check_list *checks); void github_free_checks(struct github_check_list *checks); void gcli_github_check_free(struct gcli_github_check *check); #endif /* GITHUB_CHECKS_H */ gcli-2.3.0/include/gcli/github/comments.h000066400000000000000000000035731460062271200202630ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 GITHUB_COMMENTS_H #define GITHUB_COMMENTS_H #ifdef HAVE_CONFIG_H #include #endif #include #include int github_perform_submit_comment(struct gcli_ctx *ctx, struct gcli_submit_comment_opts opts, struct gcli_fetch_buffer *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 */ gcli-2.3.0/include/gcli/github/config.h000066400000000000000000000031301460062271200176700ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 GITHUB_CONFIG_H #define GITHUB_CONFIG_H #ifdef HAVE_CONFIG_H #include #endif #include #include char *github_make_authheader(struct gcli_ctx *ctx, char const *token); #endif /* GITHUB_CONFIG_H */ gcli-2.3.0/include/gcli/github/forks.h000066400000000000000000000034041460062271200175530ustar00rootroot00000000000000/* * Copyright 2021, 2022 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) p * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef GITHUB_FORKS_H #define GITHUB_FORKS_H #ifdef HAVE_CONFIG_H #include #endif #include 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(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *_in); #endif /* GITHUB_FORKS_H */ gcli-2.3.0/include/gcli/github/gists.h000066400000000000000000000056041460062271200175640ustar00rootroot00000000000000/* * Copyright 2021, 2022 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_GITHUB_GISTS_H #define GCLI_GITHUB_GISTS_H #ifdef HAVE_CONFIG_H #include #endif #include #include #include struct gcli_gist_file { char *filename; char *language; char *url; char *type; size_t size; }; struct gcli_gist_list { struct gcli_gist *gists; size_t gists_size; }; struct gcli_gist { char *id; char *owner; char *url; char *date; char *git_pull_url; char *description; struct gcli_gist_file *files; size_t files_size; }; struct gcli_new_gist { FILE *file; char const *file_name; char const *gist_description; }; int gcli_get_gists(struct gcli_ctx *ctx, char const *user, int max, struct gcli_gist_list *list); int gcli_get_gist(struct gcli_ctx *ctx, char const *gist_id, struct gcli_gist *out); int gcli_create_gist(struct gcli_ctx *ctx, struct gcli_new_gist); int gcli_delete_gist(struct gcli_ctx *ctx, char const *gist_id); 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 * files in a gist NOT as an array but as an object whose keys are the * 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(struct gcli_ctx *ctx, struct json_stream *stream, struct gcli_gist *gist); #endif /* GCLI_GITHUB_GISTS_H */ gcli-2.3.0/include/gcli/github/issues.h000066400000000000000000000071241460062271200177450ustar00rootroot00000000000000/* * Copyright 2021, 2022 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_ISSUES_H #define GCLI_ISSUES_H #ifdef HAVE_CONFIG_H #include #endif #include #include int github_fetch_issues(struct gcli_ctx *ctx, char *url, int max, struct 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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue_number, struct gcli_issue *out); int github_issue_close(struct 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(struct gcli_ctx *ctx, struct gcli_submit_issue_options *opts, struct gcli_issue *out); 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(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(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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue, gcli_id milestone); int github_issue_clear_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue); 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); #endif /* GCLI_ISSUES_H */ gcli-2.3.0/include/gcli/github/labels.h000066400000000000000000000036161460062271200176760ustar00rootroot00000000000000/* * Copyright 2022 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 GITHUB_LABELS_H #define GITHUB_LABELS_H #ifdef HAVE_CONFIG_H #include #endif #include 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(struct gcli_ctx *ctx, char const *owner, char const *repo, struct gcli_label *label); int github_delete_label(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *label); #endif /* GITHUB_LABELS_H */ gcli-2.3.0/include/gcli/github/milestones.h000066400000000000000000000047531460062271200206210ustar00rootroot00000000000000/* * 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_GITHUB_MILESTONES_H #define GCLI_GITHUB_MILESTONES_H #include 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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone, struct gcli_milestone *out); int github_create_milestone(struct gcli_ctx *ctx, struct gcli_milestone_create_args const *args); int github_delete_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone); int github_milestone_get_issues(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone, struct gcli_issue_list *out); int github_milestone_set_duedate(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone, char const *date); #endif /* GCLI_GITHUB_MILESTONES_H */ gcli-2.3.0/include/gcli/github/pulls.h000066400000000000000000000067211460062271200175730ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 GITHUB_PULLS_H #define GITHUB_PULLS_H #ifdef HAVE_CONFIG_H #include #endif #include #include int github_search_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(struct gcli_ctx *ctx, FILE *stream, char const *owner, char const *reponame, gcli_id pr_number); 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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number, struct gcli_pull_checks_list *out); 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(struct gcli_ctx *ctx, char const *owner, char const *reponame, gcli_id pr_number); int github_pull_close(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number); int github_perform_submit_pull(struct gcli_ctx *ctx, struct gcli_submit_pull_options *opts); 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(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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number, char const *username); 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 */ gcli-2.3.0/include/gcli/github/releases.h000066400000000000000000000036511460062271200202360ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 GITHUB_RELEASES_H #define GITHUB_RELEASES_H #ifdef HAVE_CONFIG_H #include #endif #include 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(struct gcli_ctx *ctx, struct gcli_new_release const *release); int github_delete_release(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *id); #endif /* GITHUB_RELEASES_H */ gcli-2.3.0/include/gcli/github/repos.h000066400000000000000000000042621460062271200175620ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 GITHUB_REPOS_H #define GITHUB_REPOS_H #ifdef HAVE_CONFIG_H #include #endif #include int github_get_repos(struct gcli_ctx *ctx, char const *owner, int max, struct gcli_repo_list *out); int github_get_own_repos(struct gcli_ctx *ctx, int max, struct gcli_repo_list *out); int github_repo_delete(struct gcli_ctx *ctx, char const *owner, char const *repo); int github_repo_create(struct gcli_ctx *ctx, struct gcli_repo_create_options const *options, struct gcli_repo *out); int github_repo_set_visibility(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_repo_visibility vis); #endif /* GITHUB_REPOS_H */ gcli-2.3.0/include/gcli/github/sshkeys.h000066400000000000000000000033641460062271200201250ustar00rootroot00000000000000/* * 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_GITHUB_SSHKEYS_H #define GCLI_GITHUB_SSHKEYS_H #include int github_get_sshkeys(struct gcli_ctx *ctx, struct gcli_sshkey_list *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(struct gcli_ctx *ctx, gcli_id id); #endif /* GCLI_GITHUB_SSHKEYS_H */ gcli-2.3.0/include/gcli/github/status.h000066400000000000000000000033031460062271200177500ustar00rootroot00000000000000/* * Copyright 2022 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 GITHUB_STATUS_H #define GITHUB_STATUS_H #ifdef HAVE_CONFIG_H #include #endif #include 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 */ gcli-2.3.0/include/gcli/gitlab/000077500000000000000000000000001460062271200162355ustar00rootroot00000000000000gcli-2.3.0/include/gcli/gitlab/api.h000066400000000000000000000032171460062271200171620ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 GITLAB_API_H #define GITLAB_API_H #ifdef HAVE_CONFIG_H #include #endif #include 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 */ gcli-2.3.0/include/gcli/gitlab/comments.h000066400000000000000000000041411460062271200202330ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 GITLAB_COMMENTS_H #define GITLAB_COMMENTS_H #ifdef HAVE_CONFIG_H #include #endif #include #include 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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue, struct 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 */ gcli-2.3.0/include/gcli/gitlab/config.h000066400000000000000000000031301460062271200176500ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 GITLAB_CONFIG_H #define GITLAB_CONFIG_H #ifdef HAVE_CONFIG_H #include #endif #include #include char *gitlab_make_authheader(struct gcli_ctx *ctx, char const *token); #endif /* GITLAB_CONFIG_H */ gcli-2.3.0/include/gcli/gitlab/forks.h000066400000000000000000000034021460062271200175310ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 GITLAB_FORKS_H #define GITLAB_FORKS_H #ifdef HAVE_CONFIG_H #include #endif #include 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(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *_in); #endif /* GITLAB_FORKS_H */ gcli-2.3.0/include/gcli/gitlab/issues.h000066400000000000000000000071021460062271200177210ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 GITLAB_ISSUES_H #define GITLAB_ISSUES_H #ifdef HAVE_CONFIG_H #include #endif #include #include int gitlab_fetch_issues(struct gcli_ctx *ctx, char *url, int max, struct 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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue_number, struct gcli_issue *out); int gitlab_issue_close(struct 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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue_number, char const *assignee); int gitlab_perform_submit_issue(struct gcli_ctx *ctx, struct gcli_submit_issue_options *opts, struct gcli_issue *out); 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(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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue, gcli_id milestone); int gitlab_issue_clear_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue); 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 */ gcli-2.3.0/include/gcli/gitlab/labels.h000066400000000000000000000036161460062271200176560ustar00rootroot00000000000000/* * Copyright 2022 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 GITLAB_LABELS_H #define GITLAB_LABELS_H #ifdef HAVE_CONFIG_H #include #endif #include 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(struct gcli_ctx *ctx, char const *owner, char const *repo, struct gcli_label *label); int gitlab_delete_label(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *label); #endif /* GITLAB_LABELS_H */ gcli-2.3.0/include/gcli/gitlab/merge_requests.h000066400000000000000000000107361460062271200214470ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 GITLAB_MERGE_REQUESTS_H #define GITLAB_MERGE_REQUESTS_H #ifdef HAVE_CONFIG_H #include #endif #include struct gitlab_reviewer_id_list { gcli_id *reviewers; size_t reviewers_size; }; /* Structs used for internal patch generator. Gitlab does not provide * an endpoint for doing this properly. */ struct gitlab_diff { char *diff; char *old_path; char *new_path; char *a_mode; char *b_mode; bool new_file; bool renamed_file; bool deleted_file; }; struct gitlab_diff_list { struct gitlab_diff *diffs; size_t diffs_size; }; int gitlab_fetch_mrs(struct gcli_ctx *ctx, char *url, int max, struct gcli_pull_list *list); int gitlab_get_mrs(struct gcli_ctx *ctx, char const *owner, char const *reponame, struct gcli_pull_fetch_details const *details, int max, struct gcli_pull_list *out); 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(struct gcli_ctx *ctx, FILE *stream, char const *owner, char const *reponame, gcli_id mr_number); 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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id mr_number); int gitlab_mr_reopen(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id mr_number); 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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id mr_number, struct gcli_commit_list *out); int gitlab_perform_submit_mr(struct gcli_ctx *ctx, struct gcli_submit_pull_options *opts); 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(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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id mr_number, gcli_id milestone_id); int gitlab_mr_clear_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id mr_number); 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(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const id, char const *const new_title); #endif /* GITLAB_MERGE_REQUESTS_H */ gcli-2.3.0/include/gcli/gitlab/milestones.h000066400000000000000000000050421460062271200205710ustar00rootroot00000000000000/* * 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_GITLAB_MILESTONES_H #define GCLI_GITLAB_MILESTONES_H #ifdef HAVE_CONFIG_H #include #endif #include 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(struct gcli_ctx *ctx, struct gcli_milestone_create_args const *args); int gitlab_delete_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone); 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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone, struct gcli_issue_list *out); int gitlab_milestone_set_duedate(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone, char const *date); #endif /* GCLI_GITLAB_MILESTONES_H */ gcli-2.3.0/include/gcli/gitlab/pipelines.h000066400000000000000000000070161460062271200204020ustar00rootroot00000000000000/* * Copyright 2022 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 GITLAB_PIPELINES_H #define GITLAB_PIPELINES_H #ifdef HAVE_CONFIG_H #include #endif #include struct gitlab_pipeline { gcli_id id; char *status; char *created_at; char *updated_at; char *ref; char *sha; char *source; }; struct gitlab_pipeline_list { struct gitlab_pipeline *pipelines; size_t pipelines_size; }; struct gitlab_job { gcli_id id; char *status; char *stage; char *name; char *ref; char *created_at; char *started_at; char *finished_at; double duration; char *runner_name; char *runner_description; double coverage; }; struct gitlab_job_list { struct gitlab_job *jobs; size_t jobs_size; }; 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(struct gitlab_pipeline *pipeline); void gitlab_pipelines_free(struct gitlab_pipeline_list *list); 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(struct gitlab_job_list *jobs); void gitlab_free_job(struct gitlab_job *job); 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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id job_id); int gitlab_job_retry(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id job_id); 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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id mr_id, struct gitlab_pipeline_list *list); 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 */ gcli-2.3.0/include/gcli/gitlab/releases.h000066400000000000000000000040471460062271200202160ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 GITLAB_RELEASES_H #define GITLAB_RELEASES_H #ifdef HAVE_CONFIG_H #include #endif #include 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(struct gcli_ctx *ctx, struct gcli_new_release const *release); int gitlab_delete_release(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *id); void gitlab_fixup_release_assets(struct gcli_ctx *ctx, struct gcli_release *const release); #endif /* GITLAB_RELEASES_H */ gcli-2.3.0/include/gcli/gitlab/repos.h000066400000000000000000000044561460062271200175470ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 GITLAB_REPOS_H #define GITLAB_REPOS_H #ifdef HAVE_CONFIG_H #include #endif #include int gitlab_get_repo(struct gcli_ctx *ctx, char const *owner, char const *repo, struct gcli_repo *out); int gitlab_get_repos(struct gcli_ctx *ctx, char const *owner, int max, struct gcli_repo_list *out); int gitlab_get_own_repos(struct gcli_ctx *ctx, int max, struct gcli_repo_list *out); int gitlab_repo_delete(struct gcli_ctx *ctx, char const *owner, char const *repo); int gitlab_repo_create(struct gcli_ctx *ctx, struct gcli_repo_create_options const *options, struct gcli_repo *out); int gitlab_repo_set_visibility(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_repo_visibility vis); #endif /* GITLAB_REPOS_H */ gcli-2.3.0/include/gcli/gitlab/snippets.h000066400000000000000000000042041460062271200202530ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 GITLAB_SNIPPETS_H #define GITLAB_SNIPPETS_H #ifdef HAVE_CONFIG_H #include #endif #include struct gcli_gitlab_snippet { int id; char *title; char *filename; char *date; char *author; char *visibility; char *raw_url; }; struct gcli_gitlab_snippet_list { struct gcli_gitlab_snippet *snippets; size_t snippets_size; }; 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_snippet_delete(struct gcli_ctx *ctx, char const *snippet_id); int gcli_snippet_get(struct gcli_ctx *ctx, char const *snippet_id, FILE *stream); void gcli_gitlab_snippet_free(struct gcli_gitlab_snippet *snippet); #endif /* GITLAB_SNIPPETS_H */ gcli-2.3.0/include/gcli/gitlab/sshkeys.h000066400000000000000000000034541460062271200201050ustar00rootroot00000000000000/* * 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_GITLAB_SSHKEYS_H #define GCLI_GITLAB_SSHKEYS_H #ifdef HAVE_CONFIG_H #include #endif #include int gitlab_get_sshkeys(struct gcli_ctx *ctx, struct gcli_sshkey_list *list); 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(struct gcli_ctx *ctx, gcli_id id); #endif /* GCLI_GITLAB_SSHKEYS_H */ gcli-2.3.0/include/gcli/gitlab/status.h000066400000000000000000000033031460062271200177300ustar00rootroot00000000000000/* * Copyright 2022 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 GITLAB_STATUS_H #define GITLAB_STATUS_H #ifdef HAVE_CONFIG_H #include #endif #include 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 */ gcli-2.3.0/include/gcli/issues.h000066400000000000000000000111041460062271200164540ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 ISSUES_H #define ISSUES_H #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include struct gcli_issue { gcli_id number; 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; char *body; char **labels; size_t labels_size; char **assignees; size_t assignees_size; /* workaround for GitHub where PRs are also issues */ int is_pr; char *milestone; }; struct gcli_submit_issue_options { char const *owner; char const *repo; char *title; char *body; struct gcli_nvlist extra; }; struct gcli_issue_list { struct gcli_issue *issues; size_t issues_size; }; struct gcli_issue_fetch_details { bool all; /* disregard the issue state */ 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_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(struct gcli_issue_list *); 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(struct gcli_issue *it); int gcli_issue_close(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue_number); int gcli_issue_reopen(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue_number); int gcli_issue_submit(struct gcli_ctx *ctx, struct gcli_submit_issue_options *); 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(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(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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue, int milestone); int gcli_issue_clear_milestone(struct gcli_ctx *cxt, char const *owner, char const *repo, gcli_id issue); 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 */ gcli-2.3.0/include/gcli/json_gen.h000066400000000000000000000053271460062271200167550ustar00rootroot00000000000000/* * 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_JSON_GEN_H #define GCLI_JSON_GEN_H #ifdef HAVE_CONFIG_H #include #endif #include #include #include enum { GCLI_JSONGEN_ARRAY = 1, GCLI_JSONGEN_OBJECT = 2, }; struct gcli_jsongen { char *buffer; size_t buffer_size; size_t buffer_capacity; int scopes[32]; /* scope stack */ size_t scopes_size; /* scope stack pointer */ bool await_object_value; /* when in an object scope set to true if * we expect a value and not a key */ bool first_elem; /* first element in object/array */ }; 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(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 */ gcli-2.3.0/include/gcli/json_util.h000066400000000000000000000120501460062271200171500ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 JSON_UTIL_H #define JSON_UTIL_H #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #define get_int(ctx, input, out) get_int_(ctx, input, out, __func__) #define get_id(ctx, input, out) get_id_(ctx, input, out, __func__) #define get_long(ctx, input, out) get_long_(ctx, input, out, __func__) #define get_size_t(ctx, input, out) get_size_t_(ctx, input, out, __func__) #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_string(ctx, input, out) get_int_to_string_(ctx, input, out, __func__) 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(struct gcli_ctx *ctx, json_stream *input, char const *fmt, ...); static inline char const * gcli_json_bool(bool it) { return it ? "true" : "false"; } static inline int get_int_to_string_(struct gcli_ctx *ctx, json_stream *input, char **out, char const *const fn) { int rc; long val; rc = get_long_(ctx, input, &val, fn); if (rc < 0) return rc; *out = sn_asprintf("%ld", val); return 0; } #define SKIP_OBJECT_VALUE(stream) \ do { \ enum json_type value_type = json_next(stream); \ \ switch (value_type) { \ case JSON_ARRAY: \ json_skip_until(stream, JSON_ARRAY_END); \ break; \ case JSON_OBJECT: \ json_skip_until(stream, JSON_OBJECT_END); \ break; \ default: \ break; \ } \ } while (0) #endif /* JSON_UTIL_H */ gcli-2.3.0/include/gcli/labels.h000066400000000000000000000042661460062271200164160ustar00rootroot00000000000000/* * Copyright 2022 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 LABELS_H #define LABELS_H #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include struct gcli_label { gcli_id id; char *name; char *description; uint32_t colour; }; struct gcli_label_list { struct gcli_label *labels; size_t labels_size; }; 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(struct gcli_label *label); void gcli_free_labels(struct gcli_label_list *labels); int gcli_create_label(struct gcli_ctx *ctx, char const *owner, char const *repo, struct gcli_label *label); int gcli_delete_label(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *label); #endif /* LABELS_H */ gcli-2.3.0/include/gcli/milestones.h000066400000000000000000000064101460062271200173270ustar00rootroot00000000000000/* * 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_MILESTONES_H #define GCLI_MILESTONES_H #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include struct gcli_milestone { gcli_id id; char *title; char *state; char *created_at; /* Extended info */ char *description; char *updated_at; char *due_date; bool expired; /* Github and Gitea Specific */ int open_issues; int closed_issues; }; struct gcli_milestone_list { struct gcli_milestone *milestones; size_t milestones_size; }; struct gcli_milestone_create_args { /* These are const because they are coming from either an mmapped * buffer or from command line arguments. They should never ever * get free()-ed */ char const *title; char const *description; char const *owner; char const *repo; }; 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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone, struct gcli_milestone *out); int gcli_create_milestone(struct gcli_ctx *ctx, struct gcli_milestone_create_args const *args); int gcli_delete_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone); void gcli_free_milestone(struct gcli_milestone *it); void gcli_free_milestones(struct gcli_milestone_list *it); int gcli_milestone_get_issues(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone, struct gcli_issue_list *out); int gcli_milestone_set_duedate(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id milestone, char const *date); #endif /* GCLI_MILESTONES_H */ gcli-2.3.0/include/gcli/nvlist.h000066400000000000000000000040041460062271200164610ustar00rootroot00000000000000/* * 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 */ gcli-2.3.0/include/gcli/pgen.h000066400000000000000000000030601460062271200160740ustar00rootroot00000000000000#ifndef PGEN_H #define PGEN_H #ifdef HAVE_CONFIG_H #include #endif #include #include /* PGen command line options */ enum { DUMP_PLAIN = 0, DUMP_C = 1, DUMP_H = 2 }; extern int dumptype; extern FILE *outfile; extern char *outfilename; /* Types used in the parser to represent nodes in the AST */ struct strlit { char *text; }; struct ident { char *text; }; struct objentry { enum { OBJENTRY_SIMPLE, OBJENTRY_ARRAY, OBJENTRY_CONTINUATION } kind; /* either a simple field or an array */ char *jsonname; char *name; char *type; char *parser; struct objentry *next; /* linked list */ }; struct objparser { enum { OBJPARSER_ENTRIES, OBJPARSER_SELECT } kind; char *name; char *returntype; bool is_struct; struct objentry *entries; struct { char *fieldtype; char *fieldname; } select; }; struct arrayparser { char *name; bool is_struct; char *returntype; char *parser; }; void yyerror(char const *message); /* Functions to dump data before starting the actual parser */ void header_dump_c(void); void header_dump_h(void); /* Functions called while parsing */ void objparser_dump_c(struct objparser *); void objparser_dump_h(struct objparser *); void objparser_dump_plain(struct objparser *); void arrayparser_dump_c(struct arrayparser *); void arrayparser_dump_h(struct arrayparser *); void include_dump_c(char const *); void include_dump_h(char const *); /* Functions called after parsing */ void footer_dump_h(void); #endif /* PGEN_H */ gcli-2.3.0/include/gcli/pulls.h000066400000000000000000000146371460062271200163160ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 PULLS_H #define PULLS_H #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include struct gcli_pull_list { struct gcli_pull *pulls; size_t pulls_size; }; struct gcli_pull { char *author; char *state; char *title; char *body; char *created_at; char *commits_link; char *head_label; char *base_label; char *head_sha; char *base_sha; char *milestone; gcli_id id; gcli_id number; char *node_id; /* Github: GraphQL compat */ int comments; int additions; int deletions; int commits; int changed_files; int head_pipeline_id; /* GitLab specific */ char *coverage; /* Gitlab Specific */ char **labels; size_t labels_size; char **reviewers; /**< User names */ size_t reviewers_size; /**< Number of elements in the reviewers list */ bool merged; bool mergeable; bool draft; bool automerge; }; struct gcli_commit { char *sha, *long_sha, *message, *date, *author, *email; }; struct gcli_commit_list { struct gcli_commit *commits; size_t commits_size; }; /* Options to submit to the gh api for creating a PR */ struct gcli_submit_pull_options { char const *owner; char const *repo; 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 { bool all; /** Ignore status of the pull requests */ char const *author; /** Author of the pull request or NULL */ char const *label; /** a label attached to the pull request or NULL */ char const *milestone; /** a milestone this pull request is a part of or NULL */ char const *search_term; /** some text to match in the pull request or NULL */ }; /** Generic list of checks ran on a pull request * * 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 struct gitlab_pipeline* * * You can cast this type to the list type of either one of them. */ struct gcli_pull_checks_list { void *checks; size_t checks_size; int forge_type; }; int gcli_search_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(struct gcli_pull *it); void gcli_pulls_free(struct gcli_pull_list *list); 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(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(struct gcli_pull_checks_list *list); 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(struct gcli_commit_list *list); 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(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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number, enum gcli_merge_flags flags); int gcli_pull_close(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number); int gcli_pull_reopen(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number); 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(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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number, int milestone_id); int gcli_pull_clear_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number); 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(struct gcli_ctx *ctx, FILE *out, char const *owner, char const *repo, gcli_id pr_number); 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 */ gcli-2.3.0/include/gcli/releases.h000066400000000000000000000055121460062271200167520ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 RELEASES_H #define RELEASES_H #ifdef HAVE_CONFIG_H #include #endif #include #include struct gcli_release_asset { char *name; char *url; }; struct gcli_release { 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 { struct gcli_release *releases; size_t releases_size; }; struct gcli_release_asset_upload { char *label; char *name; char *path; }; #define GCLI_RELEASE_MAX_ASSETS 16 struct gcli_new_release { char const *owner; char const *repo; char const *tag; char const *name; char *body; char const *commitish; bool draft; bool prerelease; struct gcli_release_asset_upload assets[GCLI_RELEASE_MAX_ASSETS]; size_t assets_size; }; 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(struct gcli_release_list *); int gcli_create_release(struct gcli_ctx *ctx, struct gcli_new_release const *); int gcli_release_push_asset(struct gcli_ctx *, struct gcli_new_release *, struct gcli_release_asset_upload); int gcli_delete_release(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *id); void gcli_release_free(struct gcli_release *release); #endif /* RELEASES_H */ gcli-2.3.0/include/gcli/repos.h000066400000000000000000000047271460062271200163060ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 REPOS_H #define REPOS_H #ifdef HAVE_CONFIG_H #include #endif #include #include struct gcli_repo { gcli_id id; char *full_name; char *name; char *owner; char *date; char *visibility; bool is_fork; }; struct gcli_repo_list { struct gcli_repo *repos; size_t repos_size; }; struct gcli_repo_create_options { char *name; char *description; bool private; }; typedef enum { GCLI_REPO_VISIBILITY_PRIVATE = 1, GCLI_REPO_VISIBILITY_PUBLIC, } gcli_repo_visibility; int gcli_get_repos(struct gcli_ctx *ctx, char const *owner, int max, struct gcli_repo_list *list); void gcli_repos_free(struct gcli_repo_list *list); void gcli_repo_free(struct gcli_repo *it); int gcli_repo_delete(struct gcli_ctx *ctx, char const *owner, char const *repo); int gcli_repo_create(struct gcli_ctx *ctx, struct gcli_repo_create_options const *, struct gcli_repo *out); int gcli_repo_set_visibility(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_repo_visibility visibility); #endif /* REPOS_H */ gcli-2.3.0/include/gcli/sshkeys.h000066400000000000000000000040041460062271200166330ustar00rootroot00000000000000/* * 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_SSHKEYS_H #define GCLI_SSHKEYS_H #ifdef HAVE_CONFIG_H #include #endif #include #include struct gcli_sshkey { gcli_id id; char *title; char *key; char *created_at; }; struct gcli_sshkey_list { struct gcli_sshkey *keys; size_t keys_size; }; 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 */ gcli-2.3.0/include/gcli/status.h000066400000000000000000000040231460062271200164660ustar00rootroot00000000000000/* * Copyright 2022 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 STATUS_H #define STATUS_H #ifdef HAVE_CONFIG_H #include #endif #include #include struct gcli_notification { char *id; char *title; char *reason; char *date; char *type; char *repository; }; struct gcli_notification_list { struct gcli_notification *notifications; size_t notifications_size; }; 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 */ gcli-2.3.0/libgcli.pc.in000066400000000000000000000004571460062271200147760ustar00rootroot00000000000000# libgcli package-config file # prefix=@prefix@ exec_prefix=${prefix} libdir=@libdir@ includedir=@includedir@ Name: gcli Description: Library to interact with various Git forges Version: 2.0 URL: https://herrhotzenplotz.de/gcli Requires: libcurl >= 8.0 Libs: -L${libdir} -lgcli Cflags: -I${includedir} gcli-2.3.0/m4/000077500000000000000000000000001460062271200127525ustar00rootroot00000000000000gcli-2.3.0/m4/.gitkeep000066400000000000000000000000001460062271200143710ustar00rootroot00000000000000gcli-2.3.0/src/000077500000000000000000000000001460062271200132215ustar00rootroot00000000000000gcli-2.3.0/src/attachments.c000066400000000000000000000044321460062271200157030ustar00rootroot00000000000000/* * 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); } gcli-2.3.0/src/base64.c000066400000000000000000000100021460062271200144420ustar00rootroot00000000000000/* * 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; } gcli-2.3.0/src/bugzilla/000077500000000000000000000000001460062271200150325ustar00rootroot00000000000000gcli-2.3.0/src/bugzilla/api.c000066400000000000000000000035321460062271200157520ustar00rootroot00000000000000/* * 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; } gcli-2.3.0/src/bugzilla/attachments.c000066400000000000000000000044221460062271200175130ustar00rootroot00000000000000/* * 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; } gcli-2.3.0/src/bugzilla/bugs-parser.c000066400000000000000000000134421460062271200174340ustar00rootroot00000000000000/* * 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; } gcli-2.3.0/src/bugzilla/bugs.c000066400000000000000000000234031460062271200161400ustar00rootroot00000000000000/* * 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 *const ctx, struct gcli_submit_issue_options *const opts, struct gcli_issue *const 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}; struct gcli_fetch_buffer buffer = {0}, *_buffer = NULL; 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)); if (out) _buffer = &buffer; rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, _buffer); if (out && rc == 0) { struct json_stream stream = {0}; gcli_id id = 0; json_open_buffer(&stream, buffer.data, buffer.length); rc = parse_bugzilla_bug_creation_result(ctx, &stream, &id); json_close(&stream); if (rc == 0) rc = bugzilla_get_bug(ctx, NULL, NULL, id, out); } free(buffer.data); free(url); free(payload); err_jsongen_init: free(token); return rc; } gcli-2.3.0/src/bugzilla/config.c000066400000000000000000000030221460062271200164400ustar00rootroot00000000000000/* * 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; } gcli-2.3.0/src/cmd/000077500000000000000000000000001460062271200137645ustar00rootroot00000000000000gcli-2.3.0/src/cmd/api.c000066400000000000000000000063521460062271200147070ustar00rootroot00000000000000/* * Copyright 2022 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. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #ifdef HAVE_GETOPT_H #include #endif static void usage(void) { fprintf(stderr, "usage: gcli api [-a] \n"); fprintf(stderr, "OPTIONS:\n"); fprintf(stderr, " -a Fetch all pages of data\n"); fprintf(stderr, " path Path to put after the API base URL\n"); fprintf(stderr, "\n"); version(); copyright(); } static void fetch_all(char *_url) { char *url = NULL, *next_url = NULL; url = _url; do { struct gcli_fetch_buffer buffer = {0}; if (gcli_fetch(g_clictx, url, &next_url, &buffer) < 0) errx(1, "gcli: error: failed to fetch data: %s", gcli_get_error(g_clictx)); fwrite(buffer.data, buffer.length, 1, stdout); free(buffer.data); if (url != _url) free(url); } while ((url = next_url)); } int subcommand_api(int argc, char *argv[]) { char *url = NULL, *path = NULL; int ch, do_all = 0; struct option options[] = { { .name = "all", .has_arg = no_argument, .flag = NULL, .val = 'a' }, {0} }; while ((ch = getopt_long(argc, argv, "+a", options, NULL)) != -1) { switch (ch) { case 'a': do_all = 1; break; default: usage(); return EXIT_FAILURE; } } argc -= optind; argv += optind; if (argc == 1) { path = shift(&argc, &argv); } else { if (!argc) errx(1, "gcli: error: missing path"); else errx(1, "gcli: error: too many arguments"); } if (path[0] == '/') url = sn_asprintf("%s%s", gcli_get_apibase(g_clictx), path); else url = sn_asprintf("%s/%s", gcli_get_apibase(g_clictx), path); if (do_all) fetch_all(url); else if (gcli_curl(g_clictx, stdout, url, "application/json") < 0) errx(1, "gcli: error: failed to fetch data: %s", gcli_get_error(g_clictx)); free(url); return EXIT_SUCCESS; } gcli-2.3.0/src/cmd/attachments.c000066400000000000000000000110611460062271200164420ustar00rootroot00000000000000/* * 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; } gcli-2.3.0/src/cmd/ci.c000066400000000000000000000122221460062271200145220ustar00rootroot00000000000000/* * Copyright 2022 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. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #ifdef HAVE_GETOPT_H #include #endif #include static void usage(void) { fprintf(stderr, "usage: gcli ci [-o owner -r repo] [-n number] ref\n"); fprintf(stderr, "OPTIONS:\n"); fprintf(stderr, " -o owner The repository owner\n"); fprintf(stderr, " -r repo The repository name\n"); fprintf(stderr, " -n number Number of check runs to fetch (-1 = everything)\n"); fprintf(stderr, "\n"); version(); copyright(); } void github_print_checks(struct github_check_list const *const list) { gcli_tbl table; 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 }, { .name = "STARTED", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, { .name = "COMPLETED", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, { .name = "NAME", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, }; if (!list->checks_size) { fprintf(stderr, "No checks\n"); return; } table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!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, list->checks[i].conclusion, list->checks[i].started_at, list->checks[i].completed_at, list->checks[i].name); } gcli_tbl_end(table); } int github_checks(char const *const owner, char const *const repo, char const *const ref, int const max) { struct github_check_list list = {0}; int rc = 0; rc = github_get_checks(g_clictx, owner, repo, ref, max, &list); if (rc < 0) return rc; github_print_checks(&list); github_free_checks(&list); return rc; } int subcommand_ci(int argc, char *argv[]) { int ch = 0; char const *owner = NULL, *repo = NULL; char const *ref = NULL; int count = -1; /* fetch all checks by default */ /* Parse options */ struct option const options[] = { {.name = "repo", .has_arg = required_argument, .flag = NULL, .val = 'r'}, {.name = "owner", .has_arg = required_argument, .flag = NULL, .val = 'o'}, {.name = "count", .has_arg = required_argument, .flag = NULL, .val = 'c'}, {0} }; while ((ch = getopt_long(argc, argv, "n:o:r:", options, NULL)) != -1) { switch (ch) { case 'o': owner = optarg; break; case 'r': repo = optarg; break; case 'n': { char *endptr = NULL; count = strtol(optarg, &endptr, 10); if (endptr != (optarg + strlen(optarg))) err(1, "ci: cannot parse argument to -n"); } break; case '?': default: usage(); return EXIT_FAILURE; } } argc -= optind; argv += optind; /* Check that we have exactly one left argument and print proper * error messages */ if (argc < 1) { fprintf(stderr, "gcli: error: missing ref\n"); usage(); return EXIT_FAILURE; } if (argc > 1) { fprintf(stderr, "gcli: error: stray arguments\n"); usage(); return EXIT_FAILURE; } /* Save the ref */ ref = argv[0]; check_owner_and_repo(&owner, &repo); /* 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, "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, "gcli: error: failed to get github checks: %s", gcli_get_error(g_clictx)); return EXIT_SUCCESS; } gcli-2.3.0/src/cmd/cmd.c000066400000000000000000000102531460062271200146740ustar00rootroot00000000000000/* * Copyright 2022 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. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include void copyright(void) { fprintf( stderr, "Copyright 2021, 2022, 2023 Nico Sonack " " and contributors.\n"); } void version(void) { fprintf(stderr, PACKAGE_STRING" ("HOSTOS")\n"); } void longversion(void) { version(); fprintf(stderr, "Using %s\n" "Using vendored pdjson library\n" "\n" "Project website: "PACKAGE_URL"\n" "Bug reports: "PACKAGE_BUGREPORT"\n", curl_version()); } 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, "gcli: error: missing either explicit owner or repo"); if (*owner == NULL) { int rc = gcli_config_get_repo(g_clictx, owner, repo); if (rc < 0) errx(1, "gcli: error: %s", gcli_get_error(g_clictx)); } } /* Parses (and updates) the given argument list into two seperate lists: * * --add -> add_labels * --remove -> remove_labels */ void parse_labels_options(int *argc, char ***argv, const char ***_add_labels, size_t *_add_labels_size, const char ***_remove_labels, size_t *_remove_labels_size) { const char **add_labels = NULL, **remove_labels = NULL; size_t add_labels_size = 0, remove_labels_size = 0; /* Collect add/delete labels */ while (*argc > 0) { if (strcmp(**argv, "add") == 0) { shift(argc, argv); add_labels = realloc( add_labels, (add_labels_size + 1) * sizeof(*add_labels)); add_labels[add_labels_size++] = shift(argc, argv); } else if (strcmp(**argv, "remove") == 0) { shift(argc, argv); remove_labels = realloc( remove_labels, (remove_labels_size + 1) * sizeof(*remove_labels)); remove_labels[remove_labels_size++] = shift(argc, argv); } else { break; } } *_add_labels = add_labels; *_add_labels_size = add_labels_size; *_remove_labels = remove_labels; *_remove_labels_size = remove_labels_size; } /* delete the repo (and ask for confirmation) * * NOTE: this procedure is here because it is used by both the forks * and repo subcommand. Ideally it should be moved into the 'repos' * code but I don't wanna make it exported from there. */ void delete_repo(bool always_yes, const char *owner, const char *repo) { bool delete = false; if (!always_yes) { delete = sn_yesno( "Are you sure you want to delete %s/%s?", owner, repo); } else { delete = true; } if (!delete) errx(1, "gcli: Operation aborted"); if (gcli_repo_delete(g_clictx, owner, repo) < 0) errx(1, "gcli: error: failed to delete repo"); } gcli-2.3.0/src/cmd/cmdconfig.c000066400000000000000000000544211460062271200160670ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 #include #include #include #ifdef HAVE_GETOPT_H #include #endif #include #include struct gcli_config_section { TAILQ_ENTRY(gcli_config_section) next; struct gcli_config_entries entries; sn_sv title; }; struct gcli_config { TAILQ_HEAD(gcli_config_sections, gcli_config_section) sections; char const *override_default_account; char const *override_remote; int override_forgetype; int colours_disabled; /* NO_COLOR set or output is not a TTY */ int force_colours; /* -c option was given */ sn_sv buffer; void *mmap_pointer; bool inited; }; struct gcli_dotgcli { struct gcli_config_entries entries; sn_sv buffer; void *mmap_pointer; bool has_been_searched_for; bool has_been_found; }; struct cmd_ctx { struct gcli_config config; struct gcli_dotgcli local_config; }; static inline struct gcli_dotgcli * ctx_dotgcli(struct gcli_ctx *ctx) { struct cmd_ctx *cctx = gcli_get_userdata(ctx); return &cctx->local_config; } static inline struct gcli_config * ctx_config(struct gcli_ctx *ctx) { struct cmd_ctx *cctx = gcli_get_userdata(ctx); return &cctx->config; } static bool should_init_dotgcli(struct gcli_ctx *ctx) { struct gcli_dotgcli *dgcli = ctx_dotgcli(ctx); return !dgcli->has_been_searched_for || (dgcli->has_been_searched_for && !dgcli->has_been_found); } static char const * find_dotgcli(void) { char *curr_dir_path = NULL; char *dotgcli = NULL; DIR *curr_dir = NULL; struct dirent *ent = NULL; curr_dir_path = getcwd(NULL, 128); if (!curr_dir_path) err(1, "gcli: getcwd"); /* Here we are trying to traverse upwards through the directory * tree, searching for a directory called .git. * Starting point is ".".*/ do { curr_dir = opendir(curr_dir_path); if (!curr_dir) err(1, "gcli: opendir"); while ((ent = readdir(curr_dir))) { if (strcmp(".", ent->d_name) == 0 || strcmp("..", ent->d_name) == 0) continue; if (strcmp(".gcli", ent->d_name) == 0) { size_t len = strlen(curr_dir_path); dotgcli = malloc(len + strlen(ent->d_name) + 2); memcpy(dotgcli, curr_dir_path, len); dotgcli[len] = '/'; memcpy(dotgcli + len + 1, ent->d_name, strlen(ent->d_name)); dotgcli[len + 1 + strlen(ent->d_name)] = 0; break; } } if (!dotgcli) { size_t len = strlen(curr_dir_path); char *tmp = malloc(len + sizeof("/..")); memcpy(tmp, curr_dir_path, len); memcpy(tmp + len, "/..", sizeof("/..")); free(curr_dir_path); curr_dir_path = realpath(tmp, NULL); if (!curr_dir_path) err(1, "gcli: realpath at %s", tmp); free(tmp); if (strcmp("/", curr_dir_path) == 0) { free(curr_dir_path); closedir(curr_dir); // At this point we know for sure that we cannot find // a .gcli and thus return a NULL pointer return NULL; } } closedir(curr_dir); } while (dotgcli == NULL); free(curr_dir_path); return dotgcli; } static void init_local_config(struct gcli_ctx *ctx) { if (!should_init_dotgcli(ctx)) { return; } char const *path = find_dotgcli(); struct gcli_dotgcli *dgcli = ctx_dotgcli(ctx); if (!path) { dgcli->has_been_searched_for = true; dgcli->has_been_found = false; return; } dgcli->has_been_searched_for = true; dgcli->has_been_found = true; int len = sn_mmap_file(path, &dgcli->mmap_pointer); if (len < 0) 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); int curr_line = 1; while (dgcli->buffer.length > 0) { sn_sv line = sn_sv_chop_until(&dgcli->buffer, '\n'); line = sn_sv_trim(line); if (line.length == 0) errx(1, "gcli: %s:%d: Unexpected end of line", path, curr_line); // Comments if (line.data[0] == '#') { dgcli->buffer = sn_sv_trim_front(dgcli->buffer); curr_line++; continue; } sn_sv key = sn_sv_chop_until(&line, '='); key = sn_sv_trim(key); if (key.length == 0) errx(1, "gcli: %s:%d: empty key", path, curr_line); line.data += 1; line.length -= 1; sn_sv value = sn_sv_trim(line); struct gcli_config_entry *entry = calloc(sizeof(*entry), 1); TAILQ_INSERT_TAIL(&dgcli->entries, entry, next); entry->key = key; entry->value = value; dgcli->buffer = sn_sv_trim_front(dgcli->buffer); curr_line++; } free((void *)path); } struct config_parser { sn_sv buffer; int line; char const *filename; }; static void skip_ws_and_comments(struct config_parser *input) { again: while (input->buffer.length > 0) { switch (input->buffer.data[0]) { case '\n': input->line++; /* fallthrough */ case ' ': case '\t': case '\r': input->buffer.data += 1; input->buffer.length -= 1; break; default: goto not_whitespace; } } return; not_whitespace: if (input->buffer.data[0] == '#') { /* This is a comment */ sn_sv_chop_until(&input->buffer, '\n'); goto again; } } static void parse_section_entry(struct config_parser *input, struct gcli_config_section *section) { struct gcli_config_entry *entry = calloc(sizeof(*entry), 1); TAILQ_INSERT_TAIL(§ion->entries, entry, next); sn_sv key = sn_sv_chop_until(&input->buffer, '='); if (key.length == 0) errx(1, "gcli: %s:%d: empty key", input->filename, input->line); input->buffer.data += 1; input->buffer.length -= 1; sn_sv value = sn_sv_chop_until(&input->buffer, '\n'); entry->key = sn_sv_trim(key); entry->value = sn_sv_trim(value); } static sn_sv parse_section_title(struct config_parser *input) { size_t len = 0; if (input->buffer.length == 0) errx(1, "gcli: %s:%d: unexpected end of input in section title", input->filename, input->line); while (!isspace(input->buffer.data[len]) && input->buffer.data[len] != '{') len++; sn_sv title = sn_sv_from_parts(input->buffer.data, len); input->buffer.data += len; input->buffer.length -= len; skip_ws_and_comments(input); if (input->buffer.length == 0) errx(1, "gcli: %s:%d: unexpected end of input", input->filename, input->line); if (input->buffer.data[0] != '{') errx(1, "gcli: %s:%d: expected '{'", input->filename, input->line); input->buffer.length -= 1; input->buffer.data += 1; skip_ws_and_comments(input); return title; } static void parse_config_section(struct gcli_config *cfg, struct config_parser *input) { struct gcli_config_section *section = NULL; section = calloc(sizeof(*section), 1); TAILQ_INSERT_TAIL(&cfg->sections, section, next); section->title = parse_section_title(input); section->entries = (struct gcli_config_entries) TAILQ_HEAD_INITIALIZER(section->entries); while (input->buffer.length > 0 && input->buffer.data[0] != '}') { skip_ws_and_comments(input); parse_section_entry(input, section); skip_ws_and_comments(input); } if (input->buffer.length == 0) errx(1, "gcli: %s:%d: missing '}' before end of file", input->filename, input->line); input->buffer.length -= 1; input->buffer.data += 1; } static void parse_config_file(struct gcli_config *cfg, struct config_parser *input) { skip_ws_and_comments(input); while (input->buffer.length > 0) { parse_config_section(cfg, input); skip_ws_and_comments(input); } } /** * Try to load up the local config file if it exists. If we succeed, * return 0. Otherwise return -1. */ static struct gcli_config * ensure_config(struct gcli_ctx *ctx) { struct gcli_config *cfg = ctx_config(ctx); char *file_path = NULL; struct config_parser parser = {0}; if (cfg->inited) return cfg; cfg->inited = true; file_path = getenv("XDG_CONFIG_HOME"); if (!file_path) { file_path = getenv("HOME"); if (!file_path) { warnx("Neither XDG_CONFIG_HOME nor HOME set in env"); return cfg; } /* * Code duplication to avoid leaking pointers */ file_path = sn_asprintf("%s/.config/gcli/config", file_path); } else { file_path = sn_asprintf("%s/gcli/config", file_path); } if (access(file_path, R_OK) < 0) { 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, "gcli: unable to open config file"); cfg->buffer = sn_sv_from_parts(cfg->mmap_pointer, len); cfg->buffer = sn_sv_trim_front(cfg->buffer); parser.buffer = cfg->buffer; parser.line = 1; parser.filename = file_path; parse_config_file(cfg, &parser); free((void *)file_path); return cfg; } /** Check input for a value that indicates yes/true */ static int checkyes(char const *const tmp) { static char const *const yeses[] = { "1", "y", "Y" }; if (strlen(tmp) == 3) { if (tolower(tmp[0]) == 'y' && tolower(tmp[1]) == 'e' && tolower(tmp[2]) == 's') return 1; } for (size_t i = 0; i < ARRAY_SIZE(yeses); ++i) { if (strcmp(yeses[i], tmp) == 0) return 1; } return 0; } /* readenv: Read values of environment variables and pre-populate the * config structure. */ static void readenv(struct gcli_config *cfg) { char *tmp; /* A default override account. Can be overridden again by * specifying -a */ if ((tmp = getenv("GCLI_ACCOUNT"))) cfg->override_default_account = tmp; /* NO_COLOR: https://no-color.org/ * * Note: the example implementation code on the website is * semantically buggy as it just checks for the variable being set * to ANYTHING. If you set it to 0 to indicate that you want * colours it will still disable colour output. This explicitly * checks the value of the variable if it is set. I purposefully * violate the definition to get expected and sane behaviour. */ tmp = getenv("NO_COLOR"); if (tmp && tmp[0] != '\0') cfg->colours_disabled = checkyes(tmp); } int gcli_config_init_ctx(struct gcli_ctx *ctx) { struct cmd_ctx *cctx = calloc(sizeof(*cctx), 1); gcli_set_userdata(ctx, cctx); cctx->config.sections = (struct gcli_config_sections) TAILQ_HEAD_INITIALIZER(cctx->config.sections); cctx->local_config.entries = (struct gcli_config_entries) TAILQ_HEAD_INITIALIZER(cctx->local_config.entries); return 0; } int 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 * arguments. Only global options that do not alter subcommand * specific behaviour should be accepted here. */ struct gcli_config *cfg = ctx_config(ctx); int ch; const struct option options[] = { { .name = "account", .has_arg = required_argument, .flag = NULL, .val = 'a' }, { .name = "remote", .has_arg = required_argument, .flag = NULL, .val = 'r' }, { .name = "colours", .has_arg = no_argument, .flag = &cfg->colours_disabled, .val = 0 }, { .name = "type", .has_arg = required_argument, .flag = NULL, .val = 't' }, { .name = "quiet", .has_arg = no_argument, .flag = NULL, .val = 'q' }, { .name = "verbose", .has_arg = no_argument, .flag = NULL, .val = 'v' }, {0}, }; /* by default we are not verbose */ sn_setverbosity(VERBOSITY_NORMAL); /* Before we parse options, invalidate the override type so it * doesn't get confused later */ cfg->override_forgetype = -1; /* Start off by pre-populating the config structure */ readenv(cfg); while ((ch = getopt_long(*argc, *argv, "+a:r:cqvt:", options, NULL)) != -1) { switch (ch) { case 'a': { cfg->override_default_account = optarg; } break; case 'r': { cfg->override_remote = optarg; } break; case 'c': { cfg->force_colours = 1; } break; case 'q': { sn_setverbosity(VERBOSITY_QUIET); } break; case 'v': { sn_setverbosity(VERBOSITY_VERBOSE); } break; case 't': { if (strcmp(optarg, "github") == 0) { cfg->override_forgetype = GCLI_FORGE_GITHUB; } else if (strcmp(optarg, "gitlab") == 0) { 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, "gcli: error: unknown forge type '%s'. " "Have either github, gitlab or gitea.\n", optarg); return EXIT_FAILURE; } } break; case 0: break; case '?': default: return EXIT_FAILURE; } } *argc -= optind; *argv += optind; /* This one is a little odd: We are going to call getopt_long * again. Eventually. But since this is a global variable and the * getopt parser is reusing it, we need to reset it to zero. On * BSDs there is also the optreset variable, but it doesn't exist * on Solaris. I will thus not depend on it as it seems to be * working without it. */ optind = 0; cfg->inited = false; return EXIT_SUCCESS; } static struct gcli_config_section const * find_section(struct gcli_config *cfg, char const *name) { struct gcli_config_section *section; TAILQ_FOREACH(section, &cfg->sections, next) { if (sn_sv_eq_to(section->title, name)) return section; } return NULL; } struct gcli_config_entries const * gcli_config_get_section_entries(struct gcli_ctx *ctx, char const *section_name) { struct gcli_config_section const *s; struct gcli_config *cfg; cfg = ensure_config(ctx); s = find_section(cfg, section_name); if (s == NULL) return NULL; else return &s->entries; } sn_sv 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); struct gcli_config_section const *const section = find_section(cfg, section_name); if (!section) { warnx("gcli: no config section with name '%s'", section_name); return SV_NULL; } TAILQ_FOREACH(entry, §ion->entries, next) { if (sn_sv_eq_to(entry->key, key)) return entry->value; } return SV_NULL; } static sn_sv 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; TAILQ_FOREACH(entry, &lcfg->entries, next) { if (sn_sv_eq_to(entry->key, key)) return entry->value; } return SV_NULL; } char * gcli_config_get_editor(struct gcli_ctx *ctx) { ensure_config(ctx); return sn_sv_to_cstr(gcli_config_find_by_key(ctx, "defaults", "editor")); } 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_BUGZILLA] = "bugzilla-default-account",}; static char * 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); if (!act.length) return NULL; return sn_sv_to_cstr(act); } static char * 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); char *account; if (cfg->override_default_account) { account = strdup(cfg->override_default_account); } else { account = get_default_account(ctx, ftype); } return account; } 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_BUGZILLA] = "https://bugs.freebsd.org/bugzilla", }; char * gcli_config_get_apibase(struct gcli_ctx *ctx) { char *acct = gcli_config_get_account(ctx); char *url = NULL; if (acct) { sn_sv url_sv = gcli_config_find_by_key(ctx, acct, "apibase"); if (url_sv.length) url = sn_sv_to_cstr(url_sv); } if (!url) url = strdup(default_urls[gcli_config_get_forge_type(ctx)]); free(acct); return url; } char * gcli_config_get_account_name(struct gcli_ctx *ctx) { char *account = gcli_config_get_account(ctx); sn_sv actname = gcli_config_find_by_key( ctx, account, "account"); free(account); return sn_sv_to_cstr(actname); } static char * get_account_token(struct gcli_ctx *ctx) { char *account; sn_sv token; account = gcli_config_get_account(ctx); if (!account) return NULL; token = gcli_config_find_by_key(ctx, account, "token"); free(account); return sn_sv_to_cstr(token); } char * gcli_config_get_token(struct gcli_ctx *ctx) { ensure_config(ctx); return get_account_token(ctx); } sn_sv gcli_config_get_upstream(struct gcli_ctx *ctx) { init_local_config(ctx); return gcli_local_config_find_by_key(ctx, "pr.upstream"); } bool gcli_config_pr_inhibit_delete_source_branch(struct gcli_ctx *ctx) { sn_sv val; init_local_config(ctx); val = gcli_local_config_find_by_key(ctx, "pr.inhibit-delete-source-branch"); return sn_sv_eq_to(val, "yes"); } void gcli_config_get_upstream_parts(struct gcli_ctx *ctx, sn_sv *const owner, sn_sv *const repo) { ensure_config(ctx); sn_sv upstream = gcli_config_get_upstream(ctx); *owner = sn_sv_chop_until(&upstream, '/'); /* Sanity check: did we actually reach the '/'? */ if (*upstream.data != '/') errx(1, "gcli: .gcli has invalid upstream format. expected owner/repo"); upstream.data += 1; upstream.length -= 1; *repo = upstream; } sn_sv gcli_config_get_base(struct gcli_ctx *ctx) { init_local_config(ctx); return gcli_local_config_find_by_key(ctx, "pr.base"); } sn_sv gcli_config_get_override_default_account(struct gcli_ctx *ctx) { struct gcli_config *cfg; init_local_config(ctx); cfg = ctx_config(ctx); if (cfg->override_default_account) return SV((char *)cfg->override_default_account); else return SV_NULL; } static gcli_forge_type gcli_config_get_forge_type_internal(struct gcli_ctx *ctx) { struct gcli_config *cfg = ctx_config(ctx); /* Hard override */ if (cfg->override_forgetype >= 0) return cfg->override_forgetype; ensure_config(ctx); init_local_config(ctx); sn_sv entry = {0}; if (cfg->override_default_account) { char const *section = cfg->override_default_account; entry = gcli_config_find_by_key(ctx, section, "forge-type"); if (sn_sv_null(entry)) errx(1, "gcli: error: given default override account not found or " "missing forge-type"); } else { entry = gcli_local_config_find_by_key(ctx, "forge-type"); } if (!sn_sv_null(entry)) { if (sn_sv_eq_to(entry, "github")) return GCLI_FORGE_GITHUB; else if (sn_sv_eq_to(entry, "gitlab")) 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, "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, "gcli: error: cannot infer forge type. " "use -t to overrride manually."); return type; } gcli_forge_type gcli_config_get_forge_type(struct gcli_ctx *ctx) { gcli_forge_type const result = gcli_config_get_forge_type_internal(ctx); /* print the type if verbose */ if (sn_verbose()) { static int have_printed_forge_type = 0; static char const *const ftype_name[] = { [GCLI_FORGE_GITHUB] = "GitHub", [GCLI_FORGE_GITLAB] = "Gitlab", [GCLI_FORGE_GITEA] = "Gitea", [GCLI_FORGE_BUGZILLA] = "Bugzilla", }; if (!have_printed_forge_type) { have_printed_forge_type = 1; fprintf(stderr, "gcli: info: forge type is %s\n", ftype_name[result]); } } return result; } int gcli_config_get_repo(struct gcli_ctx *ctx, char const **const owner, char const **const repo) { sn_sv upstream = {0}; struct gcli_config *cfg; cfg = ensure_config(ctx); if (cfg->override_remote) { int forge = 0, rc = 0; rc = gcli_gitconfig_repo_by_remote(ctx, cfg->override_remote, owner, repo, &forge); if (rc < 0) return rc; if (forge >= 0) { if ((int)(gcli_config_get_forge_type(ctx)) != forge) return gcli_error(ctx, "forge types are inconsistent"); } return 0; } if ((upstream = gcli_config_get_upstream(ctx)).length != 0) { sn_sv const owner_sv = sn_sv_chop_until(&upstream, '/'); sn_sv const repo_sv = sn_sv_from_parts(upstream.data + 1, upstream.length - 1); *owner = sn_sv_to_cstr(owner_sv); *repo = sn_sv_to_cstr(repo_sv); return 0; } return gcli_gitconfig_repo_by_remote(ctx, NULL, owner, repo, NULL); } int gcli_config_have_colours(struct gcli_ctx *ctx) { static int tested_tty = 0; struct gcli_config *cfg; cfg = ctx_config(ctx); if (cfg->force_colours) return 1; if (cfg->colours_disabled) return 0; if (tested_tty) return !cfg->colours_disabled; if (isatty(STDOUT_FILENO)) cfg->colours_disabled = false; else cfg->colours_disabled = true; tested_tty = 1; return !cfg->colours_disabled; } gcli-2.3.0/src/cmd/colour.c000066400000000000000000000120031460062271200154270ustar00rootroot00000000000000/* * Copyright 2022 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 static struct { uint32_t code; char *sequence; } colour_table[1024]; static size_t colour_table_size; static void clean_colour_table(void) { for (size_t i = 0; i < colour_table_size; ++i) free(colour_table[i].sequence); } static char const * colour_cache_lookup(uint32_t const code) { for (size_t i = 0; i < colour_table_size; ++i) { if (colour_table[i].code == code) return colour_table[i].sequence; } return NULL; } static void colour_cache_insert(uint32_t const code, char *sequence) { colour_table[colour_table_size].code = code; colour_table[colour_table_size].sequence = sequence; colour_table_size++; } char const * gcli_setcolour256(uint32_t const code) { char *result = NULL; char const *oldresult = NULL; if (!gcli_config_have_colours(g_clictx)) return ""; if (colour_table_size == 0) atexit(clean_colour_table); oldresult = colour_cache_lookup(code); if (oldresult) return oldresult; result = sn_asprintf("\033[48;2;%02d;%02d;%02dm", (code & 0xFF000000) >> 24, (code & 0x00FF0000) >> 16, (code & 0x0000FF00) >> 8); colour_cache_insert(code, result); return result; } const char * gcli_resetcolour(void) { if (!gcli_config_have_colours(g_clictx)) return ""; return "\033[m"; } const char * gcli_setcolour(int code) { if (!gcli_config_have_colours(g_clictx)) return ""; switch (code) { case GCLI_COLOR_BLACK: return "\033[30m"; case GCLI_COLOR_RED: return "\033[31m"; case GCLI_COLOR_GREEN: return "\033[32m"; case GCLI_COLOR_YELLOW: return "\033[33m"; case GCLI_COLOR_BLUE: return "\033[34m"; case GCLI_COLOR_MAGENTA: return "\033[35m"; case GCLI_COLOR_CYAN: return "\033[36m"; case GCLI_COLOR_WHITE: return "\033[37m"; case GCLI_COLOR_DEFAULT: return "\033[39m"; default: sn_notreached; } return NULL; } char const * gcli_setbold(void) { if (!gcli_config_have_colours(g_clictx)) return ""; else return "\033[1m"; } char const * gcli_resetbold(void) { if (!gcli_config_have_colours(g_clictx)) return ""; else return "\033[22m"; } char const * gcli_state_colour_str(char const *it) { if (it) return gcli_state_colour_sv(SV((char *)it)); else return ""; } static const struct { char const *name; int code; } state_colour_table[] = { { .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 * gcli_state_colour_sv(sn_sv const state) { if (!sn_sv_null(state)) { for (size_t i = 0; i < ARRAY_SIZE(state_colour_table); ++i) { if (sn_sv_has_prefix(state, state_colour_table[i].name)) return gcli_setcolour(state_colour_table[i].code); } } return gcli_setcolour(GCLI_COLOR_DEFAULT); } gcli-2.3.0/src/cmd/comment.c000066400000000000000000000145731460062271200156040ustar00rootroot00000000000000/* * Copyright 2022 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 #include #ifdef HAVE_GETOPT_H #include #endif static void usage(void) { fprintf(stderr, "usage: gcli comment [-o owner -r repo] [-p pr | -i issue] [-y]\n"); fprintf(stderr, "OPTIONS:\n"); fprintf(stderr, " -o owner The repository owner\n"); fprintf(stderr, " -r repo The repository name\n"); fprintf(stderr, " -p pr PR id to comment under\n"); fprintf(stderr, " -i issue issue id to comment under\n"); fprintf(stderr, " -y Do not ask for confirmation\n"); version(); copyright(); } static void comment_init(struct gcli_ctx *ctx, FILE *f, void *_data) { struct gcli_submit_comment_opts *info = _data; const char *target_type = NULL; switch (info->target_type) { case ISSUE_COMMENT: target_type = "issue"; break; case PR_COMMENT: { switch (gcli_config_get_forge_type(ctx)) { case GCLI_FORGE_GITEA: case GCLI_FORGE_GITHUB: target_type = "Pull Request"; break; case GCLI_FORGE_GITLAB: target_type = "Merge Request"; break; case GCLI_FORGE_BUGZILLA: /* FIXME think about this one */ assert(0 && "unreachable"); break; } } break; } fprintf( f, "! 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 #%"PRIid"\n", info->owner, info->repo, target_type, info->target_id); } 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(struct gcli_submit_comment_opts opts, int always_yes) { 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 #%"PRIid":\n%s\n", opts.owner, opts.repo, opts.target_id, opts.message); if (!always_yes) { if (!sn_yesno("Is this okay?")) errx(1, "Aborted by user"); } rc = gcli_comment_submit(g_clictx, opts); free(message); opts.message = NULL; return rc; } int gcli_issue_comments(char const *owner, char const *repo, int const issue) { struct gcli_comment_list list = {0}; int rc = 0; rc = gcli_get_issue_comments(g_clictx, owner, repo, issue, &list); if (rc < 0) return rc; gcli_print_comment_list(&list); gcli_comments_free(&list); return rc; } int gcli_pull_comments(char const *owner, char const *repo, int const pull) { struct gcli_comment_list list = {0}; int rc = 0; rc = gcli_get_pull_comments(g_clictx, owner, repo, pull, &list); if (rc < 0) return rc; gcli_print_comment_list(&list); gcli_comments_free(&list); return rc; } void 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" "DATE : %s\n", gcli_setbold(), list->comments[i].author, gcli_resetbold(), list->comments[i].date); pretty_print(list->comments[i].body, 9, 80, stdout); putchar('\n'); } } int subcommand_comment(int argc, char *argv[]) { int ch, target_id = -1, rc = 0; char const *repo = NULL, *owner = NULL; bool always_yes = false; enum comment_target_type target_type; struct option const options[] = { { .name = "yes", .has_arg = no_argument, .flag = NULL, .val = 'y' }, { .name = "repo", .has_arg = required_argument, .flag = NULL, .val = 'r' }, { .name = "owner", .has_arg = required_argument, .flag = NULL, .val = 'o' }, { .name = "issue", .has_arg = required_argument, .flag = NULL, .val = 'i' }, { .name = "pull", .has_arg = required_argument, .flag = NULL, .val = 'p' }, {0}, }; while ((ch = getopt_long(argc, argv, "yr:o:i:p:", options, NULL)) != -1) { switch (ch) { case 'r': repo = optarg; break; case 'o': owner = optarg; break; case 'p': target_type = PR_COMMENT; goto parse_target_id; case 'i': target_type = ISSUE_COMMENT; parse_target_id: { char *endptr; target_id = strtoul(optarg, &endptr, 10); if (endptr != optarg + strlen(optarg)) err(1, "gcli: error: Cannot parse issue/PR number"); } break; case 'y': always_yes = true; break; default: usage(); return EXIT_FAILURE; } } argc -= optind; argv += optind; check_owner_and_repo(&owner, &repo); if (target_id < 0) { fprintf(stderr, "gcli: error: missing issue/PR number (use -i/-p)\n"); usage(); return EXIT_FAILURE; } rc = comment_submit((struct gcli_submit_comment_opts) { .owner = owner, .repo = repo, .target_type = target_type, .target_id = target_id }, always_yes); if (rc < 0) errx(1, "gcli: error: failed to submit comment: %s", gcli_get_error(g_clictx)); return EXIT_SUCCESS; } gcli-2.3.0/src/cmd/config.c000066400000000000000000000132331460062271200153770ustar00rootroot00000000000000/* * 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. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #ifdef HAVE_GETOPT_H #include #endif static void usage(void) { fprintf(stderr, "usage: gcli config ssh\n"); fprintf(stderr, " gcli config ssh add --title some-title --key path/to/key.pub\n"); fprintf(stderr, " gcli config ssh delete id\n"); fprintf(stderr, "\n"); version(); copyright(); } void gcli_sshkeys_print_keys(struct gcli_sshkey_list const *list) { gcli_tbl *tbl; 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 }, }; if (list->keys_size == 0) { printf("No SSH keys\n"); return; } tbl = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); for (size_t i = 0; i < list->keys_size; ++i) { gcli_tbl_add_row(tbl, list->keys[i].id, list->keys[i].created_at, list->keys[i].title); } gcli_tbl_end(tbl); } static int list_sshkeys(void) { struct gcli_sshkey_list list = {0}; if (gcli_sshkeys_get_keys(g_clictx, &list) < 0) { fprintf(stderr, "gcli: error: could not get list of SSH keys\n"); return EXIT_FAILURE; } gcli_sshkeys_print_keys(&list); gcli_sshkeys_free_keys(&list); return 0; } static int add_sshkey(int argc, char *argv[]) { char *title = NULL, *keypath = NULL; int ch; struct option options[] = { { .name = "title", .has_arg = required_argument, .flag = NULL, .val = 't' }, { .name = "key", .has_arg = required_argument, .flag = NULL, .val = 'k' }, { 0 }, }; while ((ch = getopt_long(argc, argv, "+t:k:", options, NULL)) != -1) { switch (ch) { case 't': { title = optarg; } break; case 'k': { keypath = optarg; if (access(keypath, R_OK) < 0) { fprintf(stderr, "gcli: error: cannot access %s: %s\n", keypath, strerror(errno)); return EXIT_FAILURE; } } break; default: { usage(); return EXIT_FAILURE; } break; } } if (title == NULL) { fprintf(stderr, "gcli: error: missing title\n"); usage(); return EXIT_FAILURE; } if (keypath == NULL) { fprintf(stderr, "gcli: error: missing public key path\n"); usage(); return EXIT_FAILURE; } if (gcli_sshkeys_add_key(g_clictx, title, keypath, NULL) < 0) return EXIT_FAILURE; return EXIT_SUCCESS; } static int delete_sshkey(int argc, char *argv[]) { int id; char *endptr; /* skip 'delete' keyword */ --argc; ++argv; if (argc != 1) { fprintf(stderr, "gcli: error: incorrect number of arguments\n"); usage(); return EXIT_FAILURE; } /* parse the id */ id = strtol(argv[0], &endptr, 10); if (endptr != argv[0] + strlen(argv[0])) { fprintf(stderr, "gcli: error: could not parse ID of SSH key to delete\n"); return EXIT_FAILURE; } if (gcli_sshkeys_delete_key(g_clictx, id) < 0) return EXIT_FAILURE; return EXIT_SUCCESS; } static int subcommand_ssh(int argc, char *argv[]) { char *cmdname; if (--argc == 0) return list_sshkeys(); cmdname = *(++argv); if (strcmp(cmdname, "add") == 0) return add_sshkey(argc, argv); else if (strcmp(cmdname, "delete") == 0) return delete_sshkey(argc, argv); fprintf(stderr, "gcli: error: unrecognised subcommand »%s«.\n", cmdname); usage(); return EXIT_FAILURE; } struct subcommand { char const *const name; int (*fn)(int argc, char *argv[]); } subcommands[] = { { .name = "ssh", .fn = subcommand_ssh }, }; int subcommand_config(int argc, char *argv[]) { int ch; struct option options[] = { {0} }; while ((ch = getopt_long(argc, argv, "+", options, NULL)) != -1) { switch (ch) { default: usage(); return EXIT_FAILURE; } } argc -= optind; argv += optind; /* Check if the user gave us at least one option for this * subcommand */ if (argc == 0) { fprintf(stderr, "gcli: error: missing subcommand for config\n"); usage(); return EXIT_FAILURE; } for (size_t i = 0; i < ARRAY_SIZE(subcommands); ++i) { if (strcmp(argv[0], subcommands[i].name) == 0) return subcommands[i].fn(argc, argv); } fprintf(stderr, "gcli: error: unrecognised config subcommand »%s«\n", argv[0]); usage(); return EXIT_FAILURE; } gcli-2.3.0/src/cmd/editor.c000066400000000000000000000071171460062271200154240ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 static sn_sv sv_append(sn_sv this, sn_sv const that) { /* 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; } char * gcli_editor_get_user_message( struct gcli_ctx *ctx, void (*file_initializer)(struct gcli_ctx *, FILE *, void *), void *user_data) { char *editor = getenv("EDITOR"); char *env_editor = editor; if (!editor) { editor = gcli_config_get_editor(ctx); if (!editor) errx(1, "I have no editor. Either set editor=... in your config " "file or set the EDITOR environment variable."); } char filename[31] = "/tmp/gcli_message.XXXXXXX\0"; int fd = mkstemp(filename); FILE *file = fdopen(fd, "w"); file_initializer(ctx, file, user_data); fclose(file); pid_t pid = fork(); if (pid == 0) { if (execlp(editor, editor, filename, NULL) < 0) err(1, "execlp"); } else { int status; if (waitpid(pid, &status, 0) < 0) err(1, "waitpid"); if (!(WIFEXITED(status))) errx(1, "Editor child exited abnormally"); if (WEXITSTATUS(status) != 0) errx(1, "Aborting PR. Editor command exited with code %d", WEXITSTATUS(status)); } if (!env_editor) free(editor); void *file_content = NULL; int len = sn_mmap_file(filename, &file_content); if (len < 0) err(1, "mmap"); sn_sv result = {0}; sn_sv buffer = sn_sv_from_parts(file_content, (size_t)len); buffer = sn_sv_trim_front(buffer); while (buffer.length > 0) { sn_sv line = sn_sv_chop_until(&buffer, '\n'); if (buffer.length > 0) { buffer.length -= 1; buffer.data += 1; line.length += 1; } if (line.length > 0 && line.data[0] == '!') continue; result = sv_append(result, line); } munmap(file_content, len); unlink(filename); /* 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; } gcli-2.3.0/src/cmd/forks.c000066400000000000000000000163531460062271200152640ustar00rootroot00000000000000/* * Copyright 2022 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 #ifdef HAVE_GETOPT_H #include #endif #include #include #include #include #include #include #include #include static void usage(void) { fprintf(stderr, "usage: gcli forks create [-o owner -r repo] [-i target] [-y]\n"); fprintf(stderr, " gcli forks [-o owner -r repo] [-n number] [-s] [-y] [delete]\n"); fprintf(stderr, "OPTIONS:\n"); fprintf(stderr, " -o owner The repository owner\n"); fprintf(stderr, " -r repo The repository name\n"); fprintf(stderr, " -i target Name of org or user to create the fork in\n"); fprintf(stderr, " -n number Number of forks to fetch (-1 = everything)\n"); fprintf(stderr, " -s Print (sort) in reverse order\n"); fprintf(stderr, " -y Do not ask for confirmation\n"); fprintf(stderr, "\n"); version(); copyright(); } void gcli_print_forks(enum gcli_output_flags const flags, struct gcli_fork_list const *const list, int const max) { size_t n; gcli_tbl table; struct gcli_tblcoldef cols[] = { { .name = "OWNER", .type = GCLI_TBLCOLTYPE_STRING, .flags = GCLI_TBLCOL_BOLD }, { .name = "DATE", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, { .name = "FORKS", .type = GCLI_TBLCOLTYPE_INT, .flags = GCLI_TBLCOL_JUSTIFYR }, { .name = "FULLNAME", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, }; if (list->forks_size == 0) { puts("No forks"); return; } /* Determine number of items to print */ if (max < 0 || (size_t)(max) > list->forks_size) n = list->forks_size; else n = max; table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!table) errx(1, "gcli: error: could not initialize table"); if (flags & OUTPUT_SORTED) { for (size_t i = 0; i < n; ++i) { gcli_tbl_add_row(table, list->forks[n-i-1].owner, list->forks[n-i-1].date, list->forks[n-i-1].forks, list->forks[n-i-1].full_name); } } else { for (size_t i = 0; i < n; ++i) { gcli_tbl_add_row(table, list->forks[i].owner, list->forks[i].date, list->forks[i].forks, list->forks[i].full_name); } } gcli_tbl_end(table); } static int subcommand_forks_create(int argc, char *argv[]) { int ch; char const *owner = NULL, *repo = NULL, *in = NULL; bool always_yes = false; struct option const options[] = { { .name = "repo", .has_arg = required_argument, .flag = NULL, .val = 'r' }, { .name = "owner", .has_arg = required_argument, .flag = NULL, .val = 'o' }, { .name = "into", .has_arg = required_argument, .flag = NULL, .val = 'i' }, { .name = "yes", .has_arg = no_argument, .flag = NULL, .val = 'y' }, {0}, }; while ((ch = getopt_long(argc, argv, "yo:r:i:", options, NULL)) != -1) { switch (ch) { case 'o': owner = optarg; break; case 'r': repo = optarg; break; case 'i': in = optarg; break; case 'y': always_yes = true; break; case '?': default: usage(); return EXIT_FAILURE; } } argc -= optind; argv += optind; check_owner_and_repo(&owner, &repo); if (gcli_fork_create(g_clictx, owner, repo, in) < 0) 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?")) return EXIT_SUCCESS; } if (!in) { if ((in = gcli_config_get_account_name(g_clictx)) == NULL) { errx(1, "gcli: error: could not fetch account: %s", gcli_get_error(g_clictx)); } } gcli_gitconfig_add_fork_remote(in, repo); return EXIT_SUCCESS; } int subcommand_forks(int argc, char *argv[]) { struct gcli_fork_list forks = {0}; char const *owner = NULL, *repo = NULL; int ch = 0; int count = 30; bool always_yes = false; enum gcli_output_flags flags = 0; /* detect whether we wanna create a fork */ if (argc > 1 && (strcmp(argv[1], "create") == 0)) { shift(&argc, &argv); return subcommand_forks_create(argc, argv); } struct option const options[] = { { .name = "repo", .has_arg = required_argument, .flag = NULL, .val = 'r' }, { .name = "owner", .has_arg = required_argument, .flag = NULL, .val = 'o' }, { .name = "count", .has_arg = required_argument, .flag = NULL, .val = 'n' }, { .name = "yes", .has_arg = no_argument, .flag = NULL, .val = 'y' }, { .name = "sorted", .has_arg = no_argument, .flag = NULL, .val = 's' }, {0}, }; while ((ch = getopt_long(argc, argv, "n:o:r:ys", options, NULL)) != -1) { switch (ch) { case 'o': owner = optarg; break; case 'r': repo = optarg; break; case 'y': always_yes = true; break; case 'n': { char *endptr = NULL; count = strtol(optarg, &endptr, 10); if (endptr != (optarg + strlen(optarg))) err(1, "gcli: error: unable to parse forks count argument"); if (count == 0) errx(1, "gcli: error: forks count must not be zero"); } break; case 's': flags |= OUTPUT_SORTED; break; case '?': default: usage(); return EXIT_FAILURE; } } argc -= optind; argv += optind; check_owner_and_repo(&owner, &repo); if (argc == 0) { if (gcli_get_forks(g_clictx, owner, repo, count, &forks) < 0) errx(1, "gcli: error: could not get forks: %s", gcli_get_error(g_clictx)); gcli_print_forks(flags, &forks, count); gcli_forks_free(&forks); return EXIT_SUCCESS; } for (size_t i = 0; i < (size_t)argc; ++i) { char const *action = argv[i]; if (strcmp(action, "delete") == 0) { delete_repo(always_yes, owner, repo); } else { fprintf(stderr, "gcli: error: forks: unknown action '%s'\n", action); } } return EXIT_SUCCESS; } gcli-2.3.0/src/cmd/gcli.c000066400000000000000000000245241460062271200150550ustar00rootroot00000000000000/* * Copyright 2021,2022 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 #ifdef HAVE_GETOPT_h #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void usage(void); static int subcommand_version(int argc, char *argv[]) { (void) argc; (void) argv; longversion(); copyright(); return EXIT_SUCCESS; } static struct subcommand { char const *cmd_name; char const *docstring; int (*fn)(int, char **); } default_subcommands[] = { { .cmd_name = "ci", .fn = subcommand_ci, .docstring = "Github CI status info" }, { .cmd_name = "comment", .fn = subcommand_comment, .docstring = "Comment under issues and PRs" }, { .cmd_name = "config", .fn = subcommand_config, .docstring = "Configure forges" }, { .cmd_name = "forks", .fn = subcommand_forks, .docstring = "Create, delete and list repository forks" }, { .cmd_name = "gists", .fn = subcommand_gists, .docstring = "Create, fetch and list Github Gists" }, { .cmd_name = "issues", .fn = subcommand_issues, .docstring = "Manage issues" }, { .cmd_name = "labels", .fn = subcommand_labels, .docstring = "Manage issue and PR labels" }, { .cmd_name = "milestones", .fn = subcommand_milestones, .docstring = "Milestone handling" }, { .cmd_name = "pipelines", .fn = subcommand_pipelines, .docstring = "Gitlab CI management" }, { .cmd_name = "pulls", .fn = subcommand_pulls, .docstring = "Create, view and manage PRs" }, { .cmd_name = "releases", .fn = subcommand_releases, .docstring = "Manage releases of repositories" }, { .cmd_name = "repos", .fn = subcommand_repos, .docstring = "Remote Repository management" }, { .cmd_name = "snippets", .fn = subcommand_snippets, .docstring = "Fetch and list Gitlab snippets" }, { .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)" }, { .cmd_name = "version", .fn = subcommand_version, .docstring = "Print version" }, }; static struct subcommand *subcommands = NULL; static size_t subcommands_size = 0; static void usage(void) { fprintf(stderr, "usage: gcli [options] subcommand\n\n"); fprintf(stderr, "OPTIONS:\n"); fprintf(stderr, " -a account Use the configured account instead of inferring it\n"); fprintf(stderr, " -r remote Infer account from the given git remote\n"); fprintf(stderr, " -t type Force the account type:\n"); 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"); fprintf(stderr, "SUBCOMMANDS:\n"); for (size_t i = 0; i < subcommands_size; ++i) { fprintf(stderr, " %-13.13s %s\n", subcommands[i].cmd_name, subcommands[i].docstring); } fprintf(stderr, "\n"); version(); copyright(); } /** The CMD global gcli context */ struct gcli_ctx *g_clictx = NULL; static void gcli_progress_func(bool const done) { char spinner[] = "|/-\\"; static size_t const spinner_elems = sizeof(spinner) / sizeof(*spinner); static int spinner_idx = 0; static int have_checked_stderr = 0, stderr_is_tty = 1; /* Check if stderr is a tty */ if (!have_checked_stderr) { stderr_is_tty = isatty(STDERR_FILENO); have_checked_stderr = 1; } if (!stderr_is_tty) return; /* Clear out the line when done */ if (done) { fprintf(stderr, " \r"); } else { fprintf(stderr, "Wait... %c\r", spinner[spinner_idx]); spinner_idx = (spinner_idx + 1) % (spinner_elems - 1); } } /* Abbreviated form matching: * * - we presort the subcommands array alphabetised * - then we can simply match by prefix */ static int subcommand_compare(void const *s1, void const *s2) { struct subcommand const *sc1 = s1; struct subcommand const *sc2 = s2; return strcmp(sc1->cmd_name, sc2->cmd_name); } static void presort_subcommands(void) { qsort(subcommands, subcommands_size, sizeof(*subcommands), subcommand_compare); } static bool is_unique_match(size_t const idx, char const *const name, size_t const name_len) { /* Last match is always unique */ if (idx + 1 == subcommands_size) return true; for (size_t i = idx + 1; i < subcommands_size; ++i) { if (strncmp(name, subcommands[i].cmd_name, name_len)) return true; /* doesn't match. meaning this one is unique. */ else break; /* we found a duplicate prefix. */ } 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)) break; fprintf(stderr, " - %-13.13s %s\n", subcommands[i].cmd_name, subcommands[i].docstring); } fprintf(stderr, "\n"); return false; } enum { LOOKUP_NOSUCHCMD = 1, LOOKUP_AMBIGUOUS, }; static struct subcommand const * find_subcommand(char const *const name, int *error) { size_t const name_len = strlen(name); for (size_t i = 0; i < subcommands_size; ++i) { if (strncmp(subcommands[i].cmd_name, name, name_len) == 0) { /* At least the prefix matches. Check that it is a unique match. */ if (!is_unique_match(i, name, name_len)) { if (error) *error = LOOKUP_AMBIGUOUS; return NULL; } return subcommands + i; } } /* no match */ fprintf(stderr, "gcli: error: %s: no such subcommand\n", name); if (error) *error = LOOKUP_NOSUCHCMD; return NULL; } static void add_subcommand_alias(char const *alias_name, char const *alias_for) { char *docstring; struct subcommand const *old_sc; struct subcommand *new_sc; int (*old_fn)(int, char **); old_sc = find_subcommand(alias_for, NULL); if (old_sc == NULL) { fprintf(stderr, "gcli: note: this error occured while defining the alias »%s«\n", alias_name); exit(EXIT_FAILURE); } old_fn = old_sc->fn; docstring = sn_asprintf("Alias for %s", alias_for); subcommands = realloc(subcommands, (subcommands_size + 1) * sizeof(*subcommands)); /* Copy in data */ new_sc = &subcommands[subcommands_size++]; new_sc->cmd_name = alias_name; new_sc->fn = old_fn; new_sc->docstring = docstring; } static void install_aliases(void) { struct gcli_config_entries const *entries; struct gcli_config_entry const *entry; add_subcommand_alias("pr", "pulls"); /* Search for aliases in the user config file */ entries = gcli_config_get_section_entries(g_clictx, "aliases"); if (!entries) return; TAILQ_FOREACH(entry, entries, next) { char *alias_name, *alias_for; alias_name = sn_sv_to_cstr(entry->key); alias_for = sn_sv_to_cstr(entry->value); add_subcommand_alias(alias_name, alias_for); free(alias_for); } } static void setup_subcommand_table(void) { subcommands = calloc(sizeof(*subcommands), ARRAY_SIZE(default_subcommands)); memcpy(subcommands, default_subcommands, sizeof(default_subcommands)); subcommands_size = ARRAY_SIZE(default_subcommands); } int main(int argc, char *argv[]) { char const *errmsg; struct subcommand const *sc; int error_reason; errmsg = gcli_init(&g_clictx, gcli_config_get_forge_type, gcli_config_get_token, gcli_config_get_apibase); if (errmsg) errx(1, "gcli: error: %s", errmsg); if (gcli_config_init_ctx(g_clictx) < 0) errx(1, "gcli: error: failed to init context: %s", gcli_get_error(g_clictx)); gcli_set_progress_func(g_clictx, gcli_progress_func); /* Initial setup */ setup_subcommand_table(); /* Install subcommand aliases into subcommand table */ install_aliases(); /* Sorts the subcommands array alphabatically */ presort_subcommands(); /* Parse first arguments */ if (gcli_config_parse_args(g_clictx, &argc, &argv)) { usage(); return EXIT_FAILURE; } /* Make sure we have a subcommand */ if (argc == 0) { fprintf(stderr, "gcli: error: missing subcommand\n"); usage(); return EXIT_FAILURE; } /* Search for the subcommand */ sc = find_subcommand(argv[0], &error_reason); if (sc == NULL) { if (error_reason == LOOKUP_AMBIGUOUS) version(); else usage(); return EXIT_FAILURE; } return sc->fn(argc, argv); } gcli-2.3.0/src/cmd/gists.c000066400000000000000000000257711460062271200152750ustar00rootroot00000000000000/* * Copyright 2022 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 #ifdef HAVE_GETOPT_H #include #endif #include #include static void usage(void) { fprintf(stderr, "usage: gcli gists [-u user] [-n number] [-s]\n"); fprintf(stderr, " gcli gists create [-d description] [-f file] name\n"); fprintf(stderr, " gcli gists delete [-y] id\n"); fprintf(stderr, " gcli gists get id name\n"); fprintf(stderr, "OPTIONS:\n"); fprintf(stderr, " -d description Description of the gist\n"); fprintf(stderr, " -f file Path to file to upload (otherwise stdin)\n"); fprintf(stderr, " -l Print a long list instead of a short table\n"); fprintf(stderr, " -n number Number of gists to fetch\n"); fprintf(stderr, " -s Print (sort) in reverse order\n"); fprintf(stderr, " -u user User for whom to list gists\n"); fprintf(stderr, " -y Do not ask for confirmation\n"); fprintf(stderr, "\n"); version(); copyright(); } static char const * human_readable_size(size_t const s) { if (s < 1024) return sn_asprintf("%zu B", s); if (s < 1024 * 1024) return sn_asprintf("%zu KiB", s / 1024); if (s < 1024 * 1024 * 1024) return sn_asprintf("%zu MiB", s / (1024 * 1024)); return sn_asprintf("%zu GiB", s / (1024 * 1024 * 1024)); } static inline char const * language_fmt(char const *it) { if (it) return it; else return "Unknown"; } static void print_gist_file(struct gcli_gist_file const *const file) { printf(" • %-15.15s %-8.8s %-s\n", language_fmt(file->language), human_readable_size(file->size), file->filename); } static void print_gist(enum gcli_output_flags const flags, struct gcli_gist const *const gist) { (void) flags; 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"); for (size_t i = 0; i < gist->files_size; ++i) print_gist_file(&gist->files[i]); printf("\n"); } static void gcli_print_gists_long(enum gcli_output_flags const flags, struct gcli_gist_list const *const list, int const max) { size_t n; if (max < 0 || (size_t)(max) > list->gists_size) n = list->gists_size; else n = max; if (flags & OUTPUT_SORTED) { for (size_t i = 0; i < n; ++i) print_gist(flags, &list->gists[n-i-1]); } else { for (size_t i = 0; i < n; ++i) print_gist(flags, &list->gists[i]); } } static void gcli_print_gists_short(enum gcli_output_flags const flags, struct gcli_gist_list const *const list, int const max) { size_t n; gcli_tbl table; 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) n = list->gists_size; else n = max; table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!table) errx(1, "gcli: error: could not init table"); if (flags & OUTPUT_SORTED) { for (size_t i = 0; i < n; ++i) { gcli_tbl_add_row(table, GCLI_COLOR_YELLOW, list->gists[n-i-1].id, list->gists[n-i-1].owner, list->gists[n-i-1].date, (int)list->gists[n-i-1].files_size, /* For safety pass it as int */ list->gists[n-i-1].description); } } else { for (size_t i = 0; i < n; ++i) { gcli_tbl_add_row(table, GCLI_COLOR_YELLOW, list->gists[i].id, list->gists[i].owner, list->gists[i].date, (int)list->gists[i].files_size, list->gists[i].description); } } gcli_tbl_end(table); } void gcli_print_gists(enum gcli_output_flags const flags, struct gcli_gist_list const *const list, int const max) { if (list->gists_size == 0) { puts("No Gists"); return; } if (flags & OUTPUT_LONG) /* if we are in long mode (no pun intended) */ gcli_print_gists_long(flags, list, max); else /* real mode (bad joke, I know) */ gcli_print_gists_short(flags, list, max); } static int subcommand_gist_get(int argc, char *argv[]) { shift(&argc, &argv); /* Discard the *get* */ char const *gist_id = shift(&argc, &argv); char const *file_name = shift(&argc, &argv); struct gcli_gist gist = {0}; struct gcli_gist_file *file = NULL; if (gcli_get_gist(g_clictx, gist_id, &gist) < 0) 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 (strcmp(gist.files[f].filename, file_name) == 0) { file = &gist.files[f]; break; } } if (!file) 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, "gcli: error: File is bigger than 4 MiB, refusing to print to stdout."); 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); return EXIT_SUCCESS; } static int subcommand_gist_create(int argc, char *argv[]) { char const *file = NULL; int ch; struct gcli_new_gist opts = {0}; struct option const options[] = { { .name = "file", .has_arg = required_argument, .flag = NULL, .val = 'r' }, { .name = "description", .has_arg = required_argument, .flag = NULL, .val = 'd' }, {0}, }; while ((ch = getopt_long(argc, argv, "f:d:", options, NULL)) != -1) { switch (ch) { case 'f': file = optarg; break; case 'd': opts.gist_description = optarg; break; case '?': default: usage(); return EXIT_FAILURE; } } argc -= optind; argv += optind; if (argc != 1) { fprintf(stderr, "gcli: error: missing file name for gist\n"); usage(); return EXIT_FAILURE; } opts.file_name = shift(&argc, &argv); if (file) { if ((opts.file = fopen(file, "r")) == NULL) err(1, "gcli: error: cannot open file"); } else { opts.file = stdin; } if (!opts.gist_description) opts.gist_description = "gcli paste"; if (gcli_create_gist(g_clictx, opts) < 0) errx(1, "gcli: error: failed to create gist: %s", gcli_get_error(g_clictx)); return EXIT_SUCCESS; } static int subcommand_gist_delete(int argc, char *argv[]) { bool always_yes = false; char const *gist_id = NULL; int ch; struct option const options[] = { { .name = "yes", .has_arg = no_argument, .flag = NULL, .val = 'y' }, {0}, }; while ((ch = getopt_long(argc, argv, "y", options, NULL)) != -1) { switch (ch) { case 'y': always_yes = true; break; case '?': default: usage(); return EXIT_FAILURE; } } argc -= optind; argv += optind; gist_id = shift(&argc, &argv); if (!always_yes && !sn_yesno("Are you sure you want to delete this gist?")) errx(1, "gcli: Aborted by user"); gcli_delete_gist(g_clictx, gist_id); return EXIT_SUCCESS; } static struct { char const *name; int (*fn)(int, char **); } gist_subcommands[] = { { .name = "get", .fn = subcommand_gist_get }, { .name = "create", .fn = subcommand_gist_create }, { .name = "delete", .fn = subcommand_gist_delete }, }; int subcommand_gists(int argc, char *argv[]) { 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, "gcli: error: The gists subcommand only works for Github " "forges. Please use either -a or -t to force using a " "Github account."); } for (size_t i = 0; i < ARRAY_SIZE(gist_subcommands); ++i) { if (argc > 1 && strcmp(argv[1], gist_subcommands[i].name) == 0) { argc -= 1; argv += 1; return gist_subcommands[i].fn(argc, argv); } } struct option const options[] = { { .name = "user", .has_arg = required_argument, .flag = NULL, .val = 'u' }, { .name = "count", .has_arg = required_argument, .flag = NULL, .val = 'n' }, { .name = "sorted", .has_arg = no_argument, .flag = NULL, .val = 's' }, { .name = "long", .has_arg = no_argument, .flag = NULL, .val = 'l' }, {0}, }; while ((ch = getopt_long(argc, argv, "lsn:u:", options, NULL)) != -1) { switch (ch) { case 'u': user = optarg; break; case 'n': { char *endptr = NULL; count = strtol(optarg, &endptr, 10); if (endptr != (optarg + strlen(optarg))) err(1, "gists: cannot parse gists count"); } break; case 's': flags |= OUTPUT_SORTED; break; case 'l': flags |= OUTPUT_LONG; break; case '?': default: usage(); return EXIT_FAILURE; } } argc -= optind; argv += optind; if (gcli_get_gists(g_clictx, user, count, &gists) < 0) errx(1, "gcli: error: failed to get gists: %s", gcli_get_error(g_clictx)); gcli_print_gists(flags, &gists, count); gcli_gists_free(&gists); return EXIT_SUCCESS; } gcli-2.3.0/src/cmd/gitconfig.c000066400000000000000000000322131460062271200161020ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 #include #include #include #include #include #include #define MAX_REMOTES 64 static struct gcli_gitremote remotes[MAX_REMOTES]; static size_t remotes_size; /* Resolve a worktree .git if needed */ static char * resolve_worktree_gitdir_if_needed(char *dotgit) { struct stat sb = {0}; FILE *f; char *newdir = NULL; if (stat(dotgit, &sb) < 0) err(1, "gcli: stat"); /* Real .git directory */ if (S_ISDIR(sb.st_mode)) return dotgit; f = fopen(dotgit, "r"); if (!f) err(1, "gcli: fopen"); while (!ferror(f) && !feof(f)) { char *key, *value; char buf[256] = {0}; fgets(buf, sizeof buf, f); key = buf; value = strchr(buf, ':'); if (!value) continue; *value++ = '\0'; while (*value == ' ') ++value; if (strcmp(key, "gitdir") == 0) { size_t const len = strlen(value); newdir = strdup(value); /* trim off any potential newline character */ if (newdir[len - 1] == '\n') newdir[len - 1] = '\0'; break; } } if (newdir == NULL) errx(1, "gcli: error: .git is a file but does not contain a gitdir pointer"); fclose(f); free(dotgit); return newdir; } /* Search for a file named fname in the .git directory. * * This is ugly code. However, I don't see an easier way to do * this. */ static char const * find_file_in_dotgit(char const *fname) { DIR *curr_dir = NULL; struct dirent *ent; char *curr_dir_path; char *dotgit = NULL; char *config_path = NULL; curr_dir_path = getcwd(NULL, 128); if (!curr_dir_path) err(1, "gcli: getcwd"); /* Here we are trying to traverse upwards through the directory * tree, searching for a directory called .git. * Starting point is ".".*/ do { curr_dir = opendir(curr_dir_path); if (!curr_dir) err(1, "gcli: opendir"); /* Read entries of the directory */ while ((ent = readdir(curr_dir))) { if (strcmp(".", ent->d_name) == 0 || strcmp("..", ent->d_name) == 0) continue; /* Is this the .git directory? If so, allocate some memory * to store the path into dotgit and append '/\0' */ if (strcmp(".git", ent->d_name) == 0) { size_t len = strlen(curr_dir_path); dotgit = malloc(len + strlen(ent->d_name) + 2); memcpy(dotgit, curr_dir_path, len); dotgit[len] = '/'; memcpy(dotgit + len + 1, ent->d_name, strlen(ent->d_name)); dotgit[len + 1 + strlen(ent->d_name)] = 0; break; } } /* If we reach this point and dotgit is NULL we couldn't find * the .git directory in the current directory. In this case * we append '..' to the path and resolve it. */ if (!dotgit) { size_t len = strlen(curr_dir_path); char *tmp = malloc(len + sizeof("/..")); memcpy(tmp, curr_dir_path, len); memcpy(tmp + len, "/..", sizeof("/..")); free(curr_dir_path); curr_dir_path = realpath(tmp, NULL); if (!curr_dir_path) err(1, "gcli: error: realpath at %s", tmp); free(tmp); /* Check if we reached the filesystem root */ if (strcmp("/", curr_dir_path) == 0) { free(curr_dir_path); closedir(curr_dir); warnx("not a git repository"); return NULL; } } closedir(curr_dir); } while (dotgit == NULL); free(curr_dir_path); /* In case we are working with git worktrees, the .git might be a * file that contains a pointer to the actual .git directory. Here * we call into a function that resolves this link if needed. */ dotgit = resolve_worktree_gitdir_if_needed(dotgit); /* Now search for the file in the found .git directory */ curr_dir = opendir(dotgit); if (!curr_dir) err(1, "gcli: opendir"); while ((ent = readdir(curr_dir))) { /* skip over . and .. directory entries */ if (strcmp(".", ent->d_name) == 0 || strcmp("..", ent->d_name) == 0) continue; /* We found the config file, put together it's path and return * that */ if (strcmp(fname, ent->d_name) == 0) { int len = strlen(dotgit); config_path = malloc(len + 1 + sizeof(fname)); memcpy(config_path, dotgit, len); config_path[len] = '/'; memcpy(config_path + len + 1, fname, strlen(fname) + 1); closedir(curr_dir); free(dotgit); return config_path; } } errx(1, "gcli: error: .git without a config file"); return NULL; } char const * gcli_find_gitconfig(void) { return find_file_in_dotgit("config"); } sn_sv gcli_gitconfig_get_current_branch(void) { char const *HEAD; void *mmap_pointer; sn_sv buffer; char prefix[] = "ref: refs/heads/"; HEAD = find_file_in_dotgit("HEAD"); if (!HEAD) return SV_NULL; int len = sn_mmap_file(HEAD, &mmap_pointer); if (len < 0) err(1, "gcli: mmap"); buffer = sn_sv_from_parts(mmap_pointer, len); if (sn_sv_has_prefix(buffer, prefix)) { buffer.data += sizeof(prefix) - 1; buffer.length -= sizeof(prefix) - 1; return sn_sv_trim(buffer); } else { munmap(mmap_pointer, len); return SV_NULL; } } static void http_extractor(struct gcli_gitremote *const remote, char const *prefix) { size_t prefix_size = strlen(prefix); sn_sv pair = remote->url; if (sn_sv_has_prefix(remote->url, "https://github.com/")) { prefix_size = sizeof("https://github.com/") - 1; remote->forge_type = GCLI_FORGE_GITHUB; } else if (sn_sv_has_prefix(remote->url, "https://gitlab.com/")) { prefix_size = sizeof("https://gitlab.com/") - 1; remote->forge_type = GCLI_FORGE_GITLAB; } else if (sn_sv_has_prefix(remote->url, "https://codeberg.org/")) { prefix_size = sizeof("https://codeberg.org/") - 1; remote->forge_type = GCLI_FORGE_GITEA; } else { warnx("non-github, non-gitlab and non-codeberg https remotes are " "not supported and will likely cause bugs"); } pair.length -= prefix_size; pair.data += prefix_size; remote->owner = sn_sv_chop_until(&pair, '/'); pair.data += 1; pair.length -= 1; pair = sn_sv_strip_suffix(pair, ".git"); remote->repo = pair; } static void ssh_extractor(struct gcli_gitremote *const remote, char const *prefix) { size_t prefix_size = strlen(prefix); if (sn_sv_has_prefix(remote->url, "git@github.com")) remote->forge_type = GCLI_FORGE_GITHUB; else if (sn_sv_has_prefix(remote->url, "git@gitlab.com")) remote->forge_type = GCLI_FORGE_GITLAB; else if (sn_sv_has_prefix(remote->url, "git@codeberg.org")) remote->forge_type = GCLI_FORGE_GITEA; sn_sv pair = remote->url; pair.length -= prefix_size; pair.data += prefix_size; sn_sv_chop_until(&pair, ':'); pair.data += 1; pair.length -= 1; remote->owner = sn_sv_chop_until(&pair, '/'); pair.data += 1; pair.length -= 1; pair = sn_sv_strip_suffix(pair, ".git"); remote->repo = pair; } struct forge_ex_def { char const *prefix; void (*extractor)(struct gcli_gitremote *const, char const *); } url_extractors[] = { { .prefix = "git@", .extractor = ssh_extractor }, { .prefix = "ssh://", .extractor = ssh_extractor }, { .prefix = "https://", .extractor = http_extractor }, }; static void gitconfig_parse_remote(sn_sv section_title, sn_sv entry) { sn_sv remote_name = SV_NULL; /* If there is no remote name, just return and continue with the * next section. I don't exactly know why there even are such * sections and what they are useful for, but ok. */ if (sn_sv_eq_to(sn_sv_trim(section_title), "remote")) return; /* the remote name is wrapped in double quotes */ sn_sv_chop_until(§ion_title, '"'); /* skip the first quote */ section_title.data += 1; section_title.length -= 1; remote_name = sn_sv_chop_until(§ion_title, '"'); while ((entry = sn_sv_trim_front(entry)).length > 0) { if (sn_sv_has_prefix(entry, "url")) { if (remotes_size == MAX_REMOTES) errx(1, "gcli: error: too many remotes"); struct gcli_gitremote *const remote = &remotes[remotes_size++]; remote->name = remote_name; sn_sv_chop_until(&entry, '='); entry.data += 1; entry.length -= 1; sn_sv url = sn_sv_trim(sn_sv_chop_until(&entry, '\n')); remote->url = url; remote->forge_type = -1; for (size_t i = 0; i < ARRAY_SIZE(url_extractors); ++i) { if (sn_sv_has_prefix(url, url_extractors[i].prefix)) { url_extractors[i].extractor( remote, url_extractors[i].prefix); } } } else { sn_sv_chop_until(&entry, '\n'); } } } static void gcli_gitconfig_read_gitconfig(void) { static int has_read_gitconfig = 0; if (has_read_gitconfig) return; has_read_gitconfig = 1; char const *path = NULL; sn_sv buffer = {0}; path = gcli_find_gitconfig(); if (!path) return; buffer.length = sn_mmap_file(path, (void **)&buffer.data); while (buffer.length > 0) { buffer = sn_sv_trim_front(buffer); if (buffer.length == 0) break; /* TODO: Git Config files support comments */ if (*buffer.data != '[') errx(1, "gcli: error: invalid git config"); sn_sv section_title = sn_sv_chop_until(&buffer, ']'); section_title.length -= 1; section_title.data += 1; buffer.length -= 2; buffer.data += 2; sn_sv entry = sn_sv_chop_until(&buffer, '['); if (sn_sv_has_prefix(section_title, "remote")) { gitconfig_parse_remote(section_title, entry); } else { // @@@: skip section } } } void gcli_gitconfig_add_fork_remote(char const *org, char const *repo) { char remote[64] = {0}; FILE *remote_list = popen("git remote", "r"); if (!remote_list) err(1, "gcli: popen"); /* TODO: Output informational messages */ /* Rename possibly existing origin remote to point at the * upstream */ while (fgets(remote, sizeof(remote), remote_list)) { if (strcmp(remote, "origin\n") == 0) { pid_t pid = 0; if ((pid = fork()) == 0) { printf("[INFO] git remote rename origin upstream\n"); execlp("git", "git", "remote", "rename", "origin", "upstream", NULL); } else if (pid > 0) { int status = 0; waitpid(pid, &status, 0); if (!(WIFEXITED(status) && (WEXITSTATUS(status) == 0))) errx(1, "gcli: git child process failed"); } else { err(1, "gcli: fork"); } break; } } pclose(remote_list); /* Add new remote */ { pid_t pid = 0; if ((pid = fork()) == 0) { char const *remote_url = sn_asprintf( "git@github.com:%s/%s", org, repo); printf("[INFO] git remote add origin %s\n", remote_url); execlp("git", "git", "remote", "add", "origin", remote_url, NULL); } else if (pid > 0) { int status = 0; waitpid(pid, &status, 0); if (!(WIFEXITED(status) && (WEXITSTATUS(status) == 0))) errx(1, "gcli: git child process failed"); } else { err(1, "gcli: fork"); } } } /** * Return the gcli_forge_type for the given remote or -1 if * unknown */ int gcli_gitconfig_get_forgetype(struct gcli_ctx *ctx, char const *const remote_name) { (void) ctx; gcli_gitconfig_read_gitconfig(); if (remote_name) { for (size_t i = 0; i < remotes_size; ++i) { if (sn_sv_eq_to(remotes[i].name, remote_name)) return remotes[i].forge_type; } } if (!remotes_size) { warn("no remotes to auto-detect forge"); return -1; } return remotes[0].forge_type; } int gcli_gitconfig_repo_by_remote(struct gcli_ctx *ctx, char const *const remote, char const **const owner, char const **const repo, int *const forge) { gcli_gitconfig_read_gitconfig(); if (remote) { for (size_t i = 0; i < remotes_size; ++i) { if (sn_sv_eq_to(remotes[i].name, remote)) { *owner = sn_sv_to_cstr(remotes[i].owner); *repo = sn_sv_to_cstr(remotes[i].repo); if (forge) *forge = remotes[i].forge_type; return 0; } } return gcli_error(ctx, "no such remote: %s", remote); } if (!remotes_size) return gcli_error(ctx, "no remotes to auto-detect forge"); *owner = sn_sv_to_cstr(remotes[0].owner); *repo = sn_sv_to_cstr(remotes[0].repo); if (forge) *forge = remotes[0].forge_type; return 0; } gcli-2.3.0/src/cmd/interactive.c000066400000000000000000000101501460062271200164420ustar00rootroot00000000000000/* * Copyright 2024 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 #if defined(HAVE_LIBEDIT) # if HAVE_LIBEDIT # include # define USE_EDITLINE 1 # else # define USE_EDITLINE 0 # endif #else # define USE_EDITLINE 0 #endif #if !USE_EDITLINE && defined(HAVE_READLINE) # if HAVE_READLINE # include # define USE_READLINE 1 # else # define USE_READLINE 0 # endif #else # define USE_READLINE 0 #endif #if USE_EDITLINE static char * el_prompt_fn(EditLine *el_ctx) { char *prompt = NULL; if (el_get(el_ctx, EL_CLIENTDATA, &prompt) < 0) return strdup(">"); else return prompt; } #endif /* Actual implementation for reading in the line */ static char * get_input_line(char *const prompt) { #if USE_EDITLINE static EditLine *el_ctx = NULL; char const *txt = NULL; char *result = NULL; int len = 0; if (!el_ctx) { el_ctx = el_init("gcli", stdin, stdout, stderr); el_set(el_ctx, EL_PROMPT, el_prompt_fn); el_set(el_ctx, EL_EDITOR, "emacs"); } el_set(el_ctx, EL_CLIENTDATA, prompt); txt = el_gets(el_ctx, &len); bool const is_error = txt == NULL || len < 0; bool const is_empty = len == 1 && txt[0] == '\n'; if (is_error || is_empty) return NULL; result = strdup(txt); result[len - 1] = '\0'; return result; #elif USE_READLINE char *result = readline(prompt); /* readline() returns an empty string if the input is empty. Our interface * returns NULL if the input was empty */ if (result == NULL || result[0] == '\0') { free(result); result = NULL; } return result; #else char buf[256] = {0}; /* nobody types more than 256 characters, amirite? */ fputs(prompt, stdout); fflush(stdout); fgets(buf, sizeof buf, stdin); if (buf[0] == '\n') return NULL; buf[strlen(buf)-1] = '\0'; return strdup(buf); #endif } /** Prompt for input with an optional default * * This function prompts for user input, possibly with editline * capabilities. The prompt can be specified using a format string. * An optional default value can be specified. If the default value * is NULL the user will be repeatedly prompted until the input is * non-empty. */ char * gcli_cmd_prompt(char const *const fmt, char const *const deflt, ...) { va_list vp; char *result; char prompt[256] = {0}; size_t prompt_len; va_start(vp, deflt); vsnprintf(prompt, sizeof(prompt), fmt, vp); va_end(vp); prompt_len = strlen(prompt); if (deflt) { snprintf(prompt + prompt_len, sizeof(prompt) - prompt_len, " [%s]: ", deflt); } else { strncat(prompt, ": ", sizeof(prompt) - prompt_len - 1); } do { result = get_input_line(prompt); } while (deflt == NULL && result == NULL); if (result == NULL) result = strdup(deflt); return result; } gcli-2.3.0/src/cmd/issues.c000066400000000000000000000534301460062271200154500ustar00rootroot00000000000000/* * Copyright 2022 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 #include #include #include #include #ifdef HAVE_GETOPT_H #include #endif static void 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] [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"); fprintf(stderr, " -r repo The repository name\n"); fprintf(stderr, " -y Do not ask for confirmation.\n"); fprintf(stderr, " -A author Only print issues by the given author\n"); fprintf(stderr, " -L label Filter issues by the given label\n"); fprintf(stderr, " -M milestone Filter issues by the given milestone\n"); fprintf(stderr, " -a Fetch everything including closed issues \n"); fprintf(stderr, " -s Print (sort) in reverse order\n"); fprintf(stderr, " -n number Number of issues to fetch (-1 = everything)\n"); fprintf(stderr, " -i issue ID of issue to perform actions on\n"); fprintf(stderr, "ACTIONS:\n"); fprintf(stderr, " all Display both status and and op\n"); fprintf(stderr, " status Display status information\n"); fprintf(stderr, " op Display original post\n"); fprintf(stderr, " comments Display comments\n"); fprintf(stderr, " close Close the issue\n"); fprintf(stderr, " reopen Reopen a closed issue\n"); fprintf(stderr, " assign Assign the issue to the given user\n"); fprintf(stderr, " labels ... Add or remove labels:\n"); fprintf(stderr, " add \n"); fprintf(stderr, " remove \n"); fprintf(stderr, " milestone Assign this issue to the given milestone\n"); fprintf(stderr, " milestone -d Clear the assigned milestone of the given issue\n"); fprintf(stderr, " notes Alias for comments\n"); fprintf(stderr, " title Change the title of the issue\n"); fprintf(stderr, "\n"); version(); copyright(); } void gcli_print_issues(enum gcli_output_flags const flags, struct gcli_issue_list const *const list, int const max) { int n, pruned = 0; gcli_tbl table; 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) { puts("No issues"); return; } table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!table) 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) n = list->issues_size; else n = max; /* Iterate depending on the output order */ if (flags & OUTPUT_SORTED) { for (int i = 0; i < n; ++i) { if (!list->issues[n - 1 - 1].is_pr) { gcli_tbl_add_row(table, list->issues[n - i - 1].number, list->issues[n - i - 1].comments, list->issues[n - i - 1].state, list->issues[n - i - 1].title); } else { pruned++; } } } else { for (int i = 0; i < n; ++i) { if (!list->issues[i].is_pr) { gcli_tbl_add_row(table, list->issues[i].number, list->issues[i].comments, list->issues[i].state, list->issues[i].title); } else { pruned++; } } } /* Dump the table */ gcli_tbl_end(table); /* Inform the user that we pruned pull requests from the output */ if (pruned && sn_getverbosity() != VERBOSITY_QUIET) fprintf(stderr, "info: %d pull requests pruned\n", pruned); } void 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, "%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, "%s", it->author); gcli_dict_add(dict, "STATE", GCLI_TBLCOL_STATECOLOURED, 0, "%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 ((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_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_string_list(dict, "ASSIGNEES", (char const *const *)it->assignees, it->assignees_size); } else { gcli_dict_add(dict, "ASSIGNEES", 0, 0, "none"); } /* Dump the dictionary */ gcli_dict_end(dict); } void gcli_issue_print_op(struct gcli_issue const *const it) { if (it->body) pretty_print(it->body, 4, 80, stdout); } static void issue_init_user_file(struct gcli_ctx *ctx, FILE *stream, void *_opts) { (void) ctx; struct gcli_submit_issue_options *opts = _opts; fprintf( stream, "! ISSUE TITLE : %s\n" "! Enter issue description above.\n" "! All lines starting with '!' will be discarded.\n", opts->title); } 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(struct gcli_submit_issue_options *opts, int always_yes) { int rc; opts->body = gcli_issue_get_user_message(opts); printf("The following issue will be created:\n" "\n" "TITLE : %s\n" "OWNER : %s\n" "REPO : %s\n" "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, "gcli: Submission aborted."); } rc = gcli_issue_submit(g_clictx, opts); free(opts->body); free(opts->body); return rc; } static int subcommand_issue_create_interactive(struct gcli_submit_issue_options *const opts) { char const *deflt_owner = NULL, *deflt_repo = NULL; int rc = 0; gcli_config_get_repo(g_clictx, &deflt_owner, &deflt_repo); if (!opts->owner) opts->owner = gcli_cmd_prompt("Owner", deflt_owner); if (!opts->repo) opts->repo = gcli_cmd_prompt("Repository", deflt_repo); opts->title = gcli_cmd_prompt("Title", NULL); rc = create_issue(opts, false); if (rc < 0) { fprintf(stderr, "gcli: error: %s\n", gcli_get_error(g_clictx)); return EXIT_FAILURE; } return EXIT_SUCCESS; } 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; 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, .flag = NULL, .val = 'o' }, { .name = "repo", .has_arg = required_argument, .flag = NULL, .val = 'r' }, { .name = "yes", .has_arg = no_argument, .flag = NULL, .val = 'y' }, {0}, }; while ((ch = getopt_long(argc, argv, "o:r:O:", options, NULL)) != -1) { switch (ch) { case 'o': opts.owner = optarg; break; case 'r': opts.repo = optarg; break; 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; } } argc -= optind; argv += optind; if (argc == 0) return subcommand_issue_create_interactive(&opts); check_owner_and_repo(&opts.owner, &opts.repo); if (argc != 1) { fprintf(stderr, "gcli: error: Expected one argument for issue title\n"); usage(); return EXIT_FAILURE; } opts.title = argv[0]; if (create_issue(&opts, always_yes) < 0) errx(1, "gcli: error: failed to submit issue: %s", gcli_get_error(g_clictx)); gcli_nvlist_free(&opts.extra); return EXIT_SUCCESS; } static inline int handle_issues_actions(int argc, char *argv[], char const *const owner, char const *const repo, int const issue); int subcommand_issues(int argc, char *argv[]) { 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; struct gcli_issue_fetch_details details = {0}; enum gcli_output_flags flags = 0; /* detect whether we wanna create an issue */ if (argc > 1 && (strcmp(argv[1], "create") == 0)) { shift(&argc, &argv); return subcommand_issue_create(argc, argv); } const struct option options[] = { { .name = "all", .has_arg = no_argument, .flag = NULL, .val = 'a' }, { .name = "sorted", .has_arg = no_argument, .flag = NULL, .val = 's' }, { .name = "repo", .has_arg = required_argument, .flag = NULL, .val = 'r' }, { .name = "owner", .has_arg = required_argument, .flag = NULL, .val = 'o' }, { .name = "id", .has_arg = required_argument, .flag = NULL, .val = 'i' }, { .name = "count", .has_arg = required_argument, .flag = NULL, .val = 'n' }, { .name = "author", .has_arg = required_argument, .flag = NULL, .val = 'A', }, { .name = "label", .has_arg = required_argument, .flag = NULL, .val = 'L', }, { .name = "milestone", .has_arg = required_argument, .flag = NULL, .val = 'M', }, {0}, }; /* parse options */ while ((ch = getopt_long(argc, argv, "+sn:o:r:i:aA:L:M:", options, NULL)) != -1) { switch (ch) { case 'o': owner = optarg; break; case 'r': repo = optarg; break; case 'i': { issue_id = strtol(optarg, &endptr, 10); if (endptr != (optarg + strlen(optarg))) err(1, "gcli: error: cannot parse issue number"); if (issue_id < 0) 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, "gcli: error: cannot parse issue count"); if (n < -1) errx(1, "gcli: error: issue count is out of range"); if (n == 0) errx(1, "gcli: error: issue count must not be zero"); } break; case 'a': details.all = true; break; case 's': flags |= OUTPUT_SORTED; break; case 'A': { details.author = optarg; } break; case 'L': { details.label = optarg; } break; case 'M': { details.milestone = optarg; } break; case '?': default: usage(); return EXIT_FAILURE; } } argc -= optind; argv += optind; check_owner_and_repo(&owner, &repo); /* No issue number was given, so list all open issues */ if (issue_id < 0) { /* 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); gcli_issues_free(&list); return EXIT_SUCCESS; } /* require -a to not be set */ if (details.all) { fprintf(stderr, "gcli: error: -a cannot be combined with operations on " "an issue\n"); usage(); return EXIT_FAILURE; } /* Handle all the actions */ return handle_issues_actions(argc, argv, owner, repo, issue_id); } static inline void ensure_issue(char const *const owner, char const *const repo, int const issue_id, 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, "gcli: error: failed to retrieve issue data: %s", gcli_get_error(g_clictx)); *have_fetched_issue = 1; } static inline void handle_issue_labels_action(int *argc, char ***argv, char const *const owner, char const *const repo, int const issue_id) { char const **add_labels = NULL; size_t add_labels_size = 0; char const **remove_labels = NULL; size_t remove_labels_size = 0; int rc = 0; if (argc == 0) { fprintf(stderr, "gcli: error: expected label operations\n"); usage(); exit(EXIT_FAILURE); } parse_labels_options(argc, argv, &add_labels, &add_labels_size, &remove_labels, &remove_labels_size); /* actually go about deleting and adding the labels */ if (add_labels_size) { rc = gcli_issue_add_labels(g_clictx, owner, repo, issue_id, add_labels, add_labels_size); if (rc < 0) { errx(1, "gcli: error: failed to add labels: %s", gcli_get_error(g_clictx)); } } if (remove_labels_size) { rc = gcli_issue_remove_labels(g_clictx, owner, repo, issue_id, remove_labels, remove_labels_size); if (rc < 0) { errx(1, "gcli: error: failed to remove labels: %s", gcli_get_error(g_clictx)); } } free(add_labels); free(remove_labels); } static inline void handle_issue_milestone_action(int *argc, char ***argv, char const *const owner, char const *const repo, int const issue_id) { char const *milestone_str; char *endptr; int milestone, rc; /* Set the milestone for the issue * * Check that the user provided a milestone id */ if (!argc) { fprintf(stderr, "gcli: error: missing milestone id\n"); usage(); exit(EXIT_FAILURE); } /* Fetch the milestone from the argument vector */ milestone_str = shift(argc, argv); /* Check if the milestone_str is -d indicating that we should * clear the milestone */ if (strcmp(milestone_str, "-d") == 0) { rc = gcli_issue_clear_milestone(g_clictx, owner, repo, issue_id); if (rc < 0) { errx(1, "gcli: error: could not clear milestone of issue #%d: %s", issue_id, gcli_get_error(g_clictx)); } return; } /* It is a milestone ID. Parse it. */ milestone = strtoul(milestone_str, &endptr, 10); /* Check successful for parse */ if (endptr != milestone_str + strlen(milestone_str)) { 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, "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, char const *const repo, int const issue_id) { int have_fetched_issue = 0; struct gcli_issue issue = {0}; /* Check if the user missed out on supplying actions */ if (argc == 0) { fprintf(stderr, "gcli: error: no actions supplied\n"); usage(); exit(EXIT_FAILURE); } /* execute all operations on the given issue */ while (argc > 0) { char const *operation = shift(&argc, &argv); if (strcmp("all", operation) == 0) { /* Make sure we have fetched the issue data */ ensure_issue(owner, repo, issue_id, &have_fetched_issue, &issue); gcli_issue_print_summary(&issue); puts("\nORIGINAL POST\n"); gcli_issue_print_op(&issue); } else if (strcmp("comments", operation) == 0 || strcmp("notes", operation) == 0) { /* Doesn't require fetching the issue data */ if (gcli_issue_comments(owner, repo, issue_id) < 0) errx(1, "gcli: error: failed to fetch issue comments: %s", gcli_get_error(g_clictx)); } else if (strcmp("op", operation) == 0) { /* Make sure we have fetched the issue data */ ensure_issue(owner, repo, issue_id, &have_fetched_issue, &issue); gcli_issue_print_op(&issue); } else if (strcmp("status", operation) == 0) { /* Make sure we have fetched the issue data */ ensure_issue(owner, repo, issue_id, &have_fetched_issue, &issue); gcli_issue_print_summary(&issue); } else if (strcmp("close", operation) == 0) { if (gcli_issue_close(g_clictx, owner, repo, issue_id) < 0) 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, "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, "gcli: error: failed to assign issue: %s", gcli_get_error(g_clictx)); } else if (strcmp("labels", operation) == 0) { handle_issue_labels_action( &argc, &argv, owner, repo, issue_id); } else if (strcmp("milestone", operation) == 0) { handle_issue_milestone_action( &argc, &argv, owner, repo, issue_id); } else if (strcmp("title", operation) == 0) { char const *new_title = shift(&argc, &argv); int rc = gcli_issue_set_title(g_clictx, owner, repo, issue_id, new_title); if (rc < 0) { 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, "gcli: error: unknown operation %s\n", operation); usage(); return EXIT_FAILURE; } } if (have_fetched_issue) gcli_issue_free(&issue); return EXIT_SUCCESS; } gcli-2.3.0/src/cmd/labels.c000066400000000000000000000177301460062271200154020ustar00rootroot00000000000000/* * Copyright 2022 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 #ifdef HAVE_GETOPT_H #include #endif #include #include static void usage(void) { fprintf(stderr, "usage: gcli labels create [-o owner -r repo] -n name -c colour -d description\n"); fprintf(stderr, " gcli labels delete [-o owner -r repo] id\n"); fprintf(stderr, " gcli labels [-o owner -r repo] [-n number]\n"); fprintf(stderr, "OPTIONS:\n"); fprintf(stderr, " -o owner The repository owner\n"); fprintf(stderr, " -r repo The repository name\n"); fprintf(stderr, " -n number Number of labels to fetch (-1 = everything)\n"); fprintf(stderr, " -l name Name of the new label\n"); fprintf(stderr, " -c colour Six digit hex code of the label's colour\n"); fprintf(stderr, " -d description A short description of the label\n"); fprintf(stderr, "\n"); version(); copyright(); } void gcli_labels_print(struct gcli_label_list const *const list, int const max) { size_t n; gcli_tbl table; 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 }, { .name = "DESCRIPTION", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, }; /* Determine number of items to print */ if (max < 0 || (size_t)(max) > list->labels_size) n = list->labels_size; else n = max; /* Fill table */ table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!table) errx(1, "gcli: error: could not init table"); for (size_t i = 0; i < n; ++i) { gcli_tbl_add_row(table, (long)list->labels[i].id, /* Cast is important here (#165) */ list->labels[i].colour, gcli_config_have_colours(g_clictx) ? " " : "", list->labels[i].name, list->labels[i].description); } gcli_tbl_end(table); } static int subcommand_labels_delete(int argc, char *argv[]) { int ch, rc; char const *owner = NULL, *repo = NULL; const struct option options[] = { {.name = "repo", .has_arg = required_argument, .val = 'r'}, {.name = "owner", .has_arg = required_argument, .val = 'o'}, {0}, }; while ((ch = getopt_long(argc, argv, "o:r:", options, NULL)) != -1) { switch (ch) { case 'o': owner = optarg; break; case 'r': repo = optarg; break; case '?': default: usage(); return EXIT_FAILURE; } } argc -= optind; argv += optind; check_owner_and_repo(&owner, &repo); if (argc != 1) { 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, "gcli: error: couldn't delete label\n"); return EXIT_FAILURE; } return EXIT_SUCCESS; } static int subcommand_labels_create(int argc, char *argv[]) { struct gcli_label label = {0}; struct gcli_label_list labels = { .labels = &label, .labels_size = 1 }; char const *owner = NULL, *repo = NULL; int ch; const struct option options[] = { {.name = "repo", .has_arg = required_argument, .val = 'r'}, {.name = "owner", .has_arg = required_argument, .val = 'o'}, {.name = "name", .has_arg = required_argument, .val = 'n'}, {.name = "colour", .has_arg = required_argument, .val = 'c'}, {.name = "description", .has_arg = required_argument, .val = 'd'}, {0} }; while ((ch = getopt_long(argc, argv, "n:o:r:d:c:", options, NULL)) != -1) { switch (ch) { case 'o': owner = optarg; break; case 'r': repo = optarg; 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, "gcli: error: cannot parse colour"); } break; case 'd': { label.description = optarg; } break; case 'n': { label.name = optarg; } break; case '?': default: usage(); return EXIT_FAILURE; } } argc -= optind; argv += optind; check_owner_and_repo(&owner, &repo); if (!label.name) { fprintf(stderr, "gcli: error: missing name for label\n"); usage(); return EXIT_FAILURE; } if (!label.description) { 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, "gcli: error: failed to create label: %s", gcli_get_error(g_clictx)); } /* only if we are not quieted */ if (!sn_quiet()) gcli_labels_print(&labels, 1); gcli_free_label(&label); return EXIT_SUCCESS; } static struct { char const *name; int (*fn)(int, char **); } labels_subcommands[] = { { .name = "delete", .fn = subcommand_labels_delete }, { .name = "create", .fn = subcommand_labels_create }, }; int subcommand_labels(int argc, char *argv[]) { int count = 30; int ch; char const *owner = NULL, *repo = NULL; struct gcli_label_list labels = {0}; const struct option options[] = { {.name = "repo", .has_arg = required_argument, .flag = NULL, .val = 'r'}, {.name = "owner", .has_arg = required_argument, .flag = NULL, .val = 'o'}, {.name = "count", .has_arg = required_argument, .flag = NULL, .val = 'n'}, {0} }; if (argc > 1) { for (size_t i = 0; i < ARRAY_SIZE(labels_subcommands); ++i) { if (strcmp(labels_subcommands[i].name, argv[1]) == 0) return labels_subcommands[i].fn(argc - 1, argv + 1); } } while ((ch = getopt_long(argc, argv, "n:o:r:", options, NULL)) != -1) { switch (ch) { case 'o': owner = optarg; break; case 'r': repo = optarg; break; case 'n': { char *endptr = NULL; count = strtol(optarg, &endptr, 10); if (endptr != (optarg + strlen(optarg))) errx(1, "gcli: error: cannot parse label count"); if (count == 0) errx(1, "gcli: error: number of labels must not be zero"); } break; case '?': default: usage(); return EXIT_FAILURE; } } argc -= optind; argv += optind; /* sanity check: we must have parsed everything by now */ if (argc > 0) { fprintf(stderr, "gcli: error: stray arguments\n"); usage(); return EXIT_FAILURE; } check_owner_and_repo(&owner, &repo); if (gcli_get_labels(g_clictx, owner, repo, count, &labels) < 0) errx(1, "gcli: error: could not fetch list of labels: %s", gcli_get_error(g_clictx)); gcli_labels_print(&labels, count); gcli_free_labels(&labels); return EXIT_SUCCESS; } gcli-2.3.0/src/cmd/milestones.c000066400000000000000000000276471460062271200163320ustar00rootroot00000000000000/* * 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 #ifdef HAVE_GETOPT_H #include #endif static void usage(void) { fprintf(stderr, "usage: gcli milestones [-o owner -r repo]\n"); fprintf(stderr, " gcli milestones create [-o owner -r repo] -t title [-d description]\n"); fprintf(stderr, " gcli milestones [-o owner -r repo] -i milestone actions...\n"); fprintf(stderr, "OPTIONS:\n"); fprintf(stderr, " -o owner The repository owner\n"); fprintf(stderr, " -r repo The repository name\n"); fprintf(stderr, " -i milestone Run actions for the given milestone id\n"); fprintf(stderr, " -t title Title of the milestone to create\n"); fprintf(stderr, " -d description Description the milestone to create\n"); fprintf(stderr, "ACTIONS:\n"); fprintf(stderr, " all Display both status information and issues for the milestone\n"); fprintf(stderr, " status Display general status information about the milestone\n"); fprintf(stderr, " issues List issues associated with the milestone\n"); fprintf(stderr, " set-duedate Set due date \n"); fprintf(stderr, " delete Delete this milestone\n"); fprintf(stderr, "\n"); version(); copyright(); } void gcli_print_milestones(struct gcli_milestone_list const *const list, int max) { size_t n; gcli_tbl tbl; 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 }, { .name = "TITLE", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, }; if (!list->milestones_size) { puts("No milestones"); return; } tbl = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!tbl) errx(1, "gcli: error: could not init table printer"); if (max < 0 || (size_t)(max) > list->milestones_size) n = list->milestones_size; else n = max; for (size_t i = 0; i < n; ++i) { gcli_tbl_add_row(tbl, list->milestones[i].id, list->milestones[i].state, list->milestones[i].created_at, list->milestones[i].title); } gcli_tbl_end(tbl); } void gcli_print_milestone(struct gcli_milestone const *const milestone) { gcli_dict dict; uint32_t const quirks = gcli_forge(g_clictx)->milestone_quirks; dict = gcli_dict_begin(); gcli_dict_add(dict, "ID", 0, 0, "%"PRIid, milestone->id); gcli_dict_add_string(dict, "TITLE", 0, 0, milestone->title); gcli_dict_add_string(dict, "STATE", GCLI_TBLCOL_STATECOLOURED, 0, milestone->state); gcli_dict_add_string(dict, "CREATED", 0, 0, milestone->created_at); gcli_dict_add_string(dict, "UPDATED", 0, 0, milestone->created_at); if ((quirks & GCLI_MILESTONE_QUIRKS_DUEDATE) == 0) gcli_dict_add_string(dict, "DUE", 0, 0, milestone->due_date); if ((quirks & GCLI_MILESTONE_QUIRKS_EXPIRED) == 0) gcli_dict_add_string(dict, "EXPIRED", 0, 0, sn_bool_yesno(milestone->expired)); if ((quirks & GCLI_MILESTONE_QUIRKS_NISSUES) == 0) { gcli_dict_add(dict, "OPEN ISSUES", 0, 0, "%d", milestone->open_issues); gcli_dict_add(dict, "CLOSED ISSUES", 0, 0, "%d", milestone->closed_issues); } gcli_dict_end(dict); if (milestone->description && strlen(milestone->description)) { printf("\nDESCRIPTION:\n"); pretty_print(milestone->description, 4, 80, stdout); } } static int handle_milestone_actions(int argc, char *argv[], char const *const owner, char const *const repo, int const milestone_id); static int subcommand_milestone_create(int argc, char *argv[]) { int ch; struct gcli_milestone_create_args args = {0}; struct option const options[] = { { .name = "owner", .has_arg = required_argument, .flag = NULL, .val = 'o' }, { .name = "repo", .has_arg = required_argument, .flag = NULL, .val = 'r' }, { .name = "title", .has_arg = required_argument, .flag = NULL, .val = 't' }, { .name = "description", .has_arg = required_argument, .flag = NULL, .val = 'd' }, }; /* Read in options */ while ((ch = getopt_long(argc, argv, "+o:r:t:d:", options, NULL)) != -1) { switch (ch) { case 'o': args.owner = optarg; break; case 'r': args.repo = optarg; break; case 't': args.title = optarg; break; case 'd': args.description = optarg; break; default: usage(); return 1; } } argc -= optind; argv += optind; /* sanity check argumets */ if (argc) 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, "gcli: error: missing milestone title"); /* actually create the milestone */ if (gcli_create_milestone(g_clictx, &args) < 0) errx(1, "gcli: error: could not create milestone: %s", gcli_get_error(g_clictx)); return 0; } int subcommand_milestones(int argc, char *argv[]) { int ch, rc, max = 30, milestone_id = -1; char const *repo, *owner; struct option const options[] = { { .name = "owner", .has_arg = required_argument, .flag = NULL, .val = 'o' }, { .name = "repo", .has_arg = required_argument, .flag = NULL, .val = 'r' }, { .name = "count", .has_arg = required_argument, .flag = NULL, .val = 'n' }, { .name = "id", .has_arg = required_argument, .flag = NULL, .val = 'i' }, }; /* detect whether we wanna create a milestone */ if (argc > 1 && (strcmp(argv[1], "create") == 0)) { shift(&argc, &argv); return subcommand_milestone_create(argc, argv); } /* Proceed with fetching information */ repo = NULL; owner = NULL; while ((ch = getopt_long(argc, argv, "+o:r:n:i:", options, NULL)) != -1) { switch (ch) { case 'o': { owner = optarg; } break; case 'r': { repo = optarg; } break; case 'n': { char *endptr; max = strtol(optarg, &endptr, 10); if (endptr != optarg + strlen(optarg)) 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, "gcli: error: cannot parse milestone id"); if (milestone_id < 0) errx(1, "gcli: error: milestone id must not be negative"); } break; default: { usage(); return 1; } } } argc -= optind; argv += optind; check_owner_and_repo(&owner, &repo); if (milestone_id < 0) { struct gcli_milestone_list list = {0}; rc = gcli_get_milestones(g_clictx, owner, repo, max, &list); if (rc < 0) { errx(1, "gcli: error: cannot get list of milestones: %s", gcli_get_error(g_clictx)); } gcli_print_milestones(&list, max); gcli_free_milestones(&list); return 0; } return handle_milestone_actions(argc, argv, owner, repo, milestone_id); } static void ensure_milestone(char const *const owner, char const *const repo, int const milestone_id, int *const fetched_milestone, struct gcli_milestone *const milestone) { int rc; if (*fetched_milestone) return; rc = gcli_get_milestone(g_clictx, owner, repo, milestone_id, milestone); if (rc < 0) errx(1, "gcli: error: could not get milestone %d: %s", milestone_id, gcli_get_error(g_clictx)); *fetched_milestone = 1; } static int handle_milestone_actions(int argc, char *argv[], char const *const owner, char const *const repo, int const milestone_id) { struct gcli_milestone milestone = {0}; int fetched_milestone = 0; /* Check if the user missed out on supplying actions */ if (argc == 0) { fprintf(stderr, "gcli: error: no actions supplied\n"); usage(); exit(EXIT_FAILURE); } /* Iterate over all the actions */ while (argc) { /* Read in action */ char const *action = shift(&argc, &argv); /* Dispatch */ if (strcmp(action, "all") == 0) { int rc = 0; struct gcli_issue_list issues = {0}; ensure_milestone(owner, repo, milestone_id, &fetched_milestone, &milestone); gcli_print_milestone(&milestone); rc = gcli_milestone_get_issues(g_clictx, owner, repo, milestone_id, &issues); 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); gcli_issues_free(&issues); } else if (strcmp(action, "issues") == 0) { int rc = 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, "gcli: error: failed to fetch issues: %s", gcli_get_error(g_clictx)); } /* Print them as a table */ gcli_print_issues(0, &issues, -1); /* Cleanup */ gcli_issues_free(&issues); } else if (strcmp(action, "status") == 0) { /* Make sure we have the milestone data */ ensure_milestone(owner, repo, milestone_id, &fetched_milestone, &milestone); /* Print meta */ gcli_print_milestone(&milestone); } else if (strcmp(action, "delete") == 0) { /* Delete the milestone */ 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) { char *new_date = NULL; int rc = 0; /* grab the the date that the user provided */ if (!argc) 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, "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, "gcli: error: unknown action %s\n", action); usage(); return EXIT_FAILURE; } /* Print a blank line if we are not at the end */ if (argc) putchar('\n'); } /* Cleanup the milestone if we ever fetched it */ if (fetched_milestone) gcli_free_milestone(&milestone); return EXIT_SUCCESS; } gcli-2.3.0/src/cmd/pipelines.c000066400000000000000000000311531460062271200161230ustar00rootroot00000000000000/* * Copyright 2022 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 #ifdef HAVE_GETOPT_H #include #endif #include static void usage(void) { fprintf(stderr, "usage: gcli pipelines [-o owner -r repo] [-n number]\n"); fprintf(stderr, " gcli pipelines [-o owner -r repo] -p pipeline [-n number]\n"); fprintf(stderr, " gcli pipelines [-o owner -r repo] -j job [-n number] actions...\n"); fprintf(stderr, "OPTIONS:\n"); fprintf(stderr, " -o owner The repository owner\n"); fprintf(stderr, " -r repo The repository name\n"); fprintf(stderr, " -p pipeline Fetch jobs of the given pipeline\n"); fprintf(stderr, " -j job Run actions for the given job\n"); fprintf(stderr, " -n number Number of issues to fetch (-1 = everything)\n"); fprintf(stderr, "ACTIONS:\n"); fprintf(stderr, " status Display status information\n"); fprintf(stderr, " artifacts [-o filename] Download a zip archive of the artifacts of the given job\n"); fprintf(stderr, " (default output filename: artifacts.zip)\n"); fprintf(stderr, " log Display job log\n"); fprintf(stderr, " cancel Cancel the job\n"); fprintf(stderr, " retry Retry the given job\n"); fprintf(stderr, "\n"); version(); copyright(); } int gitlab_mr_pipelines(char const *owner, char const *repo, int const mr_id) { struct gitlab_pipeline_list list = {0}; int rc = 0; rc = gitlab_get_mr_pipelines(g_clictx, owner, repo, mr_id, &list); if (rc == 0) gitlab_print_pipelines(&list); gitlab_pipelines_free(&list); return rc; } void gitlab_print_pipelines(struct gitlab_pipeline_list const *const list) { gcli_tbl table; 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 }, { .name = "UPDATED", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, { .name = "REF", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, }; if (!list->pipelines_size) { printf("No pipelines\n"); return; } table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!table) errx(1, "gcli: error: could not init table"); for (size_t i = 0; i < list->pipelines_size; ++i) { gcli_tbl_add_row(table, list->pipelines[i].id, list->pipelines[i].status, list->pipelines[i].created_at, list->pipelines[i].updated_at, list->pipelines[i].ref); } gcli_tbl_end(table); } int gitlab_pipelines(char const *owner, char const *repo, int const count) { struct gitlab_pipeline_list pipelines = {0}; int rc = 0; rc = gitlab_get_pipelines(g_clictx, owner, repo, count, &pipelines); if (rc < 0) return rc; gitlab_print_pipelines(&pipelines); gitlab_pipelines_free(&pipelines); return rc; } void gitlab_print_jobs(struct gitlab_job_list const *const list) { gcli_tbl table; 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 }, { .name = "STARTED", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, { .name = "FINISHED", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, { .name = "RUNNERDESC", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, { .name = "REF", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, }; if (!list->jobs_size) { printf("No jobs\n"); return; } table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!table) errx(1, "gcli: error: could not initialize table"); for (size_t i = 0; i < list->jobs_size; ++i) { gcli_tbl_add_row(table, list->jobs[i].id, list->jobs[i].name, list->jobs[i].status, list->jobs[i].started_at, list->jobs[i].finished_at, list->jobs[i].runner_description, list->jobs[i].ref); } gcli_tbl_end(table); } int gitlab_pipeline_jobs(char const *owner, char const *repo, long const id, int const count) { struct gitlab_job_list jobs = {0}; int rc = 0; rc = gitlab_get_pipeline_jobs(g_clictx, owner, repo, id, count, &jobs); if (rc < 0) return rc; gitlab_print_jobs(&jobs); gitlab_free_jobs(&jobs); return rc; } void gitlab_print_job_status(struct gitlab_job const *const job) { gcli_dict printer; printer = gcli_dict_begin(); gcli_dict_add(printer, "ID", 0, 0, "%"PRIid, job->id); gcli_dict_add_string(printer, "STATUS", GCLI_TBLCOL_STATECOLOURED, 0, job->status); gcli_dict_add_string(printer, "STAGE", 0, 0, job->stage); gcli_dict_add_string(printer, "NAME", GCLI_TBLCOL_BOLD, 0, job->name); gcli_dict_add_string(printer, "REF", GCLI_TBLCOL_COLOUREXPL, GCLI_COLOR_YELLOW, job->ref); gcli_dict_add_string(printer, "CREATED", 0, 0, job->created_at); gcli_dict_add_string(printer, "STARTED", 0, 0, job->started_at); gcli_dict_add_string(printer, "FINISHED", 0, 0, job->finished_at); gcli_dict_add(printer, "DURATION", 0, 0, "%-.2lfs", job->duration); gcli_dict_add(printer, "COVERAGE", 0, 0, "%.1lf%%", job->coverage); gcli_dict_add_string(printer, "RUNNER NAME", 0, 0, job->runner_name); gcli_dict_add_string(printer, "RUNNER DESCR", 0, 0, job->runner_description); gcli_dict_end(printer); } int gitlab_job_status(char const *owner, char const *repo, long const jid) { struct gitlab_job job = {0}; int rc = 0; rc = gitlab_get_job(g_clictx, owner, repo, jid, &job); if (rc < 0) return rc; gitlab_print_job_status(&job); gitlab_free_job(&job); return rc; } /* Wrappers that pass in the context to the library functions */ static int gitlab_job_log_cb(char const *owner, char const *repo, long const jid) { return gitlab_job_get_log(g_clictx, owner, repo, jid, stdout); } static int gitlab_job_cancel_cb(char const *owner, char const *repo, long const jid) { return gitlab_job_cancel(g_clictx, owner, repo, jid); } static int gitlab_job_retry_cb(char const *owner, char const *repo, long const jid) { return gitlab_job_retry(g_clictx, owner, repo, jid); } int subcommand_pipelines(int argc, char *argv[]) { int ch = 0; char const *owner = NULL, *repo = NULL; int count = 30; long pid = -1; /* pipeline id */ long jid = -1; /* job id. these are mutually exclusive. */ /* Parse options */ const struct option options[] = { {.name = "repo", .has_arg = required_argument, .flag = NULL, .val = 'r'}, {.name = "owner", .has_arg = required_argument, .flag = NULL, .val = 'o'}, {.name = "count", .has_arg = required_argument, .flag = NULL, .val = 'c'}, {.name = "pipeline", .has_arg = required_argument, .flag = NULL, .val = 'p'}, {.name = "job", .has_arg = required_argument, .flag = NULL, .val = 'j'}, {0} }; while ((ch = getopt_long(argc, argv, "+n:o:r:p:j:", options, NULL)) != -1) { switch (ch) { case 'o': owner = optarg; break; case 'r': repo = optarg; break; case 'n': { char *endptr = NULL; count = strtol(optarg, &endptr, 10); if (endptr != (optarg + strlen(optarg))) 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, "gcli: error: cannot parse argument to -p"); if (pid < 0) { 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, "gcli: error: cannot parse argument to -j"); if (jid < 0) { errx(1, "gcli: error: job id must be a positive number"); } } break; case '?': default: usage(); return EXIT_FAILURE; } } argc -= optind; argv += optind; if (pid > 0 && jid > 0) { fprintf(stderr, "gcli: error: -p and -j are mutually exclusive\n"); usage(); return EXIT_FAILURE; } check_owner_and_repo(&owner, &repo); /* 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, "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 * given pipeline */ if (pid >= 0) { /* Make sure we are interpreting things correctly */ if (argc != 0) { fprintf(stderr, "gcli: error: stray arguments\n"); usage(); return EXIT_FAILURE; } 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; } /* if the user didn't specify the -j option to list jobs, list the * pipelines instead */ if (jid < 0) { /* Make sure we are interpreting things correctly */ if (argc != 0) { fprintf(stderr, "gcli: error: stray arguments\n"); usage(); return EXIT_FAILURE; } if (gitlab_pipelines(owner, repo, count) < 0) { errx(1, "gcli: error: failed to get pipelines: %s", gcli_get_error(g_clictx)); } return EXIT_SUCCESS; } /* At this point jid contains a (hopefully) valid job id */ /* Definition of the action list */ struct { char const *name; /* Name on the cli */ int (*fn)(char const *, char const *, long); /* Function to be invoked for this action */ } job_actions[] = { { .name = "log", .fn = gitlab_job_log_cb }, { .name = "status", .fn = gitlab_job_status }, { .name = "cancel", .fn = gitlab_job_cancel_cb }, { .name = "retry", .fn = gitlab_job_retry_cb }, }; /* Check if the user missed out on supplying actions */ if (argc == 0) { fprintf(stderr, "gcli: error: no actions supplied\n"); usage(); exit(EXIT_FAILURE); } next_action: while (argc) { char const *action = shift(&argc, &argv); /* Handle the artifacts action separately because it allows a * -o flag. No other action supports flags. */ if (strcmp(action, "artifacts") == 0) { char const *outfile = "artifacts.zip"; if (argc && strcmp(argv[0], "-o") == 0) { if (argc < 2) 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, "gcli: error: failed to download file: %s", gcli_get_error(g_clictx)); } goto next_action; } /* Find the action and invoke it */ 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, "gcli: error: failed to perform action '%s'", action); goto next_action; } } fprintf(stderr, "gcli: error: unknown action '%s'\n", action); usage(); return EXIT_FAILURE; } return EXIT_SUCCESS; } gcli-2.3.0/src/cmd/pulls.c000066400000000000000000000765321460062271200153040ustar00rootroot00000000000000/* * Copyright 2022 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 #include #include #include #include #include #include #include #include #ifdef HAVE_GETOPT_H #include #endif #include #include static void usage(void) { fprintf(stderr, "usage: gcli pulls create [-o owner -r repo] [-f from]\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] [search-terms...]\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 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"); fprintf(stderr, " -d Mark newly created PR as a draft\n"); fprintf(stderr, " -f owner:branch Specify the owner and branch of the fork that is the head of a PR.\n"); fprintf(stderr, " -l label Add the given label when creating the PR\n"); fprintf(stderr, " -n number Number of PRs to fetch (-1 = everything)\n"); fprintf(stderr, " -i id ID of PR to perform actions on\n"); fprintf(stderr, " -s Print (sort) in reverse order\n"); fprintf(stderr, " -t branch Specify target branch of the PR\n"); fprintf(stderr, " -y Do not ask for confirmation.\n"); fprintf(stderr, "ACTIONS:\n"); fprintf(stderr, " all Display status, commits, op and checks of the PR\n"); fprintf(stderr, " op Display original post\n"); fprintf(stderr, " status Display PR metadata\n"); fprintf(stderr, " comments Display comments\n"); fprintf(stderr, " notes Alias for notes\n"); fprintf(stderr, " commits Display commits of the PR\n"); fprintf(stderr, " ci Display CI/Pipeline status information about the PR\n"); fprintf(stderr, " merge [-s] [-D] Merge the PR (-s = squash commits, -D = inhibit deleting source branch)\n"); fprintf(stderr, " milestone Assign this PR to a milestone\n"); fprintf(stderr, " milestone -d Clear associated milestones from the PR\n"); fprintf(stderr, " close Close the PR\n"); fprintf(stderr, " reopen Reopen a closed PR\n"); fprintf(stderr, " labels ... Add or remove labels:\n"); fprintf(stderr, " add \n"); fprintf(stderr, " remove \n"); fprintf(stderr, " diff Display changes as diff\n"); fprintf(stderr, " patch Display changes as patch series\n"); fprintf(stderr, " title Change the title of the pull request\n"); fprintf(stderr, " request-review Add as a reviewer of the PR\n"); fprintf(stderr, "\n"); version(); copyright(); } void gcli_print_pulls(enum gcli_output_flags const flags, struct gcli_pull_list const *const list, int const max) { int n; gcli_tbl table; 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 }, { .name = "CREATOR", .type = GCLI_TBLCOLTYPE_STRING, .flags = GCLI_TBLCOL_BOLD }, { .name = "NOTES", .type = GCLI_TBLCOLTYPE_INT, .flags = GCLI_TBLCOL_JUSTIFYR }, { .name = "TITLE", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, }; if (list->pulls_size == 0) { puts("No Pull Requests"); return; } /* Determine number of items to print */ if (max < 0 || (size_t)(max) > list->pulls_size) n = list->pulls_size; else n = max; /* Fill the table */ table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!table) errx(1, "gcli: error: cannot init table"); if (flags & OUTPUT_SORTED) { for (int i = 0; i < n; ++i) { gcli_tbl_add_row(table, list->pulls[n-i-1].number, list->pulls[n-i-1].state, list->pulls[n-i-1].merged, list->pulls[n-i-1].author, list->pulls[n-i-1].comments, list->pulls[n-i-1].title); } } else { for (int i = 0; i < n; ++i) { gcli_tbl_add_row(table, list->pulls[i].number, list->pulls[i].state, list->pulls[i].merged, list->pulls[i].author, list->pulls[i].comments, list->pulls[i].title); } } gcli_tbl_end(table); } int gcli_pull_print_diff(FILE *stream, char const *owner, char const *reponame, int pr_number) { return gcli_pull_get_diff(g_clictx, stream, owner, reponame, pr_number); } int gcli_pull_print_patch(FILE *stream, char const *owner, char const *reponame, int pr_number) { return gcli_pull_get_patch(g_clictx, stream, owner, reponame, pr_number); } void gcli_pull_print(struct gcli_pull const *const it) { gcli_dict dict; struct gcli_forge_descriptor const *const forge = gcli_forge(g_clictx); int const quirks = forge->pull_summary_quirks; dict = gcli_dict_begin(); gcli_dict_add(dict, "NUMBER", 0, 0, "%"PRIid, it->number); gcli_dict_add_string(dict, "TITLE", 0, 0, it->title); gcli_dict_add_string(dict, "HEAD", 0, 0, it->head_label); gcli_dict_add_string(dict, "BASE", 0, 0, it->base_label); gcli_dict_add_string(dict, "CREATED", 0, 0, it->created_at); gcli_dict_add_string(dict, "AUTHOR", GCLI_TBLCOL_BOLD, 0, it->author); gcli_dict_add_string(dict, "STATE", GCLI_TBLCOL_STATECOLOURED, 0, it->state); gcli_dict_add(dict, "COMMENTS", 0, 0, "%d", it->comments); if (it->milestone) gcli_dict_add_string(dict, "MILESTONE", 0, 0, it->milestone); if ((quirks & GCLI_PRS_QUIRK_ADDDEL) == 0) /* FIXME: move printing colours into the dictionary printer? */ gcli_dict_add(dict, "ADD:DEL", 0, 0, "%s%d%s:%s%d%s", gcli_setcolour(GCLI_COLOR_GREEN), it->additions, gcli_resetcolour(), gcli_setcolour(GCLI_COLOR_RED), it->deletions, gcli_resetcolour()); if ((quirks & GCLI_PRS_QUIRK_COMMITS) == 0) gcli_dict_add(dict, "COMMITS", 0, 0, "%d", it->commits); 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)); gcli_dict_add_string(dict, "MERGEABLE", 0, 0, sn_bool_yesno(it->mergeable)); if ((quirks & GCLI_PRS_QUIRK_DRAFT) == 0) gcli_dict_add_string(dict, "DRAFT", 0, 0, sn_bool_yesno(it->draft)); if ((quirks & GCLI_PRS_QUIRK_COVERAGE) == 0 && it->coverage) gcli_dict_add_string(dict, "COVERAGE", 0, 0, it->coverage); if (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"); } if (it->reviewers_size) { gcli_dict_add_string_list(dict, "REVIEWERS", /* cast needed because of nested const */ (char const *const *)it->reviewers, it->reviewers_size); } else { gcli_dict_add_string(dict, "REVIEWERS", 0, 0, "none"); } gcli_dict_end(dict); } void 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(struct gcli_pull_checks_list const *const list) { switch (list->forge_type) { case GCLI_FORGE_GITHUB: github_print_checks((struct github_check_list const *)(list)); break; case GCLI_FORGE_GITLAB: gitlab_print_pipelines((struct gitlab_pipeline_list const*)(list)); break; default: assert(0 && "unreachable"); } } int gcli_pull_checks(char const *owner, char const *repo, int pr_number) { struct gcli_pull_checks_list list = {0}; gcli_forge_type t = gcli_config_get_forge_type(g_clictx); list.forge_type = t; switch (t) { case GCLI_FORGE_GITHUB: case GCLI_FORGE_GITLAB: { int rc = gcli_pull_get_checks(g_clictx, owner, repo, pr_number, &list); if (rc < 0) return rc; gcli_print_checks_list(&list); gcli_pull_checks_free(&list); return 0; } break; default: puts("No checks."); return 0; /* no CI support / not implemented */ } } /** * Get a copy of the first line of the passed string. */ static char * cut_newline(char const *const _it) { char *it = strdup(_it); char *foo = it; while (*foo) { if (*foo == '\n') { *foo = 0; break; } foo += 1; } return it; } void gcli_print_commits(struct gcli_commit_list const *const list) { gcli_tbl table; 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 }, { .name = "DATE", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, { .name = "MESSAGE", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, }; if (list->commits_size == 0) { puts("No commits"); return; } table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!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); gcli_tbl_add_row(table, GCLI_COLOR_YELLOW, list->commits[i].sha, list->commits[i].author, list->commits[i].email, list->commits[i].date, message); free(message); /* message is copied by the function above */ } gcli_tbl_end(table); } int gcli_pull_commits(char const *owner, char const *repo, int const pr_number) { struct gcli_commit_list commits = {0}; int rc = 0; rc = gcli_pull_get_commits(g_clictx, owner, repo, pr_number, &commits); if (rc < 0) return rc; gcli_print_commits(&commits); gcli_commits_free(&commits); return rc; } static void pull_init_user_file(struct gcli_ctx *ctx, FILE *stream, void *_opts) { struct gcli_submit_pull_options *opts = _opts; (void) ctx; fprintf( stream, "! PR TITLE : %s\n" "! Enter PR comments above.\n" "! All lines starting with '!' will be discarded.\n", opts->title); } 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(struct gcli_submit_pull_options *const opts, int always_yes) { opts->body = gcli_pull_get_user_message(opts); fprintf(stdout, "The following PR will be created:\n" "\n" "TITLE : %s\n" "BASE : %s\n" "HEAD : %s\n" "IN : %s/%s\n" "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, "gcli: PR aborted."); return gcli_pull_submit(g_clictx, opts); } static char const * pr_try_derive_head(void) { char const *account; sn_sv branch = {0}; if ((account = gcli_config_get_account_name(g_clictx)) == NULL) { errx(1, "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) { errx(1, "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_asprintf("%s:"SV_FMT, account, SV_ARGS(branch)); } static char * derive_head(void) { char const *account; sn_sv branch = {0}; if ((account = gcli_config_get_account_name(g_clictx)) == NULL) return NULL; branch = gcli_gitconfig_get_current_branch(); if (branch.length == 0) return NULL; return sn_asprintf("%s:"SV_FMT, account, SV_ARGS(branch)); } /** Interactive version of the create subcommand */ static int subcommand_pull_create_interactive(struct gcli_submit_pull_options *const opts) { char const *deflt_owner = NULL, *deflt_repo = NULL; int rc = 0; gcli_config_get_repo(g_clictx, &deflt_owner, &deflt_repo); /* PR Source */ if (!opts->from) { char *tmp = NULL; tmp = derive_head(); opts->from = gcli_cmd_prompt("From (owner:branch)", tmp); free(tmp); tmp = NULL; } /* PR Target */ if (!opts->owner) opts->owner = gcli_cmd_prompt("Owner", deflt_owner); if (!opts->repo) opts->repo = gcli_cmd_prompt("Repository", deflt_repo); if (!opts->to) { char *tmp = NULL; sn_sv base; base = gcli_config_get_base(g_clictx); if (base.length != 0) tmp = sn_sv_to_cstr(base); opts->to = gcli_cmd_prompt("To Branch", tmp); free(tmp); tmp = NULL; } /* Meta */ opts->title = gcli_cmd_prompt("Title", NULL); opts->automerge = sn_yesno("Enable automerge?"); /* create_pull is going to pop up the editor */ rc = create_pull(opts, false); if (rc < 0) { fprintf(stderr, "gcli: error: %s\n", gcli_get_error(g_clictx)); return EXIT_FAILURE; } return EXIT_SUCCESS; } static int subcommand_pull_create(int argc, char *argv[]) { /* we'll use getopt_long here to parse the arguments */ int ch; struct gcli_submit_pull_options opts = {0}; int always_yes = 0; const struct option options[] = { { .name = "from", .has_arg = required_argument, .flag = NULL, .val = 'f' }, { .name = "to", .has_arg = required_argument, .flag = NULL, .val = 't' }, { .name = "owner", .has_arg = required_argument, .flag = NULL, .val = 'o' }, { .name = "repo", .has_arg = required_argument, .flag = NULL, .val = 'r' }, { .name = "draft", .has_arg = no_argument, .flag = &opts.draft, .val = 1 }, { .name = "label", .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, "ayf:t:do:r:l:", options, NULL)) != -1) { switch (ch) { case 'f': opts.from = optarg; break; case 't': opts.to = optarg; break; case 'd': opts.draft = 1; break; case 'o': opts.owner = optarg; break; case 'r': opts.repo = optarg; break; case 'l': /* add a label */ opts.labels = realloc( opts.labels, sizeof(*opts.labels) * (opts.labels_size + 1)); opts.labels[opts.labels_size++] = optarg; break; case 'y': always_yes = 1; break; case 'a': opts.automerge = true; break; default: usage(); return EXIT_FAILURE; } } argc -= optind; argv += optind; if (argc == 0) return subcommand_pull_create_interactive(&opts); if (!opts.from) opts.from = pr_try_derive_head(); if (!opts.to) { sn_sv base = gcli_config_get_base(g_clictx); if (base.length == 0) errx(1, "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, "gcli: error: Missing title to PR\n"); usage(); return EXIT_FAILURE; } opts.title = argv[0]; if (create_pull(&opts, always_yes) < 0) errx(1, "gcli: error: failed to submit pull request: %s", gcli_get_error(g_clictx)); free(opts.labels); return EXIT_SUCCESS; } /* Forward declaration */ static int handle_pull_actions(int argc, char *argv[], char const *owner, char const *repo, int pr); int subcommand_pulls(int argc, char *argv[]) { char *endptr = NULL; char const *owner = NULL; char const *repo = NULL; struct gcli_pull_list pulls = {0}; int ch = 0; int pr = -1; int n = 30; /* how many prs to fetch at least */ struct gcli_pull_fetch_details details = {0}; enum gcli_output_flags flags = 0; /* detect whether we wanna create a PR */ if (argc > 1 && (strcmp(argv[1], "create") == 0)) { shift(&argc, &argv); return subcommand_pull_create(argc, argv); } struct option const options[] = { { .name = "all", .has_arg = no_argument, .flag = NULL, .val = 'a' }, { .name = "author", .has_arg = no_argument, .flag = NULL, .val = 'A' }, { .name = "label", .has_arg = no_argument, .flag = NULL, .val = 'L' }, { .name = "milestone", .has_arg = no_argument, .flag = NULL, .val = 'M' }, { .name = "sorted", .has_arg = no_argument, .flag = NULL, .val = 's' }, { .name = "count", .has_arg = required_argument, .flag = NULL, .val = 'n' }, { .name = "repo", .has_arg = required_argument, .flag = NULL, .val = 'r' }, { .name = "owner", .has_arg = required_argument, .flag = NULL, .val = 'o' }, { .name = "id", .has_arg = required_argument, .flag = NULL, .val = 'i' }, {0}, }; /* Parse commandline options */ while ((ch = getopt_long(argc, argv, "+n:o:r:i:asA:L:M:", options, NULL)) != -1) { switch (ch) { case 'o': owner = optarg; break; case 'r': repo = optarg; break; case 'i': { pr = strtoul(optarg, &endptr, 10); if (endptr != (optarg + strlen(optarg))) err(1, "gcli: error: cannot parse pr number »%s«", optarg); if (pr <= 0) 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, "gcli: error: cannot parse pr count »%s«", optarg); if (n < -1) errx(1, "gcli: error: pr count is out of range"); if (n == 0) errx(1, "gcli: error: pr count must not be zero"); } break; case 'a': { details.all = true; } break; case 'A': { details.author = optarg; } break; case 'L': { details.label = optarg; } break; case 'M': { details.milestone = optarg; } break; case 's': { flags |= OUTPUT_SORTED; } break; case '?': default: usage(); return EXIT_FAILURE; } } argc -= optind; argv += optind; check_owner_and_repo(&owner, &repo); /* In case no explicit PR number was specified, list all * open PRs and exit */ if (pr < 0) { char *search_term = NULL; /* Trailing arguments indicate a search term */ if (argc) search_term = sn_join_with((char const *const *)argv, argc, " "); details.search_term = search_term; if (gcli_search_pulls(g_clictx, owner, repo, &details, n, &pulls) < 0) errx(1, "gcli: error: could not fetch pull requests: %s", gcli_get_error(g_clictx)); gcli_print_pulls(flags, &pulls, n); gcli_pulls_free(&pulls); free(search_term); details.search_term = search_term = NULL; return EXIT_SUCCESS; } /* If a PR number was given, require -a to be unset */ if (details.all || details.author) { fprintf(stderr, "gcli: error: -a and -A cannot be combined with operations on a PR\n"); usage(); return EXIT_FAILURE; } /* Hand off to actions handling */ return handle_pull_actions(argc, argv, owner, repo, pr); } struct action_ctx { int argc; char **argv; char const *owner, *repo; int pr; /* For ease of handling and not making redundant calls to the API * we'll fetch the summary only if a command requires it. Then * we'll proceed to actually handling it. */ int fetched_pull; struct gcli_pull pull; }; /** Helper routine for fetching a PR if required */ static void action_ctx_ensure_pull(struct action_ctx *ctx) { if (ctx->fetched_pull) return; if (gcli_get_pull(g_clictx, ctx->owner, ctx->repo, ctx->pr, &ctx->pull) < 0) errx(1, "gcli: error: failed to fetch pull request data: %s", gcli_get_error(g_clictx)); ctx->fetched_pull = 1; } static void action_all(struct action_ctx *ctx) { /* First make sure we have the data ready */ action_ctx_ensure_pull(ctx); /* Print meta */ gcli_pull_print(&ctx->pull); /* OP */ puts("\nORIGINAL POST"); gcli_pull_print_op(&ctx->pull); /* Commits */ puts("\nCOMMITS"); if (gcli_pull_commits(ctx->owner, ctx->repo, ctx->pr) < 0) 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, "gcli: error: failed to fetch pull request checks: %s", gcli_get_error(g_clictx)); } static void action_op(struct action_ctx *const ctx) { /* Ensure we have fetched the data */ action_ctx_ensure_pull(ctx); /* Print it */ gcli_pull_print_op(&ctx->pull); } static void action_status(struct action_ctx *const ctx) { /* Ensure we have the data */ action_ctx_ensure_pull(ctx); /* Print meta information */ gcli_pull_print(&ctx->pull); } static void action_commits(struct action_ctx *const ctx) { /* Does not require the summary */ gcli_pull_commits(ctx->owner, ctx->repo, ctx->pr); } static void action_diff(struct action_ctx *const ctx) { 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, "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, "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, "gcli: error: failed to fetch pull request checks: %s", gcli_get_error(g_clictx)); } } static void action_merge(struct action_ctx *const ctx) { enum gcli_merge_flags flags = GCLI_PULL_MERGE_DELETEHEAD; /* Default behaviour */ if (gcli_config_pr_inhibit_delete_source_branch(g_clictx)) flags = 0; if (ctx->argc > 1) { /* Check whether the user intends a squash-merge * and/or wants to delete the source branch of the * PR */ char const *word = ctx->argv[1]; if (strcmp(word, "-s") == 0 || strcmp(word, "--squash") == 0) { ctx->argc -= 1; ctx->argv += 1; flags |= GCLI_PULL_MERGE_SQUASH; } else if (strcmp(word, "-D") == 0 || strcmp(word, "--inhibit-delete") == 0) { ctx->argc -= 1; ctx->argv += 1; flags &= ~GCLI_PULL_MERGE_DELETEHEAD; } } 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, "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, "gcli: error: failed to reopen pull request: %s", gcli_get_error(g_clictx)); } } static void action_labels(struct action_ctx *const ctx) { const char **add_labels = NULL; size_t add_labels_size = 0; const char **remove_labels = NULL; size_t remove_labels_size = 0; int rc = 0; if (ctx->argc == 0) { fprintf(stderr, "gcli: error: expected label action\n"); usage(); exit(EXIT_FAILURE); } /* HACK: parse_labels_options uses shift to walk the argv. We need to put * it right at the first argument (either an add or a remove) where * it should start parsing. * * Thus, we "prematurely" advance argv and after we finished parsing * we reduce back to make the following getopt calls not trip over * a missing argv[0]. */ ctx->argc -= 1; ctx->argv += 1; parse_labels_options(&ctx->argc, &ctx->argv, &add_labels, &add_labels_size, &remove_labels, &remove_labels_size); ctx->argc += 1; ctx->argv -= 1; /* actually go about deleting and adding the labels */ 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, "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, "gcli: error: failed to remove labels: %s", gcli_get_error(g_clictx)); } } free(add_labels); free(remove_labels); } static void action_milestone(struct action_ctx *const ctx) { char const *arg; if (ctx->argc < 2) { fprintf(stderr, "error: missing arguments to milestone action\n"); usage(); exit(EXIT_FAILURE); } arg = ctx->argv[1]; ctx->argc -= 1; ctx->argv += 1; if (strcmp(arg, "-d") == 0) { 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; char *endptr; int rc = 0; milestone_id = strtoul(arg, &endptr, 10); if (endptr != arg + strlen(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, "gcli: error: failed to set milestone: %s", gcli_get_error(g_clictx)); } } } static void action_request_review(struct action_ctx *const ctx) { int rc; if (ctx->argc < 2) { 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, "gcli: error: failed to request review: %s", gcli_get_error(g_clictx)); } ctx->argc -= 1; ctx->argv += 1; } static void action_title(struct action_ctx *const ctx) { int rc = 0; if (ctx->argc < 2) { fprintf(stderr, "gcli: error: missing title\n"); usage(); exit(EXIT_FAILURE); } rc = gcli_pull_set_title(g_clictx, ctx->owner, ctx->repo, ctx->pr, ctx->argv[1]); if (rc < 0) { errx(1, "gcli: error: failed to update review title: %s", gcli_get_error(g_clictx)); } ctx->argc -= 1; ctx->argv += 1; } static struct action { char const *name; void (*fn)(struct action_ctx *ctx); } const actions[] = { { .name = "all", .fn = action_all }, { .name = "op", .fn = action_op }, { .name = "status", .fn = action_status }, { .name = "commits", .fn = action_commits }, { .name = "diff", .fn = action_diff }, { .name = "patch", .fn = action_patch }, { .name = "notes", .fn = action_comments }, { .name = "comments", .fn = action_comments }, { .name = "ci", .fn = action_ci }, { .name = "merge", .fn = action_merge }, { .name = "close", .fn = action_close }, { .name = "reopen", .fn = action_reopen }, { .name = "labels", .fn = action_labels }, { .name = "milestone", .fn = action_milestone }, { .name = "request-review", .fn = action_request_review }, { .name = "title", .fn = action_title }, }; 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; } /** Handling routine for Pull Request related actions specified on the * command line. Make sure that the usage at the top is consistent * with the actions implemented here. */ static int handle_pull_actions(int argc, char *argv[], char const *owner, char const *repo, int pr) { struct action_ctx ctx = { .argc = argc, .argv = argv, .owner = owner, .repo = repo, .pr = pr, }; /* Check if the user missed out on supplying actions */ if (argc == 0) { fprintf(stderr, "gcli: error: no actions supplied\n"); usage(); exit(EXIT_FAILURE); } /* Iterate over the argument list until the end */ while (ctx.argc > 0) { /* Grab the next action from the argument list */ const char *action = ctx.argv[0]; struct action const *handler = find_action(action); if (handler) { handler->fn(&ctx); } else { /* At this point we found an unknown action / stray * options on the command line. Error out in this case. */ fprintf(stderr, "gcli: error: unknown action %s\n", action); usage(); return EXIT_FAILURE; } ctx.argc -= 1; ctx.argv += 1; if (ctx.argc) putchar('\n'); } /* Next action */ /* Free the pull request data only when we actually fetched it */ if (ctx.fetched_pull) gcli_pull_free(&ctx.pull); return EXIT_SUCCESS; } gcli-2.3.0/src/cmd/releases.c000066400000000000000000000314441460062271200157410ustar00rootroot00000000000000/* * Copyright 2022 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 #ifdef HAVE_GETOPT_H #include #endif #include static void usage(void) { fprintf(stderr, "usage: gcli releases create [-o owner -r repo] [-n name] " "[-y] [-d] [-p] [-a asset]\n"); fprintf(stderr, " [-c commitish] [-t tag]\n"); fprintf(stderr, " gcli releases delete [-o owner -r repo] [-y] id\n"); fprintf(stderr, " gcli releases [-o owner -r repo] [-n number] [-s] [-l]\n"); fprintf(stderr, "OPTIONS:\n"); fprintf(stderr, " -o owner The repository owner\n"); fprintf(stderr, " -r repo The repository name\n"); fprintf(stderr, " -a asset Path to file to upload as release asset\n"); fprintf(stderr, " -c committish A ref/commit/branch that the release is created from\n"); fprintf(stderr, " -d Mark as a release draft\n"); fprintf(stderr, " -l Print a long list instead of a short table\n"); fprintf(stderr, " -n name Name of the created release\n"); fprintf(stderr, " -n number Number of releases to fetch (-1 = everything)\n"); fprintf(stderr, " -p Mark as a prerelease\n"); fprintf(stderr, " -t tag Name for new tag\n"); fprintf(stderr, " -y Do not ask for confirmation\n"); fprintf(stderr, "\n"); version(); copyright(); } static void gcli_print_release(enum gcli_output_flags const flags, struct gcli_release const *const it) { gcli_dict dict; (void) flags; dict = gcli_dict_begin(); 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, ""); /* asset urls */ for (size_t i = 0; i < it->assets_size; ++i) { gcli_dict_add(dict, "", 0, 0, "• %s", it->assets[i].name); gcli_dict_add(dict, "", 0, 0, " %s", it->assets[i].url); } gcli_dict_end(dict); /* body */ if (it->body) { putchar('\n'); pretty_print(it->body, 13, 80, stdout); } putchar('\n'); } static void gcli_releases_print_long(enum gcli_output_flags const flags, struct gcli_release_list const *const list, int const max) { int n; /* Determine how many items to print */ if (max < 0 || (size_t)(max) > list->releases_size) n = list->releases_size; else n = max; if (flags & OUTPUT_SORTED) { for (int i = 0; i < n; ++i) gcli_print_release(flags, &list->releases[n-i-1]); } else { for (int i = 0; i < n; ++i) gcli_print_release(flags, &list->releases[i]); } } static void gcli_releases_print_short(enum gcli_output_flags const flags, struct gcli_release_list const *const list, int const max) { size_t n; gcli_tbl table; 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) n = list->releases_size; else n = max; table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!table) errx(1, "gcli: error: could not init table"); if (flags & OUTPUT_SORTED) { for (size_t i = 0; i < n; ++i) { gcli_tbl_add_row(table, list->releases[n-i-1].id, list->releases[n-i-1].date, list->releases[n-i-1].draft, list->releases[n-i-1].prerelease, list->releases[n-i-1].name); } } else { for (size_t i = 0; i < n; ++i) { gcli_tbl_add_row(table, list->releases[i].id, list->releases[i].date, list->releases[i].draft, list->releases[i].prerelease, list->releases[i].name); } } gcli_tbl_end(table); } void gcli_releases_print(enum gcli_output_flags const flags, struct gcli_release_list const *const list, int const max) { if (list->releases_size == 0) { puts("No releases"); return; } if (flags & OUTPUT_LONG) gcli_releases_print_long(flags, list, max); else gcli_releases_print_short(flags, list, max); } static void releasemsg_init(struct gcli_ctx *ctx, FILE *f, void *_data) { struct gcli_new_release const *info = _data; (void) ctx; fprintf( f, "! Enter your release notes above, save and exit.\n" "! All lines with a leading '!' are discarded and will not\n" "! appear in the final release note.\n" "! IN : %s/%s\n" "! TAG NAME : %s\n" "! NAME : %s\n", info->owner, info->repo, info->tag, info->name); } static char * get_release_message(struct gcli_new_release const *info) { return gcli_editor_get_user_message(g_clictx, releasemsg_init, (void *)info); } static int subcommand_releases_create(int argc, char *argv[]) { struct gcli_new_release release = {0}; int ch; bool always_yes = false; struct option const options[] = { { .name = "yes", .has_arg = no_argument, .flag = NULL, .val = 'y' }, { .name = "draft", .has_arg = no_argument, .flag = NULL, .val = 'd' }, { .name = "prerelease", .has_arg = no_argument, .flag = NULL, .val = 'p' }, { .name = "name", .has_arg = required_argument, .flag = NULL, .val = 'n' }, { .name = "tag", .has_arg = required_argument, .flag = NULL, .val = 't' }, { .name = "commitish", .has_arg = required_argument, .flag = NULL, .val = 'c' }, { .name = "repo", .has_arg = required_argument, .flag = NULL, .val = 'r' }, { .name = "owner", .has_arg = required_argument, .flag = NULL, .val = 'o' }, { .name = "asset", .has_arg = required_argument, .flag = NULL, .val = 'a' }, {0}, }; while ((ch = getopt_long(argc, argv, "ydpn:t:c:r:o:a:", options, NULL)) != -1) { switch (ch) { case 'd': release.draft = true; break; case 'p': release.prerelease = true; break; case 'n': release.name = optarg; break; case 't': release.tag = optarg; break; case 'c': release.commitish = optarg; break; case 'r': release.repo = optarg; break; case 'o': release.owner = optarg; break; case 'a': { struct gcli_release_asset_upload asset = { .path = optarg, .name = optarg, .label = "unused", }; 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; } break; default: usage(); return EXIT_FAILURE; } } argc -= optind; argv += optind; check_owner_and_repo(&release.owner, &release.repo); /* make sure we have a tag for the release */ if (!release.tag) { 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, "gcli: Aborted by user"); 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; } static int subcommand_releases_delete(int argc, char *argv[]) { int ch; char const *owner = NULL, *repo = NULL; bool always_yes = false; struct option const options[] = { { .name = "repo", .has_arg = required_argument, .flag = NULL, .val = 'r' }, { .name = "owner", .has_arg = required_argument, .flag = NULL, .val = 'o' }, { .name = "yes", .has_arg = no_argument, .flag = NULL, .val = 'y' }, {0} }; while ((ch = getopt_long(argc, argv, "yo:r:", options, NULL)) != -1) { switch (ch) { case 'o': owner = optarg; break; case 'r': repo = optarg; break; case 'y': always_yes = true; break; case '?': default: usage(); return EXIT_FAILURE; } } argc -= optind; argv += optind; check_owner_and_repo(&owner, &repo); /* make sure the user supplied the release id */ if (argc != 1) { 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, "gcli: Aborted by user"); 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; } static struct { char const *name; int (*fn)(int, char **); } releases_subcommands[] = { { .name = "delete", .fn = subcommand_releases_delete }, { .name = "create", .fn = subcommand_releases_create }, }; int subcommand_releases(int argc, char *argv[]) { 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) { if (strcmp(releases_subcommands[i].name, argv[1]) == 0) return releases_subcommands[i].fn(argc - 1, argv + 1); } } /* List releases if none of the subcommands matched */ struct option const options[] = { { .name = "repo", .has_arg = required_argument, .flag = NULL, .val = 'r' }, { .name = "owner", .has_arg = required_argument, .flag = NULL, .val = 'o' }, { .name = "count", .has_arg = required_argument, .flag = NULL, .val = 'n' }, { .name = "long", .has_arg = no_argument, .flag = NULL, .val = 'l' }, { .name = "sorted", .has_arg = no_argument, .flag = NULL, .val = 's' }, {0} }; while ((ch = getopt_long(argc, argv, "sn:o:r:l", options, NULL)) != -1) { switch (ch) { case 'o': owner = optarg; break; case 'r': repo = optarg; break; case 'n': { char *endptr = NULL; count = strtol(optarg, &endptr, 10); if (endptr != (optarg + strlen(optarg))) err(1, "gcli: error: cannot parse release count"); if (count == 0) errx(1, "gcli: error: number of releases must not be zero"); } break; case 's': flags |= OUTPUT_SORTED; break; case 'l': flags |= OUTPUT_LONG; break; case '?': default: usage(); return EXIT_FAILURE; } } argc -= optind; argv += optind; /* sanity check */ if (argc > 0) { 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, "gcli: error: could not get releases: %s", gcli_get_error(g_clictx)); } gcli_releases_print(flags, &releases, count); gcli_free_releases(&releases); return EXIT_SUCCESS; } gcli-2.3.0/src/cmd/repos.c000066400000000000000000000250521460062271200152640ustar00rootroot00000000000000/* * Copyright 2022 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 #ifdef HAVE_GETOPT_H #include #endif #include static void usage(void) { fprintf(stderr, "usage: gcli repos create -r repo [-d description] [-p]\n"); fprintf(stderr, " gcli repos [-o owner -r repo] [-n number] [-s]\n"); fprintf(stderr, " gcli repos [-o owner -r repo] actions...\n"); fprintf(stderr, "OPTIONS:\n"); fprintf(stderr, " -o owner The repository owner\n"); fprintf(stderr, " -r repo The repository name\n"); fprintf(stderr, " -n number Number of repos to fetch (-1 = everything)\n"); fprintf(stderr, " -p Make the repo private\n"); fprintf(stderr, " -s Print (sort) in reverse order\n"); fprintf(stderr, "ACTIONS:\n"); fprintf(stderr, " delete [-y] Delete this repository:\n"); fprintf(stderr, " -y Do not ask for confirmation\n"); fprintf(stderr, " set-visibility Mark the reposity as public or private. Level may be one of:\n"); fprintf(stderr, " - public\n"); fprintf(stderr, " - private\n"); fprintf(stderr, "\n"); version(); copyright(); } void gcli_print_repos(enum gcli_output_flags const flags, struct gcli_repo_list const *const list, int const max) { size_t n; gcli_tbl table; 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) { puts("No repos"); return; } /* Determine number of repos to print */ if (max < 0 || (size_t)(max) > list->repos_size) n = list->repos_size; else n = max; /* init table */ table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!table) errx(1, "gcli: error: could not init table"); /* put data into table */ if (flags & OUTPUT_SORTED) { for (size_t i = 0; i < n; ++i) gcli_tbl_add_row(table, list->repos[n-i-1].is_fork, list->repos[n-i-1].visibility, list->repos[n-i-1].date, list->repos[n-i-1].full_name); } else { for (size_t i = 0; i < n; ++i) gcli_tbl_add_row(table, list->repos[i].is_fork, list->repos[i].visibility, list->repos[i].date, list->repos[i].full_name); } /* print it */ gcli_tbl_end(table); } void 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, "%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); } static int subcommand_repos_create(int argc, char *argv[]) { int ch; struct gcli_repo_create_options create_options = {0}; struct gcli_repo repo = {0}; const struct option options[] = { { .name = "repo", .has_arg = required_argument, .flag = NULL, .val = 'r' }, { .name = "private", .has_arg = no_argument, .flag = NULL, .val = 'p' }, { .name = "description", .has_arg = required_argument, .flag = NULL, .val = 'd' }, {0}, }; while ((ch = getopt_long(argc, argv, "r:d:p", options, NULL)) != -1) { switch (ch) { case 'r': create_options.name = optarg; break; case 'd': create_options.description = optarg; break; case 'p': create_options.private = true; break; case '?': default: usage(); return EXIT_FAILURE; } } argc -= optind; argv += optind; if (!create_options.name) { fprintf(stderr, "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, "gcli: error: failed to create repository: %s", gcli_get_error(g_clictx)); } gcli_repo_print(&repo); gcli_repo_free(&repo); return EXIT_SUCCESS; } static int action_delete(char const *const owner, char const *const repo, int *argc, char ***argv) { int ch; bool always_yes = false; struct option const options[] = { { .name = "yes", .has_arg = no_argument, .flag = NULL, .val = 'y' }, {0}, }; while ((ch = getopt_long(*argc, *argv, "+y", options, NULL)) != -1) { switch (ch) { case 'y': always_yes = true; break; default: usage(); return EXIT_FAILURE; } } *argc -= optind; *argv += optind; delete_repo(always_yes, owner, repo); return 0; } static gcli_repo_visibility parse_visibility(char const *str) { if (strcmp(str, "public") == 0) return GCLI_REPO_VISIBILITY_PUBLIC; else if (strcmp(str, "private") == 0) return GCLI_REPO_VISIBILITY_PRIVATE; else return -1; } /* Change the visibility level of a repository (e.g. public, private * etc) */ static int action_set_visibility(char const *const owner, char const *const repo, int *argc , char ***argv) { char const *visblty_str; gcli_repo_visibility visblty; int rc; if (*argc < 2) { fprintf(stderr, "gcli: error: missing visibility level\n"); return 1; } visblty_str = (*argv)[1]; *argv += 2; *argc -= 2; visblty = parse_visibility(visblty_str); if (visblty < 0) { 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, "gcli: error: failed to set visibility: %s\n", gcli_get_error(g_clictx)); return 1; } return 0; } static struct action { char const *const name; int (*fn)(char const *const owner, char const *const repo, int *argc, char ***argv); } const actions[] = { { .name = "delete", .fn = action_delete }, { .name = "set-visibility", .fn = action_set_visibility }, }; 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_repos(int argc, char *argv[]) { int ch, n = 30; char const *owner = NULL; char const *repo = NULL; struct gcli_repo_list repos = {0}; enum gcli_output_flags flags = 0; /* detect whether we wanna create a repo */ if (argc > 1 && (strcmp(argv[1], "create") == 0)) { shift(&argc, &argv); return subcommand_repos_create(argc, argv); } struct option const options[] = { { .name = "count", .has_arg = required_argument, .flag = NULL, .val = 'n' }, { .name = "repo", .has_arg = required_argument, .flag = NULL, .val = 'r' }, { .name = "owner", .has_arg = required_argument, .flag = NULL, .val = 'o' }, { .name = "sorted", .has_arg = no_argument, .flag = NULL, .val = 's' }, {0}, }; while ((ch = getopt_long(argc, argv, "+n:o:r:s", options, NULL)) != -1) { switch (ch) { case 'o': owner = optarg; break; case 'r': repo = optarg; break; case 's': flags |= OUTPUT_SORTED; break; case 'n': { char *endptr = NULL; n = strtol(optarg, &endptr, 10); if (endptr != (optarg + strlen(optarg))) err(1, "gcli: error: cannot parse repo count"); if (n == 0) errx(1, "gcli: error: number of repos must not be zero"); } break; case '?': default: usage(); return EXIT_FAILURE; } } argc -= optind; argv += optind; optind = 0; /* List repos of the owner */ if (argc == 0) { int rc = 0; if (repo) { fprintf(stderr, "gcli: error: no actions specified\n"); usage(); return EXIT_FAILURE; } if (!owner) owner = gcli_config_get_account_name(g_clictx); /* whenever there is no default account we would be passing NULL to * gcli_get_repos. This is bad since that causes segfaults down the * line. (https://github.com/herrhotzenplotz/gcli/issues/118) */ if (!owner) { 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, "gcli: error: failed to fetch repos: %s", gcli_get_error(g_clictx)); } gcli_print_repos(flags, &repos, n); gcli_repos_free(&repos); } else { check_owner_and_repo(&owner, &repo); while (argc) { struct action const *action = find_action(argv[0]); int rc = 0; if (!action) { fprintf(stderr, "gcli: error: unrecognised action »%s«\n", argv[0]); return EXIT_FAILURE; } rc = action->fn(owner, repo, &argc, &argv); if (rc) return rc; } } return EXIT_SUCCESS; } gcli-2.3.0/src/cmd/snippets.c000066400000000000000000000174731460062271200160110ustar00rootroot00000000000000/* * Copyright 2022 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 #ifdef HAVE_GETOPT_H #include #endif #include static void usage(void) { fprintf(stderr, "usage: gcli snippets [-n number] [-sl]\n"); fprintf(stderr, " gcli snippets delete id\n"); fprintf(stderr, " gcli snippets get id\n"); fprintf(stderr, "OPTIONS:\n"); fprintf(stderr, " -l Print a long list instead of a short table\n"); fprintf(stderr, " -n number Number of snippets to fetch\n"); fprintf(stderr, " -s Sort the output in reverse order\n"); fprintf(stderr, " -u user User for whom to list gists\n"); fprintf(stderr, "\n"); version(); copyright(); } static void gcli_print_snippet(enum gcli_output_flags const flags, struct gcli_gitlab_snippet const *const it) { gcli_dict dict; (void) flags; dict = gcli_dict_begin(); gcli_dict_add(dict, "ID", 0, 0, "%"PRIid, it->id); gcli_dict_add_string(dict, "TITLE", 0, 0, it->title); gcli_dict_add_string(dict, "AUTHOR", 0, 0, it->author); gcli_dict_add_string(dict, "FILE", 0, 0, it->filename); gcli_dict_add_string(dict, "DATE", 0, 0, it->date); gcli_dict_add_string(dict, "VSBLTY", 0, 0, it->visibility); gcli_dict_add_string(dict, "URL", 0, 0, it->raw_url); gcli_dict_end(dict); } static void gcli_print_snippets_long(enum gcli_output_flags const flags, struct gcli_gitlab_snippet_list const *const list, int const max) { int n; /* Determine number of items to print */ if (max < 0 || (size_t)(max) > list->snippets_size) n = list->snippets_size; else n = max; if (flags & OUTPUT_SORTED) { for (int i = 0; i < n; ++i) gcli_print_snippet(flags, &list->snippets[n-i-1]); } else { for (int i = 0; i < n; ++i) gcli_print_snippet(flags, &list->snippets[i]); } } static void gcli_print_snippets_short(enum gcli_output_flags const flags, struct gcli_gitlab_snippet_list const *const list, int const max) { int n; gcli_tbl table; 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 }, { .name = "AUTHOR", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, { .name = "TITLE", .type = GCLI_TBLCOLTYPE_STRING, .flags = 0 }, }; /* Determine number of items to print */ if (max < 0 || (size_t)(max) > list->snippets_size) n = list->snippets_size; else n = max; /* Fill table */ table = gcli_tbl_begin(cols, ARRAY_SIZE(cols)); if (!table) errx(1, "gcli: error: could not init table"); if (flags & OUTPUT_SORTED) { for (int i = 0; i < n; ++i) gcli_tbl_add_row(table, list->snippets[n-i-1].id, list->snippets[n-i-1].date, list->snippets[n-i-1].visibility, list->snippets[n-i-1].author, list->snippets[n-i-1].title); } else { for (int i = 0; i < n; ++i) gcli_tbl_add_row(table, list->snippets[i].id, list->snippets[i].date, list->snippets[i].visibility, list->snippets[i].author, list->snippets[i].title); } gcli_tbl_end(table); } void gcli_snippets_print(enum gcli_output_flags const flags, struct gcli_gitlab_snippet_list const *const list, int const max) { if (list->snippets_size == 0) { puts("No Snippets"); return; } if (flags & OUTPUT_LONG) gcli_print_snippets_long(flags, list, max); else gcli_print_snippets_short(flags, list, max); } static int subcommand_snippet_get(int argc, char *argv[]) { argc -= 1; argv += 1; if (!argc) { fprintf(stderr, "gcli: error: expected ID of snippet to fetch\n"); usage(); return EXIT_FAILURE; } char *snippet_id = shift(&argc, &argv); if (argc) { fprintf(stderr, "gcli: error: stray arguments\n"); usage(); return EXIT_FAILURE; } if (gcli_snippet_get(g_clictx, snippet_id, stdout) < 0) errx(1, "gcli: error: failed to fetch snippet contents: %s", gcli_get_error(g_clictx)); return EXIT_SUCCESS; } static int subcommand_snippet_delete(int argc, char *argv[]) { argc -= 1; argv += 1; if (!argc) { fprintf(stderr, "gcli: error: expected ID of snippet to delete\n"); usage(); return EXIT_FAILURE; } char *snippet_id = shift(&argc, &argv); if (argc) { fprintf(stderr, "gcli: error: trailing options\n"); usage(); return EXIT_FAILURE; } if (gcli_snippet_delete(g_clictx, snippet_id) < 0) errx(1, "gcli: error: failed to delete snippet: %s", gcli_get_error(g_clictx)); return EXIT_SUCCESS; } static struct snippet_subcommand { const char *name; int (*fn)(int argc, char *argv[]); } snippet_subcommands[] = { { .name = "get", .fn = subcommand_snippet_get }, { .name = "delete", .fn = subcommand_snippet_delete }, }; int subcommand_snippets(int argc, char *argv[]) { int ch; struct gcli_gitlab_snippet_list list = {0}; int count = 30; enum gcli_output_flags flags = 0; for (size_t i = 0; i < ARRAY_SIZE(snippet_subcommands); ++i) { if (argc > 1 && strcmp(argv[1], snippet_subcommands[i].name) == 0) { argc -= 1; argv += 1; return snippet_subcommands[i].fn(argc, argv); } } const struct option options[] = { { .name = "count", .has_arg = required_argument, .flag = NULL, .val = 'n' }, { .name = "sorted", .has_arg = no_argument, .flag = NULL, .val = 's' }, { .name = "long", .has_arg = no_argument, .flag = NULL, .val = 'l' }, {0}, }; while ((ch = getopt_long(argc, argv, "sn:l", options, NULL)) != -1) { switch (ch) { case 'n': { char *endptr = NULL; count = strtol(optarg, &endptr, 10); if (endptr != (optarg + strlen(optarg))) err(1, "gcli: error: cannot parse snippets count"); if (count == 0) errx(1, "gcli: error: snippets count must not be zero"); } break; case 's': flags |= OUTPUT_SORTED; break; case 'l': flags |= OUTPUT_LONG; break; case '?': default: usage(); return EXIT_FAILURE; } } argc -= optind; argv += optind; if (gcli_snippets_get(g_clictx, count, &list) < 0) errx(1, "gcli: error: failed to fetch snippets: %s", gcli_get_error(g_clictx)); gcli_snippets_print(flags, &list, count); gcli_snippets_free(&list); return EXIT_SUCCESS; } gcli-2.3.0/src/cmd/status.c000066400000000000000000000076361460062271200154670ustar00rootroot00000000000000/* * Copyright 2022 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 #ifdef HAVE_GETOPT_H #include #endif static void usage(void) { fprintf(stderr, "usage: gcli status -m id\n"); fprintf(stderr, " gcli status [-n number]\n"); fprintf(stderr, "OPTIONS:\n"); fprintf(stderr, " -n number Number of messages to fetch\n"); fprintf(stderr, " -m id Mark the given message as read\n"); fprintf(stderr, "\n"); version(); copyright(); } int gcli_status(int const count) { struct gcli_notification_list list = {0}; int rc = 0; rc = gcli_get_notifications(g_clictx, count, &list); if (rc < 0) return rc; gcli_print_notifications(&list); gcli_free_notifications(&list); return rc; } void 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", list->notifications[i].id, list->notifications[i].repository, list->notifications[i].type, list->notifications[i].date); if (list->notifications[i].reason) { printf(" - %s\n", list->notifications[i].reason); } else { printf("\n"); } pretty_print(list->notifications[i].title, 4, 80, stdout); putchar('\n'); } } int subcommand_status(int argc, char *argv[]) { int count = 30; int ch = 0; char *endptr = NULL; int mark = 0; const struct option options[] = { { .name = "count", .has_arg = required_argument, .flag = NULL, .val = 'n' }, { .name = "mark", .has_arg = no_argument, .flag = &mark, .val = 1 }, {0} }; while ((ch = getopt_long(argc, argv, "n:m", options, NULL)) != -1) { switch (ch) { case 'n': { count = strtol(optarg, &endptr, 10); if (endptr != optarg + strlen(optarg)) err(1, "gcli: error: cannot parse parameter to -n"); } break; case 'm': { mark = 1; } break; default: usage(); return EXIT_FAILURE; } } argc -= optind; argv += optind; if (!mark) { gcli_status(count); } else { if (count != 30) warnx("gcli: ignoring -n/--count argument"); if (argc > 1) { fprintf(stderr, "gcli: error: too many arguments for marking notifications\n"); usage(); return EXIT_FAILURE; } if (argc < 1) { 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, "gcli: error: failed to mark the notification as read: %s", gcli_get_error(g_clictx)); } return EXIT_SUCCESS; } gcli-2.3.0/src/cmd/table.c000066400000000000000000000315311460062271200152220ustar00rootroot00000000000000/* * Copyright 2022 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 /* A row */ struct gcli_tblrow; /* Internal state of a table printer. We return a handle to it in * gcli_table_init. */ struct gcli_tbl { 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 */ }; struct gcli_tblrow { struct { char *text; /* the text in the cell */ char const *colour; /* colour (ansi escape sequence) if * explicit fixed colour was given */ } *cells; }; /* Push a row into the table state */ static int table_pushrow(struct gcli_tbl *const table, struct gcli_tblrow row) { table->rows = realloc(table->rows, sizeof(*table->rows) * (table->rows_size + 1)); if (!table->rows) return -1; table->rows[table->rows_size++] = row; return 0; } /** Initialize the internal state structure of the table printer. */ gcli_tbl gcli_tbl_begin(struct gcli_tblcoldef const *const cols, size_t const cols_size) { struct gcli_tbl *tbl; /* Allocate the structure and fill in the handle */ tbl = calloc(sizeof(*tbl), 1); if (!tbl) return NULL; /* Reserve memory for the column sizes */ tbl->col_widths = calloc(sizeof(*tbl->col_widths), cols_size); if (!tbl->col_widths) { free(tbl); return NULL; } /* Store the list of columns */ tbl->cols = cols; tbl->cols_size = cols_size; /* Check the headers */ for (size_t i = 0; i < cols_size; ++i) { /* Compute the header's length and use these as initial * values */ tbl->col_widths[i] = strlen(cols[i].name); } return tbl; } static void table_freerow(struct gcli_tblrow *row, size_t const cols) { for (size_t i = 0; i < cols; ++i) free(row->cells[i].text); free(row->cells); row->cells = NULL; } static int tablerow_add_cell(struct gcli_tbl *const table, struct gcli_tblrow *const row, size_t const col, va_list *vp) { int cell_size = 0; /* Extract the explicit colour code */ if (table->cols[col].flags & GCLI_TBLCOL_COLOUREXPL) { int code = va_arg(*vp, int); /* don't free that! it's allocated and free'ed inside colour.c */ row->cells[col].colour = gcli_setcolour(code); } else if (table->cols[col].flags & GCLI_TBLCOL_256COLOUR) { uint64_t hexcode = va_arg(*vp, uint64_t); /* see comment above */ row->cells[col].colour = gcli_setcolour256(hexcode); } /* Process the content */ switch (table->cols[col].type) { case GCLI_TBLCOLTYPE_INT: { row->cells[col].text = sn_asprintf("%d", va_arg(*vp, int)); cell_size = strlen(row->cells[col].text); } break; case GCLI_TBLCOLTYPE_ID: { row->cells[col].text = sn_asprintf("%"PRIid, va_arg(*vp, uint64_t)); cell_size = strlen(row->cells[col].text); } break; case GCLI_TBLCOLTYPE_LONG: { row->cells[col].text = sn_asprintf("%ld", va_arg(*vp, long)); cell_size = strlen(row->cells[col].text); } break; case GCLI_TBLCOLTYPE_STRING: { char *it = va_arg(*vp, char *); if (!it) it = ""; /* hack */ row->cells[col].text = strdup(it); cell_size = strlen(it); } break; case GCLI_TBLCOLTYPE_DOUBLE: { row->cells[col].text = sn_asprintf("%lf", va_arg(*vp, double)); cell_size = strlen(row->cells[col].text); } break; case GCLI_TBLCOLTYPE_BOOL: { /* Do not use real _Bool type as it triggers a compiler bug in * LLVM clang 13 */ int val = va_arg(*vp, int); if (val) { row->cells[col].text = strdup("yes"); cell_size = 3; } else { row->cells[col].text = strdup("no"); cell_size = 2; } } break; default: return -1; } /* Update the column width if needed */ if (table->col_widths[col] < cell_size) table->col_widths[col] = cell_size; return 0; } int gcli_tbl_add_row(gcli_tbl _table, ...) { va_list vp; struct gcli_tblrow row = {0}; struct gcli_tbl *table = (struct gcli_tbl *)(_table); /* reserve array of cells */ row.cells = calloc(sizeof(*row.cells), table->cols_size); if (!row.cells) return -1; va_start(vp, _table); /* Step through all the columns and print the cells */ for (size_t i = 0; i < table->cols_size; ++i) { if (tablerow_add_cell(table, &row, i, &vp) < 0) { table_freerow(&row, table->cols_size); va_end(vp); return -1; } } va_end(vp); /* Push the row into the table */ if (table_pushrow(table, row) < 0) { table_freerow(&row, table->cols_size); return -1; } return 0; } static void pad(size_t const n) { for (size_t p = 0; p < n; ++p) putchar(' '); } static void dump_row(struct gcli_tbl const *const table, size_t const i) { struct gcli_tblrow const *const row = &table->rows[i]; for (size_t col = 0; col < table->cols_size; ++col) { /* Skip empty columns (as with colour indicators in no-colour * mode) */ if (table->col_widths[col] == 0) continue; /* If right justified and not last column, print padding */ if ((table->cols[col].flags & GCLI_TBLCOL_JUSTIFYR) && (col + 1) < table->cols_size) pad(table->col_widths[col] - strlen(row->cells[col].text)); /* State colour */ if (table->cols[col].flags & GCLI_TBLCOL_STATECOLOURED) printf("%s", gcli_state_colour_str(row->cells[col].text)); else if (table->cols[col].flags & (GCLI_TBLCOL_COLOUREXPL|GCLI_TBLCOL_256COLOUR)) printf("%s", row->cells[col].colour); /* Bold */ if (table->cols[col].flags & GCLI_TBLCOL_BOLD) printf("%s", gcli_setbold()); /* Print cell if it is not NULL, otherwise indicate it by * printing */ printf("%s", row->cells[col].text ? row->cells[col].text : ""); /* End colour */ if (table->cols[col].flags & (GCLI_TBLCOL_STATECOLOURED |GCLI_TBLCOL_COLOUREXPL |GCLI_TBLCOL_256COLOUR)) printf("%s", gcli_resetcolour()); /* Stop printing in bold */ if (table->cols[col].flags & GCLI_TBLCOL_BOLD) printf("%s", gcli_resetbold()); /* If not last column, print padding of 2 spaces */ if ((col + 1) < table->cols_size) { size_t padding = (table->cols[col].flags & GCLI_TBLCOL_TIGHT) ? 1 : 2; /* If left-justified, print justify-padding */ if (!(table->cols[col].flags & GCLI_TBLCOL_JUSTIFYR) && (col + 1) < table->cols_size) padding += table->col_widths[col] - strlen(row->cells[col].text); pad(padding); } } putchar('\n'); } static int gcli_tbl_dump(gcli_tbl const _table) { struct gcli_tbl const *const table = (struct gcli_tbl const *const)_table; for (size_t i = 0; i < table->cols_size; ++i) { size_t padding = 0; /* Skip empty columns e.g. in no-colour mode */ if (table->col_widths[i] == 0) continue; /* Check if we have tight column spacing */ if (table->cols[i].flags & GCLI_TBLCOL_TIGHT) padding = 1; else padding = 2; printf("%s", table->cols[i].name); if ((i + 1) < table->cols_size) pad(padding + table->col_widths[i] - strlen(table->cols[i].name)); } printf("\n"); for (size_t i = 0; i < table->rows_size; ++i) { dump_row(table, i); } return 0; } static void gcli_tbl_free(gcli_tbl _table) { struct gcli_tbl *tbl = (struct gcli_tbl *)_table; for (size_t row = 0; row < tbl->rows_size; ++row) table_freerow(&tbl->rows[row], tbl->cols_size); free(tbl->rows); free(tbl->col_widths); free(tbl); } void gcli_tbl_end(gcli_tbl tbl) { gcli_tbl_dump(tbl); gcli_tbl_free(tbl); } /* DICTIONARY *********************************************************/ struct gcli_dict { struct gcli_dict_entry { char *key; char *value; int flags; uint32_t colour_args; } *entries; size_t entries_size; size_t max_key_len; struct gcli_ctx *ctx; }; /* Create a new long list printer and return a handle to it */ gcli_dict gcli_dict_begin(void) { return calloc(sizeof(struct gcli_dict), 1); } static int gcli_dict_add_row(struct gcli_dict *list, char const *const key, int flags, int colour_args, char *value) { struct gcli_dict_entry *entry; size_t keylen; list->entries = realloc(list->entries, sizeof(*list->entries) * (list->entries_size + 1)); if (!list->entries) return -1; entry = &list->entries[list->entries_size++]; entry->key = strdup(key); entry->value = value; entry->flags = flags; entry->colour_args = colour_args; if ((keylen = strlen(key)) > list->max_key_len) list->max_key_len = keylen; return 0; } int gcli_dict_add(gcli_dict list, char const *const key, int flags, uint32_t colour_args, char const *const fmt, ...) { 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); result = calloc(1, actual + 1); if (!result) err(1, "calloc"); va_start(vp, fmt); vsnprintf(result, actual + 1, fmt, vp); va_end(vp); return gcli_dict_add_row(list, key, flags, colour_args, result); } int gcli_dict_add_string(gcli_dict list, char const *const key, int flags, uint32_t colour_args, char const *const str) { return gcli_dict_add_row(list, key, flags, colour_args, strdup(str ? str : "")); } int gcli_dict_add_sv_list(gcli_dict dict, char const *const key, sn_sv const *const list, size_t const list_size) { size_t totalsize = 0; char *catted, *hd; /* Sum of string lengths */ for (size_t i = 0; i < list_size; ++i) totalsize += list[i].length; /* Account for comma and space between each */ totalsize += (list_size - 1) * 2; /* concatenate the strings */ hd = catted = calloc(totalsize + 1, 1); for (size_t i = 0; i < list_size; ++i) { memcpy(hd, list[i].data, list[i].length); hd += list[i].length; if (i + 1 < list_size) { strcat(catted, ", "); hd += 2; } } /* Push the row into the state */ return gcli_dict_add_row(dict, key, 0, 0, catted); } int gcli_dict_add_string_list(gcli_dict dict, char const *const key, char const *const *list, size_t const list_size) { char *catted = sn_join_with( (char const *const *)list, list_size, ", "); /* yolo */ /* Push the row into the state */ return gcli_dict_add_row(dict, key, 0, 0, catted); } static void gcli_dict_free(struct gcli_dict *list) { for (size_t i = 0; i < list->entries_size; ++i) { free(list->entries[i].key); free(list->entries[i].value); } free(list->entries); free(list); } int gcli_dict_end(gcli_dict _list) { struct gcli_dict *list = _list; for (size_t i = 0; i < list->entries_size; ++i) { int flags = list->entries[i].flags; pad(list->max_key_len - strlen(list->entries[i].key)); printf("%s : ", list->entries[i].key); if (flags & GCLI_TBLCOL_BOLD) printf("%s", gcli_setbold()); if (flags & GCLI_TBLCOL_COLOUREXPL) printf("%s", gcli_setcolour(list->entries[i].colour_args)); if (flags & GCLI_TBLCOL_STATECOLOURED) printf("%s", gcli_state_colour_str(list->entries[i].value)); if (flags & GCLI_TBLCOL_256COLOUR) printf("%s", gcli_setcolour256(list->entries[i].colour_args)); puts(list->entries[i].value); if (flags & (GCLI_TBLCOL_COLOUREXPL |GCLI_TBLCOL_STATECOLOURED |GCLI_TBLCOL_256COLOUR)) printf("%s", gcli_resetcolour()); if (flags & GCLI_TBLCOL_BOLD) printf("%s", gcli_resetbold()); } gcli_dict_free(list); return 0; } gcli-2.3.0/src/comments.c000066400000000000000000000047631460062271200152240ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 void gcli_comment_free(struct gcli_comment *const it) { free(it->author); free(it->date); free(it->body); } void 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]); free(list->comments); list->comments = NULL; list->comments_size = 0; } int gcli_get_issue_comments(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const issue, struct gcli_comment_list *out) { gcli_null_check_call(get_issue_comments, ctx, owner, repo, issue, out); } int gcli_get_pull_comments(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const pull, struct gcli_comment_list *out) { gcli_null_check_call(get_pull_comments, ctx, owner, repo, pull, out); } int gcli_comment_submit(struct gcli_ctx *ctx, struct gcli_submit_comment_opts opts) { gcli_null_check_call(perform_submit_comment, ctx, opts, NULL); } gcli-2.3.0/src/ctx.c000066400000000000000000000050541460062271200141670ustar00rootroot00000000000000/* * 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 int gcli_error(struct gcli_ctx *ctx, char const *const fmt, ...) { va_list vp; char *buf; size_t len; va_start(vp, fmt); len = vsnprintf(NULL, 0, fmt, vp); va_end(vp); buf = malloc(len + 1); va_start(vp, fmt); vsnprintf(buf, len + 1, fmt, vp); va_end(vp); if (ctx->last_error) free(ctx->last_error); ctx->last_error = buf; return -1; } void * gcli_get_userdata(struct gcli_ctx const *ctx) { return ctx->usrdata; } void gcli_set_userdata(struct gcli_ctx *ctx, void *usrdata) { ctx->usrdata = usrdata; } void gcli_set_progress_func(struct gcli_ctx *ctx, void (*pfunc)(bool done)) { ctx->report_progress = pfunc; } char * gcli_get_apibase(struct gcli_ctx *ctx) { if (!ctx->apibase) ctx->apibase = ctx->get_apibase(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 = gcli_get_token(ctx); if (token && gcli_forge(ctx)->make_authheader) { hdr = gcli_forge(ctx)->make_authheader(ctx, token); } free(token); return hdr; } gcli-2.3.0/src/curl.c000066400000000000000000000464031460062271200143410ustar00rootroot00000000000000/* * Copyright 2021, 2022 Nico Sonack * Copyright 2022 Aritra Sarkar * * 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 /* Hack for NetBSD's and Oracle Solaris broken isalnum implementation */ #if defined(__NetBSD__) || (defined(__SVR4) && defined(__sun)) # ifdef isalnum # undef isalnum # endif # define isalnum gcli_curl_isalnum /* TODO: this is fucked in case we are working on an EBCDIC machine * (wtf are you doing anyways?) */ static int gcli_curl_isalnum(char const c) { return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || ('0' <= c && c <= '9'); } #endif /* __NetBSD and Oracle Solaris */ /* XXX move to gcli_ctx destructor */ void 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(struct gcli_ctx *ctx) { if (ctx->curl) { curl_easy_reset(ctx->curl); } else { ctx->curl = curl_easy_init(); if (!ctx->curl) 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(struct gcli_ctx *ctx, CURLcode code, char const *url, struct gcli_fetch_buffer *const result) { long status_code = 0; if (code != CURLE_OK) { return gcli_error(ctx, "request to %s failed: curl error: %s", url, curl_easy_strerror(code)); } curl_easy_getinfo(ctx->curl, CURLINFO_RESPONSE_CODE, &status_code); if (status_code >= 300L) { return gcli_error(ctx, "request to %s failed with code %ld: API error: %s", url, status_code, gcli_forge(ctx)->get_api_error_string(ctx, result)); } return 0; } /* 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) { /* the user may have passed null indicating that we do not care * about the result body of the request. */ if (data) { struct gcli_fetch_buffer *out = data; out->data = realloc(out->data, out->length + size * nmemb); memcpy(&(out->data[out->length]), in, size * nmemb); out->length += size * nmemb; } return size * nmemb; } /* Plain HTTP get request. * * pagination_next returns the next url to query for paged results. * Results are placed into the struct gcli_fetch_buffer. */ int 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); } static int gcli_report_progress(void *_ctx, double dltotal, double dlnow, double ultotal, double ulnow) { struct gcli_ctx *ctx = _ctx; (void) dltotal; (void) dlnow; (void) ultotal; (void) ulnow; /* not done */ ctx->report_progress(false); return 0; } /* Check the given url for a successful query */ int gcli_curl_test_success(struct gcli_ctx *ctx, char const *url) { CURLcode ret; struct gcli_fetch_buffer buffer = {0}; long status_code; bool is_success = true; int rc = 0; if ((rc = gcli_curl_ensure(ctx)) < 0) return rc; curl_easy_setopt(ctx->curl, CURLOPT_URL, 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, ctx->curl_useragent); #if defined(CURL_HTTP_VERSION_2TLS) curl_easy_setopt( ctx->curl, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2TLS); #endif curl_easy_setopt(ctx->curl, CURLOPT_TCP_KEEPALIVE, 1L); curl_easy_setopt(ctx->curl, CURLOPT_WRITEDATA, &buffer); curl_easy_setopt(ctx->curl, CURLOPT_WRITEFUNCTION, fetch_write_callback); curl_easy_setopt(ctx->curl, CURLOPT_FAILONERROR, 0L); curl_easy_setopt(ctx->curl, CURLOPT_FOLLOWLOCATION, 1L); if (ctx->report_progress) { curl_easy_setopt(ctx->curl, CURLOPT_XFERINFOFUNCTION, gcli_report_progress); curl_easy_setopt(ctx->curl, CURLOPT_XFERINFODATA, ctx); curl_easy_setopt(ctx->curl, CURLOPT_NOPROGRESS, 0L); } ret = curl_easy_perform(ctx->curl); if (ret != CURLE_OK) { is_success = false; } else { curl_easy_getinfo(ctx->curl, CURLINFO_RESPONSE_CODE, &status_code); if (status_code >= 300L) is_success = false; } if (ctx->report_progress) ctx->report_progress(true); free(buffer.data); return is_success; } /* Perform a GET request to the given URL and print the results to the * STREAM. * * content_type may be NULL. */ int gcli_curl(struct gcli_ctx *ctx, FILE *stream, char const *url, char const *content_type) { CURLcode ret; struct curl_slist *headers; struct gcli_fetch_buffer buffer = {0}; char *auth_header = NULL; int rc = 0; headers = NULL; if ((rc = gcli_curl_ensure(ctx)) < 0) return rc; if (content_type) headers = curl_slist_append(headers, content_type); auth_header = gcli_get_authheader(ctx); 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); curl_easy_setopt(ctx->curl, CURLOPT_NOPROGRESS, 1L); 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, ctx->curl_useragent); #if defined(CURL_HTTP_VERSION_2TLS) curl_easy_setopt( ctx->curl, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2TLS); #endif curl_easy_setopt(ctx->curl, CURLOPT_TCP_KEEPALIVE, 1L); curl_easy_setopt(ctx->curl, CURLOPT_WRITEDATA, &buffer); curl_easy_setopt(ctx->curl, CURLOPT_WRITEFUNCTION, fetch_write_callback); curl_easy_setopt(ctx->curl, CURLOPT_FAILONERROR, 0L); curl_easy_setopt(ctx->curl, CURLOPT_FOLLOWLOCATION, 1L); if (ctx->report_progress) { curl_easy_setopt(ctx->curl, CURLOPT_XFERINFOFUNCTION, gcli_report_progress); curl_easy_setopt(ctx->curl, CURLOPT_XFERINFODATA, ctx); curl_easy_setopt(ctx->curl, CURLOPT_NOPROGRESS, 0L); } ret = curl_easy_perform(ctx->curl); rc = gcli_curl_check_api_error(ctx, ret, url, &buffer); if (ctx->report_progress) ctx->report_progress(true); if (rc == 0) fwrite(buffer.data, 1, buffer.length, stream); free(buffer.data); curl_slist_free_all(headers); free(auth_header); return rc; } /* Callback to extract the link header for pagination handling. */ static size_t fetch_header_callback(char *ptr, size_t size, size_t nmemb, void *userdata) { char **out = userdata; size_t sz = size * nmemb; sn_sv buffer = sn_sv_from_parts(ptr, sz); sn_sv header_name = sn_sv_chop_until(&buffer, ':'); /* Despite what the documentation says, this header is called * "link" not "Link". Webdev ftw /sarc */ if (sn_sv_eq_to(header_name, "link")) { buffer.data += 1; buffer.length -= 1; buffer = sn_sv_trim_front(buffer); *out = sn_strndup(buffer.data, buffer.length); } return sz; } /* Parse the link http header for pagination */ static char * parse_link_header(char *_header) { sn_sv header = SV(_header); sn_sv entry = {0}; /* Iterate through the comma-separated list of link relations */ while ((entry = sn_sv_chop_until(&header, ',')).length > 0) { entry = sn_sv_trim(entry); /* the entries have semicolon-separated fields like so: * ; rel=\"next\" * * This chops off the url and then looks at the rest. * * We're making lots of assumptions about the input data here * without sanity checking it. If it fails, we will know. Most * likely a segfault. */ sn_sv almost_url = sn_sv_chop_until(&entry, ';'); if (sn_sv_eq_to(entry, "; rel=\"next\"")) { /* Skip the triangle brackets around the url */ almost_url.data += 1; almost_url.length -= 2; almost_url = sn_sv_trim(almost_url); return sn_sv_to_cstr(almost_url); } /* skip the comma if we have enough data */ if (header.length > 0) { header.length -= 1; header.data += 1; } } return NULL; } /* Perform a HTTP Request with the given method to the url * * - data may be NULL. * - pagination_next may be NULL. * * Results are placed in the gcli_fetch_buffer. * * All requests will be done with authorization through the * gcli_config_get_authheader function. * * If pagination_next is non-null a URL that can be queried for more * data (pagination) is placed into it. If there is no more data, it * will be set to NULL. */ int gcli_fetch_with_method( 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; 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; if ((rc = gcli_curl_ensure(ctx)) < 0) return rc; char *auth_header = gcli_get_authheader(ctx); if (sn_verbose()) fprintf(stderr, "info: cURL request %s %s...\n", method, url); headers = NULL; headers = curl_slist_append( headers, "Accept: application/vnd.github.v3+json"); headers = curl_slist_append( headers, "Content-Type: application/json"); if (auth_header) headers = curl_slist_append(headers, auth_header); /* Only clear the output buffer if we have a pointer to it. If the * user is not interested in the result we use a temporary buffer * for proper error reporting. */ if (out) { *out = (struct gcli_fetch_buffer) {0}; buf = out; } else { buf = &tmp; } curl_easy_setopt(ctx->curl, CURLOPT_URL, url); if (data) curl_easy_setopt(ctx->curl, CURLOPT_POSTFIELDS, data); curl_easy_setopt(ctx->curl, CURLOPT_HTTPHEADER, headers); 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); curl_easy_setopt(ctx->curl, CURLOPT_WRITEFUNCTION, fetch_write_callback); curl_easy_setopt(ctx->curl, CURLOPT_FAILONERROR, 0L); curl_easy_setopt(ctx->curl, CURLOPT_HEADERFUNCTION, fetch_header_callback); curl_easy_setopt(ctx->curl, CURLOPT_HEADERDATA, &link_header); curl_easy_setopt(ctx->curl, CURLOPT_FOLLOWLOCATION, 1L); if (ctx->report_progress) { curl_easy_setopt(ctx->curl, CURLOPT_XFERINFOFUNCTION, gcli_report_progress); curl_easy_setopt(ctx->curl, CURLOPT_XFERINFODATA, ctx); curl_easy_setopt(ctx->curl, CURLOPT_NOPROGRESS, 0L); } ret = curl_easy_perform(ctx->curl); rc = gcli_curl_check_api_error(ctx, ret, url, buf); if (ctx->report_progress) ctx->report_progress(true); /* only parse these headers and continue if there was no error */ if (rc == 0) { if (link_header && pagination_next) *pagination_next = parse_link_header(link_header); } else if (out) { /* error happened and we have an output buffer */ free(out->data); out->data = NULL; out->length = 0; } free(link_header); curl_slist_free_all(headers); headers = NULL; /* if the user is not interested in the result, free the temporary * buffer */ if (!out) free(tmp.data); free(auth_header); return rc; } /* Perform a POST request to the given URL and upload the buffer to it. * * Results are placed in out. * * content_type may not be NULL. */ int gcli_post_upload(struct gcli_ctx *ctx, char const *url, char const *content_type, void *buffer, size_t const buffer_size, struct gcli_fetch_buffer *const out) { CURLcode ret; struct curl_slist *headers; int rc = 0; char *auth_header, *contenttype_header, *contentsize_header; if ((rc = gcli_curl_ensure(ctx)) < 0) return rc; auth_header = gcli_get_authheader(ctx); contenttype_header = sn_asprintf("Content-Type: %s", content_type); contentsize_header = sn_asprintf("Content-Length: %zu", buffer_size); if (sn_verbose()) fprintf(stderr, "info: cURL upload POST %s...\n", url); headers = NULL; headers = curl_slist_append( headers, "Accept: application/vnd.github.v3+json"); if (auth_header) headers = curl_slist_append(headers, auth_header); headers = curl_slist_append(headers, contenttype_header); headers = curl_slist_append(headers, contentsize_header); curl_easy_setopt(ctx->curl, CURLOPT_URL, url); curl_easy_setopt(ctx->curl, CURLOPT_POST, 1L); curl_easy_setopt(ctx->curl, CURLOPT_POSTFIELDS, buffer); 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, ctx->curl_useragent); curl_easy_setopt(ctx->curl, CURLOPT_WRITEDATA, out); curl_easy_setopt(ctx->curl, CURLOPT_WRITEFUNCTION, fetch_write_callback); if (ctx->report_progress) { curl_easy_setopt(ctx->curl, CURLOPT_XFERINFOFUNCTION, gcli_report_progress); curl_easy_setopt(ctx->curl, CURLOPT_XFERINFODATA, ctx); curl_easy_setopt(ctx->curl, CURLOPT_NOPROGRESS, 0L); } ret = curl_easy_perform(ctx->curl); rc = gcli_curl_check_api_error(ctx, ret, url, out); if (ctx->report_progress) ctx->report_progress(true); curl_slist_free_all(headers); headers = NULL; free(auth_header); free(contentsize_header); free(contenttype_header); return rc; } /** gcli_gitea_upload_attachment: * * Upload the given file to the given url. This is gitea-specific * code. */ int gcli_curl_gitea_upload_attachment(struct gcli_ctx *ctx, char const *url, char const *filename, struct gcli_fetch_buffer *const out) { CURLcode ret; curl_mime *mime; curl_mimepart *contentpart; struct curl_slist *headers; int rc = 0; char *auth_header; if ((rc = gcli_curl_ensure(ctx)) < 0) return rc; auth_header = gcli_get_authheader(ctx); if (sn_verbose()) fprintf(stderr, "info: cURL upload POST %s...\n", url); headers = NULL; headers = curl_slist_append( headers, "Accept: application/json"); 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); contentpart = curl_mime_addpart(mime); /* Attach the file. It will be read when curl_easy_perform is * called. This allows us to upload large files without reading or * mapping them into memory in one chunk. */ curl_mime_name(contentpart, "attachment"); ret = curl_mime_filedata(contentpart, filename); if (ret != CURLE_OK) { errx(1, "error: could not set attachment for upload: %s", curl_easy_strerror(ret)); } curl_easy_setopt(ctx->curl, CURLOPT_URL, url); curl_easy_setopt(ctx->curl, CURLOPT_MIMEPOST, mime); curl_easy_setopt(ctx->curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(ctx->curl, CURLOPT_WRITEDATA, out); curl_easy_setopt(ctx->curl, CURLOPT_WRITEFUNCTION, fetch_write_callback); if (ctx->report_progress) { curl_easy_setopt(ctx->curl, CURLOPT_XFERINFOFUNCTION, gcli_report_progress); curl_easy_setopt(ctx->curl, CURLOPT_XFERINFODATA, ctx); curl_easy_setopt(ctx->curl, CURLOPT_NOPROGRESS, 0L); } ret = curl_easy_perform(ctx->curl); rc = gcli_curl_check_api_error(ctx, ret, url, out); if (ctx->report_progress) ctx->report_progress(true); /* Cleanup */ curl_slist_free_all(headers); headers = NULL; curl_mime_free(mime); free(auth_header); return rc; } sn_sv gcli_urlencode_sv(sn_sv const _input) { size_t input_len; size_t output_len; size_t i; char *output; char *input; input = _input.data; input_len = _input.length; output = calloc(1, 3 * input_len + 1); output_len = 0; for (i = 0; i < input_len; ++i) { if (!isalnum(input[i]) && input[i] != '-' && input[i] != '_') { unsigned val = (input[i] & 0xFF); snprintf(output + output_len, 4, "%%%2.2X", val); output_len += 3; } else { output[output_len++] = input[i]; } } return sn_sv_from_parts(output, output_len); } char * gcli_urlencode(char const *input) { sn_sv encoded = gcli_urlencode_sv(SV((char *)input)); return encoded.data; } char * gcli_urldecode(struct gcli_ctx *ctx, char const *input) { char *curlresult, *result; if (gcli_curl_ensure(ctx) < 0) return NULL; curlresult = curl_easy_unescape(ctx->curl, input, 0, NULL); if (!curlresult) { gcli_error(ctx, "could not urldecode"); return NULL; } result = strdup(curlresult); curl_free(curlresult); return result; } /* Convenience function for fetching lists. * * listptr must be a double-pointer (pointer to a pointer to the start * of the array). e.g. * * struct foolist { struct foo *foos; size_t foos_size; } *out = ...; * * listptr = &out->foos; * listsize = &out->foos_size; * * If max is -1 then everything will be fetched. */ int gcli_fetch_list(struct gcli_ctx *ctx, char *url, struct gcli_fetch_list_ctx *fl) { char *next_url = NULL; int rc; do { struct gcli_fetch_buffer buffer = {0}; rc = gcli_fetch(ctx, url, &next_url, &buffer); if (rc == 0) { struct json_stream stream = {0}; json_open_buffer(&stream, buffer.data, buffer.length); rc = fl->parse(ctx, &stream, fl->listp, fl->sizep); if (fl->filter) fl->filter(fl->listp, fl->sizep, fl->userdata); json_close(&stream); } free(buffer.data); free(url); if (rc < 0) break; } while ((url = next_url) && (fl->max == -1 || (int)(*fl->sizep) < fl->max)); free(next_url); return rc; } gcli-2.3.0/src/date_time.c000066400000000000000000000050631460062271200153240ustar00rootroot00000000000000/* * 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_normalize_date(struct gcli_ctx *ctx, int fmt, char const *const input, char *output, size_t const output_size) { struct tm tm_buf = {0}; struct tm *utm_buf; char *endptr; time_t utctime; char const *sfmt; switch (fmt) { case DATEFMT_ISO8601: sfmt = "%Y-%m-%dT%H:%M:%SZ"; assert(output_size == 21); break; case DATEFMT_GITLAB: sfmt = "%Y%m%d"; assert(output_size == 9); break; default: return gcli_error(ctx, "bad date format"); } /* Parse input time */ endptr = strptime(input, "%Y-%m-%d", &tm_buf); if (endptr == NULL || *endptr != '\0') return gcli_error(ctx, "date »%s« is invalid: want YYYY-MM-DD", input); /* Convert to UTC: Really, we should be using the _r versions of * these functions for thread-safety but since gcli doesn't do * multithreading (except for inside libcurl) we do not need to be * worried about the storage behind the pointer returned by gmtime * to be altered by another thread. */ utctime = mktime(&tm_buf); utm_buf = gmtime(&utctime); /* Format the output string - now in UTC */ strftime(output, output_size, sfmt, utm_buf); return 0; } gcli-2.3.0/src/forges.c000066400000000000000000000361751460062271200146660ustar00rootroot00000000000000/* * 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 * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static struct gcli_forge_descriptor const github_forge_descriptor = { /* Comments */ .get_issue_comments = github_get_comments, .get_pull_comments = github_get_comments, .perform_submit_comment = github_perform_submit_comment, /* Forks */ .fork_create = github_fork_create, .get_forks = github_get_forks, /* Issues */ .get_issue_summary = github_get_issue_summary, .search_issues = github_issues_search, .issue_add_labels = github_issue_add_labels, .issue_assign = github_issue_assign, .issue_clear_milestone = github_issue_clear_milestone, .issue_close = github_issue_close, .issue_remove_labels = github_issue_remove_labels, .issue_reopen = github_issue_reopen, .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, .delete_milestone = github_delete_milestone, .get_milestone = github_get_milestone, .get_milestone_issues = github_milestone_get_issues, .get_milestones = github_get_milestones, .milestone_set_duedate = github_milestone_set_duedate, /* Pull requests */ .get_pull = github_get_pull, .get_pull_checks = github_pull_get_checks, .get_pull_commits = github_get_pull_commits, .search_pulls = github_search_pulls, .perform_submit_pull = github_perform_submit_pull, .pull_add_reviewer = github_pull_add_reviewer, .pull_close = github_pull_close, .pull_get_diff = github_pull_get_diff, .pull_get_patch = github_pull_get_patch, .pull_merge = github_pull_merge, .pull_reopen = github_pull_reopen, .pull_set_title = github_pull_set_title, /* HACK: Here we can use the same functions as with issues because * PRs are the same as issues on Github and the functions have the * same types/arguments */ .pull_add_labels = github_issue_add_labels, .pull_clear_milestone = github_issue_clear_milestone, .pull_remove_labels = github_issue_remove_labels, .pull_set_milestone = github_issue_set_milestone, /* Releases */ .create_release = github_create_release, .delete_release = github_delete_release, .get_releases = github_get_releases, /* Labels */ .create_label = github_create_label, .delete_label = github_delete_label, .get_labels = github_get_labels, /* Repos */ .get_repos = github_get_repos, .repo_create = github_repo_create, .repo_delete = github_repo_delete, .repo_set_visibility = github_repo_set_visibility, /* SSH Key management */ .add_sshkey = github_add_sshkey, .delete_sshkey = github_delete_sshkey, .get_sshkeys = github_get_sshkeys, /* Notifications */ .get_notifications = github_get_notifications, .notification_mark_as_read = github_notification_mark_as_read, /* Internal stuff */ .get_api_error_string = github_api_error_string, .make_authheader = github_make_authheader, .user_object_key = "login", /* Quirks */ .milestone_quirks = GCLI_MILESTONE_QUIRKS_EXPIRED | GCLI_MILESTONE_QUIRKS_DUEDATE | GCLI_MILESTONE_QUIRKS_PULLS, .pull_summary_quirks = GCLI_PRS_QUIRK_COVERAGE | GCLI_PRS_QUIRK_AUTOMERGE, }; static struct gcli_forge_descriptor const gitlab_forge_descriptor = { /* Comments */ .get_issue_comments = gitlab_get_issue_comments, .get_pull_comments = gitlab_get_mr_comments, .perform_submit_comment = gitlab_perform_submit_comment, /* Forks */ .fork_create = gitlab_fork_create, .get_forks = gitlab_get_forks, /* Issues */ .get_issue_summary = gitlab_get_issue_summary, .search_issues = gitlab_issues_search, .issue_add_labels = gitlab_issue_add_labels, .issue_assign = gitlab_issue_assign, .issue_clear_milestone = gitlab_issue_clear_milestone, .issue_close = gitlab_issue_close, .issue_remove_labels = gitlab_issue_remove_labels, .issue_reopen = gitlab_issue_reopen, .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, .delete_milestone = gitlab_delete_milestone, .get_milestone = gitlab_get_milestone, .get_milestone_issues = gitlab_milestone_get_issues, .get_milestones = gitlab_get_milestones, .milestone_set_duedate = gitlab_milestone_set_duedate, /* Pull requests */ .get_pull = gitlab_get_pull, .get_pull_checks = (gcli_get_pull_checks_cb)gitlab_get_mr_pipelines, .get_pull_commits = gitlab_get_pull_commits, .search_pulls = gitlab_get_mrs, .perform_submit_pull = gitlab_perform_submit_mr, .pull_add_labels = gitlab_mr_add_labels, .pull_add_reviewer = gitlab_mr_add_reviewer, .pull_clear_milestone = gitlab_mr_clear_milestone, .pull_close = gitlab_mr_close, .pull_get_diff = gitlab_mr_get_diff, .pull_get_patch = gitlab_mr_get_patch, .pull_merge = gitlab_mr_merge, .pull_remove_labels = gitlab_mr_remove_labels, .pull_reopen = gitlab_mr_reopen, .pull_set_milestone = gitlab_mr_set_milestone, .pull_set_title = gitlab_mr_set_title, /* Releases */ .create_release = gitlab_create_release, .delete_release = gitlab_delete_release, .get_releases = gitlab_get_releases, /* Labels */ .create_label = gitlab_create_label, .delete_label = gitlab_delete_label, .get_labels = gitlab_get_labels, /* Repos */ .get_repos = gitlab_get_repos, .repo_create = gitlab_repo_create, .repo_delete = gitlab_repo_delete, .repo_set_visibility = gitlab_repo_set_visibility, /* SSH Key management */ .add_sshkey = gitlab_add_sshkey, .delete_sshkey = gitlab_delete_sshkey, .get_sshkeys = gitlab_get_sshkeys, /* Notifications */ .get_notifications = gitlab_get_notifications, .notification_mark_as_read = gitlab_notification_mark_as_read, /* Internal stuff */ .get_api_error_string = gitlab_api_error_string, .make_authheader = gitlab_make_authheader, .user_object_key = "username", /* Quirks */ .milestone_quirks = GCLI_MILESTONE_QUIRKS_NISSUES, .pull_summary_quirks = GCLI_PRS_QUIRK_ADDDEL | GCLI_PRS_QUIRK_COMMITS | GCLI_PRS_QUIRK_CHANGES | GCLI_PRS_QUIRK_MERGED, }; static struct gcli_forge_descriptor const gitea_forge_descriptor = { /* Comments */ .get_issue_comments = gitea_get_comments, .get_pull_comments = gitea_get_comments, .perform_submit_comment = gitea_perform_submit_comment, /* Forks */ .fork_create = gitea_fork_create, .get_forks = gitea_get_forks, /* Issues */ .get_issue_summary = gitea_get_issue_summary, .search_issues = gitea_issues_search, .issue_add_labels = gitea_issue_add_labels, .issue_assign = gitea_issue_assign, .issue_clear_milestone = gitea_issue_clear_milestone, .issue_close = gitea_issue_close, .issue_remove_labels = gitea_issue_remove_labels, .issue_reopen = gitea_issue_reopen, .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, .delete_milestone = gitea_delete_milestone, .get_milestone = gitea_get_milestone, .get_milestone_issues = gitea_milestone_get_issues, .get_milestones = gitea_get_milestones, .milestone_set_duedate = gitea_milestone_set_duedate, /* Pull requests */ .get_pull = gitea_get_pull, .get_pull_checks = gitea_pull_get_checks, /* stub, will always return an error */ .get_pull_commits = gitea_get_pull_commits, .search_pulls = gitea_search_pulls, .perform_submit_pull = gitea_pull_submit, .pull_add_labels = gitea_issue_add_labels, .pull_add_reviewer = gitea_pull_add_reviewer, .pull_clear_milestone = gitea_pull_clear_milestone, .pull_close = gitea_pull_close, .pull_get_diff = gitea_pull_get_diff, .pull_get_patch = gitea_pull_get_patch, .pull_merge = gitea_pull_merge, .pull_remove_labels = gitea_issue_remove_labels, .pull_reopen = gitea_pull_reopen, .pull_set_milestone = gitea_pull_set_milestone, .pull_set_title = gitea_pull_set_title, /* Releases */ .create_release = gitea_create_release, .delete_release = gitea_delete_release, .get_releases = gitea_get_releases, /* Labels */ .create_label = gitea_create_label, .delete_label = gitea_delete_label, .get_labels = gitea_get_labels, /* Repos */ .get_repos = gitea_get_repos, .repo_create = gitea_repo_create, .repo_delete = gitea_repo_delete, .repo_set_visibility = gitea_repo_set_visibility, /* SSH Key management */ .add_sshkey = gitea_add_sshkey, .delete_sshkey = gitea_delete_sshkey, .get_sshkeys = gitea_get_sshkeys, /* Notifications */ .get_notifications = gitea_get_notifications, .notification_mark_as_read = gitea_notification_mark_as_read, /* Internal stuff */ .make_authheader = gitea_make_authheader, .get_api_error_string = github_api_error_string, /* hack! */ .user_object_key = "username", /* Quirks */ .milestone_quirks = GCLI_MILESTONE_QUIRKS_EXPIRED | 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, }; 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: return &github_forge_descriptor; case GCLI_FORGE_GITLAB: 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 " "with -a, specifying -t or create a .gcli file."); } return NULL; } gcli-2.3.0/src/forks.c000066400000000000000000000042661460062271200145210ustar00rootroot00000000000000/* * Copyright 2021,2022 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 int gcli_get_forks(struct gcli_ctx *ctx, char const *owner, char const *repo, int const max, struct gcli_fork_list *const out) { gcli_null_check_call(get_forks, ctx, owner, repo, max, out); } int gcli_fork_create(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *_in) { gcli_null_check_call(fork_create, ctx, owner, repo, _in); } void gcli_fork_free(struct gcli_fork *fork) { free(fork->full_name); free(fork->owner); free(fork->date); } void 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]); } free(list->forks); list->forks = NULL; list->forks_size = 0; } gcli-2.3.0/src/gcli.c000066400000000000000000000042271460062271200143100ustar00rootroot00000000000000/* * 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 * 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)) return strerror(errno); (*ctx)->get_forge_type = get_forge_type; (*ctx)->get_token = get_token; (*ctx)->get_apibase = get_apibase; (*ctx)->apibase = NULL; return NULL; } void gcli_destroy(struct gcli_ctx **ctx) { if (ctx && *ctx) { free((*ctx)->apibase); free(*ctx); *ctx = NULL; /* TODO: other deinit stuff? */ } } char const * gcli_get_error(struct gcli_ctx *ctx) { if (ctx->last_error) return ctx->last_error; else return "No error"; } gcli-2.3.0/src/gitea/000077500000000000000000000000001460062271200143125ustar00rootroot00000000000000gcli-2.3.0/src/gitea/comments.c000066400000000000000000000035341460062271200163100ustar00rootroot00000000000000/* * Copyright 2022 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 int 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(struct gcli_ctx *ctx, struct gcli_submit_comment_opts opts, struct gcli_fetch_buffer *const out) { return github_perform_submit_comment(ctx, opts, out); } gcli-2.3.0/src/gitea/config.c000066400000000000000000000030411460062271200157210ustar00rootroot00000000000000/* * Copyright 2022 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 * gitea_make_authheader(struct gcli_ctx *ctx, char const *token) { (void) ctx; return sn_asprintf("Authorization: token %s", token); } gcli-2.3.0/src/gitea/forks.c000066400000000000000000000034611460062271200156060ustar00rootroot00000000000000/* * Copyright 2021,2022 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 int 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(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *_in) { return github_fork_create(ctx, owner, repo, _in); } gcli-2.3.0/src/gitea/issues.c000066400000000000000000000240201460062271200157670ustar00rootroot00000000000000/* * Copyright 2022 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 #include #include int 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) { char *url = NULL, *e_owner = NULL, *e_repo = NULL, *e_author = NULL, *e_label = NULL, *e_milestone = NULL, *e_query = NULL; struct gcli_fetch_list_ctx fl = { .listp = &out->issues, .sizep = &out->issues_size, .parse = (parsefn)(parse_github_issues), .max = max, }; 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?type=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 gcli_fetch_list(ctx, url, &fl); } int 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(struct gcli_ctx *const ctx, struct gcli_submit_issue_options *const opts, struct gcli_issue *const out) { return github_perform_submit_issue(ctx, opts, out); } /* Gitea has closed, Github has close ... go figure */ static int 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, *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); 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(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(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(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const issue_number, char const *const assignee) { 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, "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); free(e_owner); free(e_repo); rc = gcli_fetch_with_method(ctx, "PATCH", url, payload, NULL, NULL); free(payload); free(url); return rc; } /* Return the stringified id of the given label */ static char * get_id_of_label(char const *label_name, 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) return sn_asprintf("%"PRIid, list->labels[i].id); return NULL; } static void free_id_list(char *list[], size_t const list_size) { for (size_t i = 0; i < list_size; ++i) { free(list[i]); } free(list); } static char ** label_names_to_ids(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *const names[], size_t const names_size) { struct gcli_label_list list = {0}; char **ids = NULL; size_t ids_size = 0; gitea_get_labels(ctx, owner, repo, -1, &list); for (size_t i = 0; i < names_size; ++i) { char *const label_id = get_id_of_label(names[i], &list); if (!label_id) { free_id_list(ids, ids_size); ids = NULL; gcli_error(ctx, "no such label '%s'", names[i]); goto out; } ids = realloc(ids, sizeof(*ids) * (ids_size +1)); ids[ids_size++] = label_id; } out: gcli_free_labels(&list); return ids; } int 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 *payload = NULL, *url = NULL, *e_owner = NULL, *e_repo = NULL; struct gcli_jsongen gen = {0}; int rc = 0; /* First, convert to ids */ char **ids = label_names_to_ids(ctx, owner, repo, labels, labels_size); if (!ids) 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); payload = gcli_jsongen_to_string(&gen); gcli_jsongen_free(&gen); free_id_list(ids, labels_size); e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/repos/%s/%s/issues/%"PRIid"/labels", gcli_get_apibase(ctx), e_owner, e_repo, issue); free(e_owner); free(e_repo); rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, NULL); free(payload); free(url); return rc; } int 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. */ char **ids = label_names_to_ids(ctx, owner, repo, labels, labels_size); 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), e_owner, e_repo, issue, ids[i]); rc = gcli_fetch_with_method(ctx, "DELETE", url, NULL, NULL, NULL); free(url); if (rc < 0) break; } free(e_owner); free(e_repo); free_id_list(ids, labels_size); return rc; } int gitea_issue_set_milestone(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const issue, gcli_id const milestone) { return github_issue_set_milestone(ctx, owner, repo, issue, milestone); } int 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(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const issue, char const *const new_title) { return github_issue_set_title(ctx, owner, repo, issue, new_title); } gcli-2.3.0/src/gitea/labels.c000066400000000000000000000055411460062271200157250ustar00rootroot00000000000000/* * Copyright 2022 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 int 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(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(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *label) { char *url = NULL; struct gcli_label_list list = {0}; int id = -1; int rc = 0; /* Gitea wants the id of the label, not its name. thus fetch all * the labels first to then find out what the id is we need. */ rc = gitea_get_labels(ctx, owner, repo, -1, &list); if (rc < 0) return rc; /* Search for the id */ for (size_t i = 0; i < list.labels_size; ++i) { if (strcmp(list.labels[i].name, label) == 0) { id = list.labels[i].id; break; } } gcli_free_labels(&list); /* did we find a label? */ if (id < 0) return gcli_error(ctx, "label '%s' does not exist", label); /* DELETE /repos/{owner}/{repo}/labels/{} */ url = sn_asprintf("%s/repos/%s/%s/labels/%d", gcli_get_apibase(ctx), owner, repo, id); rc = gcli_fetch_with_method(ctx, "DELETE", url, NULL, NULL, NULL); free(url); return rc; } gcli-2.3.0/src/gitea/milestones.c000066400000000000000000000101601460062271200166360ustar00rootroot00000000000000/* * 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 int gitea_get_milestones(struct gcli_ctx *ctx, char const *const owner, char const *const repo, int const max, struct gcli_milestone_list *const out) { char *url; char *e_owner, *e_repo; struct gcli_fetch_list_ctx fl = { .listp = &out->milestones, .sizep = &out->milestones_size, .max = max, .parse = (parsefn)(parse_gitea_milestones), }; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/repos/%s/%s/milestones", gcli_get_apibase(ctx), e_owner, e_repo); free(e_owner); free(e_repo); return gcli_fetch_list(ctx, url, &fl); } int gitea_get_milestone(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone, struct gcli_milestone *const out) { char *url, *e_owner, *e_repo; struct gcli_fetch_buffer buffer = {0}; int rc = 0; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/repos/%s/%s/milestones/%"PRIid, gcli_get_apibase(ctx), e_owner, e_repo, milestone); free(e_owner); free(e_repo); rc = gcli_fetch(ctx, url, NULL, &buffer); if (rc == 0) { struct json_stream stream = {0}; json_open_buffer(&stream, buffer.data, buffer.length); parse_gitea_milestone(ctx, &stream, out); json_close(&stream); } free(buffer.data); free(url); return rc; } int 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(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone, struct gcli_issue_list *const out) { char *url, *e_owner, *e_repo; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/repos/%s/%s/issues?state=all&milestones=%"PRIid, gcli_get_apibase(ctx), e_owner, e_repo, milestone); free(e_repo); free(e_owner); return github_fetch_issues(ctx, url, -1, out); } int 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(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone, char const *const date) { return github_milestone_set_duedate(ctx, owner, repo, milestone, date); } gcli-2.3.0/src/gitea/pulls.c000066400000000000000000000207341460062271200156230ustar00rootroot00000000000000/* * Copyright 2022 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 int gitea_search_pulls(struct gcli_ctx *ctx, char const *owner, char const *repo, struct gcli_pull_fetch_details const *details, int const max, struct gcli_pull_list *const out) { char *url = NULL, *e_owner = NULL, *e_repo = NULL, *e_author = NULL, *e_label = NULL, *e_milestone = NULL, *e_query = NULL; struct gcli_fetch_list_ctx fl = { .listp = &out->pulls, .sizep = &out->pulls_size, .parse = (parsefn)(parse_github_pulls), .max = max, }; 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?type=pulls&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 gcli_fetch_list(ctx, url, &fl); } int 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(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(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"); return github_perform_submit_pull(ctx, opts); } int gitea_pull_merge(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const pr, enum gcli_merge_flags const flags) { 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); free(e_owner); free(e_repo); rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, NULL); free(url); free(payload); return rc; } static int 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; 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/pulls/%d", gcli_get_apibase(ctx), e_owner, e_repo, pr_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); return rc; } int 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(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(struct gcli_ctx *ctx, FILE *const stream, char const *owner, char const *repo, gcli_id const pr_number) { char *url = 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/pulls/%"PRIid".patch", gcli_get_apibase(ctx), e_owner, e_repo, pr_number); rc = gcli_curl(ctx, stream, url, NULL); free(e_owner); free(e_repo); free(url); return rc; } int 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; 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/pulls/%"PRIid".diff", gcli_get_apibase(ctx), e_owner, e_repo, pr_number); rc = gcli_curl(ctx, stream, url, NULL); free(e_owner); free(e_repo); free(url); return rc; } int 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; (void) repo; (void) pr_number; (void) out; return gcli_error(ctx, "Pull Request checks are not available on Gitea"); } int 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(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 * the case of Gitea which clear the milestone by setting it to * the integer value zero. */ return github_issue_set_milestone(ctx, owner, repo, pr_number, 0); } int 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(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id pull, char const *const title) { return github_pull_set_title(ctx, owner, repo, pull, title); } gcli-2.3.0/src/gitea/releases.c000066400000000000000000000113751460062271200162700ustar00rootroot00000000000000/* * Copyright 2022 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 int 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(struct gcli_ctx *ctx, struct gcli_fetch_buffer const *const buffer, struct gcli_release *const out) { 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(struct gcli_ctx *ctx, char *const url, struct gcli_release_asset_upload const asset) { char *e_assetname = NULL; char *request = NULL; struct gcli_fetch_buffer buffer = {0}; int rc = 0; e_assetname = gcli_urlencode(asset.name); request = sn_asprintf("%s?name=%s", url, e_assetname); rc = gcli_curl_gitea_upload_attachment(ctx, request, asset.path, &buffer); free(request); free(e_assetname); free(buffer.data); return rc; } int gitea_create_release(struct gcli_ctx *ctx, struct gcli_new_release const *release) { 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); /* 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); 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/%s/assets", gcli_get_apibase(ctx), e_owner, e_repo, response.id); for (size_t i = 0; i < release->assets_size; ++i) { printf("INFO : Uploading asset %s...\n", release->assets[i].path); rc = gitea_upload_release_asset(ctx, upload_url, release->assets[i]); if (rc < 0) break; } gcli_release_free(&response); out: free(e_owner); free(e_repo); free(upload_url); free(buffer.data); free(url); free(payload); return rc; } int gitea_delete_release(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *id) { return github_delete_release(ctx, owner, repo, id); } gcli-2.3.0/src/gitea/repos.c000066400000000000000000000061501460062271200156100ustar00rootroot00000000000000/* * Copyright 2022 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 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(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(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(struct gcli_ctx *ctx, char const *owner, char const *repo) { return github_repo_delete(ctx, owner, repo); } /* Unlike Github and Gitlab, Gitea only supports private or non-private * (thus public) repositories. Separate implementation required. */ int gitea_repo_set_visibility(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_repo_visibility vis) { char *url; char *e_owner, *e_repo; bool is_private; char *payload; int rc; switch (vis) { case GCLI_REPO_VISIBILITY_PRIVATE: is_private = true; break; case GCLI_REPO_VISIBILITY_PUBLIC: is_private = false; break; default: assert(false && "Invalid visibility"); return gcli_error(ctx, "bad or unsupported visibility level for Gitea"); } e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/repos/%s/%s", gcli_get_apibase(ctx), e_owner, e_repo); payload = sn_asprintf("{ \"private\": %s }", is_private ? "true" : "false"); rc = gcli_fetch_with_method(ctx, "PATCH", url, payload, NULL, NULL); free(payload); free(e_owner); free(e_repo); free(url); return rc; } gcli-2.3.0/src/gitea/sshkeys.c000066400000000000000000000040511460062271200161470ustar00rootroot00000000000000/* * 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 /* NOTE(Nico): The APIs of all the three forges we currently implement * are the same. Thus, we just call into the gitlab * implementation. Yes, this looks absurd. */ #include int gitea_get_sshkeys(struct gcli_ctx *ctx, struct gcli_sshkey_list *list) { return gitlab_get_sshkeys(ctx, list); } int 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(struct gcli_ctx *ctx, gcli_id const id) { return gitlab_delete_sshkey(ctx, id); } gcli-2.3.0/src/gitea/status.c000066400000000000000000000040371460062271200160050ustar00rootroot00000000000000/* * 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 int gitea_get_notifications(struct gcli_ctx *ctx, int const max, struct gcli_notification_list *const out) { char *url = NULL; struct gcli_fetch_list_ctx fl = { .listp = &out->notifications, .sizep = &out->notifications_size, .parse = (parsefn)(parse_gitea_notifications), .max = max, }; url = sn_asprintf("%s/notifications", gcli_get_apibase(ctx)); return gcli_fetch_list(ctx, url, &fl); } int gitea_notification_mark_as_read(struct gcli_ctx *ctx, char const *id) { return github_notification_mark_as_read(ctx, id); } gcli-2.3.0/src/github/000077500000000000000000000000001460062271200145035ustar00rootroot00000000000000gcli-2.3.0/src/github/api.c000066400000000000000000000035301460062271200154210ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 * github_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_github_get_error(ctx, &stream, &msg); json_close(&stream); if (rc < 0) return strdup("no message: failed to parser error response"); else return msg; } gcli-2.3.0/src/github/checks.c000066400000000000000000000055431460062271200161160ustar00rootroot00000000000000/* * Copyright 2022 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 #include int 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) { struct gcli_fetch_buffer buffer = {0}; char *url = NULL, *next_url = NULL; int rc = 0; assert(out); url = sn_asprintf("%s/repos/%s/%s/commits/%s/check-runs", gcli_get_apibase(ctx), owner, repo, ref); do { rc = gcli_fetch(ctx, url, &next_url, &buffer); if (rc == 0) { struct json_stream stream = {0}; json_open_buffer(&stream, buffer.data, buffer.length); parse_github_checks(ctx, &stream, out); json_close(&stream); } free(url); free(buffer.data); if (rc < 0) break; } while ((url = next_url) && ((int)(out->checks_size) < max || max < 0)); /* TODO: don't leak list on error */ free(next_url); return rc; } void gcli_github_check_free(struct gcli_github_check *check) { free(check->name); free(check->status); free(check->conclusion); free(check->started_at); free(check->completed_at); } void 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]); } free(list->checks); list->checks = NULL; list->checks_size = 0; } gcli-2.3.0/src/github/comments.c000066400000000000000000000060751460062271200165040ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 int 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; 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); 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); return rc; } int 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; struct gcli_fetch_list_ctx fl = { .listp = &out->comments, .sizep = &out->comments_size, .parse = (parsefn)parse_github_comments, .max = -1, }; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/repos/%s/%s/issues/%"PRIid"/comments", gcli_get_apibase(ctx), e_owner, e_repo, issue); free(e_owner); free(e_repo); return gcli_fetch_list(ctx, url, &fl); } gcli-2.3.0/src/github/config.c000066400000000000000000000030501460062271200161120ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 * github_make_authheader(struct gcli_ctx *ctx, char const *token) { (void) ctx; return sn_asprintf("Authorization: token %s", token); } gcli-2.3.0/src/github/forks.c000066400000000000000000000056151460062271200160020ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 int 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; struct gcli_fetch_list_ctx fl = { .listp = &list->forks, .sizep = &list->forks_size, .max = max, .parse = (parsefn)(parse_github_forks), }; *list = (struct gcli_fork_list) {0}; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf( "%s/repos/%s/%s/forks", gcli_get_apibase(ctx), e_owner, e_repo); free(e_owner); free(e_repo); return gcli_fetch_list(ctx, url, &fl); } int github_fork_create(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *_in) { char *url = NULL; char *e_owner = NULL; char *e_repo = NULL; char *post_data = NULL; sn_sv in = SV_NULL; int rc = 0; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/repos/%s/%s/forks", gcli_get_apibase(ctx), e_owner, e_repo); if (_in) { in = gcli_json_escape(SV((char *)_in)); post_data = sn_asprintf("{\"organization\":\""SV_FMT"\"}", SV_ARGS(in)); } rc = gcli_fetch_with_method(ctx, "POST", url, post_data, NULL, NULL); free(in.data); free(url); free(e_owner); free(e_repo); free(post_data); return rc; } gcli-2.3.0/src/github/gists.c000066400000000000000000000147101460062271200160030ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 #include /* /!\ Before changing this, see comment in gists.h /!\ */ int parse_github_gist_files_idiot_hack(struct gcli_ctx *ctx, json_stream *stream, struct gcli_gist *const gist) { (void) ctx; enum json_type next = JSON_NULL; gist->files = NULL; gist->files_size = 0; if ((next = json_next(stream)) != JSON_OBJECT) return gcli_error(ctx, "expected Gist Files Object"); while ((next = json_next(stream)) == JSON_STRING) { gist->files = realloc(gist->files, sizeof(*gist->files) * (gist->files_size + 1)); struct gcli_gist_file *it = &gist->files[gist->files_size++]; if (parse_github_gist_file(ctx, stream, it) < 0) return -1; } if (next != JSON_OBJECT_END) return gcli_error(ctx, "unclosed Gist Files Object"); return 0; } int gcli_get_gists(struct gcli_ctx *ctx, char const *user, int const max, struct gcli_gist_list *const list) { char *url = NULL; struct gcli_fetch_list_ctx fl = { .listp = &list->gists, .sizep = &list->gists_size, .parse = (parsefn)(parse_github_gists), .max = max, }; if (user) url = sn_asprintf("%s/users/%s/gists", gcli_get_apibase(ctx), user); else url = sn_asprintf("%s/gists", gcli_get_apibase(ctx)); return gcli_fetch_list(ctx, url, &fl); } int gcli_get_gist(struct gcli_ctx *ctx, char const *gist_id, struct gcli_gist *out) { char *url = NULL; struct gcli_fetch_buffer buffer = {0}; int rc = 0; url = sn_asprintf("%s/gists/%s", gcli_get_apibase(ctx), gist_id); rc = gcli_fetch(ctx, url, NULL, &buffer); if (rc == 0) { struct json_stream stream = {0}; json_open_buffer(&stream, buffer.data, buffer.length); json_set_streaming(&stream, 1); parse_github_gist(ctx, &stream, out); json_close(&stream); } free(buffer.data); free(url); return rc; } #define READ_SZ 4096 static char * read_file(FILE *f) { size_t size = 0; 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); if (bytes_read == 0) break; size += bytes_read; } if (out) { out = realloc(out, size + 1); out[size] = '\0'; } if (ferror(f)) { free(out); out = NULL; } return out; } int gcli_create_gist(struct gcli_ctx *ctx, struct gcli_new_gist opts) { char *content = NULL; char *post_data = NULL; char *url = NULL; int rc = 0; struct gcli_fetch_buffer fetch_buffer = {0}; struct gcli_jsongen gen = {0}; /* 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 * https://github.com/phadej/github/blob/master/src/GitHub/Data/Gists.hs * * From this we can infer that we're supposed to create a JSON * object like so: * * { * "description": "foobar", * "public": true, * "files": { * "barf.exe": { * "content": "#!/bin/sh\necho This file cannot be run in DOS mode" * } * } * } */ 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); /* Generate URL */ url = sn_asprintf("%s/gists", gcli_get_apibase(ctx)); /* Perferm fetch */ rc = gcli_fetch_with_method(ctx, "POST", url, post_data, NULL, &fetch_buffer); free(content); free(fetch_buffer.data); free(url); free(post_data); return rc; } int gcli_delete_gist(struct gcli_ctx *ctx, char const *gist_id) { char *url = NULL; struct gcli_fetch_buffer buffer = {0}; int rc = 0; url = sn_asprintf("%s/gists/%s", gcli_get_apibase(ctx), gist_id); rc = gcli_fetch_with_method(ctx, "DELETE", url, NULL, NULL, &buffer); free(buffer.data); free(url); return rc; } void gcli_gist_free(struct gcli_gist *g) { 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); free(g->files[j].language); free(g->files[j].url); free(g->files[j].type); } free(g->files); memset(g, 0, sizeof(*g)); } void 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]); free(list->gists); list->gists = NULL; list->gists_size = 0; } gcli-2.3.0/src/github/issues.c000066400000000000000000000354631460062271200161750ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 #include /* TODO: Remove this function once we use linked lists for storing * issues. * * This is an ugly hack caused by the sillyness of the Github API that * treats Pull Requests as issues and reports them to us when we * 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(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) { 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 | */ gcli_issue_free(&l[i-1]); memmove(&l[i-1], &l[i], sizeof(*l) * (*size - i)); *list = realloc(l, (--(*size)) * sizeof(*l)); } } } int github_fetch_issues(struct gcli_ctx *ctx, char *url, int const max, struct gcli_issue_list *const out) { struct gcli_fetch_list_ctx fl = { .listp = &out->issues, .sizep = &out->issues_size, .parse = (parsefn)(parse_github_issues), .filter = (filterfn)(github_hack_fixup_issues_that_are_actually_pulls), .max = max, }; return gcli_fetch_list(ctx, url, &fl); } static int get_milestone_id(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *milestone_name, gcli_id *out) { int rc = 0; struct gcli_milestone_list list = {0}; rc = github_get_milestones(ctx, owner, repo, -1, &list); if (rc < 0) return rc; rc = gcli_error(ctx, "%s: no such milestone", milestone_name); for (size_t i = 0; i < list.milestones_size; ++i) { if (strcmp(list.milestones[i].title, milestone_name) == 0) { *out = list.milestones[i].id; rc = 0; break; } } gcli_free_milestones(&list); return rc; } static int 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); /* first try parsing as a milestone ID, if it isn't one, * go looking for a similarly named milestone */ *out = strtoull(milestone, &endptr, 10); if (endptr == milestone + m_len) return 0; return get_milestone_id(ctx, owner, repo, milestone, 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; char *e_repo = NULL; char *e_author = NULL; char *e_label = NULL; char *e_milestone = NULL; if (details->milestone) { gcli_id milestone_id; int rc; rc = parse_github_milestone(ctx, owner, repo, details->milestone, &milestone_id); if (rc < 0) return rc; e_milestone = sn_asprintf("&milestone=%"PRIid, milestone_id); } if (details->author) { char *tmp = gcli_urlencode(details->author); e_author = sn_asprintf("&creator=%s", tmp); free(tmp); } if (details->label) { char *tmp = gcli_urlencode(details->label); e_label = sn_asprintf("&labels=%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", gcli_get_apibase(ctx), e_owner, e_repo, details->all ? "all" : "open", e_author ? e_author : "", e_label ? e_label : "", e_milestone ? e_milestone : ""); free(e_milestone); free(e_author); free(e_label); free(e_owner); free(e_repo); return github_fetch_issues(ctx, url, max, out); } int 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; struct gcli_fetch_buffer buffer = {0}; struct json_stream parser = {0}; 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); rc = gcli_fetch(ctx, url, NULL, &buffer); if (rc == 0) { json_open_buffer(&parser, buffer.data, buffer.length); json_set_streaming(&parser, true); parse_github_issue(ctx, &parser, out); json_close(&parser); } free(url); free(e_owner); free(e_repo); free(buffer.data); return rc; } 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, *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); payload = sn_asprintf("{ \"state\": \"%s\"}", state); rc = gcli_fetch_with_method(ctx, "PATCH", url, payload, NULL, NULL); free(payload); free(url); free(e_owner); free(e_repo); return rc; } int github_issue_close(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const issue) { return github_issue_patch_state(ctx, owner, repo, issue, "closed"); } 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(struct gcli_ctx *const ctx, struct gcli_submit_issue_options *const opts, struct gcli_issue *const out) { char *e_owner = NULL, *e_repo = NULL, *payload = NULL, *url = NULL; struct gcli_jsongen gen = {0}; struct gcli_fetch_buffer buffer = {0}, *_buffer = NULL; int rc = 0; /* 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); payload = gcli_jsongen_to_string(&gen); gcli_jsongen_free(&gen); /* 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); /* only read the resulting data if the issue data has been requested */ if (out) _buffer = &buffer; rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, _buffer); if (out && rc == 0) { struct json_stream stream = {0}; json_open_buffer(&stream, buffer.data, buffer.length); rc = parse_github_issue(ctx, &stream, out); json_close(&stream); } free(buffer.data); free(payload); free(url); return rc; } int github_issue_assign(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const issue_number, char const *assignee) { struct gcli_jsongen gen = {0}; char *url = NULL, *payload = NULL, *e_owner = NULL, *e_repo = NULL; int rc = 0; /* 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); 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(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; char *list = NULL; int rc = 0; assert(labels_size > 0); url = sn_asprintf("%s/repos/%s/%s/issues/%"PRIid"/labels", gcli_get_apibase(ctx), owner, repo, issue); list = sn_join_with(labels, labels_size, "\",\""); data = sn_asprintf("{ \"labels\": [\"%s\"]}", list); rc = gcli_fetch_with_method(ctx, "POST", url, data, NULL, NULL); free(url); free(data); free(list); return rc; } int 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; int rc = 0; if (labels_size != 1) { return gcli_error(ctx, "GitHub only supports removing labels from " "issues one by one."); } e_label = gcli_urlencode(labels[0]); url = sn_asprintf("%s/repos/%s/%s/issues/%"PRIid"/labels/%s", gcli_get_apibase(ctx), owner, repo, issue, e_label); rc = gcli_fetch_with_method(ctx, "DELETE", url, NULL, NULL, NULL); free(url); free(e_label); return rc; } int github_issue_set_milestone(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const issue, gcli_id const milestone) { char *url, *e_owner, *e_repo, *body; int rc; 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); body = sn_asprintf("{ \"milestone\": %"PRIid" }", milestone); rc = gcli_fetch_with_method(ctx, "PATCH", url, body, NULL, NULL); free(body); free(url); free(e_repo); free(e_owner); return rc; } int 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; char const *payload; int rc; 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); payload = "{ \"milestone\": null }"; rc = gcli_fetch_with_method(ctx, "PATCH", url, payload, NULL, NULL); free(url); free(e_repo); free(e_owner); return rc; } 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) { char *url, *e_owner, *e_repo, *payload; struct gcli_jsongen gen = {0}; int rc; /* 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); free(e_owner); free(e_repo); /* Generate payload */ gcli_jsongen_init(&gen); gcli_jsongen_begin_object(&gen); { gcli_jsongen_objmember(&gen, "title"); gcli_jsongen_string(&gen, new_title); } gcli_jsongen_end_object(&gen); payload = gcli_jsongen_to_string(&gen); gcli_jsongen_free(&gen); rc = gcli_fetch_with_method(ctx, "PATCH", url, payload, NULL, NULL); free(payload); free(url); return rc; } gcli-2.3.0/src/github/labels.c000066400000000000000000000075531460062271200161230ustar00rootroot00000000000000/* * Copyright 2022 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 int 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; struct gcli_fetch_list_ctx fl = { .listp = &out->labels, .sizep= &out->labels_size, .parse = (parsefn)(parse_github_labels), .max = max, }; *out = (struct gcli_label_list) {0}; url = sn_asprintf( "%s/repos/%s/%s/labels", gcli_get_apibase(ctx), owner, reponame); return gcli_fetch_list(ctx, url, &fl); } int github_create_label(struct gcli_ctx *ctx, char const *owner, char const *repo, struct gcli_label *const label) { 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}; /* Generate payload */ colour = 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, "description"); gcli_jsongen_string(&gen, label->description); gcli_jsongen_objmember(&gen, "color"); gcli_jsongen_string(&gen, colour); } gcli_jsongen_end_object(&gen); payload = gcli_jsongen_to_string(&gen); gcli_jsongen_free(&gen); free(colour); /* 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); parse_github_label(ctx, &stream, label); json_close(&stream); } free(url); free(payload); free(buffer.data); return rc; } int github_delete_label(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *label) { char *url = NULL; char *e_label = NULL; int rc = 0; e_label = gcli_urlencode(label); /* DELETE /repos/{owner}/{repo}/labels/{name} */ url = sn_asprintf("%s/repos/%s/%s/labels/%s", gcli_get_apibase(ctx), owner, repo, e_label); rc = gcli_fetch_with_method(ctx, "DELETE", url, NULL, NULL, NULL); free(url); free(e_label); return rc; } gcli-2.3.0/src/github/milestones.c000066400000000000000000000135661460062271200170440ustar00rootroot00000000000000/* * 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 #include #include int github_get_milestones(struct gcli_ctx *ctx, char const *const owner, char const *const repo, int const max, struct gcli_milestone_list *const out) { char *url, *e_owner, *e_repo; struct gcli_fetch_list_ctx fl = { .listp = &out->milestones, .sizep = &out->milestones_size, .parse = (parsefn)parse_github_milestones, .max = max, }; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/repos/%s/%s/milestones", gcli_get_apibase(ctx), e_owner, e_repo); free(e_owner); free(e_repo); return gcli_fetch_list(ctx, url, &fl); } int github_get_milestone(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone, struct gcli_milestone *const out) { char *url, *e_owner, *e_repo; struct gcli_fetch_buffer buffer = {0}; int rc = 0; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/repos/%s/%s/milestones/%"PRIid, gcli_get_apibase(ctx), e_owner, e_repo, milestone); free(e_repo); free(e_owner); rc = gcli_fetch(ctx, url, NULL, &buffer); if (rc == 0) { struct json_stream stream = {0}; json_open_buffer(&stream, buffer.data, buffer.length); parse_github_milestone(ctx, &stream, out); json_close(&stream); } free(url); free(buffer.data); return rc; } int github_milestone_get_issues(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone, struct gcli_issue_list *const out) { char *url, *e_owner, *e_repo; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/repos/%s/%s/issues?milestone=%"PRIid"&state=all", gcli_get_apibase(ctx), e_owner, e_repo, milestone); free(e_repo); free(e_owner); /* URL is freed by github_fetch_issues */ return github_fetch_issues(ctx, url, -1, out); } int github_create_milestone(struct gcli_ctx *ctx, struct gcli_milestone_create_args const *args) { char *url, *e_owner, *e_repo; char *json_body, *description; int rc = 0; e_owner = gcli_urlencode(args->owner); e_repo = gcli_urlencode(args->repo); if (args->description) { /* This is fine :-) */ char *e_description = gcli_json_escape_cstr(args->description); description = sn_asprintf(",\"description\": \"%s\"", e_description); free(e_description); } else { description = strdup(""); } json_body = sn_asprintf( "{" " \"title\" : \"%s\"" " %s" "}", args->title, description); url = sn_asprintf("%s/repos/%s/%s/milestones", gcli_get_apibase(ctx), e_owner, e_repo); rc = gcli_fetch_with_method(ctx, "POST", url, json_body, NULL, NULL); free(json_body); free(description); free(url); free(e_repo); free(e_owner); return rc; } int 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; int rc = 0; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/repos/%s/%s/milestones/%"PRIid, gcli_get_apibase(ctx), e_owner, e_repo, milestone); rc = gcli_fetch_with_method(ctx, "DELETE", url, NULL, NULL, NULL); free(url); free(e_repo); free(e_owner); return rc; } int github_milestone_set_duedate(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone, char const *const date) { char *url, *e_owner, *e_repo, *payload, norm_date[21] = {0}; int rc = 0; rc = gcli_normalize_date(ctx, DATEFMT_ISO8601, date, norm_date, sizeof norm_date); if (rc < 0) return rc; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/repos/%s/%s/milestones/%"PRIid, gcli_get_apibase(ctx), e_owner, e_repo, milestone); payload = sn_asprintf("{ \"due_on\": \"%s\"}", norm_date); rc = gcli_fetch_with_method(ctx, "PATCH", url, payload, NULL, NULL); free(payload); free(url); free(e_repo); free(e_owner); return rc; } gcli-2.3.0/src/github/pulls.c000066400000000000000000000415531460062271200160160ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 #include /* The following function is a hack around the stupidity of the Github * REST API. With Github's REST API it is impossible to explicitly * request a list of pull requests filtered by a given author. I guess * what you're supposed to be doing is to do the filtering * yourself. * * What this function does is to go through the list of pull requests * and removes the ones that are not authored by the given * username. It then shrinks the allocation size of the list such that * we don't confuse the malloc allocator. Really, it shouldn't change * the actual storage and instead just record the new size of the * allocation. */ static bool pull_has_label(struct gcli_pull const *p, char const *const label) { for (size_t i = 0; i < p->labels_size; ++i) { if (strcmp(p->labels[i], label) == 0) return true; } return false; } static void 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) { struct gcli_pull *pulls = *listp; struct gcli_pull *pull = &pulls[i-1]; bool should_remove = false; if (details->author && strcmp(details->author, pull->author)) should_remove = true; if (details->label && !pull_has_label(pull, details->label)) should_remove = true; if (details->milestone && pull->milestone && strcmp(pull->milestone, details->milestone)) should_remove = true; if (should_remove) { gcli_pull_free(pull); memmove(pull, &pulls[i], sizeof(*pulls) * (*sizep - i)); *listp = realloc(pulls, sizeof(*pulls) * (--(*sizep))); } } } static int github_fetch_pulls(struct gcli_ctx *ctx, char *url, struct gcli_pull_fetch_details const *details, int max, struct gcli_pull_list *const list) { struct gcli_fetch_list_ctx fl = { .listp = &list->pulls, .sizep = &list->pulls_size, .parse = (parsefn)(parse_github_pulls), .filter = (filterfn)(github_pulls_filter), .userdata = details, .max = max, }; return gcli_fetch_list(ctx, url, &fl); } static int search_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) { 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:pull-request%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_pull_search_result(ctx, &stream, out); json_close(&stream); free(buffer.data); error_fetch: free(url); return rc; } static int list_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; char *e_repo = NULL; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf( "%s/repos/%s/%s/pulls?state=%s", gcli_get_apibase(ctx), e_owner, e_repo, details->all ? "all" : "open"); free(e_owner); free(e_repo); return github_fetch_pulls(ctx, url, details, max, list); } int github_search_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) { if (details->search_term) return search_pulls(ctx, owner, repo, details, max, list); else return list_pulls(ctx, owner, repo, details, max, list); } int github_pull_get_patch(struct gcli_ctx *ctx, FILE *stream, char const *owner, char const *repo, gcli_id const pr_number) { char *url = 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/pulls/%"PRIid, gcli_get_apibase(ctx), e_owner, e_repo, pr_number); rc = gcli_curl(ctx, stream, url, "Accept: application/vnd.github.v3.patch"); free(e_owner); free(e_repo); free(url); return rc; } int github_pull_get_diff(struct gcli_ctx *ctx, FILE *stream, char const *owner, char const *repo, gcli_id const pr_number) { char *url = 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/pulls/%"PRIid, gcli_get_apibase(ctx), e_owner, e_repo, pr_number); rc = gcli_curl(ctx, stream, url, "Accept: application/vnd.github.v3.diff"); free(e_owner); free(e_repo); free(url); return rc; } /* TODO: figure out a way to get rid of the 3 consecutive urlencode * calls */ static int github_pull_delete_head_branch(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const pr_number) { struct gcli_pull pull = {0}; char *url, *e_owner, *e_repo; char const *head_branch; int rc = 0; github_get_pull(ctx, owner, repo, pr_number, &pull); head_branch = strchr(pull.head_label, ':'); head_branch++; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/repos/%s/%s/git/refs/heads/%s", gcli_get_apibase(ctx), e_owner, e_repo, head_branch); rc = gcli_fetch_with_method(ctx, "DELETE", url, NULL, NULL, NULL); free(url); free(e_owner); free(e_repo); gcli_pull_free(&pull); return rc; } int 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; char *e_owner = NULL; char *e_repo = NULL; char const *data = "{}"; bool const squash = flags & GCLI_PULL_MERGE_SQUASH; bool const delete_source = flags & GCLI_PULL_MERGE_DELETEHEAD; int rc = 0; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf( "%s/repos/%s/%s/pulls/%"PRIid"/merge?merge_method=%s", gcli_get_apibase(ctx), e_owner, e_repo, pr_number, squash ? "squash" : "merge"); rc = gcli_fetch_with_method(ctx, "PUT", url, data, NULL, NULL); if (rc == 0 && delete_source) rc = github_pull_delete_head_branch(ctx, owner, repo, pr_number); free(url); free(e_owner); free(e_repo); return rc; } 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, *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); free(e_repo); free(e_owner); rc = gcli_fetch_with_method(ctx, "PATCH", url, payload, NULL, NULL); free(url); free(payload); return rc; } int github_pull_close(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const pr) { return github_pull_patch_state(ctx, owner, repo, pr, "closed"); } 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"); } 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}; 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); return rc; } int github_perform_submit_pull(struct gcli_ctx *ctx, struct gcli_submit_pull_options *opts) { 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; 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); 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 || 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); 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(payload); free(url); return rc; } static void filter_commit_short_sha(struct gcli_commit **listp, size_t *sizep, void *_data) { (void) _data; for (size_t i = 0; i < *sizep; ++i) (*listp)[i].sha = sn_strndup((*listp)[i].long_sha, 8); } int 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; struct gcli_fetch_list_ctx fl = { .listp = &out->commits, .sizep = &out->commits_size, .max = -1, .parse = (parsefn)(parse_github_commits), .filter = (filterfn)(filter_commit_short_sha), }; 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); free(e_owner); free(e_repo); return gcli_fetch_list(ctx, url, &fl); } int 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; 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); free(e_owner); free(e_repo); rc = gcli_fetch(ctx, url, NULL, &json_buffer); if (rc == 0) { struct json_stream stream = {0}; json_open_buffer(&stream, json_buffer.data, json_buffer.length); parse_github_pull(ctx, &stream, out); json_close(&stream); } free(url); free(json_buffer.data); return rc; } int 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}; /* This is kind of a hack, but it works! * Yes, even a few months later I agree that this is a hack. */ snprintf(refname, sizeof refname, "refs%%2Fpull%%2F%"PRIid"%%2Fhead", pr_number); return github_get_checks(ctx, owner, repo, refname, -1, (struct github_check_list *)out); } int 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; struct gcli_jsongen gen = {0}; /* URL-encode repo and owner */ e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); /* /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers */ url = sn_asprintf("%s/repos/%s/%s/pulls/%"PRIid"/requested_reviewers", gcli_get_apibase(ctx), e_owner, e_repo, pr_number); /* Generate payload */ gcli_jsongen_init(&gen); gcli_jsongen_begin_object(&gen); { gcli_jsongen_objmember(&gen, "reviewers"); gcli_jsongen_begin_array(&gen); gcli_jsongen_string(&gen, username); gcli_jsongen_end_array(&gen); } gcli_jsongen_end_object(&gen); payload = gcli_jsongen_to_string(&gen); gcli_jsongen_free(&gen); /* Perform request */ rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, NULL); /* Cleanup */ free(payload); free(url); free(e_repo); free(e_owner); return rc; } int 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; struct gcli_jsongen gen = {0}; /* Generate the 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, pull); free(e_owner); free(e_repo); /* Generate the payload */ gcli_jsongen_init(&gen); gcli_jsongen_begin_object(&gen); { gcli_jsongen_objmember(&gen, "title"); gcli_jsongen_string(&gen, new_title); } gcli_jsongen_end_object(&gen); payload = gcli_jsongen_to_string(&gen); gcli_jsongen_free(&gen); /* perform request */ rc = gcli_fetch_with_method(ctx, "PATCH", url, payload, NULL, NULL); /* Cleanup */ free(payload); free(url); return rc; } gcli-2.3.0/src/github/releases.c000066400000000000000000000137431460062271200164620ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 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; struct gcli_fetch_list_ctx fl = { .listp = &list->releases, .sizep = &list->releases_size, .max = max, .parse = (parsefn)(parse_github_releases), }; *list = (struct gcli_release_list) {0}; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf( "%s/repos/%s/%s/releases", gcli_get_apibase(ctx), e_owner, e_repo); free(e_owner); free(e_repo); return gcli_fetch_list(ctx, url, &fl); } static void github_parse_single_release(struct gcli_ctx *ctx, struct gcli_fetch_buffer buffer, struct gcli_release *const out) { struct json_stream stream = {0}; json_open_buffer(&stream, buffer.data, buffer.length); json_set_streaming(&stream, 1); parse_github_release(ctx, &stream, out); json_close(&stream); } static int github_get_upload_url(struct gcli_ctx *ctx, struct gcli_release *const it, char **out) { 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; *out = sn_strndup(it->upload_url, len); return 0; } static int 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}; struct gcli_fetch_buffer buffer = {0}; int rc = 0; file_content.length = sn_mmap_file(asset.path, (void **)&file_content.data); if (file_content.length == 0) return -1; /* TODO: URL escape this */ req = sn_asprintf("%s?name=%s", url, asset.name); rc = gcli_post_upload( ctx, req, "application/octet-stream", /* HACK */ file_content.data, file_content.length, &buffer); free(req); free(buffer.data); return rc; } int github_create_release(struct gcli_ctx *ctx, struct gcli_new_release const *release) { 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; /* 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); free(e_owner); free(e_repo); rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, &buffer); if (rc < 0) goto out; github_parse_single_release(ctx, buffer, &response); rc = github_get_upload_url(ctx, &response, &upload_url); if (rc < 0) goto out; for (size_t i = 0; i < release->assets_size; ++i) { printf("INFO : Uploading asset %s...\n", release->assets[i].path); rc = github_upload_release_asset(ctx, upload_url, release->assets[i]); if (rc < 0) break; } out: gcli_release_free(&response); free(upload_url); free(buffer.data); free(url); free(payload); return rc; } int github_delete_release(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *id) { char *url = 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/releases/%s", gcli_get_apibase(ctx), e_owner, e_repo, id); rc = gcli_fetch_with_method(ctx, "DELETE", url, NULL, NULL, NULL); free(url); free(e_owner); free(e_repo); return rc; } gcli-2.3.0/src/github/repos.c000066400000000000000000000131311460062271200157760ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 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; struct gcli_fetch_list_ctx lf = { .listp = &list->repos, .sizep = &list->repos_size, .max = max, .parse = (parsefn)(parse_github_repos), }; e_owner = gcli_urlencode(owner); /* Github is a little stupid in that it distinguishes * organizations and users. Thus, we have to find out, whether the * param is a user or an actual organization. */ url = sn_asprintf("%s/users/%s", gcli_get_apibase(ctx), e_owner); /* 0 = failed, 1 = success, -1 = error (just like a BOOL in Win32 * /sarc) */ rc = gcli_curl_test_success(ctx, url); if (rc < 0) { free(url); return rc; } if (rc) { /* it is a user */ free(url); url = sn_asprintf("%s/users/%s/repos", gcli_get_apibase(ctx), e_owner); } else { /* this is an actual organization */ free(url); url = sn_asprintf("%s/orgs/%s/repos", gcli_get_apibase(ctx), e_owner); } free(e_owner); return gcli_fetch_list(ctx, url, &lf); } int github_get_own_repos(struct gcli_ctx *ctx, int const max, struct gcli_repo_list *const list) { char *url = NULL; struct gcli_fetch_list_ctx fl = { .listp = &list->repos, .sizep = &list->repos_size, .max = max, .parse = (parsefn)(parse_github_repos), }; url = sn_asprintf("%s/user/repos", gcli_get_apibase(ctx)); return gcli_fetch_list(ctx, url, &fl); } int github_repo_delete(struct gcli_ctx *ctx, char const *owner, char const *repo) { char *url = 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", gcli_get_apibase(ctx), e_owner, e_repo); rc = gcli_fetch_with_method(ctx, "DELETE", url, NULL, NULL, NULL); free(e_owner); free(e_repo); free(url); return rc; } int github_repo_create(struct gcli_ctx *ctx, struct gcli_repo_create_options const *options, struct gcli_repo *const out) { char *url, *payload; struct gcli_fetch_buffer buffer = {0}; struct gcli_jsongen gen = {0}; struct json_stream stream = {0}; int rc = 0; /* Request preparation */ url = sn_asprintf("%s/user/repos", gcli_get_apibase(ctx)); /* Construct payload */ 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, payload, NULL, out ? &buffer : NULL); if (rc == 0 && out) { json_open_buffer(&stream, buffer.data, buffer.length); parse_github_repo(ctx, &stream, out); json_close(&stream); } /* Cleanup */ free(buffer.data); free(payload); free(url); return rc; } int github_repo_set_visibility(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_repo_visibility vis) { char *url; char *e_owner, *e_repo; char const *vis_str; char *payload; int rc; switch (vis) { case GCLI_REPO_VISIBILITY_PRIVATE: vis_str = "private"; break; case GCLI_REPO_VISIBILITY_PUBLIC: vis_str = "public"; break; default: assert(false && "Invalid visibility"); return gcli_error(ctx, "bad visibility level"); } e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/repos/%s/%s", gcli_get_apibase(ctx), e_owner, e_repo); payload = sn_asprintf("{ \"visibility\": \"%s\" }", vis_str); rc = gcli_fetch_with_method(ctx, "PATCH", url, payload, NULL, NULL); free(payload); free(e_owner); free(e_repo); free(url); return rc; } gcli-2.3.0/src/github/sshkeys.c000066400000000000000000000036751460062271200163530ustar00rootroot00000000000000/* * 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 /* NOTE(Nico): This looks strange because it reuses the Gitlab code * because the APIs are the same. */ #include int github_get_sshkeys(struct gcli_ctx *ctx, struct gcli_sshkey_list *out) { return gitlab_get_sshkeys(ctx, out); } int 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(struct gcli_ctx *ctx, gcli_id const id) { return gitlab_delete_sshkey(ctx, id); } gcli-2.3.0/src/github/status.c000066400000000000000000000043341460062271200161760ustar00rootroot00000000000000/* * Copyright 2022 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 int github_get_notifications(struct gcli_ctx *ctx, int const max, struct gcli_notification_list *const out) { char *url = NULL; struct gcli_fetch_list_ctx fl = { .listp = &out->notifications, .sizep = &out->notifications_size, .parse = (parsefn)(parse_github_notifications), .max = max, }; url = sn_asprintf("%s/notifications", gcli_get_apibase(ctx)); return gcli_fetch_list(ctx, url, &fl); } int github_notification_mark_as_read(struct gcli_ctx *ctx, char const *id) { char *url = NULL; int rc = 0; url = sn_asprintf( "%s/notifications/threads/%s", gcli_get_apibase(ctx), id); rc = gcli_fetch_with_method(ctx, "PATCH", url, NULL, NULL, NULL); free(url); return rc; } gcli-2.3.0/src/gitlab/000077500000000000000000000000001460062271200144635ustar00rootroot00000000000000gcli-2.3.0/src/gitlab/api.c000066400000000000000000000055341460062271200154070ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 * gitlab_api_error_string(struct gcli_ctx *ctx, struct gcli_fetch_buffer *const buf) { char *msg = NULL; int rc; 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 || 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(struct gcli_ctx *ctx, char const *user_name) { struct gcli_fetch_buffer buffer = {0}; struct json_stream stream = {0}; char *url = NULL; char *e_username; long uid = -1; int rc; e_username = gcli_urlencode(user_name); url = sn_asprintf("%s/users?username=%s", gcli_get_apibase(ctx), e_username); uid = gcli_fetch(ctx, url, NULL, &buffer); if (uid == 0) { json_open_buffer(&stream, buffer.data, buffer.length); json_set_streaming(&stream, 1); uid = rc = gcli_json_advance(ctx, &stream, "[{s", "id"); if (rc == 0) { rc = get_long(ctx, &stream, &uid); json_close(&stream); } } free(e_username); free(url); free(buffer.data); return uid; } gcli-2.3.0/src/gitlab/comments.c000066400000000000000000000074271460062271200164660ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 int 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; struct gcli_jsongen gen = {0}; int rc = 0; e_owner = gcli_urlencode(opts.owner); e_repo = gcli_urlencode(opts.repo); switch (opts.target_type) { case ISSUE_COMMENT: type = "issues"; break; case PR_COMMENT: type = "merge_requests"; break; } 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); url = sn_asprintf("%s/projects/%s%%2F%s/%s/%"PRIid"/notes", gcli_get_apibase(ctx), e_owner, e_repo, type, opts.target_id); rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, out); free(payload); free(url); free(e_owner); free(e_repo); return rc; } int 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); struct gcli_fetch_list_ctx fl = { .listp = &out->comments, .sizep = &out->comments_size, .parse = (parsefn)parse_gitlab_comments, .max = -1, }; char *url = sn_asprintf( "%s/projects/%s%%2F%s/merge_requests/%"PRIid"/notes", gcli_get_apibase(ctx), e_owner, e_repo, mr); free(e_owner); free(e_repo); return gcli_fetch_list(ctx, url, &fl); } int 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); struct gcli_fetch_list_ctx fl = { .listp = &out->comments, .sizep = &out->comments_size, .parse = (parsefn)parse_gitlab_comments, .max = -1, }; char *url = sn_asprintf( "%s/projects/%s%%2F%s/issues/%"PRIid"/notes", gcli_get_apibase(ctx), e_owner, e_repo, issue); free(e_owner); free(e_repo); return gcli_fetch_list(ctx, url, &fl); } gcli-2.3.0/src/gitlab/config.c000066400000000000000000000030431460062271200160740ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 * gitlab_make_authheader(struct gcli_ctx *ctx, char const *token) { (void) ctx; return sn_asprintf("PRIVATE-TOKEN: %s", token); } gcli-2.3.0/src/gitlab/forks.c000066400000000000000000000056171460062271200157640ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 int 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; struct gcli_fetch_list_ctx fl = { .listp = &list->forks, .sizep = &list->forks_size, .parse = (parsefn)parse_gitlab_forks, .max = max, }; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); *list = (struct gcli_fork_list) {0}; url = sn_asprintf("%s/projects/%s%%2F%s/forks", gcli_get_apibase(ctx), e_owner, e_repo); free(e_owner); free(e_repo); return gcli_fetch_list(ctx, url, &fl); } int gitlab_fork_create(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *_in) { char *url = NULL; char *e_owner = NULL; char *e_repo = NULL; char *post_data = NULL; sn_sv in = SV_NULL; int rc = 0; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/projects/%s%%2F%s/fork", gcli_get_apibase(ctx), e_owner, e_repo); if (_in) { in = gcli_json_escape(SV((char *)_in)); post_data = sn_asprintf("{\"namespace_path\":\""SV_FMT"\"}", SV_ARGS(in)); } rc = gcli_fetch_with_method(ctx, "POST", url, post_data, NULL, NULL); free(in.data); free(url); free(post_data); free(e_owner); free(e_repo); return rc; } gcli-2.3.0/src/gitlab/issues.c000066400000000000000000000316411460062271200161470ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 /** Given the url fetch issues */ int gitlab_fetch_issues(struct gcli_ctx *ctx, char *url, int const max, struct gcli_issue_list *const out) { struct gcli_fetch_list_ctx fl = { .listp = &out->issues, .sizep = &out->issues_size, .max = max, .parse = (parsefn)(parse_gitlab_issues), }; return gcli_fetch_list(ctx, url, &fl); } int 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; char *e_repo = NULL; 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); if (details->author) { char *tmp = gcli_urlencode(details->author); e_author = sn_asprintf("%cauthor_username=%s", details->all ? '?' : '&', tmp); free(tmp); } if (details->label) { char *tmp = gcli_urlencode(details->label); int const should_do_qmark = details->all && !details->author; e_labels = sn_asprintf("%clabels=%s", should_do_qmark ? '?' : '&', tmp); free(tmp); } if (details->milestone) { char *tmp = gcli_urlencode(details->milestone); int const should_do_qmark = details->all && !details->author && !details->label; e_milestone = sn_asprintf("%cmilestone=%s", should_do_qmark ? '?' : '&', tmp); free(tmp); } 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_search ? e_search : ""); free(e_milestone); free(e_author); free(e_labels); free(e_owner); free(e_repo); return gitlab_fetch_issues(ctx, url, max, out); } int 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; struct gcli_fetch_buffer buffer = {0}; struct json_stream parser = {0}; 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); rc = gcli_fetch(ctx, url, NULL, &buffer); if (rc == 0) { json_open_buffer(&parser, buffer.data, buffer.length); json_set_streaming(&parser, true); parse_gitlab_issue(ctx, &parser, out); json_close(&parser); } free(url); free(e_owner); free(e_repo); free(buffer.data); return rc; } 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, *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); 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_close(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const issue) { return gitlab_issue_patch_state(ctx, owner, repo, issue, "close"); } 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(struct gcli_ctx *const ctx, struct gcli_submit_issue_options *const opts, struct gcli_issue *const out) { char *e_owner = NULL, *e_repo = NULL, *url = NULL, *payload = NULL; int rc = 0; struct gcli_fetch_buffer buffer = {0}, *_buffer = NULL; struct gcli_jsongen gen = {0}; 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); url = sn_asprintf("%s/projects/%s%%2F%s/issues", gcli_get_apibase(ctx), e_owner, e_repo); free(e_owner); free(e_repo); if (out) _buffer = &buffer; rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, _buffer); if (rc == 0 && out) { struct json_stream stream = {0}; json_open_buffer(&stream, buffer.data, buffer.length); rc = parse_gitlab_issue(ctx, &stream, out); json_close(&stream); } free(buffer.data); free(payload); free(url); return rc; } int 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; 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); free(e_owner); free(e_repo); rc = gcli_fetch_with_method(ctx, "PUT", url, payload, NULL, NULL); free(url); free(payload); return rc; } 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, *payload = NULL, *label_list = NULL, *e_owner = NULL, *e_repo = NULL; struct gcli_jsongen gen = {0}; int rc = 0; /* 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, ","); 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); free(label_list); 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); free(e_owner); free(e_repo); rc = gcli_fetch_with_method(ctx, "PUT", url, payload, NULL, NULL); free(url); free(payload); return rc; } int 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) { char *url, *e_owner, *e_repo; int rc = 0; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/projects/%s%%2F%s/issues/%"PRIid"?milestone_id=%"PRIid, gcli_get_apibase(ctx), e_owner, e_repo, issue, milestone); rc = gcli_fetch_with_method(ctx, "PUT", url, NULL, NULL, NULL); free(url); free(e_repo); free(e_owner); return rc; } int 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; char const *payload; int rc; /* The Gitlab API says: * * milestone_id: The global ID of a milestone to assign the * issue to. Set to 0 or provide an empty value to unassign a * milestone. * * However, the documentation is plain wrong and trying to set it * to zero does absolutely nothing. What do you expect from an * enterprise quality product?! Certainly not this kind of spanish * inquisition. Fear and surprise. That's the Gitlab API in a * nutshell.*/ 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); payload = "{ \"milestone_id\": null }"; rc = gcli_fetch_with_method(ctx, "PUT", url, payload, NULL, NULL); free(url); free(e_repo); free(e_owner); return rc; } int 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; struct gcli_jsongen gen = {0}; int rc; /* 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); free(e_owner); free(e_repo); /* Generate payload */ gcli_jsongen_init(&gen); gcli_jsongen_begin_object(&gen); { gcli_jsongen_objmember(&gen, "title"); gcli_jsongen_string(&gen, new_title); } gcli_jsongen_end_object(&gen); payload = gcli_jsongen_to_string(&gen); gcli_jsongen_free(&gen); rc = gcli_fetch_with_method(ctx, "PUT", url, payload, NULL, NULL); free(url); free(payload); return rc; } gcli-2.3.0/src/gitlab/labels.c000066400000000000000000000075641460062271200161050ustar00rootroot00000000000000/* * Copyright 2022 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 int 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; struct gcli_fetch_list_ctx fl = { .listp = &out->labels, .sizep = &out->labels_size, .max = max, .parse = (parsefn)(parse_gitlab_labels), }; *out = (struct gcli_label_list) {0}; url = sn_asprintf("%s/projects/%s%%2F%s/labels", gcli_get_apibase(ctx), owner, repo); return gcli_fetch_list(ctx, url, &fl); } int gitlab_create_label(struct gcli_ctx *ctx, char const *owner, char const *repo, struct gcli_label *const label) { 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), 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); json_set_streaming(&stream, 1); parse_gitlab_label(ctx, &stream, label); json_close(&stream); } free(payload); free(url); free(buffer.data); return rc; } int gitlab_delete_label(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *label) { char *url = NULL; char *e_label = NULL; int rc; e_label = gcli_urlencode(label); url = sn_asprintf("%s/projects/%s%%2F%s/labels/%s", gcli_get_apibase(ctx), owner, repo, e_label); rc = gcli_fetch_with_method(ctx, "DELETE", url, NULL, NULL, NULL); free(url); free(e_label); return rc; } gcli-2.3.0/src/gitlab/merge_requests.c000066400000000000000000000563011460062271200176660ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 #include #include /* for nanosleep */ /* Workaround because gitlab doesn't give us an explicit field for * this. */ static void 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"); } } int gitlab_fetch_mrs(struct gcli_ctx *ctx, char *url, int const max, struct gcli_pull_list *const list) { int rc = 0; struct gcli_fetch_list_ctx fl = { .listp = &list->pulls, .sizep = &list->pulls_size, .max = max, .parse = (parsefn)(parse_gitlab_mrs), }; rc = gcli_fetch_list(ctx, url, &fl); /* TODO: don't leak the list on error */ if (rc == 0) gitlab_mrs_fixup(list); return rc; } int 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; char *e_repo = NULL; char *e_author = NULL; char *e_label = NULL; char *e_milestone = NULL; char *e_search = NULL; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); if (details->author) { char *tmp = gcli_urlencode(details->author); bool const need_qmark = details->all; e_author = sn_asprintf("%cauthor_username=%s", need_qmark ? '?' : '&', tmp); free(tmp); } if (details->label) { char *tmp = gcli_urlencode(details->label); bool const need_qmark = details->all && !details->author; e_label = sn_asprintf("%clabels=%s", need_qmark ? '?' : '&', tmp); free(tmp); } if (details->milestone) { char *tmp = gcli_urlencode(details->milestone); bool const need_qmark = details->all && !details->author && !details->label; e_milestone = sn_asprintf("%cmilestone=%s", need_qmark ? '?' : '&', tmp); free(tmp); } if (details->search_term) { char *tmp = gcli_urlencode(details->search_term); bool const need_qmark = details->all && !details->author && !details->label && !details->milestone; e_search = sn_asprintf("%csearch=%s", need_qmark ? '?' : '&', tmp); free(tmp); } url = sn_asprintf("%s/projects/%s%%2F%s/merge_requests%s%s%s%s%s", gcli_get_apibase(ctx), e_owner, e_repo, details->all ? "" : "?state=opened", e_author ? e_author : "", e_label ? e_label : "", e_milestone ? e_milestone : "", e_search ? e_search : ""); free(e_search); free(e_milestone); free(e_label); free(e_author); free(e_owner); free(e_repo); return gitlab_fetch_mrs(ctx, url, max, list); } static void gitlab_free_diff(struct gitlab_diff *diff) { free(diff->diff); free(diff->old_path); free(diff->new_path); free(diff->a_mode); free(diff->b_mode); memset(diff, 0, sizeof(*diff)); } static void gitlab_free_diffs(struct gitlab_diff_list *list) { for (size_t i = 0; i < list->diffs_size; ++i) { gitlab_free_diff(&list->diffs[i]); } free(list->diffs); list->diffs = NULL; 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(struct gcli_ctx *ctx, FILE *stream, char const *const e_owner, char const *const e_repo, char const *const prev_commit_sha, struct gcli_commit const *const commit) { char *url; int rc; struct gitlab_diff_list list = {0}; struct gcli_fetch_list_ctx fl = { .listp = &list.diffs, .sizep = &list.diffs_size, .max = -1, .parse = (parsefn)(parse_gitlab_diffs), }; /* /projects/:id/repository/commits/:sha/diff */ url = sn_asprintf("%s/projects/%s%%2F%s/repository/commits/%s/diff", gcli_get_apibase(ctx), e_owner, e_repo, commit->sha); rc = gcli_fetch_list(ctx, url, &fl); if (rc < 0) goto err_fetch_diffs; fprintf(stream, "From %s Mon Sep 17 00:00:00 2001\n", commit->long_sha); fprintf(stream, "From: %s <%s>\n", commit->author, commit->email); fprintf(stream, "Date: %s\n", commit->date); fprintf(stream, "Subject: %s\n\n", commit->message); for (size_t i = 0; i < list.diffs_size; ++i) { gitlab_make_commit_diff(commit, &list.diffs[i], prev_commit_sha, stream); } fprintf(stream, "--\n2.42.2\n\n\n"); gitlab_free_diffs(&list); err_fetch_diffs: return rc; } int 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; struct gcli_pull pull = {0}; struct gcli_commit_list commits = {0}; char const *prev_commit_sha; char *base_sha_short; rc = gitlab_get_pull(ctx, owner, reponame, mr_number, &pull); if (rc < 0) goto err_get_pull; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(reponame); rc = gitlab_get_pull_commits(ctx, owner, reponame, mr_number, &commits); if (rc < 0) goto err_get_commit_list; base_sha_short = sn_strndup(pull.base_sha, 8); prev_commit_sha = base_sha_short; 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 - 1]); if (rc < 0) goto err_make_commit_patch; prev_commit_sha = commits.commits[i - 1].sha; } err_make_commit_patch: free(base_sha_short); gcli_commits_free(&commits); err_get_commit_list: free(e_owner); free(e_repo); err_get_pull: return rc; } int gitlab_mr_get_diff(struct gcli_ctx *ctx, FILE *stream, char const *owner, char const *reponame, gcli_id mr_number) { (void) stream; (void) owner; (void) reponame; (void) mr_number; return gcli_error(ctx, "not yet implemented"); } int 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) { struct gcli_fetch_buffer buffer = {0}; char *url = NULL; char *e_owner = NULL; char *e_repo = NULL; char const *data = "{}"; bool const squash = flags & GCLI_PULL_MERGE_SQUASH; bool const delete_source = flags & GCLI_PULL_MERGE_DELETEHEAD; int rc = 0; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); /* PUT /projects/:id/merge_requests/:merge_request_iid/merge */ url = sn_asprintf("%s/projects/%s%%2F%s/merge_requests/%"PRIid"/merge" "?squash=%s" "&should_remove_source_branch=%s", gcli_get_apibase(ctx), e_owner, e_repo, mr_number, squash ? "true" : "false", delete_source ? "true" : "false"); rc = gcli_fetch_with_method(ctx, "PUT", url, data, NULL, &buffer); free(buffer.data); free(url); free(e_owner); free(e_repo); return rc; } int gitlab_get_pull(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const pr_number, struct gcli_pull *const out) { struct gcli_fetch_buffer json_buffer = {0}; char *url = NULL; char *e_owner = NULL; char *e_repo = NULL; int rc = 0; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); /* GET /projects/:id/merge_requests/:merge_request_iid */ url = sn_asprintf("%s/projects/%s%%2F%s/merge_requests/%"PRIid, gcli_get_apibase(ctx), e_owner, e_repo, pr_number); free(e_owner); free(e_repo); rc = gcli_fetch(ctx, url, NULL, &json_buffer); if (rc == 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); } free(url); free(json_buffer.data); return rc; } int 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; struct gcli_fetch_list_ctx fl = { .listp = &out->commits, .sizep = &out->commits_size, .max = -1, .parse = (parsefn)(parse_gitlab_commits), }; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); /* GET /projects/:id/merge_requests/:merge_request_iid/commits */ url = sn_asprintf("%s/projects/%s%%2F%s/merge_requests/%"PRIid"/commits", gcli_get_apibase(ctx), e_owner, e_repo, pr_number); free(e_owner); free(e_repo); return gcli_fetch_list(ctx, url, &fl); } 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, *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/merge_requests/%"PRIid, gcli_get_apibase(ctx), e_owner, e_repo, mr); free(e_owner); free(e_repo); rc = gcli_fetch_with_method(ctx, "PUT", url, payload, NULL, NULL); free(url); free(payload); return rc; } int gitlab_mr_close(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const mr) { return gitlab_mr_patch_state(ctx, owner, repo, mr, "close"); } 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, mr_id); 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(url); return rc; } int 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. */ 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; struct gcli_fetch_buffer buffer = {0}; struct gcli_jsongen gen = {0}; struct gcli_repo target = {0}; target_branch = opts->to; 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++ = '\0'; /* Figure out the project id */ rc = gitlab_get_repo(ctx, opts->owner, opts->repo, &target); if (rc < 0) return rc; /* 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); } gcli_jsongen_objmember(&gen, "target_project_id"); gcli_jsongen_number(&gen, target.id); if (opts->labels_size) { gcli_jsongen_objmember(&gen, "labels"); 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); 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, 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_owner); free(e_repo); free(buffer.data); free(source_owner); free(payload); free(url); return rc; } 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, *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, ","); 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); gcli_jsongen_free(&gen); free(list); /* 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), e_owner, e_repo, mr); free(e_owner); free(e_repo); rc = gcli_fetch_with_method(ctx, "PUT", url, payload, NULL, NULL); free(url); free(payload); return rc; } int 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) { 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), e_owner, e_repo, mr); free(e_owner); free(e_repo); rc = gcli_fetch_with_method(ctx, "PUT", url, payload, NULL, NULL); free(url); free(payload); return rc; } int gitlab_mr_clear_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const mr) { /* GitLab's REST API docs state: * * The global ID of a milestone to assign the merge request * to. Set to 0 or provide an empty value to unassign a * milestone. */ return gitlab_mr_set_milestone(ctx, owner, repo, mr, 0); } /* Helper function to fetch the list of user ids that are reviewers * of a merge requests. */ static int 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; 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) { 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); } free(url); free(json_buffer.data); return rc; } static void gitlab_reviewer_list_free(struct gitlab_reviewer_id_list *const list) { free(list->reviewers); list->reviewers = NULL; list->reviewers_size = 0; } int 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; struct gitlab_reviewer_id_list list = {0}; struct gcli_jsongen gen = {0}; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); /* Fetch list of already existing reviewers */ rc = gitlab_mr_get_reviewers(ctx, e_owner, e_repo, mr_number, &list); if (rc < 0) goto bail_get_reviewers; /* Resolve user id from user name */ uid = gitlab_user_id(ctx, username); if (uid < 0) goto bail_resolve_user_id; /* Start generating payload */ gcli_jsongen_init(&gen); gcli_jsongen_begin_object(&gen); { gcli_jsongen_objmember(&gen, "reviewer_ids"); gcli_jsongen_begin_array(&gen); { for (size_t i = 0; i < list.reviewers_size; ++i) gcli_jsongen_number(&gen, list.reviewers[i]); /* Push new user id into list of user ids */ gcli_jsongen_number(&gen, uid); } gcli_jsongen_end_array(&gen); } gcli_jsongen_end_object(&gen); payload = gcli_jsongen_to_string(&gen); gcli_jsongen_free(&gen); /* generate URL */ url = sn_asprintf("%s/projects/%s%%2F%s/merge_requests/%"PRIid, gcli_get_apibase(ctx), e_owner, e_repo, mr_number); rc = gcli_fetch_with_method(ctx, "PUT", url, payload, NULL, NULL); free(url); free(payload); bail_resolve_user_id: gitlab_reviewer_list_free(&list); bail_get_reviewers: free(e_owner); free(e_repo); return rc; } 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) { char *url, *e_owner, *e_repo, *payload; struct gcli_jsongen gen = {0}; int rc = 0; /* Generate url * * PUT /projects/:id/merge_requests/:merge_request_iid */ 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), e_owner, e_repo, id); free(e_owner); free(e_repo); /* Generate payload */ gcli_jsongen_init(&gen); gcli_jsongen_begin_object(&gen); { gcli_jsongen_objmember(&gen, "title"); gcli_jsongen_string(&gen, new_title); } gcli_jsongen_end_object(&gen); payload = gcli_jsongen_to_string(&gen); gcli_jsongen_free(&gen); /* perform request */ rc = gcli_fetch_with_method(ctx, "PUT", url, payload, NULL, NULL); /* clean up */ free(url); free(payload); return rc; } gcli-2.3.0/src/gitlab/milestones.c000066400000000000000000000136521460062271200170200ustar00rootroot00000000000000/* * 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 #include #include #include int 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; struct gcli_fetch_list_ctx fl = { .listp = &out->milestones, .sizep = &out->milestones_size, .max = max, .parse = (parsefn)(parse_gitlab_milestones), }; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/projects/%s%%2F%s/milestones", gcli_get_apibase(ctx), e_owner, e_repo); free(e_owner); free(e_repo); return gcli_fetch_list(ctx, url, &fl); } int 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; struct gcli_fetch_buffer buffer = {0}; struct json_stream stream = {0}; int rc = 0; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/projects/%s%%2F%s/milestones/%"PRIid, gcli_get_apibase(ctx), e_owner, e_repo, milestone); rc = gcli_fetch(ctx, url, NULL, &buffer); if (rc == 0) { json_open_buffer(&stream, buffer.data, buffer.length); parse_gitlab_milestone(ctx, &stream, out); json_close(&stream); } free(buffer.data); free(url); free(e_owner); free(e_repo); return rc; } int gitlab_milestone_get_issues(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone, struct gcli_issue_list *const out) { char *url, *e_owner, *e_repo; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/projects/%s%%2F%s/milestones/%"PRIid"/issues", gcli_get_apibase(ctx), e_owner, e_repo, milestone); free(e_repo); free(e_owner); /* URL is freed by the fetch_issues call */ return gitlab_fetch_issues(ctx, url, -1, out);; } int 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; int rc = 0; e_owner = gcli_urlencode(args->owner); e_repo = gcli_urlencode(args->repo); url = sn_asprintf("%s/projects/%s%%2F%s/milestones", gcli_get_apibase(ctx), e_owner, e_repo); /* Escape and prepare the description if needed */ if (args->description) { char *e_description = gcli_json_escape_cstr(args->description); description = sn_asprintf(", \"description\": \"%s\"", e_description); free(e_description); } e_title = gcli_json_escape_cstr(args->title); json_body = sn_asprintf("{" " \"title\": \"%s\"" " %s" "}", e_title, description ? description : ""); rc = gcli_fetch_with_method(ctx, "POST", url, json_body, NULL, NULL); free(json_body); free(description); free(url); free(e_title); free(e_repo); free(e_owner); return rc; } int 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; int rc = 0; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/projects/%s%%2F%s/milestones/%"PRIid, gcli_get_apibase(ctx), e_owner, e_repo, milestone); rc = gcli_fetch_with_method(ctx, "DELETE", url, NULL, NULL, NULL); free(url); free(e_repo); free(e_owner); return rc; } int gitlab_milestone_set_duedate(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone, char const *const date) { char *url, *e_owner, *e_repo, norm_date[9] = {0}; int rc = 0; rc = gcli_normalize_date(ctx, DATEFMT_GITLAB, date, norm_date, sizeof norm_date); if (rc < 0) return rc; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/projects/%s%%2F%s/milestones/%"PRIid"?due_date=%s", gcli_get_apibase(ctx), e_owner, e_repo, milestone, norm_date); rc = gcli_fetch_with_method(ctx, "PUT", url, "", NULL, NULL); free(url); free(e_repo); free(e_owner); return rc; } gcli-2.3.0/src/gitlab/pipelines.c000066400000000000000000000166161460062271200166310ustar00rootroot00000000000000/* * Copyright 2022 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 #include #include #include static int fetch_pipelines(struct gcli_ctx *ctx, char *url, int const max, struct gitlab_pipeline_list *const list) { struct gcli_fetch_list_ctx fl = { .listp = &list->pipelines, .sizep = &list->pipelines_size, .max = max, .parse = (parsefn)(parse_gitlab_pipelines), }; return gcli_fetch_list(ctx, url, &fl); } int 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), e_owner, e_repo); free(e_owner); free(e_repo); return fetch_pipelines(ctx, url, max, list); } int 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), 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(struct gitlab_pipeline *pipeline) { free(pipeline->status); free(pipeline->created_at); free(pipeline->updated_at); free(pipeline->ref); free(pipeline->sha); free(pipeline->source); } void 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]); } free(list->pipelines); list->pipelines = NULL; list->pipelines_size = 0; } int 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, *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), e_owner, e_repo, pipeline); free(e_owner); free(e_repo); return gcli_fetch_list(ctx, url, &fl); } void gitlab_free_job(struct gitlab_job *const job) { free(job->status); free(job->stage); free(job->name); free(job->ref); free(job->created_at); free(job->started_at); free(job->finished_at); free(job->runner_name); free(job->runner_description); } void gitlab_free_jobs(struct gitlab_job_list *list) { for (size_t i = 0; i < list->jobs_size; ++i) gitlab_free_job(&list->jobs[i]); free(list->jobs); list->jobs = NULL; list->jobs_size = 0; } int gitlab_job_get_log(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const job_id, FILE *stream) { 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), e_owner, e_repo, job_id); free(e_owner); free(e_repo); rc = gcli_curl(ctx, stream, url, NULL); free(url); return rc; } int gitlab_get_job(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const jid, struct gitlab_job *const out) { 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), e_owner, e_repo, jid); free(e_owner); free(e_repo); rc = gcli_fetch(ctx, url, NULL, &buffer); if (rc == 0) { struct json_stream stream = {0}; json_open_buffer(&stream, buffer.data, buffer.length); json_set_streaming(&stream, 1); parse_gitlab_job(ctx, &stream, out); json_close(&stream); } free(buffer.data); free(url); return rc; } int gitlab_job_cancel(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const jid) { 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), e_owner, e_repo, jid); free(e_owner); free(e_repo); rc = gcli_fetch_with_method(ctx, "POST", url, NULL, NULL, NULL); free(url); return rc; } int gitlab_job_retry(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const jid) { int rc = 0; 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), e_owner, e_repo, jid); free(e_owner); free(e_repo); rc = gcli_fetch_with_method(ctx, "POST", url, NULL, NULL, NULL); free(url); return rc; } int gitlab_job_download_artifacts(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const jid, char const *const outfile) { char *url; char *e_owner, *e_repo; FILE *f; int rc = 0; f = fopen(outfile, "wb"); if (f == NULL) return -1; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); 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); return rc; } gcli-2.3.0/src/gitlab/releases.c000066400000000000000000000117731460062271200164430ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 static void 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(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(struct gcli_ctx *ctx, struct gcli_release_list *list) { /* Iterate over releases */ for (size_t i = 0; i < list->releases_size; ++i) gitlab_fixup_release_assets(ctx, &list->releases[i]); } int 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; struct gcli_fetch_list_ctx fl = { .listp = &list->releases, .sizep = &list->releases_size, .max = max, .parse = (parsefn)(parse_gitlab_releases), }; *list = (struct gcli_release_list) {0}; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); 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_list(ctx, url, &fl); if (rc == 0) fixup_release_asset_names(ctx, list); return rc; } int gitlab_create_release(struct gcli_ctx *ctx, struct gcli_new_release const *release) { char *e_owner = NULL, *e_repo = NULL, *url = NULL, *payload = NULL; struct gcli_jsongen gen = {0}; int rc = 0; /* Warnings because unsupported on gitlab */ if (release->prerelease) warnx("prereleases are not supported on GitLab, option ignored"); if (release->draft) warnx("draft releases are not supported on GitLab, option ignored"); if (release->assets_size) warnx("GitLab release asset uploads are not yet supported"); /* 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(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *id) { char *url = 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/releases/%s", gcli_get_apibase(ctx), e_owner, e_repo, id); rc = gcli_fetch_with_method(ctx, "DELETE", url, NULL, NULL, NULL); free(url); free(e_owner); free(e_repo); return rc; } gcli-2.3.0/src/gitlab/repos.c000066400000000000000000000133501460062271200157610ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 gitlab_get_repo(struct gcli_ctx *ctx, char const *owner, char const *repo, struct gcli_repo *const out) { /* GET /projects/:id */ char *url = NULL; struct gcli_fetch_buffer buffer = {0}; struct json_stream stream = {0}; char *e_owner = {0}; char *e_repo = {0}; int rc; e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/projects/%s%%2F%s", gcli_get_apibase(ctx), e_owner, e_repo); rc = gcli_fetch(ctx, url, NULL, &buffer); if (rc == 0) { json_open_buffer(&stream, buffer.data, buffer.length); parse_gitlab_repo(ctx, &stream, out); json_close(&stream); } free(buffer.data); free(e_owner); free(e_repo); free(url); return rc; } static void gitlab_repos_fixup_missing_visibility(struct gcli_repo_list *const list) { 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 (!list->repos[i].visibility) list->repos[i].visibility = strdup(public); } } int 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; struct gcli_fetch_list_ctx fl = { .listp = &list->repos, .sizep = &list->repos_size, .parse = (parsefn)(parse_gitlab_repos), .max = max, }; e_owner = gcli_urlencode(owner); url = sn_asprintf("%s/users/%s/projects", gcli_get_apibase(ctx), e_owner); free(e_owner); rc = gcli_fetch_list(ctx, url, &fl); if (rc == 0) gitlab_repos_fixup_missing_visibility(list); return rc; } int gitlab_repo_delete(struct gcli_ctx *ctx, char const *owner, char const *repo) { char *url = 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", gcli_get_apibase(ctx), e_owner, e_repo); rc = gcli_fetch_with_method(ctx, "DELETE", url, NULL, NULL, NULL); free(url); free(e_owner); free(e_repo); return rc; } int gitlab_repo_create(struct gcli_ctx *ctx, struct gcli_repo_create_options const *options, struct gcli_repo *out) { 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)); 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, payload, NULL, out ? &buffer : NULL); if (rc == 0 && out) { json_open_buffer(&stream, buffer.data, buffer.length); parse_gitlab_repo(ctx, &stream, out); json_close(&stream); } free(buffer.data); free(payload); free(url); return rc; } int gitlab_repo_set_visibility(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_repo_visibility vis) { char *url; char *e_owner, *e_repo; char const *vis_str; char *payload; int rc; switch (vis) { case GCLI_REPO_VISIBILITY_PRIVATE: vis_str = "private"; break; case GCLI_REPO_VISIBILITY_PUBLIC: vis_str = "public"; break; default: assert(false && "Invalid visibility"); return gcli_error(ctx, "bad visibility level"); } e_owner = gcli_urlencode(owner); e_repo = gcli_urlencode(repo); url = sn_asprintf("%s/projects/%s%%2F%s", gcli_get_apibase(ctx), e_owner, e_repo); payload = sn_asprintf("{ \"visibility\": \"%s\" }", vis_str); rc = gcli_fetch_with_method(ctx, "PUT", url, payload, NULL, NULL); free(payload); free(e_owner); free(e_repo); free(url); return rc; } gcli-2.3.0/src/gitlab/snippets.c000066400000000000000000000060631460062271200165010ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 #include void gcli_gitlab_snippet_free(struct gcli_gitlab_snippet *snippet) { free(snippet->title); free(snippet->filename); free(snippet->date); free(snippet->author); free(snippet->visibility); free(snippet->raw_url); } void 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]); } free(list->snippets); list->snippets = NULL; list->snippets_size = 0; } int gcli_snippets_get(struct gcli_ctx *ctx, int const max, struct gcli_gitlab_snippet_list *const out) { char *url = NULL; struct gcli_fetch_list_ctx fl = { .listp = &out->snippets, .sizep = &out->snippets_size, .max = max, .parse = (parsefn)(parse_gitlab_snippets), }; *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(struct gcli_ctx *ctx, char const *snippet_id) { int rc = 0; char *url; url = sn_asprintf("%s/snippets/%s", gcli_get_apibase(ctx), snippet_id); rc = gcli_fetch_with_method(ctx, "DELETE", url, NULL, NULL, NULL); free(url); return rc; } int gcli_snippet_get(struct gcli_ctx *ctx, char const *snippet_id, FILE *stream) { int rc = 0; char *url = sn_asprintf("%s/snippets/%s/raw", gcli_get_apibase(ctx), snippet_id); rc = gcli_curl(ctx, stream, url, NULL); free(url); return rc; } gcli-2.3.0/src/gitlab/sshkeys.c000066400000000000000000000057621460062271200163320ustar00rootroot00000000000000/* * 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 HAVE_CONFIG_H #include #endif /* HAVE_CONFIG_H */ #include #include #include #include #include #include int gitlab_get_sshkeys(struct gcli_ctx *ctx, struct gcli_sshkey_list *list) { char *url; struct gcli_fetch_list_ctx fl = { .listp = &list->keys, .sizep = &list->keys_size, .max = -1, .parse = (parsefn)(parse_gitlab_sshkeys), }; *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(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; struct gcli_fetch_buffer buf = {0}; int rc = 0; url = sn_asprintf("%s/user/keys", gcli_get_apibase(ctx)); /* Prepare payload */ e_title = gcli_json_escape_cstr(title); e_key = gcli_json_escape_cstr(pubkey); payload = sn_asprintf( "{ \"title\": \"%s\", \"key\": \"%s\" }", e_title, e_key); free(e_title); free(e_key); rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, &buf); if (rc == 0 && out) { struct json_stream stream = {0}; json_open_buffer(&stream, buf.data, buf.length); parse_gitlab_sshkey(ctx, &stream, out); json_close(&stream); } free(buf.data); return rc; } int gitlab_delete_sshkey(struct gcli_ctx *ctx, gcli_id id) { char *url; int rc = 0; url = sn_asprintf("%s/user/keys/%"PRIid, gcli_get_apibase(ctx), id); rc = gcli_fetch_with_method(ctx, "DELETE", url, NULL, NULL, NULL); free(url); return rc; } gcli-2.3.0/src/gitlab/status.c000066400000000000000000000043421460062271200161550ustar00rootroot00000000000000/* * Copyright 2022 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 int gitlab_get_notifications(struct gcli_ctx *ctx, int const max, struct gcli_notification_list *const out) { char *url = NULL; struct gcli_fetch_list_ctx fl = { .listp = &out->notifications, .sizep = &out->notifications_size, .parse = (parsefn)(parse_gitlab_todos), .max = max, }; url = sn_asprintf("%s/todos", gcli_get_apibase(ctx)); return gcli_fetch_list(ctx, url, &fl); } int gitlab_notification_mark_as_read(struct gcli_ctx *ctx, char const *id) { char *url = NULL; int rc = 0; url = sn_asprintf("%s/todos/%s/mark_as_done", gcli_get_apibase(ctx), id); rc = gcli_fetch_with_method(ctx, "POST", url, NULL, NULL, NULL); free(url); return rc; } gcli-2.3.0/src/issues.c000066400000000000000000000127231460062271200147050ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 void gcli_issue_free(struct gcli_issue *const it) { 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]); free(it->labels); it->labels = NULL; for (size_t i = 0; i < it->assignees_size; ++i) free(it->assignees[i]); free(it->assignees); it->assignees = NULL; free(it->milestone); } void 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]); free(list->issues); list->issues = NULL; list->issues_size = 0; } int 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) { gcli_null_check_call(search_issues, ctx, owner, repo, details, max, out); } int gcli_get_issue(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const issue_number, struct gcli_issue *const out) { gcli_null_check_call(get_issue_summary, ctx, owner, repo, issue_number, out); } int gcli_issue_close(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const issue_number) { gcli_null_check_call(issue_close, ctx, owner, repo, issue_number); } int gcli_issue_reopen(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const issue_number) { gcli_null_check_call(issue_reopen, ctx, owner, repo, issue_number); } int gcli_issue_submit(struct gcli_ctx *ctx, struct gcli_submit_issue_options *opts) { gcli_null_check_call(perform_submit_issue, ctx, opts, NULL); } int gcli_issue_assign(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const issue_number, char const *assignee) { gcli_null_check_call(issue_assign, ctx, owner, repo, issue_number, assignee); } int 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) { gcli_null_check_call(issue_add_labels, ctx, owner, repo, issue, labels, labels_size); } int 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) { gcli_null_check_call(issue_remove_labels, ctx, owner, repo, issue, labels, labels_size); } int gcli_issue_set_milestone(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const issue, int const milestone) { gcli_null_check_call(issue_set_milestone, ctx, owner, repo, issue, milestone); } int gcli_issue_clear_milestone(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const issue) { gcli_null_check_call(issue_clear_milestone, ctx, owner, repo, issue); } int gcli_issue_set_title(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id issue, char const *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); } } gcli-2.3.0/src/json_gen.c000066400000000000000000000145161460062271200151760ustar00rootroot00000000000000/* * 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 static void grow_buffer(struct gcli_jsongen *gen) { gen->buffer_capacity *= 2; gen->buffer = realloc(gen->buffer, gen->buffer_capacity); } int 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 * of how long usually the generated buffers are. */ memset(gen, 0, sizeof(*gen)); gen->buffer_capacity = 16; grow_buffer(gen); gen->first_elem = true; return 0; } void gcli_jsongen_free(struct gcli_jsongen *gen) { free(gen->buffer); gen->buffer = NULL; gen->buffer_size = 0; gen->buffer_capacity = 0; gen->scopes_size = 0; } char * gcli_jsongen_to_string(struct gcli_jsongen *gen) { char *buf = calloc(gen->buffer_size + 1, 1); return memcpy(buf, gen->buffer, gen->buffer_size); } static void 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(struct gcli_jsongen *gen, int const scope) { if (gen->scopes_size >= (sizeof(gen->scopes) / sizeof(*gen->scopes))) return -1; gen->scopes[gen->scopes_size++] = scope; return 0; } static int pop_scope(struct gcli_jsongen *gen) { if (gen->scopes_size == 0) return -1; return gen->scopes[--gen->scopes_size]; } static bool is_array_or_object_scope(struct gcli_jsongen *gen) { return !!gen->scopes_size; } static void append_str(struct gcli_jsongen *gen, char const *str) { size_t const len = strlen(str); fit(gen, len); memcpy(gen->buffer + gen->buffer_size, str, len); gen->buffer_size += len; } static void 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, ", "); gen->first_elem = false; } static bool is_object_scope(struct gcli_jsongen *gen) { if (gen->scopes_size == 0) return false; return gen->scopes[gen->scopes_size - 1] == GCLI_JSONGEN_OBJECT; } int 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) return -1; put_comma_if_needed(gen); if (push_scope(gen, GCLI_JSONGEN_OBJECT) < 0) return -1; append_str(gen, "{"); gen->first_elem = true; return 0; } int gcli_jsongen_end_object(struct gcli_jsongen *gen) { if (pop_scope(gen) != GCLI_JSONGEN_OBJECT) return -1; append_str(gen, "}"); gen->await_object_value = false; gen->first_elem = false; return 0; } int 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) return -1; put_comma_if_needed(gen); if (push_scope(gen, GCLI_JSONGEN_ARRAY) < 0) return -1; append_str(gen, "["); gen->first_elem = true; return 0; } int gcli_jsongen_end_array(struct gcli_jsongen *gen) { if (pop_scope(gen) != GCLI_JSONGEN_ARRAY) return -1; append_str(gen, "]"); gen->await_object_value = false; gen->first_elem = false; return 0; } static void append_vstrf(struct gcli_jsongen *gen, char const *const fmt, va_list vp) { va_list vp_copy; size_t len; va_copy(vp_copy, vp); len = vsnprintf(NULL, 0, fmt, vp_copy); fit(gen, len + 1); vsnprintf(gen->buffer + gen->buffer_size, len + 1, fmt, vp); gen->buffer_size += len; } static void append_strf(struct gcli_jsongen *gen, char const *const fmt, ...) { va_list ap; va_start(ap, fmt); append_vstrf(gen, fmt, ap); va_end(ap); } int gcli_jsongen_objmember(struct gcli_jsongen *gen, char const *const key) { if (!is_object_scope(gen)) return -1; put_comma_if_needed(gen); char *const e_key = gcli_json_escape_cstr(key); append_strf(gen, "\"%s\": ", e_key); gen->first_elem = false; gen->await_object_value = true; free(e_key); return 0; } int gcli_jsongen_number(struct gcli_jsongen *gen, long long const number) { put_comma_if_needed(gen); append_strf(gen, "%lld", number); gen->await_object_value = false; gen->first_elem = false; return 0; } int 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); append_strf(gen, "\"%s\"", e_value); gen->await_object_value = false; gen->first_elem = false; free(e_value); return 0; } int gcli_jsongen_null(struct gcli_jsongen *gen) { put_comma_if_needed(gen); append_str(gen, "null"); gen->await_object_value = false; gen->first_elem = false; return 0; } gcli-2.3.0/src/json_util.c000066400000000000000000000241231460062271200153750ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 int 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); *out = json_get_number(input); return 0; } int 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); *out = json_get_number(input); return 0; } int 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); *out = json_get_number(input); return 0; } int 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); *out = json_get_number(input); return 0; } int get_double_(struct gcli_ctx *ctx, json_stream *const input, double *out, char const *where) { enum json_type type = json_next(input); /* This is dumb but it fixes a couple of weirdnesses of the API */ if (type == JSON_NULL) { *out = 0; return 0; } if (type == JSON_NUMBER) { *out = json_get_number(input); return 0; } return gcli_error(ctx, "unexpected non-double field in %s", where); } int get_string_(struct gcli_ctx *ctx, json_stream *const input, char **out, char const *where) { enum json_type const type = json_next(input); if (type == JSON_NULL) { *out = strdup(""); return 0; } if (type != JSON_STRING) return gcli_error(ctx, "unexpected non-string field in %s", where); size_t len; char const *it = json_get_string(input, &len); if (!it) *out = strdup(""); else *out = sn_strndup(it, len); return 0; } int 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) { *out = true; return 0; } else if (value_type == JSON_FALSE || value_type == JSON_NULL) { // HACK *out = false; return 0; } return gcli_error(ctx, "unexpected non-boolean value in %s", where); } int 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) return gcli_error(ctx, "%s: user field is not an object", where); char const *expected_key = gcli_forge(ctx)->user_object_key; while (json_next(input) == JSON_STRING) { size_t len = 0; char const *key = json_get_string(input, &len); if (strncmp(expected_key, key, len) == 0) { if (json_next(input) != JSON_STRING) return gcli_error(ctx, "%s: login isn't a string", where); char const *tmp = json_get_string(input, &len); *out = sn_strndup(tmp, len); } else { json_next(input); } } return 0; } static struct { char c; char const *with; } json_escape_table[] = { { .c = '\n', .with = "\\n" }, { .c = '\t', .with = "\\t" }, { .c = '\r', .with = "\\r" }, { .c = '\\', .with = "\\\\" }, { .c = '"' , .with = "\\\"" }, }; sn_sv gcli_json_escape(sn_sv const it) { sn_sv result = {0}; result.data = calloc(2 * it.length + 1, 1); if (!result.data) err(1, "malloc"); for (size_t i = 0; i < it.length; ++i) { for (size_t c = 0; c < ARRAY_SIZE(json_escape_table); ++c) { if (json_escape_table[c].c == it.data[i]) { size_t const len = strlen(json_escape_table[c].with); memcpy(result.data + result.length, json_escape_table[c].with, len); result.length += len; goto next; } } memcpy(result.data + result.length, it.data + i, 1); result.length += 1; next: continue; } return result; } int 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) { *out = SV_NULL; return 0; } if (type != JSON_STRING) return gcli_error(ctx, "unexpected non-string field in %s", where); size_t len; char const *it = json_get_string(input, &len); char *copy = sn_strndup(it, len); *out = SV(copy); return 0; } int get_label_(struct gcli_ctx *ctx, json_stream *const input, char const **out, char const *where) { if (json_next(input) != JSON_OBJECT) return gcli_error(ctx, "%s: label field is not an object", where); while (json_next(input) == JSON_STRING) { size_t len = 0; char const *key = json_get_string(input, &len); if (strncmp("name", key, len) == 0) { if (json_next(input) != JSON_STRING) return gcli_error(ctx, "%s: name of the label is not a string", where); *out = json_get_string(input, &len); *out = sn_strndup(*out, len); } else { json_next(input); } } return 0; } int gcli_json_advance(struct gcli_ctx *ctx, json_stream *const stream, char const *fmt, ...) { va_list ap; va_start(ap, fmt); while (*fmt) { switch (*fmt++) { case '[': { if (json_next(stream) != JSON_ARRAY) return gcli_error(ctx, "expected array begin"); } break; case '{': { if (json_next(stream) != JSON_OBJECT) return gcli_error(ctx, "expected array begin"); } break; case 's': { if (json_next(stream) != JSON_STRING) return gcli_error(ctx, "expected string"); char *it = va_arg(ap, char *); size_t len = 0; char const *other = json_get_string(stream, &len); if (strncmp(it, other, len)) return gcli_error(ctx, "string unmatched"); } break; case ']': { if (json_next(stream) != JSON_ARRAY_END) return gcli_error(ctx, "expected array end"); } break; case '}': { if (json_next(stream) != JSON_OBJECT_END) return gcli_error(ctx, "expected object end"); } break; case 'i': { if (json_next(stream) != JSON_NUMBER) return gcli_error(ctx, "expected integer"); } break; } } va_end(ap); return 0; } int get_parse_int_(struct gcli_ctx *ctx, json_stream *const input, long *out, char const *function) { char *endptr = NULL; char *string; int rc = get_string_(ctx, input, &string, function); if (rc < 0) return rc; *out = strtol(string, &endptr, 10); if (endptr != string + strlen(string)) return gcli_error(ctx, "%s: cannot parse %s as integer", function, string); return 0; } int get_github_style_colour(struct gcli_ctx *ctx, json_stream *const input, uint32_t *out) { char *colour_str; char *endptr = NULL; int rc; rc = get_string(ctx, input, &colour_str); if (rc < 0) return rc; unsigned long colour = strtoul(colour_str, &endptr, 16); if (endptr != colour_str + strlen(colour_str)) return gcli_error(ctx, "%s: bad colour code returned by API", colour_str); free(colour_str); *out = ((uint32_t)(colour)) << 8; return 0; } int get_gitlab_style_colour(struct gcli_ctx *ctx, json_stream *const input, uint32_t *out) { char *colour; char *endptr = NULL; long code = 0; int rc = 0; rc = get_string(ctx, input, &colour); if (rc < 0) return rc; code = strtol(colour + 1, &endptr, 16); if (endptr != (colour + 1 + strlen(colour + 1))) return gcli_error(ctx, "%s: invalid colour code"); free(colour); *out = ((uint32_t)(code) << 8); return 0; } int get_gitea_visibility(struct gcli_ctx *ctx, json_stream *const input, char **out) { bool is_private; int rc = get_bool(ctx, input, &is_private); if (rc < 0) return rc; *out = strdup(is_private ? "private" : "public"); return 0; } int get_gitlab_can_be_merged(struct gcli_ctx *ctx, json_stream *const input, bool *out) { sn_sv tmp; int rc = 0; rc = get_sv(ctx, input, &tmp); if (rc < 0) return rc; *out = sn_sv_eq_to(tmp, "can_be_merged"); free(tmp.data); return rc; } int get_github_is_pr(struct gcli_ctx *ctx, json_stream *input, int *out) { enum json_type next = json_peek(input); (void) ctx; if (next == JSON_NULL) json_next(input); else SKIP_OBJECT_VALUE(input); *out = (next == JSON_OBJECT); return 0; } int get_int_to_sv_(struct gcli_ctx *ctx, json_stream *input, sn_sv *out, char const *function) { int rc, val; rc = get_int_(ctx, input, &val, function); if (rc < 0) return rc; *out = sn_sv_fmt("%d", val); return 0; } gcli-2.3.0/src/labels.c000066400000000000000000000044641460062271200146370ustar00rootroot00000000000000/* * Copyright 2022 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 int gcli_get_labels(struct gcli_ctx *ctx, char const *owner, char const *reponame, int const max, struct gcli_label_list *const out) { gcli_null_check_call(get_labels, ctx, owner, reponame, max, out); } void gcli_free_label(struct gcli_label *const label) { free(label->name); free(label->description); } void 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]); free(list->labels); list->labels = NULL; list->labels_size = 0; } int gcli_create_label(struct gcli_ctx *ctx, char const *owner, char const *repo, struct gcli_label *const label) { gcli_null_check_call(create_label, ctx, owner, repo, label); } int gcli_delete_label(struct gcli_ctx *ctx, char const *owner, char const *repo, char const *const label) { gcli_null_check_call(delete_label, ctx, owner, repo, label); } gcli-2.3.0/src/milestones.c000066400000000000000000000066721460062271200155620ustar00rootroot00000000000000/* * 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 int gcli_get_milestones(struct gcli_ctx *ctx, char const *const owner, char const *const repo, int const max, struct gcli_milestone_list *const out) { gcli_null_check_call(get_milestones, ctx, owner, repo, max, out); } int gcli_get_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const milestone, struct gcli_milestone *const out) { gcli_null_check_call(get_milestone, ctx, owner, repo, milestone, out); } int gcli_create_milestone(struct gcli_ctx *ctx, struct gcli_milestone_create_args const *args) { gcli_null_check_call(create_milestone, ctx, args); } int gcli_delete_milestone(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone) { gcli_null_check_call(delete_milestone, ctx, owner, repo, milestone); } void gcli_free_milestone(struct gcli_milestone *const it) { free(it->title); it->title = NULL; free(it->state); it->state = NULL; free(it->created_at); it->created_at = NULL; free(it->description); it->description = NULL; free(it->updated_at); it->updated_at = NULL; free(it->due_date); it->due_date = NULL; } void 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]); free(it->milestones); it->milestones = NULL; it->milestones_size = 0; } int gcli_milestone_get_issues(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone, struct gcli_issue_list *const out) { gcli_null_check_call(get_milestone_issues, ctx, owner, repo, milestone, out); } int gcli_milestone_set_duedate(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const milestone, char const *const date) { gcli_null_check_call(milestone_set_duedate, ctx, owner, repo, milestone, date); } gcli-2.3.0/src/nvlist.c000066400000000000000000000051121460062271200147030ustar00rootroot00000000000000/* * 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; } gcli-2.3.0/src/pgen/000077500000000000000000000000001460062271200141525ustar00rootroot00000000000000gcli-2.3.0/src/pgen/dump_c.c000066400000000000000000000176401460062271200155750ustar00rootroot00000000000000/* * Copyright 2022 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 static void pregen_array_parser(struct objparser *p, struct objentry *it) { fprintf(outfile, "static int\n" "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"); fprintf(outfile, "\t\tjson_next(stream);\n"); fprintf(outfile, "\t\tout->%s = NULL;\n", it->name); fprintf(outfile, "\t\tout->%s_size = 0;\n", it->name); fprintf(outfile, "\t\treturn 0;\n"); fprintf(outfile, "\t}\n\n"); fprintf(outfile, "\tif (json_next(stream) != JSON_ARRAY)\n"); fprintf(outfile, "\t\treturn gcli_error(ctx, \"expected array for %s array in %s\");\n\n", it->name, p->name); fprintf(outfile, "\twhile (json_peek(stream) != JSON_ARRAY_END) {\n"); fprintf(outfile, "\t\tout->%s = realloc(out->%s, sizeof(*out->%s) * (out->%s_size + 1));\n", it->name, it->name, it->name, it->name); fprintf(outfile, "\t\tmemset(&out->%s[out->%s_size], 0, sizeof(out->%s[out->%s_size]));\n", it->name, it->name, it->name, it->name); fprintf(outfile, "\t\trc = %s(ctx, stream, &out->%s[out->%s_size++]);\n", it->parser, it->name, it->name); fprintf(outfile, "\t\tif (rc < 0)\n\t\t\treturn rc;\n"); fprintf(outfile, "\t}\n\n"); fprintf(outfile, "\tif (json_next(stream) != JSON_ARRAY_END)\n"); fprintf(outfile, "\t\treturn gcli_error(ctx, \"unexpected element in array " "while parsing %s\");\n", p->name); fprintf(outfile, "\treturn 0;\n"); fprintf(outfile, "}\n\n"); } static void objparser_pregen_array_parsers(struct objparser *p) { for (struct objentry *it = p->entries; it; it = it->next) { if (it->kind == OBJENTRY_ARRAY) pregen_array_parser(p, it); } } static void objparser_dump_entries(struct objparser *p) { fprintf(outfile, "\twhile ((key_type = json_next(stream)) == JSON_STRING) {\n"); fprintf(outfile, "\t\tsize_t len;\n"); fprintf(outfile, "\t\tkey = json_get_string(stream, &len);\n"); for (struct objentry *it = p->entries; it; it = it->next) { fprintf(outfile, "\t\tif (strncmp(\"%s\", key, len) == 0) {\n", it->jsonname); if (it->kind == OBJENTRY_SIMPLE) { if (it->parser) { fprintf(outfile, "\t\t\tif (%s(ctx, stream, &out->%s) < 0)\n", it->parser, it->name); } else { fprintf(outfile, "\t\t\tif (get_%s(ctx, stream, &out->%s) < 0)\n", it->type, it->name); } fprintf(outfile, "\t\t\t\treturn -1;\n"); } else if (it->kind == OBJENTRY_ARRAY) { fprintf(outfile, "\t\t\tif (parse_%s_%s_array(ctx, stream, out) < 0)\n", p->name, it->name); fprintf(outfile, "\t\t\t\treturn -1;\n"); } else if (it->kind == OBJENTRY_CONTINUATION) { fprintf(outfile, "\t\t\tif (%s(ctx, stream, out) < 0)\n", it->parser); fprintf(outfile, "\t\t\t\treturn -1;\n"); } fprintf(outfile, "\t\t} else "); } fprintf(outfile, "\n\t\t\tSKIP_OBJECT_VALUE(stream);\n"); fprintf(outfile, "\t}\n"); } static void objparser_dump_select(struct objparser *p) { fprintf(outfile, "\twhile ((key_type = json_next(stream)) == JSON_STRING) {\n"); fprintf(outfile, "\t\tsize_t len;\n"); fprintf(outfile, "\t\tkey = json_get_string(stream, &len);\n"); fprintf(outfile, "\t\tif (strncmp(\"%s\", key, len) == 0) {\n", p->select.fieldname); fprintf(outfile, "\t\t\tif (get_%s(ctx, stream, out) < 0)\n", p->select.fieldtype); fprintf(outfile, "\t\t\t\treturn -1;\n"); fprintf(outfile, "\t\t} else "); fprintf(outfile, "\n\t\t\tSKIP_OBJECT_VALUE(stream);\n"); fprintf(outfile, "\t}\n"); } void objparser_dump_c(struct objparser *p) { objparser_pregen_array_parsers(p); fprintf(outfile, "int\n" "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"); fprintf(outfile, "\tif (json_next(stream) == JSON_NULL)\n"); fprintf(outfile, "\t\treturn 0;\n"); /* not ideal */ switch (p->kind) { case OBJPARSER_ENTRIES: objparser_dump_entries(p); break; case OBJPARSER_SELECT: objparser_dump_select(p); break; default: assert(0 && "unreached"); } fprintf(outfile, "\tif (key_type != JSON_OBJECT_END)\n"); fprintf(outfile, "\t\treturn gcli_error(ctx, \"unexpected object key type " "in parse_%s\");\n", p->name); fprintf(outfile, "\treturn 0;\n"); fprintf(outfile, "}\n\n"); } void arrayparser_dump_c(struct arrayparser *p) { fprintf(outfile, "int\n" "parse_%s(struct gcli_ctx *ctx, struct json_stream *stream, %s%s **out, " "size_t *out_size)\n", 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"); fprintf(outfile, "\t\t*out = NULL;\n"); fprintf(outfile, "\t\t*out_size = 0;\n"); fprintf(outfile, "\t\treturn 0;\n"); fprintf(outfile, "\t}\n\n"); fprintf(outfile, "\tif (json_next(stream) != JSON_ARRAY)\n"); fprintf(outfile, "\t\treturn gcli_error(ctx, \"Expected array of %s array in parse_%s\");\n\n", p->returntype, p->name); fprintf(outfile, "\twhile (json_peek(stream) != JSON_ARRAY_END) {\n"); fprintf(outfile, "\t\tint rc;\n"); 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"); fprintf(outfile, "\t\trc = %s(ctx, stream, it);\n", p->parser); fprintf(outfile, "\t\tif (rc < 0)\n"); fprintf(outfile, "\t\t\treturn rc;\n"); fprintf(outfile, "\t}\n\n"); fprintf(outfile, "\tif (json_next(stream) != JSON_ARRAY_END)\n"); fprintf(outfile, "\t\treturn gcli_error(ctx, \"unexpected element in array " "while parsing %s\");\n", p->name); fprintf(outfile, "\treturn 0;\n"); fprintf(outfile, "}\n\n"); } void include_dump_c(const char *file) { fprintf(outfile, "#include <%s>\n", file); } void header_dump_c(void) { fprintf(outfile, "#include \n"); fprintf(outfile, "#include \n"); fprintf(outfile, "#include \n"); fprintf(outfile, "#include \n"); fprintf(outfile, "#include \n"); fprintf(outfile, "#include \n"); fprintf(outfile, "#include <%.*s.h>\n", (int)(strlen(outfilename) - 2), outfilename); } gcli-2.3.0/src/pgen/dump_h.c000066400000000000000000000052351460062271200155770ustar00rootroot00000000000000/* * Copyright 2022 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 static int should_replace(char c) { return c == '_' || c == '/' || c == '.' || c == '-'; } static char * get_header_name(void) { size_t len; char *result; len = strlen(outfilename); result = calloc(len + 1, 1); for (size_t i = 0; i < len; ++i) { if (should_replace(outfilename[i])) result[i] = '_'; else result[i] = toupper(outfilename[i]); } return result; } void header_dump_h(void) { char *hname = get_header_name(); fprintf(outfile, "#ifndef %s\n", hname); fprintf(outfile, "#define %s\n\n", hname); fprintf(outfile, "#include \n"); free(hname); } void objparser_dump_h(struct objparser *p) { fprintf(outfile, "int parse_%s(struct gcli_ctx *ctx, struct json_stream *, %s%s *);\n", p->name, p->is_struct ? "struct " : "", p->returntype); } void include_dump_h(const char *file) { fprintf(outfile, "#include <%s>\n", file); } void footer_dump_h(void) { char *hname = get_header_name(); fprintf(outfile, "\n#endif /* %s */\n", hname); free(hname); } void arrayparser_dump_h(struct arrayparser *p) { 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); } gcli-2.3.0/src/pgen/dump_plain.c000066400000000000000000000035011460062271200164450ustar00rootroot00000000000000/* * Copyright 2022 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 void objparser_dump_plain(struct objparser *p) { fprintf(outfile, "object parser: name = %s, type = %s\n", p->name, p->returntype); for (struct objentry *it = p->entries; it != NULL; it = it->next) { fprintf(outfile, " entry: kind = %s, jsonname = %s, name = %s, type = %s, " "parser = %s\n", it->kind == OBJENTRY_SIMPLE ? "simple" : "array", it->name, it->jsonname, it->type, it->parser); } } gcli-2.3.0/src/pgen/lexer.l000066400000000000000000000071401460062271200154500ustar00rootroot00000000000000/* * Copyright 2022 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. */ %option noyywrap %{ #include #include #include #include "src/pgen/parser.h" int yycol = 1, yyrow = 1; char *yyfile = NULL; void yyerror(const char *msg) { fprintf(stderr, "%s:%d:%d: error: %s\n", yyfile, yyrow, yycol, msg); exit(1); } %} %% \n { yycol = 1; yyrow += 1; } [ \t] { yycol += 1; } parser { yycol += yyleng; return PARSER; } include { yycol += yyleng; return INCLUDE; } is { yycol += yyleng; return IS; } object { yycol += yyleng; return OBJECT; } with { yycol += yyleng; return WITH; } as { yycol += yyleng; return AS; } 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; } ";" { yycol += yyleng; return SEMICOLON; } "," { yycol += yyleng; return COMMA; } [A-Za-z][A-Za-z0-9_*]* { yycol += yyleng; yylval.ident.text = strdup(yytext); return IDENT; } \"[^\"]*\" { yycol += yyleng; yylval.strlit.text = strdup(yytext + 1); yylval.strlit.text[strlen(yytext + 1) - 1] = '\0'; return STRLIT; } . { yyerror("unrecognized character"); } %% gcli-2.3.0/src/pgen/parser.y000066400000000000000000000212371460062271200156450ustar00rootroot00000000000000/* * Copyright 2022 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 FILE *outfile = NULL; char *outfilename = NULL; int dumptype = 0; /* Forward declaration. Is in generated lexer.c */ extern int yylex (void); static void objparser_dump(struct objparser *); static void arrayparser_dump(struct arrayparser *); static void include_dump(const char *); static void header_dump(void); static void footer_dump(void); %} %token PARSER IS OBJECT WITH AS USE FATARROW INCLUDE %token OPAREN CPAREN SEMICOLON ARRAY OF COMMA SELECT STRUCT %union { struct strlit strlit; struct ident ident; struct objentry objentry; struct objentry *objentries; struct objparser objparser; struct arrayparser arrayparser; } %token STRLIT %token IDENT %type obj_entry %type obj_entries %type objparser %type arrayparser; %% input: instruction input | ; instruction: objparser SEMICOLON { objparser_dump(&($1)); } | arrayparser SEMICOLON { arrayparser_dump(&($1)); } | INCLUDE STRLIT SEMICOLON { include_dump($2.text); } ; 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; $$.name = $2.text; $$.returntype = $6.text; $$.select.fieldname = $8.text; $$.select.fieldtype = $10.text; } ; 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 { $$ = malloc(sizeof(*($$))); *($$) = $3; $$->next = $1; } | obj_entry { $$ = malloc(sizeof(*($$))); *($$) = $1; $$->next = NULL; } ; obj_entry: STRLIT FATARROW IDENT AS IDENT { $$.jsonname = $1.text; $$.kind = OBJENTRY_SIMPLE; $$.name = $3.text; $$.type = $5.text; $$.parser = NULL; } | STRLIT FATARROW IDENT AS IDENT USE IDENT { $$.jsonname = $1.text; $$.kind = OBJENTRY_SIMPLE; $$.name = $3.text; $$.type = $5.text; $$.parser = $7.text; } | STRLIT FATARROW IDENT AS ARRAY OF IDENT USE IDENT { $$.jsonname = $1.text; $$.kind = OBJENTRY_ARRAY; $$.name = $3.text; $$.type = $7.text; $$.parser = $9.text; } | STRLIT FATARROW USE IDENT { $$.jsonname = $1.text; $$.kind = OBJENTRY_CONTINUATION; $$.parser = $4.text; } ; %% #include #include #include #include #include extern FILE *yyin; extern char *yyfile; /******************************************************************************/ /* Table of functions to call when dumping various parts of the output * file */ struct { void (*dump_header)(void); void (*dump_footer)(void); void (*dump_objparser)(struct objparser *); void (*dump_arrayparser)(struct arrayparser *); void (*dump_include)(const char *); } dumpers[] = { [DUMP_PLAIN] = { .dump_objparser = objparser_dump_plain }, [DUMP_C] = { .dump_header = header_dump_c, .dump_objparser = objparser_dump_c, .dump_include = include_dump_c, .dump_arrayparser = arrayparser_dump_c, }, [DUMP_H] = { .dump_header = header_dump_h, .dump_objparser = objparser_dump_h, .dump_include = include_dump_h, .dump_footer = footer_dump_h, .dump_arrayparser = arrayparser_dump_h, } }; /* Helpers */ static void objparser_dump(struct objparser *p) { if (dumpers[dumptype].dump_objparser) dumpers[dumptype].dump_objparser(p); else yyerror("internal error: don't know how to dump an object parser"); } static void arrayparser_dump(struct arrayparser *p) { if (dumpers[dumptype].dump_arrayparser) dumpers[dumptype].dump_arrayparser(p); else yyerror("internal error: don't know how to dump an array parser"); } static void include_dump(const char *file) { if (dumpers[dumptype].dump_include) dumpers[dumptype].dump_include(file); } static void header_dump(void) { if (dumpers[dumptype].dump_header) dumpers[dumptype].dump_header(); } static void footer_dump(void) { if (dumpers[dumptype].dump_footer) dumpers[dumptype].dump_footer(); } /******************************************************************************/ static void usage(void) { fprintf(stderr, "usage: pgen [-v] [-o outputfile] [-t c|h|plain] [...]\n"); fprintf(stderr, "OPTIONS:\n"); fprintf(stderr, " -v Print version and exit\n"); fprintf(stderr, " -o file Dump output into the given file\n"); fprintf(stderr, " -t type Type of the output. Can be either c, h or plain.\n"); } int main(int argc, char *argv[]) { int ch; while ((ch = getopt(argc, argv, "hvo:t:")) != -1) { switch (ch) { case 'o': { if (outfile) errx(1, "cannot specify -o more than once"); outfile = fopen(optarg, "w"); outfilename = optarg; } break; case 'v': { fprintf(stderr, "pgen version 0.1\n"); exit(0); } break; case 't': { if (strcmp(optarg, "plain") == 0) dumptype = DUMP_PLAIN; else if (strcmp(optarg, "c") == 0) dumptype = DUMP_C; else if (strcmp(optarg, "h") == 0) dumptype = DUMP_H; else errx(1, "invalid dump type %s", optarg); } break; case '?': case '-': default: usage(); exit(1); } } argc -= optind; argv += optind; if (!outfile) { outfile = stdout; outfilename = ""; } header_dump(); if (argc) { for (int i = 0; i < argc; ++i) { yyfile = argv[i]; yyin = fopen(argv[i], "r"); yyparse(); fclose(yyin); } } else { yyfile = ""; yyin = stdin; yyparse(); } footer_dump(); fclose(outfile); return 0; } gcli-2.3.0/src/pulls.c000066400000000000000000000155711460062271200145350ustar00rootroot00000000000000/* * Copyright 2021,2022 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 void 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]); free(it->pulls); it->pulls = NULL; it->pulls_size = 0; } int gcli_search_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) { gcli_null_check_call(search_pulls, ctx, owner, repo, details, max, out); } int gcli_pull_get_diff(struct gcli_ctx *ctx, FILE *stream, char const *owner, char const *reponame, gcli_id const pr_number) { gcli_null_check_call(pull_get_diff, ctx, stream, owner, reponame, pr_number); } int 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) { gcli_null_check_call(get_pull_commits, ctx, owner, repo, pr_number, out); } void gcli_commits_free(struct gcli_commit_list *list) { for (size_t i = 0; i < list->commits_size; ++i) { free(list->commits[i].sha); free(list->commits[i].long_sha); free(list->commits[i].message); free(list->commits[i].date); free(list->commits[i].author); free(list->commits[i].email); } free(list->commits); list->commits = NULL; list->commits_size = 0; } void gcli_pull_free(struct gcli_pull *const it) { free(it->author); free(it->state); free(it->title); free(it->body); free(it->created_at); free(it->commits_link); free(it->head_label); free(it->base_label); free(it->head_sha); 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]); free(it->labels); } int gcli_get_pull(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const pr_number, struct gcli_pull *const out) { gcli_null_check_call(get_pull, ctx, owner, repo, pr_number, out); } int 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) { gcli_null_check_call(get_pull_checks, ctx, owner, repo, pr_number, out); } void gcli_pull_checks_free(struct gcli_pull_checks_list *list) { switch (list->forge_type) { case GCLI_FORGE_GITHUB: github_free_checks((struct github_check_list *)list); break; case GCLI_FORGE_GITLAB: gitlab_pipelines_free((struct gitlab_pipeline_list *)list); break; default: assert(0 && "unreachable"); } } int gcli_pull_submit(struct gcli_ctx *ctx, struct gcli_submit_pull_options *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(struct gcli_ctx *ctx, char const *owner, char const *reponame, gcli_id const pr_number, enum gcli_merge_flags flags) { gcli_null_check_call(pull_merge, ctx, owner, reponame, pr_number, flags); } int gcli_pull_close(struct gcli_ctx *ctx, char const *owner, char const *reponame, gcli_id const pr_number) { gcli_null_check_call(pull_close, ctx, owner, reponame, pr_number); } int gcli_pull_reopen(struct gcli_ctx *ctx, char const *owner, char const *reponame, gcli_id const pr_number) { gcli_null_check_call(pull_reopen, ctx, owner, reponame, pr_number); } int 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) { gcli_null_check_call(pull_add_labels, ctx, owner, repo, pr_number, labels, labels_size); } int 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) { gcli_null_check_call(pull_remove_labels, ctx, owner, repo, pr_number, labels, labels_size); } int gcli_pull_set_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const pr_number, int milestone_id) { gcli_null_check_call(pull_set_milestone, ctx, owner, repo, pr_number, milestone_id); } int gcli_pull_clear_milestone(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id const pr_number) { gcli_null_check_call(pull_clear_milestone, ctx, owner, repo, pr_number); } int gcli_pull_add_reviewer(struct gcli_ctx *ctx, char const *owner, char const *repo, gcli_id pr_number, char const *username) { gcli_null_check_call(pull_add_reviewer, ctx, owner, repo, pr_number, username); } int gcli_pull_get_patch(struct gcli_ctx *ctx, FILE *out, char const *owner, char const *repo, gcli_id pull_id) { gcli_null_check_call(pull_get_patch, ctx, out, owner, repo, pull_id); } int gcli_pull_set_title(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_id const pull, char const *new_title) { gcli_null_check_call(pull_set_title, ctx, owner, repo, pull, new_title); } gcli-2.3.0/src/releases.c000066400000000000000000000056511460062271200151770ustar00rootroot00000000000000/* * Copyright 2021,2022 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 int gcli_get_releases(struct gcli_ctx *ctx, char const *owner, char const *repo, int const max, struct gcli_release_list *const list) { gcli_null_check_call(get_releases, ctx, owner, repo, max, list); } void gcli_release_free(struct gcli_release *release) { 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); free(release->assets[i].url); } free(release->assets); } void 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]); } free(list->releases); list->releases = NULL; list->releases_size = 0; } int gcli_create_release(struct gcli_ctx *ctx, struct gcli_new_release const *release) { gcli_null_check_call(create_release, ctx, release); } int 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"); release->assets[release->assets_size++] = asset; return 0; } int gcli_delete_release(struct gcli_ctx *ctx, char const *const owner, char const *const repo, char const *const id) { gcli_null_check_call(delete_release, ctx, owner, repo, id); } gcli-2.3.0/src/repos.c000066400000000000000000000051131460062271200145150ustar00rootroot00000000000000/* * Copyright 2021, 2022 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 int gcli_get_repos(struct gcli_ctx *ctx, char const *owner, int const max, struct gcli_repo_list *const out) { gcli_null_check_call(get_repos, ctx, owner, max, out); } void gcli_repo_free(struct gcli_repo *it) { 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(struct gcli_repo_list *const list) { for (size_t i = 0; i < list->repos_size; ++i) { gcli_repo_free(&list->repos[i]); } free(list->repos); list->repos = NULL; list->repos_size = 0; } int gcli_repo_delete(struct gcli_ctx *ctx, char const *owner, char const *repo) { gcli_null_check_call(repo_delete, ctx, owner, repo); } int gcli_repo_create(struct gcli_ctx *ctx, struct gcli_repo_create_options const *options, struct gcli_repo *out) { gcli_null_check_call(repo_create, ctx, options, out); } int gcli_repo_set_visibility(struct gcli_ctx *ctx, char const *const owner, char const *const repo, gcli_repo_visibility vis) { gcli_null_check_call(repo_set_visibility, ctx, owner, repo, vis); } gcli-2.3.0/src/sshkeys.c000066400000000000000000000047301460062271200150620ustar00rootroot00000000000000/* * 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. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include int gcli_sshkeys_get_keys(struct gcli_ctx *ctx, struct gcli_sshkey_list *out) { gcli_null_check_call(get_sshkeys, ctx, out); } void gcli_sshkeys_free_keys(struct gcli_sshkey_list *list) { for (size_t i = 0; i < list->keys_size; ++i) { free(list->keys[i].title); free(list->keys[i].key); free(list->keys[i].created_at); } free(list->keys); list->keys = NULL; list->keys_size = 0; } int 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 = forge->add_sshkey(ctx, title, buffer, out); free(buffer); return rc; } int gcli_sshkeys_delete_key(struct gcli_ctx *ctx, gcli_id const id) { gcli_null_check_call(delete_sshkey, ctx, id); } gcli-2.3.0/src/status.c000066400000000000000000000043401460062271200147110ustar00rootroot00000000000000/* * Copyright 2022 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 int gcli_get_notifications(struct gcli_ctx *ctx, int const max, struct gcli_notification_list *const out) { gcli_null_check_call(get_notifications, ctx, max, out); } void gcli_free_notification(struct gcli_notification *const notification) { free(notification->id); free(notification->title); free(notification->reason); free(notification->date); free(notification->type); free(notification->repository); } void gcli_free_notifications(struct gcli_notification_list *list) { for (size_t i = 0; i < list->notifications_size; ++i) { gcli_free_notification(&list->notifications[i]); } free(list->notifications); list->notifications = NULL; list->notifications_size = 0; } int gcli_notification_mark_as_read(struct gcli_ctx *ctx, char const *id) { gcli_null_check_call(notification_mark_as_read, ctx, id); } gcli-2.3.0/templates/000077500000000000000000000000001460062271200144305ustar00rootroot00000000000000gcli-2.3.0/templates/bugzilla/000077500000000000000000000000001460062271200162415ustar00rootroot00000000000000gcli-2.3.0/templates/bugzilla/api.t000066400000000000000000000001111460062271200171700ustar00rootroot00000000000000parser bugzilla_get_error is object of char* select "message" as string; gcli-2.3.0/templates/bugzilla/bugs.t000066400000000000000000000050721460062271200173720ustar00rootroot00000000000000include "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); parser bugzilla_bug_creation_result is object of gcli_id select "id" as id; gcli-2.3.0/templates/gitea/000077500000000000000000000000001460062271200155215ustar00rootroot00000000000000gcli-2.3.0/templates/gitea/milestones.t000066400000000000000000000007671460062271200201020ustar00rootroot00000000000000include "gcli/milestones.h"; parser gitea_milestone is object of struct gcli_milestone with ("id" => id as id, "title" => title as string, "created_at" => created_at as string, "description" => description as string, "state" => state as string, "updated_at" => updated_at as string, "open_issues" => open_issues as int, "due_on" => due_date as string, "closed_issues" => closed_issues as int); parser gitea_milestones is array of struct gcli_milestone use parse_gitea_milestone; gcli-2.3.0/templates/gitea/status.t000066400000000000000000000011601460062271200172270ustar00rootroot00000000000000include "gcli/status.h"; parser gitea_notification_repository is object of struct gcli_notification with ("full_name" => repository as string); parser gitea_notification_status is object of struct gcli_notification with ("title" => title as string, "type" => type as string); parser gitea_notification is 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 struct gcli_notification use parse_gitea_notification; gcli-2.3.0/templates/github/000077500000000000000000000000001460062271200157125ustar00rootroot00000000000000gcli-2.3.0/templates/github/api.t000066400000000000000000000001071460062271200166460ustar00rootroot00000000000000parser github_get_error is object of char* select "message" as string; gcli-2.3.0/templates/github/checks.t000066400000000000000000000007541460062271200173450ustar00rootroot00000000000000include "gcli/github/checks.h"; parser github_check is object of struct gcli_github_check with ("name" => name as string, "status" => status as string, "conclusion" => conclusion as string, "started_at" => started_at as string, "completed_at" => completed_at as string, "id" => id as id); parser github_checks is object of struct github_check_list with ("check_runs" => checks as array of gcli_github_check use parse_github_check); gcli-2.3.0/templates/github/comments.t000066400000000000000000000004651460062271200177310ustar00rootroot00000000000000include "gcli/github/comments.h"; parser github_comment is 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 struct gcli_comment use parse_github_comment; gcli-2.3.0/templates/github/forks.t000066400000000000000000000004501460062271200172220ustar00rootroot00000000000000include "gcli/forks.h"; parser github_fork is 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 struct gcli_fork use parse_github_fork; gcli-2.3.0/templates/github/gists.t000066400000000000000000000013051460062271200172270ustar00rootroot00000000000000include "gcli/json_util.h"; include "gcli/github/gists.h"; parser github_gist_file is 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 string); parser github_gist is 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 struct gcli_gist use parse_github_gist; gcli-2.3.0/templates/github/issues.t000066400000000000000000000017771460062271200174260ustar00rootroot00000000000000include "gcli/issues.h"; include "gcli/labels.h"; include "templates/github/labels.h"; parser github_issue_milestone is object of struct gcli_issue with ("title" => milestone as string); parser github_issue is 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, "locked" => locked as bool, "labels" => labels as array of github_label use parse_github_label_text, "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 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); gcli-2.3.0/templates/github/labels.t000066400000000000000000000006111460062271200173370ustar00rootroot00000000000000include "gcli/github/labels.h"; parser github_label_text is object of char* select "name" as string; parser github_label is 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 struct gcli_label use parse_github_label; gcli-2.3.0/templates/github/milestones.t000066400000000000000000000007271460062271200202670ustar00rootroot00000000000000include "gcli/milestones.h"; parser github_milestone is object of struct gcli_milestone with ("number" => id as id, "title" => title as string, "created_at" => created_at as string, "state" => state as string, "updated_at" => updated_at as string, "description" => description as string, "open_issues" => open_issues as int, "closed_issues" => closed_issues as int); parser github_milestones is array of struct gcli_milestone use parse_github_milestone; gcli-2.3.0/templates/github/pulls.t000066400000000000000000000044031460062271200172370ustar00rootroot00000000000000include "gcli/pulls.h"; include "templates/github/labels.h"; parser github_commit_author_field is 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 struct gcli_commit with ("message" => message as string, "author" => use parse_github_commit_author_field); parser github_commit is object of struct gcli_commit with ("sha" => long_sha as string, "commit" => use parse_github_commit_commit_field); parser github_commits is array of struct gcli_commit use parse_github_commit; parser github_pull_head is object of struct gcli_pull with ("sha" => head_sha as string, "label" => head_label as string); parser github_branch_label is object of struct gcli_pull with ("label" => base_label as string); parser github_pull_milestone is 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 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, "comments" => comments as int, "additions" => additions as int, "deletions" => deletions as int, "changed_files" => changed_files as int, "merged_at" => merged as is_string, "mergeable" => mergeable as bool, "draft" => draft as bool, "user" => author as user, "head" => use parse_github_pull_head, "base" => use parse_github_branch_label, "milestone" => use parse_github_pull_milestone, "requested_reviewers" => reviewers as array of char* use parse_github_reviewer); parser github_pr_merge_message is object of char* select "message" as string; parser github_pulls is array of struct gcli_pull use parse_github_pull; parser github_pull_search_result is object of struct gcli_pull_list with ("items" => pulls as array of gcli_pull use parse_github_pull); gcli-2.3.0/templates/github/releases.t000066400000000000000000000013251460062271200177030ustar00rootroot00000000000000include "gcli/releases.h"; parser github_release_asset is object of struct gcli_release_asset with ("browser_download_url" => url as string, "name" => name as string); parser github_release is 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 string); parser github_releases is array of struct gcli_release use parse_github_release; gcli-2.3.0/templates/github/repos.t000066400000000000000000000007441460062271200172340ustar00rootroot00000000000000include "gcli/github/repos.h"; include "gcli/gitea/repos.h"; parser github_repo is object of struct gcli_repo with ("id" => id as id, "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 struct gcli_repo use parse_github_repo; gcli-2.3.0/templates/github/status.t000066400000000000000000000012521460062271200174220ustar00rootroot00000000000000include "gcli/github/status.h"; parser github_notification_subject is object of struct gcli_notification with ("title" => title as string, "type" => type as string); parser github_notification_repository is object of struct gcli_notification with ("full_name" => repository as string); parser github_notification is 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 struct gcli_notification use parse_github_notification; gcli-2.3.0/templates/gitlab/000077500000000000000000000000001460062271200156725ustar00rootroot00000000000000gcli-2.3.0/templates/gitlab/api.t000066400000000000000000000001071460062271200166260ustar00rootroot00000000000000parser gitlab_get_error is object of char* select "message" as string; gcli-2.3.0/templates/gitlab/comments.t000066400000000000000000000004641460062271200177100ustar00rootroot00000000000000include "gcli/gitlab/comments.h"; parser gitlab_comment is object of struct gcli_comment with ("created_at" => date as string, "body" => body as string, "author" => author as user, "id" => id as id); parser gitlab_comments is array of struct gcli_comment use parse_gitlab_comment; gcli-2.3.0/templates/gitlab/forks.t000066400000000000000000000007041460062271200172040ustar00rootroot00000000000000include "gcli/gitlab/forks.h"; parser gitlab_fork_namespace is object of struct gcli_fork with ("full_path" => owner as string); parser gitlab_fork is object of struct gcli_fork with ("path_with_namespace" => full_name as string, "namespace" => use parse_gitlab_fork_namespace, "created_at" => date as string, "forks_count" => forks as int); parser gitlab_forks is array of struct gcli_fork use parse_gitlab_fork; gcli-2.3.0/templates/gitlab/issues.t000066400000000000000000000016071460062271200173760ustar00rootroot00000000000000include "gcli/gitlab/issues.h"; parser gitlab_user is object of char* select "username" as string; parser gitlab_issue_milestone is object of struct gcli_issue with ("title" => milestone as string); parser gitlab_issue is 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, "discussion_locked" => locked as bool, "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 struct gcli_issue use parse_gitlab_issue; gcli-2.3.0/templates/gitlab/labels.t000066400000000000000000000005021460062271200173160ustar00rootroot00000000000000include "gcli/gitlab/labels.h"; parser gitlab_label is 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 struct gcli_label use parse_gitlab_label; gcli-2.3.0/templates/gitlab/merge_requests.t000066400000000000000000000052041460062271200211120ustar00rootroot00000000000000include "gcli/gitlab/merge_requests.h"; parser gitlab_mr_milestone is object of struct gcli_pull with ("title" => milestone as string); parser gitlab_mr_head_pipeline is 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 struct gcli_pull with ("base_sha" => base_sha as string, "head_sha" => head_sha as string); parser gitlab_mr is 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 struct gcli_pull use parse_gitlab_mr; parser gitlab_commit is object of struct gcli_commit with ("short_id" => sha as string, "id" => long_sha as string, "title" => message as string, "created_at" => date as string, "author_name" => author as string, "author_email" => email as string); 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 struct gitlab_reviewer_id_list with ("reviewers" => reviewers as array of gcli_id use parse_gitlab_reviewer_id); parser gitlab_diff is object of struct gitlab_diff with ("diff" => diff as string, "new_path" => new_path as string, "old_path" => old_path as string, "a_mode" => a_mode as string, "b_mode" => b_mode as string, "new_file" => new_file as bool, "renamed_file" => renamed_file as bool, "deleted_file" => deleted_file as bool); parser gitlab_diffs is array of struct gitlab_diff use parse_gitlab_diff; gcli-2.3.0/templates/gitlab/milestones.t000066400000000000000000000007051460062271200202430ustar00rootroot00000000000000include "gcli/milestones.h"; parser gitlab_milestone is object of struct gcli_milestone with ("title" => title as string, "id" => id as id, "state" => state as string, "created_at" => created_at as string, "description" => description as string, "updated_at" => updated_at as string, "due_date" => due_date as string, "expired" => expired as bool); parser gitlab_milestones is array of struct gcli_milestone use parse_gitlab_milestone; gcli-2.3.0/templates/gitlab/pipelines.t000066400000000000000000000021611460062271200200470ustar00rootroot00000000000000include "gcli/gitlab/pipelines.h"; parser gitlab_pipeline is object of struct gitlab_pipeline with ("status" => status as string, "created_at" => created_at as string, "updated_at" => updated_at as string, "ref" => ref as string, "sha" => sha as string, "source" => source as string, "id" => id as id); parser gitlab_pipelines is array of struct gitlab_pipeline use parse_gitlab_pipeline; parser gitlab_job_runner is object of struct gitlab_job with ("name" => runner_name as string, "description" => runner_description as string); parser gitlab_job is object of struct gitlab_job with ("status" => status as string, "stage" => stage as string, "name" => name as string, "ref" => ref as string, "created_at" => created_at as string, "started_at" => started_at as string, "finished_at" => finished_at as string, "runner" => use parse_gitlab_job_runner, "duration" => duration as double, "id" => id as id, "coverage" => coverage as double); parser gitlab_jobs is array of struct gitlab_job use parse_gitlab_job; gcli-2.3.0/templates/gitlab/releases.t000066400000000000000000000013521460062271200176630ustar00rootroot00000000000000include "gcli/gitlab/releases.h"; parser gitlab_release_asset is object of struct gcli_release_asset with ("url" => url as string); parser gitlab_release_assets is 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 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, "created_at" => date as string, "upcoming_release" => prerelease as bool); parser gitlab_releases is array of struct gcli_release use parse_gitlab_release; gcli-2.3.0/templates/gitlab/repos.t000066400000000000000000000007221460062271200172100ustar00rootroot00000000000000include "gcli/gitlab/repos.h"; parser gitlab_repo is 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 struct gcli_repo use parse_gitlab_repo; gcli-2.3.0/templates/gitlab/snippets.t000066400000000000000000000007231460062271200177260ustar00rootroot00000000000000include "gcli/json_util.h"; include "gcli/gitlab/snippets.h"; parser gitlab_snippet is object of struct gcli_gitlab_snippet with ("title" => title as string, "id" => id as int, "raw_url" => raw_url as string, "created_at" => date as string, "file_name" => filename as string, "author" => author as user, "visibility" => visibility as string); parser gitlab_snippets is array of struct gcli_gitlab_snippet use parse_gitlab_snippet; gcli-2.3.0/templates/gitlab/sshkeys.t000066400000000000000000000004311460062271200175460ustar00rootroot00000000000000include "gcli/sshkeys.h"; parser gitlab_sshkey is 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 struct gcli_sshkey use parse_gitlab_sshkey; gcli-2.3.0/templates/gitlab/status.t000066400000000000000000000010111460062271200173730ustar00rootroot00000000000000include "gcli/gitlab/status.h"; parser gitlab_project is object of struct gcli_notification with ("path_with_namespace" => repository as string); parser gitlab_todo is object of struct gcli_notification with ("updated_at" => date as string, "action_name" => reason as string, "id" => id as int_to_string, "body" => title as string, "target_type" => type as string, "project" => use parse_gitlab_project); parser gitlab_todos is array of struct gcli_notification use parse_gitlab_todo; gcli-2.3.0/tests/000077500000000000000000000000001460062271200135745ustar00rootroot00000000000000gcli-2.3.0/tests/.gitignore000066400000000000000000000000121460062271200155550ustar00rootroot00000000000000/Kyuafile gcli-2.3.0/tests/Kyuafile.in000066400000000000000000000010061460062271200156720ustar00rootroot00000000000000syntax(2) test_suite('gcli') atf_test_program{ name='json-escape' } atf_test_program{ name = 'github-parse-tests', } atf_test_program{ name = 'gitlab-parse-tests', } atf_test_program{ name = 'gitea-parse-tests', } atf_test_program{ name = 'bugzilla-parse-tests', } atf_test_program{ name = 'base64-tests' } atf_test_program{ name = 'url-encode' } atf_test_program{ name = 'pretty_print_test' } atf_test_program{ name = 'test-jsongen' } gcli-2.3.0/tests/base64-tests.c000066400000000000000000000006421460062271200161660ustar00rootroot00000000000000#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(); } gcli-2.3.0/tests/bugzilla-parse-tests.c000066400000000000000000000120501460062271200200170ustar00rootroot00000000000000/* * 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(); } gcli-2.3.0/tests/gcli_tests.h000066400000000000000000000032701460062271200161070ustar00rootroot00000000000000/* * 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_TESTS_H #define GCLI_TESTS_H #if defined(HAVE_CONFIG_H) #include #endif /* HAVE_CONFIG_H */ #include /* Helper for making assertions on my custom string views. These * should get removed at some point */ #define ATF_CHECK_SV_EQTO(view, str) \ ATF_CHECK(sn_sv_eq_to((view), str)) #endif /* GCLI_TESTS_H */ gcli-2.3.0/tests/gitea-parse-tests.c000066400000000000000000000027721460062271200173110ustar00rootroot00000000000000#include #include #include #include #include #include #include "gcli_tests.h" #include static gcli_forge_type get_gitea_forge_type(struct gcli_ctx *ctx) { (void) ctx; return GCLI_FORGE_GITEA; } static struct gcli_ctx * test_context(void) { struct gcli_ctx *ctx; ATF_REQUIRE(gcli_init(&ctx, get_gitea_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); ATF_REQUIRE(r = fopen(p, "r")); return r; } ATF_TC_WITHOUT_HEAD(gitea_simple_notification); ATF_TC_BODY(gitea_simple_notification, tc) { struct gcli_notification notification = {0}; FILE *sample; struct json_stream stream = {0}; struct gcli_ctx *ctx; ctx = test_context(); sample = open_sample("gitea_simple_notification.json"); json_open_stream(&stream, sample); ATF_REQUIRE(parse_gitea_notification(ctx, &stream, ¬ification) == 0); ATF_CHECK_STREQ(notification.id, "511579"); ATF_CHECK_STREQ(notification.title, "Remove register from C++ sources"); ATF_CHECK(notification.reason == NULL); ATF_CHECK_STREQ(notification.date, "2023-11-24T21:01:50Z"); ATF_CHECK_STREQ(notification.repository, "schilytools/schilytools"); fclose(sample); gcli_free_notification(¬ification); gcli_destroy(&ctx); } ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, gitea_simple_notification); return atf_no_error(); } gcli-2.3.0/tests/github-parse-tests.c000066400000000000000000000262151460062271200175000ustar00rootroot00000000000000/* * 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 #include #include #include #include #include "gcli_tests.h" static gcli_forge_type get_github_forge_type(struct gcli_ctx *ctx) { (void) ctx; return GCLI_FORGE_GITHUB; } static struct gcli_ctx * test_context(void) { struct gcli_ctx *ctx; ATF_REQUIRE(gcli_init(&ctx, get_github_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_github_issue); ATF_TC_BODY(simple_github_issue, tc) { struct gcli_issue issue = {0}; FILE *f; struct json_stream stream; struct gcli_ctx *ctx = test_context(); ATF_REQUIRE(f = open_sample("github_simple_issue.json")); json_open_stream(&stream, f); ATF_REQUIRE(parse_github_issue(ctx, &stream, &issue) == 0); ATF_CHECK(issue.number = 115); 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_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); ATF_CHECK(issue.assignees_size == 0); ATF_CHECK(issue.assignees == NULL); ATF_CHECK(issue.is_pr == 0); ATF_CHECK(!issue.milestone); json_close(&stream); gcli_issue_free(&issue); gcli_destroy(&ctx); } ATF_TC_WITHOUT_HEAD(simple_github_pull); ATF_TC_BODY(simple_github_pull, tc) { struct gcli_pull pull = {0}; FILE *f; struct json_stream stream; struct gcli_ctx *ctx = test_context(); ATF_REQUIRE(f = open_sample("github_simple_pull.json")); json_open_stream(&stream, f); ATF_REQUIRE(parse_github_pull(ctx, &stream, &pull) == 0); ATF_CHECK_STREQ(pull.author, "herrhotzenplotz"); ATF_CHECK_STREQ(pull.state, "closed"); ATF_CHECK_STREQ(pull.title, "mark notifications as read/done"); ATF_CHECK_STREQ(pull.body, "Fixes #99\n"); ATF_CHECK_STREQ(pull.created_at, "2022-03-22T13:20:57Z"); ATF_CHECK_STREQ(pull.head_label, "herrhotzenplotz:99"); ATF_CHECK_STREQ(pull.base_label, "herrhotzenplotz:trunk"); ATF_CHECK_STREQ(pull.head_sha, "a00f475af1e31d56c7a5839508a21e2b76a31e49"); ATF_CHECK(pull.milestone == NULL); ATF_CHECK(pull.id == 886044243); ATF_CHECK(pull.comments == 0); ATF_CHECK(pull.additions == 177); ATF_CHECK(pull.deletions == 82); ATF_CHECK(pull.commits == 6); ATF_CHECK(pull.changed_files == 13); ATF_CHECK(pull.labels == NULL); ATF_CHECK(pull.labels_size == 0); ATF_CHECK(pull.merged == true); ATF_CHECK(pull.mergeable == false); ATF_CHECK(pull.draft == false); json_close(&stream); gcli_pull_free(&pull); gcli_destroy(&ctx); } ATF_TC_WITHOUT_HEAD(simple_github_label); ATF_TC_BODY(simple_github_label, tc) { struct gcli_label label = {0}; FILE *f; struct json_stream stream; struct gcli_ctx *ctx = test_context(); ATF_REQUIRE(f = open_sample("github_simple_label.json")); json_open_stream(&stream, f); ATF_REQUIRE(parse_github_label(ctx, &stream, &label) == 0); ATF_CHECK(label.id == 3431203676); ATF_CHECK_STREQ(label.name, "bug"); ATF_CHECK_STREQ(label.description, "Something isn't working"); ATF_CHECK(label.colour == 0xd73a4a00); json_close(&stream); gcli_free_label(&label); gcli_destroy(&ctx); } ATF_TC_WITHOUT_HEAD(simple_github_milestone); ATF_TC_BODY(simple_github_milestone, tc) { struct gcli_milestone milestone = {0}; FILE *f; struct json_stream stream; struct gcli_ctx *ctx = test_context(); ATF_REQUIRE(f = open_sample("github_simple_milestone.json")); json_open_stream(&stream, f); ATF_REQUIRE(parse_github_milestone(ctx, &stream, &milestone) == 0); ATF_CHECK(milestone.id == 1); ATF_CHECK_STREQ(milestone.title, "Gitlab support"); ATF_CHECK_STREQ(milestone.state, "open"); ATF_CHECK_STREQ(milestone.created_at, "2021-12-14T07:02:05Z"); ATF_CHECK_STREQ(milestone.description, ""); ATF_CHECK_STREQ(milestone.updated_at, "2021-12-19T14:49:43Z"); ATF_CHECK(milestone.due_date == NULL); ATF_CHECK(milestone.expired == false); ATF_CHECK(milestone.open_issues == 0); ATF_CHECK(milestone.closed_issues == 8); json_close(&stream); gcli_free_milestone(&milestone); gcli_destroy(&ctx); } ATF_TC_WITHOUT_HEAD(simple_github_release); ATF_TC_BODY(simple_github_release, tc) { struct gcli_release release = {0}; FILE *f; 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_STREQ(release.id, "116031718"); ATF_CHECK(release.assets_size == 0); ATF_CHECK(release.assets == NULL); 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); json_close(&stream); gcli_release_free(&release); gcli_destroy(&ctx); } ATF_TC_WITHOUT_HEAD(simple_github_repo); ATF_TC_BODY(simple_github_repo, tc) { struct gcli_repo repo = {0}; FILE *f; struct json_stream stream; struct gcli_ctx *ctx = test_context(); ATF_REQUIRE(f = open_sample("github_simple_repo.json")); json_open_stream(&stream, f); ATF_REQUIRE(parse_github_repo(ctx, &stream, &repo) == 0); ATF_CHECK(repo.id == 415015197); 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); gcli_repo_free(&repo); gcli_destroy(&ctx); } ATF_TC_WITHOUT_HEAD(simple_github_fork); ATF_TC_BODY(simple_github_fork, tc) { struct gcli_fork fork = {0}; FILE *f; 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_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); gcli_fork_free(&fork); gcli_destroy(&ctx); } ATF_TC_WITHOUT_HEAD(simple_github_comment); ATF_TC_BODY(simple_github_comment, tc) { struct gcli_comment comment = {0}; FILE *f; struct json_stream stream; struct gcli_ctx *ctx = test_context(); ATF_REQUIRE(f = open_sample("github_simple_comment.json")); json_open_stream(&stream, f); ATF_REQUIRE(parse_github_comment(ctx, &stream, &comment) == 0); ATF_CHECK(comment.id == 1424392601); ATF_CHECK_STREQ(comment.author, "herrhotzenplotz"); ATF_CHECK_STREQ(comment.date, "2023-02-09T15:37:54Z"); ATF_CHECK_STREQ(comment.body, "Hey,\n\nthe current trunk on Github might be a little outdated. I pushed the staging branch for version 1.0.0 from Gitlab to Github (cleanup-1.0). Could you try again with that branch and see if it still faults at the same place? If it does, please provide a full backtrace and if possible check with valgrind.\n"); json_close(&stream); gcli_comment_free(&comment); gcli_destroy(&ctx); } ATF_TC_WITHOUT_HEAD(simple_github_check); ATF_TC_BODY(simple_github_check, tc) { struct gcli_github_check check = {0}; FILE *f; struct json_stream stream; struct gcli_ctx *ctx = test_context(); ATF_REQUIRE(f = open_sample("github_simple_check.json")); json_open_stream(&stream, f); ATF_REQUIRE(parse_github_check(ctx, &stream, &check) == 0); ATF_CHECK_STREQ(check.name, "test Windows x86"); ATF_CHECK_STREQ(check.status, "completed"); ATF_CHECK_STREQ(check.conclusion, "success"); ATF_CHECK_STREQ(check.started_at, "2023-09-02T06:27:37Z"); ATF_CHECK_STREQ(check.completed_at, "2023-09-02T06:29:11Z"); ATF_CHECK(check.id == 16437184455); json_close(&stream); gcli_github_check_free(&check); gcli_destroy(&ctx); } ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, simple_github_issue); ATF_TP_ADD_TC(tp, simple_github_pull); ATF_TP_ADD_TC(tp, simple_github_label); ATF_TP_ADD_TC(tp, simple_github_milestone); ATF_TP_ADD_TC(tp, simple_github_release); ATF_TP_ADD_TC(tp, simple_github_repo); ATF_TP_ADD_TC(tp, simple_github_fork); ATF_TP_ADD_TC(tp, simple_github_comment); ATF_TP_ADD_TC(tp, simple_github_check); return atf_no_error(); } gcli-2.3.0/tests/gitlab-parse-tests.c000066400000000000000000000255751460062271200174700ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include "gcli_tests.h" static gcli_forge_type get_gitlab_forge_type(struct gcli_ctx *ctx) { (void) ctx; return GCLI_FORGE_GITLAB; } static struct gcli_ctx * test_context(void) { struct gcli_ctx *ctx; ATF_REQUIRE(gcli_init(&ctx, get_gitlab_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); ATF_REQUIRE(r = fopen(p, "r")); return r; } ATF_TC_WITHOUT_HEAD(gitlab_simple_merge_request); ATF_TC_BODY(gitlab_simple_merge_request, tc) { struct json_stream stream = {0}; struct gcli_ctx *ctx = test_context(); FILE *f = open_sample("gitlab_simple_merge_request.json"); struct gcli_pull pull = {0}; json_open_stream(&stream, f); ATF_REQUIRE(parse_gitlab_mr(ctx, &stream, &pull) == 0); ATF_CHECK_STREQ(pull.author, "herrhotzenplotz"); ATF_CHECK_STREQ(pull.state, "merged"); ATF_CHECK_STREQ(pull.title, "Fix test suite"); ATF_CHECK_STREQ(pull.body, "This finally fixes the broken test suite"); ATF_CHECK_STREQ(pull.created_at, "2023-08-31T23:37:50.848Z"); ATF_CHECK(pull.commits_link == NULL); ATF_CHECK_STREQ(pull.head_label, "fix-test-suite"); ATF_CHECK_STREQ(pull.base_label, "trunk"); ATF_CHECK_STREQ(pull.head_sha, "3eab596a6806434e4a34bb19de12307ab1217af3"); ATF_CHECK(pull.milestone == NULL); ATF_CHECK(pull.id == 246912053); ATF_CHECK(pull.number == 216); ATF_CHECK(pull.comments == 0); // not supported ATF_CHECK(pull.additions == 0); // not supported ATF_CHECK(pull.deletions == 0); // not supported ATF_CHECK(pull.commits == 0); // not supported ATF_CHECK(pull.changed_files == 0); // not supported ATF_CHECK(pull.head_pipeline_id == 989409992); ATF_CHECK(pull.labels_size == 0); ATF_CHECK(pull.labels == NULL); ATF_CHECK(pull.merged == false); ATF_CHECK(pull.mergeable == true); ATF_CHECK(pull.draft == false); json_close(&stream); gcli_pull_free(&pull); gcli_destroy(&ctx); } ATF_TC_WITHOUT_HEAD(gitlab_simple_issue); ATF_TC_BODY(gitlab_simple_issue, tc) { struct json_stream stream = {0}; struct gcli_ctx *ctx = test_context(); FILE *f = open_sample("gitlab_simple_issue.json"); 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_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_STREQ(issue.body, "That would make some of the code much cleaner"); ATF_CHECK(issue.labels_size == 1); 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(issue.milestone == NULL); json_close(&stream); gcli_issue_free(&issue); gcli_destroy(&ctx); } ATF_TC_WITHOUT_HEAD(gitlab_simple_label); ATF_TC_BODY(gitlab_simple_label, tc) { struct json_stream stream = {0}; struct gcli_ctx *ctx = test_context(); FILE *f = open_sample("gitlab_simple_label.json"); struct gcli_label label = {0}; json_open_stream(&stream, f); ATF_REQUIRE(parse_gitlab_label(ctx, &stream, &label) == 0); ATF_CHECK(label.id == 24376073); ATF_CHECK_STREQ(label.name, "bug"); ATF_CHECK_STREQ(label.description, "Something isn't working as expected"); ATF_CHECK(label.colour == 0xD73A4A00); json_close(&stream); gcli_free_label(&label); gcli_destroy(&ctx); } ATF_TC_WITHOUT_HEAD(gitlab_simple_release); ATF_TC_BODY(gitlab_simple_release, tc) { struct json_stream stream = {0}; struct gcli_ctx *ctx = test_context(); FILE *f = open_sample("gitlab_simple_release.json"); struct gcli_release release = {0}; json_open_stream(&stream, f); ATF_REQUIRE(parse_gitlab_release(ctx, &stream, &release) == 0); /* NOTE(Nico): this silly hack is needed as the fixup is only * applied internally when you fetch the release list using the * public library API. */ gitlab_fixup_release_assets(ctx, &release); /* NOTE(Nico): on gitlab this is the tag name */ 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"); ATF_CHECK_STREQ(release.assets[0].url, "https://gitlab.com/herrhotzenplotz/gcli/-/archive/1.2.0/gcli-1.2.0.zip"); } { ATF_CHECK_STREQ(release.assets[1].name, "gcli-1.2.0.tar.gz"); ATF_CHECK_STREQ(release.assets[1].url, "https://gitlab.com/herrhotzenplotz/gcli/-/archive/1.2.0/gcli-1.2.0.tar.gz"); } { ATF_CHECK_STREQ(release.assets[2].name, "gcli-1.2.0.tar.bz2"); ATF_CHECK_STREQ(release.assets[2].url, "https://gitlab.com/herrhotzenplotz/gcli/-/archive/1.2.0/gcli-1.2.0.tar.bz2"); } { ATF_CHECK_STREQ(release.assets[3].name, "gcli-1.2.0.tar"); ATF_CHECK_STREQ(release.assets[3].url, "https://gitlab.com/herrhotzenplotz/gcli/-/archive/1.2.0/gcli-1.2.0.tar"); } 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); json_close(&stream); gcli_release_free(&release); gcli_destroy(&ctx); } ATF_TC_WITHOUT_HEAD(gitlab_simple_fork); ATF_TC_BODY(gitlab_simple_fork, tc) { struct json_stream stream = {0}; struct gcli_ctx *ctx = test_context(); FILE *f = open_sample("gitlab_simple_fork.json"); struct gcli_fork fork = {0}; json_open_stream(&stream, f); ATF_REQUIRE(parse_gitlab_fork(ctx, &stream, &fork) == 0); 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); gcli_fork_free(&fork); gcli_destroy(&ctx); } ATF_TC_WITHOUT_HEAD(gitlab_simple_milestone); ATF_TC_BODY(gitlab_simple_milestone, tc) { struct json_stream stream = {0}; struct gcli_ctx *ctx = test_context(); FILE *f = open_sample("gitlab_simple_milestone.json"); struct gcli_milestone milestone = {0}; json_open_stream(&stream, f); ATF_REQUIRE(parse_gitlab_milestone(ctx, &stream, &milestone) == 0); ATF_CHECK(milestone.id == 2975318); ATF_CHECK_STREQ(milestone.title, "Version 2"); ATF_CHECK_STREQ(milestone.state, "active"); ATF_CHECK_STREQ(milestone.description, "Things that need to be done for version 2"); ATF_CHECK_STREQ(milestone.created_at, "2023-02-05T19:08:20.379Z"); ATF_CHECK_STREQ(milestone.due_date, ""); ATF_CHECK_STREQ(milestone.updated_at, "2023-02-05T19:08:20.379Z"); ATF_CHECK(milestone.expired == false); json_close(&stream); gcli_free_milestone(&milestone); gcli_destroy(&ctx); /* Ignore open issues and closed issues as they are * github/gitea-specific */ } ATF_TC_WITHOUT_HEAD(gitlab_simple_pipeline); ATF_TC_BODY(gitlab_simple_pipeline, tc) { struct json_stream stream = {0}; struct gcli_ctx *ctx = test_context(); FILE *f = open_sample("gitlab_simple_pipeline.json"); struct gitlab_pipeline pipeline = {0}; json_open_stream(&stream, f); ATF_REQUIRE(parse_gitlab_pipeline(ctx, &stream, &pipeline) == 0); ATF_CHECK(pipeline.id == 989897020); ATF_CHECK_STREQ(pipeline.status, "failed"); ATF_CHECK_STREQ(pipeline.created_at, "2023-09-02T14:30:20.925Z"); ATF_CHECK_STREQ(pipeline.updated_at, "2023-09-02T14:31:40.328Z"); ATF_CHECK_STREQ(pipeline.ref, "refs/merge-requests/219/head"); ATF_CHECK_STREQ(pipeline.sha, "742affb88a297a6b34201ad61c8b5b72ec6eb679"); ATF_CHECK_STREQ(pipeline.source, "merge_request_event"); json_close(&stream); gitlab_pipeline_free(&pipeline); gcli_destroy(&ctx); } ATF_TC_WITHOUT_HEAD(gitlab_simple_repo); ATF_TC_BODY(gitlab_simple_repo, tc) { struct json_stream stream = {0}; struct gcli_ctx *ctx = test_context(); FILE *f = open_sample("gitlab_simple_repo.json"); 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_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); gcli_repo_free(&repo); gcli_destroy(&ctx); } ATF_TC_WITHOUT_HEAD(gitlab_simple_snippet); ATF_TC_BODY(gitlab_simple_snippet, tc) { struct json_stream stream = {0}; struct gcli_ctx *ctx = test_context(); FILE *f = open_sample("gitlab_simple_snippet.json"); struct gcli_gitlab_snippet snippet = {0}; json_open_stream(&stream, f); ATF_REQUIRE(parse_gitlab_snippet(ctx, &stream, &snippet) == 0); ATF_CHECK(snippet.id == 2141655); ATF_CHECK_STREQ(snippet.title, "darcy-weisbach SPARC64"); ATF_CHECK_STREQ(snippet.filename, "darcy-weisbach SPARC64"); ATF_CHECK_STREQ(snippet.date, "2021-06-28T15:47:36.214Z"); ATF_CHECK_STREQ(snippet.author, "herrhotzenplotz"); ATF_CHECK_STREQ(snippet.visibility, "public"); ATF_CHECK_STREQ(snippet.raw_url, "https://gitlab.com/-/snippets/2141655/raw"); json_close(&stream); gcli_gitlab_snippet_free(&snippet); gcli_destroy(&ctx); } ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, gitlab_simple_fork); ATF_TP_ADD_TC(tp, gitlab_simple_issue); ATF_TP_ADD_TC(tp, gitlab_simple_label); ATF_TP_ADD_TC(tp, gitlab_simple_merge_request); ATF_TP_ADD_TC(tp, gitlab_simple_milestone); ATF_TP_ADD_TC(tp, gitlab_simple_pipeline); ATF_TP_ADD_TC(tp, gitlab_simple_release); ATF_TP_ADD_TC(tp, gitlab_simple_repo); ATF_TP_ADD_TC(tp, gitlab_simple_snippet); return atf_no_error(); } gcli-2.3.0/tests/json-escape.c000066400000000000000000000020601460062271200161450ustar00rootroot00000000000000#include #include ATF_TC_WITHOUT_HEAD(newlines); ATF_TC_BODY(newlines, tc) { sn_sv const input = SV("\n\r"); sn_sv const escaped = gcli_json_escape(input); ATF_CHECK(sn_sv_eq_to(escaped, "\\n\\r")); free(escaped.data); } ATF_TC_WITHOUT_HEAD(tabs); ATF_TC_BODY(tabs, tc) { sn_sv const input = SV("\t\t\t"); sn_sv const escaped = gcli_json_escape(input); ATF_CHECK(sn_sv_eq_to(escaped, "\\t\\t\\t")); free(escaped.data); } ATF_TC_WITHOUT_HEAD(backslashes); ATF_TC_BODY(backslashes, tc) { sn_sv const input = SV("\\"); sn_sv const escaped = gcli_json_escape(input); ATF_CHECK(sn_sv_eq_to(escaped, "\\\\")); free(escaped.data); } ATF_TC_WITHOUT_HEAD(torture); ATF_TC_BODY(torture, tc) { sn_sv const input = SV("\n\r\n\n\n\t{}"); sn_sv const escaped = gcli_json_escape(input); ATF_CHECK(sn_sv_eq_to(escaped, "\\n\\r\\n\\n\\n\\t{}")); free(escaped.data); } ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, newlines); ATF_TP_ADD_TC(tp, tabs); ATF_TP_ADD_TC(tp, backslashes); ATF_TP_ADD_TC(tp, torture); return atf_no_error(); } gcli-2.3.0/tests/pretty_print_test.c000066400000000000000000000064261460062271200175520ustar00rootroot00000000000000/* * 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 static char * test_string(char const *const input) { FILE *f; long length; char *buf; static char const *const fname = "/tmp/gcli_pretty_print_test"; f = fopen(fname, "w"); pretty_print(input, 4, 80, f); length = ftell(f); fflush(f); fclose(f); f = fopen(fname, "r"); buf = malloc(length + 1); fread(buf, 1, length, f); buf[length] = '\0'; fclose(f); unlink(fname); return buf; } ATF_TC_WITHOUT_HEAD(simple_one_line); ATF_TC_BODY(simple_one_line, tc) { char *formatted = test_string(""); ATF_CHECK_STREQ(formatted, " \n"); } ATF_TC_WITHOUT_HEAD(long_line_doesnt_break); ATF_TC_BODY(long_line_doesnt_break, tc) { char const *const input = "0123456789012345678901234567890123456789012345678901234567890123456789" "0123456789012345678901234567890123456789012345678901234567890123456789"; char *formatted = test_string(input); char const expected[] = " 0123456789012345678901234567890123456789012345678901234567890123456789012345" "6789012345678901234567890123456789012345678901234567890123456789\n"; ATF_CHECK_STREQ(formatted, expected); } ATF_TC_WITHOUT_HEAD(line_overflow_break); ATF_TC_BODY(line_overflow_break, tc) { char const *const input = "0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 " "0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789"; char *formatted = test_string(input); char const expected[] = " 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 \n" " 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 \n" " 0123456789 0123456789\n"; ATF_CHECK_STREQ(formatted, expected); } ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, simple_one_line); ATF_TP_ADD_TC(tp, long_line_doesnt_break); ATF_TP_ADD_TC(tp, line_overflow_break); return atf_no_error(); } gcli-2.3.0/tests/samples/000077500000000000000000000000001460062271200152405ustar00rootroot00000000000000gcli-2.3.0/tests/samples/bugzilla_attachments.json000066400000000000000000000177751460062271200223600ustar00rootroot00000000000000{ "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" } ] } } gcli-2.3.0/tests/samples/bugzilla_comments.json000066400000000000000000000163301460062271200216540ustar00rootroot00000000000000{"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}]}}}gcli-2.3.0/tests/samples/bugzilla_simple_bug.json000066400000000000000000000025761460062271200221640ustar00rootroot00000000000000{ "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" } ] } gcli-2.3.0/tests/samples/gitea_simple_notification.json000066400000000000000000000064741460062271200233560ustar00rootroot00000000000000{ "id": 511579, "repository": { "id": 45938, "owner": { "id": 52534, "login": "schilytools", "login_name": "", "full_name": "", "email": "", "avatar_url": "https://codeberg.org/avatars/f29c1b0621d441e37b83d4bf59bf2563", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2022-05-24T12:20:59Z", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "http://schilytools.sourceforge.net/", "description": "This project maintains the schilytools, a collection of tools written or formerly managed by Jörg Schilling.", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 0, "username": "schilytools" }, "name": "schilytools", "full_name": "schilytools/schilytools", "description": "A collection of tools written or formerly managed by Jörg Schilling.", "empty": false, "private": false, "fork": false, "template": false, "parent": null, "mirror": false, "size": 16025, "language": "", "languages_url": "https://codeberg.org/api/v1/repos/schilytools/schilytools/languages", "html_url": "https://codeberg.org/schilytools/schilytools", "url": "https://codeberg.org/api/v1/repos/schilytools/schilytools", "link": "", "ssh_url": "git@codeberg.org:schilytools/schilytools.git", "clone_url": "https://codeberg.org/schilytools/schilytools.git", "original_url": "", "website": "", "stars_count": 20, "forks_count": 10, "watchers_count": 8, "open_issues_count": 21, "open_pr_counter": 1, "release_counter": 7, "default_branch": "master", "archived": false, "created_at": "2022-05-24T14:20:56Z", "updated_at": "2023-11-24T21:01:16Z", "archived_at": "1970-01-01T00:00:00Z", "has_issues": true, "internal_tracker": { "enable_time_tracker": true, "allow_only_contributors_to_track_time": true, "enable_issue_dependencies": true }, "has_wiki": true, "has_pull_requests": true, "has_projects": true, "has_releases": true, "has_packages": false, "has_actions": false, "ignore_whitespace_conflicts": false, "allow_merge_commits": true, "allow_rebase": true, "allow_rebase_explicit": true, "allow_squash_merge": true, "allow_rebase_update": true, "default_delete_branch_after_merge": false, "default_merge_style": "rebase", "default_allow_maintainer_edit": false, "avatar_url": "", "internal": false, "mirror_interval": "", "mirror_updated": "0001-01-01T00:00:00Z", "repo_transfer": null }, "subject": { "title": "Remove register from C++ sources", "url": "https://codeberg.org/api/v1/repos/schilytools/schilytools/issues/13", "latest_comment_url": "https://codeberg.org/api/v1/repos/schilytools/schilytools/issues/comments/1349554", "html_url": "https://codeberg.org/schilytools/schilytools/issues/13", "latest_comment_html_url": "https://codeberg.org/schilytools/schilytools/issues/13#issuecomment-1349554", "type": "Issue", "state": "closed" }, "unread": true, "pinned": false, "updated_at": "2023-11-24T21:01:50Z", "url": "https://codeberg.org/api/v1/notifications/threads/511579" } gcli-2.3.0/tests/samples/github_simple_check.json000066400000000000000000000070561460062271200221330ustar00rootroot00000000000000{ "id": 16437184455, "name": "test Windows x86", "node_id": "CR_kwDODwaEZM8AAAAD07uHxw", "head_sha": "03a8af3dab144ea38910c1efdcce03fb708f3179", "external_id": "85d15886-9467-5a76-a719-6058eec4b143", "url": "https://api.github.com/repos/quick-lint/quick-lint-js/check-runs/16437184455", "html_url": "https://github.com/quick-lint/quick-lint-js/actions/runs/6056687077/job/16437184455", "details_url": "https://github.com/quick-lint/quick-lint-js/actions/runs/6056687077/job/16437184455", "status": "completed", "conclusion": "success", "started_at": "2023-09-02T06:27:37Z", "completed_at": "2023-09-02T06:29:11Z", "output": { "title": null, "summary": null, "text": null, "annotations_count": 0, "annotations_url": "https://api.github.com/repos/quick-lint/quick-lint-js/check-runs/16437184455/annotations" }, "check_suite": { "id": 15750909232 }, "app": { "id": 15368, "slug": "github-actions", "node_id": "MDM6QXBwMTUzNjg=", "owner": { "login": "github", "id": 9919, "node_id": "MDEyOk9yZ2FuaXphdGlvbjk5MTk=", "avatar_url": "https://avatars.githubusercontent.com/u/9919?v=4", "gravatar_id": "", "url": "https://api.github.com/users/github", "html_url": "https://github.com/github", "followers_url": "https://api.github.com/users/github/followers", "following_url": "https://api.github.com/users/github/following{/other_user}", "gists_url": "https://api.github.com/users/github/gists{/gist_id}", "starred_url": "https://api.github.com/users/github/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/github/subscriptions", "organizations_url": "https://api.github.com/users/github/orgs", "repos_url": "https://api.github.com/users/github/repos", "events_url": "https://api.github.com/users/github/events{/privacy}", "received_events_url": "https://api.github.com/users/github/received_events", "type": "Organization", "site_admin": false }, "name": "GitHub Actions", "description": "Automate your workflow from idea to production", "external_url": "https://help.github.com/en/actions", "html_url": "https://github.com/apps/github-actions", "created_at": "2018-07-30T09:30:17Z", "updated_at": "2019-12-10T19:04:12Z", "permissions": { "actions": "write", "administration": "read", "checks": "write", "contents": "write", "deployments": "write", "discussions": "write", "issues": "write", "merge_queues": "write", "metadata": "read", "packages": "write", "pages": "write", "pull_requests": "write", "repository_hooks": "write", "repository_projects": "write", "security_events": "write", "statuses": "write", "vulnerability_alerts": "read" }, "events": [ "branch_protection_rule", "check_run", "check_suite", "create", "delete", "deployment", "deployment_status", "discussion", "discussion_comment", "fork", "gollum", "issues", "issue_comment", "label", "merge_group", "milestone", "page_build", "project", "project_card", "project_column", "public", "pull_request", "pull_request_review", "pull_request_review_comment", "push", "registry_package", "release", "repository", "repository_dispatch", "status", "watch", "workflow_dispatch", "workflow_run" ] }, "pull_requests": [] } gcli-2.3.0/tests/samples/github_simple_comment.json000066400000000000000000000042551460062271200225160ustar00rootroot00000000000000{ "url": "https://api.github.com/repos/herrhotzenplotz/gcli/issues/comments/1424392601", "html_url": "https://github.com/herrhotzenplotz/gcli/issues/116#issuecomment-1424392601", "issue_url": "https://api.github.com/repos/herrhotzenplotz/gcli/issues/116", "id": 1424392601, "node_id": "IC_kwDOGLyhHc5U5oGZ", "user": { "login": "herrhotzenplotz", "id": 34663024, "node_id": "MDQ6VXNlcjM0NjYzMDI0", "avatar_url": "https://avatars.githubusercontent.com/u/34663024?u=13531d0ed7a7eb54da4599c39ac3068c7d0d71ab&v=4", "gravatar_id": "", "url": "https://api.github.com/users/herrhotzenplotz", "html_url": "https://github.com/herrhotzenplotz", "followers_url": "https://api.github.com/users/herrhotzenplotz/followers", "following_url": "https://api.github.com/users/herrhotzenplotz/following{/other_user}", "gists_url": "https://api.github.com/users/herrhotzenplotz/gists{/gist_id}", "starred_url": "https://api.github.com/users/herrhotzenplotz/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/herrhotzenplotz/subscriptions", "organizations_url": "https://api.github.com/users/herrhotzenplotz/orgs", "repos_url": "https://api.github.com/users/herrhotzenplotz/repos", "events_url": "https://api.github.com/users/herrhotzenplotz/events{/privacy}", "received_events_url": "https://api.github.com/users/herrhotzenplotz/received_events", "type": "User", "site_admin": false }, "created_at": "2023-02-09T15:37:54Z", "updated_at": "2023-02-09T15:37:54Z", "author_association": "OWNER", "body": "Hey,\n\nthe current trunk on Github might be a little outdated. I pushed the staging branch for version 1.0.0 from Gitlab to Github (cleanup-1.0). Could you try again with that branch and see if it still faults at the same place? If it does, please provide a full backtrace and if possible check with valgrind.\n", "reactions": { "url": "https://api.github.com/repos/herrhotzenplotz/gcli/issues/comments/1424392601/reactions", "total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0 }, "performed_via_github_app": null } gcli-2.3.0/tests/samples/github_simple_fork.json000066400000000000000000000133071460062271200220130ustar00rootroot00000000000000{ "id": 639263592, "node_id": "R_kgDOJhpjaA", "name": "quick-lint-js", "full_name": "gjnoonan/quick-lint-js", "private": false, "owner": { "login": "gjnoonan", "id": 702, "node_id": "MDQ6VXNlcjcwMg==", "avatar_url": "https://avatars.githubusercontent.com/u/702?v=4", "gravatar_id": "", "url": "https://api.github.com/users/gjnoonan", "html_url": "https://github.com/gjnoonan", "followers_url": "https://api.github.com/users/gjnoonan/followers", "following_url": "https://api.github.com/users/gjnoonan/following{/other_user}", "gists_url": "https://api.github.com/users/gjnoonan/gists{/gist_id}", "starred_url": "https://api.github.com/users/gjnoonan/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/gjnoonan/subscriptions", "organizations_url": "https://api.github.com/users/gjnoonan/orgs", "repos_url": "https://api.github.com/users/gjnoonan/repos", "events_url": "https://api.github.com/users/gjnoonan/events{/privacy}", "received_events_url": "https://api.github.com/users/gjnoonan/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/gjnoonan/quick-lint-js", "description": "quick-lint-js finds bugs in JavaScript programs", "fork": true, "url": "https://api.github.com/repos/gjnoonan/quick-lint-js", "forks_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/forks", "keys_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/teams", "hooks_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/hooks", "issue_events_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/issues/events{/number}", "events_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/events", "assignees_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/assignees{/user}", "branches_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/branches{/branch}", "tags_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/tags", "blobs_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/git/refs{/sha}", "trees_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/statuses/{sha}", "languages_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/languages", "stargazers_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/stargazers", "contributors_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/contributors", "subscribers_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/subscribers", "subscription_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/subscription", "commits_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/commits{/sha}", "git_commits_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/git/commits{/sha}", "comments_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/comments{/number}", "issue_comment_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/issues/comments{/number}", "contents_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/contents/{+path}", "compare_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/merges", "archive_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/downloads", "issues_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/issues{/number}", "pulls_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/pulls{/number}", "milestones_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/milestones{/number}", "notifications_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/labels{/name}", "releases_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/releases{/id}", "deployments_url": "https://api.github.com/repos/gjnoonan/quick-lint-js/deployments", "created_at": "2023-05-11T05:37:41Z", "updated_at": "2023-05-22T08:57:35Z", "pushed_at": "2023-05-29T19:44:11Z", "git_url": "git://github.com/gjnoonan/quick-lint-js.git", "ssh_url": "git@github.com:gjnoonan/quick-lint-js.git", "clone_url": "https://github.com/gjnoonan/quick-lint-js.git", "svn_url": "https://github.com/gjnoonan/quick-lint-js", "homepage": "https://quick-lint-js.com", "size": 24816, "stargazers_count": 0, "watchers_count": 0, "language": "C++", "has_issues": false, "has_projects": true, "has_downloads": true, "has_wiki": false, "has_pages": false, "has_discussions": false, "forks_count": 0, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 0, "license": { "key": "gpl-3.0", "name": "GNU General Public License v3.0", "spdx_id": "GPL-3.0", "url": "https://api.github.com/licenses/gpl-3.0", "node_id": "MDc6TGljZW5zZTk=" }, "allow_forking": true, "is_template": false, "web_commit_signoff_required": false, "topics": [], "visibility": "public", "forks": 0, "open_issues": 0, "watchers": 0, "default_branch": "master", "permissions": { "admin": false, "maintain": false, "push": false, "triage": false, "pull": true } } gcli-2.3.0/tests/samples/github_simple_issue.json000066400000000000000000000072761460062271200222120ustar00rootroot00000000000000{ "url": "https://api.github.com/repos/herrhotzenplotz/gcli/issues/115", "repository_url": "https://api.github.com/repos/herrhotzenplotz/gcli", "labels_url": "https://api.github.com/repos/herrhotzenplotz/gcli/issues/115/labels{/name}", "comments_url": "https://api.github.com/repos/herrhotzenplotz/gcli/issues/115/comments", "events_url": "https://api.github.com/repos/herrhotzenplotz/gcli/issues/115/events", "html_url": "https://github.com/herrhotzenplotz/gcli/issues/115", "id": 1176990242, "node_id": "I_kwDOGLyhHc5GJ3Ii", "number": 115, "title": "consider removing FILE *out from printing functions", "user": { "login": "herrhotzenplotz", "id": 34663024, "node_id": "MDQ6VXNlcjM0NjYzMDI0", "avatar_url": "https://avatars.githubusercontent.com/u/34663024?v=4", "gravatar_id": "", "url": "https://api.github.com/users/herrhotzenplotz", "html_url": "https://github.com/herrhotzenplotz", "followers_url": "https://api.github.com/users/herrhotzenplotz/followers", "following_url": "https://api.github.com/users/herrhotzenplotz/following{/other_user}", "gists_url": "https://api.github.com/users/herrhotzenplotz/gists{/gist_id}", "starred_url": "https://api.github.com/users/herrhotzenplotz/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/herrhotzenplotz/subscriptions", "organizations_url": "https://api.github.com/users/herrhotzenplotz/orgs", "repos_url": "https://api.github.com/users/herrhotzenplotz/repos", "events_url": "https://api.github.com/users/herrhotzenplotz/events{/privacy}", "received_events_url": "https://api.github.com/users/herrhotzenplotz/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "closed", "locked": false, "assignee": null, "assignees": [ ], "milestone": null, "comments": 0, "created_at": "2022-03-22T16:06:10Z", "updated_at": "2022-04-13T18:33:40Z", "closed_at": "2022-04-13T18:33:40Z", "author_association": "OWNER", "active_lock_reason": null, "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", "closed_by": { "login": "herrhotzenplotz", "id": 34663024, "node_id": "MDQ6VXNlcjM0NjYzMDI0", "avatar_url": "https://avatars.githubusercontent.com/u/34663024?v=4", "gravatar_id": "", "url": "https://api.github.com/users/herrhotzenplotz", "html_url": "https://github.com/herrhotzenplotz", "followers_url": "https://api.github.com/users/herrhotzenplotz/followers", "following_url": "https://api.github.com/users/herrhotzenplotz/following{/other_user}", "gists_url": "https://api.github.com/users/herrhotzenplotz/gists{/gist_id}", "starred_url": "https://api.github.com/users/herrhotzenplotz/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/herrhotzenplotz/subscriptions", "organizations_url": "https://api.github.com/users/herrhotzenplotz/orgs", "repos_url": "https://api.github.com/users/herrhotzenplotz/repos", "events_url": "https://api.github.com/users/herrhotzenplotz/events{/privacy}", "received_events_url": "https://api.github.com/users/herrhotzenplotz/received_events", "type": "User", "site_admin": false }, "reactions": { "url": "https://api.github.com/repos/herrhotzenplotz/gcli/issues/115/reactions", "total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0 }, "timeline_url": "https://api.github.com/repos/herrhotzenplotz/gcli/issues/115/timeline", "performed_via_github_app": null, "state_reason": "completed" } gcli-2.3.0/tests/samples/github_simple_label.json000066400000000000000000000003511460062271200221240ustar00rootroot00000000000000{ "id": 3431203676, "node_id": "LA_kwDOGLyhHc7MhANc", "url": "https://api.github.com/repos/herrhotzenplotz/gcli/labels/bug", "name": "bug", "color": "d73a4a", "default": true, "description": "Something isn't working" } gcli-2.3.0/tests/samples/github_simple_milestone.json000066400000000000000000000031631460062271200230500ustar00rootroot00000000000000{ "url": "https://api.github.com/repos/herrhotzenplotz/gcli/milestones/1", "html_url": "https://github.com/herrhotzenplotz/gcli/milestone/1", "labels_url": "https://api.github.com/repos/herrhotzenplotz/gcli/milestones/1/labels", "id": 7485436, "node_id": "MI_kwDOGLyhHc4Acjf8", "number": 1, "title": "Gitlab support", "description": "", "creator": { "login": "herrhotzenplotz", "id": 34663024, "node_id": "MDQ6VXNlcjM0NjYzMDI0", "avatar_url": "https://avatars.githubusercontent.com/u/34663024?v=4", "gravatar_id": "", "url": "https://api.github.com/users/herrhotzenplotz", "html_url": "https://github.com/herrhotzenplotz", "followers_url": "https://api.github.com/users/herrhotzenplotz/followers", "following_url": "https://api.github.com/users/herrhotzenplotz/following{/other_user}", "gists_url": "https://api.github.com/users/herrhotzenplotz/gists{/gist_id}", "starred_url": "https://api.github.com/users/herrhotzenplotz/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/herrhotzenplotz/subscriptions", "organizations_url": "https://api.github.com/users/herrhotzenplotz/orgs", "repos_url": "https://api.github.com/users/herrhotzenplotz/repos", "events_url": "https://api.github.com/users/herrhotzenplotz/events{/privacy}", "received_events_url": "https://api.github.com/users/herrhotzenplotz/received_events", "type": "User", "site_admin": false }, "open_issues": 0, "closed_issues": 8, "state": "open", "created_at": "2021-12-14T07:02:05Z", "updated_at": "2021-12-19T14:49:43Z", "due_on": null, "closed_at": null } gcli-2.3.0/tests/samples/github_simple_pull.json000066400000000000000000000476751460062271200220450ustar00rootroot00000000000000{ "url": "https://api.github.com/repos/herrhotzenplotz/gcli/pulls/113", "id": 886044243, "node_id": "PR_kwDOGLyhHc40z_ZT", "html_url": "https://github.com/herrhotzenplotz/gcli/pull/113", "diff_url": "https://github.com/herrhotzenplotz/gcli/pull/113.diff", "patch_url": "https://github.com/herrhotzenplotz/gcli/pull/113.patch", "issue_url": "https://api.github.com/repos/herrhotzenplotz/gcli/issues/113", "number": 113, "state": "closed", "locked": false, "title": "mark notifications as read/done", "user": { "login": "herrhotzenplotz", "id": 34663024, "node_id": "MDQ6VXNlcjM0NjYzMDI0", "avatar_url": "https://avatars.githubusercontent.com/u/34663024?v=4", "gravatar_id": "", "url": "https://api.github.com/users/herrhotzenplotz", "html_url": "https://github.com/herrhotzenplotz", "followers_url": "https://api.github.com/users/herrhotzenplotz/followers", "following_url": "https://api.github.com/users/herrhotzenplotz/following{/other_user}", "gists_url": "https://api.github.com/users/herrhotzenplotz/gists{/gist_id}", "starred_url": "https://api.github.com/users/herrhotzenplotz/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/herrhotzenplotz/subscriptions", "organizations_url": "https://api.github.com/users/herrhotzenplotz/orgs", "repos_url": "https://api.github.com/users/herrhotzenplotz/repos", "events_url": "https://api.github.com/users/herrhotzenplotz/events{/privacy}", "received_events_url": "https://api.github.com/users/herrhotzenplotz/received_events", "type": "User", "site_admin": false }, "body": "Fixes #99\n", "created_at": "2022-03-22T13:20:57Z", "updated_at": "2022-03-22T14:27:14Z", "closed_at": "2022-03-22T14:25:52Z", "merged_at": "2022-03-22T14:25:52Z", "merge_commit_sha": "81251c35de91dcc69612dd0d4e25e7fffaa08474", "assignee": null, "assignees": [ ], "requested_reviewers": [ ], "requested_teams": [ ], "labels": [ ], "milestone": null, "draft": false, "commits_url": "https://api.github.com/repos/herrhotzenplotz/gcli/pulls/113/commits", "review_comments_url": "https://api.github.com/repos/herrhotzenplotz/gcli/pulls/113/comments", "review_comment_url": "https://api.github.com/repos/herrhotzenplotz/gcli/pulls/comments{/number}", "comments_url": "https://api.github.com/repos/herrhotzenplotz/gcli/issues/113/comments", "statuses_url": "https://api.github.com/repos/herrhotzenplotz/gcli/statuses/a00f475af1e31d56c7a5839508a21e2b76a31e49", "head": { "label": "herrhotzenplotz:99", "ref": "99", "sha": "a00f475af1e31d56c7a5839508a21e2b76a31e49", "user": { "login": "herrhotzenplotz", "id": 34663024, "node_id": "MDQ6VXNlcjM0NjYzMDI0", "avatar_url": "https://avatars.githubusercontent.com/u/34663024?v=4", "gravatar_id": "", "url": "https://api.github.com/users/herrhotzenplotz", "html_url": "https://github.com/herrhotzenplotz", "followers_url": "https://api.github.com/users/herrhotzenplotz/followers", "following_url": "https://api.github.com/users/herrhotzenplotz/following{/other_user}", "gists_url": "https://api.github.com/users/herrhotzenplotz/gists{/gist_id}", "starred_url": "https://api.github.com/users/herrhotzenplotz/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/herrhotzenplotz/subscriptions", "organizations_url": "https://api.github.com/users/herrhotzenplotz/orgs", "repos_url": "https://api.github.com/users/herrhotzenplotz/repos", "events_url": "https://api.github.com/users/herrhotzenplotz/events{/privacy}", "received_events_url": "https://api.github.com/users/herrhotzenplotz/received_events", "type": "User", "site_admin": false }, "repo": { "id": 415015197, "node_id": "R_kgDOGLyhHQ", "name": "gcli", "full_name": "herrhotzenplotz/gcli", "private": false, "owner": { "login": "herrhotzenplotz", "id": 34663024, "node_id": "MDQ6VXNlcjM0NjYzMDI0", "avatar_url": "https://avatars.githubusercontent.com/u/34663024?v=4", "gravatar_id": "", "url": "https://api.github.com/users/herrhotzenplotz", "html_url": "https://github.com/herrhotzenplotz", "followers_url": "https://api.github.com/users/herrhotzenplotz/followers", "following_url": "https://api.github.com/users/herrhotzenplotz/following{/other_user}", "gists_url": "https://api.github.com/users/herrhotzenplotz/gists{/gist_id}", "starred_url": "https://api.github.com/users/herrhotzenplotz/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/herrhotzenplotz/subscriptions", "organizations_url": "https://api.github.com/users/herrhotzenplotz/orgs", "repos_url": "https://api.github.com/users/herrhotzenplotz/repos", "events_url": "https://api.github.com/users/herrhotzenplotz/events{/privacy}", "received_events_url": "https://api.github.com/users/herrhotzenplotz/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/herrhotzenplotz/gcli", "description": "Somewhat portable and secure CLI utility to interact with both GitHub and GitLab. Development is happening at https://gitlab.com/herrhotzenplotz/gcli but you can also submit issues/PRs here.", "fork": false, "url": "https://api.github.com/repos/herrhotzenplotz/gcli", "forks_url": "https://api.github.com/repos/herrhotzenplotz/gcli/forks", "keys_url": "https://api.github.com/repos/herrhotzenplotz/gcli/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/herrhotzenplotz/gcli/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/herrhotzenplotz/gcli/teams", "hooks_url": "https://api.github.com/repos/herrhotzenplotz/gcli/hooks", "issue_events_url": "https://api.github.com/repos/herrhotzenplotz/gcli/issues/events{/number}", "events_url": "https://api.github.com/repos/herrhotzenplotz/gcli/events", "assignees_url": "https://api.github.com/repos/herrhotzenplotz/gcli/assignees{/user}", "branches_url": "https://api.github.com/repos/herrhotzenplotz/gcli/branches{/branch}", "tags_url": "https://api.github.com/repos/herrhotzenplotz/gcli/tags", "blobs_url": "https://api.github.com/repos/herrhotzenplotz/gcli/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/herrhotzenplotz/gcli/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/herrhotzenplotz/gcli/git/refs{/sha}", "trees_url": "https://api.github.com/repos/herrhotzenplotz/gcli/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/herrhotzenplotz/gcli/statuses/{sha}", "languages_url": "https://api.github.com/repos/herrhotzenplotz/gcli/languages", "stargazers_url": "https://api.github.com/repos/herrhotzenplotz/gcli/stargazers", "contributors_url": "https://api.github.com/repos/herrhotzenplotz/gcli/contributors", "subscribers_url": "https://api.github.com/repos/herrhotzenplotz/gcli/subscribers", "subscription_url": "https://api.github.com/repos/herrhotzenplotz/gcli/subscription", "commits_url": "https://api.github.com/repos/herrhotzenplotz/gcli/commits{/sha}", "git_commits_url": "https://api.github.com/repos/herrhotzenplotz/gcli/git/commits{/sha}", "comments_url": "https://api.github.com/repos/herrhotzenplotz/gcli/comments{/number}", "issue_comment_url": "https://api.github.com/repos/herrhotzenplotz/gcli/issues/comments{/number}", "contents_url": "https://api.github.com/repos/herrhotzenplotz/gcli/contents/{+path}", "compare_url": "https://api.github.com/repos/herrhotzenplotz/gcli/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/herrhotzenplotz/gcli/merges", "archive_url": "https://api.github.com/repos/herrhotzenplotz/gcli/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/herrhotzenplotz/gcli/downloads", "issues_url": "https://api.github.com/repos/herrhotzenplotz/gcli/issues{/number}", "pulls_url": "https://api.github.com/repos/herrhotzenplotz/gcli/pulls{/number}", "milestones_url": "https://api.github.com/repos/herrhotzenplotz/gcli/milestones{/number}", "notifications_url": "https://api.github.com/repos/herrhotzenplotz/gcli/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/herrhotzenplotz/gcli/labels{/name}", "releases_url": "https://api.github.com/repos/herrhotzenplotz/gcli/releases{/id}", "deployments_url": "https://api.github.com/repos/herrhotzenplotz/gcli/deployments", "created_at": "2021-10-08T14:20:15Z", "updated_at": "2023-08-15T20:58:16Z", "pushed_at": "2023-08-11T07:58:26Z", "git_url": "git://github.com/herrhotzenplotz/gcli.git", "ssh_url": "git@github.com:herrhotzenplotz/gcli.git", "clone_url": "https://github.com/herrhotzenplotz/gcli.git", "svn_url": "https://github.com/herrhotzenplotz/gcli", "homepage": "https://herrhotzenplotz.de/gcli/", "size": 2169, "stargazers_count": 19, "watchers_count": 19, "language": "C", "has_issues": true, "has_projects": false, "has_downloads": true, "has_wiki": false, "has_pages": false, "has_discussions": false, "forks_count": 0, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 0, "license": { "key": "bsd-2-clause", "name": "BSD 2-Clause \"Simplified\" License", "spdx_id": "BSD-2-Clause", "url": "https://api.github.com/licenses/bsd-2-clause", "node_id": "MDc6TGljZW5zZTQ=" }, "allow_forking": true, "is_template": false, "web_commit_signoff_required": false, "topics": [ "c", "cli", "freebsd", "github-api", "gitlab", "gitlab-api", "libcurl", "linux", "unix" ], "visibility": "public", "forks": 0, "open_issues": 0, "watchers": 19, "default_branch": "trunk" } }, "base": { "label": "herrhotzenplotz:trunk", "ref": "trunk", "sha": "5970c6ec82cefe2f4815edf42b6e982595077b1f", "user": { "login": "herrhotzenplotz", "id": 34663024, "node_id": "MDQ6VXNlcjM0NjYzMDI0", "avatar_url": "https://avatars.githubusercontent.com/u/34663024?v=4", "gravatar_id": "", "url": "https://api.github.com/users/herrhotzenplotz", "html_url": "https://github.com/herrhotzenplotz", "followers_url": "https://api.github.com/users/herrhotzenplotz/followers", "following_url": "https://api.github.com/users/herrhotzenplotz/following{/other_user}", "gists_url": "https://api.github.com/users/herrhotzenplotz/gists{/gist_id}", "starred_url": "https://api.github.com/users/herrhotzenplotz/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/herrhotzenplotz/subscriptions", "organizations_url": "https://api.github.com/users/herrhotzenplotz/orgs", "repos_url": "https://api.github.com/users/herrhotzenplotz/repos", "events_url": "https://api.github.com/users/herrhotzenplotz/events{/privacy}", "received_events_url": "https://api.github.com/users/herrhotzenplotz/received_events", "type": "User", "site_admin": false }, "repo": { "id": 415015197, "node_id": "R_kgDOGLyhHQ", "name": "gcli", "full_name": "herrhotzenplotz/gcli", "private": false, "owner": { "login": "herrhotzenplotz", "id": 34663024, "node_id": "MDQ6VXNlcjM0NjYzMDI0", "avatar_url": "https://avatars.githubusercontent.com/u/34663024?v=4", "gravatar_id": "", "url": "https://api.github.com/users/herrhotzenplotz", "html_url": "https://github.com/herrhotzenplotz", "followers_url": "https://api.github.com/users/herrhotzenplotz/followers", "following_url": "https://api.github.com/users/herrhotzenplotz/following{/other_user}", "gists_url": "https://api.github.com/users/herrhotzenplotz/gists{/gist_id}", "starred_url": "https://api.github.com/users/herrhotzenplotz/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/herrhotzenplotz/subscriptions", "organizations_url": "https://api.github.com/users/herrhotzenplotz/orgs", "repos_url": "https://api.github.com/users/herrhotzenplotz/repos", "events_url": "https://api.github.com/users/herrhotzenplotz/events{/privacy}", "received_events_url": "https://api.github.com/users/herrhotzenplotz/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/herrhotzenplotz/gcli", "description": "Somewhat portable and secure CLI utility to interact with both GitHub and GitLab. Development is happening at https://gitlab.com/herrhotzenplotz/gcli but you can also submit issues/PRs here.", "fork": false, "url": "https://api.github.com/repos/herrhotzenplotz/gcli", "forks_url": "https://api.github.com/repos/herrhotzenplotz/gcli/forks", "keys_url": "https://api.github.com/repos/herrhotzenplotz/gcli/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/herrhotzenplotz/gcli/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/herrhotzenplotz/gcli/teams", "hooks_url": "https://api.github.com/repos/herrhotzenplotz/gcli/hooks", "issue_events_url": "https://api.github.com/repos/herrhotzenplotz/gcli/issues/events{/number}", "events_url": "https://api.github.com/repos/herrhotzenplotz/gcli/events", "assignees_url": "https://api.github.com/repos/herrhotzenplotz/gcli/assignees{/user}", "branches_url": "https://api.github.com/repos/herrhotzenplotz/gcli/branches{/branch}", "tags_url": "https://api.github.com/repos/herrhotzenplotz/gcli/tags", "blobs_url": "https://api.github.com/repos/herrhotzenplotz/gcli/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/herrhotzenplotz/gcli/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/herrhotzenplotz/gcli/git/refs{/sha}", "trees_url": "https://api.github.com/repos/herrhotzenplotz/gcli/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/herrhotzenplotz/gcli/statuses/{sha}", "languages_url": "https://api.github.com/repos/herrhotzenplotz/gcli/languages", "stargazers_url": "https://api.github.com/repos/herrhotzenplotz/gcli/stargazers", "contributors_url": "https://api.github.com/repos/herrhotzenplotz/gcli/contributors", "subscribers_url": "https://api.github.com/repos/herrhotzenplotz/gcli/subscribers", "subscription_url": "https://api.github.com/repos/herrhotzenplotz/gcli/subscription", "commits_url": "https://api.github.com/repos/herrhotzenplotz/gcli/commits{/sha}", "git_commits_url": "https://api.github.com/repos/herrhotzenplotz/gcli/git/commits{/sha}", "comments_url": "https://api.github.com/repos/herrhotzenplotz/gcli/comments{/number}", "issue_comment_url": "https://api.github.com/repos/herrhotzenplotz/gcli/issues/comments{/number}", "contents_url": "https://api.github.com/repos/herrhotzenplotz/gcli/contents/{+path}", "compare_url": "https://api.github.com/repos/herrhotzenplotz/gcli/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/herrhotzenplotz/gcli/merges", "archive_url": "https://api.github.com/repos/herrhotzenplotz/gcli/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/herrhotzenplotz/gcli/downloads", "issues_url": "https://api.github.com/repos/herrhotzenplotz/gcli/issues{/number}", "pulls_url": "https://api.github.com/repos/herrhotzenplotz/gcli/pulls{/number}", "milestones_url": "https://api.github.com/repos/herrhotzenplotz/gcli/milestones{/number}", "notifications_url": "https://api.github.com/repos/herrhotzenplotz/gcli/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/herrhotzenplotz/gcli/labels{/name}", "releases_url": "https://api.github.com/repos/herrhotzenplotz/gcli/releases{/id}", "deployments_url": "https://api.github.com/repos/herrhotzenplotz/gcli/deployments", "created_at": "2021-10-08T14:20:15Z", "updated_at": "2023-08-15T20:58:16Z", "pushed_at": "2023-08-11T07:58:26Z", "git_url": "git://github.com/herrhotzenplotz/gcli.git", "ssh_url": "git@github.com:herrhotzenplotz/gcli.git", "clone_url": "https://github.com/herrhotzenplotz/gcli.git", "svn_url": "https://github.com/herrhotzenplotz/gcli", "homepage": "https://herrhotzenplotz.de/gcli/", "size": 2169, "stargazers_count": 19, "watchers_count": 19, "language": "C", "has_issues": true, "has_projects": false, "has_downloads": true, "has_wiki": false, "has_pages": false, "has_discussions": false, "forks_count": 0, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 0, "license": { "key": "bsd-2-clause", "name": "BSD 2-Clause \"Simplified\" License", "spdx_id": "BSD-2-Clause", "url": "https://api.github.com/licenses/bsd-2-clause", "node_id": "MDc6TGljZW5zZTQ=" }, "allow_forking": true, "is_template": false, "web_commit_signoff_required": false, "topics": [ "c", "cli", "freebsd", "github-api", "gitlab", "gitlab-api", "libcurl", "linux", "unix" ], "visibility": "public", "forks": 0, "open_issues": 0, "watchers": 19, "default_branch": "trunk" } }, "_links": { "self": { "href": "https://api.github.com/repos/herrhotzenplotz/gcli/pulls/113" }, "html": { "href": "https://github.com/herrhotzenplotz/gcli/pull/113" }, "issue": { "href": "https://api.github.com/repos/herrhotzenplotz/gcli/issues/113" }, "comments": { "href": "https://api.github.com/repos/herrhotzenplotz/gcli/issues/113/comments" }, "review_comments": { "href": "https://api.github.com/repos/herrhotzenplotz/gcli/pulls/113/comments" }, "review_comment": { "href": "https://api.github.com/repos/herrhotzenplotz/gcli/pulls/comments{/number}" }, "commits": { "href": "https://api.github.com/repos/herrhotzenplotz/gcli/pulls/113/commits" }, "statuses": { "href": "https://api.github.com/repos/herrhotzenplotz/gcli/statuses/a00f475af1e31d56c7a5839508a21e2b76a31e49" } }, "author_association": "OWNER", "auto_merge": null, "active_lock_reason": null, "merged": true, "mergeable": null, "rebaseable": null, "mergeable_state": "unknown", "merged_by": { "login": "herrhotzenplotz", "id": 34663024, "node_id": "MDQ6VXNlcjM0NjYzMDI0", "avatar_url": "https://avatars.githubusercontent.com/u/34663024?v=4", "gravatar_id": "", "url": "https://api.github.com/users/herrhotzenplotz", "html_url": "https://github.com/herrhotzenplotz", "followers_url": "https://api.github.com/users/herrhotzenplotz/followers", "following_url": "https://api.github.com/users/herrhotzenplotz/following{/other_user}", "gists_url": "https://api.github.com/users/herrhotzenplotz/gists{/gist_id}", "starred_url": "https://api.github.com/users/herrhotzenplotz/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/herrhotzenplotz/subscriptions", "organizations_url": "https://api.github.com/users/herrhotzenplotz/orgs", "repos_url": "https://api.github.com/users/herrhotzenplotz/repos", "events_url": "https://api.github.com/users/herrhotzenplotz/events{/privacy}", "received_events_url": "https://api.github.com/users/herrhotzenplotz/received_events", "type": "User", "site_admin": false }, "comments": 0, "review_comments": 0, "maintainer_can_modify": false, "commits": 6, "additions": 177, "deletions": 82, "changed_files": 13 } gcli-2.3.0/tests/samples/github_simple_release.json000066400000000000000000000052701460062271200224720ustar00rootroot00000000000000{ "url": "https://api.github.com/repos/herrhotzenplotz/gcli/releases/116031718", "assets_url": "https://api.github.com/repos/herrhotzenplotz/gcli/releases/116031718/assets", "upload_url": "https://uploads.github.com/repos/herrhotzenplotz/gcli/releases/116031718/assets{?name,label}", "html_url": "https://github.com/herrhotzenplotz/gcli/releases/tag/1.2.0", "id": 116031718, "author": { "login": "herrhotzenplotz", "id": 34663024, "node_id": "MDQ6VXNlcjM0NjYzMDI0", "avatar_url": "https://avatars.githubusercontent.com/u/34663024?v=4", "gravatar_id": "", "url": "https://api.github.com/users/herrhotzenplotz", "html_url": "https://github.com/herrhotzenplotz", "followers_url": "https://api.github.com/users/herrhotzenplotz/followers", "following_url": "https://api.github.com/users/herrhotzenplotz/following{/other_user}", "gists_url": "https://api.github.com/users/herrhotzenplotz/gists{/gist_id}", "starred_url": "https://api.github.com/users/herrhotzenplotz/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/herrhotzenplotz/subscriptions", "organizations_url": "https://api.github.com/users/herrhotzenplotz/orgs", "repos_url": "https://api.github.com/users/herrhotzenplotz/repos", "events_url": "https://api.github.com/users/herrhotzenplotz/events{/privacy}", "received_events_url": "https://api.github.com/users/herrhotzenplotz/received_events", "type": "User", "site_admin": false }, "node_id": "RE_kwDOGLyhHc4G6oDm", "tag_name": "1.2.0", "target_commitish": "release", "name": "1.2.0", "draft": false, "prerelease": false, "created_at": "2023-08-11T07:42:37Z", "published_at": "2023-08-11T07:58:26Z", "assets": [ ], "tarball_url": "https://api.github.com/repos/herrhotzenplotz/gcli/tarball/1.2.0", "zipball_url": "https://api.github.com/repos/herrhotzenplotz/gcli/zipball/1.2.0", "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" } gcli-2.3.0/tests/samples/github_simple_repo.json000066400000000000000000000153031460062271200220150ustar00rootroot00000000000000{ "id": 415015197, "node_id": "R_kgDOGLyhHQ", "name": "gcli", "full_name": "herrhotzenplotz/gcli", "private": false, "owner": { "login": "herrhotzenplotz", "id": 34663024, "node_id": "MDQ6VXNlcjM0NjYzMDI0", "avatar_url": "https://avatars.githubusercontent.com/u/34663024?v=4", "gravatar_id": "", "url": "https://api.github.com/users/herrhotzenplotz", "html_url": "https://github.com/herrhotzenplotz", "followers_url": "https://api.github.com/users/herrhotzenplotz/followers", "following_url": "https://api.github.com/users/herrhotzenplotz/following{/other_user}", "gists_url": "https://api.github.com/users/herrhotzenplotz/gists{/gist_id}", "starred_url": "https://api.github.com/users/herrhotzenplotz/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/herrhotzenplotz/subscriptions", "organizations_url": "https://api.github.com/users/herrhotzenplotz/orgs", "repos_url": "https://api.github.com/users/herrhotzenplotz/repos", "events_url": "https://api.github.com/users/herrhotzenplotz/events{/privacy}", "received_events_url": "https://api.github.com/users/herrhotzenplotz/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/herrhotzenplotz/gcli", "description": "Somewhat portable and secure CLI utility to interact with both GitHub and GitLab. Development is happening at https://gitlab.com/herrhotzenplotz/gcli but you can also submit issues/PRs here.", "fork": false, "url": "https://api.github.com/repos/herrhotzenplotz/gcli", "forks_url": "https://api.github.com/repos/herrhotzenplotz/gcli/forks", "keys_url": "https://api.github.com/repos/herrhotzenplotz/gcli/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/herrhotzenplotz/gcli/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/herrhotzenplotz/gcli/teams", "hooks_url": "https://api.github.com/repos/herrhotzenplotz/gcli/hooks", "issue_events_url": "https://api.github.com/repos/herrhotzenplotz/gcli/issues/events{/number}", "events_url": "https://api.github.com/repos/herrhotzenplotz/gcli/events", "assignees_url": "https://api.github.com/repos/herrhotzenplotz/gcli/assignees{/user}", "branches_url": "https://api.github.com/repos/herrhotzenplotz/gcli/branches{/branch}", "tags_url": "https://api.github.com/repos/herrhotzenplotz/gcli/tags", "blobs_url": "https://api.github.com/repos/herrhotzenplotz/gcli/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/herrhotzenplotz/gcli/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/herrhotzenplotz/gcli/git/refs{/sha}", "trees_url": "https://api.github.com/repos/herrhotzenplotz/gcli/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/herrhotzenplotz/gcli/statuses/{sha}", "languages_url": "https://api.github.com/repos/herrhotzenplotz/gcli/languages", "stargazers_url": "https://api.github.com/repos/herrhotzenplotz/gcli/stargazers", "contributors_url": "https://api.github.com/repos/herrhotzenplotz/gcli/contributors", "subscribers_url": "https://api.github.com/repos/herrhotzenplotz/gcli/subscribers", "subscription_url": "https://api.github.com/repos/herrhotzenplotz/gcli/subscription", "commits_url": "https://api.github.com/repos/herrhotzenplotz/gcli/commits{/sha}", "git_commits_url": "https://api.github.com/repos/herrhotzenplotz/gcli/git/commits{/sha}", "comments_url": "https://api.github.com/repos/herrhotzenplotz/gcli/comments{/number}", "issue_comment_url": "https://api.github.com/repos/herrhotzenplotz/gcli/issues/comments{/number}", "contents_url": "https://api.github.com/repos/herrhotzenplotz/gcli/contents/{+path}", "compare_url": "https://api.github.com/repos/herrhotzenplotz/gcli/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/herrhotzenplotz/gcli/merges", "archive_url": "https://api.github.com/repos/herrhotzenplotz/gcli/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/herrhotzenplotz/gcli/downloads", "issues_url": "https://api.github.com/repos/herrhotzenplotz/gcli/issues{/number}", "pulls_url": "https://api.github.com/repos/herrhotzenplotz/gcli/pulls{/number}", "milestones_url": "https://api.github.com/repos/herrhotzenplotz/gcli/milestones{/number}", "notifications_url": "https://api.github.com/repos/herrhotzenplotz/gcli/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/herrhotzenplotz/gcli/labels{/name}", "releases_url": "https://api.github.com/repos/herrhotzenplotz/gcli/releases{/id}", "deployments_url": "https://api.github.com/repos/herrhotzenplotz/gcli/deployments", "created_at": "2021-10-08T14:20:15Z", "updated_at": "2023-08-15T20:58:16Z", "pushed_at": "2023-09-01T22:01:34Z", "git_url": "git://github.com/herrhotzenplotz/gcli.git", "ssh_url": "git@github.com:herrhotzenplotz/gcli.git", "clone_url": "https://github.com/herrhotzenplotz/gcli.git", "svn_url": "https://github.com/herrhotzenplotz/gcli", "homepage": "https://herrhotzenplotz.de/gcli/", "size": 2531, "stargazers_count": 19, "watchers_count": 19, "language": "C", "has_issues": true, "has_projects": false, "has_downloads": true, "has_wiki": false, "has_pages": false, "has_discussions": false, "forks_count": 0, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 0, "license": { "key": "bsd-2-clause", "name": "BSD 2-Clause \"Simplified\" License", "spdx_id": "BSD-2-Clause", "url": "https://api.github.com/licenses/bsd-2-clause", "node_id": "MDc6TGljZW5zZTQ=" }, "allow_forking": true, "is_template": false, "web_commit_signoff_required": false, "topics": [ "c", "cli", "freebsd", "github-api", "gitlab", "gitlab-api", "libcurl", "linux", "unix" ], "visibility": "public", "forks": 0, "open_issues": 0, "watchers": 19, "default_branch": "trunk", "permissions": { "admin": true, "maintain": true, "push": true, "triage": true, "pull": true }, "temp_clone_token": "", "allow_squash_merge": true, "allow_merge_commit": true, "allow_rebase_merge": true, "allow_auto_merge": false, "delete_branch_on_merge": false, "allow_update_branch": false, "use_squash_pr_title_as_default": false, "squash_merge_commit_message": "COMMIT_MESSAGES", "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", "merge_commit_message": "PR_TITLE", "merge_commit_title": "MERGE_MESSAGE", "security_and_analysis": { "secret_scanning": { "status": "disabled" }, "secret_scanning_push_protection": { "status": "disabled" }, "dependabot_security_updates": { "status": "disabled" } }, "network_count": 0, "subscribers_count": 4 } gcli-2.3.0/tests/samples/gitlab_simple_fork.json000066400000000000000000000131041460062271200217660ustar00rootroot00000000000000{ "id": 39885442, "description": "Somewhat portable and secure CLI utility to interact with various Git forges.", "name": "gcli", "name_with_namespace": "Gavin-John Noonan / gcli", "path": "gcli", "path_with_namespace": "gjnoonan/gcli", "created_at": "2022-10-02T13:54:20.517Z", "default_branch": "trunk", "tag_list": [], "topics": [], "ssh_url_to_repo": "git@gitlab.com:gjnoonan/gcli.git", "http_url_to_repo": "https://gitlab.com/gjnoonan/gcli.git", "web_url": "https://gitlab.com/gjnoonan/gcli", "readme_url": "https://gitlab.com/gjnoonan/gcli/-/blob/trunk/README.md", "forks_count": 0, "avatar_url": null, "star_count": 0, "last_activity_at": "2023-09-02T14:55:09.091Z", "namespace": { "id": 242706, "name": "Gavin-John Noonan", "path": "gjnoonan", "kind": "user", "full_path": "gjnoonan", "parent_id": null, "avatar_url": "https://secure.gravatar.com/avatar/89a6c7fbad7932af9cb579e240c9b4f6?s=80&d=identicon", "web_url": "https://gitlab.com/gjnoonan" }, "container_registry_image_prefix": "registry.gitlab.com/gjnoonan/gcli", "_links": { "self": "https://gitlab.com/api/v4/projects/39885442", "issues": "https://gitlab.com/api/v4/projects/39885442/issues", "merge_requests": "https://gitlab.com/api/v4/projects/39885442/merge_requests", "repo_branches": "https://gitlab.com/api/v4/projects/39885442/repository/branches", "labels": "https://gitlab.com/api/v4/projects/39885442/labels", "events": "https://gitlab.com/api/v4/projects/39885442/events", "members": "https://gitlab.com/api/v4/projects/39885442/members", "cluster_agents": "https://gitlab.com/api/v4/projects/39885442/cluster_agents" }, "packages_enabled": true, "empty_repo": false, "archived": false, "visibility": "public", "owner": { "id": 205880, "username": "gjnoonan", "name": "Gavin-John Noonan", "state": "active", "avatar_url": "https://secure.gravatar.com/avatar/89a6c7fbad7932af9cb579e240c9b4f6?s=80&d=identicon", "web_url": "https://gitlab.com/gjnoonan" }, "resolve_outdated_diff_discussions": false, "container_expiration_policy": { "cadence": "1d", "enabled": false, "keep_n": 10, "older_than": "90d", "name_regex": ".*", "name_regex_keep": null, "next_run_at": "2022-10-03T13:54:20.551Z" }, "issues_enabled": true, "merge_requests_enabled": true, "wiki_enabled": true, "jobs_enabled": true, "snippets_enabled": true, "container_registry_enabled": true, "service_desk_enabled": true, "can_create_merge_request_in": true, "issues_access_level": "enabled", "repository_access_level": "enabled", "merge_requests_access_level": "enabled", "forking_access_level": "enabled", "wiki_access_level": "enabled", "builds_access_level": "enabled", "snippets_access_level": "enabled", "pages_access_level": "enabled", "analytics_access_level": "enabled", "container_registry_access_level": "enabled", "security_and_compliance_access_level": "private", "releases_access_level": "enabled", "environments_access_level": "enabled", "feature_flags_access_level": "enabled", "infrastructure_access_level": "enabled", "monitor_access_level": "enabled", "emails_disabled": false, "emails_enabled": true, "shared_runners_enabled": true, "lfs_enabled": true, "creator_id": 205880, "forked_from_project": { "id": 34707535, "description": "Somewhat portable and secure CLI utility to interact with various Git forges.", "name": "gcli", "name_with_namespace": "Nico Sonack / gcli", "path": "gcli", "path_with_namespace": "herrhotzenplotz/gcli", "created_at": "2022-03-22T16:57:59.891Z", "default_branch": "trunk", "tag_list": [], "topics": [], "ssh_url_to_repo": "git@gitlab.com:herrhotzenplotz/gcli.git", "http_url_to_repo": "https://gitlab.com/herrhotzenplotz/gcli.git", "web_url": "https://gitlab.com/herrhotzenplotz/gcli", "readme_url": "https://gitlab.com/herrhotzenplotz/gcli/-/blob/trunk/README.md", "forks_count": 2, "avatar_url": null, "star_count": 2, "last_activity_at": "2023-09-02T16:44:49.863Z", "namespace": { "id": 7926179, "name": "Nico Sonack", "path": "herrhotzenplotz", "kind": "user", "full_path": "herrhotzenplotz", "parent_id": null, "avatar_url": "/uploads/-/system/user/avatar/5980462/avatar.png", "web_url": "https://gitlab.com/herrhotzenplotz" } }, "mr_default_target_self": false, "import_status": "finished", "open_issues_count": 0, "description_html": "

Somewhat portable and secure CLI utility to interact with various Git forges.

", "updated_at": "2023-09-02T14:55:09.091Z", "ci_config_path": "", "public_jobs": true, "shared_with_groups": [], "only_allow_merge_if_pipeline_succeeds": false, "allow_merge_on_skipped_pipeline": null, "request_access_enabled": true, "only_allow_merge_if_all_discussions_are_resolved": false, "remove_source_branch_after_merge": true, "printing_merge_request_link_enabled": true, "merge_method": "merge", "squash_option": "default_off", "enforce_auth_checks_on_uploads": true, "suggestion_commit_message": null, "merge_commit_template": null, "squash_commit_template": null, "issue_branch_template": null, "autoclose_referenced_issues": true, "external_authorization_classification_label": "", "requirements_enabled": false, "requirements_access_level": "enabled", "security_and_compliance_enabled": false, "compliance_frameworks": [], "permissions": { "project_access": null, "group_access": null } } gcli-2.3.0/tests/samples/gitlab_simple_issue.json000066400000000000000000000034461460062271200221650ustar00rootroot00000000000000{"id":132128913,"iid":193,"project_id":34707535,"title":"Make notifications API use a list struct containing both the ptr and size","description":"That would make some of the code much cleaner","state":"closed","created_at":"2023-08-13T18:43:05.766Z","updated_at":"2023-08-23T23:51:46.360Z","closed_at":"2023-08-23T23:51:46.350Z","closed_by":{"id":5980462,"username":"herrhotzenplotz","name":"Nico Sonack","state":"active","avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/5980462/avatar.png","web_url":"https://gitlab.com/herrhotzenplotz"},"labels":["good-first-issue"],"milestone":null,"assignees":[],"author":{"id":5980462,"username":"herrhotzenplotz","name":"Nico Sonack","state":"active","avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/5980462/avatar.png","web_url":"https://gitlab.com/herrhotzenplotz"},"type":"ISSUE","assignee":null,"user_notes_count":2,"merge_requests_count":0,"upvotes":0,"downvotes":0,"due_date":null,"confidential":false,"discussion_locked":null,"issue_type":"issue","web_url":"https://gitlab.com/herrhotzenplotz/gcli/-/issues/193","time_stats":{"time_estimate":0,"total_time_spent":0,"human_time_estimate":null,"human_total_time_spent":null},"task_completion_status":{"count":0,"completed_count":0},"blocking_issues_count":0,"has_tasks":true,"task_status":"0 of 0 checklist items completed","_links":{"self":"https://gitlab.com/api/v4/projects/34707535/issues/193","notes":"https://gitlab.com/api/v4/projects/34707535/issues/193/notes","award_emoji":"https://gitlab.com/api/v4/projects/34707535/issues/193/award_emoji","project":"https://gitlab.com/api/v4/projects/34707535","closed_as_duplicate_of":null},"references":{"short":"#193","relative":"#193","full":"herrhotzenplotz/gcli#193"},"severity":"UNKNOWN","subscribed":true,"moved_to_id":null,"service_desk_reply_to":null}gcli-2.3.0/tests/samples/gitlab_simple_label.json000066400000000000000000000003551460062271200221100ustar00rootroot00000000000000{"id":24376073,"name":"bug","description":"Something isn't working as expected","description_html":"Something isn't working as expected","text_color":"#FFFFFF","color":"#d73a4a","subscribed":false,"priority":null,"is_project_label":true}gcli-2.3.0/tests/samples/gitlab_simple_merge_request.json000066400000000000000000000076711460062271200237100ustar00rootroot00000000000000{"id":246912053,"iid":216,"project_id":34707535,"title":"Fix test suite","description":"This finally fixes the broken test suite","state":"merged","created_at":"2023-08-31T23:37:50.848Z","updated_at":"2023-09-01T17:20:03.185Z","merged_by":{"id":5980462,"username":"herrhotzenplotz","name":"Nico Sonack","state":"active","avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/5980462/avatar.png","web_url":"https://gitlab.com/herrhotzenplotz"},"merge_user":{"id":5980462,"username":"herrhotzenplotz","name":"Nico Sonack","state":"active","avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/5980462/avatar.png","web_url":"https://gitlab.com/herrhotzenplotz"},"merged_at":"2023-09-01T17:20:02.486Z","closed_by":null,"closed_at":null,"target_branch":"trunk","source_branch":"fix-test-suite","user_notes_count":0,"upvotes":0,"downvotes":0,"author":{"id":5980462,"username":"herrhotzenplotz","name":"Nico Sonack","state":"active","avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/5980462/avatar.png","web_url":"https://gitlab.com/herrhotzenplotz"},"assignees":[],"assignee":null,"reviewers":[],"source_project_id":34707535,"target_project_id":34707535,"labels":[],"draft":false,"work_in_progress":false,"milestone":null,"merge_when_pipeline_succeeds":false,"merge_status":"can_be_merged","detailed_merge_status":"not_open","sha":"3eab596a6806434e4a34bb19de12307ab1217af3","merge_commit_sha":"767e01417ac36f0090ee6d5da737cfb5135e6fc6","squash_commit_sha":null,"discussion_locked":null,"should_remove_source_branch":false,"force_remove_source_branch":null,"prepared_at":"2023-08-31T23:37:58.547Z","reference":"!216","references":{"short":"!216","relative":"!216","full":"herrhotzenplotz/gcli!216"},"web_url":"https://gitlab.com/herrhotzenplotz/gcli/-/merge_requests/216","time_stats":{"time_estimate":0,"total_time_spent":0,"human_time_estimate":null,"human_total_time_spent":null},"squash":false,"squash_on_merge":false,"task_completion_status":{"count":0,"completed_count":0},"has_conflicts":false,"blocking_discussions_resolved":true,"approvals_before_merge":null,"subscribed":true,"changes_count":"98","latest_build_started_at":"2023-09-01T17:02:48.982Z","latest_build_finished_at":"2023-09-01T17:15:35.537Z","first_deployed_to_production_at":null,"pipeline":{"id":989409992,"iid":116,"project_id":34707535,"sha":"3eab596a6806434e4a34bb19de12307ab1217af3","ref":"refs/merge-requests/216/head","status":"success","source":"merge_request_event","created_at":"2023-09-01T17:02:48.091Z","updated_at":"2023-09-01T17:15:35.546Z","web_url":"https://gitlab.com/herrhotzenplotz/gcli/-/pipelines/989409992"},"head_pipeline":{"id":989409992,"iid":116,"project_id":34707535,"sha":"3eab596a6806434e4a34bb19de12307ab1217af3","ref":"refs/merge-requests/216/head","status":"success","source":"merge_request_event","created_at":"2023-09-01T17:02:48.091Z","updated_at":"2023-09-01T17:15:35.546Z","web_url":"https://gitlab.com/herrhotzenplotz/gcli/-/pipelines/989409992","before_sha":"0000000000000000000000000000000000000000","tag":false,"yaml_errors":null,"user":{"id":5980462,"username":"herrhotzenplotz","name":"Nico Sonack","state":"active","avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/5980462/avatar.png","web_url":"https://gitlab.com/herrhotzenplotz"},"started_at":"2023-09-01T17:02:48.982Z","finished_at":"2023-09-01T17:15:35.537Z","committed_at":null,"duration":450,"queued_duration":null,"coverage":null,"detailed_status":{"icon":"status_success","text":"passed","label":"passed","group":"success","tooltip":"passed","has_details":true,"details_path":"/herrhotzenplotz/gcli/-/pipelines/989409992","illustration":null,"favicon":"/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"}},"diff_refs":{"base_sha":"4295254675687c212df05a78f561566aea716780","head_sha":"3eab596a6806434e4a34bb19de12307ab1217af3","start_sha":"4295254675687c212df05a78f561566aea716780"},"merge_error":null,"first_contribution":false,"user":{"can_merge":true}}gcli-2.3.0/tests/samples/gitlab_simple_milestone.json000066400000000000000000000005201460062271200230220ustar00rootroot00000000000000{"id":2975318,"iid":2,"project_id":34707535,"title":"Version 2","description":"Things that need to be done for version 2","state":"active","created_at":"2023-02-05T19:08:20.379Z","updated_at":"2023-02-05T19:08:20.379Z","due_date":null,"start_date":null,"expired":false,"web_url":"https://gitlab.com/herrhotzenplotz/gcli/-/milestones/2"}gcli-2.3.0/tests/samples/gitlab_simple_pipeline.json000066400000000000000000000021671460062271200226410ustar00rootroot00000000000000{"id":989897020,"iid":121,"project_id":34707535,"sha":"742affb88a297a6b34201ad61c8b5b72ec6eb679","ref":"refs/merge-requests/219/head","status":"failed","source":"merge_request_event","created_at":"2023-09-02T14:30:20.925Z","updated_at":"2023-09-02T14:31:40.328Z","web_url":"https://gitlab.com/herrhotzenplotz/gcli/-/pipelines/989897020","before_sha":"0000000000000000000000000000000000000000","tag":false,"yaml_errors":null,"user":{"id":5980462,"username":"herrhotzenplotz","name":"Nico Sonack","state":"active","avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/5980462/avatar.png","web_url":"https://gitlab.com/herrhotzenplotz"},"started_at":"2023-09-02T14:30:21.867Z","finished_at":"2023-09-02T14:31:40.317Z","committed_at":null,"duration":73,"queued_duration":null,"coverage":null,"detailed_status":{"icon":"status_failed","text":"failed","label":"failed","group":"failed","tooltip":"failed","has_details":true,"details_path":"/herrhotzenplotz/gcli/-/pipelines/989897020","illustration":null,"favicon":"/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png"},"name":null}gcli-2.3.0/tests/samples/gitlab_simple_release.json000066400000000000000000000066021460062271200224520ustar00rootroot00000000000000{"name":"1.2.0","tag_name":"1.2.0","description":"# 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","created_at":"2023-08-11T07:56:06.371Z","released_at":"2023-08-11T07:56:06.371Z","upcoming_release":false,"author":{"id":5980462,"username":"herrhotzenplotz","name":"Nico Sonack","state":"active","avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/5980462/avatar.png","web_url":"https://gitlab.com/herrhotzenplotz"},"commit":{"id":"a6e295d088b4215ad52cb25d269e54b204acc471","short_id":"a6e295d0","created_at":"2023-08-11T09:42:37.000+02:00","parent_ids":["2503abe114b4701ccd42cf187349e5dc7d382a2b","f1b942e965a5d462a350faf0ff37fda86c25a931"],"title":"Merge branch 'trunk' into release","message":"Merge branch 'trunk' into release\n","author_name":"Nico Sonack","author_email":"nsonack@herrhotzenplotz.de","authored_date":"2023-08-11T09:42:37.000+02:00","committer_name":"Nico Sonack","committer_email":"nsonack@herrhotzenplotz.de","committed_date":"2023-08-11T09:42:37.000+02:00","trailers":{},"web_url":"https://gitlab.com/herrhotzenplotz/gcli/-/commit/a6e295d088b4215ad52cb25d269e54b204acc471"},"commit_path":"/herrhotzenplotz/gcli/-/commit/a6e295d088b4215ad52cb25d269e54b204acc471","tag_path":"/herrhotzenplotz/gcli/-/tags/1.2.0","assets":{"count":4,"sources":[{"format":"zip","url":"https://gitlab.com/herrhotzenplotz/gcli/-/archive/1.2.0/gcli-1.2.0.zip"},{"format":"tar.gz","url":"https://gitlab.com/herrhotzenplotz/gcli/-/archive/1.2.0/gcli-1.2.0.tar.gz"},{"format":"tar.bz2","url":"https://gitlab.com/herrhotzenplotz/gcli/-/archive/1.2.0/gcli-1.2.0.tar.bz2"},{"format":"tar","url":"https://gitlab.com/herrhotzenplotz/gcli/-/archive/1.2.0/gcli-1.2.0.tar"}],"links":[]},"evidences":[{"sha":"c4daff0bf736e2a5b4abe83f2811d01fe2d03ba287ed","filepath":"https://gitlab.com/herrhotzenplotz/gcli/-/releases/1.2.0/evidences/5392288.json","collected_at":"2023-08-11T07:56:06.694Z"}],"_links":{"closed_issues_url":"https://gitlab.com/herrhotzenplotz/gcli/-/issues?release_tag=1.2.0\u0026scope=all\u0026state=closed","closed_merge_requests_url":"https://gitlab.com/herrhotzenplotz/gcli/-/merge_requests?release_tag=1.2.0\u0026scope=all\u0026state=closed","edit_url":"https://gitlab.com/herrhotzenplotz/gcli/-/releases/1.2.0/edit","merged_merge_requests_url":"https://gitlab.com/herrhotzenplotz/gcli/-/merge_requests?release_tag=1.2.0\u0026scope=all\u0026state=merged","opened_issues_url":"https://gitlab.com/herrhotzenplotz/gcli/-/issues?release_tag=1.2.0\u0026scope=all\u0026state=opened","opened_merge_requests_url":"https://gitlab.com/herrhotzenplotz/gcli/-/merge_requests?release_tag=1.2.0\u0026scope=all\u0026state=opened","self":"https://gitlab.com/herrhotzenplotz/gcli/-/releases/1.2.0"}}gcli-2.3.0/tests/samples/gitlab_simple_repo.json000066400000000000000000000113521460062271200217750ustar00rootroot00000000000000{"id":34707535,"description":"Somewhat portable and secure CLI utility to interact with various Git forges.","name":"gcli","name_with_namespace":"Nico Sonack / gcli","path":"gcli","path_with_namespace":"herrhotzenplotz/gcli","created_at":"2022-03-22T16:57:59.891Z","default_branch":"trunk","tag_list":[],"topics":[],"ssh_url_to_repo":"git@gitlab.com:herrhotzenplotz/gcli.git","http_url_to_repo":"https://gitlab.com/herrhotzenplotz/gcli.git","web_url":"https://gitlab.com/herrhotzenplotz/gcli","readme_url":"https://gitlab.com/herrhotzenplotz/gcli/-/blob/trunk/README.md","forks_count":2,"avatar_url":null,"star_count":2,"last_activity_at":"2023-09-03T22:07:05.424Z","namespace":{"id":7926179,"name":"Nico Sonack","path":"herrhotzenplotz","kind":"user","full_path":"herrhotzenplotz","parent_id":null,"avatar_url":"/uploads/-/system/user/avatar/5980462/avatar.png","web_url":"https://gitlab.com/herrhotzenplotz"},"container_registry_image_prefix":"registry.gitlab.com/herrhotzenplotz/gcli","_links":{"self":"https://gitlab.com/api/v4/projects/34707535","issues":"https://gitlab.com/api/v4/projects/34707535/issues","merge_requests":"https://gitlab.com/api/v4/projects/34707535/merge_requests","repo_branches":"https://gitlab.com/api/v4/projects/34707535/repository/branches","labels":"https://gitlab.com/api/v4/projects/34707535/labels","events":"https://gitlab.com/api/v4/projects/34707535/events","members":"https://gitlab.com/api/v4/projects/34707535/members","cluster_agents":"https://gitlab.com/api/v4/projects/34707535/cluster_agents"},"packages_enabled":true,"empty_repo":false,"archived":false,"visibility":"public","owner":{"id":5980462,"username":"herrhotzenplotz","name":"Nico Sonack","state":"active","avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/5980462/avatar.png","web_url":"https://gitlab.com/herrhotzenplotz"},"resolve_outdated_diff_discussions":false,"container_expiration_policy":{"cadence":"1d","enabled":false,"keep_n":10,"older_than":"90d","name_regex":".*","name_regex_keep":null,"next_run_at":"2022-03-23T16:57:59.919Z"},"issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"jobs_enabled":true,"snippets_enabled":true,"container_registry_enabled":true,"service_desk_enabled":true,"service_desk_address":"contact-project+herrhotzenplotz-gcli-34707535-issue-@incoming.gitlab.com","can_create_merge_request_in":true,"issues_access_level":"enabled","repository_access_level":"enabled","merge_requests_access_level":"enabled","forking_access_level":"enabled","wiki_access_level":"enabled","builds_access_level":"enabled","snippets_access_level":"enabled","pages_access_level":"enabled","analytics_access_level":"enabled","container_registry_access_level":"enabled","security_and_compliance_access_level":"private","releases_access_level":"enabled","environments_access_level":"enabled","feature_flags_access_level":"enabled","infrastructure_access_level":"enabled","monitor_access_level":"enabled","emails_disabled":false,"emails_enabled":true,"shared_runners_enabled":true,"lfs_enabled":true,"creator_id":5980462,"import_url":"https://github.com/herrhotzenplotz/ghcli.git","import_type":"github","import_status":"finished","import_error":null,"open_issues_count":20,"description_html":"\u003cp data-sourcepos=\"1:1-1:77\" dir=\"auto\"\u003eSomewhat portable and secure CLI utility to interact with various Git forges.\u003c/p\u003e","updated_at":"2023-09-03T22:07:05.424Z","ci_default_git_depth":20,"ci_forward_deployment_enabled":true,"ci_forward_deployment_rollback_allowed":true,"ci_job_token_scope_enabled":false,"ci_separated_caches":true,"ci_allow_fork_pipelines_to_run_in_parent_project":true,"build_git_strategy":"fetch","keep_latest_artifact":true,"restrict_user_defined_variables":false,"runners_token":"GR1348941v4uDTusyFqmfzCjiTcGb","runner_token_expiration_interval":null,"group_runners_enabled":true,"auto_cancel_pending_pipelines":"enabled","build_timeout":3600,"auto_devops_enabled":false,"auto_devops_deploy_strategy":"continuous","ci_config_path":"","public_jobs":true,"shared_with_groups":[],"only_allow_merge_if_pipeline_succeeds":false,"allow_merge_on_skipped_pipeline":false,"request_access_enabled":true,"only_allow_merge_if_all_discussions_are_resolved":true,"remove_source_branch_after_merge":true,"printing_merge_request_link_enabled":true,"merge_method":"merge","squash_option":"default_off","enforce_auth_checks_on_uploads":true,"suggestion_commit_message":"","merge_commit_template":null,"squash_commit_template":null,"issue_branch_template":null,"autoclose_referenced_issues":true,"external_authorization_classification_label":"","requirements_enabled":false,"requirements_access_level":"enabled","security_and_compliance_enabled":true,"compliance_frameworks":[],"permissions":{"project_access":{"access_level":50,"notification_level":3},"group_access":null}}gcli-2.3.0/tests/samples/gitlab_simple_snippet.json000066400000000000000000000014331460062271200225110ustar00rootroot00000000000000{"id":2141655,"title":"darcy-weisbach SPARC64","description":"Paste","visibility":"public","author":{"id":5980462,"username":"herrhotzenplotz","name":"Nico Sonack","state":"active","avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/5980462/avatar.png","web_url":"https://gitlab.com/herrhotzenplotz"},"created_at":"2021-06-28T15:47:36.214Z","updated_at":"2021-06-28T15:47:36.214Z","project_id":null,"web_url":"https://gitlab.com/-/snippets/2141655","raw_url":"https://gitlab.com/-/snippets/2141655/raw","ssh_url_to_repo":"git@gitlab.com:snippets/2141655.git","http_url_to_repo":"https://gitlab.com/snippets/2141655.git","file_name":"darcy-weisbach SPARC64","files":[{"path":"darcy-weisbach SPARC64","raw_url":"https://gitlab.com/-/snippets/2141655/raw/main/darcy-weisbach%20SPARC64"}]}gcli-2.3.0/tests/test-jsongen.c000066400000000000000000000144741460062271200163720ustar00rootroot00000000000000/* * 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 ATF_TC_WITHOUT_HEAD(empty_object); ATF_TC_BODY(empty_object, tc) { struct gcli_jsongen gen = {0}; ATF_REQUIRE(gcli_jsongen_init(&gen) == 0); ATF_REQUIRE(gcli_jsongen_begin_object(&gen) == 0); ATF_REQUIRE(gcli_jsongen_end_object(&gen) == 0); char *str = gcli_jsongen_to_string(&gen); ATF_CHECK_STREQ(str, "{}"); free(str); gcli_jsongen_free(&gen); } ATF_TC_WITHOUT_HEAD(array_with_two_empty_objects); ATF_TC_BODY(array_with_two_empty_objects, tc) { struct gcli_jsongen gen = {0}; ATF_REQUIRE(gcli_jsongen_init(&gen) == 0); ATF_REQUIRE(gcli_jsongen_begin_array(&gen) == 0); for (int i = 0; i < 2; ++i) { ATF_REQUIRE(gcli_jsongen_begin_object(&gen) == 0); ATF_REQUIRE(gcli_jsongen_end_object(&gen) == 0); } ATF_REQUIRE(gcli_jsongen_end_array(&gen) == 0); char *str = gcli_jsongen_to_string(&gen); ATF_CHECK_STREQ(str, "[{}, {}]"); free(str); gcli_jsongen_free(&gen); } ATF_TC_WITHOUT_HEAD(empty_array); ATF_TC_BODY(empty_array, tc) { struct gcli_jsongen gen = {0}; ATF_REQUIRE(gcli_jsongen_init(&gen) == 0); ATF_REQUIRE(gcli_jsongen_begin_array(&gen) == 0); ATF_REQUIRE(gcli_jsongen_end_array(&gen) == 0); char *str = gcli_jsongen_to_string(&gen); ATF_CHECK_STREQ(str, "[]"); free(str); gcli_jsongen_free(&gen); } ATF_TC_WITHOUT_HEAD(object_with_number); ATF_TC_BODY(object_with_number, tc) { struct gcli_jsongen gen = {0}; ATF_REQUIRE(gcli_jsongen_init(&gen) == 0); ATF_REQUIRE(gcli_jsongen_begin_object(&gen) == 0); ATF_REQUIRE(gcli_jsongen_objmember(&gen, "number") == 0); ATF_REQUIRE(gcli_jsongen_number(&gen, 420) == 0); ATF_REQUIRE(gcli_jsongen_end_object(&gen) == 0); char *str = gcli_jsongen_to_string(&gen); ATF_CHECK_STREQ(str, "{\"number\": 420}"); free(str); gcli_jsongen_free(&gen); } ATF_TC_WITHOUT_HEAD(object_nested); ATF_TC_BODY(object_nested, tc) { struct gcli_jsongen gen = {0}; ATF_REQUIRE(gcli_jsongen_init(&gen) == 0); ATF_REQUIRE(gcli_jsongen_begin_object(&gen) == 0); ATF_REQUIRE(gcli_jsongen_objmember(&gen, "hiernenarray") == 0); ATF_REQUIRE(gcli_jsongen_begin_array(&gen) == 0); ATF_REQUIRE(gcli_jsongen_number(&gen, 69) == 0); ATF_REQUIRE(gcli_jsongen_number(&gen, 420) == 0); ATF_REQUIRE(gcli_jsongen_end_array(&gen) == 0); ATF_REQUIRE(gcli_jsongen_objmember(&gen, "empty_object") == 0); ATF_REQUIRE(gcli_jsongen_begin_object(&gen) == 0); ATF_REQUIRE(gcli_jsongen_end_object(&gen) == 0); ATF_REQUIRE(gcli_jsongen_end_object(&gen) == 0); char *str = gcli_jsongen_to_string(&gen); ATF_CHECK_STREQ(str, "{\"hiernenarray\": [69, 420], \"empty_object\": {}}"); free(str); gcli_jsongen_free(&gen); } ATF_TC_WITHOUT_HEAD(object_with_strings); ATF_TC_BODY(object_with_strings, tc) { struct gcli_jsongen gen = {0}; ATF_REQUIRE(gcli_jsongen_init(&gen) == 0); ATF_REQUIRE(gcli_jsongen_begin_object(&gen) == 0); ATF_REQUIRE(gcli_jsongen_objmember(&gen, "key") == 0); ATF_REQUIRE(gcli_jsongen_string(&gen, "value") == 0); ATF_REQUIRE(gcli_jsongen_end_object(&gen) == 0); char *str = gcli_jsongen_to_string(&gen); ATF_CHECK_STREQ(str, "{\"key\": \"value\"}"); free(str); gcli_jsongen_free(&gen); } ATF_TC_WITHOUT_HEAD(object_with_mixed_values); ATF_TC_BODY(object_with_mixed_values, tc) { struct gcli_jsongen gen = {0}; ATF_REQUIRE(gcli_jsongen_init(&gen) == 0); ATF_REQUIRE(gcli_jsongen_begin_object(&gen) == 0); ATF_REQUIRE(gcli_jsongen_objmember(&gen, "array") == 0); ATF_REQUIRE(gcli_jsongen_begin_array(&gen) == 0); ATF_REQUIRE(gcli_jsongen_number(&gen, 42) == 0); ATF_REQUIRE(gcli_jsongen_string(&gen, "a string literal") == 0); ATF_REQUIRE(gcli_jsongen_begin_object(&gen) == 0); ATF_REQUIRE(gcli_jsongen_end_object(&gen) == 0); ATF_REQUIRE(gcli_jsongen_end_array(&gen) == 0); ATF_REQUIRE(gcli_jsongen_end_object(&gen) == 0); char *str = gcli_jsongen_to_string(&gen); ATF_CHECK_STREQ(str, "{\"array\": [42, \"a string literal\", {}]}"); free(str); gcli_jsongen_free(&gen); } 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) { struct gcli_jsongen gen = {0}; ATF_REQUIRE(gcli_jsongen_init(&gen) == 0); ATF_REQUIRE(gcli_jsongen_begin_object(&gen) == 0); ATF_REQUIRE(gcli_jsongen_objmember(&gen, "key1") == 0); ATF_REQUIRE(gcli_jsongen_string(&gen, "value1") == 0); ATF_REQUIRE(gcli_jsongen_objmember(&gen, "key2") == 0); ATF_REQUIRE(gcli_jsongen_string(&gen, "value2") == 0); ATF_REQUIRE(gcli_jsongen_end_object(&gen) == 0); char *str = gcli_jsongen_to_string(&gen); ATF_CHECK_STREQ(str, "{\"key1\": \"value1\", \"key2\": \"value2\"}"); free(str); gcli_jsongen_free(&gen); } ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, empty_object); ATF_TP_ADD_TC(tp, array_with_two_empty_objects); ATF_TP_ADD_TC(tp, empty_array); ATF_TP_ADD_TC(tp, object_with_number); ATF_TP_ADD_TC(tp, object_nested); ATF_TP_ADD_TC(tp, object_with_strings); ATF_TP_ADD_TC(tp, object_with_mixed_values); ATF_TP_ADD_TC(tp, object_with_two_keys_and_values_that_are_string); return atf_no_error(); } gcli-2.3.0/tests/update-samples.sh000077500000000000000000000040741460062271200170640ustar00rootroot00000000000000#!/bin/sh -x die() { printf -- "error: $@\n" >&2 exit 1 } which gcli > /dev/null 2>&1 || die "gcli is not in PATH" which jq > /dev/null 2>&1 || die "jq is not in PATH" gcli -t github api /repos/herrhotzenplotz/gcli/issues/115 > samples/github_simple_issue.json gcli -t github api /repos/herrhotzenplotz/gcli/labels/bug > samples/github_simple_label.json gcli -t github api /repos/herrhotzenplotz/gcli/pulls/113 > samples/github_simple_pull.json gcli -t github api /repos/herrhotzenplotz/gcli/milestones/1 > samples/github_simple_milestone.json gcli -t github api /repos/herrhotzenplotz/gcli/releases/116031718 > samples/github_simple_release.json gcli -t github api /repos/herrhotzenplotz/gcli > samples/github_simple_repo.json gcli -t github api /repos/quick-lint/quick-lint-js/forks | jq '.[] | select(.id == 639263592)' > samples/github_simple_fork.json gcli -t github api /repos/herrhotzenplotz/gcli/issues/comments/1424392601 > samples/github_simple_comment.json gcli -t github api /repos/quick-lint/quick-lint-js/commits/03a8af3dab144ea38910c1efdcce03fb708f3179/check-runs | jq '.check_runs | .[] | select(.id == 16437184455)' > samples/github_simple_check.json gcli -t gitlab api /projects/herrhotzenplotz%2Fgcli/issues/193 > samples/gitlab_simple_issue.json gcli -t gitlab api /projects/herrhotzenplotz%2Fgcli/labels/24376073 > samples/gitlab_simple_label.json gcli -t gitlab api /projects/herrhotzenplotz%2Fgcli/merge_requests/216 > samples/gitlab_simple_merge_request.json gcli -t gitlab api /projects/herrhotzenplotz%2Fgcli/releases/1.2.0 > samples/gitlab_simple_release.json gcli -t gitlab api /projects/herrhotzenplotz%2Fgcli/forks | jq '.[] | select(.id == 39885442)' > samples/gitlab_simple_fork.json gcli -t gitlab api /projects/herrhotzenplotz%2Fgcli/milestones/2975318 > samples/gitlab_simple_milestone.json gcli -t gitlab api /projects/herrhotzenplotz%2Fgcli/pipelines/989897020 > samples/gitlab_simple_pipeline.json gcli -t gitlab api /projects/herrhotzenplotz%2Fgcli > samples/gitlab_simple_repo.json gcli -t gitlab api /snippets/2141655 > samples/gitlab_simple_snippet.json gcli-2.3.0/tests/url-encode.c000066400000000000000000000024631460062271200160020ustar00rootroot00000000000000#include #include ATF_TC_WITHOUT_HEAD(simple_characters); ATF_TC_BODY(simple_characters, tc) { ATF_CHECK_STREQ(gcli_urlencode("%"), "%25"); ATF_CHECK_STREQ(gcli_urlencode(" "), "%20"); ATF_CHECK_STREQ(gcli_urlencode("-"), "-"); ATF_CHECK_STREQ(gcli_urlencode("_"), "_"); } ATF_TC_WITHOUT_HEAD(umlaute); ATF_TC_BODY(umlaute, tc) { ATF_CHECK_STREQ(gcli_urlencode("Ä"), "%C3%84"); ATF_CHECK_STREQ(gcli_urlencode("ä"), "%C3%A4"); ATF_CHECK_STREQ(gcli_urlencode("Ö"), "%C3%96"); ATF_CHECK_STREQ(gcli_urlencode("ö"), "%C3%B6"); ATF_CHECK_STREQ(gcli_urlencode("Ü"), "%C3%9C"); ATF_CHECK_STREQ(gcli_urlencode("ü"), "%C3%BC"); ATF_CHECK_STREQ(gcli_urlencode("ẞ"), "%E1%BA%9E"); ATF_CHECK_STREQ(gcli_urlencode("ß"), "%C3%9F"); } ATF_TC_WITHOUT_HEAD(torture); ATF_TC_BODY(torture, tc) { char text[] = "some-random url// with %%%%%content" "Rindfleischettikettierungsüberwachungsaufgabenübertragungsgesetz"; char *escaped = gcli_urlencode(text); char *expected = "some-random%20url%2F%2F%20with%20%25%25%25%25%25content" "Rindfleischettikettierungs%C3%BCberwachungsaufgaben%C3%BCbertragungsgesetz"; ATF_CHECK_STREQ(escaped, expected); } ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, simple_characters); ATF_TP_ADD_TC(tp, umlaute); ATF_TP_ADD_TC(tp, torture); return atf_no_error(); } gcli-2.3.0/thirdparty/000077500000000000000000000000001460062271200146245ustar00rootroot00000000000000gcli-2.3.0/thirdparty/pdjson/000077500000000000000000000000001460062271200161215ustar00rootroot00000000000000gcli-2.3.0/thirdparty/pdjson/UNLICENSE000066400000000000000000000022731460062271200173750ustar00rootroot00000000000000This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to gcli-2.3.0/thirdparty/pdjson/pdjson.c000066400000000000000000000616131460062271200175710ustar00rootroot00000000000000#ifndef _POSIX_C_SOURCE # define _POSIX_C_SOURCE 200112L #elif _POSIX_C_SOURCE < 200112L # error incompatible _POSIX_C_SOURCE level #endif #include #include #include #ifndef PDJSON_H # include "pdjson.h" #endif #define JSON_FLAG_ERROR (1u << 0) #define JSON_FLAG_STREAMING (1u << 1) #if defined(_MSC_VER) && (_MSC_VER < 1900) #define json_error(json, format, ...) \ if (!(json->flags & JSON_FLAG_ERROR)) { \ json->flags |= JSON_FLAG_ERROR; \ _snprintf_s(json->errmsg, sizeof(json->errmsg), \ _TRUNCATE, \ format, \ __VA_ARGS__); \ } \ #else #define json_error(json, format, ...) \ if (!(json->flags & JSON_FLAG_ERROR)) { \ json->flags |= JSON_FLAG_ERROR; \ snprintf(json->errmsg, sizeof(json->errmsg), \ format, \ __VA_ARGS__); \ } \ #endif /* _MSC_VER */ /* See also PDJSON_STACK_MAX below. */ #ifndef PDJSON_STACK_INC # define PDJSON_STACK_INC 4 #endif struct json_stack { enum json_type type; long count; }; static enum json_type push(json_stream *json, enum json_type type) { json->stack_top++; #ifdef PDJSON_STACK_MAX if (json->stack_top > PDJSON_STACK_MAX) { json_error(json, "%s", "maximum depth of nesting reached"); return JSON_ERROR; } #endif if (json->stack_top >= json->stack_size) { struct json_stack *stack; size_t size = (json->stack_size + PDJSON_STACK_INC) * sizeof(*json->stack); stack = (struct json_stack *)json->alloc.realloc(json->stack, size); if (stack == NULL) { json_error(json, "%s", "out of memory"); return JSON_ERROR; } json->stack_size += PDJSON_STACK_INC; json->stack = stack; } json->stack[json->stack_top].type = type; json->stack[json->stack_top].count = 0; return type; } static enum json_type pop(json_stream *json, int c, enum json_type expected) { if (json->stack == NULL || json->stack[json->stack_top].type != expected) { json_error(json, "unexpected byte '%c'", c); return JSON_ERROR; } json->stack_top--; return expected == JSON_ARRAY ? JSON_ARRAY_END : JSON_OBJECT_END; } static int buffer_peek(struct json_source *source) { if (source->position < source->source.buffer.length) return source->source.buffer.buffer[source->position]; else return EOF; } static int buffer_get(struct json_source *source) { int c = source->peek(source); source->position++; return c; } static int stream_get(struct json_source *source) { source->position++; return fgetc(source->source.stream.stream); } static int stream_peek(struct json_source *source) { int c = fgetc(source->source.stream.stream); ungetc(c, source->source.stream.stream); return c; } static void init(json_stream *json) { json->lineno = 1; json->flags = JSON_FLAG_STREAMING; json->errmsg[0] = '\0'; json->ntokens = 0; json->next = (enum json_type)0; json->stack = NULL; json->stack_top = -1; json->stack_size = 0; json->data.string = NULL; json->data.string_size = 0; json->data.string_fill = 0; json->source.position = 0; json->alloc.malloc = malloc; json->alloc.realloc = realloc; json->alloc.free = free; } static enum json_type is_match(json_stream *json, const char *pattern, enum json_type type) { int c; for (const char *p = pattern; *p; p++) { if (*p != (c = json->source.get(&json->source))) { json_error(json, "expected '%c' instead of byte '%c'", *p, c); return JSON_ERROR; } } return type; } static int pushchar(json_stream *json, int c) { if (json->data.string_fill == json->data.string_size) { size_t size = json->data.string_size * 2; char *buffer = (char *)json->alloc.realloc(json->data.string, size); if (buffer == NULL) { json_error(json, "%s", "out of memory"); return -1; } else { json->data.string_size = size; json->data.string = buffer; } } json->data.string[json->data.string_fill++] = c; return 0; } static int init_string(json_stream *json) { json->data.string_fill = 0; if (json->data.string == NULL) { json->data.string_size = 1024; json->data.string = (char *)json->alloc.malloc(json->data.string_size); if (json->data.string == NULL) { json_error(json, "%s", "out of memory"); return -1; } } json->data.string[0] = '\0'; return 0; } static int encode_utf8(json_stream *json, unsigned long c) { if (c < 0x80UL) { return pushchar(json, c); } else if (c < 0x0800UL) { return !((pushchar(json, (c >> 6 & 0x1F) | 0xC0) == 0) && (pushchar(json, (c >> 0 & 0x3F) | 0x80) == 0)); } else if (c < 0x010000UL) { if (c >= 0xd800 && c <= 0xdfff) { json_error(json, "invalid codepoint %06lx", c); return -1; } return !((pushchar(json, (c >> 12 & 0x0F) | 0xE0) == 0) && (pushchar(json, (c >> 6 & 0x3F) | 0x80) == 0) && (pushchar(json, (c >> 0 & 0x3F) | 0x80) == 0)); } else if (c < 0x110000UL) { return !((pushchar(json, (c >> 18 & 0x07) | 0xF0) == 0) && (pushchar(json, (c >> 12 & 0x3F) | 0x80) == 0) && (pushchar(json, (c >> 6 & 0x3F) | 0x80) == 0) && (pushchar(json, (c >> 0 & 0x3F) | 0x80) == 0)); } else { json_error(json, "unable to encode %06lx as UTF-8", c); return -1; } } static int hexchar(int c) { switch (c) { case '0': return 0; case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; case 'a': case 'A': return 10; case 'b': case 'B': return 11; case 'c': case 'C': return 12; case 'd': case 'D': return 13; case 'e': case 'E': return 14; case 'f': case 'F': return 15; default: return -1; } } static long read_unicode_cp(json_stream *json) { long cp = 0; int shift = 12; for (size_t i = 0; i < 4; i++) { int c = json->source.get(&json->source); int hc; if (c == EOF) { json_error(json, "%s", "unterminated string literal in Unicode"); return -1; } else if ((hc = hexchar(c)) == -1) { json_error(json, "invalid escape Unicode byte '%c'", c); return -1; } cp += hc * (1 << shift); shift -= 4; } return cp; } static int read_unicode(json_stream *json) { long cp, h, l; if ((cp = read_unicode_cp(json)) == -1) { return -1; } if (cp >= 0xd800 && cp <= 0xdbff) { /* This is the high portion of a surrogate pair; we need to read the * lower portion to get the codepoint */ h = cp; int c = json->source.get(&json->source); if (c == EOF) { json_error(json, "%s", "unterminated string literal in Unicode"); return -1; } else if (c != '\\') { json_error(json, "invalid continuation for surrogate pair '%c', " "expected '\\'", c); return -1; } c = json->source.get(&json->source); if (c == EOF) { json_error(json, "%s", "unterminated string literal in Unicode"); return -1; } else if (c != 'u') { json_error(json, "invalid continuation for surrogate pair '%c', " "expected 'u'", c); return -1; } if ((l = read_unicode_cp(json)) == -1) { return -1; } if (l < 0xdc00 || l > 0xdfff) { json_error(json, "surrogate pair continuation \\u%04lx out " "of range (dc00-dfff)", l); return -1; } cp = ((h - 0xd800) * 0x400) + ((l - 0xdc00) + 0x10000); } else if (cp >= 0xdc00 && cp <= 0xdfff) { json_error(json, "dangling surrogate \\u%04lx", cp); return -1; } return encode_utf8(json, cp); } static int read_escaped(json_stream *json) { int c = json->source.get(&json->source); if (c == EOF) { json_error(json, "%s", "unterminated string literal in escape"); return -1; } else if (c == 'u') { if (read_unicode(json) != 0) return -1; } else { switch (c) { case '\\': case 'b': case 'f': case 'n': case 'r': case 't': case '/': case '"': { const char *codes = "\\bfnrt/\""; const char *p = strchr(codes, c); if (pushchar(json, "\\\b\f\n\r\t/\""[p - codes]) != 0) return -1; } break; default: json_error(json, "invalid escaped byte '%c'", c); return -1; } } return 0; } static int char_needs_escaping(int c) { if ((c >= 0) && (c < 0x20 || c == 0x22 || c == 0x5c)) { return 1; } return 0; } static int utf8_seq_length(char byte) { unsigned char u = (unsigned char) byte; if (u < 0x80) return 1; if (0x80 <= u && u <= 0xBF) { // second, third or fourth byte of a multi-byte // sequence, i.e. a "continuation byte" return 0; } else if (u == 0xC0 || u == 0xC1) { // overlong encoding of an ASCII byte return 0; } else if (0xC2 <= u && u <= 0xDF) { // 2-byte sequence return 2; } else if (0xE0 <= u && u <= 0xEF) { // 3-byte sequence return 3; } else if (0xF0 <= u && u <= 0xF4) { // 4-byte sequence return 4; } else { // u >= 0xF5 // Restricted (start of 4-, 5- or 6-byte sequence) or invalid UTF-8 return 0; } } static int is_legal_utf8(const unsigned char *bytes, int length) { if (0 == bytes || 0 == length) return 0; unsigned char a; const unsigned char* srcptr = bytes + length; switch (length) { default: return 0; // Everything else falls through when true. case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return 0; /* FALLTHRU */ case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return 0; /* FALLTHRU */ case 2: a = (*--srcptr); switch (*bytes) { case 0xE0: if (a < 0xA0 || a > 0xBF) return 0; break; case 0xED: if (a < 0x80 || a > 0x9F) return 0; break; case 0xF0: if (a < 0x90 || a > 0xBF) return 0; break; case 0xF4: if (a < 0x80 || a > 0x8F) return 0; break; default: if (a < 0x80 || a > 0xBF) return 0; break; } /* FALLTHRU */ case 1: if (*bytes >= 0x80 && *bytes < 0xC2) return 0; } return *bytes <= 0xF4; } static int read_utf8(json_stream* json, int next_char) { int count = utf8_seq_length(next_char); if (!count) { json_error(json, "%s", "invalid UTF-8 character"); return -1; } char buffer[4]; buffer[0] = next_char; int i; for (i = 1; i < count; ++i) { buffer[i] = json->source.get(&json->source);; } if (!is_legal_utf8((unsigned char*) buffer, count)) { json_error(json, "%s", "invalid UTF-8 text"); return -1; } for (i = 0; i < count; ++i) { if (pushchar(json, buffer[i]) != 0) return -1; } return 0; } static enum json_type read_string(json_stream *json) { if (init_string(json) != 0) return JSON_ERROR; while (1) { int c = json->source.get(&json->source); if (c == EOF) { json_error(json, "%s", "unterminated string literal"); return JSON_ERROR; } else if (c == '"') { if (pushchar(json, '\0') == 0) return JSON_STRING; else return JSON_ERROR; } else if (c == '\\') { if (read_escaped(json) != 0) return JSON_ERROR; } else if ((unsigned) c >= 0x80) { if (read_utf8(json, c) != 0) return JSON_ERROR; } else { if (char_needs_escaping(c)) { json_error(json, "%s", "unescaped control character in string"); return JSON_ERROR; } if (pushchar(json, c) != 0) return JSON_ERROR; } } return JSON_ERROR; } static int is_digit(int c) { return c >= 48 /*0*/ && c <= 57 /*9*/; } static int read_digits(json_stream *json) { int c; unsigned nread = 0; while (is_digit(c = json->source.peek(&json->source))) { if (pushchar(json, json->source.get(&json->source)) != 0) return -1; nread++; } if (nread == 0) { json_error(json, "expected digit instead of byte '%c'", c); return -1; } return 0; } static enum json_type read_number(json_stream *json, int c) { if (pushchar(json, c) != 0) return JSON_ERROR; if (c == '-') { c = json->source.get(&json->source); if (is_digit(c)) { return read_number(json, c); } else { json_error(json, "unexpected byte '%c' in number", c); return JSON_ERROR; } } else if (strchr("123456789", c) != NULL) { c = json->source.peek(&json->source); if (is_digit(c)) { if (read_digits(json) != 0) return JSON_ERROR; } } /* Up to decimal or exponent has been read. */ c = json->source.peek(&json->source); if (strchr(".eE", c) == NULL) { if (pushchar(json, '\0') != 0) return JSON_ERROR; else return JSON_NUMBER; } if (c == '.') { json->source.get(&json->source); // consume . if (pushchar(json, c) != 0) return JSON_ERROR; if (read_digits(json) != 0) return JSON_ERROR; } /* Check for exponent. */ c = json->source.peek(&json->source); if (c == 'e' || c == 'E') { json->source.get(&json->source); // consume e/E if (pushchar(json, c) != 0) return JSON_ERROR; c = json->source.peek(&json->source); if (c == '+' || c == '-') { json->source.get(&json->source); // consume if (pushchar(json, c) != 0) return JSON_ERROR; if (read_digits(json) != 0) return JSON_ERROR; } else if (is_digit(c)) { if (read_digits(json) != 0) return JSON_ERROR; } else { json_error(json, "unexpected byte '%c' in number", c); return JSON_ERROR; } } if (pushchar(json, '\0') != 0) return JSON_ERROR; else return JSON_NUMBER; } bool json_isspace(int c) { switch (c) { case 0x09: case 0x0a: case 0x0d: case 0x20: return true; } return false; } /* Returns the next non-whitespace character in the stream. */ static int next(json_stream *json) { int c; while (json_isspace(c = json->source.get(&json->source))) if (c == '\n') json->lineno++; return c; } static enum json_type read_value(json_stream *json, int c) { json->ntokens++; switch (c) { case EOF: json_error(json, "%s", "unexpected end of text"); return JSON_ERROR; case '{': return push(json, JSON_OBJECT); case '[': return push(json, JSON_ARRAY); case '"': return read_string(json); case 'n': return is_match(json, "ull", JSON_NULL); case 'f': return is_match(json, "alse", JSON_FALSE); case 't': return is_match(json, "rue", JSON_TRUE); case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '-': if (init_string(json) != 0) return JSON_ERROR; return read_number(json, c); default: json_error(json, "unexpected byte '%c' in value", c); return JSON_ERROR; } } enum json_type json_peek(json_stream *json) { enum json_type next; if (json->next) next = json->next; else next = json->next = json_next(json); return next; } enum json_type json_next(json_stream *json) { if (json->flags & JSON_FLAG_ERROR) return JSON_ERROR; if (json->next != 0) { enum json_type next = json->next; json->next = (enum json_type)0; return next; } if (json->ntokens > 0 && json->stack_top == (size_t)-1) { /* In the streaming mode leave any trailing whitespaces in the stream. * This allows the user to validate any desired separation between * values (such as newlines) using json_source_get/peek() with any * remaining whitespaces ignored as leading when we parse the next * value. */ if (!(json->flags & JSON_FLAG_STREAMING)) { int c; do { c = json->source.peek(&json->source); if (json_isspace(c)) { c = json->source.get(&json->source); } } while (json_isspace(c)); if (c != EOF) { json_error(json, "expected end of text instead of byte '%c'", c); return JSON_ERROR; } } return JSON_DONE; } int c = next(json); if (json->stack_top == (size_t)-1) { if (c == EOF && (json->flags & JSON_FLAG_STREAMING)) return JSON_DONE; return read_value(json, c); } if (json->stack[json->stack_top].type == JSON_ARRAY) { if (json->stack[json->stack_top].count == 0) { if (c == ']') { return pop(json, c, JSON_ARRAY); } json->stack[json->stack_top].count++; return read_value(json, c); } else if (c == ',') { json->stack[json->stack_top].count++; return read_value(json, next(json)); } else if (c == ']') { return pop(json, c, JSON_ARRAY); } else { json_error(json, "unexpected byte '%c'", c); return JSON_ERROR; } } else if (json->stack[json->stack_top].type == JSON_OBJECT) { if (json->stack[json->stack_top].count == 0) { if (c == '}') { return pop(json, c, JSON_OBJECT); } /* No member name/value pairs yet. */ enum json_type value = read_value(json, c); if (value != JSON_STRING) { if (value != JSON_ERROR) json_error(json, "%s", "expected member name or '}'"); return JSON_ERROR; } else { json->stack[json->stack_top].count++; return value; } } else if ((json->stack[json->stack_top].count % 2) == 0) { /* Expecting comma followed by member name. */ if (c != ',' && c != '}') { json_error(json, "%s", "expected ',' or '}' after member value"); return JSON_ERROR; } else if (c == '}') { return pop(json, c, JSON_OBJECT); } else { enum json_type value = read_value(json, next(json)); if (value != JSON_STRING) { if (value != JSON_ERROR) json_error(json, "%s", "expected member name"); return JSON_ERROR; } else { json->stack[json->stack_top].count++; return value; } } } else if ((json->stack[json->stack_top].count % 2) == 1) { /* Expecting colon followed by value. */ if (c != ':') { json_error(json, "%s", "expected ':' after member name"); return JSON_ERROR; } else { json->stack[json->stack_top].count++; return read_value(json, next(json)); } } } json_error(json, "%s", "invalid parser state"); return JSON_ERROR; } void json_reset(json_stream *json) { json->stack_top = -1; json->ntokens = 0; json->flags &= ~JSON_FLAG_ERROR; json->errmsg[0] = '\0'; } enum json_type json_skip(json_stream *json) { enum json_type type = json_next(json); size_t cnt_arr = 0; size_t cnt_obj = 0; for (enum json_type skip = type; ; skip = json_next(json)) { if (skip == JSON_ERROR || skip == JSON_DONE) return skip; if (skip == JSON_ARRAY) { ++cnt_arr; } else if (skip == JSON_ARRAY_END && cnt_arr > 0) { --cnt_arr; } else if (skip == JSON_OBJECT) { ++cnt_obj; } else if (skip == JSON_OBJECT_END && cnt_obj > 0) { --cnt_obj; } if (!cnt_arr && !cnt_obj) break; } return type; } enum json_type json_skip_until(json_stream *json, enum json_type type) { while (1) { enum json_type skip = json_skip(json); if (skip == JSON_ERROR || skip == JSON_DONE) return skip; if (skip == type) break; } return type; } const char *json_get_string(json_stream *json, size_t *length) { if (length != NULL) *length = json->data.string_fill; if (json->data.string == NULL) return ""; else return json->data.string; } double json_get_number(json_stream *json) { char *p = json->data.string; return p == NULL ? 0 : strtod(p, NULL); } const char *json_get_error(json_stream *json) { return json->flags & JSON_FLAG_ERROR ? json->errmsg : NULL; } size_t json_get_lineno(json_stream *json) { return json->lineno; } size_t json_get_position(json_stream *json) { return json->source.position; } size_t json_get_depth(json_stream *json) { return json->stack_top + 1; } /* Return the current parsing context, that is, JSON_OBJECT if we are inside an object, JSON_ARRAY if we are inside an array, and JSON_DONE if we are not yet/anymore in either. Additionally, for the first two cases, also return the number of parsing events that have already been observed at this level with json_next/peek(). In particular, inside an object, an odd number would indicate that the just observed JSON_STRING event is a member name. */ enum json_type json_get_context(json_stream *json, size_t *count) { if (json->stack_top == (size_t)-1) return JSON_DONE; if (count != NULL) *count = json->stack[json->stack_top].count; return json->stack[json->stack_top].type; } int json_source_get(json_stream *json) { int c = json->source.get(&json->source); if (c == '\n') json->lineno++; return c; } int json_source_peek(json_stream *json) { return json->source.peek(&json->source); } void json_open_buffer(json_stream *json, const void *buffer, size_t size) { init(json); json->source.get = buffer_get; json->source.peek = buffer_peek; json->source.source.buffer.buffer = (const char *)buffer; json->source.source.buffer.length = size; } void json_open_string(json_stream *json, const char *string) { json_open_buffer(json, string, strlen(string)); } void json_open_stream(json_stream *json, FILE * stream) { init(json); json->source.get = stream_get; json->source.peek = stream_peek; json->source.source.stream.stream = stream; } static int user_get(struct json_source *json) { return json->source.user.get(json->source.user.ptr); } static int user_peek(struct json_source *json) { return json->source.user.peek(json->source.user.ptr); } void json_open_user(json_stream *json, json_user_io get, json_user_io peek, void *user) { init(json); json->source.get = user_get; json->source.peek = user_peek; json->source.source.user.ptr = user; json->source.source.user.get = get; json->source.source.user.peek = peek; } void json_set_allocator(json_stream *json, json_allocator *a) { json->alloc = *a; } void json_set_streaming(json_stream *json, bool streaming) { if (streaming) json->flags |= JSON_FLAG_STREAMING; else json->flags &= ~JSON_FLAG_STREAMING; } void json_close(json_stream *json) { json->alloc.free(json->stack); json->alloc.free(json->data.string); } gcli-2.3.0/thirdparty/pdjson/pdjson.h000066400000000000000000000062541460062271200175760ustar00rootroot00000000000000#ifndef PDJSON_H #define PDJSON_H #ifndef PDJSON_SYMEXPORT # define PDJSON_SYMEXPORT #endif #ifdef __cplusplus extern "C" { #else #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) #include #else #ifndef bool #define bool int #define true 1 #define false 0 #endif /* bool */ #endif /* __STDC_VERSION__ */ #endif /* __cplusplus */ #include enum json_type { JSON_ERROR = 1, JSON_DONE, JSON_OBJECT, JSON_OBJECT_END, JSON_ARRAY, JSON_ARRAY_END, JSON_STRING, JSON_NUMBER, JSON_TRUE, JSON_FALSE, JSON_NULL }; struct json_allocator { void *(*malloc)(size_t); void *(*realloc)(void *, size_t); void (*free)(void *); }; typedef int (*json_user_io)(void *user); typedef struct json_stream json_stream; typedef struct json_allocator json_allocator; PDJSON_SYMEXPORT void json_open_buffer(json_stream *json, const void *buffer, size_t size); PDJSON_SYMEXPORT void json_open_string(json_stream *json, const char *string); PDJSON_SYMEXPORT void json_open_stream(json_stream *json, FILE *stream); PDJSON_SYMEXPORT void json_open_user(json_stream *json, json_user_io get, json_user_io peek, void *user); PDJSON_SYMEXPORT void json_close(json_stream *json); PDJSON_SYMEXPORT void json_set_allocator(json_stream *json, json_allocator *a); PDJSON_SYMEXPORT void json_set_streaming(json_stream *json, bool mode); PDJSON_SYMEXPORT enum json_type json_next(json_stream *json); PDJSON_SYMEXPORT enum json_type json_peek(json_stream *json); PDJSON_SYMEXPORT void json_reset(json_stream *json); PDJSON_SYMEXPORT const char *json_get_string(json_stream *json, size_t *length); PDJSON_SYMEXPORT double json_get_number(json_stream *json); PDJSON_SYMEXPORT enum json_type json_skip(json_stream *json); PDJSON_SYMEXPORT enum json_type json_skip_until(json_stream *json, enum json_type type); PDJSON_SYMEXPORT size_t json_get_lineno(json_stream *json); PDJSON_SYMEXPORT size_t json_get_position(json_stream *json); PDJSON_SYMEXPORT size_t json_get_depth(json_stream *json); PDJSON_SYMEXPORT enum json_type json_get_context(json_stream *json, size_t *count); PDJSON_SYMEXPORT const char *json_get_error(json_stream *json); PDJSON_SYMEXPORT int json_source_get(json_stream *json); PDJSON_SYMEXPORT int json_source_peek(json_stream *json); PDJSON_SYMEXPORT bool json_isspace(int c); /* internal */ struct json_source { int (*get)(struct json_source *); int (*peek)(struct json_source *); size_t position; union { struct { FILE *stream; } stream; struct { const char *buffer; size_t length; } buffer; struct { void *ptr; json_user_io get; json_user_io peek; } user; } source; }; struct json_stream { size_t lineno; struct json_stack *stack; size_t stack_top; size_t stack_size; enum json_type next; unsigned flags; struct { char *string; size_t string_fill; size_t string_size; } data; size_t ntokens; struct json_source source; struct json_allocator alloc; char errmsg[128]; }; #ifdef __cplusplus } /* extern "C" */ #endif /* __cplusplus */ #endif gcli-2.3.0/thirdparty/sn/000077500000000000000000000000001460062271200152445ustar00rootroot00000000000000gcli-2.3.0/thirdparty/sn/sn.c000066400000000000000000000206671460062271200160430ustar00rootroot00000000000000/* * Copyright 2021, 2022 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. */ /* * LibSN - things I reuse all the time. */ #include #include #include #include #include #include #include #include #include #include #include static int verbosity = VERBOSITY_NORMAL; void sn_setverbosity(int level) { verbosity = level; } int sn_getverbosity(void) { return verbosity; } void errx(int code, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fputc('\n', stderr); exit(code); } void err(int code, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, ": %s\n", strerror(errno)); exit(code); } void warnx(const char *fmt, ...) { if (!sn_verbose()) return; fputs("warning: ", stderr); va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fputc('\n', stderr); } void warn(const char *fmt, ...) { if (!sn_verbose()) return; fputs("warning: ", stderr); va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, ": %s\n", strerror(errno)); } char * sn_strndup(const char *it, size_t len) { char *result = NULL; char const *tmp = NULL; size_t actual = 0; if (!len) return NULL; tmp = it; while (tmp[actual++] && actual < len); 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; va_start(vp, fmt); actual = vsnprintf(&tmp, 1, fmt, vp); va_end(vp); result = calloc(1, actual + 1); if (!result) err(1, "calloc"); va_start(vp, fmt); vsnprintf(result, actual + 1, fmt, vp); va_end(vp); return result; } static int word_length(const char *x) { int l = 0; while (*x && !isspace(*x++)) l++; return l; } void pretty_print(const char *input, int indent, int maxlinelen, FILE *out) { const char *it = input; if (!it) return; while (*it) { int linelength = indent; fprintf(out, "%*.*s", indent, indent, ""); 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; } fprintf(out, "%.*s", w, it); it += w; linelength += w; } while (*it && (linelength < maxlinelen)); fputc('\n', out); } } int sn_mmap_file(const char *path, void **buffer) { 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; if (access(path, R_OK) < 0) err(1, "access"); 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; 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"); return stat_buf.st_size; } int sn_read_file(char const *path, char **buffer) { FILE *f; size_t len; int rc = 0; /* open file and determine length */ f = fopen(path, "r"); if (!f) return -1; if (fseek(f, 0, SEEK_END) < 0) goto err_seek; len = ftell(f); rewind(f); *buffer = malloc(len + 1); if (fread(*buffer, 1, len, f) != len) { rc = -1; goto err_read; } (*buffer)[len] = '\0'; rc = (int)(len); err_read: err_seek: fclose(f); return rc; } sn_sv sn_sv_trim_front(sn_sv it) { if (it.length == 0) return it; // TODO: not utf-8 aware while (it.length > 0) { if (!isspace(*it.data)) break; it.data++; it.length--; } return it; } static sn_sv sn_sv_trim_end(sn_sv it) { while (it.length > 0 && isspace(it.data[it.length - 1])) it.length--; return it; } sn_sv sn_sv_trim(sn_sv 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; result.length = 0; while (it->length > 0) { if (*it->data == c) break; it->data++; it->length--; result.length++; } return result; } bool sn_sv_has_prefix(sn_sv it, const char *prefix) { size_t len = strlen(prefix); if (it.length < len) return false; return strncmp(it.data, prefix, len) == 0; } bool sn_sv_eq(sn_sv this, sn_sv that) { if (this.length != that.length) return false; 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; 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); if (su_len > it_len) return it; int off = it_len - su_len; if (strncmp(it + off, suffix, su_len) == 0) it[off] = '\0'; return it; } sn_sv sn_sv_fmt(const char *fmt, ...) { char tmp = 0; va_list vp; sn_sv result = {0}; va_start(vp, fmt); 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); return result; } char * sn_sv_to_cstr(sn_sv it) { 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; va_start(vp, fmt); 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); do { printf(SV_FMT" [yN] ", SV_ARGS(message)); char c = getchar(); if (c == 'y' || c == 'Y') { result = true; break; } else if (c == '\n' || c == 'n' || c == 'N') { break; } getchar(); // consume newline character } while (!feof(stdin)); free(message.data); return result; } sn_sv sn_sv_strip_suffix(sn_sv input, const char *suffix) { sn_sv expected_suffix = SV((char *)suffix); 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); if (sn_sv_eq(expected_suffix, actual_suffix)) input.length -= expected_suffix.length; 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; 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; } buffer = calloc(1, buffer_size); if (!buffer) return NULL; 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); bufoff += len + sep_size; } return buffer; } gcli-2.3.0/thirdparty/sn/sn.h000066400000000000000000000111711460062271200160360ustar00rootroot00000000000000/* * Copyright 2021, 2022 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. */ /* * LibSN - things I reuse all the time. */ #ifndef SN_H #define SN_H #include #include #include #if defined(__GNUC__) || defined(__clang__) // https://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Function-Attributes.html #define PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK) __attribute__ ((format (printf, STRING_INDEX, FIRST_TO_CHECK))) #else #define PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK) #endif enum { VERBOSITY_NORMAL = 0, VERBOSITY_QUIET = 1, VERBOSITY_VERBOSE = 2, }; /* mostly concerning warn(x) */ void sn_setverbosity(int verbosity); int sn_getverbosity(void); static inline int sn_verbose(void) { return sn_getverbosity() == VERBOSITY_VERBOSE; } static inline int sn_normal(void) { return sn_getverbosity() == VERBOSITY_NORMAL; } static inline int sn_quiet(void) { return sn_getverbosity() == VERBOSITY_QUIET; } /* error functions */ /* print a formatted error message and exit with code */ void errx(int code, const char *fmt, ...) PRINTF_FORMAT(2, 3); /* print a formatted error message, the error retrieved from errno and exit with code */ void err(int code, const char *fmt, ...) PRINTF_FORMAT(2, 3); void warnx(const char *fmt, ...) PRINTF_FORMAT(1, 2); void warn(const char *fmt, ...) PRINTF_FORMAT(1, 2); /* for convenience */ #define sn_unimplemented errx(42, "%s: unimplemented", __func__) #define sn_notreached errx(42, "%s: unreachable", __func__) /* Weird minimum function */ static inline int sn_min(int x, int y) { if (x < 0) return y; else if (y < 0) return x; else if (x < y) return x; else return y; } /* string functions */ char *sn_strndup (const char *it, size_t len); char *sn_asprintf(const char *fmt, ...) PRINTF_FORMAT(1, 2); // modifies the underlying string char *sn_strip_suffix(char *it, const char *suffix); /* pretty functions */ 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); /* stringview */ typedef struct sn_sv sn_sv; struct sn_sv { char *data; size_t length; }; #define SV(x) (sn_sv) { .data = x, .length = strlen(x) } #define SV_FMT "%.*s" #define SV_ARGS(x) (int)x.length, x.data #define SV_NULL (sn_sv) {0} static inline sn_sv sn_sv_from_parts(char *buf, size_t 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); static inline bool sn_sv_null(sn_sv it) { 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 */ bool sn_yesno(const char *fmt, ...) PRINTF_FORMAT(1, 2); static inline const char * sn_bool_yesno(bool x) { return x ? "yes" : "no"; } char *sn_join_with(char const *const items[], size_t const items_size, char const *sep); #ifndef ARRAY_SIZE # define ARRAY_SIZE(xs) (sizeof(xs) / sizeof(xs[0])) #endif /* ARRAY_SIZE */ #endif /* SN_H */ gcli-2.3.0/todo.org000066400000000000000000000146171460062271200141210ustar00rootroot00000000000000#+TITLE: gcli todos ** TODO Convert Github stuff to generated parsers [90%] - [X] Checks - [X] Comments - [X] Forks - [X] Issues - [X] Labels - [X] Pulls - [X] Retrieve Pulls Info - [X] Get PR merge message - [X] Releases - [X] Repos - [ ] Reviews - [ ] This is GraphQL!...needs to be solved later - [X] Status ** DONE Convert Gitlab stuff to generated parers [100%] - [X] Pipelines - [X] Merge Requests - [X] Comments - [X] Issues - [X] Review - [X] Labels - [X] Releases - [X] Forks - [X] Repos - [X] Status ** DONE handle errors from the github api - [[file:src/curl.c::ghcli_fetch(const char *url, ghcli_fetch_buffer *out)][gcli_fetch]] - [[file:src/curl.c::ghcli_curl(FILE *stream, const char *url, const char *content_type)][gcli_curl]] ** TODO find a better way to pass the content type to gcli_curl ** DONE Man page ** DONE fetch PR comments ** DONE leaks of huge buffers [50%] We don't care about the little strings we sometimes malloc. But the huge buffers are annoying. - [X] Valgrind [100%] - [X] issues - [X] issues create - [X] pulls - [X] pulls create - [X] comment - [X] reviews - [ ] custom LD_PRELOAD hack ** DONE issues comments doesn't print the first comment it seems to reside in the issue data itself - solved by printing issue summaries ** TODO we don't handle pagination of the api results - [ ] See https://docs.github.com/en/rest/guides/getting-started-with-the-rest-api#pagination ** TODO speed up json_escape ** DONE repo-specific config file ** DONE comment under PR/Issue ** TODO make the use of C strings and string views more consistent ** DONE get rid of mktemp cuz binutils ld bitches about it #+begin_example src/editor.o: In function `gcli_editor_get_user_message': editor.c:(.text+0x108): warning: the use of `mktemp' is dangerous, better use `mkstemp' or `mkdtemp' #+end_example ** DONE Check for multiple Github remotes and choose the right one Solved in commit 4be0ca8. Leave it up to the user to point at the right repo. Also, there are the -o and -r flags. ** DONE Valgrind the new fork stuff ** DONE Ask the user if they want to add a git remote if a fork is created ** DONE repos subcommand fails if -o is a user ** DONE Add docs for gists subcommand ** TODO add flags for sorting - [ ] gists - [ ] releases ** DONE Creating releases [100%] - [X] body - [X] choose a git tag - [X] attach files to release (aka assets) - [X] mark as prerelease or draft ** DONE pulls commit table header is weird ** DONE Check unnecessary includes ** DONE Valgrind again ** DONE write colors test for big-endian machines ** DONE Implement adding/removing labels from github prs ** TODO CI [83%] - [X] release resources properly - [X] check that we are connecting to github if we ever use the =ci= subcommand [[file:src/gcli.c::if (gcli_config_get_forge_type() != GCLI_FORGE_GITHUB)][see here]] - [X] (maybe) integrate ci checks in status subcommand - [X] Split =status= and =summary= subcommands: - =summary= should print header and commits - =status= should print summary and checks - [X] overflow bug in id - [ ] dump logs I dunno whether i really want to implement that. the problem is that github is misbehaving and doesn't give me any association from the checks api to the actions api. maybe I wanna add an actions subcommand that handles this very case for github. ** DONE Unify Gitea and Github code Probably we want to make wrappers around the GitHub code for the cases where it works. For this to work, we need to mess with =github_get_apibase()= to return the right thing if we are looking at gitea. ** TODO Optimise =pad()= in [[file:src/table.c][file.c]] ** Label shit #+begin_example $ gcli labels bug - something is broken ... $ gcli labels create --description 'something is broken' --color FF0000 bug $ gcli labels delete bug #+end_example - for colors see [[https://github.com/git/git/blob/master/color.h][git implementation]] * On the review API - A PR has got reviews (could be none, could be a thousand) + https://api.github.com/repos/zorchenhimer/MovieNight/pulls/156/reviews - A review may have a body and comments attached to it + https://api.github.com/repos/zorchenhimer/MovieNight/pulls/156/reviews/611653998 - A review comment has got a diff hunk and a body attached to it. + https://api.github.com/repos/zorchenhimer/MovieNight/pulls/156/reviews/611653998/comments * Big refactor for libraryfication ** DONE Fix test suite - [X] Move config stuff to cmd and have callbacks that given the context give you the account details etc. This would allow you to create a mock context that returns only values that make sense in test contexts. - [X] Add support for a testing gcli context ** DONE Check errx calls if they print "error: " ** DONE Check for calls to errx in submit routines ** TODO [[file:src/github/review.c::github_review_get_reviews(char const *owner)][github_review_get_reviews]] is garbage ** TODO Clean up the generic comments code ** DONE Move printing routines to cmd code ** DONE Build a shared library ** TODO Remove global curl handle - [X] Put it into the context - [ ] Clean up handle on exit ** DONE Return errors from parsers ** DONE [[file:src/releases.c::gcli_release_push_asset(gcli_new_release *const release,][push_asset]] should not call errx but return an error code ** DONE Make [[file:src/pulls.c::gcli_pull_get_diff(gcli_ctx *ctx, FILE *stream, char const *owner,][getting a pull diff]] return an error code ** TODO Add a real changelog file ** DONE include [[file:docs/pgen.org][docs/pgen.org]] in the distribution tarball ** DONE remove gcli_print_html_url ** TODO Test suite *** TODO Github [53%] - [ ] Checks - [X] Comments - [X] Forks - [ ] Gists - [X] Milestones - [X] Releases - [X] Repos - [ ] Reviews - [ ] SSH keys - [ ] Status - [X] Issues - [X] Labels - [X] Pulls *** TODO Gitlab [61%] - [ ] Comments - [X] Forks - [X] Milestones - [X] Pipelines - [X] Releases - [X] Repos - [ ] Reviews - [ ] SSH Keys - [X] Snippets - [ ] Status - [X] Issues - [X] Labels - [X] Merge Requests *** TODO Gitea ** TODO add an ATF macro for comparing string views ** TODO Think about making IDs =unsigned long= ** TODO get rid of html_url everywhere ** TODO Check the =Kyuafile.in= for requirements on test input gcli-2.3.0/tools/000077500000000000000000000000001460062271200135725ustar00rootroot00000000000000gcli-2.3.0/tools/gentarball.sh000077500000000000000000000013471460062271200162510ustar00rootroot00000000000000#!/bin/sh findversion() { grep 'GCLI_VERSION' build.sh \ | head -1 \ | awk -F\= '{print $2}' \ | sed -E 's/[[:space:]]*\"([^[:space:]]*)\"[[:space:]]*$/\1/g' } VERSION=$(findversion) DIR=/tmp/gcli-${VERSION} mkdir -p $DIR echo "Making BZIP tarball" git archive --format=tar --prefix=gcli-$VERSION/ v$VERSION \ | bzip2 -v > $DIR/gcli-$VERSION.tar.bz2 echo "Making XZ tarball" git archive --format=tar --prefix=gcli-$VERSION/ v$VERSION \ | xz -v > $DIR/gcli-$VERSION.tar.xz echo "Making GZIP tarball" git archive --format=tar --prefix=gcli-$VERSION/ v$VERSION \ | gzip -v > $DIR/gcli-$VERSION.tar.gz OLDDIR=$(pwd) cd $DIR echo "Calculating SHA256SUMS" sha256sum *.tar* > SHA256SUMS cd $OLDDIR echo "Release Tarballs are at $DIR"