tool-trunk-checker.sh 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. #!/bin/bash
  2. # 自动运行代码质量检查
  3. # 显示使用说明
  4. show_usage() {
  5. echo "用法: $0 [选项] [文件或目录...]"
  6. echo "选项:"
  7. echo " -a, --all 检查所有文件"
  8. echo " -g, --git [默认] 只检查git相关的文件(已修改、已暂存、未跟踪)"
  9. echo " -f, --file FILE 指定要检查的特定文件"
  10. echo " -d, --dir DIR 指定要检查的目录"
  11. echo " --help 显示此帮助信息"
  12. echo ""
  13. echo "示例:"
  14. echo " $0 # 检查所有git相关文件(默认)"
  15. echo " $0 -f file1.go -f file2.py # 检查指定文件"
  16. echo " $0 -d src/ -d tests/ # 检查指定目录"
  17. echo " $0 file1.go src/ tests/ # 混合指定文件和目录"
  18. }
  19. # 默认模式:检查git相关文件
  20. MODE="git"
  21. # 解析命令行参数
  22. declare -a USER_FILES_ARRAY=()
  23. while [[ $# -gt 0 ]]; do
  24. case $1 in
  25. -a | --all)
  26. MODE="all"
  27. shift
  28. ;;
  29. -g | --git)
  30. MODE="git"
  31. shift
  32. ;;
  33. -f | --file)
  34. MODE="file"
  35. if [ -f "$2" ]; then
  36. USER_FILES_ARRAY+=("$2")
  37. else
  38. echo "警告:文件 '$2' 不存在,跳过"
  39. fi
  40. shift 2
  41. ;;
  42. -d | --dir)
  43. MODE="dir"
  44. if [ -d "$2" ]; then
  45. # 查找目录下的所有文件并添加到数组
  46. while IFS= read -r file; do
  47. [ -n "${file}" ] && [[ ${file} != */.* ]] && [[ ${file} != .* ]] && USER_FILES_ARRAY+=("${file}")
  48. done < <(find "$2" -type f 2>/dev/null | grep -v '/\.' || true)
  49. else
  50. echo "警告:目录 '$2' 不存在,跳过"
  51. fi
  52. shift 2
  53. ;;
  54. --help)
  55. show_usage
  56. exit 0
  57. ;;
  58. -*)
  59. echo "错误:未知选项 '$1'"
  60. show_usage
  61. exit 1
  62. ;;
  63. *)
  64. # 处理直接指定的文件或目录
  65. MODE="处理直接指定的文件或目录"
  66. if [ -f "$1" ]; then
  67. USER_FILES_ARRAY+=("$1")
  68. elif [ -d "$1" ]; then
  69. # 查找目录下的所有文件并添加到数组
  70. while IFS= read -r file; do
  71. [ -n "${file}" ] && [[ ${file} != */.* ]] && [[ ${file} != .* ]] && USER_FILES_ARRAY+=("${file}")
  72. done < <(find "$1" -type f 2>/dev/null | grep -v '/\.' || true)
  73. else
  74. echo "警告:'$1' 不是文件或目录,跳过"
  75. fi
  76. shift
  77. ;;
  78. esac
  79. done
  80. # 将数组转换为换行分隔的字符串
  81. if [ ${#USER_FILES_ARRAY[@]} -gt 0 ]; then
  82. USER_FILES=$(printf "%s\n" "${USER_FILES_ARRAY[@]}")
  83. else
  84. USER_FILES=""
  85. fi
  86. echo ""
  87. echo "🚀 正在运行代码质量检查..."
  88. echo "========================================"
  89. echo "模式: ${MODE}"
  90. # 工作根:优先 WORKSPACE_ROOT;容器内默认 /workspace,宿主机上回退当前 git 仓库根/当前目录
  91. WORKSPACE_ROOT="${WORKSPACE_ROOT:-/workspace}"
  92. if [ ! -d "$WORKSPACE_ROOT" ]; then
  93. WORKSPACE_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
  94. fi
  95. # 切换到工作目录(文件收集阶段;实际运行 trunk 前会再切到目标仓库根)
  96. cd "$WORKSPACE_ROOT" || exit
  97. # 解析 trunk 二进制(优先级:TRUNK_BIN 环境变量 > $WORKSPACE_ROOT/trunk > PATH 中的 trunk)
  98. resolve_trunk() {
  99. if [[ -n ${TRUNK_BIN-} ]] && [[ -x ${TRUNK_BIN} ]]; then
  100. printf '%s\n' "${TRUNK_BIN}"
  101. elif [[ -x "${WORKSPACE_ROOT}/trunk" ]]; then
  102. printf '%s\n' "${WORKSPACE_ROOT}/trunk"
  103. elif command -v trunk >/dev/null 2>&1; then
  104. command -v trunk
  105. else
  106. printf '\n'
  107. fi
  108. }
  109. TRUNK_BIN="$(resolve_trunk)"
  110. if [[ -z ${TRUNK_BIN} ]]; then
  111. echo "❌ trunk工具未找到,请设置 TRUNK_BIN 或安装 trunk"
  112. exit 1
  113. fi
  114. # 根据模式获取需要检查的文件
  115. if [ "${MODE}" = "all" ]; then
  116. # 检查所有文件
  117. ALL_FILES=$(find . -type f 2>/dev/null | grep -v '/\.' | grep -v '^/\.' | sort | uniq || true)
  118. elif [ -n "${USER_FILES}" ]; then
  119. # 使用用户指定的文件
  120. ALL_FILES="${USER_FILES}"
  121. else
  122. # 默认:检查git相关文件
  123. STAGED_FILES=$(git diff --name-only --diff-filter=d 2>/dev/null || true)
  124. NEW_FILES=$(git diff --cached --name-only --diff-filter=A 2>/dev/null || true)
  125. UNTRACKED_FILES=$(git ls-files --others --exclude-standard 2>/dev/null || true)
  126. # 使用数组安全地合并文件列表
  127. declare -a ALL_FILES_ARRAY=()
  128. # 添加已修改的文件
  129. if [ -n "${STAGED_FILES}" ]; then
  130. while IFS= read -r file; do
  131. [ -n "${file}" ] && [[ ${file} != */.* ]] && [[ ${file} != .* ]] && ALL_FILES_ARRAY+=("${file}")
  132. done <<<"${STAGED_FILES}"
  133. fi
  134. # 添加新暂存的文件
  135. if [ -n "${NEW_FILES}" ]; then
  136. while IFS= read -r file; do
  137. [ -n "${file}" ] && [[ ${file} != */.* ]] && [[ ${file} != .* ]] && ALL_FILES_ARRAY+=("${file}")
  138. done <<<"${NEW_FILES}"
  139. fi
  140. # 添加未跟踪的文件
  141. if [ -n "${UNTRACKED_FILES}" ]; then
  142. while IFS= read -r file; do
  143. [ -n "${file}" ] && [[ ${file} != */.* ]] && [[ ${file} != .* ]] && ALL_FILES_ARRAY+=("${file}")
  144. done <<<"${UNTRACKED_FILES}"
  145. fi
  146. # 去重并生成最终文件列表
  147. if [ ${#ALL_FILES_ARRAY[@]} -gt 0 ]; then
  148. ALL_FILES=$(printf "%s\n" "${ALL_FILES_ARRAY[@]}" | sort | uniq || true)
  149. else
  150. ALL_FILES=""
  151. fi
  152. fi
  153. # 去重并过滤空行和隐藏文件(仅在all模式下过滤隐藏文件)
  154. if [ "${MODE}" = "all" ] || [ -n "${USER_FILES}" ]; then
  155. # 始终过滤空行并去重
  156. ALL_FILES=$(echo "${ALL_FILES}" | grep -v '^$' | sort | uniq || true)
  157. # 仅在all模式下过滤隐藏文件(用户明确指定文件时不过滤)
  158. if [ "${MODE}" = "all" ]; then
  159. ALL_FILES=$(echo "${ALL_FILES}" | grep -v '/\.' | grep -v '^/\.' || true)
  160. fi
  161. fi
  162. if [ -z "${ALL_FILES}" ]; then
  163. echo "ℹ️ 没有找到需要检查的文件"
  164. exit 0
  165. fi
  166. # 检查文件数量
  167. FILE_COUNT=$(echo "${ALL_FILES}" | wc -l)
  168. echo ""
  169. echo "📊 共发现 ${FILE_COUNT} 个需要检查的文件:"
  170. echo "${ALL_FILES}"
  171. echo ""
  172. # 确定目标所在的 git 仓库根并切换(子模块是独立 git 仓库,需在各自仓库根运行 trunk)
  173. if [[ -n ${ALL_FILES} ]]; then
  174. FIRST_FILE="$(printf '%s\n' "${ALL_FILES}" | head -n 1)"
  175. REPO_ROOT="$(git -C "$(dirname "${FIRST_FILE}")" rev-parse --show-toplevel 2>/dev/null || echo "$WORKSPACE_ROOT")"
  176. else
  177. REPO_ROOT="/workspace"
  178. fi
  179. cd "${REPO_ROOT}" || exit
  180. # 运行trunk check检查代码质量(已禁用安全扫描类工具,仅纯代码质量/格式检查)
  181. echo ""
  182. echo "🔍 正在运行代码质量检查..."
  183. # 文件数量过多时,直接以 ${ALL_FILES} 展开成 argv 会超过 ARG_MAX(如根仓库 3 万+ 文件
  184. # ≈ 2.8MB > 2MB),触发 "Argument list too long"(E2BIG)。
  185. # 改为换行分隔经 xargs 按字节上限自动分批调用;任一批次失败即整体失败。
  186. # 用 `tr '\n' '\0' | xargs -0 -s` 而非 GNU 专有的 `xargs -d`/`-r`,兼容 macOS(BSD) xargs;
  187. # 空输入已在上面提前 exit 0,故无需 -r 的"空不执行"语义。
  188. TRUNK_BATCH_BYTES="${TRUNK_BATCH_BYTES:-131072}"
  189. if ! printf '%s\n' "${ALL_FILES}" | tr '\n' '\0' | xargs -0 -s "${TRUNK_BATCH_BYTES}" "${TRUNK_BIN}" check; then
  190. echo ""
  191. echo "❌ 代码质量检查失败!"
  192. echo ""
  193. echo "请修复上述问题后再提交"
  194. echo ""
  195. exit 1
  196. fi
  197. echo ""
  198. echo "✅ 代码质量检查通过!可以安全提交。"
  199. echo "========================================"
  200. echo ""
  201. exit 0