package main import ( "errors" "fmt" "log" "os" "regexp" "strings" git "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" gha "github.com/sethvargo/go-githubactions" ) func main() { // Prepare regexp templates. rxHeader := regexp.MustCompile(`^(\[\#[0-9Xx]+\]\s|Release)`) rxSignOff := regexp.MustCompile(`^Signed-off-by:`) // Open current git dir. r, err := git.PlainOpen("./") if err != nil { log.Fatalf("Failed to open repository: %v", err) } // Retrieve the commit history. head, err := r.Head() if err != nil { log.Fatalf("Failed to retrieve HEAD reference: %v", err) } // Create iterator over commits. commits, err := r.Log(&git.LogOptions{From: head.Hash()}) if err != nil { log.Fatalf("Failed to retrieve commit history: %v", err) } // Limit number of iterations. var lca *object.Commit if from := gha.GetInput("from"); from != "" { lca = getMergeBase(r, head, from) } // Processing result. var fail bool _ = commits.ForEach(func(c *object.Commit) error { // Stop iterator when limit is reached. if lca != nil && c.Hash == lca.Hash { return errors.New("stop") } // Parse commit data. id := c.ID().String()[:7] lines := strings.Split(strings.Trim(c.Message, "\n"), "\n") // Do not process empty commit. if len(lines) == 0 { fail = true fmt.Printf("Error: empty commit [%s]\n", id) return nil } // Check commit header. header := lines[0] if !rxHeader.MatchString(header) { fail = true fmt.Printf("Error: invalid header %s [%s]\n", header, id) return nil } // Check commit sign-off. if !rxSignOff.MatchString(lines[len(lines)-1]) { fail = true fmt.Printf("Error: missing sign-off %s [%s]\n", header, id) } return nil }) // Return non-zero code if DCO check failed. if fail { os.Exit(1) } } func getMergeBase(r *git.Repository, head *plumbing.Reference, from string) *object.Commit { h, err := r.ResolveRevision(plumbing.Revision(from)) if err != nil { log.Fatalf("Failed to resolve a reference: %v", err) } to, err := r.CommitObject(*h) if err != nil { log.Fatalf("Failed to get commit object: %v", err) } other, err := r.CommitObject(head.Hash()) if err != nil { log.Fatalf("Failed to get HEAD commit object: %v", err) } cc, err := to.MergeBase(other) if err != nil { log.Fatalf("Failed to determine merge-base: %v", err) } return cc[0] }