Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ require (
github.com/hashicorp/go-plugin v1.7.0
github.com/jellydator/ttlcache/v3 v3.4.0
github.com/joho/godotenv v1.5.1
github.com/json-iterator/go v1.1.12
github.com/kinbiko/jsonassert v1.2.0
github.com/lib/pq v1.10.9
github.com/mattn/go-sqlite3 v1.14.32
Expand Down Expand Up @@ -140,7 +141,6 @@ require (
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/julienschmidt/httprouter v1.3.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
Expand Down
12 changes: 10 additions & 2 deletions pipeline/frontend/yaml/constraint/constraint.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

"go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata"
yamlBaseTypes "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/types/base"
"go.woodpecker-ci.org/woodpecker/v3/shared/optional"
)

type (
Expand All @@ -43,7 +44,7 @@ type (
Cron List `yaml:"cron,omitempty"`
Status List `yaml:"status,omitempty"`
Matrix Map `yaml:"matrix,omitempty"`
Local yamlBaseTypes.BoolTrue `yaml:"local,omitempty"`
Local optional.Option[bool] `yaml:"local,omitempty"`
Path Path `yaml:"path,omitempty"`
Evaluate string `yaml:"evaluate,omitempty"`
Event yamlBaseTypes.StringOrSlice `yaml:"event,omitempty"`
Expand Down Expand Up @@ -102,7 +103,7 @@ func (when *When) IncludesStatusSuccess() bool {
// False if (any) non local.
func (when *When) IsLocal() bool {
for _, c := range when.Constraints {
if !c.Local.Bool() {
if !c.Local.ValueOrDefault(true) {
return false
}
}
Expand Down Expand Up @@ -132,6 +133,13 @@ func (when *When) UnmarshalYAML(value *yaml.Node) error {

// MarshalYAML implements custom Yaml marshaling.
func (when When) MarshalYAML() (any, error) {
// clean up local if true make it none as we will default to true
for i := range when.Constraints {
if when.Constraints[i].Local.ValueOrDefault(true) {
when.Constraints[i].Local = optional.None[bool]()
}
}

switch len(when.Constraints) {
case 0:
return nil, nil
Expand Down
23 changes: 15 additions & 8 deletions pipeline/frontend/yaml/constraint/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ import (
"gopkg.in/yaml.v3"

yamlBaseTypes "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/types/base"
"go.woodpecker-ci.org/woodpecker/v3/shared/optional"
)

// Path defines a runtime constrain for exclude & include paths.
type Path struct {
Include []string `yaml:"include,omitempty"`
Exclude []string `yaml:"exclude,omitempty"`
IgnoreMessage string `yaml:"ignore_message,omitempty"`
OnEmpty yamlBaseTypes.BoolTrue `yaml:"on_empty,omitempty"`
Include []string `yaml:"include,omitempty"`
Exclude []string `yaml:"exclude,omitempty"`
IgnoreMessage string `yaml:"ignore_message,omitempty"`
OnEmpty optional.Option[bool] `yaml:"on_empty,omitempty"`
}

// UnmarshalYAML unmarshal the constraint.
Expand All @@ -38,7 +39,7 @@ func (c *Path) UnmarshalYAML(value *yaml.Node) error {
Include yamlBaseTypes.StringOrSlice `yaml:"include"`
Exclude yamlBaseTypes.StringOrSlice `yaml:"exclude"`
IgnoreMessage string `yaml:"ignore_message"`
OnEmpty yamlBaseTypes.BoolTrue `yaml:"on_empty"`
OnEmpty optional.Option[bool] `yaml:"on_empty"`
}{}

var out2 yamlBaseTypes.StringOrSlice
Expand Down Expand Up @@ -67,18 +68,24 @@ func (c Path) MarshalYAML() (any, error) {
// if only Include is set return simple syntax
if len(c.Exclude) == 0 &&
len(c.IgnoreMessage) == 0 &&
c.OnEmpty.Bool() {
c.OnEmpty.ValueOrDefault(true) {
if len(c.Include) == 0 {
return nil, nil
}
return yamlBaseTypes.StringOrSlice(c.Include), nil
}

// clean up on_empty if true make it none as we will default to true
if c.OnEmpty.ValueOrDefault(true) {
c.OnEmpty = optional.None[bool]()
}

// we can not return type Path as it would lead to infinite recursion :/
return struct {
Include yamlBaseTypes.StringOrSlice `yaml:"include,omitempty"`
Exclude yamlBaseTypes.StringOrSlice `yaml:"exclude,omitempty"`
IgnoreMessage string `yaml:"ignore_message,omitempty"`
OnEmpty yamlBaseTypes.BoolTrue `yaml:"on_empty,omitempty"`
OnEmpty optional.Option[bool] `yaml:"on_empty,omitempty"`
}{
Include: c.Include,
Exclude: c.Exclude,
Expand All @@ -97,7 +104,7 @@ func (c *Path) Match(v []string, message string) bool {

// return value based on 'on_empty', if there are no commit files (empty commit)
if len(v) == 0 {
return c.OnEmpty.Bool()
return c.OnEmpty.ValueOrDefault(true)
}

if len(c.Exclude) > 0 && c.Excludes(v) {
Expand Down
57 changes: 0 additions & 57 deletions pipeline/frontend/yaml/types/base/bool.go

This file was deleted.

78 changes: 0 additions & 78 deletions pipeline/frontend/yaml/types/base/bool_test.go

This file was deleted.

82 changes: 82 additions & 0 deletions shared/optional/option.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright 2025 Woodpecker Authors.
// Copyright 2024 The Gitea Authors.
//
// Licensed under the MIT License.

package optional

import "reflect"

type Option[T any] []T

func None[T any]() Option[T] {
return nil
}

func Some[T any](v T) Option[T] {
return Option[T]{v}
}

func FromPtr[T any](v *T) Option[T] {
if v == nil {
return None[T]()
}
return Some(*v)
}

func FromNonDefault[T comparable](v T) Option[T] {
var zero T
if v == zero {
return None[T]()
}
return Some(v)
}

func (o Option[T]) Has() bool {
return o != nil
}

func (o Option[T]) Value() T {
var zero T
return o.ValueOrDefault(zero)
}

func (o Option[T]) ValueOrDefault(v T) T {
if o.Has() {
return o[0]
}
return v
}

func (o Option[T]) ToPtr() *T {
if o.Has() {
return &o[0]
}
return nil
}

// ExtractValue return value or nil and bool if object was an Optional
// it should only be used if you already have to deal with interface{} values
// and expect an Option type within it.
func ExtractValue(obj any) (any, bool) {
rt := reflect.TypeOf(obj)
if rt.Kind() != reflect.Slice {
return nil, false
}

type hasHasFunc interface {
Has() bool
}
if hasObj, ok := obj.(hasHasFunc); !ok {
return nil, false
} else if !hasObj.Has() {
return nil, true
}

rv := reflect.ValueOf(obj)
if rv.Len() != 1 {
// it's still false as optional.Option[T] types would have reported with hasObj.Has() that it is empty
return nil, false
}
return rv.Index(0).Interface(), true
}
Loading