0
0
Fork 0
mirror of https://github.com/actions/checkout.git synced 2025-10-09 08:11:56 +03:00
This commit is contained in:
andreanadr 2025-10-09 01:58:04 +00:00 committed by GitHub
commit 2b9afb483e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 6 additions and 2816 deletions

View file

@ -1,3 +0,0 @@
dist/
lib/
node_modules/

View file

@ -1,53 +0,0 @@
{
"plugins": ["jest", "@typescript-eslint"],
"extends": ["plugin:github/recommended"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 9,
"sourceType": "module",
"project": "./tsconfig.json"
},
"rules": {
"eslint-comments/no-use": "off",
"import/no-namespace": "off",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
"@typescript-eslint/no-require-imports": "error",
"@typescript-eslint/array-type": "error",
"@typescript-eslint/await-thenable": "error",
"camelcase": "off",
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
"@typescript-eslint/func-call-spacing": ["error", "never"],
"@typescript-eslint/no-array-constructor": "error",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-extraneous-class": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-for-in-array": "error",
"@typescript-eslint/no-inferrable-types": "error",
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-non-null-assertion": "warn",
"@typescript-eslint/no-unnecessary-qualifier": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-useless-constructor": "error",
"@typescript-eslint/no-var-requires": "error",
"@typescript-eslint/prefer-for-of": "warn",
"@typescript-eslint/prefer-function-type": "warn",
"@typescript-eslint/prefer-includes": "error",
"@typescript-eslint/prefer-string-starts-ends-with": "error",
"@typescript-eslint/promise-function-async": "error",
"@typescript-eslint/require-array-sort-compare": "error",
"@typescript-eslint/restrict-plus-operands": "error",
"semi": "off",
"@typescript-eslint/semi": ["error", "never"],
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/unbound-method": "error"
},
"env": {
"node": true,
"es6": true,
"jest/globals": true
}
}

2
.gitattributes vendored
View file

