Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.yml
flow-typed
8 changes: 8 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"tabWidth": 4,
"printWidth": 100,
"trailingComma": "all",
"bracketSpacing": false,
"singleQuote": true,
"arrowParens": "avoid"
}
36 changes: 18 additions & 18 deletions __test__/get-base-ref.test.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
// @-flow

const { validateBaseRef } = require("../get-base-ref");
const {validateBaseRef} = require('../get-base-ref');

describe("validateBaseRef", () => {
it("should accept HEAD", () => {
expect(validateBaseRef("HEAD")).toEqual("HEAD");
});
it("should translate a remote branch", () => {
expect(validateBaseRef("an-example-remote-branch")).toEqual(
`refs/remotes/origin/an-example-remote-branch`
);
});

const { GITHUB_BASE_REF } = process.env;
if (GITHUB_BASE_REF) {
// Skip this test, because the remote environment isn't finding local `master`
} else {
it("should accept a local branch", () => {
expect(validateBaseRef("master")).toEqual("master");
describe('validateBaseRef', () => {
it('should accept HEAD', () => {
expect(validateBaseRef('HEAD')).toEqual('HEAD');
});
it('should translate a remote branch', () => {
expect(validateBaseRef('an-example-remote-branch')).toEqual(
`refs/remotes/origin/an-example-remote-branch`,
);
});
}

const {GITHUB_BASE_REF} = process.env;
if (GITHUB_BASE_REF) {
// Skip this test, because the remote environment isn't finding local `master`
} else {
it('should accept a local branch', () => {
expect(validateBaseRef('master')).toEqual('master');
});
}
});
50 changes: 25 additions & 25 deletions exec-prom.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,35 @@
/**
* A simple promisified version of child_process.exec, so we can `await` it
*/
const { spawn } = require("child_process");
const {spawn} = require('child_process');

const execProm = (
command /*: string*/,
{ rejectOnError, ...options } /*: {rejectOnError: boolean} & mixed */ = {}
command /*: string*/,
{rejectOnError, ...options} /*: {rejectOnError: boolean} & mixed */ = {},
) /*: Promise<{err: ?number, stdout: string, stderr: string}>*/ =>
new Promise((res, rej) => {
const proc = spawn(
command,
// $FlowFixMe
{ ...options, shell: true }
);
let stdout = "";
let stderr = "";
proc.stdout.setEncoding("utf8");
proc.stderr.setEncoding("utf8");
proc.stdout.on("data", (data /*: string*/) => (stdout += data));
proc.stderr.on("data", (data /*: string*/) => (stderr += data));
proc.on("close", (code /*: number*/) => {
if (code !== 0 && rejectOnError) {
rej(new Error(`Exited with non-zero error code: ${code}`));
} else {
res({
err: code === 0 ? null : code,
stdout,
stderr,
new Promise((res, rej) => {
const proc = spawn(
command,
// $FlowFixMe
{...options, shell: true},
);
let stdout = '';
let stderr = '';
proc.stdout.setEncoding('utf8');
proc.stderr.setEncoding('utf8');
proc.stdout.on('data', (data /*: string*/) => (stdout += data));
proc.stderr.on('data', (data /*: string*/) => (stderr += data));
proc.on('close', (code /*: number*/) => {
if (code !== 0 && rejectOnError) {
rej(new Error(`Exited with non-zero error code: ${code}`));
} else {
res({
err: code === 0 ? null : code,
stdout,
stderr,
});
}
});
}
});
});

module.exports = execProm;
142 changes: 71 additions & 71 deletions get-base-ref.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,94 +13,94 @@
* TODO(jared): Consider using the github pull-request API (if we're online)
* to determine the base branch.
*/
const { execSync, spawnSync } = require("child_process");
const {execSync, spawnSync} = require('child_process');

const checkRef = (ref) => spawnSync("git", ["rev-parse", ref]).status === 0;
const checkRef = ref => spawnSync('git', ['rev-parse', ref]).status === 0;

const validateBaseRef = (baseRef /*:string*/) => {
// It's locally accessible!
if (checkRef(baseRef)) {
return baseRef;
}
// If it's not locally accessible, then it's probably a remote branch
const remote = `refs/remotes/origin/${baseRef}`;
if (checkRef(remote)) {
return remote;
}
const validateBaseRef = (baseRef /*:string*/) /*: string | null */ => {
// It's locally accessible!
if (checkRef(baseRef)) {
return baseRef;
}
// If it's not locally accessible, then it's probably a remote branch
const remote = `refs/remotes/origin/${baseRef}`;
if (checkRef(remote)) {
return remote;
}

// Otherwise return null - no valid ref provided
return null;
// Otherwise return null - no valid ref provided
return null;
};

const getBaseRef = (head /*:string*/ = "HEAD") => {
const { GITHUB_BASE_REF } = process.env;
if (GITHUB_BASE_REF) {
return validateBaseRef(GITHUB_BASE_REF);
} else {
let upstream = execSync(`git rev-parse --abbrev-ref '${head}@{upstream}'`, {
encoding: "utf8",
});
upstream = upstream.trim();
const getBaseRef = (head /*:string*/ = 'HEAD') /*: string | null */ => {
const {GITHUB_BASE_REF} = process.env;
if (GITHUB_BASE_REF) {
return validateBaseRef(GITHUB_BASE_REF);
} else {
let upstream = execSync(`git rev-parse --abbrev-ref '${head}@{upstream}'`, {
encoding: 'utf8',
});
upstream = upstream.trim();

// if upstream is local and not empty, use that.
if (upstream && !upstream.trim().startsWith("origin/")) {
return `refs/heads/${upstream}`;
}
let headRef = execSync(`git rev-parse --abbrev-ref ${head}`, {
encoding: "utf8",
});
headRef = headRef.trim();
for (let i = 1; i < 100; i++) {
try {
const stdout = execSync(
`git branch --contains ${head}~${i} --format='%(refname)'`,
{ encoding: "utf8" }
);
let lines = stdout.split("\n").filter(Boolean);
lines = lines.filter((line) => line !== `refs/heads/${headRef}`);
// if upstream is local and not empty, use that.
if (upstream && !upstream.trim().startsWith('origin/')) {
return `refs/heads/${upstream}`;
}
let headRef = execSync(`git rev-parse --abbrev-ref ${head}`, {
encoding: 'utf8',
});
headRef = headRef.trim();
for (let i = 1; i < 100; i++) {
try {
const stdout = execSync(
`git branch --contains ${head}~${i} --format='%(refname)'`,
{encoding: 'utf8'},
);
let lines = stdout.split('\n').filter(Boolean);
lines = lines.filter(line => line !== `refs/heads/${headRef}`);

// Note (Lilli): When running our actions locally, we want to be a little more
// aggressive in choosing a baseRef, going back to a shared commit on only `develop`,
// `master`, feature or release branches, so that we can cover more commits. In case,
// say, I create a bunch of experimental, first-attempt, throw-away branches that
// share commits higher in my stack...
for (const line of lines) {
if (
line === "refs/heads/develop" ||
line === "refs/heads/master" ||
line.startsWith("refs/heads/feature/") ||
line.startsWith("refs/heads/release/")
) {
return line;
}
// Note (Lilli): When running our actions locally, we want to be a little more
// aggressive in choosing a baseRef, going back to a shared commit on only `develop`,
// `master`, feature or release branches, so that we can cover more commits. In case,
// say, I create a bunch of experimental, first-attempt, throw-away branches that
// share commits higher in my stack...
for (const line of lines) {
if (
line === 'refs/heads/develop' ||
line === 'refs/heads/master' ||
line.startsWith('refs/heads/feature/') ||
line.startsWith('refs/heads/release/')
) {
return line;
}
}
} catch {
// Ran out of history, probably
return null;
}
}
} catch {
// Ran out of history, probably
// We couldn't find it
return null;
}
}
// We couldn't find it
return null;
}
};

// Multiple action microservices might encounter this, so give them a canned message to print.
// Logging from inside this lib didn't seem to make it to the GitHub Actions console, so I'll
// just pass the string back for them to log.
const cannedGithubErrorMessage = () /*:string*/ => {
const { GITHUB_BASE_REF } = process.env;
const {GITHUB_BASE_REF} = process.env;

return GITHUB_BASE_REF
? `No valid base ref given. Found \`${GITHUB_BASE_REF}\`, but \`${GITHUB_BASE_REF}\` does not ` +
`appear to be a valid branch. Perhaps this is coming from a GitHub pull-request that ` +
`you reparented, and the old parent no longer exists. This is a bug on GitHub; unless ` +
`you push a new commit, the old base ref won't update. You can try solving this by: \n` +
`\t1. Merging the new base branch into your pull-request and re-running your checks.\n` +
`\t2. Rebasing your pull-request branch onto the new base branch and re-running your checks.\n` +
`\t3. Creating and pushing an empty commit (e.g., \`$ git commit --allow-empty -m ` +
`'Trigger checks' && git push\`).`
: `No valid base ref given. The envar \`GITHUB_BASE_REF\` was null and no other base ref could ` +
`be determined.`;
return GITHUB_BASE_REF
? `No valid base ref given. Found \`${GITHUB_BASE_REF}\`, but \`${GITHUB_BASE_REF}\` does not ` +
`appear to be a valid branch. Perhaps this is coming from a GitHub pull-request that ` +
`you reparented, and the old parent no longer exists. This is a bug on GitHub; unless ` +
`you push a new commit, the old base ref won't update. You can try solving this by: \n` +
`\t1. Merging the new base branch into your pull-request and re-running your checks.\n` +
`\t2. Rebasing your pull-request branch onto the new base branch and re-running your checks.\n` +
`\t3. Creating and pushing an empty commit (e.g., \`$ git commit --allow-empty -m ` +
`'Trigger checks' && git push\`).`
: `No valid base ref given. The envar \`GITHUB_BASE_REF\` was null and no other base ref could ` +
`be determined.`;
};

module.exports = getBaseRef;
Expand Down
41 changes: 19 additions & 22 deletions git-changed-files.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
const execProm = require('./exec-prom');
const path = require('path');
const fs = require('fs');
const minimatch = require('minimatch');
const minimatch = require('minimatch'); // flow-uncovered-line

const getIgnoredPatterns = (fileContents) => {
// ok
const getIgnoredPatterns = (fileContents /*: string*/) => {
return fileContents
.split('\n')
.map((line) => {
.map(line => {
if (line.startsWith('#')) {
return null;
}
Expand All @@ -18,19 +19,16 @@ const getIgnoredPatterns = (fileContents) => {
return null;
}
const [pattern, ...attributes] = line.trim().split(' ');
if (
attributes.includes('binary') ||
attributes.includes('linguist-generated=true')
) {
if (attributes.includes('binary') || attributes.includes('linguist-generated=true')) {
return pattern;
}
return null;
})
.filter(Boolean);
};

const ignoredPatternsByDirectory = {};
const isFileIgnored = (workingDirectory, file) => {
const ignoredPatternsByDirectory /*: {[key: string]: Array<string>}*/ = {};
const isFileIgnored = (workingDirectory /*: string*/, file /*: string*/) => {
// If it's outside of the "working directory", we ignore it
if (!file.startsWith(workingDirectory)) {
return true;
Expand All @@ -49,6 +47,7 @@ const isFileIgnored = (workingDirectory, file) => {
}
}
for (const pattern of ignoredPatternsByDirectory[dir]) {
// flow-next-uncovered-line
if (minimatch(name, pattern)) {
return true;
}
Expand All @@ -65,10 +64,7 @@ const isFileIgnored = (workingDirectory, file) => {
* It also respects '.gitattributes', filtering out files that have been marked
* as "binary" or "linguist-generated=true".
*/
const gitChangedFiles = async (
base /*:string*/,
cwd /*:string*/,
) /*: Promise<Array<string>>*/ => {
const gitChangedFiles = async (base /*:string*/, cwd /*:string*/) /*: Promise<Array<string>>*/ => {
cwd = path.resolve(cwd);

// Github actions jobs can run the following steps to get a fully accurate
Expand All @@ -88,22 +84,23 @@ const gitChangedFiles = async (
// ALL_CHANGED_FILES: '${{ steps.changed.outputs.added_modified }}'
//
if (process.env.ALL_CHANGED_FILES) {
const files = JSON.parse(process.env.ALL_CHANGED_FILES);
return files.filter((path) => !isFileIgnored(cwd, path));
const files /*: Array<string> */ = JSON.parse(process.env.ALL_CHANGED_FILES); // flow-uncovered-line
return files.filter(path => !isFileIgnored(cwd, path));
}

const { stdout } = await execProm(
`git diff --name-only ${base} --relative`,
{ cwd, encoding: 'utf8', rejectOnError: true },
);
const {stdout} = await execProm(`git diff --name-only ${base} --relative`, {
cwd,
encoding: 'utf8',
rejectOnError: true,
});
return (
stdout
.split('\n')
.filter(Boolean)
.map((name) => path.join(cwd, name))
.map(name => path.join(cwd, name))
// Filter out paths that were deleted
.filter((path) => fs.existsSync(path))
.filter((path) => !isFileIgnored(cwd, path))
.filter((path /*: string*/) => fs.existsSync(path))
.filter((path /*: string*/) => !isFileIgnored(cwd, path))
);
};

Expand Down
Loading