feat(gitea): add lint-pr command
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is passing

lint-pr runs golangci-lint, captures the result and uses it directly without temporary files
It also allows to automatically retrieve the PRs base commit and run golangci-lint only against changes in the current PR
This commit is contained in:
Peter 2023-03-09 12:04:11 +01:00
parent 1707692170
commit c1c1667d9f
Signed by: prskr
GPG key ID: C1DB5D2E8DB512F9
16 changed files with 570 additions and 83 deletions

View file

@ -14,38 +14,13 @@ steps:
image: docker.io/golang:1.20-bullseye image: docker.io/golang:1.20-bullseye
network_mode: host network_mode: host
commands: commands:
- apt-get update && apt-get install -y jq
- go mod download - go mod download
- go install "github.com/vektra/mockery/v2@$(curl https://api.github.com/repos/vektra/mockery/releases | jq -r '. | first | .tag_name')" - go run github.com/magefile/mage -d build -w . generate
- go generate ./...
volumes: volumes:
- name: go-cache - name: go-cache
path: /go path: /go
- name: Lint PR - name: Lint
image: docker.io/golangci/golangci-lint:latest
network_mode: host
environment:
CGO_ENABLED: "0"
GOMEMLIMIT: "1150MiB"
NITTER_BASE_ADDRESS: https://code.icb4dc0.de
NITTER_TOKEN:
from_secret: gitea_token
when:
event:
- pull_request
commands:
- apt-get update && apt-get install -y jq
- mkdir out
- golangci-lint run --out-format json --issues-exit-code 0 --new-from-rev "$(curl https://code.icb4dc0.de/api/v1/repos/prskr/nitter/pulls/1 | jq -r '.base.sha')" > out/results.json
- go run main.go gitea pr --namespace "$${DRONE_REPO_NAMESPACE}" --repo "$${DRONE_REPO_NAME}" --result-file out/results.json --pull-index "$${DRONE_PULL_REQUEST}"
depends_on:
- Setup
volumes:
- name: go-cache
path: /go
- name: Lint repo
image: docker.io/golangci/golangci-lint:latest image: docker.io/golangci/golangci-lint:latest
environment: environment:
CGO_ENABLED: "0" CGO_ENABLED: "0"
@ -53,13 +28,8 @@ steps:
NITTER_BASE_ADDRESS: https://code.icb4dc0.de NITTER_BASE_ADDRESS: https://code.icb4dc0.de
NITTER_TOKEN: NITTER_TOKEN:
from_secret: gitea_token from_secret: gitea_token
when:
event:
- push
- tag
commands: commands:
- mkdir out - go run github.com/magefile/mage -d build -w . lint
- golangci-lint run -v
depends_on: depends_on:
- Setup - Setup
volumes: volumes:

3
.gitignore vendored
View file

@ -21,7 +21,4 @@ mock_*_test.go
# vendor/ # vendor/
out/ out/
# Go workspace file
go.work
.idea/ .idea/

33
build/format.go Normal file
View file

@ -0,0 +1,33 @@
//go:build mage
package main
import (
"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
)
func Format() {
mg.Deps(GoImports)
mg.Deps(GoFumpt)
}
func GoImports() error {
if err := ensureGoTool("goimports", "golang.org/x/tools/cmd/goimports", "latest"); err != nil {
return err
}
return sh.RunV(
"goimports",
"-local=inetmock.icb4dc0.de/inetmock",
"-w",
WorkingDir,
)
}
func GoFumpt() error {
if err := ensureGoTool("gofumpt", "mvdan.cc/gofumpt", "latest"); err != nil {
return err
}
return sh.RunV("gofumpt", "-l", "-w", WorkingDir)
}

47
build/generate.go Normal file
View file

@ -0,0 +1,47 @@
//go:build mage
package main
import (
"context"
"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
"github.com/magefile/mage/target"
"golang.org/x/exp/slog"
)
func Generate(ctx context.Context) error {
mockeryVersion, err := getLatestReleaseTag(ctx, "vektra/mockery")
if err != nil {
return err
}
if err := ensureGoTool("mockery", "github.com/vektra/mockery/v2", mockeryVersion); err != nil {
return err
}
mg.Deps(GenerateGo)
return nil
}
func GenerateGo() error {
lastMockGeneration, err := target.NewestModTime(GeneratedMockFiles...)
if err != nil {
return err
}
lastSourceModification, err := target.NewestModTime(GoSourceFiles...)
if err != nil {
return err
}
slog.Debug("Determined last time mocks where generated", slog.Time("lastMockGeneration", lastMockGeneration))
if lastMockGeneration.After(lastSourceModification) {
slog.Info("Skipping unnecessary 'go generate' invocation")
return nil
}
return sh.RunV("go", "generate", "-x", "./...")
}

