Browse Source

fetch: add --discard, run git-merge by default

This makes aur-fetch more _like_ git-pull, without using git-pull. In particular,

* Local changes without `git-commit` (a dirty worktree) can be undone
  with `git-reset` before running `git-merge` or `git-rebase`, if and
  only if new upstream commits are available. Unlike `git-pull`, this
  does not run `git-fetch` (~1s runtime) twice per package.

* The above is not done by default as with --sync=auto, but only if
  `--discard` is specified (e.g. when fetching packages with a `pkgver()`
  function, where running `makepkg` results in a dirty worktree.) Otherwise,
  local file conflicts are left to `git-merge` or `git-rebase`.

* Similarly to `git-pull`, `git merge --ff-only` is run by default unless
  `--no-commit` or `--ff` are specified. With `--rebase`, `git-rebase` is
  run instead.

* To only run `git-fetch` (previous default operation), add `--fetch-only`.

This approach is more compatible to previous (<=9.6) aur-fetch versions,
with less deprecated options. However, support for the `git-config`
option `aurutils.rebase` is not preserved.

The `aurutils.rebase` option was only functional for the first package
argument, after which `sync=auto` is set to a different value. Add a
regression test and base the check on the original implementation in
commit 1a36716e4c.
pull/1007/head
Alad Wenter 2 months ago
parent
commit
372e3cd396
  1. 13
      completions/zsh/_aur.zsh
  2. 123
      lib/aur-fetch
  3. 16
      makepkg/aurutils.changelog
  4. 150
      man1/aur-fetch.1
  5. 0
      tests/issue/0085
  6. 0
      tests/issue/0187
  7. 0
      tests/issue/0216
  8. 0
      tests/issue/0257
  9. 0
      tests/issue/0378
  10. 0
      tests/issue/0403
  11. 0
      tests/issue/0513
  12. 0
      tests/issue/0636
  13. 0
      tests/issue/0706
  14. 0
      tests/issue/0714
  15. 0
      tests/issue/0741
  16. 0
      tests/issue/0880
  17. 0
      tests/issue/0900
  18. 0
      tests/issue/0908
  19. 0
      tests/issue/0918
  20. 0
      tests/issue/0925
  21. 0
      tests/issue/0938
  22. 0
      tests/issue/0958
  23. 31
      tests/issue/1007

13
completions/zsh/_aur.zsh

