Change pipeline config path resolution (#299)

# Config resolution
- pipeline-config setting empty / not specified (default): `.woodpecker/` => `.woodpecker.yml` => `.drone.yml`
- pipeline-config setting defined by user: try that file / folder and no fallback (if a user sets some special value that is normally done for some reason)

# Changes
- pipeline-config setting will be empty by default
- remove fallback setting for config loading (simplifies config)

---
closes #133

---

* adjust config fetching mechanism

* default path empty

* remove fallback flag from ui and db
This commit is contained in:
Anbraten 2021-09-17 21:40:43 +02:00 committed by GitHub
parent d4ab506507
commit 289f0c9ad6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 331 additions and 183 deletions

View file

@ -89,12 +89,6 @@ var flags = []cli.Flag{
Name: "open",
Usage: "enable open user registration",
},
cli.StringFlag{
EnvVar: "DRONE_REPO_CONFIG,WOODPECKER_REPO_CONFIG",
Name: "repo-config",
Usage: "file path for the drone config",
Value: ".drone.yml",
},
cli.StringFlag{
EnvVar: "DRONE_DOCS,WOODPECKER_DOCS",
Name: "docs",

View file

@ -20,11 +20,9 @@ When you activate your repository Woodpecker automatically add webhooks to your
Webhooks are used to trigger pipeline executions. When you push code to your repository, open a pull request, or create a tag, your version control system will automatically send a webhook to Woodpecker which will in turn trigger pipeline execution.
## Configuration
To configure you pipeline you should place a `.woodpecker.yml` file in the root of your repository. The .woodpecker.yml file is used to define your pipeline steps. It is a superset of the widely used docker-compose file format.
To configure your pipeline you should place a `.woodpecker.yml` file in the root of your repository. The .woodpecker.yml file is used to define your pipeline steps. It is a superset of the widely used docker-compose file format.
Example pipeline configuration:

View file

@ -6,9 +6,7 @@ As the owner of a project in Woodpecker you can change some project related sett
## Pipeline path
The path to the pipeline file or folder. By default it point `.woodpecker.yml`. To use a [multi pipeline](/docs/usage/multi-pipeline) you have to change it to a folder path ending with `/` like `.woodpecker/`.
If you enable the fallback check, Woodpecker will first try to load the configuration from the defined path and if it fails to find that file it will try to use `.drone.yml`.
The path to the pipeline config file or folder. By default it is left empty which will use the following configuration resolution `.woodpecker/*.yml` -> `.woodpecker.yml` -> `.drone.yml`. If you set a custom path Woodpecker tries to load your configuration or fails if no configuration could be found at the specified location. To use a [multi pipeline](/docs/usage/multi-pipeline) you have to change it to a folder path ending with a `/` like `.woodpecker/`.
## Repository hooks

View file

@ -4,11 +4,14 @@ Some versions need some changes to the server configuration or the pipeline conf
## 0.15.0
- Default pipeline path changed to `.woodpecker/`
- Default value for custom pipeline path is now empty / un-set which results in following resolution:
`.woodpecker/*.yml` -> `.woodpecker.yml` -> `.drone.yml`
Only projects created after updating will have an empty value by default. Existing projects will stick to the current pipeline path which is `.drone.yml` in most cases.
Read more about it at the [Project Settings](/docs/usage/project-settings#pipeline-path)
**Solution:** Set configuration location via [project settings](/docs/usage/project-settings#pipeline-path).
There is still a default fallback mechanism in following order: `.woodpecker/*.yml` -> `.woodpecker.yml` -> `.drone.yml`
- ...
## 0.14.0

View file

@ -55,7 +55,6 @@ type Repo struct {
Config string `json:"config_file" meddler:"repo_config_path"`
Hash string `json:"-" meddler:"repo_hash"`
Perm *Perm `json:"-" meddler:"-"`
Fallback bool `json:"fallback" meddler:"repo_fallback"`
}
func (r *Repo) ResetVisibility() {
@ -106,5 +105,4 @@ type RepoPatch struct {
AllowDeploy *bool `json:"allow_deploy,omitempty"`
AllowTag *bool `json:"allow_tag,omitempty"`
BuildCounter *int `json:"build_counter,omitempty"`
Fallback *bool `json:"fallback,omitempty"`
}

View file

@ -429,7 +429,7 @@ func PostBuild(c *gin.Context) {
}
}
// fetch the .drone.yml file from the database
// fetch the pipeline config from database
configs, err := Config.Storage.Config.ConfigsForBuild(build.ID)
if err != nil {
logrus.Errorf("failure to get build config for %s. %s", repo.FullName, err)

View file

@ -30,28 +30,43 @@ func (cf *configFetcher) Fetch() (files []*remote.FileMeta, err error) {
for i := 0; i < 5; i++ {
select {
case <-time.After(time.Second * time.Duration(i)):
// either a file
if !strings.HasSuffix(cf.repo.Config, "/") {
file, err = cf.remote_.File(cf.user, cf.repo, cf.build, cf.repo.Config)
if err == nil {
return []*remote.FileMeta{{
Name: cf.repo.Config,
Data: file,
}}, nil
if len(cf.repo.Config) > 0 {
// either a file
if !strings.HasSuffix(cf.repo.Config, "/") {
file, err = cf.remote_.File(cf.user, cf.repo, cf.build, cf.repo.Config)
if err == nil {
return []*remote.FileMeta{{
Name: cf.repo.Config,
Data: file,
}}, nil
}
}
}
// or a folder
if strings.HasSuffix(cf.repo.Config, "/") {
files, err = cf.remote_.Dir(cf.user, cf.repo, cf.build, strings.TrimSuffix(cf.repo.Config, "/"))
// or a folder
if strings.HasSuffix(cf.repo.Config, "/") {
files, err = cf.remote_.Dir(cf.user, cf.repo, cf.build, strings.TrimSuffix(cf.repo.Config, "/"))
if err == nil {
return filterPipelineFiles(files), nil
}
}
} else {
// no user defined config so try .woodpecker/*.yml -> .woodpecker.yml -> .drone.yml
// test .woodpecker/ folder
// if folder is not supported we will get a "Not implemented" error and continue
files, err = cf.remote_.Dir(cf.user, cf.repo, cf.build, ".woodpecker")
if err == nil {
return filterPipelineFiles(files), nil
}
}
// or fallback
if cf.repo.Fallback {
file, err = cf.remote_.File(cf.user, cf.repo, cf.build, ".woodpecker.yml")
if err == nil {
return []*remote.FileMeta{{
Name: ".woodpecker.yml",
Data: file,
}}, nil
}
file, err = cf.remote_.File(cf.user, cf.repo, cf.build, ".drone.yml")
if err == nil {
return []*remote.FileMeta{{

View file

@ -1,7 +1,8 @@
package server_test
import (
"errors"
"fmt"
"path/filepath"
"testing"
"github.com/stretchr/testify/mock"
@ -15,61 +16,19 @@ func TestFetch(t *testing.T) {
t.Parallel()
testTable := []struct {
name string
repoConfig string
repoFallback bool
fileMocks []struct {
file []byte
err error
}
dirMock struct {
files []*remote.FileMeta
err error
}
name string
repoConfig string
files []string
expectedFileNames []string
expectedError bool
}{
{
name: "Single .woodpecker.yml file",
repoConfig: ".woodpecker.yml",
repoFallback: false,
fileMocks: []struct {
file []byte
err error
}{
{
file: []byte{},
err: nil,
},
},
expectedFileNames: []string{
".woodpecker.yml",
},
expectedError: false,
},
{
name: "Folder .woodpecker/",
repoConfig: ".woodpecker/",
repoFallback: false,
dirMock: struct {
files []*remote.FileMeta
err error
}{
files: []*remote.FileMeta{
{
Name: ".woodpecker/text.txt",
Data: []byte{},
},
{
Name: ".woodpecker/release.yml",
Data: []byte{},
},
{
Name: ".woodpecker/image.png",
Data: []byte{},
},
},
err: nil,
name: "Default config - .woodpecker/",
repoConfig: "",
files: []string{
".woodpecker/text.txt",
".woodpecker/release.yml",
".woodpecker/image.png",
},
expectedFileNames: []string{
".woodpecker/release.yml",
@ -77,23 +36,21 @@ func TestFetch(t *testing.T) {
expectedError: false,
},
{
name: "Requesting woodpecker-file but using fallback",
repoConfig: ".woodpecker.yml",
repoFallback: true,
fileMocks: []struct {
file []byte
err error
}{
// first call requesting regular woodpecker.yml
{
file: nil,
err: errors.New("File not found"),
},
// fallback file call
{
file: []byte{},
err: nil,
},
name: "Default config - .woodpecker.yml",
repoConfig: "",
files: []string{
".woodpecker.yml",
},
expectedFileNames: []string{
".woodpecker.yml",
},
expectedError: false,
},
{
name: "Default config - .drone.yml",
repoConfig: "",
files: []string{
".drone.yml",
},
expectedFileNames: []string{
".drone.yml",
@ -101,49 +58,99 @@ func TestFetch(t *testing.T) {
expectedError: false,
},
{
name: "Requesting folder but using fallback",
repoConfig: ".woodpecker/",
repoFallback: true,
fileMocks: []struct {
file []byte
err error
}{
{
file: []byte{},
err: nil,
},
},
dirMock: struct {
files []*remote.FileMeta
err error
}{
files: []*remote.FileMeta{},
err: errors.New("Dir not found"),
name: "Default config - Empty repo",
repoConfig: "",
files: []string{},
expectedFileNames: []string{},
expectedError: true,
},
{
name: "Default config - Additional sub-folders",
repoConfig: "",
files: []string{
".woodpecker/test.yml",
".woodpecker/sub-folder/config.yml",
},
expectedFileNames: []string{
".drone.yml",
".woodpecker/test.yml",
},
expectedError: false,
},
{
name: "Not found and disabled fallback",
repoConfig: ".woodpecker.yml",
repoFallback: false,
fileMocks: []struct {
file []byte
err error
}{
// first call requesting regular woodpecker.yml
{
file: nil,
err: errors.New("File not found"),
},
// fallback file call
{
file: []byte{},
err: errors.New("File not found"),
},
name: "Default config - Additional none .yml files",
repoConfig: "",
files: []string{
".woodpecker/notes.txt",
".woodpecker/image.png",
".woodpecker/test.yml",
},
expectedFileNames: []string{
".woodpecker/test.yml",
},
expectedError: false,
},
{
name: "Special config - folder (ignoring default files)",
repoConfig: ".my-ci-folder/",
files: []string{
".woodpecker/test.yml",
".woodpecker.yml",
".drone.yml",
".my-ci-folder/test.yml",
},
expectedFileNames: []string{
".my-ci-folder/test.yml",
},
expectedError: false,
},
{
name: "Special config - folder",
repoConfig: ".my-ci-folder/",
files: []string{
".my-ci-folder/test.yml",
},
expectedFileNames: []string{
".my-ci-folder/test.yml",
},
expectedError: false,
},
{
name: "Special config - subfolder",
repoConfig: ".my-ci-folder/my-config/",
files: []string{
".my-ci-folder/my-config/test.yml",
},
expectedFileNames: []string{
".my-ci-folder/my-config/test.yml",
},
expectedError: false,
},
{
name: "Special config - file",
repoConfig: ".config.yml",
files: []string{
".config.yml",
},
expectedFileNames: []string{
".config.yml",
},
expectedError: false,
},
{
name: "Special config - file inside subfolder",
repoConfig: ".my-ci-folder/sub-folder/config.yml",
files: []string{
".my-ci-folder/sub-folder/config.yml",
},
expectedFileNames: []string{
".my-ci-folder/sub-folder/config.yml",
},
expectedError: false,
},
{
name: "Special config - empty repo",
repoConfig: ".config.yml",
files: []string{},
expectedFileNames: []string{},
expectedError: true,
},
@ -151,13 +158,26 @@ func TestFetch(t *testing.T) {
for _, tt := range testTable {
t.Run(tt.name, func(t *testing.T) {
repo := &model.Repo{Owner: "laszlocph", Name: "drone-multipipeline", Config: tt.repoConfig, Fallback: tt.repoFallback}
repo := &model.Repo{Owner: "laszlocph", Name: "drone-multipipeline", Config: tt.repoConfig}
r := new(mocks.Remote)
for _, fileMock := range tt.fileMocks {
r.On("File", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(fileMock.file, fileMock.err).Once()
dirs := map[string][]*remote.FileMeta{}
for _, file := range tt.files {
r.On("File", mock.Anything, mock.Anything, mock.Anything, file).Return([]byte{}, nil)
path := filepath.Dir(file)
dirs[path] = append(dirs[path], &remote.FileMeta{
Name: file,
Data: []byte{},
})
}
r.On("Dir", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tt.dirMock.files, tt.dirMock.err)
for path, files := range dirs {
r.On("Dir", mock.Anything, mock.Anything, mock.Anything, path).Return(files, nil)
}
// if the previous mocks do not match return not found errors
r.On("File", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("File not found"))
r.On("Dir", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("Directory not found"))
configFetcher := server.NewConfigFetcher(
r,
@ -182,7 +202,11 @@ func TestFetch(t *testing.T) {
}
if matchingFiles != len(tt.expectedFileNames) {
t.Fatal("expected some other pipeline files", tt.expectedFileNames, files)
receivedFileNames := []string{}
for _, file := range files {
receivedFileNames = append(receivedFileNames, file.Name)
}
t.Fatal("expected some other pipeline files", tt.expectedFileNames, receivedFileNames)
}
})
}

View file

@ -52,9 +52,6 @@ func PostRepo(c *gin.Context) {
repo.Visibility = model.VisibilityPrivate
}
}
if repo.Config == "" {
repo.Config = Config.Server.RepoConfig
}
if repo.Timeout == 0 {
repo.Timeout = 60 // 1 hour default build time
}
@ -149,9 +146,6 @@ func PatchRepo(c *gin.Context) {
if in.BuildCounter != nil {
repo.Counter = *in.BuildCounter
}
if in.Fallback != nil {
repo.Fallback = *in.Fallback
}
err := store.UpdateRepo(c, repo)
if err != nil {

View file

@ -200,6 +200,10 @@ var migrations = []struct {
name: "update-builds-set-changed_files",
stmt: updateBuildsSetChangedfiles,
},
{
name: "alter-table-drop-repo-fallback",
stmt: alterTableDropRepoFallback,
},
}
// Migrate performs the database migration. If the migration fails
@ -745,3 +749,11 @@ ALTER TABLE builds ADD COLUMN changed_files TEXT
var updateBuildsSetChangedfiles = `
UPDATE builds SET changed_files='[]'
`
//
// 026_drop_repo_fallback_column.sql
//
var alterTableDropRepoFallback = `
ALTER TABLE repos DROP COLUMN repo_fallback
`

View file

@ -0,0 +1,2 @@
-- name: alter-table-drop-repo-fallback
ALTER TABLE repos DROP COLUMN repo_fallback

View file

@ -200,6 +200,10 @@ var migrations = []struct {
name: "update-builds-set-changed_files",
stmt: updateBuildsSetChangedfiles,
},
{
name: "alter-table-drop-repo-fallback",
stmt: alterTableDropRepoFallback,
},
}
// Migrate performs the database migration. If the migration fails
@ -747,3 +751,11 @@ ALTER TABLE builds ADD COLUMN changed_files TEXT;
var updateBuildsSetChangedfiles = `
UPDATE builds SET changed_files='[]'
`
//
// 026_drop_repo_fallback_column.sql
//
var alterTableDropRepoFallback = `
ALTER TABLE repos DROP COLUMN repo_fallback
`

View file

@ -0,0 +1,2 @@
-- name: alter-table-drop-repo-fallback
ALTER TABLE repos DROP COLUMN repo_fallback

View file

@ -204,6 +204,10 @@ var migrations = []struct {
name: "update-builds-set-changed_files",
stmt: updateBuildsSetChangedfiles,
},
{
name: "alter-table-drop-repo-fallback",
stmt: alterTableDropRepoFallback,
},
}
// Migrate performs the database migration. If the migration fails
@ -746,3 +750,65 @@ ALTER TABLE builds ADD COLUMN changed_files TEXT
var updateBuildsSetChangedfiles = `
UPDATE builds SET changed_files='[]'
`
//
// 026_drop_repo_fallback_column.sql
//
var alterTableDropRepoFallback = `
BEGIN TRANSACTION;
CREATE TABLE repos_new (
repo_id INTEGER PRIMARY KEY AUTOINCREMENT,
repo_user_id INTEGER,
repo_owner TEXT,
repo_name TEXT,
repo_full_name TEXT,
repo_avatar TEXT,
repo_link TEXT,
repo_clone TEXT,
repo_branch TEXT,
repo_timeout INTEGER,
repo_private BOOLEAN,
repo_trusted BOOLEAN,
repo_active BOOLEAN,
repo_allow_pr BOOLEAN,
repo_allow_push BOOLEAN,
repo_allow_deploys BOOLEAN,
repo_allow_tags BOOLEAN,
repo_hash TEXT,
repo_scm TEXT,
repo_config_path TEXT,
repo_gated BOOLEAN,
repo_visibility TEXT,
repo_counter INTEGER,
UNIQUE(repo_full_name)
);
INSERT INTO repos_new SELECT repo_id
,repo_user_id
,repo_owner
,repo_name
,repo_full_name
,repo_avatar
,repo_link
,repo_clone
,repo_branch
,repo_timeout
,repo_private
,repo_trusted
,repo_active
,repo_allow_pr
,repo_allow_push
,repo_allow_deploys
,repo_allow_tags
,repo_hash
,repo_scm
,repo_config_path
,repo_gated
,repo_visibility
,repo_counter FROM repos;
DROP TABLE repos;
ALTER TABLE repos_new RENAME TO repos;
COMMIT;
`

View file

@ -0,0 +1,56 @@
-- name: alter-table-drop-repo-fallback
BEGIN TRANSACTION;
CREATE TABLE repos_new (
repo_id INTEGER PRIMARY KEY AUTOINCREMENT,
repo_user_id INTEGER,
repo_owner TEXT,
repo_name TEXT,
repo_full_name TEXT,
repo_avatar TEXT,
repo_link TEXT,
repo_clone TEXT,
repo_branch TEXT,
repo_timeout INTEGER,
repo_private BOOLEAN,
repo_trusted BOOLEAN,
repo_active BOOLEAN,
repo_allow_pr BOOLEAN,
repo_allow_push BOOLEAN,
repo_allow_deploys BOOLEAN,
repo_allow_tags BOOLEAN,
repo_hash TEXT,
repo_scm TEXT,
repo_config_path TEXT,
repo_gated BOOLEAN,
repo_visibility TEXT,
repo_counter INTEGER,
UNIQUE(repo_full_name)
);
INSERT INTO repos_new SELECT repo_id
,repo_user_id
,repo_owner
,repo_name
,repo_full_name
,repo_avatar
,repo_link
,repo_clone
,repo_branch
,repo_timeout
,repo_private
,repo_trusted
,repo_active
,repo_allow_pr
,repo_allow_push
,repo_allow_deploys
,repo_allow_tags
,repo_hash
,repo_scm
,repo_config_path
,repo_gated
,repo_visibility
,repo_counter FROM repos;
DROP TABLE repos;
ALTER TABLE repos_new RENAME TO repos;
COMMIT;

View file

@ -93,7 +93,6 @@ func (db *datastore) RepoBatch(repos []*model.Repo) error {
repo.IsGated,
repo.Visibility,
repo.Counter,
repo.Fallback,
)
if err != nil {
return err

View file

@ -30,7 +30,6 @@ SELECT
,repo_gated
,repo_visibility
,repo_counter
,repo_fallback
FROM repos
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
WHERE perms.perm_user_id = ?
@ -61,8 +60,7 @@ INSERT IGNORE INTO repos (
,repo_gated
,repo_visibility
,repo_counter
,repo_fallback
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
-- name: repo-delete

View file

@ -448,7 +448,6 @@ SELECT
,repo_gated
,repo_visibility
,repo_counter
,repo_fallback
FROM repos
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
WHERE perms.perm_user_id = ?
@ -479,8 +478,7 @@ INSERT IGNORE INTO repos (
,repo_gated
,repo_visibility
,repo_counter
,repo_fallback
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
`
var repoDelete = `

View file

@ -30,7 +30,6 @@ SELECT
,repo_gated
,repo_visibility
,repo_counter
,repo_fallback
FROM repos
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
WHERE perms.perm_user_id = $1
@ -61,8 +60,7 @@ INSERT INTO repos (
,repo_gated
,repo_visibility
,repo_counter
,repo_fallback
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23)
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22)
ON CONFLICT (repo_full_name) DO NOTHING
-- name: repo-delete

View file

@ -451,7 +451,6 @@ SELECT
,repo_gated
,repo_visibility
,repo_counter
,repo_fallback
FROM repos
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
WHERE perms.perm_user_id = $1
@ -482,8 +481,7 @@ INSERT INTO repos (
,repo_gated
,repo_visibility
,repo_counter
,repo_fallback
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23)
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22)
ON CONFLICT (repo_full_name) DO NOTHING
`

View file

@ -30,7 +30,6 @@ SELECT
,repo_gated
,repo_visibility
,repo_counter
,repo_fallback
FROM repos
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
WHERE perms.perm_user_id = ?
@ -61,8 +60,7 @@ INSERT OR IGNORE INTO repos (
,repo_gated
,repo_visibility
,repo_counter
,repo_fallback
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
-- name: repo-delete

View file

@ -448,7 +448,6 @@ SELECT
,repo_gated
,repo_visibility
,repo_counter
,repo_fallback
FROM repos
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
WHERE perms.perm_user_id = ?
@ -479,8 +478,7 @@ INSERT OR IGNORE INTO repos (
,repo_gated
,repo_visibility
,repo_counter
,repo_fallback
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
`
var repoDelete = `

View file

@ -41,7 +41,6 @@ export default class Settings extends Component {
this.handleVisibilityChange = this.handleVisibilityChange.bind(this);
this.handleTimeoutChange = this.handleTimeoutChange.bind(this);
this.handlePathChange = this.handlePathChange.bind(this);
this.handleFallbackChange = this.handleFallbackChange.bind(this);
this.handleChange = this.handleChange.bind(this);
}
@ -74,14 +73,6 @@ export default class Settings extends Component {
value={repo.config_file}
onBlur={this.handlePathChange}
/>
<label>
<input
type="checkbox"
checked={repo.fallback}
onChange={this.handleFallbackChange}
/>
<span>Fallback to .drone.yml if path not exists</span>
</label>
</div>
</section>
<section>
@ -231,10 +222,6 @@ export default class Settings extends Component {
this.handleChange("config_file", e.target.value);
}
handleFallbackChange(e) {
this.handleChange("fallback", e.target.checked);
}
handleChange(prop, value) {
const { dispatch, drone, repo } = this.props;
let data = {};