Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b45639f
Prototype: conditional exec
mrnugget Apr 26, 2021
11b1662
Add if conditional
mrnugget Apr 26, 2021
69e538c
Add builtins
mrnugget Apr 26, 2021
b84b9e1
Add test for conditional execution
mrnugget Apr 26, 2021
d307c6e
Add test for BuildTasks and globbing
mrnugget Apr 26, 2021
b83ebc0
Extract small method
mrnugget Apr 27, 2021
e56772f
Clean up BuildTasks
mrnugget Apr 27, 2021
b459579
More cleanup
mrnugget Apr 27, 2021
a0b3a2e
Extract code into TaskBuilder
mrnugget Apr 27, 2021
f24b6d8
Extract workspace finding to TaskBuilder
mrnugget Apr 27, 2021
1ea6bd0
Use TaskBuilder in cmd
mrnugget Apr 27, 2021
3de7a42
Remove now unneeded code from Service
mrnugget Apr 27, 2021
ef33f27
Clean up test for evalStepCondition
mrnugget Apr 27, 2021
81d7dc3
Add parseAndPartialEval to statically check `if:` fields
mrnugget Apr 27, 2021
d8b9388
Clean up code
mrnugget May 3, 2021
c741d66
Make field access more robust
mrnugget May 3, 2021
eb822f0
Update schema to remove in
mrnugget May 3, 2021
2003b2d
Remove varargs
mrnugget May 3, 2021
a5d62c0
Update TaskBuilder to partial evaluate if
mrnugget May 4, 2021
3dca350
Add conditional exec to changelog and feature check
mrnugget May 4, 2021
b9fe904
Add `steps.path`, `steps.*files` to StepContext
mrnugget May 4, 2021
3f6fdf8
Reduce number of nodes in debugNode
mrnugget May 4, 2021
4180a48
Implement 'not' in partial evaluator
mrnugget May 5, 2021
29bdc6e
Implement 'ne' function
mrnugget May 5, 2021
7140607
Allow bool|string for step conditions
mrnugget May 5, 2021
6fa34e6
Improve loop
mrnugget May 5, 2021
e169c77
Incorporate feedback from review
mrnugget May 6, 2021
8db1e0e
Remove moved code
mrnugget May 6, 2021
58afec6
Do not try to execute task with 0 steps
mrnugget May 6, 2021
e2e5362
Fix tests
mrnugget May 6, 2021
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: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ All notable changes to `src-cli` are documented in this file.

### Added

