Compare commits

...

65 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
Johannes Schindelin 96f53100ba
Mark test scripts with Bash'isms to be run via Bash (#1377)
In #1369, I mistakenly replaced the hash-bang lines in the two new
scripts with `#!/bin/sh`, missing that both files contain the Bash'ism
`[[`. Symptom as per
https://github.com/actions/checkout/actions/runs/5200323109/jobs/9378889172?pr=1369#step:12:5

    __test__/verify-sparse-checkout.sh: 58: [[: not found

Let's change those hash-bang lines back to `#!/bin/bash`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
2023-06-09 11:05:29 -04:00
Tingluo Huang c85c95e3d7
Release v3.5.3 (#1376) 2023-06-09 10:47:54 -04:00
Johannes Schindelin d106d4669b
Add support for sparse checkouts (#1369)
* Add support for sparse checkouts

* sparse-checkout: optionally turn off cone mode

While it _is_ true that cone mode is the default nowadays (mainly for
performance reasons: code mode is much faster than non-cone mode), there
_are_ legitimate use cases where non-cone mode is really useful.

Let's add a flag to optionally disable cone mode.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>

* Verify minimum Git version for sparse checkout

The `git sparse-checkout` command is available only since Git version
v2.25.0. The `actions/checkout` Action actually supports older Git
versions than that; As of time of writing, the minimum version is
v2.18.0.

Instead of raising this minimum version even for users who do not
require a sparse checkout, only check for this minimum version
specifically when a sparse checkout was asked for.

Suggested-by: Tingluo Huang <tingluohuang@github.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>

* Support sparse checkout/LFS better

Instead of fetching all the LFS objects present in the current revision
in a sparse checkout, whether they are needed inside the sparse cone or
not, let's instead only pull the ones that are actually needed.

To do that, let's avoid running that preemptive `git lfs fetch` call in
case of a sparse checkout.

An alternative that was considered during the development of this patch
(and ultimately rejected) was to use `git lfs pull --include <path>...`,
but it turned out to be too inflexible because it requires exact paths,
not the patterns that are available via the sparse checkout definition,
and that risks running into command-line length limitations.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>

---------

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Co-authored-by: Daniel <daniel.fernandez@feverup.com>
2023-06-09 09:08:21 -04:00
Dimitri Papadopoulos Orfanos f095bcc56b
Fix typos found by codespell (#1287) 2023-04-19 16:55:10 +02:00
SKi 47fbe2df0a
Fix: Checkout fail in self-hosted runners when faulty submodule are checked-in (#1196)
* Fix Self hosted runner issue wrt bad submodules - solution cleanup working space.

* Fix format with npm run format output

* Add mock implementation for new function submoduleStatus

* Add 2  test cases for submodule status.

* Codeql-Action Analyse revert v1 to v2

---------

Co-authored-by: Bassem Dghaidi <568794+Link-@users.noreply.github.com>
Co-authored-by: sminnie <minnie@sankhe.com>
2023-04-14 06:26:47 -04:00
Ferenc Hammerl 8e5e7e5ab8
Release v3.5.2 (#1291)
* Update version and changelog for 3.5.2

* Update changelog
2023-04-13 14:45:33 +02:00
Ferenc Hammerl eb35239ec2
Fix: convert baseUrl to serverApiUrl 'formatted' (#1289)
* fix: convert baseUrl to apiUrl

* Run prettier

---------

Co-authored-by: 1newsr <1newsr@users.noreply.github.com>
2023-04-13 14:25:50 +02:00
Ferenc Hammerl 83b7061638
Release v3.5.1 (#1284)
* Sync checkout version with tag version

* Downgrade to 3.5.1

* Format changelog
2023-04-12 17:02:21 +02:00
Binrui Dong 40a16ebeed
Improve checkout performance on Windows runners by upgrading @actions/github dependency (#1246)
* Improve checkout performance on Windows runners by upgrading @actions/github dependency

Re: https://github.com/actions/checkout/issues/1186

@dscho discovered that the checkout action could stall for a
considerable amount of time on Windows runners waiting for PowerShell
invocations made from 'windows-release' npm package to complete.

Then I studied the dependency chain to figure out where
'windows-release' was imported:

'@actions/checkout'@main
  <- '@actions/github'@2.2.0
    <- '@octokit/endpoint'@6.0.1
    <- '@octokit/graphql'@4.3.1
    <- '@octokit/request'@5.4.2
    <- '@octokit/rest'@16.43.1
      <- 'universal-user-agent'@4.0.1
        <- 'os-name'@3.1.0
          <- 'windows-release'@3.1.0

'universal-user-agent' package dropped its dependency on 'os-name' in
https://github.com/gr2m/universal-user-agent/releases/tag/v6.0.0 .

'@actions/github' v3 removed dependency on '@octokit/rest'@16.43.1 and
allows users to move away from the old 'universal-user-agent' v4.
(https://github.com/actions/toolkit/pull/453)

This pull request attempts to update the version of '@actions/github'
used in the checkout action to avoid importing 'windows-release'.

Based on testing in my own repositories, I can see an improvement in
reduced wait time between entering the checkout action and git actually
starts to do useful work.

* Update .licenses

* Rebuild index.js
2023-04-12 13:55:27 +02:00
Cameron Booth 8f4b7f8486
Add new public key for known_hosts (#1237)
* Add new public key for known_hosts

* Fix the build!
2023-03-24 01:34:48 -04:00
Tingluo Huang cd6a9fd493
Update update-main-version.yml 2023-03-23 22:44:49 -04:00
Cory Miller 24cb908017
Bump @actions/io to v1.1.3 (#1225)
* Backfill changelog and bump actions/io
* Fill in PR for action/io upgrade
* Licensed updates
* Compile JS
2023-03-15 15:45:08 -04:00
Bassem Dghaidi 27135e314d
Upgrade dependencies (#1210)
* Upgrade ncc & json5

* Add new dist build
2023-03-09 11:42:29 -05:00
Bassem Dghaidi 7b187184d1
Upgrade codeql actions to v2 (#1209) 2023-03-09 10:59:09 -05:00
Andrew Scheller ac59398561
Fix comment typos (that got added in #770) (#1057) 2022-12-28 17:19:08 -05:00
Ryan van Zeben 3ba5ee6fac
Add in explicit reference to private checkout options (#1050) 2022-12-16 16:06:54 -05:00
Cory Miller 8856415920
Implement branch list using callbacks from exec function (#1045)
When trying to list local branches to figure out what needs cleaned up during runs on non-ephemeral Actions Runners, we use git rev-parse --symbolic-full-name to get a list of branches. This can lead to ambiguous ref name errors when there are branches and tags with similar names.

Part of the reason we use rev-parse --symbolic-full-name vs git branch --list or git rev-parse --symbolic seems to related to a bug in Git 2.18. Until we can deprecate our usage of Git 2.18, I think we need to keep --symbolic-full-name. Since part of the problem is that these ambiguous ref name errors clog the Actions annotation limits, this is a mitigation to suppress those messages until we can get rid of the workaround.
2022-12-14 16:08:53 -05:00
Vallie Joseph 755da8c3cf
3.2.0 (#1039) 2022-12-12 14:11:05 -05:00
Cory Miller 26d48e8ea1
Update @actions/io to 1.1.2 (#1029) 2022-12-12 13:44:24 -05:00
Jok bf085276ce
wrap pipeline commands for submoduleForeach in quotes (#964)
* wrap pipeline commands for submoduleForeach in quotes

* Update src/git-auth-helper.ts

drop extraneous space.

Co-authored-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* Followed CONTRIBUTING.md instructions, updating dist/index.js

* fixed package-lock.json

* updating the pipeline so it runs from sh

Co-authored-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2022-11-15 21:51:19 -05:00
Cory Miller 5c3ccc22eb
Replace datadog/squid with ubuntu/squid Docker image (#1002)
* test with ubuntu/squid

* Start a contributing guide

* remove link
2022-11-10 15:54:12 -05:00
Scott Brenner 1f9a0c22da
README - fix status badge (#967) 2022-10-17 09:49:29 -04:00
Francesco Renzi 8230315d06
Add workflow to update a main version (#942)
* Add workflow to update a main version

* New line
2022-10-04 13:44:41 +01:00
Francesco Renzi 93ea575cb5
Prepare release v3.1.0 (#940)
* Prepare changelog for v3.1.0

* Bump package
2022-10-04 10:37:06 +01:00
Francesco Renzi 6a84743051
Bump @actions/core to 1.10.0 (#939)
* Bump @actions/core to 1.10.0

* Update licenses

* Use @actions/core helper functions
2022-10-03 18:04:49 +01:00
Peter Murray e6d535c99c
Inject GitHub host to be able to clone from another GitHub instance (#922)
* Adding the ability to specify the GitHub Server URL and allowing for it to differ from the Actions workflow host

* Adding tests for injecting the GitHub URL

* Addressing code review comments for PR #922
2022-09-26 12:34:52 -04:00
Tingluo Huang 2541b1294d
Prepare changelog for v3.0.2. (#777) 2022-04-21 10:29:04 -04:00
Tingluo Huang 0ffe6f9c55
Add set-safe-directory input to allow customers to take control. (#770)
* Add set-safe-directory input to allow customers to take control.
2022-04-20 21:37:43 -04:00
Thomas Boop dcd71f6466
Enforce safe directory (#762)
* set safe directory when running checkout

* Update CHANGELOG.md
2022-04-14 14:13:20 -04:00
Tingluo Huang add3486cc3
Patch to fix the dependbot alert. (#744)
* Patch to fix the dependbot alert.

* .

* .

* .
2022-04-05 13:01:33 -04:00
dependabot[bot] 5126516654
Bump minimist from 1.2.5 to 1.2.6 (#741)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-31 10:09:15 -04:00
Edward Thomson d50f8ea767
Add v3.0 release information to changelog (#740) 2022-03-25 09:52:31 -04:00
Thomas Boop 2d1c1198e7
update test workflows to checkout v3 (#709) 2022-03-01 13:02:13 -05:00
Thomas Boop a12a3943b4
update readme for v3 (#708)
* update readme for v3

* update readme with changes

* nit grammar
2022-03-01 12:46:45 -05:00
Thomas Boop 8f9e05e482
Update to node 16 (#689)
* Update to node 16

* update setup-node version

* Update check-dist.yml

update setup node version

* update dist/index.js
2022-02-28 16:17:29 -05:00
Ameya Lokare 230611dbd0
Change secret name for PAT to not start with GITHUB_ (#623)
Github doesn't allow secret names that start with `GITHUB_` (case insensitive). Update README to choose a different prefix (GH).
2021-11-02 16:20:59 -05:00
eric sciple ec3a7ce113
set insteadOf url for org-id (#621) 2021-11-01 11:43:18 -05:00
eric sciple fd47087372
codeql should analyze lib not dist (#620) 2021-10-20 15:11:24 -05:00
eric sciple 3d677ac575
script to generate license info (#614) 2021-10-19 14:30:04 -05:00
eric sciple 826ba42d6c
npm audit fix (#612) 2021-10-19 10:05:28 -05:00
eric sciple eb8a193c1d
update dev dependencies and react to new linting rules (#611) 2021-10-19 09:52:57 -05:00
Jeremy Epling c49af7ca1f
Create codeql-analysis.yml (#602) 2021-10-18 16:28:25 -05:00
Thomas Boop 1e204e9a92
update licensed check (#606) 2021-10-13 16:22:03 -05:00
eric sciple 0299a0d2b6
update dist (#605) 2021-10-13 16:07:05 -05:00
dependabot[bot] be0f448456
Bump ws from 5.2.2 to 5.2.3 (#604)
Bumps [ws](https://github.com/websockets/ws) from 5.2.2 to 5.2.3.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/5.2.2...5.2.3)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-13 09:14:20 -05:00
dependabot[bot] 56c00a7b1f
Bump tmpl from 1.0.4 to 1.0.5 (#588)
Bumps [tmpl](https://github.com/daaku/nodejs-tmpl) from 1.0.4 to 1.0.5.
- [Release notes](https://github.com/daaku/nodejs-tmpl/releases)
- [Commits](https://github.com/daaku/nodejs-tmpl/commits/v1.0.5)

---
updated-dependencies:
- dependency-name: tmpl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-13 09:13:31 -05:00
dependabot[bot] 85e47d1a2b
Bump path-parse from 1.0.6 to 1.0.7 (#568)
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-13 09:13:04 -05:00
dependabot[bot] 3fc17f8645
Bump hosted-git-info from 2.8.5 to 2.8.9 (#500)
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.5 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.5...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-13 09:11:06 -05:00
dependabot[bot] e3bc06d986
Bump lodash from 4.17.15 to 4.17.21 (#499)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-13 09:08:31 -05:00
dependabot[bot] 442567ba57
Bump handlebars from 4.5.3 to 4.7.7 (#497)
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.5.3 to 4.7.7.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.5.3...v4.7.7)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-13 09:07:45 -05:00
dependabot[bot] 7f00b66d06
Bump y18n from 4.0.0 to 4.0.1 (#469)
Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-13 09:07:05 -05:00
dependabot[bot] eccf386318
Bump @actions/core from 1.1.3 to 1.2.6 (#361)
Bumps [@actions/core](https://github.com/actions/toolkit/tree/HEAD/packages/core) from 1.1.3 to 1.2.6.
- [Release notes](https://github.com/actions/toolkit/releases)
- [Changelog](https://github.com/actions/toolkit/blob/main/packages/core/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/core)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-13 08:57:33 -05:00
dependabot[bot] 2bd2911be9
Bump acorn from 5.7.3 to 5.7.4 (#186)
Bumps [acorn](https://github.com/acornjs/acorn) from 5.7.3 to 5.7.4.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/5.7.3...5.7.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-13 08:55:25 -05:00
Brian Cristante afe4af09a7
Create check-dist.yml (#566)
* Add check-dist.yml

* Don't need to mv to git diff

* Upload the whole dist/ directory as an artifact

* Update .github/workflows/check-dist.yml
2021-08-17 16:08:22 -04:00
107 changed files with 30735 additions and 33334 deletions

View File

@ -1,6 +1,6 @@
{ {
"plugins": ["jest", "@typescript-eslint"], "plugins": ["jest", "@typescript-eslint"],
"extends": ["plugin:github/es6"], "extends": ["plugin:github/recommended"],
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"parserOptions": { "parserOptions": {
"ecmaVersion": 9, "ecmaVersion": 9,
@ -16,13 +16,9 @@
"@typescript-eslint/no-require-imports": "error", "@typescript-eslint/no-require-imports": "error",
"@typescript-eslint/array-type": "error", "@typescript-eslint/array-type": "error",
"@typescript-eslint/await-thenable": "error", "@typescript-eslint/await-thenable": "error",
"@typescript-eslint/ban-ts-ignore": "error",
"camelcase": "off", "camelcase": "off",
"@typescript-eslint/camelcase": "error",
"@typescript-eslint/class-name-casing": "error",
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}], "@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
"@typescript-eslint/func-call-spacing": ["error", "never"], "@typescript-eslint/func-call-spacing": ["error", "never"],
"@typescript-eslint/generic-type-naming": ["error", "^[A-Z][A-Za-z]*$"],
"@typescript-eslint/no-array-constructor": "error", "@typescript-eslint/no-array-constructor": "error",
"@typescript-eslint/no-empty-interface": "error", "@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-explicit-any": "error",
@ -33,7 +29,6 @@
"@typescript-eslint/no-misused-new": "error", "@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-namespace": "error", "@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-non-null-assertion": "warn", "@typescript-eslint/no-non-null-assertion": "warn",
"@typescript-eslint/no-object-literal-type-assertion": "error",
"@typescript-eslint/no-unnecessary-qualifier": "error", "@typescript-eslint/no-unnecessary-qualifier": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error", "@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-useless-constructor": "error", "@typescript-eslint/no-useless-constructor": "error",
@ -41,7 +36,6 @@
"@typescript-eslint/prefer-for-of": "warn", "@typescript-eslint/prefer-for-of": "warn",
"@typescript-eslint/prefer-function-type": "warn", "@typescript-eslint/prefer-function-type": "warn",
"@typescript-eslint/prefer-includes": "error", "@typescript-eslint/prefer-includes": "error",
"@typescript-eslint/prefer-interface": "error",
"@typescript-eslint/prefer-string-starts-ends-with": "error", "@typescript-eslint/prefer-string-starts-ends-with": "error",
"@typescript-eslint/promise-function-async": "error", "@typescript-eslint/promise-function-async": "error",
"@typescript-eslint/require-array-sort-compare": "error", "@typescript-eslint/require-array-sort-compare": "error",

51
.github/workflows/check-dist.yml vendored Normal file
View File

@ -0,0 +1,51 @@
# `dist/index.js` is a special file in Actions.
# When you reference an action with `uses:` in a workflow,
# `index.js` is the code that will run.
# For our project, we generate this file through a build process
# from other source files.
# We need to make sure the checked-in `index.js` actually matches what we expect it to be.
name: Check dist
on:
push:
branches:
- main
paths-ignore:
- '**.md'
pull_request:
paths-ignore:
- '**.md'
workflow_dispatch:
jobs:
check-dist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set Node.js 20.x
uses: actions/setup-node@v1
with:
node-version: 20.x
- name: Install dependencies
run: npm ci
- name: Rebuild the index.js file
run: npm run build
- name: Compare the expected and actual dist/ directories
run: |
if [ "$(git diff --ignore-space-at-eol dist/ | wc -l)" -gt "0" ]; then
echo "Detected uncommitted changes after build. See status below:"
git diff
exit 1
fi
# If dist/ was different than expected, upload the expected version as an artifact
- uses: actions/upload-artifact@v2
if: ${{ failure() && steps.diff.conclusion == 'failure' }}
with:
name: dist
path: dist/

58
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@ -0,0 +1,58 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '28 9 * * 0'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
- run: npm ci
- run: npm run build
- run: rm -rf dist # We want code scanning to analyze lib instead (individual .js files)
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

View File

@ -9,12 +9,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: Check licenses name: Check licenses
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- run: npm ci - run: npm ci
- name: Install licensed - run: npm run licensed-check
run: |
cd $RUNNER_TEMP
curl -Lfs -o licensed.tar.gz https://github.com/github/licensed/releases/download/2.12.2/licensed-2.12.2-linux-x64.tar.gz
sudo tar -xzf licensed.tar.gz
sudo mv licensed /usr/local/bin/licensed
- run: licensed status

View File

@ -13,8 +13,8 @@ jobs:
steps: steps:
- uses: actions/setup-node@v1 - uses: actions/setup-node@v1
with: with:
node-version: 12.x node-version: 20.x
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- run: npm ci - run: npm ci
- run: npm run build - run: npm run build
- run: npm run format-check - run: npm run format-check
@ -32,7 +32,7 @@ jobs:
steps: steps:
# Clone this repo # Clone this repo
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
# Basic checkout # Basic checkout
- name: Checkout basic - name: Checkout basic
@ -72,6 +72,43 @@ 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
- name: Sparse checkout
uses: ./
with:
sparse-checkout: |
__test__
.github
dist
path: sparse-checkout
- name: Verify sparse checkout
run: __test__/verify-sparse-checkout.sh
# Sparse checkout (non-cone mode)
- name: Sparse checkout (non-cone mode)
uses: ./
with:
sparse-checkout: |
/__test__/
/.github/
/dist/
sparse-checkout-cone-mode: false
path: sparse-checkout-non-cone-mode
- name: Verify sparse checkout (non-cone mode)
run: __test__/verify-sparse-checkout-non-cone-mode.sh
# LFS # LFS
- name: Checkout LFS - name: Checkout LFS
uses: ./ uses: ./
@ -142,7 +179,7 @@ jobs:
options: --dns 127.0.0.1 options: --dns 127.0.0.1
services: services:
squid-proxy: squid-proxy:
image: datadog/squid:latest image: ubuntu/squid:latest
ports: ports:
- 3128:3128 - 3128:3128
env: env:
@ -150,7 +187,7 @@ jobs:
steps: steps:
# Clone this repo # Clone this repo
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
# Basic checkout using git # Basic checkout using git
- name: Checkout basic - name: Checkout basic
@ -182,7 +219,7 @@ jobs:
steps: steps:
# Clone this repo # Clone this repo
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
# Basic checkout using git # Basic checkout using git
- name: Checkout basic - name: Checkout basic
@ -205,3 +242,41 @@ jobs:
path: basic path: basic
- name: Verify basic - name: Verify basic
run: __test__/verify-basic.sh --archive run: __test__/verify-basic.sh --archive
test-git-container:
runs-on: ubuntu-latest
container: bitnami/git:latest
steps:
# Clone this repo
- name: Checkout
uses: actions/checkout@v3
with:
path: v3
# Basic checkout using git
- name: Checkout basic
uses: ./v3
with:
ref: test-data/v2/basic
- name: Verify basic
run: |
if [ ! -f "./basic-file.txt" ]; then
echo "Expected basic file does not exist"
exit 1
fi
# Verify .git folder
if [ ! -d "./.git" ]; then
echo "Expected ./.git folder to exist"
exit 1
fi
# Verify auth token
git config --global --add safe.directory "*"
git fetch --no-tags --depth=1 origin +refs/heads/main:refs/remotes/origin/main
# needed to make checkout post cleanup succeed
- name: Fix Checkout v3
uses: actions/checkout@v3
with:
path: v3

View File

@ -0,0 +1,32 @@
name: Update Main Version
run-name: Move ${{ github.event.inputs.major_version }} to ${{ github.event.inputs.target }}
on:
workflow_dispatch:
inputs:
target:
description: The tag or reference to use
required: true
major_version:
type: choice
description: The major version to update
options:
- v4
- v3
- v2
jobs:
tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Git config
run: |
git config user.name github-actions
git config user.email github-actions@github.com
- name: Tag new target
run: git tag -f ${{ github.event.inputs.major_version }} ${{ github.event.inputs.target }}
- name: Push new tag
run: git push origin ${{ github.event.inputs.major_version }} --force

2
.gitignore vendored
View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
.licenses/npm/call-bind.dep.yml generated Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
.licenses/npm/get-intrinsic.dep.yml generated Normal file

Binary file not shown.

Binary file not shown.

BIN
.licenses/npm/has-symbols.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/has.dep.yml generated Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
.licenses/npm/object-inspect.dep.yml generated Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
.licenses/npm/qs.dep.yml generated Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
.licenses/npm/side-channel.dep.yml generated Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
.licenses/npm/tr46.dep.yml generated Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
.licenses/npm/webidl-conversions.dep.yml generated Normal file

Binary file not shown.

Binary file not shown.

View File

@ -1,10 +1,67 @@
# 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
- [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)
- [Add support for sparse checkouts](https://github.com/actions/checkout/pull/1369)
## v3.5.2
- [Fix api endpoint for GHES](https://github.com/actions/checkout/pull/1289)
## v3.5.1
- [Fix slow checkout on Windows](https://github.com/actions/checkout/pull/1246)
## v3.5.0
* [Add new public key for known_hosts](https://github.com/actions/checkout/pull/1237)
## v3.4.0
- [Upgrade codeql actions to v2](https://github.com/actions/checkout/pull/1209)
- [Upgrade dependencies](https://github.com/actions/checkout/pull/1210)
- [Upgrade @actions/io](https://github.com/actions/checkout/pull/1225)
## v3.3.0
- [Implement branch list using callbacks from exec function](https://github.com/actions/checkout/pull/1045)
- [Add in explicit reference to private checkout options](https://github.com/actions/checkout/pull/1050)
- [Fix comment typos (that got added in #770)](https://github.com/actions/checkout/pull/1057)
## v3.2.0
- [Add GitHub Action to perform release](https://github.com/actions/checkout/pull/942)
- [Fix status badge](https://github.com/actions/checkout/pull/967)
- [Replace datadog/squid with ubuntu/squid Docker image](https://github.com/actions/checkout/pull/1002)
- [Wrap pipeline commands for submoduleForeach in quotes](https://github.com/actions/checkout/pull/964)
- [Update @actions/io to 1.1.2](https://github.com/actions/checkout/pull/1029)
- [Upgrading version to 3.2.0](https://github.com/actions/checkout/pull/1039)
## v3.1.0
- [Use @actions/core `saveState` and `getState`](https://github.com/actions/checkout/pull/939)
- [Add `github-server-url` input](https://github.com/actions/checkout/pull/922)
## v3.0.2
- [Add input `set-safe-directory`](https://github.com/actions/checkout/pull/770)
## v3.0.1
- [Fixed an issue where checkout failed to run in container jobs due to the new git setting `safe.directory`](https://github.com/actions/checkout/pull/762)
- [Bumped various npm package versions](https://github.com/actions/checkout/pull/744)
## v3.0.0
- [Update to node 16](https://github.com/actions/checkout/pull/689)
## v2.3.1 ## v2.3.1
- [Fix default branch resolution for .wiki and when using SSH](https://github.com/actions/checkout/pull/284) - [Fix default branch resolution for .wiki and when using SSH](https://github.com/actions/checkout/pull/284)
## v2.3.0 ## v2.3.0
- [Fallback to the default branch](https://github.com/actions/checkout/pull/278) - [Fallback to the default branch](https://github.com/actions/checkout/pull/278)

View File

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

28
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,28 @@
# Contributing
## Submitting a pull request
1. Fork and clone the repository
1. Configure and install the dependencies: `npm install`
1. Create a new branch: `git checkout -b my-branch-name`
1. Make your change, add tests, and make sure the tests still pass: `npm run test`
1. Make sure your code is correctly formatted: `npm run format`
1. Update `dist/index.js` using `npm run build`. This creates a single javascript file that is used as an entrypoint for the action
1. Push to your fork and submit a pull request
1. Pat yourself on the back and wait for your pull request to be reviewed and merged
Here are a few things you can do that will increase the likelihood of your pull request being accepted:
- Write tests.
- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
## Resources
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
- [GitHub Help](https://help.github.com)
- [Writing good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
Thanks! :heart: :heart: :heart:
GitHub Actions Team :octocat:

117
README.md
View File

@ -1,12 +1,10 @@
<p align="center"> [![Build and Test](https://github.com/actions/checkout/actions/workflows/test.yml/badge.svg)](https://github.com/actions/checkout/actions/workflows/test.yml)
<a href="https://github.com/actions/checkout"><img alt="GitHub Actions status" src="https://github.com/actions/checkout/workflows/test-local/badge.svg"></a>
</p>
# Checkout V2 # 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.
@ -14,27 +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
- Improved performance Please refer to the [release page](https://github.com/actions/checkout/releases/latest) for the latest release notes.
- Fetches only a single commit by default
- Script authenticated git commands
- Auth token persisted in the local git config
- Supports SSH
- Creates a local branch
- No longer detached HEAD when checking out a branch
- Improved layout
- The input `path` is always relative to $GITHUB_WORKSPACE
- Aligns better with container actions, where $GITHUB_WORKSPACE gets mapped in
- 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 using a job container, the container's PATH is used
Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous versions.
# Usage # Usage
<!-- start usage --> <!-- start usage -->
```yaml ```yaml
- uses: actions/checkout@v2 - 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 }}
@ -89,10 +73,31 @@ Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous
# 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
# new lines.
# Default: null
sparse-checkout: ''
# Specifies whether to use cone-mode when doing a sparse checkout.
# Default: true
sparse-checkout-cone-mode: ''
# Number of commits to fetch. 0 indicates all history for all branches and tags. # Number of commits to fetch. 0 indicates all history for all branches and tags.
# 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: ''
@ -105,11 +110,25 @@ Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous
# #
# Default: false # Default: false
submodules: '' submodules: ''
# Add repository path as safe.directory for Git global config by running `git
# config --global --add safe.directory <path>`
# Default: true
set-safe-directory: ''
# 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
github-server-url: ''
``` ```
<!-- end usage --> <!-- end usage -->
# Scenarios # Scenarios
- [Fetch only the root files](#Fetch-only-the-root-files)
- [Fetch only the root files and `.github` and `src` folder](#Fetch-only-the-root-files-and-github-and-src-folder)
- [Fetch only a single file](#Fetch-only-a-single-file)
- [Fetch all history for all tags and branches](#Fetch-all-history-for-all-tags-and-branches) - [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)
@ -120,10 +139,38 @@ Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous
- [Checkout pull request on closed event](#Checkout-pull-request-on-closed-event) - [Checkout pull request on closed event](#Checkout-pull-request-on-closed-event)
- [Push a commit using the built-in token](#Push-a-commit-using-the-built-in-token) - [Push a commit using the built-in token](#Push-a-commit-using-the-built-in-token)
## Fetch only the root files
```yaml
- uses: actions/checkout@v4
with:
sparse-checkout: .
```
## Fetch only the root files and `.github` and `src` folder
```yaml
- uses: actions/checkout@v4
with:
sparse-checkout: |
.github
src
```
## Fetch only a single file
```yaml
- uses: actions/checkout@v4
with:
sparse-checkout: |
README.md
sparse-checkout-cone-mode: false
```
## Fetch all history for all tags and branches ## Fetch all history for all tags and branches
```yaml ```yaml
- uses: actions/checkout@v2 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
``` ```
@ -131,7 +178,7 @@ Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous
## Checkout a different branch ## Checkout a different branch
```yaml ```yaml
- uses: actions/checkout@v2 - uses: actions/checkout@v4
with: with:
ref: my-branch ref: my-branch
``` ```
@ -139,7 +186,7 @@ Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous
## Checkout HEAD^ ## Checkout HEAD^
```yaml ```yaml
- uses: actions/checkout@v2 - uses: actions/checkout@v4
with: with:
fetch-depth: 2 fetch-depth: 2
- run: git checkout HEAD^ - run: git checkout HEAD^
@ -149,43 +196,45 @@ Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous
```yaml ```yaml
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
path: main path: main
- name: Checkout tools repo - name: Checkout tools repo
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
repository: my-org/my-tools repository: my-org/my-tools
path: my-tools path: my-tools
``` ```
> - If your secondary repository is private you will need to add the option noted in [Checkout multiple repos (private)](#Checkout-multiple-repos-private)
## Checkout multiple repos (nested) ## Checkout multiple repos (nested)
```yaml ```yaml
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Checkout tools repo - name: Checkout tools repo
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
repository: my-org/my-tools repository: my-org/my-tools
path: my-tools path: my-tools
``` ```
> - If your secondary repository is private you will need to add the option noted in [Checkout multiple repos (private)](#Checkout-multiple-repos-private)
## Checkout multiple repos (private) ## Checkout multiple repos (private)
```yaml ```yaml
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
path: main path: main
- name: Checkout private tools - name: Checkout private tools
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
repository: my-org/my-private-tools repository: my-org/my-private-tools
token: ${{ secrets.GitHub_PAT }} # `GitHub_PAT` is a secret that contains your PAT token: ${{ secrets.GH_PAT }} # `GH_PAT` is a secret that contains your PAT
path: my-tools path: my-tools
``` ```
@ -195,7 +244,7 @@ Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous
## Checkout pull request HEAD commit instead of merge commit ## Checkout pull request HEAD commit instead of merge commit
```yaml ```yaml
- uses: actions/checkout@v2 - uses: actions/checkout@v4
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
``` ```
@ -211,7 +260,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
``` ```
## Push a commit using the built-in token ## Push a commit using the built-in token
@ -222,7 +271,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - 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

@ -20,6 +20,7 @@ let tempHomedir: string
let git: IGitCommandManager & {env: {[key: string]: string}} let git: IGitCommandManager & {env: {[key: string]: string}}
let settings: IGitSourceSettings let settings: IGitSourceSettings
let sshPath: string let sshPath: string
let githubServerUrl: string
describe('git-auth-helper tests', () => { describe('git-auth-helper tests', () => {
beforeAll(async () => { beforeAll(async () => {
@ -67,11 +68,18 @@ describe('git-auth-helper tests', () => {
} }
}) })
const configureAuth_configuresAuthHeader = async function testAuthHeader(
'configureAuth configures auth header' testName: string,
it(configureAuth_configuresAuthHeader, async () => { serverUrl: string | undefined = undefined
) {
// Arrange // Arrange
await setup(configureAuth_configuresAuthHeader) let expectedServerUrl = 'https://github.com'
if (serverUrl) {
githubServerUrl = serverUrl
expectedServerUrl = githubServerUrl
}
await setup(testName)
expect(settings.authToken).toBeTruthy() // sanity check expect(settings.authToken).toBeTruthy() // sanity check
const authHelper = gitAuthHelper.createAuthHelper(git, settings) const authHelper = gitAuthHelper.createAuthHelper(git, settings)
@ -88,9 +96,33 @@ describe('git-auth-helper tests', () => {
).toString('base64') ).toString('base64')
expect( expect(
configContent.indexOf( configContent.indexOf(
`http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}` `http.${expectedServerUrl}/.extraheader AUTHORIZATION: basic ${basicCredential}`
) )
).toBeGreaterThanOrEqual(0) ).toBeGreaterThanOrEqual(0)
}
const configureAuth_configuresAuthHeader =
'configureAuth configures auth header'
it(configureAuth_configuresAuthHeader, async () => {
await testAuthHeader(configureAuth_configuresAuthHeader)
})
const configureAuth_AcceptsGitHubServerUrl =
'inject https://my-ghes-server.com as github server url'
it(configureAuth_AcceptsGitHubServerUrl, async () => {
await testAuthHeader(
configureAuth_AcceptsGitHubServerUrl,
'https://my-ghes-server.com'
)
})
const configureAuth_AcceptsGitHubServerUrlSetToGHEC =
'inject https://github.com as github server url'
it(configureAuth_AcceptsGitHubServerUrlSetToGHEC, async () => {
await testAuthHeader(
configureAuth_AcceptsGitHubServerUrl,
'https://github.com'
)
}) })
const configureAuth_configuresAuthHeaderEvenWhenPersistCredentialsFalse = const configureAuth_configuresAuthHeaderEvenWhenPersistCredentialsFalse =
@ -417,7 +449,7 @@ describe('git-auth-helper tests', () => {
`Did not expect file to exist: '${globalGitConfigPath}'` `Did not expect file to exist: '${globalGitConfigPath}'`
) )
} catch (err) { } catch (err) {
if (err.code !== 'ENOENT') { if ((err as any)?.code !== 'ENOENT') {
throw err throw err
} }
} }
@ -518,12 +550,17 @@ describe('git-auth-helper tests', () => {
await authHelper.configureSubmoduleAuth() await authHelper.configureSubmoduleAuth()
// Assert // Assert
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(3) expect(mockSubmoduleForeach).toHaveBeenCalledTimes(4)
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(/url.*insteadOf/) expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(
/url.*insteadOf.*git@github.com:/
)
expect(mockSubmoduleForeach.mock.calls[3][0]).toMatch(
/url.*insteadOf.*org-123456@github.com:/
)
} }
) )
@ -601,7 +638,7 @@ describe('git-auth-helper tests', () => {
await fs.promises.stat(actualKeyPath) await fs.promises.stat(actualKeyPath)
throw new Error('SSH key should have been deleted') throw new Error('SSH key should have been deleted')
} catch (err) { } catch (err) {
if (err.code !== 'ENOENT') { if ((err as any)?.code !== 'ENOENT') {
throw err throw err
} }
} }
@ -611,7 +648,7 @@ describe('git-auth-helper tests', () => {
await fs.promises.stat(actualKnownHostsPath) await fs.promises.stat(actualKnownHostsPath)
throw new Error('SSH known hosts should have been deleted') throw new Error('SSH known hosts should have been deleted')
} catch (err) { } catch (err) {
if (err.code !== 'ENOENT') { if ((err as any)?.code !== 'ENOENT') {
throw err throw err
} }
} }
@ -638,10 +675,11 @@ describe('git-auth-helper tests', () => {
expect(gitConfigContent.indexOf('http.')).toBeLessThan(0) expect(gitConfigContent.indexOf('http.')).toBeLessThan(0)
}) })
const removeGlobalAuth_removesOverride = 'removeGlobalAuth removes override' const removeGlobalConfig_removesOverride =
it(removeGlobalAuth_removesOverride, async () => { 'removeGlobalConfig removes override'
it(removeGlobalConfig_removesOverride, async () => {
// Arrange // Arrange
await setup(removeGlobalAuth_removesOverride) await setup(removeGlobalConfig_removesOverride)
const authHelper = gitAuthHelper.createAuthHelper(git, settings) const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth() await authHelper.configureAuth()
await authHelper.configureGlobalAuth() await authHelper.configureGlobalAuth()
@ -650,7 +688,7 @@ describe('git-auth-helper tests', () => {
await fs.promises.stat(path.join(git.env['HOME'], '.gitconfig')) await fs.promises.stat(path.join(git.env['HOME'], '.gitconfig'))
// Act // Act
await authHelper.removeGlobalAuth() await authHelper.removeGlobalConfig()
// Assert // Assert
expect(git.env['HOME']).toBeUndefined() expect(git.env['HOME']).toBeUndefined()
@ -658,7 +696,7 @@ describe('git-auth-helper tests', () => {
await fs.promises.stat(homeOverride) await fs.promises.stat(homeOverride)
throw new Error(`Should have been deleted '${homeOverride}'`) throw new Error(`Should have been deleted '${homeOverride}'`)
} catch (err) { } catch (err) {
if (err.code !== 'ENOENT') { if ((err as any)?.code !== 'ENOENT') {
throw err throw err
} }
} }
@ -689,6 +727,8 @@ async function setup(testName: string): Promise<void> {
branchDelete: jest.fn(), branchDelete: jest.fn(),
branchExists: jest.fn(), branchExists: jest.fn(),
branchList: jest.fn(), branchList: jest.fn(),
sparseCheckout: jest.fn(),
sparseCheckoutNonConeMode: jest.fn(),
checkout: jest.fn(), checkout: jest.fn(),
checkoutDetach: jest.fn(), checkoutDetach: jest.fn(),
config: jest.fn( config: jest.fn(
@ -732,6 +772,9 @@ async function setup(testName: string): Promise<void> {
return '' return ''
}), }),
submoduleSync: jest.fn(), submoduleSync: jest.fn(),
submoduleStatus: jest.fn(async () => {
return true
}),
submoduleUpdate: jest.fn(), submoduleUpdate: jest.fn(),
tagExists: jest.fn(), tagExists: jest.fn(),
tryClean: jest.fn(), tryClean: jest.fn(),
@ -759,7 +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: [],
sparseCheckoutConeMode: true,
fetchDepth: 1, fetchDepth: 1,
fetchTags: false,
showProgress: true,
lfs: false, lfs: false,
submodules: false, submodules: false,
nestedSubmodules: false, nestedSubmodules: false,
@ -770,7 +818,10 @@ async function setup(testName: string): Promise<void> {
repositoryPath: '', repositoryPath: '',
sshKey: sshPath ? 'some ssh private key' : '', sshKey: sshPath ? 'some ssh private key' : '',
sshKnownHosts: '', sshKnownHosts: '',
sshStrict: true sshStrict: true,
workflowOrganizationId: 123456,
setSafeDirectory: true,
githubServerUrl: githubServerUrl
} }
} }

View File

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

View File

@ -281,6 +281,65 @@ describe('git-directory-helper tests', () => {
expect(git.branchDelete).toHaveBeenCalledWith(false, 'local-branch-2') expect(git.branchDelete).toHaveBeenCalledWith(false, 'local-branch-2')
}) })
const cleanWhenSubmoduleStatusIsFalse =
'cleans when submodule status is false'
it(cleanWhenSubmoduleStatusIsFalse, async () => {
// Arrange
await setup(cleanWhenSubmoduleStatusIsFalse)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
//mock bad submodule
const submoduleStatus = git.submoduleStatus as jest.Mock<any, any>
submoduleStatus.mockImplementation(async (remote: boolean) => {
return false
})
// Act
await gitDirectoryHelper.prepareExistingDirectory(
git,
repositoryPath,
repositoryUrl,
clean,
ref
)
// Assert
const files = await fs.promises.readdir(repositoryPath)
expect(files).toHaveLength(0)
expect(git.tryClean).toHaveBeenCalled()
})
const doesNotCleanWhenSubmoduleStatusIsTrue =
'does not clean when submodule status is true'
it(doesNotCleanWhenSubmoduleStatusIsTrue, async () => {
// Arrange
await setup(doesNotCleanWhenSubmoduleStatusIsTrue)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
const submoduleStatus = git.submoduleStatus as jest.Mock<any, any>
submoduleStatus.mockImplementation(async (remote: boolean) => {
return true
})
// Act
await gitDirectoryHelper.prepareExistingDirectory(
git,
repositoryPath,
repositoryUrl,
clean,
ref
)
// Assert
const files = await fs.promises.readdir(repositoryPath)
expect(files.sort()).toEqual(['.git', 'my-file'])
expect(git.tryClean).toHaveBeenCalled()
})
const removesLockFiles = 'removes lock files' const removesLockFiles = 'removes lock files'
it(removesLockFiles, async () => { it(removesLockFiles, async () => {
// Arrange // Arrange
@ -403,6 +462,8 @@ async function setup(testName: string): Promise<void> {
branchList: jest.fn(async () => { branchList: jest.fn(async () => {
return [] return []
}), }),
sparseCheckout: jest.fn(),
sparseCheckoutNonConeMode: jest.fn(),
checkout: jest.fn(), checkout: jest.fn(),
checkoutDetach: jest.fn(), checkoutDetach: jest.fn(),
config: jest.fn(), config: jest.fn(),
@ -423,6 +484,9 @@ async function setup(testName: string): Promise<void> {
submoduleForeach: jest.fn(), submoduleForeach: jest.fn(),
submoduleSync: jest.fn(), submoduleSync: jest.fn(),
submoduleUpdate: jest.fn(), submoduleUpdate: jest.fn(),
submoduleStatus: jest.fn(async () => {
return true
}),
tagExists: jest.fn(), tagExists: jest.fn(),
tryClean: jest.fn(async () => { tryClean: jest.fn(async () => {
return true return true

View File

@ -1,9 +1,9 @@
import * as assert from 'assert'
import * as core from '@actions/core' import * as core from '@actions/core'
import * as fsHelper from '../lib/fs-helper' import * as fsHelper from '../lib/fs-helper'
import * as github from '@actions/github' import * as github from '@actions/github'
import * as inputHelper from '../lib/input-helper' import * as inputHelper from '../lib/input-helper'
import * as path from 'path' import * as path from 'path'
import * as workflowContextHelper from '../lib/workflow-context-helper'
import {IGitSourceSettings} from '../lib/git-source-settings' import {IGitSourceSettings} from '../lib/git-source-settings'
const originalGitHubWorkspace = process.env['GITHUB_WORKSPACE'] const originalGitHubWorkspace = process.env['GITHUB_WORKSPACE']
@ -43,6 +43,11 @@ describe('input-helper tests', () => {
.spyOn(fsHelper, 'directoryExistsSync') .spyOn(fsHelper, 'directoryExistsSync')
.mockImplementation((path: string) => path == gitHubWorkspace) .mockImplementation((path: string) => path == gitHubWorkspace)
// Mock ./workflowContextHelper getOrganizationId()
jest
.spyOn(workflowContextHelper, 'getOrganizationId')
.mockImplementation(() => Promise.resolve(123456))
// GitHub workspace // GitHub workspace
process.env['GITHUB_WORKSPACE'] = gitHubWorkspace process.env['GITHUB_WORKSPACE'] = gitHubWorkspace
}) })
@ -67,26 +72,32 @@ describe('input-helper tests', () => {
jest.restoreAllMocks() jest.restoreAllMocks()
}) })
it('sets defaults', () => { it('sets defaults', async () => {
const settings: IGitSourceSettings = inputHelper.getInputs() const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings).toBeTruthy() expect(settings).toBeTruthy()
expect(settings.authToken).toBeFalsy() expect(settings.authToken).toBeFalsy()
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.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')
expect(settings.repositoryOwner).toBe('some-owner') expect(settings.repositoryOwner).toBe('some-owner')
expect(settings.repositoryPath).toBe(gitHubWorkspace) expect(settings.repositoryPath).toBe(gitHubWorkspace)
expect(settings.setSafeDirectory).toBe(true)
}) })
it('qualifies ref', () => { it('qualifies ref', async () => {
let originalRef = github.context.ref let originalRef = github.context.ref
try { try {
github.context.ref = 'some-unqualified-ref' github.context.ref = 'some-unqualified-ref'
const settings: IGitSourceSettings = inputHelper.getInputs() const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings).toBeTruthy() expect(settings).toBeTruthy()
expect(settings.commit).toBe('1234567890123456789012345678901234567890') expect(settings.commit).toBe('1234567890123456789012345678901234567890')
expect(settings.ref).toBe('refs/heads/some-unqualified-ref') expect(settings.ref).toBe('refs/heads/some-unqualified-ref')
@ -95,32 +106,42 @@ describe('input-helper tests', () => {
} }
}) })
it('requires qualified repo', () => { it('requires qualified repo', async () => {
inputs.repository = 'some-unqualified-repo' inputs.repository = 'some-unqualified-repo'
assert.throws(() => { try {
inputHelper.getInputs() await inputHelper.getInputs()
}, /Invalid repository 'some-unqualified-repo'/) throw 'should not reach here'
} catch (err) {
expect(`(${(err as any).message}`).toMatch(
"Invalid repository 'some-unqualified-repo'"
)
}
}) })
it('roots path', () => { it('roots path', async () => {
inputs.path = 'some-directory/some-subdirectory' inputs.path = 'some-directory/some-subdirectory'
const settings: IGitSourceSettings = inputHelper.getInputs() const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings.repositoryPath).toBe( expect(settings.repositoryPath).toBe(
path.join(gitHubWorkspace, 'some-directory', 'some-subdirectory') path.join(gitHubWorkspace, 'some-directory', 'some-subdirectory')
) )
}) })
it('sets ref to empty when explicit sha', () => { it('sets ref to empty when explicit sha', async () => {
inputs.ref = '1111111111222222222233333333334444444444' inputs.ref = '1111111111222222222233333333334444444444'
const settings: IGitSourceSettings = inputHelper.getInputs() const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings.ref).toBeFalsy() expect(settings.ref).toBeFalsy()
expect(settings.commit).toBe('1111111111222222222233333333334444444444') expect(settings.commit).toBe('1111111111222222222233333333334444444444')
}) })
it('sets sha to empty when explicit ref', () => { it('sets sha to empty when explicit ref', async () => {
inputs.ref = 'refs/heads/some-other-ref' inputs.ref = 'refs/heads/some-other-ref'
const settings: IGitSourceSettings = inputHelper.getInputs() const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings.ref).toBe('refs/heads/some-other-ref') expect(settings.ref).toBe('refs/heads/some-other-ref')
expect(settings.commit).toBeFalsy() expect(settings.commit).toBeFalsy()
}) })
it('sets workflow organization ID', async () => {
const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings.workflowOrganizationId).toBe(123456)
})
}) })

View File

@ -16,7 +16,7 @@ describe('ref-helper tests', () => {
await refHelper.getCheckoutInfo(git, 'refs/heads/my/branch', commit) await refHelper.getCheckoutInfo(git, 'refs/heads/my/branch', commit)
throw new Error('Should not reach here') throw new Error('Should not reach here')
} catch (err) { } catch (err) {
expect(err.message).toBe('Arg git cannot be empty') expect((err as any)?.message).toBe('Arg git cannot be empty')
} }
}) })
@ -25,7 +25,9 @@ describe('ref-helper tests', () => {
await refHelper.getCheckoutInfo(git, '', '') await refHelper.getCheckoutInfo(git, '', '')
throw new Error('Should not reach here') throw new Error('Should not reach here')
} catch (err) { } catch (err) {
expect(err.message).toBe('Args ref and commit cannot both be empty') expect((err as any)?.message).toBe(
'Args ref and commit cannot both be empty'
)
} }
}) })
@ -102,7 +104,7 @@ describe('ref-helper tests', () => {
await refHelper.getCheckoutInfo(git, 'my-ref', '') await refHelper.getCheckoutInfo(git, 'my-ref', '')
throw new Error('Should not reach here') throw new Error('Should not reach here')
} catch (err) { } catch (err) {
expect(err.message).toBe( expect((err as any)?.message).toBe(
"A branch or tag with the name 'my-ref' could not be found" "A branch or tag with the name 'my-ref' could not be found"
) )
} }

View File

@ -74,7 +74,7 @@ describe('retry-helper tests', () => {
throw new Error(`some error ${++attempts}`) throw new Error(`some error ${++attempts}`)
}) })
} catch (err) { } catch (err) {
error = err error = err as Error
} }
expect(error.message).toBe('some error 3') expect(error.message).toBe('some error 3')
expect(attempts).toBe(3) expect(attempts).toBe(3)

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

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

View File

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

View File

@ -53,9 +53,29 @@ 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:
description: >
Do a sparse checkout on given patterns.
Each pattern should be separated with new lines.
default: null
sparse-checkout-cone-mode:
description: >
Specifies whether to use cone-mode when doing a sparse checkout.
default: true
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
@ -68,7 +88,13 @@ inputs:
When the `ssh-key` input is not provided, SSH URLs beginning with `git@github.com:` are When the `ssh-key` input is not provided, SSH URLs beginning with `git@github.com:` are
converted to HTTPS. converted to HTTPS.
default: false default: false
set-safe-directory:
description: Add repository path as safe.directory for Git global config by running `git config --global --add safe.directory <path>`
default: true
github-server-url:
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
runs: runs:
using: node12 using: node20
main: dist/index.js main: dist/index.js
post: dist/index.js post: dist/index.js

View File

@ -181,7 +181,7 @@ GITHUB_WORKSPACE=/home/runner/work/foo/foo
RUNNER_WORKSPACE=/home/runner/work/foo RUNNER_WORKSPACE=/home/runner/work/foo
``` ```
V2 introduces a new contraint on the checkout path. The location must now be under `github.workspace`. Whereas the checkout@v1 constraint was one level up, under `runner.workspace`. V2 introduces a new constraint on the checkout path. The location must now be under `github.workspace`. Whereas the checkout@v1 constraint was one level up, under `runner.workspace`.
V2 no longer changes `github.workspace` to follow wherever the self repo is checked-out. V2 no longer changes `github.workspace` to follow wherever the self repo is checked-out.

42536
dist/index.js vendored

File diff suppressed because one or more lines are too long

18979
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": "2.0.2", "version": "4.1.0",
"description": "checkout action", "description": "checkout action",
"main": "lib/main.js", "main": "lib/main.js",
"scripts": { "scripts": {
@ -8,7 +8,9 @@
"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",
"licensed-check": "src/misc/licensed-check.sh",
"licensed-generate": "src/misc/licensed-generate.sh"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -26,27 +28,28 @@
}, },
"homepage": "https://github.com/actions/checkout#readme", "homepage": "https://github.com/actions/checkout#readme",
"dependencies": { "dependencies": {
"@actions/core": "^1.1.3", "@actions/core": "^1.10.0",
"@actions/exec": "^1.0.1", "@actions/exec": "^1.0.1",
"@actions/github": "^2.2.0", "@actions/github": "^5.0.0",
"@actions/io": "^1.0.1", "@actions/io": "^1.1.3",
"@actions/tool-cache": "^1.1.2", "@actions/tool-cache": "^1.1.2",
"uuid": "^3.3.3" "uuid": "^3.3.3"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^24.0.23", "@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/parser": "^2.8.0", "@typescript-eslint/eslint-plugin": "^5.45.0",
"@zeit/ncc": "^0.20.5", "@typescript-eslint/parser": "^5.45.0",
"eslint": "^5.16.0", "@vercel/ncc": "^0.36.1",
"eslint-plugin-github": "^2.0.0", "eslint": "^7.32.0",
"eslint-plugin-jest": "^22.21.0", "eslint-plugin-github": "^4.3.2",
"jest": "^24.9.0", "eslint-plugin-jest": "^25.7.0",
"jest-circus": "^24.9.0", "jest": "^27.3.0",
"jest-circus": "^27.3.0",
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",
"prettier": "^1.19.1", "prettier": "^1.19.1",
"ts-jest": "^24.2.0", "ts-jest": "^27.0.7",
"typescript": "^3.6.4" "typescript": "^4.4.4"
} }
} }

View File

@ -9,7 +9,7 @@ export function directoryExistsSync(path: string, required?: boolean): boolean {
try { try {
stats = fs.statSync(path) stats = fs.statSync(path)
} catch (error) { } catch (error) {
if (error.code === 'ENOENT') { if ((error as any)?.code === 'ENOENT') {
if (!required) { if (!required) {
return false return false
} }
@ -18,7 +18,8 @@ export function directoryExistsSync(path: string, required?: boolean): boolean {
} }
throw new Error( throw new Error(
`Encountered an error when checking whether path '${path}' exists: ${error.message}` `Encountered an error when checking whether path '${path}' exists: ${(error as any)
?.message ?? error}`
) )
} }
@ -39,12 +40,13 @@ export function existsSync(path: string): boolean {
try { try {
fs.statSync(path) fs.statSync(path)
} catch (error) { } catch (error) {
if (error.code === 'ENOENT') { if ((error as any)?.code === 'ENOENT') {
return false return false
} }
throw new Error( throw new Error(
`Encountered an error when checking whether path '${path}' exists: ${error.message}` `Encountered an error when checking whether path '${path}' exists: ${(error as any)
?.message ?? error}`
) )
} }
@ -60,12 +62,13 @@ export function fileExistsSync(path: string): boolean {
try { try {
stats = fs.statSync(path) stats = fs.statSync(path)
} catch (error) { } catch (error) {
if (error.code === 'ENOENT') { if ((error as any)?.code === 'ENOENT') {
return false return false
} }
throw new Error( throw new Error(
`Encountered an error when checking whether path '${path}' exists: ${error.message}` `Encountered an error when checking whether path '${path}' exists: ${(error as any)
?.message ?? error}`
) )
} }

View File

@ -19,8 +19,9 @@ export interface IGitAuthHelper {
configureAuth(): Promise<void> configureAuth(): Promise<void>
configureGlobalAuth(): Promise<void> configureGlobalAuth(): Promise<void>
configureSubmoduleAuth(): Promise<void> configureSubmoduleAuth(): Promise<void>
configureTempGlobalConfig(): Promise<string>
removeAuth(): Promise<void> removeAuth(): Promise<void>
removeGlobalAuth(): Promise<void> removeGlobalConfig(): Promise<void>
} }
export function createAuthHelper( export function createAuthHelper(
@ -37,7 +38,7 @@ class GitAuthHelper {
private readonly tokenConfigValue: string private readonly tokenConfigValue: string
private readonly tokenPlaceholderConfigValue: string private readonly tokenPlaceholderConfigValue: string
private readonly insteadOfKey: string private readonly insteadOfKey: string
private readonly insteadOfValue: string private readonly insteadOfValues: string[] = []
private sshCommand = '' private sshCommand = ''
private sshKeyPath = '' private sshKeyPath = ''
private sshKnownHostsPath = '' private sshKnownHostsPath = ''
@ -45,13 +46,13 @@ class GitAuthHelper {
constructor( constructor(
gitCommandManager: IGitCommandManager, gitCommandManager: IGitCommandManager,
gitSourceSettings?: IGitSourceSettings gitSourceSettings: IGitSourceSettings | undefined
) { ) {
this.git = gitCommandManager this.git = gitCommandManager
this.settings = gitSourceSettings || (({} as unknown) as IGitSourceSettings) this.settings = gitSourceSettings || (({} as unknown) as IGitSourceSettings)
// Token auth header // Token auth header
const serverUrl = urlHelper.getServerUrl() const serverUrl = urlHelper.getServerUrl(this.settings.githubServerUrl)
this.tokenConfigKey = `http.${serverUrl.origin}/.extraheader` // "origin" is SCHEME://HOSTNAME[:PORT] 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}`,
@ -63,7 +64,12 @@ class GitAuthHelper {
// Instead of SSH URL // Instead of SSH URL
this.insteadOfKey = `url.${serverUrl.origin}/.insteadOf` // "origin" is SCHEME://HOSTNAME[:PORT] this.insteadOfKey = `url.${serverUrl.origin}/.insteadOf` // "origin" is SCHEME://HOSTNAME[:PORT]
this.insteadOfValue = `git@${serverUrl.hostname}:` this.insteadOfValues.push(`git@${serverUrl.hostname}:`)
if (this.settings.workflowOrganizationId) {
this.insteadOfValues.push(
`org-${this.settings.workflowOrganizationId}@github.com:`
)
}
} }
async configureAuth(): Promise<void> { async configureAuth(): Promise<void> {
@ -75,7 +81,11 @@ class GitAuthHelper {
await this.configureToken() await this.configureToken()
} }
async configureGlobalAuth(): Promise<void> { async configureTempGlobalConfig(): Promise<string> {
// Already setup global config
if (this.temporaryHomePath?.length > 0) {
return path.join(this.temporaryHomePath, '.gitconfig')
}
// Create a temp home directory // Create a temp home directory
const runnerTemp = process.env['RUNNER_TEMP'] || '' const runnerTemp = process.env['RUNNER_TEMP'] || ''
assert.ok(runnerTemp, 'RUNNER_TEMP is not defined') assert.ok(runnerTemp, 'RUNNER_TEMP is not defined')
@ -94,7 +104,7 @@ class GitAuthHelper {
await fs.promises.stat(gitConfigPath) await fs.promises.stat(gitConfigPath)
configExists = true configExists = true
} catch (err) { } catch (err) {
if (err.code !== 'ENOENT') { if ((err as any)?.code !== 'ENOENT') {
throw err throw err
} }
} }
@ -105,20 +115,28 @@ class GitAuthHelper {
await fs.promises.writeFile(newGitConfigPath, '') await fs.promises.writeFile(newGitConfigPath, '')
} }
try {
// Override HOME // Override HOME
core.info( core.info(
`Temporarily overriding HOME='${this.temporaryHomePath}' before making global git config changes` `Temporarily overriding HOME='${this.temporaryHomePath}' before making global git config changes`
) )
this.git.setEnvironmentVariable('HOME', this.temporaryHomePath) this.git.setEnvironmentVariable('HOME', this.temporaryHomePath)
return newGitConfigPath
}
async configureGlobalAuth(): Promise<void> {
// 'configureTempGlobalConfig' noops if already set, just returns the path
const newGitConfigPath = await this.configureTempGlobalConfig()
try {
// Configure the token // Configure the token
await this.configureToken(newGitConfigPath, true) 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) {
await this.git.config(this.insteadOfKey, this.insteadOfValue, true) for (const insteadOfValue of this.insteadOfValues) {
await this.git.config(this.insteadOfKey, insteadOfValue, true, true)
}
} }
} catch (err) { } catch (err) {
// Unset in case somehow written to the real global config // Unset in case somehow written to the real global config
@ -139,7 +157,8 @@ class GitAuthHelper {
// by process creation audit events, which are commonly logged. For more information, // 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 // 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(
`git config --local '${this.tokenConfigKey}' '${this.tokenPlaceholderConfigValue}' && git config --local --show-origin --name-only --get-regexp remote.origin.url`, // 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 '${this.tokenConfigKey}' '${this.tokenPlaceholderConfigValue}' && git config --local --show-origin --name-only --get-regexp remote.origin.url"`,
this.settings.nestedSubmodules this.settings.nestedSubmodules
) )
@ -159,24 +178,28 @@ class GitAuthHelper {
) )
} else { } else {
// Configure HTTPS instead of SSH // Configure HTTPS instead of SSH
for (const insteadOfValue of this.insteadOfValues) {
await this.git.submoduleForeach( await this.git.submoduleForeach(
`git config --local '${this.insteadOfKey}' '${this.insteadOfValue}'`, `git config --local --add '${this.insteadOfKey}' '${insteadOfValue}'`,
this.settings.nestedSubmodules this.settings.nestedSubmodules
) )
} }
} }
} }
}
async removeAuth(): Promise<void> { async removeAuth(): Promise<void> {
await this.removeSsh() await this.removeSsh()
await this.removeToken() await this.removeToken()
} }
async removeGlobalAuth(): Promise<void> { async removeGlobalConfig(): Promise<void> {
if (this.temporaryHomePath?.length > 0) {
core.debug(`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)
} }
}
private async configureSsh(): Promise<void> { private async configureSsh(): Promise<void> {
if (!this.settings.sshKey) { if (!this.settings.sshKey) {
@ -213,7 +236,7 @@ class GitAuthHelper {
await fs.promises.readFile(userKnownHostsPath) await fs.promises.readFile(userKnownHostsPath)
).toString() ).toString()
} catch (err) { } catch (err) {
if (err.code !== 'ENOENT') { if ((err as any)?.code !== 'ENOENT') {
throw err throw err
} }
} }
@ -224,7 +247,7 @@ class GitAuthHelper {
if (this.settings.sshKnownHosts) { if (this.settings.sshKnownHosts) {
knownHosts += `# Begin from input known hosts\n${this.settings.sshKnownHosts}\n# end from input known hosts\n` knownHosts += `# Begin from input known hosts\n${this.settings.sshKnownHosts}\n# end from input known hosts\n`
} }
knownHosts += `# Begin implicitly added github.com\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n# End implicitly added github.com\n` knownHosts += `# Begin implicitly added github.com\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=\n# End implicitly added github.com\n`
this.sshKnownHostsPath = path.join(runnerTemp, `${uniqueId}_known_hosts`) this.sshKnownHostsPath = path.join(runnerTemp, `${uniqueId}_known_hosts`)
stateHelper.setSshKnownHostsPath(this.sshKnownHostsPath) stateHelper.setSshKnownHostsPath(this.sshKnownHostsPath)
await fs.promises.writeFile(this.sshKnownHostsPath, knownHosts) await fs.promises.writeFile(this.sshKnownHostsPath, knownHosts)
@ -302,7 +325,7 @@ class GitAuthHelper {
try { try {
await io.rmRF(keyPath) await io.rmRF(keyPath)
} catch (err) { } catch (err) {
core.debug(err.message) core.debug(`${(err as any)?.message ?? err}`)
core.warning(`Failed to remove SSH key '${keyPath}'`) core.warning(`Failed to remove SSH key '${keyPath}'`)
} }
} }
@ -343,7 +366,8 @@ class GitAuthHelper {
const pattern = regexpHelper.escape(configKey) const pattern = regexpHelper.escape(configKey)
await this.git.submoduleForeach( await this.git.submoduleForeach(
`git config --local --name-only --get-regexp '${pattern}' && git config --local --unset-all '${configKey}' || :`, // 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 --name-only --get-regexp '${pattern}' && git config --local --unset-all '${configKey}' || :"`,
true true
) )
} }

View File

@ -1,5 +1,6 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as exec from '@actions/exec' import * as exec from '@actions/exec'
import * as fs from 'fs'
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'
@ -16,15 +17,26 @@ export interface IGitCommandManager {
branchDelete(remote: boolean, branch: string): Promise<void> branchDelete(remote: boolean, branch: string): Promise<void>
branchExists(remote: boolean, pattern: string): Promise<boolean> branchExists(remote: boolean, pattern: string): Promise<boolean>
branchList(remote: boolean): Promise<string[]> branchList(remote: boolean): Promise<string[]>
sparseCheckout(sparseCheckout: string[]): Promise<void>
sparseCheckoutNonConeMode(sparseCheckout: string[]): Promise<void>
checkout(ref: string, startPoint: string): Promise<void> checkout(ref: string, startPoint: string): Promise<void>
checkoutDetach(): Promise<void> checkoutDetach(): Promise<void>
config( config(
configKey: string, configKey: string,
configValue: string, configValue: string,
globalConfig?: boolean globalConfig?: boolean,
add?: boolean
): Promise<void> ): Promise<void>
configExists(configKey: string, globalConfig?: boolean): Promise<boolean> configExists(configKey: string, globalConfig?: boolean): Promise<boolean>
fetch(refSpec: string[], fetchDepth?: number): Promise<void> fetch(
refSpec: string[],
options: {
filter?: string
fetchDepth?: number
fetchTags?: boolean
showProgress?: boolean
}
): Promise<void>
getDefaultBranch(repositoryUrl: string): Promise<string> getDefaultBranch(repositoryUrl: string): Promise<string>
getWorkingDirectory(): string getWorkingDirectory(): string
init(): Promise<void> init(): Promise<void>
@ -40,6 +52,7 @@ export interface IGitCommandManager {
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>
submoduleStatus(): Promise<boolean>
tagExists(pattern: string): Promise<boolean> tagExists(pattern: string): Promise<boolean>
tryClean(): Promise<boolean> tryClean(): Promise<boolean>
tryConfigUnset(configKey: string, globalConfig?: boolean): Promise<boolean> tryConfigUnset(configKey: string, globalConfig?: boolean): Promise<boolean>
@ -50,9 +63,14 @@ export interface IGitCommandManager {
export async function createCommandManager( export async function createCommandManager(
workingDirectory: string, workingDirectory: string,
lfs: boolean lfs: boolean,
doSparseCheckout: boolean
): Promise<IGitCommandManager> { ): Promise<IGitCommandManager> {
return await GitCommandManager.createCommandManager(workingDirectory, lfs) return await GitCommandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
} }
class GitCommandManager { class GitCommandManager {
@ -62,6 +80,7 @@ class GitCommandManager {
} }
private gitPath = '' private gitPath = ''
private lfs = false private lfs = false
private doSparseCheckout = false
private workingDirectory = '' private workingDirectory = ''
// Private constructor; use createCommandManager() // Private constructor; use createCommandManager()
@ -93,8 +112,11 @@ class GitCommandManager {
// Note, this implementation uses "rev-parse --symbolic-full-name" because the output from // Note, this implementation uses "rev-parse --symbolic-full-name" because the output from
// "branch --list" is more difficult when in a detached HEAD state. // "branch --list" is more difficult when in a detached HEAD state.
// Note, this implementation uses "rev-parse --symbolic-full-name" because there is a bug
// in Git 2.18 that causes "rev-parse --symbolic" to output symbolic full names. // TODO(https://github.com/actions/checkout/issues/786): this implementation uses
// "rev-parse --symbolic-full-name" because there is a bug
// in Git 2.18 that causes "rev-parse --symbolic" to output symbolic full names. When
// 2.18 is no longer supported, we can switch back to --symbolic.
const args = ['rev-parse', '--symbolic-full-name'] const args = ['rev-parse', '--symbolic-full-name']
if (remote) { if (remote) {
@ -103,24 +125,73 @@ class GitCommandManager {
args.push('--branches') args.push('--branches')
} }
const output = await this.execGit(args) const stderr: string[] = []
const errline: string[] = []
const stdout: string[] = []
const stdline: string[] = []
for (let branch of output.stdout.trim().split('\n')) { const listeners = {
stderr: (data: Buffer) => {
stderr.push(data.toString())
},
errline: (data: Buffer) => {
errline.push(data.toString())
},
stdout: (data: Buffer) => {
stdout.push(data.toString())
},
stdline: (data: Buffer) => {
stdline.push(data.toString())
}
}
// Suppress the output in order to avoid flooding annotations with innocuous errors.
await this.execGit(args, false, true, listeners)
core.debug(`stderr callback is: ${stderr}`)
core.debug(`errline callback is: ${errline}`)
core.debug(`stdout callback is: ${stdout}`)
core.debug(`stdline callback is: ${stdline}`)
for (let branch of stdline) {
branch = branch.trim() branch = branch.trim()
if (branch) { if (!branch) {
continue
}
if (branch.startsWith('refs/heads/')) { if (branch.startsWith('refs/heads/')) {
branch = branch.substr('refs/heads/'.length) branch = branch.substring('refs/heads/'.length)
} else if (branch.startsWith('refs/remotes/')) { } else if (branch.startsWith('refs/remotes/')) {
branch = branch.substr('refs/remotes/'.length) branch = branch.substring('refs/remotes/'.length)
} }
result.push(branch) result.push(branch)
} }
}
return result return result
} }
async sparseCheckout(sparseCheckout: string[]): Promise<void> {
await this.execGit(['sparse-checkout', 'set', ...sparseCheckout])
}
async sparseCheckoutNonConeMode(sparseCheckout: string[]): Promise<void> {
await this.execGit(['config', 'core.sparseCheckout', 'true'])
const output = await this.execGit([
'rev-parse',
'--git-path',
'info/sparse-checkout'
])
const sparseCheckoutPath = path.join(
this.workingDirectory,
output.stdout.trimRight()
)
await fs.promises.appendFile(
sparseCheckoutPath,
`\n${sparseCheckout.join('\n')}\n`
)
}
async checkout(ref: string, startPoint: string): Promise<void> { async checkout(ref: string, startPoint: string): Promise<void> {
const args = ['checkout', '--progress', '--force'] const args = ['checkout', '--progress', '--force']
if (startPoint) { if (startPoint) {
@ -140,14 +211,15 @@ class GitCommandManager {
async config( async config(
configKey: string, configKey: string,
configValue: string, configValue: string,
globalConfig?: boolean globalConfig?: boolean,
add?: boolean
): Promise<void> { ): Promise<void> {
await this.execGit([ const args: string[] = ['config', globalConfig ? '--global' : '--local']
'config', if (add) {
globalConfig ? '--global' : '--local', args.push('--add')
configKey, }
configValue args.push(...[configKey, configValue])
]) await this.execGit(args)
} }
async configExists( async configExists(
@ -168,15 +240,31 @@ class GitCommandManager {
return output.exitCode === 0 return output.exitCode === 0
} }
async fetch(refSpec: string[], fetchDepth?: number): Promise<void> { async fetch(
refSpec: string[],
options: {
filter?: string
fetchDepth?: number
fetchTags?: boolean
showProgress?: boolean
}
): 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 (fetchDepth && fetchDepth > 0) { if (options.showProgress) {
args.push(`--depth=${fetchDepth}`) args.push('--progress')
}
if (options.filter) {
args.push(`--filter=${options.filter}`)
}
if (options.fetchDepth && options.fetchDepth > 0) {
args.push(`--depth=${options.fetchDepth}`)
} else if ( } else if (
fshelper.fileExistsSync( fshelper.fileExistsSync(
path.join(this.workingDirectory, '.git', 'shallow') path.join(this.workingDirectory, '.git', 'shallow')
@ -255,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
} }
@ -324,6 +412,12 @@ class GitCommandManager {
await this.execGit(args) await this.execGit(args)
} }
async submoduleStatus(): Promise<boolean> {
const output = await this.execGit(['submodule', 'status'], true)
core.debug(output.stdout)
return output.exitCode === 0
}
async tagExists(pattern: string): Promise<boolean> { async tagExists(pattern: string): Promise<boolean> {
const output = await this.execGit(['tag', '--list', pattern]) const output = await this.execGit(['tag', '--list', pattern])
return !!output.stdout.trim() return !!output.stdout.trim()
@ -383,17 +477,23 @@ class GitCommandManager {
static async createCommandManager( static async createCommandManager(
workingDirectory: string, workingDirectory: string,
lfs: boolean lfs: boolean,
doSparseCheckout: boolean
): Promise<GitCommandManager> { ): Promise<GitCommandManager> {
const result = new GitCommandManager() const result = new GitCommandManager()
await result.initializeCommandManager(workingDirectory, lfs) await result.initializeCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
return result return result
} }
private async execGit( private async execGit(
args: string[], args: string[],
allowAllExitCodes = false, allowAllExitCodes = false,
silent = false silent = false,
customListeners = {}
): Promise<GitOutput> { ): Promise<GitOutput> {
fshelper.directoryExistsSync(this.workingDirectory, true) fshelper.directoryExistsSync(this.workingDirectory, true)
@ -407,28 +507,36 @@ class GitCommandManager {
env[key] = this.gitEnv[key] env[key] = this.gitEnv[key]
} }
const stdout: string[] = [] const defaultListener = {
stdout: (data: Buffer) => {
stdout.push(data.toString())
}
}
const mergedListeners = {...defaultListener, ...customListeners}
const stdout: string[] = []
const options = { const options = {
cwd: this.workingDirectory, cwd: this.workingDirectory,
env, env,
silent, silent,
ignoreReturnCode: allowAllExitCodes, ignoreReturnCode: allowAllExitCodes,
listeners: { listeners: mergedListeners
stdout: (data: Buffer) => {
stdout.push(data.toString())
}
}
} }
result.exitCode = await exec.exec(`"${this.gitPath}"`, args, options) result.exitCode = await exec.exec(`"${this.gitPath}"`, args, options)
result.stdout = stdout.join('') result.stdout = stdout.join('')
core.debug(result.exitCode.toString())
core.debug(result.stdout)
return result return result
} }
private async initializeCommandManager( private async initializeCommandManager(
workingDirectory: string, workingDirectory: string,
lfs: boolean lfs: boolean,
doSparseCheckout: boolean
): Promise<void> { ): Promise<void> {
this.workingDirectory = workingDirectory this.workingDirectory = workingDirectory
@ -491,6 +599,16 @@ class GitCommandManager {
} }
} }
this.doSparseCheckout = doSparseCheckout
if (this.doSparseCheckout) {
// The `git sparse-checkout` command was introduced in Git v2.25.0
const minimumGitSparseCheckoutVersion = new GitVersion('2.25')
if (!gitVersion.checkMinimum(minimumGitSparseCheckoutVersion)) {
throw new Error(
`Minimum Git version required for sparse checkout is ${minimumGitSparseCheckoutVersion}. Your git ('${this.gitPath}') is ${gitVersion}`
)
}
}
// Set the user agent // Set the user agent
const gitHttpUserAgent = `git/${gitVersion} (github-actions-checkout)` const gitHttpUserAgent = `git/${gitVersion} (github-actions-checkout)`
core.debug(`Set git useragent to: ${gitHttpUserAgent}`) core.debug(`Set git useragent to: ${gitHttpUserAgent}`)

View File

@ -39,7 +39,9 @@ export async function prepareExistingDirectory(
try { try {
await io.rmRF(lockPath) await io.rmRF(lockPath)
} catch (error) { } catch (error) {
core.debug(`Unable to delete '${lockPath}'. ${error.message}`) core.debug(
`Unable to delete '${lockPath}'. ${(error as any)?.message ?? error}`
)
} }
} }
@ -79,12 +81,18 @@ export async function prepareExistingDirectory(
} }
core.endGroup() core.endGroup()
// Check for submodules and delete any existing files if submodules are present
if (!(await git.submoduleStatus())) {
remove = true
core.info('Bad Submodules found, removing existing files')
}
// Clean // Clean
if (clean) { if (clean) {
core.startGroup('Cleaning the repository') 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 further investigation, manually run 'git clean -ffdx' on the directory '${repositoryPath}'.`
) )
remove = true remove = true
} else if (!(await git.tryReset())) { } else if (!(await git.tryReset())) {

View File

@ -36,6 +36,30 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
const git = await getGitCommandManager(settings) const git = await getGitCommandManager(settings)
core.endGroup() core.endGroup()
let authHelper: gitAuthHelper.IGitAuthHelper | null = null
try {
if (git) {
authHelper = gitAuthHelper.createAuthHelper(git, settings)
if (settings.setSafeDirectory) {
// Setup the repository path as a safe directory, so if we pass this into a container job with a different user it doesn't fail
// Otherwise all git commands we run in a container fail
await authHelper.configureTempGlobalConfig()
core.info(
`Adding repository directory to the temporary git global config as a safe directory`
)
await git
.config('safe.directory', settings.repositoryPath, true, true)
.catch(error => {
core.info(
`Failed to initialize safe directory with error: ${error}`
)
})
stateHelper.setSafeDirectory()
}
}
// Prepare existing directory, otherwise recreate // Prepare existing directory, otherwise recreate
if (isExisting) { if (isExisting) {
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
@ -69,7 +93,8 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
settings.repositoryName, settings.repositoryName,
settings.ref, settings.ref,
settings.commit, settings.commit,
settings.repositoryPath settings.repositoryPath,
settings.githubServerUrl
) )
return return
} }
@ -96,8 +121,10 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
} }
core.endGroup() core.endGroup()
const authHelper = gitAuthHelper.createAuthHelper(git, settings) // If we didn't initialize it above, do it now
try { if (!authHelper) {
authHelper = gitAuthHelper.createAuthHelper(git, settings)
}
// Configure auth // Configure auth
core.startGroup('Setting up auth') core.startGroup('Setting up auth')
await authHelper.configureAuth() await authHelper.configureAuth()
@ -112,7 +139,8 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
settings.ref = await githubApiHelper.getDefaultBranch( settings.ref = await githubApiHelper.getDefaultBranch(
settings.authToken, settings.authToken,
settings.repositoryOwner, settings.repositoryOwner,
settings.repositoryName settings.repositoryName,
settings.githubServerUrl
) )
} }
core.endGroup() core.endGroup()
@ -125,23 +153,38 @@ 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
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(
settings.ref, settings.ref,
settings.commit settings.commit
) )
await git.fetch(refSpec) await git.fetch(refSpec, fetchOptions)
// When all history is fetched, the ref we're interested in may have moved to a different // 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. // commit (push or force push). If so, fetch again with a targeted refspec.
if (!(await refHelper.testRef(git, settings.ref, settings.commit))) { if (!(await refHelper.testRef(git, settings.ref, settings.commit))) {
refSpec = refHelper.getRefSpec(settings.ref, settings.commit) refSpec = refHelper.getRefSpec(settings.ref, settings.commit)
await git.fetch(refSpec) await git.fetch(refSpec, fetchOptions)
} }
} else { } else {
fetchOptions.fetchDepth = settings.fetchDepth
fetchOptions.fetchTags = settings.fetchTags
const refSpec = refHelper.getRefSpec(settings.ref, settings.commit) const refSpec = refHelper.getRefSpec(settings.ref, settings.commit)
await git.fetch(refSpec, settings.fetchDepth) await git.fetch(refSpec, fetchOptions)
} }
core.endGroup() core.endGroup()
@ -157,12 +200,24 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
// 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) { // For sparse checkouts, let `checkout` fetch the needed objects lazily.
if (settings.lfs && !settings.sparseCheckout) {
core.startGroup('Fetching LFS objects') core.startGroup('Fetching LFS objects')
await git.lfsFetch(checkoutInfo.startPoint || checkoutInfo.ref) await git.lfsFetch(checkoutInfo.startPoint || checkoutInfo.ref)
core.endGroup() core.endGroup()
} }
// Sparse checkout
if (settings.sparseCheckout) {
core.startGroup('Setting up sparse checkout')
if (settings.sparseCheckoutConeMode) {
await git.sparseCheckout(settings.sparseCheckout)
} else {
await git.sparseCheckoutNonConeMode(settings.sparseCheckout)
}
core.endGroup()
}
// Checkout // Checkout
core.startGroup('Checking out the ref') core.startGroup('Checking out the ref')
await git.checkout(checkoutInfo.ref, checkoutInfo.startPoint) await git.checkout(checkoutInfo.ref, checkoutInfo.startPoint)
@ -170,7 +225,6 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
// Submodules // Submodules
if (settings.submodules) { if (settings.submodules) {
try {
// Temporarily override global config // Temporarily override global config
core.startGroup('Setting up auth for fetching submodules') core.startGroup('Setting up auth for fetching submodules')
await authHelper.configureGlobalAuth() await authHelper.configureGlobalAuth()
@ -179,10 +233,7 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
// Checkout submodules // Checkout submodules
core.startGroup('Fetching submodules') core.startGroup('Fetching submodules')
await git.submoduleSync(settings.nestedSubmodules) await git.submoduleSync(settings.nestedSubmodules)
await git.submoduleUpdate( await git.submoduleUpdate(settings.fetchDepth, settings.nestedSubmodules)
settings.fetchDepth,
settings.nestedSubmodules
)
await git.submoduleForeach( await git.submoduleForeach(
'git config --local gc.auto 0', 'git config --local gc.auto 0',
settings.nestedSubmodules settings.nestedSubmodules
@ -195,10 +246,6 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
await authHelper.configureSubmoduleAuth() await authHelper.configureSubmoduleAuth()
core.endGroup() core.endGroup()
} }
} finally {
// Remove temporary global config override
await authHelper.removeGlobalAuth()
}
} }
// Get commit information // Get commit information
@ -214,15 +261,19 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
settings.repositoryOwner, settings.repositoryOwner,
settings.repositoryName, settings.repositoryName,
settings.ref, settings.ref,
settings.commit settings.commit,
settings.githubServerUrl
) )
} finally { } finally {
// Remove auth // Remove auth
if (authHelper) {
if (!settings.persistCredentials) { if (!settings.persistCredentials) {
core.startGroup('Removing auth') core.startGroup('Removing auth')
await authHelper.removeAuth() await authHelper.removeAuth()
core.endGroup() core.endGroup()
} }
authHelper.removeGlobalConfig()
}
} }
} }
@ -237,14 +288,37 @@ export async function cleanup(repositoryPath: string): Promise<void> {
let git: IGitCommandManager let git: IGitCommandManager
try { try {
git = await gitCommandManager.createCommandManager(repositoryPath, false) git = await gitCommandManager.createCommandManager(
repositoryPath,
false,
false
)
} catch { } catch {
return return
} }
// Remove auth // Remove auth
const authHelper = gitAuthHelper.createAuthHelper(git) const authHelper = gitAuthHelper.createAuthHelper(git)
try {
if (stateHelper.PostSetSafeDirectory) {
// Setup the repository path as a safe directory, so if we pass this into a container job with a different user it doesn't fail
// Otherwise all git commands we run in a container fail
await authHelper.configureTempGlobalConfig()
core.info(
`Adding repository directory to the temporary git global config as a safe directory`
)
await git
.config('safe.directory', repositoryPath, true, true)
.catch(error => {
core.info(`Failed to initialize safe directory with error: ${error}`)
})
}
await authHelper.removeAuth() await authHelper.removeAuth()
} finally {
await authHelper.removeGlobalConfig()
}
} }
async function getGitCommandManager( async function getGitCommandManager(
@ -254,7 +328,8 @@ async function getGitCommandManager(
try { try {
return await gitCommandManager.createCommandManager( return await gitCommandManager.createCommandManager(
settings.repositoryPath, settings.repositoryPath,
settings.lfs settings.lfs,
settings.sparseCheckout != null
) )
} catch (err) { } catch (err) {
// Git is required for LFS // Git is required for LFS

View File

@ -29,11 +29,36 @@ 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
*/
sparseCheckout: string[]
/**
* Indicates whether to use cone mode in the sparse checkout (if any)
*/
sparseCheckoutConeMode: boolean
/** /**
* The depth when fetching * The depth when fetching
*/ */
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
*/ */
@ -73,4 +98,19 @@ export interface IGitSourceSettings {
* Indicates whether to persist the credentials on disk to enable scripting authenticated git commands * Indicates whether to persist the credentials on disk to enable scripting authenticated git commands
*/ */
persistCredentials: boolean persistCredentials: boolean
/**
* Organization ID for the currently running workflow (used for auth settings)
*/
workflowOrganizationId: number | undefined
/**
* Indicates whether to add repositoryPath as safe.directory in git global config
*/
setSafeDirectory: boolean
/**
* User override on the GitHub Server/Host URL that hosts the repository to be cloned
*/
githubServerUrl: string | undefined
} }

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 {Octokit} from '@octokit/rest' import {getServerApiUrl} from './url-helper'
const IS_WINDOWS = process.platform === 'win32' const IS_WINDOWS = process.platform === 'win32'
@ -17,18 +17,19 @@ export async function downloadRepository(
repo: string, repo: string,
ref: string, ref: string,
commit: string, commit: string,
repositoryPath: string repositoryPath: string,
baseUrl?: string
): Promise<void> { ): Promise<void> {
// Determine the default branch // Determine the default branch
if (!ref && !commit) { if (!ref && !commit) {
core.info('Determining the default branch') core.info('Determining the default branch')
ref = await getDefaultBranch(authToken, owner, repo) ref = await getDefaultBranch(authToken, owner, repo, baseUrl)
} }
// 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')
return await downloadArchive(authToken, owner, repo, ref, commit) return await downloadArchive(authToken, owner, repo, ref, commit, baseUrl)
}) })
// Write archive to disk // Write archive to disk
@ -79,20 +80,26 @@ export async function downloadRepository(
export async function getDefaultBranch( export async function getDefaultBranch(
authToken: string, authToken: string,
owner: string, owner: string,
repo: string repo: string,
baseUrl?: string
): Promise<string> { ): Promise<string> {
return await retryHelper.execute(async () => { return await retryHelper.execute(async () => {
core.info('Retrieving the default branch name') core.info('Retrieving the default branch name')
const octokit = new github.GitHub(authToken) const octokit = github.getOctokit(authToken, {
baseUrl: getServerApiUrl(baseUrl)
})
let result: string let result: string
try { try {
// Get the default branch from the repo info // Get the default branch from the repo info
const response = await octokit.repos.get({owner, repo}) const response = await octokit.rest.repos.get({owner, repo})
result = response.data.default_branch result = response.data.default_branch
assert.ok(result, 'default_branch cannot be empty') assert.ok(result, 'default_branch cannot be empty')
} catch (err) { } catch (err) {
// Handle .wiki repo // Handle .wiki repo
if (err['status'] === 404 && repo.toUpperCase().endsWith('.WIKI')) { if (
(err as any)?.status === 404 &&
repo.toUpperCase().endsWith('.WIKI')
) {
result = 'master' result = 'master'
} }
// Otherwise error // Otherwise error
@ -118,21 +125,19 @@ async function downloadArchive(
owner: string, owner: string,
repo: string, repo: string,
ref: string, ref: string,
commit: string commit: string,
baseUrl?: string
): Promise<Buffer> { ): Promise<Buffer> {
const octokit = new github.GitHub(authToken) const octokit = github.getOctokit(authToken, {
const params: Octokit.ReposGetArchiveLinkParams = { baseUrl: getServerApiUrl(baseUrl)
})
const download = IS_WINDOWS
? octokit.rest.repos.downloadZipballArchive
: octokit.rest.repos.downloadTarballArchive
const response = await download({
owner: owner, owner: owner,
repo: repo, repo: repo,
archive_format: IS_WINDOWS ? 'zipball' : 'tarball',
ref: commit || ref ref: commit || ref
} })
const response = await octokit.repos.getArchiveLink(params) return Buffer.from(response.data as ArrayBuffer) // response.data is ArrayBuffer
if (response.status != 200) {
throw new Error(
`Unexpected response from GitHub API. Status: ${response.status}, Data: ${response.data}`
)
}
return Buffer.from(response.data) // response.data is ArrayBuffer
} }

View File

@ -2,9 +2,10 @@ import * as core from '@actions/core'
import * as fsHelper from './fs-helper' import * as fsHelper from './fs-helper'
import * as github from '@actions/github' import * as github from '@actions/github'
import * as path from 'path' import * as path from 'path'
import * as workflowContextHelper from './workflow-context-helper'
import {IGitSourceSettings} from './git-source-settings' import {IGitSourceSettings} from './git-source-settings'
export function getInputs(): IGitSourceSettings { export async function getInputs(): Promise<IGitSourceSettings> {
const result = ({} as unknown) as IGitSourceSettings const result = ({} as unknown) as IGitSourceSettings
// GitHub workspace // GitHub workspace
@ -81,6 +82,25 @@ export function getInputs(): 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
const sparseCheckout = core.getMultilineInput('sparse-checkout')
if (sparseCheckout.length) {
result.sparseCheckout = sparseCheckout
core.debug(`sparse checkout = ${result.sparseCheckout}`)
}
result.sparseCheckoutConeMode =
(core.getInput('sparse-checkout-cone-mode') || 'true').toUpperCase() ===
'TRUE'
// Fetch depth // Fetch depth
result.fetchDepth = Math.floor(Number(core.getInput('fetch-depth') || '1')) result.fetchDepth = Math.floor(Number(core.getInput('fetch-depth') || '1'))
if (isNaN(result.fetchDepth) || result.fetchDepth < 0) { if (isNaN(result.fetchDepth) || result.fetchDepth < 0) {
@ -88,6 +108,16 @@ export function getInputs(): 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}`)
@ -118,5 +148,16 @@ export function getInputs(): IGitSourceSettings {
result.persistCredentials = result.persistCredentials =
(core.getInput('persist-credentials') || 'false').toUpperCase() === 'TRUE' (core.getInput('persist-credentials') || 'false').toUpperCase() === 'TRUE'
// Workflow organization ID
result.workflowOrganizationId = await workflowContextHelper.getOrganizationId()
// Set safe.directory in git global config.
result.setSafeDirectory =
(core.getInput('set-safe-directory') || 'true').toUpperCase() === 'TRUE'
// Determine the GitHub URL that the repository is being hosted from
result.githubServerUrl = core.getInput('github-server-url')
core.debug(`GitHub Host URL = ${result.githubServerUrl}`)
return result return result
} }

View File

@ -7,7 +7,7 @@ import * as stateHelper from './state-helper'
async function run(): Promise<void> { async function run(): Promise<void> {
try { try {
const sourceSettings = inputHelper.getInputs() const sourceSettings = await inputHelper.getInputs()
try { try {
// Register problem matcher // Register problem matcher
@ -24,7 +24,7 @@ async function run(): Promise<void> {
coreCommand.issueCommand('remove-matcher', {owner: 'checkout-git'}, '') coreCommand.issueCommand('remove-matcher', {owner: 'checkout-git'}, '')
} }
} catch (error) { } catch (error) {
core.setFailed(error.message) core.setFailed(`${(error as any)?.message ?? error}`)
} }
} }
@ -32,7 +32,7 @@ async function cleanup(): Promise<void> {
try { try {
await gitSourceProvider.cleanup(stateHelper.RepositoryPath) await gitSourceProvider.cleanup(stateHelper.RepositoryPath)
} catch (error) { } catch (error) {
core.warning(error.message) core.warning(`${(error as any)?.message ?? error}`)
} }
} }

View File

@ -10,10 +10,10 @@ import * as yaml from 'js-yaml'
function updateUsage( function updateUsage(
actionReference: string, actionReference: string,
actionYamlPath: string = 'action.yml', actionYamlPath = 'action.yml',
readmePath: string = 'README.md', readmePath = 'README.md',
startToken: string = '<!-- start usage -->', startToken = '<!-- start usage -->',
endToken: string = '<!-- end usage -->' endToken = '<!-- end usage -->'
): void { ): void {
if (!actionReference) { if (!actionReference) {
throw new Error('Parameter actionReference must not be empty') throw new Error('Parameter actionReference must not be empty')
@ -120,7 +120,7 @@ function updateUsage(
} }
updateUsage( updateUsage(
'actions/checkout@v2', 'actions/checkout@v4',
path.join(__dirname, '..', '..', 'action.yml'), path.join(__dirname, '..', '..', 'action.yml'),
path.join(__dirname, '..', '..', 'README.md') path.join(__dirname, '..', '..', 'README.md')
) )

8
src/misc/licensed-check.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/bash
set -e
src/misc/licensed-download.sh
echo 'Running: licensed cached'
_temp/licensed-3.6.0/licensed status

24
src/misc/licensed-download.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/bash
set -e
if [ ! -f _temp/licensed-3.6.0.done ]; then
echo 'Clearing temp'
rm -rf _temp/licensed-3.6.0 || true
echo 'Downloading licensed'
mkdir -p _temp/licensed-3.6.0
pushd _temp/licensed-3.6.0
if [[ "$OSTYPE" == "darwin"* ]]; then
curl -Lfs -o licensed.tar.gz https://github.com/github/licensed/releases/download/3.6.0/licensed-3.6.0-darwin-x64.tar.gz
else
curl -Lfs -o licensed.tar.gz https://github.com/github/licensed/releases/download/3.6.0/licensed-3.6.0-linux-x64.tar.gz
fi
echo 'Extracting licenesed'
tar -xzf licensed.tar.gz
popd
touch _temp/licensed-3.6.0.done
else
echo 'Licensed already downloaded'
fi

Some files were not shown because too many files have changed in this diff Show More