~herrhotzenplotz/gcli-devel

gcli: interactive: teach gcli_cmd_prompt to return NULL when an answer is optional v1 APPLIED

Nico Sonack: 4
 interactive: teach gcli_cmd_prompt to return NULL when an answer is optional
 pulls: Add an option for adding reviewers when creating a pull request
 gitlab: implement auto-adding reviewers when creating a merge request
 github: teach github pull creation to handle requesting reviews

 11 files changed, 154 insertions(+), 58 deletions(-)
build pending: alpine.yml
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~herrhotzenplotz/gcli-devel/patches/55295/mbox | git am -3
Learn more about email & git

[PATCH gcli 1/4] interactive: teach gcli_cmd_prompt to return NULL when an answer is optional Export this patch

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 2/4] pulls: Add an option for adding reviewers when creating a pull request Export this patch

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

[PATCH gcli 3/4] gitlab: implement auto-adding reviewers when creating a merge request Export this patch

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 Export this patch

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