diff --git a/server/services/previewDeploy.js b/server/services/previewDeploy.js index 46b2b88..69a4592 100644 --- a/server/services/previewDeploy.js +++ b/server/services/previewDeploy.js @@ -4,6 +4,7 @@ import fs from 'node:fs/promises' import path from 'node:path' import { config } from '../config.js' import { listDocuments, Query } from './appwriteAdmin.js' +import { withLocks } from './keyedLock.js' const execFileAsync = promisify(execFile) @@ -428,8 +429,17 @@ export async function runDeploy(repoFullName, branch, previewConfig) { } const subdomain = previewConfig.subdomain || repoFullName.split('/').pop() - const cloneUrl = `${config.gitea.baseUrl}/${repoFullName}.git` const workDir = previewWorkDir(repoFullName) + // Deploys desselben Repos oder derselben Subdomain nacheinander: Projektaenderung und + // Gitea-Webhook kamen gleichzeitig, beide git clone liefen ins selbe workDir und + // brachen ab (Kirschner Gebaeudeservice, 30.09.2026). + return withLocks([`preview-work:${workDir}`, `preview-site:${subdomain}`], () => + deployLocked(repoFullName, branch, previewConfig, subdomain, workDir) + ) +} + +async function deployLocked(repoFullName, branch, previewConfig, subdomain, workDir) { + const cloneUrl = `${config.gitea.baseUrl}/${repoFullName}.git` const targetDir = path.join(config.preview.deployRoot, subdomain) await cloneRepo(cloneUrl, workDir, branch, config.gitea.apiToken) diff --git a/test/previewDeployLock.test.js b/test/previewDeployLock.test.js new file mode 100644 index 0000000..3af586b --- /dev/null +++ b/test/previewDeployLock.test.js @@ -0,0 +1,56 @@ +import { test, before, after } from 'node:test' +import assert from 'node:assert/strict' +import { execFileSync } from 'node:child_process' +import fs from 'node:fs/promises' +import os from 'node:os' +import path from 'node:path' + +// 2026-09-30 (Kirschner Gebäudeservice): Projektänderung und Gitea-Webhook deployten dasselbe +// Repo gleichzeitig, beide git clone liefen ins selbe Arbeitsverzeichnis und brachen ab. +let tmp +let previewDeploy +const cwd = process.cwd() + +before(async () => { + tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'preview-lock-')) + const repo = path.join(tmp, 'gitea', 'WEBklar', 'kunde.git') + const src = path.join(tmp, 'src') + await fs.mkdir(src, { recursive: true }) + // Genug Inhalt, dass ein Klon Zeit braucht und sich zwei Klone überschneiden + for (let i = 0; i < 300; i += 1) await fs.writeFile(path.join(src, `seite-${i}.html`), `
${i}
`.repeat(200)) + await fs.writeFile(path.join(src, 'index.html'), '