Compare commits

..

6 Commits

Author SHA1 Message Date
43c99f2ebc . 2019-12-10 21:09:20 -05:00
4a3a4ebf11 . 2019-12-10 21:04:38 -05:00
a5ba5cb63a . 2019-12-10 21:03:55 -05:00
31b1047b1f . 2019-12-10 21:01:08 -05:00
89cbb18acd . 2019-12-10 18:38:53 -05:00
1e6a918852 fallback to REST API to download repo 2019-12-10 17:44:45 -05:00
12 changed files with 146 additions and 291 deletions

View File

@ -1,39 +1,25 @@
name: Build and Test name: Build and Test
on: on: push
pull_request:
push:
branches:
- master
- releases/*
jobs: jobs:
build: test-archive:
runs-on: ubuntu-latest runs-on: windows-latest
steps:
- uses: actions/checkout@v1 # todo: switch to v2
- run: npm ci
- run: npm run build
- run: npm run format-check
- run: npm run lint
- run: npm run pack
- run: npm run gendocs
- run: npm test
- name: Verify no unstaged changes
run: __test__/verify-no-unstaged-changes.sh
test:
strategy:
matrix:
runs-on: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.runs-on }}
steps: steps:
# Clone this repo # Clone this repo
- name: Checkout - name: Checkout
uses: actions/checkout@v2-beta shell: bash
run: |
curl --location --user token:${{ github.token }} --output checkout.tar.gz https://api.github.com/repos/actions/checkout/tarball/${{ github.sha }}
tar -xzf checkout.tar.gz
mv */* ./
# Basic checkout # Basic checkout
- shell: cmd
run: |
echo echo hello > git.cmd
echo ::add-path::%CD%
- name: Basic checkout - name: Basic checkout
uses: ./ uses: ./
with: with:
@ -41,62 +27,5 @@ jobs:
path: basic path: basic
- name: Verify basic - name: Verify basic
shell: bash shell: bash
run: __test__/verify-basic.sh run: __test__/verify-basic.sh container
# Clean
- name: Modify work tree
shell: bash
run: __test__/modify-work-tree.sh
- name: Clean checkout
uses: ./
with:
ref: test-data/v2/basic
path: basic
- name: Verify clean
shell: bash
run: __test__/verify-clean.sh
# Side by side
- name: Side by side checkout 1
uses: ./
with:
ref: test-data/v2/side-by-side-1
path: side-by-side-1
- name: Side by side checkout 2
uses: ./
with:
ref: test-data/v2/side-by-side-2
path: side-by-side-2
- name: Verify side by side
shell: bash
run: __test__/verify-side-by-side.sh
# LFS
- name: LFS checkout
uses: ./
with:
repository: actions/checkout # hardcoded, otherwise doesn't work from a fork
ref: test-data/v2/lfs
path: lfs
lfs: true
- name: Verify LFS
shell: bash
run: __test__/verify-lfs.sh
test-job-container:
runs-on: ubuntu-latest
container: alpine:latest
steps:
# Clone this repo
# todo: after v2-beta contains the latest changes, switch this to "uses: actions/checkout@v2-beta"
- name: Checkout
uses: actions/checkout@a572f640b07e96fc5837b3adfa0e5a2ddd8dae21
# Basic checkout
- name: Basic checkout
uses: ./
with:
ref: test-data/v2/basic
path: basic
- name: Verify basic
run: __test__/verify-basic.sh --archive

View File

