Skip to content
Open
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
45 changes: 44 additions & 1 deletion checkout.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package pipeline

import "encoding/json"
import (
"encoding/json"
"fmt"

"github.com/buildkite/go-pipeline/ordered"
)

var _ json.Marshaler = (*Checkout)(nil)

Expand All @@ -12,6 +17,9 @@ type Checkout struct {
// the env var explicitly.
Submodules *bool `json:"submodules,omitempty" yaml:"submodules,omitempty"`

// Sparse configures sparse checkout. nil = unset (full checkout).
Sparse *Sparse `json:"sparse,omitempty" yaml:"sparse,omitempty"`

// RemainingFields stores any other mapping items so they at least
// survive an unmarshal-marshal round-trip.
RemainingFields map[string]any `yaml:",inline"`
Expand All @@ -20,3 +28,38 @@ type Checkout struct {
func (c *Checkout) MarshalJSON() ([]byte, error) {
return inlineFriendlyMarshalJSON(c)
}

var _ interface {
json.Marshaler
ordered.Unmarshaler
} = (*Sparse)(nil)

// Sparse models sparse checkout configuration.
type Sparse struct {
// Paths is the list of paths to include in the sparse checkout.
Paths []string `json:"paths,omitempty" yaml:"paths,omitempty"`

// RemainingFields stores any other mapping items so they at least
// survive an unmarshal-marshal round-trip.
RemainingFields map[string]any `yaml:",inline"`
}

// MarshalJSON marshals Sparse to JSON. Special handling is needed because
// yaml.v3 has "inline" but encoding/json has no concept of it.
func (s *Sparse) MarshalJSON() ([]byte, error) {
return inlineFriendlyMarshalJSON(s)
}

// UnmarshalOrdered unmarshals a Sparse from an ordered map.
func (s *Sparse) UnmarshalOrdered(o any) error {
switch o.(type) {
case *ordered.MapSA:
type wrappedSparse Sparse
if err := ordered.Unmarshal(o, (*wrappedSparse)(s)); err != nil {
return fmt.Errorf("unmarshaling sparse: %w", err)
}
return nil
default:
return fmt.Errorf("unmarshaling sparse: unsupported type %T, want a mapping", o)
}
}
124 changes: 124 additions & 0 deletions checkout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,26 @@ func TestCheckoutUnmarshalYAML(t *testing.T) {
`{skip: true}`,
Checkout{RemainingFields: map[string]any{"skip": true}},
},
{
"sparse with paths multi-line",
"sparse:\n paths:\n - .buildkite/\n - src/",
Checkout{Sparse: &Sparse{Paths: []string{".buildkite/", "src/"}}},
},
{
"sparse with paths inline",
`{sparse: {paths: [".buildkite/", "src/"]}}`,
Checkout{Sparse: &Sparse{Paths: []string{".buildkite/", "src/"}}},
},
{
"sparse omitted",
`{}`,
Checkout{},
},
{
"sparse with empty paths",
`{sparse: {paths: []}}`,
Checkout{Sparse: &Sparse{Paths: []string{}}},
},
}

for _, tc := range cases {
Expand Down Expand Up @@ -84,6 +104,16 @@ func TestCheckoutMarshalYAML(t *testing.T) {
wantSubstrs: []string{"skip: true"},
notWant: []string{"submodules"},
},
{
name: "sparse with paths",
c: Checkout{Sparse: &Sparse{Paths: []string{".buildkite/", "src/"}}},
wantSubstrs: []string{"sparse:", "paths:", ".buildkite/", "src/"},
},
{
name: "sparse nil omitted",
c: Checkout{},
notWant: []string{"sparse"},
},
}

for _, tc := range cases {
Expand Down Expand Up @@ -129,6 +159,16 @@ func TestCheckoutMarshalJSON(t *testing.T) {
},
`{"depth":1,"gibberish":"x","submodules":true}`,
},
{
"sparse with paths",
&Checkout{Sparse: &Sparse{Paths: []string{".buildkite/", "src/"}}},
`{"sparse":{"paths":[".buildkite/","src/"]}}`,
},
{
"sparse nil omitted",
&Checkout{},
`{}`,
},
}

for _, tc := range cases {
Expand Down Expand Up @@ -159,6 +199,16 @@ func TestCheckoutUnmarshalJSON(t *testing.T) {
{"submodules null", `{"submodules":null}`, Checkout{}},
{"submodules true", `{"submodules":true}`, Checkout{Submodules: ptr(true)}},
{"submodules false", `{"submodules":false}`, Checkout{Submodules: ptr(false)}},
{
"sparse with paths",
`{"sparse":{"paths":[".buildkite/","src/"]}}`,
Checkout{Sparse: &Sparse{Paths: []string{".buildkite/", "src/"}}},
},
{
"sparse null",
`{"sparse":null}`,
Checkout{},
},
}

for _, tc := range cases {
Expand All @@ -177,6 +227,80 @@ func TestCheckoutUnmarshalJSON(t *testing.T) {
}
}

func TestSparseUnmarshalYAML(t *testing.T) {
t.Parallel()

cases := []struct {
name string
input string
want Sparse
}{
{"empty", `{}`, Sparse{}},
{
"paths set",
"paths:\n - .buildkite/\n - src/",
Sparse{Paths: []string{".buildkite/", "src/"}},
},
{
"empty paths",
`{paths: []}`,
Sparse{Paths: []string{}},
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

var node yaml.Node
if err := yaml.Unmarshal([]byte(tc.input), &node); err != nil {
t.Fatalf("yaml.Unmarshal() error = %v", err)
}

var got Sparse
if err := ordered.Unmarshal(&node, &got); err != nil {
t.Fatalf("ordered.Unmarshal() error = %v", err)
}

if diff := cmp.Diff(got, tc.want); diff != "" {
t.Errorf("Sparse diff (-got +want):\n%s", diff)
}
})
}
}

func TestSparseMarshalJSON(t *testing.T) {
t.Parallel()

cases := []struct {
name string
s *Sparse
want string
}{
{"empty", &Sparse{}, `{}`},
{
"paths set",
&Sparse{Paths: []string{".buildkite/", "src/"}},
`{"paths":[".buildkite/","src/"]}`,
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

b, err := json.Marshal(tc.s)
if err != nil {
t.Fatalf("json.Marshal() error = %v", err)
}

if diff := cmp.Diff(string(b), tc.want); diff != "" {
t.Errorf("Sparse.MarshalJSON() diff (-got +want):\n%s", diff)
}
})
}
}

func TestPipelineCheckout(t *testing.T) {
t.Parallel()

Expand Down