Skip to content

Commit 72883e2

Browse files
committed
WIP
Signed-off-by: Francesco Romani <fromani@redhat.com>
1 parent 70fc817 commit 72883e2

File tree

3 files changed

+208
-0
lines changed

3 files changed

+208
-0
lines changed

Makefile.kni

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,7 @@ verify-crdgen: update-vendor
9090
.PHONY: clean
9191
clean:
9292
rm -rf ./bin
93+
94+
.PHONY: build-tools
95+
build-tools:
96+
go build -o bin/verify-commit-message hack-kni/tools/verify-commit-message.go
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"errors"
6+
"log"
7+
"os"
8+
"strings"
9+
)
10+
11+
const (
12+
exitCodeSuccess = 0
13+
exitCodeErrorWrongArguments = 1
14+
exitCodeErrorVerificationFailed = 2
15+
)
16+
17+
const (
18+
tagKNI = "[KNI]"
19+
20+
cherryPickLinePrefix = "(cherry picked from commit "
21+
cherryPickLineSuffix = ")" // yes that simple
22+
)
23+
24+
var (
25+
errEmptyCommitMessage = errors.New("empty commit message")
26+
errMissingTagKNI = errors.New("missing tag: " + tagKNI)
27+
)
28+
29+
type commitMessage struct {
30+
lines []string
31+
}
32+
33+
func newCommitMessageFromString(text string) commitMessage {
34+
var cm commitMessage
35+
scanner := bufio.NewScanner(strings.NewReader(text))
36+
for scanner.Scan() {
37+
cm.lines = append(cm.lines, scanner.Text())
38+
}
39+
log.Printf("commit message has %d lines", cm.NumLines())
40+
return cm
41+
}
42+
43+
func (cm commitMessage) NumLines() int {
44+
return len(cm.lines)
45+
}
46+
47+
func (cm commitMessage) IsEmpty() bool {
48+
return cm.NumLines() == 0
49+
}
50+
51+
func (cm commitMessage) Summary() string {
52+
return cm.lines[0]
53+
}
54+
55+
func (cm commitMessage) IsKNISpecific() bool {
56+
return strings.Contains(cm.Summary(), tagKNI)
57+
}
58+
59+
// CherryPickOrigin returns the commit hash this commit was cherry-picked
60+
// from if this commit has cherry-pick reference; otherwise returns empty string.
61+
func (cm commitMessage) CherryPickOrigin() string {
62+
for idx := cm.NumLines() - 1; idx > 0; idx-- {
63+
line := cm.lines[idx] // shortcut
64+
cmHash, ok := strings.CutPrefix(line, cherryPickLinePrefix)
65+
if !ok { // we don't have the prefix, so we don't care
66+
continue
67+
}
68+
cmHash, ok = strings.CutSuffix(chHash, cherryPickLineSuffix)
69+
if !ok { // we don't have the suffix, so we don't care
70+
continue
71+
}
72+
return chMash
73+
}
74+
return "" // nothing found
75+
}
76+
77+
func verifyCommitMessage(commitMessage string) error {
78+
cm := newCommitMessageFromString(commitMessage)
79+
if cm.IsEmpty() {
80+
return errEmptyCommitMessage
81+
}
82+
if !cm.IsKNISpecific() {
83+
return errMissingTagKNI
84+
}
85+
return nil
86+
}
87+
88+
func main() {
89+
if len(os.Args) != 2 {
90+
programName := os.Args[0]
91+
log.Printf("usage: %s wrong number of arguments expects: %s <commit-message>", programName, programName)
92+
os.Exit(1)
93+
}
94+
95+
err := verifyCommitMessage(os.Args[1])
96+
if err != nil {
97+
log.Printf("verification failed: %v", err)
98+
os.Exit(2)
99+
}
100+
101+
os.Exit(0) // all good! redundant but let's be explicit about our success
102+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"os/exec"
7+
"path/filepath"
8+
goruntime "runtime"
9+
"testing"
10+
)
11+
12+
const (
13+
binariesDir = "bin"
14+
programName = "verify-commit-message"
15+
)
16+
17+
func TestRunWithOnlyNewlines(t *testing.T) {
18+
commitMessage := `
19+
20+
21+
22+
23+
24+
`
25+
_, errBuf, err := runCommand(t, commitMessage)
26+
// run with empty commit message is unsupported and should fail
27+
if err == nil {
28+
t.Fatalf("Unexpectedly succeeded: %v (stderr=%s)\n", err, errBuf)
29+
}
30+
exitErr, ok := err.(*exec.ExitError)
31+
if !ok {
32+
t.Fatalf("Received unexpected error %T %v", err, err)
33+
}
34+
errCode := exitErr.ExitCode()
35+
if errCode != exitCodeErrorVerificationFailed {
36+
t.Fatalf("Received unexpected exit code %d", errCode)
37+
}
38+
}
39+
40+
func TestRunWithEmptyArgument(t *testing.T) {
41+
_, errBuf, err := runCommand(t, "")
42+
// run with empty commit message is unsupported and should fail
43+
if err == nil {
44+
t.Fatalf("Unexpectedly succeeded: %v (stderr=%s)\n", err, errBuf)
45+
}
46+
exitErr, ok := err.(*exec.ExitError)
47+
if !ok {
48+
t.Fatalf("Received unexpected error %T %v", err, err)
49+
}
50+
errCode := exitErr.ExitCode()
51+
if errCode != exitCodeErrorVerificationFailed {
52+
t.Fatalf("Received unexpected exit code %d", errCode)
53+
}
54+
}
55+
56+
func TestRunWithoutArguments(t *testing.T) {
57+
_, errBuf, err := runCommand(t) // note no arguments intentionally
58+
// run without arguments should fail
59+
if err == nil {
60+
t.Fatalf("Unexpectedly succeeded: %v (stderr=%s)\n", err, errBuf)
61+
}
62+
exitErr, ok := err.(*exec.ExitError)
63+
if !ok {
64+
t.Fatalf("Received unexpected error %T %v", err, err)
65+
}
66+
errCode := exitErr.ExitCode()
67+
if errCode != exitCodeErrorWrongArguments {
68+
t.Fatalf("Received unexpected exit code %d", errCode)
69+
}
70+
}
71+
72+
// runCommand returns stdout as string, stderr as string, error code
73+
func runCommand(t *testing.T, args ...string) (string, string, error) {
74+
bin, err := getBinPath()
75+
if err != nil {
76+
t.Fatalf("failed to find the binary path: %v", err)
77+
}
78+
fmt.Printf("going to use %q\n", bin)
79+
var errBuf bytes.Buffer
80+
cmd := exec.Command(bin, args...)
81+
cmd.Stderr = &errBuf
82+
out, err := cmd.Output()
83+
fmt.Printf("tool returned <%s>\n", out)
84+
return string(out), errBuf.String(), err
85+
}
86+
87+
func getBinPath() (string, error) {
88+
rootDir, err := getRootPath()
89+
if err != nil {
90+
return "", err
91+
}
92+
return filepath.Join(rootDir, binariesDir, programName), nil
93+
}
94+
95+
func getRootPath() (string, error) {
96+
_, file, _, ok := goruntime.Caller(0)
97+
if !ok {
98+
return "", fmt.Errorf("cannot retrieve tests directory")
99+
}
100+
basedir := filepath.Dir(file)
101+
return filepath.Abs(filepath.Join(basedir, "..", ".."))
102+
}

0 commit comments

Comments
 (0)