@ -13,20 +13,18 @@ Refer [here](https://help.github.com/en/articles/events-that-trigger-workflows)
# What's new # What's new
- Improved fetch performance - Improved fetch performance
- The default behavior now fetches only the commit being checked-out - The default behavior now fetches only the SHA being checked-out
- Script authenticated git commands - Script authenticated git commands
- Persists the input `token` in the local git config - Persists `with.token` in the local git config
- Enables your scripts to run authenticated git commands - Enables your scripts to run authenticated git commands
- Post-job cleanup removes the token - Post-job cleanup removes the token
- Opt out by setting the input `persist-credentials: false` - Coming soon: Opt out by setting `with.persist-credentials` to `false`
- Creates a local branch - Creates a local branch
- No longer detached HEAD when checking out a branch - No longer detached HEAD when checking out a branch
- A local branch is created with the corresponding upstream branch set - A local branch is created with the corresponding upstream branch set
- Improved layout - Improved layout
- The input `path` is always relative to $GITHUB_WORKSPACE - `with.path` is always relative to `github.workspace`
- Aligns better with container actions, where $GITHUB_WORKSPACE gets mapped in - Aligns better with container actions, where `github.workspace` gets mapped in
- Fallback to REST API download
- When Git 2.18 or higher is not in the PATH, the REST API will be used to download the files
- Removed input `submodules` - Removed input `submodules`
Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous versions. Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous versions.
@ -46,16 +44,10 @@ Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous
# Otherwise, defaults to `master`. # Otherwise, defaults to `master`.
ref: '' ref: ''
# Auth token used to fetch the repository. The token is stored in the local git # Access token for clone repository
# config, which enables your scripts to run authenticated git commands. The
# post-job step removes the token from the git config.
# Default: ${{ github.token }} # Default: ${{ github.token }}
token: '' token: ''
# Whether to persist the token in the git config
# Default: true
persist-credentials: ''
# Relative path under $GITHUB_WORKSPACE to place the repository # Relative path under $GITHUB_WORKSPACE to place the repository
path: '' path: ''
@ -97,7 +89,7 @@ Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous
```yaml ```yaml
- uses: actions/checkout@v2-beta - uses: actions/checkout@v2-beta
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.after }}
``` ```
# License # License

View File

