···
+
"tangled.sh/tangled.sh/core/api/tangled"
+
"github.com/go-git/go-git/v5/plumbing"
+
// - when a repo is modified, it results in the trigger of a "Pipeline"
+
// - a repo could consist of several workflow files
+
// * .tangled/workflows/test.yml
+
// * .tangled/workflows/lint.yml
+
// - therefore a pipeline consists of several workflows, these execute in parallel
+
// - each workflow consists of some execution steps, these execute serially
+
// this is simply a structural representation of the workflow file
+
Name string `yaml:"-"` // name of the workflow file
+
When []Constraint `yaml:"when"`
+
Dependencies Dependencies `yaml:"dependencies"`
+
Steps []Step `yaml:"steps"`
+
Environment map[string]string `yaml:"environment"`
+
CloneOpts CloneOpts `yaml:"clone"`
+
Event StringList `yaml:"event"`
+
Branch StringList `yaml:"branch"` // this is optional, and only applied on "push" events
+
Dependencies map[string][]string
+
Skip bool `yaml:"skip"`
+
Depth int `yaml:"depth"`
+
IncludeSubmodules bool `yaml:"submodules"`
+
Name string `yaml:"name"`
+
Command string `yaml:"command"`
+
TriggerKindPush string = "push"
+
TriggerKindPullRequest string = "pull_request"
+
TriggerKindManual string = "manual"
+
func FromFile(name string, contents []byte) (Workflow, error) {
+
err := yaml.Unmarshal(contents, &wf)
+
// if any of the constraints on a workflow is true, return true
+
func (w *Workflow) Match(trigger tangled.Pipeline_TriggerMetadata) bool {
+
// manual triggers always run the workflow
+
if trigger.Manual != nil {
+
// if not manual, run through the constraint list and see if any one matches
+
for _, c := range w.When {
+
// no constraints, always run this workflow
+
func (c *Constraint) Match(trigger tangled.Pipeline_TriggerMetadata) bool {
+
// manual triggers always pass this constraint
+
if trigger.Manual != nil {
+
// apply event constraints
+
match = match && c.MatchEvent(trigger.Kind)
+
// apply branch constraints for PRs
+
if trigger.PullRequest != nil {
+
match = match && c.MatchBranch(trigger.PullRequest.TargetBranch)
+
// apply ref constraints for pushes
+
if trigger.Push != nil {
+
match = match && c.MatchRef(trigger.Push.Ref)
+
func (c *Constraint) MatchBranch(branch string) bool {
+
return slices.Contains(c.Branch, branch)
+
func (c *Constraint) MatchRef(ref string) bool {
+
refName := plumbing.ReferenceName(ref)
+
if refName.IsBranch() {
+
return slices.Contains(c.Branch, refName.Short())
+
fmt.Println("no", c.Branch, refName.Short())
+
func (c *Constraint) MatchEvent(event string) bool {
+
return slices.Contains(c.Event, event)
+
// Custom unmarshaller for StringList
+
func (s *StringList) UnmarshalYAML(unmarshal func(any) error) error {
+
if err := unmarshal(&stringType); err == nil {
+
*s = []string{stringType}
+
if err := unmarshal(&sliceType); err == nil {
+
parts := make([]string, len(sliceType))
+
for k, v := range sliceType {
+
if sv, ok := v.(string); ok {
+
return fmt.Errorf("cannot unmarshal '%v' of type %T into a string value", v, v)
+
return errors.New("failed to unmarshal StringOrSlice")
+
// conversion utilities to atproto records
+
func (d Dependencies) AsRecord() []tangled.Pipeline_Dependencies_Elem {
+
var deps []tangled.Pipeline_Dependencies_Elem
+
for registry, packages := range d {
+
deps = append(deps, tangled.Pipeline_Dependencies_Elem{
+
func (s Step) AsRecord() tangled.Pipeline_Step {
+
return tangled.Pipeline_Step{
+
func (c CloneOpts) AsRecord() tangled.Pipeline_CloneOpts {
+
return tangled.Pipeline_CloneOpts{
+
Submodules: c.IncludeSubmodules,