Last active
October 28, 2019 08:48
-
-
Save Zenithar/9b261ced8e2a1843d7860dabf9171d38 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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