Merge pull request #1147 from aurutils/sync--filter
[aurutils.git] / lib / aur-fetch
blob90c87718778538fac219b48a87834f82c83df822
1 #!/bin/bash
2 # aur-fetch - retrieve build files from the AUR
3 [[ -v AUR_DEBUG ]] && set -o xtrace
4 shopt -s extglob
5 argv0=fetch
6 XDG_CACHE_HOME=${XDG_CACHE_HOME:-$HOME/.cache}
7 XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config}
8 AUR_FETCH_USE_MIRROR=${AUR_FETCH_USE_MIRROR:-0}
9 AUR_LOCATION=${AUR_LOCATION:-https://aur.archlinux.org}
10 PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[1]}(): }'
12 # Author information for merge commits
13 export GIT_AUTHOR_NAME=aurutils
14 export GIT_AUTHOR_EMAIL=aurutils@localhost
15 export GIT_COMMITTER_NAME=aurutils
16 export GIT_COMMITTER_EMAIL=aurutils@localhost
17 export GIT_HTTP_USER_AGENT=aurutils
19 # Placeholder for repositories without commits
20 git_empty_object=$(git hash-object -t tree /dev/null)
22 # default options
23 existing=0 recurse=0 discard=0 sync=fetch
25 args_csv() {
26 # shellcheck disable=SC2155
27 local str=$(printf '%s,' "$@")
28 printf '%s' "${str%,}"
31 # XXX: races with multiple fetch instances
32 results() {
33 local mode=$1 prev=$2 current=$3 path=$4 dest=$5
35 if [[ -w $dest ]]; then
36 printf >> "$dest" '%s:%s:%s:file://%s\n' "$mode" "$prev" "$current" "$path"
40 sync_package_config() {
41 case $(git config --get --type bool aurutils.rebase) in
42 true)
43 printf >&2 '%s: aurutils.rebase is set for %s\n' "$argv0" "$1"
44 printf '%s' rebase ;;
46 printf '%s' merge ;;
47 esac
50 sync_should_merge() {
51 local upstream=$1 dest=$2 pkg=$3
53 # Check if last upstream commit can be reached from $dest
54 if ! git merge-base --is-ancestor "$upstream" "$dest"; then
55 return 0
56 else
57 # Print diagnostic with prefixed argv0 (unlike git)
58 printf >&2 '%s: %s: already up to date\n' "$argv0" "$pkg"
59 return 1
63 usage() {
64 cat <<! | base64 -d
65 ICAgICAgICAgICAgIC4tLX5+LF9fCjotLi4uLiwtLS0tLS0tYH5+Jy5fLicKIGAtLCwsICAs
66 XyAgICAgIDsnflUnCiAgXywtJyAsJ2AtX187ICctLS4KIChfLyd+fiAgICAgICcnJycoOwoK
68 printf >&2 'usage: %s [-Sefr] [--rebase|--reset|--merge] [--] pkgname...\n' "$argv0"
69 exit 1
72 # option handling
73 opt_short='efrS'
74 opt_long=('auto' 'merge' 'reset' 'rebase' 'discard' 'existing' 'results:' 'ff'
75 'ff-only' 'no-ff' 'no-commit' 'recurse')
76 opt_hidden=('dump-options' 'sync:')
78 if opts=$(getopt -o "$opt_short" -l "$(args_csv "${opt_long[@]}" "${opt_hidden[@]}")" -n "$argv0" -- "$@"); then
79 eval set -- "$opts"
80 else
81 usage
84 unset rebase_args merge_args results_file
85 while true; do
86 case "$1" in
87 # fetch options
88 -S|--auto)
89 sync=auto ;;
90 -f|--discard)
91 discard=1 ;;
92 -e|--existing)
93 existing=1 ;;
94 --merge)
95 sync=merge ;;
96 --rebase)
97 sync=rebase ;;
98 --reset)
99 sync=reset ;;
100 --results)
101 shift; results_file=$(realpath -- "$1") ;;
102 # git options
103 --ff)
104 merge_args+=(-ff) ;;
105 --ff-only)
106 merge_args+=(--ff-only) ;;
107 --no-commit)
108 merge_args+=(--no-commit) ;;
109 --no-ff)
110 merge_args+=(--no-ff); rebase_args+=(--no-ff) ;;
111 # Compatibility options
112 --sync)
113 shift; sync=$1 ;;
114 -r|--recurse)
115 recurse=1 ;;
116 --dump-options)
117 printf -- '--%s\n' "${opt_long[@]}" ${AUR_DEBUG+"${opt_hidden[@]}"}
118 printf -- '%s' "${opt_short}" | sed 's/.:\?/-&\n/g'
119 exit ;;
120 --) shift; break ;;
121 esac
122 shift
123 done
125 # Default to only allowing fast-forward merges (as git-pull)
126 if (( ! ${#merge_args[@]} )); then
127 merge_args=(--ff-only)
130 # option validation
131 if [[ $sync == !(auto|merge|rebase|reset|fetch) ]]; then
132 printf >&2 '%s: invalid --sync mode\n' "$argv0"
133 exit 1
136 if (( ! $# )); then
137 printf >&2 '%s: no targets specified\n' "$argv0"
138 exit 1
141 # XXX: race with concurrent processes
142 if [[ -v results_file ]]; then
143 : >"$results_file" || exit 1 # truncate file
146 # Save stdin/depends in array (2 passes)
147 if (( recurse )); then
148 mapfile -t packages < <(aur depends --reverse "$@" | tsort)
149 wait "$!" || exit
151 elif (( $# == 1 )) && [[ $1 == "-" || $1 == "/dev/stdin" ]]; then
152 mapfile -t packages
153 else
154 packages=("$@")
155 set --
158 # Exit gracefully on empty stdin, e.g. when piping from `aur repo -u`.
159 if (( ! ${#packages[@]} )); then
160 exit 0
163 # Update revisions in local AUR mirror
164 declare -A local_clones
166 # With an AUR mirror, updates are retrieved in two steps. First, updates to the
167 # mirror are synchronized with `git-fetch`. Secondly, local clones of the miror
168 # are created and are updated with `git-fetch` and `git-merge` as usual.
169 if (( AUR_FETCH_USE_MIRROR )); then
170 while IFS=':' read -r pkg head; do
171 printf "Cloning into '%s'\n" "$pkg"
172 git -C "$pkg" --no-pager log --pretty=reference -1
174 if [[ -v results_file ]]; then
175 results 'clone' "$git_empty_object" "$head" "$PWD/$pkg" "$results_file"
177 local_clones[$pkg]=$head
178 done < <(
179 aur fetch--mirror --lclone "${packages[@]}"
181 wait "$!" || exit
184 # Main loop
185 for pkg in "${packages[@]}"; do
186 unset -f git
188 # Local clone by fetch--mirror
189 if [[ ${local_clones[$pkg]} ]]; then
190 continue
192 # Verify if the repository is hosted on AUR (#959)
193 elif (( existing )) && ! git ls-remote --exit-code "$AUR_LOCATION/$pkg" >/dev/null; then
194 printf >&2 '%s: warning: package %s is not in AUR, skipping\n' "$argv0" "$pkg"
195 continue
197 # Clone package if not existing
198 elif [[ ! -d $pkg/.git ]]; then
199 git clone "$AUR_LOCATION/$pkg" || exit 1
201 head=$(git -C "$pkg" rev-parse --verify HEAD)
202 [[ $head ]] && git -C "$pkg" --no-pager log --pretty=reference -1
204 if [[ -v results_file ]]; then
205 results 'clone' "$git_empty_object" "${head:-$git_empty_object}" "$PWD/$pkg" "$results_file"
208 # Update existing git repository
209 else
210 # Per-package lock
211 exec {fd}< "$pkg"/.git
212 flock --wait 5 "$fd" || exit 1
214 # Avoid issues with filesystem boundaries (#274)
215 git() { command git -C "$pkg" "$@"; }
217 # Retrieve per-package configuration (aurutils.rebase, #1007)
218 if [[ $sync == 'auto' ]]; then
219 sync_pkg=$(sync_package_config "$pkg")
220 else
221 sync_pkg=$sync
224 # Retrieve new upstream commits
225 git fetch origin || exit
227 # Store original HEAD for --results output
228 orig_head=$(git rev-parse --verify --quiet HEAD)
229 orig_head=${orig_head:-$git_empty_object}
231 # Merge in new history
232 upstream=origin/HEAD
234 case $sync_pkg in
235 rebase)
236 dest=HEAD
237 if sync_should_merge "$upstream" "$dest" "$pkg"; then
238 if (( discard )); then
239 git checkout ./
241 git rebase "${rebase_args[@]}" "$upstream"
242 fi ;;
243 merge)
244 dest=HEAD
245 if sync_should_merge "$upstream" "$dest" "$pkg"; then
246 if (( discard )); then
247 git checkout ./
249 git merge "${merge_args[@]}" "$upstream"
250 fi ;;
251 reset)
252 dest=$upstream
253 git reset --hard "$dest"
255 fetch)
256 dest=$upstream
258 esac || {
259 printf >&2 '%s: failed to %s %s\n' "$argv0" "$sync_pkg" "$pkg"
260 exit 1
262 head=$(git rev-parse --verify "$dest")
264 if [[ -v results_file ]]; then
265 results "$sync_pkg" "$orig_head" "$head" "$PWD/$pkg" "$results_file"
267 exec {fd}<&- # release lock
268 fi >&2 # print all git output to stderr
269 done
271 # vim: set et sw=4 sts=4 ft=sh: