284 lines
		
	
	
	
		
			6.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			284 lines
		
	
	
	
		
			6.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2021 The Gitea Authors. All rights reserved.
 | |
| // Use of this source code is governed by a MIT-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| //go:build ignore
 | |
| // +build ignore
 | |
| 
 | |
| package main
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"log"
 | |
| 	"os"
 | |
| 	"os/exec"
 | |
| 	"path/filepath"
 | |
| 	"regexp"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 
 | |
| 	"code.gitea.io/gitea/build/codeformat"
 | |
| )
 | |
| 
 | |
| // Windows has a limitation for command line arguments, the size can not exceed 32KB.
 | |
| // So we have to feed the files to some tools (like gofmt/misspell) batch by batch
 | |
| 
 | |
| // We also introduce a `gitea-fmt` command, it does better import formatting than gofmt/goimports. `gitea-fmt` calls `gofmt` internally.
 | |
| 
 | |
| var optionLogVerbose bool
 | |
| 
 | |
| func logVerbose(msg string, args ...interface{}) {
 | |
| 	if optionLogVerbose {
 | |
| 		log.Printf(msg, args...)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func passThroughCmd(cmd string, args []string) error {
 | |
| 	foundCmd, err := exec.LookPath(cmd)
 | |
| 	if err != nil {
 | |
| 		log.Fatalf("can not find cmd: %s", cmd)
 | |
| 	}
 | |
| 	c := exec.Cmd{
 | |
| 		Path:   foundCmd,
 | |
| 		Args:   args,
 | |
| 		Stdin:  os.Stdin,
 | |
| 		Stdout: os.Stdout,
 | |
| 		Stderr: os.Stderr,
 | |
| 	}
 | |
| 	return c.Run()
 | |
| }
 | |
| 
 | |
| type fileCollector struct {
 | |
| 	dirs            []string
 | |
| 	includePatterns []*regexp.Regexp
 | |
| 	excludePatterns []*regexp.Regexp
 | |
| 	batchSize       int
 | |
| }
 | |
| 
 | |
| func newFileCollector(fileFilter string, batchSize int) (*fileCollector, error) {
 | |
| 	co := &fileCollector{batchSize: batchSize}
 | |
| 	if fileFilter == "go-own" {
 | |
| 		co.dirs = []string{
 | |
| 			"build",
 | |
| 			"cmd",
 | |
| 			"contrib",
 | |
| 			"integrations",
 | |
| 			"models",
 | |
| 			"modules",
 | |
| 			"routers",
 | |
| 			"services",
 | |
| 			"tools",
 | |
| 		}
 | |
| 		co.includePatterns = append(co.includePatterns, regexp.MustCompile(`.*\.go$`))
 | |
| 
 | |
| 		co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`.*\bbindata\.go$`))
 | |
| 		co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`integrations/gitea-repositories-meta`))
 | |
| 		co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`integrations/migration-test`))
 | |
| 		co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`modules/git/tests`))
 | |
| 		co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`models/fixtures`))
 | |
| 		co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`models/migrations/fixtures`))
 | |
| 		co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`services/gitdiff/testdata`))
 | |
| 	}
 | |
| 
 | |
| 	if co.dirs == nil {
 | |
| 		return nil, fmt.Errorf("unknown file-filter: %s", fileFilter)
 | |
| 	}
 | |
| 	return co, nil
 | |
| }
 | |
| 
 | |
