~herrhotzenplotz/gcli-devel

gcli: pulls: add a checkout action with implementations for GitHub and GitLab v1 APPLIED

Nico Sonack: 1
 pulls: add a checkout action with implementations for GitHub and GitLab

 18 files changed, 442 insertions(+), 10 deletions(-)
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/54953/mbox | git am -3
Learn more about email & git

[PATCH gcli] pulls: add a checkout action with implementations for GitHub and GitLab Export this patch

This allows us to checkout a PR a'la:

	$ gcli pulls -i 42 checkout

Note that on GitLab, if a MR has been merged you cannot check it
out anymore.

Signed-off-by: Nico Sonack <nsonack@herrhotzenplotz.de>
---
 Changelog.md                   |  3 ++
 Makefile.in                    |  3 ++
 docs/gcli-pulls.1.in           |  8 ++++
 include/gcli/cmd/cmdconfig.h   |  3 +-
 include/gcli/cmd/gitconfig.h   |  7 ++-
 include/gcli/forges.h          |  8 +++-
 include/gcli/github/checkout.h | 35 ++++++++++++++
 include/gcli/gitlab/checkout.h | 35 ++++++++++++++
 include/gcli/pulls.h           |  4 +-
 include/gcli/waitproc.h        | 35 ++++++++++++++
 src/cmd/cmdconfig.c            | 22 ++++++++-
 src/cmd/gitconfig.c            | 18 ++++++-
 src/cmd/pulls.c                | 22 ++++++++-
 src/forges.c                   |  6 ++-
 src/github/checkout.c          | 86 +++++++++++++++++++++++++++++++++
 src/gitlab/checkout.c          | 87 ++++++++++++++++++++++++++++++++++
 src/pulls.c                    |  9 +++-
 src/waitproc.c                 | 61 ++++++++++++++++++++++++
 18 files changed, 442 insertions(+), 10 deletions(-)
 create mode 100644 include/gcli/github/checkout.h
 create mode 100644 include/gcli/gitlab/checkout.h
 create mode 100644 include/gcli/waitproc.h
 create mode 100644 src/github/checkout.c
 create mode 100644 src/gitlab/checkout.c
 create mode 100644 src/waitproc.c

diff --git a/Changelog.md b/Changelog.md
index e91c46f..f50db50 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -6,6 +6,9 @@ This changelog does not follow semantic versioning.

### Added

- Added a `checkout` action to the `pulls` subcommand that allows
  quickly checking out the target branch of a pull request.

### Fixed

### Changed
diff --git a/Makefile.in b/Makefile.in
index 9647e46..e8857b8 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -131,6 +131,7 @@ LIBGCLI_SRCS = \
	src/gitea/status.c \
	src/github/api.c \
	src/github/checks.c \
	src/github/checkout.c \
	src/github/comments.c \
	src/github/config.c \
	src/github/forks.c \
@@ -144,6 +145,7 @@ LIBGCLI_SRCS = \
	src/github/sshkeys.c \
	src/github/status.c \
	src/gitlab/api.c \
	src/gitlab/checkout.c \
	src/gitlab/comments.c \
	src/gitlab/config.c \
	src/gitlab/forks.c \
@@ -178,6 +180,7 @@ LIBGCLI_SRCS = \
	src/repos.c \
	src/sshkeys.c \
	src/status.c \
	src/waitproc.c \
	thirdparty/pdjson/pdjson.c \
	thirdparty/sn/sn.c

diff --git a/docs/gcli-pulls.1.in b/docs/gcli-pulls.1.in
index 8517718..0a598a0 100644
--- a/docs/gcli-pulls.1.in
+++ b/docs/gcli-pulls.1.in
@@ -139,6 +139,14 @@ implied:
.Cm op ,
.Cm commits and
.Cm ci .
.It Cm checkout
Do a git checkout of the head branch associated with this pull request.
This requires that
.Xr git 1
is available in the
.Ev PATH
and that the current working directory resides within a clone of the
target repository.
.It Cm commits
Print the list of commits associated with the Pull Requests.
.It Cm comments
diff --git a/include/gcli/cmd/cmdconfig.h b/include/gcli/cmd/cmdconfig.h
index a1df785..3356055 100644
--- a/include/gcli/cmd/cmdconfig.h
+++ b/include/gcli/cmd/cmdconfig.h
@@ -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
@@ -66,6 +66,7 @@ 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_get_remote(struct gcli_ctx *ctx, char **remote);
int gcli_config_have_colours(struct gcli_ctx *ctx);
int gcli_config_display_progress_spinner(struct gcli_ctx *ctx);
bool gcli_config_enable_experimental(struct gcli_ctx *ctx);
diff --git a/include/gcli/cmd/gitconfig.h b/include/gcli/cmd/gitconfig.h
index 0a4edeb..7b4f388 100644
--- a/include/gcli/cmd/gitconfig.h
+++ b/include/gcli/cmd/gitconfig.h
@@ -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
@@ -41,7 +41,7 @@ struct gcli_gitremote {
	sn_sv owner;
	sn_sv repo;
	sn_sv url;
	int   forge_type;
	gcli_forge_type forge_type;
};