16
build/go.mod Normal file
View file

@ -0,0 +1,16 @@
module build
go 1.20
require (
github.com/carlmjohnson/requests v0.23.2
github.com/magefile/mage v1.14.0
golang.org/x/exp v0.0.0-20230307190834-24139beb5833
golang.org/x/sync v0.1.0
)
require (
code.gitea.io/sdk/gitea v0.15.1 // indirect
github.com/hashicorp/go-version v1.2.1 // indirect
golang.org/x/net v0.5.0 // indirect
)

38
build/go.sum Normal file
View file

@ -0,0 +1,38 @@
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
code.gitea.io/sdk/gitea v0.15.1 h1:WJreC7YYuxbn0UDaPuWIe/mtiNKTvLN8MLkaw71yx/M=
code.gitea.io/sdk/gitea v0.15.1/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMVoHRBA=
github.com/carlmjohnson/requests v0.23.2 h1:SzaY+/5v8QOvt++7HTXe1xgmIb3wc/bYf2QJmrO73sM=
github.com/carlmjohnson/requests v0.23.2/go.mod h1:09VwhOaRQYCraJcByjEuvuOGO1jxUjIx6vnAEkt2ges=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s=
golang.org/x/exp v0.0.0-20230307190834-24139beb5833/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

42
build/lint.go Normal file
View file

@ -0,0 +1,42 @@
//go:build mage
package main
import (
"context"
"fmt"
"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
)
func Lint(ctx context.Context) {
mg.CtxDeps(ctx, Generate)
mg.CtxDeps(ctx, Format)
mg.Deps(LintGo)
}
func LintGo() (err error) {
if IsPRBuild {
return lintGoPR()
}
return sh.RunV(
"golangci-lint",
"run",
"-v",
"--issues-exit-code=1",
)
}
func lintGoPR() error {
return GoRun(
"main.go",
"gitea",
"lint-pr",
"--from-pr-base",
fmt.Sprintf("--namespace=%s", repoOwner),
fmt.Sprintf("--repo=%s", repoName),
fmt.Sprintf("--pull-index=%d", prIndex),
)
}

99
build/setup.go Normal file
View file

@ -0,0 +1,99 @@
//go:build mage
package main
import (
"io/fs"
"os"
"path/filepath"
"strconv"
"strings"
"golang.org/x/exp/slices"
"golang.org/x/exp/slog"
)
var (
WorkingDir string
GoSourceFiles []string
GeneratedMockFiles []string
IsReleaseBuild bool
IsPRBuild bool
repoOwner = "prskr"
repoName = "nitter"
prIndex int64 = 1
dirsToIgnore = []string{
".git",
"build",
".run",
".task",
}
)
func init() {
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr)))
IsReleaseBuild = strings.EqualFold(os.Getenv("DRONE_BUILD_EVENT"), "tag")
IsPRBuild = strings.EqualFold(os.Getenv("DRONE_BUILD_EVENT"), "pull_request")
if wd, err := os.Getwd(); err != nil {
slog.Error("Failed to get working directory", err)
os.Exit(1)
} else {
WorkingDir = wd
}
if owner := os.Getenv("DRONE_REPO_NAMESPACE"); owner != "" {
repoOwner = owner
}
if name := os.Getenv("DRONE_REPO_NAME"); name != "" {
repoName = name
}
if prIdxRaw := os.Getenv("DRONE_PULL_REQUEST"); prIdxRaw != "" {
if parsed, err := strconv.ParseInt(prIdxRaw, 10, 64); err != nil {
slog.Error("Failed to parse PR index", err)
os.Exit(1)
} else {
prIndex = parsed
}
}
if err := initSourceFiles(); err != nil {
slog.Error("Failed to init source files", err)
os.Exit(1)
}
slog.Info("Completed initialization")
}
func initSourceFiles() error {
return filepath.WalkDir(WorkingDir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
if slices.Contains(dirsToIgnore, filepath.Base(path)) {
return fs.SkipDir
}
return nil
}
_, ext, found := strings.Cut(filepath.Base(path), ".")
if !found {
return nil
}
switch ext {
case "mock.go":
GeneratedMockFiles = append(GeneratedMockFiles, path)
case "go":
GoSourceFiles = append(GoSourceFiles, path)
}
return nil
})
}

71
build/tools.go Normal file
View file

