1
0
mirror of https://github.com/actions/checkout.git synced 2026-03-07 08:51:46 +08:00

Compare commits

...

5 Commits

Author SHA1 Message Date
Marcus Tillmanns
e1b648440e
Merge 7618b1f401 into 8e8c483db8 2025-12-02 07:52:44 +01:00
eric sciple
8e8c483db8
Clarify v6 README (#2328) 2025-12-01 20:08:49 -06:00
eric sciple
033fa0dc0b
Add worktree support for persist-credentials includeIf (#2327) 2025-12-01 19:53:23 -06:00
Marcus Tillmanns
7618b1f401 Simplified the submoduleDirectories 2024-08-28 13:16:59 +02:00
Marcus Tillmanns
b6625bb44a Add string[] option to submodules
Allows checking out only specific submodules instead of all
2024-08-27 10:37:37 +02:00
13 changed files with 176 additions and 15 deletions

View File

@ -154,6 +154,17 @@ jobs:
submodules: true submodules: true
- name: Verify submodules true - name: Verify submodules true
run: __test__/verify-submodules-true.sh run: __test__/verify-submodules-true.sh
# Submodules limited
- name: Checkout submodules limited
uses: ./
with:
ref: test-data/v2/submodule-ssh-url
path: submodules-true
submodules: true
submodule-directories: submodule-level-1
- name: Verify submodules true
run: __test__/verify-submodules-true.sh
# Submodules recursive # Submodules recursive
- name: Checkout submodules recursive - name: Checkout submodules recursive
@ -165,6 +176,22 @@ jobs:
- name: Verify submodules recursive - name: Verify submodules recursive
run: __test__/verify-submodules-recursive.sh run: __test__/verify-submodules-recursive.sh
# Worktree credentials
- name: Checkout for worktree test
uses: ./
with:
path: worktree-test
- name: Verify worktree credentials
shell: bash
run: __test__/verify-worktree.sh worktree-test worktree-branch
# Worktree credentials in container step
- name: Verify worktree credentials in container step
if: runner.os == 'Linux'
uses: docker://bitnami/git:latest
with:
args: bash __test__/verify-worktree.sh worktree-test container-worktree-branch
# Basic checkout using REST API # Basic checkout using REST API
- name: Remove basic - name: Remove basic
if: runner.os != 'windows' if: runner.os != 'windows'

View File

@ -1,19 +1,19 @@
# Changelog # Changelog
## V6.0.0 ## v6.0.0
* Persist creds to a separate file by @ericsciple in https://github.com/actions/checkout/pull/2286 * Persist creds to a separate file by @ericsciple in https://github.com/actions/checkout/pull/2286
* Update README to include Node.js 24 support details and requirements by @salmanmkc in https://github.com/actions/checkout/pull/2248 * Update README to include Node.js 24 support details and requirements by @salmanmkc in https://github.com/actions/checkout/pull/2248
## V5.0.1 ## v5.0.1
* Port v6 cleanup to v5 by @ericsciple in https://github.com/actions/checkout/pull/2301 * Port v6 cleanup to v5 by @ericsciple in https://github.com/actions/checkout/pull/2301
## V5.0.0 ## v5.0.0
* Update actions checkout to use node 24 by @salmanmkc in https://github.com/actions/checkout/pull/2226 * Update actions checkout to use node 24 by @salmanmkc in https://github.com/actions/checkout/pull/2226
## V4.3.1 ## v4.3.1
* Port v6 cleanup to v4 by @ericsciple in https://github.com/actions/checkout/pull/2305 * Port v6 cleanup to v4 by @ericsciple in https://github.com/actions/checkout/pull/2305
## V4.3.0 ## v4.3.0
* docs: update README.md by @motss in https://github.com/actions/checkout/pull/1971 * docs: update README.md by @motss in https://github.com/actions/checkout/pull/1971
* Add internal repos for checking out multiple repositories by @mouismail in https://github.com/actions/checkout/pull/1977 * Add internal repos for checking out multiple repositories by @mouismail in https://github.com/actions/checkout/pull/1977
* Documentation update - add recommended permissions to Readme by @benwells in https://github.com/actions/checkout/pull/2043 * Documentation update - add recommended permissions to Readme by @benwells in https://github.com/actions/checkout/pull/2043

View File

@ -4,8 +4,9 @@
## What's new ## What's new
- Updated `persist-credentials` to store the credentials under `$RUNNER_TEMP` instead of directly in the local git config. - Improved credential security: `persist-credentials` now stores credentials in a separate file under `$RUNNER_TEMP` instead of directly in `.git/config`
- This requires a minimum Actions Runner version of [v2.329.0](https://github.com/actions/runner/releases/tag/v2.329.0) to access the persisted credentials for [Docker container action](https://docs.github.com/en/actions/tutorials/use-containerized-services/create-a-docker-container-action) scenarios. - No workflow changes required — `git fetch`, `git push`, etc. continue to work automatically
- Running authenticated git commands from a [Docker container action](https://docs.github.com/actions/sharing-automations/creating-actions/creating-a-docker-container-action) requires Actions Runner [v2.329.0](https://github.com/actions/runner/releases/tag/v2.329.0) or later
# Checkout v5 # Checkout v5
@ -149,6 +150,10 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
# Default: false # Default: false
submodules: '' submodules: ''
# A list of submodules to checkout.
# Default: null
submodule-directories: ''
# Add repository path as safe.directory for Git global config by running `git # Add repository path as safe.directory for Git global config by running `git
# config --global --add safe.directory <path>` # config --global --add safe.directory <path>`
# Default: true # Default: true

View File

@ -1162,6 +1162,7 @@ async function setup(testName: string): Promise<void> {
lfs: false, lfs: false,
submodules: false, submodules: false,
nestedSubmodules: false, nestedSubmodules: false,
submoduleDirectories: [],
persistCredentials: true, persistCredentials: true,
ref: 'refs/heads/main', ref: 'refs/heads/main',
repositoryName: 'my-repo', repositoryName: 'my-repo',

View File

@ -21,6 +21,13 @@ describe('input-helper tests', () => {
jest.spyOn(core, 'getInput').mockImplementation((name: string) => { jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
return inputs[name] return inputs[name]
}) })
// Mock getMultilineInput
jest.spyOn(core, 'getMultilineInput').mockImplementation((name: string) => {
const input: string[] = (inputs[name] || '')
.split('\n')
.filter(x => x !== '')
return input.map(inp => inp.trim())
})
// Mock error/warning/info/debug // Mock error/warning/info/debug
jest.spyOn(core, 'error').mockImplementation(jest.fn()) jest.spyOn(core, 'error').mockImplementation(jest.fn())
@ -87,6 +94,7 @@ describe('input-helper tests', () => {
expect(settings.showProgress).toBe(true) expect(settings.showProgress).toBe(true)
expect(settings.lfs).toBe(false) expect(settings.lfs).toBe(false)
expect(settings.ref).toBe('refs/heads/some-ref') expect(settings.ref).toBe('refs/heads/some-ref')
expect(settings.submoduleDirectories).toStrictEqual([])
expect(settings.repositoryName).toBe('some-repo') expect(settings.repositoryName).toBe('some-repo')
expect(settings.repositoryOwner).toBe('some-owner') expect(settings.repositoryOwner).toBe('some-owner')
expect(settings.repositoryPath).toBe(gitHubWorkspace) expect(settings.repositoryPath).toBe(gitHubWorkspace)
@ -144,4 +152,13 @@ describe('input-helper tests', () => {
const settings: IGitSourceSettings = await inputHelper.getInputs() const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings.workflowOrganizationId).toBe(123456) expect(settings.workflowOrganizationId).toBe(123456)
}) })
it('sets submoduleDirectories', async () => {
inputs['submodule-directories'] = 'submodule1\nsubmodule2'
const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings.submoduleDirectories).toStrictEqual([
'submodule1',
'submodule2'
])
expect(settings.submodules).toBe(true)
})
}) })