@ -63,7 +63,7 @@ describe('input-helper tests', () => {
it('sets defaults', () => { it('sets defaults', () => {
const settings: ISourceSettings = inputHelper.getInputs() const settings: ISourceSettings = inputHelper.getInputs()
expect(settings).toBeTruthy() expect(settings).toBeTruthy()
expect(settings.authToken).toBeFalsy() expect(settings.accessToken).toBeFalsy()
expect(settings.clean).toBe(true) expect(settings.clean).toBe(true)
expect(settings.commit).toBeTruthy() expect(settings.commit).toBeTruthy()
expect(settings.commit).toBe('1234567890123456789012345678901234567890') expect(settings.commit).toBe('1234567890123456789012345678901234567890')

View File

@ -5,7 +5,7 @@ if [ ! -f "./basic/basic-file.txt" ]; then
exit 1 exit 1
fi fi
if [ "$1" = "--archive" ]; then if [ "$1" = "container" ]; then
# Verify no .git folder # Verify no .git folder
if [ -d "./basic/.git" ]; then if [ -d "./basic/.git" ]; then
echo "Did not expect ./basic/.git folder to exist" echo "Did not expect ./basic/.git folder to exist"
@ -20,5 +20,5 @@ else
# Verify auth token # Verify auth token
cd basic cd basic
git fetch --no-tags --depth=1 origin +refs/heads/master:refs/remotes/origin/master git fetch --depth=1
fi fi

View File

@ -6,18 +6,12 @@ inputs:
default: ${{ github.repository }} default: ${{ github.repository }}
ref: ref:
description: > description: >
The branch, tag or SHA to checkout. When checking out the repository that The branch, tag or SHA to checkout. When checking out the repository
triggered a workflow, this defaults to the reference or SHA for that that triggered a workflow, this defaults to the reference or SHA for
event. Otherwise, defaults to `master`. that event. Otherwise, defaults to `master`.
token: token:
description: > description: 'Access token for clone repository'
Auth token used to fetch the repository. The token is stored in the local
git config, which enables your scripts to run authenticated git commands.
The post-job step removes the token from the git config.
default: ${{ github.token }} default: ${{ github.token }}
persist-credentials:
description: 'Whether to persist the token in the git config'
default: true
path: path:
description: 'Relative path under $GITHUB_WORKSPACE to place the repository' description: 'Relative path under $GITHUB_WORKSPACE to place the repository'
clean: clean:

88
dist/index.js vendored
View File

@ -2620,7 +2620,7 @@ exports.IsPost = !!process.env['STATE_isPost'];
/** /**
* The repository path for the POST action. The value is empty during the MAIN action. * The repository path for the POST action. The value is empty during the MAIN action.
*/ */
exports.RepositoryPath = process.env['STATE_repositoryPath'] || ''; exports.RepositoryPath = process.env['STATE_repositoryPath'];
/** /**
* Save the repository path so the POST action can retrieve the value. * Save the repository path so the POST action can retrieve the value.
*/ */
@ -4838,7 +4838,7 @@ class GitCommandManager {
} }
config(configKey, configValue) { config(configKey, configValue) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
yield this.execGit(['config', '--local', configKey, configValue]); yield this.execGit(['config', configKey, configValue]);
}); });
} }
configExists(configKey) { configExists(configKey) {
@ -4846,7 +4846,7 @@ class GitCommandManager {
const pattern = configKey.replace(/[^a-zA-Z0-9_]/g, x => { const pattern = configKey.replace(/[^a-zA-Z0-9_]/g, x => {
return `\\${x}`; return `\\${x}`;
}); });
const output = yield this.execGit(['config', '--local', '--name-only', '--get-regexp', pattern], true); const output = yield this.execGit(['config', '--name-only', '--get-regexp', pattern], true);
return output.exitCode === 0; return output.exitCode === 0;
}); });
} }
@ -4932,19 +4932,19 @@ class GitCommandManager {
} }
tryConfigUnset(configKey) { tryConfigUnset(configKey) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const output = yield this.execGit(['config', '--local', '--unset-all', configKey], true); const output = yield this.execGit(['config', '--unset-all', configKey], true);
return output.exitCode === 0; return output.exitCode === 0;
}); });
} }
tryDisableAutomaticGarbageCollection() { tryDisableAutomaticGarbageCollection() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const output = yield this.execGit(['config', '--local', 'gc.auto', '0'], true); const output = yield this.execGit(['config', 'gc.auto', '0'], true);
return output.exitCode === 0; return output.exitCode === 0;
}); });
} }
tryGetFetchUrl() { tryGetFetchUrl() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const output = yield this.execGit(['config', '--local', '--get', 'remote.origin.url'], true); const output = yield this.execGit(['config', '--get', 'remote.origin.url'], true);
if (output.exitCode !== 0) { if (output.exitCode !== 0) {
return ''; return '';
} }
@ -5121,7 +5121,7 @@ function getSource(settings) {
// Downloading using REST API // Downloading using REST API
core.info(`The repository will be downloaded using the GitHub REST API`); core.info(`The repository will be downloaded using the GitHub REST API`);
core.info(`To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH`); core.info(`To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH`);
yield githubApiHelper.downloadRepository(settings.authToken, settings.repositoryOwner, settings.repositoryName, settings.ref, settings.commit, settings.repositoryPath); yield githubApiHelper.downloadRepository(settings.accessToken, settings.repositoryOwner, settings.repositoryName, settings.ref, settings.commit, settings.repositoryPath);
} }
else { else {
// Save state for POST action // Save state for POST action
@ -5137,9 +5137,11 @@ function getSource(settings) {
} }
// Remove possible previous extraheader // Remove possible previous extraheader
yield removeGitConfig(git, authConfigKey); yield removeGitConfig(git, authConfigKey);
try { // Add extraheader (auth)
// Config auth token const base64Credentials = Buffer.from(`x-access-token:${settings.accessToken}`, 'utf8').toString('base64');
yield configureAuthToken(git, settings.authToken); core.setSecret(base64Credentials);
const authConfigValue = `AUTHORIZATION: basic ${base64Credentials}`;
yield git.config(authConfigKey, authConfigValue);
// LFS install // LFS install
if (settings.lfs) { if (settings.lfs) {
yield git.lfsInstall(); yield git.lfsInstall();
@ -5160,12 +5162,6 @@ function getSource(settings) {
// Dump some info about the checked out commit // Dump some info about the checked out commit
yield git.log1(); yield git.log1();
} }
finally {
if (!settings.persistCredentials) {
yield removeGitConfig(git, authConfigKey);
}
}
}
}); });
} }
exports.getSource = getSource; exports.getSource = getSource;
@ -5269,34 +5265,23 @@ function prepareExistingDirectory(git, repositoryPath, repositoryUrl, clean) {
} }
}); });
} }
function configureAuthToken(git, authToken) {
return __awaiter(this, void 0, void 0, function* () {
// Configure a placeholder value. This approach avoids the credential being captured
// by process creation audit events, which are commonly logged. For more information,
// refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
const placeholder = `AUTHORIZATION: basic ***`;
yield git.config(authConfigKey, placeholder);
// Determine the basic credential value
const basicCredential = Buffer.from(`x-access-token:${authToken}`, 'utf8').toString('base64');
core.setSecret(basicCredential);
// Replace the value in the config file
const configPath = path.join(git.getWorkingDirectory(), '.git', 'config');
let content = (yield fs.promises.readFile(configPath)).toString();
const placeholderIndex = content.indexOf(placeholder);
if (placeholderIndex < 0 ||
placeholderIndex != content.lastIndexOf(placeholder)) {
throw new Error('Unable to replace auth placeholder in .git/config');
}
content = content.replace(placeholder, `AUTHORIZATION: basic ${basicCredential}`);
yield fs.promises.writeFile(configPath, content);
});
}
function removeGitConfig(git, configKey) { function removeGitConfig(git, configKey) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
if ((yield git.configExists(configKey)) && if ((yield git.configExists(configKey)) &&
!(yield git.tryConfigUnset(configKey))) { !(yield git.tryConfigUnset(configKey))) {
// Load the config contents // Load the config contents
core.warning(`Failed to remove '${configKey}' from the git config`); core.warning(`Failed to remove '${configKey}' from the git config. Attempting to remove the config value by editing the file directly.`);
const configPath = path.join(git.getWorkingDirectory(), '.git', 'config');
fsHelper.fileExistsSync(configPath);
let contents = fs.readFileSync(configPath).toString() || '';
// Filter - only includes lines that do not contain the config key
const upperConfigKey = configKey.toUpperCase();
const split = contents
.split('\n')
.filter(x => !x.toUpperCase().includes(upperConfigKey));
contents = split.join('\n');
// Rewrite the config file
fs.writeFileSync(configPath, contents);
} }
}); });
} }
@ -8418,12 +8403,12 @@ const retryHelper = __importStar(__webpack_require__(587));
const toolCache = __importStar(__webpack_require__(533)); const toolCache = __importStar(__webpack_require__(533));
const v4_1 = __importDefault(__webpack_require__(826)); const v4_1 = __importDefault(__webpack_require__(826));
const IS_WINDOWS = process.platform === 'win32'; const IS_WINDOWS = process.platform === 'win32';
function downloadRepository(authToken, owner, repo, ref, commit, repositoryPath) { function downloadRepository(accessToken, owner, repo, ref, commit, repositoryPath) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
// Download the archive // Download the archive
let archiveData = yield retryHelper.execute(() => __awaiter(this, void 0, void 0, function* () { let archiveData = yield retryHelper.execute(() => __awaiter(this, void 0, void 0, function* () {
core.info('Downloading the archive'); core.info('Downloading the archive');
return yield downloadArchive(authToken, owner, repo, ref, commit); return yield downloadArchive(accessToken, owner, repo, ref, commit);
})); }));
// Write archive to disk // Write archive to disk
core.info('Writing archive to disk'); core.info('Writing archive to disk');
@ -8453,20 +8438,15 @@ function downloadRepository(authToken, owner, repo, ref, commit, repositoryPath)
for (const fileName of yield fs.promises.readdir(tempRepositoryPath)) { for (const fileName of yield fs.promises.readdir(tempRepositoryPath)) {
const sourcePath = path.join(tempRepositoryPath, fileName); const sourcePath = path.join(tempRepositoryPath, fileName);
const targetPath = path.join(repositoryPath, fileName); const targetPath = path.join(repositoryPath, fileName);
if (IS_WINDOWS) {
yield io.cp(sourcePath, targetPath, { recursive: true }); // Copy on Windows (Windows Defender may have a lock)
}
else {
yield io.mv(sourcePath, targetPath); yield io.mv(sourcePath, targetPath);
} }
}
io.rmRF(extractPath); io.rmRF(extractPath);
}); });
} }
exports.downloadRepository = downloadRepository; exports.downloadRepository = downloadRepository;
function downloadArchive(authToken, owner, repo, ref, commit) { function downloadArchive(accessToken, owner, repo, ref, commit) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const octokit = new github.GitHub(authToken); const octokit = new github.GitHub(accessToken);
const params = { const params = {
owner: owner, owner: owner,
repo: repo, repo: repo,
@ -8475,7 +8455,7 @@ function downloadArchive(authToken, owner, repo, ref, commit) {
}; };
const response = yield octokit.repos.getArchiveLink(params); const response = yield octokit.repos.getArchiveLink(params);
if (response.status != 200) { if (response.status != 200) {
throw new Error(`Unexpected response from GitHub API. Status: ${response.status}, Data: ${response.data}`); throw new Error(`Unexpected response from GitHub API. Status: '${response.status}'`);
} }
return Buffer.from(response.data); // response.data is ArrayBuffer return Buffer.from(response.data); // response.data is ArrayBuffer
}); });
@ -9822,9 +9802,6 @@ class RetryHelper {
this.maxAttempts = maxAttempts; this.maxAttempts = maxAttempts;
this.minSeconds = Math.floor(minSeconds); this.minSeconds = Math.floor(minSeconds);
this.maxSeconds = Math.floor(maxSeconds); this.maxSeconds = Math.floor(maxSeconds);
if (this.minSeconds > this.maxSeconds) {
throw new Error('min seconds should be less than or equal to max seconds');
}
} }
execute(action) { execute(action) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
@ -12779,11 +12756,8 @@ function getInputs() {
// LFS // LFS
result.lfs = (core.getInput('lfs') || 'false').toUpperCase() === 'TRUE'; result.lfs = (core.getInput('lfs') || 'false').toUpperCase() === 'TRUE';
core.debug(`lfs = ${result.lfs}`); core.debug(`lfs = ${result.lfs}`);
// Auth token // Access token
result.authToken = core.getInput('token'); result.accessToken = core.getInput('token');
// Persist credentials
result.persistCredentials =
(core.getInput('persist-credentials') || 'false').toUpperCase() === 'TRUE';
return result; return result;
} }
exports.getInputs = getInputs; exports.getInputs = getInputs;