@ -0,0 +1,71 @@
//go:build mage
package main
import (
"context"
"fmt"
"os/exec"
"path"
"path/filepath"
"github.com/carlmjohnson/requests"
"github.com/magefile/mage/sh"
"golang.org/x/exp/slog"
)
var (
GoReleaser = sh.RunCmd("goreleaser")
GoInstall = sh.RunCmd("go", "install")
GoBuild = sh.RunCmd("go", "build")
GoRun = sh.RunCmd("go", "run")
)
func getLatestReleaseTag(ctx context.Context, repo string) (tag string, err error) {
type release struct {
TagName string `json:"tag_name"`
}
var releases []release
err = requests.
URL(fmt.Sprintf("https://%s", path.Join("api.github.com/repos", repo, "releases"))).
ToJSON(&releases).
Fetch(ctx)
if err != nil {
return "", err
}
if len(releases) < 1 {
return "", fmt.Errorf("no release found for repo %s", repo)
}
return releases[0].TagName, nil
}
func ensureURLTool(ctx context.Context, toolName, downloadURL string) error {
return checkForTool(toolName, func() error {
return requests.
URL(downloadURL).
ToFile(filepath.Join("/", "usr", "local", "bin", toolName)).
Fetch(ctx)
})
}
func ensureGoTool(toolName, importPath, version string) error {
return checkForTool(toolName, func() error {
toolToInstall := fmt.Sprintf("%s@%s", importPath, version)
slog.Info("Installing Go tool", slog.String("toolToInstall", toolToInstall))
return GoInstall(toolToInstall)
})
}
func checkForTool(toolName string, fallbackAction func() error) error {
if _, err := exec.LookPath(toolName); err != nil {
slog.Warn("tool is missing", slog.String("toolName", toolName))
return fallbackAction()
}
return nil
}

6
go.work Normal file
View file

@ -0,0 +1,6 @@
go 1.20
use (
.
./build
)

9
go.work.sum Normal file
View file

@ -0,0 +1,9 @@
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

View file