51
__test__/verify-worktree.sh Executable file
View File

@ -0,0 +1,51 @@
#!/bin/bash
set -e
# Verify worktree credentials
# This test verifies that git credentials work in worktrees created after checkout
# Usage: verify-worktree.sh <checkout-path> <worktree-name>
CHECKOUT_PATH="$1"
WORKTREE_NAME="$2"
if [ -z "$CHECKOUT_PATH" ] || [ -z "$WORKTREE_NAME" ]; then
echo "Usage: verify-worktree.sh <checkout-path> <worktree-name>"
exit 1
fi
cd "$CHECKOUT_PATH"
# Add safe directory for container environments
git config --global --add safe.directory "*" 2>/dev/null || true
# Show the includeIf configuration
echo "Git config includeIf entries:"
git config --list --show-origin | grep -i include || true
# Create the worktree
echo "Creating worktree..."
git worktree add "../$WORKTREE_NAME" HEAD --detach
# Change to worktree directory
cd "../$WORKTREE_NAME"
# Verify we're in a worktree
echo "Verifying worktree gitdir:"
cat .git
# Verify credentials are available in worktree by checking extraheader is configured
echo "Checking credentials in worktree..."
if git config --list --show-origin | grep -q "extraheader"; then
echo "Credentials are configured in worktree"
else
echo "ERROR: Credentials are NOT configured in worktree"
echo "Full git config:"
git config --list --show-origin
exit 1
fi
# Verify fetch works in the worktree
echo "Fetching in worktree..."
git fetch origin
echo "Worktree credentials test passed!"

