From d8639e71afc2540b58f8bb6779297ca89f300484 Mon Sep 17 00:00:00 2001 From: dongzi Date: Mon, 20 Jul 2026 15:03:57 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E5=A4=9A=E6=AC=A1=E6=8F=90?= =?UTF-8?q?=E4=BA=A4=E6=A3=80=E6=B5=8B=E8=A1=A5=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/code-check.yaml | 126 +++++++++++++++--- .../java/com/codechecker/CodeCheckMain.java | 16 ++- 2 files changed, 124 insertions(+), 18 deletions(-) diff --git a/.gitea/workflows/code-check.yaml b/.gitea/workflows/code-check.yaml index 11e6fed..2440452 100644 --- a/.gitea/workflows/code-check.yaml +++ b/.gitea/workflows/code-check.yaml @@ -17,19 +17,17 @@ jobs: if: ${{ gitea.ref != 'refs/heads/pre' && gitea.ref != 'refs/heads/dev' && gitea.ref != 'refs/heads/master-2.0' }} runs-on: jdk11 steps: - # 浅克隆:CodeChecker 仅需 HEAD 与 HEAD~1,避免全量拉取 - # 指定 --branch 确保非默认分支推送时也能正确检出 gitea.sha + # 浅克隆当前 tip;对比基准用 push before(覆盖一次 push 的多 commit 累计 diff) + # 不必全量历史:只需能 git show before / after 两边的文件内容 - name: 检出代码 run: | git config --global http.sslVerify false - git clone --depth 2 --single-branch --branch "${{ gitea.ref_name }}" \ - "https://${{ gitea.token }}@git.niujiekeji.com/${{ gitea.repository }}.git" . - # 将 commit 绑定回本地分支,避免 detached HEAD 导致分支识别为「未知」 - git checkout -B "${{ gitea.ref_name }}" "${{ gitea.sha }}" - # git config --global http.sslVerify false - # git clone --depth 2 --single-branch --branch "${{ gitea.ref_name }}" \ - # "https://${{ gitea.token }}@git.niujiekeji.com/${{ gitea.repository }}.git" . - # git checkout ${{ gitea.sha }} + REPO_URL="https://${{ gitea.token }}@git.niujiekeji.com/${{ gitea.repository }}.git" + BRANCH="${{ gitea.ref_name }}" + NEW_SHA="${{ gitea.sha }}" + git clone --depth 1 --single-branch --branch "${BRANCH}" "${REPO_URL}" . + git checkout -B "${BRANCH}" "${NEW_SHA}" + echo "${NEW_SHA}" > /tmp/code-check-new-sha.txt - name: 检查配置文件 run: | @@ -67,17 +65,115 @@ jobs: echo "Java: $(java -version 2>&1 | head -1)" - name: 执行 CodeChecker 变更检测 + env: + # push 前 tip;新分支首次 push 时为全 0。workflow_dispatch 可能为空,下方会回退。 + PUSH_BEFORE: ${{ gitea.event.before }} + # push 事件携带的 commits 列表(JSON);用于准确统计 commit 数(不受浅克隆影响) + PUSH_COMMITS_JSON: ${{ toJson(gitea.event.commits) }} run: | - OLD_SHA=$(git rev-parse HEAD~1 2>/dev/null || echo "") - if [ -z "$OLD_SHA" ]; then - echo "首次提交,跳过变更检测" + NEW_SHA=$(cat /tmp/code-check-new-sha.txt) + OLD_SHA="${PUSH_BEFORE}" + # 新分支首次 push(before 全 0)→ 跳过 + if [ -z "$OLD_SHA" ] || echo "$OLD_SHA" | grep -Eq '^0+$'; then + # 手动触发:无 before,回退为 HEAD~1(加深 1 层后取父提交) + if [ "${{ gitea.event_name }}" = "workflow_dispatch" ]; then + git fetch --deepen 1 || true + OLD_SHA=$(git rev-parse HEAD~1 2>/dev/null || echo "") + if [ -z "$OLD_SHA" ]; then + echo "手动触发且无父提交,跳过检测" + exit 0 + fi + echo "workflow_dispatch:无 push before,回退使用 HEAD~1=${OLD_SHA}" + else + echo "新分支首次 push(before 为空/全 0),跳过检测" + exit 0 + fi + fi + if [ "$OLD_SHA" = "$NEW_SHA" ]; then + echo "before 与 after 相同,无需检测" exit 0 fi - COMMIT_TIME=$(git log -1 --format=%cd --date=format:'%Y-%m-%d %H:%M:%S') + # 确保提交对象可读 + ensure_commit() { + local sha="$1" + if git cat-file -e "${sha}^{commit}" 2>/dev/null; then + return 0 + fi + echo "本地缺少 ${sha},尝试按 SHA 浅取…" + if git fetch --depth 1 origin "${sha}"; then + git cat-file -e "${sha}^{commit}" 2>/dev/null && return 0 + fi + for d in 10 30 50 100; do + echo "deepen ${d}…" + git fetch --deepen "${d}" || true + if git cat-file -e "${sha}^{commit}" 2>/dev/null; then + return 0 + fi + done + return 1 + } + # 加深当前 tip 的历史,直到 before 成为 after 的祖先(否则 rev-list 在浅克隆下会少算) + ensure_range() { + local old="$1" + local new="$2" + local i=0 + while [ "$i" -lt 15 ]; do + if git merge-base --is-ancestor "${old}" "${new}" 2>/dev/null; then + return 0 + fi + echo "区间历史不完整,deepen 20…(第 $((i + 1)) 次)" + git fetch --deepen 20 || true + i=$((i + 1)) + done + # force-push 等非祖先关系:对象在即可做 git diff,但 rev-list 不可靠 + if git cat-file -e "${old}^{commit}" 2>/dev/null \ + && git cat-file -e "${new}^{commit}" 2>/dev/null; then + echo "警告: ${old} 不是 ${new} 的祖先(可能 force-push),commit 计数将改用事件 payload" + return 0 + fi + return 1 + } + # 优先用 Gitea push 事件的 commits 数组长度(准确且不依赖 shallow) + count_from_payload() { + if [ -z "${PUSH_COMMITS_JSON:-}" ] || [ "${PUSH_COMMITS_JSON}" = "null" ] || [ "${PUSH_COMMITS_JSON}" = "[]" ]; then + return 1 + fi + local n + n=$(printf '%s' "${PUSH_COMMITS_JSON}" | grep -oE '"id"[[:space:]]*:[[:space:]]*"[0-9a-fA-F]{7,40}"' | wc -l | tr -d ' ') + if [ -n "$n" ] && [ "$n" -gt 0 ] 2>/dev/null; then + echo "$n" + return 0 + fi + return 1 + } + if ! ensure_commit "$OLD_SHA"; then + echo "错误: 无法获取 push 前 tip ${OLD_SHA},请检查 shallow/权限" + exit 2 + fi + if ! ensure_commit "$NEW_SHA"; then + echo "错误: 无法解析当前 tip ${NEW_SHA}" + exit 2 + fi + if ! ensure_range "$OLD_SHA" "$NEW_SHA"; then + echo "错误: 无法补齐 ${OLD_SHA}..${NEW_SHA} 历史" + exit 2 + fi + echo "对比区间: ${OLD_SHA} → ${NEW_SHA}" + COMMIT_COUNT="" + if COMMIT_COUNT=$(count_from_payload); then + echo "本次 push 累计 commit 数: ${COMMIT_COUNT}(来自事件 payload)" + elif git merge-base --is-ancestor "$OLD_SHA" "$NEW_SHA" 2>/dev/null; then + COMMIT_COUNT=$(git rev-list --count "${OLD_SHA}..${NEW_SHA}" 2>/dev/null || echo "?") + echo "本次 push 累计 commit 数: ${COMMIT_COUNT}(来自 git rev-list)" + else + echo "本次 push 累计 commit 数: ?(非祖先关系且无 payload)" + fi + COMMIT_TIME=$(git log -1 --format=%cd --date=format:'%Y-%m-%d %H:%M:%S' "${NEW_SHA}") java -jar "/tmp/code-checker-${CODE_CHECKER_VERSION}.jar" \ --config .gitea/config/code-check-config.yaml \ --repo-root . \ --old-sha "$OLD_SHA" \ - --new-sha "$(git rev-parse HEAD)" \ + --new-sha "$NEW_SHA" \ + --branch "${{ gitea.ref_name }}" \ --modifier "${{ gitea.actor }}" \ --modify-time "$COMMIT_TIME" diff --git a/src/main/java/com/codechecker/CodeCheckMain.java b/src/main/java/com/codechecker/CodeCheckMain.java index 91a168d..9798e9f 100644 --- a/src/main/java/com/codechecker/CodeCheckMain.java +++ b/src/main/java/com/codechecker/CodeCheckMain.java @@ -52,6 +52,9 @@ public class CodeCheckMain implements Callable { @Option(names = "--modify-time", required = true, description = "修改时间") private String modifyTime; + @Option(names = "--branch", description = "分支名(CI 显式传入,优先于 git rev-parse)") + private String branch; + /** 程序入口 */ public static void main(String[] args) { int exitCode = new CommandLine(new CodeCheckMain()).execute(args); @@ -68,7 +71,7 @@ public class CodeCheckMain implements Callable { } GitChangeScanner gitScanner = new GitChangeScanner(repoRoot.toAbsolutePath()); - String branch = gitScanner.getCurrentBranch(); + String branchName = resolveBranch(gitScanner); DtoNestIndex nestIndex = DtoNestIndex.build(repoRoot.toAbsolutePath(), appConfig); List classReports = List.of(); List apiReports = List.of(); @@ -87,8 +90,8 @@ public class CodeCheckMain implements Callable { OverlapNotificationFilter.FilterResult filtered = OverlapNotificationFilter.apply( classReports, apiReports, appConfig.getDtoOverlapMode(), nestIndex); - int totalSent = sendClassNotifications(appConfig, filtered.classReports(), branch) - + sendApiNotifications(appConfig, filtered.apiReports(), branch); + int totalSent = sendClassNotifications(appConfig, filtered.classReports(), branchName) + + sendApiNotifications(appConfig, filtered.apiReports(), branchName); if (totalSent == 0 && appConfig.isOnlyOnChange()) { System.out.println("无变更,静默退出"); @@ -96,6 +99,13 @@ public class CodeCheckMain implements Callable { return 0; } + private String resolveBranch(GitChangeScanner gitScanner) { + if (branch != null && !branch.isBlank()) { + return branch.trim(); + } + return gitScanner.getCurrentBranch(); + } + private List analyzeClassChanges(AppConfig appConfig, GitChangeScanner gitScanner, DtoNestIndex nestIndex) throws Exception { System.out.println("=== 类变更检测 ===");