pre-push 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. #!/bin/sh
  2. #
  3. # Copyright 2024 Odin Kroeger
  4. #
  5. # This file is part of SieveManager.
  6. #
  7. # SieveManager is free software: you can redistribute it and/or
  8. # modify it under the terms of the GNU General Public License as
  9. # published by the Free Software Foundation, either version 3 of
  10. # the License, or (at your option) any later version.
  11. #
  12. # SieveManager is distributed in the hope that it will be useful,
  13. # but WITHOUT ALL WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public
  18. # License along with SieveManager. If not, see
  19. # <https://www.gnu.org/licenses/>.
  20. #
  21. set -Cefu
  22. #
  23. # Globals
  24. #
  25. progname="$(basename "$0")"
  26. : "${progname:="${0##*/}"}"
  27. readonly progname
  28. lockfile=".$progname.lock"
  29. readonly lockfile
  30. pylintflags='--disable=W0511'
  31. readonly pylintflags
  32. remote="$1"
  33. readonly remote
  34. repodir="$(git rev-parse --show-toplevel)"
  35. readonly repodir
  36. #
  37. # Functions
  38. #
  39. # Terminate children and clean up before exiting
  40. atexit() {
  41. # shellcheck disable=2319
  42. __atexit_retval="$?"
  43. trap '' EXIT HUP INT TERM
  44. set +e
  45. # shellcheck disable=2046
  46. kill -s TERM -- $(jobs -p) -$$ 2>/dev/null
  47. wait
  48. eval "${atexit-}"
  49. unset atexit
  50. return "$__atexit_retval"
  51. }
  52. # Catch a signal, clean up, and re-raise the signal
  53. catch() {
  54. caught="${1:?}"
  55. [ "${catch-}" ] || return 0
  56. atexit
  57. trap - "$caught"
  58. kill -s "$caught" "$$"
  59. }
  60. # Print a message to stderr and exit the process with a non-zero status
  61. err() {
  62. warn "$@"
  63. exit 1
  64. }
  65. # Wait to acquire a lock
  66. lock() (
  67. timeout="${1:?}"
  68. for i in $(seq 0 "$timeout")
  69. do
  70. if ( printf '%d\n' "$$" >"$lockfile"; ) >/dev/null 2>&1
  71. then
  72. return
  73. else
  74. read -r pid <"$lockfile" || :
  75. if ! [ "$pid" ] || ! kill -0 -- "$pid" >/dev/null 2>&1
  76. then
  77. warn "Removing stale $lockfile ..."
  78. rm -f .pre-push.lock
  79. elif [ "$i" -lt "$timeout" ]
  80. then
  81. sleep 1
  82. fi
  83. fi
  84. done
  85. warn "Could not acquire $lockfile in $timeout secs"
  86. return 1
  87. )
  88. # Remove the lockfile
  89. unlock() (
  90. if [ -e "$lockfile" ]
  91. then
  92. read -r pid <"$lockfile"
  93. if [ "$pid" ] && [ "$pid" -eq "$$" ]
  94. then rm -f "$lockfile"
  95. fi
  96. fi
  97. )
  98. # Print a warning to stderr
  99. warn() {
  100. printf '%s: %s\n' "$progname" "$*" >&2
  101. }
  102. #
  103. # Init
  104. #
  105. # Set up clean-up and signal handling
  106. trap atexit EXIT
  107. # shellcheck disable=2064
  108. for sig in HUP INT TERM
  109. do trap "catch $sig" "$sig"
  110. done
  111. caught=
  112. # Check that the repodir is set an accessible
  113. if ! [ "$repodir" ]
  114. then err "Could not locate repository"
  115. fi
  116. cd -P "$repodir" || exit
  117. # Make sure only one instance of pre-push is running at a time
  118. atexit="unlock ; ${atexit-}"
  119. lock 2
  120. #
  121. # Main
  122. #
  123. # Collect filenames
  124. sources=
  125. # shellcheck disable=2034
  126. while read -r localref localhash remoteref remotehash
  127. do
  128. branch="${remoteref##*/}"
  129. if [ "$(git ls-remote "$remote" "refs/heads/$branch")" ]
  130. then path="$remote/$branch"
  131. else path=
  132. fi
  133. # Filesnames should not contain whitespace
  134. # shellcheck disable=2086
  135. for file in $(git diff --name-only --diff-filter=d $path HEAD)
  136. do
  137. case $file in
  138. (*.py) sources="$sources $file"
  139. esac
  140. done
  141. readonly sources
  142. done
  143. # Check code
  144. if [ "$sources" ]
  145. then
  146. if ! git diff -s --exit-code
  147. then
  148. catch=
  149. git stash -q
  150. atexit="git stash pop -q; ${atexit-}"
  151. catch=y
  152. [ "$caught" ] && kill -s "$caught" "$$"
  153. fi
  154. # shellcheck disable=1091
  155. [ -e bin/activate ] && . ./bin/activate
  156. make "PYLINTFLAGS=$pylintflags" sources="${sources# }" check lint
  157. fi