View File

@ -116,7 +116,7 @@ class GitCommandManager {
} }
async config(configKey: string, configValue: string): Promise<void> { async config(configKey: string, configValue: string): Promise<void> {
await this.execGit(['config', '--local', configKey, configValue]) await this.execGit(['config', configKey, configValue])
} }
async configExists(configKey: string): Promise<boolean> { async configExists(configKey: string): Promise<boolean> {
@ -124,7 +124,7 @@ class GitCommandManager {
return `\\${x}` return `\\${x}`
}) })
const output = await this.execGit( const output = await this.execGit(
['config', '--local', '--name-only', '--get-regexp', pattern], ['config', '--name-only', '--get-regexp', pattern],
true true
) )
return output.exitCode === 0 return output.exitCode === 0
@ -211,23 +211,20 @@ class GitCommandManager {
async tryConfigUnset(configKey: string): Promise<boolean> { async tryConfigUnset(configKey: string): Promise<boolean> {
const output = await this.execGit( const output = await this.execGit(
['config', '--local', '--unset-all', configKey], ['config', '--unset-all', configKey],
true true
) )
return output.exitCode === 0 return output.exitCode === 0
} }
async tryDisableAutomaticGarbageCollection(): Promise<boolean> { async tryDisableAutomaticGarbageCollection(): Promise<boolean> {
const output = await this.execGit( const output = await this.execGit(['config', 'gc.auto', '0'], true)
['config', '--local', 'gc.auto', '0'],
true
)
return output.exitCode === 0 return output.exitCode === 0
} }
async tryGetFetchUrl(): Promise<string> { async tryGetFetchUrl(): Promise<string> {
const output = await this.execGit( const output = await this.execGit(
['config', '--local', '--get', 'remote.origin.url'], ['config', '--get', 'remote.origin.url'],
true true
) )

View File

@ -1,4 +1,5 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as coreCommand from '@actions/core/lib/command'
import * as fs from 'fs' import * as fs from 'fs'
import * as fsHelper from './fs-helper' import * as fsHelper from './fs-helper'
import * as gitCommandManager from './git-command-manager' import * as gitCommandManager from './git-command-manager'
@ -20,8 +21,7 @@ export interface ISourceSettings {
clean: boolean clean: boolean
fetchDepth: number fetchDepth: number
lfs: boolean lfs: boolean
authToken: string accessToken: string
persistCredentials: boolean
} }
export async function getSource(settings: ISourceSettings): Promise<void> { export async function getSource(settings: ISourceSettings): Promise<void> {
@ -65,7 +65,7 @@ export async function getSource(settings: ISourceSettings): Promise<void> {
`To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH` `To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH`
) )
await githubApiHelper.downloadRepository( await githubApiHelper.downloadRepository(
settings.authToken, settings.accessToken,
settings.repositoryOwner, settings.repositoryOwner,
settings.repositoryName, settings.repositoryName,
settings.ref, settings.ref,
@ -94,9 +94,14 @@ export async function getSource(settings: ISourceSettings): Promise<void> {
// Remove possible previous extraheader // Remove possible previous extraheader
await removeGitConfig(git, authConfigKey) await removeGitConfig(git, authConfigKey)
try { // Add extraheader (auth)
// Config auth token const base64Credentials = Buffer.from(
await configureAuthToken(git, settings.authToken) `x-access-token:${settings.accessToken}`,
'utf8'
).toString('base64')
core.setSecret(base64Credentials)
const authConfigValue = `AUTHORIZATION: basic ${base64Credentials}`
await git.config(authConfigKey, authConfigValue)
// LFS install // LFS install
if (settings.lfs) { if (settings.lfs) {
@ -126,11 +131,6 @@ export async function getSource(settings: ISourceSettings): Promise<void> {
// Dump some info about the checked out commit // Dump some info about the checked out commit
await git.log1() await git.log1()
} finally {
if (!settings.persistCredentials) {
await removeGitConfig(git, authConfigKey)
}
}
} }
} }
@ -255,40 +255,6 @@ async function prepareExistingDirectory(
} }
} }
async function configureAuthToken(
git: IGitCommandManager,
authToken: string
): Promise<void> {
// Configure a placeholder value. This approach avoids the credential being captured
// by process creation audit events, which are commonly logged. For more information,
// refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
const placeholder = `AUTHORIZATION: basic ***`
await git.config(authConfigKey, placeholder)
// Determine the basic credential value
const basicCredential = Buffer.from(
`x-access-token:${authToken}`,
'utf8'
).toString('base64')
core.setSecret(basicCredential)
// Replace the value in the config file
const configPath = path.join(git.getWorkingDirectory(), '.git', 'config')
let content = (await fs.promises.readFile(configPath)).toString()
const placeholderIndex = content.indexOf(placeholder)
if (
placeholderIndex < 0 ||
placeholderIndex != content.lastIndexOf(placeholder)
) {
throw new Error('Unable to replace auth placeholder in .git/config')
}
content = content.replace(
placeholder,
`AUTHORIZATION: basic ${basicCredential}`
)
await fs.promises.writeFile(configPath, content)
}
async function removeGitConfig( async function removeGitConfig(
git: IGitCommandManager, git: IGitCommandManager,
configKey: string configKey: string
@ -298,6 +264,21 @@ async function removeGitConfig(
!(await git.tryConfigUnset(configKey)) !(await git.tryConfigUnset(configKey))
) { ) {
// Load the config contents // Load the config contents
core.warning(`Failed to remove '${configKey}' from the git config`) core.warning(
`Failed to remove '${configKey}' from the git config. Attempting to remove the config value by editing the file directly.`
)
const configPath = path.join(git.getWorkingDirectory(), '.git', 'config')
fsHelper.fileExistsSync(configPath)
let contents = fs.readFileSync(configPath).toString() || ''
// Filter - only includes lines that do not contain the config key
const upperConfigKey = configKey.toUpperCase()
const split = contents
.split('\n')
.filter(x => !x.toUpperCase().includes(upperConfigKey))
contents = split.join('\n')
// Rewrite the config file
fs.writeFileSync(configPath, contents)
} }
} }

