forked from efforting.tech/gitea.efforting.tech
Refine checkout definition hashing and architecture docs
This commit is contained in:
@@ -74,13 +74,15 @@ async function compose(compose_config, args) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash (owner, repo_url, revision) to get an unambiguous volume name.
|
// Hash the entire checkout definition to get an unambiguous volume name.
|
||||||
// Human-readable metadata is stored as Docker volume labels instead.
|
// Any change to any field (repo, revision, key, submodules, etc.) produces
|
||||||
function worktree_volume_name(owner, repo_url, revision) {
|
// a different volume. Human-readable metadata is stored as Docker volume labels.
|
||||||
|
function worktree_volume_name(owner, repo_def) {
|
||||||
|
const sorted_keys = Object.keys(repo_def).sort()
|
||||||
|
const canonical = JSON.stringify(Object.fromEntries(sorted_keys.map(k => [k, repo_def[k]])))
|
||||||
const hash = createHash('sha256')
|
const hash = createHash('sha256')
|
||||||
.update(owner).update('\0')
|
.update(owner).update('\0')
|
||||||
.update(repo_url).update('\0')
|
.update(canonical)
|
||||||
.update(revision)
|
|
||||||
.digest('hex')
|
.digest('hex')
|
||||||
.slice(0, 24)
|
.slice(0, 24)
|
||||||
return `ci-worktree-${hash}`
|
return `ci-worktree-${hash}`
|
||||||
@@ -90,8 +92,9 @@ function worktree_volume_name(owner, repo_url, revision) {
|
|||||||
// Step 1: Prepare base worktree volume
|
// Step 1: Prepare base worktree volume
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function prepare_worktree(owner, repo_url, revision, mutable, private_key_path) {
|
async function prepare_worktree(owner, repo_def, private_key_path) {
|
||||||
const volume_name = worktree_volume_name(owner, repo_url, revision)
|
const { repo_url, revision, mutable } = repo_def
|
||||||
|
const volume_name = worktree_volume_name(owner, repo_def)
|
||||||
|
|
||||||
// Check if the volume already exists
|
// Check if the volume already exists
|
||||||
let exists = false
|
let exists = false
|
||||||
@@ -103,7 +106,7 @@ async function prepare_worktree(owner, repo_url, revision, mutable, private_key_
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (exists && !mutable) {
|
if (exists && !mutable) {
|
||||||
// Immutable revision — volume is permanent once created
|
// Immutable — volume is permanent once created
|
||||||
console.log(`Base worktree volume ${volume_name} already exists, reusing.`)
|
console.log(`Base worktree volume ${volume_name} already exists, reusing.`)
|
||||||
return volume_name
|
return volume_name
|
||||||
}
|
}
|
||||||
@@ -189,7 +192,7 @@ async function main() {
|
|||||||
const repo = checkout.repositories[0]
|
const repo = checkout.repositories[0]
|
||||||
const private_key_path = path.join(secrets_base, 'git-ssh', user, 'private', repo.key)
|
const private_key_path = path.join(secrets_base, 'git-ssh', user, 'private', repo.key)
|
||||||
|
|
||||||
const worktree_volume = await prepare_worktree(user, repo.repo_url, repo.revision, repo.mutable, private_key_path)
|
const worktree_volume = await prepare_worktree(user, repo, private_key_path)
|
||||||
console.log(`\nWorktree volume: ${worktree_volume}\n`)
|
console.log(`\nWorktree volume: ${worktree_volume}\n`)
|
||||||
|
|
||||||
const container_name = `ci-rsync-${worktree_volume.slice('ci-worktree-'.length)}`
|
const container_name = `ci-rsync-${worktree_volume.slice('ci-worktree-'.length)}`
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ Images and tasks are declared separately. A task references an image by name. Mu
|
|||||||
|
|
||||||
### Worktree layers (overlayfs)
|
### Worktree layers (overlayfs)
|
||||||
|
|
||||||
Each run mounts a single worktree into the container, but this is composed from two separately managed Docker volumes:
|
A run may check out one or more repositories. Each repository produces two separately managed Docker volumes:
|
||||||
|
|
||||||
**Base worktree** — a named Docker volume containing a plain `git checkout` of a specific `(owner, repo, revision)`. Read-only from the task container's perspective. Whether it is reused or re-checked-out on each run depends on whether the revision is mutable (see below).
|
**Base worktree** — a named Docker volume containing a plain `git checkout` of a specific repository at a specific revision. The volume name is derived from a hash of the entire checkout definition (owner, repo URL, revision, key, submodules flag, etc.) so any change to any field produces a distinct volume. Keys are sorted before hashing so field order in the config file does not affect the result. Read-only from the task container's perspective. Whether it is reused or re-checked-out on each run depends on whether the revision is mutable (see below).
|
||||||
|
|
||||||
**Worktree mutations** — a named Docker volume holding the overlayfs upper layer for a specific run. Unique per run. Contains only what the container wrote or modified — the base worktree is never touched.
|
**Worktree mutations** — a named Docker volume holding the overlayfs upper layer for a specific run. Unique per run. Contains only what the container wrote or modified — the base worktree is never touched.
|
||||||
|
|
||||||
From inside the container these appear as a single mount point (the overlayfs merged view). From outside, the CI system names and manages them separately.
|
Each repository is mounted at a distinct path inside the container. From the outside, the CI system names and manages the volumes per repository independently.
|
||||||
|
|
||||||
Using named Docker volumes means the checkout is a straightforward `git clone` or `git checkout` into a volume — no need for the `--git-dir` / `--work-tree` / `GIT_INDEX_FILE` technique. That approach remains useful for direct host-based deployments but is out of scope here.
|
Using named Docker volumes means the checkout is a straightforward `git clone` or `git checkout` into a volume — no need for the `--git-dir` / `--work-tree` / `GIT_INDEX_FILE` technique. That approach remains useful for direct host-based deployments but is out of scope here.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user