Skip to content

Instantly share code, notes, and snippets.

@Zenithar
Last active October 28, 2019 08:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Zenithar/9b261ced8e2a1843d7860dabf9171d38 to your computer and use it in GitHub Desktop.
Save Zenithar/9b261ced8e2a1843d7860dabf9171d38 to your computer and use it in GitHub Desktop.
// Copyright 2019 Thibault NORMAND
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"flag"
"fmt"
"log"
"os"
"regexp"
"strings"
"go.zenithar.org/pkg/types"
"gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/object"
)
// Change represents changed file information
type Change struct {
Action string
Filename string
}
// Trigger represents service target target trigger
type Trigger struct {
matcher *regexp.Regexp
target string
}
// -----------------------------------------------------------------------------
var (
triggers = []Trigger{
{
matcher: regexp.MustCompile("go.mod"),
target: "all",
},
{
matcher: regexp.MustCompile("api/.*"),
target: "all",
},
{
matcher: regexp.MustCompile("pkg/.*"),
target: "all",
},
{
matcher: regexp.MustCompile("exp/.*"),
target: "all",
},
{
matcher: regexp.MustCompile("tools/.*"),
target: "all",
},
{
matcher: regexp.MustCompile(".*/svc1"),
target: "svc1",
},
}
)
// -----------------------------------------------------------------------------
func splitCommit(cr string) (string, string, error) {
// Split commit string
parts := strings.SplitN(cr, "..", 2)
if len(parts) == 2 {
return parts[0], parts[1], nil
}
return cr, "head", nil
}
func getHead(repoPath string) (*plumbing.Reference, error) {
// Open current repository
r, err := git.PlainOpen(repoPath)
if err != nil {
return nil, fmt.Errorf("shouldbuild: unable to open given repository: %w", err)
}
return r.Head()
}
func changeName(ch *object.Change) string {
if ch.From.Name != "" {
return ch.From.Name
}
return ch.To.Name
}
func getChanges(repoPath string, commitFrom string, commitTo string) ([]*Change, error) {
// Open current repository
r, err := git.PlainOpen(repoPath)
if err != nil {
return nil, fmt.Errorf("shouldbuild: unable to open given repository: %w", err)
}
c, err := r.CommitObject(plumbing.NewHash(commitFrom))
if err != nil {
return nil, fmt.Errorf("shouldbuild: unable to find commit `%s`: %w", commitFrom, err)
}
prevCommit, err := r.CommitObject(plumbing.NewHash(commitTo))
if err != nil {
return nil, fmt.Errorf("shouldbuild: unable to retrieve previous commit: %w", err)
}
prevTree, err := prevCommit.Tree()
if err != nil {
return nil, fmt.Errorf("shouldbuild: unable to retrieve previous commit tree: %w", err)
}
currTree, err := c.Tree()
if err != nil {
return nil, fmt.Errorf("shouldbuild: unable to retrieve current commit tree: %w", err)
}
changes, err := currTree.Diff(prevTree)
if err != nil {
return nil, fmt.Errorf("shouldbuild: unable to calculate changes tree: %w", err)
}
var files []*Change
for _, ch := range changes {
action, err := ch.Action()
if err != nil {
continue
}
file := &Change{
Action: action.String(),
Filename: changeName(ch),
}
files = append(files, file)
}
// No error
return files, nil
}
// -----------------------------------------------------------------------------
var (
repoPath string
commitRange string
target string
)
func init() {
flag.StringVar(&repoPath, "repo", ".", "Repository path to scan")
flag.StringVar(&commitRange, "range", "", "Commit range <from>..<to>")
flag.StringVar(&target, "target", "all", "Expected target to run")
flag.Parse()
}
func main() {
if flag.NFlag() == 0 {
flag.Usage()
os.Exit(-1)
}
// Check arguments
firstCommit, lastCommit, err := splitCommit(commitRange)
if err != nil {
log.Fatalln(err)
}
// Check head usage
if strings.EqualFold(firstCommit, "HEAD") || strings.EqualFold(lastCommit, "HEAD") {
headRef, err := getHead(repoPath)
if err != nil {
log.Fatal(err)
}
if strings.EqualFold(firstCommit, "HEAD") {
firstCommit = headRef.Hash().String()
}
if strings.EqualFold(lastCommit, "HEAD") {
lastCommit = headRef.Hash().String()
}
}
log.Printf("Scanning repository `%s` for changes (%s..%s) to run `%s` ...\n", repoPath, firstCommit, lastCommit, target)
// Get all changed files
changes, err := getChanges(repoPath, firstCommit, lastCommit)
if err != nil {
log.Fatal(err)
}
// Trigger mage targets according changes
targets := map[string]bool{}
for _, ch := range changes {
fmt.Printf("%s", ch.Filename)
var fileTriggers types.StringArray
for _, trigger := range triggers {
if trigger.matcher.MatchString(ch.Filename) {
fileTriggers.AddIfNotContains(trigger.target)
if _, ok := targets[trigger.target]; !ok {
targets[trigger.target] = true
}
}
}
fmt.Printf(" [%s]\n", strings.Join(fileTriggers, ","))
}
// Check if expected target is triggerable
if _, ok := targets[target]; !ok {
log.Fatalf("Aborting, target `%s` is not concerned by build\n", target)
}
log.Printf("Can continue, target `%s` is concerned by build\n", target)
os.Exit(0)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment