mirror of
https://github.com/actions/checkout.git
synced 2026-05-14 17:18:06 +00:00
Compare commits
No commits in common. "4bd535976fec6a84b5f2d0b5585d4f461cf6ec0b" and "ed69f3bbdd331f561e02e4ba566bb550e2ea906b" have entirely different histories.
4bd535976f
...
ed69f3bbdd
@ -1,182 +0,0 @@
|
|||||||
import * as path from 'path'
|
|
||||||
|
|
||||||
const mockStartGroup = jest.fn()
|
|
||||||
const mockEndGroup = jest.fn()
|
|
||||||
const mockInfo = jest.fn()
|
|
||||||
const mockWarning = jest.fn()
|
|
||||||
const mockSetOutput = jest.fn()
|
|
||||||
const mockSetSecret = jest.fn()
|
|
||||||
|
|
||||||
const mockCreateCommandManager = jest.fn()
|
|
||||||
const mockCreateAuthHelper = jest.fn()
|
|
||||||
const mockPrepareExistingDirectory = jest.fn()
|
|
||||||
const mockGetFetchUrl = jest.fn()
|
|
||||||
const mockGetRefSpec = jest.fn()
|
|
||||||
const mockTestRef = jest.fn()
|
|
||||||
const mockGetCheckoutInfo = jest.fn()
|
|
||||||
const mockCheckCommitInfo = jest.fn()
|
|
||||||
const mockSetRepositoryPath = jest.fn()
|
|
||||||
const mockSetupCache = jest.fn()
|
|
||||||
const mockDirectoryExistsSync = jest.fn()
|
|
||||||
const mockFileExistsSync = jest.fn()
|
|
||||||
|
|
||||||
jest.mock('@actions/core', () => ({
|
|
||||||
startGroup: mockStartGroup,
|
|
||||||
endGroup: mockEndGroup,
|
|
||||||
info: mockInfo,
|
|
||||||
warning: mockWarning,
|
|
||||||
setOutput: mockSetOutput,
|
|
||||||
setSecret: mockSetSecret
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('@actions/io', () => ({
|
|
||||||
rmRF: jest.fn(),
|
|
||||||
mkdirP: jest.fn()
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('../src/fs-helper', () => ({
|
|
||||||
directoryExistsSync: mockDirectoryExistsSync,
|
|
||||||
fileExistsSync: mockFileExistsSync
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('../src/git-command-manager', () => ({
|
|
||||||
MinimumGitSparseCheckoutVersion: {},
|
|
||||||
createCommandManager: mockCreateCommandManager
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('../src/git-auth-helper', () => ({
|
|
||||||
createAuthHelper: mockCreateAuthHelper
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('../src/git-directory-helper', () => ({
|
|
||||||
prepareExistingDirectory: mockPrepareExistingDirectory
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('../src/github-api-helper', () => ({
|
|
||||||
downloadRepository: jest.fn(),
|
|
||||||
getDefaultBranch: jest.fn()
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('../src/ref-helper', () => ({
|
|
||||||
getRefSpec: mockGetRefSpec,
|
|
||||||
getCheckoutInfo: mockGetCheckoutInfo,
|
|
||||||
testRef: mockTestRef,
|
|
||||||
checkCommitInfo: mockCheckCommitInfo
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('../src/state-helper', () => ({
|
|
||||||
setRepositoryPath: mockSetRepositoryPath
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('../src/url-helper', () => ({
|
|
||||||
getFetchUrl: mockGetFetchUrl
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('../src/git-cache-helper', () => ({
|
|
||||||
GitCacheHelper: jest.fn().mockImplementation(() => ({
|
|
||||||
setupCache: mockSetupCache
|
|
||||||
}))
|
|
||||||
}))
|
|
||||||
|
|
||||||
import {getSource} from '../src/git-source-provider'
|
|
||||||
|
|
||||||
describe('getSource reference cache regression', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('updates the reference cache and reconfigures alternates for existing repositories', async () => {
|
|
||||||
const repositoryPath = '/tmp/work/repo'
|
|
||||||
const repositoryUrl = 'https://github.com/actions/checkout'
|
|
||||||
const cachePath = '/tmp/reference-cache/actions-checkout.git'
|
|
||||||
|
|
||||||
const mockGit = {
|
|
||||||
init: jest.fn(),
|
|
||||||
remoteAdd: jest.fn(),
|
|
||||||
referenceAdd: jest.fn().mockResolvedValue(undefined),
|
|
||||||
tryDisableAutomaticGarbageCollection: jest.fn().mockResolvedValue(true),
|
|
||||||
fetch: jest.fn().mockResolvedValue(undefined),
|
|
||||||
version: jest.fn().mockResolvedValue({
|
|
||||||
checkMinimum: jest.fn().mockReturnValue(true)
|
|
||||||
}),
|
|
||||||
disableSparseCheckout: jest.fn().mockResolvedValue(undefined),
|
|
||||||
checkout: jest.fn().mockResolvedValue(undefined),
|
|
||||||
log1: jest
|
|
||||||
.fn()
|
|
||||||
.mockResolvedValueOnce('commit info')
|
|
||||||
.mockResolvedValueOnce('0123456789abcdef'),
|
|
||||||
lfsInstall: jest.fn(),
|
|
||||||
submoduleSync: jest.fn(),
|
|
||||||
submoduleUpdate: jest.fn(),
|
|
||||||
submoduleForeach: jest.fn(),
|
|
||||||
config: jest.fn()
|
|
||||||
}
|
|
||||||
|
|
||||||
const mockAuthHelper = {
|
|
||||||
configureAuth: jest.fn().mockResolvedValue(undefined),
|
|
||||||
configureGlobalAuth: jest.fn().mockResolvedValue(undefined),
|
|
||||||
configureSubmoduleAuth: jest.fn().mockResolvedValue(undefined),
|
|
||||||
configureTempGlobalConfig: jest.fn().mockResolvedValue('/tmp/gitconfig'),
|
|
||||||
removeAuth: jest.fn().mockResolvedValue(undefined),
|
|
||||||
removeGlobalAuth: jest.fn().mockResolvedValue(undefined),
|
|
||||||
removeGlobalConfig: jest.fn().mockResolvedValue(undefined)
|
|
||||||
}
|
|
||||||
|
|
||||||
mockCreateCommandManager.mockResolvedValue(mockGit)
|
|
||||||
mockCreateAuthHelper.mockReturnValue(mockAuthHelper)
|
|
||||||
mockPrepareExistingDirectory.mockResolvedValue(undefined)
|
|
||||||
mockGetFetchUrl.mockReturnValue(repositoryUrl)
|
|
||||||
mockGetRefSpec.mockReturnValue(['+refs/heads/main:refs/remotes/origin/main'])
|
|
||||||
mockTestRef.mockResolvedValue(true)
|
|
||||||
mockGetCheckoutInfo.mockResolvedValue({
|
|
||||||
ref: 'refs/heads/main',
|
|
||||||
startPoint: 'refs/remotes/origin/main'
|
|
||||||
})
|
|
||||||
mockCheckCommitInfo.mockResolvedValue(undefined)
|
|
||||||
mockSetupCache.mockResolvedValue(cachePath)
|
|
||||||
mockFileExistsSync.mockReturnValue(false)
|
|
||||||
mockDirectoryExistsSync.mockImplementation((targetPath: string) => {
|
|
||||||
return (
|
|
||||||
targetPath === repositoryPath ||
|
|
||||||
targetPath === path.join(repositoryPath, '.git') ||
|
|
||||||
targetPath === path.join(cachePath, 'objects')
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
await getSource({
|
|
||||||
repositoryPath,
|
|
||||||
repositoryOwner: 'actions',
|
|
||||||
repositoryName: 'checkout',
|
|
||||||
ref: 'refs/heads/main',
|
|
||||||
commit: '0123456789abcdef',
|
|
||||||
clean: false,
|
|
||||||
filter: undefined,
|
|
||||||
sparseCheckout: undefined as any,
|
|
||||||
sparseCheckoutConeMode: false,
|
|
||||||
fetchDepth: 1,
|
|
||||||
fetchDepthExplicit: true,
|
|
||||||
fetchTags: false,
|
|
||||||
showProgress: false,
|
|
||||||
referenceCache: '/tmp/reference-cache',
|
|
||||||
lfs: false,
|
|
||||||
submodules: false,
|
|
||||||
nestedSubmodules: false,
|
|
||||||
authToken: 'token',
|
|
||||||
sshKey: '',
|
|
||||||
sshKnownHosts: '',
|
|
||||||
sshStrict: true,
|
|
||||||
sshUser: 'git',
|
|
||||||
persistCredentials: false,
|
|
||||||
workflowOrganizationId: undefined,
|
|
||||||
githubServerUrl: 'https://github.com',
|
|
||||||
setSafeDirectory: false
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
expect(mockGit.init).not.toHaveBeenCalled()
|
|
||||||
expect(mockGit.remoteAdd).not.toHaveBeenCalled()
|
|
||||||
expect(mockSetupCache).toHaveBeenCalledWith(mockGit, repositoryUrl)
|
|
||||||
expect(mockGit.referenceAdd).toHaveBeenCalledWith(
|
|
||||||
path.join(cachePath, 'objects')
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,10 +1,5 @@
|
|||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import * as fsHelper from '../src/fs-helper'
|
import {adjustFetchDepthForCache} from '../src/git-source-provider'
|
||||||
import {GitCacheHelper} from '../src/git-cache-helper'
|
|
||||||
import {
|
|
||||||
adjustFetchDepthForCache,
|
|
||||||
setupReferenceCache
|
|
||||||
} from '../src/git-source-provider'
|
|
||||||
|
|
||||||
// Mock @actions/core
|
// Mock @actions/core
|
||||||
jest.mock('@actions/core')
|
jest.mock('@actions/core')
|
||||||
@ -91,73 +86,3 @@ describe('adjustFetchDepthForCache', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('setupReferenceCache', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.restoreAllMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('does nothing when referenceCache is not set', async () => {
|
|
||||||
const git = {
|
|
||||||
referenceAdd: jest.fn()
|
|
||||||
} as any
|
|
||||||
|
|
||||||
await setupReferenceCache(git, '', 'https://github.com/actions/checkout.git')
|
|
||||||
|
|
||||||
expect(git.referenceAdd).not.toHaveBeenCalled()
|
|
||||||
expect(core.startGroup).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('updates the cache and configures alternates when cache objects exist', async () => {
|
|
||||||
const git = {
|
|
||||||
referenceAdd: jest.fn().mockResolvedValue(undefined)
|
|
||||||
} as any
|
|
||||||
const setupCacheSpy = jest
|
|
||||||
.spyOn(GitCacheHelper.prototype, 'setupCache')
|
|
||||||
.mockResolvedValue('/tmp/reference-cache/repo.git')
|
|
||||||
jest
|
|
||||||
.spyOn(fsHelper, 'directoryExistsSync')
|
|
||||||
.mockReturnValue(true)
|
|
||||||
|
|
||||||
await setupReferenceCache(
|
|
||||||
git,
|
|
||||||
'/tmp/reference-cache',
|
|
||||||
'https://github.com/actions/checkout.git'
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(setupCacheSpy).toHaveBeenCalledWith(
|
|
||||||
git,
|
|
||||||
'https://github.com/actions/checkout.git'
|
|
||||||
)
|
|
||||||
expect(git.referenceAdd).toHaveBeenCalledWith(
|
|
||||||
'/tmp/reference-cache/repo.git/objects'
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('warns when the cache objects directory is missing', async () => {
|
|
||||||
const git = {
|
|
||||||
referenceAdd: jest.fn().mockResolvedValue(undefined)
|
|
||||||
} as any
|
|
||||||
jest
|
|
||||||
.spyOn(GitCacheHelper.prototype, 'setupCache')
|
|
||||||
.mockResolvedValue('/tmp/reference-cache/repo.git')
|
|
||||||
jest
|
|
||||||
.spyOn(fsHelper, 'directoryExistsSync')
|
|
||||||
.mockReturnValue(false)
|
|
||||||
|
|
||||||
await setupReferenceCache(
|
|
||||||
git,
|
|
||||||
'/tmp/reference-cache',
|
|
||||||
'https://github.com/actions/checkout.git'
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(git.referenceAdd).not.toHaveBeenCalled()
|
|
||||||
expect(core.warning).toHaveBeenCalledWith(
|
|
||||||
'Reference repository cache objects directory /tmp/reference-cache/repo.git/objects does not exist'
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|||||||
@ -1,127 +1,37 @@
|
|||||||
# ADR 2303: Reference cache for faster checkouts
|
# Reference Cache für schnelle Checkouts
|
||||||
|
|
||||||
**Date**: 2026-03-10
|
## Zusammenfassung
|
||||||
|
Einführung eines lokal verwalteten Git-Referenz-Caches für Haupt-Repositories und Submodule, um Netzwerk-Traffic und Checkout-Zeiten auf persistenten Runnern (z.B. Self-Hosted) massiv zu reduzieren.
|
||||||
|
|
||||||
**Status**: Proposed
|
## Implementierungsplan
|
||||||
|
|
||||||
## Context
|
1. **Inputs:**
|
||||||
|
- In `action.yml` einen neuen Input `reference-cache` (Pfad zum Cache-Verzeichnis) hinzufügen. Default ist leer.
|
||||||
|
- In `src/git-source-settings.ts` und `src/input-helper.ts` den Input auslesen und bereitstellen (`settings.referenceCache`).
|
||||||
|
|
||||||
Repeated checkouts of the same repositories are expensive on runners with persistent storage.
|
2. **Cache Manager (`src/git-cache-helper.ts`):**
|
||||||
This is especially noticeable for self-hosted runners and custom runner images that execute
|
- Eine neue Klasse/Helper-Logik, die das Erstellen (`git clone --bare`) und Aktualisieren (`git fetch --force`) von Bare Cache-Repos übernimmt.
|
||||||
many jobs against the same repositories and submodules.
|
- **Namenskonvention Cache-Verzeichnis:** Damit Admin-Lesbarkeit und Kollisionsfreiheit gewährleistet sind, wird das Cache-Verzeichnis aus der Repository-URL gebildet:
|
||||||
|
- Alle Sonderzeichen in der URL durch `_` ersetzen.
|
||||||
|
- Ein kurzer Hash (z. B. erste 8 Zeichen des SHA256) der echten URL zur Eindeutigkeit anhängen.
|
||||||
|
- Beispiel: `<reference-cache>/https___github_com_actions_checkout_8f9b1c2a.git`
|
||||||
|
|
||||||
Today, each checkout fetches objects from the remote even when the runner already has most of
|
3. **Haupt-Repo Checkout (`src/git-source-provider.ts`):**
|
||||||
the repository history available locally from previous jobs. This increases network traffic,
|
- Vor dem Setup des Checkouts prüfen, ob `reference-cache` gesetzt ist.
|
||||||
slows down checkout time, and makes recursive submodule initialization more expensive than
|
- Wenn ja: den Cache-Ordner für die Haupt-URL aktualisieren/anlegen.
|
||||||
necessary.
|
- Nach dem initialen `git.init()` den Pfad in `.git/objects/info/alternates` schreiben, der auf das `objects`-Verzeichnis des Cache-Ordners zeigt.
|
||||||
|
|
||||||
Git supports reference repositories and alternates, which allow one working repository to reuse
|
4. **Submodule Checkouts (Iterativ statt monolithisch):**
|
||||||
objects from another local repository. This mechanism is a good fit for persistent runners,
|
- Der aktuelle Befehl `git submodule update --recursive` funktioniert nicht out-of-the-box mit `reference`, wenn jedes Submodul seinen individuellen Referenz-Cache benötigt.
|
||||||
provided the cache is managed safely and works for both the main repository and submodules.
|
- Wenn `reference-cache` aktiv ist und Submodule initialisiert werden sollen:
|
||||||
|
- Lese `.gitmodules` aus (alle Sub-URLs ermitteln).
|
||||||
|
- Für jedes Submodul den Cache (genauso wie in Step 2) anlegen oder aktualisieren.
|
||||||
|
- Submodul einzeln auschecken per `git submodule update --init --reference <cache-pfad/.git> <pfad>`.
|
||||||
|
- Bei der Einstellung `recursive`: In jedes Submodul-Verzeichnis wechseln und den Vorgang für `.gitmodules` rekursiv auf Skript-Ebene durchführen (anstatt Git's `--recursive` Flag einfach weiterzugeben).
|
||||||
|
|
||||||
## Decision
|
## Akzeptanzkriterien
|
||||||
|
1. **Neue Option konfigurierbar**: Der Input `reference-cache` kann übergeben werden, der Code reagiert darauf.
|
||||||
Add an optional `reference-cache` input that points to a local directory used to store managed
|
2. **Ordnerstruktur korrekt**: Der Cache-Ordner für das Hauptrepo und Submodule erhält Namen nach der "URL_Sonderzeichen_Ersetzt+SHA_Cut"-Logik.
|
||||||
bare repositories for the primary repository and its submodules.
|
3. **Bandbreite gespart / Alternates genutzt**: Beim Hauptcheckout wird eine `.git/objects/info/alternates`-Datei mit Pfad zum lokalen Cache erzeugt. Danach ausgeführte `git fetch`-Befehle sind signifikant schneller bzw. laden deutlich weniger Bytes herunter.
|
||||||
|
4. **Submodule erhalten Caches**: Auch tiefe (rekursive) Submodule profitieren für deren jeweilige Remote-URL vom Cache, da pro Submodul ein passender `--reference` Punkt dynamisch berechnet und übergeben wird.
|
||||||
### Input
|
5. **Kein --dissociate**: Aus Performance-Gründen bleibt der Arbeitsordner an den Cache gebunden (`git repack` ist zeitaufwändig). Fällt der Cache weg, muss der Workspace erst einmal neu erzeugt werden (was bei Action Runnern die Norm ist, falls es nicht ohnehin "single-use" Runner sind).
|
||||||
|
|
||||||
Add a new input in `action.yml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
reference-cache:
|
|
||||||
description: >
|
|
||||||
Path to a local directory used as a reference cache for Git clones.
|
|
||||||
```
|
|
||||||
|
|
||||||
The value is exposed through `settings.referenceCache`.
|
|
||||||
|
|
||||||
### Cache layout
|
|
||||||
|
|
||||||
Each cached repository is stored as a bare repository inside the configured cache directory.
|
|
||||||
|
|
||||||
The cache directory name is derived from the repository URL by:
|
|
||||||
|
|
||||||
- replacing non-alphanumeric characters with `_`
|
|
||||||
- appending a short SHA-256 hash of the original URL to avoid collisions
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```text
|
|
||||||
<reference-cache>/https___github_com_actions_checkout_8f9b1c2a.git
|
|
||||||
```
|
|
||||||
|
|
||||||
### Cache lifecycle
|
|
||||||
|
|
||||||
Introduce helper logic in `src/git-cache-helper.ts` responsible for:
|
|
||||||
|
|
||||||
- creating a bare cache repository with `git clone --bare`
|
|
||||||
- updating an existing bare cache repository with `git fetch --force`
|
|
||||||
- serializing access with file-based locking so concurrent jobs do not corrupt the cache
|
|
||||||
- using a temporary clone-and-rename flow to avoid leaving behind partial repositories
|
|
||||||
|
|
||||||
### Main repository checkout
|
|
||||||
|
|
||||||
When `reference-cache` is configured:
|
|
||||||
|
|
||||||
- prepare or update the cache for the main repository URL
|
|
||||||
- configure the checkout repository to use the cache through Git alternates
|
|
||||||
- keep the working repository attached to the cache instead of dissociating it
|
|
||||||
|
|
||||||
This allows later fetch operations to reuse local objects instead of downloading them again.
|
|
||||||
|
|
||||||
### Submodules
|
|
||||||
|
|
||||||
When submodules are enabled together with `reference-cache`, submodules are processed one by one
|
|
||||||
instead of relying solely on a monolithic `git submodule update --recursive` flow.
|
|
||||||
|
|
||||||
For each submodule:
|
|
||||||
|
|
||||||
- read the submodule URL from `.gitmodules`
|
|
||||||
- resolve relative URLs where possible
|
|
||||||
- create or update a dedicated cache for that submodule repository
|
|
||||||
- run `git submodule update --init --reference <cache> <path>` for that submodule
|
|
||||||
|
|
||||||
When recursive submodules are requested, repeat the same process inside each initialized submodule.
|
|
||||||
|
|
||||||
### Fetch depth behavior
|
|
||||||
|
|
||||||
When `reference-cache` is enabled, shallow fetches are usually counterproductive because object
|
|
||||||
negotiation overhead can outweigh the benefit of a local object store.
|
|
||||||
|
|
||||||
For that reason:
|
|
||||||
|
|
||||||
- the default `fetch-depth` is overridden to `0` when `reference-cache` is enabled
|
|
||||||
- if the user explicitly sets `fetch-depth`, keep the user-provided value and emit a warning
|
|
||||||
|
|
||||||
### No `--dissociate`
|
|
||||||
|
|
||||||
The checkout should remain connected to the reference cache.
|
|
||||||
|
|
||||||
Using `--dissociate` would copy objects into the working repository and typically require extra
|
|
||||||
repacking work, which reduces the performance benefit of the cache. If the cache is removed, the
|
|
||||||
workspace is expected to be recreated, which is acceptable for the target runner scenarios.
|
|
||||||
|
|
||||||
## Consequences
|
|
||||||
|
|
||||||
### Positive
|
|
||||||
|
|
||||||
- reduces network traffic for repeated checkouts on persistent runners
|
|
||||||
- improves checkout performance for the main repository and submodules
|
|
||||||
- reuses standard Git mechanisms instead of introducing a custom object store
|
|
||||||
- keeps cache naming deterministic and readable for administrators
|
|
||||||
|
|
||||||
### Trade-offs
|
|
||||||
|
|
||||||
- adds cache management complexity, including locking and recovery from interrupted operations
|
|
||||||
- submodule handling becomes more complex because each submodule may require its own cache
|
|
||||||
- benefits are limited on ephemeral runners, where the cache is not reused across jobs
|
|
||||||
- workspaces remain dependent on the presence of the cache until they are recreated
|
|
||||||
|
|
||||||
## Acceptance criteria
|
|
||||||
|
|
||||||
1. The `reference-cache` input can be configured and is exposed through the action settings.
|
|
||||||
2. Cache directories for the main repository and submodules follow the sanitized-URL-plus-hash naming scheme.
|
|
||||||
3. The main checkout uses Git alternates so later fetches can reuse local cached objects.
|
|
||||||
4. Submodules, including recursive submodules, can use repository-specific caches.
|
|
||||||
5. The checkout does not use `--dissociate` and remains attached to the cache for performance.
|
|
||||||
|
|||||||
@ -33,7 +33,6 @@
|
|||||||
"@actions/github": "^6.0.0",
|
"@actions/github": "^6.0.0",
|
||||||
"@actions/io": "^1.1.3",
|
"@actions/io": "^1.1.3",
|
||||||
"@actions/tool-cache": "^2.0.1",
|
"@actions/tool-cache": "^2.0.1",
|
||||||
"proper-lockfile": "^4.1.2",
|
|
||||||
"uuid": "^9.0.1"
|
"uuid": "^9.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -51,6 +50,7 @@
|
|||||||
"jest-circus": "^29.7.0",
|
"jest-circus": "^29.7.0",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
|
"proper-lockfile": "^4.1.2",
|
||||||
"ts-jest": "^29.2.5",
|
"ts-jest": "^29.2.5",
|
||||||
"typescript": "^5.5.4"
|
"typescript": "^5.5.4"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -68,8 +68,8 @@ export class GitCacheHelper {
|
|||||||
await git.execGit(args)
|
await git.execGit(args)
|
||||||
|
|
||||||
if (fs.existsSync(cachePath)) {
|
if (fs.existsSync(cachePath)) {
|
||||||
// In rare cases where it somehow exists but objects/ didn't, clean it up
|
// In rare cases where it somehow exists but objects/ didn't, clean it up
|
||||||
await fs.promises.rm(cachePath, { recursive: true, force: true })
|
await fs.promises.rm(cachePath, { recursive: true, force: true })
|
||||||
}
|
}
|
||||||
await fs.promises.rename(tmpPath, cachePath)
|
await fs.promises.rename(tmpPath, cachePath)
|
||||||
} catch (cloneErr) {
|
} catch (cloneErr) {
|
||||||
|
|||||||
@ -23,33 +23,7 @@ interface SubmoduleInfo {
|
|||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setupReferenceCache(
|
async function iterativeSubmoduleUpdate(
|
||||||
git: IGitCommandManager,
|
|
||||||
referenceCache: string,
|
|
||||||
repositoryUrl: string
|
|
||||||
): Promise<void> {
|
|
||||||
if (!referenceCache) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
core.startGroup('Setting up reference repository cache')
|
|
||||||
try {
|
|
||||||
const cacheHelper = new GitCacheHelper(referenceCache)
|
|
||||||
const cachePath = await cacheHelper.setupCache(git, repositoryUrl)
|
|
||||||
const cacheObjects = path.join(cachePath, 'objects')
|
|
||||||
if (fsHelper.directoryExistsSync(cacheObjects, false)) {
|
|
||||||
await git.referenceAdd(cacheObjects)
|
|
||||||
} else {
|
|
||||||
core.warning(
|
|
||||||
`Reference repository cache objects directory ${cacheObjects} does not exist`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
core.endGroup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function recursiveSubmoduleUpdate(
|
|
||||||
git: IGitCommandManager,
|
git: IGitCommandManager,
|
||||||
cacheHelper: GitCacheHelper,
|
cacheHelper: GitCacheHelper,
|
||||||
repositoryPath: string,
|
repositoryPath: string,
|
||||||
@ -171,7 +145,7 @@ async function recursiveSubmoduleUpdate(
|
|||||||
// Recursive update inside the submodule
|
// Recursive update inside the submodule
|
||||||
if (nestedSubmodules) {
|
if (nestedSubmodules) {
|
||||||
const subRepoPath = path.join(repositoryPath, info.path)
|
const subRepoPath = path.join(repositoryPath, info.path)
|
||||||
await recursiveSubmoduleUpdate(
|
await iterativeSubmoduleUpdate(
|
||||||
git,
|
git,
|
||||||
cacheHelper,
|
cacheHelper,
|
||||||
subRepoPath,
|
subRepoPath,
|
||||||
@ -302,9 +276,21 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
|
|||||||
await git.init()
|
await git.init()
|
||||||
await git.remoteAdd('origin', repositoryUrl)
|
await git.remoteAdd('origin', repositoryUrl)
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
}
|
|
||||||
|
|
||||||
await setupReferenceCache(git, settings.referenceCache, repositoryUrl)
|
// Setup reference cache if requested
|
||||||
|
if (settings.referenceCache) {
|
||||||
|
core.startGroup('Setting up reference repository cache')
|
||||||
|
const cacheHelper = new GitCacheHelper(settings.referenceCache)
|
||||||
|
const cachePath = await cacheHelper.setupCache(git, repositoryUrl)
|
||||||
|
const cacheObjects = path.join(cachePath, 'objects')
|
||||||
|
if (fsHelper.directoryExistsSync(cacheObjects, false)) {
|
||||||
|
await git.referenceAdd(cacheObjects)
|
||||||
|
} else {
|
||||||
|
core.warning(`Reference repository cache objects directory ${cacheObjects} does not exist`)
|
||||||
|
}
|
||||||
|
core.endGroup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remove global auth if it was set for reference cache,
|
// Remove global auth if it was set for reference cache,
|
||||||
// to avoid duplicate AUTHORIZATION headers during fetch
|
// to avoid duplicate AUTHORIZATION headers during fetch
|
||||||
@ -465,9 +451,9 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
|
|||||||
await git.submoduleSync(settings.nestedSubmodules)
|
await git.submoduleSync(settings.nestedSubmodules)
|
||||||
|
|
||||||
if (settings.referenceCache) {
|
if (settings.referenceCache) {
|
||||||
core.info('Recursive submodule update using reference cache')
|
core.info('Iterative submodule update using reference cache')
|
||||||
const cacheHelper = new GitCacheHelper(settings.referenceCache)
|
const cacheHelper = new GitCacheHelper(settings.referenceCache)
|
||||||
await recursiveSubmoduleUpdate(
|
await iterativeSubmoduleUpdate(
|
||||||
git,
|
git,
|
||||||
cacheHelper,
|
cacheHelper,
|
||||||
settings.repositoryPath,
|
settings.repositoryPath,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user