Skip to content

Commit

Permalink
Merge pull request #4 from joshdales/another-config-setup
Browse files Browse the repository at this point in the history
Change the structure of the config
  • Loading branch information
joshdales authored Mar 27, 2023
2 parents 5d0a66e + 2f1dfd1 commit 7f169bc
Show file tree
Hide file tree
Showing 10 changed files with 576 additions and 317 deletions.
42 changes: 26 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,39 +14,48 @@ The key is the name of the label in your repository that you want to add (eg: "m

#### Match Object

The match object allows control over the matching options, you can specify the label to be applied based on the files that have changed or the name of the branch. For the changed files options you provide a [path glob](https://github.com/isaacs/minimatch#minimatch), and a regexp for the branch names.
The match object is defined as:
The match object allows control over the matching options, you can specify the label to be applied based on the files that have changed or the name of either the base branch or the head branch. For the changed files options you provide a [path glob](https://github.com/isaacs/minimatch#minimatch), and for the branches you provide a regexp to match against the branch name.

The base match object is defined as:
```yml
- changed-files:
- any: ['list', 'of', 'globs']
- all: ['list', 'of', 'globs']
- changed-files: ['list', 'of', 'globs']
- base-branch: ['list', 'of', 'regexps']
- head-branch: ['list', 'of', 'regexps']
```
One or all fields can be provided for fine-grained matching. Unlike the top-level list, the list of path globs provided to `any` and `all` must ALL match against a path for the label to be applied.
There are two top level keys of `any` and `all`, which both accept the same config options:
```yml
- any:
- changed-files: ['list', 'of', 'globs']
- base-branch: ['list', 'of', 'regexps']
- head-branch: ['list', 'of', 'regexps']
- all:
- changed-files: ['list', 'of', 'globs']
- base-branch: ['list', 'of', 'regexps']
- head-branch: ['list', 'of', 'regexps']
```

One or all fields can be provided for fine-grained matching.
The fields are defined as follows:
* `changed-files`
* `any`: match ALL globs against ANY changed path
* `all`: match ALL globs against ALL changed paths
* `all`: all of the provided options must match in order for the label to be applied
* `any`: if any of the provided options match then a label will be applied
* `base-branch`: match a regexp against the base branch name
* `changed-files`: match a glob against the changed paths
* `head-branch`: match a regexp against the head branch name

A simple path glob is the equivalent to `any: ['glob']`. More specifically, the following two configurations are equivalent:
If a base option is provided without a top-level key then it will default to `any`. More specifically, the following two configurations are equivalent:
```yml
label1:
- changed-files: example1/*
```
and
```yml
label1:
- changed-files:
- any: ['example1/*']
- any:
- changed-files: ['example1/*']
```

From a boolean logic perspective, top-level match objects are `OR`-ed together and individual match rules within an object are `AND`-ed. Combined with `!` negation, you can write complex matching rules.
From a boolean logic perspective, top-level match objects, and options within `all` are `AND`-ed together and individual match rules within the `any` object are `OR`-ed. If path globs are combined with `!` negation, you can write complex matching rules.

#### Basic Examples

Expand Down Expand Up @@ -96,9 +105,10 @@ source:
# Add 'frontend` label to any change to *.js files as long as the `main.js` hasn't changed
frontend:
- changed-files:
- any: ['src/**/*.js']
all: ['!src/main.js']
- any:
- changed-files: ['src/**/*.js']
- all:
- changed-files: ['!src/main.js']

# Add 'feature' label to any PR where the head branch name starts with `feature` or has a `feature` section in the name
feature:
Expand Down
76 changes: 68 additions & 8 deletions __tests__/branch.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
getBranchName,
checkBranch,
checkAnyBranch,
checkAllBranch,
toBranchMatchConfig,
BranchMatchConfig
} from '../src/branch';
Expand All @@ -25,7 +26,7 @@ describe('getBranchName', () => {
});
});

