Compare commits

..

11 Commits

Author SHA1 Message Date
Cory Miller b4ffde65f4
Link to release page from what's new section (#1514) 2023-10-17 11:52:30 -04:00
Peter Bengtsson 8530928916
Correct link to GitHub Docs (#1511)
No hardcoded language in the URL and uses the correct docs.github.com domain.
2023-10-13 11:07:47 -04:00
Josh Gross 7cdaf2fbc0
Update CODEOWNERS to Launch team (#1510) 2023-10-10 11:16:51 -04:00
Cory Miller 8ade135a41
Prepare 4.1.0 release (#1496) 2023-09-22 13:40:21 -04:00
Finley Garton c533a0a4cf
Add support for partial checkout filters (#1396)
* added filter option & tests

* added build file

* fix test oversight

* added exit 1

* updated docs to specify override

* undo unneeded readme change

* set to undefined rather than empty string

* run git config in correct di

---------

Co-authored-by: Cory Miller <13227161+cory-miller@users.noreply.github.com>
2023-09-22 13:30:36 -04:00
Varun Sivapalan 72f2cec99f
Update README.md for V4 (#1452)
* Update README.md for V4

* Update actionReference in generate-docs.ts for v4
2023-09-05 09:21:52 -04:00
Tatyana Kostromskaya 3df4ab11eb
Release 4.0.0 (#1447)
* Release 4.0.0

* Add new major version to workflow
2023-09-04 14:19:40 +02:00
Simon Baird 8b5e8b7687
Support fetching without the --progress option (#1067)
Setting the `show-progress` option to false in the `with` section of the
workflow step will cause git fetch to run without `--progress`.

The motivation is to be able to suppress the noisy progress status
output which adds many hundreds of "remote: Counting objects: 85%
(386/453)" and similar lines in the workflow log.

This should be sufficient to resolve #894 and its older friends,
though the solution is different to the one proposed there because
it doesn't use the --quiet flag. IIUC git doesn't show the progress
status by default since the output is not a terminal, so that's why
removing the --progress option is all that's needed.

Adding the --quiet flag doesn't make a lot of difference once the
--progress flag is removed, and actually I think using --quiet would
suppress some other more useful output that would be better left
visible.

Signed-off-by: Simon Baird <sbaird@redhat.com>
2023-09-01 14:19:18 -04:00
Tatyana Kostromskaya 97a652b800
Update default runtime to node20 (#1436)
* .

* update workflows
2023-08-29 17:01:52 +02:00
Luke Tomlinson f43a0e5ff2
Release 3.6.0 (#1437) 2023-08-24 09:54:47 -04:00
Robert Wieczoreck 7739b9ba2e
Add option to fetch tags even if fetch-depth > 0 (#579)
* Add option to fetch tags even if fetch-depth > 0

* Add jest tests for fetchDepth and fetchTags options
2023-08-16 16:34:54 -04:00
20 changed files with 584 additions and 16031 deletions

View File

@ -24,10 +24,10 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Set Node.js 16.x - name: Set Node.js 20.x
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: 16.x node-version: 20.x
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci

View File

@ -13,7 +13,7 @@ jobs:
steps: steps:
- uses: actions/setup-node@v1 - uses: actions/setup-node@v1
with: with:
node-version: 16.x node-version: 20.x
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- run: npm ci - run: npm ci
- run: npm run build - run: npm run build
@ -72,6 +72,16 @@ jobs:
shell: bash shell: bash
run: __test__/verify-side-by-side.sh run: __test__/verify-side-by-side.sh
# Filter
- name: Fetch filter
uses: ./
with:
filter: 'blob:none'
path: fetch-filter
- name: Verify fetch filter
run: __test__/verify-fetch-filter.sh
# Sparse checkout # Sparse checkout
- name: Sparse checkout - name: Sparse checkout
uses: ./ uses: ./

View File

@ -11,6 +11,7 @@ on:
type: choice type: choice
description: The major version to update description: The major version to update
options: options:
- v4
- v3 - v3
- v2 - v2

View File

@ -1,5 +1,16 @@
# Changelog # Changelog
## v4.1.0
- [Add support for partial checkout filters](https://github.com/actions/checkout/pull/1396)
## v4.0.0
- [Support fetching without the --progress option](https://github.com/actions/checkout/pull/1067)
- [Update to node20](https://github.com/actions/checkout/pull/1436)
## v3.6.0
- [Fix: Mark test scripts with Bash'isms to be run via Bash](https://github.com/actions/checkout/pull/1377)
- [Add option to fetch tags even if fetch-depth > 0](https://github.com/actions/checkout/pull/579)
## v3.5.3 ## v3.5.3
- [Fix: Checkout fail in self-hosted runners when faulty submodule are checked-in](https://github.com/actions/checkout/pull/1196) - [Fix: Checkout fail in self-hosted runners when faulty submodule are checked-in](https://github.com/actions/checkout/pull/1196)
- [Fix typos found by codespell](https://github.com/actions/checkout/pull/1287) - [Fix typos found by codespell](https://github.com/actions/checkout/pull/1287)

View File

@ -1 +1 @@
* @actions/actions-runtime * @actions/actions-launch

View File

@ -1,10 +1,10 @@
[![Build and Test](https://github.com/actions/checkout/actions/workflows/test.yml/badge.svg)](https://github.com/actions/checkout/actions/workflows/test.yml) [![Build and Test](https://github.com/actions/checkout/actions/workflows/test.yml/badge.svg)](https://github.com/actions/checkout/actions/workflows/test.yml)
# Checkout V3 # Checkout V4
This action checks-out your repository under `$GITHUB_WORKSPACE`, so your workflow can access it. This action checks-out your repository under `$GITHUB_WORKSPACE`, so your workflow can access it.
Only a single commit is fetched by default, for the ref/SHA that triggered the workflow. Set `fetch-depth: 0` to fetch all history for all branches and tags. Refer [here](https://help.github.com/en/articles/events-that-trigger-workflows) to learn which commit `$GITHUB_SHA` points to for different events. Only a single commit is fetched by default, for the ref/SHA that triggered the workflow. Set `fetch-depth: 0` to fetch all history for all branches and tags. Refer [here](https://docs.github.com/actions/using-workflows/events-that-trigger-workflows) to learn which commit `$GITHUB_SHA` points to for different events.
The auth token is persisted in the local git config. This enables your scripts to run authenticated git commands. The token is removed during post-job cleanup. Set `persist-credentials: false` to opt-out. The auth token is persisted in the local git config. This enables your scripts to run authenticated git commands. The token is removed during post-job cleanup. Set `persist-credentials: false` to opt-out.
@ -12,14 +12,13 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
# What's new # What's new
- Updated to the node16 runtime by default Please refer to the [release page](https://github.com/actions/checkout/releases/latest) for the latest release notes.
- This requires a minimum [Actions Runner](https://github.com/actions/runner/releases/tag/v2.285.0) version of v2.285.0 to run, which is by default available in GHES 3.4 or later.
# Usage # Usage
<!-- start usage --> <!-- start usage -->
```yaml ```yaml
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
# Repository name with owner. For example, actions/checkout # Repository name with owner. For example, actions/checkout
# Default: ${{ github.repository }} # Default: ${{ github.repository }}
@ -74,8 +73,12 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
# Default: true # Default: true
clean: '' clean: ''
# Partially clone against a given filter. Overrides sparse-checkout if set.
# Default: null
filter: ''
# Do a sparse checkout on given patterns. Each pattern should be separated with # Do a sparse checkout on given patterns. Each pattern should be separated with
# new lines # new lines.
# Default: null # Default: null
sparse-checkout: '' sparse-checkout: ''
@ -87,6 +90,14 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
# Default: 1 # Default: 1
fetch-depth: '' fetch-depth: ''
# Whether to fetch tags, even if fetch-depth > 0.
# Default: false
fetch-tags: ''
# Whether to show progress status output when fetching.
# Default: true
show-progress: ''
# Whether to download Git-LFS files # Whether to download Git-LFS files
# Default: false # Default: false
lfs: '' lfs: ''
@ -131,7 +142,7 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
## Fetch only the root files ## Fetch only the root files
```yaml ```yaml
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
sparse-checkout: . sparse-checkout: .
``` ```
@ -139,7 +150,7 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
## Fetch only the root files and `.github` and `src` folder ## Fetch only the root files and `.github` and `src` folder
```yaml ```yaml
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
sparse-checkout: | sparse-checkout: |
.github .github
@ -149,7 +160,7 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
## Fetch only a single file ## Fetch only a single file
```yaml ```yaml
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
sparse-checkout: | sparse-checkout: |
README.md README.md
@ -159,7 +170,7 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
## Fetch all history for all tags and branches ## Fetch all history for all tags and branches
```yaml ```yaml
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
``` ```
@ -167,7 +178,7 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
## Checkout a different branch ## Checkout a different branch
```yaml ```yaml
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
ref: my-branch ref: my-branch
``` ```
@ -175,7 +186,7 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
## Checkout HEAD^ ## Checkout HEAD^
```yaml ```yaml
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
fetch-depth: 2 fetch-depth: 2
- run: git checkout HEAD^ - run: git checkout HEAD^
@ -185,12 +196,12 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
```yaml ```yaml
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
path: main path: main
- name: Checkout tools repo - name: Checkout tools repo
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
repository: my-org/my-tools repository: my-org/my-tools
path: my-tools path: my-tools
@ -201,10 +212,10 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
```yaml ```yaml
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Checkout tools repo - name: Checkout tools repo
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
repository: my-org/my-tools repository: my-org/my-tools
path: my-tools path: my-tools
@ -215,12 +226,12 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
```yaml ```yaml
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
path: main path: main
- name: Checkout private tools - name: Checkout private tools
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
repository: my-org/my-private-tools repository: my-org/my-private-tools
token: ${{ secrets.GH_PAT }} # `GH_PAT` is a secret that contains your PAT token: ${{ secrets.GH_PAT }} # `GH_PAT` is a secret that contains your PAT
@ -233,7 +244,7 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
## Checkout pull request HEAD commit instead of merge commit ## Checkout pull request HEAD commit instead of merge commit
```yaml ```yaml
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
``` ```
@ -249,7 +260,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
``` ```
## Push a commit using the built-in token ## Push a commit using the built-in token
@ -260,7 +271,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- run: | - run: |
date > generated.txt date > generated.txt
git config user.name github-actions git config user.name github-actions

View File

@ -94,11 +94,11 @@ describe('git-auth-helper tests', () => {
`x-access-token:${settings.authToken}`, `x-access-token:${settings.authToken}`,
'utf8' 'utf8'
).toString('base64') ).toString('base64')
// expect( expect(
// configContent.indexOf( configContent.indexOf(
// `http.${expectedServerUrl}/.extraheader AUTHORIZATION: basic ${basicCredential}` `http.${expectedServerUrl}/.extraheader AUTHORIZATION: basic ${basicCredential}`
// ) )
// ).toBeGreaterThanOrEqual(0) ).toBeGreaterThanOrEqual(0)
} }
const configureAuth_configuresAuthHeader = const configureAuth_configuresAuthHeader =
@ -145,11 +145,11 @@ describe('git-auth-helper tests', () => {
const configContent = ( const configContent = (
await fs.promises.readFile(localGitConfigPath) await fs.promises.readFile(localGitConfigPath)
).toString() ).toString()
// expect( expect(
// configContent.indexOf( configContent.indexOf(
// `http.https://github.com/.extraheader AUTHORIZATION` `http.https://github.com/.extraheader AUTHORIZATION`
// ) )
// ).toBeGreaterThanOrEqual(0) ).toBeGreaterThanOrEqual(0)
} }
) )
@ -419,11 +419,11 @@ describe('git-auth-helper tests', () => {
expect( expect(
configContent.indexOf('value-from-global-config') configContent.indexOf('value-from-global-config')
).toBeGreaterThanOrEqual(0) ).toBeGreaterThanOrEqual(0)
// expect( expect(
// configContent.indexOf( configContent.indexOf(
// `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}` `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}`
// ) )
// ).toBeGreaterThanOrEqual(0) ).toBeGreaterThanOrEqual(0)
}) })
const configureGlobalAuth_createsNewGlobalGitConfigWhenGlobalDoesNotExist = const configureGlobalAuth_createsNewGlobalGitConfigWhenGlobalDoesNotExist =
@ -463,11 +463,11 @@ describe('git-auth-helper tests', () => {
const configContent = ( const configContent = (
await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig')) await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig'))
).toString() ).toString()
// expect( expect(
// configContent.indexOf( configContent.indexOf(
// `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}` `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}`
// ) )
// ).toBeGreaterThanOrEqual(0) ).toBeGreaterThanOrEqual(0)
} }
) )
@ -554,7 +554,7 @@ describe('git-auth-helper tests', () => {
expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch( expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
/unset-all.*insteadOf/ /unset-all.*insteadOf/
) )
// expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/) expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/)
expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch( expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(
/url.*insteadOf.*git@github.com:/ /url.*insteadOf.*git@github.com:/
) )
@ -593,7 +593,7 @@ describe('git-auth-helper tests', () => {
expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch( expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
/unset-all.*insteadOf/ /unset-all.*insteadOf/
) )
// expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/) expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/)
expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(/core\.sshCommand/) expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(/core\.sshCommand/)
} }
) )
@ -802,9 +802,12 @@ async function setup(testName: string): Promise<void> {
authToken: 'some auth token', authToken: 'some auth token',
clean: true, clean: true,
commit: '', commit: '',
filter: undefined,
sparseCheckout: [], sparseCheckout: [],
sparseCheckoutConeMode: true, sparseCheckoutConeMode: true,
fetchDepth: 1, fetchDepth: 1,
fetchTags: false,
showProgress: true,
lfs: false, lfs: false,
submodules: false, submodules: false,
nestedSubmodules: false, nestedSubmodules: false,

View File

@ -88,3 +88,291 @@ describe('git-auth-helper tests', () => {
expect(branches.sort()).toEqual(['foo'].sort()) 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

@ -79,9 +79,12 @@ describe('input-helper tests', () => {
expect(settings.clean).toBe(true) expect(settings.clean).toBe(true)
expect(settings.commit).toBeTruthy() expect(settings.commit).toBeTruthy()
expect(settings.commit).toBe('1234567890123456789012345678901234567890') expect(settings.commit).toBe('1234567890123456789012345678901234567890')
expect(settings.filter).toBe(undefined)
expect(settings.sparseCheckout).toBe(undefined) expect(settings.sparseCheckout).toBe(undefined)
expect(settings.sparseCheckoutConeMode).toBe(true) expect(settings.sparseCheckoutConeMode).toBe(true)
expect(settings.fetchDepth).toBe(1) expect(settings.fetchDepth).toBe(1)
expect(settings.fetchTags).toBe(false)
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.repositoryName).toBe('some-repo') expect(settings.repositoryName).toBe('some-repo')

16
__test__/verify-fetch-filter.sh Executable file
View File

@ -0,0 +1,16 @@
#!/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

@ -53,10 +53,15 @@ inputs:
clean: clean:
description: 'Whether to execute `git clean -ffdx && git reset --hard HEAD` before fetching' description: 'Whether to execute `git clean -ffdx && git reset --hard HEAD` before fetching'
default: true default: true
filter:
description: >
Partially clone against a given filter.
Overrides sparse-checkout if set.
default: null
sparse-checkout: sparse-checkout:
description: > description: >
Do a sparse checkout on given patterns. Do a sparse checkout on given patterns.
Each pattern should be separated with new lines Each pattern should be separated with new lines.
default: null default: null
sparse-checkout-cone-mode: sparse-checkout-cone-mode:
description: > description: >
@ -65,6 +70,12 @@ inputs:
fetch-depth: fetch-depth:
description: 'Number of commits to fetch. 0 indicates all history for all branches and tags.' description: 'Number of commits to fetch. 0 indicates all history for all branches and tags.'
default: 1 default: 1
fetch-tags:
description: 'Whether to fetch tags, even if fetch-depth > 0.'
default: false
show-progress:
description: 'Whether to show progress status output when fetching.'
default: true
lfs: lfs:
description: 'Whether to download Git-LFS files' description: 'Whether to download Git-LFS files'
default: false default: false
@ -84,6 +95,6 @@ inputs:
description: The base URL for the GitHub instance that you are trying to clone from, will use environment defaults to fetch from the same instance that the workflow is running from unless specified. Example URLs are https://github.com or https://my-ghes-server.example.com description: The base URL for the GitHub instance that you are trying to clone from, will use environment defaults to fetch from the same instance that the workflow is running from unless specified. Example URLs are https://github.com or https://my-ghes-server.example.com
required: false required: false
runs: runs:
using: node16 using: node20
main: dist/index.js main: dist/index.js
post: dist/index.js post: dist/index.js

91
dist/index.js vendored
View File

@ -159,11 +159,11 @@ class GitAuthHelper {
this.sshKeyPath = ''; this.sshKeyPath = '';
this.sshKnownHostsPath = ''; this.sshKnownHostsPath = '';
this.temporaryHomePath = ''; this.temporaryHomePath = '';
this.gitConfigPath = '';
this.git = gitCommandManager; this.git = gitCommandManager;
this.settings = gitSourceSettings || {}; this.settings = gitSourceSettings || {};
// Token auth header // Token auth header
const serverUrl = urlHelper.getServerUrl(this.settings.githubServerUrl); const serverUrl = urlHelper.getServerUrl(this.settings.githubServerUrl);
this.tokenConfigKey = `http.${serverUrl.origin}/.extraheader`; // "origin" is SCHEME://HOSTNAME[:PORT]
const basicCredential = Buffer.from(`x-access-token:${this.settings.authToken}`, 'utf8').toString('base64'); const basicCredential = Buffer.from(`x-access-token:${this.settings.authToken}`, 'utf8').toString('base64');
core.setSecret(basicCredential); core.setSecret(basicCredential);
this.tokenPlaceholderConfigValue = `AUTHORIZATION: basic ***`; this.tokenPlaceholderConfigValue = `AUTHORIZATION: basic ***`;
@ -181,15 +181,12 @@ class GitAuthHelper {
yield this.removeAuth(); yield this.removeAuth();
// Configure new values // Configure new values
yield this.configureSsh(); yield this.configureSsh();
yield this.configureCredentialsHelper(); yield this.configureToken();
}); });
} }
configureTempGlobalConfig() { configureTempGlobalConfig() {
var _a, _b; var _a, _b;
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
if (!!this.gitConfigPath) {
return this.gitConfigPath;
}
// Already setup global config // Already setup global config
if (((_a = this.temporaryHomePath) === null || _a === void 0 ? void 0 : _a.length) > 0) { if (((_a = this.temporaryHomePath) === null || _a === void 0 ? void 0 : _a.length) > 0) {
return path.join(this.temporaryHomePath, '.gitconfig'); return path.join(this.temporaryHomePath, '.gitconfig');
@ -202,7 +199,7 @@ class GitAuthHelper {
yield fs.promises.mkdir(this.temporaryHomePath, { recursive: true }); yield fs.promises.mkdir(this.temporaryHomePath, { recursive: true });
// Copy the global git config // Copy the global git config
const gitConfigPath = path.join(process.env['HOME'] || os.homedir(), '.gitconfig'); const gitConfigPath = path.join(process.env['HOME'] || os.homedir(), '.gitconfig');
this.gitConfigPath = path.join(this.temporaryHomePath, '.gitconfig'); const newGitConfigPath = path.join(this.temporaryHomePath, '.gitconfig');
let configExists = false; let configExists = false;
try { try {
yield fs.promises.stat(gitConfigPath); yield fs.promises.stat(gitConfigPath);
@ -214,31 +211,16 @@ class GitAuthHelper {
} }
} }
if (configExists) { if (configExists) {
core.info(`Copying '${gitConfigPath}' to '${this.gitConfigPath}'`); core.info(`Copying '${gitConfigPath}' to '${newGitConfigPath}'`);
yield io.cp(gitConfigPath, this.gitConfigPath); yield io.cp(gitConfigPath, newGitConfigPath);
} }
else { else {
yield fs.promises.writeFile(this.gitConfigPath, ''); yield fs.promises.writeFile(newGitConfigPath, '');
} }
// Override HOME // Override HOME
core.info(`Temporarily overriding HOME='${this.temporaryHomePath}' before making global git config changes`); core.info(`Temporarily overriding HOME='${this.temporaryHomePath}' before making global git config changes`);
this.git.setEnvironmentVariable('HOME', this.temporaryHomePath); this.git.setEnvironmentVariable('HOME', this.temporaryHomePath);
return this.gitConfigPath; return newGitConfigPath;
});
}
configureCredentialsHelper() {
return __awaiter(this, void 0, void 0, function* () {
if (this.settings.lfs) {
core.info(`lfs disabled, skipping custom credentials helper`);
return;
}
const newGitConfigPath = yield this.configureTempGlobalConfig();
const credentialHelper = `
[credential]
helper = "!f() { echo username=x-access-token; echo password=${this.tokenConfigValue}; };f"
`;
core.info(`Configuring git to use a custom credential helper for aut to handle git lfs`);
yield fs.promises.appendFile(newGitConfigPath, credentialHelper);
}); });
} }
configureGlobalAuth() { configureGlobalAuth() {
@ -247,6 +229,7 @@ class GitAuthHelper {
const newGitConfigPath = yield this.configureTempGlobalConfig(); const newGitConfigPath = yield this.configureTempGlobalConfig();
try { try {
// Configure the token // Configure the token
yield this.configureToken(newGitConfigPath, true);
// Configure HTTPS instead of SSH // Configure HTTPS instead of SSH
yield this.git.tryConfigUnset(this.insteadOfKey, true); yield this.git.tryConfigUnset(this.insteadOfKey, true);
if (!this.settings.sshKey) { if (!this.settings.sshKey) {
@ -258,6 +241,7 @@ class GitAuthHelper {
catch (err) { catch (err) {
// Unset in case somehow written to the real global config // Unset in case somehow written to the real global config
core.info('Encountered an error when attempting to configure token. Attempting unconfigure.'); core.info('Encountered an error when attempting to configure token. Attempting unconfigure.');
yield this.git.tryConfigUnset(this.tokenConfigKey, true);
throw err; throw err;
} }
}); });
@ -272,7 +256,7 @@ class GitAuthHelper {
// refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing // refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
const output = yield this.git.submoduleForeach( const output = yield this.git.submoduleForeach(
// wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline // wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline
`sh -c "git config --local --show-origin --name-only --get-regexp remote.origin.url"`, this.settings.nestedSubmodules); `sh -c "git config --local '${this.tokenConfigKey}' '${this.tokenPlaceholderConfigValue}' && git config --local --show-origin --name-only --get-regexp remote.origin.url"`, this.settings.nestedSubmodules);
// Replace the placeholder // Replace the placeholder
const configPaths = output.match(/(?<=(^|\n)file:)[^\t]+(?=\tremote\.origin\.url)/g) || []; const configPaths = output.match(/(?<=(^|\n)file:)[^\t]+(?=\tremote\.origin\.url)/g) || [];
for (const configPath of configPaths) { for (const configPath of configPaths) {
@ -295,6 +279,7 @@ class GitAuthHelper {
removeAuth() { removeAuth() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
yield this.removeSsh(); yield this.removeSsh();
yield this.removeToken();
}); });
} }
removeGlobalConfig() { removeGlobalConfig() {
@ -364,6 +349,22 @@ class GitAuthHelper {
} }
}); });
} }
configureToken(configPath, globalConfig) {
return __awaiter(this, void 0, void 0, function* () {
// Validate args
assert.ok((configPath && globalConfig) || (!configPath && !globalConfig), 'Unexpected configureToken parameter combinations');
// Default config path
if (!configPath && !globalConfig) {
configPath = path.join(this.git.getWorkingDirectory(), '.git', 'config');
}
// Configure a placeholder value. This approach avoids the credential being captured
// by process creation audit events, which are commonly logged. For more information,
// refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
yield this.git.config(this.tokenConfigKey, this.tokenPlaceholderConfigValue, globalConfig);
// Replace the placeholder
yield this.replaceTokenPlaceholder(configPath || '');
});
}
replaceTokenPlaceholder(configPath) { replaceTokenPlaceholder(configPath) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
assert.ok(configPath, 'configPath is not defined'); assert.ok(configPath, 'configPath is not defined');
@ -406,6 +407,12 @@ class GitAuthHelper {
yield this.removeGitConfig(SSH_COMMAND_KEY); yield this.removeGitConfig(SSH_COMMAND_KEY);
}); });
} }
removeToken() {
return __awaiter(this, void 0, void 0, function* () {
// HTTP extra header
yield this.removeGitConfig(this.tokenConfigKey);
});
}
removeGitConfig(configKey, submoduleOnly = false) { removeGitConfig(configKey, submoduleOnly = false) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
if (!submoduleOnly) { if (!submoduleOnly) {
@ -630,10 +637,13 @@ class GitCommandManager {
fetch(refSpec, options) { fetch(refSpec, options) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const args = ['-c', 'protocol.version=2', 'fetch']; const args = ['-c', 'protocol.version=2', 'fetch'];
if (!refSpec.some(x => x === refHelper.tagsRefSpec)) { if (!refSpec.some(x => x === refHelper.tagsRefSpec) && !options.fetchTags) {
args.push('--no-tags'); args.push('--no-tags');
} }
args.push('--prune', '--progress', '--no-recurse-submodules'); args.push('--prune', '--no-recurse-submodules');
if (options.showProgress) {
args.push('--progress');
}
if (options.filter) { if (options.filter) {
args.push(`--filter=${options.filter}`); args.push(`--filter=${options.filter}`);
} }
@ -711,8 +721,8 @@ class GitCommandManager {
} }
log1(format) { log1(format) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
var args = format ? ['log', '-1', format] : ['log', '-1']; const args = format ? ['log', '-1', format] : ['log', '-1'];
var silent = format ? false : true; const silent = format ? false : true;
const output = yield this.execGit(args, false, silent); const output = yield this.execGit(args, false, silent);
return output.stdout; return output.stdout;
}); });
@ -1234,8 +1244,12 @@ function getSource(settings) {
// Fetch // Fetch
core.startGroup('Fetching the repository'); core.startGroup('Fetching the repository');
const fetchOptions = {}; const fetchOptions = {};
if (settings.sparseCheckout) if (settings.filter) {
fetchOptions.filter = settings.filter;
}
else if (settings.sparseCheckout) {
fetchOptions.filter = 'blob:none'; fetchOptions.filter = 'blob:none';
}
if (settings.fetchDepth <= 0) { if (settings.fetchDepth <= 0) {
// Fetch all branches and tags // Fetch all branches and tags
let refSpec = refHelper.getRefSpecForAllHistory(settings.ref, settings.commit); let refSpec = refHelper.getRefSpecForAllHistory(settings.ref, settings.commit);
@ -1249,6 +1263,7 @@ function getSource(settings) {
} }
else { else {
fetchOptions.fetchDepth = settings.fetchDepth; fetchOptions.fetchDepth = settings.fetchDepth;
fetchOptions.fetchTags = settings.fetchTags;
const refSpec = refHelper.getRefSpec(settings.ref, settings.commit); const refSpec = refHelper.getRefSpec(settings.ref, settings.commit);
yield git.fetch(refSpec, fetchOptions); yield git.fetch(refSpec, fetchOptions);
} }
@ -1712,6 +1727,12 @@ function getInputs() {
// Clean // Clean
result.clean = (core.getInput('clean') || 'true').toUpperCase() === 'TRUE'; result.clean = (core.getInput('clean') || 'true').toUpperCase() === 'TRUE';
core.debug(`clean = ${result.clean}`); core.debug(`clean = ${result.clean}`);
// Filter
const filter = core.getInput('filter');
if (filter) {
result.filter = filter;
}
core.debug(`filter = ${result.filter}`);
// Sparse checkout // Sparse checkout
const sparseCheckout = core.getMultilineInput('sparse-checkout'); const sparseCheckout = core.getMultilineInput('sparse-checkout');
if (sparseCheckout.length) { if (sparseCheckout.length) {
@ -1727,6 +1748,14 @@ function getInputs() {
result.fetchDepth = 0; result.fetchDepth = 0;
} }
core.debug(`fetch depth = ${result.fetchDepth}`); core.debug(`fetch depth = ${result.fetchDepth}`);
// Fetch tags
result.fetchTags =
(core.getInput('fetch-tags') || 'false').toUpperCase() === 'TRUE';
core.debug(`fetch tags = ${result.fetchTags}`);
// Show fetch progress
result.showProgress =
(core.getInput('show-progress') || 'true').toUpperCase() === 'TRUE';
core.debug(`show progress = ${result.showProgress}`);
// LFS // LFS
result.lfs = (core.getInput('lfs') || 'false').toUpperCase() === 'TRUE'; result.lfs = (core.getInput('lfs') || 'false').toUpperCase() === 'TRUE';
core.debug(`lfs = ${result.lfs}`); core.debug(`lfs = ${result.lfs}`);

15919
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "checkout", "name": "checkout",
"version": "3.5.3", "version": "4.1.0",
"description": "checkout action", "description": "checkout action",
"main": "lib/main.js", "main": "lib/main.js",
"scripts": { "scripts": {
@ -37,7 +37,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^27.0.2", "@types/jest": "^27.0.2",
"@types/node": "^12.7.12", "@types/node": "^20.5.3",
"@types/uuid": "^3.4.6", "@types/uuid": "^3.4.6",
"@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0", "@typescript-eslint/parser": "^5.45.0",

View File

@ -20,7 +20,6 @@ export interface IGitAuthHelper {
configureGlobalAuth(): Promise<void> configureGlobalAuth(): Promise<void>
configureSubmoduleAuth(): Promise<void> configureSubmoduleAuth(): Promise<void>
configureTempGlobalConfig(): Promise<string> configureTempGlobalConfig(): Promise<string>
configureCredentialsHelper(): Promise<void>
removeAuth(): Promise<void> removeAuth(): Promise<void>
removeGlobalConfig(): Promise<void> removeGlobalConfig(): Promise<void>
} }
@ -35,6 +34,7 @@ export function createAuthHelper(
class GitAuthHelper { class GitAuthHelper {
private readonly git: IGitCommandManager private readonly git: IGitCommandManager
private readonly settings: IGitSourceSettings private readonly settings: IGitSourceSettings
private readonly tokenConfigKey: string
private readonly tokenConfigValue: string private readonly tokenConfigValue: string
private readonly tokenPlaceholderConfigValue: string private readonly tokenPlaceholderConfigValue: string
private readonly insteadOfKey: string private readonly insteadOfKey: string
@ -43,7 +43,6 @@ class GitAuthHelper {
private sshKeyPath = '' private sshKeyPath = ''
private sshKnownHostsPath = '' private sshKnownHostsPath = ''
private temporaryHomePath = '' private temporaryHomePath = ''
private gitConfigPath = ''
constructor( constructor(
gitCommandManager: IGitCommandManager, gitCommandManager: IGitCommandManager,
@ -54,6 +53,7 @@ class GitAuthHelper {
// Token auth header // Token auth header
const serverUrl = urlHelper.getServerUrl(this.settings.githubServerUrl) const serverUrl = urlHelper.getServerUrl(this.settings.githubServerUrl)
this.tokenConfigKey = `http.${serverUrl.origin}/.extraheader` // "origin" is SCHEME://HOSTNAME[:PORT]
const basicCredential = Buffer.from( const basicCredential = Buffer.from(
`x-access-token:${this.settings.authToken}`, `x-access-token:${this.settings.authToken}`,
'utf8' 'utf8'
@ -78,13 +78,10 @@ class GitAuthHelper {
// Configure new values // Configure new values
await this.configureSsh() await this.configureSsh()
await this.configureCredentialsHelper() await this.configureToken()
} }
async configureTempGlobalConfig(): Promise<string> { async configureTempGlobalConfig(): Promise<string> {
if (!!this.gitConfigPath) {
return this.gitConfigPath
}
// Already setup global config // Already setup global config
if (this.temporaryHomePath?.length > 0) { if (this.temporaryHomePath?.length > 0) {
return path.join(this.temporaryHomePath, '.gitconfig') return path.join(this.temporaryHomePath, '.gitconfig')
@ -101,7 +98,7 @@ class GitAuthHelper {
process.env['HOME'] || os.homedir(), process.env['HOME'] || os.homedir(),
'.gitconfig' '.gitconfig'
) )
this.gitConfigPath = path.join(this.temporaryHomePath, '.gitconfig') const newGitConfigPath = path.join(this.temporaryHomePath, '.gitconfig')
let configExists = false let configExists = false
try { try {
await fs.promises.stat(gitConfigPath) await fs.promises.stat(gitConfigPath)
@ -112,10 +109,10 @@ class GitAuthHelper {
} }
} }
if (configExists) { if (configExists) {
core.info(`Copying '${gitConfigPath}' to '${this.gitConfigPath}'`) core.info(`Copying '${gitConfigPath}' to '${newGitConfigPath}'`)
await io.cp(gitConfigPath, this.gitConfigPath) await io.cp(gitConfigPath, newGitConfigPath)
} else { } else {
await fs.promises.writeFile(this.gitConfigPath, '') await fs.promises.writeFile(newGitConfigPath, '')
} }
// Override HOME // Override HOME
@ -124,25 +121,7 @@ class GitAuthHelper {
) )
this.git.setEnvironmentVariable('HOME', this.temporaryHomePath) this.git.setEnvironmentVariable('HOME', this.temporaryHomePath)
return this.gitConfigPath return newGitConfigPath
}
async configureCredentialsHelper(): Promise<void> {
if (this.settings.lfs) {
core.info(`lfs disabled, skipping custom credentials helper`)
return
}
const newGitConfigPath = await this.configureTempGlobalConfig()
const credentialHelper = `
[credential]
helper = "!f() { echo username=x-access-token; echo password=${this.tokenConfigValue}; };f"
`
core.info(
`Configuring git to use a custom credential helper for aut to handle git lfs`
)
await fs.promises.appendFile(newGitConfigPath, credentialHelper)
} }
async configureGlobalAuth(): Promise<void> { async configureGlobalAuth(): Promise<void> {
@ -150,6 +129,8 @@ class GitAuthHelper {
const newGitConfigPath = await this.configureTempGlobalConfig() const newGitConfigPath = await this.configureTempGlobalConfig()
try { try {
// Configure the token // Configure the token
await this.configureToken(newGitConfigPath, true)
// Configure HTTPS instead of SSH // Configure HTTPS instead of SSH
await this.git.tryConfigUnset(this.insteadOfKey, true) await this.git.tryConfigUnset(this.insteadOfKey, true)
if (!this.settings.sshKey) { if (!this.settings.sshKey) {
@ -162,6 +143,7 @@ class GitAuthHelper {
core.info( core.info(
'Encountered an error when attempting to configure token. Attempting unconfigure.' 'Encountered an error when attempting to configure token. Attempting unconfigure.'
) )
await this.git.tryConfigUnset(this.tokenConfigKey, true)
throw err throw err
} }
} }
@ -176,7 +158,7 @@ class GitAuthHelper {
// refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing // refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
const output = await this.git.submoduleForeach( const output = await this.git.submoduleForeach(
// wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline // wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline
`sh -c "git config --local --show-origin --name-only --get-regexp remote.origin.url"`, `sh -c "git config --local '${this.tokenConfigKey}' '${this.tokenPlaceholderConfigValue}' && git config --local --show-origin --name-only --get-regexp remote.origin.url"`,
this.settings.nestedSubmodules this.settings.nestedSubmodules
) )
@ -208,6 +190,7 @@ class GitAuthHelper {
async removeAuth(): Promise<void> { async removeAuth(): Promise<void> {
await this.removeSsh() await this.removeSsh()
await this.removeToken()
} }
async removeGlobalConfig(): Promise<void> { async removeGlobalConfig(): Promise<void> {
@ -289,6 +272,34 @@ class GitAuthHelper {
} }
} }
private async configureToken(
configPath?: string,
globalConfig?: boolean
): Promise<void> {
// Validate args
assert.ok(
(configPath && globalConfig) || (!configPath && !globalConfig),
'Unexpected configureToken parameter combinations'
)
// Default config path
if (!configPath && !globalConfig) {
configPath = path.join(this.git.getWorkingDirectory(), '.git', 'config')
}
// Configure a placeholder value. This approach avoids the credential being captured
// by process creation audit events, which are commonly logged. For more information,
// refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
await this.git.config(
this.tokenConfigKey,
this.tokenPlaceholderConfigValue,
globalConfig
)
// Replace the placeholder
await this.replaceTokenPlaceholder(configPath || '')
}
private async replaceTokenPlaceholder(configPath: string): Promise<void> { private async replaceTokenPlaceholder(configPath: string): Promise<void> {
assert.ok(configPath, 'configPath is not defined') assert.ok(configPath, 'configPath is not defined')
let content = (await fs.promises.readFile(configPath)).toString() let content = (await fs.promises.readFile(configPath)).toString()
@ -334,6 +345,11 @@ class GitAuthHelper {
await this.removeGitConfig(SSH_COMMAND_KEY) await this.removeGitConfig(SSH_COMMAND_KEY)
} }
private async removeToken(): Promise<void> {
// HTTP extra header
await this.removeGitConfig(this.tokenConfigKey)
}
private async removeGitConfig( private async removeGitConfig(
configKey: string, configKey: string,
submoduleOnly: boolean = false submoduleOnly: boolean = false

View File

@ -33,6 +33,8 @@ export interface IGitCommandManager {
options: { options: {
filter?: string filter?: string
fetchDepth?: number fetchDepth?: number
fetchTags?: boolean
showProgress?: boolean
} }
): Promise<void> ): Promise<void>
getDefaultBranch(repositoryUrl: string): Promise<string> getDefaultBranch(repositoryUrl: string): Promise<string>
@ -240,14 +242,22 @@ class GitCommandManager {
async fetch( async fetch(
refSpec: string[], refSpec: string[],
options: {filter?: string; fetchDepth?: number} options: {
filter?: string
fetchDepth?: number
fetchTags?: boolean
showProgress?: boolean
}
): Promise<void> { ): Promise<void> {
const args = ['-c', 'protocol.version=2', 'fetch'] const args = ['-c', 'protocol.version=2', 'fetch']
if (!refSpec.some(x => x === refHelper.tagsRefSpec)) { if (!refSpec.some(x => x === refHelper.tagsRefSpec) && !options.fetchTags) {
args.push('--no-tags') args.push('--no-tags')
} }
args.push('--prune', '--progress', '--no-recurse-submodules') args.push('--prune', '--no-recurse-submodules')
if (options.showProgress) {
args.push('--progress')
}
if (options.filter) { if (options.filter) {
args.push(`--filter=${options.filter}`) args.push(`--filter=${options.filter}`)
@ -333,8 +343,8 @@ class GitCommandManager {
} }
async log1(format?: string): Promise<string> { async log1(format?: string): Promise<string> {
var args = format ? ['log', '-1', format] : ['log', '-1'] const args = format ? ['log', '-1', format] : ['log', '-1']
var silent = format ? false : true const silent = format ? false : true
const output = await this.execGit(args, false, silent) const output = await this.execGit(args, false, silent)
return output.stdout return output.stdout
} }

View File

@ -153,8 +153,19 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
// Fetch // Fetch
core.startGroup('Fetching the repository') core.startGroup('Fetching the repository')
const fetchOptions: {filter?: string; fetchDepth?: number} = {} const fetchOptions: {
if (settings.sparseCheckout) fetchOptions.filter = 'blob:none' filter?: string
fetchDepth?: number
fetchTags?: boolean
showProgress?: boolean
} = {}
if (settings.filter) {
fetchOptions.filter = settings.filter
} else if (settings.sparseCheckout) {
fetchOptions.filter = 'blob:none'
}
if (settings.fetchDepth <= 0) { if (settings.fetchDepth <= 0) {
// Fetch all branches and tags // Fetch all branches and tags
let refSpec = refHelper.getRefSpecForAllHistory( let refSpec = refHelper.getRefSpecForAllHistory(
@ -171,6 +182,7 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
} }
} else { } else {
fetchOptions.fetchDepth = settings.fetchDepth fetchOptions.fetchDepth = settings.fetchDepth
fetchOptions.fetchTags = settings.fetchTags
const refSpec = refHelper.getRefSpec(settings.ref, settings.commit) const refSpec = refHelper.getRefSpec(settings.ref, settings.commit)
await git.fetch(refSpec, fetchOptions) await git.fetch(refSpec, fetchOptions)
} }

View File

@ -29,6 +29,11 @@ export interface IGitSourceSettings {
*/ */
clean: boolean clean: boolean
/**
* The filter determining which objects to include
*/
filter: string | undefined
/** /**
* The array of folders to make the sparse checkout * The array of folders to make the sparse checkout
*/ */
@ -44,6 +49,16 @@ export interface IGitSourceSettings {
*/ */
fetchDepth: number fetchDepth: number
/**
* Fetch tags, even if fetchDepth > 0 (default: false)
*/
fetchTags: boolean
/**
* Indicates whether to use the --progress option when fetching
*/
showProgress: boolean
/** /**
* Indicates whether to fetch LFS objects * Indicates whether to fetch LFS objects
*/ */

View File

@ -82,6 +82,14 @@ export async function getInputs(): Promise<IGitSourceSettings> {
result.clean = (core.getInput('clean') || 'true').toUpperCase() === 'TRUE' result.clean = (core.getInput('clean') || 'true').toUpperCase() === 'TRUE'
core.debug(`clean = ${result.clean}`) core.debug(`clean = ${result.clean}`)
// Filter
const filter = core.getInput('filter')
if (filter) {
result.filter = filter
}
core.debug(`filter = ${result.filter}`)
// Sparse checkout // Sparse checkout
const sparseCheckout = core.getMultilineInput('sparse-checkout') const sparseCheckout = core.getMultilineInput('sparse-checkout')
if (sparseCheckout.length) { if (sparseCheckout.length) {
@ -100,6 +108,16 @@ export async function getInputs(): Promise<IGitSourceSettings> {
} }
core.debug(`fetch depth = ${result.fetchDepth}`) core.debug(`fetch depth = ${result.fetchDepth}`)
// Fetch tags
result.fetchTags =
(core.getInput('fetch-tags') || 'false').toUpperCase() === 'TRUE'
core.debug(`fetch tags = ${result.fetchTags}`)
// Show fetch progress
result.showProgress =
(core.getInput('show-progress') || 'true').toUpperCase() === 'TRUE'
core.debug(`show progress = ${result.showProgress}`)
// LFS // LFS
result.lfs = (core.getInput('lfs') || 'false').toUpperCase() === 'TRUE' result.lfs = (core.getInput('lfs') || 'false').toUpperCase() === 'TRUE'
core.debug(`lfs = ${result.lfs}`) core.debug(`lfs = ${result.lfs}`)

View File

@ -120,7 +120,7 @@ function updateUsage(
} }
updateUsage( updateUsage(
'actions/checkout@v3', 'actions/checkout@v4',
path.join(__dirname, '..', '..', 'action.yml'), path.join(__dirname, '..', '..', 'action.yml'),
path.join(__dirname, '..', '..', 'README.md') path.join(__dirname, '..', '..', 'README.md')
) )