summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Makefile49
-rw-r--r--README.md28
-rw-r--r--asp.in246
-rw-r--r--man/asp.1.txt92
-rw-r--r--package.inc.sh148
-rw-r--r--remote.inc.sh50
7 files changed, 615 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e930cc4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+asp
+asp.1
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..18f6786
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,49 @@
+PACKAGE_NAME = asp
+VER=0
+
+PREFIX = /usr/local
+
+BINPROGS = \
+ asp
+
+MANPAGES = \
+ man/asp.1
+
+INCLUDES = \
+ package.inc.sh \
+ remote.inc.sh
+
+all: $(BINPROGS) $(MANPAGES)
+
+V_GEN = $(_v_GEN_$(V))
+_v_GEN_ = $(_v_GEN_0)
+_v_GEN_0 = @echo " GEN " $@;
+
+edit = $(V_GEN) m4 -P $@.in >$@ && chmod go-w,+x $@
+
+%: %.in $(INCLUDES)
+ $(edit)
+
+doc: $(MANPAGES)
+man/%: man/%.txt Makefile
+ a2x -d manpage \
+ -f manpage \
+ -a manversion=$(VERSION) \
+ -a manmanual="$(PACKAGE_NAME) manual" $<
+
+clean:
+ $(RM) $(BINPROGS) $(MANPAGES)
+
+install: all
+ install -dm755 $(DESTDIR)$(PREFIX)/bin
+ install -m755 $(BINPROGS) $(DESTDIR)$(PREFIX)/bin
+
+uninstall:
+ for f in $(BINPROGS); do $(RM) $(DESTDIR)$(PREFIX)/bin/$$f; done
+ $(RM) $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_archinstallscripts
+
+dist:
+ git archive --format=tar --prefix=$(PACKAGE_NAME)-$(VER)/ v$(VER) | gzip -9 > $(PACKAGE_NAME)-$(VER).tar.gz
+ gpg --detach-sign --use-agent $(PACKAGE_NAME)-$(VER).tar.gz
+
+.PHONY: all clean install uninstall dist
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..262e58b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,28 @@
+# asp
+
+`asp` is a tool to manage the build source files used to create Arch Linux
+packages. It attempts to replace the `abs` tool, offering more up to date
+sources (via the svntogit repositories) and uses a sparse checkout model to
+conserve diskspace. This probably won't be interesting to users who want a
+full checkout (for whatever reason that may be).
+
+# Setup
+
+None! Though, it should be noted that the **ASPROOT** environment variable
+will control where `asp` keeps its locally tracked packages. By default, this
+is `$HOME/asp`.
+
+# Examples
+
+Get the source files for some packages:
+
+~~~
+asp export pacman testing/systemd extra/pkgfile
+~~~
+
+List the repositories a package has been pushed to:
+
+~~~
+asp list-repos pacman
+~~~
+
diff --git a/asp.in b/asp.in
new file mode 100644
index 0000000..5393823
--- /dev/null
+++ b/asp.in
@@ -0,0 +1,246 @@
+#!/bin/bash
+
+ARCH_GIT_REPOS=(packages community)
+
+OPT_ARCH=$(uname -m)
+OPT_FORCE=0
+: ${ASPROOT:=$HOME/asp}
+
+m4_include(remote.inc.sh)
+m4_include(package.inc.sh)
+
+log_meta() {
+ printf "$1 $2\n" "${@:3}"
+}
+
+log_error() {
+ log_meta 'error:' "$@" >&2
+}
+
+log_fatal() {
+ log_error "$@"
+ exit 1
+}
+
+log_warning() {
+ log_meta 'warning:' "$@" >&2
+}
+
+log_info() {
+ log_meta '==>' "$@"
+}
+
+map() {
+ local map_r=0
+ for _ in "${@:2}"; do
+ "$1" "$_" || (( $# > 255 ? map_r=1 : ++r ))
+ done
+ return $map_r
+}
+
+usage() {
+ cat<<EOF
+asp [OPTIONS...] {COMMAND} ...
+
+Manage build sources for Arch packages.
+
+Options:
+ -a ARCH Specify an architecture other than the host's
+ -f Allow files to be overwritten
+ -h Show this help
+ -V Show package version
+
+Commands:
+ difflog NAME Show revision history with diffs
+ export NAME... Export packages
+ gc Cleanup and optimize the local repository
+ disk-usage Show amount of disk used by locally tracked packages
+ help Show this help
+ list-all List all known packages
+ list-arches NAME... List architectures for packages
+ list-local List tracked packages
+ list-repos NAME... List repos for packages
+ log NAME Show revision history
+ shortlog NAME Show revision history in short form
+ update [NAME...] Update packages (update all tracked if none specified)
+EOF
+}
+
+version() {
+ printf 'asp v0\n'
+}
+
+update() {
+ local r
+
+ for r in "${ARCH_GIT_REPOS[@]}"; do
+ log_info "updating remote '%s'" "$r"
+ remote_update "$r"
+ done
+}
+
+update_packages() {
+ local refspecs=() remote pkgname
+ declare -A refspec_map
+
+ for pkgname; do
+ package_init -n "$pkgname" remote || return 1
+ refspec_map["$remote"]+=" packages/$pkgname"
+ done
+
+ for remote in "${!refspec_map[@]}"; do
+ read -ra refspecs <<<"${refspec_map["$remote"]}"
+ remote_update_refs "$remote" "${refspecs[@]}"
+ done
+}
+
+initialize() {
+ local remote
+
+ if [[ -d .git && OPT_FORCE -eq 0 ]]; then
+ log_fatal 'refusing to overwrite existing repo in %s' "$ASPROOT"
+ fi
+
+ git init --bare || return 1
+
+ for remote in "${ARCH_GIT_REPOS[@]}"; do
+ rm -rf "$remote"
+ git remote add "$remote" git://projects.archlinux.org/svntogit/"$remote".git
+ done
+
+ touch .asp
+}
+
+dump_packages() {
+ local remote refspecs dumpfn
+
+ case $1 in
+ all)
+ dumpfn=remote_get_all_refs
+ ;;
+ local)
+ dumpfn=remote_get_tracked_refs
+ ;;
+ esac
+
+ for remote in "${ARCH_GIT_REPOS[@]}"; do
+ "$dumpfn" "$remote" refspecs
+ if [[ $refspecs ]]; then
+ printf '%s\n' "${refspecs[@]/#packages/"$remote"}"
+ fi
+ done
+}
+
+list_local() {
+ dump_packages 'local'
+}
+
+list_all() {
+ dump_packages 'all'
+}
+
+shortlog() {
+ package_log "$@" "$FUNCNAME"
+}
+
+log() {
+ package_log "$@" "$FUNCNAME"
+}
+
+difflog() {
+ package_log "$@" "$FUNCNAME"
+}
+
+gc() {
+ git prune
+ git gc
+}
+
+disk_usage() {
+ local usage
+ read usage _ < <(du -sh "$ASPROOT/objects")
+
+ log_info 'Using %s on disk.' "$usage"
+}
+
+umask 0022
+startdir=$PWD
+cd "$ASPROOT" || log_fatal "ASPROOT ($ASPROOT) does not exist!"
+[[ -f .asp ]] || initialize
+
+while getopts ':a:fhV' flag; do
+ case $flag in
+ a)
+ OPT_ARCH=$OPTARG
+ ;;
+ f)
+ OPT_FORCE=1
+ ;;
+ h)
+ usage
+ exit 0
+ ;;
+ V)
+ version
+ exit 0
+ ;;
+ \?)
+ log_fatal "invalid option -- '%s'" "$OPTARG"
+ ;;
+ :)
+ log_fatal "option '-%s' requires an argument" "$OPTARG"
+ ;;
+ esac
+done
+shift $(( OPTIND - 1 ))
+
+action=$1
+shift
+
+case $action in
+ update)
+ if (( $# == 0 )); then
+ update
+ else
+ update_packages "$@"
+ fi
+ ;;
+ list-repos)
+ map package_get_repos "$@"
+ ;;
+ list-arches)
+ map package_get_arches "$@"
+ ;;
+ list-all)
+ list_all
+ ;;
+ list-local)
+ list_local
+ ;;
+ export)
+ map package_export "$@"
+ ;;
+ shortlog)
+ shortlog "$1"
+ ;;
+ log)
+ log "$1"
+ ;;
+ difflog)
+ difflog "$1"
+ ;;
+ disk-usage)
+ disk_usage
+ ;;
+ gc)
+ gc
+ ;;
+ help)
+ usage
+ exit 0
+ ;;
+ *)
+ log_fatal 'unknown action: %s' "$action"
+ ;;
+esac
+
diff --git a/man/asp.1.txt b/man/asp.1.txt
new file mode 100644
index 0000000..3a90780
--- /dev/null
+++ b/man/asp.1.txt
@@ -0,0 +1,92 @@
+/////
+vim:set ts=4 sw=4 syntax=asciidoc noet:
+/////
+asp(1)
+======
+
+Name
+----
+asp - Manage Arch Linux build sources
+
+Synopsis
+--------
+asp [options] command [targets...]
+
+Description
+-----------
+Manage the version-controlled sources for the build scripts used to create Arch
+Linux packages. This program provides a thin wrapper over the svntogit
+repositories hosted at http://projects.archlinux.org. It aims to provide a
+replacement for abs which favors a sparse checkout.
+
+Commands
+--------
+The following commands are understood:
+
+*difflog*::
+ Show the full revision history of the target, with file diffs.
+
+*export*::
+ Dump the build source files for each target into a directory of the
+ target's name in $PWD. Targets can be specified simply as 'package' to
+ check out the source files at HEAD, or in 'repository/package' format
+ to checkout the source files which were used to push the 'package' which
+ exists in 'repository'.
+
+*gc*::
+ Perform housekeeping procedures on the local repo, optimizing and
+ compacting the repo to free disk space.
+
+*disk-usage*::
+ Report the approximate disk usage for locally tracked packages.
+
+*help*::
+ Display the command line usage and exit.
+
+*list-all*::
+ List all known packages in the repositories.
+
+*list-arches*::
+ List the architectures the given targets are available for.
+
+*list-local*::
+ List all packages which are tracked locally.
+
+*list-repos*::
+ List the repositories the given targets exist in.
+
+*log*::
+ Show the revision history of the target.
+
+*shortlog*::
+ Show a condensed revision history of the target.
+
+*update*::
+ For each target, if the package is not known to the local repository,
+ attempt to track it. If the package is tracked, update the package
+ to the newest version. If no targets are provided, all locally known
+ packages will be updated.
+
+Options
+-------
+*-a* 'architecture'::
+ When relevant, specify an architecture other than that of the current host.
+
+*-f*::
+ Allow files to be overwritten.
+
+*-h*::
+ Print a short help text and exit.
+
+*-V*::
+ Print a short version string and exit.
+
+Environment
+-----------
+*ASPROOT*::
+ Determines where the metadata is stored for locally tracked packages. Defaults
+ to '$HOME/asp'.
+
+Authors
+-------
+Dave Reisner <d@falconindy.com>
diff --git a/package.inc.sh b/package.inc.sh
new file mode 100644
index 0000000..09af4b1
--- /dev/null
+++ b/package.inc.sh
@@ -0,0 +1,148 @@
+package_init() {
+ local pkgname=$1
+ local do_update=1
+
+ if [[ $1 = -n ]]; then
+ do_update=0
+ shift
+ pkgname=$1
+ fi
+
+ package_find_remote "$pkgname" "$2" || return 1
+
+ (( do_update )) || return 0
+
+ if ! remote_is_tracking "${!2}" "$pkgname"; then
+ package_update "$pkgname" "${!2}" || return 1
+ fi
+}
+
+package_update() {
+ local pkgname=$1 remote=$2
+
+ git fetch "$remote" "packages/$pkgname"
+}
+
+package_find_remote() {
+ local pkgname=$1 out=$2
+
+ # fastpath, checks local caches only
+ for r in "${ARCH_GIT_REPOS[@]}"; do
+ if remote_is_tracking "$r" "$pkgname"; then
+ printf -v "$out" %s "$r"
+ return 0
+ fi
+ done
+
+ # slowpath, needs to talk to the remote
+ for r in "${ARCH_GIT_REPOS[@]}"; do
+ if remote_has_package "$r" "$pkgname"; then
+ printf -v "$out" %s "$r"
+ return 0
+ fi
+ done
+
+ log_error 'unknown package: %s' "$pkgname"
+
+ return 1
+}
+
+package_log() {
+ local pkgname=$1 method=$2 remote
+
+ package_init "$pkgname" remote || return
+
+ "_package_$method" "$pkgname" "$remote"
+}
+
+package_export() {
+ local pkgname=$1 remote repo arch
+ local mode objtype objid path
+
+ if [[ $pkgname = */* ]]; then
+ IFS=/ read -r repo pkgname <<<"$pkgname"
+ fi
+
+ package_init "$pkgname" remote || return 1
+
+ # support $repo/$pkgname syntax
+ if [[ $repo ]]; then
+ # TODO: add an --arch flag
+ subtree=repos/$repo-$OPT_ARCH
+ else
+ subtree=trunk
+ fi
+
+ if [[ -z $(git ls-tree "$remote/packages/$pkgname" "$subtree/") ]]; then
+ if [[ $repo ]]; then
+ log_error "package '%s' not found in repo '%s-%s'" "$pkgname" "$repo" "$OPT_ARCH"
+ return 1
+ else
+ log_error "package '%s' has no trunk directory!" "$pkgname"
+ return 1
+ fi
+ fi
+
+ if (( ! OPT_FORCE )); then
+ mkdir "$startdir/$pkgname" || return 1
+ fi
+
+ log_info 'exporting %s:%s' "$pkgname" "$subtree"
+ git archive --format=tar "$remote/packages/$pkgname" "$subtree/" |
+ bsdtar -C "$startdir" -s ",^$subtree/,$pkgname/," -xf - "$subtree/"
+}
+
+package_get_repos_with_arch() {
+ local pkgname=$1 remote=$2
+ local objtype path arch repo
+
+ while read _ objtype _ path; do
+ [[ $objtype = tree ]] || continue
+ IFS=- read repo arch <<<"${path#repos/}"
+ printf '%s %s\n' "$repo" "$arch"
+ done < <(git ls-tree "$remote/packages/$pkgname" repos/)
+}
+
+package_get_arches() {
+ local pkgname=$1 remote arch
+ declare -A arches
+
+ package_init "$pkgname" remote || return 1
+
+ while read _ arch; do
+ arches["$arch"]=1
+ done < <(package_get_repos_with_arch "$pkgname" "$remote")
+
+ printf '%s\n' "${!arches[@]}"
+}
+
+package_get_repos() {
+ local pkgname=$1 remote repo
+ declare -A repos
+
+ package_init "$pkgname" remote || return 1
+
+ while read repo _; do
+ repos["$repo"]=1
+ done < <(package_get_repos_with_arch "$pkgname" "$remote")
+
+ printf '%s\n' "${!repos[@]}"
+}
+
+_package_shortlog() {
+ local pkgname=$1 remote=$2
+
+ git log --pretty=oneline "$remote/packages/$pkgname"
+}
+
+_package_difflog() {
+ local pkgname=$1 remote=$2
+
+ git log -p "$remote/packages/$pkgname"
+}
+
+_package_log() {
+ local pkgname=$1 remote=$2
+
+ git log "$remote/packages/$pkgname"
+}
diff --git a/remote.inc.sh b/remote.inc.sh
new file mode 100644
index 0000000..b125419
--- /dev/null
+++ b/remote.inc.sh
@@ -0,0 +1,50 @@
+remote_get_all_refs() {
+ local remote=$1
+
+ mapfile -t "$2" < <(git ls-remote "$remote" 'refs/heads/packages/*' |
+ awk '{ sub(/refs\/heads\//, "", $2); print $2 }')
+}
+
+remote_has_package() {
+ local remote=$1 pkgname=$2
+
+ [[ $(git ls-remote "$remote" "$pkgname") ]]
+}
+
+remote_is_tracking() {
+ local repo=$1 pkgname=$2
+
+ git rev-parse "$repo/packages/$pkgname" &>/dev/null
+}
+
+remote_get_tracked_refs() {
+ local remote=$1
+
+ mapfile -t "$2" < <(git branch --remote 2>/dev/null |
+ awk -F'( +|/)' -v "remote=$1" \
+ '$2 == remote && $3 == "packages" { print "packages/" $4 }')
+}
+
+remote_update_refs() {
+ local remote=$1 refspecs=("${@:2}")
+
+ git fetch "$remote" "${refspecs[@]}"
+}
+
+remote_update() {
+ local remote=$1 refspecs
+
+ remote_get_tracked_refs "$remote" refspecs
+
+ # refuse to update everything
+ # TODO: allow this with a flag
+ [[ -z $refspecs ]] && return 0
+
+ remote_update_refs "$remote" "${refspecs[@]}"
+}
+
+remote_get_url() {
+ local remote=$1
+
+ git ls-remote --get-url "$remote"
+}