View File

@ -92,6 +92,10 @@ inputs:
When the `ssh-key` input is not provided, SSH URLs beginning with `git@github.com:` are When the `ssh-key` input is not provided, SSH URLs beginning with `git@github.com:` are
converted to HTTPS. converted to HTTPS.
default: false default: false
submodule-directories:
description: >
A list of submodules to checkout.
default: null
set-safe-directory: set-safe-directory:
description: Add repository path as safe.directory for Git global config by running `git config --global --add safe.directory <path>` description: Add repository path as safe.directory for Git global config by running `git config --global --add safe.directory <path>`
default: true default: true

20
dist/index.js vendored
View File

@ -412,6 +412,9 @@ class GitAuthHelper {
// Configure host includeIf // Configure host includeIf
const hostIncludeKey = `includeIf.gitdir:${gitDir}.path`; const hostIncludeKey = `includeIf.gitdir:${gitDir}.path`;
yield this.git.config(hostIncludeKey, credentialsConfigPath); yield this.git.config(hostIncludeKey, credentialsConfigPath);
// Configure host includeIf for worktrees
const hostWorktreeIncludeKey = `includeIf.gitdir:${gitDir}/worktrees/*.path`;
yield this.git.config(hostWorktreeIncludeKey, credentialsConfigPath);
// Container git directory // Container git directory
const workingDirectory = this.git.getWorkingDirectory(); const workingDirectory = this.git.getWorkingDirectory();
const githubWorkspace = process.env['GITHUB_WORKSPACE']; const githubWorkspace = process.env['GITHUB_WORKSPACE'];
@ -424,6 +427,9 @@ class GitAuthHelper {
// Configure container includeIf // Configure container includeIf
const containerIncludeKey = `includeIf.gitdir:${containerGitDir}.path`; const containerIncludeKey = `includeIf.gitdir:${containerGitDir}.path`;
yield this.git.config(containerIncludeKey, containerCredentialsPath); yield this.git.config(containerIncludeKey, containerCredentialsPath);
// Configure container includeIf for worktrees
const containerWorktreeIncludeKey = `includeIf.gitdir:${containerGitDir}/worktrees/*.path`;
yield this.git.config(containerWorktreeIncludeKey, containerCredentialsPath);
} }
}); });
} }
@ -975,10 +981,10 @@ class GitCommandManager {
yield this.execGit(args); yield this.execGit(args);
}); });
} }
submoduleUpdate(fetchDepth, recursive) { submoduleUpdate(fetchDepth, recursive, submoduleDirectories) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const args = ['-c', 'protocol.version=2']; const args = ['-c', 'protocol.version=2'];
args.push('submodule', 'update', '--init', '--force'); args.push('submodule', 'update', '--init', '--force', ...submoduleDirectories);
if (fetchDepth > 0) { if (fetchDepth > 0) {
args.push(`--depth=${fetchDepth}`); args.push(`--depth=${fetchDepth}`);
} }
@ -1576,7 +1582,7 @@ function getSource(settings) {
// Checkout submodules // Checkout submodules
core.startGroup('Fetching submodules'); core.startGroup('Fetching submodules');
yield git.submoduleSync(settings.nestedSubmodules); yield git.submoduleSync(settings.nestedSubmodules);
yield git.submoduleUpdate(settings.fetchDepth, settings.nestedSubmodules); yield git.submoduleUpdate(settings.fetchDepth, settings.nestedSubmodules, settings.submoduleDirectories);
yield git.submoduleForeach('git config --local gc.auto 0', settings.nestedSubmodules); yield git.submoduleForeach('git config --local gc.auto 0', settings.nestedSubmodules);
core.endGroup(); core.endGroup();
// Persist credentials // Persist credentials
@ -2037,6 +2043,7 @@ function getInputs() {
// Submodules // Submodules
result.submodules = false; result.submodules = false;
result.nestedSubmodules = false; result.nestedSubmodules = false;
result.submoduleDirectories = [];
const submodulesString = (core.getInput('submodules') || '').toUpperCase(); const submodulesString = (core.getInput('submodules') || '').toUpperCase();
if (submodulesString == 'RECURSIVE') { if (submodulesString == 'RECURSIVE') {
result.submodules = true; result.submodules = true;
@ -2045,8 +2052,15 @@ function getInputs() {
else if (submodulesString == 'TRUE') { else if (submodulesString == 'TRUE') {
result.submodules = true; result.submodules = true;
} }
const submoduleDirectories = core.getMultilineInput('submodule-directories');
if (submoduleDirectories.length > 0) {
result.submoduleDirectories = submoduleDirectories;
if (!result.submodules)
result.submodules = true;
}
core.debug(`submodules = ${result.submodules}`); core.debug(`submodules = ${result.submodules}`);
core.debug(`recursive submodules = ${result.nestedSubmodules}`); core.debug(`recursive submodules = ${result.nestedSubmodules}`);
core.debug(`submodule directories = ${result.submoduleDirectories}`);
// Auth token // Auth token
result.authToken = core.getInput('token', { required: true }); result.authToken = core.getInput('token', { required: true });
// SSH // SSH

View File

@ -374,6 +374,10 @@ class GitAuthHelper {
const hostIncludeKey = `includeIf.gitdir:${gitDir}.path` const hostIncludeKey = `includeIf.gitdir:${gitDir}.path`
await this.git.config(hostIncludeKey, credentialsConfigPath) await this.git.config(hostIncludeKey, credentialsConfigPath)
// Configure host includeIf for worktrees
const hostWorktreeIncludeKey = `includeIf.gitdir:${gitDir}/worktrees/*.path`
await this.git.config(hostWorktreeIncludeKey, credentialsConfigPath)
// Container git directory // Container git directory
const workingDirectory = this.git.getWorkingDirectory() const workingDirectory = this.git.getWorkingDirectory()
const githubWorkspace = process.env['GITHUB_WORKSPACE'] const githubWorkspace = process.env['GITHUB_WORKSPACE']
@ -395,6 +399,13 @@ class GitAuthHelper {
// Configure container includeIf // Configure container includeIf
const containerIncludeKey = `includeIf.gitdir:${containerGitDir}.path` const containerIncludeKey = `includeIf.gitdir:${containerGitDir}.path`
await this.git.config(containerIncludeKey, containerCredentialsPath) await this.git.config(containerIncludeKey, containerCredentialsPath)
// Configure container includeIf for worktrees
const containerWorktreeIncludeKey = `includeIf.gitdir:${containerGitDir}/worktrees/*.path`
await this.git.config(
containerWorktreeIncludeKey,
containerCredentialsPath
)
} }
} }

View File

@ -56,7 +56,11 @@ export interface IGitCommandManager {
shaExists(sha: string): Promise<boolean> shaExists(sha: string): Promise<boolean>
submoduleForeach(command: string, recursive: boolean): Promise<string> submoduleForeach(command: string, recursive: boolean): Promise<string>
submoduleSync(recursive: boolean): Promise<void> submoduleSync(recursive: boolean): Promise<void>
submoduleUpdate(fetchDepth: number, recursive: boolean): Promise<void> submoduleUpdate(
fetchDepth: number,
recursive: boolean,
submoduleDirectories: string[]
): Promise<void>
submoduleStatus(): Promise<boolean> submoduleStatus(): Promise<boolean>
tagExists(pattern: string): Promise<boolean> tagExists(pattern: string): Promise<boolean>
tryClean(): Promise<boolean> tryClean(): Promise<boolean>
@ -448,9 +452,19 @@ class GitCommandManager {
await this.execGit(args) await this.execGit(args)
} }
async submoduleUpdate(fetchDepth: number, recursive: boolean): Promise<void> { async submoduleUpdate(
fetchDepth: number,
recursive: boolean,
submoduleDirectories: string[]
): Promise<void> {
const args = ['-c', 'protocol.version=2'] const args = ['-c', 'protocol.version=2']
args.push('submodule', 'update', '--init', '--force') args.push(
'submodule',
'update',
'--init',
'--force',
...submoduleDirectories
)
if (fetchDepth > 0) { if (fetchDepth > 0) {
args.push(`--depth=${fetchDepth}`) args.push(`--depth=${fetchDepth}`)
} }

View File

@ -242,7 +242,11 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
// Checkout submodules // Checkout submodules
core.startGroup('Fetching submodules') core.startGroup('Fetching submodules')
await git.submoduleSync(settings.nestedSubmodules) await git.submoduleSync(settings.nestedSubmodules)
await git.submoduleUpdate(settings.fetchDepth, settings.nestedSubmodules) await git.submoduleUpdate(
settings.fetchDepth,
settings.nestedSubmodules,
settings.submoduleDirectories
)
await git.submoduleForeach( await git.submoduleForeach(
'git config --local gc.auto 0', 'git config --local gc.auto 0',
settings.nestedSubmodules settings.nestedSubmodules

View File

@ -74,6 +74,11 @@ export interface IGitSourceSettings {
*/ */
nestedSubmodules: boolean nestedSubmodules: boolean
/**
* Indicates which submodule paths to checkout
*/
submoduleDirectories: string[]
/** /**
* The auth token to use when fetching the repository * The auth token to use when fetching the repository
*/ */

View File

@ -125,6 +125,7 @@ export async function getInputs(): Promise<IGitSourceSettings> {
// Submodules // Submodules
result.submodules = false result.submodules = false
result.nestedSubmodules = false result.nestedSubmodules = false
result.submoduleDirectories = []
const submodulesString = (core.getInput('submodules') || '').toUpperCase() const submodulesString = (core.getInput('submodules') || '').toUpperCase()
if (submodulesString == 'RECURSIVE') { if (submodulesString == 'RECURSIVE') {
result.submodules = true result.submodules = true
@ -132,9 +133,16 @@ export async function getInputs(): Promise<IGitSourceSettings> {
} else if (submodulesString == 'TRUE') { } else if (submodulesString == 'TRUE') {
result.submodules = true result.submodules = true
} }
const submoduleDirectories = core.getMultilineInput('submodule-directories')
if (submoduleDirectories.length > 0) {
result.submoduleDirectories = submoduleDirectories
if (!result.submodules) result.submodules = true
}
core.debug(`submodules = ${result.submodules}`) core.debug(`submodules = ${result.submodules}`)
core.debug(`recursive submodules = ${result.nestedSubmodules}`) core.debug(`recursive submodules = ${result.nestedSubmodules}`)
core.debug(`submodule directories = ${result.submoduleDirectories}`)
// Auth token // Auth token
result.authToken = core.getInput('token', {required: true}) result.authToken = core.getInput('token', {required: true})