From a8be7423efb287edd5ef80002a75a853fc0c9c1d Mon Sep 17 00:00:00 2001
From: Levente Polyak <anthraxx@archlinux.org>
Date: Mon, 12 Sep 2022 01:34:08 +0200
Subject: pkgctl: add a unified command-line frontend for devtools

This is the first step of a simple and highly structured unified
interface to devtools commands in a single wrapper.

The split is based on groups like `repo`, `build` and `diff`

Signed-off-by: Levente Polyak <anthraxx@archlinux.org>
---
 doc/man/pkgctl.1.asciidoc | 32 ++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)
 create mode 100644 doc/man/pkgctl.1.asciidoc

(limited to 'doc/man/pkgctl.1.asciidoc')

diff --git a/doc/man/pkgctl.1.asciidoc b/doc/man/pkgctl.1.asciidoc
new file mode 100644
index 0000000..0455074
--- /dev/null
+++ b/doc/man/pkgctl.1.asciidoc
@@ -0,0 +1,32 @@
+pkgctl(1)
+=========
+
+Name
+----
+pkgctl - Unified command-line frontend for devtools
+
+Synopsis
+--------
+pkgctl [SUBCOMMAND] [OPTIONS]
+
+Description
+-----------
+
+TODO
+
+Subcommands
+-----------
+
+pkgctl diff::
+	Compare package files using different modes
+
+pkgctl repo::
+	Manage Git packaging repositories and their configuration
+
+See Also
+--------
+
+linkman:pkgctl-diff[1]
+linkman:pkgctl-repo[1]
+
+include::include/footer.asciidoc[]
-- 
cgit v1.2.3-54-g00ecf


From 77d800eab2419b334cafd94b2e986351919def77 Mon Sep 17 00:00:00 2001
From: Levente Polyak <anthraxx@archlinux.org>
Date: Sun, 23 Oct 2022 20:42:34 +0200
Subject: auth: implemented module to authenticate against our GitLab

This helps to have a convenient way to manage and test our personal
GitLab tokens. Those are used for certain API calls like creating new
repositories.

prefill the access token web view as per
https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#prefill-personal-access-token-name-and-scopes

Signed-off-by: Levente Polyak <anthraxx@archlinux.org>
---
 contrib/completion/zsh/_devtools.in   | 17 +++++++
 doc/man/pkgctl-auth-login.1.asciidoc  | 33 +++++++++++++
 doc/man/pkgctl-auth-status.1.asciidoc | 32 ++++++++++++
 doc/man/pkgctl-auth.1.asciidoc        | 38 ++++++++++++++
 doc/man/pkgctl.1.asciidoc             |  4 ++
 src/lib/auth.sh                       | 72 +++++++++++++++++++++++++++
 src/lib/auth/login.sh                 | 93 +++++++++++++++++++++++++++++++++++
 src/lib/auth/status.sh                | 69 ++++++++++++++++++++++++++
 src/pkgctl.in                         |  9 ++++
 9 files changed, 367 insertions(+)
 create mode 100644 doc/man/pkgctl-auth-login.1.asciidoc
 create mode 100644 doc/man/pkgctl-auth-status.1.asciidoc
 create mode 100644 doc/man/pkgctl-auth.1.asciidoc
 create mode 100644 src/lib/auth.sh
 create mode 100644 src/lib/auth/login.sh
 create mode 100644 src/lib/auth/status.sh

(limited to 'doc/man/pkgctl.1.asciidoc')

diff --git a/contrib/completion/zsh/_devtools.in b/contrib/completion/zsh/_devtools.in
index b210378..6ff6cad 100644
--- a/contrib/completion/zsh/_devtools.in
+++ b/contrib/completion/zsh/_devtools.in
@@ -18,6 +18,22 @@ _archbuild_args=(
 	'--[Introduce makechrootpkg options]:*::makechrootpkg options:=  _dispatch makechrootpkg makechrootpkg'
 )
 
