Implement dot option

This commit is contained in:
Alexander Kachkaev
2022-02-04 13:26:54 +00:00
parent 3194b4b6ce
commit b0d9292064
5 changed files with 109 additions and 27 deletions

View File

@@ -67,7 +67,6 @@ repo:
# Add '@domain/core' label to any change within the 'core' package # Add '@domain/core' label to any change within the 'core' package
@domain/core: @domain/core:
- package/core/*
- package/core/**/* - package/core/**/*
# Add 'test' label to any change to *.spec.js files within the source dir # Add 'test' label to any change to *.spec.js files within the source dir
@@ -113,7 +112,23 @@ Various inputs are defined in [`action.yml`](action.yml) to let you configure th
| `repo-token` | Token to use to authorize label changes. Typically the GITHUB_TOKEN secret | N/A | | `repo-token` | Token to use to authorize label changes. Typically the GITHUB_TOKEN secret | N/A |
| `configuration-path` | The path to the label configuration file | `.github/labeler.yml` | | `configuration-path` | The path to the label configuration file | `.github/labeler.yml` |
| `sync-labels` | Whether or not to remove labels when matching files are reverted or no longer changed by the PR | `false` | `sync-labels` | Whether or not to remove labels when matching files are reverted or no longer changed by the PR | `false`
| `dot` | Whether or not to auto-include paths starting with dot (e.g. `.github`) | `false`
# Contributions When `dot` is disabled and you want to include _all_ files in a folder:
```yml
label1:
- path/to/folder/**/*
- path/to/folder/**/.*
```
If `dot` is enabled:
```yml
label1:
- path/to/folder/**/*
```
## Contributions
Contributions are welcome! See the [Contributor's Guide](CONTRIBUTING.md). Contributions are welcome! See the [Contributor's Guide](CONTRIBUTING.md).

View File

@@ -15,15 +15,29 @@ const matchConfig = [{ any: ["*.txt"] }];
describe("checkGlobs", () => { describe("checkGlobs", () => {
it("returns true when our pattern does match changed files", () => { it("returns true when our pattern does match changed files", () => {
const changedFiles = ["foo.txt", "bar.txt"]; const changedFiles = ["foo.txt", "bar.txt"];
const result = checkGlobs(changedFiles, matchConfig); const result = checkGlobs(changedFiles, matchConfig, false);
expect(result).toBeTruthy(); expect(result).toBeTruthy();
}); });
it("returns false when our pattern does not match changed files", () => { it("returns false when our pattern does not match changed files", () => {
const changedFiles = ["foo.docx"]; const changedFiles = ["foo.docx"];
const result = checkGlobs(changedFiles, matchConfig); const result = checkGlobs(changedFiles, matchConfig, false);
expect(result).toBeFalsy(); expect(result).toBeFalsy();
}); });
it("returns false for a file starting with dot if `dot` option is false", () => {
const changedFiles = [".foo.txt"];
const result = checkGlobs(changedFiles, matchConfig, false);
expect(result).toBeFalsy();
});
it("returns false for a file starting with dot if `dot` option is true", () => {
const changedFiles = [".foo.txt"];
const result = checkGlobs(changedFiles, matchConfig, true);
expect(result).toBeTruthy();
});
}); });

View File