@ -1,12 +1,44 @@
package commands package commands
import ( import (
gosdk "code.gitea.io/sdk/gitea" "fmt"
"strings"
giteaSdk "code.gitea.io/sdk/gitea"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"code.icb4dc0.de/prskr/nitter/nitters/gitea" "code.icb4dc0.de/prskr/nitter/nitters/gitea"
) )
type reviewStateTypeValue giteaSdk.ReviewStateType
func (r *reviewStateTypeValue) String() string {
return string(*r)
}
func (r *reviewStateTypeValue) Set(s string) error {
switch strings.ToUpper(s) {
case string(giteaSdk.ReviewStateApproved):
*r = reviewStateTypeValue(giteaSdk.ReviewStateApproved)
return nil
case string(giteaSdk.ReviewStateRequestChanges):
*r = reviewStateTypeValue(giteaSdk.ReviewStateRequestChanges)
return nil
case string(giteaSdk.ReviewStateComment):
*r = reviewStateTypeValue(giteaSdk.ReviewStateComment)
return nil
default:
return fmt.Errorf("value %s doesn't match any expected state type", s)
}
}
func (r *reviewStateTypeValue) Type() string {
return "string"
}
var errorReviewStateType = reviewStateTypeValue(giteaSdk.ReviewStateComment)
//nolint:lll //flag setup causes long lines
func Gitea() *cobra.Command { func Gitea() *cobra.Command {
giteaCmd := &cobra.Command{ giteaCmd := &cobra.Command{
Use: "gitea", Use: "gitea",
@ -18,8 +50,55 @@ func Gitea() *cobra.Command {
pr := &cobra.Command{ pr := &cobra.Command{
Use: "pull-request", Use: "pull-request",
Aliases: []string{"merge-request", "pr", "mr", "pull"}, Short: "Read golangci-lint result and create a PR review",
RunE: func(cmd *cobra.Command, args []string) error { Aliases: []string{"merge-request", "pr", "pull"},
RunE: runGiteaPRNitting,
}
pr.Flags().Int64P(
"pull-index",
"i",
-1,
"PR index to add reviews to - note, this is not the ID of the PR but its number [$NITTER_PULL_INDEX]",
)
pr.Flags().Var(&errorReviewStateType, "review-state", "state in which to create the review if there are issues [comment,approved,]")
pr.Flags().StringP("result-file", "f", "", "path to the golangci-lint JSON output [$NITTER_RESULT_FILE]")
lintPr := &cobra.Command{
Use: "lint-pull-request",
Short: "Run golangci-lint directly and create a PR review based on the captured results - required golangci-lint being in $PATH",
Aliases: []string{"lint-pr", "lint-pull"},
RunE: runGiteaPRLintNitting,
}
lintPr.Flags().Var(&errorReviewStateType, "review-state", "state in which to create the review if there are issues [comment,approved,]")
lintPr.Flags().Int64P(
"pull-index",
"i",
-1,
"PR index to add reviews to - note, this is not the ID of the PR but its number [$NITTER_PULL_INDEX]",
)
lintPr.Flags().Bool(
"from-pr-base",
false,
"if enabled, nitter will get the PRs metadata and start golangci-lint with --new-from-rev=<PR base commit>",
)
lintPr.Flags().StringSlice(
"golangci-lint-extra-args",
nil,
"Extra args to append to golangci-lint run commands, keep in mind that out-format will be set to JSON and --new-from-rev if you enable --from-pr-base",
)
giteaCmd.AddCommand(pr, lintPr)
return giteaCmd
}
func runGiteaPRNitting(cmd *cobra.Command, args []string) error {
cfg, err := LoadConfig[gitea.GiteaPRConfig](cmd.Flags()) cfg, err := LoadConfig[gitea.GiteaPRConfig](cmd.Flags())
if err != nil { if err != nil {
return err return err
@ -34,7 +113,7 @@ func Gitea() *cobra.Command {
return err return err
} }
giteaClient, err := gosdk.NewClient(cfg.BaseAddress, gosdk.SetContext(cmd.Context()), gosdk.SetToken(cfg.Token)) giteaClient, err := giteaSdk.NewClient(cfg.BaseAddress, giteaSdk.SetContext(cmd.Context()), giteaSdk.SetToken(cfg.Token))
if err != nil { if err != nil {
return err return err
} }
@ -42,17 +121,38 @@ func Gitea() *cobra.Command {
nitter := gitea.NewPRNitter(giteaClient, cfg) nitter := gitea.NewPRNitter(giteaClient, cfg)
return nitter.Report(report, issues) return nitter.Report(report, issues)
}, }
func runGiteaPRLintNitting(cmd *cobra.Command, args []string) error {
cfg, err := LoadConfig[gitea.GiteaPRLintConfig](cmd.Flags())
if err != nil {
return err
} }
pr.Flags().Int64P( if err = cfg.Validate(); err != nil {
"pull-index", return err
"i", }
-1,
"PR index to add reviews to - note, this is not the ID of the PR but its number [$NITTER_PULL_INDEX]",
)
giteaCmd.AddCommand(pr) giteaClient, err := giteaSdk.NewClient(cfg.BaseAddress, giteaSdk.SetContext(cmd.Context()), giteaSdk.SetToken(cfg.Token))
if err != nil {
return err
}
return giteaCmd if cfg.FromPRBase {
var pr *giteaSdk.PullRequest
if pr, _, err = giteaClient.GetPullRequest(cfg.Namespace, cfg.Repo, cfg.PRIndex); err != nil {
return err
} else {
cfg.ExtraArgs = append(cfg.ExtraArgs, fmt.Sprintf("--new-from-rev=%s", pr.Base.Sha))
}
}
report, issues, err := RunGolangCiLint(cmd.Context(), cfg.ExtraArgs)
if err != nil {
return err
}
nitter := gitea.NewPRNitter(giteaClient, &cfg.GiteaPRConfig)
return nitter.Report(report, issues)
} }

View file

@ -1,10 +1,14 @@
package commands package commands
import ( import (
"bytes"
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"io" "io"
"os" "os"
"os/exec"
"github.com/golangci/golangci-lint/pkg/printers" "github.com/golangci/golangci-lint/pkg/printers"
"github.com/golangci/golangci-lint/pkg/report" "github.com/golangci/golangci-lint/pkg/report"
@ -39,3 +43,44 @@ func ReadResults(reader io.Reader) (*report.Data, []result.Issue, error) {
return printerResult.Report, printerResult.Issues, nil return printerResult.Report, printerResult.Issues, nil
} }
func RunGolangCiLint(ctx context.Context, extraFlags []string) (*report.Data, []result.Issue, error) {
const defaultArgsLen = 3
golangCiLintArgs := make([]string, 1, defaultArgsLen+len(extraFlags))
golangCiLintArgs[0] = "run"
golangCiLintArgs = append(golangCiLintArgs, extraFlags...)
golangCiLintArgs = append(golangCiLintArgs, "--issues-exit-code=0", "--out-format=json")
golangCiLintArgs = append(golangCiLintArgs, extraFlags...)
cmd := exec.CommandContext(
ctx,
"golangci-lint",
golangCiLintArgs...,
)
stderrBuf := bytes.NewBuffer(nil)
cmd.Stderr = stderrBuf
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, nil, err
}
if err := cmd.Start(); err != nil {
return nil, nil, err
}
var printerResult printers.JSONResult
if err := json.NewDecoder(stdout).Decode(&printerResult); err != nil {
return nil, nil, err
}
if err := cmd.Wait(); err != nil {
return nil, nil, fmt.Errorf("stderr: %s - %w", stderrBuf.String(), err)
}
return printerResult.Report, printerResult.Issues, nil
}

View file

@ -19,7 +19,6 @@ var root = &cobra.Command{
func main() { func main() {
root.PersistentFlags().StringP("namespace", "n", "", "Namespace a.k.a. organization/owner/group of the repository [$NITTER_NAMESPACE]") root.PersistentFlags().StringP("namespace", "n", "", "Namespace a.k.a. organization/owner/group of the repository [$NITTER_NAMESPACE]")
root.PersistentFlags().StringP("repo", "r", "", "Repo to interact with [$NITTER_REPO]") root.PersistentFlags().StringP("repo", "r", "", "Repo to interact with [$NITTER_REPO]")
root.PersistentFlags().StringP("result-file", "f", "", "path to the golangci-lint JSON output [$NITTER_RESULT_FILE]")
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
defer cancel() defer cancel()

View file

@ -3,6 +3,8 @@ package gitea
import ( import (
"errors" "errors"
"code.gitea.io/sdk/gitea"
"code.icb4dc0.de/prskr/nitter/nitters" "code.icb4dc0.de/prskr/nitter/nitters"
) )
@ -37,6 +39,7 @@ func (gc GiteaConfig) Validate() error {
type GiteaPRConfig struct { type GiteaPRConfig struct {
GiteaConfig `mapstructure:",squash"` GiteaConfig `mapstructure:",squash"`
PRIndex int64 `mapstructure:"pull-index"` PRIndex int64 `mapstructure:"pull-index"`
ReviewState gitea.ReviewStateType `mapstructure:"review-state"`
} }
func (gpc GiteaPRConfig) Validate() error { func (gpc GiteaPRConfig) Validate() error {
@ -50,3 +53,13 @@ func (gpc GiteaPRConfig) Validate() error {
return nil return nil
} }
type GiteaPRLintConfig struct {
GiteaPRConfig `mapstructure:",squash"`
FromPRBase bool `mapstructure:"from-pr-base"`
ExtraArgs []string `mapstructure:"golangci-lint-extra-args"`
}
func (gpc GiteaPRLintConfig) Validate() error {
return gpc.GiteaPRConfig.Validate()
}

View file

@ -47,7 +47,6 @@ type prNitter struct {
func (p prNitter) Report(report *report.Data, issues []result.Issue) error { func (p prNitter) Report(report *report.Data, issues []result.Issue) error {
templateBuf := bytes.Buffer{} templateBuf := bytes.Buffer{}
summaryData := map[string]any{ summaryData := map[string]any{
"Report": report, "Report": report,
"Issues": issues, "Issues": issues,
@ -58,13 +57,15 @@ func (p prNitter) Report(report *report.Data, issues []result.Issue) error {
} }
pullReviewOptions := gitea.CreatePullReviewOptions{ pullReviewOptions := gitea.CreatePullReviewOptions{
State: gitea.ReviewStateComment, State: p.cfg.ReviewState,
Body: templateBuf.String(), Body: templateBuf.String(),
Comments: make([]gitea.CreatePullReviewComment, 0, len(issues)), Comments: make([]gitea.CreatePullReviewComment, 0, len(issues)),
} }
if len(issues) == 0 {
pullReviewOptions.State = gitea.ReviewStateApproved
} else {
templateBuf.Reset() templateBuf.Reset()
for i := range issues { for i := range issues {
templateData := map[string]any{ templateData := map[string]any{
"Issue": issues[i], "Issue": issues[i],
@ -81,6 +82,7 @@ func (p prNitter) Report(report *report.Data, issues []result.Issue) error {
templateBuf.Reset() templateBuf.Reset()
} }
}
_, _, err := p.CreatePullReview(p.cfg.Namespace, p.cfg.Repo, p.cfg.PRIndex, pullReviewOptions) _, _, err := p.CreatePullReview(p.cfg.Namespace, p.cfg.Repo, p.cfg.PRIndex, pullReviewOptions)
if err != nil { if err != nil {