mirror of
https://gitea.com/actions/dorny-paths-filter.git
synced 2025-12-25 16:38:20 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bd03c0d68 | ||
|
|
9553dabbd8 | ||
|
|
8b399ed168 | ||
|
|
ff5bb057bf | ||
|
|
d9e86af7c0 | ||
|
|
adb239d623 |
11
CHANGELOG.md
11
CHANGELOG.md
@@ -1,5 +1,16 @@
|
||||
# Changelog
|
||||
|
||||
## v2.4.2
|
||||
- [Fixed compatibility with older (<2.23) versions of git](https://github.com/dorny/paths-filter/pull/42)
|
||||
|
||||
## v2.4.0
|
||||
- [Support pushes of tags or when tag is used as base](https://github.com/dorny/paths-filter/pull/40)
|
||||
- [Use git log to detect changes from PRs merge commit if token is not available](https://github.com/dorny/paths-filter/pull/40)
|
||||
- [Support local execution with act](https://github.com/dorny/paths-filter/pull/40)
|
||||
- [Improved processing of repository initial push](https://github.com/dorny/paths-filter/pull/40)
|
||||
- [Improved processing of first push of new branch](https://github.com/dorny/paths-filter/pull/40)
|
||||
|
||||
|
||||
## v2.3.0
|
||||
- [Improved documentation](https://github.com/dorny/paths-filter/pull/37)
|
||||
- [Change detection using git "three dot" diff](https://github.com/dorny/paths-filter/pull/35)
|
||||
|
||||
35
README.md
35
README.md
@@ -1,8 +1,7 @@
|
||||
|
||||
# paths-filter
|
||||
# Paths Changes Filter
|
||||
|
||||
This [Github Action](https://github.com/features/actions) enables conditional execution of workflow steps and jobs,
|
||||
based on the paths that are modified by pull request or in pushed commits.
|
||||
based on the files modified by pull request, feature branch or in pushed commits.
|
||||
|
||||
It saves time and resources especially in monorepo setups, where you can run slow tasks (e.g. integration tests or deployments) only for changed components.
|
||||
Github workflows built-in [path filters](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#onpushpull_requestpaths)
|
||||
@@ -24,17 +23,36 @@ doesn't allow this because they doesn't work on a level of individual jobs or st
|
||||
- Changes are detected against the most recent commit on the same branch before the push
|
||||
- Uses git commands to detect changes - repository must be already [checked out](https://github.com/actions/checkout)
|
||||
|
||||
## Example
|
||||
```yaml
|
||||
- uses: dorny/paths-filter@v2
|
||||
id: changes
|
||||
with:
|
||||
filters: |
|
||||
src:
|
||||
- 'src/**'
|
||||
|
||||
## Important notes:
|
||||
# run only if some file in 'src' folder was changed
|
||||
if: steps.changes.outputs.src == 'true'
|
||||
run: ...
|
||||
```
|
||||
For more scenarios see [examples](#examples) section.
|
||||
|
||||
## Notes:
|
||||
- Paths expressions are evaluated using [minimatch](https://github.com/isaacs/minimatch) library.
|
||||
Documentation for path expression format can be found on project github page.
|
||||
- Minimatch [dot](https://www.npmjs.com/package/minimatch#dot) option is set to true.
|
||||
Globbing will match also paths where file or folder name starts with a dot.
|
||||
- It's recommended to quote your path expressions with `'` or `"`. Otherwise you will get an error if it starts with `*`.
|
||||
- Local execution with [act](https://github.com/nektos/act) works only with alternative runner image. Default runner doesn't have `git` binary.
|
||||
- Use: `act -P ubuntu-latest=nektos/act-environments-ubuntu:18.04`
|
||||
|
||||
|
||||
# What's New
|
||||
|
||||
- Fixed compatibility with older (<2.23) versions of git
|
||||
- Support for tag pushes and tags as a base reference
|
||||
- Fixes for various edge cases when event payload is incomplete
|
||||
- Supports local execution with [act](https://github.com/nektos/act)
|
||||
- Fixed behavior of feature branch workflow:
|
||||
- Detects only changes introduced by feature branch. Later modifications on base branch are ignored.
|
||||
- Filter by type of file change:
|
||||
@@ -42,7 +60,6 @@ doesn't allow this because they doesn't work on a level of individual jobs or st
|
||||
- Custom processing of changed files:
|
||||
- Optionally export paths of all files matching the filter
|
||||
- Output can be space-delimited or in JSON format
|
||||
- Improved documentation and logging
|
||||
|
||||
For more information see [CHANGELOG](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
|
||||
|
||||
@@ -68,7 +85,7 @@ For more information see [CHANGELOG](https://github.com/actions/checkout/blob/ma
|
||||
# Filters syntax is documented by example - see examples section.
|
||||
filters: ''
|
||||
|
||||
# Branch against which the changes will be detected.
|
||||
# Branch or tag against which the changes will be detected.
|
||||
# If it references same branch it was pushed to,
|
||||
# changes are detected against the most recent commit before the push.
|
||||
# Otherwise it uses git merge-base to find best common ancestor between
|
||||
@@ -235,6 +252,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
# This may save additional git fetch roundtrip if
|
||||
# merge-base is found within latest 20 commits
|
||||
fetch-depth: 20
|
||||
- uses: dorny/paths-filter@v2
|
||||
id: filter
|
||||
with:
|
||||
|
||||
@@ -17,19 +17,13 @@ describe('parsing output of the git diff command', () => {
|
||||
})
|
||||
|
||||
describe('git utility function tests (those not invoking git)', () => {
|
||||
test('Detects if ref references a tag', () => {
|
||||
expect(git.isTagRef('refs/tags/v1.0')).toBeTruthy()
|
||||
expect(git.isTagRef('refs/heads/master')).toBeFalsy()
|
||||
expect(git.isTagRef('master')).toBeFalsy()
|
||||
})
|
||||
test('Trims "refs/" from ref', () => {
|
||||
expect(git.trimRefs('refs/heads/master')).toBe('heads/master')
|
||||
expect(git.trimRefs('heads/master')).toBe('heads/master')
|
||||
expect(git.trimRefs('master')).toBe('master')
|
||||
})
|
||||
test('Trims "refs/" and "heads/" from ref', () => {
|
||||
expect(git.trimRefsHeads('refs/heads/master')).toBe('master')
|
||||
expect(git.trimRefsHeads('heads/master')).toBe('master')
|
||||
expect(git.trimRefsHeads('master')).toBe('master')
|
||||
expect(git.getShortName('refs/heads/master')).toBe('master')
|
||||
expect(git.getShortName('heads/master')).toBe('heads/master')
|
||||
expect(git.getShortName('master')).toBe('master')
|
||||
|
||||
expect(git.getShortName('refs/tags/v1')).toBe('v1')
|
||||
expect(git.getShortName('tags/v1')).toBe('tags/v1')
|
||||
expect(git.getShortName('v1')).toBe('v1')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: 'Paths filter'
|
||||
name: 'Paths Changes Filter'
|
||||
description: 'Execute your workflow steps only if relevant files are modified.'
|
||||
author: 'Michal Dorner <dorner.michal@gmail.com>'
|
||||
inputs:
|
||||
|
||||
214
dist/index.js
vendored
214
dist/index.js
vendored
@@ -3807,27 +3807,20 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.trimRefsHeads = exports.trimRefs = exports.isTagRef = exports.listAllFilesAsAdded = exports.parseGitDiffOutput = exports.getChangesSinceRef = exports.getChangesAgainstSha = exports.NULL_SHA = void 0;
|
||||
const exec_1 = __webpack_require__(986);
|
||||
exports.getShortName = exports.getCurrentRef = exports.listAllFilesAsAdded = exports.parseGitDiffOutput = exports.getChangesSinceMergeBase = exports.getChanges = exports.getChangesInLastCommit = exports.NULL_SHA = void 0;
|
||||
const exec_1 = __importDefault(__webpack_require__(807));
|
||||
const core = __importStar(__webpack_require__(470));
|
||||
const file_1 = __webpack_require__(258);
|
||||
exports.NULL_SHA = '0000000000000000000000000000000000000000';
|
||||
async function getChangesAgainstSha(sha) {
|
||||
// Fetch single commit
|
||||
core.startGroup(`Fetching ${sha} from origin`);
|
||||
await exec_1.exec('git', ['fetch', '--depth=1', '--no-tags', 'origin', sha]);
|
||||
core.endGroup();
|
||||
// Get differences between sha and HEAD
|
||||
core.startGroup(`Change detection ${sha}..HEAD`);
|
||||
async function getChangesInLastCommit() {
|
||||
core.startGroup(`Change detection in last commit`);
|
||||
let output = '';
|
||||
try {
|
||||
// Two dots '..' change detection - directly compares two versions
|
||||
await exec_1.exec('git', ['diff', '--no-renames', '--name-status', '-z', `${sha}..HEAD`], {
|
||||
listeners: {
|
||||
stdout: (data) => (output += data.toString())
|
||||
}
|
||||
});
|
||||
output = (await exec_1.default('git', ['log', '--format=', '--no-renames', '--name-status', '-z', '-n', '1'])).stdout;
|
||||
}
|
||||
finally {
|
||||
fixStdOutNullTermination();
|
||||
@@ -3835,23 +3828,52 @@ async function getChangesAgainstSha(sha) {
|
||||
}
|
||||
return parseGitDiffOutput(output);
|
||||
}
|
||||
exports.getChangesAgainstSha = getChangesAgainstSha;
|
||||
async function getChangesSinceRef(ref, initialFetchDepth) {
|
||||
// Fetch and add base branch
|
||||
core.startGroup(`Fetching ${ref} from origin until merge-base is found`);
|
||||
await exec_1.exec('git', ['fetch', `--depth=${initialFetchDepth}`, '--no-tags', 'origin', `${ref}:${ref}`]);
|
||||
exports.getChangesInLastCommit = getChangesInLastCommit;
|
||||
async function getChanges(ref) {
|
||||
if (!(await hasCommit(ref))) {
|
||||
// Fetch single commit
|
||||
core.startGroup(`Fetching ${ref} from origin`);
|
||||
await exec_1.default('git', ['fetch', '--depth=1', '--no-tags', 'origin', ref]);
|
||||
core.endGroup();
|
||||
}
|
||||
// Get differences between ref and HEAD
|
||||
core.startGroup(`Change detection ${ref}..HEAD`);
|
||||
let output = '';
|
||||
try {
|
||||
// Two dots '..' change detection - directly compares two versions
|
||||
output = (await exec_1.default('git', ['diff', '--no-renames', '--name-status', '-z', `${ref}..HEAD`])).stdout;
|
||||
}
|
||||
finally {
|
||||
fixStdOutNullTermination();
|
||||
core.endGroup();
|
||||
}
|
||||
return parseGitDiffOutput(output);
|
||||
}
|
||||
exports.getChanges = getChanges;
|
||||
async function getChangesSinceMergeBase(ref, initialFetchDepth) {
|
||||
if (!(await hasCommit(ref))) {
|
||||
// Fetch and add base branch
|
||||
core.startGroup(`Fetching ${ref}`);
|
||||
try {
|
||||
await exec_1.default('git', ['fetch', `--depth=${initialFetchDepth}`, '--no-tags', 'origin', `${ref}:${ref}`]);
|
||||
}
|
||||
finally {
|
||||
core.endGroup();
|
||||
}
|
||||
}
|
||||
async function hasMergeBase() {
|
||||
return (await exec_1.exec('git', ['merge-base', ref, 'HEAD'], { ignoreReturnCode: true })) === 0;
|
||||
return (await exec_1.default('git', ['merge-base', ref, 'HEAD'], { ignoreReturnCode: true })).code === 0;
|
||||
}
|
||||
async function countCommits() {
|
||||
return (await getNumberOfCommits('HEAD')) + (await getNumberOfCommits(ref));
|
||||
}
|
||||
core.startGroup(`Searching for merge-base with ${ref}`);
|
||||
// Fetch more commits until merge-base is found
|
||||
if (!(await hasMergeBase())) {
|
||||
let deepen = initialFetchDepth;
|
||||
let lastCommitsCount = await countCommits();
|
||||
do {
|
||||
await exec_1.exec('git', ['fetch', `--deepen=${deepen}`, '--no-tags', '--no-auto-gc', '-q']);
|
||||
await exec_1.default('git', ['fetch', `--deepen=${deepen}`, '--no-tags']);
|
||||
const count = await countCommits();
|
||||
if (count <= lastCommitsCount) {
|
||||
core.info('No merge base found - all files will be listed as added');
|
||||
@@ -3868,11 +3890,7 @@ async function getChangesSinceRef(ref, initialFetchDepth) {
|
||||
let output = '';
|
||||
try {
|
||||
// Three dots '...' change detection - finds merge-base and compares against it
|
||||
await exec_1.exec('git', ['diff', '--no-renames', '--name-status', '-z', `${ref}...HEAD`], {
|
||||
listeners: {
|
||||
stdout: (data) => (output += data.toString())
|
||||
}
|
||||
});
|
||||
output = (await exec_1.default('git', ['diff', '--no-renames', '--name-status', '-z', `${ref}...HEAD`])).stdout;
|
||||
}
|
||||
finally {
|
||||
fixStdOutNullTermination();
|
||||
@@ -3880,7 +3898,7 @@ async function getChangesSinceRef(ref, initialFetchDepth) {
|
||||
}
|
||||
return parseGitDiffOutput(output);
|
||||
}
|
||||
exports.getChangesSinceRef = getChangesSinceRef;
|
||||
exports.getChangesSinceMergeBase = getChangesSinceMergeBase;
|
||||
function parseGitDiffOutput(output) {
|
||||
const tokens = output.split('\u0000').filter(s => s.length > 0);
|
||||
const files = [];
|
||||
@@ -3897,11 +3915,7 @@ async function listAllFilesAsAdded() {
|
||||
core.startGroup('Listing all files tracked by git');
|
||||
let output = '';
|
||||
try {
|
||||
await exec_1.exec('git', ['ls-files', '-z'], {
|
||||
listeners: {
|
||||
stdout: (data) => (output += data.toString())
|
||||
}
|
||||
});
|
||||
output = (await exec_1.default('git', ['ls-files', '-z'])).stdout;
|
||||
}
|
||||
finally {
|
||||
fixStdOutNullTermination();
|
||||
@@ -3916,32 +3930,50 @@ async function listAllFilesAsAdded() {
|
||||
}));
|
||||
}
|
||||
exports.listAllFilesAsAdded = listAllFilesAsAdded;
|
||||
function isTagRef(ref) {
|
||||
return ref.startsWith('refs/tags/');
|
||||
}
|
||||
exports.isTagRef = isTagRef;
|
||||
function trimRefs(ref) {
|
||||
return trimStart(ref, 'refs/');
|
||||
}
|
||||
exports.trimRefs = trimRefs;
|
||||
function trimRefsHeads(ref) {
|
||||
const trimRef = trimStart(ref, 'refs/');
|
||||
return trimStart(trimRef, 'heads/');
|
||||
}
|
||||
exports.trimRefsHeads = trimRefsHeads;
|
||||
async function getNumberOfCommits(ref) {
|
||||
let output = '';
|
||||
await exec_1.exec('git', ['rev-list', `--count`, ref], {
|
||||
listeners: {
|
||||
stdout: (data) => (output += data.toString())
|
||||
async function getCurrentRef() {
|
||||
core.startGroup(`Determining current ref`);
|
||||
try {
|
||||
const branch = (await exec_1.default('git', ['branch', '--show-current'])).stdout.trim();
|
||||
if (branch) {
|
||||
return branch;
|
||||
}
|
||||
});
|
||||
const describe = await exec_1.default('git', ['describe', '--tags', '--exact-match'], { ignoreReturnCode: true });
|
||||
if (describe.code === 0) {
|
||||
return describe.stdout.trim();
|
||||
}
|
||||
return (await exec_1.default('git', ['rev-parse', 'HEAD'])).stdout.trim();
|
||||
}
|
||||
finally {
|
||||
core.endGroup();
|
||||
}
|
||||
}
|
||||
exports.getCurrentRef = getCurrentRef;
|
||||
function getShortName(ref) {
|
||||
if (!ref)
|
||||
return '';
|
||||
const heads = 'refs/heads/';
|
||||
const tags = 'refs/tags/';
|
||||
if (ref.startsWith(heads))
|
||||
return ref.slice(heads.length);
|
||||
if (ref.startsWith(tags))
|
||||
return ref.slice(tags.length);
|
||||
return ref;
|
||||
}
|
||||
exports.getShortName = getShortName;
|
||||
async function hasCommit(ref) {
|
||||
core.startGroup(`Checking if commit for ${ref} is locally available`);
|
||||
try {
|
||||
return (await exec_1.default('git', ['cat-file', '-e', `${ref}^{commit}`], { ignoreReturnCode: true })).code === 0;
|
||||
}
|
||||
finally {
|
||||
core.endGroup();
|
||||
}
|
||||
}
|
||||
async function getNumberOfCommits(ref) {
|
||||
const output = (await exec_1.default('git', ['rev-list', `--count`, ref])).stdout;
|
||||
const count = parseInt(output);
|
||||
return isNaN(count) ? 0 : count;
|
||||
}
|
||||
function trimStart(ref, start) {
|
||||
return ref.startsWith(start) ? ref.substr(start.length) : ref;
|
||||
}
|
||||
function fixStdOutNullTermination() {
|
||||
// Previous command uses NULL as delimiters and output is printed to stdout.
|
||||
// We have to make sure next thing written to stdout will start on new line.
|
||||
@@ -4641,9 +4673,11 @@ function getConfigFileContent(configPath) {
|
||||
async function getChangedFiles(token, base, initialFetchDepth) {
|
||||
if (github.context.eventName === 'pull_request' || github.context.eventName === 'pull_request_target') {
|
||||
const pr = github.context.payload.pull_request;
|
||||
return token
|
||||
? await getChangedFilesFromApi(token, pr)
|
||||
: await git.getChangesSinceRef(pr.base.ref, initialFetchDepth);
|
||||
if (token) {
|
||||
return await getChangedFilesFromApi(token, pr);
|
||||
}
|
||||
core.info('Github token is not available - changes will be detected from PRs merge commit');
|
||||
return await git.getChangesInLastCommit();
|
||||
}
|
||||
else if (github.context.eventName === 'push') {
|
||||
return getChangedFilesFromPush(base, initialFetchDepth);
|
||||
@@ -4653,26 +4687,41 @@ async function getChangedFiles(token, base, initialFetchDepth) {
|
||||
}
|
||||
}
|
||||
async function getChangedFilesFromPush(base, initialFetchDepth) {
|
||||
var _a;
|
||||
const push = github.context.payload;
|
||||
// No change detection for pushed tags
|
||||
if (git.isTagRef(push.ref)) {
|
||||
core.info('Workflow is triggered by pushing of tag - all files will be listed as added');
|
||||
return await git.listAllFilesAsAdded();
|
||||
const defaultRef = (_a = push.repository) === null || _a === void 0 ? void 0 : _a.default_branch;
|
||||
const pushRef = git.getShortName(push.ref) ||
|
||||
(core.warning(`'ref' field is missing in PUSH event payload - using current branch, tag or commit SHA`),
|
||||
await git.getCurrentRef());
|
||||
const baseRef = git.getShortName(base) || defaultRef;
|
||||
if (!baseRef) {
|
||||
throw new Error("This action requires 'base' input to be configured or 'repository.default_branch' to be set in the event payload");
|
||||
}
|
||||
const baseRef = git.trimRefsHeads(base || push.repository.default_branch);
|
||||
const pushRef = git.trimRefsHeads(push.ref);
|
||||
// If base references same branch it was pushed to, we will do comparison against the previously pushed commit.
|
||||
// If base references same branch it was pushed to,
|
||||
// we will do comparison against the previously pushed commit
|
||||
if (baseRef === pushRef) {
|
||||
if (!push.before) {
|
||||
core.warning(`'before' field is missing in PUSH event payload - changes will be detected from last commit`);
|
||||
return await git.getChangesInLastCommit();
|
||||
}
|
||||
// If there is no previously pushed commit,
|
||||
// we will do comparison against the default branch or return all as added
|
||||
if (push.before === git.NULL_SHA) {
|
||||
core.info('First push of a branch detected - all files will be listed as added');
|
||||
return await git.listAllFilesAsAdded();
|
||||
if (defaultRef && baseRef !== defaultRef) {
|
||||
core.info(`First push of a branch detected - changes will be detected against the default branch ${defaultRef}`);
|
||||
return await git.getChangesSinceMergeBase(defaultRef, initialFetchDepth);
|
||||
}
|
||||
else {
|
||||
core.info('Initial push detected - all files will be listed as added');
|
||||
return await git.listAllFilesAsAdded();
|
||||
}
|
||||
}
|
||||
core.info(`Changes will be detected against the last previously pushed commit on same branch (${pushRef})`);
|
||||
return await git.getChangesAgainstSha(push.before);
|
||||
return await git.getChanges(push.before);
|
||||
}
|
||||
// Changes introduced by current branch against the base branch
|
||||
core.info(`Changes will be detected against the branch ${baseRef}`);
|
||||
return await git.getChangesSinceRef(baseRef, initialFetchDepth);
|
||||
return await git.getChangesSinceMergeBase(baseRef, initialFetchDepth);
|
||||
}
|
||||
// Uses github REST api to get list of files changed in PR
|
||||
async function getChangedFilesFromApi(token, pullRequest) {
|
||||
@@ -4764,7 +4813,7 @@ module.exports = require("https");
|
||||
/***/ 215:
|
||||
/***/ (function(module) {
|
||||
|
||||
module.exports = {"_args":[["@octokit/rest@16.43.1","C:\\Users\\Michal\\Workspace\\dorny\\pr-changed-files-filter"]],"_from":"@octokit/rest@16.43.1","_id":"@octokit/rest@16.43.1","_inBundle":false,"_integrity":"sha512-gfFKwRT/wFxq5qlNjnW2dh+qh74XgTQ2B179UX5K1HYCluioWj8Ndbgqw2PVqa1NnVJkGHp2ovMpVn/DImlmkw==","_location":"/@octokit/rest","_phantomChildren":{"@types/node":"14.0.5","deprecation":"2.3.1","once":"1.4.0","os-name":"3.1.0"},"_requested":{"type":"version","registry":true,"raw":"@octokit/rest@16.43.1","name":"@octokit/rest","escapedName":"@octokit%2frest","scope":"@octokit","rawSpec":"16.43.1","saveSpec":null,"fetchSpec":"16.43.1"},"_requiredBy":["/@actions/github"],"_resolved":"https://registry.npmjs.org/@octokit/rest/-/rest-16.43.1.tgz","_spec":"16.43.1","_where":"C:\\Users\\Michal\\Workspace\\dorny\\pr-changed-files-filter","author":{"name":"Gregor Martynus","url":"https://github.com/gr2m"},"bugs":{"url":"https://github.com/octokit/rest.js/issues"},"bundlesize":[{"path":"./dist/octokit-rest.min.js.gz","maxSize":"33 kB"}],"contributors":[{"name":"Mike de Boer","email":"info@mikedeboer.nl"},{"name":"Fabian Jakobs","email":"fabian@c9.io"},{"name":"Joe Gallo","email":"joe@brassafrax.com"},{"name":"Gregor Martynus","url":"https://github.com/gr2m"}],"dependencies":{"@octokit/auth-token":"^2.4.0","@octokit/plugin-paginate-rest":"^1.1.1","@octokit/plugin-request-log":"^1.0.0","@octokit/plugin-rest-endpoint-methods":"2.4.0","@octokit/request":"^5.2.0","@octokit/request-error":"^1.0.2","atob-lite":"^2.0.0","before-after-hook":"^2.0.0","btoa-lite":"^1.0.0","deprecation":"^2.0.0","lodash.get":"^4.4.2","lodash.set":"^4.3.2","lodash.uniq":"^4.5.0","octokit-pagination-methods":"^1.1.0","once":"^1.4.0","universal-user-agent":"^4.0.0"},"description":"GitHub REST API client for Node.js","devDependencies":{"@gimenete/type-writer":"^0.1.3","@octokit/auth":"^1.1.1","@octokit/fixtures-server":"^5.0.6","@octokit/graphql":"^4.2.0","@types/node":"^13.1.0","bundlesize":"^0.18.0","chai":"^4.1.2","compression-webpack-plugin":"^3.1.0","cypress":"^3.0.0","glob":"^7.1.2","http-proxy-agent":"^4.0.0","lodash.camelcase":"^4.3.0","lodash.merge":"^4.6.1","lodash.upperfirst":"^4.3.1","lolex":"^5.1.2","mkdirp":"^1.0.0","mocha":"^7.0.1","mustache":"^4.0.0","nock":"^11.3.3","npm-run-all":"^4.1.2","nyc":"^15.0.0","prettier":"^1.14.2","proxy":"^1.0.0","semantic-release":"^17.0.0","sinon":"^8.0.0","sinon-chai":"^3.0.0","sort-keys":"^4.0.0","string-to-arraybuffer":"^1.0.0","string-to-jsdoc-comment":"^1.0.0","typescript":"^3.3.1","webpack":"^4.0.0","webpack-bundle-analyzer":"^3.0.0","webpack-cli":"^3.0.0"},"files":["index.js","index.d.ts","lib","plugins"],"homepage":"https://github.com/octokit/rest.js#readme","keywords":["octokit","github","rest","api-client"],"license":"MIT","name":"@octokit/rest","nyc":{"ignore":["test"]},"publishConfig":{"access":"public"},"release":{"publish":["@semantic-release/npm",{"path":"@semantic-release/github","assets":["dist/*","!dist/*.map.gz"]}]},"repository":{"type":"git","url":"git+https://github.com/octokit/rest.js.git"},"scripts":{"build":"npm-run-all build:*","build:browser":"npm-run-all build:browser:*","build:browser:development":"webpack --mode development --entry . --output-library=Octokit --output=./dist/octokit-rest.js --profile --json > dist/bundle-stats.json","build:browser:production":"webpack --mode production --entry . --plugin=compression-webpack-plugin --output-library=Octokit --output-path=./dist --output-filename=octokit-rest.min.js --devtool source-map","build:ts":"npm run -s update-endpoints:typescript","coverage":"nyc report --reporter=html && open coverage/index.html","generate-bundle-report":"webpack-bundle-analyzer dist/bundle-stats.json --mode=static --no-open --report dist/bundle-report.html","lint":"prettier --check '{lib,plugins,scripts,test}/**/*.{js,json,ts}' 'docs/*.{js,json}' 'docs/src/**/*' index.js README.md package.json","lint:fix":"prettier --write '{lib,plugins,scripts,test}/**/*.{js,json,ts}' 'docs/*.{js,json}' 'docs/src/**/*' index.js README.md package.json","postvalidate:ts":"tsc --noEmit --target es6 test/typescript-validate.ts","prebuild:browser":"mkdirp dist/","pretest":"npm run -s lint","prevalidate:ts":"npm run -s build:ts","start-fixtures-server":"octokit-fixtures-server","test":"nyc mocha test/mocha-node-setup.js \"test/*/**/*-test.js\"","test:browser":"cypress run --browser chrome","update-endpoints":"npm-run-all update-endpoints:*","update-endpoints:fetch-json":"node scripts/update-endpoints/fetch-json","update-endpoints:typescript":"node scripts/update-endpoints/typescript","validate:ts":"tsc --target es6 --noImplicitAny index.d.ts"},"types":"index.d.ts","version":"16.43.1"};
|
||||
module.exports = {"_args":[["@octokit/rest@16.43.1","C:\\Users\\dha\\Workspace\\dorny\\paths-filter"]],"_from":"@octokit/rest@16.43.1","_id":"@octokit/rest@16.43.1","_inBundle":false,"_integrity":"sha512-gfFKwRT/wFxq5qlNjnW2dh+qh74XgTQ2B179UX5K1HYCluioWj8Ndbgqw2PVqa1NnVJkGHp2ovMpVn/DImlmkw==","_location":"/@octokit/rest","_phantomChildren":{"@types/node":"14.0.5","deprecation":"2.3.1","once":"1.4.0","os-name":"3.1.0"},"_requested":{"type":"version","registry":true,"raw":"@octokit/rest@16.43.1","name":"@octokit/rest","escapedName":"@octokit%2frest","scope":"@octokit","rawSpec":"16.43.1","saveSpec":null,"fetchSpec":"16.43.1"},"_requiredBy":["/@actions/github"],"_resolved":"https://registry.npmjs.org/@octokit/rest/-/rest-16.43.1.tgz","_spec":"16.43.1","_where":"C:\\Users\\dha\\Workspace\\dorny\\paths-filter","author":{"name":"Gregor Martynus","url":"https://github.com/gr2m"},"bugs":{"url":"https://github.com/octokit/rest.js/issues"},"bundlesize":[{"path":"./dist/octokit-rest.min.js.gz","maxSize":"33 kB"}],"contributors":[{"name":"Mike de Boer","email":"info@mikedeboer.nl"},{"name":"Fabian Jakobs","email":"fabian@c9.io"},{"name":"Joe Gallo","email":"joe@brassafrax.com"},{"name":"Gregor Martynus","url":"https://github.com/gr2m"}],"dependencies":{"@octokit/auth-token":"^2.4.0","@octokit/plugin-paginate-rest":"^1.1.1","@octokit/plugin-request-log":"^1.0.0","@octokit/plugin-rest-endpoint-methods":"2.4.0","@octokit/request":"^5.2.0","@octokit/request-error":"^1.0.2","atob-lite":"^2.0.0","before-after-hook":"^2.0.0","btoa-lite":"^1.0.0","deprecation":"^2.0.0","lodash.get":"^4.4.2","lodash.set":"^4.3.2","lodash.uniq":"^4.5.0","octokit-pagination-methods":"^1.1.0","once":"^1.4.0","universal-user-agent":"^4.0.0"},"description":"GitHub REST API client for Node.js","devDependencies":{"@gimenete/type-writer":"^0.1.3","@octokit/auth":"^1.1.1","@octokit/fixtures-server":"^5.0.6","@octokit/graphql":"^4.2.0","@types/node":"^13.1.0","bundlesize":"^0.18.0","chai":"^4.1.2","compression-webpack-plugin":"^3.1.0","cypress":"^3.0.0","glob":"^7.1.2","http-proxy-agent":"^4.0.0","lodash.camelcase":"^4.3.0","lodash.merge":"^4.6.1","lodash.upperfirst":"^4.3.1","lolex":"^5.1.2","mkdirp":"^1.0.0","mocha":"^7.0.1","mustache":"^4.0.0","nock":"^11.3.3","npm-run-all":"^4.1.2","nyc":"^15.0.0","prettier":"^1.14.2","proxy":"^1.0.0","semantic-release":"^17.0.0","sinon":"^8.0.0","sinon-chai":"^3.0.0","sort-keys":"^4.0.0","string-to-arraybuffer":"^1.0.0","string-to-jsdoc-comment":"^1.0.0","typescript":"^3.3.1","webpack":"^4.0.0","webpack-bundle-analyzer":"^3.0.0","webpack-cli":"^3.0.0"},"files":["index.js","index.d.ts","lib","plugins"],"homepage":"https://github.com/octokit/rest.js#readme","keywords":["octokit","github","rest","api-client"],"license":"MIT","name":"@octokit/rest","nyc":{"ignore":["test"]},"publishConfig":{"access":"public"},"release":{"publish":["@semantic-release/npm",{"path":"@semantic-release/github","assets":["dist/*","!dist/*.map.gz"]}]},"repository":{"type":"git","url":"git+https://github.com/octokit/rest.js.git"},"scripts":{"build":"npm-run-all build:*","build:browser":"npm-run-all build:browser:*","build:browser:development":"webpack --mode development --entry . --output-library=Octokit --output=./dist/octokit-rest.js --profile --json > dist/bundle-stats.json","build:browser:production":"webpack --mode production --entry . --plugin=compression-webpack-plugin --output-library=Octokit --output-path=./dist --output-filename=octokit-rest.min.js --devtool source-map","build:ts":"npm run -s update-endpoints:typescript","coverage":"nyc report --reporter=html && open coverage/index.html","generate-bundle-report":"webpack-bundle-analyzer dist/bundle-stats.json --mode=static --no-open --report dist/bundle-report.html","lint":"prettier --check '{lib,plugins,scripts,test}/**/*.{js,json,ts}' 'docs/*.{js,json}' 'docs/src/**/*' index.js README.md package.json","lint:fix":"prettier --write '{lib,plugins,scripts,test}/**/*.{js,json,ts}' 'docs/*.{js,json}' 'docs/src/**/*' index.js README.md package.json","postvalidate:ts":"tsc --noEmit --target es6 test/typescript-validate.ts","prebuild:browser":"mkdirp dist/","pretest":"npm run -s lint","prevalidate:ts":"npm run -s build:ts","start-fixtures-server":"octokit-fixtures-server","test":"nyc mocha test/mocha-node-setup.js \"test/*/**/*-test.js\"","test:browser":"cypress run --browser chrome","update-endpoints":"npm-run-all update-endpoints:*","update-endpoints:fetch-json":"node scripts/update-endpoints/fetch-json","update-endpoints:typescript":"node scripts/update-endpoints/typescript","validate:ts":"tsc --target es6 --noImplicitAny index.d.ts"},"types":"index.d.ts","version":"16.43.1"};
|
||||
|
||||
/***/ }),
|
||||
|
||||
@@ -15612,6 +15661,31 @@ exports.getUserAgent = getUserAgent;
|
||||
//# sourceMappingURL=index.js.map
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 807:
|
||||
/***/ (function(__unusedmodule, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const exec_1 = __webpack_require__(986);
|
||||
// Wraps original exec() function
|
||||
// Returns exit code and whole stdout/stderr
|
||||
async function exec(commandLine, args, options) {
|
||||
options = options || {};
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
options.listeners = {
|
||||
stdout: (data) => (stdout += data.toString()),
|
||||
stderr: (data) => (stderr += data.toString())
|
||||
};
|
||||
const code = await exec_1.exec(commandLine, args, options);
|
||||
return { code, stdout, stderr };
|
||||
}
|
||||
exports.default = exec;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 809:
|
||||
|
||||
21
src/exec.ts
Normal file
21
src/exec.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import {exec as execImpl, ExecOptions} from '@actions/exec'
|
||||
|
||||
// Wraps original exec() function
|
||||
// Returns exit code and whole stdout/stderr
|
||||
export default async function exec(commandLine: string, args?: string[], options?: ExecOptions): Promise<ExecResult> {
|
||||
options = options || {}
|
||||
let stdout = ''
|
||||
let stderr = ''
|
||||
options.listeners = {
|
||||
stdout: (data: Buffer) => (stdout += data.toString()),
|
||||
stderr: (data: Buffer) => (stderr += data.toString())
|
||||
}
|
||||
const code = await execImpl(commandLine, args, options)
|
||||
return {code, stdout, stderr}
|
||||
}
|
||||
|
||||
export interface ExecResult {
|
||||
code: number
|
||||
stdout: string
|
||||
stderr: string
|
||||
}
|
||||
124
src/git.ts
124
src/git.ts
@@ -1,25 +1,14 @@
|
||||
import {exec} from '@actions/exec'
|
||||
import exec from './exec'
|
||||
import * as core from '@actions/core'
|
||||
import {File, ChangeStatus} from './file'
|
||||
|
||||
export const NULL_SHA = '0000000000000000000000000000000000000000'
|
||||
|
||||
export async function getChangesAgainstSha(sha: string): Promise<File[]> {
|
||||
// Fetch single commit
|
||||
core.startGroup(`Fetching ${sha} from origin`)
|
||||
await exec('git', ['fetch', '--depth=1', '--no-tags', 'origin', sha])
|
||||
core.endGroup()
|
||||
|
||||
// Get differences between sha and HEAD
|
||||
core.startGroup(`Change detection ${sha}..HEAD`)
|
||||
export async function getChangesInLastCommit(): Promise<File[]> {
|
||||
core.startGroup(`Change detection in last commit`)
|
||||
let output = ''
|
||||
try {
|
||||
// Two dots '..' change detection - directly compares two versions
|
||||
await exec('git', ['diff', '--no-renames', '--name-status', '-z', `${sha}..HEAD`], {
|
||||
listeners: {
|
||||
stdout: (data: Buffer) => (output += data.toString())
|
||||
}
|
||||
})
|
||||
output = (await exec('git', ['log', '--format=', '--no-renames', '--name-status', '-z', '-n', '1'])).stdout
|
||||
} finally {
|
||||
fixStdOutNullTermination()
|
||||
core.endGroup()
|
||||
@@ -28,25 +17,54 @@ export async function getChangesAgainstSha(sha: string): Promise<File[]> {
|
||||
return parseGitDiffOutput(output)
|
||||
}
|
||||
|
||||
export async function getChangesSinceRef(ref: string, initialFetchDepth: number): Promise<File[]> {
|
||||
// Fetch and add base branch
|
||||
core.startGroup(`Fetching ${ref} from origin until merge-base is found`)
|
||||
await exec('git', ['fetch', `--depth=${initialFetchDepth}`, '--no-tags', 'origin', `${ref}:${ref}`])
|
||||
export async function getChanges(ref: string): Promise<File[]> {
|
||||
if (!(await hasCommit(ref))) {
|
||||
// Fetch single commit
|
||||
core.startGroup(`Fetching ${ref} from origin`)
|
||||
await exec('git', ['fetch', '--depth=1', '--no-tags', 'origin', ref])
|
||||
core.endGroup()
|
||||
}
|
||||
|
||||
// Get differences between ref and HEAD
|
||||
core.startGroup(`Change detection ${ref}..HEAD`)
|
||||
let output = ''
|
||||
try {
|
||||
// Two dots '..' change detection - directly compares two versions
|
||||
output = (await exec('git', ['diff', '--no-renames', '--name-status', '-z', `${ref}..HEAD`])).stdout
|
||||
} finally {
|
||||
fixStdOutNullTermination()
|
||||
core.endGroup()
|
||||
}
|
||||
|
||||
return parseGitDiffOutput(output)
|
||||
}
|
||||
|
||||
export async function getChangesSinceMergeBase(ref: string, initialFetchDepth: number): Promise<File[]> {
|
||||
if (!(await hasCommit(ref))) {
|
||||
// Fetch and add base branch
|
||||
core.startGroup(`Fetching ${ref}`)
|
||||
try {
|
||||
await exec('git', ['fetch', `--depth=${initialFetchDepth}`, '--no-tags', 'origin', `${ref}:${ref}`])
|
||||
} finally {
|
||||
core.endGroup()
|
||||
}
|
||||
}
|
||||
|
||||
async function hasMergeBase(): Promise<boolean> {
|
||||
return (await exec('git', ['merge-base', ref, 'HEAD'], {ignoreReturnCode: true})) === 0
|
||||
return (await exec('git', ['merge-base', ref, 'HEAD'], {ignoreReturnCode: true})).code === 0
|
||||
}
|
||||
|
||||
async function countCommits(): Promise<number> {
|
||||
return (await getNumberOfCommits('HEAD')) + (await getNumberOfCommits(ref))
|
||||
}
|
||||
|
||||
core.startGroup(`Searching for merge-base with ${ref}`)
|
||||
// Fetch more commits until merge-base is found
|
||||
if (!(await hasMergeBase())) {
|
||||
let deepen = initialFetchDepth
|
||||
let lastCommitsCount = await countCommits()
|
||||
do {
|
||||
await exec('git', ['fetch', `--deepen=${deepen}`, '--no-tags', '--no-auto-gc', '-q'])
|
||||
await exec('git', ['fetch', `--deepen=${deepen}`, '--no-tags'])
|
||||
const count = await countCommits()
|
||||
if (count <= lastCommitsCount) {
|
||||
core.info('No merge base found - all files will be listed as added')
|
||||
@@ -64,11 +82,7 @@ export async function getChangesSinceRef(ref: string, initialFetchDepth: number)
|
||||
let output = ''
|
||||
try {
|
||||
// Three dots '...' change detection - finds merge-base and compares against it
|
||||
await exec('git', ['diff', '--no-renames', '--name-status', '-z', `${ref}...HEAD`], {
|
||||
listeners: {
|
||||
stdout: (data: Buffer) => (output += data.toString())
|
||||
}
|
||||
})
|
||||
output = (await exec('git', ['diff', '--no-renames', '--name-status', '-z', `${ref}...HEAD`])).stdout
|
||||
} finally {
|
||||
fixStdOutNullTermination()
|
||||
core.endGroup()
|
||||
@@ -93,11 +107,7 @@ export async function listAllFilesAsAdded(): Promise<File[]> {
|
||||
core.startGroup('Listing all files tracked by git')
|
||||
let output = ''
|
||||
try {
|
||||
await exec('git', ['ls-files', '-z'], {
|
||||
listeners: {
|
||||
stdout: (data: Buffer) => (output += data.toString())
|
||||
}
|
||||
})
|
||||
output = (await exec('git', ['ls-files', '-z'])).stdout
|
||||
} finally {
|
||||
fixStdOutNullTermination()
|
||||
core.endGroup()
|
||||
@@ -112,34 +122,52 @@ export async function listAllFilesAsAdded(): Promise<File[]> {
|
||||
}))
|
||||
}
|
||||
|
||||
export function isTagRef(ref: string): boolean {
|
||||
return ref.startsWith('refs/tags/')
|
||||
export async function getCurrentRef(): Promise<string> {
|
||||
core.startGroup(`Determining current ref`)
|
||||
try {
|
||||
const branch = (await exec('git', ['branch', '--show-current'])).stdout.trim()
|
||||
if (branch) {
|
||||
return branch
|
||||
}
|
||||
|
||||
const describe = await exec('git', ['describe', '--tags', '--exact-match'], {ignoreReturnCode: true})
|
||||
if (describe.code === 0) {
|
||||
return describe.stdout.trim()
|
||||
}
|
||||
|
||||
return (await exec('git', ['rev-parse', 'HEAD'])).stdout.trim()
|
||||
} finally {
|
||||
core.endGroup()
|
||||
}
|
||||
}
|
||||
|
||||
export function trimRefs(ref: string): string {
|
||||
return trimStart(ref, 'refs/')
|
||||
export function getShortName(ref: string): string {
|
||||
if (!ref) return ''
|
||||
|
||||
const heads = 'refs/heads/'
|
||||
const tags = 'refs/tags/'
|
||||
|
||||
if (ref.startsWith(heads)) return ref.slice(heads.length)
|
||||
if (ref.startsWith(tags)) return ref.slice(tags.length)
|
||||
|
||||
return ref
|
||||
}
|
||||
|
||||
export function trimRefsHeads(ref: string): string {
|
||||
const trimRef = trimStart(ref, 'refs/')
|
||||
return trimStart(trimRef, 'heads/')
|
||||
async function hasCommit(ref: string): Promise<boolean> {
|
||||
core.startGroup(`Checking if commit for ${ref} is locally available`)
|
||||
try {
|
||||
return (await exec('git', ['cat-file', '-e', `${ref}^{commit}`], {ignoreReturnCode: true})).code === 0
|
||||
} finally {
|
||||
core.endGroup()
|
||||
}
|
||||
}
|
||||
|
||||
async function getNumberOfCommits(ref: string): Promise<number> {
|
||||
let output = ''
|
||||
await exec('git', ['rev-list', `--count`, ref], {
|
||||
listeners: {
|
||||
stdout: (data: Buffer) => (output += data.toString())
|
||||
}
|
||||
})
|
||||
const output = (await exec('git', ['rev-list', `--count`, ref])).stdout
|
||||
const count = parseInt(output)
|
||||
return isNaN(count) ? 0 : count
|
||||
}
|
||||
|
||||
function trimStart(ref: string, start: string): string {
|
||||
return ref.startsWith(start) ? ref.substr(start.length) : ref
|
||||
}
|
||||
|
||||
function fixStdOutNullTermination(): void {
|
||||
// Previous command uses NULL as delimiters and output is printed to stdout.
|
||||
// We have to make sure next thing written to stdout will start on new line.
|
||||
|
||||
49
src/main.ts
49
src/main.ts
@@ -57,9 +57,11 @@ function getConfigFileContent(configPath: string): string {
|
||||
async function getChangedFiles(token: string, base: string, initialFetchDepth: number): Promise<File[]> {
|
||||
if (github.context.eventName === 'pull_request' || github.context.eventName === 'pull_request_target') {
|
||||
const pr = github.context.payload.pull_request as Webhooks.WebhookPayloadPullRequestPullRequest
|
||||
return token
|
||||
? await getChangedFilesFromApi(token, pr)
|
||||
: await git.getChangesSinceRef(pr.base.ref, initialFetchDepth)
|
||||
if (token) {
|
||||
return await getChangedFilesFromApi(token, pr)
|
||||
}
|
||||
core.info('Github token is not available - changes will be detected from PRs merge commit')
|
||||
return await git.getChangesInLastCommit()
|
||||
} else if (github.context.eventName === 'push') {
|
||||
return getChangedFilesFromPush(base, initialFetchDepth)
|
||||
} else {
|
||||
@@ -69,30 +71,47 @@ async function getChangedFiles(token: string, base: string, initialFetchDepth: n
|
||||
|
||||
async function getChangedFilesFromPush(base: string, initialFetchDepth: number): Promise<File[]> {
|
||||
const push = github.context.payload as Webhooks.WebhookPayloadPush
|
||||
const defaultRef = push.repository?.default_branch
|
||||
|
||||
// No change detection for pushed tags
|
||||
if (git.isTagRef(push.ref)) {
|
||||
core.info('Workflow is triggered by pushing of tag - all files will be listed as added')
|
||||
return await git.listAllFilesAsAdded()
|
||||
const pushRef =
|
||||
git.getShortName(push.ref) ||
|
||||
(core.warning(`'ref' field is missing in PUSH event payload - using current branch, tag or commit SHA`),
|
||||
await git.getCurrentRef())
|
||||
|
||||
const baseRef = git.getShortName(base) || defaultRef
|
||||
if (!baseRef) {
|
||||
throw new Error(
|
||||
"This action requires 'base' input to be configured or 'repository.default_branch' to be set in the event payload"
|
||||
)
|
||||
}
|
||||
|
||||
const baseRef = git.trimRefsHeads(base || push.repository.default_branch)
|
||||
const pushRef = git.trimRefsHeads(push.ref)
|
||||
|
||||
// If base references same branch it was pushed to, we will do comparison against the previously pushed commit.
|
||||
// If base references same branch it was pushed to,
|
||||
// we will do comparison against the previously pushed commit
|
||||
if (baseRef === pushRef) {
|
||||
if (!push.before) {
|
||||
core.warning(`'before' field is missing in PUSH event payload - changes will be detected from last commit`)
|
||||
return await git.getChangesInLastCommit()
|
||||
}
|
||||
|
||||
// If there is no previously pushed commit,
|
||||
// we will do comparison against the default branch or return all as added
|
||||
if (push.before === git.NULL_SHA) {
|
||||
core.info('First push of a branch detected - all files will be listed as added')
|
||||
return await git.listAllFilesAsAdded()
|
||||
if (defaultRef && baseRef !== defaultRef) {
|
||||
core.info(`First push of a branch detected - changes will be detected against the default branch ${defaultRef}`)
|
||||
return await git.getChangesSinceMergeBase(defaultRef, initialFetchDepth)
|
||||
} else {
|
||||
core.info('Initial push detected - all files will be listed as added')
|
||||
return await git.listAllFilesAsAdded()
|
||||
}
|
||||
}
|
||||
|
||||
core.info(`Changes will be detected against the last previously pushed commit on same branch (${pushRef})`)
|
||||
return await git.getChangesAgainstSha(push.before)
|
||||
return await git.getChanges(push.before)
|
||||
}
|
||||
|
||||
// Changes introduced by current branch against the base branch
|
||||
core.info(`Changes will be detected against the branch ${baseRef}`)
|
||||
return await git.getChangesSinceRef(baseRef, initialFetchDepth)
|
||||
return await git.getChangesSinceMergeBase(baseRef, initialFetchDepth)
|
||||
}
|
||||
|
||||
// Uses github REST api to get list of files changed in PR
|
||||
|
||||
Reference in New Issue
Block a user