From 47d9ea2311af5abb4e8b5c5b56e081868e98cd95 Mon Sep 17 00:00:00 2001 From: WULCAN <7813515+WULCAN@users.noreply.github.com> Date: Sun, 17 Mar 2024 11:16:49 +0100 Subject: [PATCH] Make sure we have pulled before reading LyraConfig Move all initializing git pull into RepoGit and make RepoGit shared for each unique local git clone. As the repository specific configuration file is potentially stale, we need to do this initializing pull before any read of that configuration file. To ensure that, I replace all direct reads with a read through RepoGit. Since the factory always pulls, we know the config will be fresh after a Lyra server start. Note that the route for creating pull-requests can now theoretically pull twice, if RepoGit was not initialzied first. That is however extremely unlikely as button the create pull requests is on a page that uses RepoGit to load its data. Anyway, I beleive we will resolve this when we implement resyncing from the remotes. --- webapp/README.md | 6 +++- webapp/src/Cache.ts | 34 +++---------------- webapp/src/RepoGit.ts | 28 +++++++++++++-- .../app/api/messages/[projectName]/route.ts | 8 ++--- .../api/pull-request/[projectName]/route.ts | 4 +-- .../[projectName]/[lang]/[msgId]/route.ts | 6 ++-- 6 files changed, 44 insertions(+), 42 deletions(-) diff --git a/webapp/README.md b/webapp/README.md index c01b0e66..02634514 100644 --- a/webapp/README.md +++ b/webapp/README.md @@ -15,7 +15,11 @@ projects: github_token: << github token >> ``` -also the project repository (client repository) needs to be cloned locally. and has in the root folder config +Multiple projects are supported, and multiple projects in the same local git repository +are supported, but configuring multiple porjects with different repo_path, resolving to +same local git repository, is _not_ supported. + +The project repository (client repository) needs to be cloned locally. and has in the root folder config file `lyra.yml` with the example content: diff --git a/webapp/src/Cache.ts b/webapp/src/Cache.ts index ae9d4082..44d04c9a 100644 --- a/webapp/src/Cache.ts +++ b/webapp/src/Cache.ts @@ -1,26 +1,19 @@ /* global globalThis */ -import { debug } from '@/utils/log'; -import { IGit } from '@/utils/git/IGit'; +import { getRepoGit } from '@/RepoGit'; import { LanguageNotSupported } from '@/errors'; +import { LyraProjectConfig } from '@/utils/lyraConfig'; import { ProjectStore } from '@/store/ProjectStore'; -import { RepoGit } from '@/RepoGit'; -import { SimpleGitWrapper } from '@/utils/git/SimpleGitWrapper'; +import { ServerConfig } from '@/utils/serverConfig'; import { Store } from '@/store/Store'; import YamlTranslationAdapter from '@/utils/adapters/YamlTranslationAdapter'; -import { LyraConfig, LyraProjectConfig } from '@/utils/lyraConfig'; -import { ServerConfig, ServerProjectConfig } from '@/utils/serverConfig'; export class Cache { - private static hasPulled = new Set(); - public static async getLanguage(projectName: string, lang: string) { const serverProjectConfig = await ServerConfig.getProjectConfig(projectName); - await Cache.gitPullIfNeeded(serverProjectConfig); - const lyraConfig = await LyraConfig.readFromDir( - serverProjectConfig.repoPath, - ); + const repo = await getRepoGit(serverProjectConfig); + const lyraConfig = await repo.getLyraConfig(); const lyraProjectConfig = lyraConfig.getProjectConfigByPath( serverProjectConfig.projectPath, ); @@ -47,21 +40,4 @@ export class Cache { return globalThis.store.getProjectStore(lyraProjectConfig.absPath); } - - private static async gitPullIfNeeded(spConfig: ServerProjectConfig) { - const { repoPath, baseBranch } = spConfig; - if (Cache.hasPulled.has(repoPath)) { - debug(`repoPath: ${repoPath} is already pulled`); - return; - } - await RepoGit.cloneIfNotExist(spConfig); - debug(`prepare git options for path: ${repoPath}`); - const git: IGit = new SimpleGitWrapper(repoPath); - debug(`git checkout ${baseBranch} branch...`); - await git.checkout(baseBranch); - debug('git pull...'); - await git.pull(); - debug(`git done checkout ${baseBranch} branch and pull`); - Cache.hasPulled.add(repoPath); - } } diff --git a/webapp/src/RepoGit.ts b/webapp/src/RepoGit.ts index 89d6523a..21f67243 100644 --- a/webapp/src/RepoGit.ts +++ b/webapp/src/RepoGit.ts @@ -14,13 +14,31 @@ import { debug, info, warn } from '@/utils/log'; import { WriteLanguageFileError, WriteLanguageFileErrors } from '@/errors'; export class RepoGit { + private static repositories: { + [name: string]: Promise; + } = {}; + private readonly git: IGit; private lyraConfig?: LyraConfig; - constructor(private readonly spConfig: ServerProjectConfig) { + private constructor(private readonly spConfig: ServerProjectConfig) { this.git = new SimpleGitWrapper(spConfig.repoPath); } + static async getRepoGit(config: ServerProjectConfig): Promise { + const key = config.repoPath; + if (key in RepoGit.repositories) { + return RepoGit.repositories[key]; + } + const { promise, resolve, reject } = Promise.withResolvers(); + RepoGit.repositories[key] = promise; + + const repository = new RepoGit(config); + repository.checkoutBaseAndPull().then(() => resolve(repository), reject); + + return promise; + } + public static async cloneIfNotExist( spConfig: ServerProjectConfig, ): Promise { @@ -116,7 +134,7 @@ export class RepoGit { return response.data.html_url; } - private async getLyraConfig(): Promise { + async getLyraConfig(): Promise { if (this.lyraConfig === undefined) { await RepoGit.cloneIfNotExist(this.spConfig); this.lyraConfig = await LyraConfig.readFromDir(this.spConfig.repoPath); @@ -158,3 +176,9 @@ export class RepoGit { return paths; } } + +export async function getRepoGit( + config: ServerProjectConfig, +): Promise { + return RepoGit.getRepoGit(config); +} diff --git a/webapp/src/app/api/messages/[projectName]/route.ts b/webapp/src/app/api/messages/[projectName]/route.ts index 97cc63fb..8f67178d 100644 --- a/webapp/src/app/api/messages/[projectName]/route.ts +++ b/webapp/src/app/api/messages/[projectName]/route.ts @@ -1,7 +1,6 @@ -import { LyraConfig } from '@/utils/lyraConfig'; import MessageAdapterFactory from '@/utils/adapters/MessageAdapterFactory'; -import { RepoGit } from '@/RepoGit'; import { ServerConfig } from '@/utils/serverConfig'; +import { getRepoGit, RepoGit } from '@/RepoGit'; import { LyraConfigReadingError, ProjectNameNotFoundError, @@ -18,9 +17,8 @@ export async function GET( const serverProjectConfig = await ServerConfig.getProjectConfig(projectName); await RepoGit.cloneIfNotExist(serverProjectConfig); - const lyraConfig = await LyraConfig.readFromDir( - serverProjectConfig.repoPath, - ); + const repo = await getRepoGit(serverProjectConfig); + const lyraConfig = await repo.getLyraConfig(); const projectConfig = lyraConfig.getProjectConfigByPath( serverProjectConfig.projectPath, ); diff --git a/webapp/src/app/api/pull-request/[projectName]/route.ts b/webapp/src/app/api/pull-request/[projectName]/route.ts index b6265166..e8ba1534 100644 --- a/webapp/src/app/api/pull-request/[projectName]/route.ts +++ b/webapp/src/app/api/pull-request/[projectName]/route.ts @@ -1,5 +1,5 @@ +import { getRepoGit } from '@/RepoGit'; import { ProjectNameNotFoundError } from '@/errors'; -import { RepoGit } from '@/RepoGit'; import { NextRequest, NextResponse } from 'next/server'; import { ServerConfig, ServerProjectConfig } from '@/utils/serverConfig'; @@ -37,7 +37,7 @@ export async function POST( try { syncLock.set(repoPath, true); - const repoGit = new RepoGit(serverProjectConfig); + const repoGit = await getRepoGit(serverProjectConfig); const baseBranch = await repoGit.checkoutBaseAndPull(); const langFilePaths = await repoGit.saveLanguageFiles( serverProjectConfig.projectPath, diff --git a/webapp/src/app/api/translations/[projectName]/[lang]/[msgId]/route.ts b/webapp/src/app/api/translations/[projectName]/[lang]/[msgId]/route.ts index b73f7c10..018a5da5 100644 --- a/webapp/src/app/api/translations/[projectName]/[lang]/[msgId]/route.ts +++ b/webapp/src/app/api/translations/[projectName]/[lang]/[msgId]/route.ts @@ -1,7 +1,6 @@ import { Cache } from '@/Cache'; -import { LyraConfig } from '@/utils/lyraConfig'; -import { RepoGit } from '@/RepoGit'; import { ServerConfig } from '@/utils/serverConfig'; +import { getRepoGit, RepoGit } from '@/RepoGit'; import { LanguageNotFound, LanguageNotSupported, @@ -27,7 +26,8 @@ export async function PUT( // TODO: include getProjectConfig & readFromDir in a try/catch block and check for error to return a certain 500 error const serverProjectConfig = await ServerConfig.getProjectConfig(projectName); await RepoGit.cloneIfNotExist(serverProjectConfig); - const lyraConfig = await LyraConfig.readFromDir(serverProjectConfig.repoPath); + const repo = await getRepoGit(serverProjectConfig); + const lyraConfig = await repo.getLyraConfig(); try { const projectConfig = lyraConfig.getProjectConfigByPath(