- Starting with Sourcegraph 3.28.0 batch spec `steps` can contain an `if: <template string>` attribute that determines whether the given step will be executed. [#520](https://github.com/sourcegraph/src-cli/pull/520)

### Changed

### Fixed
Expand Down
11 changes: 8 additions & 3 deletions cmd/src/batch_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ type executeBatchSpecOpts struct {
// Sourcegraph, including execution as needed and applying the resulting batch
// spec if specified.
func executeBatchSpec(ctx context.Context, opts executeBatchSpecOpts) error {
batches.DebugOut = opts.out
svc := service.New(&service.Opts{
AllowUnsupported: opts.flags.allowUnsupported,
AllowIgnored: opts.flags.allowIgnored,
Expand Down Expand Up @@ -285,11 +286,15 @@ func executeBatchSpec(ctx context.Context, opts executeBatchSpecOpts) error {
}

pending = batchCreatePending(opts.out, "Determining workspaces")
tasks, err := svc.BuildTasks(ctx, repos, batchSpec)
tb, err := executor.NewTaskBuilder(batchSpec, svc)
if err != nil {
return errors.Wrap(err, "Calculating execution plan")
return errors.Wrap(err, "Parsing batch spec to determine workspaces")
}
batchCompletePending(pending, fmt.Sprintf("Found %d workspaces", len(tasks)))
tasks, err := tb.BuildAll(ctx, repos)
if err != nil {
return err
}
batchCompletePending(pending, fmt.Sprintf("Found %d workspaces with steps to execute", len(tasks)))

execOpts := executor.Opts{
CacheDir: opts.flags.cacheDir,
Expand Down
27 changes: 27 additions & 0 deletions internal/batches/batch_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,25 @@ type Step struct {
Files map[string]string `json:"files,omitempty" yaml:"files,omitempty"`
Outputs Outputs `json:"outputs,omitempty" yaml:"outputs,omitempty"`

If interface{} `json:"if,omitempty" yaml:"if,omitempty"`

image docker.Image
}

func (s *Step) IfCondition() string {
switch v := s.If.(type) {
case bool:
if v {
return "true"
}
return "false"
case string:
return v
default:
return ""
}
}

func (s *Step) SetImage(image docker.Image) {
s.image = image
}
Expand Down Expand Up @@ -178,6 +194,17 @@ func ParseBatchSpec(data []byte, features FeatureFlags) (*BatchSpec, error) {
errs = multierror.Append(errs, errors.New("batch spec includes workspaces, which is not supported in this Sourcegraph version"))
}

if !features.AllowConditionalExec {
for i, step := range spec.Steps {
if step.IfCondition() != "" {
errs = multierror.Append(errs, fmt.Errorf(
"step %d in batch spec uses the 'if' attribute for conditional execution, which is not supported in this Sourcegraph version",
i+1,
))
}
}
}

return &spec, errs.ErrorOrNil()
}

Expand Down
85 changes: 84 additions & 1 deletion internal/batches/batch_spec_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package batches

import "testing"
import (
"fmt"
"testing"
)

func TestParseBatchSpec(t *testing.T) {
t.Run("valid", func(t *testing.T) {
Expand Down Expand Up @@ -87,4 +90,84 @@ changesetTemplate:
t.Fatalf("wrong error. want=%q, have=%q", wantErr, haveErr)
}
})

t.Run("uses unsupported conditional exec", func(t *testing.T) {
const spec = `
name: hello-world
description: Add Hello World to READMEs
on:
- repositoriesMatchingQuery: file:README.md
steps:
- run: echo Hello World | tee -a $(find -name README.md)
if: "false"
container: alpine:3

changesetTemplate:
title: Hello World
body: My first batch change!
branch: hello-world
commit:
message: Append Hello World to all README.md files
published: false
`

_, err := ParseBatchSpec([]byte(spec), FeatureFlags{})
if err == nil {
t.Fatal("no error returned")
}

wantErr := `1 error occurred:
* step 1 in batch spec uses the 'if' attribute for conditional execution, which is not supported in this Sourcegraph version

`
haveErr := err.Error()
if haveErr != wantErr {
t.Fatalf("wrong error. want=%q, have=%q", wantErr, haveErr)
}
})

t.Run("parsing if attribute", func(t *testing.T) {
const specTemplate = `
name: hello-world
description: Add Hello World to READMEs
on:
- repositoriesMatchingQuery: file:README.md
steps:
- run: echo Hello World | tee -a $(find -name README.md)
if: %s
container: alpine:3

changesetTemplate:
title: Hello World
body: My first batch change!
branch: hello-world
commit:
message: Append Hello World to all README.md files
published: false
`

for _, tt := range []struct {
raw string
want string
}{
{raw: `"true"`, want: "true"},
{raw: `"false"`, want: "false"},
{raw: `true`, want: "true"},
{raw: `false`, want: "false"},
{raw: `"${{ foobar }}"`, want: "${{ foobar }}"},
{raw: `${{ foobar }}`, want: "${{ foobar }}"},
{raw: `foobar`, want: "foobar"},
} {
spec := fmt.Sprintf(specTemplate, tt.raw)
batchSpec, err := ParseBatchSpec([]byte(spec), FeatureFlags{AllowConditionalExec: true})
if err != nil {
t.Fatal(err)
}

fmt.Printf("len(batchSpec.Steps)=%d", len(batchSpec.Steps))
if batchSpec.Steps[0].IfCondition() != tt.want {
t.Fatalf("wrong IfCondition. want=%q, got=%q", tt.want, batchSpec.Steps[0].IfCondition())
}
}
})
}
4 changes: 3 additions & 1 deletion internal/batches/debug.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package batches

import "github.com/sourcegraph/sourcegraph/lib/output"
import (
"github.com/sourcegraph/sourcegraph/lib/output"
)

// DebugOut can be used to print debug messages in development to the TUI.
// For that it needs to be set to an actual *output.Output.
Expand Down
36 changes: 35 additions & 1 deletion internal/batches/executor/executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,6 @@ output4=integration-test-batch-change`,
wantAuthorName: "myOutputName1=main.go",
wantAuthorEmail: "myOutputName1=main.go",
},

{
name: "workspaces",
archives: []mock.RepoArchive{
Expand Down Expand Up @@ -374,6 +373,40 @@ output4=integration-test-batch-change`,
},
},
},
{
name: "step condition",
archives: []mock.RepoArchive{
{Repo: srcCLIRepo, Files: map[string]string{
"README.md": "# Welcome to the README\n",
}},
{Repo: sourcegraphRepo, Files: map[string]string{
"README.md": "# Sourcegraph README\n",
}},
},
steps: []batches.Step{
{Run: `echo -e "foobar\n" >> README.md`},
{
Run: `echo "foobar" >> hello.txt`,
If: `${{ matches repository.name "github.com/sourcegraph/sourcegra*" }}`,
},
{
Run: `echo "foobar" >> in-path.txt`,
If: `${{ matches steps.path "sub/directory/of/repo" }}`,
},
},
tasks: []*Task{
{Repository: srcCLIRepo},
{Repository: sourcegraphRepo, Path: "sub/directory/of/repo"},
},
wantFilesChanged: filesByRepository{
srcCLIRepo.ID: filesByBranch{
changesetTemplateBranch: []string{"README.md"},
},
sourcegraphRepo.ID: {
changesetTemplateBranch: []string{"README.md", "hello.txt", "in-path.txt"},
},
},
},
}

for _, tc := range tests {
Expand Down Expand Up @@ -949,5 +982,6 @@ func featuresAllEnabled() batches.FeatureFlags {
UseGzipCompression: true,
AllowTransformChanges: true,
AllowWorkspaces: true,
AllowConditionalExec: true,
}
}
Loading