Merge pull request #1016 from VorpalBlade/feature/3rd-party-completion
[aurutils.git] / lib / aur-fetch
blob39ea6a26fd5ba7bbef436cd403a6f58a90981412
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
18 # Placeholder for repositories without commits
19 git_empty_object=$(git hash-object -t tree /dev/null)
21 # default options
22 existing=0 recurse=0 discard=0 sync=fetch
24 results() {
25 local mode=$1 prev=$2 current=$3 path=$4 dest=$5
27 if [[ -w $dest ]]; then
28 printf >> "$dest" '%s:%s:%s:file://%s\n' "$mode" "$prev" "$current" "$path"
32 sync_package_config() {
33 local sync=$1 pkg=$2
35 if [[ $sync == 'auto' ]] && [[ $(git config --get --type bool aurutils.rebase) == 'true' ]]; then
36 printf >&2 '%s: aurutils.rebase is set for %s\n' "$argv0" "$pkg"
37 printf '%s' rebase
39 elif [[ $sync == 'auto' ]]; then
40 printf '%s' merge
41 else
42 printf '%s' "$sync"
46 usage() {
47 cat <<! | base64 -d
48 ICAgICAgICAgICAgIC4tLX5+LF9fCjotLi4uLiwtLS0tLS0tYH5+Jy5fLicKIGAtLCwsICAs
49 XyAgICAgIDsnflUnCiAgXywtJyAsJ2AtX187ICctLS4KIChfLyd+fiAgICAgICcnJycoOwoK
51 printf >&2 'usage: %s [-Sefr] [--rebase|--reset|--merge] [--] pkgname...\n' "$argv0"
52 exit 1
55 # option handling
56 source /usr/share/makepkg/util/parseopts.sh
58 opt_short='efrS'
59 opt_long=('auto' 'merge' 'reset' 'rebase' 'discard' 'existing' 'results:' 'ff'
60 'ff-only' 'no-ff' 'no-commit' 'recurse')
61 opt_hidden=('dump-options' 'sync:')
63 if ! parseopts "$opt_short" "${opt_long[@]}" "${opt_hidden[@]}" -- "$@"; then
64 usage
66 set -- "${OPTRET[@]}"
68 unset rebase_args merge_args results_file
69 while true; do
70 case "$1" in
71 # aur-fetch options
72 -S|--auto)
73 sync=auto ;;
74 -f|--discard)
75 discard=1 ;;
76 -e|--existing)
77 existing=1 ;;
78 --merge)
79 sync=merge ;;
80 --rebase)
81 sync=rebase ;;
82 --reset)
83 sync=reset ;;
84 --results)
85 shift; results_file=$(realpath -- "$1") ;;
86 # git options
87 --ff)
88 merge_args+=(-ff) ;;
89 --ff-only)
90 merge_args+=(--ff-only) ;;
91 --no-commit)
92 merge_args+=(--no-commit) ;;
93 --no-ff)
94 merge_args+=(--no-ff); rebase_args+=(--no-ff) ;;
95 # Compatibility options
96 --sync)
97 shift; sync=$1 ;;
98 -r|--recurse)
99 recurse=1 ;;
100 --dump-options)
101 printf -- '--%s\n' "${opt_long[@]}" ${AUR_DEBUG+"${opt_hidden[@]}"}
102 printf -- '%s' "${opt_short}" | sed 's/.:\?/-&\n/g'
103 exit ;;
104 --) shift; break ;;
105 esac
106 shift
107 done
109 # option validation
110 if [[ $sync == !(auto|merge|rebase|reset|fetch) ]]; then
111 printf >&2 '%s: invalid --sync mode\n' "$argv0"
112 exit 1
115 if (( ! $# )); then
116 printf >&2 '%s: no arguments given\n' "$argv0"
117 exit 1
120 # XXX: race with concurrent processes
121 if [[ -v results_file ]]; then
122 : >"$results_file" || exit 1 # truncate file
125 # Default to only allowing fast-forward merges (as git-pull)
126 if (( ! ${#merge_args[@]} )); then
127 merge_args=(--ff-only)
130 if (( recurse )); then
131 mapfile -t packages < <(aur depends --pkgbase "$@")
132 wait "$!"
134 elif (( $# == 1 )) && [[ $1 == "-" || $1 == "/dev/stdin" ]]; then
135 mapfile -t packages
136 else
137 packages=("$@")
138 set --
141 # Update revisions in local AUR mirror
142 declare -A local_clones
144 # With an AUR mirror, updates are retrieved in two steps. First, updates to the
145 # mirror are synchronized with `git-fetch`. Secondly, local clones of the miror
146 # are created and are updated with `git-fetch` and `git-merge` as usual.
147 if (( AUR_FETCH_USE_MIRROR )); then
148 while read -r IFS=':' pkg head; do
149 local_clones[$pkg]=$head
150 done < <(aur fetch--mirror --lclone "${packages[@]}")
151 wait "$!"
154 # Main loop
155 for pkg in "${packages[@]}"; do
156 unset -f git
157 if (( AUR_FETCH_USE_MIRROR )); then # branch origin/$pkg -> master
158 upstream=origin/$pkg
159 else
160 upstream=origin/master
163 # Verify if the repository is hosted on AUR (#959)
164 if (( existing )) && ! git ls-remote --exit-code "$AUR_LOCATION/$pkg" >/dev/null; then
165 printf >&2 '%s: warning: package %s is not in AUR, skipping\n' "$argv0" "$pkg"
166 continue
168 # Clone package if not existing
169 elif [[ ! -d $pkg/.git ]]; then
170 # Differentiate between local (fetch--mirror) and aurweb clones
171 if [[ ! ${local_clones[$pkg]} ]]; then
172 git clone "$AUR_LOCATION/$pkg" || exit 1
173 head=$(git -C "$pkg" rev-parse --verify quiet HEAD)
174 else
175 printf "Cloning into '%s'\n" "$pkg"
176 head=${local_clones[$pkg]}
177 fi >&2
179 if [[ $head ]]; then
180 git -C "$pkg" --no-pager log --pretty=reference -1
181 fi >&2
183 # XXX: race with multiple instances
184 if [[ -v results_file ]]; then
185 results 'clone' "$git_empty_object" "${head:-$git_empty_object}" "$PWD/$pkg" "$results_file"
188 # Update existing git repository
189 else
190 # Per-package lock
191 exec {fd}< "$pkg"/.git
192 flock --wait 5 "$fd" || exit 1
194 # Avoid issues with filesystem boundaries (#274)
195 git() { command git -C "$pkg" "$@"; }
197 # Retrieve new upstream commits
198 git fetch origin >&2 || exit
200 # Store original HEAD for --results output
201 orig_head=$(git rev-parse --verify --quiet HEAD)
202 orig_head=${orig_head:-$git_empty_object}
204 # Retrieve per-package configuration (defaults to global setting, #1007)
205 sync_pkg=$(sync_package_config "$sync" "$pkg")
207 # Reset working tree if new commits will be merged (--discard)
208 reset_on_update() {
209 git merge-base --is-ancestor "$1" HEAD || git checkout ./
212 # Merge in new history
213 case $sync_pkg in
214 rebase|merge)
215 (( discard )) && reset_on_update 'master@{u}'
216 ;;& # proceed to merge or rebase
217 rebase)
218 dest='HEAD'
219 git rebase "${rebase_args[@]}" "$upstream" ;;
220 merge)
221 dest='HEAD'
222 git merge "${merge_args[@]}" "$upstream" ;;
223 reset)
224 dest='master@{u}'
225 git reset --hard 'master@{u}' ;;
226 fetch)
227 # Preserve local branch
228 dest='master@{u}' ;;
229 esac >&2 || {
230 printf >&2 '%s: failed to %s %s\n' "$argv0" "$sync_pkg" "$pkg"
231 exit 1
233 head=$(git rev-parse --verify "$dest")
235 if [[ -v results_file ]]; then
236 results "$sync_pkg" "$orig_head" "$head" "$PWD/$pkg" "$results_file"
238 exec {fd}<&- # release lock
240 done
242 # vim: set et sw=4 sts=4 ft=sh: