This thread contains a patchset. You're looking at the original emails,
but you may wish to use the patch review UI.
Review patch
3
[PATCH gcli 1/4] interactive: teach gcli_cmd_prompt to return NULL when an answer is optional
This introduced two constants that I added for readability. These have been
added in places where gcli_cmd_prompt is being used in the required way.
Signed-off-by: Nico Sonack <nsonack@herrhotzenplotz.de>
---
include/gcli/cmd/interactive.h | 3 +++
src/cmd/interactive.c | 25 +++++++++++++++++++------
src/cmd/issues.c | 4 ++--
src/cmd/pulls.c | 2 +-
src/cmd/status_interactive.c | 8 ++++++--
5 files changed, 31 insertions(+), 11 deletions(-)
diff --git a/include/gcli/cmd/interactive.h b/include/gcli/cmd/interactive.h
index be7e91a..b64c77d 100644
--- a/include/gcli/cmd/interactive.h
+++ b/include/gcli/cmd/interactive.h
@@ -34,6 +34,9 @@
#include <config.h>
#endif
+#define GCLI_PROMPT_RESULT_MANDATORY NULL
+#define GCLI_PROMPT_RESULT_OPTIONAL ""
+
char *gcli_cmd_prompt(char const *const fmt, char const *const deflt, ...);
int gcli_cmd_into_pager(int (*fn)(void *), void *data);
diff --git a/src/cmd/interactive.c b/src/cmd/interactive.c
index eb576df..eaa8ce2 100644
--- a/src/cmd/interactive.c
+++ b/src/cmd/interactive.c
@@ -135,31 +135,44 @@ get_input_line(char *const prompt)
* 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. */
+ * non-empty. If the default value is an empty string NULL will be
+ * returned if the user didn't give an answer. */
char *
gcli_cmd_prompt(char const *const fmt, char const *const deflt, ...)
{
- va_list vp;
+ bool want_exit;
char *result;
char prompt[256] = {0};
size_t prompt_len;
+ va_list vp;
va_start(vp, deflt);
vsnprintf(prompt, sizeof(prompt), fmt, vp);
va_end(vp);
prompt_len = strlen(prompt);
- if (deflt) {
+ if (deflt && *deflt) {
snprintf(prompt + prompt_len, sizeof(prompt) - prompt_len, " [%s]: ", deflt);
} else {
strncat(prompt, ": ", sizeof(prompt) - prompt_len - 1);
}
- do {
+ for (;;) {
result = get_input_line(prompt);
- } while (deflt == NULL && result == NULL);
- if (result == NULL)
+ want_exit =
+ /* default is empty string */
+ (deflt && *deflt == '\0') ||
+ /* result is empty but we have a default */
+ (result == NULL && deflt != NULL) ||
+ /* we have a non-empty response from the user */
+ (result != NULL);
+
+ if (want_exit)
+ break;
+ }
+
+ if (result == NULL && deflt && *deflt)
result = strdup(deflt);
return result;
diff --git a/src/cmd/issues.c b/src/cmd/issues.c
index 6f27faf..aae952c 100644
--- a/src/cmd/issues.c
+++ b/src/cmd/issues.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Nico Sonack <nsonack@herrhotzenplotz.de>
+ * Copyright 2022-2024 Nico Sonack <nsonack@herrhotzenplotz.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -275,7 +275,7 @@ subcommand_issue_create_interactive(struct gcli_submit_issue_options *const opts
if (!opts->repo)
opts->repo = gcli_cmd_prompt("Repository", deflt_repo);
- opts->title = gcli_cmd_prompt("Title", NULL);
+ opts->title = gcli_cmd_prompt("Title", GCLI_PROMPT_RESULT_MANDATORY);
rc = create_issue(opts, false);
if (rc < 0) {
diff --git a/src/cmd/pulls.c b/src/cmd/pulls.c
index 1bc4104..5096770 100644
--- a/src/cmd/pulls.c
+++ b/src/cmd/pulls.c
@@ -491,7 +491,7 @@ subcommand_pull_create_interactive(struct gcli_submit_pull_options *const opts)
}
/* Meta */
- opts->title = gcli_cmd_prompt("Title", NULL);
+ opts->title = gcli_cmd_prompt("Title", GCLI_PROMPT_RESULT_MANDATORY);
opts->automerge = sn_yesno("Enable automerge?");
/* create_pull is going to pop up the editor */
diff --git a/src/cmd/status_interactive.c b/src/cmd/status_interactive.c
index c9b9682..e306a59 100644
--- a/src/cmd/status_interactive.c
+++ b/src/cmd/status_interactive.c
@@ -95,7 +95,10 @@ handle_issue_notification(struct gcli_notification const *const notif)
gcli_issue_print_summary(&issue);
for (;;) {
- user_input = gcli_cmd_prompt( "[%s] What? (status, discussion, quit)", NULL, notif->repository);
+ user_input = gcli_cmd_prompt(
+ "[%s] What? (status, discussion, quit)",
+ GCLI_PROMPT_RESULT_MANDATORY,
+ notif->repository);
if (strcmp(user_input, "quit") == 0 ||
strcmp(user_input, "q") == 0) {
@@ -161,7 +164,8 @@ gcli_status_interactive(void)
print_notification_table(&list);
for (;;) {
- user_input = gcli_cmd_prompt("Enter number, list or quit", NULL);
+ user_input = gcli_cmd_prompt("Enter number, list or quit",
+ GCLI_PROMPT_RESULT_MANDATORY);
if (strcmp(user_input, "q") == 0 ||
strcmp(user_input, "quit") == 0) {
--
2.45.2
[PATCH gcli 3/4] gitlab: implement auto-adding reviewers when creating a merge request
Signed-off-by: Nico Sonack <nsonack@herrhotzenplotz.de>
---
src/gitlab/merge_requests.c | 20 +++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
diff --git a/src/gitlab/merge_requests.c b/src/gitlab/merge_requests.c
index d8b9bf5..aa7924f 100644
--- a/src/gitlab/merge_requests.c
+++ b/src/gitlab/merge_requests.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2021, 2022 Nico Sonack <nsonack@herrhotzenplotz.de>
+ * Copyright 2021-2024 Nico Sonack <nsonack@herrhotzenplotz.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -678,6 +678,7 @@ gitlab_perform_submit_mr(struct gcli_ctx *ctx, struct gcli_submit_pull_options *
gcli_jsongen_objmember(&gen, "target_project_id");
gcli_jsongen_number(&gen, target.id);
+ /* Labels if any */
if (opts->labels_size) {
gcli_jsongen_objmember(&gen, "labels");
@@ -686,6 +687,23 @@ gitlab_perform_submit_mr(struct gcli_ctx *ctx, struct gcli_submit_pull_options *
gcli_jsongen_string(&gen, opts->labels[i]);
gcli_jsongen_end_array(&gen);
}
+
+ /* Reviewers if any */
+ if (opts->reviewers_size) {
+ gcli_jsongen_objmember(&gen, "reviewer_ids");
+
+ gcli_jsongen_begin_array(&gen);
+ for (size_t i = 0; i < opts->reviewers_size; ++i) {
+ int uid;
+
+ uid = gitlab_user_id(ctx, opts->reviewers[i]);
+ if (uid < 0)
+ return uid;
+
+ gcli_jsongen_number(&gen, uid);
+ }
+ gcli_jsongen_end_array(&gen);
+ }
}
gcli_jsongen_end_object(&gen);
payload = gcli_jsongen_to_string(&gen);
--
2.45.2
[PATCH gcli 4/4] github: teach github pull creation to handle requesting reviews
Signed-off-by: Nico Sonack <nsonack@herrhotzenplotz.de>
---
src/github/pulls.c | 110 +++++++++++++++++++++++++++------------------
1 file changed, 66 insertions(+), 44 deletions(-)
diff --git a/src/github/pulls.c b/src/github/pulls.c
index d40ad15..6e28760 100644
--- a/src/github/pulls.c
+++ b/src/github/pulls.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2021, 2022 Nico Sonack <nsonack@herrhotzenplotz.de>
+ * Copyright 2021-2024 Nico Sonack <nsonack@herrhotzenplotz.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -431,6 +431,52 @@ github_pull_set_automerge(struct gcli_ctx *const ctx, char const *const node_id)
return rc;
}
+static int
+github_pull_add_reviewers(struct gcli_ctx *ctx, char const *owner,
+ char const *repo, gcli_id pr_number,
+ char const *const *users, size_t users_size)
+{
+ 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);
+ for (size_t i = 0; i < users_size; ++i)
+ gcli_jsongen_string(&gen, users[i]);
+
+ 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_perform_submit_pull(struct gcli_ctx *ctx,
struct gcli_submit_pull_options *opts)
@@ -473,9 +519,12 @@ github_perform_submit_pull(struct gcli_ctx *ctx,
rc = gcli_fetch_with_method(ctx, "POST", url, payload, NULL, &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)) {
+ /* Add labels or reviewers if requested or set automerge.
+ * GitHub doesn't allow us to do this all with one request. */
+ if (rc == 0 && (opts->labels_size ||
+ opts->automerge ||
+ opts->reviewers_size))
+ {
struct json_stream json = {0};
struct gcli_pull pull = {0};
@@ -483,9 +532,17 @@ github_perform_submit_pull(struct gcli_ctx *ctx,
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);
+ rc = github_issue_add_labels(
+ ctx, opts->owner, opts->repo, pull.number,
+ (char const *const *)opts->labels,
+ opts->labels_size);
+ }
+
+ if (rc == 0 && opts->reviewers_size) {
+ rc = github_pull_add_reviewers(
+ ctx, opts->owner, opts->repo, pull.number,
+ (char const *const *)opts->reviewers,
+ opts->reviewers_size);
}
if (rc == 0 && opts->automerge) {
@@ -497,7 +554,6 @@ github_perform_submit_pull(struct gcli_ctx *ctx,
json_close(&json);
}
-
gcli_fetch_buffer_free(&buffer);
free(payload);
free(url);
@@ -594,42 +650,8 @@ 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;
+ return github_pull_add_reviewers(ctx, owner, repo, pr_number,
+ &username, 1);
}
int
--
2.45.2
[PATCH gcli 2/4] pulls: Add an option for adding reviewers when creating a pull request
Signed-off-by: Nico Sonack <nsonack@herrhotzenplotz.de>
---
docs/gcli-pulls.1.in | 7 +++++++
include/gcli/pulls.h | 2 ++
src/cmd/issues.c | 4 +++-
src/cmd/pulls.c | 27 ++++++++++++++++++++++++++-
4 files changed, 38 insertions(+), 2 deletions(-)
diff --git a/docs/gcli-pulls.1.in b/docs/gcli-pulls.1.in
index 0a598a0..06d7cfe 100644
--- a/docs/gcli-pulls.1.in
+++ b/docs/gcli-pulls.1.in
@@ -24,6 +24,7 @@
.Op Fl t Ar branch
.Op Fl f Ar owner:branch
.Op Fl y
+.Op Fl R Ar reviewer
.Op Ar "PR title..."
.Sh DESCRIPTION
Use
@@ -121,6 +122,12 @@ omit this flag and gcli will try to infer this information.
Do not ask for confirmation before creating the PR. Assume yes.
.It Fl a , -automerge
Enable the automerge feature when creating the PR.
+.It Fl R , -reviewer Ar reviewer
+Add the given
+.Ar reviewer
+as a reviewer for the pull request that is to be created.
+To add multiple people as reviewers specify this option more than
+once, one for each reviewer.
.It Ar "PR Title..."
The title of the Pull Request or Merge Request.
.El
diff --git a/include/gcli/pulls.h b/include/gcli/pulls.h
index fa40944..faedece 100644
--- a/include/gcli/pulls.h
+++ b/include/gcli/pulls.h
@@ -102,6 +102,8 @@ struct gcli_submit_pull_options {
char *body;
char **labels;
size_t labels_size;
+ char **reviewers;
+ size_t reviewers_size;
int draft;
bool automerge; /** Automatically merge the PR when a pipeline passes */
};
diff --git a/src/cmd/issues.c b/src/cmd/issues.c
index aae952c..4bca8eb 100644
--- a/src/cmd/issues.c
+++ b/src/cmd/issues.c
@@ -50,7 +50,7 @@
static void
usage(void)
{
- fprintf(stderr, "usage: gcli issues create [-o owner -r repo] [-y] [title...]\n");
+ fprintf(stderr, "usage: gcli issues create [-o owner -r repo] [-y] [-R reviewer] [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");
@@ -65,6 +65,8 @@ usage(void)
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, " -R reviewer Mark a person as a reviewer for the created PR\n");
+ fprintf(stderr, " Can be specified more than once.\n");
fprintf(stderr, "ACTIONS:\n");
fprintf(stderr, " all Display both status and and op\n");
fprintf(stderr, " status Display status information\n");
diff --git a/src/cmd/pulls.c b/src/cmd/pulls.c
index 5096770..27d0166 100644
--- a/src/cmd/pulls.c
+++ b/src/cmd/pulls.c
@@ -494,6 +494,22 @@ subcommand_pull_create_interactive(struct gcli_submit_pull_options *const opts)
opts->title = gcli_cmd_prompt("Title", GCLI_PROMPT_RESULT_MANDATORY);
opts->automerge = sn_yesno("Enable automerge?");
+ /* Reviewers */
+ for (;;) {
+ char *response;
+
+ response = gcli_cmd_prompt("Add reviewer? (name or leave empty)",
+ GCLI_PROMPT_RESULT_OPTIONAL);
+ if (response == NULL)
+ break;
+
+ opts->reviewers = realloc(
+ opts->reviewers,
+ (opts->reviewers_size + 1) * sizeof(*opts->reviewers));
+
+ opts->reviewers[opts->reviewers_size++] = response;
+ }
+
/* create_pull is going to pop up the editor */
rc = create_pull(opts, false);
if (rc < 0) {
@@ -541,10 +557,14 @@ subcommand_pull_create(int argc, char *argv[])
.has_arg = required_argument,
.flag = NULL,
.val = 'a' },
+ { .name = "reviewer",
+ .has_arg = required_argument,
+ .flag = NULL,
+ .val = 'R' },
{0},
};
- while ((ch = getopt_long(argc, argv, "ayf:t:do:r:l:", options, NULL)) != -1) {
+ while ((ch = getopt_long(argc, argv, "ayf:t:do:r:l:R:", options, NULL)) != -1) {
switch (ch) {
case 'f':
opts.from = optarg;
@@ -566,6 +586,11 @@ subcommand_pull_create(int argc, char *argv[])
opts.labels, sizeof(*opts.labels) * (opts.labels_size + 1));
opts.labels[opts.labels_size++] = optarg;
break;
+ case 'R': /* add a reviewer */
+ opts.reviewers = realloc(
+ opts.reviewers, sizeof(*opts.reviewers) * (opts.reviewers_size + 1));
+ opts.reviewers[opts.reviewers_size++] = optarg;
+ break;
case 'y':
always_yes = 1;
break;
--
2.45.2