sn_sv gcli_gitconfig_get_current_branch(void);
@@ -54,4 +54,7 @@ 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);

int gcli_gitconfig_get_remote(struct gcli_ctx *ctx, gcli_forge_type type,
                              char **remote);

#endif /* GCLI_CMD_GITCONFIG_H */
diff --git a/include/gcli/forges.h b/include/gcli/forges.h
index 420f562..3a8b6be 100644
--- a/include/gcli/forges.h
+++ b/include/gcli/forges.h
@@ -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
@@ -444,6 +444,12 @@ struct gcli_forge_descriptor {
		struct gcli_ctx *ctx,
		struct gcli_pull_create_review_details const *details);

	/** Checkout this PR */
	int (*pull_checkout)(
		struct gcli_ctx *ctx,
		char const *remote,
		gcli_id pull);

	/**
	 * Get a list of releases in the given repo */
	int (*get_releases)(
diff --git a/include/gcli/github/checkout.h b/include/gcli/github/checkout.h
new file mode 100644
index 0000000..69dc985
--- /dev/null
+++ b/include/gcli/github/checkout.h
@@ -0,0 +1,35 @@
/*
 * Copyright 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
 * 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_CHECKOUT_H
#define GITHUB_CHECKOUT_H

int github_pull_checkout(struct gcli_ctx *ctx, char const *remote, gcli_id pull);

#endif /* GITHUB_CHECKOUT_H */
diff --git a/include/gcli/gitlab/checkout.h b/include/gcli/gitlab/checkout.h
new file mode 100644
index 0000000..ddaec4c
--- /dev/null
+++ b/include/gcli/gitlab/checkout.h
@@ -0,0 +1,35 @@
/*
 * Copyright 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
 * 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_CHECKOUT_H
#define GITLAB_CHECKOUT_H

int gitlab_mr_checkout(struct gcli_ctx *ctx, char const *remote, gcli_id pull);

#endif /* GITLAB_CHECKOUT_H */
diff --git a/include/gcli/pulls.h b/include/gcli/pulls.h
index 9b01b4c..fa40944 100644
--- a/include/gcli/pulls.h
+++ b/include/gcli/pulls.h
@@ -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
@@ -224,4 +224,6 @@ int gcli_pull_get_patch(struct gcli_ctx *ctx, FILE *out, char const *owner,
char const *gcli_pull_get_meta_by_key(struct gcli_pull_create_review_details const *,
                                      char const *key);

int gcli_pull_checkout(struct gcli_ctx *ctx, char const *remote, gcli_id pull);

#endif /* PULLS_H */
diff --git a/include/gcli/waitproc.h b/include/gcli/waitproc.h
new file mode 100644
index 0000000..979f631
--- /dev/null
+++ b/include/gcli/waitproc.h
@@ -0,0 +1,35 @@
/*
 * Copyright 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
 * 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_WAITPROC_H
#define GCLI_WAITPROC_H

int gcli_wait_proc_ok(struct gcli_ctx *ctx, pid_t pid);

#endif /* GCLI_WAITPROC_H */
diff --git a/src/cmd/cmdconfig.c b/src/cmd/cmdconfig.c
index 12cb2a5..b1b0cf4 100644
--- a/src/cmd/cmdconfig.c
+++ b/src/cmd/cmdconfig.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
@@ -930,6 +930,26 @@ gcli_config_get_forge_type(struct gcli_ctx *ctx)
	return result;
}

int
gcli_config_get_remote(struct gcli_ctx *ctx, char **remote)
{
	struct gcli_config *cfg;
	gcli_forge_type type;
	int rc;

	cfg = ensure_config(ctx);

	if (cfg->override_remote) {
		*remote = strdup(cfg->override_remote);
		return 0;
	}

	type = gcli_config_get_forge_type(ctx);
	rc = gcli_gitconfig_get_remote(ctx, type, remote);

	return rc;
}

int
gcli_config_get_repo(struct gcli_ctx *ctx, char const **const owner,
                     char const **const repo)
diff --git a/src/cmd/gitconfig.c b/src/cmd/gitconfig.c
index 6066221..a24eb98 100644
--- a/src/cmd/gitconfig.c
+++ b/src/cmd/gitconfig.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
@@ -562,3 +562,19 @@ gcli_gitconfig_repo_by_remote(struct gcli_ctx *ctx, char const *const remote,

	return 0;
}

int
gcli_gitconfig_get_remote(struct gcli_ctx *ctx, gcli_forge_type const type,
                          char **remote)
{
	gcli_gitconfig_read_gitconfig();

	for (size_t i = 0; i < remotes_size; ++i) {
		if (remotes[i].forge_type == type) {
			*remote = sn_sv_to_cstr(remotes[i].url);
			return 0;
		}
	}

	return gcli_error(ctx, "no suitable remote for forge type");
}
diff --git a/src/cmd/pulls.c b/src/cmd/pulls.c
index a3ff300..1bc4104 100644
--- a/src/cmd/pulls.c
+++ b/src/cmd/pulls.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
@@ -98,6 +98,7 @@ usage(void)
	fprintf(stderr, "  patch                  Display changes as patch series\n");
	fprintf(stderr, "  title <new-title>      Change the title of the pull request\n");
	fprintf(stderr, "  request-review <user>  Add <user> as a reviewer of the PR\n");
	fprintf(stderr, "  checkout               Do a git-checkout of this PR (GitHub- and GitLab only)\n");
	if (gcli_config_enable_experimental(g_clictx))
		fprintf(stderr, "  review                 Start a review of this PR\n");

@@ -1091,6 +1092,24 @@ action_review(struct action_ctx *ctx)
	do_review_session(ctx->owner, ctx->repo, ctx->pr);
}

