diff --git a/deploy/update.sh b/deploy/update.sh index 0a286ea..0a53cc2 100644 --- a/deploy/update.sh +++ b/deploy/update.sh @@ -77,8 +77,13 @@ else fi fi -# Keep the last three releases for a quick rollback (ln -sfn /opt/togetherdecomp). +# Keep the last three releases for a quick rollback (ln -sfn /opt/togetherdecomp), and +# every release a running process was started from (it loads files from it later, e.g. workers). +inuse=$(cat /var/lib/togetherdecomp/release-*.txt 2>/dev/null) ls -1dt "$BASE"/*/ | tail -n +4 | while read -r old; do - [ "$(readlink -f "$old")" = "$(readlink -f "$LINK")" ] || rm -rf "$old" + old=$(readlink -f "$old") + [ "$old" = "$(readlink -f "$LINK")" ] && continue + echo "$inuse" | grep -qxF "$old" && continue + rm -rf "$old" done echo "== done: $(readlink -f "$LINK")" diff --git a/src/server/app.js b/src/server/app.js index eee5fad..2fa5822 100644 --- a/src/server/app.js +++ b/src/server/app.js @@ -771,6 +771,10 @@ export async function startServer(dataDir, { role = 'all' } = {}) { if (!['all', 'web', 'worker'].includes(role)) throw new Error(`unknown role ${role}; use web, worker or all`); const ctx = openServer(dataDir); ctx.runsWorker = role !== 'web'; + // deploy/update.sh keeps the releases running processes were started from. + try { + fs.writeFileSync(path.join(ctx.config.dataDir, `release-${role}.txt`), path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..') + '\n'); + } catch {} const stops = []; if (role !== 'web') { ctx.tasks.generate(); diff --git a/src/server/merge.js b/src/server/merge.js index ba54bf7..552a560 100644 --- a/src/server/merge.js +++ b/src/server/merge.js @@ -250,6 +250,10 @@ export class Merger { } } const targets = new Set(attempts.map((a) => a.symbol_id)); + // The batch a split part came from holds the other functions of these files (still in review, + // their source put back as on main); shared changes in the file may change their compiled code. + // That is not a change to functions someone else holds; their status is measured again. + if (parentId) for (const a of batchAttempts(this.store, parentId)) targets.add(a.symbol_id); const outside = this.verifier.changedOutside(check, merged.files, targets); const confirmedIds = new Set(); const results = []; diff --git a/src/server/split.js b/src/server/split.js index 4f7e44f..0949e3c 100644 --- a/src/server/split.js +++ b/src/server/split.js @@ -23,7 +23,8 @@ export function functionRegions(text) { } const out = new Map(); for (const g of groups.values()) { - const end = bodyEnd(lines, g.targetLine - 1); + // A definition runs to its closing brace; an annotated declaration to its semicolon. + const end = bodyEnd(lines, g.targetLine - 1) ?? declarationEnd(lines, g.targetLine - 1); if (end === null) return null; const region = { start: g.start, end, ids: g.ids }; for (const id of g.ids) out.set(id, region); @@ -31,6 +32,12 @@ export function functionRegions(text) { return out; } +// Index of the line after the one that ends a declaration (its semicolon) starting at `from`. +function declarationEnd(lines, from) { + for (let i = from; i < Math.min(lines.length, from + 20); i++) if (lines[i].replace(/\/\/.*$/, '').includes(';')) return i + 1; + return null; +} + // Index of the line after the brace that closes the body starting at or after line `from`. function bodyEnd(lines, from) { let depth = 0; diff --git a/test/split.test.js b/test/split.test.js index 31cb34d..b1b3ac7 100644 --- a/test/split.test.js +++ b/test/split.test.js @@ -37,3 +37,12 @@ test('withoutFunctions restores pending functions from the base, or drops new on // One definition shared by a pending and a finished function can't be split. assert.equal(withoutFunctions({ 'a.cpp': file }, ['fn_00002'], () => null), null); }); + +test('an annotated declaration is a region of its own instead of stopping the split', () => { + const text = '// STUB: fn_00010\nvoid declared(int a);\n\n// FUNCTION: fn_00011\nint defined() {\n return 1;\n}\n'; + const r = functionRegions(text); + assert.deepEqual([r.get('fn_00010').start, r.get('fn_00010').end], [0, 2]); + assert.deepEqual([r.get('fn_00011').start, r.get('fn_00011').end], [3, 7]); + const out = withoutFunctions({ 'a.cpp': text }, ['fn_00011'], () => null); + assert.equal(out['a.cpp'], '// STUB: fn_00010\nvoid declared(int a);\n\n'); +});