├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── archweb.inc.sh ├── asp.in ├── man └── asp.1.txt ├── package.inc.sh ├── remote.inc.sh ├── shell ├── bash-completion └── zsh-completion └── util.inc.sh /.gitignore: -------------------------------------------------------------------------------- 1 | asp 2 | asp.1 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Dave Reisner 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PACKAGE_NAME = asp 2 | 3 | VERSION := $(shell git describe --dirty 2>/dev/null) 4 | 5 | PREFIX = /usr/local 6 | 7 | BINPROGS = \ 8 | asp 9 | 10 | MANPAGES = \ 11 | man/asp.1 12 | 13 | BASH_COMPLETION = \ 14 | shell/bash-completion 15 | 16 | ZSH_COMPLETION = \ 17 | shell/zsh-completion 18 | 19 | INCLUDES = \ 20 | archweb.inc.sh \ 21 | package.inc.sh \ 22 | remote.inc.sh \ 23 | util.inc.sh 24 | 25 | all: $(BINPROGS) $(MANPAGES) 26 | 27 | V_GEN = $(_v_GEN_$(V)) 28 | _v_GEN_ = $(_v_GEN_0) 29 | _v_GEN_0 = @echo " GEN " $@; 30 | 31 | edit = $(V_GEN) m4 -P $@.in | sed 's/@ASP_VERSION@/$(VERSION)/' >$@ && chmod go-w,+x $@ 32 | 33 | %: %.in $(INCLUDES) 34 | $(edit) 35 | 36 | doc: $(MANPAGES) 37 | man/%: man/%.txt Makefile 38 | $(V_GEN) a2x \ 39 | -d manpage \ 40 | -f manpage \ 41 | -a manversion="$(PACKAGE_NAME) $(VERSION)" \ 42 | -a manmanual="$(PACKAGE_NAME) manual" $< 43 | 44 | check: $(BINPROGS) 45 | @for f in $(BINPROGS); do bash -O extglob -n $$f; done 46 | 47 | lint: $(BINPROGS) 48 | @for f in $(BINPROGS); do shellcheck $$f; done 49 | 50 | clean: 51 | $(RM) $(BINPROGS) $(MANPAGES) 52 | 53 | install: all 54 | install -dm755 $(DESTDIR)$(PREFIX)/bin $(DESTDIR)$(PREFIX)/share/man/man1 55 | install -m755 $(BINPROGS) $(DESTDIR)$(PREFIX)/bin 56 | install -m644 $(MANPAGES) $(DESTDIR)$(PREFIX)/share/man/man1 57 | install -Dm644 $(BASH_COMPLETION) $(DESTDIR)$(PREFIX)/share/bash-completion/completions/asp 58 | install -Dm644 $(ZSH_COMPLETION) $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_asp 59 | 60 | .PHONY: all clean install 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!CAUTION] 2 | > **THIS REPO IS ARCHIVED** 3 | > 4 | > In the context of the [git migration](https://archlinux.org/news/git-migration-announcement/) using `asp` has been deprecated in favor of `pkgctl` or plain `git`. 5 | > 6 | > For details see the respective wiki entry: https://wiki.archlinux.org/title/Arch_build_system#Retrieve_PKGBUILD_source 7 | 8 | --- 9 | 10 | # asp 11 | 12 | `asp` is a tool to manage the build source files used to create Arch Linux 13 | packages. It replaces the `abs` tool, offering more up to date sources (via the 14 | svntogit repositories) and uses a sparse checkout model to conserve diskspace. 15 | This probably won't be interesting to users who want a full checkout (for 16 | whatever reason that may be). 17 | 18 | # Setup 19 | 20 | None! Though, it should be noted that the `ASPROOT` environment variable 21 | will control where `asp` keeps its local git repo. By default, this is 22 | `${XDG_CACHE_HOME:-$HOME/.cache}/asp`. 23 | 24 | # Examples 25 | 26 | Get the source files for some packages: 27 | 28 | ~~~ 29 | asp export pacman testing/systemd extra/pkgfile 30 | ~~~ 31 | 32 | Get a fully functional git checkout of a single package: 33 | 34 | ~~~ 35 | asp checkout pkgfile 36 | ~~~ 37 | 38 | List the repositories a package has been pushed to: 39 | 40 | ~~~ 41 | asp list-repos pacman 42 | ~~~ 43 | 44 | -------------------------------------------------------------------------------- /archweb.inc.sh: -------------------------------------------------------------------------------- 1 | archweb_get_pkgbase() { 2 | local pkgbase 3 | 4 | pkgbase=$(curl -LGs 'https://archlinux.org/packages/search/json/' --data-urlencode "q=$1" | 5 | jq -r --arg pkgname "$1" 'limit(1; .results[] | select(.pkgname == $pkgname).pkgbase)') 6 | [[ $pkgbase ]] || return 7 | 8 | printf '%s\n' "$pkgbase" 9 | } 10 | -------------------------------------------------------------------------------- /asp.in: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ASP_VERSION=@ASP_VERSION@ 4 | ARCH_GIT_REPOS=(packages community) 5 | 6 | OPT_ARCH=$(uname -m) 7 | : "${ASPROOT:=${XDG_CACHE_HOME:-$HOME/.cache}/asp}" 8 | : "${ASPCACHE:=$ASPROOT/cache}" 9 | 10 | m4_include(util.inc.sh) 11 | m4_include(remote.inc.sh) 12 | m4_include(package.inc.sh) 13 | m4_include(archweb.inc.sh) 14 | 15 | usage() { 16 | cat< max )); then 68 | log_fatal '%s expects at most %d args, got %d' "${FUNCNAME[1]#action__}" "$max" "$argc" 69 | elif (( argc < min )); then 70 | log_fatal '%s expects at least %d args, got %d' "${FUNCNAME[1]#action__}" "$min" "$argc" 71 | fi 72 | } 73 | 74 | version() { 75 | printf 'asp %s\n' "$ASP_VERSION" 76 | } 77 | 78 | update_all() { 79 | local r 80 | 81 | for r in "${ARCH_GIT_REPOS[@]}"; do 82 | log_info "updating remote '%s'" "$r" 83 | remote_update "$r" 84 | done 85 | } 86 | 87 | update_local_branches() { 88 | local r=0 89 | 90 | while read -r branchname; do 91 | git branch -qf "$branchname" "refs/remotes/$branchname" || r=1 92 | done < <(git branch --no-color) 93 | 94 | return "$r" 95 | } 96 | 97 | update_remote_branches() { 98 | local refspecs=() remote pkgname 99 | declare -A refspec_map 100 | 101 | if (( $# == 0 )); then 102 | update_all 103 | return 104 | fi 105 | 106 | # map packages to remotes 107 | for pkgname; do 108 | package_init -n "$pkgname" remote || return 1 109 | refspec_map["$remote"]+=" packages/$pkgname" 110 | done 111 | 112 | # update each remote all at once 113 | for remote in "${!refspec_map[@]}"; do 114 | read -ra refspecs <<<"${refspec_map["$remote"]}" 115 | remote_update_refs "$remote" "${refspecs[@]}" 116 | done 117 | } 118 | 119 | update_packages() { 120 | update_remote_branches "$@" && update_local_branches 121 | } 122 | 123 | initialize() { 124 | local remote url 125 | 126 | umask 0022 127 | 128 | export GIT_DIR=$ASPROOT/.git 129 | 130 | if [[ ! -f $ASPROOT/.asp ]]; then 131 | git init -q "$ASPROOT" || return 1 132 | for remote in "${ARCH_GIT_REPOS[@]}"; do 133 | git remote add "$remote" "https://github.com/archlinux/svntogit-$remote.git" || return 1 134 | done 135 | 136 | touch "$ASPROOT/.asp" || return 1 137 | else 138 | # migrate from git.archlinux.org to github.com 139 | for remote in "${ARCH_GIT_REPOS[@]}"; do 140 | url=$(git remote get-url "$remote") 141 | # https://github.blog/2021-09-01-improving-git-protocol-security-github/ 142 | if [[ $url = *'git.archlinux.org'* ]] || [[ $url = *'git://github.com'* ]]; then 143 | git remote set-url "$remote" "https://github.com/archlinux/svntogit-$remote.git" 144 | fi 145 | done 146 | fi 147 | 148 | if [[ ! -d $ASPCACHE ]]; then 149 | mkdir -p "$ASPCACHE" || return 1 150 | fi 151 | 152 | return 0 153 | } 154 | 155 | dump_packages() { 156 | local remote refspecs dumpfn 157 | 158 | case $1 in 159 | all) 160 | dumpfn=remote_get_all_refs 161 | ;; 162 | local) 163 | dumpfn=remote_get_tracked_refs 164 | ;; 165 | *) 166 | log_fatal 'BUG: invalid dump type: "%s"' "$1" 167 | ;; 168 | esac 169 | 170 | for remote in "${ARCH_GIT_REPOS[@]}"; do 171 | "$dumpfn" "$remote" refspecs 172 | if [[ $refspecs ]]; then 173 | printf '%s\n' "${refspecs[@]##*/}" 174 | fi 175 | done | sort 176 | } 177 | 178 | list_local() { 179 | dump_packages 'local' 180 | } 181 | 182 | list_all() { 183 | dump_packages 'all' 184 | } 185 | 186 | shortlog() { 187 | package_log "$@" "${FUNCNAME[0]}" 188 | } 189 | 190 | log() { 191 | package_log "$@" "${FUNCNAME[0]}" 192 | } 193 | 194 | difflog() { 195 | package_log "$@" "${FUNCNAME[0]}" 196 | } 197 | 198 | gc() { 199 | git gc --prune=all 200 | } 201 | 202 | untrack() { 203 | local pkgname=$1 remote 204 | 205 | package_init -n "$pkgname" remote || return 1 206 | 207 | remote_untrack "$remote" "$pkgname" 208 | package_untrack "$pkgname" "$remote" 209 | } 210 | 211 | disk_usage() { 212 | local usage 213 | read -r usage _ < <(du -sh "$ASPROOT") 214 | 215 | log_info 'Using %s on disk.' "$usage" 216 | } 217 | 218 | action__checkout() { 219 | __require_argc 1- $# 220 | map package_checkout "$@" 221 | } 222 | 223 | action__difflog() { 224 | __require_argc 1 $# 225 | difflog "$1" 226 | } 227 | 228 | action__disk-usage() { 229 | __require_argc 0 $# 230 | disk_usage 231 | } 232 | 233 | action__export() { 234 | __require_argc 1- $# 235 | map package_export "$@" 236 | } 237 | 238 | action__gc() { 239 | __require_argc 0 $# 240 | gc 241 | } 242 | 243 | action__help() { 244 | __require_argc 0 $# 245 | usage 246 | } 247 | 248 | action__list-all() { 249 | __require_argc 0 $# 250 | list_all 251 | } 252 | 253 | action__list-arches() { 254 | __require_argc 1- $# 255 | map package_get_arches "$@" 256 | } 257 | 258 | action__list-local() { 259 | __require_argc 0 $# 260 | list_local 261 | } 262 | 263 | action__list-repos() { 264 | __require_argc 1- $# 265 | map package_get_repos "$@" 266 | } 267 | 268 | action__log() { 269 | __require_argc 1 $# 270 | log "$1" 271 | } 272 | 273 | action__shortlog() { 274 | __require_argc 1 $# 275 | shortlog "$1" 276 | } 277 | 278 | action__show() { 279 | __require_argc 1-2 $# 280 | package_show_file "$@" 281 | } 282 | 283 | action__untrack() { 284 | __require_argc 1- $# 285 | map untrack "$@" 286 | } 287 | 288 | action__update() { 289 | update_packages "$@" 290 | } 291 | 292 | action__ls-files() { 293 | __require_argc 1 $# 294 | 295 | package_list_files "$1" 296 | } 297 | 298 | action__set-git-protocol() { 299 | __require_argc 1 $# 300 | 301 | case $1 in 302 | git|http|https) 303 | ;; 304 | *) 305 | log_fatal 'invalid protocol: %s' "$1" 306 | ;; 307 | esac 308 | 309 | for remote in "${ARCH_GIT_REPOS[@]}"; do 310 | git remote set-url "$remote" "$1://github.com/archlinux/svntogit-$remote.git" 311 | done 312 | } 313 | 314 | dispatch_action() { 315 | local candidates 316 | 317 | [[ $1 ]] || log_fatal 'no action specified (use -h for help)' 318 | 319 | # exact match 320 | if declare -F "action__$1" &>/dev/null; then 321 | "action__$1" "${@:2}" 322 | return 323 | fi 324 | 325 | # prefix match 326 | mapfile -t candidates < <(compgen -A function "action__$1") 327 | case ${#candidates[*]} in 328 | 0) 329 | log_fatal 'unknown action: %s' "$1" 330 | ;; 331 | 1) 332 | "${candidates[0]}" "${@:2}" 333 | return 334 | ;; 335 | *) 336 | { 337 | printf "error: verb '%s' is ambiguous; possibilities:" "$1" 338 | printf " '%s'" "${candidates[@]#action__}" 339 | echo 340 | } >&2 341 | return 1 342 | ;; 343 | esac 344 | } 345 | 346 | initialize || log_fatal 'failed to initialize asp repository in %s' "$ASPROOT" 347 | 348 | case $1 in 349 | --version) 350 | version 351 | exit 0 352 | ;; 353 | --help) 354 | usage 355 | exit 0 356 | ;; 357 | esac 358 | 359 | while getopts ':a:hV' flag; do 360 | case $flag in 361 | a) 362 | OPT_ARCH=$OPTARG 363 | ;; 364 | h) 365 | usage 366 | exit 0 367 | ;; 368 | V) 369 | version 370 | exit 0 371 | ;; 372 | \?) 373 | log_fatal "invalid option -- '%s'" "$OPTARG" 374 | ;; 375 | :) 376 | log_fatal "option '-%s' requires an argument" "$OPTARG" 377 | ;; 378 | esac 379 | done 380 | shift $(( OPTIND - 1 )) 381 | 382 | dispatch_action "$@" 383 | -------------------------------------------------------------------------------- /man/asp.1.txt: -------------------------------------------------------------------------------- 1 | ///// 2 | vim:set ts=4 sw=4 syntax=asciidoc noet: 3 | ///// 4 | asp(1) 5 | ====== 6 | 7 | Name 8 | ---- 9 | asp - Manage Arch Linux build sources 10 | 11 | Synopsis 12 | -------- 13 | asp [options] command [targets...] 14 | 15 | Description 16 | ----------- 17 | Manage the version-controlled sources for the build scripts used to create Arch 18 | Linux packages. This program provides a thin wrapper over the svntogit 19 | repositories hosted at https://github.com/archlinux. It aims to provide a 20 | replacement for abs which favors a sparse checkout. 21 | 22 | Commands 23 | -------- 24 | The following commands are understood: 25 | 26 | *checkout* 'TARGET'...:: 27 | Create a new git repository containing the full source and history 28 | for each of the given targets. The new repository will pull from the 29 | repository in '$ASPROOT' and must be updated separately after using 30 | 'asp update'. If a checkout occurs on the same filesystem as '$ASPROOT', 31 | most of the metadata can be hard linked, making this a relatively cheap 32 | copy. 33 | 34 | *difflog* 'TARGET':: 35 | Show the full revision history of the target, with file diffs. 36 | 37 | *disk-usage*:: 38 | Report the approximate disk usage for locally tracked packages. 39 | 40 | *export* 'TARGET'...:: 41 | Dump the build source files for each target into a directory of the 42 | target's name in '$PWD'. Targets can be specified simply as 'package' to 43 | check out the source files at HEAD, or in 'repository/package' format 44 | to checkout the source files which were used to push the 'package' which 45 | exists in 'repository'. 46 | 47 | *gc*:: 48 | Perform housekeeping procedures on the local repo, optimizing and 49 | compacting the repo to free disk space. 50 | 51 | *help*:: 52 | Display the command line usage and exit. 53 | 54 | *list-all*:: 55 | List all known packages in the repositories. 56 | 57 | *list-arches* 'TARGET'...:: 58 | List the architectures the given targets are available for. 59 | 60 | *list-local*:: 61 | List all packages which are tracked locally. 62 | 63 | *list-repos* 'TARGET'...:: 64 | List the repositories the given targets exist in. 65 | 66 | *log* 'TARGET':: 67 | Show the revision history of the target. 68 | 69 | *ls-files* 'TARGET':: 70 | List source files for the given target. 71 | 72 | *set-git-protocol* 'PROTOCOL':: 73 | Set the protocol used to communicate with the remote git repositories. Must 74 | be one of 'git', 'http', or 'https'. 75 | 76 | *shortlog* 'TARGET':: 77 | Show a condensed revision history of the target. 78 | 79 | *show* 'TARGET' ['FILE']:: 80 | Show the file content of the target, which may be in the format 'package' 81 | or 'repository/package'. If an additional 'file' argument is provided, attempt 82 | to display that file rather than the PKGBUILD. If the repository is not 83 | specified, the file will be shown at the most recent revision (which may be 84 | newer than what is in the repositories). 85 | 86 | *untrack* 'TARGET'...:: 87 | Remove a remote tracking branch from the local repository. Disk usage for 88 | the removed package(s) may not be freed until garbage collection has taken 89 | place. 90 | 91 | *update* ['TARGET'...]:: 92 | For each target, if the package is not known to the local repository, 93 | attempt to track it. If the package is tracked, update the package 94 | to the newest version. If no targets are provided, all locally known 95 | packages will be updated. 96 | 97 | Options 98 | ------- 99 | *-a* 'architecture':: 100 | When relevant, specify an architecture other than that of the current host. 101 | 102 | *-h*:: 103 | Print a short help text and exit. 104 | 105 | *-V*:: 106 | Print a short version string and exit. 107 | 108 | Environment 109 | ----------- 110 | *ASPROOT*:: 111 | Determines where the metadata is stored for locally tracked packages. Defaults 112 | to '`${XDG_CACHE_HOME:-$HOME/.cache}/asp`'. 113 | 114 | *ASPCACHE*:: 115 | Determines where cached data is stored. Defaults to '$ASPROOT/cache'. Data in 116 | this directory can always be safely deleted. 117 | 118 | Authors 119 | ------- 120 | Dave Reisner 121 | -------------------------------------------------------------------------------- /package.inc.sh: -------------------------------------------------------------------------------- 1 | package_resolve() { 2 | local pkgbase 3 | 4 | [[ $pkgname ]] || log_fatal 'BUG: package_resolve called without pkgname var set' 5 | 6 | if package_find_remote "$1" "$2"; then 7 | return 0 8 | fi 9 | 10 | if pkgbase=$(archweb_get_pkgbase "$1") && package_find_remote "$pkgbase" "$2"; then 11 | log_info '%s is part of package %s' "$1" "$pkgbase" 12 | pkgname=$pkgbase 13 | return 0 14 | fi 15 | 16 | log_error 'unknown package: %s' "$pkgname" 17 | return 1 18 | } 19 | 20 | package_init() { 21 | local do_update=1 22 | 23 | if [[ $1 = -n ]]; then 24 | do_update=0 25 | shift 26 | fi 27 | 28 | pkgname=$1 29 | 30 | package_resolve "$pkgname" "$2" || return 31 | 32 | (( do_update )) || return 0 33 | 34 | remote_is_tracking "${!2}" "$pkgname" || 35 | remote_update_refs "${!2}" "packages/$pkgname" 36 | } 37 | 38 | package_find_remote() { 39 | pkgname=$1 40 | 41 | # fastpath, checks local caches only 42 | for r in "${ARCH_GIT_REPOS[@]}"; do 43 | if remote_is_tracking "$r" "$pkgname"; then 44 | printf -v "$2" %s "$r" 45 | return 0 46 | fi 47 | done 48 | 49 | # slowpath, needs to talk to the remote 50 | for r in "${ARCH_GIT_REPOS[@]}"; do 51 | if remote_has_package "$r" "$pkgname"; then 52 | printf -v "$2" %s "$r" 53 | return 0 54 | fi 55 | done 56 | 57 | return 1 58 | } 59 | 60 | package_log() { 61 | local method=$2 logargs remote 62 | pkgname=$1 63 | 64 | package_init "$pkgname" remote || return 65 | 66 | case $method in 67 | shortlog) 68 | logargs=('--pretty=oneline') 69 | ;; 70 | difflog) 71 | logargs=('-p') 72 | ;; 73 | log) 74 | logargs=() 75 | ;; 76 | *) 77 | log_fatal 'BUG: unknown log method: %s' "$method" 78 | ;; 79 | esac 80 | 81 | git log "${logargs[@]}" "$remote/packages/$pkgname" -- trunk/ 82 | } 83 | 84 | package_show_file() { 85 | local file=${2:-PKGBUILD} remote repo subtree 86 | pkgname=$1 87 | 88 | if [[ $pkgname = */* ]]; then 89 | IFS=/ read -r repo pkgname <<<"$pkgname" 90 | fi 91 | 92 | package_init "$pkgname" remote || return 93 | 94 | if [[ $file != */* ]]; then 95 | if [[ $repo ]]; then 96 | subtree=repos/$repo-$OPT_ARCH/ 97 | else 98 | subtree=trunk/ 99 | fi 100 | fi 101 | 102 | git show "remotes/$remote/packages/$pkgname:$subtree$file" 103 | } 104 | 105 | package_list_files() { 106 | local remote subtree=trunk 107 | pkgname=$1 108 | 109 | if [[ $pkgname = */* ]]; then 110 | IFS=/ read -r repo pkgname <<<"$pkgname" 111 | fi 112 | 113 | package_init "$pkgname" remote || return 114 | 115 | if [[ $repo ]]; then 116 | subtree=repos/$repo-$OPT_ARCH 117 | fi 118 | 119 | 120 | git ls-tree -r --name-only "remotes/$remote/packages/$pkgname" "$subtree" | 121 | awk -v "prefix=$subtree/" 'sub(prefix, "")' 122 | } 123 | 124 | package_export() { 125 | local remote repo arch=$OPT_ARCH arches subtree=trunk 126 | pkgname=$1 127 | 128 | if [[ $pkgname = */* ]]; then 129 | IFS=/ read -r repo pkgname <<<"$pkgname" 130 | fi 131 | 132 | package_init "$pkgname" remote || return 133 | 134 | if [[ $repo ]]; then 135 | mapfile -t arches < <(package_get_arches "$pkgname") 136 | if (( ${#arches[*]} == 1 )) && [[ ${arches[0]} = any ]]; then 137 | arch=any 138 | fi 139 | subtree=repos/$repo-$arch 140 | fi 141 | 142 | if ! git show "remotes/$remote/packages/$pkgname:$subtree/" &>/dev/null; then 143 | if [[ $repo ]]; then 144 | log_error "package '%s' not found in repo '%s-%s'" "$pkgname" "$repo" "$OPT_ARCH" 145 | return 1 146 | else 147 | log_error "package '%s' has no trunk directory!" "$pkgname" 148 | return 1 149 | fi 150 | fi 151 | 152 | mkdir "$pkgname" || return 153 | 154 | log_info 'exporting %s:%s' "$pkgname" "$subtree" 155 | git archive --format=tar "remotes/$remote/packages/$pkgname" "$subtree/" | 156 | tar --transform "s,^$subtree,$pkgname," -xf - "$subtree/" 157 | } 158 | 159 | package_checkout() { 160 | local remote 161 | pkgname=$1 162 | 163 | package_init "$pkgname" remote || return 164 | 165 | git show-ref -q "refs/heads/$remote/packages/$pkgname" || 166 | git branch -qf --no-track {,}"$remote/packages/$pkgname" 167 | 168 | quiet_git clone \ 169 | --shared \ 170 | --single-branch \ 171 | --branch "$remote/packages/$pkgname" \ 172 | --config "pull.rebase=true" \ 173 | "$ASPROOT" "$pkgname" || return 174 | } 175 | 176 | package_get_repos_with_arch() { 177 | local remote=$2 path arch repo 178 | pkgname=$1 179 | 180 | while read -r path; do 181 | path=${path##*/} 182 | repo=${path%-*} 183 | arch=${path##*-} 184 | printf '%s %s\n' "$repo" "$arch" 185 | done < <(git ls-tree --name-only "remotes/$remote/packages/$pkgname" repos/) 186 | } 187 | 188 | package_get_arches() { 189 | local remote arch 190 | declare -A arches 191 | pkgname=$1 192 | 193 | package_init "$pkgname" remote || return 194 | 195 | while read -r _ arch; do 196 | arches["$arch"]=1 197 | done < <(package_get_repos_with_arch "$pkgname" "$remote") 198 | 199 | printf '%s\n' "${!arches[@]}" 200 | } 201 | 202 | package_get_repos() { 203 | local remote repo 204 | declare -A repos 205 | pkgname=$1 206 | 207 | package_init "$pkgname" remote || return 208 | 209 | while read -r repo _; do 210 | repos["$repo"]=1 211 | done < <(package_get_repos_with_arch "$pkgname" "$remote") 212 | 213 | printf '%s\n' "${!repos[@]}" 214 | } 215 | 216 | package_untrack() { 217 | local remote=$2 218 | pkgname=$1 219 | 220 | if git show-ref -q "refs/heads/$remote/packages/$pkgname"; then 221 | git branch -D "$remote/packages/$pkgname" 222 | fi 223 | } 224 | -------------------------------------------------------------------------------- /remote.inc.sh: -------------------------------------------------------------------------------- 1 | __remote_refcache_update() { 2 | local remote=$1 cachefile=$ASPCACHE/remote-$remote refs 3 | 4 | refs=$(git ls-remote "$remote" 'refs/heads/packages/*') || 5 | log_fatal "failed to update remote $remote" 6 | 7 | printf '%s' "$refs" | 8 | awk '{ sub(/refs\/heads\/packages\//, "", $2); print $2 }' >"$cachefile" 9 | } 10 | 11 | __remote_refcache_is_stale() { 12 | local now cachetime cachefile=$1 ttl=3600 13 | 14 | printf -v now '%(%s)T' -1 15 | 16 | # The cache is stale if we've exceeded the TTL. 17 | if ! cachetime=$(stat -c %Y "$cachefile" 2>/dev/null) || 18 | (( now > (cachetime + ttl) )); then 19 | return 0 20 | fi 21 | 22 | # We also consider the cache to be stale when this script is newer than the 23 | # cache. This allows upgrades to asp to implicitly wipe the cache and not 24 | # make any guarantees about the file format. 25 | if (( $(stat -c %Y "${BASH_SOURCE[0]}" 2>/dev/null) > cachetime )); then 26 | return 0 27 | fi 28 | 29 | return 1 30 | } 31 | 32 | __remote_refcache_get() { 33 | local remote=$1 cachefile=$ASPCACHE/remote-$remote 34 | 35 | if __remote_refcache_is_stale "$cachefile"; then 36 | __remote_refcache_update "$remote" 37 | fi 38 | 39 | mapfile -t "$2" <"$cachefile" 40 | } 41 | 42 | remote_get_all_refs() { 43 | local remote=$1 44 | 45 | __remote_refcache_get "$remote" "$2" 46 | } 47 | 48 | remote_has_package() { 49 | local remote=$1 pkgname=$2 refs 50 | 51 | remote_get_all_refs "$remote" refs 52 | 53 | in_array "$pkgname" "${refs[@]}" 54 | } 55 | 56 | remote_is_tracking() { 57 | local repo=$1 pkgname=$2 58 | 59 | git show-ref -q "$repo/packages/$pkgname" 60 | } 61 | 62 | remote_get_tracked_refs() { 63 | local remote=$1 64 | 65 | mapfile -t "$2" < \ 66 | <(git for-each-ref --format='%(refname:strip=3)' "refs/remotes/$remote") 67 | } 68 | 69 | remote_update_refs() { 70 | local remote=$1 refspecs=("${@:2}") 71 | 72 | quiet_git fetch "$remote" "${refspecs[@]}" 73 | } 74 | 75 | remote_update() { 76 | local remote=$1 refspecs 77 | 78 | remote_get_tracked_refs "$remote" refspecs 79 | 80 | # refuse to update everything 81 | [[ -z $refspecs ]] && return 0 82 | 83 | remote_update_refs "$remote" "${refspecs[@]}" 84 | } 85 | 86 | remote_untrack() { 87 | local remote=$1 pkgname=$2 88 | 89 | if git show-ref -q "refs/remotes/$remote/packages/$pkgname"; then 90 | git branch -dr "$remote/packages/$pkgname" 91 | fi 92 | } 93 | -------------------------------------------------------------------------------- /shell/bash-completion: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | in_array() { 4 | for _ in "${@:2}"; do 5 | [[ $_ = "$1" ]] && return 0 6 | done 7 | return 1 8 | } 9 | 10 | _asp() { 11 | local verb='' i cur prev comps 12 | 13 | _get_comp_words_by_ref cur prev 14 | 15 | # top level commands 16 | local -A verbs=( 17 | [ALL_PACKAGES]='checkout difflog export list-arches list-repos log shortlog show ls-files' 18 | [LOCAL_PACKAGES]='untrack update' 19 | [NONE]='disk-usage gc help list-all list-local' 20 | [PROTO]='set-git-protocol' 21 | ) 22 | 23 | # flags 24 | local -A opts=( 25 | [UNKNOWN]='-a' 26 | [NONE]='-h -V' 27 | ) 28 | 29 | if in_array "$prev" ${opts[UNKNOWN]}; then 30 | return 0 31 | fi 32 | 33 | if [[ $cur = -* ]]; then 34 | COMPREPLY=( $(compgen -W '${opts[*]}' -- "$cur") ) 35 | return 0 36 | fi 37 | 38 | # verb completion 39 | for (( i = 0; i < ${#COMP_WORDS[@]}; ++i )); do 40 | word=${COMP_WORDS[i]} 41 | if in_array "$word" ${verbs[ALL_PACKAGES]}; then 42 | verb=$word 43 | comps=$(ASP_GIT_QUIET=1 \asp list-all | sed 's,.*/,,') 44 | break 45 | elif in_array "$word" ${verbs[LOCAL_PACKAGES]}; then 46 | verb=$word 47 | comps=$(ASP_GIT_QUIET=1 \asp list-local | sed 's,.*/,,') 48 | break 49 | elif in_array "$word" ${verbs[PROTO]}; then 50 | verb=$word 51 | comps='git http https' 52 | break 53 | elif in_array "$word" ${verbs[NONE]}; then 54 | verb=$word 55 | break 56 | fi 57 | done 58 | 59 | # sub-verb completion 60 | case $verb in 61 | show) 62 | if (( i < ${#COMP_WORDS[@]} - 2 )); then 63 | comps=$(ASP_GIT_QUIET=1 \asp ls-files "${COMP_WORDS[i+1]}" 2>/dev/null) 64 | fi 65 | ;; 66 | '') 67 | comps=${verbs[*]} 68 | ;; 69 | esac 70 | 71 | if [[ $comps ]]; then 72 | COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) 73 | fi 74 | } 75 | 76 | complete -F _asp asp 77 | -------------------------------------------------------------------------------- /shell/zsh-completion: -------------------------------------------------------------------------------- 1 | #compdef asp 2 | 3 | _asp_command() { 4 | local -a _asp_cmds 5 | _asp_cmds=( 6 | 'checkout' 7 | 'difflog' 8 | 'export' 9 | 'gc' 10 | 'disk-usage' 11 | 'help' 12 | 'list-all' 13 | 'list-arches' 14 | 'list-local' 15 | 'list-repos' 16 | 'ls-files' 17 | 'log' 18 | 'shortlog' 19 | 'show' 20 | 'set-git-protocol' 21 | 'update' 22 | 'untrack' 23 | ) 24 | 25 | if (( CURRENT == 1 )); then 26 | _describe -t commands 'asp command' _asp_cmds || compadd "$@" 27 | else 28 | local curcontext="$curcontext" 29 | cmd="${${_asp_cmds[(r)$words[1]:*]%%:*}}" 30 | if (( $#cmd )); then 31 | if (( $+functions[_asp_$cmd] )); then 32 | _asp_$cmd 33 | else 34 | _message "no more options" 35 | fi 36 | else 37 | _message "unknown asp command: $words[1]" 38 | fi 39 | fi 40 | } 41 | 42 | _arguments \ 43 | '-a[architecture]' \ 44 | '-h[print help and exit]' \ 45 | '-V[print version and exit]' \ 46 | '*::asp command:_asp_command' 47 | 48 | # vim: set et sw=2 ts=2 ft=zsh : 49 | -------------------------------------------------------------------------------- /util.inc.sh: -------------------------------------------------------------------------------- 1 | log_meta() { 2 | # shellcheck disable=SC2059 3 | printf "$1 $2\\n" "${@:3}" 4 | } 5 | 6 | log_error() { 7 | log_meta 'error:' "$@" >&2 8 | } 9 | 10 | log_fatal() { 11 | log_error "$@" 12 | exit 1 13 | } 14 | 15 | log_warning() { 16 | log_meta 'warning:' "$@" >&2 17 | } 18 | 19 | log_info() { 20 | log_meta '==>' "$@" 21 | } 22 | 23 | map() { 24 | local map_r=0 25 | for _ in "${@:2}"; do 26 | "$1" "$_" || map_r=1 27 | done 28 | return $map_r 29 | } 30 | 31 | in_array() { 32 | local item needle=$1 33 | 34 | for item in "${@:2}"; do 35 | [[ $item = "$needle" ]] && return 0 36 | done 37 | 38 | return 1 39 | } 40 | 41 | quiet_git() { 42 | [[ $ASP_GIT_QUIET ]] && set -- "$1" -q "${@:2}" 43 | 44 | command git "$@" 45 | } 46 | --------------------------------------------------------------------------------