Compare commits

...

21 Commits

Author SHA1 Message Date
eric sciple 61b9e3751b
improve description for fetch-depth (#301) 2020-07-12 21:02:24 -04:00
eric sciple 28c7f3d2b5 changelog 2020-06-18 10:27:39 -04:00
eric sciple fb6f360df2
fix default branch for .wiki and when using ssh (#284) 2020-06-18 10:20:33 -04:00
eric sciple b4483adec3 changelog 2020-06-16 13:48:53 -04:00
eric sciple 00a3be8934
determine default branch (#278) 2020-06-16 13:41:01 -04:00
eric sciple 453ee27fca update troubleshooting instructions to include 'npm run format' 2020-05-31 17:48:51 -04:00
Daniel Hwang 65865e15a1
build because all is no more (#264) 2020-05-31 17:46:53 -04:00
eric sciple aabbfeb2ce changelog 2020-05-27 12:37:40 -04:00
eric sciple e52d022eb5
Fetch all history for all tags and branches when fetch-depth=0 (#258) 2020-05-27 09:54:28 -04:00
eric sciple 2ff2fbdea4
telemetry for incorrect merge commit (#253) 2020-05-21 11:09:16 -04:00
eric sciple df86c829eb
fix readme (#251) 2020-05-20 10:20:52 -04:00
Peter Evans 97b30c411c
fix prettier glob pattern (#247) 2020-05-19 12:34:05 -04:00
eric sciple 86f86b36ef changelog 2020-05-19 10:27:02 -04:00
eric sciple 7523e23789
switch GITHUB_URL to GITHUB_SERVER_URL (#248) 2020-05-18 13:05:15 -04:00
eric sciple ac455590d1
consume new @actions/github for GHES support (#236) 2020-05-07 12:11:11 -04:00
eric sciple 94c2de77cc Update changelog 2020-04-02 16:04:37 -04:00
eric sciple 01aecccf73
group output (#191) 2020-03-27 13:12:15 -04:00
eric sciple 85b1f35505
changes to support ghes alpha release (#199) 2020-03-25 15:12:22 -04:00
eric sciple 574281d34c update readme 2020-03-19 22:17:25 -04:00
Jef LeCompte fbb30c60ab
Removed doc based on new changes (#192) 2020-03-19 22:03:41 -04:00
eric sciple 58070a9fc3
update input descriptions in adr (#189) 2020-03-19 10:26:00 -04:00
20 changed files with 14902 additions and 677 deletions

View File

@ -1,5 +1,40 @@
# Changelog # Changelog
## v2.3.1
- [Fix default branch resolution for .wiki and when using SSH](https://github.com/actions/checkout/pull/284)
## v2.3.0
- [Fallback to the default branch](https://github.com/actions/checkout/pull/278)
## v2.2.0
- [Fetch all history for all tags and branches when fetch-depth=0](https://github.com/actions/checkout/pull/258)
## v2.1.1
- Changes to support GHES ([here](https://github.com/actions/checkout/pull/236) and [here](https://github.com/actions/checkout/pull/248))
## v2.1.0
- [Group output](https://github.com/actions/checkout/pull/191)
- [Changes to support GHES alpha release](https://github.com/actions/checkout/pull/199)
- [Persist core.sshCommand for submodules](https://github.com/actions/checkout/pull/184)
- [Add support ssh](https://github.com/actions/checkout/pull/163)
- [Convert submodule SSH URL to HTTPS, when not using SSH](https://github.com/actions/checkout/pull/179)
- [Add submodule support](https://github.com/actions/checkout/pull/157)
- [Follow proxy settings](https://github.com/actions/checkout/pull/144)
- [Fix ref for pr closed event when a pr is merged](https://github.com/actions/checkout/pull/141)
- [Fix issue checking detached when git less than 2.22](https://github.com/actions/checkout/pull/128)
## v2.0.0
- [Do not pass cred on command line](https://github.com/actions/checkout/pull/108)
- [Add input persist-credentials](https://github.com/actions/checkout/pull/107)
- [Fallback to REST API to download repo](https://github.com/actions/checkout/pull/104)
## v2 (beta) ## v2 (beta)
- Improved fetch performance - Improved fetch performance

View File

@ -6,7 +6,7 @@
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` to fetch more history. 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://help.github.com/en/articles/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.
@ -18,6 +18,7 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
- Fetches only a single commit by default - Fetches only a single commit by default
- Script authenticated git commands - Script authenticated git commands
- Auth token persisted in the local git config - Auth token persisted in the local git config
- Supports SSH
- Creates a local branch - Creates a local branch
- No longer detached HEAD when checking out a branch - No longer detached HEAD when checking out a branch
- Improved layout - Improved layout
@ -26,7 +27,6 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
- Fallback to REST API download - Fallback to REST API download
- When Git 2.18 or higher is not in the PATH, the REST API will be used to download the files - When Git 2.18 or higher is not in the PATH, the REST API will be used to download the files
- When using a job container, the container's PATH is used - When using a job container, the container's PATH is used
- Removed input `submodules`
Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous versions. Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous versions.
@ -42,7 +42,7 @@ Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous
# The branch, tag or SHA to checkout. When checking out the repository that # The branch, tag or SHA to checkout. When checking out the repository that
# triggered a workflow, this defaults to the reference or SHA for that event. # triggered a workflow, this defaults to the reference or SHA for that event.
# Otherwise, defaults to `master`. # Otherwise, uses the default branch.
ref: '' ref: ''
# Personal access token (PAT) used to fetch the repository. The PAT is configured # Personal access token (PAT) used to fetch the repository. The PAT is configured
@ -89,7 +89,7 @@ Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous
# Default: true # Default: true
clean: '' clean: ''
# Number of commits to fetch. 0 indicates all history. # Number of commits to fetch. 0 indicates all history for all branches and tags.
# Default: 1 # Default: 1
fetch-depth: '' fetch-depth: ''
@ -110,6 +110,7 @@ Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous
# Scenarios # Scenarios
- [Fetch all history for all tags and branches](#Fetch-all-history-for-all-tags-and-branches)
- [Checkout a different branch](#Checkout-a-different-branch) - [Checkout a different branch](#Checkout-a-different-branch)
- [Checkout HEAD^](#Checkout-HEAD) - [Checkout HEAD^](#Checkout-HEAD)
- [Checkout multiple repos (side by side)](#Checkout-multiple-repos-side-by-side) - [Checkout multiple repos (side by side)](#Checkout-multiple-repos-side-by-side)
@ -117,10 +118,14 @@ Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous
- [Checkout multiple repos (private)](#Checkout-multiple-repos-private) - [Checkout multiple repos (private)](#Checkout-multiple-repos-private)
- [Checkout pull request HEAD commit instead of merge commit](#Checkout-pull-request-HEAD-commit-instead-of-merge-commit) - [Checkout pull request HEAD commit instead of merge commit](#Checkout-pull-request-HEAD-commit-instead-of-merge-commit)
- [Checkout pull request on closed event](#Checkout-pull-request-on-closed-event) - [Checkout pull request on closed event](#Checkout-pull-request-on-closed-event)
- [Checkout submodules](#Checkout-submodules)
- [Fetch all tags](#Fetch-all-tags) ## Fetch all history for all tags and branches
- [Fetch all branches](#Fetch-all-branches)
- [Fetch all history for all tags and branches](#Fetch-all-history-for-all-tags-and-branches) ```yaml
- uses: actions/checkout@v2
with:
fetch-depth: 0
```
## Checkout a different branch ## Checkout a different branch
@ -208,43 +213,6 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
``` ```
## Checkout submodules
```yaml
- uses: actions/checkout@v2
- name: Checkout submodules
shell: bash
run: |
# If your submodules are configured to use SSH instead of HTTPS please uncomment the following line
# git config --global url."https://github.com/".insteadOf "git@github.com:"
auth_header="$(git config --local --get http.https://github.com/.extraheader)"
git submodule sync --recursive
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
```
## Fetch all tags
```yaml
- uses: actions/checkout@v2
- run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
```
## Fetch all branches
```yaml
- uses: actions/checkout@v2
- run: |
git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/*
```
## Fetch all history for all tags and branches
```yaml
- uses: actions/checkout@v2
- run: |
git fetch --prune --unshallow
```
# License # License
The scripts and documentation in this project are released under the [MIT License](LICENSE) The scripts and documentation in this project are released under the [MIT License](LICENSE)

View File

@ -714,6 +714,7 @@ async function setup(testName: string): Promise<void> {
), ),
env: {}, env: {},
fetch: jest.fn(), fetch: jest.fn(),
getDefaultBranch: jest.fn(),
getWorkingDirectory: jest.fn(() => workspace), getWorkingDirectory: jest.fn(() => workspace),
init: jest.fn(), init: jest.fn(),
isDetached: jest.fn(), isDetached: jest.fn(),
@ -722,9 +723,11 @@ async function setup(testName: string): Promise<void> {
log1: jest.fn(), log1: jest.fn(),
remoteAdd: jest.fn(), remoteAdd: jest.fn(),
removeEnvironmentVariable: jest.fn((name: string) => delete git.env[name]), removeEnvironmentVariable: jest.fn((name: string) => delete git.env[name]),
revParse: jest.fn(),
setEnvironmentVariable: jest.fn((name: string, value: string) => { setEnvironmentVariable: jest.fn((name: string, value: string) => {
git.env[name] = value git.env[name] = value
}), }),
shaExists: jest.fn(),
submoduleForeach: jest.fn(async () => { submoduleForeach: jest.fn(async () => {
return '' return ''
}), }),

View File

@ -9,6 +9,7 @@ const testWorkspace = path.join(__dirname, '_temp', 'git-directory-helper')
let repositoryPath: string let repositoryPath: string
let repositoryUrl: string let repositoryUrl: string
let clean: boolean let clean: boolean
let ref: string
let git: IGitCommandManager let git: IGitCommandManager
describe('git-directory-helper tests', () => { describe('git-directory-helper tests', () => {
@ -41,7 +42,8 @@ describe('git-directory-helper tests', () => {
git, git,
repositoryPath, repositoryPath,
repositoryUrl, repositoryUrl,
clean clean,
ref
) )
// Assert // Assert
@ -63,7 +65,8 @@ describe('git-directory-helper tests', () => {
git, git,
repositoryPath, repositoryPath,
repositoryUrl, repositoryUrl,
clean clean,
ref
) )
// Assert // Assert
@ -88,7 +91,8 @@ describe('git-directory-helper tests', () => {
git, git,
repositoryPath, repositoryPath,
repositoryUrl, repositoryUrl,
clean clean,
ref
) )
// Assert // Assert
@ -109,7 +113,8 @@ describe('git-directory-helper tests', () => {
git, git,
repositoryPath, repositoryPath,
repositoryUrl, repositoryUrl,
clean clean,
ref
) )
// Assert // Assert
@ -137,7 +142,8 @@ describe('git-directory-helper tests', () => {
git, git,
repositoryPath, repositoryPath,
repositoryUrl, repositoryUrl,
clean clean,
ref
) )
// Assert // Assert
@ -163,7 +169,8 @@ describe('git-directory-helper tests', () => {
git, git,
repositoryPath, repositoryPath,
differentRepositoryUrl, differentRepositoryUrl,
clean clean,
ref
) )
// Assert // Assert
@ -187,7 +194,8 @@ describe('git-directory-helper tests', () => {
git, git,
repositoryPath, repositoryPath,
repositoryUrl, repositoryUrl,
clean clean,
ref
) )
// Assert // Assert
@ -212,7 +220,8 @@ describe('git-directory-helper tests', () => {
git, git,
repositoryPath, repositoryPath,
repositoryUrl, repositoryUrl,
clean clean,
ref
) )
// Assert // Assert
@ -236,7 +245,8 @@ describe('git-directory-helper tests', () => {
undefined, undefined,
repositoryPath, repositoryPath,
repositoryUrl, repositoryUrl,
clean clean,
ref
) )
// Assert // Assert
@ -260,7 +270,8 @@ describe('git-directory-helper tests', () => {
git, git,
repositoryPath, repositoryPath,
repositoryUrl, repositoryUrl,
clean clean,
ref
) )
// Assert // Assert
@ -290,7 +301,8 @@ describe('git-directory-helper tests', () => {
git, git,
repositoryPath, repositoryPath,
repositoryUrl, repositoryUrl,
clean clean,
ref
) )
// Assert // Assert
@ -305,29 +317,66 @@ describe('git-directory-helper tests', () => {
expect(git.tryReset).not.toHaveBeenCalled() expect(git.tryReset).not.toHaveBeenCalled()
}) })
const removesRemoteBranches = 'removes local branches' const removesAncestorRemoteBranch = 'removes ancestor remote branch'
it(removesRemoteBranches, async () => { it(removesAncestorRemoteBranch, async () => {
// Arrange // Arrange
await setup(removesRemoteBranches) await setup(removesAncestorRemoteBranch)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '') await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
const mockBranchList = git.branchList as jest.Mock<any, any> const mockBranchList = git.branchList as jest.Mock<any, any>
mockBranchList.mockImplementation(async (remote: boolean) => { mockBranchList.mockImplementation(async (remote: boolean) => {
return remote ? ['remote-branch-1', 'remote-branch-2'] : [] return remote ? ['origin/remote-branch-1', 'origin/remote-branch-2'] : []
}) })
ref = 'remote-branch-1/conflict'
// Act // Act
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
repositoryUrl, repositoryUrl,
clean clean,
ref
) )
// Assert // Assert
const files = await fs.promises.readdir(repositoryPath) const files = await fs.promises.readdir(repositoryPath)
expect(files.sort()).toEqual(['.git', 'my-file']) expect(files.sort()).toEqual(['.git', 'my-file'])
expect(git.branchDelete).toHaveBeenCalledWith(true, 'remote-branch-1') expect(git.branchDelete).toHaveBeenCalledTimes(1)
expect(git.branchDelete).toHaveBeenCalledWith(true, 'remote-branch-2') expect(git.branchDelete).toHaveBeenCalledWith(
true,
'origin/remote-branch-1'
)
})
const removesDescendantRemoteBranches = 'removes descendant remote branch'
it(removesDescendantRemoteBranches, async () => {
// Arrange
await setup(removesDescendantRemoteBranches)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
const mockBranchList = git.branchList as jest.Mock<any, any>
mockBranchList.mockImplementation(async (remote: boolean) => {
return remote
? ['origin/remote-branch-1/conflict', 'origin/remote-branch-2']
: []
})
ref = 'remote-branch-1'
// Act
await gitDirectoryHelper.prepareExistingDirectory(
git,
repositoryPath,
repositoryUrl,
clean,
ref
)
// Assert
const files = await fs.promises.readdir(repositoryPath)
expect(files.sort()).toEqual(['.git', 'my-file'])
expect(git.branchDelete).toHaveBeenCalledTimes(1)
expect(git.branchDelete).toHaveBeenCalledWith(
true,
'origin/remote-branch-1/conflict'
)
}) })
}) })
@ -344,6 +393,9 @@ async function setup(testName: string): Promise<void> {
// Clean // Clean
clean = true clean = true
// Ref
ref = ''
// Git command manager // Git command manager
git = { git = {
branchDelete: jest.fn(), branchDelete: jest.fn(),
@ -356,6 +408,7 @@ async function setup(testName: string): Promise<void> {
config: jest.fn(), config: jest.fn(),
configExists: jest.fn(), configExists: jest.fn(),
fetch: jest.fn(), fetch: jest.fn(),
getDefaultBranch: jest.fn(),
getWorkingDirectory: jest.fn(() => repositoryPath), getWorkingDirectory: jest.fn(() => repositoryPath),
init: jest.fn(), init: jest.fn(),
isDetached: jest.fn(), isDetached: jest.fn(),
@ -364,7 +417,9 @@ async function setup(testName: string): Promise<void> {
log1: jest.fn(), log1: jest.fn(),
remoteAdd: jest.fn(), remoteAdd: jest.fn(),
removeEnvironmentVariable: jest.fn(), removeEnvironmentVariable: jest.fn(),
revParse: jest.fn(),
setEnvironmentVariable: jest.fn(), setEnvironmentVariable: jest.fn(),
shaExists: jest.fn(),
submoduleForeach: jest.fn(), submoduleForeach: jest.fn(),
submoduleSync: jest.fn(), submoduleSync: jest.fn(),
submoduleUpdate: jest.fn(), submoduleUpdate: jest.fn(),

View File

@ -110,13 +110,6 @@ describe('input-helper tests', () => {
) )
}) })
it('sets correct default ref/sha for other repo', () => {
inputs.repository = 'some-owner/some-other-repo'
const settings: IGitSourceSettings = inputHelper.getInputs()
expect(settings.ref).toBe('refs/heads/master')
expect(settings.commit).toBeFalsy()
})
it('sets ref to empty when explicit sha', () => { it('sets ref to empty when explicit sha', () => {
inputs.ref = '1111111111222222222233333333334444444444' inputs.ref = '1111111111222222222233333333334444444444'
const settings: IGitSourceSettings = inputHelper.getInputs() const settings: IGitSourceSettings = inputHelper.getInputs()

View File

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

View File

@ -8,7 +8,7 @@ inputs:
description: > description: >
The branch, tag or SHA to checkout. When checking out the repository that The branch, tag or SHA to checkout. When checking out the repository that
triggered a workflow, this defaults to the reference or SHA for that triggered a workflow, this defaults to the reference or SHA for that
event. Otherwise, defaults to `master`. event. Otherwise, uses the default branch.
token: token:
description: > description: >
Personal access token (PAT) used to fetch the repository. The PAT is configured Personal access token (PAT) used to fetch the repository. The PAT is configured
@ -54,7 +54,7 @@ inputs:
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
fetch-depth: fetch-depth:
description: 'Number of commits to fetch. 0 indicates all history.' description: 'Number of commits to fetch. 0 indicates all history for all branches and tags.'
default: 1 default: 1
lfs: lfs:
description: 'Whether to download Git-LFS files' description: 'Whether to download Git-LFS files'

View File

@ -29,14 +29,26 @@ We want to take this opportunity to make behavioral changes, from v1. This docum
description: > description: >
Personal access token (PAT) used to fetch the repository. The PAT is configured Personal access token (PAT) used to fetch the repository. The PAT is configured
with the local git config, which enables your scripts to run authenticated git with the local git config, which enables your scripts to run authenticated git
commands. The post-job step removes the PAT. [Learn more about creating and using commands. The post-job step removes the PAT.
encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
We recommend using a service account with the least permissions necessary.
Also when generating a new PAT, select the least scopes necessary.
[Learn more about creating and using encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
default: ${{ github.token }} default: ${{ github.token }}
ssh-key: ssh-key:
description: > description: >
SSH key used to fetch the repository. SSH key is configured with the local SSH key used to fetch the repository. The SSH key is configured with the local
git config, which enables your scripts to run authenticated git commands. git config, which enables your scripts to run authenticated git commands.
The post-job step removes the SSH key. [Learn more about creating and using The post-job step removes the SSH key.
We recommend using a service account with the least permissions necessary.
[Learn more about creating and using
encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets) encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
ssh-known-hosts: ssh-known-hosts:
description: > description: >
@ -44,7 +56,10 @@ We want to take this opportunity to make behavioral changes, from v1. This docum
SSH keys for a host may be obtained using the utility `ssh-keyscan`. For example, SSH keys for a host may be obtained using the utility `ssh-keyscan`. For example,
`ssh-keyscan github.com`. The public key for github.com is always implicitly added. `ssh-keyscan github.com`. The public key for github.com is always implicitly added.
ssh-strict: ssh-strict:
description: 'Whether to perform strict host key checking' description: >
Whether to perform strict host key checking. When true, adds the options `StrictHostKeyChecking=yes`
and `CheckHostIP=no` to the SSH command line. Use the input `ssh-known-hosts` to
configure additional hosts.
default: true default: true
persist-credentials: persist-credentials:
description: 'Whether to configure the token or SSH key with the local git config' description: 'Whether to configure the token or SSH key with the local git config'
@ -55,7 +70,7 @@ We want to take this opportunity to make behavioral changes, from v1. This docum
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
fetch-depth: fetch-depth:
description: 'Number of commits to fetch. 0 indicates all history.' description: 'Number of commits to fetch. 0 indicates all history for all tags and branches.'
default: 1 default: 1
lfs: lfs:
description: 'Whether to download Git-LFS files' description: 'Whether to download Git-LFS files'
@ -64,7 +79,11 @@ We want to take this opportunity to make behavioral changes, from v1. This docum
description: > description: >
Whether to checkout submodules: `true` to checkout submodules or `recursive` to Whether to checkout submodules: `true` to checkout submodules or `recursive` to
recursively checkout submodules. recursively checkout submodules.
default: 'false'
When the `ssh-key` input is not provided, SSH URLs beginning with `git@github.com:` are
converted to HTTPS.
default: false
``` ```
Note: Note:

14679
dist/index.js vendored

File diff suppressed because one or more lines are too long

127
package-lock.json generated
View File

@ -15,19 +15,19 @@
"integrity": "sha512-nvFkxwiicvpzNiCBF4wFBDfnBvi7xp/as7LE1hBxBxKG2L29+gkIPBiLKMVORL+Hg3JNf07AKRfl0V5djoypjQ==" "integrity": "sha512-nvFkxwiicvpzNiCBF4wFBDfnBvi7xp/as7LE1hBxBxKG2L29+gkIPBiLKMVORL+Hg3JNf07AKRfl0V5djoypjQ=="
}, },
"@actions/github": { "@actions/github": {
"version": "2.1.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@actions/github/-/github-2.2.0.tgz",
"integrity": "sha512-G4ncMlh4pLLAvNgHUYUtpWQ1zPf/VYqmRH9oshxLabdaOOnp7i1hgSgzr2xne2YUaSND3uqemd3YYTIsm2f/KQ==", "integrity": "sha512-9UAZqn8ywdR70n3GwVle4N8ALosQs4z50N7XMXrSTUVOmVpaBC5kE3TRTT7qQdi3OaQV24mjGuJZsHUmhD+ZXw==",
"requires": { "requires": {
"@actions/http-client": "^1.0.3", "@actions/http-client": "^1.0.3",
"@octokit/graphql": "^4.3.1", "@octokit/graphql": "^4.3.1",
"@octokit/rest": "^16.15.0" "@octokit/rest": "^16.43.1"
} }
}, },
"@actions/http-client": { "@actions/http-client": {
"version": "1.0.3", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.8.tgz",
"integrity": "sha512-wFwh1U4adB/Zsk4cc9kVqaBOHoknhp/pJQk+aWTocbAZWpIl4Zx/At83WFRLXvxB+5HVTWOACM6qjULMZfQSfw==", "integrity": "sha512-G4JjJ6f9Hb3Zvejj+ewLLKLf99ZC+9v+yCxoYf9vSyH+WkzPLB2LuUtRMGNkooMqdugGBFStIKXOuvH1W+EctA==",
"requires": { "requires": {
"tunnel": "0.0.6" "tunnel": "0.0.6"
}, },
@ -622,13 +622,23 @@
} }
}, },
"@octokit/endpoint": { "@octokit/endpoint": {
"version": "5.5.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-5.5.1.tgz", "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.1.tgz",
"integrity": "sha512-nBFhRUb5YzVTCX/iAK1MgQ4uWo89Gu0TH00qQHoYRCsE12dWcG1OiLd7v2EIo2+tpUKPMOQ62QFy9hy9Vg2ULg==", "integrity": "sha512-pOPHaSz57SFT/m3R5P8MUu4wLPszokn5pXcB/pzavLTQf2jbU+6iayTvzaY6/BiotuRS0qyEUkx3QglT4U958A==",
"requires": { "requires": {
"@octokit/types": "^2.0.0", "@octokit/types": "^2.11.1",
"is-plain-object": "^3.0.0", "is-plain-object": "^3.0.0",
"universal-user-agent": "^4.0.0" "universal-user-agent": "^5.0.0"
},
"dependencies": {
"universal-user-agent": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-5.0.0.tgz",
"integrity": "sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q==",
"requires": {
"os-name": "^3.1.0"
}
}
} }
}, },
"@octokit/graphql": { "@octokit/graphql": {
@ -641,25 +651,57 @@
"universal-user-agent": "^4.0.0" "universal-user-agent": "^4.0.0"
} }
}, },
"@octokit/request": { "@octokit/plugin-paginate-rest": {
"version": "5.3.1", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.3.1.tgz", "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-1.1.2.tgz",
"integrity": "sha512-5/X0AL1ZgoU32fAepTfEoggFinO3rxsMLtzhlUX+RctLrusn/CApJuGFCd0v7GMFhF+8UiCsTTfsu7Fh1HnEJg==", "integrity": "sha512-jbsSoi5Q1pj63sC16XIUboklNw+8tL9VOnJsWycWYR78TKss5PVpIPb1TUUcMQ+bBh7cY579cVAWmf5qG+dw+Q==",
"requires": { "requires": {
"@octokit/endpoint": "^5.5.0", "@octokit/types": "^2.0.1"
"@octokit/request-error": "^1.0.1", }
"@octokit/types": "^2.0.0", },
"@octokit/plugin-request-log": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.0.tgz",
"integrity": "sha512-ywoxP68aOT3zHCLgWZgwUJatiENeHE7xJzYjfz8WI0goynp96wETBF+d95b8g/uL4QmS6owPVlaxiz3wyMAzcw=="
},
"@octokit/plugin-rest-endpoint-methods": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-2.4.0.tgz",
"integrity": "sha512-EZi/AWhtkdfAYi01obpX0DF7U6b1VRr30QNQ5xSFPITMdLSfhcBqjamE3F+sKcxPbD7eZuMHu3Qkk2V+JGxBDQ==",
"requires": {
"@octokit/types": "^2.0.1",
"deprecation": "^2.3.1"
}
},
"@octokit/request": {
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.2.tgz",
"integrity": "sha512-zKdnGuQ2TQ2vFk9VU8awFT4+EYf92Z/v3OlzRaSh4RIP0H6cvW1BFPXq4XYvNez+TPQjqN+0uSkCYnMFFhcFrw==",
"requires": {
"@octokit/endpoint": "^6.0.1",
"@octokit/request-error": "^2.0.0",
"@octokit/types": "^2.11.1",
"deprecation": "^2.0.0", "deprecation": "^2.0.0",
"is-plain-object": "^3.0.0", "is-plain-object": "^3.0.0",
"node-fetch": "^2.3.0", "node-fetch": "^2.3.0",
"once": "^1.4.0", "once": "^1.4.0",
"universal-user-agent": "^4.0.0" "universal-user-agent": "^5.0.0"
},
"dependencies": {
"universal-user-agent": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-5.0.0.tgz",
"integrity": "sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q==",
"requires": {
"os-name": "^3.1.0"
}
}
} }
}, },
"@octokit/request-error": { "@octokit/request-error": {
"version": "1.2.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.0.tgz",
"integrity": "sha512-DNBhROBYjjV/I9n7A8kVkmQNkqFAMem90dSxqvPq57e2hBr7mNTX98y3R2zDpqMQHVRpBDjsvsfIGgBzy+4PAg==", "integrity": "sha512-rtYicB4Absc60rUv74Rjpzek84UbVHGHJRu4fNVlZ1mCcyUPPuzFfG9Rn6sjHrd95DEsmjSt1Axlc699ZlbDkw==",
"requires": { "requires": {
"@octokit/types": "^2.0.0", "@octokit/types": "^2.0.0",
"deprecation": "^2.0.0", "deprecation": "^2.0.0",
@ -667,11 +709,14 @@
} }
}, },
"@octokit/rest": { "@octokit/rest": {
"version": "16.38.1", "version": "16.43.1",
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.38.1.tgz", "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.43.1.tgz",
"integrity": "sha512-zyNFx+/Bd1EXt7LQjfrc6H4wryBQ/oDuZeZhGMBSFr1eMPFDmpEweFQR3R25zjKwBQpDY7L5GQO6A3XSaOfV1w==", "integrity": "sha512-gfFKwRT/wFxq5qlNjnW2dh+qh74XgTQ2B179UX5K1HYCluioWj8Ndbgqw2PVqa1NnVJkGHp2ovMpVn/DImlmkw==",
"requires": { "requires": {
"@octokit/auth-token": "^2.4.0", "@octokit/auth-token": "^2.4.0",
"@octokit/plugin-paginate-rest": "^1.1.1",
"@octokit/plugin-request-log": "^1.0.0",
"@octokit/plugin-rest-endpoint-methods": "2.4.0",
"@octokit/request": "^5.2.0", "@octokit/request": "^5.2.0",
"@octokit/request-error": "^1.0.2", "@octokit/request-error": "^1.0.2",
"atob-lite": "^2.0.0", "atob-lite": "^2.0.0",
@ -684,12 +729,24 @@
"octokit-pagination-methods": "^1.1.0", "octokit-pagination-methods": "^1.1.0",
"once": "^1.4.0", "once": "^1.4.0",
"universal-user-agent": "^4.0.0" "universal-user-agent": "^4.0.0"
},
"dependencies": {
"@octokit/request-error": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-1.2.1.tgz",
"integrity": "sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA==",
"requires": {
"@octokit/types": "^2.0.0",
"deprecation": "^2.0.0",
"once": "^1.4.0"
}
}
} }
}, },
"@octokit/types": { "@octokit/types": {
"version": "2.1.1", "version": "2.14.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.1.1.tgz", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.14.0.tgz",
"integrity": "sha512-89LOYH+d/vsbDX785NOfLxTW88GjNd0lWRz1DVPVsZgg9Yett5O+3MOvwo7iHgvUwbFz0mf/yPIjBkUbs4kxoQ==", "integrity": "sha512-1w2wxpN45rEXPDFeB7rGain7wcJ/aTRg8bdILITVnS0O7a4zEGELa3JmIe+jeLdekQjvZRbVfNPqS+mi5fKCKQ==",
"requires": { "requires": {
"@types/node": ">= 8" "@types/node": ">= 8"
} }
@ -6720,9 +6777,9 @@
} }
}, },
"universal-user-agent": { "universal-user-agent": {
"version": "4.0.0", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-4.0.0.tgz", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-4.0.1.tgz",
"integrity": "sha512-eM8knLpev67iBDizr/YtqkJsF3GK8gzDc6st/WKzrTuPtcsOKW/0IdL4cnMBsU69pOx0otavLWBDGTwg+dB0aA==", "integrity": "sha512-LnST3ebHwVL2aNe4mejI9IQh2HfZ1RLo8Io2HugSif8ekzD1TlWpHpColOB/eh8JHMLkGH3Akqf040I+4ylNxg==",
"requires": { "requires": {
"os-name": "^3.1.0" "os-name": "^3.1.0"
} }
@ -6901,9 +6958,9 @@
"dev": true "dev": true
}, },
"windows-release": { "windows-release": {
"version": "3.2.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.2.0.tgz", "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.3.0.tgz",
"integrity": "sha512-QTlz2hKLrdqukrsapKsINzqMgOUpQW268eJ0OaOpJN32h272waxR9fkB9VoWRtK7uKHG5EHJcTXQBD8XZVJkFA==", "integrity": "sha512-2HetyTg1Y+R+rUgrKeUEhAG/ZuOmTrI1NBb3ZyAGQMYmOJjBBPe4MTodghRkmLJZHwkuPi02anbeGP+Zf401LQ==",
"requires": { "requires": {
"execa": "^1.0.0" "execa": "^1.0.0"
} }

View File

@ -5,8 +5,8 @@
"main": "lib/main.js", "main": "lib/main.js",
"scripts": { "scripts": {
"build": "tsc && ncc build && node lib/misc/generate-docs.js", "build": "tsc && ncc build && node lib/misc/generate-docs.js",
"format": "prettier --write **/*.ts", "format": "prettier --write '**/*.ts'",
"format-check": "prettier --check **/*.ts", "format-check": "prettier --check '**/*.ts'",
"lint": "eslint src/**/*.ts", "lint": "eslint src/**/*.ts",
"test": "jest" "test": "jest"
}, },
@ -28,7 +28,7 @@
"dependencies": { "dependencies": {
"@actions/core": "^1.1.3", "@actions/core": "^1.1.3",
"@actions/exec": "^1.0.1", "@actions/exec": "^1.0.1",
"@actions/github": "^2.0.2", "@actions/github": "^2.2.0",
"@actions/io": "^1.0.1", "@actions/io": "^1.0.1",
"@actions/tool-cache": "^1.1.2", "@actions/tool-cache": "^1.1.2",
"uuid": "^3.3.3" "uuid": "^3.3.3"

View File

@ -7,12 +7,12 @@ import * as os from 'os'
import * as path from 'path' import * as path from 'path'
import * as regexpHelper from './regexp-helper' import * as regexpHelper from './regexp-helper'
import * as stateHelper from './state-helper' import * as stateHelper from './state-helper'
import * as urlHelper from './url-helper'
import {default as uuid} from 'uuid/v4' import {default as uuid} from 'uuid/v4'
import {IGitCommandManager} from './git-command-manager' import {IGitCommandManager} from './git-command-manager'
import {IGitSourceSettings} from './git-source-settings' import {IGitSourceSettings} from './git-source-settings'
const IS_WINDOWS = process.platform === 'win32' const IS_WINDOWS = process.platform === 'win32'
const HOSTNAME = 'github.com'
const SSH_COMMAND_KEY = 'core.sshCommand' const SSH_COMMAND_KEY = 'core.sshCommand'
export interface IGitAuthHelper { export interface IGitAuthHelper {
@ -33,15 +33,15 @@ 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 = `http.https://${HOSTNAME}/.extraheader` private readonly tokenConfigKey: string
private readonly tokenConfigValue: string
private readonly tokenPlaceholderConfigValue: string private readonly tokenPlaceholderConfigValue: string
private readonly insteadOfKey: string = `url.https://${HOSTNAME}/.insteadOf` private readonly insteadOfKey: string
private readonly insteadOfValue: string = `git@${HOSTNAME}:` private readonly insteadOfValue: string
private sshCommand = '' private sshCommand = ''
private sshKeyPath = '' private sshKeyPath = ''
private sshKnownHostsPath = '' private sshKnownHostsPath = ''
private temporaryHomePath = '' private temporaryHomePath = ''
private tokenConfigValue: string
constructor( constructor(
gitCommandManager: IGitCommandManager, gitCommandManager: IGitCommandManager,
@ -51,6 +51,8 @@ class GitAuthHelper {
this.settings = gitSourceSettings || (({} as unknown) as IGitSourceSettings) this.settings = gitSourceSettings || (({} as unknown) as IGitSourceSettings)
// Token auth header // Token auth header
const serverUrl = urlHelper.getServerUrl()
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'
@ -58,6 +60,10 @@ class GitAuthHelper {
core.setSecret(basicCredential) core.setSecret(basicCredential)
this.tokenPlaceholderConfigValue = `AUTHORIZATION: basic ***` this.tokenPlaceholderConfigValue = `AUTHORIZATION: basic ***`
this.tokenConfigValue = `AUTHORIZATION: basic ${basicCredential}` this.tokenConfigValue = `AUTHORIZATION: basic ${basicCredential}`
// Instead of SSH URL
this.insteadOfKey = `url.${serverUrl.origin}/.insteadOf` // "origin" is SCHEME://HOSTNAME[:PORT]
this.insteadOfValue = `git@${serverUrl.hostname}:`
} }
async configureAuth(): Promise<void> { async configureAuth(): Promise<void> {
@ -167,7 +173,7 @@ class GitAuthHelper {
} }
async removeGlobalAuth(): Promise<void> { async removeGlobalAuth(): Promise<void> {
core.info(`Unsetting HOME override`) core.debug(`Unsetting HOME override`)
this.git.removeEnvironmentVariable('HOME') this.git.removeEnvironmentVariable('HOME')
await io.rmRF(this.temporaryHomePath) await io.rmRF(this.temporaryHomePath)
} }

View File

@ -3,6 +3,7 @@ import * as exec from '@actions/exec'
import * as fshelper from './fs-helper' import * as fshelper from './fs-helper'
import * as io from '@actions/io' import * as io from '@actions/io'
import * as path from 'path' import * as path from 'path'
import * as refHelper from './ref-helper'
import * as regexpHelper from './regexp-helper' import * as regexpHelper from './regexp-helper'
import * as retryHelper from './retry-helper' import * as retryHelper from './retry-helper'
import {GitVersion} from './git-version' import {GitVersion} from './git-version'
@ -23,16 +24,19 @@ export interface IGitCommandManager {
globalConfig?: boolean globalConfig?: boolean
): Promise<void> ): Promise<void>
configExists(configKey: string, globalConfig?: boolean): Promise<boolean> configExists(configKey: string, globalConfig?: boolean): Promise<boolean>
fetch(fetchDepth: number, refSpec: string[]): Promise<void> fetch(refSpec: string[], fetchDepth?: number): Promise<void>
getDefaultBranch(repositoryUrl: string): Promise<string>
getWorkingDirectory(): string getWorkingDirectory(): string
init(): Promise<void> init(): Promise<void>
isDetached(): Promise<boolean> isDetached(): Promise<boolean>
lfsFetch(ref: string): Promise<void> lfsFetch(ref: string): Promise<void>
lfsInstall(): Promise<void> lfsInstall(): Promise<void>
log1(): Promise<void> log1(): Promise<string>
remoteAdd(remoteName: string, remoteUrl: string): Promise<void> remoteAdd(remoteName: string, remoteUrl: string): Promise<void>
removeEnvironmentVariable(name: string): void removeEnvironmentVariable(name: string): void
revParse(ref: string): Promise<string>
setEnvironmentVariable(name: string, value: string): void setEnvironmentVariable(name: string, value: string): void
shaExists(sha: string): Promise<boolean>
submoduleForeach(command: string, recursive: boolean): Promise<string> submoduleForeach(command: string, recursive: boolean): Promise<string>
submoduleSync(recursive: boolean): Promise<void> submoduleSync(recursive: boolean): Promise<void>
submoduleUpdate(fetchDepth: number, recursive: boolean): Promise<void> submoduleUpdate(fetchDepth: number, recursive: boolean): Promise<void>
@ -164,17 +168,14 @@ class GitCommandManager {
return output.exitCode === 0 return output.exitCode === 0
} }
async fetch(fetchDepth: number, refSpec: string[]): Promise<void> { async fetch(refSpec: string[], fetchDepth?: number): Promise<void> {
const args = [ const args = ['-c', 'protocol.version=2', 'fetch']
'-c', if (!refSpec.some(x => x === refHelper.tagsRefSpec)) {
'protocol.version=2', args.push('--no-tags')
'fetch', }
'--no-tags',
'--prune', args.push('--prune', '--progress', '--no-recurse-submodules')
'--progress', if (fetchDepth && fetchDepth > 0) {
'--no-recurse-submodules'
]
if (fetchDepth > 0) {
args.push(`--depth=${fetchDepth}`) args.push(`--depth=${fetchDepth}`)
} else if ( } else if (
fshelper.fileExistsSync( fshelper.fileExistsSync(
@ -195,6 +196,34 @@ class GitCommandManager {
}) })
} }
async getDefaultBranch(repositoryUrl: string): Promise<string> {
let output: GitOutput | undefined
await retryHelper.execute(async () => {
output = await this.execGit([
'ls-remote',
'--quiet',
'--exit-code',
'--symref',
repositoryUrl,
'HEAD'
])
})
if (output) {
// Satisfy compiler, will always be set
for (let line of output.stdout.trim().split('\n')) {
line = line.trim()
if (line.startsWith('ref:') || line.endsWith('HEAD')) {
return line
.substr('ref:'.length, line.length - 'ref:'.length - 'HEAD'.length)
.trim()
}
}
}
throw new Error('Unexpected output when retrieving default branch')
}
getWorkingDirectory(): string { getWorkingDirectory(): string {
return this.workingDirectory return this.workingDirectory
} }
@ -225,8 +254,9 @@ class GitCommandManager {
await this.execGit(['lfs', 'install', '--local']) await this.execGit(['lfs', 'install', '--local'])
} }
async log1(): Promise<void> { async log1(): Promise<string> {
await this.execGit(['log', '-1']) const output = await this.execGit(['log', '-1'])
return output.stdout
} }
async remoteAdd(remoteName: string, remoteUrl: string): Promise<void> { async remoteAdd(remoteName: string, remoteUrl: string): Promise<void> {
@ -237,10 +267,27 @@ class GitCommandManager {
delete this.gitEnv[name] delete this.gitEnv[name]
} }
/**
* Resolves a ref to a SHA. For a branch or lightweight tag, the commit SHA is returned.
* For an annotated tag, the tag SHA is returned.
* @param {string} ref For example: 'refs/heads/master' or '/refs/tags/v1'
* @returns {Promise<string>}
*/
async revParse(ref: string): Promise<string> {
const output = await this.execGit(['rev-parse', ref])
return output.stdout.trim()
}
setEnvironmentVariable(name: string, value: string): void { setEnvironmentVariable(name: string, value: string): void {
this.gitEnv[name] = value this.gitEnv[name] = value
} }
async shaExists(sha: string): Promise<boolean> {
const args = ['rev-parse', '--verify', '--quiet', `${sha}^{object}`]
const output = await this.execGit(args, true)
return output.exitCode === 0
}
async submoduleForeach(command: string, recursive: boolean): Promise<string> { async submoduleForeach(command: string, recursive: boolean): Promise<string> {
const args = ['submodule', 'foreach'] const args = ['submodule', 'foreach']
if (recursive) { if (recursive) {

View File

@ -1,3 +1,4 @@
import * as assert from 'assert'
import * as core from '@actions/core' import * as core from '@actions/core'
import * as fs from 'fs' import * as fs from 'fs'
import * as fsHelper from './fs-helper' import * as fsHelper from './fs-helper'
@ -9,8 +10,13 @@ export async function prepareExistingDirectory(
git: IGitCommandManager | undefined, git: IGitCommandManager | undefined,
repositoryPath: string, repositoryPath: string,
repositoryUrl: string, repositoryUrl: string,
clean: boolean clean: boolean,
ref: string
): Promise<void> { ): Promise<void> {
assert.ok(repositoryPath, 'Expected repositoryPath to be defined')
assert.ok(repositoryUrl, 'Expected repositoryUrl to be defined')
// Indicates whether to delete the directory contents
let remove = false let remove = false
// Check whether using git or REST API // Check whether using git or REST API
@ -38,6 +44,7 @@ export async function prepareExistingDirectory(
} }
try { try {
core.startGroup('Removing previously created refs, to avoid conflicts')
// Checkout detached HEAD // Checkout detached HEAD
if (!(await git.isDetached())) { if (!(await git.isDetached())) {
await git.checkoutDetach() await git.checkoutDetach()
@ -49,14 +56,32 @@ export async function prepareExistingDirectory(
await git.branchDelete(false, branch) await git.branchDelete(false, branch)
} }
// Remove all refs/remotes/origin/* to avoid conflicts // Remove any conflicting refs/remotes/origin/*
branches = await git.branchList(true) // Example 1: Consider ref is refs/heads/foo and previously fetched refs/remotes/origin/foo/bar
for (const branch of branches) { // Example 2: Consider ref is refs/heads/foo/bar and previously fetched refs/remotes/origin/foo
await git.branchDelete(true, branch) if (ref) {
ref = ref.startsWith('refs/') ? ref : `refs/heads/${ref}`
if (ref.startsWith('refs/heads/')) {
const upperName1 = ref.toUpperCase().substr('REFS/HEADS/'.length)
const upperName1Slash = `${upperName1}/`
branches = await git.branchList(true)
for (const branch of branches) {
const upperName2 = branch.substr('origin/'.length).toUpperCase()
const upperName2Slash = `${upperName2}/`
if (
upperName1.startsWith(upperName2Slash) ||
upperName2.startsWith(upperName1Slash)
) {
await git.branchDelete(true, branch)
}
}
}
} }
core.endGroup()
// Clean // Clean
if (clean) { if (clean) {
core.startGroup('Cleaning the repository')
if (!(await git.tryClean())) { if (!(await git.tryClean())) {
core.debug( core.debug(
`The clean command failed. This might be caused by: 1) path too long, 2) permission issue, or 3) file in use. For futher investigation, manually run 'git clean -ffdx' on the directory '${repositoryPath}'.` `The clean command failed. This might be caused by: 1) path too long, 2) permission issue, or 3) file in use. For futher investigation, manually run 'git clean -ffdx' on the directory '${repositoryPath}'.`
@ -65,6 +90,7 @@ export async function prepareExistingDirectory(
} else if (!(await git.tryReset())) { } else if (!(await git.tryReset())) {
remove = true remove = true
} }
core.endGroup()
if (remove) { if (remove) {
core.warning( core.warning(

View File

@ -8,23 +8,16 @@ import * as io from '@actions/io'
import * as path from 'path' import * as path from 'path'
import * as refHelper from './ref-helper' import * as refHelper from './ref-helper'
import * as stateHelper from './state-helper' import * as stateHelper from './state-helper'
import * as urlHelper from './url-helper'
import {IGitCommandManager} from './git-command-manager' import {IGitCommandManager} from './git-command-manager'
import {IGitSourceSettings} from './git-source-settings' import {IGitSourceSettings} from './git-source-settings'
const hostname = 'github.com'
export async function getSource(settings: IGitSourceSettings): Promise<void> { export async function getSource(settings: IGitSourceSettings): Promise<void> {
// Repository URL // Repository URL
core.info( core.info(
`Syncing repository: ${settings.repositoryOwner}/${settings.repositoryName}` `Syncing repository: ${settings.repositoryOwner}/${settings.repositoryName}`
) )
const repositoryUrl = settings.sshKey const repositoryUrl = urlHelper.getFetchUrl(settings)
? `git@${hostname}:${encodeURIComponent(
settings.repositoryOwner
)}/${encodeURIComponent(settings.repositoryName)}.git`
: `https://${hostname}/${encodeURIComponent(
settings.repositoryOwner
)}/${encodeURIComponent(settings.repositoryName)}`
// Remove conflicting file path // Remove conflicting file path
if (fsHelper.fileExistsSync(settings.repositoryPath)) { if (fsHelper.fileExistsSync(settings.repositoryPath)) {
@ -39,7 +32,9 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
} }
// Git command manager // Git command manager
core.startGroup('Getting Git version info')
const git = await getGitCommandManager(settings) const git = await getGitCommandManager(settings)
core.endGroup()
// Prepare existing directory, otherwise recreate // Prepare existing directory, otherwise recreate
if (isExisting) { if (isExisting) {
@ -47,7 +42,8 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
git, git,
settings.repositoryPath, settings.repositoryPath,
repositoryUrl, repositoryUrl,
settings.clean settings.clean,
settings.ref
) )
} }
@ -85,21 +81,42 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
if ( if (
!fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git')) !fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git'))
) { ) {
core.startGroup('Initializing the repository')
await git.init() await git.init()
await git.remoteAdd('origin', repositoryUrl) await git.remoteAdd('origin', repositoryUrl)
core.endGroup()
} }
// Disable automatic garbage collection // Disable automatic garbage collection
core.startGroup('Disabling automatic garbage collection')
if (!(await git.tryDisableAutomaticGarbageCollection())) { if (!(await git.tryDisableAutomaticGarbageCollection())) {
core.warning( core.warning(
`Unable to turn off git automatic garbage collection. The git fetch operation may trigger garbage collection and cause a delay.` `Unable to turn off git automatic garbage collection. The git fetch operation may trigger garbage collection and cause a delay.`
) )
} }
core.endGroup()
const authHelper = gitAuthHelper.createAuthHelper(git, settings) const authHelper = gitAuthHelper.createAuthHelper(git, settings)
try { try {
// Configure auth // Configure auth
core.startGroup('Setting up auth')
await authHelper.configureAuth() await authHelper.configureAuth()
core.endGroup()
// Determine the default branch
if (!settings.ref && !settings.commit) {
core.startGroup('Determining the default branch')
if (settings.sshKey) {
settings.ref = await git.getDefaultBranch(repositoryUrl)
} else {
settings.ref = await githubApiHelper.getDefaultBranch(
settings.authToken,
settings.repositoryOwner,
settings.repositoryName
)
}
core.endGroup()
}
// LFS install // LFS install
if (settings.lfs) { if (settings.lfs) {
@ -107,33 +124,60 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
} }
// Fetch // Fetch
const refSpec = refHelper.getRefSpec(settings.ref, settings.commit) core.startGroup('Fetching the repository')
await git.fetch(settings.fetchDepth, refSpec) if (settings.fetchDepth <= 0) {
// Fetch all branches and tags
let refSpec = refHelper.getRefSpecForAllHistory(
settings.ref,
settings.commit
)
await git.fetch(refSpec)
// When all history is fetched, the ref we're interested in may have moved to a different
// commit (push or force push). If so, fetch again with a targeted refspec.
if (!(await refHelper.testRef(git, settings.ref, settings.commit))) {
refSpec = refHelper.getRefSpec(settings.ref, settings.commit)
await git.fetch(refSpec)
}
} else {
const refSpec = refHelper.getRefSpec(settings.ref, settings.commit)
await git.fetch(refSpec, settings.fetchDepth)
}
core.endGroup()
// Checkout info // Checkout info
core.startGroup('Determining the checkout info')
const checkoutInfo = await refHelper.getCheckoutInfo( const checkoutInfo = await refHelper.getCheckoutInfo(
git, git,
settings.ref, settings.ref,
settings.commit settings.commit
) )
core.endGroup()
// LFS fetch // LFS fetch
// Explicit lfs-fetch to avoid slow checkout (fetches one lfs object at a time). // Explicit lfs-fetch to avoid slow checkout (fetches one lfs object at a time).
// Explicit lfs fetch will fetch lfs objects in parallel. // Explicit lfs fetch will fetch lfs objects in parallel.
if (settings.lfs) { if (settings.lfs) {
core.startGroup('Fetching LFS objects')
await git.lfsFetch(checkoutInfo.startPoint || checkoutInfo.ref) await git.lfsFetch(checkoutInfo.startPoint || checkoutInfo.ref)
core.endGroup()
} }
// Checkout // Checkout
core.startGroup('Checking out the ref')
await git.checkout(checkoutInfo.ref, checkoutInfo.startPoint) await git.checkout(checkoutInfo.ref, checkoutInfo.startPoint)
core.endGroup()
// Submodules // Submodules
if (settings.submodules) { if (settings.submodules) {
try { try {
// Temporarily override global config // Temporarily override global config
core.startGroup('Setting up auth for fetching submodules')
await authHelper.configureGlobalAuth() await authHelper.configureGlobalAuth()
core.endGroup()
// Checkout submodules // Checkout submodules
core.startGroup('Fetching submodules')
await git.submoduleSync(settings.nestedSubmodules) await git.submoduleSync(settings.nestedSubmodules)
await git.submoduleUpdate( await git.submoduleUpdate(
settings.fetchDepth, settings.fetchDepth,
@ -143,10 +187,13 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
'git config --local gc.auto 0', 'git config --local gc.auto 0',
settings.nestedSubmodules settings.nestedSubmodules
) )
core.endGroup()
// Persist credentials // Persist credentials
if (settings.persistCredentials) { if (settings.persistCredentials) {
core.startGroup('Persisting credentials for submodules')
await authHelper.configureSubmoduleAuth() await authHelper.configureSubmoduleAuth()
core.endGroup()
} }
} finally { } finally {
// Remove temporary global config override // Remove temporary global config override
@ -155,11 +202,23 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
} }
// Dump some info about the checked out commit // Dump some info about the checked out commit
await git.log1() const commitInfo = await git.log1()
// Check for incorrect pull request merge commit
await refHelper.checkCommitInfo(
settings.authToken,
commitInfo,
settings.repositoryOwner,
settings.repositoryName,
settings.ref,
settings.commit
)
} finally { } finally {
// Remove auth // Remove auth
if (!settings.persistCredentials) { if (!settings.persistCredentials) {
core.startGroup('Removing auth')
await authHelper.removeAuth() await authHelper.removeAuth()
core.endGroup()
} }
} }
} }

View File

@ -1,17 +1,76 @@
export interface IGitSourceSettings { export interface IGitSourceSettings {
/**
* The location on disk where the repository will be placed
*/
repositoryPath: string repositoryPath: string
/**
* The repository owner
*/
repositoryOwner: string repositoryOwner: string
/**
* The repository name
*/
repositoryName: string repositoryName: string
/**
* The ref to fetch
*/
ref: string ref: string
/**
* The commit to checkout
*/
commit: string commit: string
/**
* Indicates whether to clean the repository
*/
clean: boolean clean: boolean
/**
* The depth when fetching
*/
fetchDepth: number fetchDepth: number
/**
* Indicates whether to fetch LFS objects
*/
lfs: boolean lfs: boolean
/**
* Indicates whether to checkout submodules
*/
submodules: boolean submodules: boolean
/**
* Indicates whether to recursively checkout submodules
*/
nestedSubmodules: boolean nestedSubmodules: boolean
/**
* The auth token to use when fetching the repository
*/
authToken: string authToken: string
/**
* The SSH key to configure
*/
sshKey: string sshKey: string
/**
* Additional SSH known hosts
*/
sshKnownHosts: string sshKnownHosts: string
/**
* Indicates whether the server must be a known host
*/
sshStrict: boolean sshStrict: boolean
/**
* Indicates whether to persist the credentials on disk to enable scripting authenticated git commands
*/
persistCredentials: boolean persistCredentials: boolean
} }

View File

@ -7,7 +7,7 @@ import * as path from 'path'
import * as retryHelper from './retry-helper' import * as retryHelper from './retry-helper'
import * as toolCache from '@actions/tool-cache' import * as toolCache from '@actions/tool-cache'
import {default as uuid} from 'uuid/v4' import {default as uuid} from 'uuid/v4'
import {ReposGetArchiveLinkParams} from '@octokit/rest' import {Octokit} from '@octokit/rest'
const IS_WINDOWS = process.platform === 'win32' const IS_WINDOWS = process.platform === 'win32'
@ -19,6 +19,12 @@ export async function downloadRepository(
commit: string, commit: string,
repositoryPath: string repositoryPath: string
): Promise<void> { ): Promise<void> {
// Determine the default branch
if (!ref && !commit) {
core.info('Determining the default branch')
ref = await getDefaultBranch(authToken, owner, repo)
}
// Download the archive // Download the archive
let archiveData = await retryHelper.execute(async () => { let archiveData = await retryHelper.execute(async () => {
core.info('Downloading the archive') core.info('Downloading the archive')
@ -67,6 +73,46 @@ export async function downloadRepository(
io.rmRF(extractPath) io.rmRF(extractPath)
} }
/**
* Looks up the default branch name
*/
export async function getDefaultBranch(
authToken: string,
owner: string,
repo: string
): Promise<string> {
return await retryHelper.execute(async () => {
core.info('Retrieving the default branch name')
const octokit = new github.GitHub(authToken)
let result: string
try {
// Get the default branch from the repo info
const response = await octokit.repos.get({owner, repo})
result = response.data.default_branch
assert.ok(result, 'default_branch cannot be empty')
} catch (err) {
// Handle .wiki repo
if (err['status'] === 404 && repo.toUpperCase().endsWith('.WIKI')) {
result = 'master'
}
// Otherwise error
else {
throw err
}
}
// Print the default branch
core.info(`Default branch '${result}'`)
// Prefix with 'refs/heads'
if (!result.startsWith('refs/')) {
result = `refs/heads/${result}`
}
return result
})
}
async function downloadArchive( async function downloadArchive(
authToken: string, authToken: string,
owner: string, owner: string,
@ -75,7 +121,7 @@ async function downloadArchive(
commit: string commit: string
): Promise<Buffer> { ): Promise<Buffer> {
const octokit = new github.GitHub(authToken) const octokit = new github.GitHub(authToken)
const params: ReposGetArchiveLinkParams = { const params: Octokit.ReposGetArchiveLinkParams = {
owner: owner, owner: owner,
repo: repo, repo: repo,
archive_format: IS_WINDOWS ? 'zipball' : 'tarball', archive_format: IS_WINDOWS ? 'zipball' : 'tarball',

View File

@ -68,10 +68,6 @@ export function getInputs(): IGitSourceSettings {
result.ref = `refs/heads/${result.ref}` result.ref = `refs/heads/${result.ref}`
} }
} }
if (!result.ref && !result.commit) {
result.ref = 'refs/heads/master'
}
} }
// SHA? // SHA?
else if (result.ref.match(/^[0-9a-fA-F]{40}$/)) { else if (result.ref.match(/^[0-9a-fA-F]{40}$/)) {
@ -110,7 +106,7 @@ export function getInputs(): IGitSourceSettings {
core.debug(`recursive submodules = ${result.nestedSubmodules}`) core.debug(`recursive submodules = ${result.nestedSubmodules}`)
// Auth token // Auth token
result.authToken = core.getInput('token') result.authToken = core.getInput('token', {required: true})
// SSH // SSH
result.sshKey = core.getInput('ssh-key') result.sshKey = core.getInput('ssh-key')

View File

@ -1,4 +1,9 @@
import {URL} from 'url'
import {IGitCommandManager} from './git-command-manager' import {IGitCommandManager} from './git-command-manager'
import * as core from '@actions/core'
import * as github from '@actions/github'
export const tagsRefSpec = '+refs/tags/*:refs/tags/*'
export interface ICheckoutInfo { export interface ICheckoutInfo {
ref: string ref: string
@ -57,6 +62,16 @@ export async function getCheckoutInfo(
return result return result
} }
export function getRefSpecForAllHistory(ref: string, commit: string): string[] {
const result = ['+refs/heads/*:refs/remotes/origin/*', tagsRefSpec]
if (ref && ref.toUpperCase().startsWith('REFS/PULL/')) {
const branch = ref.substring('refs/pull/'.length)
result.push(`+${commit || ref}:refs/remotes/pull/${branch}`)
}
return result
}
export function getRefSpec(ref: string, commit: string): string[] { export function getRefSpec(ref: string, commit: string): string[] {
if (!ref && !commit) { if (!ref && !commit) {
throw new Error('Args ref and commit cannot both be empty') throw new Error('Args ref and commit cannot both be empty')
@ -107,3 +122,162 @@ export function getRefSpec(ref: string, commit: string): string[] {
return [`+${ref}:${ref}`] return [`+${ref}:${ref}`]
} }
} }
/**
* Tests whether the initial fetch created the ref at the expected commit
*/
export async function testRef(
git: IGitCommandManager,
ref: string,
commit: string
): Promise<boolean> {
if (!git) {
throw new Error('Arg git cannot be empty')
}
if (!ref && !commit) {
throw new Error('Args ref and commit cannot both be empty')
}
// No SHA? Nothing to test
if (!commit) {
return true
}
// SHA only?
else if (!ref) {
return await git.shaExists(commit)
}
const upperRef = ref.toUpperCase()
// refs/heads/
if (upperRef.startsWith('REFS/HEADS/')) {
const branch = ref.substring('refs/heads/'.length)
return (
(await git.branchExists(true, `origin/${branch}`)) &&
commit === (await git.revParse(`refs/remotes/origin/${branch}`))
)
}
// refs/pull/
else if (upperRef.startsWith('REFS/PULL/')) {
// Assume matches because fetched using the commit
return true
}
// refs/tags/
else if (upperRef.startsWith('REFS/TAGS/')) {
const tagName = ref.substring('refs/tags/'.length)
return (
(await git.tagExists(tagName)) && commit === (await git.revParse(ref))
)
}
// Unexpected
else {
core.debug(`Unexpected ref format '${ref}' when testing ref info`)
return true
}
}
export async function checkCommitInfo(
token: string,
commitInfo: string,
repositoryOwner: string,
repositoryName: string,
ref: string,
commit: string
): Promise<void> {
try {
// GHES?
if (isGhes()) {
return
}
// Auth token?
if (!token) {
return
}
// Public PR synchronize, for workflow repo?
if (
fromPayload('repository.private') !== false ||
github.context.eventName !== 'pull_request' ||
fromPayload('action') !== 'synchronize' ||
repositoryOwner !== github.context.repo.owner ||
repositoryName !== github.context.repo.repo ||
ref !== github.context.ref ||
!ref.startsWith('refs/pull/') ||
commit !== github.context.sha
) {
return
}
// Head SHA
const expectedHeadSha = fromPayload('after')
if (!expectedHeadSha) {
core.debug('Unable to determine head sha')
return
}
// Base SHA
const expectedBaseSha = fromPayload('pull_request.base.sha')
if (!expectedBaseSha) {
core.debug('Unable to determine base sha')
return
}
// Expected message?
const expectedMessage = `Merge ${expectedHeadSha} into ${expectedBaseSha}`
if (commitInfo.indexOf(expectedMessage) >= 0) {
return
}
// Extract details from message
const match = commitInfo.match(/Merge ([0-9a-f]{40}) into ([0-9a-f]{40})/)
if (!match) {
core.debug('Unexpected message format')
return
}
// Post telemetry
const actualHeadSha = match[1]
if (actualHeadSha !== expectedHeadSha) {
core.debug(
`Expected head sha ${expectedHeadSha}; actual head sha ${actualHeadSha}`
)
const octokit = new github.GitHub(token, {
userAgent: `actions-checkout-tracepoint/1.0 (code=STALE_MERGE;owner=${repositoryOwner};repo=${repositoryName};pr=${fromPayload(
'number'
)};run_id=${
process.env['GITHUB_RUN_ID']
};expected_head_sha=${expectedHeadSha};actual_head_sha=${actualHeadSha})`
})
await octokit.repos.get({owner: repositoryOwner, repo: repositoryName})
}
} catch (err) {
core.debug(`Error when validating commit info: ${err.stack}`)
}
}
function fromPayload(path: string): any {
return select(github.context.payload, path)
}
function select(obj: any, path: string): any {
if (!obj) {
return undefined
}
const i = path.indexOf('.')
if (i < 0) {
return obj[path]
}
const key = path.substr(0, i)
return select(obj[key], path.substr(i + 1))
}
function isGhes(): boolean {
const ghUrl = new URL(
process.env['GITHUB_SERVER_URL'] || 'https://github.com'
)
return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM'
}

29
src/url-helper.ts Normal file
View File

@ -0,0 +1,29 @@
import * as assert from 'assert'
import {IGitSourceSettings} from './git-source-settings'
import {URL} from 'url'
export function getFetchUrl(settings: IGitSourceSettings): string {
assert.ok(
settings.repositoryOwner,
'settings.repositoryOwner must be defined'
)
assert.ok(settings.repositoryName, 'settings.repositoryName must be defined')
const serviceUrl = getServerUrl()
const encodedOwner = encodeURIComponent(settings.repositoryOwner)
const encodedName = encodeURIComponent(settings.repositoryName)
if (settings.sshKey) {
return `git@${serviceUrl.hostname}:${encodedOwner}/${encodedName}.git`
}
// "origin" is SCHEME://HOSTNAME[:PORT]
return `${serviceUrl.origin}/${encodedOwner}/${encodedName}`
}
export function getServerUrl(): URL {
// todo: remove GITHUB_URL after support for GHES Alpha is no longer needed
return new URL(
process.env['GITHUB_SERVER_URL'] ||
process.env['GITHUB_URL'] ||
'https://github.com'
)
}