You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

gitup 2.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. #!/usr/bin/env bash
  2. declare -A colors
  3. colors[red]=$(tput setaf 1)
  4. colors[green]=$(tput setaf 2)
  5. colors[blue]=$(tput setaf 4)
  6. colors[reset]=$(tput sgr0)
  7. declare processes=4
  8. declare quiet
  9. declare force
  10. declare -a dirs
  11. declare -a ignore_dir
  12. declare -a ignore_dirs
  13. declare -a errs
  14. usage() {
  15. LESS=-FEXR less <<'HELP'
  16. gitup [OPTIONS] [dirs]
  17. search for git repos and update them
  18. if unspecified, dir defaults to $HOME
  19. -i [dir] comma separated list of directory paths to not search
  20. -p [number] how many processes to run `git pull` in parallel
  21. -q quiet level, may be stacked
  22. first level suppresses output from `git pull`
  23. second level suppresses job info
  24. -F don't run interactively, `git pull` all dirs
  25. use with caution, make sure you know which dirs will be matched
  26. works best if gitup is provided a list of dirs known to have git repos
  27. -h print this help
  28. HELP
  29. }
  30. color() {
  31. local c
  32. c="$1"
  33. shift
  34. printf '%s' "${colors[$c]}"
  35. printf '%s\n' "$@"
  36. printf '%s' "${colors[reset]}"
  37. }
  38. err() {
  39. color red "$@" >&2
  40. }
  41. die() {
  42. [[ -n "$1" ]] && err "$1"
  43. exit 1
  44. }
  45. has() {
  46. local verbose
  47. if [[ $1 = '-v' ]]; then
  48. verbose=1
  49. shift
  50. fi
  51. for c; do c="${c%% *}"
  52. if ! command -v "$c" &> /dev/null; then
  53. (( "$verbose" > 0 )) && err "$c not found"
  54. return 1
  55. fi
  56. done
  57. }
  58. has -v fzf git || die
  59. while getopts ':hqp:i:F' x; do o="$OPTARG"
  60. case "$x" in
  61. h) usage; exit; ;;
  62. p) processes="$o" ;;
  63. q) (( ++quiet )) ;;
  64. i) IFS=',' read -ra ignore_dir <<< "$o" ;;
  65. F) (( ++force )) ;;
  66. esac
  67. done
  68. shift $(( OPTIND - 1 ))
  69. while :; do
  70. if [[ -d "$1" ]]; then
  71. dirs+=( "$1" )
  72. fi
  73. shift || break
  74. done
  75. for o in "${ignore_dir[@]}"; do
  76. ignore_dirs+=( -path "*/$o" -o )
  77. done
  78. (( ${#dirs[@]} > 0 )) || dirs=("$HOME")
  79. mapfile -t repos < <(find "${dirs[@]}" \
  80. \( "${ignore_dirs[@]}" \
  81. -fstype 'devfs' \
  82. -o -fstype 'devtmpfs' \
  83. -o -fstype 'proc' \
  84. \) -prune -o -name '.git' -printf '%h\n' 2> /dev/null |
  85. fzf --multi --cycle --inline-info +s -e ${force:+-f /})
  86. (( ${#repos[@]} > 0 )) || exit
  87. update() {
  88. local name dir
  89. dir="$1"
  90. name="${dir##*/}"
  91. (( quiet > 1 )) || color blue ":: updating $name"
  92. if git -C "$dir" pull ${quiet:+-q}; then
  93. (( quiet > 1 )) || color green ":: updated $name"
  94. else
  95. errs+=( "$name" )
  96. fi
  97. }
  98. for d in "${repos[@]}"; do
  99. (( count++ >= processes )) && wait -n
  100. update "$d" &
  101. done
  102. wait
  103. if (( "${#errs[@]}" > 0 )); then
  104. color red 'The following packages failed to update:'
  105. color red " ${errs[*]}"
  106. fi
  107. color green "updated ${#repos[@]} repos"