diff --git a/packages/core/src/backends/gitea/API.ts b/packages/core/src/backends/gitea/API.ts index 78a6dc63..6f5c0ff5 100644 --- a/packages/core/src/backends/gitea/API.ts +++ b/packages/core/src/backends/gitea/API.ts @@ -362,38 +362,50 @@ export default class API { async persistFiles(dataFiles: DataFile[], mediaFiles: AssetProxy[], options: PersistOptions) { // eslint-disable-next-line @typescript-eslint/no-explicit-any - [mediaFiles, dataFiles].forEach(async list => { - list.forEach(async file => { - const item: { raw?: string; sha?: string; toBase64?: () => Promise } = file; - const contentBase64 = await result( - item, - 'toBase64', - partial(this.toBase64, item.raw as string), - ); - if (options.newEntry) { - await this.request(`${this.repoURL}/contents/${file.path}`, { - method: 'POST', - body: JSON.stringify({ - branch: this.branch, - content: contentBase64, - message: options.commitMessage, - signoff: false, - }), - }); - } else { - const oldSha = await this.getFileSha(file.path); - await this.request(`${this.repoURL}/contents/${file.path}`, { - method: 'PUT', - body: JSON.stringify({ - branch: this.branch, - content: contentBase64, - message: options.commitMessage, - sha: oldSha, - signoff: false, - }), - }); - } - }); + const files: (DataFile | AssetProxy)[] = mediaFiles.concat(dataFiles as any); + for (const file of files) { + const item: { raw?: string; sha?: string; toBase64?: () => Promise } = file; + const contentBase64 = await result( + item, + 'toBase64', + partial(this.toBase64, item.raw as string), + ); + try { + const oldSha = await this.getFileSha(file.path); + await this.updateBlob(contentBase64, file, options, oldSha!); + } catch { + await this.createBlob(contentBase64, file, options); + } + } + } + + async updateBlob( + contentBase64: string, + file: AssetProxy | DataFile, + options: PersistOptions, + oldSha: string, + ) { + await this.request(`${this.repoURL}/contents/${file.path}`, { + method: 'PUT', + body: JSON.stringify({ + branch: this.branch, + content: contentBase64, + message: options.commitMessage, + sha: oldSha, + signoff: false, + }), + }); + } + + async createBlob(contentBase64: string, file: AssetProxy | DataFile, options: PersistOptions) { + await this.request(`${this.repoURL}/contents/${file.path}`, { + method: 'POST', + body: JSON.stringify({ + branch: this.branch, + content: contentBase64, + message: options.commitMessage, + signoff: false, + }), }); } @@ -415,12 +427,12 @@ export default class API { if (file) { return file.sha; } else { - console.error('File not found'); + throw new APIError('Not Found', 404, API_NAME); } } async deleteFiles(paths: string[], message: string) { - paths.forEach(async file => { + for (const file of paths) { const meta: ContentsResponse = await this.request(`${this.repoURL}/contents/${file}`, { method: 'GET', }); @@ -428,7 +440,7 @@ export default class API { method: 'DELETE', body: JSON.stringify({ branch: this.branch, message, sha: meta.sha, signoff: false }), }); - }); + } } toBase64(str: string) { diff --git a/packages/core/src/backends/gitea/__tests__/API.spec.ts b/packages/core/src/backends/gitea/__tests__/API.spec.ts index f2cb8d06..1b89fc65 100644 --- a/packages/core/src/backends/gitea/__tests__/API.spec.ts +++ b/packages/core/src/backends/gitea/__tests__/API.spec.ts @@ -101,7 +101,7 @@ describe('gitea API', () => { }); describe('persistFiles', () => { - it('should create a new file', async () => { + it('should check if file exists and create a new file', async () => { const api = new API({ branch: 'master', repo: 'owner/repo' }); const responses = { @@ -127,9 +127,13 @@ describe('gitea API', () => { newEntry: true, }); - expect(api.request).toHaveBeenCalledTimes(1); + expect(api.request).toHaveBeenCalledTimes(2); expect((api.request as jest.Mock).mock.calls[0]).toEqual([ + '/repos/owner/repo/git/trees/master:content%2Fposts', + ]); + + expect((api.request as jest.Mock).mock.calls[1]).toEqual([ '/repos/owner/repo/contents/content/posts/new-post.md', { method: 'POST', @@ -174,11 +178,25 @@ describe('gitea API', () => { newEntry: false, }); - expect(api.request).toHaveBeenCalledTimes(1); + expect(api.request).toHaveBeenCalledTimes(2); expect((api.request as jest.Mock).mock.calls[0]).toEqual([ '/repos/owner/repo/git/trees/master:content%2Fposts', ]); + + expect((api.request as jest.Mock).mock.calls[1]).toEqual([ + '/repos/owner/repo/contents/content/posts/update-post.md', + { + method: 'PUT', + body: JSON.stringify({ + branch: 'master', + content: Base64.encode(entry.dataFiles[0].raw), + message: 'commitMessage', + sha: 'old-sha', + signoff: false, + }), + }, + ]); }); }); diff --git a/packages/docs/content/docs/gitea-backend.mdx b/packages/docs/content/docs/gitea-backend.mdx index 4a70dd08..a512cd69 100644 --- a/packages/docs/content/docs/gitea-backend.mdx +++ b/packages/docs/content/docs/gitea-backend.mdx @@ -9,13 +9,15 @@ beta: true For repositories stored on Gitea, the `gitea` backend allows CMS users to log in directly with their Gitea account. Note that all users must have push access to your content repository for this to work. +Because of the [lack](https://github.com/go-gitea/gitea/issues/14619) of a Gitea API endpoint for multifile commits, when using this backend, separate commits are created for every changed file. Please make sure this is handled correctly by your CI. + ## Authentication Because Gitea requires a server for authentication and Netlify doesn't support Gitea, a custom OAuth provider needs to be used for basic Gitea authentication. To enable basic Gitea authentication: -1. Setup an own OAuth provider, for example with [scm-oauth](https://github.com/denyskon/scm-oauth-provider). +1. Setup an own OAuth provider, for example with [Teabag](https://github.com/denyskon/teabag). 2. Add the following lines to your Static CMS `config` file: