feat: file check
squash all previous commits
This commit is contained in:
parent
52491478a4
commit
05876c290d
28
.gitmodules
vendored
28
.gitmodules
vendored
|
@ -38,6 +38,34 @@
|
||||||
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/FOCS-dev/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/FOCS-dev/JOJ3-examples.git
|
||||||
|
branch = healthcheck/asciifile
|
||||||
|
[submodule "examples/healthcheck/asciimsg"]
|
||||||
|
path = examples/healthcheck/asciimsg
|
||||||
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
|
||||||
|
branch = healthcheck/asciimsg
|
||||||
|
[submodule "examples/healthcheck/forbiddenfile"]
|
||||||
|
path = examples/healthcheck/forbiddenfile
|
||||||
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
|
||||||
|
branch = healthcheck/forbiddenfile
|
||||||
|
[submodule "examples/healthcheck/meta"]
|
||||||
|
path = examples/healthcheck/meta
|
||||||
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
|
||||||
|
branch = healthcheck/meta
|
||||||
|
[submodule "examples/healthcheck/release"]
|
||||||
|
path = examples/healthcheck/release
|
||||||
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
|
||||||
|
branch = healthcheck/release
|
||||||
|
[submodule "examples/healthcheck/reposize"]
|
||||||
|
path = examples/healthcheck/reposize
|
||||||
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
|
||||||
|
branch = healthcheck/reposize
|
||||||
|
[submodule "examples/healthcheck/repoverify"]
|
||||||
|
path = examples/healthcheck/repoverify
|
||||||
|
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/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/FOCS-dev/JOJ3-examples.git
|
||||||
|
|
|
@ -84,3 +84,7 @@ 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.
|
||||||
|
|
||||||
|
### HealthCheck
|
||||||
|
|
||||||
|
The repohealth check will return a json list to for check result. The structure of json file is in `pkg/healthcheck/util.go`
|
||||||
|
|
78
cmd/healthcheck/main.go
Normal file
78
cmd/healthcheck/main.go
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"focs.ji.sjtu.edu.cn/git/FOCS-dev/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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generally, err is used for runtime errors, and checkRes is used for the result of the checks.
|
||||||
|
func main() {
|
||||||
|
var info []healthcheck.CheckStage
|
||||||
|
var gitWhitelist, metaFile, releaseTags []string
|
||||||
|
var tmp healthcheck.CheckStage
|
||||||
|
|
||||||
|
rootDir := flag.String("root", "", "")
|
||||||
|
repo := flag.String("repo", "", "")
|
||||||
|
droneBranch := flag.String("droneBranch", "", "")
|
||||||
|
releaseCategories := flag.String("releaseCategories", "", "")
|
||||||
|
releaseNumber := flag.Int("releaseNumber", 0, "")
|
||||||
|
// FIXME: for drone usage
|
||||||
|
adminDir := flag.String("admin", "", "") // adminDir is for config files
|
||||||
|
parseMultiValueFlag(&gitWhitelist, "whitelist", "")
|
||||||
|
parseMultiValueFlag(&metaFile, "meta", "")
|
||||||
|
parseMultiValueFlag(&releaseTags, "releaseTags", "")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
tmp = healthcheck.RepoSize()
|
||||||
|
info = append(info, tmp)
|
||||||
|
|
||||||
|
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 {
|
||||||
|
fmt.Fprint(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Printf("%s", jsonData)
|
||||||
|
}
|
|
@ -14,6 +14,18 @@ import (
|
||||||
|
|
||||||
func compareStageResults(t *testing.T, actual, expected []stage.StageResult, regex bool) {
|
func compareStageResults(t *testing.T, actual, expected []stage.StageResult, regex bool) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
// For Test
|
||||||
|
fmt.Println("Actual:")
|
||||||
|
for _, result := range actual {
|
||||||
|
fmt.Println(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Expected:")
|
||||||
|
for _, result := range expected {
|
||||||
|
fmt.Println(result)
|
||||||
|
}
|
||||||
|
|
||||||
if len(actual) != len(expected) {
|
if len(actual) != len(expected) {
|
||||||
t.Fatalf("len(actual) = %d, expected %d", len(actual), len(expected))
|
t.Fatalf("len(actual) = %d, expected %d", len(actual), len(expected))
|
||||||
}
|
}
|
||||||
|
|
1
examples/healthcheck/asciifile
Submodule
1
examples/healthcheck/asciifile
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 46804afd2ebe8787a9d43711b191e424134ce25b
|
1
examples/healthcheck/asciimsg
Submodule
1
examples/healthcheck/asciimsg
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit e9ed6a464a507730ef956bb8ea6dbe84fffea7ad
|
1
examples/healthcheck/forbiddenfile
Submodule
1
examples/healthcheck/forbiddenfile
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 245d036af0cbfe3745ef303d239a2d7225067d0b
|
1
examples/healthcheck/meta
Submodule
1
examples/healthcheck/meta
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 4f5e444940d2c383a0b069405e2ef42b01878bc5
|
1
examples/healthcheck/release
Submodule
1
examples/healthcheck/release
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit a4cedea002a198c2dae373f7ee6f6dc67753fae6
|
1
examples/healthcheck/reposize
Submodule
1
examples/healthcheck/reposize
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit d78308bcaaaeda9ead86069652288ae911503e33
|
1
examples/healthcheck/repoverify
Submodule
1
examples/healthcheck/repoverify
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit a7a3bd0894c2e727e3ab3f9ddda3d438fbb86b30
|
|
@ -5,6 +5,7 @@ import (
|
||||||
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/cppcheck"
|
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/cppcheck"
|
||||||
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/cpplint"
|
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/cpplint"
|
||||||
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/diff"
|
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/diff"
|
||||||
|
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/healthcheck"
|
||||||
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/keyword"
|
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/keyword"
|
||||||
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/resultstatus"
|
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/resultstatus"
|
||||||
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/sample"
|
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/sample"
|
||||||
|
|
9
internal/parsers/healthcheck/meta.go
Normal file
9
internal/parsers/healthcheck/meta.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package healthcheck
|
||||||
|
|
||||||
|
import "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
||||||
|
|
||||||
|
var name = "healthcheck"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
stage.RegisterParser(name, &Healthcheck{})
|
||||||
|
}
|
49
internal/parsers/healthcheck/parser.go
Normal file
49
internal/parsers/healthcheck/parser.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package healthcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
// "encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
|
||||||
|
// "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/pkg/healthcheck"
|
||||||
|
"github.com/criyle/go-judge/envexec"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Conf struct {
|
||||||
|
Score int
|
||||||
|
Comment string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Healthcheck 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,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stage.ParserResult{
|
||||||
|
Score: 0,
|
||||||
|
Comment: stdout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Healthcheck) 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
|
||||||
|
}
|
|
@ -57,13 +57,13 @@ func (*Keyword) Run(results []stage.ExecutorResult, confAny any) (
|
||||||
return nil, true, err
|
return nil, true, err
|
||||||
}
|
}
|
||||||
var res []stage.ParserResult
|
var res []stage.ParserResult
|
||||||
forceQuit := false
|
end := false
|
||||||
for _, result := range results {
|
for _, result := range results {
|
||||||
tmp, matched := Parse(result, *conf)
|
tmp, matched := Parse(result, *conf)
|
||||||
if matched && conf.EndOnMatch {
|
if matched && conf.EndOnMatch {
|
||||||
forceQuit = true
|
end = true
|
||||||
}
|
}
|
||||||
res = append(res, tmp)
|
res = append(res, tmp)
|
||||||
}
|
}
|
||||||
return res, forceQuit, nil
|
return res, end, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,12 +19,12 @@ func (*ResultStatus) Run(results []stage.ExecutorResult, confAny any) (
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, true, err
|
return nil, true, err
|
||||||
}
|
}
|
||||||
forceQuit := false
|
end := false
|
||||||
var res []stage.ParserResult
|
var res []stage.ParserResult
|
||||||
for _, result := range results {
|
for _, result := range results {
|
||||||
comment := ""
|
comment := ""
|
||||||
if result.Status != stage.Status(envexec.StatusAccepted) {
|
if result.Status != stage.Status(envexec.StatusAccepted) {
|
||||||
forceQuit = true
|
end = true
|
||||||
comment = fmt.Sprintf(
|
comment = fmt.Sprintf(
|
||||||
"Unexpected executor status: %s.", result.Status,
|
"Unexpected executor status: %s.", result.Status,
|
||||||
)
|
)
|
||||||
|
@ -34,5 +34,5 @@ func (*ResultStatus) Run(results []stage.ExecutorResult, confAny any) (
|
||||||
Comment: comment,
|
Comment: comment,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return res, forceQuit, nil
|
return res, end, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,5 +166,4 @@ type ParserResult struct {
|
||||||
type StageResult struct {
|
type StageResult struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Results []ParserResult `json:"results"`
|
Results []ParserResult `json:"results"`
|
||||||
ForceQuit bool `json:"force_quit"`
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ func Run(stages []Stage) []StageResult {
|
||||||
slog.Error("parser not found", "name", stage.ParserName)
|
slog.Error("parser not found", "name", stage.ParserName)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
parserResults, forceQuit, err := parser.Run(executorResults, stage.ParserConf)
|
parserResults, end, 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.ExecutorName, "error", err)
|
||||||
break
|
break
|
||||||
|
@ -35,9 +35,8 @@ func Run(stages []Stage) []StageResult {
|
||||||
stageResults = append(stageResults, StageResult{
|
stageResults = append(stageResults, StageResult{
|
||||||
Name: stage.Name,
|
Name: stage.Name,
|
||||||
Results: parserResults,
|
Results: parserResults,
|
||||||
ForceQuit: forceQuit,
|
|
||||||
})
|
})
|
||||||
if forceQuit {
|
if end {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
83
pkg/healthcheck/commit.go
Normal file
83
pkg/healthcheck/commit.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
package healthcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing/object"
|
||||||
|
)
|
||||||
|
|
||||||
|
// nonAsciiMsg checks for non-ASCII characters in the commit message.
|
||||||
|
// If the message starts with "Merge pull request", it skips the non-ASCII characters check.
|
||||||
|
// 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.
|
||||||
|
// Otherwise, it returns nil indicating that the commit message is valid.
|
||||||
|
func NonAsciiMsg(root string) (jsonOut CheckStage) {
|
||||||
|
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")
|
||||||
|
repo, err := git.PlainOpen(root)
|
||||||
|
if err != nil {
|
||||||
|
jsonOut.StdOut += "Failed"
|
||||||
|
jsonOut.ExitCode = 1
|
||||||
|
jsonOut.ErrorLog = fmt.Errorf("Error openning git repo%w", err)
|
||||||
|
return jsonOut
|
||||||
|
}
|
||||||
|
|
||||||
|
ref, err := repo.Head()
|
||||||
|
if err != nil {
|
||||||
|
jsonOut.StdOut += "Failed"
|
||||||
|
jsonOut.ExitCode = 1
|
||||||
|
jsonOut.ErrorLog = fmt.Errorf("Error getting reference%w", err)
|
||||||
|
return jsonOut
|
||||||
|
}
|
||||||
|
commits, err := repo.Log(&git.LogOptions{From: ref.Hash()})
|
||||||
|
if err != nil {
|
||||||
|
jsonOut.StdOut += "Failed"
|
||||||
|
jsonOut.ExitCode = 1
|
||||||
|
jsonOut.ErrorLog = fmt.Errorf("Error getting commits%w", err)
|
||||||
|
return jsonOut
|
||||||
|
}
|
||||||
|
|
||||||
|
var msgs []string
|
||||||
|
err = commits.ForEach(func(c *object.Commit) error {
|
||||||
|
msgs = append(msgs, c.Message)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
jsonOut.StdOut += "Failed"
|
||||||
|
jsonOut.ExitCode = 1
|
||||||
|
jsonOut.ErrorLog = fmt.Errorf("Error converting log to string%w", err)
|
||||||
|
return jsonOut
|
||||||
|
}
|
||||||
|
|
||||||
|
var nonAsciiMsgs []string
|
||||||
|
for _, msg := range msgs {
|
||||||
|
if msg == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(msg, "Merge pull request") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, c := range msg {
|
||||||
|
if c > unicode.MaxASCII {
|
||||||
|
nonAsciiMsgs = append(nonAsciiMsgs, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(nonAsciiMsgs) > 0 {
|
||||||
|
jsonOut.StdOut += "Failed"
|
||||||
|
jsonOut.ExitCode = 105
|
||||||
|
jsonOut.StdErr = "Non-ASCII characters in commit messages:\n" + strings.Join(nonAsciiMsgs, "\n")
|
||||||
|
return jsonOut
|
||||||
|
}
|
||||||
|
jsonOut.StdOut += "OK"
|
||||||
|
return jsonOut
|
||||||
|
}
|
94
pkg/healthcheck/forbidden.go
Normal file
94
pkg/healthcheck/forbidden.go
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
package healthcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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) ([]string, error) {
|
||||||
|
var matches []string
|
||||||
|
|
||||||
|
var regexList []*regexp.Regexp
|
||||||
|
regexList, err := getRegex(fileList)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error compiling regex:", err)
|
||||||
|
return nil, 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" {
|
||||||
|
return filepath.SkipDir
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, repo string, droneBranch string) (jsonOut CheckStage) {
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
jsonOut.StdOut += "Failed"
|
||||||
|
jsonOut.ExitCode = 1
|
||||||
|
jsonOut.ErrorLog = err
|
||||||
|
return jsonOut
|
||||||
|
}
|
||||||
|
|
||||||
|
var message string
|
||||||
|
|
||||||
|
if len(forbids) > 0 {
|
||||||
|
message += fmt.Sprint(103, "the following forbidden files were found: ")
|
||||||
|
for _, file := range forbids {
|
||||||
|
message += fmt.Sprint(file, ", ")
|
||||||
|
}
|
||||||
|
message += "\n\nTo fix it, first make a backup of your repository and then run the following commands:\nfor i in "
|
||||||
|
for _, file := range forbids {
|
||||||
|
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")
|
||||||
|
jsonOut.StdOut += "Failed"
|
||||||
|
jsonOut.ExitCode = 103
|
||||||
|
jsonOut.StdErr = message
|
||||||
|
return jsonOut
|
||||||
|
} else {
|
||||||
|
jsonOut.StdOut += "OK"
|
||||||
|
return jsonOut
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
package healthcheck
|
|
86
pkg/healthcheck/meta.go
Normal file
86
pkg/healthcheck/meta.go
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
package healthcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"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 subsitution 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) (jsonOut CheckStage) {
|
||||||
|
unmatchedList, umatchedRes, err := getMetas(rootDir, fileList)
|
||||||
|
jsonOut = CheckStage{
|
||||||
|
Name: "metaFile",
|
||||||
|
StdOut: "Checking the existence of meta file: ",
|
||||||
|
ExitCode: 0,
|
||||||
|
StdErr: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
jsonOut.ExitCode = 1
|
||||||
|
jsonOut.ErrorLog = err
|
||||||
|
return jsonOut
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(unmatchedList) == 0 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
90
pkg/healthcheck/nonascii.go
Normal file
90
pkg/healthcheck/nonascii.go
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
package healthcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"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) ([]string, error) {
|
||||||
|
var nonAscii []string
|
||||||
|
|
||||||
|
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" {
|
||||||
|
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) (jsonOut CheckStage) {
|
||||||
|
jsonOut = CheckStage{
|
||||||
|
Name: "NonAsciiFiles",
|
||||||
|
StdOut: "Checking for non-ascii files: ",
|
||||||
|
ExitCode: 0,
|
||||||
|
StdErr: "",
|
||||||
|
}
|
||||||
|
nonAscii, err := getNonAscii(root)
|
||||||
|
if err != nil {
|
||||||
|
jsonOut.StdOut += "Failed"
|
||||||
|
jsonOut.ExitCode = 1
|
||||||
|
jsonOut.ErrorLog = err
|
||||||
|
return jsonOut
|
||||||
|
}
|
||||||
|
|
||||||
|
var nonAsciiRes string
|
||||||
|
if len(nonAscii) > 0 {
|
||||||
|
nonAsciiRes = fmt.Sprintf("Non-ASCII characters found in the following files:\n" + strings.Join(nonAscii, "\n"))
|
||||||
|
jsonOut.ExitCode = 105
|
||||||
|
jsonOut.StdOut += "Failed"
|
||||||
|
} else {
|
||||||
|
jsonOut.StdOut += "OK"
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonOut.StdErr = nonAsciiRes
|
||||||
|
|
||||||
|
return jsonOut
|
||||||
|
}
|
88
pkg/healthcheck/release.go
Normal file
88
pkg/healthcheck/release.go
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
package healthcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func catTags(all []string) (out string) {
|
||||||
|
out = ""
|
||||||
|
for _, str := range all {
|
||||||
|
out += str + " "
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTagsFromRepo(repoPath string) ([]string, error) {
|
||||||
|
repo, err := git.PlainOpen(repoPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Cannot open repo: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
refs, err := repo.Tags()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Cannot get 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 while iterating tags: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tags, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckReleases(repoPath string, category string, n int) (jsonOut CheckStage) {
|
||||||
|
jsonOut = CheckStage{
|
||||||
|
Name: "ReleaseCheck",
|
||||||
|
StdOut: "Checking release tag: ",
|
||||||
|
ExitCode: 0,
|
||||||
|
StdErr: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
tags, err := getTagsFromRepo(repoPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error in 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 {
|
||||||
|
tagList := catTags(tags)
|
||||||
|
jsonOut.ExitCode = 107
|
||||||
|
jsonOut.StdOut = "Failed"
|
||||||
|
jsonOut.StdErr = fmt.Sprintf("wrong release tag '%s', please use one of %s aborting", target, tagList)
|
||||||
|
return jsonOut
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonOut.StdOut += "OK"
|
||||||
|
jsonOut.ExitCode = 0
|
||||||
|
jsonOut.StdErr = "Fine"
|
||||||
|
return jsonOut
|
||||||
|
}
|
56
pkg/healthcheck/reposize.go
Normal file
56
pkg/healthcheck/reposize.go
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package healthcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"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() (jsonOut CheckStage) {
|
||||||
|
jsonOut = CheckStage{
|
||||||
|
Name: "RepoSize",
|
||||||
|
StdOut: "Checking repository size: ",
|
||||||
|
ExitCode: 0,
|
||||||
|
StdErr: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
jsonOut.StdOut += "Failed"
|
||||||
|
jsonOut.ExitCode = 1
|
||||||
|
jsonOut.ErrorLog = fmt.Errorf("Error running git command:%w", err)
|
||||||
|
return jsonOut
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
jsonOut.StdOut += "Failed"
|
||||||
|
jsonOut.ExitCode = 1
|
||||||
|
jsonOut.ErrorLog = fmt.Errorf("Error running git command:%w", err)
|
||||||
|
return jsonOut
|
||||||
|
}
|
||||||
|
sum += size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sum <= 2048 {
|
||||||
|
jsonOut.StdOut += "OK"
|
||||||
|
return jsonOut
|
||||||
|
}
|
||||||
|
jsonOut.StdOut += "Failed"
|
||||||
|
jsonOut.ExitCode = 100
|
||||||
|
jsonOut.StdErr = "repository larger than 2MB, please clean up or contact the teaching team."
|
||||||
|
return jsonOut
|
||||||
|
}
|
39
pkg/healthcheck/utils.go
Normal file
39
pkg/healthcheck/utils.go
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package healthcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"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 of check"`
|
||||||
|
StdOut string `json:"stdout"`
|
||||||
|
ExitCode int `json:"exit code"`
|
||||||
|
StdErr string `json:"stderr"`
|
||||||
|
ErrorLog error `json:"errorLog"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
114
pkg/healthcheck/verify.go
Normal file
114
pkg/healthcheck/verify.go
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
package healthcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fileExists checks if a file exists at the specified path.
|
||||||
|
func fileExists(filePath string) bool {
|
||||||
|
_, err := os.Stat(filePath)
|
||||||
|
return !os.IsNotExist(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// filesMatch checks if two files are identical.
|
||||||
|
func filesMatch(file1, file2 string) (bool, error) {
|
||||||
|
f1, err := os.Open(file1)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer f1.Close()
|
||||||
|
|
||||||
|
f2, err := os.Open(file2)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer f2.Close()
|
||||||
|
|
||||||
|
scanner1 := bufio.NewScanner(f1)
|
||||||
|
scanner2 := bufio.NewScanner(f2)
|
||||||
|
|
||||||
|
for scanner1.Scan() && scanner2.Scan() {
|
||||||
|
line1 := scanner1.Text()
|
||||||
|
line2 := scanner2.Text()
|
||||||
|
|
||||||
|
if line1 != line2 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if scanner1.Scan() || scanner2.Scan() {
|
||||||
|
// One file has more lines than the other
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// compareDirectories compares the contents of two directories.
|
||||||
|
func compareDirectories(dir1, dir2 string, jsonOut *CheckStage) error {
|
||||||
|
allMatch := true
|
||||||
|
var message string
|
||||||
|
err := filepath.Walk(dir1, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.IsDir() {
|
||||||
|
relPath, _ := filepath.Rel(dir1, path)
|
||||||
|
file2 := filepath.Join(dir2, relPath)
|
||||||
|
|
||||||
|
if !fileExists(file2) {
|
||||||
|
// fmt.Printf("File %s in %s is missing in %s\n, please immediately revert your changes!\n", relPath, dir1, dir2)
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
message += "File is not identical"
|
||||||
|
allMatch = false
|
||||||
|
jsonOut.StdOut += "Failed"
|
||||||
|
jsonOut.ExitCode = 101
|
||||||
|
jsonOut.StdErr = message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if allMatch {
|
||||||
|
jsonOut.StdOut += "OK!"
|
||||||
|
} else {
|
||||||
|
jsonOut.StdOut += "Failed!"
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user