# How actions/checkout Resolves Git References: A Deep Dive into the Source Code

> Explore how actions/checkout resolves git references in its source code. Understand the three-phase process for determining checkout targets, fetch ref-specs, and commit validation.

- Repository: [GitHub Actions/checkout](https://github.com/actions/checkout)
- Tags: deep-dive
- Published: 2026-07-05

---

**The actions/checkout action resolves git references through a three-phase process in [`src/ref-helper.ts`](https://github.com/actions/checkout/blob/main/src/ref-helper.ts) that determines the checkout target, builds fetch ref-specs, and validates the resulting commit against the requested ref.**

Whether you provide a branch name, tag, pull request number, or full commit SHA, actions/checkout must translate that input into a concrete git ref that can be fetched and checked out. This resolution logic lives primarily in the [`src/ref-helper.ts`](https://github.com/actions/checkout/blob/main/src/ref-helper.ts) file within the `actions/checkout` repository and operates through a carefully orchestrated pipeline.

## The Three-Phase Resolution Pipeline

The reference resolution process follows three distinct phases, each handled by specific functions in [`src/ref-helper.ts`](https://github.com/actions/checkout/blob/main/src/ref-helper.ts).

### Phase 1: Determining the Checkout Target with getCheckoutInfo()

The `getCheckoutInfo()` function builds an `ICheckoutInfo` object that instructs the runner which ref to fetch and, for branch-type refs, which remote ref serves as the *start point* (upstream branch).

- **Raw SHA input**: When `ref` is empty but `commit` contains a SHA, that SHA becomes `result.ref` directly.
- **Fully-qualified refs**: For inputs like `refs/heads/main` or `refs/pull/123/merge`, the function extracts the short name and maps it to the corresponding remote ref. For example, `refs/heads/main` becomes `result.ref = "main"` with `result.startPoint = "refs/remotes/origin/main"`.
- **Unqualified names**: For inputs like "main" or "v1.0", the code queries the remote using `git.branchExists()` and `git.tagExists()` to determine whether the name refers to a branch or tag. If neither exists, the action throws an error.

### Phase 2: Building Fetch Ref-Specs with getRefSpec()

The `getRefSpec()` function returns the list of `+src:dst` specifications that tell Git which refs to bring into the shallow clone.

- **Tag fetching**: When `fetchTags` is true, the generic ref-spec `+refs/tags/*:refs/tags/*` is always added.
- **Commit SHA resolution**: When a specific SHA is supplied, the function creates a ref-spec mapping that SHA to the appropriate remote ref, such as `+<sha>:refs/remotes/pull/123` for pull request refs.
- **Unqualified refs**: The function falls back to wildcard fetches of both heads and tags (`+refs/heads/*:refs/remotes/origin/*` and `+refs/tags/*:refs/tags/*`) so the remote can be matched locally after fetching.
- **Fully-qualified refs**: These receive direct mapping in the format `+<ref>:<dest>`.

### Phase 3: Validating the Fetched Reference with testRef()

After the fetch completes, `testRef()` confirms that the fetched ref points to the expected commit.

- **Branch validation**: The function verifies that `origin/<branch>` exists and that its SHA matches the requested commit.
- **Tag validation**: For tags, it dereferences annotated tags using the `^{commit}` suffix and compares the resulting SHA.
- **Failure handling**: If validation fails, the action triggers the retry logic in [`src/retry-helper.ts`](https://github.com/actions/checkout/blob/main/src/retry-helper.ts) to perform a full fetch of all history before retrying resolution.

## Orchestration and Error Handling

These three phases are orchestrated by [`src/git-source-provider.ts`](https://github.com/actions/checkout/blob/main/src/git-source-provider.ts), which calls `getCheckoutInfo()`, executes `git.fetch(refSpec)` with the specs from `getRefSpec()`, and finally invokes `testRef()` to ensure success. The provider also handles special cases such as shallow clones, sparse checkout, and submodule initialization.

When `testRef()` reports a mismatch—such as when a force-push occurs between the initial fetch and validation—the retry logic in [`src/retry-helper.ts`](https://github.com/actions/checkout/blob/main/src/retry-helper.ts) implements exponential backoff before attempting a full history fetch.

## Practical Examples

### Workflow Configuration (YAML)

```yaml
steps:
  - uses: actions/checkout@v4
    with:
      # Resolve a branch name

      ref: main
  
  - uses: actions/checkout@v4
    with:
      # Resolve a specific commit SHA

      ref: ''
      sha: 4a1b2c3d4e5f6g7h8i9j0klmnopqrstuv
  
  - uses: actions/checkout@v4
    with:
      # Resolve a pull request

      ref: refs/pull/123/merge
      fetch-tags: true

```

### Programmatic Usage (JavaScript)

```javascript
import * as core from '@actions/core'
import * as checkout from '@actions/checkout'

async function run() {
  const ref = core.getInput('ref')
  const sha = core.getInput('sha')
  
  // Internally runs getCheckoutInfo(), getRefSpec(), 
  // fetch(), and testRef()
  await checkout.checkout({
    repository: 'owner/repo',
    ref,
    sha,
    fetchTags: true
  })
}

run()

```

## Summary

- **Reference resolution** occurs in three phases: target determination via `getCheckoutInfo()`, ref-spec construction via `getRefSpec()`, and validation via `testRef()` in [`src/ref-helper.ts`](https://github.com/actions/checkout/blob/main/src/ref-helper.ts).
- **Unqualified refs** (like "main" or "v1.0") require remote existence checks using `git.branchExists()` and `git.tagExists()` before resolution.
- **Ref-spec generation** adapts to input types, using wildcards for unqualified names, direct mappings for fully-qualified refs, and SHA-specific mappings for commit references.
- **Validation and retry logic** ensures correctness through `testRef()` and falls back to full history fetches via [`src/retry-helper.ts`](https://github.com/actions/checkout/blob/main/src/retry-helper.ts) when shallow clones prove insufficient.

## Frequently Asked Questions

### How does actions/checkout handle unqualified ref names like "main" or "v1.0"?

When you provide an unqualified name, the action queries the remote repository using `git.branchExists()` and `git.tagExists()` (implemented in [`src/git-command-manager.ts`](https://github.com/actions/checkout/blob/main/src/git-command-manager.ts)) to determine whether the name refers to a branch or tag. If exactly one matches, the action proceeds with that type; if neither or both match ambiguously, the resolution fails with an error.

### What happens when actions/checkout receives a specific commit SHA?

When the `ref` input is empty and `commit` contains a SHA, `getCheckoutInfo()` sets that SHA as the direct target. The `getRefSpec()` function then creates a specific ref-spec mapping that SHA to the appropriate remote ref location, allowing the fetch to retrieve just that commit rather than entire branch histories.

### How does the action validate that the fetched ref matches the requested commit?

The `testRef()` function performs post-fetch validation by checking that `origin/<branch>` exists and matches the expected SHA for branches, or by dereferencing annotated tags with `^{commit}` and comparing SHAs for tags. This verification catches race conditions where the remote ref might have changed between the fetch and checkout operations.

### Where does the retry logic live when ref resolution fails?

The retry logic resides in [`src/retry-helper.ts`](https://github.com/actions/checkout/blob/main/src/retry-helper.ts), which implements exponential backoff. When `testRef()` detects a mismatch—such as when the shallow clone lacks sufficient history to verify the ref—the helper triggers a full fetch of all repository history before attempting the resolution process again.