@@ -18,10 +18,24 @@ const yamlFixtures = {
"only_pdfs.yml": fs.readFileSync("__tests__/fixtures/only_pdfs.yml"), "only_pdfs.yml": fs.readFileSync("__tests__/fixtures/only_pdfs.yml"),
}; };
const configureInput = (
mockInput: Partial<{
"repo-token": string;
"configuration-path": string;
"sync-labels": boolean;
dot: boolean;
}>
) => {
jest
.spyOn(core, "getInput")
.mockImplementation((name: string, ...opts) => mockInput[name]);
};
afterAll(() => jest.restoreAllMocks()); afterAll(() => jest.restoreAllMocks());
describe("run", () => { describe("run", () => {
it("adds labels to PRs that match our glob patterns", async () => { it("(with dot: false) adds labels to PRs that match our glob patterns", async () => {
configureInput({});
usingLabelerConfigYaml("only_pdfs.yml"); usingLabelerConfigYaml("only_pdfs.yml");
mockGitHubResponseChangedFiles("foo.pdf"); mockGitHubResponseChangedFiles("foo.pdf");
@@ -37,7 +51,36 @@ describe("run", () => {
}); });
}); });
it("does not add labels to PRs that do not match our glob patterns", async () => { it("(with dot: true) adds labels to PRs that match our glob patterns", async () => {
configureInput({ dot: true });
usingLabelerConfigYaml("only_pdfs.yml");
mockGitHubResponseChangedFiles(".foo.pdf");
await run();
expect(removeLabelMock).toHaveBeenCalledTimes(0);
expect(addLabelsMock).toHaveBeenCalledTimes(1);
expect(addLabelsMock).toHaveBeenCalledWith({
owner: "monalisa",
repo: "helloworld",
issue_number: 123,
labels: ["touched-a-pdf-file"],
});
});
it("(with dot: false) does not add labels to PRs that do not match our glob patterns", async () => {
configureInput({});
usingLabelerConfigYaml("only_pdfs.yml");
mockGitHubResponseChangedFiles(".foo.pdf");
await run();
expect(removeLabelMock).toHaveBeenCalledTimes(0);
expect(addLabelsMock).toHaveBeenCalledTimes(0);
});
it("(with dot: true) does not add labels to PRs that do not match our glob patterns", async () => {
configureInput({ dot: true });
usingLabelerConfigYaml("only_pdfs.yml"); usingLabelerConfigYaml("only_pdfs.yml");
mockGitHubResponseChangedFiles("foo.txt"); mockGitHubResponseChangedFiles("foo.txt");
@@ -48,15 +91,11 @@ describe("run", () => {
}); });
it("(with sync-labels: true) it deletes preexisting PR labels that no longer match the glob pattern", async () => { it("(with sync-labels: true) it deletes preexisting PR labels that no longer match the glob pattern", async () => {
let mockInput = { configureInput({
"repo-token": "foo", "repo-token": "foo",
"configuration-path": "bar", "configuration-path": "bar",
"sync-labels": true, "sync-labels": true,
}; });
jest
.spyOn(core, "getInput")
.mockImplementation((name: string, ...opts) => mockInput[name]);
usingLabelerConfigYaml("only_pdfs.yml"); usingLabelerConfigYaml("only_pdfs.yml");
mockGitHubResponseChangedFiles("foo.txt"); mockGitHubResponseChangedFiles("foo.txt");
@@ -79,15 +118,11 @@ describe("run", () => {
}); });
it("(with sync-labels: false) it issues no delete calls even when there are preexisting PR labels that no longer match the glob pattern", async () => { it("(with sync-labels: false) it issues no delete calls even when there are preexisting PR labels that no longer match the glob pattern", async () => {
let mockInput = { configureInput({
"repo-token": "foo", "repo-token": "foo",
"configuration-path": "bar", "configuration-path": "bar",
"sync-labels": false, "sync-labels": false,
}; });
jest
.spyOn(core, "getInput")
.mockImplementation((name: string, ...opts) => mockInput[name]);
usingLabelerConfigYaml("only_pdfs.yml"); usingLabelerConfigYaml("only_pdfs.yml");
mockGitHubResponseChangedFiles("foo.txt"); mockGitHubResponseChangedFiles("foo.txt");

View File

@@ -12,6 +12,10 @@ inputs:
description: 'Whether or not to remove labels when matching files are reverted' description: 'Whether or not to remove labels when matching files are reverted'
default: false default: false
required: false required: false
dot:
description: 'Whether or not to auto-include paths starting with dot (e.g. `.github`)'
default: false
required: false
runs: runs:
using: 'node12' using: 'node12'

View File

@@ -16,6 +16,7 @@ export async function run() {
const token = core.getInput("repo-token", { required: true }); const token = core.getInput("repo-token", { required: true });
const configPath = core.getInput("configuration-path", { required: true }); const configPath = core.getInput("configuration-path", { required: true });
const syncLabels = !!core.getInput("sync-labels", { required: false }); const syncLabels = !!core.getInput("sync-labels", { required: false });
const dot = !!core.getInput("dot", { required: false });
const prNumber = getPrNumber(); const prNumber = getPrNumber();
if (!prNumber) { if (!prNumber) {
@@ -42,7 +43,7 @@ export async function run() {
const labelsToRemove: string[] = []; const labelsToRemove: string[] = [];
for (const [label, globs] of labelGlobs.entries()) { for (const [label, globs] of labelGlobs.entries()) {
core.debug(`processing ${label}`); core.debug(`processing ${label}`);
if (checkGlobs(changedFiles, globs)) { if (checkGlobs(changedFiles, globs, dot)) {
labels.push(label); labels.push(label);
} else if (pullRequest.labels.find((l) => l.name === label)) { } else if (pullRequest.labels.find((l) => l.name === label)) {
labelsToRemove.push(label); labelsToRemove.push(label);
@@ -157,12 +158,13 @@ function printPattern(matcher: IMinimatch): string {
export function checkGlobs( export function checkGlobs(
changedFiles: string[], changedFiles: string[],
globs: StringOrMatchConfig[] globs: StringOrMatchConfig[],
dot: boolean
): boolean { ): boolean {
for (const glob of globs) { for (const glob of globs) {
core.debug(` checking pattern ${JSON.stringify(glob)}`); core.debug(` checking pattern ${JSON.stringify(glob)}`);
const matchConfig = toMatchConfig(glob); const matchConfig = toMatchConfig(glob);
if (checkMatch(changedFiles, matchConfig)) { if (checkMatch(changedFiles, matchConfig, dot)) {
return true; return true;
} }
} }
@@ -184,8 +186,12 @@ function isMatch(changedFile: string, matchers: IMinimatch[]): boolean {
} }
// equivalent to "Array.some()" but expanded for debugging and clarity // equivalent to "Array.some()" but expanded for debugging and clarity
function checkAny(changedFiles: string[], globs: string[]): boolean { function checkAny(
const matchers = globs.map((g) => new Minimatch(g)); changedFiles: string[],
globs: string[],
dot: boolean
): boolean {
const matchers = globs.map((g) => new Minimatch(g, { dot }));
core.debug(` checking "any" patterns`); core.debug(` checking "any" patterns`);
for (const changedFile of changedFiles) { for (const changedFile of changedFiles) {
if (isMatch(changedFile, matchers)) { if (isMatch(changedFile, matchers)) {
@@ -199,7 +205,11 @@ function checkAny(changedFiles: string[], globs: string[]): boolean {
} }
// equivalent to "Array.every()" but expanded for debugging and clarity // equivalent to "Array.every()" but expanded for debugging and clarity
function checkAll(changedFiles: string[], globs: string[]): boolean { function checkAll(
changedFiles: string[],
globs: string[],
dot: boolean
): boolean {
const matchers = globs.map((g) => new Minimatch(g)); const matchers = globs.map((g) => new Minimatch(g));
core.debug(` checking "all" patterns`); core.debug(` checking "all" patterns`);
for (const changedFile of changedFiles) { for (const changedFile of changedFiles) {
@@ -213,15 +223,19 @@ function checkAll(changedFiles: string[], globs: string[]): boolean {
return true; return true;
} }
function checkMatch(changedFiles: string[], matchConfig: MatchConfig): boolean { function checkMatch(
changedFiles: string[],
matchConfig: MatchConfig,
dot: boolean
): boolean {
if (matchConfig.all !== undefined) { if (matchConfig.all !== undefined) {
if (!checkAll(changedFiles, matchConfig.all)) { if (!checkAll(changedFiles, matchConfig.all, dot)) {
return false; return false;
} }
} }
if (matchConfig.any !== undefined) { if (matchConfig.any !== undefined) {
if (!checkAny(changedFiles, matchConfig.any)) { if (!checkAny(changedFiles, matchConfig.any, dot)) {
return false; return false;
} }
} }