diff --git a/README.ja.md b/README.ja.md
index 9a4d3e6d..4c0532bb 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -10,9 +10,12 @@
GitHub Actions 用のヘルパー
+## Table of Contents
+
-**Table of Contents**
+
+Details
- [使用方法](#%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95)
- [Logger](#logger)
@@ -23,6 +26,7 @@ GitHub Actions 用のヘルパー
- [ContextHelper](#contexthelper)
- [Author](#author)
+
## 使用方法
diff --git a/README.md b/README.md
index 6cbca706..ac6d9182 100644
--- a/README.md
+++ b/README.md
@@ -10,9 +10,12 @@
Helper for GitHub Actions.
+## Table of Contents
+
-**Table of Contents**
+
+Details
- [Usage](#usage)
- [Logger](#logger)
@@ -23,6 +26,7 @@ Helper for GitHub Actions.
- [ContextHelper](#contexthelper)
- [Author](#author)
+
## Usage
diff --git a/__tests__/git-helper.test.ts b/__tests__/git-helper.test.ts
index 6c231812..eb282bea 100644
--- a/__tests__/git-helper.test.ts
+++ b/__tests__/git-helper.test.ts
@@ -70,13 +70,13 @@ describe('GitHelper', () => {
]);
});
- it('should run git checkout', async() => {
+ it('should run git clone PR', async() => {
setExists(false);
const mockExec = spyOnExec();
- expect(await helper.clone(workDir, context({
+ await helper.clone(workDir, context({
ref: 'refs/pull/123/merge',
- })));
+ }));
execCalledWith(mockExec, [
'git clone \'--depth=3\' \'https://octocat:token1@github.com/hello/world.git\' \'.\' > /dev/null 2>&1 || :',
@@ -85,48 +85,64 @@ describe('GitHelper', () => {
]);
});
- it('should throw error', async() => {
+ it('should run checkout', async() => {
setExists(false);
+ const mockExec = spyOnExec();
+
+ await helper.clone(workDir, context({
+ ref: 'refs/tags/v1.2.3',
+ sha: '1234567890',
+ }));
- await expect(helper.clone(workDir, context({
- ref: '',
- sha: '',
- }))).rejects.toThrow('Invalid context.');
+ execCalledWith(mockExec, [
+ 'git init \'.\'',
+ 'git remote add origin \'https://octocat:token1@github.com/hello/world.git\' > /dev/null 2>&1 || :',
+ 'git fetch --no-tags origin \'refs/tags/v1.2.3:refs/tags/v1.2.3\' || :',
+ 'git checkout -qf 1234567890',
+ ]);
});
});
describe('checkout', () => {
- it('should run checkout 1', async() => {
+ it('should run checkout branch', async() => {
const mockExec = spyOnExec();
await helper.checkout(workDir, context());
execCalledWith(mockExec, [
- 'git clone \'--depth=3\' \'https://octocat:token1@github.com/hello/world.git\' \'.\' > /dev/null 2>&1',
- 'git fetch \'https://octocat:token1@github.com/hello/world.git\' refs/heads/test-ref > /dev/null 2>&1',
+ 'rm -rdf \'.work\'',
+ 'git init \'.\'',
+ 'git remote add origin \'https://octocat:token1@github.com/hello/world.git\' > /dev/null 2>&1 || :',
+ 'git fetch --no-tags origin \'refs/heads/test-ref:refs/remotes/origin/test-ref\' || :',
'git checkout -qf test-sha',
]);
});
- it('should run checkout 2', async() => {
+ it('should run checkout merge ref', async() => {
const mockExec = spyOnExec();
- await helper.checkout(workDir, context({sha: ''}));
+ await helper.checkout(workDir, context({ref: 'refs/pull/123/merge'}));
execCalledWith(mockExec, [
- 'git clone \'https://octocat:token1@github.com/hello/world.git\' \'.\' > /dev/null 2>&1',
- 'git checkout -qf test-ref',
+ 'rm -rdf \'.work\'',
+ 'git init \'.\'',
+ 'git remote add origin \'https://octocat:token1@github.com/hello/world.git\' > /dev/null 2>&1 || :',
+ 'git fetch --no-tags origin \'refs/pull/123/merge:refs/pull/123/merge\' || :',
+ 'git checkout -qf test-sha',
]);
});
- it('should run checkout 3', async() => {
+ it('should run checkout tag', async() => {
const mockExec = spyOnExec();
- await helper.checkout(workDir, context({sha: '', ref: 'refs/tags/test-tag'}));
+ await helper.checkout(workDir, context({ref: 'refs/tags/v1.2.3'}));
execCalledWith(mockExec, [
- 'git clone \'https://octocat:token1@github.com/hello/world.git\' \'.\' > /dev/null 2>&1',
- 'git checkout -qf refs/tags/test-tag',
+ 'rm -rdf \'.work\'',
+ 'git init \'.\'',
+ 'git remote add origin \'https://octocat:token1@github.com/hello/world.git\' > /dev/null 2>&1 || :',
+ 'git fetch --no-tags origin \'refs/tags/v1.2.3:refs/tags/v1.2.3\' || :',
+ 'git checkout -qf test-sha',
]);
});
});
@@ -451,6 +467,24 @@ describe('GitHelper', () => {
'git show \'--stat-count=10\' HEAD',
]);
});
+
+ it('should run git commit with options', async() => {
+ setChildProcessParams({stdout: 'M file1\n\nM file2\n'});
+ const mockExec = spyOnExec();
+
+ expect(await helper.commit(workDir, 'test', {
+ count: 20,
+ allowEmpty: true,
+ args: ['--dry-run'],
+ })).toBeTruthy();
+
+ execCalledWith(mockExec, [
+ 'git add --all',
+ 'git status --short -uno',
+ 'git commit --allow-empty --dry-run -qm test',
+ 'git show \'--stat-count=20\' HEAD',
+ ]);
+ });
});
describe('fetchTags', () => {
diff --git a/__tests__/utils.test.ts b/__tests__/utils.test.ts
index 59c033a1..82798ae5 100644
--- a/__tests__/utils.test.ts
+++ b/__tests__/utils.test.ts
@@ -6,6 +6,7 @@ import { Utils } from '../src';
const {getWorkspace, getActor, escapeRegExp, getRegExp, getPrefixRegExp, getSuffixRegExp, useNpm, versionCompare, getOctokit} = Utils;
const {isSemanticVersioningTagName, isPrRef, getPrMergeRef, getBoolValue, replaceAll, getPrHeadRef, arrayChunk, sleep} = Utils;
const {getBranch, getRefForUpdate, uniqueArray, getBuildInfo, split, getArrayInput, generateNewPatchVersion, getPrBranch} = Utils;
+const {isBranch, isTagRef, normalizeRef, trimRef, getTag, getRefspec} = Utils;
jest.useFakeTimers();
@@ -482,3 +483,63 @@ describe('getOctokit', () => {
expect(() => getOctokit()).toThrow();
});
});
+
+describe('isBranch', () => {
+ it('should return true', () => {
+ expect(isBranch('refs/heads/master')).toBe(true);
+ expect(isBranch('heads/master')).toBe(true);
+ });
+
+ it('should return false', () => {
+ expect(isBranch('test')).toBe(false);
+ expect(isBranch('heads')).toBe(false);
+ });
+});
+
+describe('isTagRef', () => {
+ it('should return true', () => {
+ expect(isTagRef('refs/tags/v1.2.3')).toBe(true);
+ });
+
+ it('should return false', () => {
+ expect(isTagRef('refs/heads/master')).toBe(false);
+ expect(isTagRef('heads/master')).toBe(false);
+ });
+});
+
+describe('normalizeRef', () => {
+ it('should normalize ref', () => {
+ expect(normalizeRef('master')).toBe('refs/heads/master');
+ expect(normalizeRef('refs/heads/master')).toBe('refs/heads/master');
+ expect(normalizeRef('refs/tags/v1.2.3')).toBe('refs/tags/v1.2.3');
+ expect(normalizeRef('refs/pull/123/merge')).toBe('refs/pull/123/merge');
+ });
+});
+
+describe('trimRef', () => {
+ it('should trim ref', () => {
+ expect(trimRef('master')).toBe('master');
+ expect(trimRef('refs/heads/master')).toBe('master');
+ expect(trimRef('refs/tags/v1.2.3')).toBe('v1.2.3');
+ expect(trimRef('refs/pull/123/merge')).toBe('123/merge');
+ });
+});
+
+describe('getTag', () => {
+ it('should get tag', () => {
+ expect(getTag('master')).toBe('');
+ expect(getTag('heads/master')).toBe('');
+ expect(getTag('refs/heads/master')).toBe('');
+ expect(getTag('refs/tags/v1.2.3')).toBe('v1.2.3');
+ expect(getTag('refs/pull/123/merge')).toBe('');
+ });
+});
+
+describe('getRefspec', () => {
+ it('should get refspec', () => {
+ expect(getRefspec('master')).toBe('refs/heads/master:refs/remotes/origin/master');
+ expect(getRefspec('refs/heads/master', 'test')).toBe('refs/heads/master:refs/remotes/test/master');
+ expect(getRefspec('refs/tags/v1.2.3')).toBe('refs/tags/v1.2.3:refs/tags/v1.2.3');
+ expect(getRefspec('refs/pull/123/merge')).toBe('refs/pull/123/merge:refs/pull/123/merge');
+ });
+});
diff --git a/jest.config.js b/jest.config.js
index 0098a6ea..92a6974a 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -1,9 +1,9 @@
module.exports = {
clearMocks: true,
- moduleFileExtensions: [ 'js', 'ts' ],
- setupFiles: [ '/jest.setup.ts' ],
+ moduleFileExtensions: ['js', 'ts'],
+ setupFiles: ['/jest.setup.ts'],
testEnvironment: 'node',
- testMatch: [ '**/*.test.ts' ],
+ testMatch: ['**/*.test.ts'],
testRunner: 'jest-circus/runner',
transform: {
'^.+\\.ts$': 'ts-jest',
diff --git a/package.json b/package.json
index c79dc88e..2c1fc3ba 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@technote-space/github-action-helper",
- "version": "0.8.4",
+ "version": "0.8.5",
"description": "Helper to filter GitHub Action.",
"author": {
"name": "Technote",
@@ -32,7 +32,7 @@
"sprintf-js": "^1.1.2"
},
"devDependencies": {
- "@technote-space/github-action-test-helper": "^0.1.5",
+ "@technote-space/github-action-test-helper": "^0.2.0",
"@types/jest": "^25.1.1",
"@types/node": "^13.7.0",
"@typescript-eslint/eslint-plugin": "^2.19.0",
diff --git a/src/git-helper.ts b/src/git-helper.ts
index 67bf5165..df029f38 100644
--- a/src/git-helper.ts
+++ b/src/git-helper.ts
@@ -1,7 +1,7 @@
import fs from 'fs';
import { Context } from '@actions/github/lib/context';
import { Command, Logger } from './index';
-import { getBranch, isBranch, isPrRef, isCloned, split, generateNewPatchVersion, arrayChunk, versionCompare, getAccessToken } from './utils';
+import { getBranch, isBranch, isPrRef, getRefspec, isCloned, split, generateNewPatchVersion, arrayChunk, versionCompare, getAccessToken } from './utils';
import { getGitUrlWithToken } from './context-helper';
type CommandType = string | {
@@ -82,17 +82,34 @@ export default class GitHelper {
}
};
+ /**
+ * @param {string} workDir work dir
+ * @return {Promise} void
+ */
+ private initialize = async(workDir: string): Promise => {
+ if (fs.existsSync(workDir)) {
+ await this.runCommand(workDir, {command: 'rm', args: ['-rdf', workDir]});
+ }
+ fs.mkdirSync(workDir, {recursive: true});
+ await this.runCommand(workDir, {command: 'git init', args: ['.']});
+ };
+
+ /**
+ * @param {Context} context context
+ * @return {string} origin
+ */
+ private getOrigin = (context: Context): string => this.origin ?? getGitUrlWithToken(context, this.token);
+
/**
* @param {string} workDir work dir
* @param {Context} context context
* @return {Promise} void
*/
public addOrigin = async(workDir: string, context: Context): Promise => {
- const url = this.getOrigin(context);
await this.initialize(workDir);
await this.runCommand(workDir, {
command: 'git remote add',
- args: ['origin', url],
+ args: ['origin', getGitUrlWithToken(context, this.token)],
quiet: this.isQuiet(),
altCommand: 'git remote add origin',
suppressError: true,
@@ -115,12 +132,6 @@ export default class GitHelper {
*/
private isQuiet = (): boolean => !this.origin || this.quietIfNotOrigin;
- /**
- * @param {Context} context context
- * @return {string} origin
- */
- private getOrigin = (context: Context): string => this.origin ?? getGitUrlWithToken(context, this.token);
-
/**
* @param {string} workDir work dir
* @return {Promise} branch name
@@ -199,64 +210,6 @@ export default class GitHelper {
}
};
- /**
- * @param {string} workDir work dir
- * @param {Context} context context
- * @return {Promise} void
- */
- public checkout = async(workDir: string, context: Context): Promise => {
- const url = this.getOrigin(context);
- if (this.cloneDepth && context.sha) {
- await this.runCommand(workDir, [
- {
- command: 'git clone',
- args: [this.cloneDepth, url, '.'],
- quiet: this.isQuiet(),
- altCommand: 'git clone',
- },
- {
- command: 'git fetch',
- args: [url, context.ref],
- quiet: this.isQuiet(),
- altCommand: `git fetch origin ${context.ref}`,
- },
- {
- command: 'git checkout',
- args: ['-qf', context.sha],
- },
- ]);
- } else {
- const checkout = context.sha || getBranch(context) || context.ref;
- if (!checkout) {
- throw new Error('Invalid context.');
- }
- await this.runCommand(workDir, [
- {
- command: 'git clone',
- args: [url, '.'],
- quiet: this.isQuiet(),
- altCommand: 'git clone',
- },
- {
- command: 'git checkout',
- args: ['-qf', checkout],
- },
- ]);
- }
- };
-
- /**
- * @param {string} workDir work dir
- * @return {Promise} void
- */
- private initialize = async(workDir: string): Promise => {
- if (fs.existsSync(workDir)) {
- await this.runCommand(workDir, {command: 'rm', args: ['-rdf', workDir]});
- }
- fs.mkdirSync(workDir, {recursive: true});
- await this.runCommand(workDir, {command: 'git init', args: ['.']});
- };
-
/**
* @param {string} workDir work dir
* @param {string} branch branch
@@ -288,6 +241,22 @@ export default class GitHelper {
});
};
+ /**
+ * @param {string} workDir work dir
+ * @param {Context} context context
+ * @return {Promise} void
+ */
+ public checkout = async(workDir: string, context: Context): Promise => {
+ await this.fetchOrigin(workDir, context, ['--no-tags'], [getRefspec(context)]);
+ await this.runCommand(workDir, [
+ {
+ command: 'git checkout',
+ args: ['-qf', context.sha],
+ stderrToStdout: true,
+ },
+ ]);
+ };
+
/**
* @param {string} workDir work dir
* @param {string} branch branch
@@ -387,8 +356,9 @@ export default class GitHelper {
/**
* @param {string} workDir work dir
* @param {string} message message
+ * @param {object} options options
*/
- public commit = async(workDir: string, message: string): Promise => {
+ public commit = async(workDir: string, message: string, options?: { count?: number; allowEmpty?: boolean; args?: Array }): Promise => {
await this.runCommand(workDir, {command: 'git add', args: ['--all']});
if (!await this.checkDiff(workDir)) {
@@ -396,20 +366,24 @@ export default class GitHelper {
return false;
}
- await this.makeCommit(workDir, message);
+ await this.makeCommit(workDir, message, options);
return true;
};
/**
* @param {string} workDir work dir
* @param {string} message message
- * @param {number} count stat count
+ * @param {object} options options
*/
- public makeCommit = async(workDir: string, message: string, count = 10): Promise => { // eslint-disable-line no-magic-numbers
+ public makeCommit = async(workDir: string, message: string, options?: { count?: number; allowEmpty?: boolean; args?: Array }): Promise => {
+ const count = options?.count ?? 10; // eslint-disable-line no-magic-numbers
+ const allowEmpty = options?.allowEmpty ?? false;
+ const args = options?.args ?? [];
+
await this.runCommand(workDir, [
{
command: 'git commit',
- args: ['-qm', message],
+ args: [allowEmpty ? '--allow-empty' : '', ...args, '-qm', message],
},
{
command: 'git show',
diff --git a/src/utils.ts b/src/utils.ts
index c1728427..d8d1e132 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -32,7 +32,9 @@ export const isCloned = (workDir: string): boolean => fs.existsSync(path.resolve
export const isSemanticVersioningTagName = (tagName: string): boolean => /^v?\d+(\.\d+)*$/i.test(tagName);
-export const isBranch = (ref: string | Context): boolean => /^(refs\/)?heads/.test(getRef(ref));
+export const isBranch = (ref: string | Context): boolean => /^(refs\/)?heads\//.test(getRef(ref));
+
+export const isTagRef = (ref: string | Context): boolean => /^refs\/?tags\//.test(getRef(ref));
export const isRemoteBranch = (ref: string | Context): boolean => /^(refs\/)?remotes\/origin\//.test(getRef(ref));
@@ -56,6 +58,16 @@ export const getBranch = (ref: string | Context, defaultIsEmpty = true): string
export const getPrBranch = (context: Context): string => context.payload.pull_request?.head.ref ?? '';
+export const normalizeRef = (ref: string | Context): string => /^refs\//.test(getRef(ref)) ? getRef(ref) : `refs/heads/${getRef(ref)}`;
+
+export const trimRef = (ref: string | Context): string => getRef(ref).replace(/^refs\/(heads|tags|pull)\//, '');
+
+export const getTag = (ref: string | Context): string => isTagRef(ref) ? trimRef(ref) : '';
+
+const saveTarget = (ref: string | Context, origin: string): string => isTagRef(ref) ? 'tags' : isPrRef(ref) ? 'pull' : `remotes/${origin}`;
+
+export const getRefspec = (ref: string | Context, origin = 'origin'): string => `${normalizeRef(ref)}:refs/${saveTarget(ref, origin)}/${trimRef(ref)}`;
+
export const getAccessToken = (required: boolean): string => getInput('GITHUB_TOKEN', {required});
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
diff --git a/yarn.lock b/yarn.lock
index 247d5c29..464d112c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -450,10 +450,10 @@
dependencies:
type-detect "4.0.8"
-"@technote-space/github-action-test-helper@^0.1.5":
- version "0.1.5"
- resolved "https://registry.yarnpkg.com/@technote-space/github-action-test-helper/-/github-action-test-helper-0.1.5.tgz#2847edf76c0e905f0c3a757eb27e89199442408c"
- integrity sha512-v0twcYEBGyeRZRsBrh54chvsiqduTl1NBQMweXmPbNRPJRChCEYUinIPSSYRN4BGsCk2naCeudx5Zjfxv4obew==
+"@technote-space/github-action-test-helper@^0.2.0":
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/@technote-space/github-action-test-helper/-/github-action-test-helper-0.2.0.tgz#6a5dc8599fac75c19c3b7562a278427d44b17d7f"
+ integrity sha512-xxQEh4MiJJg/x4nSknEJVoLPYx2tdkCGaAGrhdg/SmHfWgipDdcP+HxC8FQLRzJHJdDfTOS7YV/+hRaDkStOTw==
dependencies:
"@actions/github" "^2.1.0"
js-yaml "^3.13.1"
@@ -1284,9 +1284,9 @@ escape-string-regexp@^1.0.5:
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
escodegen@^1.11.1:
- version "1.13.0"
- resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.13.0.tgz#c7adf9bd3f3cc675bb752f202f79a720189cab29"
- integrity sha512-eYk2dCkxR07DsHA/X2hRBj0CFAZeri/LyDMc0C8JT1Hqi6JnVpMhJ7XFITbb0+yZS3lVkaPL2oCkZ3AVmeVbMw==
+ version "1.14.1"
+ resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.1.tgz#ba01d0c8278b5e95a9a45350142026659027a457"
+ integrity sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==
dependencies:
esprima "^4.0.1"
estraverse "^4.2.0"
@@ -3297,9 +3297,9 @@ resolve@1.1.7:
integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
resolve@1.x, resolve@^1.3.2:
- version "1.15.0"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.0.tgz#1b7ca96073ebb52e741ffd799f6b39ea462c67f5"
- integrity sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw==
+ version "1.15.1"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8"
+ integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==
dependencies:
path-parse "^1.0.6"