feat: repo health check (#16) #17
|
@ -1,7 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
@ -38,10 +37,7 @@ func setupSlog() {
|
||||||
|
|
||||||
// Generally, err is used for runtime errors, and checkRes is used for the result of the checks.
|
// Generally, err is used for runtime errors, and checkRes is used for the result of the checks.
|
||||||
func main() {
|
func main() {
|
||||||
var info []healthcheck.CheckStage
|
|
||||||
var gitWhitelist, metaFile, releaseTags []string
|
var gitWhitelist, metaFile, releaseTags []string
|
||||||
var tmp healthcheck.CheckStage
|
|
||||||
|
|
||||||
rootDir := flag.String("root", "", "")
|
rootDir := flag.String("root", "", "")
|
||||||
repo := flag.String("repo", "", "")
|
repo := flag.String("repo", "", "")
|
||||||
droneBranch := flag.String("droneBranch", "", "")
|
droneBranch := flag.String("droneBranch", "", "")
|
||||||
|
@ -54,33 +50,35 @@ func main() {
|
||||||
parseMultiValueFlag(&releaseTags, "releaseTags", "")
|
parseMultiValueFlag(&releaseTags, "releaseTags", "")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
setupSlog()
|
setupSlog()
|
||||||
tmp = healthcheck.RepoSize()
|
var err error
|
||||||
info = append(info, tmp)
|
err = healthcheck.RepoSize()
|
||||||
|
|
||||||
tmp = healthcheck.ForbiddenCheck(*rootDir, gitWhitelist, *repo, *droneBranch)
|
|
||||||
info = append(info, tmp)
|
|
||||||
|
|
||||||
tmp = healthcheck.MetaCheck(*rootDir, metaFile)
|
|
||||||
info = append(info, tmp)
|
|
||||||
|
|
||||||
tmp = healthcheck.NonAsciiFiles(*rootDir)
|
|
||||||
info = append(info, tmp)
|
|
||||||
|
|
||||||
tmp = healthcheck.NonAsciiMsg(*rootDir)
|
|
||||||
info = append(info, tmp)
|
|
||||||
|
|
||||||
// TODO: find a way to test the release tag
|
|
||||||
tmp = healthcheck.CheckReleases(*rootDir, *releaseCategories, *releaseNumber)
|
|
||||||
info = append(info, tmp)
|
|
||||||
|
|
||||||
// FIXME: for drone usage
|
|
||||||
tmp = healthcheck.Verify(*rootDir, *adminDir)
|
|
||||||
info = append(info, tmp)
|
|
||||||
|
|
||||||
jsonData, err := json.Marshal(info)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprint(os.Stderr, err)
|
fmt.Printf("## Repo Size Check Failed:\n%s\n", err.Error())
|
||||||
os.Exit(1)
|
}
|
||||||
|
err = healthcheck.ForbiddenCheck(*rootDir, gitWhitelist, *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())
|
||||||
bomingzh marked this conversation as resolved
Outdated
|
|||||||
|
}
|
||||||
|
err = healthcheck.NonAsciiFiles(*rootDir)
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
// TODO: find a way to test the release tag
|
||||||
|
err = healthcheck.CheckReleases(*rootDir, *releaseCategories, *releaseNumber)
|
||||||
bomingzh marked this conversation as resolved
Outdated
张泊明518370910136
commented
check releases need to use Gitea API, just check tags is enough check releases need to use Gitea API, just check tags is enough
汪睿522370910169
commented
Yep, only check tags Yep, only check tags
张泊明518370910136
commented
Where is it tested? Where is it tested?
|
|||||||
|
if err != nil {
|
||||||
|
fmt.Printf("## Release Tag Check Failed:\n%s\n", err.Error())
|
||||||
|
}
|
||||||
|
// FIXME: for drone usage
|
||||||
|
err = healthcheck.VerifyDirectory(*rootDir, *adminDir)
|
||||||
zzjc123 marked this conversation as resolved
Outdated
张泊明518370910136
commented
what should be fixed? what should be fixed?
周赵嘉程521432910016
commented
I think it is due to we can only test it when it is deployed on drone so I just skip it when running the code. If not I couldn't push the code. I think it is due to we can only test it when it is deployed on drone so I just skip it when running the code. If not I couldn't push the code.
张泊明518370910136
commented
I think checking an empty value for skipping it is enough. No dir specified = no verify. I think checking an empty value for skipping it is enough. No dir specified = no verify.
|
|||||||
|
if err != nil {
|
||||||
|
fmt.Printf("## Directory File Check Failed:\n%s\n", err.Error())
|
||||||
}
|
}
|
||||||
fmt.Printf("%s", jsonData)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,41 +7,35 @@ import (
|
||||||
"github.com/criyle/go-judge/envexec"
|
"github.com/criyle/go-judge/envexec"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Conf struct {
|
|
||||||
Score int
|
|
||||||
Comment string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Healthcheck struct{}
|
type Healthcheck struct{}
|
||||||
|
|
||||||
func Parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult {
|
func Parse(executorResult stage.ExecutorResult) (stage.ParserResult, bool) {
|
||||||
stdout := executorResult.Files["stdout"]
|
stdout := executorResult.Files["stdout"]
|
||||||
stderr := executorResult.Files["stderr"]
|
stderr := executorResult.Files["stderr"]
|
||||||
if executorResult.Status != stage.Status(envexec.StatusAccepted) {
|
if executorResult.Status != stage.Status(envexec.StatusAccepted) {
|
||||||
return stage.ParserResult{
|
return stage.ParserResult{
|
||||||
Score: 0,
|
Score: 0,
|
||||||
Comment: fmt.Sprintf(
|
Comment: fmt.Sprintf(
|
||||||
"Unexpected executor status: %s.\nStderr: %s",
|
"Unexpected executor status: %s.\nStdout: %s\nStderr: %s",
|
||||||
executorResult.Status, stderr,
|
executorResult.Status, stdout, stderr,
|
||||||
),
|
),
|
||||||
}
|
}, true
|
||||||
}
|
}
|
||||||
return stage.ParserResult{
|
return stage.ParserResult{
|
||||||
Score: 0,
|
Score: 0,
|
||||||
Comment: stdout,
|
Comment: stdout,
|
||||||
}
|
}, stdout != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Healthcheck) Run(results []stage.ExecutorResult, confAny any) (
|
func (*Healthcheck) Run(results []stage.ExecutorResult, confAny any) (
|
||||||
[]stage.ParserResult, bool, error,
|
[]stage.ParserResult, bool, error,
|
||||||
) {
|
) {
|
||||||
conf, err := stage.DecodeConf[Conf](confAny)
|
|
||||||
if err != nil {
|
|
||||||
return nil, true, err
|
|
||||||
}
|
|
||||||
var res []stage.ParserResult
|
var res []stage.ParserResult
|
||||||
zzjc123 marked this conversation as resolved
Outdated
张泊明518370910136
commented
Comments need to be human-readable. You can use markdown format here. I suggest only keep the stderr field. You can get exitcode directly from executor if needed. Use logger to log any details for student to debug as they can check the log info from drone. A comment of health check should look like this: Repo SizePass Forbidden FileThe following forbidden files were found: README.md, conf.toml, expected.json, healthcheck, stderr, stdin.sh, stdout
Check name...check detail... Comments need to be human-readable. You can use markdown format here. I suggest only keep the stderr field. You can get exitcode directly from executor if needed. Use logger to log any details for student to debug as they can check the log info from drone.
A comment of health check should look like this:
### Repo Size
Pass
### Forbidden File
The following forbidden files were found: README.md, conf.toml, expected.json, healthcheck, stderr, stdin.sh, stdout
To fix it, first make a backup of your repository and then run the following commands:
```
...
```
### Check name...
check detail...
|
|||||||
|
forceQuit := false
|
||||||
for _, result := range results {
|
for _, result := range results {
|
||||||
res = append(res, Parse(result, *conf))
|
parserResult, forceQuitResult := Parse(result)
|
||||||
|
res = append(res, parserResult)
|
||||||
|
forceQuit = forceQuit || forceQuitResult
|
||||||
}
|
}
|
||||||
return res, false, nil
|
return res, forceQuit, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package healthcheck
|
package healthcheck
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
@ -14,36 +15,23 @@ import (
|
||||||
// Otherwise, it iterates over each character in the message and checks if it is a non-ASCII character.
|
// Otherwise, 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.
|
// 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.
|
// Otherwise, it returns nil indicating that the commit message is valid.
|
||||||
func NonAsciiMsg(root string) (jsonOut CheckStage) {
|
func NonAsciiMsg(root string) error {
|
||||||
jsonOut = CheckStage{
|
|
||||||
Name: "NonAsciiMsg",
|
|
||||||
StdOut: "Checking for non-ASCII characters in commit message: ",
|
|
||||||
ExitCode: 0,
|
|
||||||
StdErr: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
// cmd := exec.Command("git", "log", "--encoding=UTF-8", "--format=%B")
|
// cmd := exec.Command("git", "log", "--encoding=UTF-8", "--format=%B")
|
||||||
repo, err := git.PlainOpen(root)
|
repo, err := git.PlainOpen(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonOut.StdOut += "Failed"
|
|
||||||
jsonOut.ExitCode = 1
|
|
||||||
slog.Error("openning git repo", "err", err)
|
slog.Error("openning git repo", "err", err)
|
||||||
return jsonOut
|
return fmt.Errorf("error openning git repo: %v", err)
|
||||||
}
|
}
|
||||||
zzjc123 marked this conversation as resolved
Outdated
张泊明518370910136
commented
https://github.com/go-git/go-git#in-memory-example Try to avoid https://github.com/go-git/go-git#in-memory-example
Try to avoid `exec.Command`.
|
|||||||
|
|
||||||
ref, err := repo.Head()
|
ref, err := repo.Head()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonOut.StdOut += "Failed"
|
|
||||||
jsonOut.ExitCode = 1
|
|
||||||
slog.Error("getting reference", "err", err)
|
slog.Error("getting reference", "err", err)
|
||||||
return jsonOut
|
return fmt.Errorf("error getting reference: %v", err)
|
||||||
}
|
}
|
||||||
commits, err := repo.Log(&git.LogOptions{From: ref.Hash()})
|
commits, err := repo.Log(&git.LogOptions{From: ref.Hash()})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonOut.StdOut += "Failed"
|
|
||||||
jsonOut.ExitCode = 1
|
|
||||||
slog.Error("getting commits", "err", err)
|
slog.Error("getting commits", "err", err)
|
||||||
return jsonOut
|
return fmt.Errorf("error getting commits: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var msgs []string
|
var msgs []string
|
||||||
|
@ -52,10 +40,8 @@ func NonAsciiMsg(root string) (jsonOut CheckStage) {
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonOut.StdOut += "Failed"
|
slog.Error("iterating commits", "err", err)
|
||||||
jsonOut.ExitCode = 1
|
return fmt.Errorf("error iterating commits: %v", err)
|
||||||
slog.Error("converting log to string", "err", err)
|
|
||||||
return jsonOut
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var nonAsciiMsgs []string
|
var nonAsciiMsgs []string
|
||||||
|
@ -73,11 +59,7 @@ func NonAsciiMsg(root string) (jsonOut CheckStage) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(nonAsciiMsgs) > 0 {
|
if len(nonAsciiMsgs) > 0 {
|
||||||
jsonOut.StdOut += "Failed"
|
return fmt.Errorf("Non-ASCII characters in commit messages:\n" + strings.Join(nonAsciiMsgs, "\n"))
|
||||||
jsonOut.ExitCode = 105
|
|
||||||
jsonOut.StdErr = "Non-ASCII characters in commit messages:\n" + strings.Join(nonAsciiMsgs, "\n")
|
|
||||||
return jsonOut
|
|
||||||
}
|
}
|
||||||
jsonOut.StdOut += "OK"
|
return nil
|
||||||
return jsonOut
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ func getForbiddens(root string, fileList []string) ([]string, error) {
|
||||||
var regexList []*regexp.Regexp
|
var regexList []*regexp.Regexp
|
||||||
regexList, err := getRegex(fileList)
|
regexList, err := getRegex(fileList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error compiling regex:", err)
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,24 +51,11 @@ func getForbiddens(root string, fileList []string) ([]string, error) {
|
||||||
|
|
||||||
// forbiddenCheck checks for forbidden files in the specified root directory.
|
// 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.
|
// It prints the list of forbidden files found, along with instructions on how to fix them.
|
||||||
func ForbiddenCheck(rootDir string, regexList []string, repo string, droneBranch string) (jsonOut CheckStage) {
|
func ForbiddenCheck(rootDir string, regexList []string, repo string, droneBranch string) error {
|
||||||
jsonOut = CheckStage{
|
|
||||||
Name: "forbiddenFile",
|
|
||||||
StdOut: "Checking forbidden files: ",
|
|
||||||
ExitCode: 0,
|
|
||||||
StdErr: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
// INFO: test case
|
|
||||||
// regexList := []string{`\.$`, `\.git`, `\.drone.yml`, `Makefile`, `CMakeLists.txt`,`.*\.go`,`.*\.toml`, `.*\.c`, `.*\.cc`, `.*\.cpp`, `.*\.h`, `.*\.md`}
|
|
||||||
// rootDir = "/Users/zhouzhaojiacheng/Desktop/STUDENT_ORG/TechJI/Dev/joj/JOJ3"
|
|
||||||
|
|
||||||
forbids, err := getForbiddens(rootDir, regexList)
|
forbids, err := getForbiddens(rootDir, regexList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonOut.StdOut += "Failed"
|
slog.Error("getting forbiddens", "error", err)
|
||||||
jsonOut.ExitCode = 1
|
return fmt.Errorf("error getting forbiddens: %w", err)
|
||||||
slog.Error("get forbiddens", "error", err)
|
|
||||||
return jsonOut
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var message string
|
var message string
|
||||||
|
@ -84,12 +70,7 @@ func ForbiddenCheck(rootDir string, regexList []string, repo string, droneBranch
|
||||||
message += fmt.Sprint(file, " ")
|
message += fmt.Sprint(file, " ")
|
||||||
}
|
}
|
||||||
message += fmt.Sprint("; do git filter-repo --force --invert-paths --path \\\"\\$i\\\"; done\ngit remote add origin ", repo, "\ngit push --set-upstream origin ", droneBranch, " --force")
|
message += fmt.Sprint("; do git filter-repo --force --invert-paths --path \\\"\\$i\\\"; done\ngit remote add origin ", repo, "\ngit push --set-upstream origin ", droneBranch, " --force")
|
||||||
jsonOut.StdOut += "Failed"
|
return fmt.Errorf(message)
|
||||||
jsonOut.ExitCode = 103
|
|
||||||
jsonOut.StdErr = message
|
|
||||||
return jsonOut
|
|
||||||
} else {
|
|
||||||
jsonOut.StdOut += "OK"
|
|
||||||
return jsonOut
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ func getMetas(rootDir string, fileList []string) ([]string, string, error) {
|
||||||
|
|
||||||
files, err := os.ReadDir(rootDir)
|
files, err := os.ReadDir(rootDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", fmt.Errorf("Error reading directory:%w", err)
|
return nil, "", fmt.Errorf("error reading directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
matched := false
|
matched := false
|
||||||
|
@ -60,28 +60,14 @@ func getMetas(rootDir string, fileList []string) ([]string, string, error) {
|
||||||
|
|
||||||
// metaCheck performs a check for metadata files in the specified root directory.
|
// metaCheck performs a check for metadata files in the specified root directory.
|
||||||
// It prints a message if any required metadata files are missing.
|
// It prints a message if any required metadata files are missing.
|
||||||
func MetaCheck(rootDir string, fileList []string) (jsonOut CheckStage) {
|
func MetaCheck(rootDir string, fileList []string) error {
|
||||||
unmatchedList, umatchedRes, err := getMetas(rootDir, fileList)
|
unmatchedList, umatchedRes, err := getMetas(rootDir, fileList)
|
||||||
jsonOut = CheckStage{
|
|
||||||
Name: "metaFile",
|
|
||||||
StdOut: "Checking the existence of meta file: ",
|
|
||||||
ExitCode: 0,
|
|
||||||
StdErr: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonOut.ExitCode = 1
|
slog.Error("getting metas", "err", err)
|
||||||
slog.Error("get metas", "err", err)
|
return fmt.Errorf("error getting metas: %w", err)
|
||||||
return jsonOut
|
|
||||||
}
|
}
|
||||||
|
if len(unmatchedList) != 0 {
|
||||||
if len(unmatchedList) == 0 {
|
return fmt.Errorf("%d important project files missing\n"+umatchedRes, len(unmatchedList))
|
||||||
jsonOut.StdOut += "OK"
|
|
||||||
return jsonOut
|
|
||||||
} else {
|
|
||||||
jsonOut.ExitCode = 104
|
|
||||||
jsonOut.StdOut += "Failed"
|
|
||||||
jsonOut.StdErr = fmt.Sprintf("%d important project files missing\n"+umatchedRes, len(unmatchedList))
|
|
||||||
return jsonOut
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,31 +61,14 @@ func getNonAscii(root string) ([]string, error) {
|
||||||
|
|
||||||
// nonAsciiFiles checks for non-ASCII characters in files within the specified root directory.
|
// nonAsciiFiles checks for non-ASCII characters in files within the specified root directory.
|
||||||
zzjc123 marked this conversation as resolved
Outdated
张泊明518370910136
commented
And do not just And do not just `panic` on these errors. These errors are not unrecoverable. You can just record these errors and let the program continue working on the other parts. Or just return the error to the upper level and let that function decides what is the next step.
|
|||||||
// It prints a message with the paths to files containing non-ASCII characters, if any.
|
// It prints a message with the paths to files containing non-ASCII characters, if any.
|
||||||
func NonAsciiFiles(root string) (jsonOut CheckStage) {
|
func NonAsciiFiles(root string) error {
|
||||||
jsonOut = CheckStage{
|
|
||||||
Name: "NonAsciiFiles",
|
|
||||||
StdOut: "Checking for non-ascii files: ",
|
|
||||||
ExitCode: 0,
|
|
||||||
StdErr: "",
|
|
||||||
}
|
|
||||||
nonAscii, err := getNonAscii(root)
|
nonAscii, err := getNonAscii(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonOut.StdOut += "Failed"
|
slog.Error("getting non-ascii", "err", err)
|
||||||
jsonOut.ExitCode = 1
|
return fmt.Errorf("error getting non-ascii: %w", err)
|
||||||
slog.Error("get non-ascii", "err", err)
|
|
||||||
return jsonOut
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var nonAsciiRes string
|
|
||||||
if len(nonAscii) > 0 {
|
if len(nonAscii) > 0 {
|
||||||
nonAsciiRes = fmt.Sprintf("Non-ASCII characters found in the following files:\n" + strings.Join(nonAscii, "\n"))
|
return fmt.Errorf("Non-ASCII characters found in the following files:\n" + strings.Join(nonAscii, "\n"))
|
||||||
jsonOut.ExitCode = 105
|
|
||||||
jsonOut.StdOut += "Failed"
|
|
||||||
} else {
|
|
||||||
jsonOut.StdOut += "OK"
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
jsonOut.StdErr = nonAsciiRes
|
|
||||||
|
|
||||||
return jsonOut
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package healthcheck
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
|
@ -19,12 +18,12 @@ func catTags(all []string) (out string) {
|
||||||
func getTagsFromRepo(repoPath string) ([]string, error) {
|
func getTagsFromRepo(repoPath string) ([]string, error) {
|
||||||
repo, err := git.PlainOpen(repoPath)
|
repo, err := git.PlainOpen(repoPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Cannot open repo: %v", err)
|
return nil, fmt.Errorf("error opening repo: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
refs, err := repo.Tags()
|
refs, err := repo.Tags()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Cannot get tags: %v", err)
|
return nil, fmt.Errorf("error getting tags: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var tags []string
|
var tags []string
|
||||||
|
@ -33,27 +32,18 @@ func getTagsFromRepo(repoPath string) ([]string, error) {
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zzjc123 marked this conversation as resolved
Outdated
张泊明518370910136
commented
why not just let the parameter fully determine the target tag? e.g. why not just let the parameter fully determine the target tag? e.g. `func CheckReleases(repoPath, target string)`
汪睿522370910169
commented
Because we cannot decide how many hw or project release need to be checked yes. The int n is used to specif thr number Because we cannot decide how many hw or project release need to be checked yes. The int n is used to specif thr number
张泊明518370910136
commented
So since we need to pass So since we need to pass `category` and `n` to this function, we still need to decide the count of hw&proj when generating `conf.toml`. I do not see much difference.
|
|||||||
return nil, fmt.Errorf("Error while iterating tags: %v", err)
|
return nil, fmt.Errorf("error iterating tags: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return tags, nil
|
return tags, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckReleases(repoPath string, category string, n int) (jsonOut CheckStage) {
|
func CheckReleases(repoPath string, category string, n int) error {
|
||||||
jsonOut = CheckStage{
|
|
||||||
Name: "ReleaseCheck",
|
|
||||||
StdOut: "Checking release tag: ",
|
|
||||||
ExitCode: 0,
|
|
||||||
StdErr: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
tags, err := getTagsFromRepo(repoPath)
|
tags, err := getTagsFromRepo(repoPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error in getting tags: %v", err)
|
return fmt.Errorf("error getting tags: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var prefix string
|
var prefix string
|
||||||
|
|
||||||
switch category {
|
switch category {
|
||||||
case "exam":
|
case "exam":
|
||||||
prefix = "e"
|
prefix = "e"
|
||||||
|
@ -64,7 +54,6 @@ func CheckReleases(repoPath string, category string, n int) (jsonOut CheckStage)
|
||||||
default:
|
default:
|
||||||
prefix = "a"
|
prefix = "a"
|
||||||
}
|
}
|
||||||
|
|
||||||
target := prefix + fmt.Sprintf("%d", n)
|
target := prefix + fmt.Sprintf("%d", n)
|
||||||
found := false
|
found := false
|
||||||
for _, tag := range tags {
|
for _, tag := range tags {
|
||||||
张泊明518370910136
commented
Why is the Why is the `target` release tag wrong?
汪睿522370910169
commented
I intended to mean release tag is missing or wrong. I intended to mean release tag is missing or wrong.
汪睿522370910169
commented
Do we need to specify missing or wrong? I think only we need to inform students that their release is wrong. Do we need to specify missing or wrong? I think only we need to inform students that their release is wrong.
张泊明518370910136
commented
Do we need to test if they submit extra tags? Do we need to test if they submit extra tags?
汪睿522370910169
commented
I think extra is fine, as long as they don't miss any tags. I think extra is fine, as long as they don't miss any tags.
张泊明518370910136
commented
Then just print the error on missing tags. Then just print the error on missing tags.
|
|||||||
|
@ -75,14 +64,7 @@ func CheckReleases(repoPath string, category string, n int) (jsonOut CheckStage)
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
tagList := catTags(tags)
|
tagList := catTags(tags)
|
||||||
jsonOut.ExitCode = 107
|
return fmt.Errorf("Wrong release tag '%s'. Please use one of %s.", target, tagList)
|
||||||
jsonOut.StdOut = "Failed"
|
|
||||||
jsonOut.StdErr = fmt.Sprintf("wrong release tag '%s', please use one of %s aborting", target, tagList)
|
|
||||||
return jsonOut
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
jsonOut.StdOut += "OK"
|
|
||||||
jsonOut.ExitCode = 0
|
|
||||||
jsonOut.StdErr = "Fine"
|
|
||||||
return jsonOut
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package healthcheck
|
package healthcheck
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -9,25 +10,15 @@ import (
|
||||||
|
|
||||||
// RepoSize checks the size of the repository to determine if it is oversized.
|
// 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,
|
// It executes the 'git count-objects -v' command to obtain the size information,
|
||||||
func RepoSize() (jsonOut CheckStage) {
|
func RepoSize() error {
|
||||||
jsonOut = CheckStage{
|
|
||||||
Name: "RepoSize",
|
|
||||||
StdOut: "Checking repository size: ",
|
|
||||||
ExitCode: 0,
|
|
||||||
StdErr: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: reimplement here when go-git is available
|
// TODO: reimplement here when go-git is available
|
||||||
// https://github.com/go-git/go-git/blob/master/COMPATIBILITY.md
|
// https://github.com/go-git/go-git/blob/master/COMPATIBILITY.md
|
||||||
cmd := exec.Command("git", "count-objects", "-v")
|
cmd := exec.Command("git", "count-objects", "-v")
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonOut.StdOut += "Failed"
|
|
||||||
jsonOut.ExitCode = 1
|
|
||||||
slog.Error("running git command:", "err", err)
|
slog.Error("running git command:", "err", err)
|
||||||
return jsonOut
|
return fmt.Errorf("error running git command: %w", err)
|
||||||
bomingzh marked this conversation as resolved
Outdated
张泊明518370910136
commented
add a comment about link to https://github.com/go-git/go-git/blob/master/COMPATIBILITY.md#plumbing-commands , we just can not use go-git to implement it. add a comment about link to https://github.com/go-git/go-git/blob/master/COMPATIBILITY.md#plumbing-commands , we just can not use go-git to implement it.
张泊明518370910136
commented
@manuel :
@manuel :
add a "TODO comment" in the source code with
- a note to reimplement with go-git when available
- link to feature/compatibility page of go-git
|
|||||||
}
|
}
|
||||||
|
|
||||||
lines := strings.Split(string(output), "\n")
|
lines := strings.Split(string(output), "\n")
|
||||||
var sum int
|
var sum int
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
|
@ -36,21 +27,14 @@ func RepoSize() (jsonOut CheckStage) {
|
||||||
sizeStr := fields[1]
|
sizeStr := fields[1]
|
||||||
size, err := strconv.Atoi(sizeStr)
|
size, err := strconv.Atoi(sizeStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonOut.StdOut += "Failed"
|
|
||||||
jsonOut.ExitCode = 1
|
|
||||||
slog.Error("running git command:", "err", err)
|
slog.Error("running git command:", "err", err)
|
||||||
return jsonOut
|
return fmt.Errorf("error running git command: %w", err)
|
||||||
}
|
}
|
||||||
sum += size
|
sum += size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if sum > 2048 {
|
||||||
if sum <= 2048 {
|
return fmt.Errorf("Repository larger than 2MB. Please clean up or contact the teaching team.")
|
||||||
jsonOut.StdOut += "OK"
|
|
||||||
return jsonOut
|
|
||||||
}
|
}
|
||||||
jsonOut.StdOut += "Failed"
|
return nil
|
||||||
jsonOut.ExitCode = 100
|
|
||||||
jsonOut.StdErr = "repository larger than 2MB, please clean up or contact the teaching team."
|
|
||||||
return jsonOut
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,15 +5,6 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// For ExitCode, see https://focs.ji.sjtu.edu.cn/git/TAs/resources/src/branch/drone/dronelib.checks
|
|
||||||
// 1 for unrecoverable error and 0 for succeses
|
|
||||||
type CheckStage struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
StdOut string `json:"stdout"`
|
|
||||||
ExitCode int `json:"exit code"`
|
|
||||||
StdErr string `json:"stderr"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// addExt appends the specified extension to each file name in the given fileList.
|
// addExt appends the specified extension to each file name in the given fileList.
|
||||||
// It modifies the original fileList in place.
|
// It modifies the original fileList in place.
|
||||||
func addExt(fileList []string, ext string) {
|
func addExt(fileList []string, ext string) {
|
||||||
|
|
|
@ -47,68 +47,27 @@ func filesMatch(file1, file2 string) (bool, error) {
|
||||||
return true, nil
|
return true, nil
|
||||||
bomingzh marked this conversation as resolved
Outdated
张泊明518370910136
commented
ditto ditto
|
|||||||
}
|
}
|
||||||
|
|
||||||
// compareDirectories compares the contents of two directories.
|
// VerifyDirectory checks if the contents of two directories are identical.
|
||||||
func compareDirectories(dir1, dir2 string, jsonOut *CheckStage) error {
|
func VerifyDirectory(rootDir, compareDir string) error {
|
||||||
allMatch := true
|
err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
|
||||||
var message string
|
|
||||||
err := filepath.Walk(dir1, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("error walking directory: %w", err)
|
||||||
}
|
}
|
||||||
bomingzh marked this conversation as resolved
Outdated
张泊明518370910136
commented
remove it remove it
|
|||||||
|
|
||||||
if !info.IsDir() {
|
if !info.IsDir() {
|
||||||
relPath, _ := filepath.Rel(dir1, path)
|
relPath, _ := filepath.Rel(rootDir, path)
|
||||||
file2 := filepath.Join(dir2, relPath)
|
file2 := filepath.Join(compareDir, relPath)
|
||||||
|
|
||||||
if !fileExists(file2) {
|
if !fileExists(file2) {
|
||||||
// fmt.Printf("File %s in %s is missing in %s\n, please immediately revert your changes!\n", relPath, dir1, dir2)
|
return fmt.Errorf("File %s in %s is missing in %s. Please immediately revert your changes!\n", relPath, rootDir, compareDir)
|
||||||
message += "File missing"
|
|
||||||
allMatch = false
|
|
||||||
jsonOut.StdOut += "Failed"
|
|
||||||
jsonOut.ExitCode = 101
|
|
||||||
jsonOut.StdErr = message
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fmt.Printf("Checking integrity of %s:\n", path)
|
|
||||||
match, err := filesMatch(path, file2)
|
match, err := filesMatch(path, file2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("error matching files: %w", err)
|
||||||
张泊明518370910136
commented
remove it remove it
张泊明518370910136
commented
I mean remove the I mean remove the `os.Exit(1)`, and also the one on line 55.
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !match {
|
if !match {
|
||||||
// fmt.Printf("File %s in %s is not identical to %s\nPlease revert your changes or contact the teaching team if you have a valid reason for adjusting them.\n", relPath, dir1, dir2)
|
return fmt.Errorf("File %s in %s is not identical to %s. Please revert your changes or contact the teaching team if you have a valid reason for adjusting them.\n", relPath, rootDir, compareDir)
|
||||||
message += "File is not identical"
|
|
||||||
allMatch = false
|
|
||||||
jsonOut.StdOut += "Failed"
|
|
||||||
jsonOut.ExitCode = 101
|
|
||||||
jsonOut.StdErr = message
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
张泊明518370910136
commented
why not just return error on not all passed? why not just return error on not all passed?
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if allMatch {
|
|
||||||
jsonOut.StdOut += "OK!"
|
|
||||||
} else {
|
|
||||||
jsonOut.StdOut += "Failed!"
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify checks if the contents of two directories are identical.
|
|
||||||
func Verify(rootDir, compareDir string) CheckStage {
|
|
||||||
jsonOut := CheckStage{
|
|
||||||
Name: "verifyFile",
|
|
||||||
StdOut: "Checking files to be verified: ",
|
|
||||||
ExitCode: 0,
|
|
||||||
StdErr: "",
|
|
||||||
}
|
|
||||||
err := compareDirectories(rootDir, compareDir, &jsonOut)
|
|
||||||
// fmt.Println("Comparison finished ")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error:", err)
|
|
||||||
}
|
|
||||||
return jsonOut
|
|
||||||
}
|
|
||||||
|
|
is it still TODO?
Done yet