Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 20 additions & 16 deletions docs/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -682,7 +682,7 @@ Pre conditions use the status and action-scoped environment available when prepa

### Checkout action

**🟡 Supported subset.** The final v1.2.0, v2.8.0, and v3.7.0 release commits are admitted exactly. Resolved commits in the v4-and-later range of the static [`actions/checkout` upstream `main` snapshot](https://github.com/actions/checkout/tree/f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a) are also admitted. The following known releases remain admitted even when their commits aren't reachable from that snapshot:
**🟡 Supported subset.** Immutable commits captured from frozen upstream tags, `main`, `master`, and `releases/v1` through `releases/v6` snapshots are admitted. The snapshot includes historical development and release commits across v1 through v7. These known releases identify the principal contracts:

| Release | Commit |
| --- | --- |
Expand All @@ -695,34 +695,38 @@ Pre conditions use the status and action-scoped environment available when prepa
| v7.0.0 corpus pin | [`9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0`](https://github.com/actions/checkout/tree/9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0) |
| v7.0.1 | [`3d3c42e5aac5ba805825da76410c181273ba90b1`](https://github.com/actions/checkout/tree/3d3c42e5aac5ba805825da76410c181273ba90b1) |

Mutable refs work only while they resolve to the upstream `main` snapshot or a known release above. Every admitted commit uses the native adapter; the upstream JavaScript doesn't run. Each admitted release accepts only the inputs it declares, so earlier releases reject later inputs. Other pre-v3.7.0 commits and unknown commits are unsupported. Compilation emits `W_CHECKOUT_LEGACY_RELEASE` for v1.2.0 and v2.8.0 to nudge an upgrade to v4 or later. Maintainers can update the v4-and-later snapshot with `go generate ./internal/action/integration`; this doesn't widen release admission.
Mutable refs work only while they resolve to a commit in the frozen snapshots. Every admitted commit uses the native adapter; the upstream JavaScript doesn't run. Each commit retains the inputs, full-history default, and outputs declared by its upstream contract. For example, early v2 commits reject later v2 inputs, and v4.0 and v4.1 commits don't expose the `ref` and `commit` outputs. Commits absent from the snapshots and manifests with unsupported output contracts remain unsupported. Compilation emits `W_CHECKOUT_LEGACY_RELEASE` for v1.2.0 and v2.8.0 to nudge an upgrade to v4 or later.

Maintainers can refresh the frozen refs and per-commit profiles with `go generate ./internal/action/integration`. Regeneration admits only commits reachable from the selected upstream tags and branches at that time; it doesn't blanket-admit future commits.

The adapter checks out a detached commit or static branch from the event repository at the workspace root or a clean top-level directory. It uses Buildkite repository-provider Git credentials when the job provides them; otherwise, it fetches anonymously. Credentials are scoped to each fetch command and verified submodule fetch command and are never persisted.

An explicit input is accepted only when the snapshotted manifest for that commit declares it. The following value restrictions then apply:

| Input | Supported values |
| --- | --- |
| `repository` | Omitted, or the event `owner/repo`. |
| `ref` | Omitted, empty, a lowercase 40-hex commit, or a static branch in the event repository. A direct `github.sha` or `needs.<job>.outputs.<name>` expression must resolve at runtime to the exact event SHA. |
| `token` | Omitted only. |
| `ssh-key`, `ssh-known-hosts` | v2.8.0 and later: omitted or empty. v1.2.0: omitted. |
| `ssh-strict` | v2.8.0 and later: omitted or `true`. v1.2.0: omitted. |
| `ssh-user` | v4 and later: omitted or `git`. Earlier releases: omitted. |
| `persist-credentials` | v2.8.0 and later: omitted or `false`. v1.2.0: omitted. |
| `ssh-key`, `ssh-known-hosts` | When declared by the commit: omitted or empty. Otherwise omitted. |
| `ssh-strict` | When declared by the commit: omitted or `true`. Otherwise omitted. |
| `ssh-user` | When declared by the commit: omitted or `git`. Otherwise omitted. |
| `persist-credentials` | When declared by the commit: omitted or `false`. Otherwise omitted. |
| `path` | Omitted, empty, or one clean non-`.git` top-level workspace directory. |
| `clean` | Omitted or `true`; the root workspace or selected path must be empty or absent. |
| `filter` | v4 and later: omitted or empty. Earlier releases: omitted. |
| `sparse-checkout` | v3.7.0 and later: omitted or empty. Earlier releases: omitted. |
| `sparse-checkout-cone-mode` | v3.7.0 and later: omitted or `true`. Earlier releases: omitted. |
| `fetch-depth` | Omitted or a nonnegative integer; `0` fetches full history. v1.2.0 fetches full history when omitted. |
| `fetch-tags` | v3.7.0 and later: omitted, `true`, or `false`. Earlier releases: omitted. |
| `show-progress` | v4 and later: omitted, `true`, or `false`. Earlier releases: omitted. |
| `filter` | When declared by the commit: omitted or empty. Otherwise omitted. |
| `sparse-checkout` | When declared by the commit: omitted or empty. Otherwise omitted. |
| `sparse-checkout-cone-mode` | When declared by the commit: omitted or `true`. Otherwise omitted. |
| `fetch-depth` | Omitted or a nonnegative integer; `0` fetches full history. Historical runner-plugin commits fetch full history when omitted. |
| `fetch-tags` | When declared by the commit: omitted, `true`, or `false`. Otherwise omitted. |
| `show-progress` | When declared by the commit: omitted, `true`, or `false`. Otherwise omitted. |
| `lfs` | Omitted or `false`. |
| `submodules` | Omitted, `false`, `true`, or `recursive`; whitespace is trimmed and casing is ignored. |
| `set-safe-directory` | v2.8.0 and later: omitted or `true`. v1.2.0: omitted. |
| `github-server-url` | v3.7.0 and later: omitted, empty, or `https://github.com`. Earlier releases: omitted. |
| `allow-unsafe-pr-checkout` | v2.8.0 and later: omitted or `false`. v1.2.0: omitted. |
| `set-safe-directory` | When declared by the commit: omitted or `true`. Otherwise omitted. |
| `github-server-url` | When declared by the commit: omitted, empty, or `https://github.com`. Otherwise omitted. |
| `allow-unsafe-pr-checkout` | When declared by the commit: omitted or `false`. Otherwise omitted. |

The `ref` and `commit` outputs are unavailable for v1.2.0, v2.8.0, and v3.7.0. Upstream added them in v4.2.0.
The `ref` and `commit` outputs are available only for commits whose action manifest declares them. Upstream added both outputs in v4.2.0.

The `false` value and omission do not run submodule commands. The `true` value runs native Git for direct children, and `recursive` includes nested children. Relative URLs and `fetch-depth` follow native Git behavior. Public and private GitHub submodules are supported under the job's repository access; external HTTPS submodules are anonymous. `git@github.com:` URLs are rewritten to HTTPS. Other SSH and non-HTTPS transports are unsupported.

Expand Down
70 changes: 26 additions & 44 deletions internal/action/integration/checkout.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:generate ../../../scripts/update-checkout-main-commits
//go:generate go run ./cmd/generate-checkout-profiles

package integration

Expand All @@ -12,10 +12,8 @@ import (
)

const (
// CheckoutV1Commit through CheckoutV7Commit are the current audited release
// implementations. CheckoutV1Commit, CheckoutV2Commit, and CheckoutV3Commit
// are the final v1, v2, and v3 releases and are admitted exactly rather
// than extending the v4-and-later main-branch snapshot.
// CheckoutV1Commit through CheckoutV7Commit identify principal release
// contracts within the broader frozen upstream snapshots.
// CheckoutV7InitialCommit is retained because it is pinned by the OSS
// compatibility corpus; its later v7.0.1 changes do not affect the adapter's
// bounded exact-event-SHA operation.
Expand All @@ -40,47 +38,31 @@ var checkoutCommits = map[string]string{
CheckoutV7Commit: "v7.0.1",
}

// checkoutInputIntroduced records the earliest admitted release generation
// declaring each version-gated input. Inputs absent from this map are declared
// by every admitted release.
var checkoutInputIntroduced = map[string]int{
"ssh-key": 2,
"ssh-known-hosts": 2,
"ssh-strict": 2,
"persist-credentials": 2,
"set-safe-directory": 2,
"allow-unsafe-pr-checkout": 2,
"fetch-tags": 3,
"sparse-checkout": 3,
"sparse-checkout-cone-mode": 3,
"github-server-url": 3,
"filter": 4,
"show-progress": 4,
"ssh-user": 4,
// checkoutContract records the adapter-visible contract declared by one
// immutable upstream action manifest.
type checkoutContract struct {
// inputs is a sorted, comma-separated set of names declared by the
// immutable upstream action manifest.
inputs string
fullHistory bool
refOutput, commitOutput bool
}

func checkoutGeneration(commit string) int {
switch commit {
case CheckoutV1Commit:
return 1
case CheckoutV2Commit:
return 2
case CheckoutV3Commit:
return 3
}
return 4
func (c checkoutContract) declaresInput(name string) bool {
return strings.Contains(","+c.inputs+",", ","+name+",")
}

// CheckoutSupportsOutputs reports whether the admitted release declares the
// ref and commit outputs, added upstream in v4.2.0.
func CheckoutSupportsOutputs(commit string) bool {
return checkoutGeneration(commit) >= 4
contract, ok := checkoutCommitContracts[commit]
return ok && contract.refOutput && contract.commitOutput
}

// CheckoutDefaultsToFullHistory reports whether the admitted release fetched
// full history when fetch-depth was omitted, as v1's runner plugin did.
func CheckoutDefaultsToFullHistory(commit string) bool {
return commit == CheckoutV1Commit
return checkoutCommitContracts[commit].fullHistory
}

// LegacyCheckoutRelease reports the admitted release label for the v1 and v2
Expand All @@ -92,15 +74,12 @@ func LegacyCheckoutRelease(commit string) (string, bool) {
return "", false
}

// validateCheckoutCommit admits known releases and a static snapshot of
// commits reachable from upstream main. Mutable references are resolved before
// this check, so changes after the snapshot are rejected until regeneration.
// validateCheckoutCommit admits commits with contracts captured from frozen
// upstream release and main snapshots. Mutable references are resolved before
// this check, so changes after the snapshots are rejected until regeneration.
func validateCheckoutCommit(commit string) error {
if _, ok := checkoutCommits[commit]; ok {
return nil
}
if _, ok := checkoutMainCommits[commit]; !ok {
supported := append(sortedCheckoutCommits(), "upstream main snapshot ("+checkoutMainSnapshotCommit+")")
if _, ok := checkoutCommitContracts[commit]; !ok {
supported := append(sortedCheckoutCommits(), "frozen upstream release and main snapshots (main "+checkoutMainSnapshotCommit+")")
return versionError("actions/checkout", "native adapter", commit, supported)
}
return nil
Expand All @@ -118,17 +97,20 @@ func sortedCheckoutCommits() []string {
// ValidateCheckoutInputs enforces the release-specific input contract
// implemented by the tokenless event-repository checkout adapter.
func ValidateCheckoutInputs(commit string, inputs map[string]string, repository, sha string) error {
contract, ok := checkoutCommitContracts[commit]
if !ok {
return versionError("actions/checkout", "native adapter", commit, append(sortedCheckoutCommits(), "frozen upstream release and main snapshots (main "+checkoutMainSnapshotCommit+")"))
}
names := sortedNames(inputs)
seen := make(map[string]bool, len(names))
generation := checkoutGeneration(commit)
for _, name := range names {
value := inputs[name]
normalized := strings.ToLower(name)
if seen[normalized] {
return fmt.Errorf("duplicate case-insensitive input %q is unsupported", name)
}
seen[normalized] = true
if checkoutInputIntroduced[normalized] > generation {
if !contract.declaresInput(normalized) {
return fmt.Errorf("explicit input %q is unsupported by this actions/checkout release", name)
}
switch normalized {
Expand Down
103 changes: 0 additions & 103 deletions internal/action/integration/checkout_main_commits_generated.go

This file was deleted.

Loading