已合并
【fix】修复pre-commit的oat无告警报错 #3826
zhangfands创建于 7月6日
【fix】修复pre-commit的oat无告警报错 #3826
已合并
共 1 个文件变更+164-27
| @@ -1,10 +1,10 @@ | |||
| 1 | #!/bin/sh | 1 | #!/bin/sh |
| 2 | # ----------------------------------------------------------------------------------------------------------- | 2 | # ----------------------------------------------------------------------------------------------------------- |
| 3 | # Copyright (c) 2026 Huawei Technologies Co., Ltd. | 3 | # Copyright (c) 2026 Huawei Technologies Co., Ltd. |
| 4 | -# This program is free software, you can redistribute it and/or modify it under the terms and conditions of | 4 | +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of |
| 5 | # CANN Open Software License Agreement Version 2.0 (the "License"). | 5 | # CANN Open Software License Agreement Version 2.0 (the "License"). |
| 6 | # Please refer to the License for details. You may not use this file except in compliance with the License. | 6 | # Please refer to the License for details. You may not use this file except in compliance with the License. |
| 7 | -# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | 7 | +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, |
| 8 | # INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | 8 | # INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. |
| 9 | # See LICENSE in the root of the software repository for the full text of the License. | 9 | # See LICENSE in the root of the software repository for the full text of the License. |
| 10 | # ----------------------------------------------------------------------------------------------------------- | 10 | # ----------------------------------------------------------------------------------------------------------- |
| @@ -48,12 +48,25 @@ if [ -z "$_PYTHON" ]; then | |||
| 48 | fi | 48 | fi |
| 49 | 49 | ||
| 50 | # --------------------------------------------------------------------------- | 50 | # --------------------------------------------------------------------------- |
| 51 | -# 1. Ensure oat-py is installed | 51 | +# 1. Ensure oat-py is installed (serialized via flock to prevent parallel pip conflicts) |
| 52 | # --------------------------------------------------------------------------- | 52 | # --------------------------------------------------------------------------- |
| 53 | _OAT_OK=$("$_PYTHON" -c "import importlib.util; print('ok' if importlib.util.find_spec('oat') else 'missing')" 2>/dev/null || echo "missing") | 53 | _OAT_OK=$("$_PYTHON" -c "import importlib.util; print('ok' if importlib.util.find_spec('oat') else 'missing')" 2>/dev/null || echo "missing") |
| 54 | if [ "$_OAT_OK" != "ok" ]; then | 54 | if [ "$_OAT_OK" != "ok" ]; then |
| 55 | echo "[OAT] oat-py not found. Installing oat-py>=1.0.1 ..." | 55 | echo "[OAT] oat-py not found. Installing oat-py>=1.0.1 ..." |
| 56 | - "$_PYTHON" -m pip install --quiet "oat-py>=1.0.1" | 56 | + _LOCK_FILE="/tmp/oat_pip_install.lock" |
| 57 | + # flock ensures only one concurrent invocation runs pip install at a time; | ||
| 58 | + # re-check inside the lock so processes that waited skip redundant installs | ||
| 59 | + if command -v flock >/dev/null 2>&1; then | ||
| 60 | + ( | ||
| 61 | + flock -w 120 9 | ||
| 62 | + _RECHECK=$("$_PYTHON" -c "import importlib.util; print('ok' if importlib.util.find_spec('oat') else 'missing')" 2>/dev/null || echo "missing") | ||
| 63 | + if [ "$_RECHECK" != "ok" ]; then | ||
| 64 | + "$_PYTHON" -m pip install --quiet "oat-py>=1.0.1" | ||
| 65 | + fi | ||
| 66 | + ) 9>"$_LOCK_FILE" | ||
| 67 | + else | ||
| 68 | + "$_PYTHON" -m pip install --quiet "oat-py>=1.0.1" | ||
| 69 | + fi | ||
| 57 | _OAT_OK=$("$_PYTHON" -c "import importlib.util; print('ok' if importlib.util.find_spec('oat') else 'missing')" 2>/dev/null || echo "missing") | 70 | _OAT_OK=$("$_PYTHON" -c "import importlib.util; print('ok' if importlib.util.find_spec('oat') else 'missing')" 2>/dev/null || echo "missing") |
| 58 | if [ "$_OAT_OK" != "ok" ]; then | 71 | if [ "$_OAT_OK" != "ok" ]; then |
| 59 | echo "[OAT] [WARNING] Failed to install oat-py. Please run: pip install oat-py>=1.0.1" | 72 | echo "[OAT] [WARNING] Failed to install oat-py. Please run: pip install oat-py>=1.0.1" |
| @@ -68,10 +81,99 @@ fi | |||
| 68 | # --------------------------------------------------------------------------- | 81 | # --------------------------------------------------------------------------- |
| 69 | REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) | 82 | REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) |
| 70 | REPO_NAME=$(basename "$REPO_ROOT") | 83 | REPO_NAME=$(basename "$REPO_ROOT") |
| 71 | -OAT_REPORT_DIR="$REPO_ROOT/oat_reports" | 84 | +OAT_REPORT_DIR="${TMPDIR:-/tmp}/oat_reports_$$" |
| 85 | +OAT_RESULT_DIR="$REPO_ROOT/oat_reports" | ||
| 72 | 86 | ||
| 73 | -echo "[OAT] Running OAT scan (Python Edition) — INCREMENTAL MODE" | 87 | +# --------------------------------------------------------------------------- |
| 88 | +# 2a. PR-range deduplication (pure git, no CI env vars required) | ||
| 89 | +# | ||
| 90 | +# Strategy: | ||
| 91 | +# 1. Find the merge-base between HEAD and the upstream branch (origin/master | ||
| 92 | +# or similar). If found, this is a feature-branch context ??collect ALL | ||
| 93 | +# files changed since the branch diverged (full PR diff). | ||
| 94 | +# 2. Key a done-marker on the HEAD SHA. The first invocation runs the scan; | ||
| 95 | +# every subsequent invocation for the same HEAD exits 0 immediately. | ||
| 96 | +# This eliminates redundant scans when CI calls the hook once per commit. | ||
| 97 | +# 3. If no merge-base is found (e.g. committing directly on master) fall back | ||
| 98 | +# to scanning only the currently staged files, with no done-marker. | ||
| 99 | +# --------------------------------------------------------------------------- | ||
| 100 | +_PR_MERGE_BASE="" | ||
| 101 | +_DONE_MARKER="" | ||
| 102 | +_HEAD_SHA=$(git rev-parse HEAD 2>/dev/null | cut -c1-12 || true) | ||
| 103 | + | ||
| 104 | +if [ -n "$_HEAD_SHA" ]; then | ||
| 105 | + # Try to find merge-base with a known upstream branch | ||
| 106 | + if [ -n "${PRE_COMMIT_FROM_REF:-}" ]; then | ||
| 107 | + # pre-commit --from-ref/--to-ref: use the provided base directly | ||
| 108 | + _PR_MERGE_BASE=$(git merge-base "$PRE_COMMIT_FROM_REF" HEAD 2>/dev/null || true) | ||
| 109 | + fi | ||
| 110 | + if [ -z "$_PR_MERGE_BASE" ]; then | ||
| 111 | + # Search all remotes (origin, upstream, etc.) for common trunk branch names. | ||
| 112 | + # This handles fork setups where origin=fork and upstream=main-repo. | ||
| 113 | + _CANDIDATE_BASE="" | ||
| 114 | + _CANDIDATE_DIST=0 | ||
| 115 | + for _b in $(git for-each-ref --format='%(refname:short)' \ | ||
| 116 | + 'refs/remotes/*/master' 'refs/remotes/*/main' \ | ||
| 117 | + 'refs/remotes/*/develop' 'refs/remotes/*/dev' 2>/dev/null); do | ||
| 118 | + _mb=$(git merge-base HEAD "$_b" 2>/dev/null || true) | ||
| 119 | + [ -z "$_mb" ] && continue | ||
| 120 | + # Skip if merge-base is HEAD itself (branch is ahead of or equal to HEAD) | ||
| 121 | + [ "$_mb" = "$(git rev-parse HEAD 2>/dev/null)" ] && continue | ||
| 122 | + # Pick the candidate whose merge-base is furthest from HEAD | ||
| 123 | + # (most commits since divergence = most likely true PR base) | ||
| 124 | + _dist=$(git rev-list --count "$_mb"..HEAD 2>/dev/null || echo 0) | ||
| 125 | + if [ -z "$_CANDIDATE_BASE" ] || [ "$_dist" -gt "$_CANDIDATE_DIST" ]; then | ||
| 126 | + _CANDIDATE_BASE="$_mb" | ||
| 127 | + _CANDIDATE_DIST="$_dist" | ||
| 128 | + fi | ||
| 129 | + done | ||
| 130 | + _PR_MERGE_BASE="$_CANDIDATE_BASE" | ||
| 131 | + fi | ||
| 132 | + | ||
| 133 | + # Only use PR-range mode when HEAD has diverged from the upstream base | ||
| 134 | + if [ -n "$_PR_MERGE_BASE" ] && [ "$_PR_MERGE_BASE" != "$(git rev-parse HEAD 2>/dev/null)" ]; then | ||
| 135 | + mkdir -p "$OAT_RESULT_DIR" | ||
| 136 | + _DONE_MARKER="$OAT_RESULT_DIR/.done_${_HEAD_SHA}" | ||
| 137 | + if [ -f "$_DONE_MARKER" ]; then | ||
| 138 | + echo "[OAT] [SKIP] Already scanned HEAD=${_HEAD_SHA}. Skipping duplicate invocation." | ||
| 139 | + exit 0 | ||
| 140 | + fi | ||
| 141 | + else | ||
| 142 | + # HEAD == merge-base: on the upstream branch itself, no PR range | ||
| 143 | + _PR_MERGE_BASE="" | ||
| 144 | + fi | ||
| 145 | +fi | ||
| 146 | + | ||
| 147 | +# --------------------------------------------------------------------------- | ||
| 148 | +# 2b. Deduplicate parallel invocations within the same pre-commit run. | ||
| 149 | +# Only the first instance that acquires the lock actually runs the scan; | ||
| 150 | +# all others wait then exit 0. | ||
| 151 | +# --------------------------------------------------------------------------- | ||
| 152 | +if command -v flock >/dev/null 2>&1; then | ||
| 153 | + _OAT_SCAN_LOCK="${TMPDIR:-/tmp}/oat_scan_$(echo "$REPO_ROOT" | tr '/\\: ' '____').lock" | ||
| 154 | + exec 9>"$_OAT_SCAN_LOCK" | ||
| 155 | + if ! flock -n 9; then | ||
| 156 | + echo "[OAT] Another OAT scan instance is running. Waiting..." | ||
| 157 | + flock -w 120 9 | ||
| 158 | + # Re-check done marker: if the scanning instance completed normally, skip. | ||
| 159 | + # If it exited abnormally (no marker), fall through and run the scan ourselves. | ||
| 160 | + if [ -n "$_DONE_MARKER" ] && [ -f "$_DONE_MARKER" ]; then | ||
| 161 | + echo "[OAT] Scan already completed by another instance. Skipping." | ||
| 162 | + exit 0 | ||
| 163 | + fi | ||
| 164 | + echo "[OAT] Previous instance did not complete. Proceeding with scan..." | ||
| 165 | + # Fall through to run the scan below | ||
| 166 | + fi | ||
| 167 | + # This instance now owns the lock and will run the scan. | ||
| 168 | +fi | ||
| 169 | + | ||
| 170 | +echo "[OAT] Running OAT scan (Python Edition) ??INCREMENTAL MODE" | ||
| 74 | echo "[OAT] Project: $REPO_NAME" | 171 | echo "[OAT] Project: $REPO_NAME" |
| 172 | +if [ -n "$_PR_MERGE_BASE" ]; then | ||
| 173 | + echo "[OAT] Mode: PR range ??scanning all files changed since merge-base" | ||
| 174 | +else | ||
| 175 | + echo "[OAT] Mode: staged files ??scanning only currently staged files" | ||
| 176 | +fi | ||
| 75 | 177 | ||
| 76 | # --------------------------------------------------------------------------- | 178 | # --------------------------------------------------------------------------- |
| 77 | # 3. Collect staged files | 179 | # 3. Collect staged files |
| @@ -93,7 +195,31 @@ if [ $# -gt 0 ]; then | |||
| 93 | FILE_LIST="$FILE_LIST,$_abs" | 195 | FILE_LIST="$FILE_LIST,$_abs" |
| 94 | fi | 196 | fi |
| 95 | done | 197 | done |
| 198 | +elif [ -n "$_PR_MERGE_BASE" ]; then | ||
| 199 | + # PR range mode: collect ALL files changed since the branch diverged | ||
| 200 | + _STAGED=$(git diff --name-only --diff-filter=ACM "$_PR_MERGE_BASE" HEAD \ | ||
| 201 | + 2>/dev/null || true) | ||
| 202 | + if [ -z "$_STAGED" ]; then | ||
| 203 | + echo "[OAT] No files changed in PR range. Skipping." | ||
| 204 | + [ -n "$_DONE_MARKER" ] && touch "$_DONE_MARKER" 2>/dev/null || true | ||
| 205 | + exit 0 | ||
| 206 | + fi | ||
| 207 | + FILE_COUNT=$(echo "$_STAGED" | wc -l | tr -d ' ') | ||
| 208 | + FILE_LIST="" | ||
| 209 | + for _f in $_STAGED; do | ||
| 210 | + case "$_f" in | ||
| 211 | + /*) _abs="$_f" ;; | ||
| 212 | + *) _abs="$REPO_ROOT/$_f" ;; | ||
| 213 | + esac | ||
| 214 | + [ -f "$_abs" ] || continue | ||
| 215 | + if [ -z "$FILE_LIST" ]; then | ||
| 216 | + FILE_LIST="$_abs" | ||
| 217 | + else | ||
| 218 | + FILE_LIST="$FILE_LIST,$_abs" | ||
| 219 | + fi | ||
| 220 | + done | ||
| 96 | else | 221 | else |
| 222 | + # Staged files mode: on upstream branch directly, scan only staged files | ||
| 97 | _STAGED=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null || true) | 223 | _STAGED=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null || true) |
| 98 | if [ -z "$_STAGED" ]; then | 224 | if [ -z "$_STAGED" ]; then |
| 99 | echo "[OAT] No staged files to check. Skipping." | 225 | echo "[OAT] No staged files to check. Skipping." |
| @@ -123,20 +249,13 @@ fi | |||
| 123 | echo "[OAT] Checking $FILE_COUNT staged file(s)..." | 249 | echo "[OAT] Checking $FILE_COUNT staged file(s)..." |
| 124 | 250 | ||
| 125 | # --------------------------------------------------------------------------- | 251 | # --------------------------------------------------------------------------- |
| 126 | -# 4. Ensure oat_reports/ exists and is in .gitignore | 252 | +# 4. Ensure report directories exist |
| 127 | # --------------------------------------------------------------------------- | 253 | # --------------------------------------------------------------------------- |
| 128 | mkdir -p "$OAT_REPORT_DIR" | 254 | mkdir -p "$OAT_REPORT_DIR" |
| 129 | - | 255 | +mkdir -p "$OAT_RESULT_DIR" |
| 130 | -_GITIGNORE="$REPO_ROOT/.gitignore" | ||
| 131 | -for _entry in "oat_reports" "log"; do | ||
| 132 | - if ! grep -qE "^${_entry}/?$" "$_GITIGNORE" 2>/dev/null; then | ||
| 133 | - printf "\n%s/\n" "$_entry" >> "$_GITIGNORE" 2>/dev/null || true | ||
| 134 | - echo "[OAT] Added ${_entry}/ to .gitignore" | ||
| 135 | - fi | ||
| 136 | -done | ||
| 137 | 256 | ||
| 138 | # --------------------------------------------------------------------------- | 257 | # --------------------------------------------------------------------------- |
| 139 | -# 5. Build oat command — use OAT.xml if present in repo root | 258 | +# 5. Build oat command ??use OAT.xml if present in repo root |
| 140 | # --------------------------------------------------------------------------- | 259 | # --------------------------------------------------------------------------- |
| 141 | _OAT_CMD="$_PYTHON -m oat -mode s -s $REPO_ROOT -r $OAT_REPORT_DIR -n $REPO_NAME -w 1 -f $FILE_LIST" | 260 | _OAT_CMD="$_PYTHON -m oat -mode s -s $REPO_ROOT -r $OAT_REPORT_DIR -n $REPO_NAME -w 1 -f $FILE_LIST" |
| 142 | 261 | ||
| @@ -170,7 +289,7 @@ fi | |||
| 170 | # Only: Invalid File Type + License Header Invalid (no copyright) | 289 | # Only: Invalid File Type + License Header Invalid (no copyright) |
| 171 | # --------------------------------------------------------------------------- | 290 | # --------------------------------------------------------------------------- |
| 172 | REPORT_FILE="$OAT_REPORT_DIR/PlainReport_${REPO_NAME}.txt" | 291 | REPORT_FILE="$OAT_REPORT_DIR/PlainReport_${REPO_NAME}.txt" |
| 173 | -RESULT_FILE="$OAT_REPORT_DIR/result.txt" | 292 | +RESULT_FILE="$OAT_RESULT_DIR/result.txt" |
| 174 | 293 | ||
| 175 | # Section headers used as stop-boundaries when extracting sections | 294 | # Section headers used as stop-boundaries when extracting sections |
| 176 | _ALL_HEADERS="Invalid File Type Total Count:|License Not Compatible Total Count:|License Header Invalid Total Count:|Copyright Header Invalid Total Count:|No License File Total Count:|No Readme.OpenSource Total Count:|No Readme Total Count:|Import Invalid Total Count:|Redundant License File Total Count:|Third Party Software Info Total Count:" | 295 | _ALL_HEADERS="Invalid File Type Total Count:|License Not Compatible Total Count:|License Header Invalid Total Count:|Copyright Header Invalid Total Count:|No License File Total Count:|No Readme.OpenSource Total Count:|No Readme Total Count:|Import Invalid Total Count:|Redundant License File Total Count:|Third Party Software Info Total Count:" |
| @@ -201,17 +320,29 @@ _extract_section() { | |||
| 201 | 320 | ||
| 202 | if [ ! -f "$REPORT_FILE" ]; then | 321 | if [ ! -f "$REPORT_FILE" ]; then |
| 203 | if [ "$_OAT_RC" -eq 0 ]; then | 322 | if [ "$_OAT_RC" -eq 0 ]; then |
| 323 | + # oat exited cleanly with no report: all staged files were filtered out | ||
| 204 | echo "[OAT] [OK] All checks passed ($FILE_COUNT file(s) checked)." | 324 | echo "[OAT] [OK] All checks passed ($FILE_COUNT file(s) checked)." |
| 325 | + [ -n "$_DONE_MARKER" ] && touch "$_DONE_MARKER" 2>/dev/null || true | ||
| 326 | + rm -rf "$OAT_REPORT_DIR" | ||
| 205 | exit 0 | 327 | exit 0 |
| 206 | else | 328 | else |
| 207 | - echo "[OAT] [WARNING] Report not found: $REPORT_FILE" | 329 | + # oat returned exit code 1 but produced no report — possible disk issue |
| 330 | + # or oat internal error. Do not silently pass; block the commit. | ||
| 331 | + echo "" | ||
| 332 | + echo "[OAT] [ERROR] oat exited with code $_OAT_RC but no report was generated." | ||
| 333 | + echo "[OAT] This may indicate a disk error or an oat internal bug." | ||
| 334 | + echo "[OAT] To investigate, run manually:" | ||
| 335 | + echo " $_OAT_CMD" | ||
| 336 | + echo "[OAT] Blocking commit to prevent silent compliance bypass." | ||
| 337 | + echo "" | ||
| 338 | + rm -rf "$OAT_REPORT_DIR" | ||
| 208 | exit 1 | 339 | exit 1 |
| 209 | fi | 340 | fi |
| 210 | fi | 341 | fi |
| 211 | 342 | ||
| 212 | -# Parse counts | 343 | +# Parse counts ??use || true to prevent set -e from triggering if grep finds no match |
| 213 | -_INVALID_TYPE=$(grep "^Invalid File Type Total Count:" "$REPORT_FILE" | grep -oE '[0-9]+' | head -1) | 344 | +_INVALID_TYPE=$(grep "^Invalid File Type Total Count:" "$REPORT_FILE" | grep -oE '[0-9]+' | head -1 || true) |
| 214 | -_LICENSE_INVALID=$(grep "^License Header Invalid Total Count:" "$REPORT_FILE" | grep -oE '[0-9]+' | head -1) | 345 | +_LICENSE_INVALID=$(grep "^License Header Invalid Total Count:" "$REPORT_FILE" | grep -oE '[0-9]+' | head -1 || true) |
| 215 | _INVALID_TYPE=${_INVALID_TYPE:-0} | 346 | _INVALID_TYPE=${_INVALID_TYPE:-0} |
| 216 | _LICENSE_INVALID=${_LICENSE_INVALID:-0} | 347 | _LICENSE_INVALID=${_LICENSE_INVALID:-0} |
| 217 | 348 | ||
| @@ -240,11 +371,8 @@ _SECTION_LIC=$(_extract_section "$REPORT_FILE" "License Header Invalid Total Cou | |||
| 240 | echo "===================================" | 371 | echo "===================================" |
| 241 | } > "$RESULT_FILE" | 372 | } > "$RESULT_FILE" |
| 242 | 373 | ||
| 243 | -# Clean up full plain report (keep only result.txt) | ||
| 244 | -rm -f "$REPORT_FILE" | ||
| 245 | - | ||
| 246 | # --------------------------------------------------------------------------- | 374 | # --------------------------------------------------------------------------- |
| 247 | -# 8. Block commit if issues found | 375 | +# 8. Block commit if issues found; always clean up temp dir |
| 248 | # --------------------------------------------------------------------------- | 376 | # --------------------------------------------------------------------------- |
| 249 | TOTAL_ISSUES=$(( _INVALID_TYPE + _LICENSE_INVALID )) | 377 | TOTAL_ISSUES=$(( _INVALID_TYPE + _LICENSE_INVALID )) |
| 250 | 378 | ||
| @@ -258,12 +386,15 @@ if [ "$TOTAL_ISSUES" -gt 0 ]; then | |||
| 258 | echo " - Invalid File Type: $_INVALID_TYPE" | 386 | echo " - Invalid File Type: $_INVALID_TYPE" |
| 259 | echo " - License Header Invalid: $_LICENSE_INVALID" | 387 | echo " - License Header Invalid: $_LICENSE_INVALID" |
| 260 | echo "" | 388 | echo "" |
| 261 | - echo "[OAT] Details:" | 389 | + echo "[OAT] Details (also saved to: $RESULT_FILE):" |
| 262 | - echo " cat $RESULT_FILE" | 390 | + echo "---" |
| 391 | + cat "$RESULT_FILE" | ||
| 392 | + echo "---" | ||
| 263 | echo "" | 393 | echo "" |
| 264 | echo "Fix the issues and recommit, or skip with:" | 394 | echo "Fix the issues and recommit, or skip with:" |
| 265 | echo " git commit --no-verify" | 395 | echo " git commit --no-verify" |
| 266 | echo "" | 396 | echo "" |
| 397 | + rm -rf "$OAT_REPORT_DIR" | ||
| 267 | exit 1 | 398 | exit 1 |
| 268 | fi | 399 | fi |
| 269 | 400 | ||
| @@ -271,4 +402,10 @@ echo "" | |||
| 271 | echo "[OAT] [OK] All checks passed ($FILE_COUNT file(s) checked)." | 402 | echo "[OAT] [OK] All checks passed ($FILE_COUNT file(s) checked)." |
| 272 | echo "[OAT] Summary: cat $RESULT_FILE" | 403 | echo "[OAT] Summary: cat $RESULT_FILE" |
| 273 | echo "" | 404 | echo "" |
| 405 | +# Mark scan as done for CI range mode (prevents redundant re-runs on same PR head) | ||
| 406 | +if [ -n "$_DONE_MARKER" ]; then | ||
| 407 | + touch "$_DONE_MARKER" 2>/dev/null || true | ||
| 408 | + echo "[OAT] Done-marker created: $_DONE_MARKER" | ||
| 409 | +fi | ||
| 410 | +rm -rf "$OAT_REPORT_DIR" | ||
| 274 | exit 0 | 411 | exit 0 |