@ -1 +1 @@
.licenses/** -diff linguist-generated=true .licenses/

View file

@ -3,7 +3,7 @@ version: 2
updates: updates:
- package-ecosystem: "npm" - package-ecosystem: "npm"
directory: "/" directory: "npm/actions"
schedule: schedule:
interval: "weekly" interval: "weekly"
groups: groups:
@ -11,7 +11,7 @@ updates:
# NPM: Only group minor and patch updates (we want to carefully review major updates) # NPM: Only group minor and patch updates (we want to carefully review major updates)
update-types: [minor, patch] update-types: [minor, patch]
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"
directory: "/" directory: "github/actions"
schedule: schedule:
interval: "weekly" interval: "weekly"
groups: groups:

View file

@ -6,7 +6,7 @@ on:
jobs: jobs:
publish: publish:
runs-on: ubuntu-latest runs-on: android-latest
permissions: permissions:
contents: read contents: read
id-token: write id-token: write

View file

@ -1,59 +0,0 @@
name: Publish test-ubuntu-git Container
on:
# Use an on demand workflow trigger.
# (Forked copies of actions/checkout won't have permission to update GHCR.io/actions,
# so avoid trigger events that run automatically.)
workflow_dispatch:
inputs:
publish:
description: 'Publish to ghcr.io? (main branch only)'
type: boolean
required: true
default: false
env:
REGISTRY: ghcr.io
IMAGE_NAME: actions/test-ubuntu-git
jobs:
build-and-push-image:
runs-on: ubuntu-latest
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Use `docker/login-action` to log in to GHCR.io.
# Once published, the packages are scoped to the account defined here.
- name: Log in to the ghcr.io container registry
uses: docker/login-action@v3.3.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Format Timestamp
id: timestamp
# Use `date` with a custom format to achieve the key=value format GITHUB_OUTPUT expects.
run: date -u "+now=%Y%m%d.%H%M%S.%3NZ" >> "$GITHUB_OUTPUT"
- name: Issue Image Publish Warning
if: ${{ inputs.publish && github.ref_name != 'main' }}
run: echo "::warning::test-ubuntu-git images can only be published from the actions/checkout 'main' branch. Workflow will continue with push/publish disabled."
# Use `docker/build-push-action` to build (and optionally publish) the image.
- name: Build Docker Image (with optional Push)
uses: docker/build-push-action@v6.5.0
with:
context: .
file: images/test-ubuntu-git.Dockerfile
# For now, attempts to push to ghcr.io must target the `main` branch.
# In the future, consider also allowing attempts from `releases/*` branches.
push: ${{ inputs.publish && github.ref_name == 'main' }}
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}.${{ steps.timestamp.outputs.now }}

2
.gitignore vendored
View file

@ -1,5 +1,3 @@
__test__/_temp __test__/_temp
_temp/ _temp/
lib/
node_modules/ node_modules/
.vscode/

View file

@ -1,5 +1,6 @@
sources: sources:
npm: true npm: true
github: true
allowed: allowed:
- apache-2.0 - apache-2.0
@ -7,8 +8,3 @@ allowed:
- bsd-3-clause - bsd-3-clause
- isc - isc
- mit - mit
- cc0-1.0
- unlicense
reviewed:
npm:

View file

@ -1,11 +0,0 @@
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": false,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": false,
"arrowParens": "avoid",
"parser": "typescript"
}

View file

@ -47,7 +47,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
- uses: actions/checkout@v5 - uses: actions/checkout@v5
with: with:
# Repository name with owner. For example, actions/checkout # Repository name with owner. For example, actions/checkout
# Default: ${{ github.repository }} # Default: ${{ andreanadr.repository }}
repository: '' repository: ''
# 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 that

View file

@ -1,856 +0,0 @@
import * as core from '@actions/core'
import * as fs from 'fs'
import * as gitAuthHelper from '../lib/git-auth-helper'
import * as io from '@actions/io'
import * as os from 'os'
import * as path from 'path'
import * as stateHelper from '../lib/state-helper'
import {IGitCommandManager} from '../lib/git-command-manager'
import {IGitSourceSettings} from '../lib/git-source-settings'
const isWindows = process.platform === 'win32'
const testWorkspace = path.join(__dirname, '_temp', 'git-auth-helper')
const originalRunnerTemp = process.env['RUNNER_TEMP']
const originalHome = process.env['HOME']
let workspace: string
let localGitConfigPath: string
let globalGitConfigPath: string
let runnerTemp: string
let tempHomedir: string
let git: IGitCommandManager & {env: {[key: string]: string}}
let settings: IGitSourceSettings
let sshPath: string
let githubServerUrl: string
describe('git-auth-helper tests', () => {
beforeAll(async () => {
// SSH
sshPath = await io.which('ssh')
// Clear test workspace
await io.rmRF(testWorkspace)
})
beforeEach(() => {
// Mock setSecret
jest.spyOn(core, 'setSecret').mockImplementation((secret: string) => {})
// Mock error/warning/info/debug
jest.spyOn(core, 'error').mockImplementation(jest.fn())
jest.spyOn(core, 'warning').mockImplementation(jest.fn())
jest.spyOn(core, 'info').mockImplementation(jest.fn())
jest.spyOn(core, 'debug').mockImplementation(jest.fn())
// Mock state helper
jest.spyOn(stateHelper, 'setSshKeyPath').mockImplementation(jest.fn())
jest
.spyOn(stateHelper, 'setSshKnownHostsPath')
.mockImplementation(jest.fn())
})
afterEach(() => {
// Unregister mocks
jest.restoreAllMocks()
// Restore HOME
if (originalHome) {
process.env['HOME'] = originalHome
} else {
delete process.env['HOME']
}
})
afterAll(() => {
// Restore RUNNER_TEMP
delete process.env['RUNNER_TEMP']
if (originalRunnerTemp) {
process.env['RUNNER_TEMP'] = originalRunnerTemp
}
})
async function testAuthHeader(
testName: string,
serverUrl: string | undefined = undefined
) {
// Arrange
let expectedServerUrl = 'https://github.com'
if (serverUrl) {
githubServerUrl = serverUrl
expectedServerUrl = githubServerUrl
}
await setup(testName)
expect(settings.authToken).toBeTruthy() // sanity check
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
// Act
await authHelper.configureAuth()
// Assert config
const configContent = (
await fs.promises.readFile(localGitConfigPath)
).toString()
const basicCredential = Buffer.from(
`x-access-token:${settings.authToken}`,
'utf8'
).toString('base64')
expect(
configContent.indexOf(
`http.${expectedServerUrl}/.extraheader AUTHORIZATION: basic ${basicCredential}`
)
).toBeGreaterThanOrEqual(0)
}
const configureAuth_configuresAuthHeader =
'configureAuth configures auth header'
it(configureAuth_configuresAuthHeader, async () => {
await testAuthHeader(configureAuth_configuresAuthHeader)
})
const configureAuth_AcceptsGitHubServerUrl =
'inject https://my-ghes-server.com as github server url'
it(configureAuth_AcceptsGitHubServerUrl, async () => {
await testAuthHeader(
configureAuth_AcceptsGitHubServerUrl,
'https://my-ghes-server.com'
)
})
const configureAuth_AcceptsGitHubServerUrlSetToGHEC =
'inject https://github.com as github server url'
it(configureAuth_AcceptsGitHubServerUrlSetToGHEC, async () => {
await testAuthHeader(
configureAuth_AcceptsGitHubServerUrl,
'https://github.com'
)
})
const configureAuth_configuresAuthHeaderEvenWhenPersistCredentialsFalse =
'configureAuth configures auth header even when persist credentials false'
it(
configureAuth_configuresAuthHeaderEvenWhenPersistCredentialsFalse,
async () => {
// Arrange
await setup(
configureAuth_configuresAuthHeaderEvenWhenPersistCredentialsFalse
)
expect(settings.authToken).toBeTruthy() // sanity check
settings.persistCredentials = false
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
// Act
await authHelper.configureAuth()
// Assert config
const configContent = (
await fs.promises.readFile(localGitConfigPath)
).toString()
expect(
configContent.indexOf(
`http.https://github.com/.extraheader AUTHORIZATION`
)
).toBeGreaterThanOrEqual(0)
}
)
const configureAuth_copiesUserKnownHosts =
'configureAuth copies user known hosts'
it(configureAuth_copiesUserKnownHosts, async () => {
if (!sshPath) {
process.stdout.write(
`Skipped test "${configureAuth_copiesUserKnownHosts}". Executable 'ssh' not found in the PATH.\n`
)
return
}
// Arange
await setup(configureAuth_copiesUserKnownHosts)
expect(settings.sshKey).toBeTruthy() // sanity check
// Mock fs.promises.readFile
const realReadFile = fs.promises.readFile
jest
.spyOn(fs.promises, 'readFile')
.mockImplementation(async (file: any, options: any): Promise<Buffer> => {
const userKnownHostsPath = path.join(
os.homedir(),
'.ssh',
'known_hosts'
)
if (file === userKnownHostsPath) {
return Buffer.from('some-domain.com ssh-rsa ABCDEF')
}
return await realReadFile(file, options)
})
// Act
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth()
// Assert known hosts
const actualSshKnownHostsPath = await getActualSshKnownHostsPath()
const actualSshKnownHostsContent = (
await fs.promises.readFile(actualSshKnownHostsPath)
).toString()
expect(actualSshKnownHostsContent).toMatch(
/some-domain\.com ssh-rsa ABCDEF/
)
expect(actualSshKnownHostsContent).toMatch(/github\.com ssh-rsa AAAAB3N/)
})
const configureAuth_registersBasicCredentialAsSecret =
'configureAuth registers basic credential as secret'
it(configureAuth_registersBasicCredentialAsSecret, async () => {
// Arrange
await setup(configureAuth_registersBasicCredentialAsSecret)
expect(settings.authToken).toBeTruthy() // sanity check
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
// Act
await authHelper.configureAuth()
// Assert secret
const setSecretSpy = core.setSecret as jest.Mock<any, any>
expect(setSecretSpy).toHaveBeenCalledTimes(1)
const expectedSecret = Buffer.from(
`x-access-token:${settings.authToken}`,
'utf8'
).toString('base64')
expect(setSecretSpy).toHaveBeenCalledWith(expectedSecret)
})
const setsSshCommandEnvVarWhenPersistCredentialsFalse =
'sets SSH command env var when persist-credentials false'
it(setsSshCommandEnvVarWhenPersistCredentialsFalse, async () => {
if (!sshPath) {
process.stdout.write(
`Skipped test "${setsSshCommandEnvVarWhenPersistCredentialsFalse}". Executable 'ssh' not found in the PATH.\n`
)
return
}
// Arrange
await setup(setsSshCommandEnvVarWhenPersistCredentialsFalse)
settings.persistCredentials = false
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
// Act
await authHelper.configureAuth()
// Assert git env var
const actualKeyPath = await getActualSshKeyPath()
const actualKnownHostsPath = await getActualSshKnownHostsPath()
const expectedSshCommand = `"${sshPath}" -i "$RUNNER_TEMP/${path.basename(
actualKeyPath
)}" -o StrictHostKeyChecking=yes -o CheckHostIP=no -o "UserKnownHostsFile=$RUNNER_TEMP/${path.basename(
actualKnownHostsPath
)}"`
expect(git.setEnvironmentVariable).toHaveBeenCalledWith(
'GIT_SSH_COMMAND',
expectedSshCommand
)
// Asserty git config
const gitConfigLines = (await fs.promises.readFile(localGitConfigPath))
.toString()
.split('\n')
.filter(x => x)
expect(gitConfigLines).toHaveLength(1)
expect(gitConfigLines[0]).toMatch(/^http\./)
})
const configureAuth_setsSshCommandWhenPersistCredentialsTrue =
'sets SSH command when persist-credentials true'
it(configureAuth_setsSshCommandWhenPersistCredentialsTrue, async () => {
if (!sshPath) {
process.stdout.write(
`Skipped test "${configureAuth_setsSshCommandWhenPersistCredentialsTrue}". Executable 'ssh' not found in the PATH.\n`
)
return
}
// Arrange
await setup(configureAuth_setsSshCommandWhenPersistCredentialsTrue)
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
// Act
await authHelper.configureAuth()
// Assert git env var
const actualKeyPath = await getActualSshKeyPath()
const actualKnownHostsPath = await getActualSshKnownHostsPath()
const expectedSshCommand = `"${sshPath}" -i "$RUNNER_TEMP/${path.basename(
actualKeyPath
)}" -o StrictHostKeyChecking=yes -o CheckHostIP=no -o "UserKnownHostsFile=$RUNNER_TEMP/${path.basename(
actualKnownHostsPath
)}"`
expect(git.setEnvironmentVariable).toHaveBeenCalledWith(
'GIT_SSH_COMMAND',
expectedSshCommand
)
// Asserty git config
expect(git.config).toHaveBeenCalledWith(
'core.sshCommand',
expectedSshCommand
)
})
const configureAuth_writesExplicitKnownHosts = 'writes explicit known hosts'
it(configureAuth_writesExplicitKnownHosts, async () => {
if (!sshPath) {
process.stdout.write(
`Skipped test "${configureAuth_writesExplicitKnownHosts}". Executable 'ssh' not found in the PATH.\n`
)
return
}
// Arrange
await setup(configureAuth_writesExplicitKnownHosts)
expect(settings.sshKey).toBeTruthy() // sanity check
settings.sshKnownHosts = 'my-custom-host.com ssh-rsa ABC123'
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
// Act
await authHelper.configureAuth()
// Assert known hosts
const actualSshKnownHostsPath = await getActualSshKnownHostsPath()
const actualSshKnownHostsContent = (
await fs.promises.readFile(actualSshKnownHostsPath)
).toString()
expect(actualSshKnownHostsContent).toMatch(
/my-custom-host\.com ssh-rsa ABC123/
)
expect(actualSshKnownHostsContent).toMatch(/github\.com ssh-rsa AAAAB3N/)
})
const configureAuth_writesSshKeyAndImplicitKnownHosts =
'writes SSH key and implicit known hosts'
it(configureAuth_writesSshKeyAndImplicitKnownHosts, async () => {
if (!sshPath) {
process.stdout.write(
`Skipped test "${configureAuth_writesSshKeyAndImplicitKnownHosts}". Executable 'ssh' not found in the PATH.\n`
)
return
}
// Arrange
await setup(configureAuth_writesSshKeyAndImplicitKnownHosts)
expect(settings.sshKey).toBeTruthy() // sanity check
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
// Act
await authHelper.configureAuth()
// Assert SSH key
const actualSshKeyPath = await getActualSshKeyPath()
expect(actualSshKeyPath).toBeTruthy()
const actualSshKeyContent = (
await fs.promises.readFile(actualSshKeyPath)
).toString()
expect(actualSshKeyContent).toBe(settings.sshKey + '\n')
if (!isWindows) {
// Assert read/write for user, not group or others.
// Otherwise SSH client will error.
expect((await fs.promises.stat(actualSshKeyPath)).mode & 0o777).toBe(
0o600
)
}
// Assert known hosts
const actualSshKnownHostsPath = await getActualSshKnownHostsPath()
const actualSshKnownHostsContent = (
await fs.promises.readFile(actualSshKnownHostsPath)
).toString()
expect(actualSshKnownHostsContent).toMatch(/github\.com ssh-rsa AAAAB3N/)
})
const configureGlobalAuth_configuresUrlInsteadOfWhenSshKeyNotSet =
'configureGlobalAuth configures URL insteadOf when SSH key not set'
it(configureGlobalAuth_configuresUrlInsteadOfWhenSshKeyNotSet, async () => {
// Arrange
await setup(configureGlobalAuth_configuresUrlInsteadOfWhenSshKeyNotSet)
settings.sshKey = ''
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
// Act
await authHelper.configureAuth()
await authHelper.configureGlobalAuth()
// Assert temporary global config
expect(git.env['HOME']).toBeTruthy()
const configContent = (
await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig'))
).toString()
expect(
configContent.indexOf(`url.https://github.com/.insteadOf git@github.com`)
).toBeGreaterThanOrEqual(0)
})
const configureGlobalAuth_copiesGlobalGitConfig =
'configureGlobalAuth copies global git config'
it(configureGlobalAuth_copiesGlobalGitConfig, async () => {
// Arrange
await setup(configureGlobalAuth_copiesGlobalGitConfig)
await fs.promises.writeFile(globalGitConfigPath, 'value-from-global-config')
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
// Act
await authHelper.configureAuth()
await authHelper.configureGlobalAuth()
// Assert original global config not altered
let configContent = (
await fs.promises.readFile(globalGitConfigPath)
).toString()
expect(configContent).toBe('value-from-global-config')
// Assert temporary global config
expect(git.env['HOME']).toBeTruthy()
const basicCredential = Buffer.from(
`x-access-token:${settings.authToken}`,
'utf8'
).toString('base64')
configContent = (
await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig'))
).toString()
expect(
configContent.indexOf('value-from-global-config')
).toBeGreaterThanOrEqual(0)
expect(
configContent.indexOf(
`http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}`
)
).toBeGreaterThanOrEqual(0)
})
const configureGlobalAuth_createsNewGlobalGitConfigWhenGlobalDoesNotExist =
'configureGlobalAuth creates new git config when global does not exist'
it(
configureGlobalAuth_createsNewGlobalGitConfigWhenGlobalDoesNotExist,
async () => {
// Arrange
await setup(
configureGlobalAuth_createsNewGlobalGitConfigWhenGlobalDoesNotExist
)
await io.rmRF(globalGitConfigPath)
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
// Act
await authHelper.configureAuth()
await authHelper.configureGlobalAuth()
// Assert original global config not recreated
try {
await fs.promises.stat(globalGitConfigPath)
throw new Error(
`Did not expect file to exist: '${globalGitConfigPath}'`
)
} catch (err) {
if ((err as any)?.code !== 'ENOENT') {
throw err
}
}
// Assert temporary global config
expect(git.env['HOME']).toBeTruthy()
const basicCredential = Buffer.from(
`x-access-token:${settings.authToken}`,
'utf8'
).toString('base64')
const configContent = (
await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig'))
).toString()
expect(
configContent.indexOf(
`http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}`
)
).toBeGreaterThanOrEqual(0)
}
)
const configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeyNotSet =
'configureSubmoduleAuth configures submodules when persist credentials false and SSH key not set'
it(
configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeyNotSet,
async () => {
// Arrange
await setup(
configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeyNotSet
)
settings.persistCredentials = false
settings.sshKey = ''
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth()
const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
mockSubmoduleForeach.mockClear() // reset calls
// Act
await authHelper.configureSubmoduleAuth()
// Assert
expect(mockSubmoduleForeach).toBeCalledTimes(1)
expect(mockSubmoduleForeach.mock.calls[0][0] as string).toMatch(
/unset-all.*insteadOf/
)
}
)
const configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeySet =
'configureSubmoduleAuth configures submodules when persist credentials false and SSH key set'
it(
configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeySet,
async () => {
if (!sshPath) {
process.stdout.write(
`Skipped test "${configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeySet}". Executable 'ssh' not found in the PATH.\n`
)
return
}
// Arrange
await setup(
configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeySet
)
settings.persistCredentials = false
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth()
const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
mockSubmoduleForeach.mockClear() // reset calls
// Act
await authHelper.configureSubmoduleAuth()
// Assert
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(1)
expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
/unset-all.*insteadOf/
)
}
)
const configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeyNotSet =
'configureSubmoduleAuth configures submodules when persist credentials true and SSH key not set'
it(
configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeyNotSet,
async () => {
// Arrange
await setup(
configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeyNotSet
)
settings.sshKey = ''
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth()
const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
mockSubmoduleForeach.mockClear() // reset calls
// Act
await authHelper.configureSubmoduleAuth()
// Assert
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(4)
expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
/unset-all.*insteadOf/
)
expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/)
expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(
/url.*insteadOf.*git@github.com:/
)
expect(mockSubmoduleForeach.mock.calls[3][0]).toMatch(
/url.*insteadOf.*org-123456@github.com:/
)
}
)
const configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeySet =
'configureSubmoduleAuth configures submodules when persist credentials true and SSH key set'
it(
configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeySet,
async () => {
if (!sshPath) {
process.stdout.write(
`Skipped test "${configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeySet}". Executable 'ssh' not found in the PATH.\n`
)
return
}
// Arrange
await setup(
configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeySet
)
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth()
const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
mockSubmoduleForeach.mockClear() // reset calls
// Act
await authHelper.configureSubmoduleAuth()
// Assert
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(3)
expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
/unset-all.*insteadOf/
)
expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/)
expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(/core\.sshCommand/)
}
)
const removeAuth_removesSshCommand = 'removeAuth removes SSH command'
it(removeAuth_removesSshCommand, async () => {
if (!sshPath) {
process.stdout.write(
`Skipped test "${removeAuth_removesSshCommand}". Executable 'ssh' not found in the PATH.\n`
)
return
}
// Arrange
await setup(removeAuth_removesSshCommand)
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth()
let gitConfigContent = (
await fs.promises.readFile(localGitConfigPath)
).toString()
expect(gitConfigContent.indexOf('core.sshCommand')).toBeGreaterThanOrEqual(
0
) // sanity check
const actualKeyPath = await getActualSshKeyPath()
expect(actualKeyPath).toBeTruthy()
await fs.promises.stat(actualKeyPath)
const actualKnownHostsPath = await getActualSshKnownHostsPath()
expect(actualKnownHostsPath).toBeTruthy()
await fs.promises.stat(actualKnownHostsPath)
// Act
await authHelper.removeAuth()
// Assert git config
gitConfigContent = (
await fs.promises.readFile(localGitConfigPath)
).toString()
expect(gitConfigContent.indexOf('core.sshCommand')).toBeLessThan(0)
// Assert SSH key file
try {
await fs.promises.stat(actualKeyPath)
throw new Error('SSH key should have been deleted')
} catch (err) {
if ((err as any)?.code !== 'ENOENT') {
throw err
}
}
// Assert known hosts file
try {
await fs.promises.stat(actualKnownHostsPath)
throw new Error('SSH known hosts should have been deleted')
} catch (err) {
if ((err as any)?.code !== 'ENOENT') {
throw err
}
}
})
const removeAuth_removesToken = 'removeAuth removes token'
it(removeAuth_removesToken, async () => {
// Arrange
await setup(removeAuth_removesToken)
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth()
let gitConfigContent = (
await fs.promises.readFile(localGitConfigPath)
).toString()
expect(gitConfigContent.indexOf('http.')).toBeGreaterThanOrEqual(0) // sanity check
// Act
await authHelper.removeAuth()
// Assert git config
gitConfigContent = (
await fs.promises.readFile(localGitConfigPath)
).toString()
expect(gitConfigContent.indexOf('http.')).toBeLessThan(0)
})
const removeGlobalConfig_removesOverride =
'removeGlobalConfig removes override'
it(removeGlobalConfig_removesOverride, async () => {
// Arrange
await setup(removeGlobalConfig_removesOverride)
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth()
await authHelper.configureGlobalAuth()
const homeOverride = git.env['HOME'] // Sanity check
expect(homeOverride).toBeTruthy()
await fs.promises.stat(path.join(git.env['HOME'], '.gitconfig'))
// Act
await authHelper.removeGlobalConfig()
// Assert
expect(git.env['HOME']).toBeUndefined()
try {
await fs.promises.stat(homeOverride)
throw new Error(`Should have been deleted '${homeOverride}'`)
} catch (err) {
if ((err as any)?.code !== 'ENOENT') {
throw err
}
}
})
})
async function setup(testName: string): Promise<void> {
testName = testName.replace(/[^a-zA-Z0-9_]+/g, '-')
// Directories
workspace = path.join(testWorkspace, testName, 'workspace')
runnerTemp = path.join(testWorkspace, testName, 'runner-temp')
tempHomedir = path.join(testWorkspace, testName, 'home-dir')
await fs.promises.mkdir(workspace, {recursive: true})
await fs.promises.mkdir(runnerTemp, {recursive: true})
await fs.promises.mkdir(tempHomedir, {recursive: true})
process.env['RUNNER_TEMP'] = runnerTemp
process.env['HOME'] = tempHomedir
// Create git config
globalGitConfigPath = path.join(tempHomedir, '.gitconfig')
await fs.promises.writeFile(globalGitConfigPath, '')
localGitConfigPath = path.join(workspace, '.git', 'config')
await fs.promises.mkdir(path.dirname(localGitConfigPath), {recursive: true})
await fs.promises.writeFile(localGitConfigPath, '')
git = {
branchDelete: jest.fn(),
branchExists: jest.fn(),
branchList: jest.fn(),
disableSparseCheckout: jest.fn(),
sparseCheckout: jest.fn(),
sparseCheckoutNonConeMode: jest.fn(),
checkout: jest.fn(),
checkoutDetach: jest.fn(),
config: jest.fn(
async (key: string, value: string, globalConfig?: boolean) => {
const configPath = globalConfig
? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
: localGitConfigPath
await fs.promises.appendFile(configPath, `\n${key} ${value}`)
}
),
configExists: jest.fn(
async (key: string, globalConfig?: boolean): Promise<boolean> => {
const configPath = globalConfig
? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
: localGitConfigPath
const content = await fs.promises.readFile(configPath)
const lines = content
.toString()
.split('\n')
.filter(x => x)
return lines.some(x => x.startsWith(key))
}
),
env: {},
fetch: jest.fn(),
getDefaultBranch: jest.fn(),
getWorkingDirectory: jest.fn(() => workspace),
init: jest.fn(),
isDetached: jest.fn(),
lfsFetch: jest.fn(),
lfsInstall: jest.fn(),
log1: jest.fn(),
remoteAdd: jest.fn(),
removeEnvironmentVariable: jest.fn((name: string) => delete git.env[name]),
revParse: jest.fn(),
setEnvironmentVariable: jest.fn((name: string, value: string) => {
git.env[name] = value
}),
shaExists: jest.fn(),
submoduleForeach: jest.fn(async () => {
return ''
}),
submoduleSync: jest.fn(),
submoduleStatus: jest.fn(async () => {
return true
}),
submoduleUpdate: jest.fn(),
tagExists: jest.fn(),
tryClean: jest.fn(),
tryConfigUnset: jest.fn(
async (key: string, globalConfig?: boolean): Promise<boolean> => {
const configPath = globalConfig
? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
: localGitConfigPath
let content = await fs.promises.readFile(configPath)
let lines = content
.toString()
.split('\n')
.filter(x => x)
.filter(x => !x.startsWith(key))
await fs.promises.writeFile(configPath, lines.join('\n'))
return true
}
),
tryDisableAutomaticGarbageCollection: jest.fn(),
tryGetFetchUrl: jest.fn(),
tryReset: jest.fn(),
version: jest.fn()
}
settings = {
authToken: 'some auth token',
clean: true,
commit: '',
filter: undefined,
sparseCheckout: [],
sparseCheckoutConeMode: true,
fetchDepth: 1,
fetchTags: false,
showProgress: true,
lfs: false,
submodules: false,
nestedSubmodules: false,
persistCredentials: true,
ref: 'refs/heads/main',
repositoryName: 'my-repo',
repositoryOwner: 'my-org',
repositoryPath: '',
sshKey: sshPath ? 'some ssh private key' : '',
sshKnownHosts: '',
sshStrict: true,
sshUser: '',
workflowOrganizationId: 123456,
setSafeDirectory: true,
githubServerUrl: githubServerUrl
}
}
async function getActualSshKeyPath(): Promise<string> {
let actualTempFiles = (await fs.promises.readdir(runnerTemp))
.sort()
.map(x => path.join(runnerTemp, x))
if (actualTempFiles.length === 0) {
return ''
}
expect(actualTempFiles).toHaveLength(2)
expect(actualTempFiles[0].endsWith('_known_hosts')).toBeFalsy()
return actualTempFiles[0]
}
async function getActualSshKnownHostsPath(): Promise<string> {
let actualTempFiles = (await fs.promises.readdir(runnerTemp))
.sort()
.map(x => path.join(runnerTemp, x))
if (actualTempFiles.length === 0) {
return ''
}
expect(actualTempFiles).toHaveLength(2)
expect(actualTempFiles[1].endsWith('_known_hosts')).toBeTruthy()
expect(actualTempFiles[1].startsWith(actualTempFiles[0])).toBeTruthy()
return actualTempFiles[1]
}

View file

@ -1,378 +0,0 @@
import * as exec from '@actions/exec'
import * as fshelper from '../lib/fs-helper'
import * as commandManager from '../lib/git-command-manager'
let git: commandManager.IGitCommandManager
let mockExec = jest.fn()
describe('git-auth-helper tests', () => {
beforeAll(async () => {})
beforeEach(async () => {
jest.spyOn(fshelper, 'fileExistsSync').mockImplementation(jest.fn())
jest.spyOn(fshelper, 'directoryExistsSync').mockImplementation(jest.fn())
})
afterEach(() => {
jest.restoreAllMocks()
})
afterAll(() => {})
it('branch list matches', async () => {
mockExec.mockImplementation((path, args, options) => {
console.log(args, options.listeners.stdout)
if (args.includes('version')) {
options.listeners.stdout(Buffer.from('2.18'))
return 0
}
if (args.includes('rev-parse')) {
options.listeners.stdline(Buffer.from('refs/heads/foo'))
options.listeners.stdline(Buffer.from('refs/heads/bar'))
return 0
}
return 1
})
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
git = await commandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
let branches = await git.branchList(false)
expect(branches).toHaveLength(2)
expect(branches.sort()).toEqual(['foo', 'bar'].sort())
})
it('ambiguous ref name output is captured', async () => {
mockExec.mockImplementation((path, args, options) => {
console.log(args, options.listeners.stdout)
if (args.includes('version')) {
options.listeners.stdout(Buffer.from('2.18'))
return 0
}
if (args.includes('rev-parse')) {
options.listeners.stdline(Buffer.from('refs/heads/foo'))
// If refs/tags/v1 and refs/heads/tags/v1 existed on this repository
options.listeners.errline(
Buffer.from("error: refname 'tags/v1' is ambiguous")
)
return 0
}
return 1
})
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
git = await commandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
let branches = await git.branchList(false)
expect(branches).toHaveLength(1)
expect(branches.sort()).toEqual(['foo'].sort())
})
})
describe('Test fetchDepth and fetchTags options', () => {
beforeEach(async () => {
jest.spyOn(fshelper, 'fileExistsSync').mockImplementation(jest.fn())
jest.spyOn(fshelper, 'directoryExistsSync').mockImplementation(jest.fn())
mockExec.mockImplementation((path, args, options) => {
console.log(args, options.listeners.stdout)
if (args.includes('version')) {
options.listeners.stdout(Buffer.from('2.18'))
}
return 0
})
})
afterEach(() => {
jest.restoreAllMocks()
})
it('should call execGit with the correct arguments when fetchDepth is 0 and fetchTags is true', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
git = await commandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
const refSpec = ['refspec1', 'refspec2']
const options = {
filter: 'filterValue',
fetchDepth: 0,
fetchTags: true
}
await git.fetch(refSpec, options)
expect(mockExec).toHaveBeenCalledWith(
expect.any(String),
[
'-c',
'protocol.version=2',
'fetch',
'--prune',
'--no-recurse-submodules',
'--filter=filterValue',
'origin',
'refspec1',
'refspec2'
],
expect.any(Object)
)
})
it('should call execGit with the correct arguments when fetchDepth is 0 and fetchTags is false', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
git = await commandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
const refSpec = ['refspec1', 'refspec2']
const options = {
filter: 'filterValue',
fetchDepth: 0,
fetchTags: false
}
await git.fetch(refSpec, options)
expect(mockExec).toHaveBeenCalledWith(
expect.any(String),
[
'-c',
'protocol.version=2',
'fetch',
'--no-tags',
'--prune',
'--no-recurse-submodules',
'--filter=filterValue',
'origin',
'refspec1',
'refspec2'
],
expect.any(Object)
)
})
it('should call execGit with the correct arguments when fetchDepth is 1 and fetchTags is false', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
git = await commandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
const refSpec = ['refspec1', 'refspec2']
const options = {
filter: 'filterValue',
fetchDepth: 1,
fetchTags: false
}
await git.fetch(refSpec, options)
expect(mockExec).toHaveBeenCalledWith(
expect.any(String),
[
'-c',
'protocol.version=2',
'fetch',
'--no-tags',
'--prune',
'--no-recurse-submodules',
'--filter=filterValue',
'--depth=1',
'origin',
'refspec1',
'refspec2'
],
expect.any(Object)
)
})
it('should call execGit with the correct arguments when fetchDepth is 1 and fetchTags is true', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
git = await commandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
const refSpec = ['refspec1', 'refspec2']
const options = {
filter: 'filterValue',
fetchDepth: 1,
fetchTags: true
}
await git.fetch(refSpec, options)
expect(mockExec).toHaveBeenCalledWith(
expect.any(String),
[
'-c',
'protocol.version=2',
'fetch',
'--prune',
'--no-recurse-submodules',
'--filter=filterValue',
'--depth=1',
'origin',
'refspec1',
'refspec2'
],
expect.any(Object)
)
})
it('should call execGit with the correct arguments when showProgress is true', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
git = await commandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
const refSpec = ['refspec1', 'refspec2']
const options = {
filter: 'filterValue',
showProgress: true
}
await git.fetch(refSpec, options)
expect(mockExec).toHaveBeenCalledWith(
expect.any(String),
[
'-c',
'protocol.version=2',
'fetch',
'--no-tags',
'--prune',
'--no-recurse-submodules',
'--progress',
'--filter=filterValue',
'origin',
'refspec1',
'refspec2'
],
expect.any(Object)
)
})
it('should call execGit with the correct arguments when fetchDepth is 42 and showProgress is true', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
git = await commandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
const refSpec = ['refspec1', 'refspec2']
const options = {
filter: 'filterValue',
fetchDepth: 42,
showProgress: true
}
await git.fetch(refSpec, options)
expect(mockExec).toHaveBeenCalledWith(
expect.any(String),
[
'-c',
'protocol.version=2',
'fetch',
'--no-tags',
'--prune',
'--no-recurse-submodules',
'--progress',
'--filter=filterValue',
'--depth=42',
'origin',
'refspec1',
'refspec2'
],
expect.any(Object)
)
})
it('should call execGit with the correct arguments when fetchTags is true and showProgress is true', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
git = await commandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
const refSpec = ['refspec1', 'refspec2']
const options = {
filter: 'filterValue',
fetchTags: true,
showProgress: true
}
await git.fetch(refSpec, options)
expect(mockExec).toHaveBeenCalledWith(
expect.any(String),
[
'-c',
'protocol.version=2',
'fetch',
'--prune',
'--no-recurse-submodules',
'--progress',
'--filter=filterValue',
'origin',
'refspec1',
'refspec2'
],
expect.any(Object)
)
})
})

View file

@ -1,507 +0,0 @@
import * as core from '@actions/core'
import * as fs from 'fs'
import * as gitDirectoryHelper from '../lib/git-directory-helper'
import * as io from '@actions/io'
import * as path from 'path'
import {IGitCommandManager} from '../lib/git-command-manager'
const testWorkspace = path.join(__dirname, '_temp', 'git-directory-helper')
let repositoryPath: string
let repositoryUrl: string
let clean: boolean
let ref: string
let git: IGitCommandManager
describe('git-directory-helper tests', () => {
beforeAll(async () => {
// Clear test workspace
await io.rmRF(testWorkspace)
})
beforeEach(() => {
// Mock error/warning/info/debug
jest.spyOn(core, 'error').mockImplementation(jest.fn())
jest.spyOn(core, 'warning').mockImplementation(jest.fn())
jest.spyOn(core, 'info').mockImplementation(jest.fn())
jest.spyOn(core, 'debug').mockImplementation(jest.fn())
})
afterEach(() => {
// Unregister mocks
jest.restoreAllMocks()
})
const cleansWhenCleanTrue = 'cleans when clean true'
it(cleansWhenCleanTrue, async () => {
// Arrange
await setup(cleansWhenCleanTrue)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
// Act
await gitDirectoryHelper.prepareExistingDirectory(
git,
repositoryPath,
repositoryUrl,
clean,
ref
)
// Assert
const files = await fs.promises.readdir(repositoryPath)
expect(files.sort()).toEqual(['.git', 'my-file'])
expect(git.tryClean).toHaveBeenCalled()
expect(git.tryReset).toHaveBeenCalled()
expect(core.warning).not.toHaveBeenCalled()
})
const checkoutDetachWhenNotDetached = 'checkout detach when not detached'
it(checkoutDetachWhenNotDetached, async () => {
// Arrange
await setup(checkoutDetachWhenNotDetached)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
// Act
await gitDirectoryHelper.prepareExistingDirectory(
git,
repositoryPath,
repositoryUrl,
clean,
ref
)
// Assert
const files = await fs.promises.readdir(repositoryPath)
expect(files.sort()).toEqual(['.git', 'my-file'])
expect(git.checkoutDetach).toHaveBeenCalled()
})
const doesNotCheckoutDetachWhenNotAlreadyDetached =
'does not checkout detach when already detached'
it(doesNotCheckoutDetachWhenNotAlreadyDetached, async () => {
// Arrange
await setup(doesNotCheckoutDetachWhenNotAlreadyDetached)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
const mockIsDetached = git.isDetached as jest.Mock<any, any>
mockIsDetached.mockImplementation(async () => {
return true
})
// Act
await gitDirectoryHelper.prepareExistingDirectory(
git,
repositoryPath,
repositoryUrl,
clean,
ref
)
// Assert
const files = await fs.promises.readdir(repositoryPath)
expect(files.sort()).toEqual(['.git', 'my-file'])
expect(git.checkoutDetach).not.toHaveBeenCalled()
})
const doesNotCleanWhenCleanFalse = 'does not clean when clean false'
it(doesNotCleanWhenCleanFalse, async () => {
// Arrange
await setup(doesNotCleanWhenCleanFalse)
clean = false
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
// Act
await gitDirectoryHelper.prepareExistingDirectory(
git,
repositoryPath,
repositoryUrl,
clean,
ref
)
// Assert
const files = await fs.promises.readdir(repositoryPath)
expect(files.sort()).toEqual(['.git', 'my-file'])
expect(git.isDetached).toHaveBeenCalled()
expect(git.branchList).toHaveBeenCalled()
expect(core.warning).not.toHaveBeenCalled()
expect(git.tryClean).not.toHaveBeenCalled()
expect(git.tryReset).not.toHaveBeenCalled()
})
const removesContentsWhenCleanFails = 'removes contents when clean fails'
it(removesContentsWhenCleanFails, async () => {
// Arrange
await setup(removesContentsWhenCleanFails)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
let mockTryClean = git.tryClean as jest.Mock<any, any>
mockTryClean.mockImplementation(async () => {
return false
})
// Act
await gitDirectoryHelper.prepareExistingDirectory(
git,
repositoryPath,
repositoryUrl,
clean,
ref
)
// Assert
const files = await fs.promises.readdir(repositoryPath)
expect(files).toHaveLength(0)
expect(git.tryClean).toHaveBeenCalled()
expect(core.warning).toHaveBeenCalled()
expect(git.tryReset).not.toHaveBeenCalled()
})
const removesContentsWhenDifferentRepositoryUrl =
'removes contents when different repository url'
it(removesContentsWhenDifferentRepositoryUrl, async () => {
// Arrange
await setup(removesContentsWhenDifferentRepositoryUrl)
clean = false
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
const differentRepositoryUrl =
'https://github.com/my-different-org/my-different-repo'
// Act
await gitDirectoryHelper.prepareExistingDirectory(
git,
repositoryPath,
differentRepositoryUrl,
clean,
ref
)
// Assert
const files = await fs.promises.readdir(repositoryPath)
expect(files).toHaveLength(0)
expect(core.warning).not.toHaveBeenCalled()
expect(git.isDetached).not.toHaveBeenCalled()
})
const removesContentsWhenNoGitDirectory =
'removes contents when no git directory'
it(removesContentsWhenNoGitDirectory, async () => {
// Arrange
await setup(removesContentsWhenNoGitDirectory)
clean = false
await io.rmRF(path.join(repositoryPath, '.git'))
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
// Act
await gitDirectoryHelper.prepareExistingDirectory(
git,
repositoryPath,
repositoryUrl,
clean,
ref
)
// Assert
const files = await fs.promises.readdir(repositoryPath)
expect(files).toHaveLength(0)
expect(core.warning).not.toHaveBeenCalled()
expect(git.isDetached).not.toHaveBeenCalled()
})
const removesContentsWhenResetFails = 'removes contents when reset fails'
it(removesContentsWhenResetFails, async () => {
// Arrange
await setup(removesContentsWhenResetFails)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
let mockTryReset = git.tryReset as jest.Mock<any, any>
mockTryReset.mockImplementation(async () => {
return false
})
// Act
await gitDirectoryHelper.prepareExistingDirectory(
git,
repositoryPath,
repositoryUrl,
clean,
ref
)
// Assert
const files = await fs.promises.readdir(repositoryPath)
expect(files).toHaveLength(0)
expect(git.tryClean).toHaveBeenCalled()
expect(git.tryReset).toHaveBeenCalled()
expect(core.warning).toHaveBeenCalled()
})
const removesContentsWhenUndefinedGitCommandManager =
'removes contents when undefined git command manager'
it(removesContentsWhenUndefinedGitCommandManager, async () => {
// Arrange
await setup(removesContentsWhenUndefinedGitCommandManager)
clean = false
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
// Act
await gitDirectoryHelper.prepareExistingDirectory(
undefined,
repositoryPath,
repositoryUrl,
clean,
ref
)
// Assert
const files = await fs.promises.readdir(repositoryPath)
expect(files).toHaveLength(0)
expect(core.warning).not.toHaveBeenCalled()
})
const removesLocalBranches = 'removes local branches'
it(removesLocalBranches, async () => {
// Arrange
await setup(removesLocalBranches)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
const mockBranchList = git.branchList as jest.Mock<any, any>
mockBranchList.mockImplementation(async (remote: boolean) => {
return remote ? [] : ['local-branch-1', 'local-branch-2']
})
// Act
await gitDirectoryHelper.prepareExistingDirectory(
git,
repositoryPath,
repositoryUrl,
clean,
ref
)
// Assert
const files = await fs.promises.readdir(repositoryPath)
expect(files.sort()).toEqual(['.git', 'my-file'])
expect(git.branchDelete).toHaveBeenCalledWith(false, 'local-branch-1')
expect(git.branchDelete).toHaveBeenCalledWith(false, 'local-branch-2')
})
const cleanWhenSubmoduleStatusIsFalse =
'cleans when submodule status is false'
it(cleanWhenSubmoduleStatusIsFalse, async () => {
// Arrange
await setup(cleanWhenSubmoduleStatusIsFalse)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
//mock bad submodule
const submoduleStatus = git.submoduleStatus as jest.Mock<any, any>
submoduleStatus.mockImplementation(async (remote: boolean) => {
return false
})
// Act
await gitDirectoryHelper.prepareExistingDirectory(
git,
repositoryPath,
repositoryUrl,
clean,
ref
)
// Assert
const files = await fs.promises.readdir(repositoryPath)
expect(files).toHaveLength(0)
expect(git.tryClean).toHaveBeenCalled()
})
const doesNotCleanWhenSubmoduleStatusIsTrue =
'does not clean when submodule status is true'
it(doesNotCleanWhenSubmoduleStatusIsTrue, async () => {
// Arrange
await setup(doesNotCleanWhenSubmoduleStatusIsTrue)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
const submoduleStatus = git.submoduleStatus as jest.Mock<any, any>
submoduleStatus.mockImplementation(async (remote: boolean) => {
return true
})
// Act
await gitDirectoryHelper.prepareExistingDirectory(
git,
repositoryPath,
repositoryUrl,
clean,
ref
)
// Assert
const files = await fs.promises.readdir(repositoryPath)
expect(files.sort()).toEqual(['.git', 'my-file'])
expect(git.tryClean).toHaveBeenCalled()
})
const removesLockFiles = 'removes lock files'
it(removesLockFiles, async () => {
// Arrange
await setup(removesLockFiles)
clean = false
await fs.promises.writeFile(
path.join(repositoryPath, '.git', 'index.lock'),
''
)
await fs.promises.writeFile(
path.join(repositoryPath, '.git', 'shallow.lock'),
''
)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
// Act
await gitDirectoryHelper.prepareExistingDirectory(
git,
repositoryPath,
repositoryUrl,
clean,
ref
)
// Assert
let files = await fs.promises.readdir(path.join(repositoryPath, '.git'))
expect(files).toHaveLength(0)
files = await fs.promises.readdir(repositoryPath)
expect(files.sort()).toEqual(['.git', 'my-file'])
expect(git.isDetached).toHaveBeenCalled()
expect(git.branchList).toHaveBeenCalled()
expect(core.warning).not.toHaveBeenCalled()
expect(git.tryClean).not.toHaveBeenCalled()
expect(git.tryReset).not.toHaveBeenCalled()
})
const removesAncestorRemoteBranch = 'removes ancestor remote branch'
it(removesAncestorRemoteBranch, async () => {
// Arrange
await setup(removesAncestorRemoteBranch)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
const mockBranchList = git.branchList as jest.Mock<any, any>
mockBranchList.mockImplementation(async (remote: boolean) => {
return remote ? ['origin/remote-branch-1', 'origin/remote-branch-2'] : []
})
ref = 'remote-branch-1/conflict'
// Act
await gitDirectoryHelper.prepareExistingDirectory(
git,
repositoryPath,
repositoryUrl,
clean,
ref
)
// Assert
const files = await fs.promises.readdir(repositoryPath)
expect(files.sort()).toEqual(['.git', 'my-file'])
expect(git.branchDelete).toHaveBeenCalledTimes(1)
expect(git.branchDelete).toHaveBeenCalledWith(
true,
'origin/remote-branch-1'
)
})
const removesDescendantRemoteBranches = 'removes descendant remote branch'
it(removesDescendantRemoteBranches, async () => {
// Arrange
await setup(removesDescendantRemoteBranches)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
const mockBranchList = git.branchList as jest.Mock<any, any>
mockBranchList.mockImplementation(async (remote: boolean) => {
return remote
? ['origin/remote-branch-1/conflict', 'origin/remote-branch-2']
: []
})
ref = 'remote-branch-1'
// Act
await gitDirectoryHelper.prepareExistingDirectory(
git,
repositoryPath,
repositoryUrl,
clean,
ref
)
// Assert
const files = await fs.promises.readdir(repositoryPath)
expect(files.sort()).toEqual(['.git', 'my-file'])
expect(git.branchDelete).toHaveBeenCalledTimes(1)
expect(git.branchDelete).toHaveBeenCalledWith(
true,
'origin/remote-branch-1/conflict'
)
})
})
async function setup(testName: string): Promise<void> {
testName = testName.replace(/[^a-zA-Z0-9_]+/g, '-')
// Repository directory
repositoryPath = path.join(testWorkspace, testName)
await fs.promises.mkdir(path.join(repositoryPath, '.git'), {recursive: true})
// Repository URL
repositoryUrl = 'https://github.com/my-org/my-repo'
// Clean
clean = true
// Ref
ref = ''
// Git command manager
git = {
branchDelete: jest.fn(),
branchExists: jest.fn(),
branchList: jest.fn(async () => {
return []
}),
disableSparseCheckout: jest.fn(),
sparseCheckout: jest.fn(),
sparseCheckoutNonConeMode: jest.fn(),
checkout: jest.fn(),
checkoutDetach: jest.fn(),
config: jest.fn(),
configExists: jest.fn(),
fetch: jest.fn(),
getDefaultBranch: jest.fn(),
getWorkingDirectory: jest.fn(() => repositoryPath),
init: jest.fn(),
isDetached: jest.fn(),
lfsFetch: jest.fn(),
lfsInstall: jest.fn(),
log1: jest.fn(),
remoteAdd: jest.fn(),
removeEnvironmentVariable: jest.fn(),
revParse: jest.fn(),
setEnvironmentVariable: jest.fn(),
shaExists: jest.fn(),
submoduleForeach: jest.fn(),
submoduleSync: jest.fn(),
submoduleUpdate: jest.fn(),
submoduleStatus: jest.fn(async () => {
return true
}),
tagExists: jest.fn(),
tryClean: jest.fn(async () => {
return true
}),
tryConfigUnset: jest.fn(),
tryDisableAutomaticGarbageCollection: jest.fn(),
tryGetFetchUrl: jest.fn(async () => {
// Sanity check - this function shouldn't be called when the .git directory doesn't exist
await fs.promises.stat(path.join(repositoryPath, '.git'))
return repositoryUrl
}),
tryReset: jest.fn(async () => {
return true
}),
version: jest.fn()
}
}

View file

@ -1,86 +0,0 @@
import {GitVersion} from '../src/git-version'
import {MinimumGitSparseCheckoutVersion} from '../src/git-command-manager'
describe('git-version tests', () => {
it('basics', async () => {
let version = new GitVersion('')
expect(version.isValid()).toBeFalsy()
version = new GitVersion('asdf')
expect(version.isValid()).toBeFalsy()
version = new GitVersion('1.2')
expect(version.isValid()).toBeTruthy()
expect(version.toString()).toBe('1.2')
version = new GitVersion('1.2.3')
expect(version.isValid()).toBeTruthy()
expect(version.toString()).toBe('1.2.3')
})
it('check minimum', async () => {
let version = new GitVersion('4.5')
expect(version.checkMinimum(new GitVersion('3.6'))).toBeTruthy()
expect(version.checkMinimum(new GitVersion('3.6.7'))).toBeTruthy()
expect(version.checkMinimum(new GitVersion('4.4'))).toBeTruthy()
expect(version.checkMinimum(new GitVersion('4.5'))).toBeTruthy()
expect(version.checkMinimum(new GitVersion('4.5.0'))).toBeTruthy()
expect(version.checkMinimum(new GitVersion('4.6'))).toBeFalsy()
expect(version.checkMinimum(new GitVersion('4.6.0'))).toBeFalsy()
expect(version.checkMinimum(new GitVersion('5.1'))).toBeFalsy()
expect(version.checkMinimum(new GitVersion('5.1.2'))).toBeFalsy()
version = new GitVersion('4.5.6')
expect(version.checkMinimum(new GitVersion('3.6'))).toBeTruthy()
expect(version.checkMinimum(new GitVersion('3.6.7'))).toBeTruthy()
expect(version.checkMinimum(new GitVersion('4.4'))).toBeTruthy()
expect(version.checkMinimum(new GitVersion('4.5'))).toBeTruthy()
expect(version.checkMinimum(new GitVersion('4.5.5'))).toBeTruthy()
expect(version.checkMinimum(new GitVersion('4.5.6'))).toBeTruthy()
expect(version.checkMinimum(new GitVersion('4.5.7'))).toBeFalsy()
expect(version.checkMinimum(new GitVersion('4.6'))).toBeFalsy()
expect(version.checkMinimum(new GitVersion('4.6.0'))).toBeFalsy()
expect(version.checkMinimum(new GitVersion('5.1'))).toBeFalsy()
expect(version.checkMinimum(new GitVersion('5.1.2'))).toBeFalsy()
})
it('sparse checkout', async () => {
const minSparseVer = MinimumGitSparseCheckoutVersion
expect(new GitVersion('1.0').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('1.99').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.0').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.24').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.24.0').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.24.9').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.25').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.25.0').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.25.1').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.25.9').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.26').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.26.0').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.26.1').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.26.9').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.27').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.27.0').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.27.1').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.27.9').checkMinimum(minSparseVer)).toBeFalsy()
// /---------------------------------------
// ^^^ before / after vvv
// --------------------------/
expect(new GitVersion('2.28').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('2.28.0').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('2.28.1').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('2.28.9').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('2.29').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('2.29.0').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('2.29.1').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('2.29.9').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('2.99').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('3.0').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('3.99').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('4.0').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('4.99').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('5.0').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('5.99').checkMinimum(minSparseVer)).toBeTruthy()
})
})

View file

@ -1,147 +0,0 @@
import * as core from '@actions/core'
import * as fsHelper from '../lib/fs-helper'
import * as github from '@actions/github'
import * as inputHelper from '../lib/input-helper'
import * as path from 'path'
import * as workflowContextHelper from '../lib/workflow-context-helper'
import {IGitSourceSettings} from '../lib/git-source-settings'
const originalGitHubWorkspace = process.env['GITHUB_WORKSPACE']
const gitHubWorkspace = path.resolve('/checkout-tests/workspace')
// Inputs for mock @actions/core
let inputs = {} as any
// Shallow clone original @actions/github context
let originalContext = {...github.context}
describe('input-helper tests', () => {
beforeAll(() => {
// Mock getInput
jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
return inputs[name]
})
// Mock error/warning/info/debug
jest.spyOn(core, 'error').mockImplementation(jest.fn())
jest.spyOn(core, 'warning').mockImplementation(jest.fn())
jest.spyOn(core, 'info').mockImplementation(jest.fn())
jest.spyOn(core, 'debug').mockImplementation(jest.fn())
// Mock github context
jest.spyOn(github.context, 'repo', 'get').mockImplementation(() => {
return {
owner: 'some-owner',
repo: 'some-repo'
}
})
github.context.ref = 'refs/heads/some-ref'
github.context.sha = '1234567890123456789012345678901234567890'
// Mock ./fs-helper directoryExistsSync()
jest
.spyOn(fsHelper, 'directoryExistsSync')
.mockImplementation((path: string) => path == gitHubWorkspace)
// Mock ./workflowContextHelper getOrganizationId()
jest
.spyOn(workflowContextHelper, 'getOrganizationId')
.mockImplementation(() => Promise.resolve(123456))
// GitHub workspace
process.env['GITHUB_WORKSPACE'] = gitHubWorkspace
})
beforeEach(() => {
// Reset inputs
inputs = {}
})
afterAll(() => {
// Restore GitHub workspace
delete process.env['GITHUB_WORKSPACE']
if (originalGitHubWorkspace) {
process.env['GITHUB_WORKSPACE'] = originalGitHubWorkspace
}
// Restore @actions/github context
github.context.ref = originalContext.ref
github.context.sha = originalContext.sha
// Restore
jest.restoreAllMocks()
})
it('sets defaults', async () => {
const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings).toBeTruthy()
expect(settings.authToken).toBeFalsy()
expect(settings.clean).toBe(true)
expect(settings.commit).toBeTruthy()
expect(settings.commit).toBe('1234567890123456789012345678901234567890')
expect(settings.filter).toBe(undefined)
expect(settings.sparseCheckout).toBe(undefined)
expect(settings.sparseCheckoutConeMode).toBe(true)
expect(settings.fetchDepth).toBe(1)
expect(settings.fetchTags).toBe(false)
expect(settings.showProgress).toBe(true)
expect(settings.lfs).toBe(false)
expect(settings.ref).toBe('refs/heads/some-ref')
expect(settings.repositoryName).toBe('some-repo')
expect(settings.repositoryOwner).toBe('some-owner')
expect(settings.repositoryPath).toBe(gitHubWorkspace)
expect(settings.setSafeDirectory).toBe(true)
})
it('qualifies ref', async () => {
let originalRef = github.context.ref
try {
github.context.ref = 'some-unqualified-ref'
const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings).toBeTruthy()
expect(settings.commit).toBe('1234567890123456789012345678901234567890')
expect(settings.ref).toBe('refs/heads/some-unqualified-ref')
} finally {
github.context.ref = originalRef
}
})
it('requires qualified repo', async () => {
inputs.repository = 'some-unqualified-repo'
try {
await inputHelper.getInputs()
throw 'should not reach here'
} catch (err) {
expect(`(${(err as any).message}`).toMatch(
"Invalid repository 'some-unqualified-repo'"
)
}
})
it('roots path', async () => {
inputs.path = 'some-directory/some-subdirectory'
const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings.repositoryPath).toBe(
path.join(gitHubWorkspace, 'some-directory', 'some-subdirectory')
)
})
it('sets ref to empty when explicit sha', async () => {
inputs.ref = '1111111111222222222233333333334444444444'
const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings.ref).toBeFalsy()
expect(settings.commit).toBe('1111111111222222222233333333334444444444')
})
it('sets sha to empty when explicit ref', async () => {
inputs.ref = 'refs/heads/some-other-ref'
const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings.ref).toBe('refs/heads/some-other-ref')
expect(settings.commit).toBeFalsy()
})
it('sets workflow organization ID', async () => {
const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings.workflowOrganizationId).toBe(123456)
})
})

View file

@ -1,10 +0,0 @@
#!/bin/bash
if [ ! -f "./basic/basic-file.txt" ]; then
echo "Expected basic file does not exist"
exit 1
fi
echo hello >> ./basic/basic-file.txt
echo hello >> ./basic/new-file.txt
git -C ./basic status

View file

@ -1,6 +0,0 @@
mkdir override-git-version
cd override-git-version
echo @echo override git version 1.2.3 > git.cmd
echo "%CD%" >> $GITHUB_PATH
cd ..

View file

@ -1,9 +0,0 @@
#!/bin/sh
mkdir override-git-version
cd override-git-version
echo "#!/bin/sh" > git
echo "echo override git version 1.2.3" >> git
chmod +x git
echo "$(pwd)" >> $GITHUB_PATH
cd ..

View file

@ -1,190 +0,0 @@
import * as assert from 'assert'
import * as refHelper from '../lib/ref-helper'
import {IGitCommandManager} from '../lib/git-command-manager'
const commit = '1234567890123456789012345678901234567890'
let git: IGitCommandManager
describe('ref-helper tests', () => {
beforeEach(() => {
git = {} as unknown as IGitCommandManager
})
it('getCheckoutInfo requires git', async () => {
const git = null as unknown as IGitCommandManager
try {
await refHelper.getCheckoutInfo(git, 'refs/heads/my/branch', commit)
throw new Error('Should not reach here')
} catch (err) {
expect((err as any)?.message).toBe('Arg git cannot be empty')
}
})
it('getCheckoutInfo requires ref or commit', async () => {
try {
await refHelper.getCheckoutInfo(git, '', '')
throw new Error('Should not reach here')
} catch (err) {
expect((err as any)?.message).toBe(
'Args ref and commit cannot both be empty'
)
}
})
it('getCheckoutInfo sha only', async () => {
const checkoutInfo = await refHelper.getCheckoutInfo(git, '', commit)
expect(checkoutInfo.ref).toBe(commit)
expect(checkoutInfo.startPoint).toBeFalsy()
})
it('getCheckoutInfo refs/heads/', async () => {
const checkoutInfo = await refHelper.getCheckoutInfo(
git,
'refs/heads/my/branch',
commit
)
expect(checkoutInfo.ref).toBe('my/branch')
expect(checkoutInfo.startPoint).toBe('refs/remotes/origin/my/branch')
})
it('getCheckoutInfo refs/pull/', async () => {
const checkoutInfo = await refHelper.getCheckoutInfo(
git,
'refs/pull/123/merge',
commit
)
expect(checkoutInfo.ref).toBe('refs/remotes/pull/123/merge')
expect(checkoutInfo.startPoint).toBeFalsy()
})
it('getCheckoutInfo refs/tags/', async () => {
const checkoutInfo = await refHelper.getCheckoutInfo(
git,
'refs/tags/my-tag',
commit
)
expect(checkoutInfo.ref).toBe('refs/tags/my-tag')
expect(checkoutInfo.startPoint).toBeFalsy()
})
it('getCheckoutInfo refs/', async () => {
const checkoutInfo = await refHelper.getCheckoutInfo(
git,
'refs/gh/queue/main/pr-123',
commit
)
expect(checkoutInfo.ref).toBe(commit)
expect(checkoutInfo.startPoint).toBeFalsy()
})
it('getCheckoutInfo refs/ without commit', async () => {
const checkoutInfo = await refHelper.getCheckoutInfo(
git,
'refs/non-standard-ref',
''
)
expect(checkoutInfo.ref).toBe('refs/non-standard-ref')
expect(checkoutInfo.startPoint).toBeFalsy()
})
it('getCheckoutInfo unqualified branch only', async () => {
git.branchExists = jest.fn(async (remote: boolean, pattern: string) => {
return true
})
const checkoutInfo = await refHelper.getCheckoutInfo(git, 'my/branch', '')
expect(checkoutInfo.ref).toBe('my/branch')
expect(checkoutInfo.startPoint).toBe('refs/remotes/origin/my/branch')
})
it('getCheckoutInfo unqualified tag only', async () => {
git.branchExists = jest.fn(async (remote: boolean, pattern: string) => {
return false
})
git.tagExists = jest.fn(async (pattern: string) => {
return true
})
const checkoutInfo = await refHelper.getCheckoutInfo(git, 'my-tag', '')
expect(checkoutInfo.ref).toBe('refs/tags/my-tag')
expect(checkoutInfo.startPoint).toBeFalsy()
})
it('getCheckoutInfo unqualified ref only, not a branch or tag', async () => {
git.branchExists = jest.fn(async (remote: boolean, pattern: string) => {
return false
})
git.tagExists = jest.fn(async (pattern: string) => {
return false
})
try {
await refHelper.getCheckoutInfo(git, 'my-ref', '')
throw new Error('Should not reach here')
} catch (err) {
expect((err as any)?.message).toBe(
"A branch or tag with the name 'my-ref' could not be found"
)
}
})
it('getRefSpec requires ref or commit', async () => {
assert.throws(
() => refHelper.getRefSpec('', ''),
/Args ref and commit cannot both be empty/
)
})
it('getRefSpec sha + refs/heads/', async () => {
const refSpec = refHelper.getRefSpec('refs/heads/my/branch', commit)
expect(refSpec.length).toBe(1)
expect(refSpec[0]).toBe(`+${commit}:refs/remotes/origin/my/branch`)
})
it('getRefSpec sha + refs/pull/', async () => {
const refSpec = refHelper.getRefSpec('refs/pull/123/merge', commit)
expect(refSpec.length).toBe(1)
expect(refSpec[0]).toBe(`+${commit}:refs/remotes/pull/123/merge`)
})
it('getRefSpec sha + refs/tags/', async () => {
const refSpec = refHelper.getRefSpec('refs/tags/my-tag', commit)
expect(refSpec.length).toBe(1)
expect(refSpec[0]).toBe(`+${commit}:refs/tags/my-tag`)
})
it('getRefSpec sha only', async () => {
const refSpec = refHelper.getRefSpec('', commit)
expect(refSpec.length).toBe(1)
expect(refSpec[0]).toBe(commit)
})
it('getRefSpec unqualified ref only', async () => {
const refSpec = refHelper.getRefSpec('my-ref', '')
expect(refSpec.length).toBe(2)
expect(refSpec[0]).toBe('+refs/heads/my-ref*:refs/remotes/origin/my-ref*')
expect(refSpec[1]).toBe('+refs/tags/my-ref*:refs/tags/my-ref*')
})
it('getRefSpec refs/heads/ only', async () => {
const refSpec = refHelper.getRefSpec('refs/heads/my/branch', '')
expect(refSpec.length).toBe(1)
expect(refSpec[0]).toBe(
'+refs/heads/my/branch:refs/remotes/origin/my/branch'
)
})
it('getRefSpec refs/pull/ only', async () => {
const refSpec = refHelper.getRefSpec('refs/pull/123/merge', '')
expect(refSpec.length).toBe(1)
expect(refSpec[0]).toBe('+refs/pull/123/merge:refs/remotes/pull/123/merge')
})
it('getRefSpec refs/tags/ only', async () => {
const refSpec = refHelper.getRefSpec('refs/tags/my-tag', '')
expect(refSpec.length).toBe(1)
expect(refSpec[0]).toBe('+refs/tags/my-tag:refs/tags/my-tag')
})
})

View file

@ -1,87 +0,0 @@
import * as core from '@actions/core'
import {RetryHelper} from '../lib/retry-helper'
let info: string[]
let retryHelper: any
describe('retry-helper tests', () => {
beforeAll(() => {
// Mock @actions/core info()
jest.spyOn(core, 'info').mockImplementation((message: string) => {
info.push(message)
})
retryHelper = new RetryHelper(3, 0, 0)
})
beforeEach(() => {
// Reset info
info = []
})
afterAll(() => {
// Restore
jest.restoreAllMocks()
})
it('first attempt succeeds', async () => {
const actual = await retryHelper.execute(async () => {
return 'some result'
})
expect(actual).toBe('some result')
expect(info).toHaveLength(0)
})
it('second attempt succeeds', async () => {
let attempts = 0
const actual = await retryHelper.execute(() => {
if (++attempts == 1) {
throw new Error('some error')
}
return Promise.resolve('some result')
})
expect(attempts).toBe(2)
expect(actual).toBe('some result')
expect(info).toHaveLength(2)
expect(info[0]).toBe('some error')
expect(info[1]).toMatch(/Waiting .+ seconds before trying again/)
})
it('third attempt succeeds', async () => {
let attempts = 0
const actual = await retryHelper.execute(() => {
if (++attempts < 3) {
throw new Error(`some error ${attempts}`)
}
return Promise.resolve('some result')
})
expect(attempts).toBe(3)
expect(actual).toBe('some result')
expect(info).toHaveLength(4)
expect(info[0]).toBe('some error 1')
expect(info[1]).toMatch(/Waiting .+ seconds before trying again/)
expect(info[2]).toBe('some error 2')
expect(info[3]).toMatch(/Waiting .+ seconds before trying again/)
})
it('all attempts fail succeeds', async () => {
let attempts = 0
let error: Error = null as unknown as Error
try {
await retryHelper.execute(() => {
throw new Error(`some error ${++attempts}`)
})
} catch (err) {
error = err as Error
}
expect(error.message).toBe('some error 3')
expect(attempts).toBe(3)
expect(info).toHaveLength(4)
expect(info[0]).toBe('some error 1')
expect(info[1]).toMatch(/Waiting .+ seconds before trying again/)
expect(info[2]).toBe('some error 2')
expect(info[3]).toMatch(/Waiting .+ seconds before trying again/)
})
})

View file

@ -1,92 +0,0 @@
import * as urlHelper from '../src/url-helper'
describe('getServerUrl tests', () => {
it('basics', async () => {
// Note that URL::toString will append a trailing / when passed just a domain name ...
expect(urlHelper.getServerUrl().toString()).toBe('https://github.com/')
expect(urlHelper.getServerUrl(' ').toString()).toBe('https://github.com/')
expect(urlHelper.getServerUrl(' ').toString()).toBe('https://github.com/')
expect(urlHelper.getServerUrl('http://contoso.com').toString()).toBe(
'http://contoso.com/'
)
expect(urlHelper.getServerUrl('https://contoso.com').toString()).toBe(
'https://contoso.com/'
)
expect(urlHelper.getServerUrl('https://contoso.com/').toString()).toBe(
'https://contoso.com/'
)
// ... but can't make that same assumption when passed an URL that includes some deeper path.
expect(urlHelper.getServerUrl('https://contoso.com/a/b').toString()).toBe(
'https://contoso.com/a/b'
)
})
})
describe('isGhes tests', () => {
const pristineEnv = process.env
beforeEach(() => {
jest.resetModules()
process.env = {...pristineEnv}
})
afterAll(() => {
process.env = pristineEnv
})
it('basics', async () => {
delete process.env['GITHUB_SERVER_URL']
expect(urlHelper.isGhes()).toBeFalsy()
expect(urlHelper.isGhes('https://github.com')).toBeFalsy()
expect(urlHelper.isGhes('https://contoso.ghe.com')).toBeFalsy()
expect(urlHelper.isGhes('https://test.github.localhost')).toBeFalsy()
expect(urlHelper.isGhes('https://src.onpremise.fabrikam.com')).toBeTruthy()
})
it('returns false when the GITHUB_SERVER_URL environment variable is not defined', async () => {
delete process.env['GITHUB_SERVER_URL']
expect(urlHelper.isGhes()).toBeFalsy()
})
it('returns false when the GITHUB_SERVER_URL environment variable is set to github.com', async () => {
process.env['GITHUB_SERVER_URL'] = 'https://github.com'
expect(urlHelper.isGhes()).toBeFalsy()
})
it('returns false when the GITHUB_SERVER_URL environment variable is set to a GitHub Enterprise Cloud-style URL', async () => {
process.env['GITHUB_SERVER_URL'] = 'https://contoso.ghe.com'
expect(urlHelper.isGhes()).toBeFalsy()
})
it('returns false when the GITHUB_SERVER_URL environment variable has a .localhost suffix', async () => {
process.env['GITHUB_SERVER_URL'] = 'https://mock-github.localhost'
expect(urlHelper.isGhes()).toBeFalsy()
})
it('returns true when the GITHUB_SERVER_URL environment variable is set to some other URL', async () => {
process.env['GITHUB_SERVER_URL'] = 'https://src.onpremise.fabrikam.com'
expect(urlHelper.isGhes()).toBeTruthy()
})
})
describe('getServerApiUrl tests', () => {
it('basics', async () => {
expect(urlHelper.getServerApiUrl()).toBe('https://api.github.com')
expect(urlHelper.getServerApiUrl('https://github.com')).toBe(
'https://api.github.com'
)
expect(urlHelper.getServerApiUrl('https://GitHub.com')).toBe(
'https://api.github.com'
)
expect(urlHelper.getServerApiUrl('https://contoso.ghe.com')).toBe(
'https://api.contoso.ghe.com'
)
expect(urlHelper.getServerApiUrl('https://fabrikam.GHE.COM')).toBe(
'https://api.fabrikam.ghe.com'
)
expect(
urlHelper.getServerApiUrl('https://src.onpremise.fabrikam.com')
).toBe('https://src.onpremise.fabrikam.com/api/v3')
})
})

View file

@ -1,38 +0,0 @@
#!/bin/sh
if [ ! -f "./basic/basic-file.txt" ]; then
echo "Expected basic file does not exist"
exit 1
fi
if [ "$1" = "--archive" ]; then
# Verify no .git folder
if [ -d "./basic/.git" ]; then
echo "Did not expect ./basic/.git folder to exist"
exit 1
fi
else
# Verify .git folder
if [ ! -d "./basic/.git" ]; then
echo "Expected ./basic/.git folder to exist"
exit 1
fi
# Verify that sparse-checkout is disabled.
SPARSE_CHECKOUT_ENABLED=$(git -C ./basic config --local --get-all core.sparseCheckout)
if [ "$SPARSE_CHECKOUT_ENABLED" != "" ]; then
echo "Expected sparse-checkout to be disabled (discovered: $SPARSE_CHECKOUT_ENABLED)"
exit 1
fi
# Verify git configuration shows worktreeConfig is effectively disabled
WORKTREE_CONFIG_ENABLED=$(git -C ./basic config --local --get-all extensions.worktreeConfig)
if [[ "$WORKTREE_CONFIG_ENABLED" != "" ]]; then
echo "Expected extensions.worktreeConfig (boolean) to be disabled in git config. This could be an artifact of sparse checkout functionality."
exit 1
fi
# Verify auth token
cd basic
git fetch --no-tags --depth=1 origin +refs/heads/main:refs/remotes/origin/main
fi

View file

@ -1,13 +0,0 @@
#!/bin/bash
if [[ "$(git -C ./basic status --porcelain)" != "" ]]; then
echo ----------------------------------------
echo git status
echo ----------------------------------------
git status
echo ----------------------------------------
echo git diff
echo ----------------------------------------
git diff
exit 1
fi

View file

@ -1,16 +0,0 @@
#!/bin/bash
# Verify .git folder
if [ ! -d "./fetch-filter/.git" ]; then
echo "Expected ./fetch-filter/.git folder to exist"
exit 1
fi
# Verify .git/config contains partialclonefilter
CLONE_FILTER=$(git -C fetch-filter config --local --get remote.origin.partialclonefilter)
if [ "$CLONE_FILTER" != "blob:none" ]; then
echo "Expected ./fetch-filter/.git/config to have 'remote.origin.partialclonefilter' set to 'blob:none'"
exit 1
fi

View file

@ -1,11 +0,0 @@
#!/bin/bash
if [ ! -f "./lfs/regular-file.txt" ]; then
echo "Expected regular file does not exist"
exit 1
fi
if [ ! -f "./lfs/lfs-file.bin" ]; then
echo "Expected lfs file does not exist"
exit 1
fi

View file

@ -1,17 +0,0 @@
#!/bin/bash
if [[ "$(git status --porcelain)" != "" ]]; then
echo ----------------------------------------
echo git status
echo ----------------------------------------
git status
echo ----------------------------------------
echo git diff
echo ----------------------------------------
git diff
echo ----------------------------------------
echo Troubleshooting
echo ----------------------------------------
echo "::error::Unstaged changes detected. Locally try running: git clean -ffdx && npm ci && npm run format && npm run build"
exit 1
fi

View file

@ -1,11 +0,0 @@
#!/bin/bash
if [ ! -f "./side-by-side-1/side-by-side-test-file-1.txt" ]; then
echo "Expected file 1 does not exist"
exit 1
fi
if [ ! -f "./side-by-side-2/side-by-side-test-file-2.txt" ]; then
echo "Expected file 2 does not exist"
exit 1
fi

View file

@ -1,51 +0,0 @@
#!/bin/bash
# Verify .git folder
if [ ! -d "./sparse-checkout-non-cone-mode/.git" ]; then
echo "Expected ./sparse-checkout-non-cone-mode/.git folder to exist"
exit 1
fi
# Verify sparse-checkout (non-cone-mode)
cd sparse-checkout-non-cone-mode
ENABLED=$(git config --local --get-all core.sparseCheckout)
if [ "$?" != "0" ]; then
echo "Failed to verify that sparse-checkout is enabled"
exit 1
fi
# Check that sparse-checkout is enabled
if [ "$ENABLED" != "true" ]; then
echo "Expected sparse-checkout to be enabled (is: $ENABLED)"
exit 1
fi
SPARSE_CHECKOUT_FILE=$(git rev-parse --git-path info/sparse-checkout)
if [ "$?" != "0" ]; then
echo "Failed to validate sparse-checkout"
exit 1
fi
# Check that sparse-checkout list is not empty
if [ ! -f "$SPARSE_CHECKOUT_FILE" ]; then
echo "Expected sparse-checkout file to exist"
exit 1
fi
# Check that all folders from sparse-checkout exists
for pattern in $(cat "$SPARSE_CHECKOUT_FILE")
do
if [ ! -d "${pattern#/}" ]; then
echo "Expected directory '${pattern#/}' to exist"
exit 1
fi
done
# Verify that the root directory is not checked out
if [ -f README.md ]; then
echo "Expected top-level files not to exist"
exit 1
fi

View file

@ -1,63 +0,0 @@
#!/bin/bash
# Verify .git folder
if [ ! -d "./sparse-checkout/.git" ]; then
echo "Expected ./sparse-checkout/.git folder to exist"
exit 1
fi
# Verify sparse-checkout
cd sparse-checkout
SPARSE=$(git sparse-checkout list)
if [ "$?" != "0" ]; then
echo "Failed to validate sparse-checkout"
exit 1
fi
# Check that sparse-checkout list is not empty
if [ -z "$SPARSE" ]; then
echo "Expected sparse-checkout list to not be empty"
exit 1
fi
# Check that all folders of the sparse checkout exist
for pattern in $SPARSE
do
if [ ! -d "$pattern" ]; then
echo "Expected directory '$pattern' to exist"
exit 1
fi
done
checkSparse () {
if [ ! -d "./$1" ]; then
echo "Expected directory '$1' to exist"
exit 1
fi
for file in $(git ls-tree -r --name-only HEAD $1)
do
if [ ! -f "$file" ]; then
echo "Expected file '$file' to exist"
exit 1
fi
done
}
# Check that all folders and their children have been checked out
checkSparse __test__
checkSparse .github
checkSparse dist
# Check that only sparse-checkout folders have been checked out
for pattern in $(git ls-tree --name-only HEAD)
do
if [ -d "$pattern" ]; then
if [[ "$pattern" != "__test__" && "$pattern" != ".github" && "$pattern" != "dist" ]]; then
echo "Expected directory '$pattern' to not exist"
exit 1
fi
fi
done

View file

@ -1,11 +0,0 @@
#!/bin/bash
if [ ! -f "./submodules-false/regular-file.txt" ]; then
echo "Expected regular file does not exist"
exit 1
fi
if [ -f "./submodules-false/submodule-level-1/submodule-file.txt" ]; then
echo "Unexpected submodule file exists"
exit 1
fi

View file

@ -1,26 +0,0 @@
#!/bin/bash
if [ ! -f "./submodules-recursive/regular-file.txt" ]; then
echo "Expected regular file does not exist"
exit 1
fi
if [ ! -f "./submodules-recursive/submodule-level-1/submodule-file.txt" ]; then
echo "Expected submodule file does not exist"
exit 1
fi
if [ ! -f "./submodules-recursive/submodule-level-1/submodule-level-2/nested-submodule-file.txt" ]; then
echo "Expected nested submodule file does not exists"
exit 1
fi
echo "Testing persisted credential"
pushd ./submodules-recursive/submodule-level-1/submodule-level-2
git config --local --name-only --get-regexp http.+extraheader && git fetch
if [ "$?" != "0" ]; then
echo "Failed to validate persisted credential"
popd
exit 1
fi
popd

View file

@ -1,26 +0,0 @@
#!/bin/bash
if [ ! -f "./submodules-true/regular-file.txt" ]; then
echo "Expected regular file does not exist"
exit 1
fi
if [ ! -f "./submodules-true/submodule-level-1/submodule-file.txt" ]; then
echo "Expected submodule file does not exist"
exit 1
fi
if [ -f "./submodules-true/submodule-level-1/submodule-level-2/nested-submodule-file.txt" ]; then
echo "Unexpected nested submodule file exists"
exit 1
fi
echo "Testing persisted credential"
pushd ./submodules-true/submodule-level-1
git config --local --name-only --get-regexp http.+extraheader && git fetch
if [ "$?" != "0" ]; then
echo "Failed to validate persisted credential"
popd
exit 1
fi
popd

View file

@ -1,12 +0,0 @@
# Defines the test-ubuntu-git Container Image.
# Consumed by actions/checkout CI/CD validation workflows.
FROM ubuntu:latest
RUN apt update
RUN apt install -y git
LABEL org.opencontainers.image.title="Ubuntu + git (validation image)"
LABEL org.opencontainers.image.description="Ubuntu image with git pre-installed. Intended primarily for testing `actions/checkout` during CI/CD workflows."
LABEL org.opencontainers.image.documentation="https://github.com/actions/checkout/tree/main/images/test-ubuntu-git.md"
LABEL org.opencontainers.image.licenses=MIT

View file

@ -1,15 +0,0 @@
# `test-ubuntu-git` Container Image
[![Publish test-ubuntu-git Container](https://github.com/actions/checkout/actions/workflows/update-test-ubuntu-git.yml/badge.svg)](https://github.com/actions/checkout/actions/workflows/update-test-ubuntu-git.yml)
## Purpose
`test-ubuntu-git` is a container image hosted on the GitHub Container Registry, `ghcr.io`.
It is intended primarily for testing the [`actions/checkout` repository](https://github.com/actions/checkout) as part of `actions/checkout`'s CI/CD workflows.
The composition of `test-ubuntu-git` is intentionally minimal. It is comprised of [git](https://git-scm.com/) installed on top of a [base-level ubuntu image](https://hub.docker.com/_/ubuntu/tags).
# License
`test-ubuntu-git` is released under the [MIT License](/LICENSE).