Send patches - preferably formatted by git format-patch - to patches at archlinux32 dot org.
summaryrefslogtreecommitdiff
path: root/src/lib/repo
diff options
context:
space:
mode:
authorAndreas Baumann <mail@andreasbaumann.cc>2023-05-29 16:05:59 +0200
committerAndreas Baumann <mail@andreasbaumann.cc>2023-05-29 16:05:59 +0200
commitbe8ac63f28a402acbf7e21972ac5d3b45bed1f79 (patch)
tree6eb83c09f6549ae41988a25243a2b424a4f99a4e /src/lib/repo
parentbfc22eea7e4c6877fe8b9d89fa574cb0729466db (diff)
parenta07df0beeaeea1bf5665512bacc7a013eece4602 (diff)
merged with devtools 1.0.1 upstream (git repo migration)
Diffstat (limited to 'src/lib/repo')
-rw-r--r--src/lib/repo/clone.sh199
-rw-r--r--src/lib/repo/configure.sh259
-rw-r--r--src/lib/repo/create.sh113
-rw-r--r--src/lib/repo/switch.sh119
-rw-r--r--src/lib/repo/web.sh84
5 files changed, 774 insertions, 0 deletions
diff --git a/src/lib/repo/clone.sh b/src/lib/repo/clone.sh
new file mode 100644
index 0000000..08bded4
--- /dev/null
+++ b/src/lib/repo/clone.sh
@@ -0,0 +1,199 @@
+#!/bin/bash
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+[[ -z ${DEVTOOLS_INCLUDE_REPO_CLONE_SH:-} ]] || return 0
+DEVTOOLS_INCLUDE_REPO_CLONE_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
+# shellcheck source=src/lib/repo/configure.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/repo/configure.sh
+
+source /usr/share/makepkg/util/message.sh
+
+set -e
+
+
+pkgctl_repo_clone_usage() {
+ local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}}
+ cat <<- _EOF_
+ Usage: ${COMMAND} [OPTIONS] [PKGBASE]...
+
+ Clone Git packaging repositories from the canonical namespace.
+
+ The configure command is subsequently invoked to synchronize the distro
+ specs and makepkg.conf settings. The protocol option can be used
+ for cloning packaging repositories without SSH access using read-only
+ HTTPS.
+
+ OPTIONS
+ -m, --maintainer=NAME Clone all packages of the named maintainer
+ --protocol https Clone the repository over https
+ --switch VERSION Switch the current working tree to a specified version
+ --universe Clone all existing packages, useful for cache warming
+ -j, --jobs N Run up to N jobs in parallel (default: $(nproc))
+ -h, --help Show this help text
+
+ EXAMPLES
+ $ ${COMMAND} libfoo linux libbar
+ $ ${COMMAND} --maintainer mynickname
+ $ ${COMMAND} --switch 1:1.0-2 libfoo
+_EOF_
+}
+
+pkgctl_repo_clone() {
+ if (( $# < 1 )); then
+ pkgctl_repo_clone_usage
+ exit 0
+ fi
+
+ # options
+ local GIT_REPO_BASE_URL=${GIT_PACKAGING_URL_SSH}
+ local CLONE_ALL=0
+ local MAINTAINER=
+ local VERSION=
+ local CONFIGURE_OPTIONS=()
+ local jobs=
+ jobs=$(nproc)
+
+ # variables
+ local command=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}}
+ local project_path
+
+ while (( $# )); do
+ case $1 in
+ -h|--help)
+ pkgctl_repo_clone_usage
+ exit 0
+ ;;
+ --protocol=https)
+ GIT_REPO_BASE_URL=${GIT_PACKAGING_URL_HTTPS}
+ CONFIGURE_OPTIONS+=("$1")
+ shift
+ ;;
+ --protocol)
+ (( $# <= 1 )) && die "missing argument for %s" "$1"
+ if [[ $2 == https ]]; then
+ GIT_REPO_BASE_URL=${GIT_PACKAGING_URL_HTTPS}
+ else
+ die "unsupported protocol: %s" "$2"
+ fi
+ CONFIGURE_OPTIONS+=("$1" "$2")
+ shift 2
+ ;;
+ -m|--maintainer)
+ (( $# <= 1 )) && die "missing argument for %s" "$1"
+ MAINTAINER="$2"
+ shift 2
+ ;;
+ --maintainer=*)
+ MAINTAINER="${1#*=}"
+ shift
+ ;;
+ --switch)
+ (( $# <= 1 )) && die "missing argument for %s" "$1"
+ # shellcheck source=src/lib/repo/switch.sh
+ source "${_DEVTOOLS_LIBRARY_DIR}"/lib/repo/switch.sh
+ VERSION="$2"
+ shift 2
+ ;;
+ --switch=*)
+ # shellcheck source=src/lib/repo/switch.sh
+ source "${_DEVTOOLS_LIBRARY_DIR}"/lib/repo/switch.sh
+ VERSION="${1#*=}"
+ shift
+ ;;
+ --universe)
+ CLONE_ALL=1
+ shift
+ ;;
+ -j|--jobs)
+ (( $# <= 1 )) && die "missing argument for %s" "$1"
+ jobs=$2
+ shift 2
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*)
+ die "invalid argument: %s" "$1"
+ ;;
+ *)
+ pkgbases=("$@")
+ break
+ ;;
+ esac
+ done
+
+ # Query packages of a maintainer
+ if [[ -n ${MAINTAINER} ]]; then
+ stat_busy "Query packages"
+ max_pages=$(curl --silent --location --fail --retry 3 --retry-delay 3 "https://archlinux.org/packages/search/json/?sort=name&maintainer=${MAINTAINER}" | jq -r '.num_pages')
+ if [[ ! ${max_pages} =~ ([[:digit:]]) ]]; then
+ stat_done
+ warning "found no packages for maintainer ${MAINTAINER}"
+ exit 0
+ fi
+ mapfile -t pkgbases < <(for page in $(seq "${max_pages}"); do
+ curl --silent --location --fail --retry 3 --retry-delay 3 "https://archlinux.org/packages/search/json/?sort=name&maintainer=${MAINTAINER}&page=${page}" | jq -r '.results[].pkgbase'
+ stat_progress
+ done | sort --unique)
+ stat_done
+ fi
+
+ # Query all released packages
+ if (( CLONE_ALL )); then
+ stat_busy "Query all released packages"
+ max_pages=$(curl --silent --location --fail --retry 3 --retry-delay 3 "https://archlinux.org/packages/search/json/?sort=name" | jq -r '.num_pages')
+ if [[ ! ${max_pages} =~ ([[:digit:]]) ]]; then
+ stat_done
+ die "failed to query packages"
+ fi
+ mapfile -t pkgbases < <(for page in $(seq "${max_pages}"); do
+ curl --silent --location --fail --retry 3 --retry-delay 3 "https://archlinux.org/packages/search/json/?sort=name&page=${page}" | jq -r '.results[].pkgbase'
+ stat_progress
+ done | sort --unique)
+ stat_done
+ fi
+
+ # parallelization
+ if [[ ${jobs} != 1 ]] && (( ${#pkgbases[@]} > 1 )); then
+ # force colors in parallel if parent process is colorized
+ if [[ -n ${BOLD} ]]; then
+ export DEVTOOLS_COLOR=always
+ fi
+ # assign command options
+ if [[ -n "${VERSION}" ]]; then
+ command+=" --switch '${VERSION}'"
+ fi
+ if ! parallel --bar --jobs "${jobs}" "${command}" ::: "${pkgbases[@]}"; then
+ die 'Failed to clone some packages, please check the output'
+ exit 1
+ fi
+ exit 0
+ fi
+
+ for pkgbase in "${pkgbases[@]}"; do
+ if [[ ! -d ${pkgbase} ]]; then
+ msg "Cloning ${pkgbase} ..."
+ project_path=$(gitlab_project_name_to_path "${pkgbase}")
+ remote_url="${GIT_REPO_BASE_URL}/${project_path}.git"
+ if ! git clone --origin origin "${remote_url}" "${pkgbase}"; then
+ die 'failed to clone %s' "${pkgbase}"
+ fi
+ else
+ warning "Skip cloning ${pkgbase}: Directory exists"
+ fi
+
+ pkgctl_repo_configure "${CONFIGURE_OPTIONS[@]}" "${pkgbase}"
+
+ if [[ -n "${VERSION}" ]]; then
+ pkgctl_repo_switch "${VERSION}" "${pkgbase}"
+ fi
+ done
+}
diff --git a/src/lib/repo/configure.sh b/src/lib/repo/configure.sh
new file mode 100644
index 0000000..73300ae
--- /dev/null
+++ b/src/lib/repo/configure.sh
@@ -0,0 +1,259 @@
+#!/bin/bash
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+[[ -z ${DEVTOOLS_INCLUDE_REPO_CONFIGURE_SH:-} ]] || return 0
+DEVTOOLS_INCLUDE_REPO_CONFIGURE_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
+
+source /usr/share/makepkg/util/config.sh
+source /usr/share/makepkg/util/message.sh
+
+set -e
+
+
+pkgctl_repo_configure_usage() {
+ local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}}
+ cat <<- _EOF_
+ Usage: ${COMMAND} [OPTIONS] [PATH]...
+
+ Configure Git packaging repositories according to distro specs and
+ makepkg.conf settings.
+
+ Git author information and the used signing key is set up from
+ makepkg.conf read from any valid location like /etc or XDG_CONFIG_HOME.
+
+ The remote protocol is automatically determined from the author email
+ address by choosing SSH for all official packager identities and
+ read-only HTTPS otherwise.
+
+ OPTIONS
+ --protocol https Configure remote url to use https
+ -j, --jobs N Run up to N jobs in parallel (default: $(nproc))
+ -h, --help Show this help text
+
+ EXAMPLES
+ $ ${COMMAND} *
+_EOF_
+}
+
+get_packager_name() {
+ local packager=$1
+ local packager_pattern="(.+) <(.+@.+)>"
+ local name
+
+ if [[ ! $packager =~ $packager_pattern ]]; then
+ return 1
+ fi
+
+ name=$(echo "${packager}"|sed -E "s/${packager_pattern}/\1/")
+ printf "%s" "${name}"
+}
+
+get_packager_email() {
+ local packager=$1
+ local packager_pattern="(.+) <(.+@.+)>"
+ local email
+
+ if [[ ! $packager =~ $packager_pattern ]]; then
+ return 1
+ fi
+
+ email=$(echo "${packager}"|sed -E "s/${packager_pattern}/\2/")
+ printf "%s" "${email}"
+}
+
+is_packager_name_valid() {
+ local packager_name=$1
+ if [[ -z ${packager_name} ]]; then
+ return 1
+ elif [[ ${packager_name} == "John Doe" ]]; then
+ return 1
+ elif [[ ${packager_name} == "Unknown Packager" ]]; then
+ return 1
+ fi
+ return 0
+}
+
+is_packager_email_official() {
+ local packager_email=$1
+ if [[ -z ${packager_email} ]]; then
+ return 1
+ elif [[ $packager_email =~ .+@archlinux.org ]]; then
+ return 0
+ fi
+ return 1
+}
+
+pkgctl_repo_configure() {
+ # options
+ local GIT_REPO_BASE_URL=${GIT_PACKAGING_URL_HTTPS}
+ local official=0
+ local proto=https
+ local proto_force=0
+ local jobs=
+ jobs=$(nproc)
+ local paths=()
+
+ # variables
+ local -r command=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}}
+ local path realpath pkgbase remote_url project_path
+ local PACKAGER GPGKEY packager_name packager_email
+
+ while (( $# )); do
+ case $1 in
+ -h|--help)
+ pkgctl_repo_configure_usage
+ exit 0
+ ;;
+ --protocol=https)
+ proto_force=1
+ shift
+ ;;
+ --protocol)
+ (( $# <= 1 )) && die "missing argument for %s" "$1"
+ if [[ $2 == https ]]; then
+ proto_force=1
+ else
+ die "unsupported protocol: %s" "$2"
+ fi
+ shift 2
+ ;;
+ -j|--jobs)
+ (( $# <= 1 )) && die "missing argument for %s" "$1"
+ jobs=$2
+ shift 2
+ ;;
+ --)
+ 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_repo_configure_usage
+ exit 1
+ fi
+ fi
+
+ # Load makepkg.conf variables to be available for packager identity
+ msg "Collecting packager identity from makepkg.conf"
+ # shellcheck disable=2119
+ load_makepkg_config
+ if [[ -n ${PACKAGER} ]]; then
+ if ! packager_name=$(get_packager_name "${PACKAGER}") || \
+ ! packager_email=$(get_packager_email "${PACKAGER}"); then
+ die "invalid PACKAGER format '${PACKAGER}' in makepkg.conf"
+ fi
+ if ! is_packager_name_valid "${packager_name}"; then
+ die "invalid PACKAGER '${PACKAGER}' in makepkg.conf"
+ fi
+ if is_packager_email_official "${packager_email}"; then
+ official=1
+ if (( ! proto_force )); then
+ proto=ssh
+ GIT_REPO_BASE_URL=${GIT_PACKAGING_URL_SSH}
+ fi
+ fi
+ fi
+
+ msg2 "name : ${packager_name:-${YELLOW}undefined${ALL_OFF}}"
+ msg2 "email : ${packager_email:-${YELLOW}undefined${ALL_OFF}}"
+ msg2 "gpg-key : ${GPGKEY:-${YELLOW}undefined${ALL_OFF}}"
+ if [[ ${proto} == ssh ]]; then
+ msg2 "protocol: ${GREEN}${proto}${ALL_OFF}"
+ else
+ msg2 "protocol: ${YELLOW}${proto}${ALL_OFF}"
+ fi
+
+ # parallelization
+ if [[ ${jobs} != 1 ]] && (( ${#paths[@]} > 1 )); then
+ if [[ -n ${BOLD} ]]; then
+ export DEVTOOLS_COLOR=always
+ fi
+ if ! parallel --bar --jobs "${jobs}" "${command}" ::: "${paths[@]}"; then
+ die 'Failed to configure some packages, please check the output'
+ exit 1
+ fi
+ exit 0
+ fi
+
+ for path in "${paths[@]}"; do
+ if ! realpath=$(realpath -e "${path}"); then
+ die "No such directory: ${path}"
+ fi
+
+ pkgbase=$(basename "${realpath}")
+ pkgbase=${pkgbase%.git}
+ msg "Configuring ${pkgbase}"
+
+ if [[ ! -d "${path}/.git" ]]; then
+ die "Not a Git repository: ${path}"
+ fi
+
+ pushd "${path}" >/dev/null
+
+ project_path=$(gitlab_project_name_to_path "${pkgbase}")
+ remote_url="${GIT_REPO_BASE_URL}/${project_path}.git"
+ if ! git remote add origin "${remote_url}" &>/dev/null; then
+ git remote set-url origin "${remote_url}"
+ fi
+
+ # move the master branch to main
+ if [[ $(git symbolic-ref --quiet --short HEAD) == master ]]; then
+ git branch --move main
+ git config branch.main.merge refs/heads/main
+ fi
+
+ git config devtools.version "${GIT_REPO_SPEC_VERSION}"
+ git config pull.rebase true
+ git config branch.autoSetupRebase always
+ git config branch.main.remote origin
+ git config branch.main.rebase true
+
+ git config transfer.fsckobjects true
+ git config fetch.fsckobjects true
+ git config receive.fsckobjects true
+
+ # setup author identity
+ if [[ -n ${packager_name} ]]; then
+ git config user.name "${packager_name}"
+ git config user.email "${packager_email}"
+ fi
+
+ # force gpg for official packagers
+ if (( official )); then
+ git config commit.gpgsign true
+ fi
+
+ # set custom pgp key from makepkg.conf
+ if [[ -n $GPGKEY ]]; then
+ git config commit.gpgsign true
+ git config user.signingKey "${GPGKEY}"
+ fi
+
+ if ! git ls-remote origin &>/dev/null; then
+ warning "configured remote origin may not exist, run:"
+ msg2 "pkgctl repo create ${pkgbase}"
+ fi
+
+ popd >/dev/null
+ done
+}
diff --git a/src/lib/repo/create.sh b/src/lib/repo/create.sh
new file mode 100644
index 0000000..31b46e1
--- /dev/null
+++ b/src/lib/repo/create.sh
@@ -0,0 +1,113 @@
+#!/bin/bash
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+[[ -z ${DEVTOOLS_INCLUDE_REPO_CREATE_SH:-} ]] || return 0
+DEVTOOLS_INCLUDE_REPO_CREATE_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
+# shellcheck source=src/lib/repo/clone.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/repo/clone.sh
+# shellcheck source=src/lib/repo/configure.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/repo/configure.sh
+
+set -e
+
+
+pkgctl_repo_create_usage() {
+ local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}}
+ cat <<- _EOF_
+ Usage: ${COMMAND} [OPTIONS] [PKGBASE]...
+
+ Create a new Git packaging repository in the canonical GitLab namespace.
+
+ This command requires a valid GitLab API authentication. To setup a new
+ GitLab token or check the currently configured one please consult the
+ 'auth' subcommand for further instructions.
+
+ If invoked without a parameter, try to create a packaging repository
+ based on the PKGBUILD from the current working directory and configure
+ the local repository afterwards.
+
+ OPTIONS
+ -c, --clone Clone the Git repository after creation
+ -h, --help Show this help text
+
+ EXAMPLES
+ $ ${COMMAND} libfoo
+_EOF_
+}
+
+pkgctl_repo_create() {
+ # options
+ local pkgbases=()
+ local pkgbase
+ local clone=0
+ local configure=0
+
+ # variables
+ local path
+
+ while (( $# )); do
+ case $1 in
+ -h|--help)
+ pkgctl_repo_create_usage
+ exit 0
+ ;;
+ -c|--clone)
+ clone=1
+ shift
+ ;;
+ -*)
+ die "invalid argument: %s" "$1"
+ ;;
+ *)
+ pkgbases=("$@")
+ break
+ ;;
+ esac
+ done
+
+ # check if invoked without any path from within a packaging repo
+ if (( ${#pkgbases[@]} == 0 )); then
+ if [[ -f PKGBUILD ]]; then
+ if ! path=$(realpath -e .); then
+ die "failed to read path from current directory"
+ fi
+ pkgbases=("$(basename "${path}")")
+ clone=0
+ configure=1
+ else
+ pkgctl_repo_create_usage
+ exit 1
+ fi
+ fi
+
+ # create projects
+ for pkgbase in "${pkgbases[@]}"; do
+ if ! gitlab_api_create_project "${pkgbase}" >/dev/null; then
+ die "failed to create project: ${pkgbase}"
+ fi
+ msg_success "Successfully created ${pkgbase}"
+ if (( clone )); then
+ pkgctl_repo_clone "${pkgbase}"
+ elif (( configure )); then
+ pkgctl_repo_configure
+ fi
+ done
+
+ # some convenience hints if not in auto clone/configure mode
+ if (( ! clone )) && (( ! configure )); then
+ cat <<- _EOF_
+
+ For new clones:
+ $(msg2 "pkgctl repo clone ${pkgbases[*]}")
+ For existing clones:
+ $(msg2 "pkgctl repo configure ${pkgbases[*]}")
+ _EOF_
+ fi
+}
diff --git a/src/lib/repo/switch.sh b/src/lib/repo/switch.sh
new file mode 100644
index 0000000..f411ac2
--- /dev/null
+++ b/src/lib/repo/switch.sh
@@ -0,0 +1,119 @@
+#!/bin/bash
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+[[ -z ${DEVTOOLS_INCLUDE_REPO_SWITCH_SH:-} ]] || return 0
+DEVTOOLS_INCLUDE_REPO_SWITCH_SH=1
+
+_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@}
+# shellcheck source=src/lib/common.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh
+
+source /usr/share/makepkg/util/message.sh
+
+set -e
+
+
+pkgctl_repo_switch_usage() {
+ local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}}
+ cat <<- _EOF_
+ Usage: ${COMMAND} [OPTIONS] [VERSION] [PKGBASE]...
+
+ Switch a package source repository to a specified version, tag or
+ branch. The working tree and the index are updated to match the
+ specified ref.
+
+ If a version identifier is specified in the pacman version format, that
+ identifier is automatically translated to the Git tag name accordingly.
+
+ The current working directory is used if no PKGBASE is specified.
+
+ OPTIONS
+ --discard-changes Discard changes if index or working tree is dirty
+ -f, --force An alias for --discard-changes
+ -h, --help Show this help text
+
+ EXAMPLES
+ $ ${COMMAND} 1.14.6-1 gopass gopass-jsonapi
+ $ ${COMMAND} --force 2:1.19.5-1
+ $ ${COMMAND} main
+_EOF_
+}
+
+pkgctl_repo_switch() {
+ if (( $# < 1 )); then
+ pkgctl_repo_switch_usage
+ exit 0
+ fi
+
+ # options
+ local VERSION
+ local GIT_REF
+ local GIT_CHECKOUT_OPTIONS=()
+ local paths path realpath pkgbase
+
+ while (( $# )); do
+ case $1 in
+ -h|--help)
+ pkgctl_repo_switch_usage
+ exit 0
+ ;;
+ -f|--force|--discard-changes)
+ GIT_CHECKOUT_OPTIONS+=("--force")
+ shift
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*)
+ # - is special to switch back to previous version
+ if [[ $1 != - ]]; then
+ die "invalid argument: %s" "$1"
+ fi
+ ;;&
+ *)
+ if [[ -n ${VERSION} ]]; then
+ break
+ fi
+ VERSION=$1
+ shift
+ ;;
+ esac
+ done
+
+ if [[ -z ${VERSION} ]]; then
+ error "missing positional argument 'VERSION'"
+ pkgctl_repo_switch_usage
+ exit 1
+ fi
+
+ GIT_REF="$(get_tag_from_pkgver "${VERSION}")"
+ paths=("$@")
+
+ # check if invoked without any path from within a packaging repo
+ if (( ${#paths[@]} == 0 )); then
+ if [[ -f PKGBUILD ]]; then
+ paths=(".")
+ else
+ die "Not a package repository: $(realpath -- .)"
+ fi
+ fi
+
+ for path in "${paths[@]}"; do
+ if ! realpath=$(realpath -e -- "${path}"); then
+ die "No such directory: ${path}"
+ fi
+ pkgbase=$(basename "${realpath}")
+
+ if [[ ! -d "${path}/.git" ]]; then
+ error "Not a Git repository: ${path}"
+ continue
+ fi
+
+ if ! git -C "${path}" checkout "${GIT_CHECKOUT_OPTIONS[@]}" "${GIT_REF}"; then
+ die "Failed to switch ${pkgbase} to version ${VERSION}"
+ fi
+ msg "Successfully switched ${pkgbase} to version ${VERSION}"
+ done
+}
diff --git a/src/lib/repo/web.sh b/src/lib/repo/web.sh
new file mode 100644
index 0000000..45ea53b
--- /dev/null
+++ b/src/lib/repo/web.sh
@@ -0,0 +1,84 @@
+#!/bin/bash
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+[[ -z ${DEVTOOLS_INCLUDE_REPO_WEB_SH:-} ]] || return 0
+DEVTOOLS_INCLUDE_REPO_WEB_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_repo_web_usage() {
+ local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}}
+ cat <<- _EOF_
+ Usage: ${COMMAND} [OPTIONS] [PKGBASE]...
+
+ Open the packaging repository's website via xdg-open. If called with
+ no arguments, open the package cloned in the current working directory.
+
+ OPTIONS
+ -h, --help Show this help text
+
+ EXAMPLES
+ $ ${COMMAND} linux
+_EOF_
+}
+
+pkgctl_repo_web() {
+ local pkgbases=()
+ local path giturl pkgbase
+
+ # option checking
+ while (( $# )); do
+ case $1 in
+ -h|--help)
+ pkgctl_repo_web_usage
+ exit 0
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*)
+ die "invalid argument: %s" "$1"
+ ;;
+ *)
+ pkgbases=("$@")
+ break
+ ;;
+ esac
+ done
+
+ # Check if web mode has xdg-open
+ if ! command -v xdg-open &>/dev/null; then
+ die "The web command requires 'xdg-open'"
+ fi
+
+ # Check if used without pkgnames in a packaging directory
+ if (( ! $# )); then
+ path=${PWD}
+ if [[ ! -d "${path}/.git" ]]; then
+ die "Not a Git repository: ${path}"
+ fi
+
+ giturl=$(git -C "${path}" remote get-url origin)
+ if [[ ${giturl} != *${GIT_PACKAGING_NAMESPACE}* ]]; then
+ die "Not a packaging repository: ${path}"
+ fi
+
+ pkgbase=$(basename "${giturl}")
+ pkgbase=${pkgbase%.git}
+ pkgbases=("${pkgbase}")
+ fi
+
+ for pkgbase in "${pkgbases[@]}"; do
+ path=$(gitlab_project_name_to_path "${pkgbase}")
+ xdg-open "${GIT_PACKAGING_URL_HTTPS}/${path}"
+ done
+}