From 2ff91e6a055c79d913dda6e7c2774b4615659fe0 Mon Sep 17 00:00:00 2001 From: Alex Eftimie Date: Mon, 28 Jun 2021 23:50:35 +0200 Subject: [PATCH] Add support for path-prefix condition (#174) Example: ```yaml when: path: '*.md' ``` should match only builds in which the commit added/removed or modified files with the *.md extension Co-authored-by: 6543 <6543@obermui.de> --- cncd/pipeline/pipeline/frontend/metadata.go | 13 +-- .../pipeline/frontend/yaml/constraint.go | 85 ++++++++++++++++- .../pipeline/frontend/yaml/constraint_test.go | 94 +++++++++++++++++++ docs/docs/pipeline.md | 23 +++++ go.mod | 4 + go.sum | 17 ++++ model/build.go | 65 ++++++------- remote/github/convert.go | 35 ++++--- remote/github/fixtures/hooks.go | 5 +- remote/github/parse_test.go | 2 + remote/github/types.go | 4 + server/procBuilder.go | 2 + store/datastore/ddl/mysql/ddl_gen.go | 20 ++++ .../025_add_builds_changed_files_column.sql | 5 + store/datastore/ddl/postgres/ddl_gen.go | 20 ++++ .../025_add_builds_changed_files_column.sql | 5 + store/datastore/ddl/sqlite/ddl_gen.go | 20 ++++ .../025_add_builds_changed_files_column.sql | 5 + 18 files changed, 373 insertions(+), 51 deletions(-) create mode 100644 store/datastore/ddl/mysql/files/025_add_builds_changed_files_column.sql create mode 100644 store/datastore/ddl/postgres/files/025_add_builds_changed_files_column.sql create mode 100644 store/datastore/ddl/sqlite/files/025_add_builds_changed_files_column.sql diff --git a/cncd/pipeline/pipeline/frontend/metadata.go b/cncd/pipeline/pipeline/frontend/metadata.go index a76423b3b..d5570247f 100644 --- a/cncd/pipeline/pipeline/frontend/metadata.go +++ b/cncd/pipeline/pipeline/frontend/metadata.go @@ -54,12 +54,13 @@ type ( // Commit defines runtime metadata for a commit. Commit struct { - Sha string `json:"sha,omitempty"` - Ref string `json:"ref,omitempty"` - Refspec string `json:"refspec,omitempty"` - Branch string `json:"branch,omitempty"` - Message string `json:"message,omitempty"` - Author Author `json:"author,omitempty"` + Sha string `json:"sha,omitempty"` + Ref string `json:"ref,omitempty"` + Refspec string `json:"refspec,omitempty"` + Branch string `json:"branch,omitempty"` + Message string `json:"message,omitempty"` + Author Author `json:"author,omitempty"` + ChangedFiles []string `json:"changed_files,omitempty"` } // Author defines runtime metadata for a commit author. diff --git a/cncd/pipeline/pipeline/frontend/yaml/constraint.go b/cncd/pipeline/pipeline/frontend/yaml/constraint.go index 33e22de95..28a7e85cf 100644 --- a/cncd/pipeline/pipeline/frontend/yaml/constraint.go +++ b/cncd/pipeline/pipeline/frontend/yaml/constraint.go @@ -3,6 +3,7 @@ package yaml import ( "fmt" "path/filepath" + "strings" libcompose "github.com/docker/libcompose/yaml" "github.com/woodpecker-ci/woodpecker/cncd/pipeline/pipeline/frontend" @@ -23,6 +24,7 @@ type ( Status Constraint Matrix ConstraintMap Local types.BoolTrue + Path ConstraintPath } // Constraint defines a runtime constraint. @@ -36,6 +38,13 @@ type ( Include map[string]string Exclude map[string]string } + + // ConstraintPath defines a runtime constrain for paths + ConstraintPath struct { + Include []string + Exclude []string + IgnoreMessage string `yaml:"ignore_message,omitempty"` + } ) // Match returns true if all constraints match the given input. If a single @@ -48,7 +57,8 @@ func (c *Constraints) Match(metadata frontend.Metadata) bool { c.Repo.Match(metadata.Repo.Name) && c.Ref.Match(metadata.Curr.Commit.Ref) && c.Instance.Match(metadata.Sys.Host) && - c.Matrix.Match(metadata.Job.Matrix) + c.Matrix.Match(metadata.Job.Matrix) && + c.Path.Match(metadata.Curr.Commit.ChangedFiles, metadata.Curr.Commit.Message) } // Match returns true if the string matches the include patterns and does not @@ -163,3 +173,76 @@ func (c *ConstraintMap) UnmarshalYAML(unmarshal func(interface{}) error) error { } return nil } + +// UnmarshalYAML unmarshals the constraint. +func (c *ConstraintPath) UnmarshalYAML(value *yaml.Node) error { + var out1 = struct { + Include libcompose.Stringorslice `yaml:"include,omitempty"` + Exclude libcompose.Stringorslice `yaml:"exclude,omitempty"` + IgnoreMessage string `yaml:"ignore_message,omitempty"` + }{} + + var out2 libcompose.Stringorslice + + err1 := value.Decode(&out1) + err2 := value.Decode(&out2) + + c.Exclude = out1.Exclude + c.IgnoreMessage = out1.IgnoreMessage + c.Include = append( + out1.Include, + out2..., + ) + + if err1 != nil && err2 != nil { + y, _ := yaml.Marshal(value) + return fmt.Errorf("Could not parse condition: %s", y) + } + + return nil +} + +// Match returns true if file paths in string slice matches the include and not exclude patterns +// or if commit message contains ignore message. +func (c *ConstraintPath) Match(v []string, message string) bool { + // ignore file pattern matches if the commit message contains a pattern + if len(c.IgnoreMessage) > 0 && strings.Contains(strings.ToLower(message), strings.ToLower(c.IgnoreMessage)) { + return true + } + // always match if there are no commit files (empty commit) + if len(v) == 0 { + return true + } + + if len(c.Exclude) > 0 && c.Excludes(v) { + return false + } + if len(c.Include) > 0 && !c.Includes(v) { + return false + } + return true +} + +// Includes returns true if the string matches any of the include patterns. +func (c *ConstraintPath) Includes(v []string) bool { + for _, pattern := range c.Include { + for _, file := range v { + if ok, _ := filepath.Match(pattern, file); ok { + return true + } + } + } + return false +} + +// Excludes returns true if the string matches any of the exclude patterns. +func (c *ConstraintPath) Excludes(v []string) bool { + for _, pattern := range c.Exclude { + for _, file := range v { + if ok, _ := filepath.Match(pattern, file); ok { + return true + } + } + } + return false +} diff --git a/cncd/pipeline/pipeline/frontend/yaml/constraint_test.go b/cncd/pipeline/pipeline/frontend/yaml/constraint_test.go index 8c3b7dd3b..e4f99af5a 100644 --- a/cncd/pipeline/pipeline/frontend/yaml/constraint_test.go +++ b/cncd/pipeline/pipeline/frontend/yaml/constraint_test.go @@ -150,6 +150,94 @@ func TestConstraint(t *testing.T) { } } +func TestConstraintList(t *testing.T) { + testdata := []struct { + conf string + with []string + message string + want bool + }{ + { + conf: "", + with: []string{"CHANGELOG.md", "README.md"}, + want: true, + }, + { + conf: "CHANGELOG.md", + with: []string{"CHANGELOG.md", "README.md"}, + want: true, + }, + { + conf: "'*.md'", + with: []string{"CHANGELOG.md", "README.md"}, + want: true, + }, + { + conf: "['*.md']", + with: []string{"CHANGELOG.md", "README.md"}, + want: true, + }, + { + conf: "{ include: [ README.md ] }", + with: []string{"CHANGELOG.md"}, + want: false, + }, + { + conf: "{ exclude: [ README.md ] }", + with: []string{"design.md"}, + want: true, + }, + // include and exclude blocks + { + conf: "{ include: [ '*.md', '*.ini' ], exclude: [ CHANGELOG.md ] }", + with: []string{"README.md"}, + want: true, + }, + { + conf: "{ include: [ '*.md' ], exclude: [ CHANGELOG.md ] }", + with: []string{"CHANGELOG.md"}, + want: false, + }, + { + conf: "{ include: [ '*.md' ], exclude: [ CHANGELOG.md ] }", + with: []string{"README.md", "CHANGELOG.md"}, + want: false, + }, + // commit message ignore matches + { + conf: "{ include: [ README.md ], ignore_message: '[ALL]' }", + with: []string{"CHANGELOG.md"}, + message: "Build them [ALL]", + want: true, + }, + { + conf: "{ exclude: [ '*.php' ], ignore_message: '[ALL]' }", + with: []string{"myfile.php"}, + message: "Build them [ALL]", + want: true, + }, + { + conf: "{ ignore_message: '[ALL]' }", + with: []string{}, + message: "Build them [ALL]", + want: true, + }, + // empty commit + { + conf: "{ include: [ README.md ] }", + with: []string{}, + want: true, + }, + } + for _, test := range testdata { + c := parseConstraintPath(test.conf) + got, want := c.Match(test.with, test.message), test.want + if got != want { + t.Errorf("Expect %q matches %q is %v", test.with, test.conf, want) + } + } +} + func TestConstraintMap(t *testing.T) { testdata := []struct { conf string @@ -371,3 +459,9 @@ func parseConstraintMap(s string) *ConstraintMap { yaml.Unmarshal([]byte(s), c) return c } + +func parseConstraintPath(s string) *ConstraintPath { + c := &ConstraintPath{} + yaml.Unmarshal([]byte(s), c) + return c +} diff --git a/docs/docs/pipeline.md b/docs/docs/pipeline.md index baab18f3a..b66172a3a 100644 --- a/docs/docs/pipeline.md +++ b/docs/docs/pipeline.md @@ -384,6 +384,29 @@ when: instance: stage.drone.company.com ``` +Execute a step only on commit with certain files added/removed/modified: + +**NOTE: Feature is only available for Github repositories.** + +```diff +when: + path: "src/*" +``` + +Execute a step only on commit excluding certain files added/removed/modified: + + +**NOTE: Feature is only available for Github repositories.** + +```diff +when: + path: + exclude: [ '*.md', '*.ini' ] + ignore_message: "[ALL]" +``` + +> Note for `path` conditions: passing `[ALL]` inside the commit message will ignore all path conditions. + #### Failure Execution Woodpecker uses the container exit code to determine the success or failure status of a build. Non-zero exit codes fail the build and cause the pipeline to immediately exit. diff --git a/go.mod b/go.mod index 25df2b512..71b9ea0d5 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,8 @@ require ( docker.io/go-docker v1.0.0 github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 // indirect + github.com/bmatcuk/doublestar v1.3.4 // indirect + github.com/bradrydzewski/togo v0.0.0-20180401185031-50a0e4726e74 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/dgrijalva/jwt-go v0.0.0-20150904212456-c1da56349675 github.com/dimfeld/httptreemux v5.0.1+incompatible @@ -55,6 +57,8 @@ require ( github.com/stretchr/testify v1.5.1 github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5 github.com/urfave/cli v1.22.4 + github.com/vektra/mockery v1.1.2 // indirect + github.com/woodpecker-ci/togo v0.0.0-20180401185031-50a0e4726e74 // indirect golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 golang.org/x/net v0.0.0-20210614182718-04defd469f4e golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d diff --git a/go.sum b/go.sum index d95a19f34..29379ecc8 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,10 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0= +github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= +github.com/bradrydzewski/togo v0.0.0-20180401185031-50a0e4726e74 h1:fE72rAOk9gpizJL3mNv+Ez+3yt/GCoZWtkKEPLTZvvM= +github.com/bradrydzewski/togo v0.0.0-20180401185031-50a0e4726e74/go.mod h1:+zgWTTgi3saXD5N9SSA+LYteMbFoIJKJ9WEPXoV0jQA= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -227,15 +231,23 @@ github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vektra/mockery v1.1.2 h1:uc0Yn67rJpjt8U/mAZimdCKn9AeA97BOkjpmtBSlfP4= +github.com/vektra/mockery v1.1.2/go.mod h1:VcfZjKaFOPO+MpN4ZvwPjs4c48lkq1o3Ym8yHZJu0jU= +github.com/woodpecker-ci/togo v0.0.0-20180401185031-50a0e4726e74 h1:q/tWgA3hMWrAQqsS4yfhc0+w4RevBGr9ghem/bFFDRY= +github.com/woodpecker-ci/togo v0.0.0-20180401185031-50a0e4726e74/go.mod h1:lykh/ei/caPO6sv4NN+pqnDTo8kEKhZcnhafN8GhGNs= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -246,6 +258,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -283,7 +296,11 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200323144430-8dcfad9e016e h1:ssd5ulOvVWlh4kDSUF2SqzmMeWfjmwDXM+uGw/aQjRE= +golang.org/x/tools v0.0.0-20200323144430-8dcfad9e016e/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= diff --git a/model/build.go b/model/build.go index f1c7e00e2..4dc6235cc 100644 --- a/model/build.go +++ b/model/build.go @@ -16,38 +16,39 @@ package model // swagger:model build type Build struct { - ID int64 `json:"id" meddler:"build_id,pk"` - RepoID int64 `json:"-" meddler:"build_repo_id"` - ConfigID int64 `json:"-" meddler:"build_config_id"` - Number int `json:"number" meddler:"build_number"` - Parent int `json:"parent" meddler:"build_parent"` - Event string `json:"event" meddler:"build_event"` - Status string `json:"status" meddler:"build_status"` - Error string `json:"error" meddler:"build_error"` - Enqueued int64 `json:"enqueued_at" meddler:"build_enqueued"` - Created int64 `json:"created_at" meddler:"build_created"` - Started int64 `json:"started_at" meddler:"build_started"` - Finished int64 `json:"finished_at" meddler:"build_finished"` - Deploy string `json:"deploy_to" meddler:"build_deploy"` - Commit string `json:"commit" meddler:"build_commit"` - Branch string `json:"branch" meddler:"build_branch"` - Ref string `json:"ref" meddler:"build_ref"` - Refspec string `json:"refspec" meddler:"build_refspec"` - Remote string `json:"remote" meddler:"build_remote"` - Title string `json:"title" meddler:"build_title"` - Message string `json:"message" meddler:"build_message"` - Timestamp int64 `json:"timestamp" meddler:"build_timestamp"` - Sender string `json:"sender" meddler:"build_sender"` - Author string `json:"author" meddler:"build_author"` - Avatar string `json:"author_avatar" meddler:"build_avatar"` - Email string `json:"author_email" meddler:"build_email"` - Link string `json:"link_url" meddler:"build_link"` - Signed bool `json:"signed" meddler:"build_signed"` // deprecate - Verified bool `json:"verified" meddler:"build_verified"` // deprecate - Reviewer string `json:"reviewed_by" meddler:"build_reviewer"` - Reviewed int64 `json:"reviewed_at" meddler:"build_reviewed"` - Procs []*Proc `json:"procs,omitempty" meddler:"-"` - Files []*File `json:"files,omitempty" meddler:"-"` + ID int64 `json:"id" meddler:"build_id,pk"` + RepoID int64 `json:"-" meddler:"build_repo_id"` + ConfigID int64 `json:"-" meddler:"build_config_id"` + Number int `json:"number" meddler:"build_number"` + Parent int `json:"parent" meddler:"build_parent"` + Event string `json:"event" meddler:"build_event"` + Status string `json:"status" meddler:"build_status"` + Error string `json:"error" meddler:"build_error"` + Enqueued int64 `json:"enqueued_at" meddler:"build_enqueued"` + Created int64 `json:"created_at" meddler:"build_created"` + Started int64 `json:"started_at" meddler:"build_started"` + Finished int64 `json:"finished_at" meddler:"build_finished"` + Deploy string `json:"deploy_to" meddler:"build_deploy"` + Commit string `json:"commit" meddler:"build_commit"` + Branch string `json:"branch" meddler:"build_branch"` + Ref string `json:"ref" meddler:"build_ref"` + Refspec string `json:"refspec" meddler:"build_refspec"` + Remote string `json:"remote" meddler:"build_remote"` + Title string `json:"title" meddler:"build_title"` + Message string `json:"message" meddler:"build_message"` + Timestamp int64 `json:"timestamp" meddler:"build_timestamp"` + Sender string `json:"sender" meddler:"build_sender"` + Author string `json:"author" meddler:"build_author"` + Avatar string `json:"author_avatar" meddler:"build_avatar"` + Email string `json:"author_email" meddler:"build_email"` + Link string `json:"link_url" meddler:"build_link"` + Signed bool `json:"signed" meddler:"build_signed"` // deprecate + Verified bool `json:"verified" meddler:"build_verified"` // deprecate + Reviewer string `json:"reviewed_by" meddler:"build_reviewer"` + Reviewed int64 `json:"reviewed_at" meddler:"build_reviewed"` + Procs []*Proc `json:"procs,omitempty" meddler:"-"` + Files []*File `json:"files,omitempty" meddler:"-"` + ChangedFiles []string `json:"changed_files,omitempty" meddler:"changed_files,json"` } // Trim trims string values that would otherwise exceed diff --git a/remote/github/convert.go b/remote/github/convert.go index 8ca4f70d0..ca810178c 100644 --- a/remote/github/convert.go +++ b/remote/github/convert.go @@ -195,18 +195,20 @@ func convertRepoHook(from *webhook) *model.Repo { // convertPushHook is a helper function used to extract the Build details // from a push webhook and convert to the common Drone Build structure. func convertPushHook(from *webhook) *model.Build { + files := getChangedFilesFromWebhook(from) build := &model.Build{ - Event: model.EventPush, - Commit: from.Head.ID, - Ref: from.Ref, - Link: from.Head.URL, - Branch: strings.Replace(from.Ref, "refs/heads/", "", -1), - Message: from.Head.Message, - Email: from.Head.Author.Email, - Avatar: from.Sender.Avatar, - Author: from.Sender.Login, - Remote: from.Repo.CloneURL, - Sender: from.Sender.Login, + Event: model.EventPush, + Commit: from.Head.ID, + Ref: from.Ref, + Link: from.Head.URL, + Branch: strings.Replace(from.Ref, "refs/heads/", "", -1), + Message: from.Head.Message, + Email: from.Head.Author.Email, + Avatar: from.Sender.Avatar, + Author: from.Sender.Login, + Remote: from.Repo.CloneURL, + Sender: from.Sender.Login, + ChangedFiles: files, } if len(build.Author) == 0 { build.Author = from.Head.Author.Username @@ -282,3 +284,14 @@ func convertPullHook(from *webhook, merge bool) *model.Build { } return build } + +func getChangedFilesFromWebhook(from *webhook) []string { + var files []string + files = append(files, from.Head.Added...) + files = append(files, from.Head.Removed...) + files = append(files, from.Head.Modified...) + if len(files) == 0 { + files = make([]string, 0) + } + return files +} diff --git a/remote/github/fixtures/hooks.go b/remote/github/fixtures/hooks.go index 8bb36e6ee..adb4f70ed 100644 --- a/remote/github/fixtures/hooks.go +++ b/remote/github/fixtures/hooks.go @@ -35,7 +35,10 @@ const HookPush = ` "name": "baxterthehacker", "email": "baxterthehacker@users.noreply.github.com", "username": "baxterthehacker" - } + }, + "added": ["CHANGELOG.md"], + "removed": [], + "modified": ["app/controller/application.rb"] }, "repository": { "id": 35129377, diff --git a/remote/github/parse_test.go b/remote/github/parse_test.go index 4684bc04f..32a3b7622 100644 --- a/remote/github/parse_test.go +++ b/remote/github/parse_test.go @@ -60,6 +60,8 @@ func Test_parser(t *testing.T) { g.Assert(r != nil).IsTrue() g.Assert(b != nil).IsTrue() g.Assert(b.Event).Equal(model.EventPush) + expectedFiles := []string{"CHANGELOG.md", "app/controller/application.rb"} + g.Assert(b.ChangedFiles).Equal(expectedFiles) }) }) diff --git a/remote/github/types.go b/remote/github/types.go index c7882ada1..79a57c701 100644 --- a/remote/github/types.go +++ b/remote/github/types.go @@ -37,6 +37,10 @@ type webhook struct { Email string `json:"email"` Username string `json:"username"` } `json:"committer"` + + Added []string `json:"added"` + Removed []string `json:"removed"` + Modified []string `json:"modified"` } `json:"head_commit"` Sender struct { diff --git a/server/procBuilder.go b/server/procBuilder.go index 518ab2b07..f680604cd 100644 --- a/server/procBuilder.go +++ b/server/procBuilder.go @@ -319,6 +319,7 @@ func metadataFromStruct(repo *model.Repo, build, last *model.Build, proc *model. Email: build.Email, Avatar: build.Avatar, }, + ChangedFiles: build.ChangedFiles, }, }, Prev: frontend.Build{ @@ -341,6 +342,7 @@ func metadataFromStruct(repo *model.Repo, build, last *model.Build, proc *model. Email: last.Email, Avatar: last.Avatar, }, + ChangedFiles: last.ChangedFiles, }, }, Job: frontend.Job{ diff --git a/store/datastore/ddl/mysql/ddl_gen.go b/store/datastore/ddl/mysql/ddl_gen.go index a4c959759..c4b1100a7 100644 --- a/store/datastore/ddl/mysql/ddl_gen.go +++ b/store/datastore/ddl/mysql/ddl_gen.go @@ -192,6 +192,14 @@ var migrations = []struct { name: "update-table-set-repo-fallback-again", stmt: updateTableSetRepoFallbackAgain, }, + { + name: "add-builds-changed_files-column", + stmt: addBuildsChangedfilesColumn, + }, + { + name: "update-builds-set-changed_files", + stmt: updateBuildsSetChangedfiles, + }, } // Migrate performs the database migration. If the migration fails @@ -725,3 +733,15 @@ UPDATE repos SET repo_fallback='false' var updateTableSetRepoFallbackAgain = ` UPDATE repos SET repo_fallback='false' ` + +// +// 025_add_builds_changed_files_column.sql +// + +var addBuildsChangedfilesColumn = ` +ALTER TABLE builds ADD COLUMN changed_files TEXT +` + +var updateBuildsSetChangedfiles = ` +UPDATE builds SET changed_files='[]' +` diff --git a/store/datastore/ddl/mysql/files/025_add_builds_changed_files_column.sql b/store/datastore/ddl/mysql/files/025_add_builds_changed_files_column.sql new file mode 100644 index 000000000..2ff3c14be --- /dev/null +++ b/store/datastore/ddl/mysql/files/025_add_builds_changed_files_column.sql @@ -0,0 +1,5 @@ +-- name: add-builds-changed_files-column +ALTER TABLE builds ADD COLUMN changed_files TEXT + +-- name: update-builds-set-changed_files +UPDATE builds SET changed_files='[]' \ No newline at end of file diff --git a/store/datastore/ddl/postgres/ddl_gen.go b/store/datastore/ddl/postgres/ddl_gen.go index 32fe66ad3..6366bcd3a 100644 --- a/store/datastore/ddl/postgres/ddl_gen.go +++ b/store/datastore/ddl/postgres/ddl_gen.go @@ -192,6 +192,14 @@ var migrations = []struct { name: "update-table-set-repo-fallback-again", stmt: updateTableSetRepoFallbackAgain, }, + { + name: "add-builds-changed_files-column", + stmt: addBuildsChangedfilesColumn, + }, + { + name: "update-builds-set-changed_files", + stmt: updateBuildsSetChangedfiles, + }, } // Migrate performs the database migration. If the migration fails @@ -727,3 +735,15 @@ UPDATE repos SET repo_fallback='false' var updateTableSetRepoFallbackAgain = ` UPDATE repos SET repo_fallback='false' ` + +// +// 025_add_builds_changed_files_column.sql +// + +var addBuildsChangedfilesColumn = ` +ALTER TABLE builds ADD COLUMN changed_files TEXT; +` + +var updateBuildsSetChangedfiles = ` +UPDATE builds SET changed_files='[]' +` diff --git a/store/datastore/ddl/postgres/files/025_add_builds_changed_files_column.sql b/store/datastore/ddl/postgres/files/025_add_builds_changed_files_column.sql new file mode 100644 index 000000000..0406bb4e2 --- /dev/null +++ b/store/datastore/ddl/postgres/files/025_add_builds_changed_files_column.sql @@ -0,0 +1,5 @@ +-- name: add-builds-changed_files-column +ALTER TABLE builds ADD COLUMN changed_files TEXT; + +-- name: update-builds-set-changed_files +UPDATE builds SET changed_files='[]' \ No newline at end of file diff --git a/store/datastore/ddl/sqlite/ddl_gen.go b/store/datastore/ddl/sqlite/ddl_gen.go index ba26a47bf..d014b47cf 100644 --- a/store/datastore/ddl/sqlite/ddl_gen.go +++ b/store/datastore/ddl/sqlite/ddl_gen.go @@ -196,6 +196,14 @@ var migrations = []struct { name: "update-table-set-repo-fallback-again", stmt: updateTableSetRepoFallbackAgain, }, + { + name: "add-builds-changed_files-column", + stmt: addBuildsChangedfilesColumn, + }, + { + name: "update-builds-set-changed_files", + stmt: updateBuildsSetChangedfiles, + }, } // Migrate performs the database migration. If the migration fails @@ -726,3 +734,15 @@ UPDATE repos SET repo_fallback='false' var updateTableSetRepoFallbackAgain = ` UPDATE repos SET repo_fallback='false' ` + +// +// 025_add_builds_changed_files_column.sql +// + +var addBuildsChangedfilesColumn = ` +ALTER TABLE builds ADD COLUMN changed_files TEXT +` + +var updateBuildsSetChangedfiles = ` +UPDATE builds SET changed_files='[]' +` diff --git a/store/datastore/ddl/sqlite/files/025_add_builds_changed_files_column.sql b/store/datastore/ddl/sqlite/files/025_add_builds_changed_files_column.sql new file mode 100644 index 000000000..e834d7fbc --- /dev/null +++ b/store/datastore/ddl/sqlite/files/025_add_builds_changed_files_column.sql @@ -0,0 +1,5 @@ +-- name: add-builds-changed_files-column +ALTER TABLE builds ADD COLUMN changed_files TEXT + +-- name: update-builds-set-changed_files +UPDATE builds SET changed_files='[]'