|
|
|
#!/usr/bin/env bash
|
|
|
|
|
|
|
|
declare -A colors
|
|
|
|
colors[red]=$(tput setaf 1)
|
|
|
|
colors[green]=$(tput setaf 2)
|
|
|
|
colors[blue]=$(tput setaf 4)
|
|
|
|
colors[reset]=$(tput sgr0)
|
|
|
|
declare processes=4
|
|
|
|
declare quiet
|
|
|
|
declare force
|
|
|
|
declare -a dirs
|
|
|
|
declare -a ignore_dir
|
|
|
|
declare -a ignore_dirs
|
|
|
|
declare -a errs
|
|
|
|
|
|
|
|
usage() {
|
|
|
|
LESS=-FEXR less <<'HELP'
|
|
|
|
gitup [OPTIONS] [dirs]
|
|
|
|
|
|
|
|
search for git repos and update them
|
|
|
|
|
|
|
|
if unspecified, dir defaults to $HOME
|
|
|
|
|
|
|
|
-i [dir] comma separated list of directory paths to not search
|
|
|
|
-p [number] how many processes to run `git pull` in parallel
|
|
|
|
-q quiet level, may be stacked
|
|
|
|
first level suppresses output from `git pull`
|
|
|
|
second level suppresses job info
|
|
|
|
-F don't run interactively, `git pull` all dirs
|
|
|
|
use with caution, make sure you know which dirs will be matched
|
|
|
|
works best if gitup is provided a list of dirs known to have git repos
|
|
|
|
-h print this help
|
|
|
|
HELP
|
|
|
|
}
|
|
|
|
|
|
|
|
color() {
|
|
|
|
local c
|
|
|
|
c="$1"
|
|
|
|
shift
|
|
|
|
printf '%s' "${colors[$c]}"
|
|
|
|
printf '%s\n' "$@"
|
|
|
|
printf '%s' "${colors[reset]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
err() {
|
|
|
|
color red "$@" >&2
|
|
|
|
}
|
|
|
|
|
|
|
|
die() {
|
|
|
|
[[ -n "$1" ]] && err "$1"
|
|
|
|
exit 1
|
|
|
|
}
|
|
|
|
|
|
|
|
has() {
|
|
|
|
local verbose
|
|
|
|
if [[ $1 = '-v' ]]; then
|
|
|
|
verbose=1
|
|
|
|
shift
|
|
|
|
fi
|
|
|
|
for c; do c="${c%% *}"
|
|
|
|
if ! command -v "$c" &> /dev/null; then
|
|
|
|
(( "$verbose" > 0 )) && err "$c not found"
|
|
|
|
return 1
|
|
|
|
fi
|
|
|
|
done
|
|
|
|
}
|
|
|
|
|
|
|
|
has -v fzf git || die
|
|
|
|
|
|
|
|
while getopts ':hqp:i:F' x; do o="$OPTARG"
|
|
|
|
case "$x" in
|
|
|
|
h) usage; exit; ;;
|
|
|
|
p) processes="$o" ;;
|
|
|
|
q) (( ++quiet )) ;;
|
|
|
|
i) IFS=',' read -ra ignore_dir <<< "$o" ;;
|
|
|
|
F) (( ++force )) ;;
|
|
|
|
esac
|
|
|
|
done
|
|
|
|
shift $(( OPTIND - 1 ))
|
|
|
|
|
|
|
|
while :; do
|
|
|
|
if [[ -d "$1" ]]; then
|
|
|
|
dirs+=( "$1" )
|
|
|
|
fi
|
|
|
|
shift || break
|
|
|
|
done
|
|
|
|
|
|
|
|
for o in "${ignore_dir[@]}"; do
|
|
|
|
ignore_dirs+=( -path "*/$o" -o )
|
|
|
|
done
|
|
|
|
|
|
|
|
(( ${#dirs[@]} > 0 )) || dirs=("$HOME")
|
|
|
|
|
|
|
|
mapfile -t repos < <(find "${dirs[@]}" \
|
|
|
|
\( "${ignore_dirs[@]}" \
|
|
|
|
-fstype 'devfs' \
|
|
|
|
-o -fstype 'devtmpfs' \
|
|
|
|
-o -fstype 'proc' \
|
|
|
|
\) -prune -o -name '.git' -printf '%h\n' 2> /dev/null |
|
|
|
|
fzf --multi --cycle --inline-info +s -e ${force:+-f /})
|
|
|
|
|
|
|
|
(( ${#repos[@]} > 0 )) || exit
|
|
|
|
|
|
|
|
update() {
|
|
|
|
local name dir
|
|
|
|
dir="$1"
|
|
|
|
name="${dir##*/}"
|
|
|
|
(( quiet > 1 )) || color blue ":: updating $name"
|
|
|
|
if git -C "$dir" pull ${quiet:+-q}; then
|
|
|
|
(( quiet > 1 )) || color green ":: updated $name"
|
|
|
|
else
|
|
|
|
errs+=( "$name" )
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
for d in "${repos[@]}"; do
|
|
|
|
(( count++ >= processes )) && wait -n
|
|
|
|
update "$d" &
|
|
|
|
done
|
|
|
|
wait
|
|
|
|
|
|
|
|
if (( "${#errs[@]}" > 0 )); then
|
|
|
|
color red 'The following packages failed to update:'
|
|
|
|
color red " ${errs[*]}"
|
|
|
|
fi
|
|
|
|
color green "updated ${#repos[@]} repos"
|