describe('checkBranch', () => {
describe('checkAllBranch', () => {
beforeEach(() => {
github.context.payload.pull_request!.head = {
ref: 'test/feature/123'
Expand All @@ -38,14 +39,73 @@ describe('checkBranch', () => {
describe('when a single pattern is provided', () => {
describe('and the pattern matches the head branch', () => {
it('returns true', () => {
const result = checkBranch(['^test'], 'head');
const result = checkAllBranch(['^test'], 'head');
expect(result).toBe(true);
});
});

describe('and the pattern does not match the head branch', () => {
it('returns false', () => {
const result = checkBranch(['^feature/'], 'head');
const result = checkAllBranch(['^feature/'], 'head');
expect(result).toBe(false);
});
});
});

describe('when multiple patterns are provided', () => {
describe('and not all patterns matched', () => {
it('returns false', () => {
const result = checkAllBranch(['^test/', '^feature/'], 'head');
expect(result).toBe(false);
});
});

describe('and all patterns match', () => {
it('returns true', () => {
const result = checkAllBranch(['^test/', '/feature/'], 'head');
expect(result).toBe(true);
});
});

describe('and no patterns match', () => {
it('returns false', () => {
const result = checkAllBranch(['^feature/', '/test$'], 'head');
expect(result).toBe(false);
});
});
});

describe('when the branch to check is specified as the base branch', () => {
describe('and the pattern matches the base branch', () => {
it('returns true', () => {
const result = checkAllBranch(['^main$'], 'base');
expect(result).toBe(true);
});
});
});
});

describe('checkAnyBranch', () => {
beforeEach(() => {
github.context.payload.pull_request!.head = {
ref: 'test/feature/123'
};
github.context.payload.pull_request!.base = {
ref: 'main'
};
});

describe('when a single pattern is provided', () => {
describe('and the pattern matches the head branch', () => {
it('returns true', () => {
const result = checkAnyBranch(['^test'], 'head');
expect(result).toBe(true);
});
});

describe('and the pattern does not match the head branch', () => {
it('returns false', () => {
const result = checkAnyBranch(['^feature/'], 'head');
expect(result).toBe(false);
});
});
Expand All @@ -54,21 +114,21 @@ describe('checkBranch', () => {
describe('when multiple patterns are provided', () => {
describe('and at least one pattern matches', () => {
it('returns true', () => {
const result = checkBranch(['^test/', '^feature/'], 'head');
const result = checkAnyBranch(['^test/', '^feature/'], 'head');
expect(result).toBe(true);
});
});

describe('and all patterns match', () => {
it('returns true', () => {
const result = checkBranch(['^test/', '/feature/'], 'head');
const result = checkAnyBranch(['^test/', '/feature/'], 'head');
expect(result).toBe(true);
});
});

describe('and no patterns match', () => {
it('returns false', () => {
const result = checkBranch(['^feature/', '/test$'], 'head');
const result = checkAnyBranch(['^feature/', '/test$'], 'head');
expect(result).toBe(false);
});
});
Expand All @@ -77,7 +137,7 @@ describe('checkBranch', () => {
describe('when the branch to check is specified as the base branch', () => {
describe('and the pattern matches the base branch', () => {
it('returns true', () => {
const result = checkBranch(['^main$'], 'base');
const result = checkAnyBranch(['^main$'], 'base');
expect(result).toBe(true);
});
});
Expand Down
116 changes: 19 additions & 97 deletions __tests__/changedFiles.test.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import {
ChangedFilesMatchConfig,
checkAll,
checkAny,
checkAllChangedFiles,
checkAnyChangedFiles,
toChangedFilesMatchConfig
} from '../src/changedFiles';

jest.mock('@actions/core');
jest.mock('@actions/github');

describe('checkAll', () => {
describe('checkAllChangedFiles', () => {
const changedFiles = ['foo.txt', 'bar.txt'];

describe('when the globs match every file that has changed', () => {
const globs = ['*.txt'];

it('returns true', () => {
const result = checkAll(changedFiles, globs);
const result = checkAllChangedFiles(changedFiles, globs);
expect(result).toBe(true);
});
});
Expand All @@ -24,20 +24,20 @@ describe('checkAll', () => {
const globs = ['foo.txt'];

it('returns false', () => {
const result = checkAll(changedFiles, globs);
const result = checkAllChangedFiles(changedFiles, globs);
expect(result).toBe(false);
});
});
});

describe('checkAny', () => {
describe('checkAnyChangedFiles', () => {
const changedFiles = ['foo.txt', 'bar.txt'];

describe('when the globs match any of the files that have changed', () => {
const globs = ['foo.txt'];

it('returns true', () => {
const result = checkAny(changedFiles, globs);
const result = checkAnyChangedFiles(changedFiles, globs);
expect(result).toBe(true);
});
});
Expand All @@ -46,7 +46,7 @@ describe('checkAny', () => {
const globs = ['*.md'];

it('returns false', () => {
const result = checkAny(changedFiles, globs);
const result = checkAnyChangedFiles(changedFiles, globs);
expect(result).toBe(false);
});
});
Expand All @@ -63,122 +63,44 @@ describe('toChangedFilesMatchConfig', () => {
});

describe(`when there is a 'changed-files' key in the config`, () => {
describe(`and both 'any' and 'all' keys are present`, () => {
const config = {'changed-files': [{all: 'testing'}, {any: 'testing'}]};
describe('and the value is an array of strings', () => {
const config = {'changed-files': ['testing']};

it('sets both values in the config object', () => {
it('sets the value in the config object', () => {
const result = toChangedFilesMatchConfig(config);
expect(result).toEqual<ChangedFilesMatchConfig>({
changedFiles: {
any: ['testing'],
all: ['testing']
}
});
});
});

describe(`and it contains a 'all' key`, () => {
describe('with a value of a string', () => {
const config = {'changed-files': [{all: 'testing'}]};

it('sets the value to be an array of strings in the config object', () => {
const result = toChangedFilesMatchConfig(config);
expect(result).toEqual<ChangedFilesMatchConfig>({
changedFiles: {
all: ['testing']
}
});
});
});

describe('with a value of an array of strings', () => {
const config = {'changed-files': [{all: ['testing']}]};

it('sets the value in the config object', () => {
const result = toChangedFilesMatchConfig(config);
expect(result).toEqual<ChangedFilesMatchConfig>({
changedFiles: {
all: ['testing']
}
});
});
});
});

describe(`and it contains a 'any' key`, () => {
describe('with a value of a string', () => {
const config = {'changed-files': [{any: 'testing'}]};

it('sets the value to be an array of strings on the config object', () => {
const result = toChangedFilesMatchConfig(config);
expect(result).toEqual<ChangedFilesMatchConfig>({
changedFiles: {
any: ['testing']
}
});
});
});

describe('with a value of an array of strings', () => {
const config = {'changed-files': [{any: ['testing']}]};

it('sets the value in the config object', () => {
const result = toChangedFilesMatchConfig(config);
expect(result).toEqual<ChangedFilesMatchConfig>({
changedFiles: {
any: ['testing']
}
});
changedFiles: ['testing']
});
});
});

describe('and the value is a string', () => {
const config = {'changed-files': 'testing'};

it(`sets the string as an array under an 'any' key`, () => {
it(`sets the string as an array in the config object`, () => {
const result = toChangedFilesMatchConfig(config);
expect(result).toEqual<ChangedFilesMatchConfig>({
changedFiles: {
any: ['testing']
}
changedFiles: ['testing']
});
});
});

describe('and the value is an array of strings', () => {
const config = {'changed-files': ['testing']};
describe('but the value is an empty string', () => {
const config = {'changed-files': ''};

it(`sets the array under an 'any' key`, () => {
it(`returns an empty object`, () => {
const result = toChangedFilesMatchConfig(config);
expect(result).toEqual<ChangedFilesMatchConfig>({
changedFiles: {
any: ['testing']
}
});
expect(result).toEqual<ChangedFilesMatchConfig>({});
});
});

describe('but it has empty values', () => {
describe('but the value is an empty array', () => {
const config = {'changed-files': []};

it(`returns an empty object`, () => {
const result = toChangedFilesMatchConfig(config);
expect(result).toEqual<ChangedFilesMatchConfig>({});
});
});

describe('and there is an unknown value in the config', () => {
const config = {'changed-files': [{any: 'testing'}, {sneaky: 'test'}]};

it(`does not set the value in the match config`, () => {
const result = toChangedFilesMatchConfig(config);
expect(result).toEqual<ChangedFilesMatchConfig>({
changedFiles: {
any: ['testing']
}
});
});
});
});
});
Loading

0 comments on commit 7f169bc

Please sign in to comment.