Compare commits
94 Commits
6798c2a19a
...
06ce9ad92b
Author | SHA1 | Date | |
---|---|---|---|
06ce9ad92b | |||
d5454a1abd | |||
4a56cd81aa | |||
dee296143f | |||
5c2fe4ffcb | |||
64ae632003 | |||
f5ba04b8e7 | |||
d723d98de0 | |||
7cc6747a25 | |||
5e4786c3b5 | |||
4666eb7375 | |||
02d0f49070 | |||
049a5e6e7e | |||
28bf70d0a9 | |||
914ad6c7e3 | |||
18b7193bc0 | |||
2029cc8ae9 | |||
6456a64bca | |||
d8ed5809cb | |||
79446e7826 | |||
0826a3d14a | |||
ff8f700506 | |||
198d329c8e | |||
43a799e958 | |||
fa2d98007e | |||
fc68f7bcb4 | |||
5038cf118d | |||
0471834bca | |||
a16418f3a6 | |||
16cbf4c1ef | |||
97556cf220 | |||
b40072a485 | |||
2a55fe1c3d | |||
2b5769545d | |||
a9a829da3f | |||
4c00904f25 | |||
e2bf6d3cf1 | |||
dd8dc68eeb | |||
c13b3465c1 | |||
a5a4a649dc | |||
3682a36fde | |||
7b3edb2361 | |||
6c8332af29 | |||
7ad3a3ab1f | |||
03065b0b69 | |||
9800becc96 | |||
f665e5b2b1 | |||
7ce4d29d0e | |||
6af7be6cb1 | |||
863e562743 | |||
4b4c83a609 | |||
5893b54054 | |||
1bb0ddd253 | |||
3a8e3dfcf4 | |||
d172fae912 | |||
8992d9df7a | |||
a66dbc29e4 | |||
b7a1ad0833 | |||
5cf3d5f9ce | |||
9765ea1007 | |||
5083b02a6f | |||
9e5c933fbd | |||
61dada5c40 | |||
2291264df0 | |||
d597c73dd2 | |||
1253404c49 | |||
d03927457a | |||
d5a96515ec | |||
3cc96821ad | |||
5922d795b3 | |||
967a859c9f | |||
3ccdd394b2 | |||
5a860f1203 | |||
52491478a4 | |||
eac7a62ebe | |||
![]() |
2c742852fb | ||
776de48d03 | |||
![]() |
fdf84546da | ||
![]() |
1d90d72978 | ||
![]() |
6fbbe7ece0 | ||
![]() |
96c71bcd63 | ||
![]() |
756a531bac | ||
![]() |
fd69aebac5 | ||
9f0e9bf9f3 | |||
2928e6321f | |||
f81581b928 | |||
27a2aac537 | |||
e4091f9ba4 | |||
739069a18f | |||
97f89d210b | |||
b608a22cfd | |||
2f8604f647 | |||
c5d31d9a1a | |||
9445f93e44 |
31
.drone.yml
31
.drone.yml
|
@ -1,31 +0,0 @@
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: ssh
|
|
||||||
name: CI
|
|
||||||
server:
|
|
||||||
host: 111.186.59.59
|
|
||||||
user: drone
|
|
||||||
ssh_key:
|
|
||||||
from_secret: joj3-test
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: prepare
|
|
||||||
commands:
|
|
||||||
- go env -w GOPROXY=https://goproxy.cn,direct
|
|
||||||
- whoami
|
|
||||||
- pwd
|
|
||||||
- env
|
|
||||||
- go version
|
|
||||||
- go env
|
|
||||||
- git status -v
|
|
||||||
- git log -1
|
|
||||||
- name: build
|
|
||||||
commands:
|
|
||||||
- make
|
|
||||||
- name: test
|
|
||||||
commands:
|
|
||||||
- make prepare-test
|
|
||||||
- make test
|
|
||||||
- name: store
|
|
||||||
commands:
|
|
||||||
- cp build/joj3 /home/drone/.local/bin/joj3
|
|
65
.gitea/workflows/build.yaml
Normal file
65
.gitea/workflows/build.yaml
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
---
|
||||||
|
name: build
|
||||||
|
on:
|
||||||
|
- push
|
||||||
|
- pull_request
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
container:
|
||||||
|
image: focs.ji.sjtu.edu.cn:5000/gitea/runner-images:ubuntu-latest
|
||||||
|
volumes:
|
||||||
|
- /home/actions/.ssh:/root/.ssh
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: https://gitea.com/BoYanZh/checkout@focs
|
||||||
|
- name: Setup Go 1.23.1
|
||||||
|
run: |
|
||||||
|
wget -q https://studygolang.com/dl/golang/go1.23.2.linux-amd64.tar.gz
|
||||||
|
rm -rf /usr/local/go
|
||||||
|
tar -C /usr/local -xzf go1.23.2.linux-amd64.tar.gz
|
||||||
|
rm -rf go1.23.2.linux-amd64.tar.gz
|
||||||
|
echo "PATH=$PATH:/usr/local/go/bin:/root/go/bin" >> $GITHUB_ENV
|
||||||
|
- name: Display Go version
|
||||||
|
run: go version
|
||||||
|
- name: Prepare
|
||||||
|
run: |
|
||||||
|
go env -w GO111MODULE=on
|
||||||
|
go env -w GOPROXY=https://goproxy.io,direct
|
||||||
|
chown -R root:root /root/.ssh
|
||||||
|
- name: Setup golangci-lint
|
||||||
|
run: |
|
||||||
|
wget -q https://ghp.ci/https://github.com/golangci/golangci-lint/releases/download/v1.61.0/golangci-lint-1.61.0-linux-amd64.tar.gz
|
||||||
|
tar -C /tmp -xzf golangci-lint-1.61.0-linux-amd64.tar.gz
|
||||||
|
rm -rf golangci-lint-1.61.0-linux-amd64.tar.gz
|
||||||
|
mkdir -p /root/go/bin
|
||||||
|
mv /tmp/golangci-lint-1.61.0-linux-amd64/golangci-lint /root/go/bin
|
||||||
|
- name: Lint
|
||||||
|
run: make lint
|
||||||
|
- name: Build
|
||||||
|
run: make build
|
||||||
|
- name: Version
|
||||||
|
run: ./build/joj3 -version
|
||||||
|
- name: Test
|
||||||
|
run: |
|
||||||
|
make prepare-test
|
||||||
|
make ci-test
|
||||||
|
trigger-build-image:
|
||||||
|
container:
|
||||||
|
image: focs.ji.sjtu.edu.cn:5000/gitea/runner-images:ubuntu-latest
|
||||||
|
volumes:
|
||||||
|
- /home/actions/.ssh:/root/.ssh
|
||||||
|
needs: build
|
||||||
|
if: github.ref == 'refs/heads/master'
|
||||||
|
steps:
|
||||||
|
- name: Set up Git
|
||||||
|
run: |
|
||||||
|
git config --global user.name "gitea-actions[bot]"
|
||||||
|
git config --global user.email "gitea-actions[bot]@users.noreply.gitea.com"
|
||||||
|
chown -R root:root /root/.ssh
|
||||||
|
- name: Clone, Commit and Push
|
||||||
|
run: |
|
||||||
|
git clone ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/runner-images.git runner-images
|
||||||
|
cd runner-images
|
||||||
|
git commit --allow-empty -m "chore: trigger gitea actions by JOJ3"
|
||||||
|
git push
|
66
.gitmodules
vendored
66
.gitmodules
vendored
|
@ -1,36 +1,72 @@
|
||||||
|
[submodule "examples/diff/basic"]
|
||||||
|
path = examples/diff/basic
|
||||||
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
|
||||||
|
branch = diff/basic
|
||||||
|
[submodule "examples/diff/complex"]
|
||||||
|
path = examples/diff/complex
|
||||||
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
|
||||||
|
branch = diff/complex
|
||||||
[submodule "examples/cpplint/sillycode"]
|
[submodule "examples/cpplint/sillycode"]
|
||||||
path = examples/cpplint/sillycode
|
path = examples/cpplint/sillycode
|
||||||
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
|
||||||
branch = cpplint/sillycode
|
branch = cpplint/sillycode
|
||||||
[submodule "examples/compile/success"]
|
[submodule "examples/compile/success"]
|
||||||
path = examples/compile/success
|
path = examples/compile/success
|
||||||
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
|
||||||
branch = compile/success
|
branch = compile/success
|
||||||
[submodule "examples/compile/error"]
|
[submodule "examples/compile/error"]
|
||||||
path = examples/compile/error
|
path = examples/compile/error
|
||||||
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
|
||||||
branch = compile/error
|
branch = compile/error
|
||||||
[submodule "examples/dummy/success"]
|
[submodule "examples/sample/success"]
|
||||||
path = examples/dummy/success
|
path = examples/sample/success
|
||||||
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
|
||||||
branch = dummy/success
|
branch = sample/success
|
||||||
[submodule "examples/dummy/error"]
|
[submodule "examples/sample/error"]
|
||||||
path = examples/dummy/error
|
path = examples/sample/error
|
||||||
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
|
||||||
branch = dummy/error
|
branch = sample/error
|
||||||
[submodule "examples/keyword/cpplint/sillycode"]
|
[submodule "examples/keyword/cpplint/sillycode"]
|
||||||
path = examples/keyword/cpplint/sillycode
|
path = examples/keyword/cpplint/sillycode
|
||||||
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
|
||||||
branch = keyword/cpplint/sillycode
|
branch = keyword/cpplint/sillycode
|
||||||
[submodule "examples/clangtidy/sillycode"]
|
[submodule "examples/clangtidy/sillycode"]
|
||||||
path = examples/clangtidy/sillycode
|
path = examples/clangtidy/sillycode
|
||||||
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
|
||||||
branch = clangtidy/sillycode
|
branch = clangtidy/sillycode
|
||||||
[submodule "examples/keyword/clangtidy/sillycode"]
|
[submodule "examples/keyword/clangtidy/sillycode"]
|
||||||
path = examples/keyword/clangtidy/sillycode
|
path = examples/keyword/clangtidy/sillycode
|
||||||
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
|
||||||
branch = keyword/clangtidy/sillycode
|
branch = keyword/clangtidy/sillycode
|
||||||
|
[submodule "examples/healthcheck/asciifile"]
|
||||||
|
path = examples/healthcheck/asciifile
|
||||||
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
|
||||||
|
branch = healthcheck/asciifile
|
||||||
|
[submodule "examples/healthcheck/asciimsg"]
|
||||||
|
path = examples/healthcheck/asciimsg
|
||||||
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
|
||||||
|
branch = healthcheck/asciimsg
|
||||||
|
[submodule "examples/healthcheck/forbiddenfile"]
|
||||||
|
path = examples/healthcheck/forbiddenfile
|
||||||
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
|
||||||
|
branch = healthcheck/forbiddenfile
|
||||||
|
[submodule "examples/healthcheck/meta"]
|
||||||
|
path = examples/healthcheck/meta
|
||||||
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
|
||||||
|
branch = healthcheck/meta
|
||||||
|
[submodule "examples/healthcheck/release"]
|
||||||
|
path = examples/healthcheck/release
|
||||||
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
|
||||||
|
branch = healthcheck/release
|
||||||
|
[submodule "examples/healthcheck/reposize"]
|
||||||
|
path = examples/healthcheck/reposize
|
||||||
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
|
||||||
|
branch = healthcheck/reposize
|
||||||
|
[submodule "examples/healthcheck/repoverify"]
|
||||||
|
path = examples/healthcheck/repoverify
|
||||||
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
|
||||||
|
branch = healthcheck/repoverify
|
||||||
[submodule "examples/cppcheck/sillycode"]
|
[submodule "examples/cppcheck/sillycode"]
|
||||||
path = examples/cppcheck/sillycode
|
path = examples/cppcheck/sillycode
|
||||||
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
|
||||||
branch = cppcheck/sillycode
|
branch = cppcheck/sillycode
|
||||||
|
|
|
@ -1,24 +1,37 @@
|
||||||
---
|
---
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.5.0
|
rev: v4.6.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
|
- repo: https://github.com/mrtazz/checkmake.git
|
||||||
|
rev: 0.2.2
|
||||||
|
hooks:
|
||||||
|
- id: checkmake
|
||||||
- repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt
|
- repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt
|
||||||
rev: 0.2.3
|
rev: 0.2.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: yamlfmt
|
- id: yamlfmt
|
||||||
- repo: https://github.com/Bahjat/pre-commit-golang
|
|
||||||
rev: v1.0.2
|
|
||||||
hooks:
|
|
||||||
- id: gofumpt
|
|
||||||
- repo: https://github.com/dnephin/pre-commit-golang
|
- repo: https://github.com/dnephin/pre-commit-golang
|
||||||
rev: v0.5.1
|
rev: v0.5.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: go-fmt
|
|
||||||
- id: go-imports
|
|
||||||
- id: golangci-lint
|
- id: golangci-lint
|
||||||
- id: go-unit-tests
|
|
||||||
- id: go-mod-tidy
|
- id: go-mod-tidy
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: make-build
|
||||||
|
name: make build
|
||||||
|
entry: make build
|
||||||
|
language: system
|
||||||
|
always_run: true
|
||||||
|
stages: [commit]
|
||||||
|
pass_filenames: false
|
||||||
|
- id: make-test
|
||||||
|
name: make test
|
||||||
|
entry: make test
|
||||||
|
language: system
|
||||||
|
always_run: true
|
||||||
|
stages: [commit]
|
||||||
|
pass_filenames: false
|
||||||
|
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Joint Online Judge
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
13
Makefile
13
Makefile
|
@ -1,9 +1,10 @@
|
||||||
.PHONY: all build clean prepare-test test
|
.PHONY: all build clean lint prepare-test test ci-test
|
||||||
|
|
||||||
BUILD_DIR = ./build
|
BUILD_DIR = ./build
|
||||||
TMP_DIR = ./tmp
|
TMP_DIR = ./tmp
|
||||||
APPS := $(notdir $(wildcard ./cmd/*))
|
APPS := $(notdir $(wildcard ./cmd/*))
|
||||||
FLAGS := "-s -w"
|
VERSION := $(shell git rev-parse --short HEAD)
|
||||||
|
FLAGS := "-s -w -X main.Version=$(VERSION)"
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
|
@ -15,9 +16,17 @@ clean:
|
||||||
rm -rf $(TMP_DIR)/*
|
rm -rf $(TMP_DIR)/*
|
||||||
rm -rf *.out
|
rm -rf *.out
|
||||||
|
|
||||||
|
lint:
|
||||||
|
golangci-lint run
|
||||||
|
|
||||||
prepare-test:
|
prepare-test:
|
||||||
git submodule update --init --remote
|
git submodule update --init --remote
|
||||||
|
|
||||||
test:
|
test:
|
||||||
./scripts/prepare_test_repos.sh $(TMP_DIR)
|
./scripts/prepare_test_repos.sh $(TMP_DIR)
|
||||||
go test -coverprofile cover.out -v ./...
|
go test -coverprofile cover.out -v ./...
|
||||||
|
|
||||||
|
ci-test:
|
||||||
|
./scripts/prepare_test_repos.sh $(TMP_DIR)
|
||||||
|
./scripts/run_foreach_test_repos.sh $(TMP_DIR) "sed -i '2i \ \ \"sandboxExecServer\": \"172.17.0.1:5051\",' conf.json"
|
||||||
|
go test -coverprofile cover.out -v ./...
|
||||||
|
|
120
README.md
120
README.md
|
@ -1,5 +1,7 @@
|
||||||
# JOJ3
|
# JOJ3
|
||||||
|
|
||||||
|
[](https://goreportcard.com/report/github.com/joint-online-judge/JOJ3)
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
1. Make sure you are in a Unix-like OS (Linux, MacOS). For Windows, use [WSL 2](https://learn.microsoft.com/en-us/windows/wsl/install).
|
1. Make sure you are in a Unix-like OS (Linux, MacOS). For Windows, use [WSL 2](https://learn.microsoft.com/en-us/windows/wsl/install).
|
||||||
|
@ -9,18 +11,20 @@
|
||||||
3. Enable cgroup v2 for your OS. Check [here](https://stackoverflow.com/a/73376219/13724598). So that you do not need root permission to run `go-judge`.
|
3. Enable cgroup v2 for your OS. Check [here](https://stackoverflow.com/a/73376219/13724598). So that you do not need root permission to run `go-judge`.
|
||||||
|
|
||||||
4. Clone [go-judge](https://github.com/criyle/go-judge).
|
4. Clone [go-judge](https://github.com/criyle/go-judge).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ git clone https://github.com/criyle/go-judge && cd go-judge
|
$ git clone https://github.com/criyle/go-judge && cd go-judge
|
||||||
$ go build -o ./tmp/go-judge ./cmd/go-judge
|
$ go build -o ./tmp/go-judge ./cmd/go-judge
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Run `go-judge`.
|
5. Run `go-judge`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ # make sure you are in go-judge directory
|
$ # make sure you are in go-judge directory
|
||||||
$ ./tmp/go-judge -enable-grpc -enable-debug -enable-metrics
|
$ ./tmp/go-judge -http-addr 0.0.0.0:5050 -grpc-addr 0.0.0.0:5051 -monitor-addr 0.0.0.0:5052 -enable-grpc -enable-debug -enable-metrics
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Pull submodules. It might be slow, so only run it when necessary.
|
6. Pull submodules. It might be slow, so only run it when the test branches are out of date.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ # make sure you are in JOJ3 directory
|
$ # make sure you are in JOJ3 directory
|
||||||
|
@ -40,45 +44,18 @@ Note: you may fail the test if the checking tools are not installed. e.g. For th
|
||||||
```bash
|
```bash
|
||||||
$ make test
|
$ make test
|
||||||
go test -coverprofile cover.out -v ./...
|
go test -coverprofile cover.out -v ./...
|
||||||
focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/cmd/dummy coverage: 0.0% of statements
|
...
|
||||||
? focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/executors [no test files]
|
|
||||||
? focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers [no test files]
|
|
||||||
? focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/pkg/healthcheck [no test files]
|
|
||||||
focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/executors/dummy coverage: 0.0% of statements
|
|
||||||
focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/cpplint coverage: 0.0% of statements
|
|
||||||
focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/diff coverage: 0.0% of statements
|
|
||||||
focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage coverage: 0.0% of statements
|
|
||||||
focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/pkg/dummy coverage: 0.0% of statements
|
|
||||||
focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/keyword coverage: 0.0% of statements
|
|
||||||
focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/dummy coverage: 0.0% of statements
|
|
||||||
focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/executors/sandbox coverage: 0.0% of statements
|
|
||||||
focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/resultstatus coverage: 0.0% of statements
|
|
||||||
=== RUN TestMain
|
|
||||||
=== RUN TestMain/compile/error
|
|
||||||
=== RUN TestMain/compile/success
|
|
||||||
=== RUN TestMain/cpplint/sillycode
|
|
||||||
=== RUN TestMain/dummy/error
|
|
||||||
=== RUN TestMain/dummy/success
|
|
||||||
=== RUN TestMain/keyword/clang-tidy/sillycode
|
|
||||||
=== RUN TestMain/keyword/cpplint/sillycode
|
|
||||||
--- PASS: TestMain (2.28s)
|
|
||||||
--- PASS: TestMain/compile/error (0.03s)
|
|
||||||
--- PASS: TestMain/compile/success (0.42s)
|
|
||||||
--- PASS: TestMain/cpplint/sillycode (0.14s)
|
|
||||||
--- PASS: TestMain/dummy/error (0.01s)
|
|
||||||
--- PASS: TestMain/dummy/success (0.01s)
|
|
||||||
--- PASS: TestMain/keyword/clang-tidy/sillycode (1.57s)
|
|
||||||
--- PASS: TestMain/keyword/cpplint/sillycode (0.11s)
|
|
||||||
PASS
|
PASS
|
||||||
coverage: 74.0% of statements
|
coverage: 74.0% of statements
|
||||||
ok focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/cmd/joj3 2.290s coverage: 74.0% of statements
|
ok github.com/joint-online-judge/JOJ3/cmd/joj3 2.290s coverage: 74.0% of statements
|
||||||
```
|
```
|
||||||
|
|
||||||
### For developers
|
### For developers
|
||||||
|
|
||||||
1. Install [`pre-commit`](https://pre-commit.com/), [`golangci-lint`](https://golangci-lint.run), [`goimports`](https://golang.org/x/tools/cmd/goimports), [`gofumpt`](https://github.com/mvdan/gofumpt).
|
1. Install [`pre-commit`](https://pre-commit.com/), [`golangci-lint`](https://golangci-lint.run).
|
||||||
|
|
||||||
2. Install the pre-commit hooks. It will run some checks before you commit.
|
2. Install the pre-commit hooks. It will run some checks before you commit.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ pre-commit install
|
$ pre-commit install
|
||||||
pre-commit installed at .git/hooks/pre-commit
|
pre-commit installed at .git/hooks/pre-commit
|
||||||
|
@ -100,7 +77,7 @@ Check `Cmd` at <https://github.com/criyle/go-judge#rest-api-interface>.
|
||||||
|
|
||||||
Some difference:
|
Some difference:
|
||||||
|
|
||||||
- `CopyInCwd bool`: set to `true` to add everything in the current working directory to `CopyIn`.
|
- `CopyInDir string`: set to non-empty string to add everything in that directory to `CopyIn`.
|
||||||
- `CopyInCached map[string]string`: key: file name in the sandbox, value: file name used in `CopyOutCached`.
|
- `CopyInCached map[string]string`: key: file name in the sandbox, value: file name used in `CopyOutCached`.
|
||||||
- `LocalFile`: now supports the relative path
|
- `LocalFile`: now supports the relative path
|
||||||
|
|
||||||
|
@ -112,3 +89,78 @@ Check the `Result` at <https://github.com/criyle/go-judge#rest-api-interface>.
|
||||||
|
|
||||||
- `Score int`: score of the stage.
|
- `Score int`: score of the stage.
|
||||||
- `Comment string`: comment on the stage.
|
- `Comment string`: comment on the stage.
|
||||||
|
|
||||||
|
## Binaries (under `/cmd` and `/pkg`)
|
||||||
|
|
||||||
|
### JOJ3
|
||||||
|
|
||||||
|
JOJ3 itself.
|
||||||
|
|
||||||
|
#### CLI arguments
|
||||||
|
|
||||||
|
1. `-meta-conf` to specify the meta configuration file path. JOJ3 will try to match the commit message by the `regex`, once matched, the configuration file in the `filename` will be load and run the whole process.
|
||||||
|
2. `-msg` to specify message to trigger the running. If empty, JOJ3 will use git commit message on HEAD.
|
||||||
|
|
||||||
|
### Sample
|
||||||
|
|
||||||
|
Just a sample on how to write an executable that can be called by the executor.
|
||||||
|
|
||||||
|
### HealthCheck
|
||||||
|
|
||||||
|
The repohealth check will return a json list to for check result. The structure follows the score-comment pattern.
|
||||||
|
|
||||||
|
HealthCheck currently includes, `reposize`, `forbidden file`, `Metafile existence`, `non-ascii character` in file and message, `release tag`, and `ci files invariance` check.
|
||||||
|
|
||||||
|
The workflow is `joj3` pass cli args to healthcheck binary. See `./cmd/healthcheck/main.go` to view all flags.
|
||||||
|
|
||||||
|
## Executors (under `/internal/executors`)
|
||||||
|
|
||||||
|
### Dummy
|
||||||
|
|
||||||
|
Do not execute any command. Just return empty `ExecutorResult` slice.
|
||||||
|
|
||||||
|
### Sandbox
|
||||||
|
|
||||||
|
Run the commands in `go-judge` and output the `ExecutorResult` slice.
|
||||||
|
|
||||||
|
## Parsers (under `/internal/parsers`)
|
||||||
|
|
||||||
|
### Clang Tidy
|
||||||
|
|
||||||
|
Parser for `clang-tidy`, check `/examples/clangtidy` on how to call `clang-tidy` with proper parameters.
|
||||||
|
|
||||||
|
### Cppcheck
|
||||||
|
|
||||||
|
Parser for `cppcheck`, check `/examples/cppcheck` on how to call `cppcheck` with proper parameters.
|
||||||
|
|
||||||
|
### Cpplint
|
||||||
|
|
||||||
|
Parser for `cpplint`, check `/examples/cpplint` on how to call `cpplint` with proper parameters.
|
||||||
|
|
||||||
|
### Diff
|
||||||
|
|
||||||
|
Compare the specified output of `ExecutorResult` with the content of the answer file. If they are the same, then score will be given. Just like a normal online judge system.
|
||||||
|
|
||||||
|
### Dummy
|
||||||
|
|
||||||
|
Does not parse the output of `ExecutorResult`. It just output what is set inside the configuration file as score and comment. Currently it is used to output metadata for `joint-teapot`.
|
||||||
|
|
||||||
|
In `joint-teapot`, it will take the content before `-` of the comment of the first stage with name `metadata` as the exercise name and record in the scoreboard. (e.g. If the comment is `p2-s2-0xdeadbeef`, then the exercise name is `p2`.)
|
||||||
|
|
||||||
|
The comment in `metadata` can also be used to skip teapot commands. With `skip-teapot` in the comment, teapot will not run. And with `skip-scoreboard`, `skip-failed-table`, and `skip-result-issue`, only the corresponding step will be skipped, while the others will be executed. (e.g. If the comment is `p2-s2-0xdeadbeef-skip-scoreboard-skip-result-issue`, then only failed table step in teapot will run.)
|
||||||
|
|
||||||
|
### Healthcheck
|
||||||
|
|
||||||
|
Parser for the `healthcheck` binary mentioned before.
|
||||||
|
|
||||||
|
### Keyword
|
||||||
|
|
||||||
|
Match the given keyword from the specified output of `ExecutorResult`. For each match, a deduction of score is given. Can be useful if we do not have a specific parser for a code quality tool. Check `/examples/keyword`.
|
||||||
|
|
||||||
|
### Result Status
|
||||||
|
|
||||||
|
Only check if all the status of the executor is `StatusAccepted`. Can be used to check whether the command run in the executor exit normally.
|
||||||
|
|
||||||
|
### Sample
|
||||||
|
|
||||||
|
Parser for the `sample` binary mentioned before. Only used as a sample.
|
||||||
|
|
89
cmd/healthcheck/main.go
Normal file
89
cmd/healthcheck/main.go
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/joint-online-judge/JOJ3/pkg/healthcheck"
|
||||||
|
)
|
||||||
|
|
||||||
|
// parseMultiValueFlag parses a multi-value command-line flag and appends its values to the provided slice.
|
||||||
|
// It registers a flag with the specified name and description, associating it with a multiStringValue receiver.
|
||||||
|
func parseMultiValueFlag(values *[]string, flagName, description string) {
|
||||||
|
flag.Var((*multiStringValue)(values), flagName, description)
|
||||||
|
}
|
||||||
|
|
||||||
|
type multiStringValue []string
|
||||||
|
|
||||||
|
// Set appends a new value to the multiStringValue slice.
|
||||||
|
// It satisfies the flag.Value interface, allowing multiStringValue to be used as a flag value.
|
||||||
|
func (m *multiStringValue) Set(value string) error {
|
||||||
|
*m = append(*m, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *multiStringValue) String() string {
|
||||||
|
return fmt.Sprintf("%v", *m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupSlog() {
|
||||||
|
opts := &slog.HandlerOptions{}
|
||||||
|
handler := slog.NewTextHandler(os.Stderr, opts)
|
||||||
|
logger := slog.New(handler)
|
||||||
|
slog.SetDefault(logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
var Version string
|
||||||
|
|
||||||
|
// Generally, err is used for runtime errors, and checkRes is used for the result of the checks.
|
||||||
|
func main() {
|
||||||
|
var gitWhitelist, metaFile []string
|
||||||
|
showVersion := flag.Bool("version", false, "print current version")
|
||||||
|
rootDir := flag.String("root", "", "")
|
||||||
|
repo := flag.String("repo", "", "")
|
||||||
|
localList := flag.String("localList", "", "")
|
||||||
|
droneBranch := flag.String("droneBranch", "", "")
|
||||||
|
releaseCategories := flag.String("releaseCategories", "", "")
|
||||||
|
releaseNumber := flag.Int("releaseNumber", 0, "")
|
||||||
|
checkFileNameList := flag.String("checkFileNameList", "", "Comma-separated list of files to check.")
|
||||||
|
checkFileSumList := flag.String("checkFileSumList", "", "Comma-separated list of expected checksums.")
|
||||||
|
parseMultiValueFlag(&gitWhitelist, "whitelist", "")
|
||||||
|
parseMultiValueFlag(&metaFile, "meta", "")
|
||||||
|
flag.Parse()
|
||||||
|
if *showVersion {
|
||||||
|
fmt.Println(Version)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setupSlog()
|
||||||
|
var err error
|
||||||
|
err = healthcheck.RepoSize()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("## Repo Size Check Failed:\n%s\n", err.Error())
|
||||||
|
}
|
||||||
|
err = healthcheck.ForbiddenCheck(*rootDir, gitWhitelist, *localList, *repo, *droneBranch)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("## Forbidden File Check Failed:\n%s\n", err.Error())
|
||||||
|
}
|
||||||
|
err = healthcheck.MetaCheck(*rootDir, metaFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("## Forbidden File Check Failed:\n%s\n", err.Error())
|
||||||
|
}
|
||||||
|
err = healthcheck.NonAsciiFiles(*rootDir, *localList)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("## Non-ASCII Characters File Check Failed:\n%s\n", err.Error())
|
||||||
|
}
|
||||||
|
err = healthcheck.NonAsciiMsg(*rootDir)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("## Non-ASCII Characters Commit Message Check Failed:\n%s\n", err.Error())
|
||||||
|
}
|
||||||
|
err = healthcheck.CheckTags(*rootDir, *releaseCategories, *releaseNumber)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("## Release Tag Check Failed:\n%s\n", err.Error())
|
||||||
|
}
|
||||||
|
err = healthcheck.VerifyFiles(*rootDir, *checkFileNameList, *checkFileSumList)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("## Repo File Check Failed:\n%s\n", err.Error())
|
||||||
|
}
|
||||||
|
}
|
136
cmd/joj3/conf.go
136
cmd/joj3/conf.go
|
@ -1,19 +1,25 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
"github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
"github.com/go-git/go-git/v5"
|
|
||||||
"github.com/koding/multiconfig"
|
"github.com/koding/multiconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Conf struct {
|
type Conf struct {
|
||||||
LogLevel int `default:"0"`
|
SandboxExecServer string `default:"localhost:5051"`
|
||||||
OutputPath string `default:"joj3_result.json"`
|
SandboxToken string `default:""`
|
||||||
Stages []struct {
|
LogPath string `default:""`
|
||||||
|
OutputPath string `default:"joj3_result.json"`
|
||||||
|
Stages []struct {
|
||||||
Name string
|
Name string
|
||||||
|
Group string
|
||||||
Executor struct {
|
Executor struct {
|
||||||
Name string
|
Name string
|
||||||
With struct {
|
With struct {
|
||||||
|
@ -46,7 +52,7 @@ type OptionalCmd struct {
|
||||||
|
|
||||||
CopyIn *map[string]stage.CmdFile
|
CopyIn *map[string]stage.CmdFile
|
||||||
CopyInCached *map[string]string
|
CopyInCached *map[string]string
|
||||||
CopyInCwd *bool
|
CopyInDir *string
|
||||||
|
|
||||||
CopyOut *[]string
|
CopyOut *[]string
|
||||||
CopyOutCached *[]string
|
CopyOutCached *[]string
|
||||||
|
@ -59,33 +65,107 @@ type OptionalCmd struct {
|
||||||
AddressSpaceLimit *bool
|
AddressSpaceLimit *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseConfFile(path string) Conf {
|
type ConventionalCommit struct {
|
||||||
m := multiconfig.NewWithPath(path)
|
Type string
|
||||||
conf := Conf{}
|
Scope string
|
||||||
err := m.Load(&conf)
|
Description string
|
||||||
if err != nil {
|
Body string
|
||||||
slog.Error("parse stages conf", "error", err)
|
Footer string
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return conf
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func commitMsgToConf() (conf Conf, err error) {
|
func parseConventionalCommit(commit string) (*ConventionalCommit, error) {
|
||||||
r, err := git.PlainOpen(".")
|
re := regexp.MustCompile(`^(\w+)(\(([^)]+)\))?!?: (.+)(\n\n(.+))?(\n\n(.+))?$`)
|
||||||
if err != nil {
|
matches := re.FindStringSubmatch(strings.TrimSpace(commit))
|
||||||
|
if matches == nil {
|
||||||
|
return nil, fmt.Errorf("invalid conventional commit format")
|
||||||
|
}
|
||||||
|
cc := &ConventionalCommit{
|
||||||
|
Type: matches[1],
|
||||||
|
Scope: matches[3],
|
||||||
|
Description: strings.TrimSpace(matches[4]),
|
||||||
|
Body: strings.TrimSpace(matches[6]),
|
||||||
|
Footer: strings.TrimSpace(matches[8]),
|
||||||
|
}
|
||||||
|
return cc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseConfFile(path string) (conf Conf, err error) {
|
||||||
|
d := &multiconfig.DefaultLoader{}
|
||||||
|
d.Loader = multiconfig.MultiLoader(
|
||||||
|
&multiconfig.TagLoader{},
|
||||||
|
&multiconfig.JSONLoader{Path: path},
|
||||||
|
)
|
||||||
|
d.Validator = multiconfig.MultiValidator(&multiconfig.RequiredValidator{})
|
||||||
|
if err = d.Load(&conf); err != nil {
|
||||||
|
slog.Error("parse stages conf", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ref, err := r.Head()
|
if err = d.Validate(&conf); err != nil {
|
||||||
if err != nil {
|
slog.Error("validate stages conf", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
commit, err := r.CommitObject(ref.Hash())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
msg := commit.Message
|
|
||||||
slog.Debug("commit msg to conf", "msg", msg)
|
|
||||||
// TODO: parse msg to conf name
|
|
||||||
conf = parseConfFile("conf.toml")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseMsg(confRoot, confName, msg string) (conf Conf, group string, err error) {
|
||||||
|
slog.Info("parse msg", "msg", msg)
|
||||||
|
conventionalCommit, err := parseConventionalCommit(msg)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slog.Info("conventional commit", "commit", conventionalCommit)
|
||||||
|
confRoot = filepath.Clean(confRoot)
|
||||||
|
confPath := filepath.Clean(fmt.Sprintf("%s/%s/%s",
|
||||||
|
confRoot, conventionalCommit.Scope, confName))
|
||||||
|
relPath, err := filepath.Rel(confRoot, confPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(relPath, "..") {
|
||||||
|
err = fmt.Errorf("invalid scope as path: %s", conventionalCommit.Scope)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slog.Info("try to load conf", "path", confPath)
|
||||||
|
conf, err = parseConfFile(confPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
groupKeywords := []string{"joj"}
|
||||||
|
for _, groupKeyword := range groupKeywords {
|
||||||
|
if strings.Contains(
|
||||||
|
strings.ToLower(conventionalCommit.Description), groupKeyword) {
|
||||||
|
group = groupKeyword
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slog.Debug("conf loaded", "conf", conf)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func listValidScopes(confRoot, confName, msg string) ([]string, error) {
|
||||||
|
conventionalCommit, err := parseConventionalCommit(msg)
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
|
slog.Info("conventional commit", "commit", conventionalCommit)
|
||||||
|
confRoot = filepath.Clean(confRoot)
|
||||||
|
validScopes := []string{}
|
||||||
|
err = filepath.Walk(confRoot, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("list valid scopes", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
confPath := filepath.Join(path, confName)
|
||||||
|
if _, err := os.Stat(confPath); err == nil {
|
||||||
|
relPath, err := filepath.Rel(confRoot, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
validScopes = append(validScopes, relPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return validScopes, err
|
||||||
|
}
|
||||||
|
|
86
cmd/joj3/conf_test.go
Normal file
86
cmd/joj3/conf_test.go
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseConventionalCommit(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
commit string
|
||||||
|
want *ConventionalCommit
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Simple feat commit",
|
||||||
|
commit: "feat: add new feature",
|
||||||
|
want: &ConventionalCommit{
|
||||||
|
Type: "feat",
|
||||||
|
Description: "add new feature",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Commit with scope",
|
||||||
|
commit: "fix(core): resolve memory leak",
|
||||||
|
want: &ConventionalCommit{
|
||||||
|
Type: "fix",
|
||||||
|
Scope: "core",
|
||||||
|
Description: "resolve memory leak",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Breaking change commit",
|
||||||
|
commit: "feat(api)!: redesign user authentication",
|
||||||
|
want: &ConventionalCommit{
|
||||||
|
Type: "feat",
|
||||||
|
Scope: "api",
|
||||||
|
Description: "redesign user authentication",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Commit with body",
|
||||||
|
commit: "docs: update README\n\nAdd installation instructions and improve examples",
|
||||||
|
want: &ConventionalCommit{
|
||||||
|
Type: "docs",
|
||||||
|
Description: "update README",
|
||||||
|
Body: "Add installation instructions and improve examples",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Full commit with body and footer",
|
||||||
|
commit: "feat(auth)!: implement OAuth2\n\nThis commit adds OAuth2 support to the authentication system.\n\nBREAKING CHANGE: Previous authentication tokens are no longer valid.",
|
||||||
|
want: &ConventionalCommit{
|
||||||
|
Type: "feat",
|
||||||
|
Scope: "auth",
|
||||||
|
Description: "implement OAuth2",
|
||||||
|
Body: "This commit adds OAuth2 support to the authentication system.",
|
||||||
|
Footer: "BREAKING CHANGE: Previous authentication tokens are no longer valid.",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid commit format",
|
||||||
|
commit: "This is not a valid conventional commit",
|
||||||
|
want: nil,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := parseConventionalCommit(tt.commit)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("ParseConventionalCommit() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("ParseConventionalCommit() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
77
cmd/joj3/log.go
Normal file
77
cmd/joj3/log.go
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type multiHandler struct {
|
||||||
|
handlers []slog.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *multiHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
||||||
|
for _, handler := range h.handlers {
|
||||||
|
if handler.Enabled(ctx, level) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *multiHandler) Handle(ctx context.Context, r slog.Record) error {
|
||||||
|
for _, handler := range h.handlers {
|
||||||
|
if handler.Enabled(ctx, r.Level) {
|
||||||
|
if err := handler.Handle(ctx, r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *multiHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||||
|
handlers := make([]slog.Handler, len(h.handlers))
|
||||||
|
for i, handler := range h.handlers {
|
||||||
|
handlers[i] = handler.WithAttrs(attrs)
|
||||||
|
}
|
||||||
|
return &multiHandler{handlers: handlers}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *multiHandler) WithGroup(name string) slog.Handler {
|
||||||
|
handlers := make([]slog.Handler, len(h.handlers))
|
||||||
|
for i, handler := range h.handlers {
|
||||||
|
handlers[i] = handler.WithGroup(name)
|
||||||
|
}
|
||||||
|
return &multiHandler{handlers: handlers}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupSlog(logPath string) error {
|
||||||
|
handlers := []slog.Handler{}
|
||||||
|
if logPath != "" {
|
||||||
|
// File handler for debug logs
|
||||||
|
debugFile, err := os.OpenFile(logPath,
|
||||||
|
os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
debugHandler := slog.NewTextHandler(debugFile, &slog.HandlerOptions{
|
||||||
|
Level: slog.LevelDebug,
|
||||||
|
})
|
||||||
|
handlers = append(handlers, debugHandler)
|
||||||
|
}
|
||||||
|
// Stderr handler for info logs and above
|
||||||
|
stderrHandler := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
||||||
|
Level: slog.LevelInfo,
|
||||||
|
})
|
||||||
|
handlers = append(handlers, stderrHandler)
|
||||||
|
// Create a multi-handler
|
||||||
|
multiHandler := &multiHandler{handlers: handlers}
|
||||||
|
// Set the default logger
|
||||||
|
logger := slog.New(multiHandler)
|
||||||
|
slog.SetDefault(logger)
|
||||||
|
if logPath != "" {
|
||||||
|
slog.Info("debug log", "path", logPath)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
127
cmd/joj3/main.go
127
cmd/joj3/main.go
|
@ -2,28 +2,42 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/executors"
|
"github.com/joint-online-judge/JOJ3/internal/executors"
|
||||||
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers"
|
_ "github.com/joint-online-judge/JOJ3/internal/parsers"
|
||||||
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
"github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupSlog(conf Conf) {
|
func getCommitMsg() (msg string, err error) {
|
||||||
lvl := new(slog.LevelVar)
|
r, err := git.PlainOpen(".")
|
||||||
lvl.Set(slog.Level(conf.LogLevel))
|
if err != nil {
|
||||||
opts := &slog.HandlerOptions{Level: lvl}
|
return
|
||||||
handler := slog.NewTextHandler(os.Stderr, opts)
|
}
|
||||||
logger := slog.New(handler)
|
ref, err := r.Head()
|
||||||
slog.SetDefault(logger)
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
commit, err := r.CommitObject(ref.Hash())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg = commit.Message
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateStages(conf Conf) []stage.Stage {
|
func generateStages(conf Conf, group string) ([]stage.Stage, error) {
|
||||||
stages := []stage.Stage{}
|
stages := []stage.Stage{}
|
||||||
for _, s := range conf.Stages {
|
for _, s := range conf.Stages {
|
||||||
|
if s.Group != "" && group != "" && group != s.Group {
|
||||||
|
continue
|
||||||
|
}
|
||||||
var cmds []stage.Cmd
|
var cmds []stage.Cmd
|
||||||
defaultCmd := s.Executor.With.Default
|
defaultCmd := s.Executor.With.Default
|
||||||
for _, optionalCmd := range s.Executor.With.Cases {
|
for _, optionalCmd := range s.Executor.With.Cases {
|
||||||
|
@ -31,7 +45,7 @@ func generateStages(conf Conf) []stage.Stage {
|
||||||
err := copier.Copy(&cmd, &optionalCmd)
|
err := copier.Copy(&cmd, &optionalCmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("generate stages", "error", err)
|
slog.Error("generate stages", "error", err)
|
||||||
os.Exit(1)
|
return stages, err
|
||||||
}
|
}
|
||||||
// since these 3 values are pointers, copier will always copy
|
// since these 3 values are pointers, copier will always copy
|
||||||
// them, so we need to check them manually
|
// them, so we need to check them manually
|
||||||
|
@ -49,7 +63,6 @@ func generateStages(conf Conf) []stage.Stage {
|
||||||
if len(s.Executor.With.Cases) == 0 {
|
if len(s.Executor.With.Cases) == 0 {
|
||||||
cmds = []stage.Cmd{defaultCmd}
|
cmds = []stage.Cmd{defaultCmd}
|
||||||
}
|
}
|
||||||
slog.Debug("parse stages conf", "cmds", cmds)
|
|
||||||
stages = append(stages, stage.Stage{
|
stages = append(stages, stage.Stage{
|
||||||
Name: s.Name,
|
Name: s.Name,
|
||||||
ExecutorName: s.Executor.Name,
|
ExecutorName: s.Executor.Name,
|
||||||
|
@ -58,29 +71,91 @@ func generateStages(conf Conf) []stage.Stage {
|
||||||
ParserConf: s.Parser.With,
|
ParserConf: s.Parser.With,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return stages
|
slog.Debug("stages generated", "stages", stages)
|
||||||
|
return stages, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func outputResult(conf Conf, results []stage.StageResult) error {
|
func outputResult(outputPath string, results []stage.StageResult) error {
|
||||||
|
slog.Info("output result start", "path", outputPath)
|
||||||
|
slog.Debug("output result start", "path", outputPath, "results", results)
|
||||||
content, err := json.Marshal(results)
|
content, err := json.Marshal(results)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return os.WriteFile(conf.OutputPath,
|
return os.WriteFile(outputPath,
|
||||||
append(content, []byte("\n")...), 0o600)
|
append(content, []byte("\n")...), 0o600)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
var (
|
||||||
conf, err := commitMsgToConf()
|
confRoot string
|
||||||
|
confName string
|
||||||
|
msg string
|
||||||
|
showVersion *bool
|
||||||
|
Version string = "debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.StringVar(&confRoot, "conf-root", ".", "root path for all config files")
|
||||||
|
flag.StringVar(&confName, "conf-name", "conf.json", "filename for config files")
|
||||||
|
flag.StringVar(&msg, "msg", "", "message to trigger the running, leave empty to use git commit message on HEAD")
|
||||||
|
showVersion = flag.Bool("version", false, "print current version")
|
||||||
|
}
|
||||||
|
|
||||||
|
func mainImpl() error {
|
||||||
|
if err := setupSlog(""); err != nil { // before conf is loaded
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
flag.Parse()
|
||||||
|
if *showVersion {
|
||||||
|
fmt.Println(Version)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
slog.Info("start joj3", "version", Version)
|
||||||
|
if msg == "" {
|
||||||
|
var err error
|
||||||
|
msg, err = getCommitMsg()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("get commit msg", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conf, group, err := parseMsg(confRoot, confName, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("no conf found", "error", err)
|
slog.Error("parse msg", "error", err)
|
||||||
|
validScopes, scopeErr := listValidScopes(
|
||||||
|
confRoot, confName, msg)
|
||||||
|
if scopeErr != nil {
|
||||||
|
slog.Error("list valid scopes", "error", scopeErr)
|
||||||
|
return scopeErr
|
||||||
|
}
|
||||||
|
slog.Info("hint: valid scopes in commit message", "scopes", validScopes)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := setupSlog(conf.LogPath); err != nil { // after conf is loaded
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
executors.InitWithConf(conf.SandboxExecServer, conf.SandboxToken)
|
||||||
|
stages, err := generateStages(conf, group)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("generate stages", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer stage.Cleanup()
|
||||||
|
results, err := stage.Run(stages)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("run stages", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := outputResult(conf.OutputPath, results); err != nil {
|
||||||
|
slog.Error("output result", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := mainImpl(); err != nil {
|
||||||
|
slog.Error("main exit", "error", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
setupSlog(conf)
|
|
||||||
stages := generateStages(conf)
|
|
||||||
defer stage.Cleanup()
|
|
||||||
results := stage.Run(stages)
|
|
||||||
if err := outputResult(conf, results); err != nil {
|
|
||||||
slog.Error("output result", "error", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
"github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
)
|
)
|
||||||
|
|
||||||
func compareStageResults(t *testing.T, actual, expected []stage.StageResult, regex bool) {
|
func compareStageResults(t *testing.T, actual, expected []stage.StageResult, regex bool) {
|
||||||
|
@ -63,7 +63,7 @@ func readStageResults(t *testing.T, path string) []stage.StageResult {
|
||||||
|
|
||||||
func TestMain(t *testing.T) {
|
func TestMain(t *testing.T) {
|
||||||
var tests []string
|
var tests []string
|
||||||
root := "../../tmp/submodules/JOJ3-examples"
|
root := "../../tmp/submodules/JOJ3-examples/examples/"
|
||||||
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -6,13 +6,20 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/pkg/dummy"
|
"github.com/joint-online-judge/JOJ3/pkg/sample"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var Version string
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
showVersion := flag.Bool("version", false, "print current version")
|
||||||
score := flag.Int("score", 0, "score")
|
score := flag.Int("score", 0, "score")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
res, err := dummy.Run(dummy.Conf{Score: *score})
|
if *showVersion {
|
||||||
|
fmt.Println(Version)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res, err := sample.Run(sample.Conf{Score: *score})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprint(os.Stderr, err)
|
fmt.Fprint(os.Stderr, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
|
@ -1 +1 @@
|
||||||
Subproject commit 34f1bcd9cf3e2905ecfd95284b25eae2568d38df
|
Subproject commit 30b179812335b427a431c303ed3453779f898796
|
|
@ -1 +1 @@
|
||||||
Subproject commit 9f0d88eff4e9d7d3ac58f460609272f7d2e527e7
|
Subproject commit b16b4a9679bb97d8703a3c70d3b75dd06e9fd4df
|
|
@ -1 +1 @@
|
||||||
Subproject commit a90071148cb7e39cf3e93b18a1e71632d7e974a3
|
Subproject commit ca21e3ef7980339e433c6531d8e2aae9678eff2d
|
|
@ -1 +1 @@
|
||||||
Subproject commit d33b07f18bf36c77dcf9a012d265dc7bd45fac9f
|
Subproject commit 866e73258c2d8f87b0000754a16f9b9c7de0c0fa
|
|
@ -1 +1 @@
|
||||||
Subproject commit 6d47ed4f6f7b8632784513edd8a206689503fc56
|
Subproject commit af355d91abf1ed62f19e0703dad55d18eb1252f8
|
1
examples/diff/basic
Submodule
1
examples/diff/basic
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit cc5d991cb401b0c8cfd8e03a76fb9994158d2819
|
1
examples/diff/complex
Submodule
1
examples/diff/complex
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit f29fbdac51f851e782f0db13f8640c6ba0aec49d
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 9cf0d994aa613dc38f2bc99f42d0dd7509c3d42c
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit c2fe51b4cd485339fd18924196b01eed6848ba1d
|
|
1
examples/healthcheck/asciifile
Submodule
1
examples/healthcheck/asciifile
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 146b2f829a91594c89f1d1379d312b91c5f97a55
|
1
examples/healthcheck/asciimsg
Submodule
1
examples/healthcheck/asciimsg
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit ffd9ab13c7e403d0afc7f1dfc86ec131f9992498
|
1
examples/healthcheck/forbiddenfile
Submodule
1
examples/healthcheck/forbiddenfile
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit b93a0254c2842e4d9c3a4b8f832cd6b17bf284f9
|
1
examples/healthcheck/meta
Submodule
1
examples/healthcheck/meta
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit b4693716ee97bd5d9814c512b036aabeb12c7fd6
|
1
examples/healthcheck/release
Submodule
1
examples/healthcheck/release
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 50ec9ccbb6d7fe9b5352a018b6ac6445ac5c354f
|
1
examples/healthcheck/reposize
Submodule
1
examples/healthcheck/reposize
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 9c010a0f8d501223f94d677a4aed70f0162092e2
|
1
examples/healthcheck/repoverify
Submodule
1
examples/healthcheck/repoverify
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 99d95c2841517f36a2063c641ea2e1b9b6190c76
|
|
@ -1 +1 @@
|
||||||
Subproject commit 9de8a32bc3a55f0652d487421384d0a163bf0aaa
|
Subproject commit 1f962c75faccc9e703a8cd46e0a939551306bd4c
|
|
@ -1 +1 @@
|
||||||
Subproject commit 9be8c3c329342059f49683a4e810938e8321adbe
|
Subproject commit 507ae70e81c08a6becdc8219a2c62b5aa8c3db69
|
1
examples/sample/error
Submodule
1
examples/sample/error
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 31b319157fb221636fcdf8d6a752a4a5e46d6a6d
|
1
examples/sample/success
Submodule
1
examples/sample/success
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 7a98095a322419939bffa8636207090493108a3c
|
39
go.mod
39
go.mod
|
@ -1,24 +1,24 @@
|
||||||
module focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3
|
module github.com/joint-online-judge/JOJ3
|
||||||
|
|
||||||
go 1.22.1
|
go 1.23.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/criyle/go-judge v1.8.2
|
github.com/criyle/go-judge v1.8.5
|
||||||
github.com/go-git/go-git/v5 v5.11.0
|
github.com/go-git/go-git/v5 v5.12.0
|
||||||
github.com/jinzhu/copier v0.4.0
|
github.com/jinzhu/copier v0.4.0
|
||||||
github.com/koding/multiconfig v0.0.0-20171124222453-69c27309b2d7
|
github.com/koding/multiconfig v0.0.0-20171124222453-69c27309b2d7
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
google.golang.org/grpc v1.62.0
|
google.golang.org/grpc v1.67.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
dario.cat/mergo v1.0.0 // indirect
|
dario.cat/mergo v1.0.0 // indirect
|
||||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
|
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
||||||
github.com/cloudflare/circl v1.3.3 // indirect
|
github.com/cloudflare/circl v1.4.0 // indirect
|
||||||
github.com/creack/pty v1.1.21 // indirect
|
github.com/creack/pty v1.1.21 // indirect
|
||||||
github.com/criyle/go-sandbox v0.10.1 // indirect
|
github.com/criyle/go-sandbox v0.10.4 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
github.com/fatih/camelcase v1.0.0 // indirect
|
github.com/fatih/camelcase v1.0.0 // indirect
|
||||||
|
@ -26,22 +26,21 @@ require (
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
github.com/go-git/go-billy/v5 v5.5.0 // indirect
|
github.com/go-git/go-billy/v5 v5.5.0 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||||
github.com/sergi/go-diff v1.1.0 // indirect
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||||
github.com/skeema/knownhosts v1.2.1 // indirect
|
github.com/skeema/knownhosts v1.2.2 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
golang.org/x/crypto v0.20.0 // indirect
|
golang.org/x/crypto v0.27.0 // indirect
|
||||||
golang.org/x/mod v0.12.0 // indirect
|
golang.org/x/mod v0.17.0 // indirect
|
||||||
golang.org/x/net v0.21.0 // indirect
|
golang.org/x/net v0.29.0 // indirect
|
||||||
golang.org/x/sync v0.6.0 // indirect
|
golang.org/x/sync v0.8.0 // indirect
|
||||||
golang.org/x/sys v0.17.0 // indirect
|
golang.org/x/sys v0.25.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.18.0 // indirect
|
||||||
golang.org/x/tools v0.13.0 // indirect
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
|
||||||
google.golang.org/protobuf v1.32.0 // indirect
|
google.golang.org/protobuf v1.34.2 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
87
go.sum
87
go.sum
|
@ -5,21 +5,22 @@ github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
|
||||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg=
|
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||||
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
|
|
||||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||||
|
github.com/cloudflare/circl v1.4.0 h1:BV7h5MgrktNzytKmWjpOtdYrf0lkkbF8YMlBGPhJQrY=
|
||||||
|
github.com/cloudflare/circl v1.4.0/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
|
||||||
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
|
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
|
||||||
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
github.com/criyle/go-judge v1.8.2 h1:dGjLqJRBifqsLVZj1scr23zdM4wPe98HTIVgYzPuRxA=
|
github.com/criyle/go-judge v1.8.5 h1:4G1kMvIdYQ5S9EajcrYNyCYovE80Rgkt8Pg0dCxb2EM=
|
||||||
github.com/criyle/go-judge v1.8.2/go.mod h1:3RgsMp21D+UvXzkpOGsVFbLe2T2Lwk8jPEmCntQrvHQ=
|
github.com/criyle/go-judge v1.8.5/go.mod h1:+0lDizAqZugQ4NQvpYmR5ujpVaRUqEl/9ZiZ84nrOIU=
|
||||||
github.com/criyle/go-sandbox v0.10.1 h1:z9Il/UXQwKEvIwdr1wVheWWWAqGWtdTItBmEsWqFqT4=
|
github.com/criyle/go-sandbox v0.10.4 h1:EHJrJj5V/VSrjm1Y0ZJAea5zPASoOtn1CPZRzhTUYIM=
|
||||||
github.com/criyle/go-sandbox v0.10.1/go.mod h1:ivPw/HEh5unxVRlXJxCgkgTCuy+cxTkQDX7D2XQf/kg=
|
github.com/criyle/go-sandbox v0.10.4/go.mod h1:sYJUuTmJ72Jilkc1/PO7eDdpJq3rOZ55o8MxzP80vw0=
|
||||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
@ -33,22 +34,18 @@ github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8
|
||||||
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
||||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||||
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
|
||||||
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
|
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||||
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
|
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
|
||||||
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
|
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||||
github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4=
|
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
|
||||||
github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY=
|
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
|
@ -78,16 +75,16 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ=
|
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
|
||||||
github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
|
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
@ -96,12 +93,12 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
|
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||||
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
|
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
@ -109,13 +106,13 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
@ -129,15 +126,15 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
|
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
@ -145,24 +142,21 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641 h1:DKU1r6Tj5s1vlU/moGhuGz7E3xRfwjdAfDzbsaQJtEY=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs=
|
google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw=
|
||||||
google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
|
google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
||||||
google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
|
||||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
|
||||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
@ -170,7 +164,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
|
||||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
package executors
|
package executors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/executors/dummy"
|
_ "github.com/joint-online-judge/JOJ3/internal/executors/dummy"
|
||||||
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/executors/sandbox"
|
"github.com/joint-online-judge/JOJ3/internal/executors/sandbox"
|
||||||
)
|
)
|
||||||
|
|
||||||
// this file does nothing but imports to ensure all the init() functions
|
// this file does nothing but imports to ensure all the init() functions
|
||||||
// in the subpackages are called
|
// in the subpackages are called
|
||||||
|
|
||||||
|
// overwrite the default registered executors
|
||||||
|
func InitWithConf(sandboxExecServer, sandboxToken string) {
|
||||||
|
sandbox.InitWithConf(sandboxExecServer, sandboxToken)
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package dummy
|
package dummy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
|
||||||
"github.com/criyle/go-judge/envexec"
|
"github.com/criyle/go-judge/envexec"
|
||||||
|
"github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Dummy struct{}
|
type Dummy struct{}
|
||||||
|
@ -11,9 +11,9 @@ func (e *Dummy) Run(cmds []stage.Cmd) ([]stage.ExecutorResult, error) {
|
||||||
var res []stage.ExecutorResult
|
var res []stage.ExecutorResult
|
||||||
for range cmds {
|
for range cmds {
|
||||||
res = append(res, stage.ExecutorResult{
|
res = append(res, stage.ExecutorResult{
|
||||||
Status: stage.Status(envexec.StatusInvalid),
|
Status: stage.Status(envexec.StatusAccepted),
|
||||||
ExitStatus: 0,
|
ExitStatus: 0,
|
||||||
Error: "I'm a dummy",
|
Error: "",
|
||||||
Time: 0,
|
Time: 0,
|
||||||
Memory: 0,
|
Memory: 0,
|
||||||
RunTime: 0,
|
RunTime: 0,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package dummy
|
package dummy
|
||||||
|
|
||||||
import "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
import "github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
|
|
||||||
var name = "dummy"
|
var name = "dummy"
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
package sandbox
|
package sandbox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
|
||||||
"github.com/criyle/go-judge/pb"
|
"github.com/criyle/go-judge/pb"
|
||||||
|
"github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
)
|
)
|
||||||
|
|
||||||
// copied from https://github.com/criyle/go-judge/blob/master/cmd/go-judge-shell/grpc.go
|
// copied from https://github.com/criyle/go-judge/blob/master/cmd/go-judge-shell/grpc.go
|
||||||
|
@ -27,7 +28,7 @@ func convertPBCmd(cmd []stage.Cmd) []*pb.Request_CmdType {
|
||||||
CpuSetLimit: c.CPUSetLimit,
|
CpuSetLimit: c.CPUSetLimit,
|
||||||
DataSegmentLimit: c.DataSegmentLimit,
|
DataSegmentLimit: c.DataSegmentLimit,
|
||||||
AddressSpaceLimit: c.AddressSpaceLimit,
|
AddressSpaceLimit: c.AddressSpaceLimit,
|
||||||
CopyIn: convertPBCopyIn(c.CopyIn, c.CopyInCwd),
|
CopyIn: convertPBCopyIn(c.CopyIn, c.CopyInDir),
|
||||||
CopyOut: convertPBCopyOut(c.CopyOut),
|
CopyOut: convertPBCopyOut(c.CopyOut),
|
||||||
CopyOutCached: convertPBCopyOut(c.CopyOutCached),
|
CopyOutCached: convertPBCopyOut(c.CopyOutCached),
|
||||||
CopyOutMax: c.CopyOutMax,
|
CopyOutMax: c.CopyOutMax,
|
||||||
|
@ -38,9 +39,11 @@ func convertPBCmd(cmd []stage.Cmd) []*pb.Request_CmdType {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertPBCopyIn(copyIn map[string]stage.CmdFile, copyInCwd bool) map[string]*pb.Request_File {
|
func convertPBCopyIn(
|
||||||
if copyInCwd {
|
copyIn map[string]stage.CmdFile, copyInDir string,
|
||||||
_ = filepath.Walk(".",
|
) map[string]*pb.Request_File {
|
||||||
|
if copyInDir != "" {
|
||||||
|
_ = filepath.Walk(copyInDir,
|
||||||
func(path string, info os.FileInfo, err error) error {
|
func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -113,7 +116,12 @@ func convertPBFile(i stage.CmdFile) *pb.Request_File {
|
||||||
i.Src = &absPath
|
i.Src = &absPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &pb.Request_File{File: &pb.Request_File_Local{Local: &pb.Request_LocalFile{Src: *i.Src}}}
|
s, err := os.ReadFile(*i.Src)
|
||||||
|
if err != nil {
|
||||||
|
s = []byte{}
|
||||||
|
slog.Error("read file error", "path", *i.Src, "error", err)
|
||||||
|
}
|
||||||
|
return &pb.Request_File{File: &pb.Request_File_Memory{Memory: &pb.Request_MemoryFile{Content: s}}}
|
||||||
case i.Content != nil:
|
case i.Content != nil:
|
||||||
s := strToBytes(*i.Content)
|
s := strToBytes(*i.Content)
|
||||||
return &pb.Request_File{File: &pb.Request_File_Memory{Memory: &pb.Request_MemoryFile{Content: s}}}
|
return &pb.Request_File{File: &pb.Request_File_Memory{Memory: &pb.Request_MemoryFile{Content: s}}}
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
|
||||||
"github.com/criyle/go-judge/pb"
|
"github.com/criyle/go-judge/pb"
|
||||||
|
"github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Sandbox struct {
|
type Sandbox struct {
|
||||||
|
@ -18,6 +18,7 @@ type Sandbox struct {
|
||||||
func (e *Sandbox) Run(cmds []stage.Cmd) ([]stage.ExecutorResult, error) {
|
func (e *Sandbox) Run(cmds []stage.Cmd) ([]stage.ExecutorResult, error) {
|
||||||
var err error
|
var err error
|
||||||
if e.execClient == nil {
|
if e.execClient == nil {
|
||||||
|
slog.Debug("create exec client", "server", e.execServer)
|
||||||
e.execClient, err = createExecClient(e.execServer, e.token)
|
e.execClient, err = createExecClient(e.execServer, e.token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -27,7 +27,7 @@ func createGRPCConnection(addr, token string) (*grpc.ClientConn, error) {
|
||||||
if token != "" {
|
if token != "" {
|
||||||
opts = append(opts, grpc.WithPerRPCCredentials(newTokenAuth(token)))
|
opts = append(opts, grpc.WithPerRPCCredentials(newTokenAuth(token)))
|
||||||
}
|
}
|
||||||
return grpc.Dial(addr, opts...)
|
return grpc.NewClient(addr, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
type tokenAuth struct {
|
type tokenAuth struct {
|
||||||
|
|
|
@ -1,16 +1,24 @@
|
||||||
package sandbox
|
package sandbox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
"github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
)
|
)
|
||||||
|
|
||||||
var name = "sandbox"
|
var name = "sandbox"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
stage.RegisterExecutor(name, &Sandbox{
|
stage.RegisterExecutor(name, &Sandbox{
|
||||||
// TODO: read from conf
|
|
||||||
execServer: "localhost:5051",
|
execServer: "localhost:5051",
|
||||||
token: "",
|
token: "",
|
||||||
cachedMap: make(map[string]string),
|
cachedMap: make(map[string]string),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// overwrite the default registered executor
|
||||||
|
func InitWithConf(execServer, token string) {
|
||||||
|
stage.RegisterExecutor(name, &Sandbox{
|
||||||
|
execServer: execServer,
|
||||||
|
token: token,
|
||||||
|
cachedMap: make(map[string]string),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
package parsers
|
package parsers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/clangtidy"
|
_ "github.com/joint-online-judge/JOJ3/internal/parsers/clangtidy"
|
||||||
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/cppcheck"
|
_ "github.com/joint-online-judge/JOJ3/internal/parsers/cppcheck"
|
||||||
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/cpplint"
|
_ "github.com/joint-online-judge/JOJ3/internal/parsers/cpplint"
|
||||||
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/diff"
|
_ "github.com/joint-online-judge/JOJ3/internal/parsers/diff"
|
||||||
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/dummy"
|
_ "github.com/joint-online-judge/JOJ3/internal/parsers/dummy"
|
||||||
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/keyword"
|
_ "github.com/joint-online-judge/JOJ3/internal/parsers/healthcheck"
|
||||||
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/resultstatus"
|
_ "github.com/joint-online-judge/JOJ3/internal/parsers/keyword"
|
||||||
|
_ "github.com/joint-online-judge/JOJ3/internal/parsers/resultstatus"
|
||||||
|
_ "github.com/joint-online-judge/JOJ3/internal/parsers/sample"
|
||||||
)
|
)
|
||||||
|
|
||||||
// this file does nothing but imports to ensure all the init() functions
|
// this file does nothing but imports to ensure all the init() functions
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package clangtidy
|
package clangtidy
|
||||||
|
|
||||||
import "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
import "github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
|
|
||||||
var name = "clangtidy"
|
var name = "clangtidy"
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
|
||||||
"github.com/criyle/go-judge/envexec"
|
"github.com/criyle/go-judge/envexec"
|
||||||
|
"github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Match struct {
|
type Match struct {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package cppcheck
|
package cppcheck
|
||||||
|
|
||||||
import "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
import "github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
|
|
||||||
var name = "cppcheck"
|
var name = "cppcheck"
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
|
||||||
"github.com/criyle/go-judge/envexec"
|
"github.com/criyle/go-judge/envexec"
|
||||||
|
"github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CppCheck struct{}
|
type CppCheck struct{}
|
||||||
|
|
|
@ -29,7 +29,7 @@ func severityFromString(severityString string) (Severity, error) {
|
||||||
case "information":
|
case "information":
|
||||||
return INFORMATION, nil
|
return INFORMATION, nil
|
||||||
default:
|
default:
|
||||||
return UNKNOWN, fmt.Errorf("unkown severity type \"%s\" for cppcheck", severityString)
|
return UNKNOWN, fmt.Errorf("unknown severity type \"%s\" for cppcheck", severityString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package cpplint
|
package cpplint
|
||||||
|
|
||||||
import "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
import "github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
|
|
||||||
var name = "cpplint"
|
var name = "cpplint"
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
"github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Conf struct {
|
type Conf struct {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package diff
|
package diff
|
||||||
|
|
||||||
import "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
import "github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
|
|
||||||
var name = "diff"
|
var name = "diff"
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,33 @@ package diff
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
"github.com/criyle/go-judge/envexec"
|
||||||
|
"github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
|
)
|
||||||
|
|
||||||
|
// operation represents the type of edit operation.
|
||||||
|
type operation uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
INSERT operation = iota + 1
|
||||||
|
DELETE
|
||||||
|
MOVE
|
||||||
)
|
)
|
||||||
|
|
||||||
type Conf struct {
|
type Conf struct {
|
||||||
Cases []struct {
|
Cases []struct {
|
||||||
Score int
|
IgnoreResultStatus bool
|
||||||
StdoutPath string
|
Outputs []struct {
|
||||||
|
Score int
|
||||||
|
FileName string
|
||||||
|
AnswerPath string
|
||||||
|
IgnoreWhitespace bool
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,25 +44,206 @@ func (*Diff) Run(results []stage.ExecutorResult, confAny any) (
|
||||||
if len(conf.Cases) != len(results) {
|
if len(conf.Cases) != len(results) {
|
||||||
return nil, true, fmt.Errorf("cases number not match")
|
return nil, true, fmt.Errorf("cases number not match")
|
||||||
}
|
}
|
||||||
|
|
||||||
var res []stage.ParserResult
|
var res []stage.ParserResult
|
||||||
|
forceQuit := false
|
||||||
for i, caseConf := range conf.Cases {
|
for i, caseConf := range conf.Cases {
|
||||||
result := results[i]
|
result := results[i]
|
||||||
score := 0
|
score := 0
|
||||||
stdout, err := os.ReadFile(caseConf.StdoutPath)
|
comment := ""
|
||||||
if err != nil {
|
if !caseConf.IgnoreResultStatus &&
|
||||||
return nil, true, err
|
result.Status != stage.Status(envexec.StatusAccepted) {
|
||||||
}
|
forceQuit = true
|
||||||
// TODO: more compare strategies
|
comment += fmt.Sprintf(
|
||||||
if string(stdout) == result.Files["stdout"] {
|
"Unexpected executor status: %s.", result.Status,
|
||||||
score = caseConf.Score
|
)
|
||||||
|
} else {
|
||||||
|
for _, output := range caseConf.Outputs {
|
||||||
|
answer, err := os.ReadFile(output.AnswerPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, true, err
|
||||||
|
}
|
||||||
|
slog.Debug("compare", "filename", output.FileName,
|
||||||
|
"answer path", output.AnswerPath,
|
||||||
|
"actual", result.Files[output.FileName],
|
||||||
|
"answer", string(answer))
|
||||||
|
// If no difference, assign score
|
||||||
|
if compareChars(string(answer), result.Files[output.FileName], output.IgnoreWhitespace) {
|
||||||
|
score += output.Score
|
||||||
|
} else {
|
||||||
|
// Convert answer to string and split by lines
|
||||||
|
stdoutLines := strings.Split(string(answer), "\n")
|
||||||
|
resultLines := strings.Split(result.Files[output.FileName], "\n")
|
||||||
|
|
||||||
|
// Generate Myers diff
|
||||||
|
diffOps := myersDiff(stdoutLines, resultLines)
|
||||||
|
|
||||||
|
// Generate diff block with surrounding context
|
||||||
|
diffOutput := generateDiffWithContext(stdoutLines, resultLines, diffOps)
|
||||||
|
comment += fmt.Sprintf(
|
||||||
|
"difference found in %s:\n```diff\n%s```\n",
|
||||||
|
output.FileName, diffOutput,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
res = append(res, stage.ParserResult{
|
res = append(res, stage.ParserResult{
|
||||||
Score: score,
|
Score: score,
|
||||||
Comment: fmt.Sprintf(
|
Comment: comment,
|
||||||
"executor status: run time: %d ns, memory: %d bytes",
|
|
||||||
result.RunTime, result.Memory,
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return res, false, nil
|
|
||||||
|
return res, forceQuit, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// compareChars compares two strings character by character, optionally ignoring whitespace.
|
||||||
|
func compareChars(stdout, result string, ignoreWhitespace bool) bool {
|
||||||
|
if ignoreWhitespace {
|
||||||
|
stdout = removeWhitespace(stdout)
|
||||||
|
result = removeWhitespace(result)
|
||||||
|
}
|
||||||
|
return stdout == result
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeWhitespace removes all whitespace characters from the string.
|
||||||
|
func removeWhitespace(s string) string {
|
||||||
|
var b strings.Builder
|
||||||
|
for _, r := range s {
|
||||||
|
if !unicode.IsSpace(r) {
|
||||||
|
b.WriteRune(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// myersDiff computes the Myers' diff between two slices of strings.
|
||||||
|
// src: https://github.com/cj1128/myers-diff/blob/master/main.go
|
||||||
|
func myersDiff(src, dst []string) []operation {
|
||||||
|
n := len(src)
|
||||||
|
m := len(dst)
|
||||||
|
max := n + m
|
||||||
|
var trace []map[int]int
|
||||||
|
var x, y int
|
||||||
|
|
||||||
|
loop:
|
||||||
|
for d := 0; d <= max; d++ {
|
||||||
|
v := make(map[int]int, d+2)
|
||||||
|
trace = append(trace, v)
|
||||||
|
|
||||||
|
if d == 0 {
|
||||||
|
t := 0
|
||||||
|
for len(src) > t && len(dst) > t && src[t] == dst[t] {
|
||||||
|
t++
|
||||||
|
}
|
||||||
|
v[0] = t
|
||||||
|
if t == len(src) && len(src) == len(dst) {
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
lastV := trace[d-1]
|
||||||
|
|
||||||
|
for k := -d; k <= d; k += 2 {
|
||||||
|
if k == -d || (k != d && lastV[k-1] < lastV[k+1]) {
|
||||||
|
x = lastV[k+1]
|
||||||
|
} else {
|
||||||
|
x = lastV[k-1] + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
y = x - k
|
||||||
|
|
||||||
|
for x < n && y < m && src[x] == dst[y] {
|
||||||
|
x, y = x+1, y+1
|
||||||
|
}
|
||||||
|
|
||||||
|
v[k] = x
|
||||||
|
|
||||||
|
if x == n && y == m {
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var script []operation
|
||||||
|
x = n
|
||||||
|
y = m
|
||||||
|
var k, prevK, prevX, prevY int
|
||||||
|
|
||||||
|
for d := len(trace) - 1; d > 0; d-- {
|
||||||
|
k = x - y
|
||||||
|
lastV := trace[d-1]
|
||||||
|
|
||||||
|
if k == -d || (k != d && lastV[k-1] < lastV[k+1]) {
|
||||||
|
prevK = k + 1
|
||||||
|
} else {
|
||||||
|
prevK = k - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
prevX = lastV[prevK]
|
||||||
|
prevY = prevX - prevK
|
||||||
|
|
||||||
|
for x > prevX && y > prevY {
|
||||||
|
script = append(script, MOVE)
|
||||||
|
x -= 1
|
||||||
|
y -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if x == prevX {
|
||||||
|
script = append(script, INSERT)
|
||||||
|
} else {
|
||||||
|
script = append(script, DELETE)
|
||||||
|
}
|
||||||
|
|
||||||
|
x, y = prevX, prevY
|
||||||
|
}
|
||||||
|
|
||||||
|
if trace[0][0] != 0 {
|
||||||
|
for i := 0; i < trace[0][0]; i++ {
|
||||||
|
script = append(script, MOVE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return reverse(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
// reverse reverses a slice of operations.
|
||||||
|
func reverse(s []operation) []operation {
|
||||||
|
result := make([]operation, len(s))
|
||||||
|
for i, v := range s {
|
||||||
|
result[len(s)-1-i] = v
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateDiffWithContext creates a diff block with surrounding context from stdout and result.
|
||||||
|
func generateDiffWithContext(stdoutLines, resultLines []string, ops []operation) string {
|
||||||
|
var diffBuilder strings.Builder
|
||||||
|
|
||||||
|
srcIndex, dstIndex := 0, 0
|
||||||
|
|
||||||
|
for _, op := range ops {
|
||||||
|
switch op {
|
||||||
|
case INSERT:
|
||||||
|
if dstIndex < len(resultLines) {
|
||||||
|
diffBuilder.WriteString(fmt.Sprintf("+ %s\n", resultLines[dstIndex]))
|
||||||
|
dstIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
case MOVE:
|
||||||
|
if srcIndex < len(stdoutLines) {
|
||||||
|
diffBuilder.WriteString(fmt.Sprintf(" %s\n", stdoutLines[srcIndex]))
|
||||||
|
srcIndex++
|
||||||
|
dstIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
case DELETE:
|
||||||
|
if srcIndex < len(stdoutLines) {
|
||||||
|
diffBuilder.WriteString(fmt.Sprintf("- %s\n", stdoutLines[srcIndex]))
|
||||||
|
srcIndex++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return diffBuilder.String()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package dummy
|
package dummy
|
||||||
|
|
||||||
import "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
import "github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
|
|
||||||
var name = "dummy"
|
var name = "dummy"
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
package dummy
|
package dummy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
|
||||||
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/pkg/dummy"
|
|
||||||
"github.com/criyle/go-judge/envexec"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Conf struct {
|
type Conf struct {
|
||||||
|
@ -16,32 +11,6 @@ type Conf struct {
|
||||||
|
|
||||||
type Dummy struct{}
|
type Dummy struct{}
|
||||||
|
|
||||||
func Parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult {
|
|
||||||
stdout := executorResult.Files["stdout"]
|
|
||||||
stderr := executorResult.Files["stderr"]
|
|
||||||
if executorResult.Status != stage.Status(envexec.StatusAccepted) {
|
|
||||||
return stage.ParserResult{
|
|
||||||
Score: 0,
|
|
||||||
Comment: fmt.Sprintf(
|
|
||||||
"Unexpected executor status: %s.\nStderr: %s",
|
|
||||||
executorResult.Status, stderr,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var dummyResult dummy.Result
|
|
||||||
err := json.Unmarshal([]byte(stdout), &dummyResult)
|
|
||||||
if err != nil {
|
|
||||||
return stage.ParserResult{
|
|
||||||
Score: 0,
|
|
||||||
Comment: fmt.Sprintf("Failed to parse result: %s", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return stage.ParserResult{
|
|
||||||
Score: dummyResult.Score + conf.Score,
|
|
||||||
Comment: dummyResult.Comment + conf.Comment,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Dummy) Run(results []stage.ExecutorResult, confAny any) (
|
func (*Dummy) Run(results []stage.ExecutorResult, confAny any) (
|
||||||
[]stage.ParserResult, bool, error,
|
[]stage.ParserResult, bool, error,
|
||||||
) {
|
) {
|
||||||
|
@ -50,8 +19,8 @@ func (*Dummy) Run(results []stage.ExecutorResult, confAny any) (
|
||||||
return nil, true, err
|
return nil, true, err
|
||||||
}
|
}
|
||||||
var res []stage.ParserResult
|
var res []stage.ParserResult
|
||||||
for _, result := range results {
|
for range results {
|
||||||
res = append(res, Parse(result, *conf))
|
res = append(res, stage.ParserResult{Score: conf.Score, Comment: conf.Comment})
|
||||||
}
|
}
|
||||||
return res, false, nil
|
return res, false, nil
|
||||||
}
|
}
|
||||||
|
|
9
internal/parsers/elf/meta.go
Normal file
9
internal/parsers/elf/meta.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package elf
|
||||||
|
|
||||||
|
import "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
||||||
|
|
||||||
|
var name = "elf"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
stage.RegisterParser(name, &Elf{})
|
||||||
|
}
|
41
internal/parsers/elf/model.go
Normal file
41
internal/parsers/elf/model.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package elf
|
||||||
|
|
||||||
|
type Toplevel struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Modules []Module `json:"modules"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Module struct {
|
||||||
|
Entries []Entry `json:"entries"`
|
||||||
|
DebugInfo string `json:"debug_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Entry []any
|
||||||
|
|
||||||
|
type Report struct {
|
||||||
|
File string `json:"file"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Cases []Case `json:"cases" mapstructure:"cases"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Case struct {
|
||||||
|
Binders []Binder `mapstructure:"binders"`
|
||||||
|
Context string `mapstructure:"context"`
|
||||||
|
Depths int `mapstructure:"depths"`
|
||||||
|
Code string `mapstructure:"code"`
|
||||||
|
Plain int `mapstructure:"plain"`
|
||||||
|
Weighed float64 `mapstructure:"weighed"`
|
||||||
|
Detail string `mapstructure:"detail"`
|
||||||
|
SimilarityRate float64 `mapstructure:"similarity_rate"`
|
||||||
|
Sources []Source `mapstructure:"srcs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Binder struct {
|
||||||
|
Binder string `json:"binder"`
|
||||||
|
Pos string `json:"pos"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Source struct {
|
||||||
|
Context string `json:"context"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
}
|
96
internal/parsers/elf/parser.go
Normal file
96
internal/parsers/elf/parser.go
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
package elf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
||||||
|
"github.com/criyle/go-judge/envexec"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Conf struct {
|
||||||
|
Score int
|
||||||
|
Comment string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Elf struct{}
|
||||||
|
|
||||||
|
func Parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult {
|
||||||
|
stdout := executorResult.Files["stdout"]
|
||||||
|
stderr := executorResult.Files["stderr"]
|
||||||
|
if executorResult.Status != stage.Status(envexec.StatusAccepted) {
|
||||||
|
return stage.ParserResult{
|
||||||
|
Score: 0,
|
||||||
|
Comment: fmt.Sprintf(
|
||||||
|
"Unexpected executor status: %s.\nStderr: %s",
|
||||||
|
executorResult.Status, stderr,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var topLevel Toplevel
|
||||||
|
err := json.Unmarshal([]byte(stdout), &topLevel)
|
||||||
|
if err != nil {
|
||||||
|
return stage.ParserResult{
|
||||||
|
Score: 0,
|
||||||
|
Comment: fmt.Sprintf("Failed to parse result: %s", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, module := range topLevel.Modules {
|
||||||
|
for _, entry := range module.Entries {
|
||||||
|
kind := entry[0].(string)
|
||||||
|
report := Report{}
|
||||||
|
err := mapstructure.Decode(entry[1], &report)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("elf parse", "mapstructure decode err", err)
|
||||||
|
}
|
||||||
|
slog.Debug("elf parse", "report file", report.File)
|
||||||
|
slog.Debug("elf parse", "report name", report.Name)
|
||||||
|
slog.Debug("elf parse", "report kind", kind)
|
||||||
|
for _, caseObj := range report.Cases {
|
||||||
|
switch kind {
|
||||||
|
case "ParenDep":
|
||||||
|
slog.Debug("elf parse", "binders", caseObj.Binders)
|
||||||
|
slog.Debug("elf parse", "context", caseObj.Context)
|
||||||
|
slog.Debug("elf parse", "depths", caseObj.Depths)
|
||||||
|
slog.Debug("elf parse", "code", caseObj.Code)
|
||||||
|
case "CodeLen":
|
||||||
|
slog.Debug("elf parse", "binders", caseObj.Binders)
|
||||||
|
slog.Debug("elf parse", "context", caseObj.Context)
|
||||||
|
slog.Debug("elf parse", "plain", caseObj.Plain)
|
||||||
|
slog.Debug("elf parse", "weighed", caseObj.Weighed)
|
||||||
|
slog.Debug("elf parse", "code", caseObj.Code)
|
||||||
|
case "OverArity":
|
||||||
|
slog.Debug("elf parse", "binders", caseObj.Binders)
|
||||||
|
slog.Debug("elf parse", "context", caseObj.Context)
|
||||||
|
slog.Debug("elf parse", "detail", caseObj.Detail)
|
||||||
|
slog.Debug("elf parse", "code", caseObj.Code)
|
||||||
|
case "CodeDup":
|
||||||
|
slog.Debug("elf parse", "similarity rate", caseObj.SimilarityRate)
|
||||||
|
for _, source := range caseObj.Sources {
|
||||||
|
slog.Debug("elf parse", "context", source.Context, "code", source.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stage.ParserResult{
|
||||||
|
Score: conf.Score,
|
||||||
|
Comment: conf.Comment,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Elf) Run(results []stage.ExecutorResult, confAny any) (
|
||||||
|
[]stage.ParserResult, bool, error,
|
||||||
|
) {
|
||||||
|
conf, err := stage.DecodeConf[Conf](confAny)
|
||||||
|
if err != nil {
|
||||||
|
return nil, true, err
|
||||||
|
}
|
||||||
|
var res []stage.ParserResult
|
||||||
|
for _, result := range results {
|
||||||
|
res = append(res, Parse(result, *conf))
|
||||||
|
}
|
||||||
|
return res, false, nil
|
||||||
|
}
|
9
internal/parsers/healthcheck/meta.go
Normal file
9
internal/parsers/healthcheck/meta.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package healthcheck
|
||||||
|
|
||||||
|
import "github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
|
|
||||||
|
var name = "healthcheck"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
stage.RegisterParser(name, &Healthcheck{})
|
||||||
|
}
|
41
internal/parsers/healthcheck/parser.go
Normal file
41
internal/parsers/healthcheck/parser.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package healthcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/criyle/go-judge/envexec"
|
||||||
|
"github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Healthcheck struct{}
|
||||||
|
|
||||||
|
func Parse(executorResult stage.ExecutorResult) (stage.ParserResult, bool) {
|
||||||
|
stdout := executorResult.Files["stdout"]
|
||||||
|
stderr := executorResult.Files["stderr"]
|
||||||
|
if executorResult.Status != stage.Status(envexec.StatusAccepted) {
|
||||||
|
return stage.ParserResult{
|
||||||
|
Score: 0,
|
||||||
|
Comment: fmt.Sprintf(
|
||||||
|
"Unexpected executor status: %s.\nStdout: %s\nStderr: %s",
|
||||||
|
executorResult.Status, stdout, stderr,
|
||||||
|
),
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
return stage.ParserResult{
|
||||||
|
Score: 0,
|
||||||
|
Comment: stdout,
|
||||||
|
}, stdout != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Healthcheck) Run(results []stage.ExecutorResult, confAny any) (
|
||||||
|
[]stage.ParserResult, bool, error,
|
||||||
|
) {
|
||||||
|
var res []stage.ParserResult
|
||||||
|
forceQuit := false
|
||||||
|
for _, result := range results {
|
||||||
|
parserResult, forceQuitResult := Parse(result)
|
||||||
|
res = append(res, parserResult)
|
||||||
|
forceQuit = forceQuit || forceQuitResult
|
||||||
|
}
|
||||||
|
return res, forceQuit, nil
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
package keyword
|
package keyword
|
||||||
|
|
||||||
import "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
import "github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
|
|
||||||
var name = "keyword"
|
var name = "keyword"
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
"github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Match struct {
|
type Match struct {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package resultstatus
|
package resultstatus
|
||||||
|
|
||||||
import "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
import "github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
|
|
||||||
var name = "result-status"
|
var name = "result-status"
|
||||||
|
|
||||||
|
|
|
@ -3,26 +3,28 @@ package resultstatus
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
|
||||||
"github.com/criyle/go-judge/envexec"
|
"github.com/criyle/go-judge/envexec"
|
||||||
|
"github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Conf struct{}
|
type Conf struct {
|
||||||
|
Score int
|
||||||
|
Comment string
|
||||||
|
}
|
||||||
|
|
||||||
type ResultStatus struct{}
|
type ResultStatus struct{}
|
||||||
|
|
||||||
func (*ResultStatus) Run(results []stage.ExecutorResult, confAny any) (
|
func (*ResultStatus) Run(results []stage.ExecutorResult, confAny any) (
|
||||||
[]stage.ParserResult, bool, error,
|
[]stage.ParserResult, bool, error,
|
||||||
) {
|
) {
|
||||||
// TODO: more conf options
|
conf, err := stage.DecodeConf[Conf](confAny)
|
||||||
_, err := stage.DecodeConf[Conf](confAny)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, true, err
|
return nil, true, err
|
||||||
}
|
}
|
||||||
forceQuit := false
|
forceQuit := false
|
||||||
var res []stage.ParserResult
|
var res []stage.ParserResult
|
||||||
for _, result := range results {
|
for _, result := range results {
|
||||||
comment := ""
|
comment := conf.Comment
|
||||||
if result.Status != stage.Status(envexec.StatusAccepted) {
|
if result.Status != stage.Status(envexec.StatusAccepted) {
|
||||||
forceQuit = true
|
forceQuit = true
|
||||||
comment = fmt.Sprintf(
|
comment = fmt.Sprintf(
|
||||||
|
@ -30,7 +32,7 @@ func (*ResultStatus) Run(results []stage.ExecutorResult, confAny any) (
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
res = append(res, stage.ParserResult{
|
res = append(res, stage.ParserResult{
|
||||||
Score: 0,
|
Score: conf.Score,
|
||||||
Comment: comment,
|
Comment: comment,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
9
internal/parsers/sample/meta.go
Normal file
9
internal/parsers/sample/meta.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package sample
|
||||||
|
|
||||||
|
import "github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
|
|
||||||
|
var name = "sample"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
stage.RegisterParser(name, &Sample{})
|
||||||
|
}
|
57
internal/parsers/sample/parser.go
Normal file
57
internal/parsers/sample/parser.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package sample
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/criyle/go-judge/envexec"
|
||||||
|
"github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
|
"github.com/joint-online-judge/JOJ3/pkg/sample"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Conf struct {
|
||||||
|
Score int
|
||||||
|
Comment string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Sample struct{}
|
||||||
|
|
||||||
|
func Parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult {
|
||||||
|
stdout := executorResult.Files["stdout"]
|
||||||
|
stderr := executorResult.Files["stderr"]
|
||||||
|
if executorResult.Status != stage.Status(envexec.StatusAccepted) {
|
||||||
|
return stage.ParserResult{
|
||||||
|
Score: 0,
|
||||||
|
Comment: fmt.Sprintf(
|
||||||
|
"Unexpected executor status: %s.\nStderr: %s",
|
||||||
|
executorResult.Status, stderr,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var sampleResult sample.Result
|
||||||
|
err := json.Unmarshal([]byte(stdout), &sampleResult)
|
||||||
|
if err != nil {
|
||||||
|
return stage.ParserResult{
|
||||||
|
Score: 0,
|
||||||
|
Comment: fmt.Sprintf("Failed to parse result: %s", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stage.ParserResult{
|
||||||
|
Score: sampleResult.Score + conf.Score,
|
||||||
|
Comment: sampleResult.Comment + conf.Comment,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Sample) Run(results []stage.ExecutorResult, confAny any) (
|
||||||
|
[]stage.ParserResult, bool, error,
|
||||||
|
) {
|
||||||
|
conf, err := stage.DecodeConf[Conf](confAny)
|
||||||
|
if err != nil {
|
||||||
|
return nil, true, err
|
||||||
|
}
|
||||||
|
var res []stage.ParserResult
|
||||||
|
for _, result := range results {
|
||||||
|
res = append(res, Parse(result, *conf))
|
||||||
|
}
|
||||||
|
return res, false, nil
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
package stage
|
package stage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/criyle/go-judge/envexec"
|
"github.com/criyle/go-judge/envexec"
|
||||||
)
|
)
|
||||||
|
@ -47,7 +47,7 @@ type Cmd struct {
|
||||||
|
|
||||||
CopyIn map[string]CmdFile `json:"copyIn"`
|
CopyIn map[string]CmdFile `json:"copyIn"`
|
||||||
CopyInCached map[string]string `json:"copyInCached"`
|
CopyInCached map[string]string `json:"copyInCached"`
|
||||||
CopyInCwd bool `json:"copyInCwd"`
|
CopyInDir string `json:"copyInDir"`
|
||||||
|
|
||||||
CopyOut []string `json:"copyOut"`
|
CopyOut []string `json:"copyOut"`
|
||||||
CopyOutCached []string `json:"copyOutCached"`
|
CopyOutCached []string `json:"copyOutCached"`
|
||||||
|
@ -126,8 +126,8 @@ func (r ExecutorResult) String() string {
|
||||||
Status Status
|
Status Status
|
||||||
ExitStatus int
|
ExitStatus int
|
||||||
Error string
|
Error string
|
||||||
Time time.Duration
|
Time uint64
|
||||||
RunTime time.Duration
|
RunTime uint64
|
||||||
Memory envexec.Size
|
Memory envexec.Size
|
||||||
Files map[string]string
|
Files map[string]string
|
||||||
FileIDs map[string]string
|
FileIDs map[string]string
|
||||||
|
@ -137,8 +137,8 @@ func (r ExecutorResult) String() string {
|
||||||
Status: r.Status,
|
Status: r.Status,
|
||||||
ExitStatus: r.ExitStatus,
|
ExitStatus: r.ExitStatus,
|
||||||
Error: r.Error,
|
Error: r.Error,
|
||||||
Time: time.Duration(r.Time),
|
Time: r.Time,
|
||||||
RunTime: time.Duration(r.RunTime),
|
RunTime: r.RunTime,
|
||||||
Memory: envexec.Size(r.Memory),
|
Memory: envexec.Size(r.Memory),
|
||||||
Files: make(map[string]string),
|
Files: make(map[string]string),
|
||||||
FileIDs: r.FileIDs,
|
FileIDs: r.FileIDs,
|
||||||
|
@ -163,8 +163,17 @@ type ParserResult struct {
|
||||||
Comment string `json:"comment"`
|
Comment string `json:"comment"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type StageResult struct {
|
type NonNullSlice[T any] []T
|
||||||
Name string `json:"name"`
|
|
||||||
Results []ParserResult `json:"results"`
|
func (s NonNullSlice[T]) MarshalJSON() ([]byte, error) {
|
||||||
ForceQuit bool `json:"force_quit"`
|
if len(s) == 0 {
|
||||||
|
return []byte("[]"), nil
|
||||||
|
}
|
||||||
|
return json.Marshal([]T(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
type StageResult struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Results NonNullSlice[ParserResult] `json:"results"`
|
||||||
|
ForceQuit bool `json:"force_quit"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,48 @@
|
||||||
package stage
|
package stage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Run(stages []Stage) []StageResult {
|
func Run(stages []Stage) (stageResults []StageResult, err error) {
|
||||||
stageResults := []StageResult{}
|
var executorResults []ExecutorResult
|
||||||
|
var parserResults []ParserResult
|
||||||
|
var forceQuit bool
|
||||||
|
slog.Info("stage run start")
|
||||||
for _, stage := range stages {
|
for _, stage := range stages {
|
||||||
slog.Debug("stage start", "name", stage.Name)
|
slog.Info("stage start", "name", stage.Name)
|
||||||
slog.Debug("executor run start", "cmds", stage.ExecutorCmds)
|
slog.Info("executor run start", "name", stage.ExecutorName)
|
||||||
|
slog.Debug("executor run start", "name", stage.ExecutorName,
|
||||||
|
"cmds", stage.ExecutorCmds)
|
||||||
executor, ok := executorMap[stage.ExecutorName]
|
executor, ok := executorMap[stage.ExecutorName]
|
||||||
if !ok {
|
if !ok {
|
||||||
slog.Error("executor not found", "name", stage.ExecutorName)
|
slog.Error("executor not found", "name", stage.ExecutorName)
|
||||||
break
|
err = fmt.Errorf("executor not found: %s", stage.ExecutorName)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
executorResults, err := executor.Run(stage.ExecutorCmds)
|
executorResults, err = executor.Run(stage.ExecutorCmds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("executor run error", "name", stage.ExecutorName, "error", err)
|
slog.Error("executor run error", "name", stage.ExecutorName, "error", err)
|
||||||
break
|
return
|
||||||
}
|
}
|
||||||
slog.Debug("executor run done", "results", executorResults)
|
slog.Debug("executor run done", "results", executorResults)
|
||||||
slog.Debug("parser run start", "conf", stage.ParserConf)
|
for _, executorResult := range executorResults {
|
||||||
|
slog.Debug("executor run done", "result.Files", executorResult.Files)
|
||||||
|
}
|
||||||
|
slog.Info("parser run start", "name", stage.ParserName)
|
||||||
|
slog.Debug("parser run start", "name", stage.ParserName,
|
||||||
|
"conf", stage.ParserConf)
|
||||||
parser, ok := parserMap[stage.ParserName]
|
parser, ok := parserMap[stage.ParserName]
|
||||||
if !ok {
|
if !ok {
|
||||||
slog.Error("parser not found", "name", stage.ParserName)
|
slog.Error("parser not found", "name", stage.ParserName)
|
||||||
break
|
err = fmt.Errorf("parser not found: %s", stage.ParserName)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
parserResults, forceQuit, err := parser.Run(executorResults, stage.ParserConf)
|
parserResults, forceQuit, err = parser.Run(executorResults, stage.ParserConf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("parser run error", "name", stage.ExecutorName, "error", err)
|
slog.Error("parser run error", "name", stage.ParserName, "error", err)
|
||||||
break
|
return
|
||||||
}
|
}
|
||||||
slog.Debug("parser run done", "results", parserResults)
|
slog.Debug("parser run done", "results", parserResults)
|
||||||
stageResults = append(stageResults, StageResult{
|
stageResults = append(stageResults, StageResult{
|
||||||
|
@ -38,19 +51,19 @@ func Run(stages []Stage) []StageResult {
|
||||||
ForceQuit: forceQuit,
|
ForceQuit: forceQuit,
|
||||||
})
|
})
|
||||||
if forceQuit {
|
if forceQuit {
|
||||||
break
|
slog.Error("parser force quit", "name", stage.ParserName)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return stageResults
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func Cleanup() {
|
func Cleanup() {
|
||||||
|
slog.Info("stage cleanup start")
|
||||||
for name, executor := range executorMap {
|
for name, executor := range executorMap {
|
||||||
slog.Debug("executor cleanup start", "name", name)
|
|
||||||
err := executor.Cleanup()
|
err := executor.Cleanup()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("executor cleanup error", "name", name, "error", err)
|
slog.Error("executor cleanup error", "name", name, "error", err)
|
||||||
}
|
}
|
||||||
slog.Debug("executor cleanup done", "name", name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
package stage
|
package stage
|
||||||
|
|
||||||
import "github.com/mitchellh/mapstructure"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
)
|
||||||
|
|
||||||
func DecodeConf[T any](confAny any) (*T, error) {
|
func DecodeConf[T any](confAny any) (*T, error) {
|
||||||
var conf T
|
var conf T
|
||||||
err := mapstructure.Decode(confAny, &conf)
|
err := mapstructure.Decode(confAny, &conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to decode conf: %w", err)
|
||||||
}
|
}
|
||||||
return &conf, nil
|
return &conf, nil
|
||||||
}
|
}
|
||||||
|
|
76
pkg/healthcheck/commit.go
Normal file
76
pkg/healthcheck/commit.go
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
package healthcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkMsg(msg string) bool {
|
||||||
|
// List of prefixes to ignore in the commit message
|
||||||
|
ignoredPrefixes := []string{
|
||||||
|
"Co-authored-by:",
|
||||||
|
"Reviewed-by:",
|
||||||
|
"Co-committed-by:",
|
||||||
|
"Reviewed-on:",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split message by lines and ignore specific lines with prefixes
|
||||||
|
lines := strings.Split(msg, "\n")
|
||||||
|
for i, line := range lines {
|
||||||
|
trimmedLine := strings.TrimSpace(line)
|
||||||
|
ignore := false
|
||||||
|
if i != 0 {
|
||||||
|
for _, prefix := range ignoredPrefixes {
|
||||||
|
if strings.HasPrefix(trimmedLine, prefix) {
|
||||||
|
ignore = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ignore {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Check for non-ASCII characters in the rest of the lines
|
||||||
|
for _, c := range line {
|
||||||
|
if c > unicode.MaxASCII {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// nonAsciiMsg checks for non-ASCII characters in the commit message.
|
||||||
|
// It iterates over each character in the message and checks if it is a non-ASCII character.
|
||||||
|
// If a non-ASCII character is found, it returns an error indicating not to use non-ASCII characters in commit messages.
|
||||||
|
// Otherwise, it returns nil indicating that the commit message is valid.
|
||||||
|
// It skips the non-ASCII characters check for lines starting with specific keywords like "Co-authored-by", "Reviewed-by", and "Co-committed-by".
|
||||||
|
func NonAsciiMsg(root string) error {
|
||||||
|
repo, err := git.PlainOpen(root)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("opening git repo", "err", err)
|
||||||
|
return fmt.Errorf("error opening git repo: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ref, err := repo.Head()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("getting reference", "err", err)
|
||||||
|
return fmt.Errorf("error getting reference: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
commit, err := repo.CommitObject(ref.Hash())
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("getting latest commit", "err", err)
|
||||||
|
return fmt.Errorf("error getting latest commit: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := commit.Message
|
||||||
|
if !checkMsg(msg) {
|
||||||
|
return fmt.Errorf("Non-ASCII characters in commit messages:\n%s", msg)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
83
pkg/healthcheck/commit_test.go
Normal file
83
pkg/healthcheck/commit_test.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
package healthcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCheckMsg(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
message string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid ASCII message",
|
||||||
|
message: "This is a valid commit message",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Message with non-ASCII character",
|
||||||
|
message: "This message contains a non-ASCII character: é",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Message with ignored prefix",
|
||||||
|
message: "First line\nCo-authored-by: John Doe <john@example.com>\nThis is a valid message",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Message with ignored prefix and non-ASCII character in content",
|
||||||
|
message: "First line\nCo-authored-by: John Doe <john@example.com>\nThis message has a non-ASCII character: ñ",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Message with ignored prefix in the first line",
|
||||||
|
message: "Co-authored-by: Jöhn Döe <john@example.com>",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Multi-line message with all valid ASCII",
|
||||||
|
message: "First line\nSecond line\nThird line",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Multi-line message with non-ASCII in middle",
|
||||||
|
message: "First line\nSecond line with ö\nThird line",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Multi-line message with non-ASCII in the first line",
|
||||||
|
message: "First line with ö\nSecond line",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Message with multiple ignored prefixes",
|
||||||
|
message: "First line\nCo-authored-by: John Doe <john@example.com>\nReviewed-by: Jane Smith <jane@example.com>\nValid content",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty message",
|
||||||
|
message: "",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Message with only whitespace",
|
||||||
|
message: " \n \t ",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Message with non-ASCII after ignored prefix",
|
||||||
|
message: "First line\nCo-authored-by: John Doe <john@example.com>\nReviewed-by: Jöhn Döe <john@example.com>",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := checkMsg(tt.message)
|
||||||
|
if result != tt.expected {
|
||||||
|
t.Errorf("checkMsg(%q) = %v, want %v", tt.message, result, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
90
pkg/healthcheck/forbidden.go
Normal file
90
pkg/healthcheck/forbidden.go
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
package healthcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getForbiddens retrieves a list of forbidden files in the specified root directory.
|
||||||
|
// It searches for files that do not match the specified regex patterns in the given file list.
|
||||||
|
func getForbiddens(root string, fileList []string, localList string) ([]string, error) {
|
||||||
|
var matches []string
|
||||||
|
|
||||||
|
var regexList []*regexp.Regexp
|
||||||
|
regexList, err := getRegex(fileList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var dirs []string
|
||||||
|
|
||||||
|
if localList != "" {
|
||||||
|
file, err := os.Open(localList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to open file %s: %v\n", localList, err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
dirs = append(dirs, scanner.Text())
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("Error reading file %s: %v\n", localList, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsDir() {
|
||||||
|
if info.Name() == ".git" || info.Name() == ".gitea" || info.Name() == "ci" || (localList != "" && inString(info.Name(), dirs)) {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match := false
|
||||||
|
for _, regex := range regexList {
|
||||||
|
if regex.MatchString(info.Name()) {
|
||||||
|
match = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !match {
|
||||||
|
matches = append(matches, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return matches, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// forbiddenCheck checks for forbidden files in the specified root directory.
|
||||||
|
// It prints the list of forbidden files found, along with instructions on how to fix them.
|
||||||
|
func ForbiddenCheck(rootDir string, regexList []string, localList string, repo string, droneBranch string) error {
|
||||||
|
forbids, err := getForbiddens(rootDir, regexList, localList)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("getting forbiddens", "error", err)
|
||||||
|
return fmt.Errorf("error getting forbiddens: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(forbids) > 0 {
|
||||||
|
return fmt.Errorf("The following forbidden files were found: %s\n\nTo fix it, first make a backup of your repository and then run the following commands:\nfor i in %s%s",
|
||||||
|
strings.Join(forbids, ", "),
|
||||||
|
strings.Join(forbids, " "),
|
||||||
|
fmt.Sprint(
|
||||||
|
"; do git filter-repo --force --invert-paths --path \"$i\"; done\ngit remote add origin ",
|
||||||
|
repo, "\ngit push --set-upstream origin ",
|
||||||
|
droneBranch, " --force"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
package healthcheck
|
|
73
pkg/healthcheck/meta.go
Normal file
73
pkg/healthcheck/meta.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package healthcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getMetas retrieves a list of metadata files that are expected to exist in the specified root directory.
|
||||||
|
// It checks for the existence of each file in the fileList and provides instructions if any file is missing.
|
||||||
|
func getMetas(rootDir string, fileList []string) ([]string, string, error) {
|
||||||
|
addExt(fileList, "\\.*")
|
||||||
|
regexList, err := getRegex(fileList)
|
||||||
|
var unmatchedList []string
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
files, err := os.ReadDir(rootDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("error reading directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
matched := false
|
||||||
|
umatchedRes := ""
|
||||||
|
|
||||||
|
// TODO: it seems that there is no good find substitution now
|
||||||
|
// modify current code if exist a better solution
|
||||||
|
for i, regex := range regexList {
|
||||||
|
for _, file := range files {
|
||||||
|
if file.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if regex.MatchString(file.Name()) {
|
||||||
|
matched = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !matched {
|
||||||
|
unmatchedList = append(unmatchedList, fileList[i])
|
||||||
|
str := fmt.Sprint("\tno ", fileList[i], " file found")
|
||||||
|
switch fileList[i] {
|
||||||
|
case "readme\\.*":
|
||||||
|
str += ", please refer to https://www.makeareadme.com/ for more information"
|
||||||
|
case "changelog\\.*":
|
||||||
|
str += ", please refer to https://keepachangelog.com/en/1.1.0/ for more information"
|
||||||
|
default:
|
||||||
|
str += ""
|
||||||
|
}
|
||||||
|
str += "\n"
|
||||||
|
|
||||||
|
umatchedRes += str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return unmatchedList, umatchedRes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// metaCheck performs a check for metadata files in the specified root directory.
|
||||||
|
// It prints a message if any required metadata files are missing.
|
||||||
|
func MetaCheck(rootDir string, fileList []string) error {
|
||||||
|
unmatchedList, umatchedRes, err := getMetas(rootDir, fileList)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("getting metas", "err", err)
|
||||||
|
return fmt.Errorf("error getting metas: %w", err)
|
||||||
|
}
|
||||||
|
if len(unmatchedList) != 0 {
|
||||||
|
return fmt.Errorf("%d important project files missing\n"+umatchedRes, len(unmatchedList))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
93
pkg/healthcheck/nonascii.go
Normal file
93
pkg/healthcheck/nonascii.go
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
package healthcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getNonAscii retrieves a list of files in the specified root directory that contain non-ASCII characters.
|
||||||
|
// It searches for non-ASCII characters in each file's content and returns a list of paths to files containing non-ASCII characters.
|
||||||
|
func getNonAscii(root string, localList string) ([]string, error) {
|
||||||
|
var nonAscii []string
|
||||||
|
|
||||||
|
var dirs []string
|
||||||
|
|
||||||
|
if localList != "" {
|
||||||
|
file, err := os.Open(localList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to open file %s: %v\n", localList, err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
dirs = append(dirs, scanner.Text())
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("Error reading file %s: %v\n", localList, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsDir() {
|
||||||
|
if info.Name() == ".git" || info.Name() == ".gitea" || info.Name() == "ci" || (localList != "" && inString(info.Name(), dirs)) {
|
||||||
|
return filepath.SkipDir
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Name() == "healthcheck" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
cont := true
|
||||||
|
for _, c := range scanner.Text() {
|
||||||
|
if c > unicode.MaxASCII {
|
||||||
|
nonAscii = append(nonAscii, "\t"+path)
|
||||||
|
cont = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !cont {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return nonAscii, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// nonAsciiFiles checks for non-ASCII characters in files within the specified root directory.
|
||||||
|
// It prints a message with the paths to files containing non-ASCII characters, if any.
|
||||||
|
func NonAsciiFiles(root string, localList string) error {
|
||||||
|
nonAscii, err := getNonAscii(root, localList)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("getting non-ascii", "err", err)
|
||||||
|
return fmt.Errorf("error getting non-ascii: %w", err)
|
||||||
|
}
|
||||||
|
if len(nonAscii) > 0 {
|
||||||
|
return fmt.Errorf("Non-ASCII characters found in the following files:\n%s",
|
||||||
|
strings.Join(nonAscii, "\n"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
40
pkg/healthcheck/reposize.go
Normal file
40
pkg/healthcheck/reposize.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package healthcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RepoSize checks the size of the repository to determine if it is oversized.
|
||||||
|
// It executes the 'git count-objects -v' command to obtain the size information,
|
||||||
|
func RepoSize() error {
|
||||||
|
// TODO: reimplement here when go-git is available
|
||||||
|
// https://github.com/go-git/go-git/blob/master/COMPATIBILITY.md
|
||||||
|
cmd := exec.Command("git", "count-objects", "-v")
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("running git command:", "err", err)
|
||||||
|
return fmt.Errorf("error running git command: %w", err)
|
||||||
|
}
|
||||||
|
lines := strings.Split(string(output), "\n")
|
||||||
|
var sum int
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.Contains(line, "size") {
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
sizeStr := fields[1]
|
||||||
|
size, err := strconv.Atoi(sizeStr)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("running git command:", "err", err)
|
||||||
|
return fmt.Errorf("error running git command: %w", err)
|
||||||
|
}
|
||||||
|
sum += size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sum > 2048 {
|
||||||
|
return fmt.Errorf("Repository larger than 2MB. Please clean up or contact the teaching team.")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
66
pkg/healthcheck/tag.go
Normal file
66
pkg/healthcheck/tag.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
package healthcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getTagsFromRepo(repoPath string) ([]string, error) {
|
||||||
|
repo, err := git.PlainOpen(repoPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error opening repo: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
refs, err := repo.Tags()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting tags: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var tags []string
|
||||||
|
err = refs.ForEach(func(ref *plumbing.Reference) error {
|
||||||
|
tags = append(tags, ref.Name().Short())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error iterating tags: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tags, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckTags(repoPath string, category string, n int) error {
|
||||||
|
// INFO: if category not specified, skipping this check by default
|
||||||
|
if category == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tags, err := getTagsFromRepo(repoPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting tags: %v", err)
|
||||||
|
}
|
||||||
|
var prefix string
|
||||||
|
switch category {
|
||||||
|
case "exam":
|
||||||
|
prefix = "e"
|
||||||
|
case "project":
|
||||||
|
prefix = "p"
|
||||||
|
case "homework":
|
||||||
|
prefix = "h"
|
||||||
|
default:
|
||||||
|
prefix = "a"
|
||||||
|
}
|
||||||
|
target := prefix + fmt.Sprintf("%d", n)
|
||||||
|
found := false
|
||||||
|
for _, tag := range tags {
|
||||||
|
if tag == target {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("Wrong release tag '%s' or missing release tags. Please use one of '%s'.", strings.Join(tags, "', '"), target)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
38
pkg/healthcheck/utils.go
Normal file
38
pkg/healthcheck/utils.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package healthcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func inString(str1 string, strList []string) bool {
|
||||||
|
for _, str := range strList {
|
||||||
|
if str1 == str {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// addExt appends the specified extension to each file name in the given fileList.
|
||||||
|
// It modifies the original fileList in place.
|
||||||
|
func addExt(fileList []string, ext string) {
|
||||||
|
for i, file := range fileList {
|
||||||
|
fileList[i] = file + ext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRegex compiles each regex pattern in the fileList into a []*regexp.Regexp slice.
|
||||||
|
// It returns a slice containing compiled regular expressions.
|
||||||
|
func getRegex(fileList []string) ([]*regexp.Regexp, error) {
|
||||||
|
var regexList []*regexp.Regexp
|
||||||
|
for _, pattern := range fileList {
|
||||||
|
regex, err := regexp.Compile("(?i)" + pattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error compiling regex:%w", err)
|
||||||
|
}
|
||||||
|
regexList = append(regexList, regex)
|
||||||
|
}
|
||||||
|
|
||||||
|
return regexList, nil
|
||||||
|
}
|
63
pkg/healthcheck/verify.go
Normal file
63
pkg/healthcheck/verify.go
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
package healthcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getChecksum calculates the SHA-256 checksum of a file
|
||||||
|
func getChecksum(filePath string) (string, error) {
|
||||||
|
// Open the file
|
||||||
|
file, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// Calculate SHA-256
|
||||||
|
hash := sha256.New()
|
||||||
|
if _, err := io.Copy(hash, file); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return hex.EncodeToString(hash.Sum(nil)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkFileChecksum checks if a single file's checksum matches the expected value
|
||||||
|
func checkFileChecksum(rootDir, fileName, expectedChecksum string) error {
|
||||||
|
filePath := filepath.Join(rootDir, strings.TrimSpace(fileName))
|
||||||
|
actualChecksum, err := getChecksum(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error reading file %s: %v", filePath, err)
|
||||||
|
}
|
||||||
|
if actualChecksum != expectedChecksum {
|
||||||
|
return fmt.Errorf("Checksum for %s failed. Expected %s, but got %s. Please revert your changes or contact the teaching team if you have a valid reason for adjusting them.", filePath, expectedChecksum, actualChecksum)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func VerifyFiles(rootDir string, checkFileNameList string, checkFileSumList string) error {
|
||||||
|
if len(checkFileNameList) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fileNames := strings.Split(checkFileNameList, ",")
|
||||||
|
checkSums := strings.Split(checkFileSumList, ",")
|
||||||
|
// Check if the number of files matches the number of checksums
|
||||||
|
if len(fileNames) != len(checkSums) {
|
||||||
|
return fmt.Errorf("Error: The number of files and checksums do not match.")
|
||||||
|
}
|
||||||
|
// Check each file's checksum
|
||||||
|
for i, fileName := range fileNames {
|
||||||
|
expectedChecksum := strings.TrimSpace(checkSums[i])
|
||||||
|
err := checkFileChecksum(rootDir, fileName, expectedChecksum)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package dummy
|
package sample
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -19,10 +19,10 @@ func Run(conf Conf) (res Result, err error) {
|
||||||
// the sandbox, the logs will not show in drone output directly.
|
// the sandbox, the logs will not show in drone output directly.
|
||||||
// If there are more kinds of errors need to be handled separately, add
|
// If there are more kinds of errors need to be handled separately, add
|
||||||
// more fields in the Result struct, don't mess everything up in Stderr.
|
// more fields in the Result struct, don't mess everything up in Stderr.
|
||||||
err = fmt.Errorf("dummy negative score: %d", conf.Score)
|
err = fmt.Errorf("sample negative score: %d", conf.Score)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
res.Score = conf.Score
|
res.Score = conf.Score
|
||||||
res.Comment = "dummy comment"
|
res.Comment = "sample comment"
|
||||||
return
|
return
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -ex
|
set -e
|
||||||
declare -A repo_names
|
declare -A repo_names
|
||||||
tmp_dir=${1:-./tmp}
|
tmp_dir=${1:-./tmp}
|
||||||
submodules_dir="$tmp_dir/submodules"
|
submodules_dir="$tmp_dir/submodules"
|
||||||
|
@ -18,14 +18,14 @@ for submodule in $submodules; do
|
||||||
else
|
else
|
||||||
cd $repo_dir
|
cd $repo_dir
|
||||||
git fetch --all
|
git fetch --all
|
||||||
cd -
|
cd - > /dev/null
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
repo_names[$repo_name]=1
|
repo_names[$repo_name]=1
|
||||||
cd $repo_dir
|
cd $repo_dir
|
||||||
git checkout -q $branch
|
git checkout -q $branch
|
||||||
git reset -q --hard origin/$branch
|
git reset -q --hard origin/$branch
|
||||||
cd -
|
cd - > /dev/null
|
||||||
submodule_dir="$submodules_dir/$repo_name/$submodule"
|
submodule_dir="$submodules_dir/$repo_name/$submodule"
|
||||||
mkdir -p $submodule_dir
|
mkdir -p $submodule_dir
|
||||||
cp -rT $repo_dir $submodule_dir
|
cp -rT $repo_dir $submodule_dir
|
||||||
|
|
21
scripts/run_foreach_test_repos.sh
Executable file
21
scripts/run_foreach_test_repos.sh
Executable file
|
@ -0,0 +1,21 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
tmp_dir=${1:-./tmp}
|
||||||
|
JOJ3=$(git rev-parse --show-toplevel)/build/joj3
|
||||||
|
command=${2:-$JOJ3}
|
||||||
|
submodules_dir="$tmp_dir/submodules"
|
||||||
|
submodules=$(git config --file .gitmodules --get-regexp path | awk '{ print $2 }')
|
||||||
|
for submodule in $submodules; do
|
||||||
|
url=$(git config --file .gitmodules --get-regexp "submodule.$submodule.url" | awk '{ print $2 }')
|
||||||
|
repo_name=$(echo $url | rev | cut -d'/' -f 1 | rev | cut -d'.' -f 1)
|
||||||
|
submodule_dir="$submodules_dir/$repo_name/$submodule"
|
||||||
|
cd $submodule_dir
|
||||||
|
eval "$command"
|
||||||
|
if [[ $command == $JOJ3 ]]; then
|
||||||
|
if [ -f "./expected.json" ]; then
|
||||||
|
mv -f "joj3_result.json" "expected.json"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
cd - > /dev/null
|
||||||
|
done
|
Loading…
Reference in New Issue
Block a user