static void
action_checkout(struct action_ctx *ctx)
{
	char *remote;
	int rc = 0;

	rc = gcli_config_get_remote(g_clictx, &remote);
	if (rc < 0)
		errx(1, "gcli: error: %s", gcli_get_error(g_clictx));

	if (gcli_pull_checkout(g_clictx, remote, ctx->pr) < 0) {
		errx(1, "gcli: error: failed to checkout pull: %s",
		     gcli_get_error(g_clictx));
	}

	free(remote);
}

static struct action {
	char const *name;
	void (*fn)(struct action_ctx *ctx);
@@ -1112,6 +1131,7 @@ static struct action {
	{ .name = "request-review", .fn = action_request_review },
	{ .name = "title",          .fn = action_title          },
	{ .name = "review",         .fn = action_review         },
	{ .name = "checkout",       .fn = action_checkout       },
};

static size_t const actions_size = ARRAY_SIZE(actions);
diff --git a/src/forges.c b/src/forges.c
index 31ffd11..b9573d6 100644
--- a/src/forges.c
+++ b/src/forges.c
@@ -1,5 +1,5 @@
/*
 * Copyright 2021, 2022, 2023 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
@@ -32,6 +32,7 @@
#include <gcli/forges.h>

#include <gcli/github/api.h>
#include <gcli/github/checkout.h>
#include <gcli/github/comments.h>
#include <gcli/github/config.h>
#include <gcli/github/forks.h>
@@ -45,6 +46,7 @@
#include <gcli/github/status.h>

#include <gcli/gitlab/api.h>
#include <gcli/gitlab/checkout.h>
#include <gcli/gitlab/comments.h>
#include <gcli/gitlab/config.h>
#include <gcli/gitlab/forks.h>
@@ -125,6 +127,7 @@ github_forge_descriptor =
	.pull_merge                = github_pull_merge,
	.pull_reopen               = github_pull_reopen,
	.pull_set_title            = github_pull_set_title,
	.pull_checkout             = github_pull_checkout,

	/* 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
@@ -230,6 +233,7 @@ gitlab_forge_descriptor =
	.pull_reopen               = gitlab_mr_reopen,
	.pull_set_milestone        = gitlab_mr_set_milestone,
	.pull_set_title            = gitlab_mr_set_title,
	.pull_checkout             = gitlab_mr_checkout,

	/* Releases */
	.create_release            = gitlab_create_release,
diff --git a/src/github/checkout.c b/src/github/checkout.c
new file mode 100644
index 0000000..5f6e4cb
--- /dev/null
+++ b/src/github/checkout.c
@@ -0,0 +1,86 @@
/*
 * Copyright 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
 * 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 <gcli/gcli.h>
#include <gcli/waitproc.h>

#include <sn/sn.h>

#include <stdlib.h>
#include <unistd.h>

int
github_pull_checkout(struct gcli_ctx *ctx, char const *const remote, gcli_id const pr_id)
{
	/* FIXME: this is more than not ideal! */
	char *remote_ref, *local_ref, *refspec;
	int rc;
	pid_t pid;

