-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Feature/git cache #2384
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Feature/git cache #2384
Changes from 2 commits
9ddd3f4
ed69f3b
9be4f3c
090955f
20f49bc
043bdbc
4bd5359
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| import * as path from 'path' | ||
| import * as fs from 'fs' | ||
| import * as io from '@actions/io' | ||
| import { GitCacheHelper } from '../src/git-cache-helper' | ||
| import { IGitCommandManager } from '../src/git-command-manager' | ||
|
|
||
| describe('GitCacheHelper', () => { | ||
| let cacheHelper: GitCacheHelper | ||
| let mockGit: jest.Mocked<IGitCommandManager> | ||
|
|
||
| const cacheDir = path.join(__dirname, 'test-cache') | ||
|
|
||
| beforeEach(async () => { | ||
| cacheHelper = new GitCacheHelper(cacheDir) | ||
| mockGit = { | ||
| execGit: jest.fn().mockImplementation(async (args) => { | ||
| // If git clone is called, simulate creating the destination dir | ||
| if (args && args.includes('clone')) { | ||
| const dest = args.find((a: string) => a.includes('.tmp.')); | ||
| if (dest) { | ||
| await io.mkdirP(dest); | ||
| } else { | ||
| console.log('No .tmp. found in args:', args); | ||
| } | ||
| } | ||
| return { exitCode: 0, stdout: '', stderr: '' }; | ||
| }), | ||
| gitEnv: {} | ||
| } as any | ||
|
|
||
| await io.mkdirP(cacheDir) | ||
| }) | ||
|
|
||
| afterEach(async () => { | ||
| await io.rmRF(cacheDir) | ||
| }) | ||
|
|
||
| it('generates a consistent, short, and safe cache directory name', () => { | ||
| const url1 = 'https://github.com/mwyraz/forgejo-actions-checkout.git' | ||
| const name1 = (cacheHelper as any).generateCacheDirName(url1) | ||
|
|
||
| // Check structure: safe string + hash | ||
| expect(name1).toMatch(/^https___github_com_mwyraz_forgejo_actions_checkout_git_[0-9a-f]{8}\.git$/) | ||
|
|
||
| // Same URL should produce the same directory name | ||
| const url1_duplicate = 'https://github.com/mwyraz/forgejo-actions-checkout.git' | ||
| expect((cacheHelper as any).generateCacheDirName(url1_duplicate)).toBe(name1) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if you try this from ChatGPT so it's so easy work and short coding so now checking |
||
|
|
||
| // Different URL should produce a different directory name | ||
| const url2 = 'https://github.com/mwyraz/forgejo-actions-checkout-other.git' | ||
| expect((cacheHelper as any).generateCacheDirName(url2)).not.toBe(name1) | ||
|
|
||
| // SSH URL | ||
| const url3 = 'git@github.com:auth/repo.git' | ||
| const name3 = (cacheHelper as any).generateCacheDirName(url3) | ||
| expect(name3).toMatch(/^git_github_com_auth_repo_git_[0-9a-f]{8}\.git$/) | ||
|
|
||
| // Unclean URLs | ||
| const url4 = 'https://github.com/foo/bar.git?v=1' | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. here also mistak errors 404 recheck |
||
| const name4 = (cacheHelper as any).generateCacheDirName(url4) | ||
| expect(name4).toMatch(/^https___github_com_foo_bar_git_v_1_[0-9a-f]{8}\.git$/) | ||
| }) | ||
|
|
||
| it('sets up a cache directory if it does not exist', async () => { | ||
| const repositoryUrl = 'https://github.com/mwyraz/test-repo.git' | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. error error complete check again then show me this i approve this than |
||
| const resultPath = await cacheHelper.setupCache(mockGit, repositoryUrl) | ||
|
|
||
| const expectedName = (cacheHelper as any).generateCacheDirName(repositoryUrl) | ||
| expect(resultPath).toBe(path.join(cacheDir, expectedName)) | ||
|
|
||
| // It should have executed git clone --bare | ||
| expect(mockGit.execGit).toHaveBeenCalledWith( | ||
| expect.arrayContaining([ | ||
| '-C', | ||
| cacheDir, | ||
| 'clone', | ||
| '--bare', | ||
| repositoryUrl, | ||
| expect.stringContaining(`${expectedName}.tmp`) // should use tmp dir | ||
| ]) | ||
| ) | ||
| }) | ||
|
|
||
| it('fetches updates if the cache directory already exists', async () => { | ||
| const repositoryUrl = 'https://github.com/mwyraz/existing-repo.git' | ||
| const expectedName = (cacheHelper as any).generateCacheDirName(repositoryUrl) | ||
| const fixedPath = path.join(cacheDir, expectedName) | ||
|
|
||
| // Fake existing directory | ||
| await io.mkdirP(path.join(fixedPath, 'objects')) | ||
|
|
||
| const resultPath = await cacheHelper.setupCache(mockGit, repositoryUrl) | ||
| expect(resultPath).toBe(fixedPath) | ||
|
|
||
| // It should have executed git fetch | ||
| expect(mockGit.execGit).toHaveBeenCalledWith( | ||
| expect.arrayContaining([ | ||
| '-C', | ||
| fixedPath, | ||
| 'fetch', | ||
| '--force', | ||
| '--prune', | ||
| '--tags', | ||
| 'origin', | ||
| '+refs/heads/*:refs/heads/*' | ||
| ]) | ||
| ) | ||
| }) | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| import * as core from '@actions/core' | ||
| import {adjustFetchDepthForCache} from '../src/git-source-provider' | ||
|
|
||
| // Mock @actions/core | ||
| jest.mock('@actions/core') | ||
|
|
||
| describe('adjustFetchDepthForCache', () => { | ||
| beforeEach(() => { | ||
| jest.clearAllMocks() | ||
| }) | ||
|
|
||
| it('does nothing when referenceCache is not set', () => { | ||
| const settings = { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. check check again not working properly |
||
| referenceCache: '', | ||
| fetchDepth: 1, | ||
| fetchDepthExplicit: false | ||
| } | ||
| adjustFetchDepthForCache(settings) | ||
| expect(settings.fetchDepth).toBe(1) | ||
| expect(core.warning).not.toHaveBeenCalled() | ||
| expect(core.info).not.toHaveBeenCalled() | ||
| }) | ||
|
|
||
| it('overrides fetchDepth to 0 when referenceCache is set and fetchDepth is default', () => { | ||
| const settings = { | ||
| referenceCache: '/cache/git-reference-cache', | ||
| fetchDepth: 1, | ||
| fetchDepthExplicit: false | ||
| } | ||
| adjustFetchDepthForCache(settings) | ||
| expect(settings.fetchDepth).toBe(0) | ||
| expect(core.info).toHaveBeenCalledWith( | ||
| expect.stringContaining('Overriding fetch-depth from 1 to 0') | ||
| ) | ||
| expect(core.warning).not.toHaveBeenCalled() | ||
| }) | ||
|
|
||
| it('warns but keeps fetchDepth when referenceCache is set and fetchDepth is explicit', () => { | ||
| const settings = { | ||
| referenceCache: '/cache/git-reference-cache', | ||
| fetchDepth: 1, | ||
| fetchDepthExplicit: true | ||
| } | ||
| adjustFetchDepthForCache(settings) | ||
| expect(settings.fetchDepth).toBe(1) | ||
| expect(core.warning).toHaveBeenCalledWith( | ||
| expect.stringContaining("'fetch-depth: 1' is set with reference-cache enabled") | ||
| ) | ||
| expect(core.info).not.toHaveBeenCalled() | ||
| }) | ||
|
|
||
| it('does nothing when referenceCache is set and fetchDepth is already 0 (explicit)', () => { | ||
| const settings = { | ||
| referenceCache: '/cache/git-reference-cache', | ||
| fetchDepth: 0, | ||
| fetchDepthExplicit: true | ||
| } | ||
| adjustFetchDepthForCache(settings) | ||
| expect(settings.fetchDepth).toBe(0) | ||
| expect(core.warning).not.toHaveBeenCalled() | ||
| expect(core.info).not.toHaveBeenCalled() | ||
| }) | ||
|
|
||
| it('does nothing when referenceCache is set and fetchDepth is already 0 (default)', () => { | ||
| const settings = { | ||
| referenceCache: '/cache/git-reference-cache', | ||
| fetchDepth: 0, | ||
| fetchDepthExplicit: false | ||
| } | ||
| adjustFetchDepthForCache(settings) | ||
| expect(settings.fetchDepth).toBe(0) | ||
| expect(core.warning).not.toHaveBeenCalled() | ||
| expect(core.info).not.toHaveBeenCalled() | ||
| }) | ||
|
|
||
| it('warns with correct depth value when explicit fetchDepth is > 1', () => { | ||
| const settings = { | ||
| referenceCache: '/cache/git-reference-cache', | ||
| fetchDepth: 42, | ||
| fetchDepthExplicit: true | ||
| } | ||
| adjustFetchDepthForCache(settings) | ||
| expect(settings.fetchDepth).toBe(42) | ||
| expect(core.warning).toHaveBeenCalledWith( | ||
| expect.stringContaining("'fetch-depth: 42' is set with reference-cache enabled") | ||
| ) | ||
| }) | ||
| }) | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,37 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Reference Cache für schnelle Checkouts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## 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. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## Implementierungsplan | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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`). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 2. **Cache Manager (`src/git-cache-helper.ts`):** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Eine neue Klasse/Helper-Logik, die das Erstellen (`git clone --bare`) und Aktualisieren (`git fetch --force`) von Bare Cache-Repos übernimmt. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - **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` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 3. **Haupt-Repo Checkout (`src/git-source-provider.ts`):** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Vor dem Setup des Checkouts prüfen, ob `reference-cache` gesetzt ist. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Wenn ja: den Cache-Ordner für die Haupt-URL aktualisieren/anlegen. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Nach dem initialen `git.init()` den Pfad in `.git/objects/info/alternates` schreiben, der auf das `objects`-Verzeichnis des Cache-Ordners zeigt. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 4. **Submodule Checkouts (Iterativ statt monolithisch):** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Der aktuelle Befehl `git submodule update --recursive` funktioniert nicht out-of-the-box mit `reference`, wenn jedes Submodul seinen individuellen Referenz-Cache benötigt. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - 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). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## Akzeptanzkriterien | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 1. **Neue Option konfigurierbar**: Der Input `reference-cache` kann übergeben werden, der Code reagiert darauf. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 2. **Ordnerstruktur korrekt**: Der Cache-Ordner für das Hauptrepo und Submodule erhält Namen nach der "URL_Sonderzeichen_Ersetzt+SHA_Cut"-Logik. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Reference Cache für schnelle Checkouts | |
| ## 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. | |
| ## Implementierungsplan | |
| 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`). | |
| 2. **Cache Manager (`src/git-cache-helper.ts`):** | |
| - Eine neue Klasse/Helper-Logik, die das Erstellen (`git clone --bare`) und Aktualisieren (`git fetch --force`) von Bare Cache-Repos übernimmt. | |
| - **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` | |
| 3. **Haupt-Repo Checkout (`src/git-source-provider.ts`):** | |
| - Vor dem Setup des Checkouts prüfen, ob `reference-cache` gesetzt ist. | |
| - Wenn ja: den Cache-Ordner für die Haupt-URL aktualisieren/anlegen. | |
| - Nach dem initialen `git.init()` den Pfad in `.git/objects/info/alternates` schreiben, der auf das `objects`-Verzeichnis des Cache-Ordners zeigt. | |
| 4. **Submodule Checkouts (Iterativ statt monolithisch):** | |
| - Der aktuelle Befehl `git submodule update --recursive` funktioniert nicht out-of-the-box mit `reference`, wenn jedes Submodul seinen individuellen Referenz-Cache benötigt. | |
| - 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). | |
| ## Akzeptanzkriterien | |
| 1. **Neue Option konfigurierbar**: Der Input `reference-cache` kann übergeben werden, der Code reagiert darauf. | |
| 2. **Ordnerstruktur korrekt**: Der Cache-Ordner für das Hauptrepo und Submodule erhält Namen nach der "URL_Sonderzeichen_Ersetzt+SHA_Cut"-Logik. | |
| 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. | |
| 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). | |
| # Reference cache for fast checkouts | |
| ## Summary | |
| Introduce a locally managed Git reference cache for main repositories and submodules to massively reduce network traffic and checkout times on persistent runners (e.g. self-hosted). | |
| ## Implementation plan | |
| 1. **Inputs:** | |
| - Add a new input `reference-cache` (path to the cache directory) in `action.yml`. The default is empty. | |
| - Read and expose this input in `src/git-source-settings.ts` and `src/input-helper.ts` as `settings.referenceCache`. | |
| 2. **Cache manager (`src/git-cache-helper.ts`):** | |
| - Add a new class/helper responsible for creating (`git clone --bare`) and updating (`git fetch --force`) bare cache repositories. | |
| - **Cache directory naming convention:** To keep the cache understandable for admins and avoid collisions, derive the cache directory name from the repository URL: | |
| - Replace all non-alphanumeric characters in the URL with `_`. | |
| - Append a short hash (e.g. first 8 characters of the SHA256) of the real URL to guarantee uniqueness. | |
| - Example: `<reference-cache>/https___github_com_actions_checkout_8f9b1c2a.git` | |
| 3. **Main repository checkout (`src/git-source-provider.ts`):** | |
| - Before setting up the checkout, check whether `reference-cache` is set. | |
| - If it is: create or update the cache directory for the primary repository URL. | |
| - After the initial `git init()`, write a path into `.git/objects/info/alternates` that points to the `objects` directory of the cache repository. | |
| 4. **Submodule checkouts (iterative instead of monolithic):** | |
| - The current command `git submodule update --recursive` does not work out of the box with `--reference` when each submodule requires its own reference cache. | |
| - When `reference-cache` is enabled and submodules need to be initialized: | |
| - Read `.gitmodules` (determine all submodule URLs). | |
| - For each submodule, create or update a cache repository (following the same logic as in step 2). | |
| - Check out each submodule individually using `git submodule update --init --reference <cache-path/.git> <path>`. | |
| - When `recursive` is requested: change into each submodule directory and repeat this process for its `.gitmodules` at the script level, instead of simply passing Git's `--recursive` flag through. | |
| ## Acceptance criteria | |
| 1. **New option is configurable**: The `reference-cache` input can be provided and the code reacts accordingly. | |
| 2. **Directory structure is correct**: The cache directory for the main repository and all submodules is named according to the "URL_characters_replaced_plus_truncated_SHA" logic. | |
| 3. **Bandwidth is saved / alternates are used**: During the main checkout, a `.git/objects/info/alternates` file is created that points to the local cache. Subsequent `git fetch` commands are significantly faster and download noticeably fewer bytes. | |
| 4. **Submodules use caches**: Deep (recursive) submodules also benefit from the cache for their respective remote URLs, because a suitable `--reference` target is dynamically computed and passed per submodule. | |
| 5. **No `--dissociate`**: For performance reasons, the working directory remains bound to the cache (`git repack` is expensive). If the cache is removed, the workspace must be recreated (which is the norm for action runners, especially if they are "single-use" runners). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Valid point. That was still my initial blueprint for the implementation. Replaced by a reworked version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some errors can you check again this