| func (fc *fileCollector) matchPatterns(path string, regexps []*regexp.Regexp) bool {
 | |
| 	path = strings.ReplaceAll(path, "\\", "/")
 | |
| 	for _, re := range regexps {
 | |
| 		if re.MatchString(path) {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func (fc *fileCollector) collectFiles() (res [][]string, err error) {
 | |
| 	var batch []string
 | |
| 	for _, dir := range fc.dirs {
 | |
| 		err = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
 | |
| 			include := len(fc.includePatterns) == 0 || fc.matchPatterns(path, fc.includePatterns)
 | |
| 			exclude := fc.matchPatterns(path, fc.excludePatterns)
 | |
| 			process := include && !exclude
 | |
| 			if !process {
 | |
| 				if d.IsDir() {
 | |
| 					if exclude {
 | |
| 						logVerbose("exclude dir %s", path)
 | |
| 						return filepath.SkipDir
 | |
| 					}
 | |
| 					// for a directory, if it is not excluded explicitly, we should walk into
 | |
| 					return nil
 | |
| 				}
 | |
| 				// for a file, we skip it if it shouldn't be processed
 | |
| 				logVerbose("skip process %s", path)
 | |
| 				return nil
 | |
| 			}
 | |
| 			if d.IsDir() {
 | |
| 				// skip dir, we don't add dirs to the file list now
 | |
| 				return nil
 | |
| 			}
 | |
| 			if len(batch) >= fc.batchSize {
 | |
| 				res = append(res, batch)
 | |
| 				batch = nil
 | |
| 			}
 | |
| 			batch = append(batch, path)
 | |
| 			return nil
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 	res = append(res, batch)
 | |
| 	return res, nil
 | |
| }
 | |
| 
 | |
| // substArgFiles expands the {file-list} to a real file list for commands
 | |
| func substArgFiles(args, files []string) []string {
 | |
| 	for i, s := range args {
 | |
| 		if s == "{file-list}" {
 | |
| 			newArgs := append(args[:i], files...)
 | |
| 			newArgs = append(newArgs, args[i+1:]...)
 | |
| 			return newArgs
 | |
| 		}
 | |
| 	}
 | |
| 	return args
 | |
| }
 | |
| 
 | |
| func exitWithCmdErrors(subCmd string, subArgs []string, cmdErrors []error) {
 | |
| 	for _, err := range cmdErrors {
 | |
| 		if err != nil {
 | |
| 			if exitError, ok := err.(*exec.ExitError); ok {
 | |
| 				exitCode := exitError.ExitCode()
 | |
| 				log.Printf("run command failed (code=%d): %s %v", exitCode, subCmd, subArgs)
 | |
| 				os.Exit(exitCode)
 | |
| 			} else {
 | |
| 				log.Fatalf("run command failed (err=%s) %s %v", err, subCmd, subArgs)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func parseArgs() (mainOptions map[string]string, subCmd string, subArgs []string) {
 | |
| 	mainOptions = map[string]string{}
 | |
| 	for i := 1; i < len(os.Args); i++ {
 | |
| 		arg := os.Args[i]
 | |
| 		if arg == "" {
 | |
| 			break
 | |
| 		}
 | |
| 		if arg[0] == '-' {
 | |
| 			arg = strings.TrimPrefix(arg, "-")
 | |
| 			arg = strings.TrimPrefix(arg, "-")
 | |
| 			fields := strings.SplitN(arg, "=", 2)
 | |
| 			if len(fields) == 1 {
 | |
| 				mainOptions[fields[0]] = "1"
 | |
| 			} else {
 | |
| 				mainOptions[fields[0]] = fields[1]
 | |
| 			}
 | |
| 		} else {
 | |
| 			subCmd = arg
 | |
| 			subArgs = os.Args[i+1:]
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func showUsage() {
 | |
| 	fmt.Printf(`Usage: %[1]s [options] {command} [arguments]
 | |
| 
 | |
| Options:
 | |
|   --verbose
 | |
|   --file-filter=go-own
 | |
|   --batch-size=100
 | |
| 
 | |
| Commands:
 | |
|   %[1]s gofmt ...
 | |
|   %[1]s misspell ...
 | |
| 
 | |
| Arguments:
 | |
|   {file-list}     the file list
 | |
| 
 | |
| Example:
 | |
|   %[1]s gofmt -s -d {file-list}
 | |
| 
 | |
| `, "file-batch-exec")
 | |
| }
 | |
| 
 | |
| func newFileCollectorFromMainOptions(mainOptions map[string]string) (fc *fileCollector, err error) {
 | |
| 	fileFilter := mainOptions["file-filter"]
 | |
| 	if fileFilter == "" {
 | |
| 		fileFilter = "go-own"
 | |
| 	}
 | |
| 	batchSize, _ := strconv.Atoi(mainOptions["batch-size"])
 | |
| 	if batchSize == 0 {
 | |
| 		batchSize = 100
 | |
| 	}
 | |
| 
 | |
| 	return newFileCollector(fileFilter, batchSize)
 | |
| }
 | |
| 
 | |
| func containsString(a []string, s string) bool {
 | |
| 	for _, v := range a {
 | |
| 		if v == s {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func giteaFormatGoImports(files []string) error {
 | |
| 	for _, file := range files {
 | |
| 		if err := codeformat.FormatGoImports(file); err != nil {
 | |
| 			log.Printf("failed to format go imports: %s, err=%v", file, err)
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func main() {
 | |
| 	mainOptions, subCmd, subArgs := parseArgs()
 | |
| 	if subCmd == "" {
 | |
| 		showUsage()
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| 	optionLogVerbose = mainOptions["verbose"] != ""
 | |
| 
 | |
| 	fc, err := newFileCollectorFromMainOptions(mainOptions)
 | |
| 	if err != nil {
 | |
| 		log.Fatalf("can not create file collector: %s", err.Error())
 | |
| 	}
 | |
| 
 | |
| 	fileBatches, err := fc.collectFiles()
 | |
| 	if err != nil {
 | |
| 		log.Fatalf("can not collect files: %s", err.Error())
 | |
| 	}
 | |
| 
 | |
| 	processed := 0
 | |
| 	var cmdErrors []error
 | |
| 	for _, files := range fileBatches {
 | |
| 		if len(files) == 0 {
 | |
| 			break
 | |
| 		}
 | |
| 		substArgs := substArgFiles(subArgs, files)
 | |
| 		logVerbose("batch cmd: %s %v", subCmd, substArgs)
 | |
| 		switch subCmd {
 | |
| 		case "gitea-fmt":
 | |
| 			if containsString(subArgs, "-w") {
 | |
| 				cmdErrors = append(cmdErrors, giteaFormatGoImports(files))
 | |
| 			}
 | |
| 			cmdErrors = append(cmdErrors, passThroughCmd("gofmt", substArgs))
 | |
| 		case "misspell":
 | |
| 			cmdErrors = append(cmdErrors, passThroughCmd("misspell", substArgs))
 | |
| 		default:
 | |
| 			log.Fatalf("unknown cmd: %s %v", subCmd, subArgs)
 | |
| 		}
 | |
| 		processed += len(files)
 | |
| 	}
 | |
| 
 | |
| 	logVerbose("processed %d files", processed)
 | |
| 	exitWithCmdErrors(subCmd, subArgs, cmdErrors)
 | |
| }
 | 