+_pkgctl_auth_cmds=(
+	"pkgctl auth command"
+	"login[Authenticate with the GitLab instance]"
+	"status[View authentication status]"
+)
+
+_pkgctl_auth_login_args=(
+	'(-g --gen-access-token)'{-g,--gen-access-token}'[Open the URL to generate a new personal access token]'
+	'(-h --help)'{-h,--help}'[Display usage]'
+)
+
+_pkgctl_auth_status_args=(
+	'(-t --show-token)'{-t,--show-token}'[Display the auth token]'
+	'(-h --help)'{-h,--help}'[Display usage]'
+)
+
 _pkgctl_repo_cmds=(
 	"pkgctl repo command"
 	"clone[Clone a package repository]"
@@ -154,6 +170,7 @@ _devtools_completions_all_packages() {
 
 _pkgctl_cmds=(
 	"pkgctl command"
+	"auth[Authenticate with services like GitLab]"
 	"diff[Compare package files using different modes]"
 	"repo[Manage Git packaging repositories and their configuration]"
 )
diff --git a/doc/man/pkgctl-auth-login.1.asciidoc b/doc/man/pkgctl-auth-login.1.asciidoc
new file mode 100644
index 0000000..9c32ab2
--- /dev/null
+++ b/doc/man/pkgctl-auth-login.1.asciidoc
@@ -0,0 +1,33 @@
+pkgctl-auth-login(1)
+====================
+
+Name
+----
+pkgctl-auth-login - Authenticate with the GitLab instance
+
+Synopsis
+--------
+pkgctl auth login [OPTIONS]
+
+Description
+-----------
+
+Interactively authenticate with the GitLab instance.
+
+The minimum required scopes for the token are: 'api', 'write_repository'.
+
+Options
+-------
+
+*-g, --gen-access-token*::
+	Open the URL to generate a new personal access token
+
+*-h, --help*::
+	Show a help text
+
+See Also
+--------
+
+linkman:pkgctl-auth-status[1]
+
+include::include/footer.asciidoc[]
diff --git a/doc/man/pkgctl-auth-status.1.asciidoc b/doc/man/pkgctl-auth-status.1.asciidoc
new file mode 100644
index 0000000..e23ee2e
--- /dev/null
+++ b/doc/man/pkgctl-auth-status.1.asciidoc
@@ -0,0 +1,32 @@
+pkgctl-auth-status(1)
+=====================
+
+Name
+----
+pkgctl-auth-status - View authentication status
+
+Synopsis
+--------
+pkgctl auth status [OPTIONS]
+
+Description
+-----------
+
+Verifies and displays information about your authentication state of
+services like the GitLab instance and reports issues if any.
+
+Options
+-------
+
+*-t, --show-token*::
+	Display the auth token
+
+*-h, --help*::
+	Show a help text
+
+See Also
+--------
+
+linkman:pkgctl-auth-login[1]
+
+include::include/footer.asciidoc[]
diff --git a/doc/man/pkgctl-auth.1.asciidoc b/doc/man/pkgctl-auth.1.asciidoc
new file mode 100644
index 0000000..4912b02
--- /dev/null
+++ b/doc/man/pkgctl-auth.1.asciidoc
@@ -0,0 +1,38 @@
+pkgctl-auth(1)
+==============
+
+Name
+----
+pkgctl-auth - Authenticate with serivces like GitLab.
+
+Synopsis
+--------
+pkgctl auth [OPTIONS] [SUBCOMMAND]
+
+Description
+-----------
+
+Manage the authorization for the GitLab instance and show its current status.
+
+Options
+-------
+
+*-h, --help*::
+	Show a help text
+
+Subcommands
+-----------
+
+pkgctl auth login::
+	Authenticate with the GitLab instance
+
+pkgctl auth status::
+	View authentication status
+
+See Also
+--------
+
+linkman:pkgctl-auth-login[1]
+linkman:pkgctl-auth-status[1]
+
+include::include/footer.asciidoc[]
diff --git a/doc/man/pkgctl.1.asciidoc b/doc/man/pkgctl.1.asciidoc
index 0455074..45d5187 100644
--- a/doc/man/pkgctl.1.asciidoc
+++ b/doc/man/pkgctl.1.asciidoc
@@ -17,6 +17,9 @@ TODO
 Subcommands
 -----------
 
+pkgctl auth::
+	Authenticate with services like GitLab
+
 pkgctl diff::
 	Compare package files using different modes
 
@@ -26,6 +29,7 @@ pkgctl repo::
 See Also
 --------
 
+linkman:pkgctl-auth[1]
 linkman:pkgctl-diff[1]
 linkman:pkgctl-repo[1]
 
diff --git a/src/lib/auth.sh b/src/lib/auth.sh
new file mode 100644
index 0000000..77d6a90
--- /dev/null
+++ b/src/lib/auth.sh
@@ -0,0 +1,72 @@
+#!/hint/bash
+#
+# This may be included with or without `set -euE`
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+[[ -z ${DEVTOOLS_INCLUDE_AUTH_SH:-} ]] || return 0
+DEVTOOLS_INCLUDE_AUTH_SH=1
+
+_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@}
+
+set -e
+
+
+pkgctl_auth_usage() {
+	local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}}
+	cat <<- _EOF_
+		Usage: ${COMMAND} [COMMAND] [OPTIONS]
+
+		Authenticate with services like GitLab.
+
+		COMMANDS
+		    login    Authenticate with the GitLab instance
+		    status   View authentication status
+
+		OPTIONS
+		    -h, --help     Show this help text
+
+		EXAMPLES
+		    $ ${COMMAND} login --gen-access-token
+		    $ ${COMMAND} status
+_EOF_
+}
+
+pkgctl_auth() {
+	if (( $# < 1 )); then
+		pkgctl_auth_usage
+		exit 0
+	fi
+
+	# option checking
+	while (( $# )); do
+		case $1 in
+			-h|--help)
+				pkgctl_auth_usage
+				exit 0
+				;;
+			login)
+				_DEVTOOLS_COMMAND+=" $1"
+				shift
+				# shellcheck source=src/lib/auth/login.sh
+				source "${_DEVTOOLS_LIBRARY_DIR}"/lib/auth/login.sh
+				pkgctl_auth_login "$@"
+				exit 0
+				;;
+			status)
+				_DEVTOOLS_COMMAND+=" $1"
+				shift
+				# shellcheck source=src/lib/auth/status.sh
+				source "${_DEVTOOLS_LIBRARY_DIR}"/lib/auth/status.sh
+				pkgctl_auth_status "$@"
+				exit 0
+				;;
+			-*)
+				die "invalid argument: %s" "$1"
+				;;
+			*)
+				die "invalid command: %s" "$1"
+				;;
+		esac
+	done
+}
diff --git a/src/lib/auth/login.sh b/src/lib/auth/login.sh
new file mode 100644
index 0000000..083e80a
--- /dev/null
+++ b/src/lib/auth/login.sh
@@ -0,0 +1,93 @@
+#!/hint/bash
+#
+# This may be included with or without `set -euE`
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+[[ -z ${DEVTOOLS_INCLUDE_AUTH_LOGIN_SH:-} ]] || return 0
+DEVTOOLS_INCLUDE_AUTH_LOGIN_SH=1
+
+_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@}
+# shellcheck source=src/lib/common.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh
+# shellcheck source=src/lib/config.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/config.sh
+# shellcheck source=src/lib/api/gitlab.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/api/gitlab.sh
+
+set -e
+
+
+pkgctl_auth_login_usage() {
+	local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}}
+	cat <<- _EOF_
+		Usage: ${COMMAND} [OPTIONS]
+
+		Interactively authenticate with the GitLab instance.
+
+		The minimum required scopes for the token are: 'api', 'write_repository'.
+
+		OPTIONS
+		    -g, --gen-access-token   Open the URL to generate a new personal access token
+		    -h, --help               Show this help text
+
+		EXAMPLES
+		    $ ${COMMAND}
+		    $ ${COMMAND} --gen-access-token
+_EOF_
+}
+
+
+pkgctl_auth_login() {
+	local token personal_access_token_url
+	local GEN_ACESS_TOKEN=0
+
+	# option checking
+	while (( $# )); do
+		case $1 in
+			-h|--help)
+				pkgctl_auth_login_usage
+				exit 0
+				;;
+			-g|--gen-access-token)
+				GEN_ACESS_TOKEN=1
+				shift
+				;;
+			*)
+				die "invalid argument: %s" "$1"
+				;;
+		esac
+	done
+
+	personal_access_token_url="https://${GITLAB_HOST}/-/profile/personal_access_tokens?name=pkgctl+token&scopes=api,write_repository"
+
+    cat <<- _EOF_
+	Logging into ${BOLD}${GITLAB_HOST}${ALL_OFF}
+
+	Tip: you can generate a Personal Access Token here ${personal_access_token_url}
+	The minimum required scopes are 'api' and 'write_repository'.
+_EOF_
+
+	if (( GEN_ACESS_TOKEN )); then
+		xdg-open "${personal_access_token_url}" 2>/dev/null
+	fi
+
+	# read token from stdin
+	read -s -r -p "${GREEN}?${ALL_OFF} ${BOLD}Paste your authentication token:${ALL_OFF} " token
+	echo
+
+	if [[ -z ${token} ]]; then
+		msg_error "  No token provided"
+		exit 1
+	fi
+
+	# check if the passed token works
+	GITLAB_TOKEN="${token}"
+	if ! result=$(gitlab_api_get_user); then
+		printf "%s\n" "$result"
+		exit 1
+	fi
+
+	msg_success "  Logged in as ${BOLD}${result}${ALL_OFF}"
+	save_devtools_config
+}
diff --git a/src/lib/auth/status.sh b/src/lib/auth/status.sh
new file mode 100644
index 0000000..6cbaab1
--- /dev/null
+++ b/src/lib/auth/status.sh
@@ -0,0 +1,69 @@
+#!/hint/bash
+#
+# This may be included with or without `set -euE`
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+[[ -z ${DEVTOOLS_INCLUDE_AUTH_STATUS_SH:-} ]] || return 0
+DEVTOOLS_INCLUDE_AUTH_STATUS_SH=1
+
+_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@}
+# shellcheck source=src/lib/common.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh
+# shellcheck source=src/lib/api/gitlab.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/api/gitlab.sh
+
+set -e
+
+
+pkgctl_auth_status_usage() {
+	local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}}
+	cat <<- _EOF_
+		Usage: ${COMMAND} [OPTIONS]
+
+		Verifies and displays information about your authentication state of
+		services like the GitLab instance and reports issues if any.
+
+		OPTIONS
+		    -t, --show-token   Display the auth token
+		    -h, --help         Show this help text
+
+		EXAMPLES
+		    $ ${COMMAND}
+		    $ ${COMMAND} --show-token
+_EOF_
+}
+
+pkgctl_auth_status() {
+	local SHOW_TOKEN=0
+	# option checking
+	while (( $# )); do
+		case $1 in
+			-h|--help)
+				pkgctl_auth_status_usage
+				exit 0
+				;;
+			-t|--show-token)
+				SHOW_TOKEN=1
+				shift
+				;;
+			*)
+				die "invalid argument: %s" "$1"
+				;;
+		esac
+	done
+
+	printf "%s\n" "${BOLD}${GITLAB_HOST}${ALL_OFF}"
+	# shellcheck disable=2119
+	if ! username=$(gitlab_api_get_user); then
+		printf "%s\n" "${username}"
+		exit 1
+	fi
+
+	msg_success "  Logged in as ${BOLD}${username}${ALL_OFF}"
+	if (( SHOW_TOKEN )); then
+		msg_success "  Token: ${GITLAB_TOKEN}"
+	else
+		msg_success "  Token: **************************"
+	fi
+}
diff --git a/src/pkgctl.in b/src/pkgctl.in
index 35305c1..e024d1f 100644
--- a/src/pkgctl.in
+++ b/src/pkgctl.in
@@ -19,6 +19,7 @@ usage() {
 		Unified command-line frontend for devtools.
 
 		COMMANDS
+		    auth    Authenticate with services like GitLab
 		    diff    Compare package files using different modes
 		    repo    Manage Git packaging repositories and their configuration
 
@@ -51,6 +52,14 @@ while (( $# )); do
 			pkgctl_repo "$@"
 			exit 0
 			;;
+		auth)
+			_DEVTOOLS_COMMAND+=" $1"
+			shift
+			# shellcheck source=src/lib/auth.sh
+			source "${_DEVTOOLS_LIBRARY_DIR}"/lib/auth.sh
+			pkgctl_auth "$@"
+			exit 0
+			;;
 		diff)
 			_DEVTOOLS_COMMAND+=" $1"
 			shift
-- 
cgit v1.2.3-54-g00ecf


From f834fc4700053cb9a83956c98835b0a158cc054c Mon Sep 17 00:00:00 2001
From: Levente Polyak <anthraxx@archlinux.org>
Date: Sun, 11 Dec 2022 16:16:16 +0100
Subject: db: command for Pacman database modification like update, move etc

---
 contrib/completion/zsh/_devtools.in | 26 ++++++++++++
 doc/man/pkgctl-db-move.1.asciidoc   | 24 +++++++++++
 doc/man/pkgctl-db-remove.1.asciidoc | 26 ++++++++++++
 doc/man/pkgctl-db-update.1.asciidoc | 23 +++++++++++
 doc/man/pkgctl.1.asciidoc           |  4 ++
 src/lib/db.sh                       | 80 +++++++++++++++++++++++++++++++++++++
 src/lib/db/move.sh                  | 64 +++++++++++++++++++++++++++++
 src/lib/db/remove.sh                | 69 ++++++++++++++++++++++++++++++++
 src/lib/db/update.sh                | 46 +++++++++++++++++++++
 src/pkgctl.in                       |  9 +++++
 10 files changed, 371 insertions(+)
 create mode 100644 doc/man/pkgctl-db-move.1.asciidoc
 create mode 100644 doc/man/pkgctl-db-remove.1.asciidoc
 create mode 100644 doc/man/pkgctl-db-update.1.asciidoc
 create mode 100644 src/lib/db.sh
 create mode 100644 src/lib/db/move.sh
 create mode 100644 src/lib/db/remove.sh
 create mode 100644 src/lib/db/update.sh

(limited to 'doc/man/pkgctl.1.asciidoc')

diff --git a/contrib/completion/zsh/_devtools.in b/contrib/completion/zsh/_devtools.in
index 3395338..62d9fea 100644
--- a/contrib/completion/zsh/_devtools.in
+++ b/contrib/completion/zsh/_devtools.in
@@ -34,6 +34,31 @@ _pkgctl_auth_status_args=(
 	'(-h --help)'{-h,--help}'[Display usage]'
 )
 
+_pkgctl_db_cmds=(
+	"pkgctl db command"
+	"move[Move packages between pacman repositories]"
+	"remove[Remove packages from pacman repositories]"
+	"update[Update the pacman database as final release step]"
+)
+
+_pkgctl_db_move_args=(
+	'(-h --help)'{-h,--help}'[Display usage]'
+	"1:src-repo:($_repos[*])"
+	"2:target-repo:($_repos[*])"
+	'*:pkgbase:_devtools_completions_all_packages'
+)
+
+_pkgctl_db_remove_args=(
+	'(-a --arch=)'{-a,--arch=}"[Override the architecture (disables auto-detection)]:arch:($_arch[*])"
+	'(-h --help)'{-h,--help}'[Display usage]'
+	"1:repo:($_repos[*])"
+	'*:pkgbase:_devtools_completions_all_packages'
+)
+
+_pkgctl_db_update_args=(
+	'(-h --help)'{-h,--help}'[Display usage]'
+)
+
 _pkgctl_repo_cmds=(
 	"pkgctl repo command"
 	"clone[Clone a package repository]"
@@ -178,6 +203,7 @@ _devtools_completions_all_packages() {
 _pkgctl_cmds=(
 	"pkgctl command"
 	"auth[Authenticate with services like GitLab]"
+	"db[Pacman database modification for packge update, move etc]"
 	"diff[Compare package files using different modes]"
 	"repo[Manage Git packaging repositories and their configuration]"
 )
diff --git a/doc/man/pkgctl-db-move.1.asciidoc b/doc/man/pkgctl-db-move.1.asciidoc
new file mode 100644
index 0000000..1ee02f8
--- /dev/null
+++ b/doc/man/pkgctl-db-move.1.asciidoc
@@ -0,0 +1,24 @@
+pkgctl-db-move(1)
+=================
+
+Name
+----
+pkgctl-db-update - Update the binary repository as final release step
+
+Synopsis
+--------
+pkgctl db update [OPTIONS]
+
+Description
+-----------
+
+Update the pacman database as final release step for packages that
+have been transfered and staged on 'repos.archlinux.org'.
+
+Options
+-------
+
+*-h, --help*::
+	Show a help text
+
+include::include/footer.asciidoc[]
diff --git a/doc/man/pkgctl-db-remove.1.asciidoc b/doc/man/pkgctl-db-remove.1.asciidoc
new file mode 100644
index 0000000..a95766d
--- /dev/null
+++ b/doc/man/pkgctl-db-remove.1.asciidoc
@@ -0,0 +1,26 @@
+pkgctl-db-remove(1)
+===================
+
+Name
+----
+pkgctl-db-remove - Remove packages from binary repositories
+
+Synopsis
+--------
+pkgctl db remove [OPTIONS] [REPO] [PKGBASE]...
+
+Description
+-----------
+
+Remove packages from pacman repositories.
+
+Options
+-------
+
+*-a, --arch* 'ARCH'::
+	Override the architecture (disables auto-detection)
+
+*-h, --help*::
+	Show a help text
+
+include::include/footer.asciidoc[]
diff --git a/doc/man/pkgctl-db-update.1.asciidoc b/doc/man/pkgctl-db-update.1.asciidoc
new file mode 100644
index 0000000..fa7205e
--- /dev/null
+++ b/doc/man/pkgctl-db-update.1.asciidoc
@@ -0,0 +1,23 @@
+pkgctl-db-move(1)
+=================
+
+Name
+----
+pkgctl-db-move - Move packages between binary repositories
+
+Synopsis
+--------
+pkgctl db move [OPTIONS] [SOURCE_REPO] [TARGET_REPO] [PKGBASE]...
+
+Description
+-----------
+
+Move packages between pacman repositories.
+
+Options
+-------
+
+*-h, --help*::
+	Show a help text
+
+include::include/footer.asciidoc[]
diff --git a/doc/man/pkgctl.1.asciidoc b/doc/man/pkgctl.1.asciidoc
index 45d5187..93fed5b 100644
--- a/doc/man/pkgctl.1.asciidoc
+++ b/doc/man/pkgctl.1.asciidoc
@@ -20,6 +20,9 @@ Subcommands
 pkgctl auth::
 	Authenticate with services like GitLab
 
+pkgctl db::
+	Pacman database modification for packge update, move etc
+
 pkgctl diff::
 	Compare package files using different modes
 
@@ -30,6 +33,7 @@ See Also
 --------
 
 linkman:pkgctl-auth[1]
+linkman:pkgctl-db[1]
 linkman:pkgctl-diff[1]
 linkman:pkgctl-repo[1]
 
diff --git a/src/lib/db.sh b/src/lib/db.sh
new file mode 100644
index 0000000..397ff0d
--- /dev/null
+++ b/src/lib/db.sh
@@ -0,0 +1,80 @@
+#!/hint/bash
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+[[ -z ${DEVTOOLS_INCLUDE_DB_SH:-} ]] || return 0
+DEVTOOLS_INCLUDE_DB_SH=1
+
+_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@}
+
+set -e
+
+
+pkgctl_db_usage() {
+	local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}}
+	cat <<- _EOF_
+		Usage: ${COMMAND} [COMMAND] [OPTIONS]
+
+		Pacman database modification for packge update, move etc
+
+		COMMANDS
+		    move      Move packages between pacman repositories
+		    remove    Remove packages from pacman repositories
+		    update    Update the pacman database as final release step
+
+		OPTIONS
+		    -h, --help    Show this help text
+
+		EXAMPLES
+		    $ ${COMMAND} move extra-staging extra-testing libfoo libbar
+		    $ ${COMMAND} remove core-testing libfoo libbar
+		    $ ${COMMAND} update
+_EOF_
+}
+
+pkgctl_db() {
+	if (( $# < 1 )); then
+		pkgctl_db_usage
+		exit 0
+	fi
+
+	# option checking
+	while (( $# )); do
+		case $1 in
+			-h|--help)
+				pkgctl_db_usage
+				exit 0
+				;;
+			move)
+				_DEVTOOLS_COMMAND+=" $1"
+				shift
+				# shellcheck source=src/lib/db/move.sh
+				source "${_DEVTOOLS_LIBRARY_DIR}"/lib/db/move.sh
+				pkgctl_db_move "$@"
+				exit 0
+				;;
+			remove)
+				_DEVTOOLS_COMMAND+=" $1"
+				shift
+				# shellcheck source=src/lib/db/remove.sh
+				source "${_DEVTOOLS_LIBRARY_DIR}"/lib/db/remove.sh
+				pkgctl_db_remove "$@"
+				exit 0
+				;;
+			update)
+				_DEVTOOLS_COMMAND+=" $1"
+				shift
+				# shellcheck source=src/lib/db/update.sh
+				source "${_DEVTOOLS_LIBRARY_DIR}"/lib/db/update.sh
+				pkgctl_db_update "$@"
+				exit 0
+				;;
+			-*)
+				die "invalid argument: %s" "$1"
+				;;
+			*)
+				die "invalid command: %s" "$1"
+				;;
+		esac
+	done
+}
diff --git a/src/lib/db/move.sh b/src/lib/db/move.sh
new file mode 100644
index 0000000..825b350
--- /dev/null
+++ b/src/lib/db/move.sh
@@ -0,0 +1,64 @@
+#!/hint/bash
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+[[ -z ${DEVTOOLS_INCLUDE_DB_MOVE_SH:-} ]] || return 0
+DEVTOOLS_INCLUDE_DB_MOVE_SH=1
+
+_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@}
+# shellcheck source=src/lib/common.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh
+
+set -e
+
+
+pkgctl_db_move_usage() {
+	local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}}
+	cat <<- _EOF_
+		Usage: ${COMMAND} [OPTIONS] [SOURCE_REPO] [TARGET_REPO] [PKGBASE]...
+
+		Move packages between binary repositories.
+
+		OPTIONS
+		    -h, --help         Show this help text
+
+		EXAMPLES
+		    $ ${COMMAND} extra-staging extra-testing libfoo libbar
+		    $ ${COMMAND} extra core libfoo libbar
+_EOF_
+}
+
+pkgctl_db_move() {
+	local SOURCE_REPO=""
+	local TARGET_REPO=""
+	local PKGBASES=()
+
+	# option checking
+	while (( $# )); do
+		case $1 in
+			-h|--help)
+				pkgctl_db_move_usage
+				exit 0
+				;;
+			-*)
+				die "invalid argument: %s" "$1"
+				;;
+			*)
+				break
+				;;
+		esac
+	done
+
+	if (( $# < 3 )); then
+		pkgctl_db_move_usage
+		exit 1
+	fi
+
+	SOURCE_REPO=$1
+	TARGET_REPO=$2
+	shift 2
+	PKGBASES+=("$@")
+
+	# shellcheck disable=SC2029
+	ssh "${PACKAGING_REPO_RELEASE_HOST}" db-move "${SOURCE_REPO}" "${TARGET_REPO}" "${PKGBASES[@]}"
+}
diff --git a/src/lib/db/remove.sh b/src/lib/db/remove.sh
new file mode 100644
index 0000000..ba21c83
--- /dev/null
+++ b/src/lib/db/remove.sh
@@ -0,0 +1,69 @@
+#!/hint/bash
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+[[ -z ${DEVTOOLS_INCLUDE_DB_REMOVE_SH:-} ]] || return 0
+DEVTOOLS_INCLUDE_DB_REMOVE_SH=1
+
+_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@}
+# shellcheck source=src/lib/common.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh
+
+set -e
+
+
+pkgctl_db_remove_usage() {
+	local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}}
+	cat <<- _EOF_
+		Usage: ${COMMAND} [OPTIONS] [REPO] [PKGBASE]...
+
+		Remove packages from binary repositories.
+
+		OPTIONS
+		    -a, --arch    Override the architecture (disables auto-detection)
+		    -h, --help    Show this help text
+
+		EXAMPLES
+		    $ ${COMMAND} core-testing libfoo libbar
+		    $ ${COMMAND} --arch x86_64 core libyay
+_EOF_
+}
+
+pkgctl_db_remove() {
+	local REPO=""
+	local ARCH=any
+	local PKGBASES=()
+
+	# option checking
+	while (( $# )); do
+		case $1 in
+			-h|--help)
+				pkgctl_db_remove_usage
+				exit 0
+				;;
+			-a|--arch)
+				(( $# <= 1 )) && die "missing argument for %s" "$1"
+				ARCH=$2
+				shift 2
+				;;
+			-*)
+				die "invalid argument: %s" "$1"
+				;;
+			*)
+				break
+				;;
+		esac
+	done
+
+	if (( $# < 2 )); then
+		pkgctl_db_remove_usage
+		exit 1
+	fi
+
+	REPO=$1
+	shift
+	PKGBASES+=("$@")
+
+	# shellcheck disable=SC2029
+	ssh "${PACKAGING_REPO_RELEASE_HOST}" db-remove "${REPO}" "${ARCH}" "${PKGBASES[@]}"
+}
diff --git a/src/lib/db/update.sh b/src/lib/db/update.sh
new file mode 100644
index 0000000..269720d
--- /dev/null
+++ b/src/lib/db/update.sh
@@ -0,0 +1,46 @@
+#!/hint/bash
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+[[ -z ${DEVTOOLS_INCLUDE_DB_UPDATE_SH:-} ]] || return 0
+DEVTOOLS_INCLUDE_DB_UPDATE_SH=1
+
+_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@}
+# shellcheck source=src/lib/common.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh
+
+set -e
+
+
+pkgctl_db_update_usage() {
+	local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}}
+	cat <<- _EOF_
+		Usage: ${COMMAND} [OPTIONS]
+
+		Update the binary repository as final release step for packages that
+		have been transfered and staged on ${PACKAGING_REPO_RELEASE_HOST}.
+
+		OPTIONS
+		    -h, --help    Show this help text
+
+		EXAMPLES
+		    $ ${COMMAND}
+_EOF_
+}
+
+pkgctl_db_update() {
+	# option checking
+	while (( $# )); do
+		case $1 in
+			-h|--help)
+				pkgctl_db_update_usage
+				exit 0
+				;;
+			*)
+				die "invalid argument: %s" "$1"
+				;;
+		esac
+	done
+
+	ssh "${PACKAGING_REPO_RELEASE_HOST}" db-update
+}
diff --git a/src/pkgctl.in b/src/pkgctl.in
index e024d1f..d9e1b4c 100644
--- a/src/pkgctl.in
+++ b/src/pkgctl.in
@@ -20,6 +20,7 @@ usage() {
 
 		COMMANDS
 		    auth    Authenticate with services like GitLab
+		    db      Pacman database modification for packge update, move etc
 		    diff    Compare package files using different modes
 		    repo    Manage Git packaging repositories and their configuration
 
@@ -60,6 +61,14 @@ while (( $# )); do
 			pkgctl_auth "$@"
 			exit 0
 			;;
+		db)
+			_DEVTOOLS_COMMAND+=" $1"
+			shift
+			# shellcheck source=src/lib/auth.sh
+			source "${_DEVTOOLS_LIBRARY_DIR}"/lib/db.sh
+			pkgctl_db "$@"
+			exit 0
+			;;
 		diff)
 			_DEVTOOLS_COMMAND+=" $1"
 			shift
-- 
cgit v1.2.3-54-g00ecf


From 5752488ef114513c8f75d753cf91d5b61dfa0660 Mon Sep 17 00:00:00 2001
From: Levente Polyak <anthraxx@archlinux.org>
Date: Sat, 17 Dec 2022 01:51:15 +0100
Subject: release: command to commit, tag and upload build artifacts

This is a smart and more convenient invocation of the classical
commitpkg and archrelease with auto-discovery for target repositories
and a shorthand option to directly call db-update.
---
 contrib/completion/zsh/_devtools.in |  11 +++
 doc/man/pkgctl-release.1.asciidoc   |  49 +++++++++++
 doc/man/pkgctl.1.asciidoc           |   4 +
 src/lib/release.sh                  | 167 ++++++++++++++++++++++++++++++++++++
 src/lib/util/pacman.sh              |  52 +++++++++++
 src/pkgctl.in                       |   9 ++
 6 files changed, 292 insertions(+)
 create mode 100644 doc/man/pkgctl-release.1.asciidoc
 create mode 100644 src/lib/release.sh
 create mode 100644 src/lib/util/pacman.sh

(limited to 'doc/man/pkgctl.1.asciidoc')

diff --git a/contrib/completion/zsh/_devtools.in b/contrib/completion/zsh/_devtools.in
index 62d9fea..2961f6f 100644
--- a/contrib/completion/zsh/_devtools.in
+++ b/contrib/completion/zsh/_devtools.in
@@ -59,6 +59,16 @@ _pkgctl_db_update_args=(
 	'(-h --help)'{-h,--help}'[Display usage]'
 )
 
+_pkgctl_release_args=(
+	'(-m --message=)'{-m,--message=}"[Use the given <msg> as the commit message]:message:"
+	'(-r --repo=)'{-r,--repo=}"[Specify a target repository (disables auto-detection)]:repo:($_repos[*])"
+	'(-s --staging)'{-s,--staging}'[Release to the staging counterpart of the auto-detected repo]'
+	'(-t --testing)'{-t,--testing}'[Release to the testing counterpart of the auto-detected repo]'
+	'(-u --db-update)'{-u,--db-update}'[Automatically update the pacman database after uploading]'
+	'(-h --help)'{-h,--help}'[Display usage]'
+	'*:git_dir:_files -/'
+)
+
 _pkgctl_repo_cmds=(
 	"pkgctl repo command"
 	"clone[Clone a package repository]"
@@ -205,6 +215,7 @@ _pkgctl_cmds=(
 	"auth[Authenticate with services like GitLab]"
 	"db[Pacman database modification for packge update, move etc]"
 	"diff[Compare package files using different modes]"
+	"release[Release step to commit, tag and upload build artifacts]"
 	"repo[Manage Git packaging repositories and their configuration]"
 )
 
diff --git a/doc/man/pkgctl-release.1.asciidoc b/doc/man/pkgctl-release.1.asciidoc
new file mode 100644
index 0000000..c991db4
--- /dev/null
+++ b/doc/man/pkgctl-release.1.asciidoc
@@ -0,0 +1,49 @@
+pkgctl-release(1)
+=================
+
+Name
+----
+pkgctl-release - Release step to commit, tag and upload build artifacts
+
+Synopsis
+--------
+pkgctl release [OPTIONS] [PATH...]
+
+Description
+-----------
+
+Modified version controlled files will first be staged for commit,
+afterwards a Git tag matching the pkgver will be created and finally
+all build artifacts will be uploaded.
+
+By default the target pacman repository will be auto-detected by querying
+the repo it is currently released in. When initially adding a new package
+to the repositories, the target repo must be specified manually.
+
+Options
+-------
+
+*-m, --message* 'MSG'::
+	Use the given <msg> as the commit message
+
+*-r, --repo* 'REPO'::
+	Specify a target repository (disables auto-detection)
+
+*-s, --staging*::
+	Build against the staging counterpart of the auto-detected repo
+
+*-t, --testing*::
+	Build against the testing counterpart of the auto-detected repo
+
+*-u, --db-update*::
+	Automatically update the pacman database after uploading
+
+*-h, --help*::
+	Show a help text
+
+See Also
+--------
+
+linkman:pkgctl-db-update[1]
+
+include::include/footer.asciidoc[]
diff --git a/doc/man/pkgctl.1.asciidoc b/doc/man/pkgctl.1.asciidoc
index 93fed5b..8da5d19 100644
--- a/doc/man/pkgctl.1.asciidoc
+++ b/doc/man/pkgctl.1.asciidoc
@@ -26,6 +26,9 @@ pkgctl db::
 pkgctl diff::
 	Compare package files using different modes
 
+pkgctl release::
+	Release step to commit, tag and upload build artifacts
+
 pkgctl repo::
 	Manage Git packaging repositories and their configuration
 
@@ -35,6 +38,7 @@ See Also
 linkman:pkgctl-auth[1]
 linkman:pkgctl-db[1]
 linkman:pkgctl-diff[1]
+linkman:pkgctl-release[1]
 linkman:pkgctl-repo[1]
 
 include::include/footer.asciidoc[]
diff --git a/src/lib/release.sh b/src/lib/release.sh
new file mode 100644
index 0000000..aabbd35
--- /dev/null
+++ b/src/lib/release.sh
@@ -0,0 +1,167 @@
+#!/hint/bash
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+[[ -z ${DEVTOOLS_INCLUDE_RELEASE_SH:-} ]] || return 0
+DEVTOOLS_INCLUDE_RELEASE_SH=1
+
+_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@}
+# shellcheck source=src/lib/db/update.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/db/update.sh
+# shellcheck source=src/lib/util/pacman.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/util/pacman.sh
+# shellcheck source=src/lib/valid-repos.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/valid-repos.sh
+
+source /usr/share/makepkg/util/util.sh
+
+set -e
+
+
+pkgctl_release_usage() {
+	local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}}
+	cat <<- _EOF_
+		Usage: ${COMMAND} [OPTIONS] [PATH]...
+
+		Release step to commit, tag and upload build artifacts
+
+		Modified version controlled files will first be staged for commit,
+		afterwards a Git tag matching the pkgver will be created and finally
+		all build artifacts will be uploaded.
+
+		By default the target pacman repository will be auto-detected by querying
+		the repo it is currently released in. When initially adding a new package
+		to the repositories, the target repo must be specified manually.
+
+		OPTIONS
+		    -m, --message MSG   Use the given <msg> as the commit message
+		    -r, --repo REPO     Specify a target repository (disables auto-detection)
+		    -s, --staging       Release to the staging counterpart of the auto-detected repo
+		    -t, --testing       Release to the testing counterpart of the auto-detected repo
+		    -u, --db-update     Automatically update the pacman database after uploading
+		    -h, --help          Show this help text
+
+		EXAMPLES
+		    $ ${COMMAND}
+		    $ ${COMMAND} --repo core-testing --message 'libyay 0.42 rebuild' libfoo libbar
+		    $ ${COMMAND} --staging --db-update libfoo
+_EOF_
+}
+
+pkgctl_release_check_option_group() {
+	local option=$1
+	local repo=$2
+	local testing=$3
+	local staging=$4
+	if [[ -n "${repo}" ]] || (( testing )) || (( staging )); then
+		die "The argument '%s' cannot be used with one or more of the other specified arguments" "${option}"
+		exit 1
+	fi
+	return 0
+}
+
+pkgctl_release() {
+	if (( $# < 1 )) && [[ ! -f PKGBUILD ]]; then
+		pkgctl_release_usage
+		exit 1
+	fi
+
+	local MESSAGE=""
+	local PKGBASES=()
+	local REPO=""
+	local TESTING=0
+	local STAGING=0
+	local DB_UPDATE=0
+
+	local path pkgbase pkgnames repo repos
+
+	# option checking
+	while (( $# )); do
+		case $1 in
+			-h|--help)
+				pkgctl_release_usage
+				exit 0
+				;;
+			-m|--message)
+				(( $# <= 1 )) && die "missing argument for %s" "$1"
+				MESSAGE=$2
+				shift 2
+				;;
+			-r|--repo)
+				(( $# <= 1 )) && die "missing argument for %s" "$1"
+				pkgctl_release_check_option_group '--repo' "${REPO}" "${TESTING}" "${STAGING}"
+				REPO=$2
+				shift 2
+				;;
+			-s|--staging)
+				pkgctl_release_check_option_group '--staging' "${REPO}" "${TESTING}" "${STAGING}"
+				STAGING=1
+				shift
+				;;
+			-t|--testing)
+				pkgctl_release_check_option_group '--testing' "${REPO}" "${TESTING}" "${STAGING}"
+				TESTING=1
+				shift
+				;;
+			-u|--db-update)
+				DB_UPDATE=1
+				shift
+				;;
+			-*)
+				die "invalid option: %s" "$1"
+				;;
+			*)
+				PKGBASES+=("$@")
+				break
+				;;
+		esac
+	done
+
+	# Resolve package from current working directory
+	if (( 0 == ${#PKGBASES[@]} )); then
+		PKGBASES=("$PWD")
+	fi
+
+	# Update pacman cache for auto-detection
+	if [[ -z ${REPO} ]]; then
+		update_pacman_repo_cache
+	# Check valid repos if not resolved dynamically
+	elif ! in_array "${REPO}" "${_repos[@]}"; then
+		die "Invalid repository target: %s" "${REPO}"
+	fi
+
+	for path in "${PKGBASES[@]}"; do
+		pushd "${path}" >/dev/null
+		pkgbase=$(basename "${path}")
+
+		if [[ -n ${REPO} ]]; then
+			repo=${REPO}
+		else
+			if ! repo=$(get_pacman_repo_from_pkgbuild PKGBUILD); then
+				die 'Failed to get pacman repo'
+			fi
+			if [[ -z "${repo}" ]]; then
+				die 'Unknown repo, please specify --repo for new packages'
+			fi
+		fi
+
+		if (( TESTING )); then
+			repo="${repo}-testing"
+		elif (( STAGING )); then
+			repo="${repo}-staging"
+		elif [[ $repo == core ]]; then
+			repo="${repo}-testing"
+		fi
+
+		msg "Releasing ${pkgbase} to ${repo}"
+		commitpkg "${repo}" "${MESSAGE}"
+
+		unset repo
+		popd >/dev/null
+	done
+
+	if (( DB_UPDATE )); then
+		# shellcheck disable=2119
+		pkgctl_db_update
+	fi
+}
diff --git a/src/lib/util/pacman.sh b/src/lib/util/pacman.sh
new file mode 100644
index 0000000..f6c2d5f
--- /dev/null
+++ b/src/lib/util/pacman.sh
@@ -0,0 +1,52 @@
+#!/hint/bash
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+[[ -z ${DEVTOOLS_INCLUDE_UTIL_PACMAN_SH:-} ]] || return 0
+DEVTOOLS_INCLUDE_UTIL_PACMAN_SH=1
+
+_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@}
+# shellcheck source=src/lib/common.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh
+
+set -e
+
+
+readonly _DEVTOOLS_PACMAN_CACHE_DIR=${XDG_CACHE_DIR:-$HOME/.cache}/devtools/pacman/db
+readonly _DEVTOOLS_PACMAN_CONF_DIR=${_DEVTOOLS_LIBRARY_DIR}/pacman.conf.d
+readonly _DEVTOOLS_MAKEPKG_CONF_DIR=${_DEVTOOLS_LIBRARY_DIR}/makepkg.conf.d
+
+
+update_pacman_repo_cache() {
+	mkdir -p "${_DEVTOOLS_PACMAN_CACHE_DIR}"
+	msg "Updating pacman database cache"
+	lock 10 "${_DEVTOOLS_PACMAN_CACHE_DIR}.lock" "Locking pacman database cache"
+	fakeroot -- pacman --config "${_DEVTOOLS_PACMAN_CONF_DIR}/multilib.conf" \
+		--dbpath "${_DEVTOOLS_PACMAN_CACHE_DIR}" \
+		-Sy
+	lock_close 10
+}
+
+get_pacman_repo_from_pkgbuild() {
+	local path=${1:-PKGBUILD}
+
+	# shellcheck source=contrib/makepkg/PKGBUILD.proto
+	mapfile -t pkgnames < <(source "${path}"; printf "%s\n" "${pkgname[@]}")
+
+	if (( ${#pkgnames[@]} == 0 )); then
+		die 'Failed to get pkgname from %s' "${path}"
+		return
+	fi
+
+	slock 10 "${_DEVTOOLS_PACMAN_CACHE_DIR}.lock" "Locking pacman database cache"
+	mapfile -t repos < <(pacman --config "${_DEVTOOLS_PACMAN_CONF_DIR}/multilib.conf" \
+		--dbpath "${_DEVTOOLS_PACMAN_CACHE_DIR}" \
+		-S \
+		--print \
+		--print-format '%n %r' \
+		"${pkgnames[0]}" | grep -E "^${pkgnames[0]} " | awk '{print $2}'
+	)
+	lock_close 10
+
+	printf "%s" "${repos[0]}"
+}
diff --git a/src/pkgctl.in b/src/pkgctl.in
index d9e1b4c..47409de 100644
--- a/src/pkgctl.in
+++ b/src/pkgctl.in
@@ -22,6 +22,7 @@ usage() {
 		    auth    Authenticate with services like GitLab
 		    db      Pacman database modification for packge update, move etc
 		    diff    Compare package files using different modes
+		    release Release step to commit, tag and upload build artifacts
 		    repo    Manage Git packaging repositories and their configuration
 
 		OPTIONS
@@ -75,6 +76,14 @@ while (( $# )); do
 			diffpkg "$@"
 			exit 0
 			;;
+		release)
+			_DEVTOOLS_COMMAND+=" $1"
+			shift
+			# shellcheck source=src/lib/release.sh
+			source "${_DEVTOOLS_LIBRARY_DIR}"/lib/release.sh
+			pkgctl_release "$@"
+			exit 0
+			;;
 		*)
 			die "invalid command: %s" "$1"
 			;;
-- 
cgit v1.2.3-54-g00ecf


From 1d7f9972151b0a52297880c8f9e5f28a1d7fe597 Mon Sep 17 00:00:00 2001
From: Levente Polyak <anthraxx@archlinux.org>
Date: Sat, 14 Jan 2023 18:20:29 +0100
Subject: build: command to build packages inside a clean chroot

---
 .gitignore                          |   2 +-
 contrib/completion/zsh/_devtools.in |  20 ++
 doc/man/pkgctl-build.1.asciidoc     |  80 ++++++++
 doc/man/pkgctl.1.asciidoc           |   4 +
 src/lib/build/build.sh              | 387 ++++++++++++++++++++++++++++++++++++
 src/lib/util/git.sh                 |  24 +++
 src/pkgctl.in                       |   9 +
 7 files changed, 525 insertions(+), 1 deletion(-)
 create mode 100644 doc/man/pkgctl-build.1.asciidoc
 create mode 100644 src/lib/build/build.sh
 create mode 100644 src/lib/util/git.sh

(limited to 'doc/man/pkgctl.1.asciidoc')

diff --git a/.gitignore b/.gitignore
index f5d2d27..3e34a0c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,3 @@
 *~
 devtools-*.tar.gz*
-build/
+/build/
diff --git a/contrib/completion/zsh/_devtools.in b/contrib/completion/zsh/_devtools.in
index 2961f6f..c743667 100644
--- a/contrib/completion/zsh/_devtools.in
+++ b/contrib/completion/zsh/_devtools.in
@@ -34,6 +34,25 @@ _pkgctl_auth_status_args=(
 	'(-h --help)'{-h,--help}'[Display usage]'
 )
 
+_pkgctl_build_args=(
+	"--arch=[Specify architectures to build for (disables auto-detection)]:arch:($_arch[*])"
+	"--repo=[Specify a target repository (disables auto-detection)]:repo:($_repos[*])"
+	'(-s --staging)'{-s,--staging}'[Build against the staging counterpart of the auto-detected repo]'
+	'(-t --testing)'{-t,--testing}'[Build against the testing counterpart of the auto-detected repo]'
+	'(-o --offload)'{-o,--offload}'[Build on a remote server and transfer artifacts afterwards]'
+	'(-c --clean)'{-c,--clean}'[Recreate the chroot before building]'
+	'(-I --install)'{-I,--install}'[Install a package into the working copy of the chroot]:target:_files -g "*.pkg.tar.*(.)"'
+	'--pkgver=[Set pkgver, reset pkgrel and update checksums]:pkgver:'
+	'--pkgrel=[Set pkgrel to a given value]:pkgrel:'
+	'--rebuild[Increment the pkgrel variable]'
+	'(-e --edit)'{-e,--edit}'[Edit the PKGBUILD before building]'
+	'(-r --release)'{-r,--release}'[Automatically commit, tag and release after building]'
+	'(-m --message=)'{-m,--message=}"[Use the given <msg> as the commit message]:message:"
+	'(-u --db-update)'{-u,--db-update}'[Automatically update the pacman database as last action]'
+	'(-h --help)'{-h,--help}'[Display usage]'
+	'*:git_dir:_files -/'
+)
+
 _pkgctl_db_cmds=(
 	"pkgctl db command"
 	"move[Move packages between pacman repositories]"
@@ -213,6 +232,7 @@ _devtools_completions_all_packages() {
 _pkgctl_cmds=(
 	"pkgctl command"
 	"auth[Authenticate with services like GitLab]"
+	"build[Build packages inside a clean chroot]"
 	"db[Pacman database modification for packge update, move etc]"
 	"diff[Compare package files using different modes]"
 	"release[Release step to commit, tag and upload build artifacts]"
diff --git a/doc/man/pkgctl-build.1.asciidoc b/doc/man/pkgctl-build.1.asciidoc
new file mode 100644
index 0000000..132c0a4
--- /dev/null
+++ b/doc/man/pkgctl-build.1.asciidoc
@@ -0,0 +1,80 @@
+pkgctl-build(1)
+===============
+
+Name
+----
+pkgctl-build - Build packages inside a clean chroot
+
+Synopsis
+--------
+pkgctl build [OPTIONS] [PATH...]
+
+Description
+-----------
+
+TODO
+
+Build Options
+-------------
+
+*--arch* 'ARCH'::
+	Specify architectures to build for (disables auto-detection)
+
+*--repo* 'REPO'::
+	Specify a target repository (disables auto-detection)
+
+*-s, --staging*::
+	Build against the staging counterpart of the auto-detected repo
+
+*-t, --testing*::
+	Build against the testing counterpart of the auto-detected repo
+
+*-o, --offload*::
+	Build on a remote server and transfer artifacts afterwards
+
+*-c, --clean*::
+	Recreate the chroot before building
+
+*-I, --install* 'FILE'::
+	Install a package into the working copy of the chroot
+
+PKGBUILD Options
+----------------
+
+*--pkgver*='PKGVER'::
+	Set pkgver, reset pkgrel and update checksums
+
+*--pkgrel*='PKGREL'::
+	Set pkgrel to a given value
+
+*--rebuild*::
+	Increment the current pkgrel variable
+
+*-e, --edit*::
+	Edit the PKGBUILD before building
+
+Release Options
+---------------
+
+*-r, --release*::
+	Automatically commit, tag and release after building
+
+*-m, --message* 'MSG'::
+	Use the given <msg> as the commit message
+
+*-u, --db-update*::
+	Automatically update the pacman database as last action
+
+Options
+-------
+
+*-h, --help*::
+	Show a help text
+
+See Also
+--------
+
+linkman:pkgctl-release[1]
+linkman:pkgctl-db-update[1]
+
+include::include/footer.asciidoc[]
diff --git a/doc/man/pkgctl.1.asciidoc b/doc/man/pkgctl.1.asciidoc
index 8da5d19..c5a6174 100644
--- a/doc/man/pkgctl.1.asciidoc
+++ b/doc/man/pkgctl.1.asciidoc
@@ -20,6 +20,9 @@ Subcommands
 pkgctl auth::
 	Authenticate with services like GitLab
 
+pkgctl build::
+	Build packages inside a clean chroot
+
 pkgctl db::
 	Pacman database modification for packge update, move etc
 
@@ -36,6 +39,7 @@ See Also
 --------
 
 linkman:pkgctl-auth[1]
+linkman:pkgctl-build[1]
 linkman:pkgctl-db[1]
 linkman:pkgctl-diff[1]
 linkman:pkgctl-release[1]
diff --git a/src/lib/build/build.sh b/src/lib/build/build.sh
new file mode 100644
index 0000000..e32c79f
--- /dev/null
+++ b/src/lib/build/build.sh
@@ -0,0 +1,387 @@
+#!/hint/bash
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+[[ -z ${DEVTOOLS_INCLUDE_BUILD_SH:-} ]] || return 0
+DEVTOOLS_INCLUDE_BUILD_SH=1
+
+_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@}
+# shellcheck source=src/lib/common.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh
+# shellcheck source=src/lib/db/update.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/db/update.sh
+# shellcheck source=src/lib/release.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/release.sh
+# shellcheck source=src/lib/util/git.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/util/git.sh
+# shellcheck source=src/lib/util/pacman.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/util/pacman.sh
+# shellcheck source=src/lib/valid-repos.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/valid-repos.sh
+# shellcheck source=src/lib/valid-tags.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/valid-tags.sh
+
+source /usr/share/makepkg/util/config.sh
+source /usr/share/makepkg/util/message.sh
+
+set -e
+
+
+pkgctl_build_usage() {
+	local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}}
+    cat <<- _EOF_
+		Usage: ${COMMAND} [OPTIONS] [PATH]...
+
+		Build packages inside a clean chroot
+
+		When a new pkgver is set using the appropriate PKGBUILD options the
+		checksums are automatically updated.
+
+		TODO
+
+		BUILD OPTIONS
+		    --arch ARCH          Specify architectures to build for (disables auto-detection)
+		    --repo REPO          Specify a target repository (disables auto-detection)
+		    -s, --staging        Build against the staging counterpart of the auto-detected repo
+		    -t, --testing        Build against the testing counterpart of the auto-detected repo
+		    -o, --offload        Build on a remote server and transfer artifacts afterwards
+		    -c, --clean          Recreate the chroot before building
+		    -I, --install FILE   Install a package into the working copy of the chroot
+
+		PKGBUILD OPTIONS
+		    --pkgver=PKGVER      Set pkgver, reset pkgrel and update checksums
+		    --pkgrel=PKGREL      Set pkgrel to a given value
+		    --rebuild            Increment the current pkgrel variable
+		    -e, --edit           Edit the PKGBUILD before building
+
+		RELEASE OPTIONS
+		    -r, --release        Automatically commit, tag and release after building
+		    -m, --message MSG    Use the given <msg> as the commit message
+		    -u, --db-update      Automatically update the pacman database as last action
+
+		OPTIONS
+		    -h, --help           Show this help text
+
+		EXAMPLES
+		    $ ${COMMAND}
+		    $ ${COMMAND} --rebuild --staging --message 'libyay 0.42 rebuild' libfoo libbar
+		    $ ${COMMAND} --pkgver 1.42 --release --db-update
+_EOF_
+}
+
+pkgctl_build_check_option_group_repo() {
+	local option=$1
+	local repo=$2
+	local testing=$3
+	local staging=$4
+	if ( (( testing )) && (( staging )) ) ||
+		( [[ $repo =~ ^.*-(staging|testing)$ ]] && ( (( testing )) || (( staging )) )); then
+		die "The argument '%s' cannot be used with one or more of the other specified arguments" "${option}"
+		exit 1
+	fi
+	return 0
+}
+
+pkgctl_build_check_option_group_ver() {
+	local option=$1
+	local pkgver=$2
+	local pkgrel=$3
+	local rebuild=$4
+	if [[ -n "${pkgver}" ]] || [[ -n "${pkgrel}" ]] || (( rebuild )); then
+		die "The argument '%s' cannot be used with one or more of the other specified arguments" "${option}"
+		exit 1
+	fi
+	return 0
+}
+
+# TODO: import pgp keys
+pkgctl_build() {
+	if (( $# < 1 )) && [[ ! -f PKGBUILD ]]; then
+		pkgctl_build_usage
+		exit 1
+	fi
+
+	local UPDPKGSUMS=0
+	local EDIT=0
+	local REBUILD=0
+	local OFFLOAD=0
+	local STAGING=0
+	local TESTING=0
+	local RELEASE=0
+	local DB_UPDATE=0
+
+	local REPO=
+	local PKGVER=
+	local PKGREL=
+	local MESSAGE=
+
+	local paths=()
+	local BUILD_ARCH=()
+	local BUILD_OPTIONS=()
+	local MAKECHROOT_OPTIONS=()
+	local RELEASE_OPTIONS=()
+
+	local PTS
+	PTS="$(tty | sed 's|/dev/pts/||')"
+	local WORKER="${USER}-${PTS}"
+
+	# variables
+	local path pkgbase pkgrepo source
+
+	while (( $# )); do
+		case $1 in
+			-h|--help)
+				pkgctl_build_usage
+				exit 0
+				;;
+			--repo)
+				(( $# <= 1 )) && die "missing argument for %s" "$1"
+				REPO="${2}"
+				pkgctl_build_check_option_group_repo '--repo' "${REPO}" "${TESTING}" "${STAGING}"
+				shift 2
+				;;
+			--arch)
+				(( $# <= 1 )) && die "missing argument for %s" "$1"
+				if [[ ${2} == all ]]; then
+					BUILD_ARCH=("${_arch[@]::${#_arch[@]}-1}")
+				elif [[ ${2} == any ]]; then
+					BUILD_ARCH=("${_arch[0]}")
+				elif ! in_array "${2}" "${BUILD_ARCH[@]}"; then
+					if ! in_array "${2}" "${_arch[@]}"; then
+						die 'invalid architecture: %s' "${2}"
+					fi
+					BUILD_ARCH+=("${2}")
+				fi
+				shift 2
+				;;
+			--pkgver=*)
+				pkgctl_build_check_option_group_ver '--pkgver' "${PKGVER}" "${PKGREL}" "${REBUILD}"
+				PKGVER="${1#*=}"
+				PKGREL=1
+				UPDPKGSUMS=1
+				shift
+				;;
+			--pkgrel=*)
+				pkgctl_build_check_option_group_ver '--pkgrel' "${PKGVER}" "${PKGREL}" "${REBUILD}"
+				PKGREL="${1#*=}"
+				shift
+				;;
+			--rebuild)
+				# shellcheck source=src/lib/util/git.sh
+				source "${_DEVTOOLS_LIBRARY_DIR}"/lib/util/git.sh
+				pkgctl_build_check_option_group_ver '--rebuild' "${PKGVER}" "${PKGREL}" "${REBUILD}"
+				REBUILD=1
+				shift
+				;;
+			-e|--edit)
+				EDIT=1
+				shift
+				;;
+			-o|--offload)
+				OFFLOAD=1
+				shift
+				;;
+			-s|--staging)
+				STAGING=1
+				pkgctl_build_check_option_group_repo '--staging' "${REPO}" "${TESTING}" "${STAGING}"
+				shift
+				;;
+			-t|--testing)
+				TESTING=1
+				pkgctl_build_check_option_group_repo '--testing' "${REPO}" "${TESTING}" "${STAGING}"
+				shift
+				;;
+			-c|--clean)
+				BUILD_OPTIONS+=("-c")
+				shift
+				;;
+			-I|--install)
+				(( $# <= 1 )) && die "missing argument for %s" "$1"
+				MAKECHROOT_OPTIONS+=("-I" "$2")
+				warning 'installing packages into the chroot may break reproducible builds, use with caution!'
+				shift 2
+				;;
+			-r|--release)
+				# shellcheck source=src/lib/release.sh
+				source "${_DEVTOOLS_LIBRARY_DIR}"/lib/release.sh
+				RELEASE=1
+				shift
+				;;
+			-m|--message)
+				(( $# <= 1 )) && die "missing argument for %s" "$1"
+				MESSAGE=$2
+				RELEASE_OPTIONS+=("--message" "${MESSAGE}")
+				shift 2
+				;;
+			-u|--db-update)
+				DB_UPDATE=1
+				shift
+				;;
+			--)
+				shift
+				break
+				;;
+			-*)
+				die "invalid argument: %s" "$1"
+				;;
+			*)
+				paths=("$@")
+				break
+				;;
+		esac
+	done
+
+	# check if invoked without any path from within a packaging repo
+	if (( ${#paths[@]} == 0 )); then
+		if [[ -f PKGBUILD ]]; then
+			paths=(".")
+		else
+			pkgctl_build_usage
+			exit 1
+		fi
+	fi
+
+	# Update pacman cache for auto-detection
+	if [[ -z ${REPO} ]]; then
+		update_pacman_repo_cache
+	# Check valid repos if not resolved dynamically
+	elif ! in_array "${REPO}" "${_repos[@]}"; then
+		die "Invalid repository target: %s" "${REPO}"
+	fi
+
+	for path in "${paths[@]}"; do
+		pushd "${path}" >/dev/null
+
+		if [[ ! -f PKGBUILD ]]; then
+			die 'PKGBUILD not found in %s' "${path}"
+		fi
+
+		source=()
+		# shellcheck source=contrib/makepkg/PKGBUILD.proto
+		. ./PKGBUILD
+		pkgbase=${pkgbase:-$pkgname}
+		pkgrepo=${REPO}
+		msg "Building ${pkgbase}"
+
+		# auto-detection of build target
+		if [[ -z ${pkgrepo} ]]; then
+			if ! pkgrepo=$(get_pacman_repo_from_pkgbuild PKGBUILD); then
+				die 'failed to get pacman repo'
+			fi
+			if [[ -z "${pkgrepo}" ]]; then
+				die 'unknown repo, please specify --repo for new packages'
+			fi
+		fi
+
+		# special cases to resolve final build target
+		if (( TESTING )); then
+			pkgrepo="${pkgrepo}-testing"
+		elif (( STAGING )); then
+			pkgrepo="${pkgrepo}-staging"
+		elif [[ $pkgrepo == core ]]; then
+			pkgrepo="${pkgrepo}-testing"
+		fi
+
+		# auto-detection of build architecture
+		if [[ $pkgrepo = multilib* ]]; then
+			BUILD_ARCH=("")
+		elif (( ${#BUILD_ARCH[@]} == 0 )); then
+			if in_array any "${arch[@]}"; then
+				BUILD_ARCH=("${_arch[0]}")
+			else
+				BUILD_ARCH+=("${arch[@]}")
+			fi
+		fi
+
+		# print gathered build modes
+		msg2 "repo: ${pkgrepo}"
+		msg2 "arch: ${BUILD_ARCH[*]}"
+
+		# increment pkgrel on rebuild
+		if (( REBUILD )); then
+			# try to figure out of pkgrel has been changed
+			if ! old_pkgrel=$(git_diff_tree HEAD PKGBUILD | grep --perl-regexp --only-matching --max-count=1 '^-pkgrel=\K\w+'); then
+				old_pkgrel=${pkgrel}
+			fi
+			# check if pkgrel conforms expectations
+			[[ ${pkgrel/.*} =~ ^[0-9]+$ ]] || die "Non-standard pkgrel declaration"
+			[[ ${old_pkgrel/.*} =~ ^[0-9]+$ ]] || die "Non-standard pkgrel declaration"
+			# increment pkgrel if it hasn't been changed yet
+			if [[ ${pkgrel} = "${old_pkgrel}" ]]; then
+				PKGREL=$((${pkgrel/.*}+1))
+			else
+				warning 'ignoring --rebuild as pkgrel has already been incremented from %s to %s' "${old_pkgrel}" "${pkgrel}"
+			fi
+		fi
+
+		# update pkgver
+		if [[ -n ${PKGVER} ]]; then
+			if [[ $(type -t pkgver) == function ]]; then
+				# TODO: check if die or warn, if we provide _commit _gitcommit setter maybe?
+				warning 'setting pkgver variable has no effect if the PKGBUILD has a pkgver() function'
+			fi
+			msg "Bumping pkgver to ${PKGVER}"
+			grep --extended-regexp --quiet --max-count=1 "^pkgver=${pkgver}$" PKGBUILD || die "Non-standard pkgver declaration"
+			sed --regexp-extended "s|^(pkgver=)${pkgver}$|\1${PKGVER}|g" -i PKGBUILD
+		fi
+
+		# update pkgrel
+		if [[ -n ${PKGREL} ]]; then
+			msg "Bumping pkgrel to ${PKGREL}"
+			grep --extended-regexp --quiet --max-count=1 "^pkgrel=${pkgrel}$" PKGBUILD || die "Non-standard pkgrel declaration"
+			sed --regexp-extended "s|^(pkgrel=)${pkgrel}$|\1${PKGREL}|g" -i PKGBUILD
+		fi
+
+		# edit PKGBUILD
+		if (( EDIT )); then
+			stat_busy 'Editing PKGBUILD'
+			if [[ -n $VISUAL ]]; then
+				$VISUAL PKGBUILD || die
+			elif [[ -n $EDITOR ]]; then
+				$EDITOR PKGBUILD || die
+			elif command -v vi &>/dev/null; then
+				vi PKGBUILD || die
+			else
+				die "need \$VISUAL or \$EDITOR to edit the PKGBUILD"
+			fi
+			stat_done
+		fi
+
+		# update checksums if any sources are declared
+		if (( UPDPKGSUMS )) && (( ${#source[@]} >= 1 )); then
+			updpkgsums
+		fi
+
+		# execute build
+		for arch in "${BUILD_ARCH[@]}"; do
+			if [[ -n $arch ]]; then
+				msg "Building ${pkgbase} for [${pkgrepo}] (${arch})"
+				BUILDTOOL="${pkgrepo}-${arch}-build"
+			else
+				msg "Building ${pkgbase} for [${pkgrepo}]"
+				BUILDTOOL="${pkgrepo}-build"
+			fi
+
+			if (( OFFLOAD )); then
+				offload-build --repo "${pkgrepo}" -- "${BUILD_OPTIONS[@]}" -- "${MAKECHROOT_OPTIONS[@]}" -l "${WORKER}"
+			else
+				"${BUILDTOOL}" "${BUILD_OPTIONS[@]}" -- "${MAKECHROOT_OPTIONS[@]}" -l "${WORKER}"
+			fi
+		done
+
+		# release the build
+		if (( RELEASE )); then
+			pkgctl_release --repo "${pkgrepo}" "${RELEASE_OPTIONS[@]}"
+		fi
+
+		# reset common PKGBUILD variables
+		unset pkgbase pkgname arch pkgrepo source pkgver pkgrel validpgpkeys
+		popd >/dev/null
+	done
+
+	# update the binary package repo db as last action
+	if (( RELEASE )) && (( DB_UPDATE )); then
+		# shellcheck disable=2119
+		pkgctl_db_update
+	fi
+}
diff --git a/src/lib/util/git.sh b/src/lib/util/git.sh
new file mode 100644
index 0000000..c4af662
--- /dev/null
+++ b/src/lib/util/git.sh
@@ -0,0 +1,24 @@
+#!/hint/bash
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+[[ -z ${DEVTOOLS_INCLUDE_UTIL_GIT_SH:-} ]] || return 0
+DEVTOOLS_INCLUDE_UTIL_GIT_SH=1
+
+_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@}
+
+
+git_diff_tree() {
+	local commit=$1
+	local path=$2
+	git \
+		--no-pager \
+		diff \
+		--color=never \
+		--color-moved=no \
+		--unified=0 \
+		--no-prefix \
+		--no-ext-diff \
+		"${commit}" \
+		-- "${path}"
+}
diff --git a/src/pkgctl.in b/src/pkgctl.in
index 47409de..40f9259 100644
--- a/src/pkgctl.in
+++ b/src/pkgctl.in
@@ -20,6 +20,7 @@ usage() {
 
 		COMMANDS
 		    auth    Authenticate with services like GitLab
+		    build   Build packages inside a clean chroot
 		    db      Pacman database modification for packge update, move etc
 		    diff    Compare package files using different modes
 		    release Release step to commit, tag and upload build artifacts
@@ -46,6 +47,14 @@ while (( $# )); do
 			usage
 			exit 0
 			;;
+		build)
+			_DEVTOOLS_COMMAND+=" $1"
+			shift
+			# shellcheck source=src/lib/build/build.sh
+			source "${_DEVTOOLS_LIBRARY_DIR}"/lib/build/build.sh
+			pkgctl_build "$@"
+			exit 0
+			;;
 		repo)
 			_DEVTOOLS_COMMAND+=" $1"
 			shift
-- 
cgit v1.2.3-54-g00ecf


From 645a5a9f047ada2fd76d6149ed24aa888547a52d Mon Sep 17 00:00:00 2001
From: Christian Heusel <christian@heusel.eu>
Date: Thu, 13 Apr 2023 17:44:48 +0200
Subject: pkgctl: introduce the version subcommand

Related to https://gitlab.archlinux.org/archlinux/devtools/-/issues/125

Closes #125

Signed-off-by: Christian Heusel <christian@heusel.eu>
Co-Authored-By: Levente Polyak <anthraxx@archlinux.org>
---
 contrib/completion/zsh/_devtools.in |  6 +++++
 doc/man/pkgctl-version.1.asciidoc   | 23 ++++++++++++++++++
 doc/man/pkgctl.1.asciidoc           | 13 ++++++++++
 src/lib/version/version.sh          | 47 +++++++++++++++++++++++++++++++++++++
 src/pkgctl.in                       |  9 +++++++
 5 files changed, 98 insertions(+)
 create mode 100644 doc/man/pkgctl-version.1.asciidoc
 create mode 100644 src/lib/version/version.sh

(limited to 'doc/man/pkgctl.1.asciidoc')

diff --git a/contrib/completion/zsh/_devtools.in b/contrib/completion/zsh/_devtools.in
index cc8d5cf..20c37ce 100644
--- a/contrib/completion/zsh/_devtools.in
+++ b/contrib/completion/zsh/_devtools.in
@@ -231,9 +231,15 @@ _pkgctl_cmds=(
 	"diff[Compare package files using different modes]"
 	"release[Release step to commit, tag and upload build artifacts]"
 	"repo[Manage Git packaging repositories and their configuration]"
+	"version[Show pkgctl version information]"
 )
 
 _pkgctl_args=(
+	'(-V --version)'{-V,--version}'[Show pkgctl version information]'
+	'(-h --help)'{-h,--help}'[Display usage]'
+)
+
+_pkgctl_version_args=(
 	'(-h --help)'{-h,--help}'[Display usage]'
 )
 
diff --git a/doc/man/pkgctl-version.1.asciidoc b/doc/man/pkgctl-version.1.asciidoc
new file mode 100644
index 0000000..9beebf5
--- /dev/null
+++ b/doc/man/pkgctl-version.1.asciidoc
@@ -0,0 +1,23 @@
+pkgctl-version(1)
+=================
+
+Name
+----
+pkgctl-version - Show pkgctl version information
+
+Synopsis
+--------
+pkgctl version [OPTIONS]
+
+Description
+-----------
+
+Shows the current version information of pkgctl.
+
+Options
+-------
+
+*-h, --help*::
+	Show a help text
+
+include::include/footer.asciidoc[]
diff --git a/doc/man/pkgctl.1.asciidoc b/doc/man/pkgctl.1.asciidoc
index c5a6174..72cda1e 100644
--- a/doc/man/pkgctl.1.asciidoc
+++ b/doc/man/pkgctl.1.asciidoc
@@ -14,6 +14,15 @@ Description
 
 TODO
 
+Options
+-------
+
+*-V, --version*::
+	Show pkgctl version information
+
+*-h, --help*::
+	Show a help text
+
 Subcommands
 -----------
 
@@ -35,6 +44,9 @@ pkgctl release::
 pkgctl repo::
 	Manage Git packaging repositories and their configuration
 
+pkgctl version::
+	Show pkgctl version information
+
 See Also
 --------
 
@@ -44,5 +56,6 @@ linkman:pkgctl-db[1]
 linkman:pkgctl-diff[1]
 linkman:pkgctl-release[1]
 linkman:pkgctl-repo[1]
+linkman:pkgctl-version[1]
 
 include::include/footer.asciidoc[]
diff --git a/src/lib/version/version.sh b/src/lib/version/version.sh
new file mode 100644
index 0000000..d00a460
--- /dev/null
+++ b/src/lib/version/version.sh
@@ -0,0 +1,47 @@
+#!/hint/bash
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+[[ -z ${DEVTOOLS_INCLUDE_VERSION_SH:-} ]] || return 0
+DEVTOOLS_INCLUDE_VERSION_SH=1
+
+_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@}
+
+source /usr/share/makepkg/util/message.sh
+
+set -e
+
+
+pkgctl_version_usage() {
+	local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}}
+    cat <<- _EOF_
+		Usage: ${COMMAND} [OPTIONS]
+
+		Shows the current version information of pkgctl
+
+		OPTIONS
+		    -h, --help    Show this help text
+_EOF_
+}
+
+pkgctl_version_print() {
+	cat <<- _EOF_
+		pkgctl @buildtoolver@
+_EOF_
+}
+
+pkgctl_version() {
+	while (( $# )); do
+		case $1 in
+			-h|--help)
+				pkgctl_version_usage
+				exit 0
+				;;
+			*)
+				die "invalid argument: %s" "$1"
+				;;
+		esac
+	done
+
+	pkgctl_version_print
+}
diff --git a/src/pkgctl.in b/src/pkgctl.in
index 40f9259..1797b32 100644
--- a/src/pkgctl.in
+++ b/src/pkgctl.in
@@ -25,6 +25,7 @@ usage() {
 		    diff    Compare package files using different modes
 		    release Release step to commit, tag and upload build artifacts
 		    repo    Manage Git packaging repositories and their configuration
+		    version Show pkgctl version information
 
 		OPTIONS
 		    -h, --help     Show this help text
@@ -93,6 +94,14 @@ while (( $# )); do
 			pkgctl_release "$@"
 			exit 0
 			;;
+		version|--version|-V)
+			_DEVTOOLS_COMMAND+=" $1"
+			shift
+			# shellcheck source=src/lib/version/version.sh
+			source "${_DEVTOOLS_LIBRARY_DIR}"/lib/version/version.sh
+			pkgctl_version "$@"
+			exit 0
+			;;
 		*)
 			die "invalid command: %s" "$1"
 			;;
-- 
cgit v1.2.3-54-g00ecf

