mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-09-29 10:43:42 +08:00
add shellcheck-like static analysis (#38877)
This commit is contained in:
+10
-10
@@ -18,21 +18,21 @@ function agnos_init {
|
||||
sudo chmod 660 /dev/adsprpc-smd /dev/ion /dev/kgsl-3d0
|
||||
|
||||
# Check if AGNOS update is required
|
||||
if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then
|
||||
if [ "$(< /VERSION)" != "$AGNOS_VERSION" ]; then
|
||||
AGNOS_PY="$DIR/openpilot/common/hardware/comma/agnos.py"
|
||||
MANIFEST="$DIR/openpilot/system/hardware/comma/agnos.json"
|
||||
if $AGNOS_PY --verify $MANIFEST; then
|
||||
if "$AGNOS_PY" --verify "$MANIFEST"; then
|
||||
sudo reboot
|
||||
fi
|
||||
while true; do
|
||||
$DIR/openpilot/common/hardware/comma/updater $AGNOS_PY $MANIFEST
|
||||
"$DIR/openpilot/common/hardware/comma/updater" "$AGNOS_PY" "$MANIFEST"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
function launch {
|
||||
# Remove orphaned git lock if it exists on boot
|
||||
[ -f "$DIR/.git/index.lock" ] && rm -f $DIR/.git/index.lock
|
||||
[ -f "$DIR/.git/index.lock" ] && rm -f "$DIR/.git/index.lock"
|
||||
|
||||
# Check to see if there's a valid overlay-based update available. Conditions
|
||||
# are as follows:
|
||||
@@ -44,7 +44,7 @@ function launch {
|
||||
# that completed successfully and synced to disk.
|
||||
|
||||
if [ -f "${DIR}/.overlay_init" ]; then
|
||||
find ${DIR}/.git -newer ${DIR}/.overlay_init | grep -q '.' 2> /dev/null
|
||||
find "${DIR}/.git" -newer "${DIR}/.overlay_init" | grep -q '.' 2> /dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "${DIR} has been modified, skipping overlay update installation"
|
||||
else
|
||||
@@ -53,9 +53,9 @@ function launch {
|
||||
echo "Valid overlay update found, installing"
|
||||
LAUNCHER_LOCATION="${BASH_SOURCE[0]}"
|
||||
|
||||
mv $DIR /data/safe_staging/old_openpilot
|
||||
mv "${STAGING_ROOT}/finalized" $DIR
|
||||
cd $DIR
|
||||
mv "$DIR" /data/safe_staging/old_openpilot
|
||||
mv "${STAGING_ROOT}/finalized" "$DIR"
|
||||
cd "$DIR"
|
||||
|
||||
echo "Restarting launch script ${LAUNCHER_LOCATION}"
|
||||
unset AGNOS_VERSION
|
||||
@@ -69,7 +69,7 @@ function launch {
|
||||
fi
|
||||
|
||||
# handle pythonpath
|
||||
ln -sfn $(pwd) /data/pythonpath
|
||||
ln -sfn "$(pwd)" /data/pythonpath
|
||||
export PYTHONPATH="$PWD"
|
||||
|
||||
# submodule package symlinks for PYTHONPATH imports on device.
|
||||
@@ -90,7 +90,7 @@ function launch {
|
||||
|
||||
# start manager
|
||||
cd openpilot/system/manager
|
||||
if [ ! -f $DIR/prebuilt ]; then
|
||||
if [ ! -f "$DIR/prebuilt" ]; then
|
||||
./build.py
|
||||
fi
|
||||
./manager.py
|
||||
|
||||
@@ -23,8 +23,8 @@ done
|
||||
|
||||
# sudo apt install inkscape
|
||||
|
||||
for svg in $(find $DIR -type f | grep svg$); do
|
||||
bunx svgo $svg --multipass --pretty --indent 2
|
||||
for svg in $(find "$DIR" -type f | grep svg$); do
|
||||
bunx svgo "$svg" --multipass --pretty --indent 2
|
||||
|
||||
# convert to PNG
|
||||
png="${svg%.svg}.png"
|
||||
|
||||
@@ -3,7 +3,7 @@ set -e
|
||||
|
||||
SCRIPT_DIR=$(dirname "$0")
|
||||
BASEDIR=$(realpath "$SCRIPT_DIR/../../../")
|
||||
cd $BASEDIR
|
||||
cd "$BASEDIR"
|
||||
|
||||
# tests that our build system's dependencies are configured properly,
|
||||
# needs a machine with lots of cores
|
||||
@@ -11,7 +11,7 @@ cd $BASEDIR
|
||||
# helpful commands:
|
||||
# scons -Q --tree=derived
|
||||
|
||||
cd $BASEDIR/opendbc_repo/
|
||||
cd "$BASEDIR/opendbc_repo/"
|
||||
scons --clean
|
||||
scons --no-cache --random
|
||||
if ! scons -q; then
|
||||
|
||||
@@ -29,7 +29,7 @@ if [ -d /data/safe_staging/ ]; then
|
||||
fi
|
||||
|
||||
CONTINUE_PATH="/data/continue.sh"
|
||||
tee $CONTINUE_PATH << EOF
|
||||
tee "$CONTINUE_PATH" << EOF
|
||||
#!/usr/bin/env bash
|
||||
|
||||
sudo abctl --set_success
|
||||
@@ -54,7 +54,7 @@ done
|
||||
|
||||
sleep infinity
|
||||
EOF
|
||||
chmod +x $CONTINUE_PATH
|
||||
chmod +x "$CONTINUE_PATH"
|
||||
|
||||
export GIT_LFS_SKIP_SMUDGE=1
|
||||
pull_lfs() {
|
||||
@@ -87,16 +87,16 @@ pull_lfs() {
|
||||
safe_checkout() {
|
||||
# completely clean TEST_DIR
|
||||
|
||||
cd $SOURCE_DIR
|
||||
cd "$SOURCE_DIR"
|
||||
|
||||
# cleanup orphaned locks
|
||||
find .git -type f -name "*.lock" -exec rm {} +
|
||||
|
||||
git reset --hard
|
||||
git fetch --no-tags --no-recurse-submodules -j4 --verbose --depth 1 origin $GIT_COMMIT
|
||||
git fetch --no-tags --no-recurse-submodules -j4 --verbose --depth 1 origin "$GIT_COMMIT"
|
||||
find . -maxdepth 1 -not -path './.git' -not -name '.' -not -name '..' -exec rm -rf '{}' \;
|
||||
git reset --hard $GIT_COMMIT
|
||||
git checkout $GIT_COMMIT
|
||||
git reset --hard "$GIT_COMMIT"
|
||||
git checkout "$GIT_COMMIT"
|
||||
git clean -xdff
|
||||
git submodule sync
|
||||
git submodule foreach --recursive "git reset --hard && git clean -xdff"
|
||||
@@ -106,22 +106,22 @@ safe_checkout() {
|
||||
pull_lfs
|
||||
|
||||
echo "git checkout done, t=$SECONDS"
|
||||
du -hs $SOURCE_DIR $SOURCE_DIR/.git
|
||||
du -hs "$SOURCE_DIR" "$SOURCE_DIR/.git"
|
||||
|
||||
rsync -a --delete $SOURCE_DIR $TEST_DIR
|
||||
rsync -a --delete "$SOURCE_DIR" "$TEST_DIR"
|
||||
}
|
||||
|
||||
unsafe_checkout() {( set -e
|
||||
# checkout directly in test dir, leave old build products
|
||||
|
||||
cd $TEST_DIR
|
||||
cd "$TEST_DIR"
|
||||
|
||||
# cleanup orphaned locks
|
||||
find .git -type f -name "*.lock" -exec rm {} +
|
||||
|
||||
git fetch --no-tags --no-recurse-submodules -j8 --verbose --depth 1 origin $GIT_COMMIT
|
||||
git checkout --force --no-recurse-submodules $GIT_COMMIT
|
||||
git reset --hard $GIT_COMMIT
|
||||
git fetch --no-tags --no-recurse-submodules -j8 --verbose --depth 1 origin "$GIT_COMMIT"
|
||||
git checkout --force --no-recurse-submodules "$GIT_COMMIT"
|
||||
git reset --hard "$GIT_COMMIT"
|
||||
git clean -dff
|
||||
git submodule sync
|
||||
git submodule foreach --recursive "git reset --hard && git clean -df"
|
||||
@@ -135,7 +135,7 @@ export GIT_PACK_THREADS=8
|
||||
|
||||
# set up environment
|
||||
if [ ! -d "$SOURCE_DIR" ]; then
|
||||
git clone https://github.com/commaai/openpilot.git $SOURCE_DIR
|
||||
git clone https://github.com/commaai/openpilot.git "$SOURCE_DIR"
|
||||
fi
|
||||
|
||||
if [ ! -z "$UNSAFE" ]; then
|
||||
@@ -152,7 +152,7 @@ else
|
||||
fi
|
||||
|
||||
# submodule package symlinks for PYTHONPATH imports on device (same as launch_chffrplus.sh)
|
||||
cd $TEST_DIR
|
||||
cd "$TEST_DIR"
|
||||
ln -sfn msgq_repo/msgq msgq
|
||||
ln -sfn opendbc_repo/opendbc opendbc
|
||||
ln -sfn rednose_repo/rednose rednose
|
||||
|
||||
@@ -4,7 +4,7 @@ set -euo pipefail
|
||||
DIR="$(cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd)"
|
||||
ROOT="$DIR/../../../"
|
||||
|
||||
cd $DIR
|
||||
cd "$DIR"
|
||||
./update_translations.py
|
||||
|
||||
command -v codex >/dev/null || {
|
||||
|
||||
@@ -4,6 +4,6 @@ while :; do
|
||||
./camerad &
|
||||
pid="$!"
|
||||
sleep 2
|
||||
kill -2 $pid
|
||||
wait $pid
|
||||
kill -2 "$pid"
|
||||
wait "$pid"
|
||||
done
|
||||
|
||||
@@ -18,4 +18,4 @@ SCRIPT_DIR=$(dirname "$0")
|
||||
OPENPILOT_DIR=$SCRIPT_DIR/../../
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||
cd $OPENPILOT_DIR/system/manager && exec ./manager.py
|
||||
cd "$OPENPILOT_DIR/system/manager" && exec ./manager.py
|
||||
|
||||
+2
-2
@@ -6,6 +6,6 @@ if [ $# -eq 0 ]; then
|
||||
fi
|
||||
|
||||
BASE="https://github.com/commaai/openpilot/pull/"
|
||||
PR_NUM="$(echo $1 | grep -o -E '[0-9]+')"
|
||||
PR_NUM="$(echo "$1" | grep -o -E '[0-9]+')"
|
||||
|
||||
curl -L $BASE/$PR_NUM.patch | git apply -3
|
||||
curl -L "$BASE/$PR_NUM.patch" | git apply -3
|
||||
|
||||
@@ -7,10 +7,10 @@ if [ $# -eq 0 ]; then
|
||||
fi
|
||||
|
||||
BASE="https://github.com/commaai/openpilot/pull/"
|
||||
PR_NUM="$(echo $1 | grep -o -E '[0-9]+')"
|
||||
PR_NUM="$(echo "$1" | grep -o -E '[0-9]+')"
|
||||
BRANCH=tmp-pr${PR_NUM}
|
||||
|
||||
git branch -D -f $BRANCH || true
|
||||
git fetch -u -f origin pull/$PR_NUM/head:$BRANCH
|
||||
git switch $BRANCH
|
||||
git branch -D -f "$BRANCH" || true
|
||||
git fetch -u -f origin "pull/$PR_NUM/head:$BRANCH"
|
||||
git switch "$BRANCH"
|
||||
git reset --hard FETCH_HEAD
|
||||
|
||||
@@ -11,7 +11,7 @@ BRANCH="master"
|
||||
RUNS="20"
|
||||
|
||||
COOKIE_JAR=/tmp/cookies
|
||||
CRUMB=$(curl -s --cookie-jar $COOKIE_JAR 'https://jenkins.comma.life/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)')
|
||||
CRUMB=$(curl -s --cookie-jar "$COOKIE_JAR" 'https://jenkins.comma.life/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)')
|
||||
|
||||
FIRST_LOOP=1
|
||||
|
||||
@@ -25,13 +25,13 @@ function loop() {
|
||||
|
||||
if [[ $FIRST_LOOP ]]; then
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
GIT_LFS_SKIP_SMUDGE=1 git clone --quiet -b $BRANCH --depth=1 --no-tags git@github.com:commaai/openpilot $TEMP_DIR
|
||||
git -C $TEMP_DIR checkout --quiet -b $JENKINS_BRANCH
|
||||
echo "TESTING: $(date)" >> $TEMP_DIR/testing_jenkins
|
||||
git -C $TEMP_DIR add testing_jenkins
|
||||
git -C $TEMP_DIR commit --quiet -m "testing"
|
||||
git -C $TEMP_DIR push --quiet -f origin $JENKINS_BRANCH
|
||||
rm -rf $TEMP_DIR
|
||||
GIT_LFS_SKIP_SMUDGE=1 git clone --quiet -b "$BRANCH" --depth=1 --no-tags git@github.com:commaai/openpilot "$TEMP_DIR"
|
||||
git -C "$TEMP_DIR" checkout --quiet -b "$JENKINS_BRANCH"
|
||||
echo "TESTING: $(date)" >> "$TEMP_DIR/testing_jenkins"
|
||||
git -C "$TEMP_DIR" add testing_jenkins
|
||||
git -C "$TEMP_DIR" commit --quiet -m "testing"
|
||||
git -C "$TEMP_DIR" push --quiet -f origin "$JENKINS_BRANCH"
|
||||
rm -rf "$TEMP_DIR"
|
||||
FIRST_BUILD=1
|
||||
echo ''
|
||||
echo 'waiting on Jenkins...'
|
||||
@@ -40,15 +40,15 @@ function loop() {
|
||||
FIRST_LOOP=""
|
||||
fi
|
||||
|
||||
FIRST_BUILD=$(curl -s $API_ROUTE/api/json | jq .nextBuildNumber)
|
||||
FIRST_BUILD=$(curl -s "$API_ROUTE/api/json" | jq .nextBuildNumber)
|
||||
LAST_BUILD=$((FIRST_BUILD+N-1))
|
||||
TEST_BUILDS=( $(seq $FIRST_BUILD $LAST_BUILD) )
|
||||
read -r -a TEST_BUILDS <<< "$(seq -s ' ' "$FIRST_BUILD" "$LAST_BUILD")"
|
||||
|
||||
# Start N new builds
|
||||
for i in ${TEST_BUILDS[@]};
|
||||
for i in "${TEST_BUILDS[@]}";
|
||||
do
|
||||
echo "Starting build $i"
|
||||
curl -s --output /dev/null --cookie $COOKIE_JAR -H "$CRUMB" -X POST $API_ROUTE/build?delay=0sec
|
||||
curl -s --output /dev/null --cookie "$COOKIE_JAR" -H "$CRUMB" -X POST "$API_ROUTE/build?delay=0sec"
|
||||
sleep 5
|
||||
done
|
||||
echo ""
|
||||
@@ -58,14 +58,14 @@ function loop() {
|
||||
sleep 30
|
||||
|
||||
count=0
|
||||
for i in ${TEST_BUILDS[@]};
|
||||
for i in "${TEST_BUILDS[@]}";
|
||||
do
|
||||
RES=$(curl -s -w "\n%{http_code}" --cookie $COOKIE_JAR -H "$CRUMB" $API_ROUTE/$i/api/json)
|
||||
RES=$(curl -s -w "\n%{http_code}" --cookie "$COOKIE_JAR" -H "$CRUMB" "$API_ROUTE/$i/api/json")
|
||||
HTTP_CODE=$(tail -n1 <<< "$RES")
|
||||
JSON=$(sed '$ d' <<< "$RES")
|
||||
|
||||
if [[ $HTTP_CODE == "200" ]]; then
|
||||
STILL_RUNNING=$(echo $JSON | jq .inProgress)
|
||||
STILL_RUNNING=$(echo "$JSON" | jq .inProgress)
|
||||
if [[ $STILL_RUNNING == "true" ]]; then
|
||||
echo -e "Build $i: ${YELLOW}still running${NC}"
|
||||
continue
|
||||
@@ -119,11 +119,11 @@ function _looper() {
|
||||
echo -e "You are about to start $RUNS Jenkins builds against the $BRANCH branch."
|
||||
echo -e "If you expect this to run overnight, ${UNDERLINE}${BOLD}unplug the cold reboot power switch${NC} from the testing closet before."
|
||||
echo ""
|
||||
read -p "Press (y/Y) to confirm: " choice
|
||||
read -r -p "Press (y/Y) to confirm: " choice
|
||||
if [[ "$choice" == "y" || "$choice" == "Y" ]]; then
|
||||
loop
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
_looper $@
|
||||
_looper "$@"
|
||||
|
||||
@@ -4,4 +4,4 @@ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
|
||||
|
||||
export FINGERPRINT="TOYOTA_COROLLA_TSS2"
|
||||
export SKIP_FW_QUERY="1"
|
||||
$DIR/../launch_openpilot.sh
|
||||
"$DIR/../launch_openpilot.sh"
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
FAIL=0
|
||||
|
||||
if grep -n '\(#\|//\)\([[:space:]]*\)NOMERGE' $@; then
|
||||
if grep -n '\(#\|//\)\([[:space:]]*\)NOMERGE' "$@"; then
|
||||
echo -e "NOMERGE comments found! Remove them before merging\n"
|
||||
FAIL=1
|
||||
fi
|
||||
|
||||
exit $FAIL
|
||||
exit "$FAIL"
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
FAIL=0
|
||||
|
||||
if grep '^#!.*python' $@ | grep -v '#!/usr/bin/env python3$'; then
|
||||
if grep '^#!.*python' "$@" | grep -v '#!/usr/bin/env python3$'; then
|
||||
echo -e "Invalid shebang! Must use '#!/usr/bin/env python3'\n"
|
||||
FAIL=1
|
||||
fi
|
||||
|
||||
if grep '^#!.*bash' $@ | grep -v '#!/usr/bin/env bash$'; then
|
||||
if grep '^#!.*bash' "$@" | grep -v '#!/usr/bin/env bash$'; then
|
||||
echo -e "Invalid shebang! Must use '#!/usr/bin/env bash'"
|
||||
FAIL=1
|
||||
fi
|
||||
|
||||
exit $FAIL
|
||||
exit "$FAIL"
|
||||
|
||||
Executable
+132
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
"""A minimal shellcheck-like static analysis tool for shell scripts.
|
||||
|
||||
Covers syntax, unquoted expansions, scalar $@ assignments, and read -r.
|
||||
"""
|
||||
import re
|
||||
import argparse
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
VARIABLE = re.compile(r"\$(?:\{[^}\n]*\}|[A-Za-z_]\w*|[@*0-9])")
|
||||
ASSIGNMENT = re.compile(r"[A-Za-z_]\w*(?:\[[^]]*\])?\+?=")
|
||||
OPAQUE = re.compile(r"\$?\(\([^\n]*?\)\)|\[\[.*?\]\]", re.DOTALL)
|
||||
HEREDOC = re.compile(r"<<(-?)\s*('[^']+'|\"[^\"]+\"|\\?[A-Za-z_]\w*)")
|
||||
|
||||
|
||||
def commands(text):
|
||||
def scan(i=0, end="", pattern_group=False):
|
||||
start, quote, words, expansions, documents = i, False, [], [], []
|
||||
cases = []
|
||||
while i < len(text):
|
||||
c = text[i]
|
||||
if c == "\\":
|
||||
i += 2
|
||||
continue
|
||||
if c == "'" and not quote:
|
||||
i = text.find("'", i + 1) + 1 or len(text)
|
||||
continue
|
||||
if c == '"':
|
||||
quote = not quote
|
||||
elif (not quote or text.startswith("$((", i)) and (match := OPAQUE.match(text, i)):
|
||||
i = match.end()
|
||||
continue
|
||||
elif text.startswith("$(", i):
|
||||
if not quote:
|
||||
expansions.append(i)
|
||||
i = yield from scan(i + 2, ")")
|
||||
continue
|
||||
elif match := VARIABLE.match(text, i):
|
||||
if not quote and not match[0].startswith("${#"):
|
||||
expansions.append(i)
|
||||
i = match.end()
|
||||
continue
|
||||
elif not quote:
|
||||
if text.startswith("<<<", i):
|
||||
i += 3
|
||||
continue
|
||||
if c == "#" and i == start:
|
||||
i = text.find("\n", i)
|
||||
i = len(text) if i < 0 else i
|
||||
start = i
|
||||
continue
|
||||
if not text.startswith("<<<", i) and (match := HEREDOC.match(text, i)):
|
||||
documents.append((match[2].strip("'\"").lstrip("\\"), bool(match[1])))
|
||||
i = match.end()
|
||||
continue
|
||||
if c in " \t\r\n;|&()":
|
||||
if start < i:
|
||||
words.append((text[start:i], start, expansions))
|
||||
if len(words) >= 3 and words[0][0] == "case" and words[-1][0] == "in":
|
||||
cases.append(True)
|
||||
words = []
|
||||
if words and words[0][0] == "esac" and cases:
|
||||
cases.pop()
|
||||
words = []
|
||||
pattern = bool(cases and cases[-1])
|
||||
expansions = []
|
||||
if c in "\n;|&()":
|
||||
if words and not (pattern or pattern_group) and (c != ")" or end):
|
||||
yield words
|
||||
words = []
|
||||
if c == end and not pattern:
|
||||
return i + 1
|
||||
if c == ")" and pattern:
|
||||
cases[-1] = False
|
||||
if cases and (terminator := re.match(r";(?:;&|;|&)", text[i:])):
|
||||
cases[-1] = True
|
||||
i += len(terminator[0]) - 1
|
||||
if c == "(":
|
||||
if pattern and i == start:
|
||||
cases[-1] = False
|
||||
i = yield from scan(i + 1, ")", pattern or pattern_group)
|
||||
start = i
|
||||
continue
|
||||
if c == "\n":
|
||||
for delimiter, strip_tabs in documents:
|
||||
while i < len(text):
|
||||
stop = text.find("\n", i + 1)
|
||||
stop = len(text) if stop < 0 else stop
|
||||
line, i = text[i + 1:stop], stop
|
||||
if (line.lstrip("\t") if strip_tabs else line) == delimiter:
|
||||
break
|
||||
documents = []
|
||||
start = i + 1
|
||||
i += 1
|
||||
if start < i:
|
||||
words.append((text[start:i], start, expansions))
|
||||
if words:
|
||||
yield words
|
||||
return i
|
||||
yield from scan()
|
||||
|
||||
|
||||
def check_text(text):
|
||||
for words in commands(text):
|
||||
command = next((w for w, _, _ in words if not ASSIGNMENT.match(w) and w not in {"if", "then", "elif", "while", "until", "do", "!"}), "")
|
||||
prefix = True
|
||||
for index, (word, start, expansions) in enumerate(words):
|
||||
assignment = ASSIGNMENT.match(word) and (prefix or command in {"export", "local", "declare", "readonly", "typeset"})
|
||||
prefix = prefix and (bool(assignment) or word in {"if", "then", "elif", "while", "until", "do", "!"})
|
||||
for offset in expansions:
|
||||
array = word in {"$@", "$*"} or "[@]" in word or "[*]" in word
|
||||
if not assignment and (command not in {"case", "for", "select"} or array) and not (index and words[index - 1][0] == "<<<"):
|
||||
yield text.count("\n", 0, offset) + 1, "Quote this expansion to prevent word splitting and globbing"
|
||||
if assignment and re.fullmatch(r'"[^"\n]*\$@[^"\n]*"', ASSIGNMENT.sub("", word, count=1)):
|
||||
yield text.count("\n", 0, start) + 1, 'Use an array for "$@", or "$*" to join arguments'
|
||||
if command == "read" and not any(re.fullmatch(r"-[A-Za-z]*r[A-Za-z0-9]*", w) for w, _, _ in words):
|
||||
yield text.count("\n", 0, next(start for w, start, _ in words if w == "read")) + 1, "Use read -r to preserve backslashes"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("filenames", nargs="+")
|
||||
failed = False
|
||||
for filename in parser.parse_args().filenames:
|
||||
syntax = subprocess.run(["bash", "-n", "--", filename], check=False)
|
||||
failed |= syntax.returncode != 0
|
||||
if syntax.returncode == 0:
|
||||
for line, message in check_text(Path(filename).read_text()):
|
||||
print(f"{filename}:{line}: {message}")
|
||||
failed = True
|
||||
raise SystemExit(failed)
|
||||
+32
-23
@@ -9,7 +9,7 @@ NC='\033[0m'
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||
ROOT="$DIR/../../"
|
||||
cd $ROOT
|
||||
cd "$ROOT"
|
||||
|
||||
FAILED=0
|
||||
|
||||
@@ -26,10 +26,10 @@ function run() {
|
||||
done
|
||||
|
||||
shift 1;
|
||||
CMD="$@"
|
||||
CMD=("$@")
|
||||
|
||||
set +e
|
||||
log="$((eval "$CMD" ) 2>&1)"
|
||||
log="$("${CMD[@]}" 2>&1)"
|
||||
|
||||
if [[ $? -eq 0 ]]; then
|
||||
echo -e "[${GREEN}✔${NC}]"
|
||||
@@ -42,23 +42,21 @@ function run() {
|
||||
}
|
||||
|
||||
function run_tests() {
|
||||
ALL_FILES=$1
|
||||
PYTHON_FILES=$2
|
||||
|
||||
run "ruff" ruff check openpilot --quiet
|
||||
run "check_dependencies" python3 $DIR/check_dependencies.py
|
||||
run "check_indentation" $DIR/check_indentation.py $PYTHON_FILES
|
||||
run "check_added_large_files" $DIR/check_added_large_files.py --maxkb=120 $ALL_FILES
|
||||
run "check_shebang_scripts_are_executable" $DIR/check_shebang_scripts_are_executable.py $ALL_FILES
|
||||
run "check_shebang_format" $DIR/check_shebang_format.sh $ALL_FILES
|
||||
run "check_nomerge_comments" $DIR/check_nomerge_comments.sh $ALL_FILES
|
||||
run "check_shell" python3 "$DIR/check_shell.py" "${SHELL_FILES[@]}"
|
||||
run "check_dependencies" python3 "$DIR/check_dependencies.py"
|
||||
run "check_indentation" "$DIR/check_indentation.py" "${PYTHON_FILES[@]}"
|
||||
run "check_added_large_files" "$DIR/check_added_large_files.py" --maxkb=120 "${ALL_FILES[@]}"
|
||||
run "check_shebang_scripts_are_executable" "$DIR/check_shebang_scripts_are_executable.py" "${ALL_FILES[@]}"
|
||||
run "check_shebang_format" "$DIR/check_shebang_format.sh" "${ALL_FILES[@]}"
|
||||
run "check_nomerge_comments" "$DIR/check_nomerge_comments.sh" "${ALL_FILES[@]}"
|
||||
|
||||
if [[ -z "$FAST" ]]; then
|
||||
run "ty" ty check openpilot
|
||||
run "codespell" codespell $ALL_FILES
|
||||
run "codespell" codespell "${ALL_FILES[@]}"
|
||||
fi
|
||||
|
||||
return $FAILED
|
||||
return "$FAILED"
|
||||
}
|
||||
|
||||
function help() {
|
||||
@@ -68,6 +66,7 @@ function help() {
|
||||
echo ""
|
||||
echo -e "${BOLD}${UNDERLINE}Tests:${NC}"
|
||||
echo -e " ${BOLD}ruff${NC}"
|
||||
echo -e " ${BOLD}check_shell${NC}"
|
||||
echo -e " ${BOLD}check_dependencies${NC}"
|
||||
echo -e " ${BOLD}check_indentation${NC}"
|
||||
echo -e " ${BOLD}ty${NC}"
|
||||
@@ -103,16 +102,26 @@ while [[ $# -gt 0 ]]; do
|
||||
esac
|
||||
done
|
||||
|
||||
RUN=$([ -z "$RUN" ] && echo "" || echo "!($(echo $RUN | sed 's/ /|/g'))")
|
||||
SKIP="@($(echo $SKIP | sed 's/ /|/g'))"
|
||||
RUN=$([ -z "$RUN" ] && echo "" || echo "!($(echo "$RUN" | sed 's/ /|/g'))")
|
||||
SKIP="@($(echo "$SKIP" | sed 's/ /|/g'))"
|
||||
|
||||
GIT_FILES="$(git ls-files openpilot)"
|
||||
ALL_FILES=""
|
||||
for f in $GIT_FILES; do
|
||||
ALL_FILES=()
|
||||
PYTHON_FILES=()
|
||||
while IFS= read -r -d '' f; do
|
||||
if [[ -f $f ]]; then
|
||||
ALL_FILES+="$f"$'\n'
|
||||
ALL_FILES+=("$f")
|
||||
if [[ $f == *.py ]]; then
|
||||
PYTHON_FILES+=("$f")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
PYTHON_FILES=$(echo "$ALL_FILES" | grep --color=never '.py$' || true)
|
||||
done < <(git ls-files -z openpilot)
|
||||
|
||||
run_tests "$ALL_FILES" "$PYTHON_FILES"
|
||||
# Include tooling, launchers, and the extensionless Git hook.
|
||||
SHELL_FILES=()
|
||||
while IFS= read -r -d '' f; do
|
||||
if [[ -f $f ]]; then
|
||||
SHELL_FILES+=("$f")
|
||||
fi
|
||||
done < <(git ls-files -z '*.sh' '*.bash' scripts/post-commit)
|
||||
|
||||
run_tests
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
function fail {
|
||||
echo $1 >&2
|
||||
echo "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ function retry {
|
||||
"$@" && break || {
|
||||
if [[ $n -lt $max ]]; then
|
||||
((n++))
|
||||
sleep $delay;
|
||||
sleep "$delay";
|
||||
else
|
||||
fail "The command has failed after $n attempts."
|
||||
fi
|
||||
|
||||
+6
-6
@@ -14,9 +14,9 @@ UNDERLINE='\033[4m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m'
|
||||
|
||||
SHELL_NAME="$(basename ${SHELL})"
|
||||
RC_FILE="${HOME}/.$(basename ${SHELL})rc"
|
||||
if [ "$(uname)" == "Darwin" ] && [ $SHELL == "/bin/bash" ]; then
|
||||
SHELL_NAME="$(basename "${SHELL}")"
|
||||
RC_FILE="${HOME}/.$(basename "${SHELL}")rc"
|
||||
if [ "$(uname)" == "Darwin" ] && [ "$SHELL" == "/bin/bash" ]; then
|
||||
RC_FILE="$HOME/.bash_profile"
|
||||
fi
|
||||
|
||||
@@ -250,7 +250,7 @@ function op_venv() {
|
||||
case $SHELL_NAME in
|
||||
"zsh")
|
||||
ZSHRC_DIR=$(mktemp -d 2>/dev/null || mktemp -d -t 'tmp_zsh')
|
||||
echo "source \"$RC_FILE\"; source \"$OPENPILOT_ROOT/.venv/bin/activate\"" >> $ZSHRC_DIR/.zshrc
|
||||
echo "source \"$RC_FILE\"; source \"$OPENPILOT_ROOT/.venv/bin/activate\"" >> "$ZSHRC_DIR/.zshrc"
|
||||
ZDOTDIR=$ZSHRC_DIR zsh ;;
|
||||
*)
|
||||
bash --rcfile <(echo "source \"$RC_FILE\"; source \"$OPENPILOT_ROOT/.venv/bin/activate\"") ;;
|
||||
@@ -405,14 +405,14 @@ function op_start() {
|
||||
if [[ -f "/AGNOS" ]]; then
|
||||
op_before_cmd
|
||||
op_check_agnos_update
|
||||
op_run_command sudo systemctl restart comma $@
|
||||
op_run_command sudo systemctl restart comma "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
function op_stop() {
|
||||
if [[ -f "/AGNOS" ]]; then
|
||||
op_before_cmd
|
||||
op_run_command sudo systemctl stop comma $@
|
||||
op_run_command sudo systemctl stop comma "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ set -e
|
||||
set -x
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
|
||||
cd $DIR
|
||||
cd "$DIR"
|
||||
|
||||
BUILD_DIR=/data/openpilot
|
||||
SOURCE_DIR="$(git rev-parse --show-toplevel)"
|
||||
@@ -19,26 +19,26 @@ BUILD_BRANCH=release-mici-staging
|
||||
|
||||
|
||||
# set git identity
|
||||
source $DIR/identity.sh
|
||||
source "$DIR/identity.sh"
|
||||
|
||||
echo "[-] Setting up repo T=$SECONDS"
|
||||
if ! git -C "$SOURCE_DIR" worktree remove --force "$BUILD_DIR" 2>/dev/null; then
|
||||
rm -rf $BUILD_DIR
|
||||
rm -rf "$BUILD_DIR"
|
||||
fi
|
||||
git -C "$SOURCE_DIR" worktree prune
|
||||
git -C "$SOURCE_DIR" worktree add --detach --no-checkout "$BUILD_DIR"
|
||||
cd $BUILD_DIR
|
||||
cd "$BUILD_DIR"
|
||||
git update-ref -d "refs/heads/$BUILD_BRANCH"
|
||||
git symbolic-ref HEAD "refs/heads/$BUILD_BRANCH"
|
||||
git read-tree --empty
|
||||
|
||||
# do the files copy
|
||||
echo "[-] copying files T=$SECONDS"
|
||||
cd $SOURCE_DIR
|
||||
cd "$SOURCE_DIR"
|
||||
./tools/release/release_files.py | xargs -0 cp -pR --parents -t "$BUILD_DIR" --
|
||||
|
||||
# in the directory
|
||||
cd $BUILD_DIR
|
||||
cd "$BUILD_DIR"
|
||||
|
||||
# use the full CPU available for speeding up the build.
|
||||
# openpilot resets the CPU frequencies when test_onroad.py runs below.
|
||||
@@ -88,7 +88,7 @@ git -c core.compression=0 add -f .
|
||||
git -c core.compression=0 -c gc.auto=0 commit -m "openpilot v$VERSION"
|
||||
|
||||
# Run tests
|
||||
cd $BUILD_DIR
|
||||
cd "$BUILD_DIR"
|
||||
RELEASE=1 ./openpilot/selfdrive/test/test_onroad.py "$@"
|
||||
#tools/test_runner.py openpilot/selfdrive/car/tests/test_car_interfaces.py
|
||||
|
||||
|
||||
@@ -3,23 +3,23 @@ set -ex
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
|
||||
|
||||
SOURCE_DIR="$(git -C $DIR rev-parse --show-toplevel)"
|
||||
SOURCE_DIR="$(git -C "$DIR" rev-parse --show-toplevel)"
|
||||
if [ -z "$TARGET_DIR" ]; then
|
||||
TARGET_DIR="$(mktemp -d)"
|
||||
fi
|
||||
|
||||
# set git identity
|
||||
source $DIR/identity.sh
|
||||
source "$DIR/identity.sh"
|
||||
|
||||
echo "[-] Setting up target repo T=$SECONDS"
|
||||
|
||||
rm -rf $TARGET_DIR
|
||||
mkdir -p $TARGET_DIR
|
||||
cd $TARGET_DIR
|
||||
cp -r $SOURCE_DIR/.git $TARGET_DIR
|
||||
rm -rf "$TARGET_DIR"
|
||||
mkdir -p "$TARGET_DIR"
|
||||
cd "$TARGET_DIR"
|
||||
cp -r "$SOURCE_DIR/.git" "$TARGET_DIR"
|
||||
|
||||
echo "[-] setting up stripped branch sync T=$SECONDS"
|
||||
cd $TARGET_DIR
|
||||
cd "$TARGET_DIR"
|
||||
|
||||
# tmp branch
|
||||
git checkout --orphan tmp
|
||||
@@ -32,20 +32,20 @@ find . -maxdepth 1 -not -path './.git' -not -name '.' -not -name '..' -exec rm -
|
||||
|
||||
# do the files copy
|
||||
echo "[-] copying files T=$SECONDS"
|
||||
cd $SOURCE_DIR
|
||||
cd "$SOURCE_DIR"
|
||||
./tools/release/release_files.py | xargs -0 cp -pR --parents -t "$TARGET_DIR" --
|
||||
|
||||
# in the directory
|
||||
cd $TARGET_DIR
|
||||
cd "$TARGET_DIR"
|
||||
rm -rf .git/modules/
|
||||
|
||||
find openpilot/selfdrive/modeld/models -name '*.onnx' -size +95M -exec ./openpilot/common/file_chunker.py {} \;
|
||||
|
||||
# include source commit hash and build date in commit
|
||||
GIT_HASH=$(git --git-dir=$SOURCE_DIR/.git rev-parse HEAD)
|
||||
GIT_COMMIT_DATE=$(git --git-dir=$SOURCE_DIR/.git show --no-patch --format='%ct %ci' HEAD)
|
||||
GIT_HASH=$(git --git-dir="$SOURCE_DIR/.git" rev-parse HEAD)
|
||||
GIT_COMMIT_DATE=$(git --git-dir="$SOURCE_DIR/.git" show --no-patch --format='%ct %ci' HEAD)
|
||||
DATETIME=$(date '+%Y-%m-%dT%H:%M:%S')
|
||||
VERSION=$(cat $SOURCE_DIR/openpilot/common/version.h | awk -F\" '{print $2}')
|
||||
VERSION=$(cat "$SOURCE_DIR/openpilot/common/version.h" | awk -F\" '{print $2}')
|
||||
|
||||
echo -n "$GIT_HASH" > git_src_commit
|
||||
echo -n "$GIT_COMMIT_DATE" > git_src_commit_date
|
||||
@@ -82,7 +82,7 @@ if [ ! -z "$BRANCH" ]; then
|
||||
git config --local core.hooksPath .git/hooks
|
||||
git lfs update --force
|
||||
# uploading the larger pack is faster than spending CPU to optimize it
|
||||
git -c pack.window=0 -c pack.depth=0 -c pack.compression=0 push -f origin tmp:$BRANCH
|
||||
git -c pack.window=0 -c pack.depth=0 -c pack.compression=0 push -f origin "tmp:$BRANCH"
|
||||
fi
|
||||
|
||||
echo "[-] done T=$SECONDS, ready at $TARGET_DIR"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
set -e
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
|
||||
cd $DIR
|
||||
cd "$DIR"
|
||||
|
||||
if [ ! -z "$(git status --porcelain)" ]; then
|
||||
echo "Dirty working tree after build:"
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
while read hash submodule ref; do
|
||||
while read -r hash submodule ref; do
|
||||
if [ "$submodule" = "tinygrad_repo" ]; then
|
||||
echo "Skipping $submodule"
|
||||
continue
|
||||
fi
|
||||
|
||||
git -C $submodule fetch --depth 100 origin master
|
||||
git -C $submodule branch -r --contains $hash | grep "origin/master"
|
||||
git -C "$submodule" fetch --depth 100 origin master
|
||||
git -C "$submodule" branch -r --contains "$hash" | grep "origin/master"
|
||||
if [ "$?" -eq 0 ]; then
|
||||
echo "$submodule ok"
|
||||
else
|
||||
|
||||
@@ -36,7 +36,7 @@ SSH_PORT=2222
|
||||
while ss -tln | grep -q ":${SSH_PORT} "; do
|
||||
SSH_PORT=$((SSH_PORT + 1))
|
||||
done
|
||||
adb forward tcp:${SSH_PORT} tcp:22
|
||||
adb forward tcp:"${SSH_PORT}" tcp:22
|
||||
|
||||
# SSH!
|
||||
ssh comma@localhost -p ${SSH_PORT} "$@"
|
||||
ssh comma@localhost -p "${SSH_PORT}" "$@"
|
||||
|
||||
+8
-8
@@ -59,10 +59,10 @@ function ask_dir() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
read
|
||||
read -r
|
||||
if [[ ! -z "$REPLY" ]]; then
|
||||
mkdir -p $REPLY
|
||||
OPENPILOT_ROOT="$(realpath $REPLY)/openpilot"
|
||||
mkdir -p "$REPLY"
|
||||
OPENPILOT_ROOT="$(realpath "$REPLY")/openpilot"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ function check_git() {
|
||||
function git_clone() {
|
||||
st="$(date +%s)"
|
||||
echo "Cloning openpilot..."
|
||||
if $(git clone --filter=blob:none https://github.com/commaai/openpilot.git "$OPENPILOT_ROOT"); then
|
||||
if git clone --filter=blob:none https://github.com/commaai/openpilot.git "$OPENPILOT_ROOT"; then
|
||||
if [[ -f $OPENPILOT_ROOT/launch_openpilot.sh ]]; then
|
||||
et="$(date +%s)"
|
||||
echo -e " ↳ [${GREEN}✔${NC}] Successfully cloned openpilot in $((et - st)) seconds.\n"
|
||||
@@ -127,10 +127,10 @@ function git_clone() {
|
||||
}
|
||||
|
||||
function install_with_op() {
|
||||
cd $OPENPILOT_ROOT
|
||||
$OPENPILOT_ROOT/tools/op.sh post-commit
|
||||
cd "$OPENPILOT_ROOT"
|
||||
"$OPENPILOT_ROOT/tools/op.sh" post-commit
|
||||
|
||||
if ! $OPENPILOT_ROOT/tools/op.sh setup; then
|
||||
if ! "$OPENPILOT_ROOT/tools/op.sh" setup; then
|
||||
echo -e "\n[${RED}✗${NC}] failed to install openpilot!"
|
||||
return 1
|
||||
fi
|
||||
@@ -147,5 +147,5 @@ check_stdin
|
||||
ask_dir
|
||||
check_dir
|
||||
check_git
|
||||
[ -z $SKIP_GIT_CLONE ] && git_clone
|
||||
[ -z "$SKIP_GIT_CLONE" ] && git_clone
|
||||
install_with_op
|
||||
|
||||
+14
-14
@@ -20,14 +20,14 @@ function retry() {
|
||||
}
|
||||
|
||||
function install_linux_deps() {
|
||||
SUDO=""
|
||||
SUDO=()
|
||||
|
||||
if [[ ! $(id -u) -eq 0 ]]; then
|
||||
if [[ -z $(which sudo) ]]; then
|
||||
echo "Please install sudo or run as root"
|
||||
exit 1
|
||||
fi
|
||||
SUDO="sudo"
|
||||
SUDO=(sudo)
|
||||
fi
|
||||
|
||||
local missing_linux_deps=0
|
||||
@@ -51,28 +51,28 @@ function install_linux_deps() {
|
||||
# the native package managers are slow, so skip if we can
|
||||
echo "[ ] system packages already installed t=$SECONDS"
|
||||
elif command -v apt-get > /dev/null 2>&1; then
|
||||
$SUDO apt-get update
|
||||
$SUDO apt-get install -y --no-install-recommends ca-certificates build-essential curl libcurl4-openssl-dev locales git xclip wl-clipboard
|
||||
"${SUDO[@]}" apt-get update
|
||||
"${SUDO[@]}" apt-get install -y --no-install-recommends ca-certificates build-essential curl libcurl4-openssl-dev locales git xclip wl-clipboard
|
||||
elif command -v dnf > /dev/null 2>&1; then
|
||||
$SUDO dnf install -y ca-certificates gcc gcc-c++ make curl libcurl-devel glibc-langpack-en git
|
||||
"${SUDO[@]}" dnf install -y ca-certificates gcc gcc-c++ make curl libcurl-devel glibc-langpack-en git
|
||||
elif command -v yum > /dev/null 2>&1; then
|
||||
$SUDO yum install -y ca-certificates gcc gcc-c++ make curl libcurl-devel glibc-langpack-en git
|
||||
"${SUDO[@]}" yum install -y ca-certificates gcc gcc-c++ make curl libcurl-devel glibc-langpack-en git
|
||||
elif command -v pacman > /dev/null 2>&1; then
|
||||
$SUDO pacman -Syu --noconfirm --needed base-devel ca-certificates curl git
|
||||
"${SUDO[@]}" pacman -Syu --noconfirm --needed base-devel ca-certificates curl git
|
||||
elif command -v zypper > /dev/null 2>&1; then
|
||||
$SUDO zypper --non-interactive refresh
|
||||
$SUDO zypper --non-interactive install ca-certificates gcc gcc-c++ make curl libcurl-devel glibc-locale git
|
||||
"${SUDO[@]}" zypper --non-interactive refresh
|
||||
"${SUDO[@]}" zypper --non-interactive install ca-certificates gcc gcc-c++ make curl libcurl-devel glibc-locale git
|
||||
elif command -v apk > /dev/null 2>&1; then
|
||||
$SUDO apk add --no-cache ca-certificates build-base curl curl-dev musl-locales git
|
||||
"${SUDO[@]}" apk add --no-cache ca-certificates build-base curl curl-dev musl-locales git
|
||||
elif command -v xbps-install > /dev/null 2>&1; then
|
||||
$SUDO xbps-install -Syu base-devel ca-certificates curl git libcurl-devel glibc-locales
|
||||
"${SUDO[@]}" xbps-install -Syu base-devel ca-certificates curl git libcurl-devel glibc-locales
|
||||
else
|
||||
echo "Unsupported Linux distribution. Supported package managers: apt-get, dnf, yum, pacman, zypper, apk, xbps-install."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -d "/etc/udev/rules.d/" ]]; then
|
||||
$SUDO tee /etc/udev/rules.d/11-openpilot.rules > /dev/null <<-EOF
|
||||
"${SUDO[@]}" tee /etc/udev/rules.d/11-openpilot.rules > /dev/null <<-EOF
|
||||
# Panda Jungle devices
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddcf", MODE="0666"
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddef", MODE="0666"
|
||||
@@ -91,9 +91,9 @@ function install_linux_deps() {
|
||||
EOF
|
||||
|
||||
# delete the old ones
|
||||
$SUDO rm -f /etc/udev/rules.d/11-panda.rules /etc/udev/rules.d/12-panda_jungle.rules /etc/udev/rules.d/50-comma-adb.rules
|
||||
"${SUDO[@]}" rm -f /etc/udev/rules.d/11-panda.rules /etc/udev/rules.d/12-panda_jungle.rules /etc/udev/rules.d/50-comma-adb.rules
|
||||
|
||||
$SUDO udevadm control --reload-rules && $SUDO udevadm trigger || true
|
||||
"${SUDO[@]}" udevadm control --reload-rules && "${SUDO[@]}" udevadm trigger || true
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user