View File

@ -12,7 +12,7 @@ import {ReposGetArchiveLinkParams} from '@octokit/rest'
const IS_WINDOWS = process.platform === 'win32' const IS_WINDOWS = process.platform === 'win32'
export async function downloadRepository( export async function downloadRepository(
authToken: string, accessToken: string,
owner: string, owner: string,
repo: string, repo: string,
ref: string, ref: string,
@ -22,7 +22,7 @@ export async function downloadRepository(
// Download the archive // Download the archive
let archiveData = await retryHelper.execute(async () => { let archiveData = await retryHelper.execute(async () => {
core.info('Downloading the archive') core.info('Downloading the archive')
return await downloadArchive(authToken, owner, repo, ref, commit) return await downloadArchive(accessToken, owner, repo, ref, commit)
}) })
// Write archive to disk // Write archive to disk
@ -58,23 +58,19 @@ export async function downloadRepository(
for (const fileName of await fs.promises.readdir(tempRepositoryPath)) { for (const fileName of await fs.promises.readdir(tempRepositoryPath)) {
const sourcePath = path.join(tempRepositoryPath, fileName) const sourcePath = path.join(tempRepositoryPath, fileName)
const targetPath = path.join(repositoryPath, fileName) const targetPath = path.join(repositoryPath, fileName)
if (IS_WINDOWS) {
await io.cp(sourcePath, targetPath, {recursive: true}) // Copy on Windows (Windows Defender may have a lock)
} else {
await io.mv(sourcePath, targetPath) await io.mv(sourcePath, targetPath)
} }
}
io.rmRF(extractPath) io.rmRF(extractPath)
} }
async function downloadArchive( async function downloadArchive(
authToken: string, accessToken: string,
owner: string, owner: string,
repo: string, repo: string,
ref: string, ref: string,
commit: string commit: string
): Promise<Buffer> { ): Promise<Buffer> {
const octokit = new github.GitHub(authToken) const octokit = new github.GitHub(accessToken)
const params: ReposGetArchiveLinkParams = { const params: ReposGetArchiveLinkParams = {
owner: owner, owner: owner,
repo: repo, repo: repo,
@ -84,7 +80,7 @@ async function downloadArchive(
const response = await octokit.repos.getArchiveLink(params) const response = await octokit.repos.getArchiveLink(params)
if (response.status != 200) { if (response.status != 200) {
throw new Error( throw new Error(
`Unexpected response from GitHub API. Status: ${response.status}, Data: ${response.data}` `Unexpected response from GitHub API. Status: '${response.status}'`
) )
} }

View File

@ -97,12 +97,8 @@ export function getInputs(): ISourceSettings {
result.lfs = (core.getInput('lfs') || 'false').toUpperCase() === 'TRUE' result.lfs = (core.getInput('lfs') || 'false').toUpperCase() === 'TRUE'
core.debug(`lfs = ${result.lfs}`) core.debug(`lfs = ${result.lfs}`)
// Auth token // Access token
result.authToken = core.getInput('token') result.accessToken = core.getInput('token')
// Persist credentials
result.persistCredentials =
(core.getInput('persist-credentials') || 'false').toUpperCase() === 'TRUE'
return result return result
} }

View File

@ -17,9 +17,6 @@ export class RetryHelper {
this.maxAttempts = maxAttempts this.maxAttempts = maxAttempts
this.minSeconds = Math.floor(minSeconds) this.minSeconds = Math.floor(minSeconds)
this.maxSeconds = Math.floor(maxSeconds) this.maxSeconds = Math.floor(maxSeconds)
if (this.minSeconds > this.maxSeconds) {
throw new Error('min seconds should be less than or equal to max seconds')
}
} }
async execute<T>(action: () => Promise<T>): Promise<T> { async execute<T>(action: () => Promise<T>): Promise<T> {

View File

@ -9,8 +9,7 @@ export const IsPost = !!process.env['STATE_isPost']
/** /**
* The repository path for the POST action. The value is empty during the MAIN action. * The repository path for the POST action. The value is empty during the MAIN action.
*/ */
export const RepositoryPath = export const RepositoryPath = process.env['STATE_repositoryPath'] as string
(process.env['STATE_repositoryPath'] as string) || ''
/** /**
* Save the repository path so the POST action can retrieve the value. * Save the repository path so the POST action can retrieve the value.