chore(deps): update actions/checkout action to v6.0.2#2993
Open
renovate[bot] wants to merge 1 commit intomasterfrom
Open
chore(deps): update actions/checkout action to v6.0.2#2993renovate[bot] wants to merge 1 commit intomasterfrom
renovate[bot] wants to merge 1 commit intomasterfrom
Conversation
7178559 to
37f88c8
Compare
|
[puLL-Merge] - actions/checkout@v6.0.0..v6.0.2 Diffdiff --git .github/workflows/check-dist.yml .github/workflows/check-dist.yml
index db3e37f2b..c7d49620f 100644
--- .github/workflows/check-dist.yml
+++ .github/workflows/check-dist.yml
@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4.1.6
+ - uses: actions/checkout@v6
- name: Set Node.js 24.x
uses: actions/setup-node@v4
diff --git .github/workflows/codeql-analysis.yml .github/workflows/codeql-analysis.yml
index 778d474d8..377fae951 100644
--- .github/workflows/codeql-analysis.yml
+++ .github/workflows/codeql-analysis.yml
@@ -39,7 +39,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v4.1.6
+ uses: actions/checkout@v6
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
diff --git .github/workflows/licensed.yml .github/workflows/licensed.yml
index 1f71aa749..36e70e2c1 100644
--- .github/workflows/licensed.yml
+++ .github/workflows/licensed.yml
@@ -9,6 +9,6 @@ jobs:
runs-on: ubuntu-latest
name: Check licenses
steps:
- - uses: actions/checkout@v4.1.6
+ - uses: actions/checkout@v6
- run: npm ci
- run: npm run licensed-check
\ No newline at end of file
diff --git .github/workflows/publish-immutable-actions.yml .github/workflows/publish-immutable-actions.yml
index 87c020728..44d571ba9 100644
--- .github/workflows/publish-immutable-actions.yml
+++ .github/workflows/publish-immutable-actions.yml
@@ -14,7 +14,7 @@ jobs:
steps:
- name: Checking out
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
- name: Publish
id: publish
uses: actions/publish-immutable-action@0.0.3
diff --git .github/workflows/test.yml .github/workflows/test.yml
index 7c47d7b6a..0383c88d7 100644
--- .github/workflows/test.yml
+++ .github/workflows/test.yml
@@ -19,7 +19,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 24.x
- - uses: actions/checkout@v4.1.6
+ - uses: actions/checkout@v6
- run: npm ci
- run: npm run build
- run: npm run format-check
@@ -37,7 +37,7 @@ jobs:
steps:
# Clone this repo
- name: Checkout
- uses: actions/checkout@v4.1.6
+ uses: actions/checkout@v6
# Basic checkout
- name: Checkout basic
@@ -87,6 +87,17 @@ jobs:
- name: Verify fetch filter
run: __test__/verify-fetch-filter.sh
+ # Fetch tags
+ - name: Checkout with fetch-tags
+ uses: ./
+ with:
+ ref: test-data/v2/basic
+ path: fetch-tags-test
+ fetch-tags: true
+ - name: Verify fetch-tags
+ shell: bash
+ run: __test__/verify-fetch-tags.sh
+
# Sparse checkout
- name: Sparse checkout
uses: ./
@@ -165,6 +176,22 @@ jobs:
- name: Verify submodules recursive
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
- name: Remove basic
if: runner.os != 'windows'
@@ -202,7 +229,7 @@ jobs:
steps:
# Clone this repo
- name: Checkout
- uses: actions/checkout@v4.1.6
+ uses: actions/checkout@v6
# Basic checkout using git
- name: Checkout basic
@@ -234,7 +261,7 @@ jobs:
steps:
# Clone this repo
- name: Checkout
- uses: actions/checkout@v4.1.6
+ uses: actions/checkout@v6
# Basic checkout using git
- name: Checkout basic
@@ -264,7 +291,7 @@ jobs:
steps:
# Clone this repo
- name: Checkout
- uses: actions/checkout@v4.1.6
+ uses: actions/checkout@v6
with:
path: localClone
@@ -291,8 +318,8 @@ jobs:
git fetch --no-tags --depth=1 origin +refs/heads/main:refs/remotes/origin/main
# needed to make checkout post cleanup succeed
- - name: Fix Checkout v4
- uses: actions/checkout@v4.1.6
+ - name: Fix Checkout v6
+ uses: actions/checkout@v6
with:
path: localClone
@@ -301,7 +328,7 @@ jobs:
steps:
# Clone this repo
- name: Checkout
- uses: actions/checkout@v4.1.6
+ uses: actions/checkout@v6
with:
path: actions-checkout
diff --git .github/workflows/update-main-version.yml .github/workflows/update-main-version.yml
index 643b954e4..b3b23fe4e 100644
--- .github/workflows/update-main-version.yml
+++ .github/workflows/update-main-version.yml
@@ -23,7 +23,7 @@ jobs:
# Note this update workflow can also be used as a rollback tool.
# For that reason, it's best to pin `actions/checkout` to a known, stable version
# (typically, about two releases back).
- - uses: actions/checkout@v4.1.6
+ - uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Git config
diff --git .github/workflows/update-test-ubuntu-git.yml .github/workflows/update-test-ubuntu-git.yml
index 5c252b98d..10e4dac93 100644
--- .github/workflows/update-test-ubuntu-git.yml
+++ .github/workflows/update-test-ubuntu-git.yml
@@ -26,7 +26,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
# Use `docker/login-action` to log in to GHCR.io.
# Once published, the packages are scoped to the account defined here.
diff --git CHANGELOG.md CHANGELOG.md
index 25befb782..6d5a6f302 100644
--- CHANGELOG.md
+++ CHANGELOG.md
@@ -1,19 +1,19 @@
# Changelog
-## V6.0.0
+## v6.0.0
* 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
-## V5.0.1
+## v5.0.1
* 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
-## V4.3.1
+## v4.3.1
* 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
* 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
diff --git README.md README.md
index 5ad476f49..f0f65f9f6 100644
--- README.md
+++ README.md
@@ -4,8 +4,9 @@
## What's new
-- Updated `persist-credentials` to store the credentials under `$RUNNER_TEMP` instead of directly in the local 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.
+- Improved credential security: `persist-credentials` now stores credentials in a separate file under `$RUNNER_TEMP` instead of directly in `.git/config`
+- 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
@@ -51,7 +52,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
<!-- start usage -->
```yaml
-- uses: actions/checkout@v5
+- uses: actions/checkout@v6
with:
# Repository name with owner. For example, actions/checkout
# Default: ${{ github.repository }}
@@ -190,7 +191,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
## Fetch only the root files
```yaml
-- uses: actions/checkout@v5
+- uses: actions/checkout@v6
with:
sparse-checkout: .
```
@@ -198,7 +199,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
## Fetch only the root files and `.github` and `src` folder
```yaml
-- uses: actions/checkout@v5
+- uses: actions/checkout@v6
with:
sparse-checkout: |
.github
@@ -208,7 +209,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
## Fetch only a single file
```yaml
-- uses: actions/checkout@v5
+- uses: actions/checkout@v6
with:
sparse-checkout: |
README.md
@@ -218,7 +219,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
## Fetch all history for all tags and branches
```yaml
-- uses: actions/checkout@v5
+- uses: actions/checkout@v6
with:
fetch-depth: 0
```
@@ -226,7 +227,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
## Checkout a different branch
```yaml
-- uses: actions/checkout@v5
+- uses: actions/checkout@v6
with:
ref: my-branch
```
@@ -234,7 +235,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
## Checkout HEAD^
```yaml
-- uses: actions/checkout@v5
+- uses: actions/checkout@v6
with:
fetch-depth: 2
- run: git checkout HEAD^
@@ -244,12 +245,12 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
```yaml
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
path: main
- name: Checkout tools repo
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
repository: my-org/my-tools
path: my-tools
@@ -260,10 +261,10 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
```yaml
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Checkout tools repo
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
repository: my-org/my-tools
path: my-tools
@@ -274,12 +275,12 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
```yaml
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
path: main
- name: Checkout private tools
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
repository: my-org/my-private-tools
token: ${{ secrets.GH_PAT }} # `GH_PAT` is a secret that contains your PAT
@@ -292,7 +293,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
## Checkout pull request HEAD commit instead of merge commit
```yaml
-- uses: actions/checkout@v5
+- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
```
@@ -308,7 +309,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
```
## Push a commit using the built-in token
@@ -319,7 +320,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- run: |
date > generated.txt
# Note: the following account information will not work on GHES
@@ -341,7 +342,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
ref: ${{ github.head_ref }}
- run: |
diff --git __test__/git-command-manager.test.ts __test__/git-command-manager.test.ts
index cea73d4dd..8a97d827a 100644
--- __test__/git-command-manager.test.ts
+++ __test__/git-command-manager.test.ts
@@ -108,7 +108,7 @@ describe('Test fetchDepth and fetchTags options', () => {
jest.restoreAllMocks()
})
- it('should call execGit with the correct arguments when fetchDepth is 0 and fetchTags is true', async () => {
+ it('should call execGit with the correct arguments when fetchDepth is 0', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
const lfs = false
@@ -122,8 +122,7 @@ describe('Test fetchDepth and fetchTags options', () => {
const refSpec = ['refspec1', 'refspec2']
const options = {
filter: 'filterValue',
- fetchDepth: 0,
- fetchTags: true
+ fetchDepth: 0
}
await git.fetch(refSpec, options)
@@ -134,6 +133,7 @@ describe('Test fetchDepth and fetchTags options', () => {
'-c',
'protocol.version=2',
'fetch',
+ '--no-tags',
'--prune',
'--no-recurse-submodules',
'--filter=filterValue',
@@ -145,7 +145,7 @@ describe('Test fetchDepth and fetchTags options', () => {
)
})
- it('should call execGit with the correct arguments when fetchDepth is 0 and fetchTags is false', async () => {
+ it('should call execGit with the correct arguments when fetchDepth is 0 and refSpec includes tags', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
@@ -156,11 +156,10 @@ describe('Test fetchDepth and fetchTags options', () => {
lfs,
doSparseCheckout
)
- const refSpec = ['refspec1', 'refspec2']
+ const refSpec = ['refspec1', 'refspec2', '+refs/tags/*:refs/tags/*']
const options = {
filter: 'filterValue',
- fetchDepth: 0,
- fetchTags: false
+ fetchDepth: 0
}
await git.fetch(refSpec, options)
@@ -177,13 +176,14 @@ describe('Test fetchDepth and fetchTags options', () => {
'--filter=filterValue',
'origin',
'refspec1',
- 'refspec2'
+ 'refspec2',
+ '+refs/tags/*:refs/tags/*'
],
expect.any(Object)
)
})
- it('should call execGit with the correct arguments when fetchDepth is 1 and fetchTags is false', async () => {
+ it('should call execGit with the correct arguments when fetchDepth is 1', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
@@ -197,8 +197,7 @@ describe('Test fetchDepth and fetchTags options', () => {
const refSpec = ['refspec1', 'refspec2']
const options = {
filter: 'filterValue',
- fetchDepth: 1,
- fetchTags: false
+ fetchDepth: 1
}
await git.fetch(refSpec, options)
@@ -222,7 +221,7 @@ describe('Test fetchDepth and fetchTags options', () => {
)
})
- it('should call execGit with the correct arguments when fetchDepth is 1 and fetchTags is true', async () => {
+ it('should call execGit with the correct arguments when fetchDepth is 1 and refSpec includes tags', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
@@ -233,11 +232,10 @@ describe('Test fetchDepth and fetchTags options', () => {
lfs,
doSparseCheckout
)
- const refSpec = ['refspec1', 'refspec2']
+ const refSpec = ['refspec1', 'refspec2', '+refs/tags/*:refs/tags/*']
const options = {
filter: 'filterValue',
- fetchDepth: 1,
- fetchTags: true
+ fetchDepth: 1
}
await git.fetch(refSpec, options)
@@ -248,13 +246,15 @@ describe('Test fetchDepth and fetchTags options', () => {
'-c',
'protocol.version=2',
'fetch',
+ '--no-tags',
'--prune',
'--no-recurse-submodules',
'--filter=filterValue',
'--depth=1',
'origin',
'refspec1',
- 'refspec2'
+ 'refspec2',
+ '+refs/tags/*:refs/tags/*'
],
expect.any(Object)
)
@@ -338,7 +338,7 @@ describe('Test fetchDepth and fetchTags options', () => {
)
})
- it('should call execGit with the correct arguments when fetchTags is true and showProgress is true', async () => {
+ it('should call execGit with the correct arguments when showProgress is true and refSpec includes tags', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
@@ -349,10 +349,9 @@ describe('Test fetchDepth and fetchTags options', () => {
lfs,
doSparseCheckout
)
- const refSpec = ['refspec1', 'refspec2']
+ const refSpec = ['refspec1', 'refspec2', '+refs/tags/*:refs/tags/*']
const options = {
filter: 'filterValue',
- fetchTags: true,
showProgress: true
}
@@ -364,15 +363,134 @@ describe('Test fetchDepth and fetchTags options', () => {
'-c',
'protocol.version=2',
'fetch',
+ '--no-tags',
'--prune',
'--no-recurse-submodules',
'--progress',
'--filter=filterValue',
'origin',
'refspec1',
- 'refspec2'
+ 'refspec2',
+ '+refs/tags/*:refs/tags/*'
],
expect.any(Object)
)
})
})
+
+describe('git user-agent with orchestration ID', () => {
+ beforeEach(async () => {
+ jest.spyOn(fshelper, 'fileExistsSync').mockImplementation(jest.fn())
+ jest.spyOn(fshelper, 'directoryExistsSync').mockImplementation(jest.fn())
+ })
+
+ afterEach(() => {
+ jest.restoreAllMocks()
+ // Clean up environment variable to prevent test pollution
+ delete process.env['ACTIONS_ORCHESTRATION_ID']
+ })
+
+ it('should include orchestration ID in user-agent when ACTIONS_ORCHESTRATION_ID is set', async () => {
+ const orchId = 'test-orch-id-12345'
+ process.env['ACTIONS_ORCHESTRATION_ID'] = orchId
+
+ let capturedEnv: any = null
+ mockExec.mockImplementation((path, args, options) => {
+ if (args.includes('version')) {
+ options.listeners.stdout(Buffer.from('2.18'))
+ }
+ // Capture env on any command
+ capturedEnv = options.env
+ return 0
+ })
+ jest.spyOn(exec, 'exec').mockImplementation(mockExec)
+
+ const workingDirectory = 'test'
+ const lfs = false
+ const doSparseCheckout = false
+ git = await commandManager.createCommandManager(
+ workingDirectory,
+ lfs,
+ doSparseCheckout
+ )
+
+ // Call a git command to trigger env capture after user-agent is set
+ await git.init()
+
+ // Verify the user agent includes the orchestration ID
+ expect(git).toBeDefined()
+ expect(capturedEnv).toBeDefined()
+ expect(capturedEnv['GIT_HTTP_USER_AGENT']).toBe(
+ `git/2.18 (github-actions-checkout) actions_orchestration_id/${orchId}`
+ )
+ })
+
+ it('should sanitize invalid characters in orchestration ID', async () => {
+ const orchId = 'test (with) special/chars'
+ process.env['ACTIONS_ORCHESTRATION_ID'] = orchId
+
+ let capturedEnv: any = null
+ mockExec.mockImplementation((path, args, options) => {
+ if (args.includes('version')) {
+ options.listeners.stdout(Buffer.from('2.18'))
+ }
+ // Capture env on any command
+ capturedEnv = options.env
+ return 0
+ })
+ jest.spyOn(exec, 'exec').mockImplementation(mockExec)
+
+ const workingDirectory = 'test'
+ const lfs = false
+ const doSparseCheckout = false
+ git = await commandManager.createCommandManager(
+ workingDirectory,
+ lfs,
+ doSparseCheckout
+ )
+
+ // Call a git command to trigger env capture after user-agent is set
+ await git.init()
+
+ // Verify the user agent has sanitized orchestration ID (spaces, parentheses, slash replaced)
+ expect(git).toBeDefined()
+ expect(capturedEnv).toBeDefined()
+ expect(capturedEnv['GIT_HTTP_USER_AGENT']).toBe(
+ 'git/2.18 (github-actions-checkout) actions_orchestration_id/test__with__special_chars'
+ )
+ })
+
+ it('should not modify user-agent when ACTIONS_ORCHESTRATION_ID is not set', async () => {
+ delete process.env['ACTIONS_ORCHESTRATION_ID']
+
+ let capturedEnv: any = null
+ mockExec.mockImplementation((path, args, options) => {
+ if (args.includes('version')) {
+ options.listeners.stdout(Buffer.from('2.18'))
+ }
+ // Capture env on any command
+ capturedEnv = options.env
+ return 0
+ })
+ jest.spyOn(exec, 'exec').mockImplementation(mockExec)
+
+ const workingDirectory = 'test'
+ const lfs = false
+ const doSparseCheckout = false
+ git = await commandManager.createCommandManager(
+ workingDirectory,
+ lfs,
+ doSparseCheckout
+ )
+
+ // Call a git command to trigger env capture after user-agent is set
+ await git.init()
+
+ // Verify the user agent does NOT contain orchestration ID
+ expect(git).toBeDefined()
+ expect(capturedEnv).toBeDefined()
+ expect(capturedEnv['GIT_HTTP_USER_AGENT']).toBe(
+ 'git/2.18 (github-actions-checkout)'
+ )
+ })
+})
diff --git __test__/ref-helper.test.ts __test__/ref-helper.test.ts
index 5c8d76b87..4943abd6d 100644
--- __test__/ref-helper.test.ts
+++ __test__/ref-helper.test.ts
@@ -152,7 +152,22 @@ describe('ref-helper tests', () => {
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`)
+ expect(refSpec[0]).toBe(`+refs/tags/my-tag:refs/tags/my-tag`)
+ })
+
+ it('getRefSpec sha + refs/tags/ with fetchTags', async () => {
+ // When fetchTags is true, only include tags wildcard (specific tag is redundant)
+ const refSpec = refHelper.getRefSpec('refs/tags/my-tag', commit, true)
+ expect(refSpec.length).toBe(1)
+ expect(refSpec[0]).toBe('+refs/tags/*:refs/tags/*')
+ })
+
+ it('getRefSpec sha + refs/heads/ with fetchTags', async () => {
+ // When fetchTags is true, include both the branch refspec and tags wildcard
+ const refSpec = refHelper.getRefSpec('refs/heads/my/branch', commit, true)
+ expect(refSpec.length).toBe(2)
+ expect(refSpec[0]).toBe('+refs/tags/*:refs/tags/*')
+ expect(refSpec[1]).toBe(`+${commit}:refs/remotes/origin/my/branch`)
})
it('getRefSpec sha only', async () => {
@@ -168,6 +183,14 @@ describe('ref-helper tests', () => {
expect(refSpec[1]).toBe('+refs/tags/my-ref*:refs/tags/my-ref*')
})
+ it('getRefSpec unqualified ref only with fetchTags', async () => {
+ // When fetchTags is true, skip specific tag pattern since wildcard covers all
+ const refSpec = refHelper.getRefSpec('my-ref', '', true)
+ expect(refSpec.length).toBe(2)
+ expect(refSpec[0]).toBe('+refs/tags/*:refs/tags/*')
+ expect(refSpec[1]).toBe('+refs/heads/my-ref*:refs/remotes/origin/my-ref*')
+ })
+
it('getRefSpec refs/heads/ only', async () => {
const refSpec = refHelper.getRefSpec('refs/heads/my/branch', '')
expect(refSpec.length).toBe(1)
@@ -187,4 +210,21 @@ describe('ref-helper tests', () => {
expect(refSpec.length).toBe(1)
expect(refSpec[0]).toBe('+refs/tags/my-tag:refs/tags/my-tag')
})
+
+ it('getRefSpec refs/tags/ only with fetchTags', async () => {
+ // When fetchTags is true, only include tags wildcard (specific tag is redundant)
+ const refSpec = refHelper.getRefSpec('refs/tags/my-tag', '', true)
+ expect(refSpec.length).toBe(1)
+ expect(refSpec[0]).toBe('+refs/tags/*:refs/tags/*')
+ })
+
+ it('getRefSpec refs/heads/ only with fetchTags', async () => {
+ // When fetchTags is true, include both the branch refspec and tags wildcard
+ const refSpec = refHelper.getRefSpec('refs/heads/my/branch', '', true)
+ expect(refSpec.length).toBe(2)
+ expect(refSpec[0]).toBe('+refs/tags/*:refs/tags/*')
+ expect(refSpec[1]).toBe(
+ '+refs/heads/my/branch:refs/remotes/origin/my/branch'
+ )
+ })
})
diff --git a/__test__/verify-fetch-tags.sh b/__test__/verify-fetch-tags.sh
new file mode 100755
index 000000000..74cff1ed6
--- /dev/null
+++ __test__/verify-fetch-tags.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+# Verify tags were fetched
+TAG_COUNT=$(git -C ./fetch-tags-test tag | wc -l)
+if [ "$TAG_COUNT" -eq 0 ]; then
+ echo "Expected tags to be fetched, but found none"
+ exit 1
+fi
+echo "Found $TAG_COUNT tags"
diff --git a/__test__/verify-worktree.sh b/__test__/verify-worktree.sh
new file mode 100755
index 000000000..3a4d3e4df
--- /dev/null
+++ __test__/verify-worktree.sh
@@ -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!"
diff --git dist/index.js dist/index.js
index a251a1966..fe3f3170e 100644
--- dist/index.js
+++ dist/index.js
@@ -412,6 +412,9 @@ class GitAuthHelper {
// Configure host includeIf
const hostIncludeKey = `includeIf.gitdir:${gitDir}.path`;
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
const workingDirectory = this.git.getWorkingDirectory();
const githubWorkspace = process.env['GITHUB_WORKSPACE'];
@@ -424,6 +427,9 @@ class GitAuthHelper {
// Configure container includeIf
const containerIncludeKey = `includeIf.gitdir:${containerGitDir}.path`;
yield this.git.config(containerIncludeKey, containerCredentialsPath);
+ // Configure container includeIf for worktrees
+ const containerWorktreeIncludeKey = `includeIf.gitdir:${containerGitDir}/worktrees/*.path`;
+ yield this.git.config(containerWorktreeIncludeKey, containerCredentialsPath);
}
});
}
@@ -647,7 +653,6 @@ const fs = __importStar(__nccwpck_require__(7147));
const fshelper = __importStar(__nccwpck_require__(7219));
const io = __importStar(__nccwpck_require__(7436));
const path = __importStar(__nccwpck_require__(1017));
-const refHelper = __importStar(__nccwpck_require__(8601));
const regexpHelper = __importStar(__nccwpck_require__(3120));
const retryHelper = __importStar(__nccwpck_require__(2155));
const git_version_1 = __nccwpck_require__(3142);
@@ -825,9 +830,9 @@ class GitCommandManager {
fetch(refSpec, options) {
return __awaiter(this, void 0, void 0, function* () {
const args = ['-c', 'protocol.version=2', 'fetch'];
- if (!refSpec.some(x => x === refHelper.tagsRefSpec) && !options.fetchTags) {
- args.push('--no-tags');
- }
+ // Always use --no-tags for explicit control over tag fetching
+ // Tags are fetched explicitly via refspec when needed
+ args.push('--no-tags');
args.push('--prune', '--no-recurse-submodules');
if (options.showProgress) {
args.push('--progress');
@@ -1200,7 +1205,17 @@ class GitCommandManager {
}
}
// Set the user agent
- const gitHttpUserAgent = `git/${this.gitVersion} (github-actions-checkout)`;
+ let gitHttpUserAgent = `git/${this.gitVersion} (github-actions-checkout)`;
+ // Append orchestration ID if set
+ const orchId = process.env['ACTIONS_ORCHESTRATION_ID'];
+ if (orchId) {
+ // Sanitize the orchestration ID to ensure it contains only valid characters
+ // Valid characters: 0-9, a-z, _, -, .
+ const sanitizedId = orchId.replace(/[^a-z0-9_.-]/gi, '_');
+ if (sanitizedId) {
+ gitHttpUserAgent = `${gitHttpUserAgent} actions_orchestration_id/${sanitizedId}`;
+ }
+ }
core.debug(`Set git useragent to: ${gitHttpUserAgent}`);
this.gitEnv['GIT_HTTP_USER_AGENT'] = gitHttpUserAgent;
});
@@ -1523,13 +1538,26 @@ function getSource(settings) {
if (!(yield refHelper.testRef(git, settings.ref, settings.commit))) {
refSpec = refHelper.getRefSpec(settings.ref, settings.commit);
yield git.fetch(refSpec, fetchOptions);
+ // Verify the ref now matches. For branches, the targeted fetch above brings
+ // in the specific commit. For tags (fetched by ref), this will fail if
+ // the tag was moved after the workflow was triggered.
+ if (!(yield refHelper.testRef(git, settings.ref, settings.commit))) {
+ throw new Error(`The ref '${settings.ref}' does not point to the expected commit '${settings.commit}'. ` +
+ `The ref may have been updated after the workflow was triggered.`);
+ }
}
}
else {
fetchOptions.fetchDepth = settings.fetchDepth;
- fetchOptions.fetchTags = settings.fetchTags;
- const refSpec = refHelper.getRefSpec(settings.ref, settings.commit);
+ const refSpec = refHelper.getRefSpec(settings.ref, settings.commit, settings.fetchTags);
yield git.fetch(refSpec, fetchOptions);
+ // For tags, verify the ref still points to the expected commit.
+ // Tags are fetched by ref (not commit), so if a tag was moved after the
+ // workflow was triggered, we would silently check out the wrong commit.
+ if (!(yield refHelper.testRef(git, settings.ref, settings.commit))) {
+ throw new Error(`The ref '${settings.ref}' does not point to the expected commit '${settings.commit}'. ` +
+ `The ref may have been updated after the workflow was triggered.`);
+ }
}
core.endGroup();
// Checkout info
@@ -2268,53 +2296,67 @@ function getRefSpecForAllHistory(ref, commit) {
}
return result;
}
-function getRefSpec(ref, commit) {
+function getRefSpec(ref, commit, fetchTags) {
if (!ref && !commit) {
throw new Error('Args ref and commit cannot both be empty');
}
const upperRef = (ref || '').toUpperCase();
+ const result = [];
+ // When fetchTags is true, always include the tags refspec
+ if (fetchTags) {
+ result.push(exports.tagsRefSpec);
+ }
// SHA
if (commit) {
// refs/heads
if (upperRef.startsWith('REFS/HEADS/')) {
const branch = ref.substring('refs/heads/'.length);
- return [`+${commit}:refs/remotes/origin/${branch}`];
+ result.push(`+${commit}:refs/remotes/origin/${branch}`);
}
// refs/pull/
else if (upperRef.startsWith('REFS/PULL/')) {
const branch = ref.substring('refs/pull/'.length);
- return [`+${commit}:refs/remotes/pull/${branch}`];
+ result.push(`+${commit}:refs/remotes/pull/${branch}`);
}
// refs/tags/
else if (upperRef.startsWith('REFS/TAGS/')) {
- return [`+${commit}:${ref}`];
+ if (!fetchTags) {
+ result.push(`+${ref}:${ref}`);
+ }
}
// Otherwise no destination ref
else {
- return [commit];
+ result.push(commit);
}
}
// Unqualified ref, check for a matching branch or tag
else if (!upperRef.startsWith('REFS/')) {
- return [
- `+refs/heads/${ref}*:refs/remotes/origin/${ref}*`,
- `+refs/tags/${ref}*:refs/tags/${ref}*`
- ];
+ result.push(`+refs/heads/${ref}*:refs/remotes/origin/${ref}*`);
+ if (!fetchTags) {
+ result.push(`+refs/tags/${ref}*:refs/tags/${ref}*`);
+ }
}
// refs/heads/
else if (upperRef.startsWith('REFS/HEADS/')) {
const branch = ref.substring('refs/heads/'.length);
- return [`+${ref}:refs/remotes/origin/${branch}`];
+ result.push(`+${ref}:refs/remotes/origin/${branch}`);
}
// refs/pull/
else if (upperRef.startsWith('REFS/PULL/')) {
const branch = ref.substring('refs/pull/'.length);
- return [`+${ref}:refs/remotes/pull/${branch}`];
+ result.push(`+${ref}:refs/remotes/pull/${branch}`);
}
// refs/tags/
+ else if (upperRef.startsWith('REFS/TAGS/')) {
+ if (!fetchTags) {
+ result.push(`+${ref}:${ref}`);
+ }
+ }
+ // Other refs
else {
- return [`+${ref}:${ref}`];
+ result.push(`+${ref}:${ref}`);
}
+ return result;
}
/**
* Tests whether the initial fetch created the ref at the expected commit
@@ -2350,7 +2392,9 @@ function testRef(git, ref, commit) {
// refs/tags/
else if (upperRef.startsWith('REFS/TAGS/')) {
const tagName = ref.substring('refs/tags/'.length);
- return ((yield git.tagExists(tagName)) && commit === (yield git.revParse(ref)));
+ // Use ^{commit} to dereference annotated tags to their underlying commit
+ return ((yield git.tagExists(tagName)) &&
+ commit === (yield git.revParse(`${ref}^{commit}`)));
}
// Unexpected
else {
diff --git src/git-auth-helper.ts src/git-auth-helper.ts
index a1950a60c..e67db148a 100644
--- src/git-auth-helper.ts
+++ src/git-auth-helper.ts
@@ -374,6 +374,10 @@ class GitAuthHelper {
const hostIncludeKey = `includeIf.gitdir:${gitDir}.path`
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
const workingDirectory = this.git.getWorkingDirectory()
const githubWorkspace = process.env['GITHUB_WORKSPACE']
@@ -395,6 +399,13 @@ class GitAuthHelper {
// Configure container includeIf
const containerIncludeKey = `includeIf.gitdir:${containerGitDir}.path`
await this.git.config(containerIncludeKey, containerCredentialsPath)
+
+ // Configure container includeIf for worktrees
+ const containerWorktreeIncludeKey = `includeIf.gitdir:${containerGitDir}/worktrees/*.path`
+ await this.git.config(
+ containerWorktreeIncludeKey,
+ containerCredentialsPath
+ )
}
}
diff --git src/git-command-manager.ts src/git-command-manager.ts
index a45e15a86..f5ba40e9f 100644
--- src/git-command-manager.ts
+++ src/git-command-manager.ts
@@ -37,7 +37,6 @@ export interface IGitCommandManager {
options: {
filter?: string
fetchDepth?: number
- fetchTags?: boolean
showProgress?: boolean
}
): Promise<void>
@@ -280,14 +279,13 @@ class GitCommandManager {
options: {
filter?: string
fetchDepth?: number
- fetchTags?: boolean
showProgress?: boolean
}
): Promise<void> {
const args = ['-c', 'protocol.version=2', 'fetch']
- if (!refSpec.some(x => x === refHelper.tagsRefSpec) && !options.fetchTags) {
- args.push('--no-tags')
- }
+ // Always use --no-tags for explicit control over tag fetching
+ // Tags are fetched explicitly via refspec when needed
+ args.push('--no-tags')
args.push('--prune', '--no-recurse-submodules')
if (options.showProgress) {
@@ -730,7 +728,19 @@ class GitCommandManager {
}
}
// Set the user agent
- const gitHttpUserAgent = `git/${this.gitVersion} (github-actions-checkout)`
+ let gitHttpUserAgent = `git/${this.gitVersion} (github-actions-checkout)`
+
+ // Append orchestration ID if set
+ const orchId = process.env['ACTIONS_ORCHESTRATION_ID']
+ if (orchId) {
+ // Sanitize the orchestration ID to ensure it contains only valid characters
+ // Valid characters: 0-9, a-z, _, -, .
+ const sanitizedId = orchId.replace(/[^a-z0-9_.-]/gi, '_')
+ if (sanitizedId) {
+ gitHttpUserAgent = `${gitHttpUserAgent} actions_orchestration_id/${sanitizedId}`
+ }
+ }
+
core.debug(`Set git useragent to: ${gitHttpUserAgent}`)
this.gitEnv['GIT_HTTP_USER_AGENT'] = gitHttpUserAgent
}
diff --git src/git-source-provider.ts src/git-source-provider.ts
index 2d3513897..ec871784f 100644
--- src/git-source-provider.ts
+++ src/git-source-provider.ts
@@ -159,7 +159,6 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
const fetchOptions: {
filter?: string
fetchDepth?: number
- fetchTags?: boolean
showProgress?: boolean
} = {}
@@ -182,12 +181,35 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
if (!(await refHelper.testRef(git, settings.ref, settings.commit))) {
refSpec = refHelper.getRefSpec(settings.ref, settings.commit)
await git.fetch(refSpec, fetchOptions)
+
+ // Verify the ref now matches. For branches, the targeted fetch above brings
+ // in the specific commit. For tags (fetched by ref), this will fail if
+ // the tag was moved after the workflow was triggered.
+ if (!(await refHelper.testRef(git, settings.ref, settings.commit))) {
+ throw new Error(
+ `The ref '${settings.ref}' does not point to the expected commit '${settings.commit}'. ` +
+ `The ref may have been updated after the workflow was triggered.`
+ )
+ }
}
} else {
fetchOptions.fetchDepth = settings.fetchDepth
- fetchOptions.fetchTags = settings.fetchTags
- const refSpec = refHelper.getRefSpec(settings.ref, settings.commit)
+ const refSpec = refHelper.getRefSpec(
+ settings.ref,
+ settings.commit,
+ settings.fetchTags
+ )
await git.fetch(refSpec, fetchOptions)
+
+ // For tags, verify the ref still points to the expected commit.
+ // Tags are fetched by ref (not commit), so if a tag was moved after the
+ // workflow was triggered, we would silently check out the wrong commit.
+ if (!(await refHelper.testRef(git, settings.ref, settings.commit))) {
+ throw new Error(
+ `The ref '${settings.ref}' does not point to the expected commit '${settings.commit}'. ` +
+ `The ref may have been updated after the workflow was triggered.`
+ )
+ }
}
core.endGroup()
diff --git src/misc/generate-docs.ts src/misc/generate-docs.ts
index 6d4816f15..b78f035c5 100644
--- src/misc/generate-docs.ts
+++ src/misc/generate-docs.ts
@@ -120,7 +120,7 @@ function updateUsage(
}
updateUsage(
- 'actions/checkout@v5',
+ 'actions/checkout@v6',
path.join(__dirname, '..', '..', 'action.yml'),
path.join(__dirname, '..', '..', 'README.md')
)
diff --git src/ref-helper.ts src/ref-helper.ts
index 58f929098..5130f53d7 100644
--- src/ref-helper.ts
+++ src/ref-helper.ts
@@ -76,55 +76,75 @@ export function getRefSpecForAllHistory(ref: string, commit: string): string[] {
return result
}
-export function getRefSpec(ref: string, commit: string): string[] {
+export function getRefSpec(
+ ref: string,
+ commit: string,
+ fetchTags?: boolean
+): string[] {
if (!ref && !commit) {
throw new Error('Args ref and commit cannot both be empty')
}
const upperRef = (ref || '').toUpperCase()
+ const result: string[] = []
+
+ // When fetchTags is true, always include the tags refspec
+ if (fetchTags) {
+ result.push(tagsRefSpec)
+ }
// SHA
if (commit) {
// refs/heads
if (upperRef.startsWith('REFS/HEADS/')) {
const branch = ref.substring('refs/heads/'.length)
- return [`+${commit}:refs/remotes/origin/${branch}`]
+ result.push(`+${commit}:refs/remotes/origin/${branch}`)
}
// refs/pull/
else if (upperRef.startsWith('REFS/PULL/')) {
const branch = ref.substring('refs/pull/'.length)
- return [`+${commit}:refs/remotes/pull/${branch}`]
+ result.push(`+${commit}:refs/remotes/pull/${branch}`)
}
// refs/tags/
else if (upperRef.startsWith('REFS/TAGS/')) {
- return [`+${commit}:${ref}`]
+ if (!fetchTags) {
+ result.push(`+${ref}:${ref}`)
+ }
}
// Otherwise no destination ref
else {
- return [commit]
+ result.push(commit)
}
}
// Unqualified ref, check for a matching branch or tag
else if (!upperRef.startsWith('REFS/')) {
- return [
- `+refs/heads/${ref}*:refs/remotes/origin/${ref}*`,
- `+refs/tags/${ref}*:refs/tags/${ref}*`
- ]
+ result.push(`+refs/heads/${ref}*:refs/remotes/origin/${ref}*`)
+ if (!fetchTags) {
+ result.push(`+refs/tags/${ref}*:refs/tags/${ref}*`)
+ }
}
// refs/heads/
else if (upperRef.startsWith('REFS/HEADS/')) {
const branch = ref.substring('refs/heads/'.length)
- return [`+${ref}:refs/remotes/origin/${branch}`]
+ result.push(`+${ref}:refs/remotes/origin/${branch}`)
}
// refs/pull/
else if (upperRef.startsWith('REFS/PULL/')) {
const branch = ref.substring('refs/pull/'.length)
- return [`+${ref}:refs/remotes/pull/${branch}`]
+ result.push(`+${ref}:refs/remotes/pull/${branch}`)
}
// refs/tags/
+ else if (upperRef.startsWith('REFS/TAGS/')) {
+ if (!fetchTags) {
+ result.push(`+${ref}:${ref}`)
+ }
+ }
+ // Other refs
else {
- return [`+${ref}:${ref}`]
+ result.push(`+${ref}:${ref}`)
}
+
+ return result
}
/**
@@ -170,8 +190,10 @@ export async function testRef(
// refs/tags/
else if (upperRef.startsWith('REFS/TAGS/')) {
const tagName = ref.substring('refs/tags/'.length)
+ // Use ^{commit} to dereference annotated tags to their underlying commit
return (
- (await git.tagExists(tagName)) && commit === (await git.revParse(ref))
+ (await git.tagExists(tagName)) &&
+ commit === (await git.revParse(`${ref}^{commit}`))
)
}
// Unexpected
DescriptionThis PR introduces several improvements to the
Possible Issues
Security Hotspots
ChangesChanges
sequenceDiagram
participant Runner as GitHub Runner
participant Action as checkout action
participant Git as git CLI
participant Remote as GitHub Remote
Runner->>Action: Start checkout (ref, commit, fetchTags)
Action->>Git: createCommandManager()
Git-->>Action: git version (+ set user-agent with orchId)
Action->>Git: init()
Action->>Git: remoteAdd(origin, url)
alt persist-credentials
Action->>Git: config(includeIf.gitdir:...path)
Action->>Git: config(includeIf.gitdir:.../worktrees/*.path)
Action->>Git: config(includeIf.gitdir:container...path)
Action->>Git: config(includeIf.gitdir:container.../worktrees/*.path)
end
Action->>Action: getRefSpec(ref, commit, fetchTags)
Note over Action: If fetchTags, prepend +refs/tags/*:refs/tags/*
Note over Action: Always pass --no-tags to fetch
Action->>Git: fetch(refSpec, {fetchDepth, ...})
Git->>Remote: git fetch --no-tags --prune ... refSpecs
Remote-->>Git: objects + refs
Action->>Git: testRef(ref, commit)
Note over Git: For tags: revParse(ref^{commit})
Git-->>Action: match? true/false
alt ref mismatch
Action-->>Runner: Error: ref moved after trigger
else ref matches
Action->>Git: checkout(ref, startPoint)
Git-->>Action: working tree updated
Action-->>Runner: Checkout complete
end
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
v6.0.0→v6.0.2Release Notes
actions/checkout (actions/checkout)
v6.0.2Compare Source
v6.0.1Compare Source
Configuration
📅 Schedule: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.