diff --git a/Makefile.am b/Makefile.am index 03ced5bf..dcea2267 100644 --- a/Makefile.am +++ b/Makefile.am @@ -39,6 +39,7 @@ gcli_LDADD = libgcli.la libpdjson.la libsn.la 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 \ @@ -58,6 +59,7 @@ gcli_SOURCES = \ src/cmd/api.c \ src/cmd/ci.c \ src/cmd/comment.c \ + src/cmd/config.c \ src/cmd/forks.c \ src/cmd/gists.c \ src/cmd/issues.c \ @@ -102,6 +104,7 @@ TEMPLATES = \ templates/gitlab/releases.t \ templates/gitlab/repos.t \ templates/gitlab/review.t \ + templates/gitlab/sshkeys.t \ templates/gitlab/status.t \ templates/gitlab/snippets.t \ templates/gitea/milestones.t @@ -125,6 +128,7 @@ libgcli_la_SOURCES = \ src/review.c include/gcli/review.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/table.c include/gcli/table.h \ src/gitlab/api.c include/gcli/gitlab/api.h \ src/gitlab/comments.c include/gcli/gitlab/comments.h \ @@ -139,6 +143,7 @@ libgcli_la_SOURCES = \ src/gitlab/repos.c include/gcli/gitlab/repos.h \ src/gitlab/review.c include/gcli/gitlab/review.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 \ @@ -153,6 +158,7 @@ libgcli_la_SOURCES = \ src/github/review.c include/gcli/github/review.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 \ @@ -161,6 +167,7 @@ libgcli_la_SOURCES = \ 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/milestones.c include/gcli/gitea/milestones.h \ $(TEMPLATES) diff --git a/configure.ac b/configure.ac index 90fefa05..02062496 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.69]) -AC_INIT([gcli],[1.1.0],[nsonack@herrhotzenplotz.de],[gcli],[https://gitlab.com/herrhotzenplotz/gcli/]) +AC_INIT([gcli],[1.2.0],[nsonack@herrhotzenplotz.de],[gcli],[https://gitlab.com/herrhotzenplotz/gcli/]) AM_INIT_AUTOMAKE([1.0 foreign subdir-objects dist-bzip2 dist-xz -Wall]) dnl Release Date. @@ -38,7 +38,7 @@ 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"], +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"], @@ -64,6 +64,7 @@ dnl Generate and substitute various files AC_CONFIG_FILES([Makefile docs/gcli-api.1 docs/gcli-comment.1 + docs/gcli-config.1 docs/gcli-forks.1 docs/gcli-gists.1 docs/gcli-issues.1 diff --git a/docs/gcli-config.1.in b/docs/gcli-config.1.in new file mode 100644 index 00000000..2a61264f --- /dev/null +++ b/docs/gcli-config.1.in @@ -0,0 +1,67 @@ +.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 a SSH public key for the current user. +.It Cm ssh delete +Delete a 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 at @PACKAGE_URL@, via E-Mail to @PACKAGE_BUGREPORT@ +or on Github. diff --git a/docs/gcli-issues.1.in b/docs/gcli-issues.1.in index de5bed49..6adfb230 100644 --- a/docs/gcli-issues.1.in +++ b/docs/gcli-issues.1.in @@ -107,6 +107,12 @@ Remove the given label from the issue. .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. .El .Sh EXAMPLES Print a list of issues in the current project: diff --git a/docs/gcli-pulls.1.in b/docs/gcli-pulls.1.in index 4dc13de6..855683de 100644 --- a/docs/gcli-pulls.1.in +++ b/docs/gcli-pulls.1.in @@ -141,6 +141,15 @@ 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 reviews Print reviews including comments under them. .It Cm labels Op Ar options diff --git a/docs/gcli.1.in b/docs/gcli.1.in index eb69388e..54c10e80 100644 --- a/docs/gcli.1.in +++ b/docs/gcli.1.in @@ -70,6 +70,10 @@ Create and manage releases. See .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 @@ -280,6 +284,7 @@ working from contain the relevant forge and repository information. .Xr gcli-releases 1 , .Xr gcli-comment 1 .Xr gcli-pipelines 1 +.Xr gcli-config 1 .Sh HISTORY The idea for .Nm diff --git a/include/gcli/cmd.h b/include/gcli/cmd.h index 54ed75e6..bd8d9ee2 100644 --- a/include/gcli/cmd.h +++ b/include/gcli/cmd.h @@ -61,6 +61,7 @@ void delete_repo(bool always_yes, const char *owner, const char *repo); int subcommand_api(int argc, char *argv[]); int subcommand_ci(int argc, char *argv[]); int subcommand_comment(int argc, char *argv[]); +int subcommand_config(int argc, char *argv[]); int subcommand_forks(int argc, char *argv[]); int subcommand_gists(int argc, char *argv[]); int subcommand_issues(int argc, char *argv[]); diff --git a/include/gcli/forges.h b/include/gcli/forges.h index 445fdfa0..ad295da3 100644 --- a/include/gcli/forges.h +++ b/include/gcli/forges.h @@ -44,6 +44,7 @@ #include #include #include +#include #include typedef struct gcli_forge_descriptor gcli_forge_descriptor; @@ -319,6 +320,21 @@ struct gcli_forge_descriptor { char const *const labels[], size_t labels_size); + /** + * Assign a PR to a milestone */ + int (*pr_set_milestone)( + char const *owner, + char const *repo, + int pr, + int milestone_id); + + /** + * Clear a milestone on a PR */ + int (*pr_clear_milestone)( + char const *owner, + char const *repo, + int pr); + /** * Get a list of releases in the given repo */ int (*get_releases)( @@ -416,6 +432,20 @@ struct gcli_forge_descriptor { * Get the user account name */ sn_sv (*get_account)(void); + /** + * Get list of SSH keys */ + int (*get_sshkeys)(gcli_sshkey_list *); + + /** + * Add an SSH public key */ + int (*add_sshkey)(char const *title, + char const *public_key_path, + gcli_sshkey *out); + + /** + * Delete an SSH public key by its ID */ + int (*delete_sshkey)(int id); + /** * Get the error string from the API */ char const *(*get_api_error_string)( diff --git a/include/gcli/gitea/issues.h b/include/gcli/gitea/issues.h index 0cb9f097..3f4b5b6c 100644 --- a/include/gcli/gitea/issues.h +++ b/include/gcli/gitea/issues.h @@ -80,4 +80,8 @@ int gitea_issue_set_milestone(char const *owner, int issue, int milestone); +int gitea_issue_clear_milestone(char const *owner, + char const *repo, + int issue); + #endif /* GITEA_ISSUES_H */ diff --git a/include/gcli/gitea/pulls.h b/include/gcli/gitea/pulls.h index 11162c04..05d2e839 100644 --- a/include/gcli/gitea/pulls.h +++ b/include/gcli/gitea/pulls.h @@ -77,4 +77,13 @@ int gitea_pull_checks(char const *owner, char const *repo, int pr_number); +int gitea_pull_set_milestone(char const *owner, + char const *repo, + int pr_number, + int milestone_id); + +int gitea_pull_clear_milestone(char const *owner, + char const *repo, + int pr_number); + #endif /* GITEA_PULLS_H */ diff --git a/include/gcli/gitea/sshkeys.h b/include/gcli/gitea/sshkeys.h new file mode 100644 index 00000000..5f047573 --- /dev/null +++ b/include/gcli/gitea/sshkeys.h @@ -0,0 +1,41 @@ +/* + * Copyright 2023 Nico Sonack + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GCLI_GITEA_SSHKEYS_H +#define GCLI_GITEA_SSHKEYS_H + +#include + +int gitea_get_sshkeys(gcli_sshkey_list *out); +int gitea_add_sshkey(char const *title, + char const *public_key_data, + gcli_sshkey *out); +int gitea_delete_sshkey(int id); + +#endif /* GCLI_GITEA_SSHKEYS_H */ diff --git a/include/gcli/github/sshkeys.h b/include/gcli/github/sshkeys.h new file mode 100644 index 00000000..a68144eb --- /dev/null +++ b/include/gcli/github/sshkeys.h @@ -0,0 +1,41 @@ +/* + * Copyright 2023 Nico Sonack + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GCLI_GITHUB_SSHKEYS_H +#define GCLI_GITHUB_SSHKEYS_H + +#include + +int github_get_sshkeys(gcli_sshkey_list *out); +int github_add_sshkey(char const *const title, + char const *const pubkey, + gcli_sshkey *out); +int github_delete_sshkey(int id); + +#endif /* GCLI_GITHUB_SSHKEYS_H */ diff --git a/include/gcli/gitlab/merge_requests.h b/include/gcli/gitlab/merge_requests.h index 663e77cd..c110296b 100644 --- a/include/gcli/gitlab/merge_requests.h +++ b/include/gcli/gitlab/merge_requests.h @@ -88,4 +88,13 @@ void gitlab_mr_remove_labels(char const *owner, char const *const labels[], size_t labels_size); +int gitlab_mr_set_milestone(char const *owner, + char const *repo, + int mr, + int milestone_id); + +int gitlab_mr_clear_milestone(char const *owner, + char const *repo, + int mr); + #endif /* GITLAB_MERGE_REQUESTS_H */ diff --git a/include/gcli/gitlab/sshkeys.h b/include/gcli/gitlab/sshkeys.h new file mode 100644 index 00000000..a0e78521 --- /dev/null +++ b/include/gcli/gitlab/sshkeys.h @@ -0,0 +1,45 @@ +/* + * 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(gcli_sshkey_list *list); +int gitlab_add_sshkey(char const *const title, + char const *const pubkey, + gcli_sshkey *const out); +int gitlab_delete_sshkey(int id); + +#endif /* GCLI_GITLAB_SSHKEYS_H */ diff --git a/include/gcli/pulls.h b/include/gcli/pulls.h index 0664c348..e40714fa 100644 --- a/include/gcli/pulls.h +++ b/include/gcli/pulls.h @@ -61,6 +61,7 @@ struct gcli_pull { char *head_label; char *base_label; char *head_sha; + char *milestone; int id; int number; int comments; @@ -167,4 +168,13 @@ void gcli_pull_remove_labels(char const *owner, char const *const labels[], size_t labels_size); +int gcli_pull_set_milestone(char const *owner, + char const *repo, + int pr_number, + int milestone_id); + +int gcli_pull_clear_milestone(char const *owner, + char const *repo, + int pr_number); + #endif /* PULLS_H */ diff --git a/include/gcli/sshkeys.h b/include/gcli/sshkeys.h new file mode 100644 index 00000000..d594b26a --- /dev/null +++ b/include/gcli/sshkeys.h @@ -0,0 +1,62 @@ +/* + * Copyright 2023 Nico Sonack + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GCLI_SSHKEYS_H +#define GCLI_SSHKEYS_H + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +struct gcli_sshkey { + int id; + char *title; + char *key; + char *created_at; +}; + +struct gcli_sshkey_list { + struct gcli_sshkey *keys; + size_t keys_size; +}; + +typedef struct gcli_sshkey gcli_sshkey; +typedef struct gcli_sshkey_list gcli_sshkey_list; + +int gcli_sshkeys_get_keys(gcli_sshkey_list *out); +int gcli_sshkeys_add_key(char const *title, + char const *public_key_path, + gcli_sshkey *out); +int gcli_sshkeys_delete_key(int id); +void gcli_sshkeys_print_keys(gcli_sshkey_list const *list); +void gcli_sshkeys_free_keys(gcli_sshkey_list *list); + +#endif /* GCLI_SSHKEYS_H */ diff --git a/src/cmd/config.c b/src/cmd/config.c new file mode 100644 index 00000000..71d7f464 --- /dev/null +++ b/src/cmd/config.c @@ -0,0 +1,219 @@ +/* + * 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 + +#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(); +} + +static int +list_sshkeys(void) +{ + gcli_sshkey_list list = {0}; + + if (gcli_sshkeys_get_keys(&list) < 0) { + fprintf(stderr, "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, "error: cannot access %s: %s\n", + keypath, strerror(errno)); + return EXIT_FAILURE; + } + } break; + default: { + usage(); + return EXIT_FAILURE; + } break; + } + } + + if (title == NULL) { + fprintf(stderr, "error: missing title\n"); + usage(); + return EXIT_FAILURE; + } + + if (keypath == NULL) { + fprintf(stderr, "error: missing public key path\n"); + usage(); + return EXIT_FAILURE; + } + + if (gcli_sshkeys_add_key(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, "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, "error: could not parse ID of SSH key to delete\n"); + return EXIT_FAILURE; + } + + if (gcli_sshkeys_delete_key(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, "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, "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, "error: unrecognised config subcommand »%s«\n", + argv[0]); + usage(); + + return EXIT_FAILURE; +} diff --git a/src/cmd/issues.c b/src/cmd/issues.c index 8b4bbf10..e345f67e 100644 --- a/src/cmd/issues.c +++ b/src/cmd/issues.c @@ -68,6 +68,7 @@ usage(void) 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, "\n"); version(); copyright(); @@ -378,7 +379,8 @@ handle_issues_actions(int argc, char *argv[], puts("\nORIGINAL POST\n"); gcli_issue_print_op(&issue); - } else if (strcmp("comments", operation) == 0) { + } else if (strcmp("comments", operation) == 0 || + strcmp("notes", operation) == 0) { /* Doesn't require fetching the issue data */ gcli_issue_comments(owner, repo, issue_id); diff --git a/src/cmd/pulls.c b/src/cmd/pulls.c index a5f35f7c..28180832 100644 --- a/src/cmd/pulls.c +++ b/src/cmd/pulls.c @@ -46,7 +46,7 @@ static void usage(void) { fprintf(stderr, "usage: gcli pulls create [-o owner -r repo] [-f from]\n"); - fprintf(stderr, " [-t to] [-d] [-l label]\n"); + fprintf(stderr, " [-t to] [-d] [-l label] pull-request-title\n"); fprintf(stderr, " gcli pulls [-o owner -r repo] [-a] [-A author ][-n number] [-s]\n"); fprintf(stderr, " gcli pulls [-o owner -r repo] -i pull-id actions...\n"); fprintf(stderr, "OPTIONS:\n"); @@ -67,9 +67,12 @@ usage(void) 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"); @@ -415,7 +418,8 @@ handle_pull_actions(int argc, char *argv[], } else if (strcmp(action, "diff") == 0) { gcli_print_pull_diff(stdout, owner, repo, pr); - } else if (strcmp(action, "comments") == 0) { + } else if (strcmp(action, "comments") == 0 || + strcmp(action, "notes") == 0) { gcli_pull_comments(owner, repo, pr); } else if (strcmp(action, "ci") == 0) { @@ -486,6 +490,24 @@ handle_pull_actions(int argc, char *argv[], free(add_labels); free(remove_labels); + } else if (strcmp("milestone", action) == 0) { + char const *arg = shift(&argc, &argv); + + if (strcmp(arg, "-d") == 0) { + gcli_pull_clear_milestone(owner, repo, pr); + + } else { + int milestone_id = 0; + char *endptr; + + milestone_id = strtoul(arg, &endptr, 10); + if (endptr != arg + strlen(arg)) { + fprintf(stderr, "error: cannot parse milestone id »%s«\n", arg); + return EXIT_FAILURE; + } + + gcli_pull_set_milestone(owner, repo, pr, milestone_id); + } } else { /* At this point we found an unknown action / stray * options on the command line. Error out in this case. */ diff --git a/src/forges.c b/src/forges.c index 3353de4a..b1d1d767 100644 --- a/src/forges.c +++ b/src/forges.c @@ -43,6 +43,7 @@ #include #include #include +#include #include #include @@ -58,6 +59,7 @@ #include #include #include +#include #include #include @@ -68,6 +70,7 @@ #include #include #include +#include static gcli_forge_descriptor const github_forge_descriptor = @@ -109,6 +112,12 @@ github_forge_descriptor = .perform_submit_pr = github_perform_submit_pull, .get_pull_commits = github_get_pull_commits, .get_pull = github_get_pull, + + /* This works because the function signatures are the same and + * GitHub treats pull requests as issues */ + .pr_set_milestone = github_issue_set_milestone, + .pr_clear_milestone = github_issue_clear_milestone, + .get_releases = github_get_releases, .create_release = github_create_release, .delete_release = github_delete_release, @@ -120,6 +129,11 @@ github_forge_descriptor = .get_reviews = github_review_get_reviews, .repo_create = github_repo_create, .repo_delete = github_repo_delete, + + .get_sshkeys = github_get_sshkeys, + .add_sshkey = github_add_sshkey, + .delete_sshkey = github_delete_sshkey, + .get_notifications = github_get_notifications, .notification_mark_as_read = github_notification_mark_as_read, .get_authheader = github_get_authheader, @@ -168,6 +182,8 @@ gitlab_forge_descriptor = .get_pull = gitlab_get_pull, .pr_add_labels = gitlab_mr_add_labels, .pr_remove_labels = gitlab_mr_remove_labels, + .pr_set_milestone = gitlab_mr_set_milestone, + .pr_clear_milestone = gitlab_mr_clear_milestone, .get_releases = gitlab_get_releases, .create_release = gitlab_create_release, .delete_release = gitlab_delete_release, @@ -183,6 +199,9 @@ gitlab_forge_descriptor = .notification_mark_as_read = gitlab_notification_mark_as_read, .get_authheader = gitlab_get_authheader, .get_account = gitlab_get_account, + .get_sshkeys = gitlab_get_sshkeys, + .add_sshkey = gitlab_add_sshkey, + .delete_sshkey = gitlab_delete_sshkey, .get_api_error_string = gitlab_api_error_string, .user_object_key = "username", .html_url_key = "web_url", @@ -203,6 +222,7 @@ gitea_forge_descriptor = .issue_reopen = gitea_issue_reopen, .issue_assign = gitea_issue_assign, .issue_set_milestone = gitea_issue_set_milestone, + .issue_clear_milestone = gitea_issue_clear_milestone, .get_issue_comments = gitea_get_comments, .get_milestones = gitea_get_milestones, .get_milestone = gitea_get_milestone, @@ -224,6 +244,8 @@ gitea_forge_descriptor = .get_pull_comments = gitea_get_comments, .get_pull = gitea_get_pull, .get_pull_commits = gitea_get_pull_commits, + .pr_set_milestone = gitea_pull_set_milestone, + .pr_clear_milestone = gitea_pull_clear_milestone, .get_releases = gitea_get_releases, .create_release = gitea_create_release, .delete_release = gitea_delete_release, @@ -239,6 +261,10 @@ gitea_forge_descriptor = .repo_create = gitea_repo_create, .repo_delete = gitea_repo_delete, + .get_sshkeys = gitea_get_sshkeys, + .add_sshkey = gitea_add_sshkey, + .delete_sshkey = gitea_delete_sshkey, + .get_authheader = gitea_get_authheader, .get_account = gitea_get_account, .get_api_error_string = github_api_error_string, /* hack! */ diff --git a/src/gcli.c b/src/gcli.c index f45d52ff..e0d52d49 100644 --- a/src/gcli.c +++ b/src/gcli.c @@ -66,6 +66,9 @@ static struct subcommand { { .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" }, diff --git a/src/gitea/issues.c b/src/gitea/issues.c index 8a650a2a..f988902e 100644 --- a/src/gitea/issues.c +++ b/src/gitea/issues.c @@ -259,3 +259,11 @@ gitea_issue_set_milestone(char const *const owner, { return github_issue_set_milestone(owner, repo, issue, milestone); } + +int +gitea_issue_clear_milestone(char const *owner, + char const *repo, + int issue) +{ + return github_issue_set_milestone(owner, repo, issue, 0); +} diff --git a/src/gitea/pulls.c b/src/gitea/pulls.c index 30394aa8..064a83db 100644 --- a/src/gitea/pulls.c +++ b/src/gitea/pulls.c @@ -30,6 +30,7 @@ #include #include #include +#include int gitea_get_pulls(char const *owner, @@ -182,3 +183,24 @@ gitea_pull_checks(char const *owner, return 0; } + +int +gitea_pull_set_milestone(char const *owner, + char const *repo, + int pr_number, + int milestone_id) +{ + return github_issue_set_milestone(owner, repo, pr_number, milestone_id); +} + +int +gitea_pull_clear_milestone(char const *owner, + char const *repo, + int 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(owner, repo, pr_number, 0); +} diff --git a/src/gitea/sshkeys.c b/src/gitea/sshkeys.c new file mode 100644 index 00000000..ae0c2532 --- /dev/null +++ b/src/gitea/sshkeys.c @@ -0,0 +1,57 @@ +/* + * 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(gcli_sshkey_list *list) +{ + return gitlab_get_sshkeys(list); +} + +int +gitea_add_sshkey(char const *const title, + char const *const pubkey, + gcli_sshkey *const out) +{ + return gitlab_add_sshkey(title, pubkey, out); +} + +int +gitea_delete_sshkey(int id) +{ + return gitlab_delete_sshkey(id); +} diff --git a/src/github/sshkeys.c b/src/github/sshkeys.c new file mode 100644 index 00000000..f2cd61ae --- /dev/null +++ b/src/github/sshkeys.c @@ -0,0 +1,54 @@ +/* + * 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(gcli_sshkey_list *out) +{ + return gitlab_get_sshkeys(out); +} + +int +github_add_sshkey(char const *const title, + char const *const pubkey, + gcli_sshkey *out) +{ + return gitlab_add_sshkey(title, pubkey, out); +} + +int +github_delete_sshkey(int id) +{ + return gitlab_delete_sshkey(id); +} diff --git a/src/gitlab/merge_requests.c b/src/gitlab/merge_requests.c index 8af461f5..3fcbeeaa 100644 --- a/src/gitlab/merge_requests.c +++ b/src/gitlab/merge_requests.c @@ -37,6 +37,16 @@ #include +/* Workaround because gitlab doesn't give us an explicit field for + * this. */ +static void +gitlab_mrs_fixup(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(char *url, int const max, gcli_pull_list *const list) { @@ -56,6 +66,8 @@ gitlab_fetch_mrs(char *url, int const max, gcli_pull_list *const list) free(url); + gitlab_mrs_fixup(list); + return 0; } @@ -418,3 +430,40 @@ gitlab_mr_remove_labels(char const *owner, free(list); free(buffer.data); } + +int +gitlab_mr_set_milestone(char const *owner, + char const *repo, + int mr, + int milestone_id) +{ + char *url = NULL; + char *data = NULL; + + url = sn_asprintf("%s/projects/%s%%2F%s/merge_requests/%d", + gitlab_get_apibase(), owner, repo, mr); + + data = sn_asprintf("{ \"milestone_id\": \"%d\"}", milestone_id); + + gcli_fetch_with_method("PUT", url, data, NULL, NULL); + + free(url); + free(data); + + return 0; +} + +int +gitlab_mr_clear_milestone(char const *owner, + char const *repo, + int 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. */ + gitlab_mr_set_milestone(owner, repo, mr, 0); + + return 0; +} diff --git a/src/gitlab/sshkeys.c b/src/gitlab/sshkeys.c new file mode 100644 index 00000000..fdd966fa --- /dev/null +++ b/src/gitlab/sshkeys.c @@ -0,0 +1,113 @@ +/* + * 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 + +#include + +int +gitlab_get_sshkeys(gcli_sshkey_list *list) +{ + char *url, *next_url = NULL; + + *list = (gcli_sshkey_list) {0}; + url = sn_asprintf("%s/user/keys", gcli_get_apibase()); + + do { + gcli_fetch_buffer buf = {0}; + json_stream str; + + gcli_fetch(url, &next_url, &buf); + json_open_buffer(&str, buf.data, buf.length); + parse_gitlab_sshkeys(&str, &list->keys, &list->keys_size); + + json_close(&str); + free(buf.data); + free(url); + } while ((url = next_url)); + + return 0; +} + +int +gitlab_add_sshkey(char const *const title, + char const *const pubkey, + gcli_sshkey *const out) +{ + char *url, *payload; + char *e_title, *e_key; + gcli_fetch_buffer buf = {0}; + + url = sn_asprintf("%s/user/keys", gcli_get_apibase()); + + /* 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); + + gcli_fetch_with_method("POST", url, payload, NULL, &buf); + if (out) { + json_stream str; + + json_open_buffer(&str, buf.data, buf.length); + parse_gitlab_sshkey(&str, out); + json_close(&str); + } + + free(buf.data); + + return 0; +} + +int +gitlab_delete_sshkey(int id) +{ + char *url; + + url = sn_asprintf("%s/user/keys/%d", gcli_get_apibase(), id); + gcli_fetch_with_method("DELETE", url, NULL, NULL, NULL); + + free(url); + + return 0; +} diff --git a/src/issues.c b/src/issues.c index ddcc79cd..50683ebc 100644 --- a/src/issues.c +++ b/src/issues.c @@ -83,6 +83,7 @@ gcli_print_issues_table(enum gcli_output_flags const flags, gcli_tbl table; gcli_tblcoldef cols[] = { { .name = "NUMBER", .type = GCLI_TBLCOLTYPE_INT, .flags = GCLI_TBLCOL_JUSTIFYR }, + { .name = "NOTES", .type = GCLI_TBLCOLTYPE_INT, .flags = GCLI_TBLCOL_JUSTIFYR }, { .name = "STATE", .type = GCLI_TBLCOLTYPE_SV, .flags = GCLI_TBLCOL_STATECOLOURED }, { .name = "TITLE", .type = GCLI_TBLCOLTYPE_SV, .flags = 0 }, }; @@ -108,6 +109,7 @@ gcli_print_issues_table(enum gcli_output_flags const flags, 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 { @@ -119,6 +121,7 @@ gcli_print_issues_table(enum gcli_output_flags const flags, 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 { diff --git a/src/pulls.c b/src/pulls.c index 5bacc677..4a68efa5 100644 --- a/src/pulls.c +++ b/src/pulls.c @@ -73,6 +73,7 @@ gcli_print_pulls_table(enum gcli_output_flags const flags, { .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 }, }; @@ -99,6 +100,7 @@ gcli_print_pulls_table(enum gcli_output_flags const flags, 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 { @@ -108,6 +110,7 @@ gcli_print_pulls_table(enum gcli_output_flags const flags, list->pulls[i].state, list->pulls[i].merged, list->pulls[i].author, + list->pulls[i].comments, list->pulls[i].title); } } @@ -141,6 +144,9 @@ gcli_pull_print_status(gcli_pull const *const it) 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 (!(forge->pull_summary_quirks & GCLI_PRS_QUIRK_ADDDEL)) /* FIXME: move printing colours into the dictionary printer? */ gcli_dict_add(dict, "ADD:DEL", 0, 0, "%s%d%s:%s%d%s", @@ -279,7 +285,6 @@ gcli_pull_free(gcli_pull *const it) free(it->labels); } - void gcli_get_pull(char const *owner, char const *repo, @@ -389,3 +394,20 @@ gcli_pull_remove_labels(char const *owner, gcli_forge()->pr_remove_labels( owner, repo, pr_number, labels, labels_size); } + +int +gcli_pull_set_milestone(char const *owner, + char const *repo, + int pr_number, + int milestone_id) +{ + return gcli_forge()->pr_set_milestone(owner, repo, pr_number, milestone_id); +} + +int +gcli_pull_clear_milestone(char const *owner, + char const *repo, + int pr_number) +{ + return gcli_forge()->pr_clear_milestone(owner, repo, pr_number); +} diff --git a/src/sshkeys.c b/src/sshkeys.c new file mode 100644 index 00000000..9ae77473 --- /dev/null +++ b/src/sshkeys.c @@ -0,0 +1,106 @@ +/* + * 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 + +int +gcli_sshkeys_get_keys(gcli_sshkey_list *out) +{ + return gcli_forge()->get_sshkeys(out); +} + +void +gcli_sshkeys_print_keys(gcli_sshkey_list const *list) +{ + gcli_tbl *tbl; + gcli_tblcoldef cols[] = { + { .name = "ID", .type = GCLI_TBLCOLTYPE_INT, .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); +} + +void +gcli_sshkeys_free_keys(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(char const *title, + char const *public_key_path, + gcli_sshkey *out) +{ + int rc; + char *buffer; + + rc = sn_read_file(public_key_path, &buffer); + if (rc < 0) + return rc; + + rc = gcli_forge()->add_sshkey(title, buffer, out); + free(buffer); + + return rc; +} + +int +gcli_sshkeys_delete_key(int id) +{ + return gcli_forge()->delete_sshkey(id); +} diff --git a/templates/github/pulls.t b/templates/github/pulls.t index 805460da..fa4b9ec1 100644 --- a/templates/github/pulls.t +++ b/templates/github/pulls.t @@ -29,6 +29,10 @@ parser github_branch_label is object of gcli_pull with ("label" => base_label as string); +parser github_pull_milestone is +object of gcli_pull with + ("title" => milestone as string); + parser github_pull is object of gcli_pull with ("title" => title as string, @@ -49,7 +53,8 @@ object of gcli_pull with "draft" => draft as bool, "user" => author as user, "head" => use parse_github_pull_head, - "base" => use parse_github_branch_label); + "base" => use parse_github_branch_label, + "milestone" => use parse_github_pull_milestone); parser github_pr_merge_message is object of char* select "message" as string; diff --git a/templates/gitlab/merge_requests.t b/templates/gitlab/merge_requests.t index eb00ceea..3ef123e6 100644 --- a/templates/gitlab/merge_requests.t +++ b/templates/gitlab/merge_requests.t @@ -1,5 +1,9 @@ include "gcli/gitlab/merge_requests.h"; +parser gitlab_mr_milestone is +object of gcli_pull with + ("title" => milestone as string); + parser gitlab_mr is object of gcli_pull with ("title" => title as string, @@ -15,7 +19,8 @@ object of gcli_pull with "author" => author as user, "source_branch" => head_label as string, "sha" => head_sha as string, - "target_branch" => base_label as string); + "target_branch" => base_label as string, + "milestone" => use parse_gitlab_mr_milestone); parser gitlab_mrs is array of gcli_pull use parse_gitlab_mr; diff --git a/templates/gitlab/sshkeys.t b/templates/gitlab/sshkeys.t new file mode 100644 index 00000000..73bf9d76 --- /dev/null +++ b/templates/gitlab/sshkeys.t @@ -0,0 +1,10 @@ +include "gcli/sshkeys.h"; + +parser gitlab_sshkey is +object of gcli_sshkey with + ("title" => title as string, + "id" => id as int, + "key" => key as string, + "created_at" => created_at as string); + +parser gitlab_sshkeys is array of gcli_sshkey use parse_gitlab_sshkey; diff --git a/thirdparty/sn/sn.c b/thirdparty/sn/sn.c index 4c36b53e..d504bc69 100644 --- a/thirdparty/sn/sn.c +++ b/thirdparty/sn/sn.c @@ -236,6 +236,41 @@ sn_mmap_file(const char *path, void **buffer) 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) { diff --git a/thirdparty/sn/sn.h b/thirdparty/sn/sn.h index 5552701f..d30b0ba1 100644 --- a/thirdparty/sn/sn.h +++ b/thirdparty/sn/sn.h @@ -110,6 +110,7 @@ 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;