	remote_ref = sn_asprintf("refs/pull/%"PRIid"/head", pr_id);
	local_ref = sn_asprintf("github/pr/%"PRIid, pr_id);
	refspec = sn_asprintf("%s:%s", remote_ref, local_ref);

	pid = fork();
	if (pid < 0)
		return gcli_error(ctx, "could not fork");

	if (pid == 0) {
		rc = execlp("git", "git", "fetch", remote, refspec, NULL);
		if (rc < 0)
			exit(EXIT_FAILURE);

		/* NOTREACHED */
	}

	rc = gcli_wait_proc_ok(ctx, pid);
	if (rc < 0)
		return rc;

	free(remote_ref); remote_ref = NULL;
	free(refspec); refspec = NULL;

	pid = fork();
	if (pid < 0)
		return gcli_error(ctx, "could not fork");

	if (pid == 0) {
		rc = execlp("git", "git", "checkout", "--track", local_ref, NULL);
		if (rc < 0)
			exit(EXIT_FAILURE);

		/* NOTREACHED */
	}

	rc = gcli_wait_proc_ok(ctx, pid);

	free(local_ref); local_ref = NULL;

	return rc;
}
diff --git a/src/gitlab/checkout.c b/src/gitlab/checkout.c
new file mode 100644
index 0000000..41d5b2f
--- /dev/null
+++ b/src/gitlab/checkout.c
@@ -0,0 +1,87 @@
/*
 * Copyright 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
 * 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 <gcli/gcli.h>
#include <gcli/gitlab/checkout.h>
#include <gcli/waitproc.h>

#include <sn/sn.h>

#include <stdlib.h>
#include <unistd.h>

int
gitlab_mr_checkout(struct gcli_ctx *ctx, char const *const remote, gcli_id const pr_id)
{
	/* FIXME: this is more than not ideal! */
	char *remote_ref, *local_ref, *refspec;
	int rc;
	pid_t pid;

	remote_ref = sn_asprintf("merge-requests/%"PRIid"/head", pr_id);
	local_ref = sn_asprintf("gitlab/mr/%"PRIid, pr_id);
	refspec = sn_asprintf("%s:%s", remote_ref, local_ref);

	pid = fork();
	if (pid < 0)
		return gcli_error(ctx, "could not fork");

	if (pid == 0) {
		rc = execlp("git", "git", "fetch", remote, refspec, NULL);
		if (rc < 0)
			exit(EXIT_FAILURE);

		/* NOTREACHED */
	}

	rc = gcli_wait_proc_ok(ctx, pid);
	if (rc < 0)
		return rc;

	free(remote_ref); remote_ref = NULL;
	free(refspec); refspec = NULL;

	pid = fork();
	if (pid < 0)
		return gcli_error(ctx, "could not fork");

	if (pid == 0) {
		rc = execlp("git", "git", "checkout", "--track", local_ref, NULL);
		if (rc < 0)
			exit(EXIT_FAILURE);

		/* NOTREACHED */
	}

	rc = gcli_wait_proc_ok(ctx, pid);

	free(local_ref); local_ref = NULL;

	return rc;
}
diff --git a/src/pulls.c b/src/pulls.c
index f792d93..84b1fd1 100644
--- a/src/pulls.c
+++ b/src/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
@@ -255,3 +255,10 @@ gcli_pull_get_meta_by_key(struct gcli_pull_create_review_details const *details,

	return NULL;
}

int
gcli_pull_checkout(struct gcli_ctx *ctx, char const *const remote,
                   gcli_id const pull)
{
	gcli_null_check_call(pull_checkout, ctx, remote, pull);
}
diff --git a/src/waitproc.c b/src/waitproc.c
new file mode 100644
index 0000000..aa4b962
--- /dev/null
+++ b/src/waitproc.c
@@ -0,0 +1,61 @@
/*
 * Copyright 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
 * 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 <gcli/gcli.h>

#include <errno.h>
#include <string.h>
#include <sys/wait.h>

int
gcli_wait_proc_ok(struct gcli_ctx *ctx, pid_t pid)
{
	int status;

	if (waitpid(pid, &status, WEXITED) == -1) {
		return gcli_error(ctx, "failed to wait for child process: %s",
		                  strerror(errno));
	}

	if (WIFEXITED(status)) {
		int exit_code = WEXITSTATUS(status);
		if (exit_code) {
			return gcli_error(ctx, "child exited with error code %d",
			                  WEXITSTATUS(status));
		}
		return 0;
	}

	if (WIFSIGNALED(status)) {
		return gcli_error(ctx, "child exited due to signal %d",
		                  WTERMSIG(status));
	}

	return gcli_error(ctx, "unknown child status");
}
-- 
2.45.2