@ -193,8 +193,9 @@ _aur_fetch() {
local -A sync_types
sync_types[reset]="discard local changes"
sync_types[pull]="run git-pull to merge in upstream changes"
sync_types[fetch]="run git-fetch instead of git-pull"
sync_types[merge]="run git-merge to incorporate upstream changes"
sync_types[rebase]="run git-rebase to incorporate upstream changes"
sync_types[fetch]="only run git-fetch"
local -a sync_type_strings=()
local k
@ -203,12 +204,14 @@ _aur_fetch() {
done
args=(
'--existing[if a git repository is not found for a given package, ignore it instead of running git-clone]'
'--existing[if a package has no matching repository on AUR, ignore it instead of running git-clone]'
'(-r --recurse)'{-r,--recurse}'[download packages and their dependencies with aur-depends]'
'--results=[write colon-delimited output to FILE]:file: _files'
"(--reset --no-pull)--sync=[configure handling of local changes]:mode:(($sync_type_strings))"
'--discard[discard uncommited changes if git-rebase or git-merge result in new commits]'
#"(--reset --no-pull)--sync=[configure handling of local changes]:mode:(($sync_type_strings))"
"(--sync)--rebase[${sync_types[rebase]}, alias for --sync=rebase]"
"(--sync)--reset[${sync_types[reset]}, alias for --sync=reset]"
"(--sync)--no-pull[${sync_types[fetch]}, alias for --sync=fetch]"
"(--sync)--fetch-only[${sync_types[fetch]}, alias for --sync=fetch]"
)
# This is to handle the fact that -r/--recurse changes the meaning of positional arguments
if [[ $words[(ie)-r] -le ${#words} || $words[(ie)--recurse] -le ${#words} ]]; then

123
lib/aur-fetch

@ -13,8 +13,11 @@ export GIT_AUTHOR_EMAIL=aurutils@localhost
export GIT_COMMITTER_NAME=aurutils
export GIT_COMMITTER_EMAIL=aurutils@localhost
# git "empty" object, SHA1 4b825dc642cb6eb9a060e54bf8d69288fbee4904
git_empty_object=$(git hash-object -t tree /dev/null)
# default options
existing=0 recurse=0 sync=pull
existing=0 recurse=0 discard=0 sync=merge
results() {
local mode=$1 prev=$2 current=$3 path=$4 dest=$5
@ -41,43 +44,44 @@ if [[ ! -v NO_COLOR ]] && [[ ! -v AUR_DEBUG ]]; then
fi
opt_short='rS'
opt_long=('no-pull' 'rebase' 'reset' 'no-rebase' 'results:' 'existing' 'recurse'
'no-commit' 'autostash' 'ff-only' 'sync:')
opt_hidden=('dump-options')
opt_long=('existing' 'fetch-only' 'reset' 'rebase' 'results:' 'discard'
'ff' 'ff-only' 'no-ff' 'no-commit' 'recurse')
opt_hidden=('dump-options' 'sync:')
if ! parseopts "$opt_short" "${opt_long[@]}" "${opt_hidden[@]}" -- "$@"; then
usage
fi
set -- "${OPTRET[@]}"
unset pull_args results_file
unset rebase_args merge_args results_file
while true; do
case "$1" in
## fetch options
# aur-fetch options
--discard)
discard=1 ;;
--existing)
existing=1 ;;
--no-pull)
--fetch-only)
sync=fetch ;;
--rebase)
sync=rebase ;;
--reset)
sync=reset ;;
--results)
shift; results_file=$(realpath -- "$1") ;;
## git-pull options
--autostash)
pull_args+=(--autostash) ;;
# git options
--ff)
merge_args+=(-ff) ;;
--ff-only)
pull_args+=(--ff-only) ;;
--rebase)
pull_args+=(--rebase) ;;
--no-rebase)
pull_args+=(--no-rebase) ;;
merge_args+=(--ff-only) ;;
--no-commit)
# --no-ff so that a fast-forward merge also adds no new commits
pull_args+=(--no-commit --no-ff) ;;
## Compatibility options
merge_args+=(--no-commit) ;;
--no-ff)
merge_args+=(--no-ff); rebase_args+=(--no-ff) ;;
# Compatibility options
-S)
printf >&2 'deprecation notice: %s -S is an alias for --sync=pull\n' "$argv0"
sync=pull ;;
printf >&2 'deprecation notice: %s -S is an alias for --discard\n' "$argv0"
discard=1; sync=merge ;;
--sync)
shift; sync=$1 ;;
--recurse|-r)
@ -91,31 +95,32 @@ while true; do
shift
done
# Compatiblity options for aur-fetch <=9.6
case $sync in
rebase)
printf >&2 'deprecation notice: %s --sync=rebase is an alias for --rebase\n' "$argv0"
sync=pull; pull_args+=(--rebase) ;;
auto)
printf >&2 'deprecation notice: %s --sync=auto is an alias for --sync=pull\n' "$argv0"
sync=pull ;;
!(pull|fetch|reset))
error '%s: invalid --sync option: %s' "$argv0" "$sync"
usage ;;
esac
if [[ $sync == 'auto' ]]; then
printf >&2 'deprecation notice: %s --sync=auto is an alias for --discard\n' "$argv0"
discard=1; sync=merge
elif [[ $sync == !(merge|rebase|reset|fetch) ]]; then
error '%s: invalid --sync mode' "$argv0"
exit 1
fi
if (( ! $# )); then
error '%s: no arguments given' "$argv0"
exit 1
fi
# Default to only allowing fast-forward merges (as git-pull)
if (( ! ${#merge_args[@]} )); then
merge_args=(--ff-only)
fi
if [[ -v results_file ]]; then
: >"$results_file" || exit 1 # truncate file
fi
# Main loop
if (( recurse )); then
aur depends --pkgbase "$@" # stdin handled by aur-depends
elif (( $# == 1 )) && [[ $1 == "-" || $1 == "/dev/stdin" ]]; then
tee # noop
else
@ -123,6 +128,7 @@ else
fi | while read -r pkg; do
unset -f git
# Verify if the repository is hosted on AUR (#959)
if (( existing )) && ! git ls-remote --exit-code "$AUR_LOCATION/$pkg" >/dev/null; then
warning '%s: package is not in AUR, skipping' "$argv0" "$pkg"
continue
@ -131,45 +137,52 @@ fi | while read -r pkg; do
# Avoid issues with filesystem boundaries (#274)
git() { command git -C "$pkg" "$@"; }
# Store HEAD pre-merge for --results output
prev_head=$(git rev-parse --verify --quiet HEAD)
# Retrieve new upstream commits
git fetch -v origin >&2 || exit
# Store original HEAD for --results output
orig_head=$(git rev-parse --verify --quiet HEAD)
orig_head=${orig_head:-$git_empty_object}
if [[ ! $prev_head ]]; then
warning '%s: %s: HEAD not found, defaulting to empty tree' "$argv0" "$pkg"
prev_head=$(git hash-object -t tree /dev/null)
# Check if merge/rebase will have an effect; if so, reset the branch HEAD
if (( discard )) && [[ $sync == @(rebase|merge) ]]; then
git merge-base --is-ancestor 'master@{u}' HEAD || git reset --hard HEAD
fi
# Merge in new history
ref=0
case $sync in
pull)
ref='HEAD'
git pull origin master "${pull_args[@]}" -v ;;
fetch)
ref='FETCH_HEAD'
git fetch origin -v ;;
reset)
ref='master@{upstream}'
git fetch origin -v && git reset --hard "$ref" ;;
esac >&2 || {
error '%s: %s: --sync=%s failed' "$argv0" "$pkg" "$sync"
if [[ $sync == 'rebase' ]] || { [[ $sync == 'merge' ]] && [[ $(git config --get --type bool aurutils.rebase) == 'true' ]]; }; then
git rebase -v "${rebase_args[@]}" origin master
dest='HEAD'
elif [[ $sync == 'merge' ]]; then
git merge -v "${merge_args[@]}" origin master
dest='HEAD'
elif [[ $sync == 'reset' ]]; then
git reset --hard 'master@{u}'
dest='master@{u}'
elif [[ $sync == 'fetch' ]]; then
# Leave branch untouched
dest='master@{u}'
fi >&2 || {
error '%s: %s: failed to %s repository' "$argv0" "$pkg" "$sync"
exit 1
}
if [[ -v results_file ]]; then
commit=$(git rev-parse --verify "$ref")
results "$sync" "$prev_head" "$commit" "$PWD/$pkg" "$results_file"
head=$(git rev-parse --verify "$dest")
results "$sync" "$orig_head" "$head" "$PWD/$pkg" "$results_file"
fi
# Otherwise, try to clone anew
elif git clone "$AUR_LOCATION/$pkg" >&2; then
# Verify if the repository is empty (#959)
if head=$(git -C "$pkg" rev-parse --verify --quiet HEAD); then
git -C "$pkg" --no-pager log --pretty=reference -1 >&2
fi
if [[ -v results_file ]]; then
results 'clone' '0' "${head:-0}" "$PWD/$pkg" "$results_file"
results 'clone' "$git_empty_object" "${head:-$git_empty_object}" "$PWD/$pkg" "$results_file"
fi
else
error '%s: %s: failed to clone repository' "$argv0" "$pkg"

16
makepkg/aurutils.changelog

@ -12,15 +12,15 @@
+ use `AUR_PACMAN_AUTH` as elevation command (prior: `PACMAN_AUTH`)
* `aur-fetch`
+ `git-pull` is now the default operation (`--sync=pull`)
- local changes are no longer discarded automatically
- add `--no-pull` for running `git-fetch`
- add `--ff-only`, `--autostash`, `--no-commit`, `--rebase`, `--no-rebase` as `git-pull` options
- set per-repository rebase with `pull.rebase` git configuration
- deprecate `--sync=rebase`, `--sync=auto`
+ add `--reset` (shorthand for `--sync=reset`)
+ change default operation to `git fetch && git-merge --ff-only`
- local (uncommited) changes are preserved, unless `--discard` is specified
- set default author for merge commits to `aurutils@localhost`
- add `--fetch-only` to only run `git-fetch`
- add `--discard` to run `git-reset` on new upstream commits
- add `--ff`, `--ff-only`, `--no-ff`, `--no-commit` options
- deprecate `-S`, `--sync=auto`
+ add `--reset` and `--rebase`, deprecate `--sync=reset`, `--sync=rebase`
+ support multiple branches, with commits merged from `origin/master`
+ `--results` output now uses `pull`, `reset` and `fetch` keywords
* `aur-pkglist`
+ output is now independent of command-line argument order

150
man1/aur-fetch.1

@ -1,10 +1,9 @@
.TH AUR-FETCH 1 2022-06-09 AURUTILS
.TH AUR-FETCH 1 2022-06-24 AURUTILS
.SH NAME
aur\-fetch \- download packages from the AUR
.
.SH SYNOPSIS
.SY "aur fetch"
.OP \-S
.SY "aur fetch [--rebase] [--reset]"
.IR pkgbase " [" pkgbase... ]
.YS
.
@ -19,34 +18,80 @@ or
packages are taken from standard input.
.PP
.
By default, git repositories are retrieved with
.BR git (1)
repositories are retrieved with
.BR git\-clone (1)
if they do not exist. Otherwise, they are updated with
.B git\-pull
from the
.B origin/master
branch.
if they do not exist. Otherwise, the
.B origin
remote is updated with
.B git\-fetch
and changes are merged with
.B git\-merge \-\-ff\-only
by default. Alternatively,
.BR git\-rebase ,
.BR git\-reset
or only
.BR git\-fetch
can be run.
.PP
Since AUR packages have
.B master
as the only remote branch, changes are always merged
from
.BR origin/master .
Local changes may however live in separate, feature branches.
.
.SH OPTIONS
.TP
.BR \-\-sync=pull
.BR \-\-sync=merge
Run
.BR git\-pull
to merge in upstream changes. This is the default.
.BR git\-merge
to merge in upstream changes. This is the default, unless
.B aurutils.rebase
equals
.BR true
in
.BR git\-config (1)
(see
.B \-\-sync=merge
for details.)
.IP
If no
.BR git\-merge (1)
options are specified,
only fast-forwards are allowed
.RB ( "git\-merge \-\-ff\-only" ).
.B aur\-fetch
forwards the
.BR \-\-rebase ,
.BR \-\-no\-rebase ,
.BR \-\-ff\-only
and
.BR \-\-ff ,
.BR \-\-ff\-only ,
.BR \-\-no\-commit
and
.BR \-\-no\-ff
options to
.BR git\-pull .
.B \-\-no\-commit
additionally forwards
.B git pull \-\-no\-ff
so that no commits are merged even when a fast-forward is possible.
.B git\-merge
when specified.
.IP
.B Note:
When previewing changes with
.BR \-\-no\-commit ,
it is advised to also use
.BR \-\-no\-ff
so that no commits are applied to the index, even when a fast-forward is possible. See
.BR git\-merge (1)
for details.
.
.TP
.BR \-\-rebase ", " \-\-sync=rebase
Run
.BR git\-rebase (1)
instead of
.BR git\-merge (1),
re-applying local commits on top of upstream AUR commits. The
.B \-\-no\-ff
option is supported and forwarded to
.BR git\-merge
when specified.
.IP
.B Warning:
If a rebase does not apply cleanly, the user is expected to fix any
@ -59,22 +104,42 @@ option should thus be used with care. See
for details.
.
.TP
.BR \-\-sync=reset ", " \-\-reset
.BR \-\-reset ", " \-\-sync=rebase
Retrieve new revisions with
.B git\-fetch origin
and
.B git\-reset
to the
.B master@{upstream}
commit, discarding any local changes.
commit, removing any local commits.
.
.TP
.BR \-\-sync=fetch ", " \-\-no\-pull
Run
.B git\-fetch
instead of
.BR git\-pull .
.IP
.BR \-\-fetch\-only ", " \-\-sync=fetch
Only retrieve new revisions with
.BR git\-fetch ,
if the repository exists, otherwise run
.BR git\-clone .
.
.TP
.BR \-\-discard
When using
.BR \-\-sync=merge
or
.BR \-\-sync=rebase ,
uncommited changes may cause the operation to fail. The
.B \-\-discard
option discards these changes
.B git \-\-reset HEAD
.I if
either
.B git\-merge
or
.B git\-rebase
would result in new changes. This is verified by checking if
.B master@{upstream}
is an ancestor of the
.B HEAD
commit.
.
.TP
.BR \-\-existing
@ -91,9 +156,10 @@ If this option is specified, arguments must be supplied by
instead of by
.BR pkgbase .
.IP
This can be written as the pipeline:
This is equivalent to the pipeline:
.IP
.EX
$ aur depends --pkgbase "$@" | aur fetch -
aur depends --pkgbase "$@" | aur fetch -
.EE
.
.TP
@ -107,7 +173,8 @@ Possible values for
.I action
are
.BR clone ,
.BR pull ,
.BR merge ,
.BR rebase ,
and
.BR fetch .
If
@ -116,12 +183,25 @@ is set to
.BR clone ,
.I <head_from>
is set to
.BR 0 .
.BR 4b825dc642cb6eb9a060e54bf8d69288fbee4904 ,
the
.BR git (1)
empty tree object.
.I <path>
is the absolute path to the corresponding git repository.
.IP
Can be used by higher level tools to differentiate new clones from
updates to existing repositories.
.IP
.B Note:
When using
.B \-\-no\-commit \-\-no\-ff
and
.BR git\-merge(1) ,
.I head_from
and
.I head_to
will be identical.
.
.SH SEE ALSO
.ad l
@ -131,10 +211,10 @@ updates to existing repositories.
.BR git (1),
.BR git\-clone (1),
.BR git\-config (1),
.BR git\-diff (1),
.BR git\-fetch (1),
.BR git\-log (1),
.BR git\-merge (1),
.BR git\-pull (1),
.BR git\-rebase (1),
.BR git\-reset (1),
.BR git\-rev\-parse (1)
.

0
tests/issue/085 → tests/issue/0085

0
tests/issue/187 → tests/issue/0187

0
tests/issue/216 → tests/issue/0216

0
tests/issue/257 → tests/issue/0257

0
tests/issue/378 → tests/issue/0378

0
tests/issue/403 → tests/issue/0403

0
tests/issue/513 → tests/issue/0513

0
tests/issue/636 → tests/issue/0636

0
tests/issue/706 → tests/issue/0706

0
tests/issue/714 → tests/issue/0714

0
tests/issue/741 → tests/issue/0741

0
tests/issue/880 → tests/issue/0880

0
tests/issue/900 → tests/issue/0900

0
tests/issue/908 → tests/issue/0908

0
tests/issue/918 → tests/issue/0918

0
tests/issue/925 → tests/issue/0925

0
tests/issue/938 → tests/issue/0938

0
tests/issue/958 → tests/issue/0958

31
tests/issue/1007

@ -0,0 +1,31 @@
#!/bin/bash
set -ex
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
cd "$tmp"
aur fetch aurutils aurutils-git yuzu
cd aurutils
git config --add aurutils.rebase true
echo '# test for issue/1007' >>PKGBUILD
git commit -m 'test for issue/1007' PKGBUILD
head1=$(git rev-parse --verify HEAD)
cd ../aurutils-git
git reset --hard HEAD^1
head2=$(git rev-parse --verify HEAD)
cd ../yuzu
git reset --hard HEAD^1
head3=$(git rev-parse --verify HEAD)
cd ..
aur fetch --sync=auto aurutils aurutils-git yuzu
cd aurutils
[[ $(git rev-parse --verify HEAD) == $head1 ]]
cd ../aurutils-git
[[ $(git rev-parse --verify HEAD) != $head2 ]]
cd ../yuzu
[[ $(git rev-parse --verify HEAD) != $head3 ]]
Loading…
Cancel
Save