├── .gitignore ├── LICENSE ├── README.md ├── helpers.go ├── pre-commit └── precommit-vet-lint.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | # Output of the go coverage tool, specifically when used with LiteIDE 27 | *.out 28 | 29 | # external packages folder 30 | vendor/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Ashwini Chaudhary 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Precommit-vet-lint 2 | === 3 | 4 | A precommit hook that runs on your staged Go file and reports errors returned by [Golint](https://github.com/golang/lint) and [Vet](https://golang.org/cmd/vet/). 5 | 6 | ### Installation 7 | --- 8 | 9 | ```bash 10 | go get -u github.com/golang/lint/golint 11 | go get -u github.com/ashwch/precommit-vet-lint 12 | ``` 13 | 14 | ### Usage 15 | --- 16 | 17 | Copy the contents of pre-commit file included with this repo in your `.git/hooks/pre-commit` file. Make sure the file has executable permissions. 18 | 19 | 20 | ### Example 21 | 22 | ```bash 23 | $ git status 24 | On branch master 25 | Changes to be committed: 26 | (use "git reset HEAD ..." to unstage) 27 | 28 | new file: helpers.go 29 | new file: precommit-vet-lint.go 30 | 31 | Untracked files: 32 | (use "git add ..." to include in what will be committed) 33 | 34 | precommit-vet-lint 35 | 36 | $ git commit -m "Added core files" 37 | Running Golint and Vet... 38 | Failed Following linting error(s) found: 39 | helpers.go:14:1: exported function CheckError should have comment or be unexported 40 | helpers.go:20:1: exported function GetStagedFiles should have comment or be unexported 41 | helpers.go:48:1: exported function GetProjectBasePath should have comment or be unexported 42 | helpers.go:53:1: exported function GetStagedContent should have comment or be unexported 43 | helpers.go:60:1: exported function GetLintErrors should have comment or be unexported 44 | helpers.go:69:1: exported function GetVetErrors should have comment or be unexported 45 | helpers.go:83:6: exported type TempDir should have comment or be unexported 46 | helpers.go:87:1: exported method TempDir.Create should have comment or be unexported 47 | helpers.go:93:1: exported method TempDir.Close should have comment or be unexported. 48 | ``` 49 | -------------------------------------------------------------------------------- /helpers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "path" 10 | "strings" 11 | ) 12 | 13 | // CheckError is a helper function for handling errors 14 | func CheckError(err error) { 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | } 19 | 20 | // GetStagedFiles return a list of all Go files currently staged. 21 | func GetStagedFiles() []string { 22 | cmd := exec.Command("bash", "-c", "git diff --diff-filter=ACMRTUXB --cached HEAD --name-only | egrep '[.]go$'") 23 | stdout, _ := cmd.Output() 24 | stdoutString := strings.TrimSpace(string(stdout)) 25 | if stdoutString != "" { 26 | return strings.Split(stdoutString, "\n") 27 | } 28 | return make([]string, 0) 29 | } 30 | 31 | func getPathToDotGit() string { 32 | cmd := exec.Command("bash", "-c", "git rev-parse --git-dir") 33 | stdout, err := cmd.Output() 34 | CheckError(err) 35 | 36 | dotGitPath := strings.TrimSpace(string(stdout)) 37 | cwd, err := os.Getwd() 38 | CheckError(err) 39 | 40 | if dotGitPath == ".git" { 41 | return path.Join(cwd, dotGitPath) 42 | } else if dotGitPath == "." { 43 | return cwd 44 | } 45 | return dotGitPath 46 | 47 | } 48 | 49 | // GetStagedContent returns the content of a staged file. 50 | func GetStagedContent(file string) []byte { 51 | cmd := exec.Command("bash", "-c", fmt.Sprintf("git show :%s", file)) 52 | stdout, err := cmd.Output() 53 | CheckError(err) 54 | return stdout 55 | } 56 | 57 | // GetLintErrors retuns the error returned by `golint` command on a file. 58 | func GetLintErrors(file string) (string, error) { 59 | cmd := exec.Command("bash", "-c", fmt.Sprintf("golint -set_exit_status %s", file)) 60 | stdout, err := cmd.CombinedOutput() 61 | if err != nil { 62 | return removeLastLine(string(stdout)), err 63 | } 64 | return "", nil 65 | } 66 | 67 | // GetVetErrors retuns the error returned by `go vet` command on a file. 68 | func GetVetErrors(file string) (string, error) { 69 | cmd := exec.Command("bash", "-c", fmt.Sprintf("go vet %s", file)) 70 | stdout, err := cmd.CombinedOutput() 71 | if err != nil { 72 | return removeLastLine(string(stdout)), err 73 | } 74 | return "", nil 75 | } 76 | 77 | func removeLastLine(str string) string { 78 | newlineIndex := strings.LastIndex(strings.TrimSpace(str), "\n") 79 | return str[:newlineIndex] 80 | } 81 | 82 | // TempDir is a struct that is used to store path to a temp directory. 83 | type TempDir struct { 84 | Path string 85 | } 86 | 87 | // Create creates a temp directory and assigns the path to this directory to TempDir.Path 88 | func (dir *TempDir) Create() { 89 | path, err := ioutil.TempDir("", "pre-commit-vet-lint") 90 | CheckError(err) 91 | dir.Path = path 92 | } 93 | 94 | // Close deletes the temporary directory whose path was stored under TempDir.Path 95 | func (dir *TempDir) Close() { 96 | err := os.RemoveAll(dir.Path) 97 | CheckError(err) 98 | } 99 | -------------------------------------------------------------------------------- /pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | printf "Running Golint and Vet...\n" 4 | CMD=$("precommit-vet-lint") 5 | 6 | RESULT=$? 7 | if [ $RESULT -ne 0 ]; then 8 | printf "Failed \n$CMD." 9 | exit 1 10 | fi 11 | printf "No issues found.\n" 12 | 13 | exit 0 -------------------------------------------------------------------------------- /precommit-vet-lint.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | ) 10 | 11 | func main() { 12 | 13 | lintErrors := make([]string, 0) 14 | vetErrors := make([]string, 0) 15 | stagedFiles := GetStagedFiles() 16 | 17 | temp := TempDir{} 18 | temp.Create() 19 | defer temp.Close() 20 | 21 | for _, file := range stagedFiles { 22 | fileDir, fileName := filepath.Split(file) 23 | tempFileDir := filepath.Join(temp.Path, fileDir) 24 | if _, err := os.Stat(tempFileDir); os.IsNotExist(err) { 25 | err = os.MkdirAll(tempFileDir, os.ModePerm) 26 | } 27 | tempFilePath := filepath.Join(tempFileDir, fileName) 28 | stagedContent := GetStagedContent(file) 29 | err := ioutil.WriteFile(tempFilePath, stagedContent, 0655) 30 | CheckError(err) 31 | lintErrorMessage, err := GetLintErrors(tempFilePath) 32 | if err != nil { 33 | lintErrors = append(lintErrors, strings.Replace(lintErrorMessage, tempFilePath, file, -1)) 34 | } 35 | 36 | vetErrorMessage, err := GetVetErrors(tempFilePath) 37 | if err != nil { 38 | vetErrors = append(vetErrors, strings.Replace(vetErrorMessage, tempFilePath, file, -1)) 39 | } 40 | } 41 | if len(lintErrors) != 0 { 42 | fmt.Println("Following linting error(s) found:") 43 | fmt.Println() 44 | for _, errMsg := range lintErrors { 45 | fmt.Println(errMsg) 46 | } 47 | fmt.Println() 48 | fmt.Println() 49 | } 50 | 51 | if len(vetErrors) != 0 { 52 | fmt.Println("Following vet error(s) found:") 53 | fmt.Println() 54 | for _, errMsg := range vetErrors { 55 | fmt.Println(errMsg) 56 | } 57 | } 58 | if len(lintErrors) != 0 || len(vetErrors) != 0 { 59 | os.Exit(1) 60 | } 61 | } 62 | --------------------------------------------------------------------------------