mirror of
https://github.com/HeyPuter/puter.git
synced 2026-09-23 21:56:57 +00:00
fix: handle fs duplicates on write (#2910)
This commit is contained in:
@@ -1,12 +1,25 @@
|
||||
name: Docker Image CI
|
||||
|
||||
# Builds only on calver tag pushes: YY.MM or YY.MM.p (all numeric).
|
||||
# Each build publishes three tags: <version>, latest, main.
|
||||
# Two ways in:
|
||||
# - push of a calver tag (YY.MM or YY.MM.p) → publishes <version>, latest, main
|
||||
# - workflow_dispatch → publishes latest + main from whichever ref the user
|
||||
# picks in the Actions UI. Used to fast-forward :latest/:main when the
|
||||
# branch has moved ahead of the most recent release tag.
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '[0-9][0-9].[0-9][0-9]'
|
||||
- '[0-9][0-9].[0-9][0-9].[0-9]*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
push_latest:
|
||||
description: 'Push :latest tag'
|
||||
type: boolean
|
||||
default: true
|
||||
push_main:
|
||||
description: 'Push :main tag'
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
@@ -22,6 +35,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Validate calver tag
|
||||
if: github.event_name == 'push'
|
||||
env:
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
@@ -51,10 +65,13 @@ jobs:
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"
|
||||
# type=ref,event=tag only emits on tag pushes, so manual runs skip
|
||||
# the calver tag and just publish whichever of latest / main the
|
||||
# dispatch inputs asked for. On a tag push, both default to true.
|
||||
tags: |
|
||||
type=ref,event=tag
|
||||
type=raw,value=latest
|
||||
type=raw,value=main
|
||||
type=raw,value=latest,enable=${{ github.event_name == 'push' || inputs.push_latest }}
|
||||
type=raw,value=main,enable=${{ github.event_name == 'push' || inputs.push_main }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
|
||||
@@ -2722,17 +2722,46 @@ export class FSService extends PuterService {
|
||||
}
|
||||
}
|
||||
|
||||
const created = await this.stores.fsEntry.createNonFileEntry({
|
||||
userId,
|
||||
parent,
|
||||
name,
|
||||
kind: 'directory',
|
||||
thumbnail: input.thumbnail ?? null,
|
||||
});
|
||||
let created: FSEntry;
|
||||
try {
|
||||
created = await this.stores.fsEntry.createNonFileEntry({
|
||||
userId,
|
||||
parent,
|
||||
name,
|
||||
kind: 'directory',
|
||||
thumbnail: input.thumbnail ?? null,
|
||||
});
|
||||
} catch (err) {
|
||||
// Concurrent mkdir race: another caller inserted the same
|
||||
// (parent_id, name) between our existence check above and the
|
||||
// INSERT, tripping the unique key. mkdir on an existing dir is
|
||||
// documented as idempotent — re-fetch and return the winner if
|
||||
// it's a directory; otherwise surface the same 409 the pre-INSERT
|
||||
// check would have produced.
|
||||
if (!this.#isUniqueViolation(err)) throw err;
|
||||
const insertedPath =
|
||||
parent.path === '/' ? `/${name}` : `${parent.path}/${name}`;
|
||||
const raced =
|
||||
await this.stores.fsEntry.getEntryByPath(insertedPath);
|
||||
if (raced?.isDir) return raced;
|
||||
if (raced) {
|
||||
throw new HttpError(
|
||||
409,
|
||||
`An entry already exists at ${insertedPath}`,
|
||||
);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
this.#emitFsEvent('fs.create.directory', created);
|
||||
return created;
|
||||
}
|
||||
|
||||
#isUniqueViolation(err: unknown): boolean {
|
||||
if (!(err instanceof Error) || !('code' in err)) return false;
|
||||
const code = (err as { code?: unknown }).code;
|
||||
return code === 'ER_DUP_ENTRY' || code === 'SQLITE_CONSTRAINT';
|
||||
}
|
||||
|
||||
/**
|
||||
* Touch: create an empty file at `path` if missing; otherwise bump
|
||||
* timestamps.
|
||||
|
||||
Reference in New Issue
Block a user