Skip to content
This repository has been archived by the owner on Jul 18, 2022. It is now read-only.

Commit

Permalink
Read configuration as tracked in previous run
Browse files Browse the repository at this point in the history
This commit makes sure that the previous run's configuration path
is saved. It also makes sure the path is read at the previous run's
revision to match existing headers based on previous run's config
data.

It also makes sure that the tracking file is not touched when there
is no file to process.

Fixes #27.
  • Loading branch information
Florent Biville committed Jan 29, 2019
1 parent 624c017 commit ece3139
Show file tree
Hide file tree
Showing 25 changed files with 939 additions and 540 deletions.
14 changes: 10 additions & 4 deletions build.sh
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
#!/bin/bash
set -e
set -euo pipefail

dep ensure
rm vcs_mocks/*.go || true && rm fs_mocks/*.go || true \
&& go get github.com/vektra/mockery/.../ \

rm helper_mocks/*.go || true \
&& rm vcs_mocks/*.go || true \
&& rm fs_mocks/*.go || true \
&& rm core_mocks/*.go || true

go get github.com/vektra/mockery/.../ \
&& mockery -output helper_mocks -outpkg helper_mocks -dir helper -name Clock \
&& mockery -output vcs_mocks -outpkg vcs_mocks -dir vcs -name Vcs \
&& mockery -output vcs_mocks -outpkg vcs_mocks -dir vcs -name VersioningClient \
&& mockery -output fs_mocks -outpkg fs_mocks -dir fs -name FileWriter \
&& mockery -output fs_mocks -outpkg fs_mocks -dir fs -name FileReader \
&& mockery -output fs_mocks -outpkg fs_mocks -dir fs -name File \
&& mockery -output fs_mocks -outpkg fs_mocks -dir fs -name PathMatcher \
&& mockery -output fs_mocks -outpkg fs_mocks -dir fs -name ExecutionTracker
&& mockery -output core_mocks -outpkg core_mocks -dir core -name ExecutionTracker

go build ./...
go clean -testcache && go test -v ./...
rm headache 2> /dev/null || true && go build
44 changes: 20 additions & 24 deletions core/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ import (
"regexp"
)

func DefaultSystemConfiguration() SystemConfiguration {
return SystemConfiguration{
func DefaultSystemConfiguration() *SystemConfiguration {
return &SystemConfiguration{
VersioningClient: &vcs.Client{
Vcs: vcs.Git{},
Vcs: &vcs.Git{},
},
FileSystem: fs.DefaultFileSystem(),
Clock: helper.SystemClock{},
Expand All @@ -35,7 +35,7 @@ func DefaultSystemConfiguration() SystemConfiguration {

type SystemConfiguration struct {
VersioningClient vcs.VersioningClient
FileSystem fs.FileSystem
FileSystem *fs.FileSystem
Clock helper.Clock
}

Expand All @@ -54,25 +54,22 @@ type ChangeSet struct {
}

func ParseConfiguration(
config Configuration,
sysConfig SystemConfiguration,
tracker fs.ExecutionTracker,
currentConfig *Configuration,
system *SystemConfiguration,
tracker ExecutionTracker,
pathMatcher fs.PathMatcher) (*ChangeSet, error) {

// TODO: config.HeaderFile may have changed since last run - this may cause e.g. to not find the previous header file!
headerContents, err := tracker.ReadLinesAtLastExecutionRevision(config.HeaderFile)
versionedTemplate, err := tracker.RetrieveVersionedTemplate(currentConfig)
if err != nil {
return nil, err
}

// TODO: config.TemplateData may have changed since last run -- this may change the detection regex!
// note: comment style does not matter as the detection regex is designed to be insensitive to the style in use
contents, err := ParseTemplate(headerContents, config.TemplateData, ParseCommentStyle(config.CommentStyle))
contents, err := ParseTemplate(versionedTemplate, ParseCommentStyle(currentConfig.CommentStyle))
if err != nil {
return nil, err
}

changes, err := getFileChanges(config, sysConfig, tracker, pathMatcher)
changes, err := getAffectedFiles(currentConfig, system, versionedTemplate, pathMatcher)
if err != nil {
return nil, err
}
Expand All @@ -84,26 +81,25 @@ func ParseConfiguration(
}, nil
}

func getFileChanges(config Configuration,
sysConfig SystemConfiguration,
tracker fs.ExecutionTracker,
func getAffectedFiles(config *Configuration,
sysConfig *SystemConfiguration,
versionedTemplate *VersionedHeaderTemplate,
pathMatcher fs.PathMatcher) ([]vcs.FileChange, error) {

versioningClient := sysConfig.VersioningClient
fileSystem := sysConfig.FileSystem
revision, err := tracker.GetLastExecutionRevision()
if err != nil {
return nil, err
}
var changes []vcs.FileChange
// TODO: centralize this check between here and ExecutionTracker#ReadLinesAtLastExecutionRevision
if revision == "" {
var (
changes []vcs.FileChange
err error
)

if versionedTemplate.RequiresFullScan() {
changes, err = pathMatcher.ScanAllFiles(config.Includes, config.Excludes, fileSystem)
if err != nil {
return nil, err
}
} else {
fileChanges, err := versioningClient.GetChanges(revision)
fileChanges, err := versioningClient.GetChanges(versionedTemplate.Revision)
if err != nil {
return nil, err
}
Expand Down
59 changes: 59 additions & 0 deletions core/configuration_loader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package core

import (
"encoding/json"
"github.com/fbiville/headache/fs"
jsonsch "github.com/xeipuuv/gojsonschema"
"log"
)

type ConfigurationLoader struct {
Reader fs.FileReader
}

func (cl *ConfigurationLoader) ReadConfiguration(configFile *string) (*Configuration, error) {
err := cl.validateConfiguration(configFile)
if err != nil {
return nil, err
}

payload, err := cl.Reader.Read(*configFile)
if err != nil {
return nil, err
}
configuration, err := cl.UnmarshallConfiguration(payload)
if err != nil {
return nil, err
}
return configuration, err
}

func (cl *ConfigurationLoader) UnmarshallConfiguration(configurationPayload []byte) (*Configuration, error) {
result := Configuration{}
err := json.Unmarshal(configurationPayload, &result)
if err != nil {
return nil, err
}
return &result, nil
}

func (cl *ConfigurationLoader) validateConfiguration(configFile *string) error {
schema := loadSchema()
if schema == nil {
return nil
}
jsonSchemaValidator := JsonSchemaValidator{
Schema: schema,
FileReader: cl.Reader,
}
return jsonSchemaValidator.Validate("file://" + *configFile)
}

func loadSchema() *jsonsch.Schema {
schema, err := jsonsch.NewSchema(jsonsch.NewReferenceLoader("https://fbiville.github.io/headache/v1.0.0-M01/schema.json"))
if err != nil {
log.Printf("headache configuration warning: cannot load schema, skipping configuration validation. See reason below:\n\t%v\n", err)
return nil
}
return schema
}
120 changes: 76 additions & 44 deletions core/configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package core_test

import (
"github.com/fbiville/headache/core"
"github.com/fbiville/headache/core_mocks"
"github.com/fbiville/headache/fs"
"github.com/fbiville/headache/fs_mocks"
"github.com/fbiville/headache/helper_mocks"
Expand All @@ -33,36 +34,42 @@ var _ = Describe("Configuration parser", func() {
t GinkgoTInterface
fileReader *fs_mocks.FileReader
fileWriter *fs_mocks.FileWriter
fileSystem fs.FileSystem
fileSystem *fs.FileSystem
versioningClient *vcs_mocks.VersioningClient
tracker *fs_mocks.ExecutionTracker
tracker *core_mocks.ExecutionTracker
pathMatcher *fs_mocks.PathMatcher
clock *helper_mocks.Clock
initialChanges []FileChange
includes []string
excludes []string
resultingChanges []FileChange
systemConfiguration core.SystemConfiguration
systemConfiguration *core.SystemConfiguration
data map[string]string
revision string
)

BeforeEach(func() {
t = GinkgoT()
fileReader = new(fs_mocks.FileReader)
fileWriter = new(fs_mocks.FileWriter)
fileSystem = fs.FileSystem{FileWriter: fileWriter, FileReader: fileReader}
fileSystem = &fs.FileSystem{FileWriter: fileWriter, FileReader: fileReader}
versioningClient = new(vcs_mocks.VersioningClient)
tracker = new(fs_mocks.ExecutionTracker)
tracker = new(core_mocks.ExecutionTracker)
pathMatcher = new(fs_mocks.PathMatcher)
clock = new(helper_mocks.Clock)
initialChanges = []FileChange{{Path: "hello-world.go"}, {Path: "license.txt"}}
includes = []string{"../fixtures/hello_*.go"}
excludes = []string{}
resultingChanges = []FileChange{initialChanges[0]}
systemConfiguration = core.SystemConfiguration{
systemConfiguration = &core.SystemConfiguration{
FileSystem: fileSystem,
Clock: clock,
VersioningClient: versioningClient,
}
data = map[string]string{
"Owner": "ACME Labs",
}
revision = "some-sha"
})

AfterEach(func() {
Expand All @@ -75,21 +82,18 @@ var _ = Describe("Configuration parser", func() {
})

It("pre-computes the final configuration", func() {
tracker.On("ReadLinesAtLastExecutionRevision", "some-header").
Return(unchangedHeaderContents("Copyright {{.Year}} {{.Owner}}\n\nSome fictional license"), nil)
tracker.On("GetLastExecutionRevision").Return("some-sha", nil)
versioningClient.On("GetChanges", "some-sha").Return(initialChanges, nil)
pathMatcher.On("MatchFiles", initialChanges, includes, excludes, fileSystem).Return(resultingChanges)
versioningClient.On("AddMetadata", resultingChanges, clock).Return(resultingChanges, nil)

configuration := core.Configuration{
configuration := &core.Configuration{
HeaderFile: "some-header",
CommentStyle: "SlashSlash",
Includes: includes,
Excludes: excludes,
TemplateData: map[string]string{
"Owner": "ACME Labs",
}}
TemplateData: data,
}
tracker.On("RetrieveVersionedTemplate", configuration).
Return(unchangedHeaderContents("Copyright {{.Year}} {{.Owner}}\n\nSome fictional license", data, revision), nil)
versioningClient.On("GetChanges", revision).Return(initialChanges, nil)
pathMatcher.On("MatchFiles", initialChanges, includes, excludes, fileSystem).Return(resultingChanges)
versioningClient.On("AddMetadata", resultingChanges, clock).Return(resultingChanges, nil)

changeSet, err := core.ParseConfiguration(configuration, systemConfiguration, tracker, pathMatcher)

Expand All @@ -99,21 +103,18 @@ var _ = Describe("Configuration parser", func() {
})

It("pre-computes the header contents with a different comment style", func() {
tracker.On("ReadLinesAtLastExecutionRevision", "some-header").
Return(unchangedHeaderContents("Copyright {{.Year}} {{.Owner}}\n\nSome fictional license"), nil)
tracker.On("GetLastExecutionRevision").Return("some-sha", nil)
versioningClient.On("GetChanges", "some-sha").Return(initialChanges, nil)
pathMatcher.On("MatchFiles", initialChanges, includes, excludes, fileSystem).Return(resultingChanges)
versioningClient.On("AddMetadata", resultingChanges, clock).Return(resultingChanges, nil)

configuration := core.Configuration{
configuration := &core.Configuration{
HeaderFile: "some-header",
CommentStyle: "SlashStar",
Includes: includes,
Excludes: excludes,
TemplateData: map[string]string{
"Owner": "ACME Labs",
}}
TemplateData: data,
}
tracker.On("RetrieveVersionedTemplate", configuration).
Return(unchangedHeaderContents("Copyright {{.Year}} {{.Owner}}\n\nSome fictional license", data, revision), nil)
versioningClient.On("GetChanges", revision).Return(initialChanges, nil)
pathMatcher.On("MatchFiles", initialChanges, includes, excludes, fileSystem).Return(resultingChanges)
versioningClient.On("AddMetadata", resultingChanges, clock).Return(resultingChanges, nil)

changeSet, err := core.ParseConfiguration(configuration, systemConfiguration, tracker, pathMatcher)

Expand All @@ -127,21 +128,18 @@ var _ = Describe("Configuration parser", func() {
})

It("pre-computes a regex that allows to detect headers", func() {
tracker.On("ReadLinesAtLastExecutionRevision", "some-header").
Return(unchangedHeaderContents("Copyright {{.Year}} {{.Owner}}"), nil)
tracker.On("GetLastExecutionRevision").Return("some-sha", nil)
versioningClient.On("GetChanges", "some-sha").Return(initialChanges, nil)
pathMatcher.On("MatchFiles", initialChanges, includes, excludes, fileSystem).Return(resultingChanges)
versioningClient.On("AddMetadata", resultingChanges, clock).Return(resultingChanges, nil)

configuration := core.Configuration{
configuration := &core.Configuration{
HeaderFile: "some-header",
CommentStyle: "SlashStar",
Includes: includes,
Excludes: excludes,
TemplateData: map[string]string{
"Owner": "ACME Labs",
}}
TemplateData: data,
}
tracker.On("RetrieveVersionedTemplate", configuration).
Return(unchangedHeaderContents("Copyright {{.Year}} {{.Owner}}", data, revision), nil)
versioningClient.On("GetChanges", revision).Return(initialChanges, nil)
pathMatcher.On("MatchFiles", initialChanges, includes, excludes, fileSystem).Return(resultingChanges)
versioningClient.On("AddMetadata", resultingChanges, clock).Return(resultingChanges, nil)

changeSet, err := core.ParseConfiguration(configuration, systemConfiguration, tracker, pathMatcher)

Expand All @@ -159,14 +157,48 @@ var _ = Describe("Configuration parser", func() {
Expect(regex.MatchString("// Copyright 2009-2012 ACME!")).To(BeTrue(),
"Regex should match contents with different data and comment style")
})

It("computes the header regex based on previous configuration", func() {
configuration := &core.Configuration{
HeaderFile: "some-header",
CommentStyle: "SlashSlash",
Includes: includes,
Excludes: excludes,
TemplateData: data,
}
tracker.On("RetrieveVersionedTemplate", configuration).
Return(&core.VersionedHeaderTemplate{
Current: template("new\nheader {{.Owner}}", map[string]string{"Owner": "Someone"}),
Revision: revision,
Previous: template("{{.Notice}} - old\nheader", map[string]string{"Notice": "Redding"}),
}, nil)
pathMatcher.On("ScanAllFiles", includes, excludes, fileSystem).Return(resultingChanges, nil)
versioningClient.On("AddMetadata", resultingChanges, clock).Return(resultingChanges, nil)

changeSet, err := core.ParseConfiguration(configuration, systemConfiguration, tracker, pathMatcher)

regex := changeSet.HeaderRegex
Expect(err).To(BeNil())
Expect(regex.MatchString("// Redding - old\n// header")).To(BeTrue(),
"Regex should match headers generated from previous run")
})
})

func unchangedHeaderContents(str string) fs.HeaderContents {
lines := strings.Split(str, "\n")
return fs.HeaderContents{
PreviousLines: lines,
CurrentLines: lines,
func unchangedHeaderContents(lines string, data map[string]string, revision string) *core.VersionedHeaderTemplate {
unchangedTemplate := template(lines, data)
return &core.VersionedHeaderTemplate{
Current: unchangedTemplate,
Revision: revision,
Previous: unchangedTemplate,
}
}

func template(lines string, data map[string]string) *core.HeaderTemplate {
unchangedTemplate := &core.HeaderTemplate{
Lines: strings.Split(lines, "\n"),
Data: data,
}
return unchangedTemplate
}

func onlyPaths(changes []FileChange) []FileChange {
Expand Down
Loading

0 comments on commit ece3139

Please sign in to comment.